Merge ~paelzer/ubuntu/+source/open-iscsi:lp-1919461-test-python3-compat into ubuntu/+source/open-iscsi:ubuntu/hirsute-devel

Proposed by Christian Ehrhardt 
Status: Merged
Approved by: Christian Ehrhardt 
Approved revision: 01ee4f90decdc5f7c8387b09bb904bcba34477ea
Merge reported by: Bryce Harrington
Merged at revision: 01ee4f90decdc5f7c8387b09bb904bcba34477ea
Proposed branch: ~paelzer/ubuntu/+source/open-iscsi:lp-1919461-test-python3-compat
Merge into: ubuntu/+source/open-iscsi:ubuntu/hirsute-devel
Diff against target: 1887 lines (+853/-236)
9 files modified
debian/changelog (+18/-0)
debian/patches/series (+3/-0)
debian/patches/upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch (+156/-0)
debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch (+47/-0)
debian/patches/upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch (+58/-0)
debian/tests/control (+1/-1)
debian/tests/test-open-iscsi.py (+14/-13)
debian/tests/testlib.py (+555/-220)
debian/tests/testsuite (+1/-2)
Reviewer Review Type Date Requested Status
Christian Ehrhardt  (community) Approve
Canonical Server packageset reviewers Pending
Canonical Server Pending
Review via email: mp+399963@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :
01ee4f9... by Christian Ehrhardt 

changelog: Add patches from upstream [from Debian 2.1.3-2]

Signed-off-by: Christian Ehrhardt <email address hidden>

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Hmm, I've just seen open-iscsi in the merge opportunities.
I wonder if we should add this to my upload ...

   * [efb5512] Add patches from upstream:
     Fix memory leak in iscsiadm, Fix iscsiadm segfault when exiting, and
     Fix iscsistart login issue when target is delayed. The last one should
     fix #980085. (Closes: #980085)

I think we should, .... adding ...
Uploaded 2.1.3-1ubuntu5~ppa3 to the PPA and pushed to the MP

Revision history for this message
Dimitri John Ledkov (xnox) wrote :

I would not have bothered with keeping python2 compat, and simply did python3-only. Even to the point of SRUing python3 autopkgtest back.

I.e. no fallbacks to old python api, no imports from __future__.

But I guess it does help with testing to ensure that 2&3 still work on i.e. focal.

Revision history for this message
Utkarsh Gupta (utkarsh) wrote :

* Changelog:
  - [√] changelog entry correct version and targeted codename
  - [?] changelog entries correct

I get:
W: libopeniscsiusr0.2.0: debian-changelog-line-too-long line 8
...maybe move the link to the next line?

Another minor/trivial comment here:
The last line of the d/ch entry says "fix #980085. (Closes: #980085)" which doesn't really help me understand what the change was in the first glance. I understand that this was exactly the commit message/header in Debian, but maybe worth adding a one-liner about what this is about?
This would help others to quickly see what changed or fix from just reading the d/ch entry. Anyway, this is just me so please feel free to ignore this.

  - [√] update-maintainer has been run

* Actual changes:
  - [-] no further upstream version to consider
  - [√] debian changes look safe

* New Delta:
  - [√] patches match what was proposed upstream
  - [√] patches correctly included in debian/patches/series
  - [√] patches have correct DEP3 metadata

No "Origin" field but that's okay since these changes are actually a part of Debian so this part of the delta can be very easily dropped. So all good here.

* Build/Test:
  - [√] build is ok
  - [√] verified PPA package installs/uninstalls
  - [√] autopkgtest against the PPA package passes

Ran autopkgtest against what's in the archive, which failed, as the bug report correctly highlights and then ran autopkgtest against this branch, which passed, as intended, which correctly fixes the issue.
cf: autopkgtest [17:32:52]: @@@@@@@@@@@@@@@@@@@@ summary
install PASS
testsuite PASS
nested PASS

So besides the trivial comments above, this looks good! \o/

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Thanks for the catch ont he CL, auto-log at fail.
Fixed

I added a hint what #980085 is - that was worth to ask for

For the patches yeah, since they came in the "upstream" subdir and I mentioned in the CL where they came from I thought that I can keep them as-is. Then it also will be easy to compare when dropping them on the next merge.

Thank you Utkarsh, combined with my tests that is enough for me to upload

review: Approve
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

To ssh://git.launchpad.net/~usd-import-team/ubuntu/+source/open-iscsi
 * [new tag] upload/2.1.3-1ubuntu5 -> upload/2.1.3-1ubuntu5

Uploading to ubuntu (via ftp to upload.ubuntu.com):
  Uploading open-iscsi_2.1.3-1ubuntu5.dsc: done.
  Uploading open-iscsi_2.1.3-1ubuntu5.debian.tar.xz: done.
  Uploading open-iscsi_2.1.3-1ubuntu5_source.buildinfo: done.
  Uploading open-iscsi_2.1.3-1ubuntu5_source.changes: done.
Successfully uploaded packages.

Revision history for this message
Bryce Harrington (bryce) wrote :

Migrated
  - Current Version: 2.1.3-1ubuntu5
  - Proposed Version: None
  - Debian Version: 2.1.3-1ubuntu5
  - New Version: 2.1.3-1ubuntu5

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/debian/changelog b/debian/changelog
index 0b9f498..827c0eb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
1open-iscsi (2.1.3-1ubuntu5) hirsute; urgency=medium
2
3 * make d/t/testuite python3 compatible (LP: #1919461)
4 - backport changes from qa-regression-testing since we diverged
5 - various tests: make py3 compliant, fix formatting a bit
6 - tests: convert deprecated assertEquals() to assertEqual()
7 - Remove all references to python-utils
8 - d/t/testlib.py: update to last state from git+ssh://git.launchpad.net/qa-regression-testing
9 - d/t/{control,testsuite}: bump to use python3
10 - d/t/test-open-iscsi.py: switch shebang to python3
11 - d/t/test-open-iscsi.py: adapt to updated testlib
12 * Add patches from upstream [this is from Debian upload 2.1.3-2]:
13 - Fix memory leak in iscsiadm, Fix iscsiadm segfault when exiting, and
14 - Fix iscsistart login issue when target is delayed. The last one should
15 - fix #980085. (Closes: #980085)
16
17 -- Christian Ehrhardt <christian.ehrhardt@canonical.com> Thu, 18 Mar 2021 09:44:07 +0100
18
1open-iscsi (2.1.3-1ubuntu4) hirsute; urgency=medium19open-iscsi (2.1.3-1ubuntu4) hirsute; urgency=medium
220
3 * Rebuild again so s390x pick up the right debhelper21 * Rebuild again so s390x pick up the right debhelper
diff --git a/debian/patches/series b/debian/patches/series
index 909a8f3..8260850 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,4 @@
1upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch
2upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch
3upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch
1lp1755858-default-iscsid_conf-to-iscsid_socket.patch4lp1755858-default-iscsid_conf-to-iscsid_socket.patch
diff --git a/debian/patches/upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch b/debian/patches/upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch
2new file mode 1006445new file mode 100644
index 0000000..dda9a00
--- /dev/null
+++ b/debian/patches/upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch
@@ -0,0 +1,156 @@
1From: Wenchao Hao <haowenchao@huawei.com>
2Date: Tue, 29 Dec 2020 20:30:25 +0800
3Subject: iscsiadm: Fix memory leak in iscsiadm
4
5Memory allocated by iscsi_context_new() would not be freed if
6error occurred during parameters parser stage and goto free_ifaces
7is used to jump to resource clean.
8
9Since all resource clean is performed after verified, so change
10all goto free_ifaces to goto out where handles resource better.
11
12Signed-off-by: Wenchao Hao <haowenchao@huawei.com>
13---
14 libopeniscsiusr/context.c | 6 +++++-
15 usr/iscsiadm.c | 27 +++++++++++++--------------
16 2 files changed, 18 insertions(+), 15 deletions(-)
17
18diff --git a/libopeniscsiusr/context.c b/libopeniscsiusr/context.c
19index fe92155..c5e869f 100644
20--- a/libopeniscsiusr/context.c
21+++ b/libopeniscsiusr/context.c
22@@ -55,8 +55,12 @@ struct iscsi_context *iscsi_context_new(void)
23
24 void iscsi_context_free(struct iscsi_context *ctx)
25 {
26- if (ctx != NULL)
27+ if (ctx == NULL)
28+ return;
29+
30+ if (ctx->db)
31 _idbm_free(ctx->db);
32+
33 free(ctx);
34 }
35
36diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c
37index ea1643b..3987168 100644
38--- a/usr/iscsiadm.c
39+++ b/usr/iscsiadm.c
40@@ -3627,7 +3627,7 @@ main(int argc, char **argv)
41 "Priority must be greater than or "
42 "equal to zero.", killiscsid);
43 rc = ISCSI_ERR_INVAL;
44- goto free_ifaces;
45+ goto out;
46 }
47 break;
48 case 't':
49@@ -3639,7 +3639,7 @@ main(int argc, char **argv)
50 log_error("can not recognize operation: '%s'",
51 optarg);
52 rc = ISCSI_ERR_INVAL;
53- goto free_ifaces;
54+ goto out;
55 }
56 break;
57 case 'n':
58@@ -3651,7 +3651,7 @@ main(int argc, char **argv)
59 case 'H':
60 host_no = parse_host_info(optarg, &rc);
61 if (rc)
62- goto free_ifaces;
63+ goto out;
64 break;
65 case 'r':
66 sid = iscsi_sysfs_get_sid_from_path(optarg);
67@@ -3659,7 +3659,7 @@ main(int argc, char **argv)
68 log_error("invalid sid '%s'",
69 optarg);
70 rc = ISCSI_ERR_INVAL;
71- goto free_ifaces;
72+ goto out;
73 }
74 break;
75 case 'R':
76@@ -3710,7 +3710,7 @@ main(int argc, char **argv)
77 mode = str_to_mode(optarg);
78 rc = verify_mode_params(argc, argv, mode);
79 if (ISCSI_SUCCESS != rc)
80- goto free_ifaces;
81+ goto out;
82 break;
83 case 'C':
84 sub_mode = str_to_submode(optarg);
85@@ -3739,11 +3739,11 @@ main(int argc, char **argv)
86 printf("Invalid iface name %s. Must be from "
87 "1 to %d characters.\n",
88 optarg, ISCSI_MAX_IFACE_LEN - 1);
89- goto free_ifaces;
90+ goto out;
91 } else if (!iface || rc) {
92 printf("Could not add iface %s.", optarg);
93 rc = ISCSI_ERR_INVAL;
94- goto free_ifaces;
95+ goto out;
96 }
97
98 list_add_tail(&iface->list, &ifaces);
99@@ -3760,7 +3760,7 @@ main(int argc, char **argv)
100 log_error("Invalid index %s. %s.",
101 optarg, strerror(errno));
102 rc = ISCSI_ERR_INVAL;
103- goto free_ifaces;
104+ goto out;
105 }
106 break;
107 case 'A':
108@@ -3778,7 +3778,7 @@ main(int argc, char **argv)
109 if (!param) {
110 log_error("Cannot allocate memory for params.");
111 rc = ISCSI_ERR_NOMEM;
112- goto free_ifaces;
113+ goto out;
114 }
115 list_add_tail(&param->list, &params);
116 name = NULL;
117@@ -3789,12 +3789,12 @@ main(int argc, char **argv)
118 if (optopt) {
119 log_error("unrecognized character '%c'", optopt);
120 rc = ISCSI_ERR_INVAL;
121- goto free_ifaces;
122+ goto out;
123 }
124
125 if (killiscsid >= 0) {
126 kill_iscsid(killiscsid, timeout);
127- goto free_ifaces;
128+ goto out;
129 }
130
131 if (mode < 0)
132@@ -3802,14 +3802,14 @@ main(int argc, char **argv)
133
134 if (mode == MODE_FW) {
135 rc = exec_fw_op(NULL, NULL, info_level, do_login, op);
136- goto free_ifaces;
137+ goto out;
138 }
139
140 increase_max_files();
141 if (idbm_init(get_config_file)) {
142 log_warning("exiting due to idbm configuration error");
143 rc = ISCSI_ERR_IDBM;
144- goto free_ifaces;
145+ goto out;
146 }
147
148 switch (mode) {
149@@ -4070,7 +4070,6 @@ out:
150 free(rec);
151 iscsi_sessions_free(ses, se_count);
152 idbm_terminate();
153-free_ifaces:
154 list_for_each_entry_safe(iface, tmp, &ifaces, list) {
155 list_del(&iface->list);
156 free(iface);
diff --git a/debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch b/debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch
0new file mode 100644157new file mode 100644
index 0000000..183bdaa
--- /dev/null
+++ b/debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch
@@ -0,0 +1,47 @@
1From: Lee Duncan <lduncan@suse.com>
2Date: Tue, 26 Jan 2021 11:48:32 -0800
3Subject: Fix iscsiadm segfault when exiting
4
5Commit b532ad67d495d added some cleanup code
6to iscsiadm right before it exits, but it
7used a list_for_each_entry() to iterate through
8a list was being deleted, when it should use
9list_for_each_entry_safe().
10
11Fixes: b532ad67d495d
12---
13 usr/iscsiadm.c | 8 ++++----
14 1 file changed, 4 insertions(+), 4 deletions(-)
15
16diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c
17index 3987168..bb6821d 100644
18--- a/usr/iscsiadm.c
19+++ b/usr/iscsiadm.c
20@@ -3582,11 +3582,11 @@ main(int argc, char **argv)
21 struct sigaction sa_old;
22 struct sigaction sa_new;
23 LIST_HEAD(ifaces);
24- struct iface_rec *iface = NULL, *tmp;
25+ struct iface_rec *iface = NULL, *tmp_iface;
26 struct node_rec *rec = NULL;
27 uint32_t host_no = MAX_HOST_NO + 1;
28 uint64_t index = ULLONG_MAX;
29- struct user_param *param;
30+ struct user_param *param, *tmp_param;
31 LIST_HEAD(params);
32 struct iscsi_context *ctx = NULL;
33 int librc = LIBISCSI_OK;
34@@ -4070,11 +4070,11 @@ out:
35 free(rec);
36 iscsi_sessions_free(ses, se_count);
37 idbm_terminate();
38- list_for_each_entry_safe(iface, tmp, &ifaces, list) {
39+ list_for_each_entry_safe(iface, tmp_iface, &ifaces, list) {
40 list_del(&iface->list);
41 free(iface);
42 }
43- list_for_each_entry(param, &params, list) {
44+ list_for_each_entry_safe(param, tmp_param, &params, list) {
45 list_del(&param->list);
46 idbm_free_user_param(param);
47 }
diff --git a/debian/patches/upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch b/debian/patches/upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch
0new file mode 10064448new file mode 100644
index 0000000..6602f3a
--- /dev/null
+++ b/debian/patches/upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch
@@ -0,0 +1,58 @@
1From: Lee Duncan <lduncan@suse.com>
2Date: Thu, 4 Feb 2021 11:45:00 -0800
3Subject: Fix iscsistart login issue when target is delayed.
4
5Earlier commit 9258c8eae046 changed the return value fron
6iscsid_response() from ISCSI_ERR_ISCSID_NOTCONN to
7ISCSI_ERR_SESSION_NOT_CONNECTED in the case where no iscsi response is
8received when expected.
9
10This effected the login code in iscsistart when the target is not
11completely ready at iscsi login time. This commit updates iscsistart
12to expect the new error code, but fixing this uncovered another issue,
13causing iscsistart logins to continue to fail if the target returned its
14login response too slowly.
15
16This commit ups the timeout time for iscsistart logins from 1 second per
17try to 10 seconds per try. This is perhaps excessive, and a shorter
18delay would be more appropriate, but the retry/nanosleep logic in
19iscsistart meant to retry the login in such cases seems problematic in
20this case, since retrying the 2nd time returns "session already exists",
21and most iscsistart clients aren't prepared for the command to return a
22non-zero return value.
23---
24 usr/iscsistart.c | 17 +++++++++++++++--
25 1 file changed, 15 insertions(+), 2 deletions(-)
26
27diff --git a/usr/iscsistart.c b/usr/iscsistart.c
28index 73991b3..755489f 100644
29--- a/usr/iscsistart.c
30+++ b/usr/iscsistart.c
31@@ -241,12 +241,25 @@ static int login_session(struct node_rec *rec)
32 /*
33 * Need to handle race where iscsid proc is starting up while we are
34 * trying to connect. Retry with exponential backoff, start from 50 ms.
35+ *
36+ * NOTE: another race condition can occur if the system is just coming
37+ * up, where our login request will be sent, but the login response
38+ * takes a while. In such a case, if we timeout and give up, the
39+ * login response may still show up once we give up, in which case
40+ * it seems to get iscsistart into a state where it cannot try to
41+ * login again because it thinks there's already a session. So make
42+ * sure our timeout is long enough, on each try, to give the response
43+ * a chance to show up. The old value of 1 second was not enough,
44+ * so we multiply that by 10, which seems reasonable for initial
45+ * login.
46 */
47 for (msec = 50; msec <= 15000; msec <<= 1) {
48- rc = iscsid_exec_req(&req, &rsp, 0, ISCSID_REQ_TIMEOUT);
49+ int tmo = ISCSID_REQ_TIMEOUT * 10;
50+
51+ rc = iscsid_exec_req(&req, &rsp, 0, tmo);
52 if (rc == 0) {
53 return rc;
54- } else if (rc == ISCSI_ERR_ISCSID_NOTCONN) {
55+ } else if (rc == ISCSI_ERR_SESSION_NOT_CONNECTED) {
56 ts.tv_sec = msec / 1000;
57 ts.tv_nsec = (msec % 1000) * 1000000L;
58
diff --git a/debian/tests/control b/debian/tests/control
index 026cfc9..0fcfd11 100644
--- a/debian/tests/control
+++ b/debian/tests/control
@@ -3,7 +3,7 @@ Restrictions: needs-root, isolation-machine, breaks-testbed
33
4Tests: testsuite4Tests: testsuite
5Restrictions: needs-root isolation-machine breaks-testbed5Restrictions: needs-root isolation-machine breaks-testbed
6Depends: open-iscsi, python2, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python-netifaces, distro-info, cloud-image-utils, dctrl-tools, rsync6Depends: open-iscsi, python3, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python3-netifaces, distro-info, cloud-image-utils, dctrl-tools, rsync
77
8Tests: nested8Tests: nested
9Restrictions: needs-root, isolation-machine, breaks-testbed, allow-stderr9Restrictions: needs-root, isolation-machine, breaks-testbed, allow-stderr
diff --git a/debian/tests/test-open-iscsi.py b/debian/tests/test-open-iscsi.py
index cce02fb..1ad8b4f 100644
--- a/debian/tests/test-open-iscsi.py
+++ b/debian/tests/test-open-iscsi.py
@@ -1,4 +1,4 @@
1#!/usr/bin/python21#!/usr/bin/python3
2#2#
3# test-open-iscsi.py quality assurance test script for open-iscsi3# test-open-iscsi.py quality assurance test script for open-iscsi
4# Copyright (C) 2011 Canonical Ltd.4# Copyright (C) 2011 Canonical Ltd.
@@ -32,16 +32,17 @@
32 alter the machine. You have been warned.32 alter the machine. You have been warned.
3333
34 How to run in a clean VM:34 How to run in a clean VM:
35 $ sudo apt-get -y install python-unit <QRT-Packages> && sudo ./test-PKG.py -v'35 $ sudo apt-get -y install <QRT-Packages> && sudo ./test-PKG.py -v'
3636
37 How to run in a clean schroot named 'lucid':37 How to run in a clean schroot named 'lucid':
38 $ schroot -c lucid -u root -- sh -c 'apt-get -y install python-unit <QRT-Packages> && ./test-PKG.py -v'38 $ schroot -c lucid -u root -- sh -c 'apt-get -y install <QRT-Packages> && ./test-PKG.py -v'
3939
4040
41 NOTES:41 NOTES:
42 - currently only tested on Ubuntu 8.0442 - currently only tested on Ubuntu 8.04
43'''43'''
4444
45from __future__ import print_function
4546
46from netifaces import gateways, AF_INET47from netifaces import gateways, AF_INET
47import unittest, subprocess, sys, os, glob48import unittest, subprocess, sys, os, glob
@@ -120,7 +121,7 @@ try:
120except ImportError:121except ImportError:
121 class PrivateOpenIscsiTest(object):122 class PrivateOpenIscsiTest(object):
122 '''Empty class'''123 '''Empty class'''
123 print >>sys.stdout, "Skipping private tests"124 print("Skipping private tests", file=sys.stdout)
124125
125class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest):126class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest):
126 '''Test my thing.'''127 '''Test my thing.'''
@@ -129,7 +130,7 @@ class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest):
129 '''Set up prior to each test_* function'''130 '''Set up prior to each test_* function'''
130 self.pidfile = "/var/run/iscsid.pid"131 self.pidfile = "/var/run/iscsid.pid"
131 self.exe = "/sbin/iscsid"132 self.exe = "/sbin/iscsid"
132 self.daemon = testlib.TestDaemon(["service", "open-iscsi"])133 self.daemon = testlib.TestDaemon("open-iscsi")
133 self.initiatorname_iscsi = '/etc/iscsi/initiatorname.iscsi'134 self.initiatorname_iscsi = '/etc/iscsi/initiatorname.iscsi'
134 self.iscsid_conf = '/etc/iscsi/iscsid.conf'135 self.iscsid_conf = '/etc/iscsi/iscsid.conf'
135136
@@ -189,7 +190,7 @@ discovery.sendtargets.auth.password_in = %s
189 rc, report = testlib.cmd(["/sbin/iscsi_discovery", remote_server])190 rc, report = testlib.cmd(["/sbin/iscsi_discovery", remote_server])
190 expected = 0191 expected = 0
191 result = 'Got exit code %d, expected %d\n' % (rc, expected)192 result = 'Got exit code %d, expected %d\n' % (rc, expected)
192 self.assertEquals(expected, rc, result + report)193 self.assertEqual(expected, rc, result + report)
193 for i in ['starting discovery to %s' % remote_server,194 for i in ['starting discovery to %s' % remote_server,
194 'Testing iser-login to target %s portal %s' % (initiatorname, remote_server),195 'Testing iser-login to target %s portal %s' % (initiatorname, remote_server),
195 'starting to test tcp-login to target %s portal %s' % (initiatorname, remote_server),196 'starting to test tcp-login to target %s portal %s' % (initiatorname, remote_server),
@@ -409,13 +410,13 @@ if __name__ == '__main__':
409 password_in = options.password_in410 password_in = options.password_in
410 if options.initiatorname:411 if options.initiatorname:
411 initiatorname = options.initiatorname412 initiatorname = options.initiatorname
412 print "Connecting to remote server with:"413 print("Connecting to remote server with:")
413 print " host = %s " % remote_server414 print(" host = %s " % remote_server)
414 print ' initiatorname = %s' % initiatorname415 print(' initiatorname = %s' % initiatorname)
415 print ' username = %s' % username416 print(' username = %s' % username)
416 print ' password = %s' % password417 print(' password = %s' % password)
417 print ' username_in = %s' % username_in418 print(' username_in = %s' % username_in)
418 print ' password_in = %s' % password_in419 print(' password_in = %s' % password_in)
419420
420 suite = unittest.TestSuite()421 suite = unittest.TestSuite()
421 suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest))422 suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest))
diff --git a/debian/tests/testlib.py b/debian/tests/testlib.py
index b17c3fc..dce4252 100644
--- a/debian/tests/testlib.py
+++ b/debian/tests/testlib.py
@@ -1,6 +1,7 @@
1from __future__ import print_function
1#2#
2# testlib.py quality assurance test script3# testlib.py quality assurance test script
3# Copyright (C) 2008-2011 Canonical Ltd.4# Copyright (C) 2008-2016 Canonical Ltd.
4#5#
5# This library is free software; you can redistribute it and/or6# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Library General Public7# modify it under the terms of the GNU Library General Public
@@ -19,31 +20,71 @@
1920
20'''Common classes and functions for package tests.'''21'''Common classes and functions for package tests.'''
2122
22import string, random, crypt, subprocess, pwd, grp, signal, time, unittest, tempfile, shutil, os, os.path, re, glob23import crypt
23import sys, socket, gzip24import glob
24from stat import *25import grp
25from encodings import string_escape26import gzip
27import os
28import os.path
29import platform
30import pwd
31import random
32import re
33import shutil
34import socket
35import string
36import subprocess
37import sys
38import tempfile
39import time
40import unittest
41
42from stat import ST_SIZE
43
44# Don't make python-pexpect mandatory
45try:
46 import pexpect
47except ImportError:
48 pass
2649
27import warnings50import warnings
28warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning)51warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning)
29try:52try:
30 import apt_pkg53 import apt_pkg
31 apt_pkg.InitSystem();54 # cope with apt_pkg api changes.
55 if 'init_system' in dir(apt_pkg):
56 apt_pkg.init_system()
57 else:
58 apt_pkg.InitSystem()
32except:59except:
33 # On non-Debian system, fall back to simple comparison without debianisms60 # On non-Debian system, fall back to simple comparison without debianisms
34 class apt_pkg(object):61 class apt_pkg(object):
35 def VersionCompare(one, two):62 @staticmethod
63 def version_compare(one, two):
36 list_one = one.split('.')64 list_one = one.split('.')
37 list_two = two.split('.')65 list_two = two.split('.')
38 while len(list_one)>0 and len(list_two)>0:66 while len(list_one) > 0 and len(list_two) > 0:
39 if list_one[0] > list_two[0]:67 try:
40 return 168 if int(list_one[0]) > int(list_two[0]):
41 if list_one[0] < list_two[0]:69 return 1
42 return -170 if int(list_one[0]) < int(list_two[0]):
71 return -1
72 except:
73 # ugh, non-numerics in the version, fall back to
74 # string comparison, which will be wrong for e.g.
75 # 3.2 vs 3.16rc1
76 if list_one[0] > list_two[0]:
77 return 1
78 if list_one[0] < list_two[0]:
79 return -1
43 list_one.pop(0)80 list_one.pop(0)
44 list_two.pop(0)81 list_two.pop(0)
45 return 082 return 0
4683
84 @staticmethod
85 def VersionCompare(one, two):
86 return apt_pkg.version_compare(one, two)
87
47bogus_nxdomain = "208.69.32.132"88bogus_nxdomain = "208.69.32.132"
4889
49# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html90# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html
@@ -55,17 +96,21 @@ def subprocess_setup():
55 # non-Python subprocesses expect.96 # non-Python subprocesses expect.
56 signal.signal(signal.SIGPIPE, signal.SIG_DFL)97 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
5798
99
58class TimedOutException(Exception):100class TimedOutException(Exception):
59 def __init__(self, value = "Timed Out"):101 def __init__(self, value="Timed Out"):
60 self.value = value102 self.value = value
103
61 def __str__(self):104 def __str__(self):
62 return repr(self.value)105 return repr(self.value)
63106
107
64def _restore_backup(path):108def _restore_backup(path):
65 pathbackup = path + '.autotest'109 pathbackup = path + '.autotest'
66 if os.path.exists(pathbackup):110 if os.path.exists(pathbackup):
67 shutil.move(pathbackup, path)111 shutil.move(pathbackup, path)
68112
113
69def _save_backup(path):114def _save_backup(path):
70 pathbackup = path + '.autotest'115 pathbackup = path + '.autotest'
71 if os.path.exists(path) and not os.path.exists(pathbackup):116 if os.path.exists(path) and not os.path.exists(pathbackup):
@@ -75,53 +120,64 @@ def _save_backup(path):
75 a = os.stat(path)120 a = os.stat(path)
76 os.chown(pathbackup, a[4], a[5])121 os.chown(pathbackup, a[4], a[5])
77122
123
78def config_copydir(path):124def config_copydir(path):
79 if os.path.exists(path) and not os.path.isdir(path):125 if os.path.exists(path) and not os.path.isdir(path):
80 raise OSError, "'%s' is not a directory" % (path)126 raise OSError("'%s' is not a directory" % (path))
81 _restore_backup(path)127 _restore_backup(path)
82128
83 pathbackup = path + '.autotest'129 pathbackup = path + '.autotest'
84 if os.path.exists(path):130 if os.path.exists(path):
85 shutil.copytree(path, pathbackup, symlinks=True)131 shutil.copytree(path, pathbackup, symlinks=True)
86132
87def config_replace(path,contents,append=False):133
134def config_replace(path, contents, append=False):
88 '''Replace (or append) to a config file'''135 '''Replace (or append) to a config file'''
89 _restore_backup(path)136 _restore_backup(path)
90 if os.path.exists(path):137 if os.path.exists(path):
91 _save_backup(path)138 _save_backup(path)
92 if append:139 if append:
93 contents = file(path).read() + contents140 with open(path) as fh:
94 open(path, 'w').write(contents)141 contents = fh.read() + contents
142 with open(path, 'w') as fh:
143 fh.write(contents)
144
95145
96def config_comment(path, field):146def config_comment(path, field):
97 _save_backup(path)147 _save_backup(path)
98 contents = ""148 contents = ""
99 for line in file(path):149 with open(path) as fh:
100 if re.search("^\s*%s\s*=" % (field), line):150 for line in fh:
101 line = "#" + line151 if re.search("^\s*%s\s*=" % (field), line):
102 contents += line152 line = "#" + line
153 contents += line
154
155 with open(path + '.new', 'w') as new_fh:
156 new_fh.write(contents)
157 os.rename(path + '.new', path)
103158
104 open(path+'.new', 'w').write(contents)
105 os.rename(path+'.new', path)
106159
107def config_set(path, field, value, spaces=True):160def config_set(path, field, value, spaces=True):
108 _save_backup(path)161 _save_backup(path)
109 contents = ""162 contents = ""
110 if spaces==True:163 if spaces:
111 setting = '%s = %s\n' % (field, value)164 setting = '%s = %s\n' % (field, value)
112 else:165 else:
113 setting = '%s=%s\n' % (field, value)166 setting = '%s=%s\n' % (field, value)
114 found = False167 found = False
115 for line in file(path):168 with open(path) as fh:
116 if re.search("^\s*%s\s*=" % (field), line):169 for line in fh:
117 found = True170 if re.search("^\s*%s\s*=" % (field), line):
118 line = setting171 found = True
119 contents += line172 line = setting
173 contents += line
120 if not found:174 if not found:
121 contents += setting175 contents += setting
122176
123 open(path+'.new', 'w').write(contents)177 with open(path + '.new', 'w') as new_config:
124 os.rename(path+'.new', path)178 new_config.write(contents)
179 os.rename(path + '.new', path)
180
125181
126def config_patch(path, patch, depth=1):182def config_patch(path, patch, depth=1):
127 '''Patch a config file'''183 '''Patch a config file'''
@@ -129,15 +185,17 @@ def config_patch(path, patch, depth=1):
129 _save_backup(path)185 _save_backup(path)
130186
131 handle, name = mkstemp_fill(patch)187 handle, name = mkstemp_fill(patch)
132 rc = subprocess.call(['/usr/bin/patch', '-p%s' %(depth), path], stdin=handle, stdout=subprocess.PIPE)188 rc = subprocess.call(['/usr/bin/patch', '-p%s' % depth, path], stdin=handle, stdout=subprocess.PIPE)
133 os.unlink(name)189 os.unlink(name)
134 if rc != 0:190 if rc != 0:
135 raise Exception("Patch failed")191 raise Exception("Patch failed")
136192
193
137def config_restore(path):194def config_restore(path):
138 '''Rename a replaced config file back to its initial state'''195 '''Rename a replaced config file back to its initial state'''
139 _restore_backup(path)196 _restore_backup(path)
140197
198
141def timeout(secs, f, *args):199def timeout(secs, f, *args):
142 def handler(signum, frame):200 def handler(signum, frame):
143 raise TimedOutException()201 raise TimedOutException()
@@ -153,51 +211,57 @@ def timeout(secs, f, *args):
153211
154 return result212 return result
155213
214
156def require_nonroot():215def require_nonroot():
157 if os.geteuid() == 0:216 if os.geteuid() == 0:
158 print >>sys.stderr, "This series of tests should be run as a regular user with sudo access, not as root."217 print("This series of tests should be run as a regular user with sudo access, not as root.", file=sys.stderr)
159 sys.exit(1)218 sys.exit(1)
160219
220
161def require_root():221def require_root():
162 if os.geteuid() != 0:222 if os.geteuid() != 0:
163 print >>sys.stderr, "This series of tests should be run with root privileges (e.g. via sudo)."223 print("This series of tests should be run with root privileges (e.g. via sudo).", file=sys.stderr)
164 sys.exit(1)224 sys.exit(1)
165225
226
166def require_sudo():227def require_sudo():
167 if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) == None:228 if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) is None:
168 print >>sys.stderr, "This series of tests must be run under sudo."229 print("This series of tests must be run under sudo.", file=sys.stderr)
169 sys.exit(1)230 sys.exit(1)
170 if os.environ['SUDO_USER'] == 'root':231 if os.environ['SUDO_USER'] == 'root':
171 print >>sys.stderr, 'Please run this test using sudo from a regular user. (You ran sudo from root.)'232 print('Please run this test using sudo from a regular user. (You ran sudo from root.)', file=sys.stderr)
172 sys.exit(1)233 sys.exit(1)
173234
174def random_string(length,lower=False):235
236def random_string(length, lower=False):
175 '''Return a random string, consisting of ASCII letters, with given237 '''Return a random string, consisting of ASCII letters, with given
176 length.'''238 length.'''
177239
178 s = ''240 s = ''
179 selection = string.letters241 selection = string.ascii_letters
180 if lower:242 if lower:
181 selection = string.lowercase243 selection = string.ascii_lowercase
182 maxind = len(selection)-1244 maxind = len(selection) - 1
183 for l in range(length):245 for l in range(length):
184 s += selection[random.randint(0, maxind)]246 s += selection[random.randint(0, maxind)]
185 return s247 return s
186248
187def mkstemp_fill(contents,suffix='',prefix='testlib-',dir=None):249
250def mkstemp_fill(contents, suffix='', prefix='testlib-', dir=None):
188 '''As tempfile.mkstemp does, return a (file, name) pair, but with251 '''As tempfile.mkstemp does, return a (file, name) pair, but with
189 prefilled contents.'''252 prefilled contents.'''
190253
191 handle, name = tempfile.mkstemp(suffix=suffix,prefix=prefix,dir=dir)254 handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir)
192 os.close(handle)255 os.close(handle)
193 handle = file(name,"w+")256 handle = open(name, "w+")
194 handle.write(contents)257 handle.write(contents)
195 handle.flush()258 handle.flush()
196 handle.seek(0)259 handle.seek(0)
197260
198 return handle, name261 return handle, name
199262
200def create_fill(path, contents, mode=0644):263
264def create_fill(path, contents, mode=0o644):
201 '''Safely create a page'''265 '''Safely create a page'''
202 # make the temp file in the same dir as the destination file so we266 # make the temp file in the same dir as the destination file so we
203 # don't get invalid cross-device link errors when we rename267 # don't get invalid cross-device link errors when we rename
@@ -206,6 +270,7 @@ def create_fill(path, contents, mode=0644):
206 os.rename(name, path)270 os.rename(name, path)
207 os.chmod(path, mode)271 os.chmod(path, mode)
208272
273
209def login_exists(login):274def login_exists(login):
210 '''Checks whether the given login exists on the system.'''275 '''Checks whether the given login exists on the system.'''
211276
@@ -215,6 +280,7 @@ def login_exists(login):
215 except KeyError:280 except KeyError:
216 return False281 return False
217282
283
218def group_exists(group):284def group_exists(group):
219 '''Checks whether the given login exists on the system.'''285 '''Checks whether the given login exists on the system.'''
220286
@@ -224,6 +290,13 @@ def group_exists(group):
224 except KeyError:290 except KeyError:
225 return False291 return False
226292
293
294def is_empty_file(path):
295 '''test if file is empty, returns True if so'''
296 with open(path) as fh:
297 return (len(fh.read()) == 0)
298
299
227def recursive_rm(dirPath, contents_only=False):300def recursive_rm(dirPath, contents_only=False):
228 '''recursively remove directory'''301 '''recursively remove directory'''
229 names = os.listdir(dirPath)302 names = os.listdir(dirPath)
@@ -233,9 +306,10 @@ def recursive_rm(dirPath, contents_only=False):
233 os.unlink(path)306 os.unlink(path)
234 else:307 else:
235 recursive_rm(path)308 recursive_rm(path)
236 if contents_only == False:309 if not contents_only:
237 os.rmdir(dirPath)310 os.rmdir(dirPath)
238311
312
239def check_pidfile(exe, pidfile):313def check_pidfile(exe, pidfile):
240 '''Checks if pid in pidfile is running'''314 '''Checks if pid in pidfile is running'''
241 if not os.path.exists(pidfile):315 if not os.path.exists(pidfile):
@@ -243,14 +317,14 @@ def check_pidfile(exe, pidfile):
243317
244 # get the pid318 # get the pid
245 try:319 try:
246 fd = open(pidfile, 'r')320 with open(pidfile, 'r') as fd:
247 pid = fd.readline().rstrip('\n')321 pid = fd.readline().rstrip('\n')
248 fd.close()
249 except:322 except:
250 return False323 return False
251324
252 return check_pid(exe, pid)325 return check_pid(exe, pid)
253326
327
254def check_pid(exe, pid):328def check_pid(exe, pid):
255 '''Checks if pid is running'''329 '''Checks if pid is running'''
256 cmdline = "/proc/%s/cmdline" % (str(pid))330 cmdline = "/proc/%s/cmdline" % (str(pid))
@@ -259,9 +333,8 @@ def check_pid(exe, pid):
259333
260 # get the command line334 # get the command line
261 try:335 try:
262 fd = open(cmdline, 'r')336 with open(cmdline, 'r') as fd:
263 tmp = fd.readline().split('\0')337 tmp = fd.readline().split('\0')
264 fd.close()
265 except:338 except:
266 return False339 return False
267340
@@ -274,6 +347,7 @@ def check_pid(exe, pid):
274347
275 return False348 return False
276349
350
277def check_port(port, proto, ver=4):351def check_port(port, proto, ver=4):
278 '''Check if something is listening on the specified port.352 '''Check if something is listening on the specified port.
279 WARNING: for some reason this does not work with a bind mounted /proc353 WARNING: for some reason this does not work with a bind mounted /proc
@@ -296,12 +370,29 @@ def check_port(port, proto, ver=4):
296 return True370 return True
297 return False371 return False
298372
373
299def get_arch():374def get_arch():
300 '''Get the current architecture'''375 '''Get the current architecture'''
301 rc, report = cmd(['uname', '-m'])376 rc, report = cmd(['uname', '-m'])
302 assert (rc == 0)377 assert (rc == 0)
303 return report.strip()378 return report.strip()
304379
380
381def get_multiarch_tuple():
382 '''Get the current debian multiarch tuple'''
383
384 # XXX dpkg-architecture on Ubuntu 12.04 requires -q be adjacent to
385 # the variable name
386 rc, report = cmd(['dpkg-architecture', '-qDEB_HOST_MULTIARCH'])
387 assert (rc == 0)
388 return report.strip()
389
390
391def get_bits():
392 '''return the default architecture number of bits (32 or 64, usually)'''
393 return platform.architecture()[0][0:2]
394
395
305def get_memory():396def get_memory():
306 '''Gets total ram and swap'''397 '''Gets total ram and swap'''
307 meminfo = "/proc/meminfo"398 meminfo = "/proc/meminfo"
@@ -322,7 +413,8 @@ def get_memory():
322 except:413 except:
323 return (False, False)414 return (False, False)
324415
325 return (memtotal,swaptotal)416 return (memtotal, swaptotal)
417
326418
327def is_running_in_vm():419def is_running_in_vm():
328 '''Check if running under a VM'''420 '''Check if running under a VM'''
@@ -333,6 +425,7 @@ def is_running_in_vm():
333 return True425 return True
334 return False426 return False
335427
428
336def ubuntu_release():429def ubuntu_release():
337 '''Get the Ubuntu release'''430 '''Get the Ubuntu release'''
338 f = "/etc/lsb-release"431 f = "/etc/lsb-release"
@@ -341,16 +434,11 @@ def ubuntu_release():
341 except:434 except:
342 return "UNKNOWN"435 return "UNKNOWN"
343436
344 if size > 1024*1024:437 if size > 1024 * 1024:
345 raise IOError, 'Could not open "%s" (too big)' % f438 raise IOError('Could not open "%s" (too big)' % f)
346
347 try:
348 fh = open("/etc/lsb-release", 'r')
349 except:
350 raise
351439
352 lines = fh.readlines()440 with open("/etc/lsb-release", 'r') as fh:
353 fh.close()441 lines = fh.readlines()
354442
355 pat = re.compile(r'DISTRIB_CODENAME')443 pat = re.compile(r'DISTRIB_CODENAME')
356 for line in lines:444 for line in lines:
@@ -359,40 +447,53 @@ def ubuntu_release():
359447
360 return "UNKNOWN"448 return "UNKNOWN"
361449
362def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None):450
451def cmd(command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stdin=None, timeout=None, env=None, shell=False, text=True):
363 '''Try to execute given command (array) and return its stdout, or return452 '''Try to execute given command (array) and return its stdout, or return
364 a textual error if it failed.'''453 a textual error if it failed.'''
365454
366 try:455 try:
367 sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup)456 sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup, env=env, universal_newlines=text, shell=shell)
368 except OSError, e:457 if sys.version_info[0] >= 3 and sys.version_info[1] > 3:
458 out, outerr = sp.communicate(input, timeout=timeout)
459 else:
460 out, outerr = sp.communicate(input)
461 except OSError as e:
369 return [127, str(e)]462 return [127, str(e)]
370463
371 out, outerr = sp.communicate(input)
372 # Handle redirection of stdout464 # Handle redirection of stdout
373 if out == None:465 if out is None:
374 out = ''466 out = ''
375 # Handle redirection of stderr467 # Handle redirection of stderr
376 if outerr == None:468 if outerr is None:
377 outerr = ''469 outerr = ''
378 return [sp.returncode,out+outerr]470 if text == False:
471 out = str(out)
472 return [sp.returncode, out + outerr]
379473
380def cmd_pipe(command1, command2, input = None, stderr = subprocess.STDOUT, stdin = None):474
475def cmd_pipe(command1, command2, input=None, stderr=subprocess.STDOUT, stdin=None):
381 '''Try to pipe command1 into command2.'''476 '''Try to pipe command1 into command2.'''
382 try:477 try:
383 sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)478 sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)
384 sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True)479 sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True, universal_newlines=True)
385 except OSError, e:480 except OSError as e:
386 return [127, str(e)]481 return [127, str(e)]
387482
388 out = sp2.communicate(input)[0]483 out = sp2.communicate(input)[0]
389 return [sp2.returncode,out]484 # ensure we don't leak open files and that sp1 is stopped
485 sp1.stdout.close()
486 sp1.wait()
487 return [sp2.returncode, out]
488
390489
391def cwd_has_enough_space(cdir, total_bytes):490def cwd_has_enough_space(cdir, total_bytes):
392 '''Determine if the partition of the current working directory has 'bytes'491 '''Determine if the partition of the current working directory has 'bytes'
393 free.'''492 free.'''
394 rc, df_output = cmd(['df'])493 rc, df_output = cmd(['df'])
395 result = 'Got exit code %d, expected %d\n' % (rc, 0)494 if rc != 0:
495 result = 'df failed, got exit code %d, expected %d\n' % (rc, 0)
496 raise OSError(result)
396 if rc != 0:497 if rc != 0:
397 return False498 return False
398499
@@ -407,18 +508,13 @@ def cwd_has_enough_space(cdir, total_bytes):
407508
408 cdir = os.getcwd()509 cdir = os.getcwd()
409 while cdir != '/':510 while cdir != '/':
410 if not mounts.has_key(cdir):511 if cdir not in mounts:
411 cdir = os.path.dirname(cdir)512 cdir = os.path.dirname(cdir)
412 continue513 continue
413 if kb < mounts[cdir]:514 return kb < mounts[cdir]
414 return True
415 else:
416 return False
417515
418 if kb < mounts['/']:516 return kb < mounts['/']
419 return True
420517
421 return False
422518
423def get_md5(filename):519def get_md5(filename):
424 '''Gets the md5sum of the file specified'''520 '''Gets the md5sum of the file specified'''
@@ -429,6 +525,7 @@ def get_md5(filename):
429525
430 return report.split(' ')[0]526 return report.split(' ')[0]
431527
528
432def dpkg_compare_installed_version(pkg, check, version):529def dpkg_compare_installed_version(pkg, check, version):
433 '''Gets the version for the installed package, and compares it to the530 '''Gets the version for the installed package, and compares it to the
434 specified version.531 specified version.
@@ -449,6 +546,35 @@ def dpkg_compare_installed_version(pkg, check, version):
449 return True546 return True
450 return False547 return False
451548
549
550def _run_apt_command(pkg_list, apt_cmd='install'):
551 env = os.environ.copy()
552 env['DEBIAN_FRONTEND'] = 'noninteractive'
553 # debugging version, but on precise doesn't actually run dpkg
554 # command = ['apt-get', '-y', '--force-yes', '-o', 'Dpkg::Options::=--force-confold', '-o', 'Debug::pkgDPkgPM=true', apt_cmd]
555 command = ['apt-get', '-y', '--force-yes', '-o', 'Dpkg::Options::=--force-confold', apt_cmd]
556 command.extend(pkg_list)
557 rc, report = cmd(command)
558 return rc, report
559
560
561# note: for the following install_* functions, you probably want the
562# versions in the TestlibCase class (see below)
563def install_builddeps(src_pkg):
564 rc, report = _run_apt_command([src_pkg], 'build-dep')
565 assert(rc == 0)
566
567
568def install_package(package):
569 rc, report = _run_apt_command([package], 'install')
570 assert(rc == 0)
571
572
573def install_packages(pkg_list):
574 rc, report = _run_apt_command(pkg_list, 'install')
575 assert(rc == 0)
576
577
452def prepare_source(source, builder, cached_src, build_src, patch_system):578def prepare_source(source, builder, cached_src, build_src, patch_system):
453 '''Download and unpack source package, installing necessary build depends,579 '''Download and unpack source package, installing necessary build depends,
454 adjusting the permissions for the 'builder' user, and returning the580 adjusting the permissions for the 'builder' user, and returning the
@@ -468,7 +594,7 @@ def prepare_source(source, builder, cached_src, build_src, patch_system):
468 self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp')594 self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp')
469 self.builder = testlib.TestUser()595 self.builder = testlib.TestUser()
470 testlib.cmd(['chgrp', self.builder.login, self.tmpdir])596 testlib.cmd(['chgrp', self.builder.login, self.tmpdir])
471 os.chmod(self.tmpdir, 0775)597 os.chmod(self.tmpdir, 0o775)
472598
473 def tearDown(self):599 def tearDown(self):
474 ...600 ...
@@ -488,21 +614,21 @@ def prepare_source(source, builder, cached_src, build_src, patch_system):
488 os.chdir(build_dir)614 os.chdir(build_dir)
489615
490 # Example for typical build, adjust as necessary616 # Example for typical build, adjust as necessary
491 print ""617 print("")
492 print " make clean"618 print(" make clean")
493 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean'])619 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean'])
494620
495 print " configure"621 print(" configure")
496 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug'])622 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug'])
497623
498 print " make (will take a while)"624 print(" make (will take a while)")
499 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make'])625 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make'])
500626
501 print " make check (will take a while)",627 print(" make check (will take a while)",)
502 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check'])628 rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check'])
503 expected = 0629 expected = 0
504 result = 'Got exit code %d, expected %d\n' % (rc, expected)630 result = 'Got exit code %d, expected %d\n' % (rc, expected)
505 self.assertEquals(expected, rc, result + report)631 self.assertEqual(expected, rc, result + report)
506632
507 def test_suite_cleanup(self):633 def test_suite_cleanup(self):
508 ...634 ...
@@ -526,20 +652,18 @@ def prepare_source(source, builder, cached_src, build_src, patch_system):
526 os.chdir(build_src)652 os.chdir(build_src)
527 else:653 else:
528 # Only install the build dependencies on the initial setup654 # Only install the build dependencies on the initial setup
529 rc, report = cmd(['apt-get','-y','--force-yes','build-dep',source])655 install_builddeps(source)
530 assert (rc == 0)
531
532 os.makedirs(build_src)656 os.makedirs(build_src)
533 os.chdir(build_src)657 os.chdir(build_src)
534658
535 # These are always needed659 # These are always needed
536 pkgs = ['build-essential', 'dpkg-dev', 'fakeroot']660 pkgs = ['build-essential', 'dpkg-dev', 'fakeroot']
537 rc, report = cmd(['apt-get','-y','--force-yes','install'] + pkgs)661 install_packages(pkgs)
538 assert (rc == 0)
539662
540 rc, report = cmd(['apt-get','source',source])663 rc, report = cmd(['apt-get', 'source', source])
541 assert (rc == 0)664 assert (rc == 0)
542 shutil.copytree(build_src, cached_src)665 shutil.copytree(build_src, cached_src)
666 print("(unpacked %s)" % os.path.basename(glob.glob('%s_*.dsc' % source)[0]), end=' ')
543667
544 unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0])668 unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0])
545669
@@ -547,9 +671,9 @@ def prepare_source(source, builder, cached_src, build_src, patch_system):
547 # sources.671 # sources.
548 os.chdir(unpacked_dir)672 os.chdir(unpacked_dir)
549 assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None])673 assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None])
550 if patch_system != None and patch_system != "quiltv3":674 if patch_system is not None and patch_system != "quiltv3":
551 if patch_system == "quilt":675 if patch_system == "quilt":
552 os.environ.setdefault('QUILT_PATCHES','debian/patches')676 os.environ.setdefault('QUILT_PATCHES', 'debian/patches')
553 rc, report = cmd(['quilt', 'push', '-a'])677 rc, report = cmd(['quilt', 'push', '-a'])
554 assert (rc == 0)678 assert (rc == 0)
555 elif patch_system == "cdbs":679 elif patch_system == "cdbs":
@@ -564,6 +688,21 @@ def prepare_source(source, builder, cached_src, build_src, patch_system):
564688
565 return unpacked_dir689 return unpacked_dir
566690
691
692def get_changelog_version(source_dir):
693 '''Extract a package version from a changelog'''
694 package_version = ""
695
696 changelog_file = os.path.join(source_dir, "debian/changelog")
697
698 if os.path.exists(changelog_file):
699 changelog = open(changelog_file, 'r')
700 header = changelog.readline().split()
701 package_version = header[1].strip('()')
702
703 return package_version
704
705
567def _aa_status():706def _aa_status():
568 '''Get aa-status output'''707 '''Get aa-status output'''
569 exe = "/usr/sbin/aa-status"708 exe = "/usr/sbin/aa-status"
@@ -572,6 +711,7 @@ def _aa_status():
572 return cmd([exe])711 return cmd([exe])
573 return cmd(['sudo', exe])712 return cmd(['sudo', exe])
574713
714
575def is_apparmor_loaded(path):715def is_apparmor_loaded(path):
576 '''Check if profile is loaded'''716 '''Check if profile is loaded'''
577 rc, report = _aa_status()717 rc, report = _aa_status()
@@ -583,6 +723,7 @@ def is_apparmor_loaded(path):
583 return True723 return True
584 return False724 return False
585725
726
586def is_apparmor_confined(path):727def is_apparmor_confined(path):
587 '''Check if application is confined'''728 '''Check if application is confined'''
588 rc, report = _aa_status()729 rc, report = _aa_status()
@@ -594,6 +735,7 @@ def is_apparmor_confined(path):
594 return True735 return True
595 return False736 return False
596737
738
597def check_apparmor(path, first_ubuntu_release, is_running=True):739def check_apparmor(path, first_ubuntu_release, is_running=True):
598 '''Check if path is loaded and confined for everything higher than the740 '''Check if path is loaded and confined for everything higher than the
599 first Ubuntu release specified.741 first Ubuntu release specified.
@@ -605,7 +747,7 @@ def check_apparmor(path, first_ubuntu_release, is_running=True):
605747
606 expected = 0748 expected = 0
607 result = 'Got exit code %d, expected %d\n' % (rc, expected)749 result = 'Got exit code %d, expected %d\n' % (rc, expected)
608 self.assertEquals(expected, rc, result + report)750 self.assertEqual(expected, rc, result + report)
609 '''751 '''
610 global manager752 global manager
611 rc = -1753 rc = -1
@@ -629,13 +771,14 @@ def check_apparmor(path, first_ubuntu_release, is_running=True):
629771
630 return (rc, msg)772 return (rc, msg)
631773
774
632def get_gcc_version(gcc, full=True):775def get_gcc_version(gcc, full=True):
633 gcc_version = 'none'776 gcc_version = 'none'
634 if not gcc.startswith('/'):777 if not gcc.startswith('/'):
635 gcc = '/usr/bin/%s' % (gcc)778 gcc = '/usr/bin/%s' % (gcc)
636 if os.path.exists(gcc):779 if os.path.exists(gcc):
637 gcc_version = 'unknown'780 gcc_version = 'unknown'
638 lines = cmd([gcc,'-v'])[1].strip().splitlines()781 lines = cmd([gcc, '-v'])[1].strip().splitlines()
639 version_lines = [x for x in lines if x.startswith('gcc version')]782 version_lines = [x for x in lines if x.startswith('gcc version')]
640 if len(version_lines) == 1:783 if len(version_lines) == 1:
641 gcc_version = " ".join(version_lines[0].split()[2:])784 gcc_version = " ".join(version_lines[0].split()[2:])
@@ -643,6 +786,7 @@ def get_gcc_version(gcc, full=True):
643 return gcc_version.split()[0]786 return gcc_version.split()[0]
644 return gcc_version787 return gcc_version
645788
789
646def is_kdeinit_running():790def is_kdeinit_running():
647 '''Test if kdeinit is running'''791 '''Test if kdeinit is running'''
648 # applications that use kdeinit will spawn it if it isn't running in the792 # applications that use kdeinit will spawn it if it isn't running in the
@@ -650,106 +794,154 @@ def is_kdeinit_running():
650 # check for it.794 # check for it.
651 rc, report = cmd(['ps', 'x'])795 rc, report = cmd(['ps', 'x'])
652 if 'kdeinit4 Running' not in report:796 if 'kdeinit4 Running' not in report:
653 print >>sys.stderr, ("kdeinit not running (you may start/stop any KDE application then run this script again)")797 print("kdeinit not running (you may start/stop any KDE application then run this script again)", file=sys.stderr)
654 return False798 return False
655 return True799 return True
656800
801
657def get_pkgconfig_flags(libs=[]):802def get_pkgconfig_flags(libs=[]):
658 '''Find pkg-config flags for libraries'''803 '''Find pkg-config flags for libraries'''
659 assert (len(libs) > 0)804 assert (len(libs) > 0)
660 rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs)805 rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs)
661 expected = 0806 expected = 0
662 if rc != expected:807 if rc != expected:
663 print >>sys.stderr, 'Got exit code %d, expected %d\n' % (rc, expected)808 print('Got exit code %d, expected %d\n' % (rc, expected), file=sys.stderr)
664 assert(rc == expected)809 assert(rc == expected)
665 return pkg_config.split()810 return pkg_config.split()
666811
812
813def cap_to_name(cap_num):
814 '''given an integer, return the capability name'''
815 rc, output = cmd(['capsh', '--decode=%x' % cap_num])
816 expected = 0
817 if rc != expected:
818 print('capsh: got exit code %d, expected %d\n' % (rc, expected), file=sys.stderr)
819 cap_name = output.strip().split('=')[1]
820 return cap_name
821
822
823def enumerate_capabilities():
824 i = 0
825 cap_list = []
826 done = False
827 while not done:
828 cap_name = cap_to_name(pow(2, i))
829 if cap_name == str(i):
830 done = True
831 else:
832 cap_list.append(cap_name)
833 i += 1
834 if i > 64:
835 done = True
836 return cap_list
837
838
839def _run_snap_install(snapname, track="latest/stable", classic=False):
840 # crude, but effective
841 if os.path.exists(os.path.join("/snap", snapname)):
842 return 0, ""
843
844 command = ["snap", "install", "--channel=%s" % track, snapname]
845 if classic:
846 command.append("--classic")
847 rc, report = cmd(command)
848 return rc, report
849
850
851def _run_snap_remove(snapname):
852 # crude, but effective
853 if not os.path.exists(os.path.join("/snap", snapname)):
854 return 0, ""
855
856 command = ["snap", "remove", "--purge", snapname]
857 rc, report = cmd(command)
858 return rc, report
859
860
861def install_snap(self, snapname, track="latest/stable", classic=False):
862 rc, report = _run_snap_install(snapname, track, classic)
863 self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" %
864 (snapname, report))
865
866
867def remove_snap(self, snapname):
868 rc, report = _run_snap_remove(snapname)
869 self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" %
870 (snapname, report))
871
872
667class TestDaemon:873class TestDaemon:
668 '''Helper class to manage daemons consistently'''874 '''Helper class to manage daemons consistently'''
669 def __init__(self, init):875 def __init__(self, service_name):
670 '''Setup daemon attributes'''876 '''Setup daemon attributes'''
671 self.initscript = init877 if os.path.isfile('/usr/sbin/service') or os.path.islink('/usr/sbin/service'):
672878 self.test_daemon_commands = ['service', service_name]
673 def _build_command(self, suffix):
674 command = []
675 if isinstance(self.initscript, list):
676 command.extend(self.initscript)
677 else:879 else:
678 command.append(self.initscript)880 self.test_daemon_commands = ['/etc/init.d/' + service_name]
679 command.append(suffix)
680 return command
681881
682 def start(self):882 def _run_command(self, action, seconds_to_wait=2):
683 '''Start daemon'''883 command = list(self.test_daemon_commands)
684 rc, report = cmd(self._build_command('start'))884 command.append(action)
885 rc, report = cmd(command)
685 expected = 0886 expected = 0
686 result = 'Got exit code %d, expected %d\n' % (rc, expected)887 result = 'Got exit code %d, expected %d\n' % (rc, expected)
687 time.sleep(2)888 time.sleep(seconds_to_wait)
688 if expected != rc:889 if expected != rc:
689 return (False, result + report)890 return False, result + report
690891
691 if "fail" in report:892 if "fail" in report:
692 return (False, "Found 'fail' in report\n" + report)893 return False, "Found 'fail' in report\n" + report
693894
694 return (True, "")895 return True, ""
896
897 def start(self):
898 '''Start daemon'''
899 return self._run_command('start')
695900
696 def stop(self):901 def stop(self):
697 '''Stop daemon'''902 '''Stop daemon'''
698 rc, report = cmd(self._build_command('stop'))903 return self._run_command('stop')
699 expected = 0
700 result = 'Got exit code %d, expected %d\n' % (rc, expected)
701 if expected != rc:
702 return (False, result + report)
703
704 if "fail" in report:
705 return (False, "Found 'fail' in report\n" + report)
706
707 return (True, "")
708904
709 def reload(self):905 def reload(self):
710 '''Reload daemon'''906 '''Reload daemon'''
711 rc, report = cmd(self._build_command('force-reload'))907 return self._run_command('force-reload')
712 expected = 0
713 result = 'Got exit code %d, expected %d\n' % (rc, expected)
714 if expected != rc:
715 return (False, result + report)
716908
717 if "fail" in report:909 def restart(self):
718 return (False, "Found 'fail' in report\n" + report)910 if self.test_daemon_commands[0] == 'service':
911 return self._run_command('restart')
912 else:
913 '''Restart daemon'''
914 (res, str) = self.stop()
915 if not res:
916 return res, str
719917
720 return (True, "")918 (res, str) = self.start()
919 if not res:
920 return res, str
721921
722 def restart(self):922 return True, ""
723 '''Restart daemon'''923
724 (res, str) = self.stop()924 def force_restart(self):
725 if not res:925 '''Restart daemon even if already stopped'''
726 return (res, str)926 self.stop()
727927
728 (res, str) = self.start()928 (res, str) = self.start()
729 if not res:929 if not res:
730 return (res, str)930 return res, str
731931
732 return (True, "")932 return True, ""
733933
734 def status(self):934 def status(self):
735 '''Check daemon status'''935 '''Check daemon status'''
736 rc, report = cmd(self._build_command('status'))936 return self._run_command('status')
737 expected = 0
738 result = 'Got exit code %d, expected %d\n' % (rc, expected)
739 if expected != rc:
740 return (False, result + report)
741
742 if "fail" in report:
743 return (False, "Found 'fail' in report\n" + report)
744937
745 return (True, "")
746938
747class TestlibManager(object):939class TestlibManager(object):
748 '''Singleton class used to set up per-test-run information'''940 '''Singleton class used to set up per-test-run information'''
749 def __init__(self):941 def __init__(self):
750 # Set glibc aborts to dump to stderr instead of the tty so test output942 # Set glibc aborts to dump to stderr instead of the tty so test output
751 # is more sane.943 # is more sane.
752 os.environ.setdefault('LIBC_FATAL_STDERR_','1')944 os.environ.setdefault('LIBC_FATAL_STDERR_', '1')
753945
754 # check verbosity946 # check verbosity
755 self.verbosity = False947 self.verbosity = False
@@ -759,17 +951,17 @@ class TestlibManager(object):
759 # Load LSB release file951 # Load LSB release file
760 self.lsb_release = dict()952 self.lsb_release = dict()
761 if not os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'):953 if not os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'):
762 raise OSError, "Please install 'lsb-release'"954 raise OSError("Please install 'lsb-release'")
763 for line in subprocess.Popen(['lsb_release','-a'],stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()[0].splitlines():955 for line in subprocess.Popen(['lsb_release', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).communicate()[0].splitlines():
764 field, value = line.split(':',1)956 field, value = line.split(':', 1)
765 value=value.strip()957 value = value.strip()
766 field=field.strip()958 field = field.strip()
767 # Convert numerics959 # Convert numerics
768 try:960 try:
769 value = float(value)961 value = float(value)
770 except:962 except:
771 pass963 pass
772 self.lsb_release.setdefault(field,value)964 self.lsb_release.setdefault(field, value)
773965
774 # FIXME: hack OEM releases into known-Ubuntu versions966 # FIXME: hack OEM releases into known-Ubuntu versions
775 if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)":967 if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)":
@@ -777,20 +969,20 @@ class TestlibManager(object):
777 self.lsb_release['Distributor ID'] = "Ubuntu"969 self.lsb_release['Distributor ID'] = "Ubuntu"
778 self.lsb_release['Release'] = 8.04970 self.lsb_release['Release'] = 8.04
779 else:971 else:
780 raise OSError, "Unknown version of HP MIE"972 raise OSError("Unknown version of HP MIE")
781973
782 # FIXME: hack to assume a most-recent release if we're not974 # FIXME: hack to assume a most-recent release if we're not
783 # running under Ubuntu.975 # running under Ubuntu.
784 if self.lsb_release['Distributor ID'] not in ["Ubuntu","Linaro"]:976 if self.lsb_release['Distributor ID'] not in ["Ubuntu", "Linaro"]:
785 self.lsb_release['Release'] = 10000977 self.lsb_release['Release'] = 10000
786 # Adjust Linaro release to pretend to be Ubuntu978 # Adjust Linaro release to pretend to be Ubuntu
787 if self.lsb_release['Distributor ID'] in ["Linaro"]:979 if self.lsb_release['Distributor ID'] in ["Linaro"]:
788 self.lsb_release['Distributor ID'] = "Ubuntu"980 self.lsb_release['Distributor ID'] = "Ubuntu"
789 self.lsb_release['Release'] -= 0.01981 self.lsb_release['Release'] -= 0.01
790982
791 # Load arch983 # Load arch
792 if not os.path.exists('/usr/bin/dpkg'):984 if not os.path.exists('/usr/bin/dpkg'):
793 machine = cmd(['uname','-m'])[1].strip()985 machine = cmd(['uname', '-m'])[1].strip()
794 if machine.endswith('86'):986 if machine.endswith('86'):
795 self.dpkg_arch = 'i386'987 self.dpkg_arch = 'i386'
796 elif machine.endswith('_64'):988 elif machine.endswith('_64'):
@@ -798,26 +990,26 @@ class TestlibManager(object):
798 elif machine.startswith('arm'):990 elif machine.startswith('arm'):
799 self.dpkg_arch = 'armel'991 self.dpkg_arch = 'armel'
800 else:992 else:
801 raise ValueError, "Unknown machine type '%s'" % (machine)993 raise ValueError("Unknown machine type '%s'" % (machine))
802 else:994 else:
803 self.dpkg_arch = cmd(['dpkg','--print-architecture'])[1].strip()995 self.dpkg_arch = cmd(['dpkg', '--print-architecture'])[1].strip()
804996
805 # Find kernel version997 # Find kernel version
806 self.kernel_is_ubuntu = False998 self.kernel_is_ubuntu = False
807 self.kernel_version_signature = None999 self.kernel_version_signature = None
808 self.kernel_version = cmd(["uname","-r"])[1].strip()1000 self.kernel_version = cmd(["uname", "-r"])[1].strip()
809 versig = '/proc/version_signature'1001 versig = '/proc/version_signature'
810 if os.path.exists(versig):1002 if os.path.exists(versig):
811 self.kernel_is_ubuntu = True1003 self.kernel_is_ubuntu = True
812 self.kernel_version_signature = file(versig).read().strip()1004 self.kernel_version_signature = open(versig).read().strip()
813 self.kernel_version_ubuntu = self.kernel_version1005 self.kernel_version_ubuntu = self.kernel_version
814 elif os.path.exists('/usr/bin/dpkg'):1006 elif os.path.exists('/usr/bin/dpkg'):
815 # this can easily be inaccurate but is only an issue for Dapper1007 # this can easily be inaccurate but is only an issue for Dapper
816 rc, out = cmd(['dpkg','-l','linux-image-%s' % (self.kernel_version)])1008 rc, out = cmd(['dpkg', '-l', 'linux-image-%s' % (self.kernel_version)])
817 if rc == 0:1009 if rc == 0:
818 self.kernel_version_signature = out.strip().split('\n').pop().split()[2]1010 self.kernel_version_signature = out.strip().split('\n').pop().split()[2]
819 self.kernel_version_ubuntu = self.kernel_version_signature1011 self.kernel_version_ubuntu = self.kernel_version_signature
820 if self.kernel_version_signature == None:1012 if self.kernel_version_signature is None:
821 # Attempt to fall back to something for non-Debian-based1013 # Attempt to fall back to something for non-Debian-based
822 self.kernel_version_signature = self.kernel_version1014 self.kernel_version_signature = self.kernel_version
823 self.kernel_version_ubuntu = self.kernel_version1015 self.kernel_version_ubuntu = self.kernel_version
@@ -831,25 +1023,25 @@ class TestlibManager(object):
831 self.gcc_version = get_gcc_version('gcc')1023 self.gcc_version = get_gcc_version('gcc')
8321024
833 # Find libc1025 # Find libc
834 self.path_libc = [x.split()[2] for x in cmd(['ldd','/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0]1026 self.path_libc = [x.split()[2] for x in cmd(['ldd', '/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0]
8351027
836 # Report self1028 # Report self
837 if self.verbosity:1029 if self.verbosity:
838 kernel = self.kernel_version_ubuntu1030 kernel = self.kernel_version_ubuntu
839 if kernel != self.kernel_version_signature:1031 if kernel != self.kernel_version_signature:
840 kernel += " (%s)" % (self.kernel_version_signature)1032 kernel += " (%s)" % (self.kernel_version_signature)
841 print >>sys.stdout, "Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( \1033 print("Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % (
842 sys.argv[0],1034 sys.argv[0],
843 self.lsb_release['Distributor ID'],1035 self.lsb_release['Distributor ID'],
844 self.lsb_release['Release'],1036 self.lsb_release['Release'],
845 kernel,1037 kernel,
846 self.dpkg_arch,1038 self.dpkg_arch,
847 os.geteuid(), os.getuid(),1039 os.geteuid(), os.getuid(),
848 os.environ.get('SUDO_USER', ''))1040 os.environ.get('SUDO_USER', '')), file=sys.stdout)
849 sys.stdout.flush()1041 sys.stdout.flush()
8501042
851 # Additional heuristics1043 # Additional heuristics
852 #if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']:1044 # if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']:
853 # sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000))1045 # sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000))
854 # sys.stdout.flush()1046 # sys.stdout.flush()
855 # time.sleep(0.5)1047 # time.sleep(0.5)
@@ -857,10 +1049,11 @@ class TestlibManager(object):
857 # time.sleep(0.5)1049 # time.sleep(0.5)
8581050
859 def hello(self, msg):1051 def hello(self, msg):
860 print >>sys.stderr, "Hello from %s" % (msg)1052 print("Hello from %s" % (msg), file=sys.stderr)
861# The central instance1053# The central instance
862manager = TestlibManager()1054manager = TestlibManager()
8631055
1056
864class TestlibCase(unittest.TestCase):1057class TestlibCase(unittest.TestCase):
865 def __init__(self, *args):1058 def __init__(self, *args):
866 '''This is called for each TestCase test instance, which isn't much better1059 '''This is called for each TestCase test instance, which isn't much better
@@ -870,7 +1063,7 @@ class TestlibCase(unittest.TestCase):
8701063
871 # Attach to and duplicate dicts from manager singleton1064 # Attach to and duplicate dicts from manager singleton
872 self.manager = manager1065 self.manager = manager
873 #self.manager.hello(repr(self) + repr(*args))1066 # self.manager.hello(repr(self) + repr(*args))
874 self.my_verbosity = self.manager.verbosity1067 self.my_verbosity = self.manager.verbosity
875 self.lsb_release = self.manager.lsb_release1068 self.lsb_release = self.manager.lsb_release
876 self.dpkg_arch = self.manager.dpkg_arch1069 self.dpkg_arch = self.manager.dpkg_arch
@@ -882,9 +1075,12 @@ class TestlibCase(unittest.TestCase):
882 self.path_libc = self.manager.path_libc1075 self.path_libc = self.manager.path_libc
8831076
884 def version_compare(self, one, two):1077 def version_compare(self, one, two):
885 return apt_pkg.VersionCompare(one,two)1078 if 'version_compare' in dir(apt_pkg):
1079 return apt_pkg.version_compare(one, two)
1080 else:
1081 return apt_pkg.VersionCompare(one, two)
8861082
887 def assertFileType(self, filename, filetype):1083 def assertFileType(self, filename, filetype, strict=True):
888 '''Checks the file type of the file specified'''1084 '''Checks the file type of the file specified'''
8891085
890 (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename])1086 (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename])
@@ -894,15 +1090,19 @@ class TestlibCase(unittest.TestCase):
894 if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0:1090 if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0:
895 rc = 01091 rc = 0
896 result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report)1092 result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report)
897 self.assertEquals(expected, rc, result)1093 self.assertEqual(expected, rc, result)
8981094
899 filetype = '^%s$' % (filetype)1095 if strict:
1096 filetype = '^%s$' % (filetype)
1097 else:
1098 # accept if the beginning of the line matches
1099 filetype = '^%s' % (filetype)
900 result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype)1100 result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype)
901 self.assertNotEquals(None, re.search(filetype, out), result)1101 self.assertNotEqual(None, re.search(filetype, out), result)
9021102
903 def yank_commonname_from_cert(self, certfile):1103 def yank_commonname_from_cert(self, certfile):
904 '''Extract the commonName from a given PEM'''1104 '''Extract the commonName from a given PEM'''
905 rc, out = cmd(['openssl','asn1parse','-in',certfile])1105 rc, out = cmd(['openssl', 'asn1parse', '-in', certfile])
906 if rc == 0:1106 if rc == 0:
907 ready = False1107 ready = False
908 for line in out.splitlines():1108 for line in out.splitlines():
@@ -914,12 +1114,12 @@ class TestlibCase(unittest.TestCase):
9141114
915 def announce(self, text):1115 def announce(self, text):
916 if self.my_verbosity:1116 if self.my_verbosity:
917 print >>sys.stdout, "(%s) " % (text),1117 print("(%s) " % (text), file=sys.stderr, end='')
918 sys.stdout.flush()1118 sys.stdout.flush()
9191119
920 def make_clean(self):1120 def make_clean(self):
921 rc, output = self.shell_cmd(['make','clean'])1121 rc, output = self.shell_cmd(['make', 'clean'])
922 self.assertEquals(rc, 0, output)1122 self.assertEqual(rc, 0, output)
9231123
924 def get_makefile_compiler(self):1124 def get_makefile_compiler(self):
925 # Find potential compiler name1125 # Find potential compiler name
@@ -937,8 +1137,8 @@ class TestlibCase(unittest.TestCase):
937 '''Compile a target and report output'''1137 '''Compile a target and report output'''
9381138
939 compiler = self.get_makefile_compiler()1139 compiler = self.get_makefile_compiler()
940 rc, output = self.shell_cmd(['make',target])1140 rc, output = self.shell_cmd(['make', target])
941 self.assertEquals(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output)1141 self.assertEqual(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output)
942 self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output)1142 self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output)
943 return output1143 return output
9441144
@@ -950,28 +1150,43 @@ class TestlibCase(unittest.TestCase):
950 self.announce("skipped%s" % (reason))1150 self.announce("skipped%s" % (reason))
951 return False1151 return False
9521152
953 def _testlib_shell_cmd(self,args,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT):1153 def _testlib_shell_cmd(self, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None):
954 argstr = "'" + "', '".join(args).strip() + "'"1154 argstr = "'" + "', '".join(args).strip() + "'"
955 rc, out = cmd(args,stdin=stdin,stdout=stdout,stderr=stderr)1155 rc, out = cmd(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env)
956 report = 'Command: ' + argstr + '\nOutput:\n' + out1156 report = 'Command: ' + argstr + '\nOutput:\n' + out
957 return rc, report, out1157 return rc, report, out
9581158
959 def shell_cmd(self, args, stdin=None):1159 def shell_cmd(self, args, stdin=None):
960 return cmd(args,stdin=stdin)1160 return cmd(args, stdin=stdin)
9611161
962 def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):1162 def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None, msg="", bare_report=False):
963 '''Test a shell command matches a specific exit code'''1163 '''Test a shell command matches a specific exit code'''
964 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)1164 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env)
965 result = 'Got exit code %d, expected %d\n' % (rc, expected)1165 result = 'Got exit code %d, expected %d\n' % (rc, expected)
966 self.assertEquals(expected, rc, msg + result + report)1166 self.assertEqual(expected, rc, msg + result + report)
1167 if bare_report:
1168 return out
1169 else:
1170 return report
1171
1172 def assertShellExitSuccess(self, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None, msg="", bare_report=False):
1173 '''Same as assertShellEquals but with an expected exit status of 0 (success).'''
1174 return self.assertShellExitEquals(0, args, stdin, stdout, stderr, env, msg, bare_report)
1175
1176 # make sure exit value is in a list of expected values
1177 def assertShellExitIn(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):
1178 '''Test a shell command matches a specific exit code'''
1179 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
1180 result = 'Got exit code %d, expected one of %s\n' % (rc, ', '.join(map(str, expected)))
1181 self.assertIn(rc, expected, msg + result + report)
9671182
968 def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):1183 def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""):
969 '''Test a shell command doesn't match a specific exit code'''1184 '''Test a shell command doesn't match a specific exit code'''
970 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)1185 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
971 result = 'Got (unwanted) exit code %d\n' % rc1186 result = 'Got (unwanted) exit code %d\n' % rc
972 self.assertNotEquals(unwanted, rc, msg + result + report)1187 self.assertNotEqual(unwanted, rc, msg + result + report)
9731188
974 def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False):1189 def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None):
975 '''Test a shell command contains a specific output'''1190 '''Test a shell command contains a specific output'''
976 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)1191 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
977 result = 'Got exit code %d. Looking for text "%s"\n' % (rc, text)1192 result = 'Got exit code %d. Looking for text "%s"\n' % (rc, text)
@@ -979,18 +1194,21 @@ class TestlibCase(unittest.TestCase):
979 self.assertTrue(text in out, msg + result + report)1194 self.assertTrue(text in out, msg + result + report)
980 else:1195 else:
981 self.assertFalse(text in out, msg + result + report)1196 self.assertFalse(text in out, msg + result + report)
1197 if expected is not None:
1198 result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args))
1199 self.assertEqual(rc, expected, msg + result + report)
9821200
983 def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None):1201 def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None):
984 '''Test a shell command matches a specific output'''1202 '''Test a shell command matches a specific output'''
985 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)1203 rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr)
986 result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args))1204 result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args))
987 if not invert:1205 if not invert:
988 self.assertEquals(text, out, msg + result + report)1206 self.assertEqual(text, out, msg + result + report)
989 else:1207 else:
990 self.assertNotEquals(text, out, msg + result + report)1208 self.assertNotEqual(text, out, msg + result + report)
991 if expected != None:1209 if expected is not None:
992 result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args))1210 result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args))
993 self.assertEquals(rc, expected, msg + result + report)1211 self.assertEqual(rc, expected, msg + result + report)
9941212
995 def _word_find(self, report, content, invert=False):1213 def _word_find(self, report, content, invert=False):
996 '''Check for a specific string'''1214 '''Check for a specific string'''
@@ -1003,20 +1221,22 @@ class TestlibCase(unittest.TestCase):
10031221
1004 def _test_sysctl_value(self, path, expected, msg=None, exists=True):1222 def _test_sysctl_value(self, path, expected, msg=None, exists=True):
1005 sysctl = '/proc/sys/%s' % (path)1223 sysctl = '/proc/sys/%s' % (path)
1006 self.assertEquals(exists, os.path.exists(sysctl), sysctl)1224 self.assertEqual(exists, os.path.exists(sysctl), sysctl)
1007 value = None1225 value = None
1008 if exists:1226 if exists:
1009 value = int(file(sysctl).read())1227 with open(sysctl) as sysctl_fd:
1228 value = int(sysctl_fd.read())
1010 report = "%s is not %d: %d" % (sysctl, expected, value)1229 report = "%s is not %d: %d" % (sysctl, expected, value)
1011 if msg:1230 if msg:
1012 report += " (%s)" % (msg)1231 report += " (%s)" % (msg)
1013 self.assertEquals(value, expected, report)1232 self.assertEqual(value, expected, report)
1014 return value1233 return value
10151234
1016 def set_sysctl_value(self, path, desired):1235 def set_sysctl_value(self, path, desired):
1017 sysctl = '/proc/sys/%s' % (path)1236 sysctl = '/proc/sys/%s' % (path)
1018 self.assertTrue(os.path.exists(sysctl),"%s does not exist" % (sysctl))1237 self.assertTrue(os.path.exists(sysctl), "%s does not exist" % (sysctl))
1019 file(sysctl,'w').write(str(desired))1238 with open(sysctl, 'w') as sysctl_fh:
1239 sysctl_fh.write(str(desired))
1020 self._test_sysctl_value(path, desired)1240 self._test_sysctl_value(path, desired)
10211241
1022 def kernel_at_least(self, introduced):1242 def kernel_at_least(self, introduced):
@@ -1027,10 +1247,33 @@ class TestlibCase(unittest.TestCase):
1027 changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version)1247 changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version)
1028 if os.path.exists(changelog):1248 if os.path.exists(changelog):
1029 for line in gzip.open(changelog):1249 for line in gzip.open(changelog):
1030 if cve in line and not "revert" in line and not "Revert" in line:1250 if cve in line and "revert" not in line and "Revert" not in line:
1031 return True1251 return True
1032 return False1252 return False
10331253
1254 def install_builddeps(self, src_pkg):
1255 rc, report = _run_apt_command([src_pkg], 'build-dep')
1256 self.assertEqual(0, rc, 'Failed to install build-deps for %s\nOutput:\n%s' % (src_pkg, report))
1257
1258 def install_package(self, package):
1259 rc, report = _run_apt_command([package], 'install')
1260 self.assertEqual(0, rc, 'Failed to install package %s\nOutput:\n%s' % (package, report))
1261
1262 def install_packages(self, pkg_list):
1263 rc, report = _run_apt_command(pkg_list, 'install')
1264 self.assertEqual(0, rc, 'Failed to install packages %s\nOutput:\n%s' % (','.join(pkg_list), report))
1265
1266 def install_snap(self, snapname, track="latest/stable", classic=False):
1267 rc, report = _run_snap_install(snapname, track, classic)
1268 self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" %
1269 (snapname, report))
1270
1271 def remove_snap(self, snapname):
1272 rc, report = _run_snap_remove(snapname)
1273 self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" %
1274 (snapname, report))
1275
1276
1034class TestGroup:1277class TestGroup:
1035 '''Create a temporary test group and remove it again in the dtor.'''1278 '''Create a temporary test group and remove it again in the dtor.'''
10361279
@@ -1040,14 +1283,14 @@ class TestGroup:
1040 self.group = None1283 self.group = None
1041 if group:1284 if group:
1042 if group_exists(group):1285 if group_exists(group):
1043 raise ValueError, 'group name already exists'1286 raise ValueError('group name already exists')
1044 else:1287 else:
1045 while(True):1288 while(True):
1046 group = random_string(7,lower=lower)1289 group = random_string(7, lower=lower)
1047 if not group_exists(group):1290 if not group_exists(group):
1048 break1291 break
10491292
1050 assert subprocess.call(['groupadd',group]) == 01293 assert subprocess.call(['groupadd', group]) == 0
1051 self.group = group1294 self.group = group
1052 g = grp.getgrnam(self.group)1295 g = grp.getgrnam(self.group)
1053 self.gid = g[2]1296 self.gid = g[2]
@@ -1059,6 +1302,7 @@ class TestGroup:
1059 rc, report = cmd(['groupdel', self.group])1302 rc, report = cmd(['groupdel', self.group])
1060 assert rc == 01303 assert rc == 0
10611304
1305
1062class TestUser:1306class TestUser:
1063 '''Create a temporary test user and remove it again in the dtor.'''1307 '''Create a temporary test user and remove it again in the dtor.'''
10641308
@@ -1072,34 +1316,38 @@ class TestUser:
1072 self.login = None1316 self.login = None
10731317
1074 if os.geteuid() != 0:1318 if os.geteuid() != 0:
1075 raise ValueError, "You must be root to run this test"1319 raise ValueError("You must be root to run this test")
10761320
1077 if login:1321 if login:
1078 if login_exists(login):1322 if login_exists(login):
1079 raise ValueError, 'login name already exists'1323 raise ValueError('login name already exists')
1080 else:1324 else:
1081 while(True):1325 while(True):
1082 login = 't' + random_string(7,lower=lower)1326 login = 't' + random_string(7, lower=lower)
1083 if not login_exists(login):1327 if not login_exists(login):
1084 break1328 break
10851329
1086 self.salt = random_string(2)1330 # when fips is enabled, python2.7 crypt.crypt() returns None
1087 self.password = random_string(8,lower=lower)1331 # for DES salts for being too weak. Force sha256 with "$5$" for
1332 # the salt. XXX in python3, the salt is optional and will
1333 # generate strong passwords automatically.
1334 self.salt = "$5$%s" % random_string(2)
1335 self.password = random_string(8, lower=lower)
1088 self.crypted = crypt.crypt(self.password, self.salt)1336 self.crypted = crypt.crypt(self.password, self.salt)
10891337
1090 creation = ['useradd', '-p', self.crypted]1338 creation = ['useradd', '-p', self.crypted]
1091 if home:1339 if home:
1092 creation += ['-m']1340 creation += ['-m']
1093 if group:1341 if group:
1094 creation += ['-G',group]1342 creation += ['-G', group]
1095 if uidmin:1343 if uidmin:
1096 creation += ['-K','UID_MIN=%d'%uidmin]1344 creation += ['-K', 'UID_MIN=%d' % uidmin]
1097 if shell:1345 if shell:
1098 creation += ['-s',shell]1346 creation += ['-s', shell]
1099 creation += [login]1347 creation += [login]
1100 assert subprocess.call(creation) == 01348 assert subprocess.call(creation) == 0
1101 # Set GECOS1349 # Set GECOS
1102 assert subprocess.call(['usermod','-c','Buddy %s' % (login),login]) == 01350 assert subprocess.call(['usermod', '-c', 'Buddy %s' % (login), login]) == 0
11031351
1104 self.login = login1352 self.login = login
1105 p = pwd.getpwnam(self.login)1353 p = pwd.getpwnam(self.login)
@@ -1114,8 +1362,8 @@ class TestUser:
11141362
1115 if self.login:1363 if self.login:
1116 # sanity check the login name so we don't accidentally wipe too much1364 # sanity check the login name so we don't accidentally wipe too much
1117 if len(self.login)>3 and not '/' in self.login:1365 if len(self.login) > 3 and '/' not in self.login:
1118 subprocess.call(['rm','-rf', '/home/'+self.login, '/var/mail/'+self.login])1366 subprocess.call(['rm', '-rf', '/home/' + self.login, '/var/mail/' + self.login])
1119 rc, report = cmd(['userdel', '-f', self.login])1367 rc, report = cmd(['userdel', '-f', self.login])
1120 assert rc == 01368 assert rc == 0
11211369
@@ -1123,13 +1371,99 @@ class TestUser:
1123 '''Add user to the specified group name'''1371 '''Add user to the specified group name'''
1124 rc, report = cmd(['usermod', '-G', group, self.login])1372 rc, report = cmd(['usermod', '-G', group, self.login])
1125 if rc != 0:1373 if rc != 0:
1126 print report1374 print(report)
1127 assert rc == 01375 assert rc == 0
11281376
1377
1378class AddUser:
1379 '''Create a temporary test user and remove it again in the dtor.'''
1380
1381 def __init__(self, login=None, home=True, encrypt_home=False, group=None, lower=False, shell=None):
1382 '''Create a new user account with a random password.
1383
1384 By default, the login name is random, too, but can be explicitly
1385 specified with 'login'. By default, a home directory is created, this
1386 can be suppressed with 'home=False'.
1387
1388 This class differs from the TestUser class in that the adduser/deluser
1389 tools are used rather than the useradd/user/del tools. The adduser
1390 program is the only commandline program that can be used to add a new
1391 user with an encrypted home directory. It is possible that the AddUser
1392 class may replace the TestUser class in the future.'''
1393
1394 self.login = None
1395
1396 if os.geteuid() != 0:
1397 raise ValueError("You must be root to run this test")
1398
1399 if login:
1400 if login_exists(login):
1401 raise ValueError('login name already exists')
1402 else:
1403 while(True):
1404 login = 't' + random_string(7, lower=True)
1405 if not login_exists(login):
1406 break
1407
1408 self.password = random_string(8, lower=lower)
1409
1410 creation = ['adduser', '--quiet']
1411
1412 if not home:
1413 creation += ['--no-create-home']
1414 elif encrypt_home:
1415 creation += ['--encrypt-home']
1416
1417 if shell:
1418 creation += ['--shell', shell]
1419
1420 creation += ['--gecos', 'Buddy %s' % (login), login]
1421
1422 child = pexpect.spawn(creation.pop(0), creation, timeout=5)
1423 assert child.expect(b'(Enter new UNIX|New) password:') == 0
1424 child.sendline(self.password)
1425 assert child.expect(b'Retype new (UNIX )?password:') == 0
1426 child.sendline(self.password)
1427
1428 child.wait()
1429 child.close()
1430 assert child.exitstatus == 0
1431 assert child.signalstatus is None
1432
1433 self.login = login
1434
1435 if group:
1436 assert self.add_to_group(group) == 0
1437
1438 p = pwd.getpwnam(self.login)
1439 self.uid = p[2]
1440 self.gid = p[3]
1441 self.gecos = p[4]
1442 self.home = p[5]
1443 self.shell = p[6]
1444
1445 def __del__(self):
1446 '''Remove the created user account.'''
1447
1448 if self.login:
1449 # sanity check the login name so we don't accidentally wipe too much
1450 rc, report = cmd(['deluser', '--remove-home', self.login])
1451 assert rc == 0
1452
1453 def add_to_group(self, group):
1454 '''Add user to the specified group name'''
1455 rc, report = cmd(['adduser', self.login, group])
1456 if rc != 0:
1457 print(report)
1458 assert rc == 0
1459
1460
1129# Timeout handler using alarm() from John P. Speno's Pythonic Avocado1461# Timeout handler using alarm() from John P. Speno's Pythonic Avocado
1130class TimeoutFunctionException(Exception):1462class TimeoutFunctionException(Exception):
1131 """Exception to raise on a timeout"""1463 """Exception to raise on a timeout"""
1132 pass1464 pass
1465
1466
1133class TimeoutFunction:1467class TimeoutFunction:
1134 def __init__(self, function, timeout):1468 def __init__(self, function, timeout):
1135 self.timeout = timeout1469 self.timeout = timeout
@@ -1148,6 +1482,7 @@ class TimeoutFunction:
1148 signal.alarm(0)1482 signal.alarm(0)
1149 return result1483 return result
11501484
1485
1151def main():1486def main():
1152 print "hi"1487 print("hi")
1153 unittest.main()1488 unittest.main()
diff --git a/debian/tests/testsuite b/debian/tests/testsuite
index 47c7a70..29cf597 100644
--- a/debian/tests/testsuite
+++ b/debian/tests/testsuite
@@ -3,5 +3,4 @@
3# Testing open-iscsi3# Testing open-iscsi
4#-------------------4#-------------------
5set -e5set -e
6python2 `dirname $0`/test-open-iscsi.py 2>&16python3 `dirname $0`/test-open-iscsi.py 2>&1
7

Subscribers

People subscribed via source and target branches