Merge ~paelzer/ubuntu/+source/open-iscsi:lp-1919461-test-python3-compat into ubuntu/+source/open-iscsi:ubuntu/hirsute-devel
- Git
- lp:~paelzer/ubuntu/+source/open-iscsi
- lp-1919461-test-python3-compat
- Merge into ubuntu/hirsute-devel
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) |
||||
Related bugs: |
|
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 |
Commit message
Description of the change
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>
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
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.
Utkarsh Gupta (utkarsh) wrote : | # |
* Changelog:
- [√] changelog entry correct version and targeted codename
- [?] changelog entries correct
I get:
W: libopeniscsiusr
...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 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]: @@@@@@@
install PASS
testsuite PASS
nested PASS
So besides the trivial comments above, this looks good! \o/
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
Christian Ehrhardt (paelzer) wrote : | # |
To ssh://git.
* [new tag] upload/
Uploading to ubuntu (via ftp to upload.ubuntu.com):
Uploading open-iscsi_
Uploading open-iscsi_
Uploading open-iscsi_
Uploading open-iscsi_
Successfully uploaded packages.
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
1 | diff --git a/debian/changelog b/debian/changelog |
2 | index 0b9f498..827c0eb 100644 |
3 | --- a/debian/changelog |
4 | +++ b/debian/changelog |
5 | @@ -1,3 +1,21 @@ |
6 | +open-iscsi (2.1.3-1ubuntu5) hirsute; urgency=medium |
7 | + |
8 | + * make d/t/testuite python3 compatible (LP: #1919461) |
9 | + - backport changes from qa-regression-testing since we diverged |
10 | + - various tests: make py3 compliant, fix formatting a bit |
11 | + - tests: convert deprecated assertEquals() to assertEqual() |
12 | + - Remove all references to python-utils |
13 | + - d/t/testlib.py: update to last state from git+ssh://git.launchpad.net/qa-regression-testing |
14 | + - d/t/{control,testsuite}: bump to use python3 |
15 | + - d/t/test-open-iscsi.py: switch shebang to python3 |
16 | + - d/t/test-open-iscsi.py: adapt to updated testlib |
17 | + * Add patches from upstream [this is from Debian upload 2.1.3-2]: |
18 | + - Fix memory leak in iscsiadm, Fix iscsiadm segfault when exiting, and |
19 | + - Fix iscsistart login issue when target is delayed. The last one should |
20 | + - fix #980085. (Closes: #980085) |
21 | + |
22 | + -- Christian Ehrhardt <christian.ehrhardt@canonical.com> Thu, 18 Mar 2021 09:44:07 +0100 |
23 | + |
24 | open-iscsi (2.1.3-1ubuntu4) hirsute; urgency=medium |
25 | |
26 | * Rebuild again so s390x pick up the right debhelper |
27 | diff --git a/debian/patches/series b/debian/patches/series |
28 | index 909a8f3..8260850 100644 |
29 | --- a/debian/patches/series |
30 | +++ b/debian/patches/series |
31 | @@ -1 +1,4 @@ |
32 | +upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch |
33 | +upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch |
34 | +upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch |
35 | lp1755858-default-iscsid_conf-to-iscsid_socket.patch |
36 | 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 |
37 | new file mode 100644 |
38 | index 0000000..dda9a00 |
39 | --- /dev/null |
40 | +++ b/debian/patches/upstream/0001-iscsiadm-Fix-memory-leak-in-iscsiadm.patch |
41 | @@ -0,0 +1,156 @@ |
42 | +From: Wenchao Hao <haowenchao@huawei.com> |
43 | +Date: Tue, 29 Dec 2020 20:30:25 +0800 |
44 | +Subject: iscsiadm: Fix memory leak in iscsiadm |
45 | + |
46 | +Memory allocated by iscsi_context_new() would not be freed if |
47 | +error occurred during parameters parser stage and goto free_ifaces |
48 | +is used to jump to resource clean. |
49 | + |
50 | +Since all resource clean is performed after verified, so change |
51 | +all goto free_ifaces to goto out where handles resource better. |
52 | + |
53 | +Signed-off-by: Wenchao Hao <haowenchao@huawei.com> |
54 | +--- |
55 | + libopeniscsiusr/context.c | 6 +++++- |
56 | + usr/iscsiadm.c | 27 +++++++++++++-------------- |
57 | + 2 files changed, 18 insertions(+), 15 deletions(-) |
58 | + |
59 | +diff --git a/libopeniscsiusr/context.c b/libopeniscsiusr/context.c |
60 | +index fe92155..c5e869f 100644 |
61 | +--- a/libopeniscsiusr/context.c |
62 | ++++ b/libopeniscsiusr/context.c |
63 | +@@ -55,8 +55,12 @@ struct iscsi_context *iscsi_context_new(void) |
64 | + |
65 | + void iscsi_context_free(struct iscsi_context *ctx) |
66 | + { |
67 | +- if (ctx != NULL) |
68 | ++ if (ctx == NULL) |
69 | ++ return; |
70 | ++ |
71 | ++ if (ctx->db) |
72 | + _idbm_free(ctx->db); |
73 | ++ |
74 | + free(ctx); |
75 | + } |
76 | + |
77 | +diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c |
78 | +index ea1643b..3987168 100644 |
79 | +--- a/usr/iscsiadm.c |
80 | ++++ b/usr/iscsiadm.c |
81 | +@@ -3627,7 +3627,7 @@ main(int argc, char **argv) |
82 | + "Priority must be greater than or " |
83 | + "equal to zero.", killiscsid); |
84 | + rc = ISCSI_ERR_INVAL; |
85 | +- goto free_ifaces; |
86 | ++ goto out; |
87 | + } |
88 | + break; |
89 | + case 't': |
90 | +@@ -3639,7 +3639,7 @@ main(int argc, char **argv) |
91 | + log_error("can not recognize operation: '%s'", |
92 | + optarg); |
93 | + rc = ISCSI_ERR_INVAL; |
94 | +- goto free_ifaces; |
95 | ++ goto out; |
96 | + } |
97 | + break; |
98 | + case 'n': |
99 | +@@ -3651,7 +3651,7 @@ main(int argc, char **argv) |
100 | + case 'H': |
101 | + host_no = parse_host_info(optarg, &rc); |
102 | + if (rc) |
103 | +- goto free_ifaces; |
104 | ++ goto out; |
105 | + break; |
106 | + case 'r': |
107 | + sid = iscsi_sysfs_get_sid_from_path(optarg); |
108 | +@@ -3659,7 +3659,7 @@ main(int argc, char **argv) |
109 | + log_error("invalid sid '%s'", |
110 | + optarg); |
111 | + rc = ISCSI_ERR_INVAL; |
112 | +- goto free_ifaces; |
113 | ++ goto out; |
114 | + } |
115 | + break; |
116 | + case 'R': |
117 | +@@ -3710,7 +3710,7 @@ main(int argc, char **argv) |
118 | + mode = str_to_mode(optarg); |
119 | + rc = verify_mode_params(argc, argv, mode); |
120 | + if (ISCSI_SUCCESS != rc) |
121 | +- goto free_ifaces; |
122 | ++ goto out; |
123 | + break; |
124 | + case 'C': |
125 | + sub_mode = str_to_submode(optarg); |
126 | +@@ -3739,11 +3739,11 @@ main(int argc, char **argv) |
127 | + printf("Invalid iface name %s. Must be from " |
128 | + "1 to %d characters.\n", |
129 | + optarg, ISCSI_MAX_IFACE_LEN - 1); |
130 | +- goto free_ifaces; |
131 | ++ goto out; |
132 | + } else if (!iface || rc) { |
133 | + printf("Could not add iface %s.", optarg); |
134 | + rc = ISCSI_ERR_INVAL; |
135 | +- goto free_ifaces; |
136 | ++ goto out; |
137 | + } |
138 | + |
139 | + list_add_tail(&iface->list, &ifaces); |
140 | +@@ -3760,7 +3760,7 @@ main(int argc, char **argv) |
141 | + log_error("Invalid index %s. %s.", |
142 | + optarg, strerror(errno)); |
143 | + rc = ISCSI_ERR_INVAL; |
144 | +- goto free_ifaces; |
145 | ++ goto out; |
146 | + } |
147 | + break; |
148 | + case 'A': |
149 | +@@ -3778,7 +3778,7 @@ main(int argc, char **argv) |
150 | + if (!param) { |
151 | + log_error("Cannot allocate memory for params."); |
152 | + rc = ISCSI_ERR_NOMEM; |
153 | +- goto free_ifaces; |
154 | ++ goto out; |
155 | + } |
156 | + list_add_tail(¶m->list, ¶ms); |
157 | + name = NULL; |
158 | +@@ -3789,12 +3789,12 @@ main(int argc, char **argv) |
159 | + if (optopt) { |
160 | + log_error("unrecognized character '%c'", optopt); |
161 | + rc = ISCSI_ERR_INVAL; |
162 | +- goto free_ifaces; |
163 | ++ goto out; |
164 | + } |
165 | + |
166 | + if (killiscsid >= 0) { |
167 | + kill_iscsid(killiscsid, timeout); |
168 | +- goto free_ifaces; |
169 | ++ goto out; |
170 | + } |
171 | + |
172 | + if (mode < 0) |
173 | +@@ -3802,14 +3802,14 @@ main(int argc, char **argv) |
174 | + |
175 | + if (mode == MODE_FW) { |
176 | + rc = exec_fw_op(NULL, NULL, info_level, do_login, op); |
177 | +- goto free_ifaces; |
178 | ++ goto out; |
179 | + } |
180 | + |
181 | + increase_max_files(); |
182 | + if (idbm_init(get_config_file)) { |
183 | + log_warning("exiting due to idbm configuration error"); |
184 | + rc = ISCSI_ERR_IDBM; |
185 | +- goto free_ifaces; |
186 | ++ goto out; |
187 | + } |
188 | + |
189 | + switch (mode) { |
190 | +@@ -4070,7 +4070,6 @@ out: |
191 | + free(rec); |
192 | + iscsi_sessions_free(ses, se_count); |
193 | + idbm_terminate(); |
194 | +-free_ifaces: |
195 | + list_for_each_entry_safe(iface, tmp, &ifaces, list) { |
196 | + list_del(&iface->list); |
197 | + free(iface); |
198 | diff --git a/debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch b/debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch |
199 | new file mode 100644 |
200 | index 0000000..183bdaa |
201 | --- /dev/null |
202 | +++ b/debian/patches/upstream/0002-Fix-iscsiadm-segfault-when-exiting.patch |
203 | @@ -0,0 +1,47 @@ |
204 | +From: Lee Duncan <lduncan@suse.com> |
205 | +Date: Tue, 26 Jan 2021 11:48:32 -0800 |
206 | +Subject: Fix iscsiadm segfault when exiting |
207 | + |
208 | +Commit b532ad67d495d added some cleanup code |
209 | +to iscsiadm right before it exits, but it |
210 | +used a list_for_each_entry() to iterate through |
211 | +a list was being deleted, when it should use |
212 | +list_for_each_entry_safe(). |
213 | + |
214 | +Fixes: b532ad67d495d |
215 | +--- |
216 | + usr/iscsiadm.c | 8 ++++---- |
217 | + 1 file changed, 4 insertions(+), 4 deletions(-) |
218 | + |
219 | +diff --git a/usr/iscsiadm.c b/usr/iscsiadm.c |
220 | +index 3987168..bb6821d 100644 |
221 | +--- a/usr/iscsiadm.c |
222 | ++++ b/usr/iscsiadm.c |
223 | +@@ -3582,11 +3582,11 @@ main(int argc, char **argv) |
224 | + struct sigaction sa_old; |
225 | + struct sigaction sa_new; |
226 | + LIST_HEAD(ifaces); |
227 | +- struct iface_rec *iface = NULL, *tmp; |
228 | ++ struct iface_rec *iface = NULL, *tmp_iface; |
229 | + struct node_rec *rec = NULL; |
230 | + uint32_t host_no = MAX_HOST_NO + 1; |
231 | + uint64_t index = ULLONG_MAX; |
232 | +- struct user_param *param; |
233 | ++ struct user_param *param, *tmp_param; |
234 | + LIST_HEAD(params); |
235 | + struct iscsi_context *ctx = NULL; |
236 | + int librc = LIBISCSI_OK; |
237 | +@@ -4070,11 +4070,11 @@ out: |
238 | + free(rec); |
239 | + iscsi_sessions_free(ses, se_count); |
240 | + idbm_terminate(); |
241 | +- list_for_each_entry_safe(iface, tmp, &ifaces, list) { |
242 | ++ list_for_each_entry_safe(iface, tmp_iface, &ifaces, list) { |
243 | + list_del(&iface->list); |
244 | + free(iface); |
245 | + } |
246 | +- list_for_each_entry(param, ¶ms, list) { |
247 | ++ list_for_each_entry_safe(param, tmp_param, ¶ms, list) { |
248 | + list_del(¶m->list); |
249 | + idbm_free_user_param(param); |
250 | + } |
251 | 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 |
252 | new file mode 100644 |
253 | index 0000000..6602f3a |
254 | --- /dev/null |
255 | +++ b/debian/patches/upstream/0003-Fix-iscsistart-login-issue-when-target-is-delayed.patch |
256 | @@ -0,0 +1,58 @@ |
257 | +From: Lee Duncan <lduncan@suse.com> |
258 | +Date: Thu, 4 Feb 2021 11:45:00 -0800 |
259 | +Subject: Fix iscsistart login issue when target is delayed. |
260 | + |
261 | +Earlier commit 9258c8eae046 changed the return value fron |
262 | +iscsid_response() from ISCSI_ERR_ISCSID_NOTCONN to |
263 | +ISCSI_ERR_SESSION_NOT_CONNECTED in the case where no iscsi response is |
264 | +received when expected. |
265 | + |
266 | +This effected the login code in iscsistart when the target is not |
267 | +completely ready at iscsi login time. This commit updates iscsistart |
268 | +to expect the new error code, but fixing this uncovered another issue, |
269 | +causing iscsistart logins to continue to fail if the target returned its |
270 | +login response too slowly. |
271 | + |
272 | +This commit ups the timeout time for iscsistart logins from 1 second per |
273 | +try to 10 seconds per try. This is perhaps excessive, and a shorter |
274 | +delay would be more appropriate, but the retry/nanosleep logic in |
275 | +iscsistart meant to retry the login in such cases seems problematic in |
276 | +this case, since retrying the 2nd time returns "session already exists", |
277 | +and most iscsistart clients aren't prepared for the command to return a |
278 | +non-zero return value. |
279 | +--- |
280 | + usr/iscsistart.c | 17 +++++++++++++++-- |
281 | + 1 file changed, 15 insertions(+), 2 deletions(-) |
282 | + |
283 | +diff --git a/usr/iscsistart.c b/usr/iscsistart.c |
284 | +index 73991b3..755489f 100644 |
285 | +--- a/usr/iscsistart.c |
286 | ++++ b/usr/iscsistart.c |
287 | +@@ -241,12 +241,25 @@ static int login_session(struct node_rec *rec) |
288 | + /* |
289 | + * Need to handle race where iscsid proc is starting up while we are |
290 | + * trying to connect. Retry with exponential backoff, start from 50 ms. |
291 | ++ * |
292 | ++ * NOTE: another race condition can occur if the system is just coming |
293 | ++ * up, where our login request will be sent, but the login response |
294 | ++ * takes a while. In such a case, if we timeout and give up, the |
295 | ++ * login response may still show up once we give up, in which case |
296 | ++ * it seems to get iscsistart into a state where it cannot try to |
297 | ++ * login again because it thinks there's already a session. So make |
298 | ++ * sure our timeout is long enough, on each try, to give the response |
299 | ++ * a chance to show up. The old value of 1 second was not enough, |
300 | ++ * so we multiply that by 10, which seems reasonable for initial |
301 | ++ * login. |
302 | + */ |
303 | + for (msec = 50; msec <= 15000; msec <<= 1) { |
304 | +- rc = iscsid_exec_req(&req, &rsp, 0, ISCSID_REQ_TIMEOUT); |
305 | ++ int tmo = ISCSID_REQ_TIMEOUT * 10; |
306 | ++ |
307 | ++ rc = iscsid_exec_req(&req, &rsp, 0, tmo); |
308 | + if (rc == 0) { |
309 | + return rc; |
310 | +- } else if (rc == ISCSI_ERR_ISCSID_NOTCONN) { |
311 | ++ } else if (rc == ISCSI_ERR_SESSION_NOT_CONNECTED) { |
312 | + ts.tv_sec = msec / 1000; |
313 | + ts.tv_nsec = (msec % 1000) * 1000000L; |
314 | + |
315 | diff --git a/debian/tests/control b/debian/tests/control |
316 | index 026cfc9..0fcfd11 100644 |
317 | --- a/debian/tests/control |
318 | +++ b/debian/tests/control |
319 | @@ -3,7 +3,7 @@ Restrictions: needs-root, isolation-machine, breaks-testbed |
320 | |
321 | Tests: testsuite |
322 | Restrictions: needs-root isolation-machine breaks-testbed |
323 | -Depends: open-iscsi, python2, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python-netifaces, distro-info, cloud-image-utils, dctrl-tools, rsync |
324 | +Depends: open-iscsi, python3, tgt, qemu-system, ubuntu-cloudimage-keyring, simplestreams, python3-netifaces, distro-info, cloud-image-utils, dctrl-tools, rsync |
325 | |
326 | Tests: nested |
327 | Restrictions: needs-root, isolation-machine, breaks-testbed, allow-stderr |
328 | diff --git a/debian/tests/test-open-iscsi.py b/debian/tests/test-open-iscsi.py |
329 | index cce02fb..1ad8b4f 100644 |
330 | --- a/debian/tests/test-open-iscsi.py |
331 | +++ b/debian/tests/test-open-iscsi.py |
332 | @@ -1,4 +1,4 @@ |
333 | -#!/usr/bin/python2 |
334 | +#!/usr/bin/python3 |
335 | # |
336 | # test-open-iscsi.py quality assurance test script for open-iscsi |
337 | # Copyright (C) 2011 Canonical Ltd. |
338 | @@ -32,16 +32,17 @@ |
339 | alter the machine. You have been warned. |
340 | |
341 | How to run in a clean VM: |
342 | - $ sudo apt-get -y install python-unit <QRT-Packages> && sudo ./test-PKG.py -v' |
343 | + $ sudo apt-get -y install <QRT-Packages> && sudo ./test-PKG.py -v' |
344 | |
345 | How to run in a clean schroot named 'lucid': |
346 | - $ schroot -c lucid -u root -- sh -c 'apt-get -y install python-unit <QRT-Packages> && ./test-PKG.py -v' |
347 | + $ schroot -c lucid -u root -- sh -c 'apt-get -y install <QRT-Packages> && ./test-PKG.py -v' |
348 | |
349 | |
350 | NOTES: |
351 | - currently only tested on Ubuntu 8.04 |
352 | ''' |
353 | |
354 | +from __future__ import print_function |
355 | |
356 | from netifaces import gateways, AF_INET |
357 | import unittest, subprocess, sys, os, glob |
358 | @@ -120,7 +121,7 @@ try: |
359 | except ImportError: |
360 | class PrivateOpenIscsiTest(object): |
361 | '''Empty class''' |
362 | - print >>sys.stdout, "Skipping private tests" |
363 | + print("Skipping private tests", file=sys.stdout) |
364 | |
365 | class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest): |
366 | '''Test my thing.''' |
367 | @@ -129,7 +130,7 @@ class OpenIscsiTest(testlib.TestlibCase, PrivateOpenIscsiTest): |
368 | '''Set up prior to each test_* function''' |
369 | self.pidfile = "/var/run/iscsid.pid" |
370 | self.exe = "/sbin/iscsid" |
371 | - self.daemon = testlib.TestDaemon(["service", "open-iscsi"]) |
372 | + self.daemon = testlib.TestDaemon("open-iscsi") |
373 | self.initiatorname_iscsi = '/etc/iscsi/initiatorname.iscsi' |
374 | self.iscsid_conf = '/etc/iscsi/iscsid.conf' |
375 | |
376 | @@ -189,7 +190,7 @@ discovery.sendtargets.auth.password_in = %s |
377 | rc, report = testlib.cmd(["/sbin/iscsi_discovery", remote_server]) |
378 | expected = 0 |
379 | result = 'Got exit code %d, expected %d\n' % (rc, expected) |
380 | - self.assertEquals(expected, rc, result + report) |
381 | + self.assertEqual(expected, rc, result + report) |
382 | for i in ['starting discovery to %s' % remote_server, |
383 | 'Testing iser-login to target %s portal %s' % (initiatorname, remote_server), |
384 | 'starting to test tcp-login to target %s portal %s' % (initiatorname, remote_server), |
385 | @@ -409,13 +410,13 @@ if __name__ == '__main__': |
386 | password_in = options.password_in |
387 | if options.initiatorname: |
388 | initiatorname = options.initiatorname |
389 | - print "Connecting to remote server with:" |
390 | - print " host = %s " % remote_server |
391 | - print ' initiatorname = %s' % initiatorname |
392 | - print ' username = %s' % username |
393 | - print ' password = %s' % password |
394 | - print ' username_in = %s' % username_in |
395 | - print ' password_in = %s' % password_in |
396 | + print("Connecting to remote server with:") |
397 | + print(" host = %s " % remote_server) |
398 | + print(' initiatorname = %s' % initiatorname) |
399 | + print(' username = %s' % username) |
400 | + print(' password = %s' % password) |
401 | + print(' username_in = %s' % username_in) |
402 | + print(' password_in = %s' % password_in) |
403 | |
404 | suite = unittest.TestSuite() |
405 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(OpenIscsiTest)) |
406 | diff --git a/debian/tests/testlib.py b/debian/tests/testlib.py |
407 | index b17c3fc..dce4252 100644 |
408 | --- a/debian/tests/testlib.py |
409 | +++ b/debian/tests/testlib.py |
410 | @@ -1,6 +1,7 @@ |
411 | +from __future__ import print_function |
412 | # |
413 | # testlib.py quality assurance test script |
414 | -# Copyright (C) 2008-2011 Canonical Ltd. |
415 | +# Copyright (C) 2008-2016 Canonical Ltd. |
416 | # |
417 | # This library is free software; you can redistribute it and/or |
418 | # modify it under the terms of the GNU Library General Public |
419 | @@ -19,31 +20,71 @@ |
420 | |
421 | '''Common classes and functions for package tests.''' |
422 | |
423 | -import string, random, crypt, subprocess, pwd, grp, signal, time, unittest, tempfile, shutil, os, os.path, re, glob |
424 | -import sys, socket, gzip |
425 | -from stat import * |
426 | -from encodings import string_escape |
427 | +import crypt |
428 | +import glob |
429 | +import grp |
430 | +import gzip |
431 | +import os |
432 | +import os.path |
433 | +import platform |
434 | +import pwd |
435 | +import random |
436 | +import re |
437 | +import shutil |
438 | +import socket |
439 | +import string |
440 | +import subprocess |
441 | +import sys |
442 | +import tempfile |
443 | +import time |
444 | +import unittest |
445 | + |
446 | +from stat import ST_SIZE |
447 | + |
448 | +# Don't make python-pexpect mandatory |
449 | +try: |
450 | + import pexpect |
451 | +except ImportError: |
452 | + pass |
453 | |
454 | import warnings |
455 | warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning) |
456 | try: |
457 | import apt_pkg |
458 | - apt_pkg.InitSystem(); |
459 | + # cope with apt_pkg api changes. |
460 | + if 'init_system' in dir(apt_pkg): |
461 | + apt_pkg.init_system() |
462 | + else: |
463 | + apt_pkg.InitSystem() |
464 | except: |
465 | # On non-Debian system, fall back to simple comparison without debianisms |
466 | class apt_pkg(object): |
467 | - def VersionCompare(one, two): |
468 | + @staticmethod |
469 | + def version_compare(one, two): |
470 | list_one = one.split('.') |
471 | list_two = two.split('.') |
472 | - while len(list_one)>0 and len(list_two)>0: |
473 | - if list_one[0] > list_two[0]: |
474 | - return 1 |
475 | - if list_one[0] < list_two[0]: |
476 | - return -1 |
477 | + while len(list_one) > 0 and len(list_two) > 0: |
478 | + try: |
479 | + if int(list_one[0]) > int(list_two[0]): |
480 | + return 1 |
481 | + if int(list_one[0]) < int(list_two[0]): |
482 | + return -1 |
483 | + except: |
484 | + # ugh, non-numerics in the version, fall back to |
485 | + # string comparison, which will be wrong for e.g. |
486 | + # 3.2 vs 3.16rc1 |
487 | + if list_one[0] > list_two[0]: |
488 | + return 1 |
489 | + if list_one[0] < list_two[0]: |
490 | + return -1 |
491 | list_one.pop(0) |
492 | list_two.pop(0) |
493 | return 0 |
494 | |
495 | + @staticmethod |
496 | + def VersionCompare(one, two): |
497 | + return apt_pkg.version_compare(one, two) |
498 | + |
499 | bogus_nxdomain = "208.69.32.132" |
500 | |
501 | # http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html |
502 | @@ -55,17 +96,21 @@ def subprocess_setup(): |
503 | # non-Python subprocesses expect. |
504 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) |
505 | |
506 | + |
507 | class TimedOutException(Exception): |
508 | - def __init__(self, value = "Timed Out"): |
509 | + def __init__(self, value="Timed Out"): |
510 | self.value = value |
511 | + |
512 | def __str__(self): |
513 | return repr(self.value) |
514 | |
515 | + |
516 | def _restore_backup(path): |
517 | pathbackup = path + '.autotest' |
518 | if os.path.exists(pathbackup): |
519 | shutil.move(pathbackup, path) |
520 | |
521 | + |
522 | def _save_backup(path): |
523 | pathbackup = path + '.autotest' |
524 | if os.path.exists(path) and not os.path.exists(pathbackup): |
525 | @@ -75,53 +120,64 @@ def _save_backup(path): |
526 | a = os.stat(path) |
527 | os.chown(pathbackup, a[4], a[5]) |
528 | |
529 | + |
530 | def config_copydir(path): |
531 | if os.path.exists(path) and not os.path.isdir(path): |
532 | - raise OSError, "'%s' is not a directory" % (path) |
533 | + raise OSError("'%s' is not a directory" % (path)) |
534 | _restore_backup(path) |
535 | |
536 | pathbackup = path + '.autotest' |
537 | if os.path.exists(path): |
538 | shutil.copytree(path, pathbackup, symlinks=True) |
539 | |
540 | -def config_replace(path,contents,append=False): |
541 | + |
542 | +def config_replace(path, contents, append=False): |
543 | '''Replace (or append) to a config file''' |
544 | _restore_backup(path) |
545 | if os.path.exists(path): |
546 | _save_backup(path) |
547 | if append: |
548 | - contents = file(path).read() + contents |
549 | - open(path, 'w').write(contents) |
550 | + with open(path) as fh: |
551 | + contents = fh.read() + contents |
552 | + with open(path, 'w') as fh: |
553 | + fh.write(contents) |
554 | + |
555 | |
556 | def config_comment(path, field): |
557 | _save_backup(path) |
558 | contents = "" |
559 | - for line in file(path): |
560 | - if re.search("^\s*%s\s*=" % (field), line): |
561 | - line = "#" + line |
562 | - contents += line |
563 | + with open(path) as fh: |
564 | + for line in fh: |
565 | + if re.search("^\s*%s\s*=" % (field), line): |
566 | + line = "#" + line |
567 | + contents += line |
568 | + |
569 | + with open(path + '.new', 'w') as new_fh: |
570 | + new_fh.write(contents) |
571 | + os.rename(path + '.new', path) |
572 | |
573 | - open(path+'.new', 'w').write(contents) |
574 | - os.rename(path+'.new', path) |
575 | |
576 | def config_set(path, field, value, spaces=True): |
577 | _save_backup(path) |
578 | contents = "" |
579 | - if spaces==True: |
580 | + if spaces: |
581 | setting = '%s = %s\n' % (field, value) |
582 | else: |
583 | setting = '%s=%s\n' % (field, value) |
584 | found = False |
585 | - for line in file(path): |
586 | - if re.search("^\s*%s\s*=" % (field), line): |
587 | - found = True |
588 | - line = setting |
589 | - contents += line |
590 | + with open(path) as fh: |
591 | + for line in fh: |
592 | + if re.search("^\s*%s\s*=" % (field), line): |
593 | + found = True |
594 | + line = setting |
595 | + contents += line |
596 | if not found: |
597 | contents += setting |
598 | |
599 | - open(path+'.new', 'w').write(contents) |
600 | - os.rename(path+'.new', path) |
601 | + with open(path + '.new', 'w') as new_config: |
602 | + new_config.write(contents) |
603 | + os.rename(path + '.new', path) |
604 | + |
605 | |
606 | def config_patch(path, patch, depth=1): |
607 | '''Patch a config file''' |
608 | @@ -129,15 +185,17 @@ def config_patch(path, patch, depth=1): |
609 | _save_backup(path) |
610 | |
611 | handle, name = mkstemp_fill(patch) |
612 | - rc = subprocess.call(['/usr/bin/patch', '-p%s' %(depth), path], stdin=handle, stdout=subprocess.PIPE) |
613 | + rc = subprocess.call(['/usr/bin/patch', '-p%s' % depth, path], stdin=handle, stdout=subprocess.PIPE) |
614 | os.unlink(name) |
615 | if rc != 0: |
616 | raise Exception("Patch failed") |
617 | |
618 | + |
619 | def config_restore(path): |
620 | '''Rename a replaced config file back to its initial state''' |
621 | _restore_backup(path) |
622 | |
623 | + |
624 | def timeout(secs, f, *args): |
625 | def handler(signum, frame): |
626 | raise TimedOutException() |
627 | @@ -153,51 +211,57 @@ def timeout(secs, f, *args): |
628 | |
629 | return result |
630 | |
631 | + |
632 | def require_nonroot(): |
633 | if os.geteuid() == 0: |
634 | - print >>sys.stderr, "This series of tests should be run as a regular user with sudo access, not as root." |
635 | + print("This series of tests should be run as a regular user with sudo access, not as root.", file=sys.stderr) |
636 | sys.exit(1) |
637 | |
638 | + |
639 | def require_root(): |
640 | if os.geteuid() != 0: |
641 | - print >>sys.stderr, "This series of tests should be run with root privileges (e.g. via sudo)." |
642 | + print("This series of tests should be run with root privileges (e.g. via sudo).", file=sys.stderr) |
643 | sys.exit(1) |
644 | |
645 | + |
646 | def require_sudo(): |
647 | - if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) == None: |
648 | - print >>sys.stderr, "This series of tests must be run under sudo." |
649 | + if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) is None: |
650 | + print("This series of tests must be run under sudo.", file=sys.stderr) |
651 | sys.exit(1) |
652 | if os.environ['SUDO_USER'] == 'root': |
653 | - print >>sys.stderr, 'Please run this test using sudo from a regular user. (You ran sudo from root.)' |
654 | + print('Please run this test using sudo from a regular user. (You ran sudo from root.)', file=sys.stderr) |
655 | sys.exit(1) |
656 | |
657 | -def random_string(length,lower=False): |
658 | + |
659 | +def random_string(length, lower=False): |
660 | '''Return a random string, consisting of ASCII letters, with given |
661 | length.''' |
662 | |
663 | s = '' |
664 | - selection = string.letters |
665 | + selection = string.ascii_letters |
666 | if lower: |
667 | - selection = string.lowercase |
668 | - maxind = len(selection)-1 |
669 | + selection = string.ascii_lowercase |
670 | + maxind = len(selection) - 1 |
671 | for l in range(length): |
672 | s += selection[random.randint(0, maxind)] |
673 | return s |
674 | |
675 | -def mkstemp_fill(contents,suffix='',prefix='testlib-',dir=None): |
676 | + |
677 | +def mkstemp_fill(contents, suffix='', prefix='testlib-', dir=None): |
678 | '''As tempfile.mkstemp does, return a (file, name) pair, but with |
679 | prefilled contents.''' |
680 | |
681 | - handle, name = tempfile.mkstemp(suffix=suffix,prefix=prefix,dir=dir) |
682 | + handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) |
683 | os.close(handle) |
684 | - handle = file(name,"w+") |
685 | + handle = open(name, "w+") |
686 | handle.write(contents) |
687 | handle.flush() |
688 | handle.seek(0) |
689 | |
690 | return handle, name |
691 | |
692 | -def create_fill(path, contents, mode=0644): |
693 | + |
694 | +def create_fill(path, contents, mode=0o644): |
695 | '''Safely create a page''' |
696 | # make the temp file in the same dir as the destination file so we |
697 | # don't get invalid cross-device link errors when we rename |
698 | @@ -206,6 +270,7 @@ def create_fill(path, contents, mode=0644): |
699 | os.rename(name, path) |
700 | os.chmod(path, mode) |
701 | |
702 | + |
703 | def login_exists(login): |
704 | '''Checks whether the given login exists on the system.''' |
705 | |
706 | @@ -215,6 +280,7 @@ def login_exists(login): |
707 | except KeyError: |
708 | return False |
709 | |
710 | + |
711 | def group_exists(group): |
712 | '''Checks whether the given login exists on the system.''' |
713 | |
714 | @@ -224,6 +290,13 @@ def group_exists(group): |
715 | except KeyError: |
716 | return False |
717 | |
718 | + |
719 | +def is_empty_file(path): |
720 | + '''test if file is empty, returns True if so''' |
721 | + with open(path) as fh: |
722 | + return (len(fh.read()) == 0) |
723 | + |
724 | + |
725 | def recursive_rm(dirPath, contents_only=False): |
726 | '''recursively remove directory''' |
727 | names = os.listdir(dirPath) |
728 | @@ -233,9 +306,10 @@ def recursive_rm(dirPath, contents_only=False): |
729 | os.unlink(path) |
730 | else: |
731 | recursive_rm(path) |
732 | - if contents_only == False: |
733 | + if not contents_only: |
734 | os.rmdir(dirPath) |
735 | |
736 | + |
737 | def check_pidfile(exe, pidfile): |
738 | '''Checks if pid in pidfile is running''' |
739 | if not os.path.exists(pidfile): |
740 | @@ -243,14 +317,14 @@ def check_pidfile(exe, pidfile): |
741 | |
742 | # get the pid |
743 | try: |
744 | - fd = open(pidfile, 'r') |
745 | - pid = fd.readline().rstrip('\n') |
746 | - fd.close() |
747 | + with open(pidfile, 'r') as fd: |
748 | + pid = fd.readline().rstrip('\n') |
749 | except: |
750 | return False |
751 | |
752 | return check_pid(exe, pid) |
753 | |
754 | + |
755 | def check_pid(exe, pid): |
756 | '''Checks if pid is running''' |
757 | cmdline = "/proc/%s/cmdline" % (str(pid)) |
758 | @@ -259,9 +333,8 @@ def check_pid(exe, pid): |
759 | |
760 | # get the command line |
761 | try: |
762 | - fd = open(cmdline, 'r') |
763 | - tmp = fd.readline().split('\0') |
764 | - fd.close() |
765 | + with open(cmdline, 'r') as fd: |
766 | + tmp = fd.readline().split('\0') |
767 | except: |
768 | return False |
769 | |
770 | @@ -274,6 +347,7 @@ def check_pid(exe, pid): |
771 | |
772 | return False |
773 | |
774 | + |
775 | def check_port(port, proto, ver=4): |
776 | '''Check if something is listening on the specified port. |
777 | WARNING: for some reason this does not work with a bind mounted /proc |
778 | @@ -296,12 +370,29 @@ def check_port(port, proto, ver=4): |
779 | return True |
780 | return False |
781 | |
782 | + |
783 | def get_arch(): |
784 | '''Get the current architecture''' |
785 | rc, report = cmd(['uname', '-m']) |
786 | assert (rc == 0) |
787 | return report.strip() |
788 | |
789 | + |
790 | +def get_multiarch_tuple(): |
791 | + '''Get the current debian multiarch tuple''' |
792 | + |
793 | + # XXX dpkg-architecture on Ubuntu 12.04 requires -q be adjacent to |
794 | + # the variable name |
795 | + rc, report = cmd(['dpkg-architecture', '-qDEB_HOST_MULTIARCH']) |
796 | + assert (rc == 0) |
797 | + return report.strip() |
798 | + |
799 | + |
800 | +def get_bits(): |
801 | + '''return the default architecture number of bits (32 or 64, usually)''' |
802 | + return platform.architecture()[0][0:2] |
803 | + |
804 | + |
805 | def get_memory(): |
806 | '''Gets total ram and swap''' |
807 | meminfo = "/proc/meminfo" |
808 | @@ -322,7 +413,8 @@ def get_memory(): |
809 | except: |
810 | return (False, False) |
811 | |
812 | - return (memtotal,swaptotal) |
813 | + return (memtotal, swaptotal) |
814 | + |
815 | |
816 | def is_running_in_vm(): |
817 | '''Check if running under a VM''' |
818 | @@ -333,6 +425,7 @@ def is_running_in_vm(): |
819 | return True |
820 | return False |
821 | |
822 | + |
823 | def ubuntu_release(): |
824 | '''Get the Ubuntu release''' |
825 | f = "/etc/lsb-release" |
826 | @@ -341,16 +434,11 @@ def ubuntu_release(): |
827 | except: |
828 | return "UNKNOWN" |
829 | |
830 | - if size > 1024*1024: |
831 | - raise IOError, 'Could not open "%s" (too big)' % f |
832 | - |
833 | - try: |
834 | - fh = open("/etc/lsb-release", 'r') |
835 | - except: |
836 | - raise |
837 | + if size > 1024 * 1024: |
838 | + raise IOError('Could not open "%s" (too big)' % f) |
839 | |
840 | - lines = fh.readlines() |
841 | - fh.close() |
842 | + with open("/etc/lsb-release", 'r') as fh: |
843 | + lines = fh.readlines() |
844 | |
845 | pat = re.compile(r'DISTRIB_CODENAME') |
846 | for line in lines: |
847 | @@ -359,40 +447,53 @@ def ubuntu_release(): |
848 | |
849 | return "UNKNOWN" |
850 | |
851 | -def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None): |
852 | + |
853 | +def cmd(command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stdin=None, timeout=None, env=None, shell=False, text=True): |
854 | '''Try to execute given command (array) and return its stdout, or return |
855 | a textual error if it failed.''' |
856 | |
857 | try: |
858 | - sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup) |
859 | - except OSError, e: |
860 | + sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup, env=env, universal_newlines=text, shell=shell) |
861 | + if sys.version_info[0] >= 3 and sys.version_info[1] > 3: |
862 | + out, outerr = sp.communicate(input, timeout=timeout) |
863 | + else: |
864 | + out, outerr = sp.communicate(input) |
865 | + except OSError as e: |
866 | return [127, str(e)] |
867 | |
868 | - out, outerr = sp.communicate(input) |
869 | # Handle redirection of stdout |
870 | - if out == None: |
871 | + if out is None: |
872 | out = '' |
873 | # Handle redirection of stderr |
874 | - if outerr == None: |
875 | + if outerr is None: |
876 | outerr = '' |
877 | - return [sp.returncode,out+outerr] |
878 | + if text == False: |
879 | + out = str(out) |
880 | + return [sp.returncode, out + outerr] |
881 | |
882 | -def cmd_pipe(command1, command2, input = None, stderr = subprocess.STDOUT, stdin = None): |
883 | + |
884 | +def cmd_pipe(command1, command2, input=None, stderr=subprocess.STDOUT, stdin=None): |
885 | '''Try to pipe command1 into command2.''' |
886 | try: |
887 | sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True) |
888 | - sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True) |
889 | - except OSError, e: |
890 | + sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True, universal_newlines=True) |
891 | + except OSError as e: |
892 | return [127, str(e)] |
893 | |
894 | out = sp2.communicate(input)[0] |
895 | - return [sp2.returncode,out] |
896 | + # ensure we don't leak open files and that sp1 is stopped |
897 | + sp1.stdout.close() |
898 | + sp1.wait() |
899 | + return [sp2.returncode, out] |
900 | + |
901 | |
902 | def cwd_has_enough_space(cdir, total_bytes): |
903 | '''Determine if the partition of the current working directory has 'bytes' |
904 | free.''' |
905 | rc, df_output = cmd(['df']) |
906 | - result = 'Got exit code %d, expected %d\n' % (rc, 0) |
907 | + if rc != 0: |
908 | + result = 'df failed, got exit code %d, expected %d\n' % (rc, 0) |
909 | + raise OSError(result) |
910 | if rc != 0: |
911 | return False |
912 | |
913 | @@ -407,18 +508,13 @@ def cwd_has_enough_space(cdir, total_bytes): |
914 | |
915 | cdir = os.getcwd() |
916 | while cdir != '/': |
917 | - if not mounts.has_key(cdir): |
918 | + if cdir not in mounts: |
919 | cdir = os.path.dirname(cdir) |
920 | continue |
921 | - if kb < mounts[cdir]: |
922 | - return True |
923 | - else: |
924 | - return False |
925 | + return kb < mounts[cdir] |
926 | |
927 | - if kb < mounts['/']: |
928 | - return True |
929 | + return kb < mounts['/'] |
930 | |
931 | - return False |
932 | |
933 | def get_md5(filename): |
934 | '''Gets the md5sum of the file specified''' |
935 | @@ -429,6 +525,7 @@ def get_md5(filename): |
936 | |
937 | return report.split(' ')[0] |
938 | |
939 | + |
940 | def dpkg_compare_installed_version(pkg, check, version): |
941 | '''Gets the version for the installed package, and compares it to the |
942 | specified version. |
943 | @@ -449,6 +546,35 @@ def dpkg_compare_installed_version(pkg, check, version): |
944 | return True |
945 | return False |
946 | |
947 | + |
948 | +def _run_apt_command(pkg_list, apt_cmd='install'): |
949 | + env = os.environ.copy() |
950 | + env['DEBIAN_FRONTEND'] = 'noninteractive' |
951 | + # debugging version, but on precise doesn't actually run dpkg |
952 | + # command = ['apt-get', '-y', '--force-yes', '-o', 'Dpkg::Options::=--force-confold', '-o', 'Debug::pkgDPkgPM=true', apt_cmd] |
953 | + command = ['apt-get', '-y', '--force-yes', '-o', 'Dpkg::Options::=--force-confold', apt_cmd] |
954 | + command.extend(pkg_list) |
955 | + rc, report = cmd(command) |
956 | + return rc, report |
957 | + |
958 | + |
959 | +# note: for the following install_* functions, you probably want the |
960 | +# versions in the TestlibCase class (see below) |
961 | +def install_builddeps(src_pkg): |
962 | + rc, report = _run_apt_command([src_pkg], 'build-dep') |
963 | + assert(rc == 0) |
964 | + |
965 | + |
966 | +def install_package(package): |
967 | + rc, report = _run_apt_command([package], 'install') |
968 | + assert(rc == 0) |
969 | + |
970 | + |
971 | +def install_packages(pkg_list): |
972 | + rc, report = _run_apt_command(pkg_list, 'install') |
973 | + assert(rc == 0) |
974 | + |
975 | + |
976 | def prepare_source(source, builder, cached_src, build_src, patch_system): |
977 | '''Download and unpack source package, installing necessary build depends, |
978 | adjusting the permissions for the 'builder' user, and returning the |
979 | @@ -468,7 +594,7 @@ def prepare_source(source, builder, cached_src, build_src, patch_system): |
980 | self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp') |
981 | self.builder = testlib.TestUser() |
982 | testlib.cmd(['chgrp', self.builder.login, self.tmpdir]) |
983 | - os.chmod(self.tmpdir, 0775) |
984 | + os.chmod(self.tmpdir, 0o775) |
985 | |
986 | def tearDown(self): |
987 | ... |
988 | @@ -488,21 +614,21 @@ def prepare_source(source, builder, cached_src, build_src, patch_system): |
989 | os.chdir(build_dir) |
990 | |
991 | # Example for typical build, adjust as necessary |
992 | - print "" |
993 | - print " make clean" |
994 | + print("") |
995 | + print(" make clean") |
996 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean']) |
997 | |
998 | - print " configure" |
999 | + print(" configure") |
1000 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug']) |
1001 | |
1002 | - print " make (will take a while)" |
1003 | + print(" make (will take a while)") |
1004 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make']) |
1005 | |
1006 | - print " make check (will take a while)", |
1007 | + print(" make check (will take a while)",) |
1008 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check']) |
1009 | expected = 0 |
1010 | result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1011 | - self.assertEquals(expected, rc, result + report) |
1012 | + self.assertEqual(expected, rc, result + report) |
1013 | |
1014 | def test_suite_cleanup(self): |
1015 | ... |
1016 | @@ -526,20 +652,18 @@ def prepare_source(source, builder, cached_src, build_src, patch_system): |
1017 | os.chdir(build_src) |
1018 | else: |
1019 | # Only install the build dependencies on the initial setup |
1020 | - rc, report = cmd(['apt-get','-y','--force-yes','build-dep',source]) |
1021 | - assert (rc == 0) |
1022 | - |
1023 | + install_builddeps(source) |
1024 | os.makedirs(build_src) |
1025 | os.chdir(build_src) |
1026 | |
1027 | # These are always needed |
1028 | pkgs = ['build-essential', 'dpkg-dev', 'fakeroot'] |
1029 | - rc, report = cmd(['apt-get','-y','--force-yes','install'] + pkgs) |
1030 | - assert (rc == 0) |
1031 | + install_packages(pkgs) |
1032 | |
1033 | - rc, report = cmd(['apt-get','source',source]) |
1034 | + rc, report = cmd(['apt-get', 'source', source]) |
1035 | assert (rc == 0) |
1036 | shutil.copytree(build_src, cached_src) |
1037 | + print("(unpacked %s)" % os.path.basename(glob.glob('%s_*.dsc' % source)[0]), end=' ') |
1038 | |
1039 | unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0]) |
1040 | |
1041 | @@ -547,9 +671,9 @@ def prepare_source(source, builder, cached_src, build_src, patch_system): |
1042 | # sources. |
1043 | os.chdir(unpacked_dir) |
1044 | assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None]) |
1045 | - if patch_system != None and patch_system != "quiltv3": |
1046 | + if patch_system is not None and patch_system != "quiltv3": |
1047 | if patch_system == "quilt": |
1048 | - os.environ.setdefault('QUILT_PATCHES','debian/patches') |
1049 | + os.environ.setdefault('QUILT_PATCHES', 'debian/patches') |
1050 | rc, report = cmd(['quilt', 'push', '-a']) |
1051 | assert (rc == 0) |
1052 | elif patch_system == "cdbs": |
1053 | @@ -564,6 +688,21 @@ def prepare_source(source, builder, cached_src, build_src, patch_system): |
1054 | |
1055 | return unpacked_dir |
1056 | |
1057 | + |
1058 | +def get_changelog_version(source_dir): |
1059 | + '''Extract a package version from a changelog''' |
1060 | + package_version = "" |
1061 | + |
1062 | + changelog_file = os.path.join(source_dir, "debian/changelog") |
1063 | + |
1064 | + if os.path.exists(changelog_file): |
1065 | + changelog = open(changelog_file, 'r') |
1066 | + header = changelog.readline().split() |
1067 | + package_version = header[1].strip('()') |
1068 | + |
1069 | + return package_version |
1070 | + |
1071 | + |
1072 | def _aa_status(): |
1073 | '''Get aa-status output''' |
1074 | exe = "/usr/sbin/aa-status" |
1075 | @@ -572,6 +711,7 @@ def _aa_status(): |
1076 | return cmd([exe]) |
1077 | return cmd(['sudo', exe]) |
1078 | |
1079 | + |
1080 | def is_apparmor_loaded(path): |
1081 | '''Check if profile is loaded''' |
1082 | rc, report = _aa_status() |
1083 | @@ -583,6 +723,7 @@ def is_apparmor_loaded(path): |
1084 | return True |
1085 | return False |
1086 | |
1087 | + |
1088 | def is_apparmor_confined(path): |
1089 | '''Check if application is confined''' |
1090 | rc, report = _aa_status() |
1091 | @@ -594,6 +735,7 @@ def is_apparmor_confined(path): |
1092 | return True |
1093 | return False |
1094 | |
1095 | + |
1096 | def check_apparmor(path, first_ubuntu_release, is_running=True): |
1097 | '''Check if path is loaded and confined for everything higher than the |
1098 | first Ubuntu release specified. |
1099 | @@ -605,7 +747,7 @@ def check_apparmor(path, first_ubuntu_release, is_running=True): |
1100 | |
1101 | expected = 0 |
1102 | result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1103 | - self.assertEquals(expected, rc, result + report) |
1104 | + self.assertEqual(expected, rc, result + report) |
1105 | ''' |
1106 | global manager |
1107 | rc = -1 |
1108 | @@ -629,13 +771,14 @@ def check_apparmor(path, first_ubuntu_release, is_running=True): |
1109 | |
1110 | return (rc, msg) |
1111 | |
1112 | + |
1113 | def get_gcc_version(gcc, full=True): |
1114 | gcc_version = 'none' |
1115 | if not gcc.startswith('/'): |
1116 | gcc = '/usr/bin/%s' % (gcc) |
1117 | if os.path.exists(gcc): |
1118 | gcc_version = 'unknown' |
1119 | - lines = cmd([gcc,'-v'])[1].strip().splitlines() |
1120 | + lines = cmd([gcc, '-v'])[1].strip().splitlines() |
1121 | version_lines = [x for x in lines if x.startswith('gcc version')] |
1122 | if len(version_lines) == 1: |
1123 | gcc_version = " ".join(version_lines[0].split()[2:]) |
1124 | @@ -643,6 +786,7 @@ def get_gcc_version(gcc, full=True): |
1125 | return gcc_version.split()[0] |
1126 | return gcc_version |
1127 | |
1128 | + |
1129 | def is_kdeinit_running(): |
1130 | '''Test if kdeinit is running''' |
1131 | # applications that use kdeinit will spawn it if it isn't running in the |
1132 | @@ -650,106 +794,154 @@ def is_kdeinit_running(): |
1133 | # check for it. |
1134 | rc, report = cmd(['ps', 'x']) |
1135 | if 'kdeinit4 Running' not in report: |
1136 | - print >>sys.stderr, ("kdeinit not running (you may start/stop any KDE application then run this script again)") |
1137 | + print("kdeinit not running (you may start/stop any KDE application then run this script again)", file=sys.stderr) |
1138 | return False |
1139 | return True |
1140 | |
1141 | + |
1142 | def get_pkgconfig_flags(libs=[]): |
1143 | '''Find pkg-config flags for libraries''' |
1144 | assert (len(libs) > 0) |
1145 | rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs) |
1146 | expected = 0 |
1147 | if rc != expected: |
1148 | - print >>sys.stderr, 'Got exit code %d, expected %d\n' % (rc, expected) |
1149 | + print('Got exit code %d, expected %d\n' % (rc, expected), file=sys.stderr) |
1150 | assert(rc == expected) |
1151 | return pkg_config.split() |
1152 | |
1153 | + |
1154 | +def cap_to_name(cap_num): |
1155 | + '''given an integer, return the capability name''' |
1156 | + rc, output = cmd(['capsh', '--decode=%x' % cap_num]) |
1157 | + expected = 0 |
1158 | + if rc != expected: |
1159 | + print('capsh: got exit code %d, expected %d\n' % (rc, expected), file=sys.stderr) |
1160 | + cap_name = output.strip().split('=')[1] |
1161 | + return cap_name |
1162 | + |
1163 | + |
1164 | +def enumerate_capabilities(): |
1165 | + i = 0 |
1166 | + cap_list = [] |
1167 | + done = False |
1168 | + while not done: |
1169 | + cap_name = cap_to_name(pow(2, i)) |
1170 | + if cap_name == str(i): |
1171 | + done = True |
1172 | + else: |
1173 | + cap_list.append(cap_name) |
1174 | + i += 1 |
1175 | + if i > 64: |
1176 | + done = True |
1177 | + return cap_list |
1178 | + |
1179 | + |
1180 | +def _run_snap_install(snapname, track="latest/stable", classic=False): |
1181 | + # crude, but effective |
1182 | + if os.path.exists(os.path.join("/snap", snapname)): |
1183 | + return 0, "" |
1184 | + |
1185 | + command = ["snap", "install", "--channel=%s" % track, snapname] |
1186 | + if classic: |
1187 | + command.append("--classic") |
1188 | + rc, report = cmd(command) |
1189 | + return rc, report |
1190 | + |
1191 | + |
1192 | +def _run_snap_remove(snapname): |
1193 | + # crude, but effective |
1194 | + if not os.path.exists(os.path.join("/snap", snapname)): |
1195 | + return 0, "" |
1196 | + |
1197 | + command = ["snap", "remove", "--purge", snapname] |
1198 | + rc, report = cmd(command) |
1199 | + return rc, report |
1200 | + |
1201 | + |
1202 | +def install_snap(self, snapname, track="latest/stable", classic=False): |
1203 | + rc, report = _run_snap_install(snapname, track, classic) |
1204 | + self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" % |
1205 | + (snapname, report)) |
1206 | + |
1207 | + |
1208 | +def remove_snap(self, snapname): |
1209 | + rc, report = _run_snap_remove(snapname) |
1210 | + self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" % |
1211 | + (snapname, report)) |
1212 | + |
1213 | + |
1214 | class TestDaemon: |
1215 | '''Helper class to manage daemons consistently''' |
1216 | - def __init__(self, init): |
1217 | + def __init__(self, service_name): |
1218 | '''Setup daemon attributes''' |
1219 | - self.initscript = init |
1220 | - |
1221 | - def _build_command(self, suffix): |
1222 | - command = [] |
1223 | - if isinstance(self.initscript, list): |
1224 | - command.extend(self.initscript) |
1225 | + if os.path.isfile('/usr/sbin/service') or os.path.islink('/usr/sbin/service'): |
1226 | + self.test_daemon_commands = ['service', service_name] |
1227 | else: |
1228 | - command.append(self.initscript) |
1229 | - command.append(suffix) |
1230 | - return command |
1231 | + self.test_daemon_commands = ['/etc/init.d/' + service_name] |
1232 | |
1233 | - def start(self): |
1234 | - '''Start daemon''' |
1235 | - rc, report = cmd(self._build_command('start')) |
1236 | + def _run_command(self, action, seconds_to_wait=2): |
1237 | + command = list(self.test_daemon_commands) |
1238 | + command.append(action) |
1239 | + rc, report = cmd(command) |
1240 | expected = 0 |
1241 | result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1242 | - time.sleep(2) |
1243 | + time.sleep(seconds_to_wait) |
1244 | if expected != rc: |
1245 | - return (False, result + report) |
1246 | + return False, result + report |
1247 | |
1248 | if "fail" in report: |
1249 | - return (False, "Found 'fail' in report\n" + report) |
1250 | + return False, "Found 'fail' in report\n" + report |
1251 | |
1252 | - return (True, "") |
1253 | + return True, "" |
1254 | + |
1255 | + def start(self): |
1256 | + '''Start daemon''' |
1257 | + return self._run_command('start') |
1258 | |
1259 | def stop(self): |
1260 | '''Stop daemon''' |
1261 | - rc, report = cmd(self._build_command('stop')) |
1262 | - expected = 0 |
1263 | - result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1264 | - if expected != rc: |
1265 | - return (False, result + report) |
1266 | - |
1267 | - if "fail" in report: |
1268 | - return (False, "Found 'fail' in report\n" + report) |
1269 | - |
1270 | - return (True, "") |
1271 | + return self._run_command('stop') |
1272 | |
1273 | def reload(self): |
1274 | '''Reload daemon''' |
1275 | - rc, report = cmd(self._build_command('force-reload')) |
1276 | - expected = 0 |
1277 | - result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1278 | - if expected != rc: |
1279 | - return (False, result + report) |
1280 | + return self._run_command('force-reload') |
1281 | |
1282 | - if "fail" in report: |
1283 | - return (False, "Found 'fail' in report\n" + report) |
1284 | + def restart(self): |
1285 | + if self.test_daemon_commands[0] == 'service': |
1286 | + return self._run_command('restart') |
1287 | + else: |
1288 | + '''Restart daemon''' |
1289 | + (res, str) = self.stop() |
1290 | + if not res: |
1291 | + return res, str |
1292 | |
1293 | - return (True, "") |
1294 | + (res, str) = self.start() |
1295 | + if not res: |
1296 | + return res, str |
1297 | |
1298 | - def restart(self): |
1299 | - '''Restart daemon''' |
1300 | - (res, str) = self.stop() |
1301 | - if not res: |
1302 | - return (res, str) |
1303 | + return True, "" |
1304 | + |
1305 | + def force_restart(self): |
1306 | + '''Restart daemon even if already stopped''' |
1307 | + self.stop() |
1308 | |
1309 | (res, str) = self.start() |
1310 | if not res: |
1311 | - return (res, str) |
1312 | + return res, str |
1313 | |
1314 | - return (True, "") |
1315 | + return True, "" |
1316 | |
1317 | def status(self): |
1318 | '''Check daemon status''' |
1319 | - rc, report = cmd(self._build_command('status')) |
1320 | - expected = 0 |
1321 | - result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1322 | - if expected != rc: |
1323 | - return (False, result + report) |
1324 | - |
1325 | - if "fail" in report: |
1326 | - return (False, "Found 'fail' in report\n" + report) |
1327 | + return self._run_command('status') |
1328 | |
1329 | - return (True, "") |
1330 | |
1331 | class TestlibManager(object): |
1332 | '''Singleton class used to set up per-test-run information''' |
1333 | def __init__(self): |
1334 | # Set glibc aborts to dump to stderr instead of the tty so test output |
1335 | # is more sane. |
1336 | - os.environ.setdefault('LIBC_FATAL_STDERR_','1') |
1337 | + os.environ.setdefault('LIBC_FATAL_STDERR_', '1') |
1338 | |
1339 | # check verbosity |
1340 | self.verbosity = False |
1341 | @@ -759,17 +951,17 @@ class TestlibManager(object): |
1342 | # Load LSB release file |
1343 | self.lsb_release = dict() |
1344 | if not os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'): |
1345 | - raise OSError, "Please install 'lsb-release'" |
1346 | - for line in subprocess.Popen(['lsb_release','-a'],stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()[0].splitlines(): |
1347 | - field, value = line.split(':',1) |
1348 | - value=value.strip() |
1349 | - field=field.strip() |
1350 | + raise OSError("Please install 'lsb-release'") |
1351 | + for line in subprocess.Popen(['lsb_release', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True).communicate()[0].splitlines(): |
1352 | + field, value = line.split(':', 1) |
1353 | + value = value.strip() |
1354 | + field = field.strip() |
1355 | # Convert numerics |
1356 | try: |
1357 | value = float(value) |
1358 | except: |
1359 | pass |
1360 | - self.lsb_release.setdefault(field,value) |
1361 | + self.lsb_release.setdefault(field, value) |
1362 | |
1363 | # FIXME: hack OEM releases into known-Ubuntu versions |
1364 | if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)": |
1365 | @@ -777,20 +969,20 @@ class TestlibManager(object): |
1366 | self.lsb_release['Distributor ID'] = "Ubuntu" |
1367 | self.lsb_release['Release'] = 8.04 |
1368 | else: |
1369 | - raise OSError, "Unknown version of HP MIE" |
1370 | + raise OSError("Unknown version of HP MIE") |
1371 | |
1372 | # FIXME: hack to assume a most-recent release if we're not |
1373 | # running under Ubuntu. |
1374 | - if self.lsb_release['Distributor ID'] not in ["Ubuntu","Linaro"]: |
1375 | + if self.lsb_release['Distributor ID'] not in ["Ubuntu", "Linaro"]: |
1376 | self.lsb_release['Release'] = 10000 |
1377 | # Adjust Linaro release to pretend to be Ubuntu |
1378 | if self.lsb_release['Distributor ID'] in ["Linaro"]: |
1379 | - self.lsb_release['Distributor ID'] = "Ubuntu" |
1380 | + self.lsb_release['Distributor ID'] = "Ubuntu" |
1381 | self.lsb_release['Release'] -= 0.01 |
1382 | |
1383 | # Load arch |
1384 | if not os.path.exists('/usr/bin/dpkg'): |
1385 | - machine = cmd(['uname','-m'])[1].strip() |
1386 | + machine = cmd(['uname', '-m'])[1].strip() |
1387 | if machine.endswith('86'): |
1388 | self.dpkg_arch = 'i386' |
1389 | elif machine.endswith('_64'): |
1390 | @@ -798,26 +990,26 @@ class TestlibManager(object): |
1391 | elif machine.startswith('arm'): |
1392 | self.dpkg_arch = 'armel' |
1393 | else: |
1394 | - raise ValueError, "Unknown machine type '%s'" % (machine) |
1395 | + raise ValueError("Unknown machine type '%s'" % (machine)) |
1396 | else: |
1397 | - self.dpkg_arch = cmd(['dpkg','--print-architecture'])[1].strip() |
1398 | + self.dpkg_arch = cmd(['dpkg', '--print-architecture'])[1].strip() |
1399 | |
1400 | # Find kernel version |
1401 | self.kernel_is_ubuntu = False |
1402 | self.kernel_version_signature = None |
1403 | - self.kernel_version = cmd(["uname","-r"])[1].strip() |
1404 | + self.kernel_version = cmd(["uname", "-r"])[1].strip() |
1405 | versig = '/proc/version_signature' |
1406 | if os.path.exists(versig): |
1407 | self.kernel_is_ubuntu = True |
1408 | - self.kernel_version_signature = file(versig).read().strip() |
1409 | + self.kernel_version_signature = open(versig).read().strip() |
1410 | self.kernel_version_ubuntu = self.kernel_version |
1411 | elif os.path.exists('/usr/bin/dpkg'): |
1412 | # this can easily be inaccurate but is only an issue for Dapper |
1413 | - rc, out = cmd(['dpkg','-l','linux-image-%s' % (self.kernel_version)]) |
1414 | + rc, out = cmd(['dpkg', '-l', 'linux-image-%s' % (self.kernel_version)]) |
1415 | if rc == 0: |
1416 | self.kernel_version_signature = out.strip().split('\n').pop().split()[2] |
1417 | self.kernel_version_ubuntu = self.kernel_version_signature |
1418 | - if self.kernel_version_signature == None: |
1419 | + if self.kernel_version_signature is None: |
1420 | # Attempt to fall back to something for non-Debian-based |
1421 | self.kernel_version_signature = self.kernel_version |
1422 | self.kernel_version_ubuntu = self.kernel_version |
1423 | @@ -831,25 +1023,25 @@ class TestlibManager(object): |
1424 | self.gcc_version = get_gcc_version('gcc') |
1425 | |
1426 | # Find libc |
1427 | - self.path_libc = [x.split()[2] for x in cmd(['ldd','/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0] |
1428 | + self.path_libc = [x.split()[2] for x in cmd(['ldd', '/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0] |
1429 | |
1430 | # Report self |
1431 | if self.verbosity: |
1432 | kernel = self.kernel_version_ubuntu |
1433 | if kernel != self.kernel_version_signature: |
1434 | kernel += " (%s)" % (self.kernel_version_signature) |
1435 | - print >>sys.stdout, "Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( \ |
1436 | + print("Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( |
1437 | sys.argv[0], |
1438 | self.lsb_release['Distributor ID'], |
1439 | self.lsb_release['Release'], |
1440 | kernel, |
1441 | self.dpkg_arch, |
1442 | os.geteuid(), os.getuid(), |
1443 | - os.environ.get('SUDO_USER', '')) |
1444 | + os.environ.get('SUDO_USER', '')), file=sys.stdout) |
1445 | sys.stdout.flush() |
1446 | |
1447 | # Additional heuristics |
1448 | - #if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']: |
1449 | + # if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']: |
1450 | # sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000)) |
1451 | # sys.stdout.flush() |
1452 | # time.sleep(0.5) |
1453 | @@ -857,10 +1049,11 @@ class TestlibManager(object): |
1454 | # time.sleep(0.5) |
1455 | |
1456 | def hello(self, msg): |
1457 | - print >>sys.stderr, "Hello from %s" % (msg) |
1458 | + print("Hello from %s" % (msg), file=sys.stderr) |
1459 | # The central instance |
1460 | manager = TestlibManager() |
1461 | |
1462 | + |
1463 | class TestlibCase(unittest.TestCase): |
1464 | def __init__(self, *args): |
1465 | '''This is called for each TestCase test instance, which isn't much better |
1466 | @@ -870,7 +1063,7 @@ class TestlibCase(unittest.TestCase): |
1467 | |
1468 | # Attach to and duplicate dicts from manager singleton |
1469 | self.manager = manager |
1470 | - #self.manager.hello(repr(self) + repr(*args)) |
1471 | + # self.manager.hello(repr(self) + repr(*args)) |
1472 | self.my_verbosity = self.manager.verbosity |
1473 | self.lsb_release = self.manager.lsb_release |
1474 | self.dpkg_arch = self.manager.dpkg_arch |
1475 | @@ -882,9 +1075,12 @@ class TestlibCase(unittest.TestCase): |
1476 | self.path_libc = self.manager.path_libc |
1477 | |
1478 | def version_compare(self, one, two): |
1479 | - return apt_pkg.VersionCompare(one,two) |
1480 | + if 'version_compare' in dir(apt_pkg): |
1481 | + return apt_pkg.version_compare(one, two) |
1482 | + else: |
1483 | + return apt_pkg.VersionCompare(one, two) |
1484 | |
1485 | - def assertFileType(self, filename, filetype): |
1486 | + def assertFileType(self, filename, filetype, strict=True): |
1487 | '''Checks the file type of the file specified''' |
1488 | |
1489 | (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename]) |
1490 | @@ -894,15 +1090,19 @@ class TestlibCase(unittest.TestCase): |
1491 | if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0: |
1492 | rc = 0 |
1493 | result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report) |
1494 | - self.assertEquals(expected, rc, result) |
1495 | + self.assertEqual(expected, rc, result) |
1496 | |
1497 | - filetype = '^%s$' % (filetype) |
1498 | + if strict: |
1499 | + filetype = '^%s$' % (filetype) |
1500 | + else: |
1501 | + # accept if the beginning of the line matches |
1502 | + filetype = '^%s' % (filetype) |
1503 | result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype) |
1504 | - self.assertNotEquals(None, re.search(filetype, out), result) |
1505 | + self.assertNotEqual(None, re.search(filetype, out), result) |
1506 | |
1507 | def yank_commonname_from_cert(self, certfile): |
1508 | '''Extract the commonName from a given PEM''' |
1509 | - rc, out = cmd(['openssl','asn1parse','-in',certfile]) |
1510 | + rc, out = cmd(['openssl', 'asn1parse', '-in', certfile]) |
1511 | if rc == 0: |
1512 | ready = False |
1513 | for line in out.splitlines(): |
1514 | @@ -914,12 +1114,12 @@ class TestlibCase(unittest.TestCase): |
1515 | |
1516 | def announce(self, text): |
1517 | if self.my_verbosity: |
1518 | - print >>sys.stdout, "(%s) " % (text), |
1519 | + print("(%s) " % (text), file=sys.stderr, end='') |
1520 | sys.stdout.flush() |
1521 | |
1522 | def make_clean(self): |
1523 | - rc, output = self.shell_cmd(['make','clean']) |
1524 | - self.assertEquals(rc, 0, output) |
1525 | + rc, output = self.shell_cmd(['make', 'clean']) |
1526 | + self.assertEqual(rc, 0, output) |
1527 | |
1528 | def get_makefile_compiler(self): |
1529 | # Find potential compiler name |
1530 | @@ -937,8 +1137,8 @@ class TestlibCase(unittest.TestCase): |
1531 | '''Compile a target and report output''' |
1532 | |
1533 | compiler = self.get_makefile_compiler() |
1534 | - rc, output = self.shell_cmd(['make',target]) |
1535 | - self.assertEquals(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output) |
1536 | + rc, output = self.shell_cmd(['make', target]) |
1537 | + self.assertEqual(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output) |
1538 | self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output) |
1539 | return output |
1540 | |
1541 | @@ -950,28 +1150,43 @@ class TestlibCase(unittest.TestCase): |
1542 | self.announce("skipped%s" % (reason)) |
1543 | return False |
1544 | |
1545 | - def _testlib_shell_cmd(self,args,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT): |
1546 | + def _testlib_shell_cmd(self, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None): |
1547 | argstr = "'" + "', '".join(args).strip() + "'" |
1548 | - rc, out = cmd(args,stdin=stdin,stdout=stdout,stderr=stderr) |
1549 | + rc, out = cmd(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env) |
1550 | report = 'Command: ' + argstr + '\nOutput:\n' + out |
1551 | return rc, report, out |
1552 | |
1553 | def shell_cmd(self, args, stdin=None): |
1554 | - return cmd(args,stdin=stdin) |
1555 | + return cmd(args, stdin=stdin) |
1556 | |
1557 | - def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): |
1558 | + def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None, msg="", bare_report=False): |
1559 | '''Test a shell command matches a specific exit code''' |
1560 | - rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) |
1561 | + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr, env=env) |
1562 | result = 'Got exit code %d, expected %d\n' % (rc, expected) |
1563 | - self.assertEquals(expected, rc, msg + result + report) |
1564 | + self.assertEqual(expected, rc, msg + result + report) |
1565 | + if bare_report: |
1566 | + return out |
1567 | + else: |
1568 | + return report |
1569 | + |
1570 | + def assertShellExitSuccess(self, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=None, msg="", bare_report=False): |
1571 | + '''Same as assertShellEquals but with an expected exit status of 0 (success).''' |
1572 | + return self.assertShellExitEquals(0, args, stdin, stdout, stderr, env, msg, bare_report) |
1573 | + |
1574 | + # make sure exit value is in a list of expected values |
1575 | + def assertShellExitIn(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): |
1576 | + '''Test a shell command matches a specific exit code''' |
1577 | + rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) |
1578 | + result = 'Got exit code %d, expected one of %s\n' % (rc, ', '.join(map(str, expected))) |
1579 | + self.assertIn(rc, expected, msg + result + report) |
1580 | |
1581 | def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): |
1582 | '''Test a shell command doesn't match a specific exit code''' |
1583 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) |
1584 | result = 'Got (unwanted) exit code %d\n' % rc |
1585 | - self.assertNotEquals(unwanted, rc, msg + result + report) |
1586 | + self.assertNotEqual(unwanted, rc, msg + result + report) |
1587 | |
1588 | - def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False): |
1589 | + def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None): |
1590 | '''Test a shell command contains a specific output''' |
1591 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) |
1592 | result = 'Got exit code %d. Looking for text "%s"\n' % (rc, text) |
1593 | @@ -979,18 +1194,21 @@ class TestlibCase(unittest.TestCase): |
1594 | self.assertTrue(text in out, msg + result + report) |
1595 | else: |
1596 | self.assertFalse(text in out, msg + result + report) |
1597 | + if expected is not None: |
1598 | + result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args)) |
1599 | + self.assertEqual(rc, expected, msg + result + report) |
1600 | |
1601 | def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None): |
1602 | '''Test a shell command matches a specific output''' |
1603 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) |
1604 | result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args)) |
1605 | if not invert: |
1606 | - self.assertEquals(text, out, msg + result + report) |
1607 | + self.assertEqual(text, out, msg + result + report) |
1608 | else: |
1609 | - self.assertNotEquals(text, out, msg + result + report) |
1610 | - if expected != None: |
1611 | + self.assertNotEqual(text, out, msg + result + report) |
1612 | + if expected is not None: |
1613 | result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args)) |
1614 | - self.assertEquals(rc, expected, msg + result + report) |
1615 | + self.assertEqual(rc, expected, msg + result + report) |
1616 | |
1617 | def _word_find(self, report, content, invert=False): |
1618 | '''Check for a specific string''' |
1619 | @@ -1003,20 +1221,22 @@ class TestlibCase(unittest.TestCase): |
1620 | |
1621 | def _test_sysctl_value(self, path, expected, msg=None, exists=True): |
1622 | sysctl = '/proc/sys/%s' % (path) |
1623 | - self.assertEquals(exists, os.path.exists(sysctl), sysctl) |
1624 | + self.assertEqual(exists, os.path.exists(sysctl), sysctl) |
1625 | value = None |
1626 | if exists: |
1627 | - value = int(file(sysctl).read()) |
1628 | + with open(sysctl) as sysctl_fd: |
1629 | + value = int(sysctl_fd.read()) |
1630 | report = "%s is not %d: %d" % (sysctl, expected, value) |
1631 | if msg: |
1632 | report += " (%s)" % (msg) |
1633 | - self.assertEquals(value, expected, report) |
1634 | + self.assertEqual(value, expected, report) |
1635 | return value |
1636 | |
1637 | def set_sysctl_value(self, path, desired): |
1638 | sysctl = '/proc/sys/%s' % (path) |
1639 | - self.assertTrue(os.path.exists(sysctl),"%s does not exist" % (sysctl)) |
1640 | - file(sysctl,'w').write(str(desired)) |
1641 | + self.assertTrue(os.path.exists(sysctl), "%s does not exist" % (sysctl)) |
1642 | + with open(sysctl, 'w') as sysctl_fh: |
1643 | + sysctl_fh.write(str(desired)) |
1644 | self._test_sysctl_value(path, desired) |
1645 | |
1646 | def kernel_at_least(self, introduced): |
1647 | @@ -1027,10 +1247,33 @@ class TestlibCase(unittest.TestCase): |
1648 | changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version) |
1649 | if os.path.exists(changelog): |
1650 | for line in gzip.open(changelog): |
1651 | - if cve in line and not "revert" in line and not "Revert" in line: |
1652 | + if cve in line and "revert" not in line and "Revert" not in line: |
1653 | return True |
1654 | return False |
1655 | |
1656 | + def install_builddeps(self, src_pkg): |
1657 | + rc, report = _run_apt_command([src_pkg], 'build-dep') |
1658 | + self.assertEqual(0, rc, 'Failed to install build-deps for %s\nOutput:\n%s' % (src_pkg, report)) |
1659 | + |
1660 | + def install_package(self, package): |
1661 | + rc, report = _run_apt_command([package], 'install') |
1662 | + self.assertEqual(0, rc, 'Failed to install package %s\nOutput:\n%s' % (package, report)) |
1663 | + |
1664 | + def install_packages(self, pkg_list): |
1665 | + rc, report = _run_apt_command(pkg_list, 'install') |
1666 | + self.assertEqual(0, rc, 'Failed to install packages %s\nOutput:\n%s' % (','.join(pkg_list), report)) |
1667 | + |
1668 | + def install_snap(self, snapname, track="latest/stable", classic=False): |
1669 | + rc, report = _run_snap_install(snapname, track, classic) |
1670 | + self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" % |
1671 | + (snapname, report)) |
1672 | + |
1673 | + def remove_snap(self, snapname): |
1674 | + rc, report = _run_snap_remove(snapname) |
1675 | + self.assertEqual(0, rc, "Failed to install snap %s\nOutput:\n%s" % |
1676 | + (snapname, report)) |
1677 | + |
1678 | + |
1679 | class TestGroup: |
1680 | '''Create a temporary test group and remove it again in the dtor.''' |
1681 | |
1682 | @@ -1040,14 +1283,14 @@ class TestGroup: |
1683 | self.group = None |
1684 | if group: |
1685 | if group_exists(group): |
1686 | - raise ValueError, 'group name already exists' |
1687 | + raise ValueError('group name already exists') |
1688 | else: |
1689 | while(True): |
1690 | - group = random_string(7,lower=lower) |
1691 | + group = random_string(7, lower=lower) |
1692 | if not group_exists(group): |
1693 | break |
1694 | |
1695 | - assert subprocess.call(['groupadd',group]) == 0 |
1696 | + assert subprocess.call(['groupadd', group]) == 0 |
1697 | self.group = group |
1698 | g = grp.getgrnam(self.group) |
1699 | self.gid = g[2] |
1700 | @@ -1059,6 +1302,7 @@ class TestGroup: |
1701 | rc, report = cmd(['groupdel', self.group]) |
1702 | assert rc == 0 |
1703 | |
1704 | + |
1705 | class TestUser: |
1706 | '''Create a temporary test user and remove it again in the dtor.''' |
1707 | |
1708 | @@ -1072,34 +1316,38 @@ class TestUser: |
1709 | self.login = None |
1710 | |
1711 | if os.geteuid() != 0: |
1712 | - raise ValueError, "You must be root to run this test" |
1713 | + raise ValueError("You must be root to run this test") |
1714 | |
1715 | if login: |
1716 | if login_exists(login): |
1717 | - raise ValueError, 'login name already exists' |
1718 | + raise ValueError('login name already exists') |
1719 | else: |
1720 | while(True): |
1721 | - login = 't' + random_string(7,lower=lower) |
1722 | + login = 't' + random_string(7, lower=lower) |
1723 | if not login_exists(login): |
1724 | break |
1725 | |
1726 | - self.salt = random_string(2) |
1727 | - self.password = random_string(8,lower=lower) |
1728 | + # when fips is enabled, python2.7 crypt.crypt() returns None |
1729 | + # for DES salts for being too weak. Force sha256 with "$5$" for |
1730 | + # the salt. XXX in python3, the salt is optional and will |
1731 | + # generate strong passwords automatically. |
1732 | + self.salt = "$5$%s" % random_string(2) |
1733 | + self.password = random_string(8, lower=lower) |
1734 | self.crypted = crypt.crypt(self.password, self.salt) |
1735 | |
1736 | creation = ['useradd', '-p', self.crypted] |
1737 | if home: |
1738 | creation += ['-m'] |
1739 | if group: |
1740 | - creation += ['-G',group] |
1741 | + creation += ['-G', group] |
1742 | if uidmin: |
1743 | - creation += ['-K','UID_MIN=%d'%uidmin] |
1744 | + creation += ['-K', 'UID_MIN=%d' % uidmin] |
1745 | if shell: |
1746 | - creation += ['-s',shell] |
1747 | + creation += ['-s', shell] |
1748 | creation += [login] |
1749 | assert subprocess.call(creation) == 0 |
1750 | # Set GECOS |
1751 | - assert subprocess.call(['usermod','-c','Buddy %s' % (login),login]) == 0 |
1752 | + assert subprocess.call(['usermod', '-c', 'Buddy %s' % (login), login]) == 0 |
1753 | |
1754 | self.login = login |
1755 | p = pwd.getpwnam(self.login) |
1756 | @@ -1114,8 +1362,8 @@ class TestUser: |
1757 | |
1758 | if self.login: |
1759 | # sanity check the login name so we don't accidentally wipe too much |
1760 | - if len(self.login)>3 and not '/' in self.login: |
1761 | - subprocess.call(['rm','-rf', '/home/'+self.login, '/var/mail/'+self.login]) |
1762 | + if len(self.login) > 3 and '/' not in self.login: |
1763 | + subprocess.call(['rm', '-rf', '/home/' + self.login, '/var/mail/' + self.login]) |
1764 | rc, report = cmd(['userdel', '-f', self.login]) |
1765 | assert rc == 0 |
1766 | |
1767 | @@ -1123,13 +1371,99 @@ class TestUser: |
1768 | '''Add user to the specified group name''' |
1769 | rc, report = cmd(['usermod', '-G', group, self.login]) |
1770 | if rc != 0: |
1771 | - print report |
1772 | + print(report) |
1773 | assert rc == 0 |
1774 | |
1775 | + |
1776 | +class AddUser: |
1777 | + '''Create a temporary test user and remove it again in the dtor.''' |
1778 | + |
1779 | + def __init__(self, login=None, home=True, encrypt_home=False, group=None, lower=False, shell=None): |
1780 | + '''Create a new user account with a random password. |
1781 | + |
1782 | + By default, the login name is random, too, but can be explicitly |
1783 | + specified with 'login'. By default, a home directory is created, this |
1784 | + can be suppressed with 'home=False'. |
1785 | + |
1786 | + This class differs from the TestUser class in that the adduser/deluser |
1787 | + tools are used rather than the useradd/user/del tools. The adduser |
1788 | + program is the only commandline program that can be used to add a new |
1789 | + user with an encrypted home directory. It is possible that the AddUser |
1790 | + class may replace the TestUser class in the future.''' |
1791 | + |
1792 | + self.login = None |
1793 | + |
1794 | + if os.geteuid() != 0: |
1795 | + raise ValueError("You must be root to run this test") |
1796 | + |
1797 | + if login: |
1798 | + if login_exists(login): |
1799 | + raise ValueError('login name already exists') |
1800 | + else: |
1801 | + while(True): |
1802 | + login = 't' + random_string(7, lower=True) |
1803 | + if not login_exists(login): |
1804 | + break |
1805 | + |
1806 | + self.password = random_string(8, lower=lower) |
1807 | + |
1808 | + creation = ['adduser', '--quiet'] |
1809 | + |
1810 | + if not home: |
1811 | + creation += ['--no-create-home'] |
1812 | + elif encrypt_home: |
1813 | + creation += ['--encrypt-home'] |
1814 | + |
1815 | + if shell: |
1816 | + creation += ['--shell', shell] |
1817 | + |
1818 | + creation += ['--gecos', 'Buddy %s' % (login), login] |
1819 | + |
1820 | + child = pexpect.spawn(creation.pop(0), creation, timeout=5) |
1821 | + assert child.expect(b'(Enter new UNIX|New) password:') == 0 |
1822 | + child.sendline(self.password) |
1823 | + assert child.expect(b'Retype new (UNIX )?password:') == 0 |
1824 | + child.sendline(self.password) |
1825 | + |
1826 | + child.wait() |
1827 | + child.close() |
1828 | + assert child.exitstatus == 0 |
1829 | + assert child.signalstatus is None |
1830 | + |
1831 | + self.login = login |
1832 | + |
1833 | + if group: |
1834 | + assert self.add_to_group(group) == 0 |
1835 | + |
1836 | + p = pwd.getpwnam(self.login) |
1837 | + self.uid = p[2] |
1838 | + self.gid = p[3] |
1839 | + self.gecos = p[4] |
1840 | + self.home = p[5] |
1841 | + self.shell = p[6] |
1842 | + |
1843 | + def __del__(self): |
1844 | + '''Remove the created user account.''' |
1845 | + |
1846 | + if self.login: |
1847 | + # sanity check the login name so we don't accidentally wipe too much |
1848 | + rc, report = cmd(['deluser', '--remove-home', self.login]) |
1849 | + assert rc == 0 |
1850 | + |
1851 | + def add_to_group(self, group): |
1852 | + '''Add user to the specified group name''' |
1853 | + rc, report = cmd(['adduser', self.login, group]) |
1854 | + if rc != 0: |
1855 | + print(report) |
1856 | + assert rc == 0 |
1857 | + |
1858 | + |
1859 | # Timeout handler using alarm() from John P. Speno's Pythonic Avocado |
1860 | class TimeoutFunctionException(Exception): |
1861 | """Exception to raise on a timeout""" |
1862 | pass |
1863 | + |
1864 | + |
1865 | class TimeoutFunction: |
1866 | def __init__(self, function, timeout): |
1867 | self.timeout = timeout |
1868 | @@ -1148,6 +1482,7 @@ class TimeoutFunction: |
1869 | signal.alarm(0) |
1870 | return result |
1871 | |
1872 | + |
1873 | def main(): |
1874 | - print "hi" |
1875 | + print("hi") |
1876 | unittest.main() |
1877 | diff --git a/debian/tests/testsuite b/debian/tests/testsuite |
1878 | index 47c7a70..29cf597 100644 |
1879 | --- a/debian/tests/testsuite |
1880 | +++ b/debian/tests/testsuite |
1881 | @@ -3,5 +3,4 @@ |
1882 | # Testing open-iscsi |
1883 | #------------------- |
1884 | set -e |
1885 | -python2 `dirname $0`/test-open-iscsi.py 2>&1 |
1886 | - |
1887 | +python3 `dirname $0`/test-open-iscsi.py 2>&1 |
PPA: https:/ /launchpad. net/~ci- train-ppa- service/ +archive/ ubuntu/ 4495/ /bileto. ubuntu. com/#/ticket/ 4495
Bileto: https:/
Tests are all good on all architectures.