Merge lp:~yolanda.robla/nut/dep-8-tests into lp:ubuntu/saucy/nut
- dep-8-tests
- Merge into saucy
Status: | Work in progress |
---|---|
Proposed branch: | lp:~yolanda.robla/nut/dep-8-tests |
Merge into: | lp:ubuntu/saucy/nut |
Diff against target: |
1621 lines (+1582/-0) 6 files modified
debian/changelog (+6/-0) debian/control (+1/-0) debian/tests/control (+3/-0) debian/tests/nut (+6/-0) debian/tests/test-nut.py (+422/-0) debian/tests/testlib.py (+1144/-0) |
To merge this branch: | bzr merge lp:~yolanda.robla/nut/dep-8-tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pitt | Needs Fixing | ||
James Hunt (community) | Approve | ||
Daniel Holbach (community) | Needs Fixing | ||
Jean-Baptiste Lallement (community) | Approve | ||
Review via email: mp+161414@code.launchpad.net |
Commit message
Description of the change
- 42. By Yolanda Robla
-
fixing test, no need to install packages
Daniel Holbach (dholbach) wrote : | # |
Could you add a changelog entry in debian/changelog for the upload? It might also be a good idea to forward the change to Debian.
- 43. By Yolanda Robla
-
d/tests: added dep-8-tests
James Hunt (jamesodhunt) wrote : | # |
LGTM. Has this been submitted to Debian yet?
Yolanda Robla (yolanda.robla) wrote : | # |
No, nothing submitted, i was waiting for the MP to be approved.
Jamie Strandboge (jdstrand) wrote : | # |
FYI, this has now diverged from QRT. I fixed it in a different way when /etc/init.d/nut moved to /etc/init.
Yolanda Robla (yolanda.robla) wrote : | # |
There are problems with nut branch, it's giving an error:http://
Changes have been submitted to debian manually, so they can be picked up when they are accepted.
Martin Pitt (pitti) wrote : | # |
I ran this on current saucy (run-adt-test -sS lp:~yolanda.robla/nut/dep-8-tests nut) and got a failure:
adt-run: trace: & ubtree0t-nut: - - - - - - - - - - results - - - - - - - - - -
ubtree0t-nut FAIL non-zero exit status 1
adt-run: trace: & ubtree0t-nut: - - - - - - - - - - stdout - - - - - - - - - -
test_CVE_2012_2944 (__main_
Test CVE-2012-2944 ... FAIL
test_daemons_pid (__main_
Test daemons using PID files ... ok
test_daemons_
Test daemons using "service status" ... ok
test_upsc_
Test NUT client interface (upsc): device(s) listing ... ok
test_upsd_IPv4 (__main_
Test upsd IPv4 reachability ... ok
test_upsd_IPv6 (__main_
Test upsd IPv6 reachability ... ok
test_upsmon_notif (__main_
Test upsmon notifications ... ok
test_upsrw (__main_
Test upsrw ... ok
=======
FAIL: test_CVE_2012_2944 (__main_
Test CVE-2012-2944
-------
Traceback (most recent call last):
File "/tmp/tmp.
self.
AssertionError: Found /var/run/
-------
Ran 8 tests in 62.702s
May this be related to what Jamie said in above comment?
Please set back to "needs review" once you are done, setting to WIP now to make it disappear from the sponsoring queue.
Thanks!
Unmerged revisions
- 43. By Yolanda Robla
-
d/tests: added dep-8-tests
- 42. By Yolanda Robla
-
fixing test, no need to install packages
- 41. By Yolanda Robla
-
adding dep-8 tests
Preview Diff
1 | === modified file 'debian/changelog' | |||
2 | --- debian/changelog 2009-12-18 14:04:47 +0000 | |||
3 | +++ debian/changelog 2013-05-07 21:46:24 +0000 | |||
4 | @@ -1,3 +1,9 @@ | |||
5 | 1 | nut (2.4.1-3.2ubuntu2) saucy; urgency=low | ||
6 | 2 | |||
7 | 3 | * d/tests: added dep-8-tests | ||
8 | 4 | |||
9 | 5 | -- Yolanda <yolanda.robla@canonical.com> Tue, 07 May 2013 23:42:31 +0200 | ||
10 | 6 | |||
11 | 1 | nut (2.4.1-3.2ubuntu1) lucid; urgency=low | 7 | nut (2.4.1-3.2ubuntu1) lucid; urgency=low |
12 | 2 | 8 | ||
13 | 3 | * Resynchronize with debian, remaining changes: | 9 | * Resynchronize with debian, remaining changes: |
14 | 4 | 10 | ||
15 | === modified file 'debian/control' | |||
16 | --- debian/control 2009-11-06 01:34:44 +0000 | |||
17 | +++ debian/control 2013-05-07 21:46:24 +0000 | |||
18 | @@ -9,6 +9,7 @@ | |||
19 | 9 | Homepage: http://www.networkupstools.org | 9 | Homepage: http://www.networkupstools.org |
20 | 10 | Vcs-Browser: http://svn.debian.org/wsvn/nut | 10 | Vcs-Browser: http://svn.debian.org/wsvn/nut |
21 | 11 | Vcs-Svn: svn://svn.debian.org/nut/trunk | 11 | Vcs-Svn: svn://svn.debian.org/nut/trunk |
22 | 12 | XS-Testsuite: autopkgtest | ||
23 | 12 | 13 | ||
24 | 13 | Package: nut | 14 | Package: nut |
25 | 14 | Architecture: any | 15 | Architecture: any |
26 | 15 | 16 | ||
27 | === added directory 'debian/tests' | |||
28 | === added file 'debian/tests/control' | |||
29 | --- debian/tests/control 1970-01-01 00:00:00 +0000 | |||
30 | +++ debian/tests/control 2013-05-07 21:46:24 +0000 | |||
31 | @@ -0,0 +1,3 @@ | |||
32 | 1 | Tests: nut | ||
33 | 2 | Depends: python-unit, nut-server, nut-client | ||
34 | 3 | Restrictions: needs-root | ||
35 | 0 | 4 | ||
36 | === added file 'debian/tests/nut' | |||
37 | --- debian/tests/nut 1970-01-01 00:00:00 +0000 | |||
38 | +++ debian/tests/nut 2013-05-07 21:46:24 +0000 | |||
39 | @@ -0,0 +1,6 @@ | |||
40 | 1 | #!/bin/bash | ||
41 | 2 | #------------ | ||
42 | 3 | # Testing nut | ||
43 | 4 | #------------ | ||
44 | 5 | set -e | ||
45 | 6 | python `dirname $0`/test-nut.py 2>&1 | ||
46 | 0 | 7 | ||
47 | === added file 'debian/tests/test-nut.py' | |||
48 | --- debian/tests/test-nut.py 1970-01-01 00:00:00 +0000 | |||
49 | +++ debian/tests/test-nut.py 2013-05-07 21:46:24 +0000 | |||
50 | @@ -0,0 +1,422 @@ | |||
51 | 1 | #!/usr/bin/python | ||
52 | 2 | # | ||
53 | 3 | # test-nut.py quality assurance test script | ||
54 | 4 | # Copyright (C) 2008-2011 Arnaud Quette <aquette@debian.org> | ||
55 | 5 | # Copyright (C) 2012 Jamie Strandboge <jamie@canonical.com> | ||
56 | 6 | # | ||
57 | 7 | # This program is free software: you can redistribute it and/or modify | ||
58 | 8 | # it under the terms of the GNU General Public License version 3, | ||
59 | 9 | # as published by the Free Software Foundation. | ||
60 | 10 | # | ||
61 | 11 | # This program is distributed in the hope that it will be useful, | ||
62 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
63 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
64 | 14 | # GNU General Public License for more details. | ||
65 | 15 | # | ||
66 | 16 | # You should have received a copy of the GNU General Public License | ||
67 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
68 | 18 | # | ||
69 | 19 | |||
70 | 20 | ''' | ||
71 | 21 | *** IMPORTANT *** | ||
72 | 22 | DO NOT RUN ON A PRODUCTION SERVER. | ||
73 | 23 | *** IMPORTANT *** | ||
74 | 24 | |||
75 | 25 | How to run (up to natty): | ||
76 | 26 | $ sudo apt-get -y install python-unit nut | ||
77 | 27 | $ sudo ./test-nut.py -v | ||
78 | 28 | |||
79 | 29 | How to run (oneiric+): | ||
80 | 30 | $ sudo apt-get -y install python-unit nut-server nut-client | ||
81 | 31 | $ sudo ./test-nut.py -v | ||
82 | 32 | |||
83 | 33 | NOTE: | ||
84 | 34 | - NUT architecture (helps understanding): | ||
85 | 35 | http://www.networkupstools.org/docs/developer-guide.chunked/ar01s02.html#_the_layering | ||
86 | 36 | |||
87 | 37 | - These tests only validate the NUT software framework itself (communication | ||
88 | 38 | between the drivers, server and client layers ; events propagation and | ||
89 | 39 | detection). The critical part of NUT, Ie the driver layer which | ||
90 | 40 | communicate with actual devices, can only be tested with real hardware! | ||
91 | 41 | |||
92 | 42 | - These tests use the NUT simulation driver (dummy-ups) to emulate real | ||
93 | 43 | hardware behavior, and generate events (power failure, low battery, ...). | ||
94 | 44 | |||
95 | 45 | TODO: | ||
96 | 46 | - improve test duration, by reworking NutTestCommon._setUp() and the way | ||
97 | 47 | daemons are started (ie, always) | ||
98 | 48 | - more events testing (upsmon / upssched) | ||
99 | 49 | - test syslog and wall output | ||
100 | 50 | - test UPS redundancy | ||
101 | 51 | - test Powerchain (once available!) | ||
102 | 52 | - test AppArmor (once available!) | ||
103 | 53 | - add hardware testing as Private tests? | ||
104 | 54 | - load a .dev file, and test a full output | ||
105 | 55 | |||
106 | 56 | QA INFORMATION: | ||
107 | 57 | - NUT provides "make check" and "make distcheck" in its source distribution | ||
108 | 58 | - NUT provides Quality Assurance information, to track all efforts: | ||
109 | 59 | http://www.networkupstools.org/nut-qa.html | ||
110 | 60 | ''' | ||
111 | 61 | |||
112 | 62 | # QRT-Packages: python-unit netcat-openbsd | ||
113 | 63 | # QRT-Alternates: nut-server nut | ||
114 | 64 | # QRT-Alternates: nut-client nut | ||
115 | 65 | # nut-dev is needed for the dummy driver on hardy | ||
116 | 66 | # QRT-Alternates: nut-dev | ||
117 | 67 | # QRT-Privilege: root | ||
118 | 68 | # QRT-Depends: | ||
119 | 69 | |||
120 | 70 | |||
121 | 71 | import unittest, subprocess, sys, os, time | ||
122 | 72 | import tempfile | ||
123 | 73 | import testlib | ||
124 | 74 | |||
125 | 75 | use_private = True | ||
126 | 76 | try: | ||
127 | 77 | from private.qrt.nut import PrivateNutTest | ||
128 | 78 | except ImportError: | ||
129 | 79 | class PrivateNutTest(object): | ||
130 | 80 | '''Empty class''' | ||
131 | 81 | print >>sys.stdout, "Skipping private tests" | ||
132 | 82 | |||
133 | 83 | |||
134 | 84 | class NutTestCommon(testlib.TestlibCase): | ||
135 | 85 | '''Common functions''' | ||
136 | 86 | |||
137 | 87 | # FIXME: initscript will be splitted into nut-server and nut-client | ||
138 | 88 | # (Debian bug #634858) | ||
139 | 89 | initscript = "/etc/init.d/nut-server" | ||
140 | 90 | hosts_file = "/etc/hosts" | ||
141 | 91 | powerdownflag = "/etc/killpower" | ||
142 | 92 | shutdowncmd = "/tmp/shutdowncmd" | ||
143 | 93 | notifyscript = "/tmp/nutifyme" | ||
144 | 94 | notifylog = "/tmp/notify.log" | ||
145 | 95 | |||
146 | 96 | def _setUp(self): | ||
147 | 97 | '''Set up prior to each test_* function''' | ||
148 | 98 | '''We generate a NUT config using the dummmy-ups driver | ||
149 | 99 | and standard settings for local monitoring | ||
150 | 100 | ''' | ||
151 | 101 | self.tmpdir = "" | ||
152 | 102 | self.rundir = "/var/run/nut" | ||
153 | 103 | testlib.cmd(['/bin/rm -f' + self.powerdownflag]) | ||
154 | 104 | |||
155 | 105 | testlib.config_replace('/etc/nut/ups.conf', ''' | ||
156 | 106 | [dummy-dev1] | ||
157 | 107 | driver = dummy-ups | ||
158 | 108 | port = dummy.dev | ||
159 | 109 | desc = "simulation device" | ||
160 | 110 | ''') | ||
161 | 111 | |||
162 | 112 | if self.lsb_release['Release'] <= 8.04: | ||
163 | 113 | testlib.config_replace('/etc/nut/upsd.conf', ''' | ||
164 | 114 | ACL dummy-net 127.0.0.1/8 | ||
165 | 115 | ACL dummy-net2 ::1/64 | ||
166 | 116 | ACL all 0.0.0.0/0 | ||
167 | 117 | ACCEPT dummy-net dummy-net2 | ||
168 | 118 | REJECT all | ||
169 | 119 | ''') | ||
170 | 120 | else: | ||
171 | 121 | testlib.config_replace('/etc/nut/upsd.conf', '''# just to touch the file''') | ||
172 | 122 | |||
173 | 123 | extra_cfgs = '' | ||
174 | 124 | if self.lsb_release['Release'] <= 8.04: | ||
175 | 125 | extra_cfgs = ''' allowfrom = dummy-net dummy-net2 | ||
176 | 126 | ''' | ||
177 | 127 | testlib.config_replace('/etc/nut/upsd.users', ''' | ||
178 | 128 | [admin] | ||
179 | 129 | password = dummypass | ||
180 | 130 | actions = SET | ||
181 | 131 | instcmds = ALL | ||
182 | 132 | %s | ||
183 | 133 | [monuser] | ||
184 | 134 | password = dummypass | ||
185 | 135 | upsmon master | ||
186 | 136 | %s ''' %(extra_cfgs, extra_cfgs)) | ||
187 | 137 | |||
188 | 138 | testlib.config_replace('/etc/nut/upsmon.conf', ''' | ||
189 | 139 | MONITOR dummy-dev1@localhost 1 monuser dummy-pass master | ||
190 | 140 | MINSUPPLIES 1 | ||
191 | 141 | SHUTDOWNCMD "/usr/bin/touch ''' + self.shutdowncmd + '"\n' | ||
192 | 142 | '''POWERDOWNFLAG ''' + self.powerdownflag + '\n' | ||
193 | 143 | ''' | ||
194 | 144 | NOTIFYCMD ''' + self.notifyscript + '\n' | ||
195 | 145 | ''' | ||
196 | 146 | NOTIFYFLAG ONLINE SYSLOG+EXEC | ||
197 | 147 | NOTIFYFLAG ONBATT SYSLOG+EXEC | ||
198 | 148 | NOTIFYFLAG LOWBATT SYSLOG+EXEC | ||
199 | 149 | NOTIFYFLAG FSD SYSLOG+EXEC | ||
200 | 150 | # NOTIFYFLAG COMMOK SYSLOG+EXEC | ||
201 | 151 | # NOTIFYFLAG COMMBAD SYSLOG+EXEC | ||
202 | 152 | NOTIFYFLAG SHUTDOWN SYSLOG+EXEC | ||
203 | 153 | # NOTIFYFLAG REPLBATT SYSLOG+EXEC | ||
204 | 154 | # NOTIFYFLAG NOCOMM SYSLOG+EXEC | ||
205 | 155 | # NOTIFYFLAG NOPARENT SYSLOG+EXEC | ||
206 | 156 | |||
207 | 157 | # Shorten test duration by: | ||
208 | 158 | # Speeding up polling frequency | ||
209 | 159 | POLLFREQ 2 | ||
210 | 160 | # And final wait delay | ||
211 | 161 | FINALDELAY 0 | ||
212 | 162 | ''' | ||
213 | 163 | ) | ||
214 | 164 | |||
215 | 165 | testlib.create_fill(self.notifyscript, ''' | ||
216 | 166 | #! /bin/bash | ||
217 | 167 | echo "$*" > ''' + self.notifylog + '\n', mode=0755) | ||
218 | 168 | |||
219 | 169 | # dummy-ups absolutely needs a data file, even if empty | ||
220 | 170 | testlib.config_replace('/etc/nut/dummy.dev', ''' | ||
221 | 171 | ups.mfr: Dummy Manufacturer | ||
222 | 172 | ups.model: Dummy UPS | ||
223 | 173 | ups.status: OL | ||
224 | 174 | # Set a big enough timer to avoid value reset, due to reading loop | ||
225 | 175 | TIMER 600 | ||
226 | 176 | ''') | ||
227 | 177 | |||
228 | 178 | testlib.config_replace('/etc/nut/nut.conf', '''MODE=standalone''') | ||
229 | 179 | |||
230 | 180 | # Add known friendly IP names for localhost v4 and v6 | ||
231 | 181 | # FIXME: find a way to determine if v4 / v6 are enabled, and a way to | ||
232 | 182 | # get v4 / v6 names | ||
233 | 183 | testlib.config_replace(self.hosts_file, '''# | ||
234 | 184 | 127.0.0.1 localv4 | ||
235 | 185 | ::1 localv6 | ||
236 | 186 | ''', append=True) | ||
237 | 187 | |||
238 | 188 | if self.lsb_release['Release'] <= 8.04: | ||
239 | 189 | testlib.config_replace('/etc/default/nut', '''# | ||
240 | 190 | START_UPSD=yes | ||
241 | 191 | UPSD_OPTIONS="" | ||
242 | 192 | START_UPSMON=yes | ||
243 | 193 | UPSMON_OPTIONS="" | ||
244 | 194 | ''', append=False) | ||
245 | 195 | |||
246 | 196 | # Start the framework | ||
247 | 197 | self._restart() | ||
248 | 198 | |||
249 | 199 | def _tearDown(self): | ||
250 | 200 | '''Clean up after each test_* function''' | ||
251 | 201 | self._stop() | ||
252 | 202 | time.sleep(2) | ||
253 | 203 | os.unlink('/etc/nut/ups.conf') | ||
254 | 204 | os.unlink('/etc/nut/upsd.conf') | ||
255 | 205 | os.unlink('/etc/nut/upsd.users') | ||
256 | 206 | os.unlink('/etc/nut/upsmon.conf') | ||
257 | 207 | os.unlink('/etc/nut/dummy.dev') | ||
258 | 208 | os.unlink('/etc/nut/nut.conf') | ||
259 | 209 | testlib.config_restore('/etc/nut/ups.conf') | ||
260 | 210 | testlib.config_restore('/etc/nut/upsd.conf') | ||
261 | 211 | testlib.config_restore('/etc/nut/upsd.users') | ||
262 | 212 | testlib.config_restore('/etc/nut/upsmon.conf') | ||
263 | 213 | testlib.config_restore('/etc/nut/dummy.dev') | ||
264 | 214 | testlib.config_restore('/etc/nut/nut.conf') | ||
265 | 215 | if os.path.exists(self.notifyscript): | ||
266 | 216 | os.unlink(self.notifyscript) | ||
267 | 217 | if os.path.exists(self.shutdowncmd): | ||
268 | 218 | os.unlink(self.shutdowncmd) | ||
269 | 219 | testlib.config_restore(self.hosts_file) | ||
270 | 220 | if self.lsb_release['Release'] <= 8.04: | ||
271 | 221 | testlib.config_restore('/etc/default/nut') | ||
272 | 222 | |||
273 | 223 | if os.path.exists(self.tmpdir): | ||
274 | 224 | testlib.recursive_rm(self.tmpdir) | ||
275 | 225 | |||
276 | 226 | # this is needed because of the potentially hung upsd process in the | ||
277 | 227 | # CVE-2012-2944 test | ||
278 | 228 | testlib.cmd(['killall', 'upsd']) | ||
279 | 229 | testlib.cmd(['killall', '-9', 'upsd']) | ||
280 | 230 | |||
281 | 231 | def _start(self): | ||
282 | 232 | '''Start NUT''' | ||
283 | 233 | rc, report = testlib.cmd([self.initscript, 'start']) | ||
284 | 234 | expected = 0 | ||
285 | 235 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
286 | 236 | self.assertEquals(expected, rc, result + report) | ||
287 | 237 | time.sleep(2) | ||
288 | 238 | |||
289 | 239 | def _stop(self): | ||
290 | 240 | '''Stop NUT''' | ||
291 | 241 | rc, report = testlib.cmd([self.initscript, 'stop']) | ||
292 | 242 | expected = 0 | ||
293 | 243 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
294 | 244 | self.assertEquals(expected, rc, result + report) | ||
295 | 245 | |||
296 | 246 | def _reload(self): | ||
297 | 247 | '''Reload NUT''' | ||
298 | 248 | rc, report = testlib.cmd([self.initscript, 'force-reload']) | ||
299 | 249 | expected = 0 | ||
300 | 250 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
301 | 251 | self.assertEquals(expected, rc, result + report) | ||
302 | 252 | |||
303 | 253 | def _restart(self): | ||
304 | 254 | '''Restart NUT''' | ||
305 | 255 | self._stop() | ||
306 | 256 | time.sleep(2) | ||
307 | 257 | self._start() | ||
308 | 258 | |||
309 | 259 | def _status(self): | ||
310 | 260 | '''NUT Status''' | ||
311 | 261 | rc, report = testlib.cmd([self.initscript, 'status']) | ||
312 | 262 | expected = 0 | ||
313 | 263 | if self.lsb_release['Release'] <= 8.04: | ||
314 | 264 | self._skipped("init script does not support status command") | ||
315 | 265 | expected = 1 | ||
316 | 266 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
317 | 267 | self.assertEquals(expected, rc, result + report) | ||
318 | 268 | |||
319 | 269 | def _testDaemons(self, daemons): | ||
320 | 270 | '''Daemons running''' | ||
321 | 271 | for d in daemons: | ||
322 | 272 | # A note on the driver pid file: its name is | ||
323 | 273 | # <ups.conf section name>-<driver name>.pid | ||
324 | 274 | # ex: dummy-dev1-dummy-ups.pid | ||
325 | 275 | if d == 'dummy-ups' : | ||
326 | 276 | pidfile = os.path.join(self.rundir, 'dummy-ups-dummy-dev1.pid') | ||
327 | 277 | else : | ||
328 | 278 | pidfile = os.path.join(self.rundir, d + '.pid') | ||
329 | 279 | warning = "Could not find pidfile '" + pidfile + "'" | ||
330 | 280 | self.assertTrue(os.path.exists(pidfile), warning) | ||
331 | 281 | self.assertTrue(testlib.check_pidfile(d, pidfile), d + ' is not running') | ||
332 | 282 | |||
333 | 283 | def _nut_setvar(self, var, value): | ||
334 | 284 | '''Test upsrw''' | ||
335 | 285 | rc, report = testlib.cmd(['/bin/upsrw', '-s', var + '=' + value, | ||
336 | 286 | '-u', 'admin' , '-p', 'dummypass', 'dummy-dev1@localhost']) | ||
337 | 287 | self.assertTrue(rc == 0, 'upsrw: ' + report) | ||
338 | 288 | return rc,report | ||
339 | 289 | |||
340 | 290 | |||
341 | 291 | class BasicTest(NutTestCommon, PrivateNutTest): | ||
342 | 292 | '''Test basic NUT functionalities''' | ||
343 | 293 | |||
344 | 294 | def setUp(self): | ||
345 | 295 | '''Setup mechanisms''' | ||
346 | 296 | NutTestCommon._setUp(self) | ||
347 | 297 | |||
348 | 298 | def tearDown(self): | ||
349 | 299 | '''Shutdown methods''' | ||
350 | 300 | NutTestCommon._tearDown(self) | ||
351 | 301 | |||
352 | 302 | def test_daemons_service(self): | ||
353 | 303 | '''Test daemons using "service status"''' | ||
354 | 304 | self._status() | ||
355 | 305 | |||
356 | 306 | def test_daemons_pid(self): | ||
357 | 307 | '''Test daemons using PID files''' | ||
358 | 308 | # upsmon does not work because ups-client is still missing | ||
359 | 309 | daemons = [ 'dummy-ups', 'upsd'] | ||
360 | 310 | self._testDaemons(daemons) | ||
361 | 311 | |||
362 | 312 | def test_upsd_IPv4(self): | ||
363 | 313 | '''Test upsd IPv4 reachability''' | ||
364 | 314 | rc, report = testlib.cmd(['/bin/upsc', '-l', 'localv4']) | ||
365 | 315 | self.assertTrue('dummy-dev1' in report, 'dummy-dev1 should be present in device(s) listing: ' + report) | ||
366 | 316 | |||
367 | 317 | def test_upsd_IPv6(self): | ||
368 | 318 | '''Test upsd IPv6 reachability''' | ||
369 | 319 | rc, report = testlib.cmd(['/bin/upsc', '-l', 'localv6']) | ||
370 | 320 | self.assertTrue('dummy-dev1' in report, 'dummy-dev1 should be present in device(s) listing: ' + report) | ||
371 | 321 | |||
372 | 322 | def test_upsc_device_list(self): | ||
373 | 323 | '''Test NUT client interface (upsc): device(s) listing''' | ||
374 | 324 | rc, report = testlib.cmd(['/bin/upsc', '-L']) | ||
375 | 325 | self.assertTrue('dummy-dev1: simulation device' in report, 'dummy-dev1 should be present in device(s) listing: ' + report) | ||
376 | 326 | |||
377 | 327 | def _test_upsc_status(self): | ||
378 | 328 | '''Test NUT client interface (upsc): data access''' | ||
379 | 329 | rc, report = testlib.cmd(['/bin/upsc', 'dummy-dev1', 'ups.status']) | ||
380 | 330 | self.assertTrue('OL' in report, 'UPS Status: ' + report + 'should be OL') | ||
381 | 331 | |||
382 | 332 | #def test_upsc_powerchain(self): | ||
383 | 333 | # '''Test NUT client interface (upsc): Powerchain(s) listing''' | ||
384 | 334 | # rc, report = testlib.cmd(['/bin/upsc', '-p']) | ||
385 | 335 | # Result == Main ; dummy-dev1 ; $hostname | ||
386 | 336 | # self.assertTrue('dummy-dev1' in report, 'dummy-dev1 should be present in device(s) listing: ' + report) | ||
387 | 337 | |||
388 | 338 | def test_upsrw(self): | ||
389 | 339 | '''Test upsrw''' | ||
390 | 340 | # Set ups.status to OB (On Battery)... | ||
391 | 341 | self._nut_setvar('ups.model', 'Test') | ||
392 | 342 | time.sleep(2) | ||
393 | 343 | # and check the result on the client side | ||
394 | 344 | rc, report = testlib.cmd(['/bin/upsc', 'dummy-dev1@localhost', 'ups.model']) | ||
395 | 345 | self.assertTrue('Test' in report, 'UPS Model: ' + report + 'should be Test') | ||
396 | 346 | |||
397 | 347 | # FIXME: need a simulation counterpart, not yet implemented | ||
398 | 348 | #def test_upscmd(self): | ||
399 | 349 | # '''Test upscmd''' | ||
400 | 350 | |||
401 | 351 | def test_upsmon_notif(self): | ||
402 | 352 | '''Test upsmon notifications''' | ||
403 | 353 | # Set ups.status to OB (On Battery)... | ||
404 | 354 | self._nut_setvar('ups.status', 'OB') | ||
405 | 355 | time.sleep(1) | ||
406 | 356 | # and check the result on the client side | ||
407 | 357 | rc, report = testlib.cmd(['/bin/upsc', 'dummy-dev1@localhost', 'ups.status']) | ||
408 | 358 | self.assertTrue('OB' in report, 'UPS Status: ' + report + 'should be OB') | ||
409 | 359 | |||
410 | 360 | #def test_upsmon_shutdown(self): | ||
411 | 361 | # '''Test upsmon basic shutdown (single UPS, low battery status)''' | ||
412 | 362 | # self._nut_setvar('ups.status', 'OB LB') | ||
413 | 363 | # time.sleep(2) | ||
414 | 364 | # # and check the result on the client side | ||
415 | 365 | # rc, report = testlib.cmd(['/bin/upsc', 'dummy-dev1@localhost', 'ups.status']) | ||
416 | 366 | # self.assertTrue('OB LB' in report, 'UPS Status: ' + report + 'should be OB LB') | ||
417 | 367 | # # FIXME: improve with a 2 sec loop * 5 tries | ||
418 | 368 | # time.sleep(3) | ||
419 | 369 | # # Check for powerdownflag and shutdowncmd (needed for halt!) | ||
420 | 370 | # # FIXME: replace by a call to 'upsmon -K' | ||
421 | 371 | # self.assertTrue(os.path.exists(self.powerdownflag), 'POWERDOWNFLAG has not been set!') | ||
422 | 372 | # self.assertTrue(os.path.exists(self.shutdowncmd), 'SHUTDOWNCMD has not been executed!') | ||
423 | 373 | |||
424 | 374 | def test_CVE_2012_2944(self): | ||
425 | 375 | '''Test CVE-2012-2944''' | ||
426 | 376 | self.tmpdir = tempfile.mkdtemp(dir='/tmp', prefix="testlib-") | ||
427 | 377 | # First send bad input. We need to do this in a script because python | ||
428 | 378 | # functions don't like our embedded NULs | ||
429 | 379 | script = os.path.join(self.tmpdir, 'script.sh') | ||
430 | 380 | contents = '''#!/bin/sh | ||
431 | 381 | printf '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\n' | nc -q 1 127.0.0.1 3493 | ||
432 | 382 | sleep 1 | ||
433 | 383 | dd if=/dev/urandom count=64 | nc -q 1 127.0.0.1 3493 | ||
434 | 384 | ''' | ||
435 | 385 | testlib.create_fill(script, contents, mode=0755) | ||
436 | 386 | rc, report = testlib.cmd([script]) | ||
437 | 387 | |||
438 | 388 | # It should not have crashed. Let's see if it did | ||
439 | 389 | self._testDaemons(['upsd']) | ||
440 | 390 | self.assertTrue('ERR UNKNOWN-COMMAND' in report, "Could not find 'ERR UNKNOWN-COMMAND' in:\n%s" % report) | ||
441 | 391 | |||
442 | 392 | # This CVE may also result in a hung upsd. Try to kill it, if it is | ||
443 | 393 | # still around, it is hung | ||
444 | 394 | testlib.cmd(['killall', 'upsd']) | ||
445 | 395 | pidfile = os.path.join(self.rundir, 'upsd.pid') | ||
446 | 396 | self.assertFalse(os.path.exists(pidfile), "Found %s" % pidfile) | ||
447 | 397 | self.assertFalse(testlib.check_pidfile('upsd', pidfile), 'upsd is hung') | ||
448 | 398 | #subprocess.call(['bash']) | ||
449 | 399 | |||
450 | 400 | # FIXME | ||
451 | 401 | #class AdvancedTest(NutTestCommon, PrivateNutTest): | ||
452 | 402 | # '''Test advanced NUT functionalities''' | ||
453 | 403 | |||
454 | 404 | if __name__ == '__main__': | ||
455 | 405 | |||
456 | 406 | suite = unittest.TestSuite() | ||
457 | 407 | # more configurable | ||
458 | 408 | if (len(sys.argv) == 1 or sys.argv[1] == '-v'): | ||
459 | 409 | suite.addTest(unittest.TestLoader().loadTestsFromTestCase(BasicTest)) | ||
460 | 410 | |||
461 | 411 | # Pull in private tests | ||
462 | 412 | #if use_private: | ||
463 | 413 | # suite.addTest(unittest.TestLoader().loadTestsFromTestCase(MyPrivateTest)) | ||
464 | 414 | |||
465 | 415 | else: | ||
466 | 416 | print '''Usage: | ||
467 | 417 | test-nut.py [-v] basic tests | ||
468 | 418 | ''' | ||
469 | 419 | sys.exit(1) | ||
470 | 420 | rc = unittest.TextTestRunner(verbosity=2).run(suite) | ||
471 | 421 | if not rc.wasSuccessful(): | ||
472 | 422 | sys.exit(1) | ||
473 | 0 | 423 | ||
474 | === added file 'debian/tests/testlib.py' | |||
475 | --- debian/tests/testlib.py 1970-01-01 00:00:00 +0000 | |||
476 | +++ debian/tests/testlib.py 2013-05-07 21:46:24 +0000 | |||
477 | @@ -0,0 +1,1144 @@ | |||
478 | 1 | # | ||
479 | 2 | # testlib.py quality assurance test script | ||
480 | 3 | # Copyright (C) 2008-2011 Canonical Ltd. | ||
481 | 4 | # | ||
482 | 5 | # This library is free software; you can redistribute it and/or | ||
483 | 6 | # modify it under the terms of the GNU Library General Public | ||
484 | 7 | # License as published by the Free Software Foundation; either | ||
485 | 8 | # version 2 of the License. | ||
486 | 9 | # | ||
487 | 10 | # This library is distributed in the hope that it will be useful, | ||
488 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
489 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
490 | 13 | # Library General Public License for more details. | ||
491 | 14 | # | ||
492 | 15 | # You should have received a copy of the GNU Library General Public | ||
493 | 16 | # License along with this program. If not, see | ||
494 | 17 | # <http://www.gnu.org/licenses/>. | ||
495 | 18 | # | ||
496 | 19 | |||
497 | 20 | '''Common classes and functions for package tests.''' | ||
498 | 21 | |||
499 | 22 | import string, random, crypt, subprocess, pwd, grp, signal, time, unittest, tempfile, shutil, os, os.path, re, glob | ||
500 | 23 | import sys, socket, gzip | ||
501 | 24 | from stat import * | ||
502 | 25 | from encodings import string_escape | ||
503 | 26 | |||
504 | 27 | import warnings | ||
505 | 28 | warnings.filterwarnings('ignore', message=r'.*apt_pkg\.TagFile.*', category=DeprecationWarning) | ||
506 | 29 | try: | ||
507 | 30 | import apt_pkg | ||
508 | 31 | apt_pkg.InitSystem(); | ||
509 | 32 | except: | ||
510 | 33 | # On non-Debian system, fall back to simple comparison without debianisms | ||
511 | 34 | class apt_pkg(object): | ||
512 | 35 | def VersionCompare(one, two): | ||
513 | 36 | list_one = one.split('.') | ||
514 | 37 | list_two = two.split('.') | ||
515 | 38 | while len(list_one)>0 and len(list_two)>0: | ||
516 | 39 | if list_one[0] > list_two[0]: | ||
517 | 40 | return 1 | ||
518 | 41 | if list_one[0] < list_two[0]: | ||
519 | 42 | return -1 | ||
520 | 43 | list_one.pop(0) | ||
521 | 44 | list_two.pop(0) | ||
522 | 45 | return 0 | ||
523 | 46 | |||
524 | 47 | bogus_nxdomain = "208.69.32.132" | ||
525 | 48 | |||
526 | 49 | # http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html | ||
527 | 50 | # This is needed so that the subprocesses that produce endless output | ||
528 | 51 | # actually quit when the reader goes away. | ||
529 | 52 | import signal | ||
530 | 53 | def subprocess_setup(): | ||
531 | 54 | # Python installs a SIGPIPE handler by default. This is usually not what | ||
532 | 55 | # non-Python subprocesses expect. | ||
533 | 56 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) | ||
534 | 57 | |||
535 | 58 | class TimedOutException(Exception): | ||
536 | 59 | def __init__(self, value = "Timed Out"): | ||
537 | 60 | self.value = value | ||
538 | 61 | def __str__(self): | ||
539 | 62 | return repr(self.value) | ||
540 | 63 | |||
541 | 64 | def _restore_backup(path): | ||
542 | 65 | pathbackup = path + '.autotest' | ||
543 | 66 | if os.path.exists(pathbackup): | ||
544 | 67 | shutil.move(pathbackup, path) | ||
545 | 68 | |||
546 | 69 | def _save_backup(path): | ||
547 | 70 | pathbackup = path + '.autotest' | ||
548 | 71 | if os.path.exists(path) and not os.path.exists(pathbackup): | ||
549 | 72 | shutil.copy2(path, pathbackup) | ||
550 | 73 | # copy2 does not copy ownership, so do it here. | ||
551 | 74 | # Reference: http://docs.python.org/library/shutil.html | ||
552 | 75 | a = os.stat(path) | ||
553 | 76 | os.chown(pathbackup, a[4], a[5]) | ||
554 | 77 | |||
555 | 78 | def config_copydir(path): | ||
556 | 79 | if os.path.exists(path) and not os.path.isdir(path): | ||
557 | 80 | raise OSError, "'%s' is not a directory" % (path) | ||
558 | 81 | _restore_backup(path) | ||
559 | 82 | |||
560 | 83 | pathbackup = path + '.autotest' | ||
561 | 84 | if os.path.exists(path): | ||
562 | 85 | shutil.copytree(path, pathbackup, symlinks=True) | ||
563 | 86 | |||
564 | 87 | def config_replace(path,contents,append=False): | ||
565 | 88 | '''Replace (or append) to a config file''' | ||
566 | 89 | _restore_backup(path) | ||
567 | 90 | if os.path.exists(path): | ||
568 | 91 | _save_backup(path) | ||
569 | 92 | if append: | ||
570 | 93 | contents = file(path).read() + contents | ||
571 | 94 | open(path, 'w').write(contents) | ||
572 | 95 | |||
573 | 96 | def config_comment(path, field): | ||
574 | 97 | _save_backup(path) | ||
575 | 98 | contents = "" | ||
576 | 99 | for line in file(path): | ||
577 | 100 | if re.search("^\s*%s\s*=" % (field), line): | ||
578 | 101 | line = "#" + line | ||
579 | 102 | contents += line | ||
580 | 103 | |||
581 | 104 | open(path+'.new', 'w').write(contents) | ||
582 | 105 | os.rename(path+'.new', path) | ||
583 | 106 | |||
584 | 107 | def config_set(path, field, value, spaces=True): | ||
585 | 108 | _save_backup(path) | ||
586 | 109 | contents = "" | ||
587 | 110 | if spaces==True: | ||
588 | 111 | setting = '%s = %s\n' % (field, value) | ||
589 | 112 | else: | ||
590 | 113 | setting = '%s=%s\n' % (field, value) | ||
591 | 114 | found = False | ||
592 | 115 | for line in file(path): | ||
593 | 116 | if re.search("^\s*%s\s*=" % (field), line): | ||
594 | 117 | found = True | ||
595 | 118 | line = setting | ||
596 | 119 | contents += line | ||
597 | 120 | if not found: | ||
598 | 121 | contents += setting | ||
599 | 122 | |||
600 | 123 | open(path+'.new', 'w').write(contents) | ||
601 | 124 | os.rename(path+'.new', path) | ||
602 | 125 | |||
603 | 126 | def config_patch(path, patch, depth=1): | ||
604 | 127 | '''Patch a config file''' | ||
605 | 128 | _restore_backup(path) | ||
606 | 129 | _save_backup(path) | ||
607 | 130 | |||
608 | 131 | handle, name = mkstemp_fill(patch) | ||
609 | 132 | rc = subprocess.call(['/usr/bin/patch', '-p%s' %(depth), path], stdin=handle, stdout=subprocess.PIPE) | ||
610 | 133 | os.unlink(name) | ||
611 | 134 | if rc != 0: | ||
612 | 135 | raise Exception("Patch failed") | ||
613 | 136 | |||
614 | 137 | def config_restore(path): | ||
615 | 138 | '''Rename a replaced config file back to its initial state''' | ||
616 | 139 | _restore_backup(path) | ||
617 | 140 | |||
618 | 141 | def timeout(secs, f, *args): | ||
619 | 142 | def handler(signum, frame): | ||
620 | 143 | raise TimedOutException() | ||
621 | 144 | |||
622 | 145 | old = signal.signal(signal.SIGALRM, handler) | ||
623 | 146 | result = None | ||
624 | 147 | signal.alarm(secs) | ||
625 | 148 | try: | ||
626 | 149 | result = f(*args) | ||
627 | 150 | finally: | ||
628 | 151 | signal.alarm(0) | ||
629 | 152 | signal.signal(signal.SIGALRM, old) | ||
630 | 153 | |||
631 | 154 | return result | ||
632 | 155 | |||
633 | 156 | def require_nonroot(): | ||
634 | 157 | if os.geteuid() == 0: | ||
635 | 158 | print >>sys.stderr, "This series of tests should be run as a regular user with sudo access, not as root." | ||
636 | 159 | sys.exit(1) | ||
637 | 160 | |||
638 | 161 | def require_root(): | ||
639 | 162 | if os.geteuid() != 0: | ||
640 | 163 | print >>sys.stderr, "This series of tests should be run with root privileges (e.g. via sudo)." | ||
641 | 164 | sys.exit(1) | ||
642 | 165 | |||
643 | 166 | def require_sudo(): | ||
644 | 167 | if os.geteuid() != 0 or os.environ.get('SUDO_USER', None) == None: | ||
645 | 168 | print >>sys.stderr, "This series of tests must be run under sudo." | ||
646 | 169 | sys.exit(1) | ||
647 | 170 | if os.environ['SUDO_USER'] == 'root': | ||
648 | 171 | print >>sys.stderr, 'Please run this test using sudo from a regular user. (You ran sudo from root.)' | ||
649 | 172 | sys.exit(1) | ||
650 | 173 | |||
651 | 174 | def random_string(length,lower=False): | ||
652 | 175 | '''Return a random string, consisting of ASCII letters, with given | ||
653 | 176 | length.''' | ||
654 | 177 | |||
655 | 178 | s = '' | ||
656 | 179 | selection = string.letters | ||
657 | 180 | if lower: | ||
658 | 181 | selection = string.lowercase | ||
659 | 182 | maxind = len(selection)-1 | ||
660 | 183 | for l in range(length): | ||
661 | 184 | s += selection[random.randint(0, maxind)] | ||
662 | 185 | return s | ||
663 | 186 | |||
664 | 187 | def mkstemp_fill(contents,suffix='',prefix='testlib-',dir=None): | ||
665 | 188 | '''As tempfile.mkstemp does, return a (file, name) pair, but with | ||
666 | 189 | prefilled contents.''' | ||
667 | 190 | |||
668 | 191 | handle, name = tempfile.mkstemp(suffix=suffix,prefix=prefix,dir=dir) | ||
669 | 192 | os.close(handle) | ||
670 | 193 | handle = file(name,"w+") | ||
671 | 194 | handle.write(contents) | ||
672 | 195 | handle.flush() | ||
673 | 196 | handle.seek(0) | ||
674 | 197 | |||
675 | 198 | return handle, name | ||
676 | 199 | |||
677 | 200 | def create_fill(path, contents, mode=0644): | ||
678 | 201 | '''Safely create a page''' | ||
679 | 202 | # make the temp file in the same dir as the destination file so we | ||
680 | 203 | # don't get invalid cross-device link errors when we rename | ||
681 | 204 | handle, name = mkstemp_fill(contents, dir=os.path.dirname(path)) | ||
682 | 205 | handle.close() | ||
683 | 206 | os.rename(name, path) | ||
684 | 207 | os.chmod(path, mode) | ||
685 | 208 | |||
686 | 209 | def login_exists(login): | ||
687 | 210 | '''Checks whether the given login exists on the system.''' | ||
688 | 211 | |||
689 | 212 | try: | ||
690 | 213 | pwd.getpwnam(login) | ||
691 | 214 | return True | ||
692 | 215 | except KeyError: | ||
693 | 216 | return False | ||
694 | 217 | |||
695 | 218 | def group_exists(group): | ||
696 | 219 | '''Checks whether the given login exists on the system.''' | ||
697 | 220 | |||
698 | 221 | try: | ||
699 | 222 | grp.getgrnam(group) | ||
700 | 223 | return True | ||
701 | 224 | except KeyError: | ||
702 | 225 | return False | ||
703 | 226 | |||
704 | 227 | def recursive_rm(dirPath, contents_only=False): | ||
705 | 228 | '''recursively remove directory''' | ||
706 | 229 | names = os.listdir(dirPath) | ||
707 | 230 | for name in names: | ||
708 | 231 | path = os.path.join(dirPath, name) | ||
709 | 232 | if os.path.islink(path) or not os.path.isdir(path): | ||
710 | 233 | os.unlink(path) | ||
711 | 234 | else: | ||
712 | 235 | recursive_rm(path) | ||
713 | 236 | if contents_only == False: | ||
714 | 237 | os.rmdir(dirPath) | ||
715 | 238 | |||
716 | 239 | def check_pidfile(exe, pidfile): | ||
717 | 240 | '''Checks if pid in pidfile is running''' | ||
718 | 241 | if not os.path.exists(pidfile): | ||
719 | 242 | return False | ||
720 | 243 | |||
721 | 244 | # get the pid | ||
722 | 245 | try: | ||
723 | 246 | fd = open(pidfile, 'r') | ||
724 | 247 | pid = fd.readline().rstrip('\n') | ||
725 | 248 | fd.close() | ||
726 | 249 | except: | ||
727 | 250 | return False | ||
728 | 251 | |||
729 | 252 | return check_pid(exe, pid) | ||
730 | 253 | |||
731 | 254 | def check_pid(exe, pid): | ||
732 | 255 | '''Checks if pid is running''' | ||
733 | 256 | cmdline = "/proc/%s/cmdline" % (str(pid)) | ||
734 | 257 | if not os.path.exists(cmdline): | ||
735 | 258 | return False | ||
736 | 259 | |||
737 | 260 | # get the command line | ||
738 | 261 | try: | ||
739 | 262 | fd = open(cmdline, 'r') | ||
740 | 263 | tmp = fd.readline().split('\0') | ||
741 | 264 | fd.close() | ||
742 | 265 | except: | ||
743 | 266 | return False | ||
744 | 267 | |||
745 | 268 | # this allows us to match absolute paths or just the executable name | ||
746 | 269 | if re.match('^' + exe + '$', tmp[0]) or \ | ||
747 | 270 | re.match('.*/' + exe + '$', tmp[0]) or \ | ||
748 | 271 | re.match('^' + exe + ': ', tmp[0]) or \ | ||
749 | 272 | re.match('^\(' + exe + '\)', tmp[0]): | ||
750 | 273 | return True | ||
751 | 274 | |||
752 | 275 | return False | ||
753 | 276 | |||
754 | 277 | def check_port(port, proto, ver=4): | ||
755 | 278 | '''Check if something is listening on the specified port. | ||
756 | 279 | WARNING: for some reason this does not work with a bind mounted /proc | ||
757 | 280 | ''' | ||
758 | 281 | assert (port >= 1) | ||
759 | 282 | assert (port <= 65535) | ||
760 | 283 | assert (proto.lower() == "tcp" or proto.lower() == "udp") | ||
761 | 284 | assert (ver == 4 or ver == 6) | ||
762 | 285 | |||
763 | 286 | fn = "/proc/net/%s" % (proto) | ||
764 | 287 | if ver == 6: | ||
765 | 288 | fn += str(ver) | ||
766 | 289 | |||
767 | 290 | rc, report = cmd(['cat', fn]) | ||
768 | 291 | assert (rc == 0) | ||
769 | 292 | |||
770 | 293 | hport = "%0.4x" % port | ||
771 | 294 | |||
772 | 295 | if re.search(': [0-9a-f]{8}:%s [0-9a-f]' % str(hport).lower(), report.lower()): | ||
773 | 296 | return True | ||
774 | 297 | return False | ||
775 | 298 | |||
776 | 299 | def get_arch(): | ||
777 | 300 | '''Get the current architecture''' | ||
778 | 301 | rc, report = cmd(['uname', '-m']) | ||
779 | 302 | assert (rc == 0) | ||
780 | 303 | return report.strip() | ||
781 | 304 | |||
782 | 305 | def get_memory(): | ||
783 | 306 | '''Gets total ram and swap''' | ||
784 | 307 | meminfo = "/proc/meminfo" | ||
785 | 308 | memtotal = 0 | ||
786 | 309 | swaptotal = 0 | ||
787 | 310 | if not os.path.exists(meminfo): | ||
788 | 311 | return (False, False) | ||
789 | 312 | |||
790 | 313 | try: | ||
791 | 314 | fd = open(meminfo, 'r') | ||
792 | 315 | for line in fd.readlines(): | ||
793 | 316 | splitline = line.split() | ||
794 | 317 | if splitline[0] == 'MemTotal:': | ||
795 | 318 | memtotal = int(splitline[1]) | ||
796 | 319 | elif splitline[0] == 'SwapTotal:': | ||
797 | 320 | swaptotal = int(splitline[1]) | ||
798 | 321 | fd.close() | ||
799 | 322 | except: | ||
800 | 323 | return (False, False) | ||
801 | 324 | |||
802 | 325 | return (memtotal,swaptotal) | ||
803 | 326 | |||
804 | 327 | def is_running_in_vm(): | ||
805 | 328 | '''Check if running under a VM''' | ||
806 | 329 | # add other virtualization environments here | ||
807 | 330 | for search in ['QEMU Virtual CPU']: | ||
808 | 331 | rc, report = cmd_pipe(['dmesg'], ['grep', search]) | ||
809 | 332 | if rc == 0: | ||
810 | 333 | return True | ||
811 | 334 | return False | ||
812 | 335 | |||
813 | 336 | def ubuntu_release(): | ||
814 | 337 | '''Get the Ubuntu release''' | ||
815 | 338 | f = "/etc/lsb-release" | ||
816 | 339 | try: | ||
817 | 340 | size = os.stat(f)[ST_SIZE] | ||
818 | 341 | except: | ||
819 | 342 | return "UNKNOWN" | ||
820 | 343 | |||
821 | 344 | if size > 1024*1024: | ||
822 | 345 | raise IOError, 'Could not open "%s" (too big)' % f | ||
823 | 346 | |||
824 | 347 | try: | ||
825 | 348 | fh = open("/etc/lsb-release", 'r') | ||
826 | 349 | except: | ||
827 | 350 | raise | ||
828 | 351 | |||
829 | 352 | lines = fh.readlines() | ||
830 | 353 | fh.close() | ||
831 | 354 | |||
832 | 355 | pat = re.compile(r'DISTRIB_CODENAME') | ||
833 | 356 | for line in lines: | ||
834 | 357 | if pat.search(line): | ||
835 | 358 | return line.split('=')[1].rstrip('\n').rstrip('\r') | ||
836 | 359 | |||
837 | 360 | return "UNKNOWN" | ||
838 | 361 | |||
839 | 362 | def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None): | ||
840 | 363 | '''Try to execute given command (array) and return its stdout, or return | ||
841 | 364 | a textual error if it failed.''' | ||
842 | 365 | |||
843 | 366 | try: | ||
844 | 367 | sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup) | ||
845 | 368 | except OSError, e: | ||
846 | 369 | return [127, str(e)] | ||
847 | 370 | |||
848 | 371 | out, outerr = sp.communicate(input) | ||
849 | 372 | # Handle redirection of stdout | ||
850 | 373 | if out == None: | ||
851 | 374 | out = '' | ||
852 | 375 | # Handle redirection of stderr | ||
853 | 376 | if outerr == None: | ||
854 | 377 | outerr = '' | ||
855 | 378 | return [sp.returncode,out+outerr] | ||
856 | 379 | |||
857 | 380 | def cmd_pipe(command1, command2, input = None, stderr = subprocess.STDOUT, stdin = None): | ||
858 | 381 | '''Try to pipe command1 into command2.''' | ||
859 | 382 | try: | ||
860 | 383 | sp1 = subprocess.Popen(command1, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, close_fds=True) | ||
861 | 384 | sp2 = subprocess.Popen(command2, stdin=sp1.stdout, stdout=subprocess.PIPE, stderr=stderr, close_fds=True) | ||
862 | 385 | except OSError, e: | ||
863 | 386 | return [127, str(e)] | ||
864 | 387 | |||
865 | 388 | out = sp2.communicate(input)[0] | ||
866 | 389 | return [sp2.returncode,out] | ||
867 | 390 | |||
868 | 391 | def cwd_has_enough_space(cdir, total_bytes): | ||
869 | 392 | '''Determine if the partition of the current working directory has 'bytes' | ||
870 | 393 | free.''' | ||
871 | 394 | rc, df_output = cmd(['df']) | ||
872 | 395 | result = 'Got exit code %d, expected %d\n' % (rc, 0) | ||
873 | 396 | if rc != 0: | ||
874 | 397 | return False | ||
875 | 398 | |||
876 | 399 | kb = total_bytes / 1024 | ||
877 | 400 | |||
878 | 401 | mounts = dict() | ||
879 | 402 | for line in df_output.splitlines(): | ||
880 | 403 | if '/' not in line: | ||
881 | 404 | continue | ||
882 | 405 | tmp = line.split() | ||
883 | 406 | mounts[tmp[5]] = int(tmp[3]) | ||
884 | 407 | |||
885 | 408 | cdir = os.getcwd() | ||
886 | 409 | while cdir != '/': | ||
887 | 410 | if not mounts.has_key(cdir): | ||
888 | 411 | cdir = os.path.dirname(cdir) | ||
889 | 412 | continue | ||
890 | 413 | if kb < mounts[cdir]: | ||
891 | 414 | return True | ||
892 | 415 | else: | ||
893 | 416 | return False | ||
894 | 417 | |||
895 | 418 | if kb < mounts['/']: | ||
896 | 419 | return True | ||
897 | 420 | |||
898 | 421 | return False | ||
899 | 422 | |||
900 | 423 | def get_md5(filename): | ||
901 | 424 | '''Gets the md5sum of the file specified''' | ||
902 | 425 | |||
903 | 426 | (rc, report) = cmd(["/usr/bin/md5sum", "-b", filename]) | ||
904 | 427 | expected = 0 | ||
905 | 428 | assert (expected == rc) | ||
906 | 429 | |||
907 | 430 | return report.split(' ')[0] | ||
908 | 431 | |||
909 | 432 | def dpkg_compare_installed_version(pkg, check, version): | ||
910 | 433 | '''Gets the version for the installed package, and compares it to the | ||
911 | 434 | specified version. | ||
912 | 435 | ''' | ||
913 | 436 | (rc, report) = cmd(["/usr/bin/dpkg", "-s", pkg]) | ||
914 | 437 | assert (rc == 0) | ||
915 | 438 | assert ("Status: install ok installed" in report) | ||
916 | 439 | installed_version = "" | ||
917 | 440 | for line in report.splitlines(): | ||
918 | 441 | if line.startswith("Version: "): | ||
919 | 442 | installed_version = line.split()[1] | ||
920 | 443 | |||
921 | 444 | assert (installed_version != "") | ||
922 | 445 | |||
923 | 446 | (rc, report) = cmd(["/usr/bin/dpkg", "--compare-versions", installed_version, check, version]) | ||
924 | 447 | assert (rc == 0 or rc == 1) | ||
925 | 448 | if rc == 0: | ||
926 | 449 | return True | ||
927 | 450 | return False | ||
928 | 451 | |||
929 | 452 | def prepare_source(source, builder, cached_src, build_src, patch_system): | ||
930 | 453 | '''Download and unpack source package, installing necessary build depends, | ||
931 | 454 | adjusting the permissions for the 'builder' user, and returning the | ||
932 | 455 | directory of the unpacked source. Patch system can be one of: | ||
933 | 456 | - cdbs | ||
934 | 457 | - dpatch | ||
935 | 458 | - quilt | ||
936 | 459 | - quiltv3 | ||
937 | 460 | - None (not the string) | ||
938 | 461 | |||
939 | 462 | This is normally used like this: | ||
940 | 463 | |||
941 | 464 | def setUp(self): | ||
942 | 465 | ... | ||
943 | 466 | self.topdir = os.getcwd() | ||
944 | 467 | self.cached_src = os.path.join(os.getcwd(), "source") | ||
945 | 468 | self.tmpdir = tempfile.mkdtemp(prefix='testlib', dir='/tmp') | ||
946 | 469 | self.builder = testlib.TestUser() | ||
947 | 470 | testlib.cmd(['chgrp', self.builder.login, self.tmpdir]) | ||
948 | 471 | os.chmod(self.tmpdir, 0775) | ||
949 | 472 | |||
950 | 473 | def tearDown(self): | ||
951 | 474 | ... | ||
952 | 475 | self.builder = None | ||
953 | 476 | self.topdir = os.getcwd() | ||
954 | 477 | if os.path.exists(self.tmpdir): | ||
955 | 478 | testlib.recursive_rm(self.tmpdir) | ||
956 | 479 | |||
957 | 480 | def test_suite_build(self): | ||
958 | 481 | ... | ||
959 | 482 | build_dir = testlib.prepare_source('foo', \ | ||
960 | 483 | self.builder, \ | ||
961 | 484 | self.cached_src, \ | ||
962 | 485 | os.path.join(self.tmpdir, \ | ||
963 | 486 | os.path.basename(self.cached_src)), | ||
964 | 487 | "quilt") | ||
965 | 488 | os.chdir(build_dir) | ||
966 | 489 | |||
967 | 490 | # Example for typical build, adjust as necessary | ||
968 | 491 | print "" | ||
969 | 492 | print " make clean" | ||
970 | 493 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'clean']) | ||
971 | 494 | |||
972 | 495 | print " configure" | ||
973 | 496 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, './configure', '--prefix=%s' % self.tmpdir, '--enable-debug']) | ||
974 | 497 | |||
975 | 498 | print " make (will take a while)" | ||
976 | 499 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make']) | ||
977 | 500 | |||
978 | 501 | print " make check (will take a while)", | ||
979 | 502 | rc, report = testlib.cmd(['sudo', '-u', self.builder.login, 'make', 'check']) | ||
980 | 503 | expected = 0 | ||
981 | 504 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
982 | 505 | self.assertEquals(expected, rc, result + report) | ||
983 | 506 | |||
984 | 507 | def test_suite_cleanup(self): | ||
985 | 508 | ... | ||
986 | 509 | if os.path.exists(self.cached_src): | ||
987 | 510 | testlib.recursive_rm(self.cached_src) | ||
988 | 511 | |||
989 | 512 | It is up to the caller to clean up cached_src and build_src (as in the | ||
990 | 513 | above example, often the build_src is in a tmpdir that is cleaned in | ||
991 | 514 | tearDown() and the cached_src is cleaned in a one time clean-up | ||
992 | 515 | operation (eg 'test_suite_cleanup()) which must be run after the build | ||
993 | 516 | suite test (obviously). | ||
994 | 517 | ''' | ||
995 | 518 | |||
996 | 519 | # Make sure we have a clean slate | ||
997 | 520 | assert (os.path.exists(os.path.dirname(build_src))) | ||
998 | 521 | assert (not os.path.exists(build_src)) | ||
999 | 522 | |||
1000 | 523 | cdir = os.getcwd() | ||
1001 | 524 | if os.path.exists(cached_src): | ||
1002 | 525 | shutil.copytree(cached_src, build_src) | ||
1003 | 526 | os.chdir(build_src) | ||
1004 | 527 | else: | ||
1005 | 528 | # Only install the build dependencies on the initial setup | ||
1006 | 529 | rc, report = cmd(['apt-get','-y','--force-yes','build-dep',source]) | ||
1007 | 530 | assert (rc == 0) | ||
1008 | 531 | |||
1009 | 532 | os.makedirs(build_src) | ||
1010 | 533 | os.chdir(build_src) | ||
1011 | 534 | |||
1012 | 535 | # These are always needed | ||
1013 | 536 | pkgs = ['build-essential', 'dpkg-dev', 'fakeroot'] | ||
1014 | 537 | rc, report = cmd(['apt-get','-y','--force-yes','install'] + pkgs) | ||
1015 | 538 | assert (rc == 0) | ||
1016 | 539 | |||
1017 | 540 | rc, report = cmd(['apt-get','source',source]) | ||
1018 | 541 | assert (rc == 0) | ||
1019 | 542 | shutil.copytree(build_src, cached_src) | ||
1020 | 543 | |||
1021 | 544 | unpacked_dir = os.path.join(build_src, glob.glob('%s-*' % source)[0]) | ||
1022 | 545 | |||
1023 | 546 | # Now apply the patches. Do it here so that we don't mess up our cached | ||
1024 | 547 | # sources. | ||
1025 | 548 | os.chdir(unpacked_dir) | ||
1026 | 549 | assert (patch_system in ['cdbs', 'dpatch', 'quilt', 'quiltv3', None]) | ||
1027 | 550 | if patch_system != None and patch_system != "quiltv3": | ||
1028 | 551 | if patch_system == "quilt": | ||
1029 | 552 | os.environ.setdefault('QUILT_PATCHES','debian/patches') | ||
1030 | 553 | rc, report = cmd(['quilt', 'push', '-a']) | ||
1031 | 554 | assert (rc == 0) | ||
1032 | 555 | elif patch_system == "cdbs": | ||
1033 | 556 | rc, report = cmd(['./debian/rules', 'apply-patches']) | ||
1034 | 557 | assert (rc == 0) | ||
1035 | 558 | elif patch_system == "dpatch": | ||
1036 | 559 | rc, report = cmd(['dpatch', 'apply-all']) | ||
1037 | 560 | assert (rc == 0) | ||
1038 | 561 | |||
1039 | 562 | cmd(['chown', '-R', '%s:%s' % (builder.uid, builder.gid), build_src]) | ||
1040 | 563 | os.chdir(cdir) | ||
1041 | 564 | |||
1042 | 565 | return unpacked_dir | ||
1043 | 566 | |||
1044 | 567 | def _aa_status(): | ||
1045 | 568 | '''Get aa-status output''' | ||
1046 | 569 | exe = "/usr/sbin/aa-status" | ||
1047 | 570 | assert (os.path.exists(exe)) | ||
1048 | 571 | if os.geteuid() == 0: | ||
1049 | 572 | return cmd([exe]) | ||
1050 | 573 | return cmd(['sudo', exe]) | ||
1051 | 574 | |||
1052 | 575 | def is_apparmor_loaded(path): | ||
1053 | 576 | '''Check if profile is loaded''' | ||
1054 | 577 | rc, report = _aa_status() | ||
1055 | 578 | if rc != 0: | ||
1056 | 579 | return False | ||
1057 | 580 | |||
1058 | 581 | for line in report.splitlines(): | ||
1059 | 582 | if line.endswith(path): | ||
1060 | 583 | return True | ||
1061 | 584 | return False | ||
1062 | 585 | |||
1063 | 586 | def is_apparmor_confined(path): | ||
1064 | 587 | '''Check if application is confined''' | ||
1065 | 588 | rc, report = _aa_status() | ||
1066 | 589 | if rc != 0: | ||
1067 | 590 | return False | ||
1068 | 591 | |||
1069 | 592 | for line in report.splitlines(): | ||
1070 | 593 | if re.search('%s \(' % path, line): | ||
1071 | 594 | return True | ||
1072 | 595 | return False | ||
1073 | 596 | |||
1074 | 597 | def check_apparmor(path, first_ubuntu_release, is_running=True): | ||
1075 | 598 | '''Check if path is loaded and confined for everything higher than the | ||
1076 | 599 | first Ubuntu release specified. | ||
1077 | 600 | |||
1078 | 601 | Usage: | ||
1079 | 602 | rc, report = testlib.check_apparmor('/usr/sbin/foo', 8.04, is_running=True) | ||
1080 | 603 | if rc < 0: | ||
1081 | 604 | return self._skipped(report) | ||
1082 | 605 | |||
1083 | 606 | expected = 0 | ||
1084 | 607 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1085 | 608 | self.assertEquals(expected, rc, result + report) | ||
1086 | 609 | ''' | ||
1087 | 610 | global manager | ||
1088 | 611 | rc = -1 | ||
1089 | 612 | |||
1090 | 613 | if manager.lsb_release["Release"] < first_ubuntu_release: | ||
1091 | 614 | return (rc, "Skipped apparmor check") | ||
1092 | 615 | |||
1093 | 616 | if not os.path.exists('/sbin/apparmor_parser'): | ||
1094 | 617 | return (rc, "Skipped (couldn't find apparmor_parser)") | ||
1095 | 618 | |||
1096 | 619 | rc = 0 | ||
1097 | 620 | msg = "" | ||
1098 | 621 | if not is_apparmor_loaded(path): | ||
1099 | 622 | rc = 1 | ||
1100 | 623 | msg = "Profile not loaded for '%s'" % path | ||
1101 | 624 | |||
1102 | 625 | # this check only makes sense it the 'path' is currently executing | ||
1103 | 626 | if is_running and rc == 0 and not is_apparmor_confined(path): | ||
1104 | 627 | rc = 1 | ||
1105 | 628 | msg = "'%s' is not running in enforce mode" % path | ||
1106 | 629 | |||
1107 | 630 | return (rc, msg) | ||
1108 | 631 | |||
1109 | 632 | def get_gcc_version(gcc, full=True): | ||
1110 | 633 | gcc_version = 'none' | ||
1111 | 634 | if not gcc.startswith('/'): | ||
1112 | 635 | gcc = '/usr/bin/%s' % (gcc) | ||
1113 | 636 | if os.path.exists(gcc): | ||
1114 | 637 | gcc_version = 'unknown' | ||
1115 | 638 | lines = cmd([gcc,'-v'])[1].strip().splitlines() | ||
1116 | 639 | version_lines = [x for x in lines if x.startswith('gcc version')] | ||
1117 | 640 | if len(version_lines) == 1: | ||
1118 | 641 | gcc_version = " ".join(version_lines[0].split()[2:]) | ||
1119 | 642 | if not full: | ||
1120 | 643 | return gcc_version.split()[0] | ||
1121 | 644 | return gcc_version | ||
1122 | 645 | |||
1123 | 646 | def is_kdeinit_running(): | ||
1124 | 647 | '''Test if kdeinit is running''' | ||
1125 | 648 | # applications that use kdeinit will spawn it if it isn't running in the | ||
1126 | 649 | # test. This is a problem because it does not exit. This is a helper to | ||
1127 | 650 | # check for it. | ||
1128 | 651 | rc, report = cmd(['ps', 'x']) | ||
1129 | 652 | if 'kdeinit4 Running' not in report: | ||
1130 | 653 | print >>sys.stderr, ("kdeinit not running (you may start/stop any KDE application then run this script again)") | ||
1131 | 654 | return False | ||
1132 | 655 | return True | ||
1133 | 656 | |||
1134 | 657 | def get_pkgconfig_flags(libs=[]): | ||
1135 | 658 | '''Find pkg-config flags for libraries''' | ||
1136 | 659 | assert (len(libs) > 0) | ||
1137 | 660 | rc, pkg_config = cmd(['pkg-config', '--cflags', '--libs'] + libs) | ||
1138 | 661 | expected = 0 | ||
1139 | 662 | if rc != expected: | ||
1140 | 663 | print >>sys.stderr, 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1141 | 664 | assert(rc == expected) | ||
1142 | 665 | return pkg_config.split() | ||
1143 | 666 | |||
1144 | 667 | class TestDaemon: | ||
1145 | 668 | '''Helper class to manage daemons consistently''' | ||
1146 | 669 | def __init__(self, init): | ||
1147 | 670 | '''Setup daemon attributes''' | ||
1148 | 671 | self.initscript = init | ||
1149 | 672 | |||
1150 | 673 | def start(self): | ||
1151 | 674 | '''Start daemon''' | ||
1152 | 675 | rc, report = cmd([self.initscript, 'start']) | ||
1153 | 676 | expected = 0 | ||
1154 | 677 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1155 | 678 | time.sleep(2) | ||
1156 | 679 | if expected != rc: | ||
1157 | 680 | return (False, result + report) | ||
1158 | 681 | |||
1159 | 682 | if "fail" in report: | ||
1160 | 683 | return (False, "Found 'fail' in report\n" + report) | ||
1161 | 684 | |||
1162 | 685 | return (True, "") | ||
1163 | 686 | |||
1164 | 687 | def stop(self): | ||
1165 | 688 | '''Stop daemon''' | ||
1166 | 689 | rc, report = cmd([self.initscript, 'stop']) | ||
1167 | 690 | expected = 0 | ||
1168 | 691 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1169 | 692 | if expected != rc: | ||
1170 | 693 | return (False, result + report) | ||
1171 | 694 | |||
1172 | 695 | if "fail" in report: | ||
1173 | 696 | return (False, "Found 'fail' in report\n" + report) | ||
1174 | 697 | |||
1175 | 698 | return (True, "") | ||
1176 | 699 | |||
1177 | 700 | def reload(self): | ||
1178 | 701 | '''Reload daemon''' | ||
1179 | 702 | rc, report = cmd([self.initscript, 'force-reload']) | ||
1180 | 703 | expected = 0 | ||
1181 | 704 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1182 | 705 | if expected != rc: | ||
1183 | 706 | return (False, result + report) | ||
1184 | 707 | |||
1185 | 708 | if "fail" in report: | ||
1186 | 709 | return (False, "Found 'fail' in report\n" + report) | ||
1187 | 710 | |||
1188 | 711 | return (True, "") | ||
1189 | 712 | |||
1190 | 713 | def restart(self): | ||
1191 | 714 | '''Restart daemon''' | ||
1192 | 715 | (res, str) = self.stop() | ||
1193 | 716 | if not res: | ||
1194 | 717 | return (res, str) | ||
1195 | 718 | |||
1196 | 719 | (res, str) = self.start() | ||
1197 | 720 | if not res: | ||
1198 | 721 | return (res, str) | ||
1199 | 722 | |||
1200 | 723 | return (True, "") | ||
1201 | 724 | |||
1202 | 725 | def status(self): | ||
1203 | 726 | '''Check daemon status''' | ||
1204 | 727 | rc, report = cmd([self.initscript, 'status']) | ||
1205 | 728 | expected = 0 | ||
1206 | 729 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1207 | 730 | if expected != rc: | ||
1208 | 731 | return (False, result + report) | ||
1209 | 732 | |||
1210 | 733 | if "fail" in report: | ||
1211 | 734 | return (False, "Found 'fail' in report\n" + report) | ||
1212 | 735 | |||
1213 | 736 | return (True, "") | ||
1214 | 737 | |||
1215 | 738 | class TestlibManager(object): | ||
1216 | 739 | '''Singleton class used to set up per-test-run information''' | ||
1217 | 740 | def __init__(self): | ||
1218 | 741 | # Set glibc aborts to dump to stderr instead of the tty so test output | ||
1219 | 742 | # is more sane. | ||
1220 | 743 | os.environ.setdefault('LIBC_FATAL_STDERR_','1') | ||
1221 | 744 | |||
1222 | 745 | # check verbosity | ||
1223 | 746 | self.verbosity = False | ||
1224 | 747 | if (len(sys.argv) > 1 and '-v' in sys.argv[1:]): | ||
1225 | 748 | self.verbosity = True | ||
1226 | 749 | |||
1227 | 750 | # Load LSB release file | ||
1228 | 751 | self.lsb_release = dict() | ||
1229 | 752 | if not os.path.exists('/usr/bin/lsb_release') and not os.path.exists('/bin/lsb_release'): | ||
1230 | 753 | raise OSError, "Please install 'lsb-release'" | ||
1231 | 754 | for line in subprocess.Popen(['lsb_release','-a'],stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()[0].splitlines(): | ||
1232 | 755 | field, value = line.split(':',1) | ||
1233 | 756 | value=value.strip() | ||
1234 | 757 | field=field.strip() | ||
1235 | 758 | # Convert numerics | ||
1236 | 759 | try: | ||
1237 | 760 | value = float(value) | ||
1238 | 761 | except: | ||
1239 | 762 | pass | ||
1240 | 763 | self.lsb_release.setdefault(field,value) | ||
1241 | 764 | |||
1242 | 765 | # FIXME: hack OEM releases into known-Ubuntu versions | ||
1243 | 766 | if self.lsb_release['Distributor ID'] == "HP MIE (Mobile Internet Experience)": | ||
1244 | 767 | if self.lsb_release['Release'] == 1.0: | ||
1245 | 768 | self.lsb_release['Distributor ID'] = "Ubuntu" | ||
1246 | 769 | self.lsb_release['Release'] = 8.04 | ||
1247 | 770 | else: | ||
1248 | 771 | raise OSError, "Unknown version of HP MIE" | ||
1249 | 772 | |||
1250 | 773 | # FIXME: hack to assume a most-recent release if we're not | ||
1251 | 774 | # running under Ubuntu. | ||
1252 | 775 | if self.lsb_release['Distributor ID'] not in ["Ubuntu","Linaro"]: | ||
1253 | 776 | self.lsb_release['Release'] = 10000 | ||
1254 | 777 | # Adjust Linaro release to pretend to be Ubuntu | ||
1255 | 778 | if self.lsb_release['Distributor ID'] in ["Linaro"]: | ||
1256 | 779 | self.lsb_release['Distributor ID'] = "Ubuntu" | ||
1257 | 780 | self.lsb_release['Release'] -= 0.01 | ||
1258 | 781 | |||
1259 | 782 | # Load arch | ||
1260 | 783 | if not os.path.exists('/usr/bin/dpkg'): | ||
1261 | 784 | machine = cmd(['uname','-m'])[1].strip() | ||
1262 | 785 | if machine.endswith('86'): | ||
1263 | 786 | self.dpkg_arch = 'i386' | ||
1264 | 787 | elif machine.endswith('_64'): | ||
1265 | 788 | self.dpkg_arch = 'amd64' | ||
1266 | 789 | elif machine.startswith('arm'): | ||
1267 | 790 | self.dpkg_arch = 'armel' | ||
1268 | 791 | else: | ||
1269 | 792 | raise ValueError, "Unknown machine type '%s'" % (machine) | ||
1270 | 793 | else: | ||
1271 | 794 | self.dpkg_arch = cmd(['dpkg','--print-architecture'])[1].strip() | ||
1272 | 795 | |||
1273 | 796 | # Find kernel version | ||
1274 | 797 | self.kernel_is_ubuntu = False | ||
1275 | 798 | self.kernel_version_signature = None | ||
1276 | 799 | self.kernel_version = cmd(["uname","-r"])[1].strip() | ||
1277 | 800 | versig = '/proc/version_signature' | ||
1278 | 801 | if os.path.exists(versig): | ||
1279 | 802 | self.kernel_is_ubuntu = True | ||
1280 | 803 | self.kernel_version_signature = file(versig).read().strip() | ||
1281 | 804 | self.kernel_version_ubuntu = self.kernel_version | ||
1282 | 805 | elif os.path.exists('/usr/bin/dpkg'): | ||
1283 | 806 | # this can easily be inaccurate but is only an issue for Dapper | ||
1284 | 807 | rc, out = cmd(['dpkg','-l','linux-image-%s' % (self.kernel_version)]) | ||
1285 | 808 | if rc == 0: | ||
1286 | 809 | self.kernel_version_signature = out.strip().split('\n').pop().split()[2] | ||
1287 | 810 | self.kernel_version_ubuntu = self.kernel_version_signature | ||
1288 | 811 | if self.kernel_version_signature == None: | ||
1289 | 812 | # Attempt to fall back to something for non-Debian-based | ||
1290 | 813 | self.kernel_version_signature = self.kernel_version | ||
1291 | 814 | self.kernel_version_ubuntu = self.kernel_version | ||
1292 | 815 | # Build ubuntu version without hardware suffix | ||
1293 | 816 | try: | ||
1294 | 817 | self.kernel_version_ubuntu = "-".join([x for x in self.kernel_version_signature.split(' ')[1].split('-') if re.search('^[0-9]', x)]) | ||
1295 | 818 | except: | ||
1296 | 819 | pass | ||
1297 | 820 | |||
1298 | 821 | # Find gcc version | ||
1299 | 822 | self.gcc_version = get_gcc_version('gcc') | ||
1300 | 823 | |||
1301 | 824 | # Find libc | ||
1302 | 825 | self.path_libc = [x.split()[2] for x in cmd(['ldd','/bin/ls'])[1].splitlines() if x.startswith('\tlibc.so.')][0] | ||
1303 | 826 | |||
1304 | 827 | # Report self | ||
1305 | 828 | if self.verbosity: | ||
1306 | 829 | kernel = self.kernel_version_ubuntu | ||
1307 | 830 | if kernel != self.kernel_version_signature: | ||
1308 | 831 | kernel += " (%s)" % (self.kernel_version_signature) | ||
1309 | 832 | print >>sys.stdout, "Running test: '%s' distro: '%s %.2f' kernel: '%s' arch: '%s' uid: %d/%d SUDO_USER: '%s')" % ( \ | ||
1310 | 833 | sys.argv[0], | ||
1311 | 834 | self.lsb_release['Distributor ID'], | ||
1312 | 835 | self.lsb_release['Release'], | ||
1313 | 836 | kernel, | ||
1314 | 837 | self.dpkg_arch, | ||
1315 | 838 | os.geteuid(), os.getuid(), | ||
1316 | 839 | os.environ.get('SUDO_USER', '')) | ||
1317 | 840 | sys.stdout.flush() | ||
1318 | 841 | |||
1319 | 842 | # Additional heuristics | ||
1320 | 843 | #if os.environ.get('SUDO_USER', os.environ.get('USER', '')) in ['mdeslaur']: | ||
1321 | 844 | # sys.stdout.write("Replying to Marc Deslauriers in http://launchpad.net/bugs/%d: " % random.randint(600000, 980000)) | ||
1322 | 845 | # sys.stdout.flush() | ||
1323 | 846 | # time.sleep(0.5) | ||
1324 | 847 | # sys.stdout.write("destroyed\n") | ||
1325 | 848 | # time.sleep(0.5) | ||
1326 | 849 | |||
1327 | 850 | def hello(self, msg): | ||
1328 | 851 | print >>sys.stderr, "Hello from %s" % (msg) | ||
1329 | 852 | # The central instance | ||
1330 | 853 | manager = TestlibManager() | ||
1331 | 854 | |||
1332 | 855 | class TestlibCase(unittest.TestCase): | ||
1333 | 856 | def __init__(self, *args): | ||
1334 | 857 | '''This is called for each TestCase test instance, which isn't much better | ||
1335 | 858 | than SetUp.''' | ||
1336 | 859 | |||
1337 | 860 | unittest.TestCase.__init__(self, *args) | ||
1338 | 861 | |||
1339 | 862 | # Attach to and duplicate dicts from manager singleton | ||
1340 | 863 | self.manager = manager | ||
1341 | 864 | #self.manager.hello(repr(self) + repr(*args)) | ||
1342 | 865 | self.my_verbosity = self.manager.verbosity | ||
1343 | 866 | self.lsb_release = self.manager.lsb_release | ||
1344 | 867 | self.dpkg_arch = self.manager.dpkg_arch | ||
1345 | 868 | self.kernel_version = self.manager.kernel_version | ||
1346 | 869 | self.kernel_version_signature = self.manager.kernel_version_signature | ||
1347 | 870 | self.kernel_version_ubuntu = self.manager.kernel_version_ubuntu | ||
1348 | 871 | self.kernel_is_ubuntu = self.manager.kernel_is_ubuntu | ||
1349 | 872 | self.gcc_version = self.manager.gcc_version | ||
1350 | 873 | self.path_libc = self.manager.path_libc | ||
1351 | 874 | |||
1352 | 875 | def version_compare(self, one, two): | ||
1353 | 876 | return apt_pkg.VersionCompare(one,two) | ||
1354 | 877 | |||
1355 | 878 | def assertFileType(self, filename, filetype): | ||
1356 | 879 | '''Checks the file type of the file specified''' | ||
1357 | 880 | |||
1358 | 881 | (rc, report, out) = self._testlib_shell_cmd(["/usr/bin/file", "-b", filename]) | ||
1359 | 882 | out = out.strip() | ||
1360 | 883 | expected = 0 | ||
1361 | 884 | # Absolutely no idea why this happens on Hardy | ||
1362 | 885 | if self.lsb_release['Release'] == 8.04 and rc == 255 and len(out) > 0: | ||
1363 | 886 | rc = 0 | ||
1364 | 887 | result = 'Got exit code %d, expected %d:\n%s\n' % (rc, expected, report) | ||
1365 | 888 | self.assertEquals(expected, rc, result) | ||
1366 | 889 | |||
1367 | 890 | filetype = '^%s$' % (filetype) | ||
1368 | 891 | result = 'File type reported by file: [%s], expected regex: [%s]\n' % (out, filetype) | ||
1369 | 892 | self.assertNotEquals(None, re.search(filetype, out), result) | ||
1370 | 893 | |||
1371 | 894 | def yank_commonname_from_cert(self, certfile): | ||
1372 | 895 | '''Extract the commonName from a given PEM''' | ||
1373 | 896 | rc, out = cmd(['openssl','asn1parse','-in',certfile]) | ||
1374 | 897 | if rc == 0: | ||
1375 | 898 | ready = False | ||
1376 | 899 | for line in out.splitlines(): | ||
1377 | 900 | if ready: | ||
1378 | 901 | return line.split(':')[-1] | ||
1379 | 902 | if ':commonName' in line: | ||
1380 | 903 | ready = True | ||
1381 | 904 | return socket.getfqdn() | ||
1382 | 905 | |||
1383 | 906 | def announce(self, text): | ||
1384 | 907 | if self.my_verbosity: | ||
1385 | 908 | print >>sys.stdout, "(%s) " % (text), | ||
1386 | 909 | sys.stdout.flush() | ||
1387 | 910 | |||
1388 | 911 | def make_clean(self): | ||
1389 | 912 | rc, output = self.shell_cmd(['make','clean']) | ||
1390 | 913 | self.assertEquals(rc, 0, output) | ||
1391 | 914 | |||
1392 | 915 | def get_makefile_compiler(self): | ||
1393 | 916 | # Find potential compiler name | ||
1394 | 917 | compiler = 'gcc' | ||
1395 | 918 | if os.path.exists('Makefile'): | ||
1396 | 919 | for line in open('Makefile'): | ||
1397 | 920 | if line.startswith('CC') and '=' in line: | ||
1398 | 921 | items = [x.strip() for x in line.split('=')] | ||
1399 | 922 | if items[0] == 'CC': | ||
1400 | 923 | compiler = items[1] | ||
1401 | 924 | break | ||
1402 | 925 | return compiler | ||
1403 | 926 | |||
1404 | 927 | def make_target(self, target, expected=0): | ||
1405 | 928 | '''Compile a target and report output''' | ||
1406 | 929 | |||
1407 | 930 | compiler = self.get_makefile_compiler() | ||
1408 | 931 | rc, output = self.shell_cmd(['make',target]) | ||
1409 | 932 | self.assertEquals(rc, expected, 'rc(%d)!=%d:\n' % (rc, expected) + output) | ||
1410 | 933 | self.assertTrue('%s ' % (compiler) in output, 'Expected "%s":' % (compiler) + output) | ||
1411 | 934 | return output | ||
1412 | 935 | |||
1413 | 936 | # call as return testlib.skipped() | ||
1414 | 937 | def _skipped(self, reason=""): | ||
1415 | 938 | '''Provide a visible way to indicate that a test was skipped''' | ||
1416 | 939 | if reason != "": | ||
1417 | 940 | reason = ': %s' % (reason) | ||
1418 | 941 | self.announce("skipped%s" % (reason)) | ||
1419 | 942 | return False | ||
1420 | 943 | |||
1421 | 944 | def _testlib_shell_cmd(self,args,stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT): | ||
1422 | 945 | argstr = "'" + "', '".join(args).strip() + "'" | ||
1423 | 946 | rc, out = cmd(args,stdin=stdin,stdout=stdout,stderr=stderr) | ||
1424 | 947 | report = 'Command: ' + argstr + '\nOutput:\n' + out | ||
1425 | 948 | return rc, report, out | ||
1426 | 949 | |||
1427 | 950 | def shell_cmd(self, args, stdin=None): | ||
1428 | 951 | return cmd(args,stdin=stdin) | ||
1429 | 952 | |||
1430 | 953 | def assertShellExitEquals(self, expected, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): | ||
1431 | 954 | '''Test a shell command matches a specific exit code''' | ||
1432 | 955 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) | ||
1433 | 956 | result = 'Got exit code %d, expected %d\n' % (rc, expected) | ||
1434 | 957 | self.assertEquals(expected, rc, msg + result + report) | ||
1435 | 958 | |||
1436 | 959 | def assertShellExitNotEquals(self, unwanted, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg=""): | ||
1437 | 960 | '''Test a shell command doesn't match a specific exit code''' | ||
1438 | 961 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) | ||
1439 | 962 | result = 'Got (unwanted) exit code %d\n' % rc | ||
1440 | 963 | self.assertNotEquals(unwanted, rc, msg + result + report) | ||
1441 | 964 | |||
1442 | 965 | def assertShellOutputContains(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False): | ||
1443 | 966 | '''Test a shell command contains a specific output''' | ||
1444 | 967 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) | ||
1445 | 968 | result = 'Got exit code %d. Looking for text "%s"\n' % (rc, text) | ||
1446 | 969 | if not invert: | ||
1447 | 970 | self.assertTrue(text in out, msg + result + report) | ||
1448 | 971 | else: | ||
1449 | 972 | self.assertFalse(text in out, msg + result + report) | ||
1450 | 973 | |||
1451 | 974 | def assertShellOutputEquals(self, text, args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, msg="", invert=False, expected=None): | ||
1452 | 975 | '''Test a shell command matches a specific output''' | ||
1453 | 976 | rc, report, out = self._testlib_shell_cmd(args, stdin=stdin, stdout=stdout, stderr=stderr) | ||
1454 | 977 | result = 'Got exit code %d. Looking for exact text "%s" (%s)\n' % (rc, text, " ".join(args)) | ||
1455 | 978 | if not invert: | ||
1456 | 979 | self.assertEquals(text, out, msg + result + report) | ||
1457 | 980 | else: | ||
1458 | 981 | self.assertNotEquals(text, out, msg + result + report) | ||
1459 | 982 | if expected != None: | ||
1460 | 983 | result = 'Got exit code %d. Expected %d (%s)\n' % (rc, expected, " ".join(args)) | ||
1461 | 984 | self.assertEquals(rc, expected, msg + result + report) | ||
1462 | 985 | |||
1463 | 986 | def _word_find(self, report, content, invert=False): | ||
1464 | 987 | '''Check for a specific string''' | ||
1465 | 988 | if invert: | ||
1466 | 989 | warning = 'Found "%s"\n' % content | ||
1467 | 990 | self.assertTrue(content not in report, warning + report) | ||
1468 | 991 | else: | ||
1469 | 992 | warning = 'Could not find "%s"\n' % content | ||
1470 | 993 | self.assertTrue(content in report, warning + report) | ||
1471 | 994 | |||
1472 | 995 | def _test_sysctl_value(self, path, expected, msg=None, exists=True): | ||
1473 | 996 | sysctl = '/proc/sys/%s' % (path) | ||
1474 | 997 | self.assertEquals(exists, os.path.exists(sysctl), sysctl) | ||
1475 | 998 | value = None | ||
1476 | 999 | if exists: | ||
1477 | 1000 | value = int(file(sysctl).read()) | ||
1478 | 1001 | report = "%s is not %d: %d" % (sysctl, expected, value) | ||
1479 | 1002 | if msg: | ||
1480 | 1003 | report += " (%s)" % (msg) | ||
1481 | 1004 | self.assertEquals(value, expected, report) | ||
1482 | 1005 | return value | ||
1483 | 1006 | |||
1484 | 1007 | def set_sysctl_value(self, path, desired): | ||
1485 | 1008 | sysctl = '/proc/sys/%s' % (path) | ||
1486 | 1009 | self.assertTrue(os.path.exists(sysctl),"%s does not exist" % (sysctl)) | ||
1487 | 1010 | file(sysctl,'w').write(str(desired)) | ||
1488 | 1011 | self._test_sysctl_value(path, desired) | ||
1489 | 1012 | |||
1490 | 1013 | def kernel_at_least(self, introduced): | ||
1491 | 1014 | return self.version_compare(self.kernel_version_ubuntu, | ||
1492 | 1015 | introduced) >= 0 | ||
1493 | 1016 | |||
1494 | 1017 | def kernel_claims_cve_fixed(self, cve): | ||
1495 | 1018 | changelog = "/usr/share/doc/linux-image-%s/changelog.Debian.gz" % (self.kernel_version) | ||
1496 | 1019 | if os.path.exists(changelog): | ||
1497 | 1020 | for line in gzip.open(changelog): | ||
1498 | 1021 | if cve in line and not "revert" in line and not "Revert" in line: | ||
1499 | 1022 | return True | ||
1500 | 1023 | return False | ||
1501 | 1024 | |||
1502 | 1025 | class TestGroup: | ||
1503 | 1026 | '''Create a temporary test group and remove it again in the dtor.''' | ||
1504 | 1027 | |||
1505 | 1028 | def __init__(self, group=None, lower=False): | ||
1506 | 1029 | '''Create a new group''' | ||
1507 | 1030 | |||
1508 | 1031 | self.group = None | ||
1509 | 1032 | if group: | ||
1510 | 1033 | if group_exists(group): | ||
1511 | 1034 | raise ValueError, 'group name already exists' | ||
1512 | 1035 | else: | ||
1513 | 1036 | while(True): | ||
1514 | 1037 | group = random_string(7,lower=lower) | ||
1515 | 1038 | if not group_exists(group): | ||
1516 | 1039 | break | ||
1517 | 1040 | |||
1518 | 1041 | assert subprocess.call(['groupadd',group]) == 0 | ||
1519 | 1042 | self.group = group | ||
1520 | 1043 | g = grp.getgrnam(self.group) | ||
1521 | 1044 | self.gid = g[2] | ||
1522 | 1045 | |||
1523 | 1046 | def __del__(self): | ||
1524 | 1047 | '''Remove the created group.''' | ||
1525 | 1048 | |||
1526 | 1049 | if self.group: | ||
1527 | 1050 | rc, report = cmd(['groupdel', self.group]) | ||
1528 | 1051 | assert rc == 0 | ||
1529 | 1052 | |||
1530 | 1053 | class TestUser: | ||
1531 | 1054 | '''Create a temporary test user and remove it again in the dtor.''' | ||
1532 | 1055 | |||
1533 | 1056 | def __init__(self, login=None, home=True, group=None, uidmin=None, lower=False, shell=None): | ||
1534 | 1057 | '''Create a new user account with a random password. | ||
1535 | 1058 | |||
1536 | 1059 | By default, the login name is random, too, but can be explicitly | ||
1537 | 1060 | specified with 'login'. By default, a home directory is created, this | ||
1538 | 1061 | can be suppressed with 'home=False'.''' | ||
1539 | 1062 | |||
1540 | 1063 | self.login = None | ||
1541 | 1064 | |||
1542 | 1065 | if os.geteuid() != 0: | ||
1543 | 1066 | raise ValueError, "You must be root to run this test" | ||
1544 | 1067 | |||
1545 | 1068 | if login: | ||
1546 | 1069 | if login_exists(login): | ||
1547 | 1070 | raise ValueError, 'login name already exists' | ||
1548 | 1071 | else: | ||
1549 | 1072 | while(True): | ||
1550 | 1073 | login = 't' + random_string(7,lower=lower) | ||
1551 | 1074 | if not login_exists(login): | ||
1552 | 1075 | break | ||
1553 | 1076 | |||
1554 | 1077 | self.salt = random_string(2) | ||
1555 | 1078 | self.password = random_string(8,lower=lower) | ||
1556 | 1079 | self.crypted = crypt.crypt(self.password, self.salt) | ||
1557 | 1080 | |||
1558 | 1081 | creation = ['useradd', '-p', self.crypted] | ||
1559 | 1082 | if home: | ||
1560 | 1083 | creation += ['-m'] | ||
1561 | 1084 | if group: | ||
1562 | 1085 | creation += ['-G',group] | ||
1563 | 1086 | if uidmin: | ||
1564 | 1087 | creation += ['-K','UID_MIN=%d'%uidmin] | ||
1565 | 1088 | if shell: | ||
1566 | 1089 | creation += ['-s',shell] | ||
1567 | 1090 | creation += [login] | ||
1568 | 1091 | assert subprocess.call(creation) == 0 | ||
1569 | 1092 | # Set GECOS | ||
1570 | 1093 | assert subprocess.call(['usermod','-c','Buddy %s' % (login),login]) == 0 | ||
1571 | 1094 | |||
1572 | 1095 | self.login = login | ||
1573 | 1096 | p = pwd.getpwnam(self.login) | ||
1574 | 1097 | self.uid = p[2] | ||
1575 | 1098 | self.gid = p[3] | ||
1576 | 1099 | self.gecos = p[4] | ||
1577 | 1100 | self.home = p[5] | ||
1578 | 1101 | self.shell = p[6] | ||
1579 | 1102 | |||
1580 | 1103 | def __del__(self): | ||
1581 | 1104 | '''Remove the created user account.''' | ||
1582 | 1105 | |||
1583 | 1106 | if self.login: | ||
1584 | 1107 | # sanity check the login name so we don't accidentally wipe too much | ||
1585 | 1108 | if len(self.login)>3 and not '/' in self.login: | ||
1586 | 1109 | subprocess.call(['rm','-rf', '/home/'+self.login, '/var/mail/'+self.login]) | ||
1587 | 1110 | rc, report = cmd(['userdel', '-f', self.login]) | ||
1588 | 1111 | assert rc == 0 | ||
1589 | 1112 | |||
1590 | 1113 | def add_to_group(self, group): | ||
1591 | 1114 | '''Add user to the specified group name''' | ||
1592 | 1115 | rc, report = cmd(['usermod', '-G', group, self.login]) | ||
1593 | 1116 | if rc != 0: | ||
1594 | 1117 | print report | ||
1595 | 1118 | assert rc == 0 | ||
1596 | 1119 | |||
1597 | 1120 | # Timeout handler using alarm() from John P. Speno's Pythonic Avocado | ||
1598 | 1121 | class TimeoutFunctionException(Exception): | ||
1599 | 1122 | """Exception to raise on a timeout""" | ||
1600 | 1123 | pass | ||
1601 | 1124 | class TimeoutFunction: | ||
1602 | 1125 | def __init__(self, function, timeout): | ||
1603 | 1126 | self.timeout = timeout | ||
1604 | 1127 | self.function = function | ||
1605 | 1128 | |||
1606 | 1129 | def handle_timeout(self, signum, frame): | ||
1607 | 1130 | raise TimeoutFunctionException() | ||
1608 | 1131 | |||
1609 | 1132 | def __call__(self, *args, **kwargs): | ||
1610 | 1133 | old = signal.signal(signal.SIGALRM, self.handle_timeout) | ||
1611 | 1134 | signal.alarm(self.timeout) | ||
1612 | 1135 | try: | ||
1613 | 1136 | result = self.function(*args, **kwargs) | ||
1614 | 1137 | finally: | ||
1615 | 1138 | signal.signal(signal.SIGALRM, old) | ||
1616 | 1139 | signal.alarm(0) | ||
1617 | 1140 | return result | ||
1618 | 1141 | |||
1619 | 1142 | def main(): | ||
1620 | 1143 | print "hi" | ||
1621 | 1144 | unittest.main() |
Thanks for your work. Reviewed and tested on the QA infrastructure and it works fine.
On a side note, if several tests from the QRT are going to be ported to DEP8 it would make sense to package testlib separately to avoid code redundancy and make maintenance easier.