Print this page
4206 history_003_pos relies on exact size of history log and entries
4207 history_008_pos depends on obsolete internal history log message
4208 Typo in zfs_main.c: "posxiuser"
4209 Populate zfstest with the remainder of the STF tests
Reviewed by: Sonu Pillai <sonu.pillai@delphix.com>
Reviewed by: Will Guyette <will.guyette@delphix.com>
Reviewed by: Eric Diven <eric.diven@delphix.com>
Reviewed by: Christopher Siden <christopher.siden@delphix.com>
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/test/test-runner/cmd/run.py
+++ new/usr/src/test/test-runner/cmd/run.py
1 1 #!/usr/bin/python2.6
2 2
3 3 #
4 4 # This file and its contents are supplied under the terms of the
↓ open down ↓ |
4 lines elided |
↑ open up ↑ |
5 5 # Common Development and Distribution License ("CDDL"), version 1.0.
6 6 # You may only use this file in accordance with the terms of version
7 7 # 1.0 of the CDDL.
8 8 #
9 9 # A full copy of the text of the CDDL should have accompanied this
10 10 # source. A copy of the CDDL is also available via the Internet at
11 11 # http://www.illumos.org/license/CDDL.
12 12 #
13 13
14 14 #
15 -# Copyright (c) 2012 by Delphix. All rights reserved.
15 +# Copyright (c) 2013 by Delphix. All rights reserved.
16 16 #
17 17
18 18 import ConfigParser
19 19 import os
20 20 import logging
21 21 from datetime import datetime
22 22 from optparse import OptionParser
23 23 from pwd import getpwnam
24 24 from pwd import getpwuid
25 25 from select import select
26 26 from subprocess import PIPE
27 27 from subprocess import Popen
28 28 from sys import argv
29 29 from sys import exit
30 30 from threading import Timer
31 31 from time import time
32 32
33 33 BASEDIR = '/var/tmp/test_results'
34 34 KILL = '/usr/bin/kill'
35 35 TRUE = '/usr/bin/true'
36 36 SUDO = '/usr/bin/sudo'
37 37
38 38
39 39 class Result(object):
40 40 total = 0
41 41 runresults = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0}
42 42
43 43 def __init__(self):
44 44 self.starttime = None
45 45 self.returncode = None
46 46 self.runtime = ''
47 47 self.stdout = []
48 48 self.stderr = []
49 49 self.result = ''
50 50
51 51 def done(self, proc, killed):
52 52 """
53 53 Finalize the results of this Cmd.
54 54 """
55 55 Result.total += 1
56 56 m, s = divmod(time() - self.starttime, 60)
57 57 self.runtime = '%02d:%02d' % (m, s)
58 58 self.returncode = proc.returncode
59 59 if killed:
60 60 self.result = 'KILLED'
61 61 Result.runresults['KILLED'] += 1
62 62 elif self.returncode is 0:
63 63 self.result = 'PASS'
64 64 Result.runresults['PASS'] += 1
65 65 elif self.returncode is not 0:
66 66 self.result = 'FAIL'
67 67 Result.runresults['FAIL'] += 1
68 68
69 69
70 70 class Output(object):
71 71 """
72 72 This class is a slightly modified version of the 'Stream' class found
73 73 here: http://goo.gl/aSGfv
74 74 """
75 75 def __init__(self, stream):
76 76 self.stream = stream
77 77 self._buf = ''
78 78 self.lines = []
79 79
80 80 def fileno(self):
81 81 return self.stream.fileno()
82 82
83 83 def read(self, drain=0):
84 84 """
85 85 Read from the file descriptor. If 'drain' set, read until EOF.
86 86 """
87 87 while self._read() is not None:
88 88 if not drain:
89 89 break
90 90
91 91 def _read(self):
92 92 """
93 93 Read up to 4k of data from this output stream. Collect the output
94 94 up to the last newline, and append it to any leftover data from a
95 95 previous call. The lines are stored as a (timestamp, data) tuple
96 96 for easy sorting/merging later.
97 97 """
98 98 fd = self.fileno()
99 99 buf = os.read(fd, 4096)
100 100 if not buf:
101 101 return None
102 102 if '\n' not in buf:
103 103 self._buf += buf
104 104 return []
105 105
106 106 buf = self._buf + buf
107 107 tmp, rest = buf.rsplit('\n', 1)
108 108 self._buf = rest
109 109 now = datetime.now()
110 110 rows = tmp.split('\n')
111 111 self.lines += [(now, r) for r in rows]
112 112
113 113
114 114 class Cmd(object):
115 115 verified_users = []
116 116
117 117 def __init__(self, pathname, outputdir=None, timeout=None, user=None):
118 118 self.pathname = pathname
119 119 self.outputdir = outputdir or 'BASEDIR'
120 120 self.timeout = timeout or 60
121 121 self.user = user or ''
122 122 self.killed = False
123 123 self.result = Result()
124 124
125 125 def __str__(self):
126 126 return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nUser: %s\n" % (
127 127 self.pathname, self.outputdir, self.timeout, self.user)
128 128
129 129 def kill_cmd(self, proc):
130 130 """
131 131 Kill a running command due to timeout, or ^C from the keyboard. If
132 132 sudo is required, this user was verified previously.
133 133 """
134 134 self.killed = True
135 135 do_sudo = len(self.user) != 0
136 136 signal = '-TERM'
137 137
138 138 cmd = [SUDO, KILL, signal, str(proc.pid)]
139 139 if not do_sudo:
140 140 del cmd[0]
141 141
142 142 try:
143 143 kp = Popen(cmd)
144 144 kp.wait()
145 145 except:
146 146 pass
147 147
148 148 def update_cmd_privs(self, cmd, user):
149 149 """
150 150 If a user has been specified to run this Cmd and we're not already
151 151 running as that user, prepend the appropriate sudo command to run
152 152 as that user.
153 153 """
154 154 me = getpwuid(os.getuid())
155 155
156 156 if not user or user is me:
157 157 return cmd
158 158
159 159 ret = '%s -E -u %s %s' % (SUDO, user, cmd)
160 160 return ret.split(' ')
161 161
162 162 def collect_output(self, proc):
163 163 """
164 164 Read from stdout/stderr as data becomes available, until the
165 165 process is no longer running. Return the lines from the stdout and
166 166 stderr Output objects.
167 167 """
168 168 out = Output(proc.stdout)
169 169 err = Output(proc.stderr)
170 170 res = []
171 171 while proc.returncode is None:
172 172 proc.poll()
173 173 res = select([out, err], [], [], .1)
174 174 for fd in res[0]:
175 175 fd.read()
176 176 for fd in res[0]:
177 177 fd.read(drain=1)
178 178
179 179 return out.lines, err.lines
180 180
181 181 def run(self, options):
182 182 """
183 183 This is the main function that runs each individual test.
184 184 Determine whether or not the command requires sudo, and modify it
185 185 if needed. Run the command, and update the result object.
186 186 """
187 187 if options.dryrun is True:
188 188 print self
189 189 return
190 190
191 191 privcmd = self.update_cmd_privs(self.pathname, self.user)
192 192 try:
193 193 old = os.umask(0)
194 194 if not os.path.isdir(self.outputdir):
195 195 os.makedirs(self.outputdir, mode=0777)
196 196 os.umask(old)
197 197 except OSError, e:
198 198 fail('%s' % e)
199 199
200 200 try:
201 201 self.result.starttime = time()
202 202 proc = Popen(privcmd, stdout=PIPE, stderr=PIPE)
203 203 t = Timer(int(self.timeout), self.kill_cmd, [proc])
204 204 t.start()
205 205 self.result.stdout, self.result.stderr = self.collect_output(proc)
206 206 except KeyboardInterrupt:
207 207 self.kill_cmd(proc)
208 208 fail('\nRun terminated at user request.')
209 209 finally:
210 210 t.cancel()
211 211
212 212 self.result.done(proc, self.killed)
213 213
214 214 def skip(self):
215 215 """
216 216 Initialize enough of the test result that we can log a skipped
217 217 command.
218 218 """
219 219 Result.total += 1
220 220 Result.runresults['SKIP'] += 1
221 221 self.result.stdout = self.result.stderr = []
222 222 self.result.starttime = time()
223 223 m, s = divmod(time() - self.result.starttime, 60)
224 224 self.result.runtime = '%02d:%02d' % (m, s)
225 225 self.result.result = 'SKIP'
226 226
↓ open down ↓ |
201 lines elided |
↑ open up ↑ |
227 227 def log(self, logger, options):
228 228 """
229 229 This function is responsible for writing all output. This includes
230 230 the console output, the logfile of all results (with timestamped
231 231 merged stdout and stderr), and for each test, the unmodified
232 232 stdout/stderr/merged in it's own file.
233 233 """
234 234 if logger is None:
235 235 return
236 236
237 - user = ' (run as %s)' % self.user if len(self.user) else ''
237 + logname = getpwuid(os.getuid()).pw_name
238 + user = ' (run as %s)' % (self.user if len(self.user) else logname)
238 239 msga = 'Test: %s%s ' % (self.pathname, user)
239 240 msgb = '[%s] [%s]' % (self.result.runtime, self.result.result)
240 241 pad = ' ' * (80 - (len(msga) + len(msgb)))
241 242
242 243 # If -q is specified, only print a line for tests that didn't pass.
243 244 # This means passing tests need to be logged as DEBUG, or the one
244 245 # line summary will only be printed in the logfile for failures.
245 246 if not options.quiet:
246 247 logger.info('%s%s%s' % (msga, pad, msgb))
247 248 elif self.result.result is not 'PASS':
248 249 logger.info('%s%s%s' % (msga, pad, msgb))
249 250 else:
250 251 logger.debug('%s%s%s' % (msga, pad, msgb))
251 252
252 253 lines = self.result.stdout + self.result.stderr
253 254 for dt, line in sorted(lines):
254 255 logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line))
255 256
256 257 if len(self.result.stdout):
257 258 with open(os.path.join(self.outputdir, 'stdout'), 'w') as out:
258 259 for _, line in self.result.stdout:
259 260 os.write(out.fileno(), '%s\n' % line)
260 261 if len(self.result.stderr):
261 262 with open(os.path.join(self.outputdir, 'stderr'), 'w') as err:
262 263 for _, line in self.result.stderr:
263 264 os.write(err.fileno(), '%s\n' % line)
264 265 if len(self.result.stdout) and len(self.result.stderr):
265 266 with open(os.path.join(self.outputdir, 'merged'), 'w') as merged:
266 267 for _, line in sorted(lines):
267 268 os.write(merged.fileno(), '%s\n' % line)
268 269
269 270
270 271 class Test(Cmd):
271 272 props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
272 273 'post_user']
273 274
274 275 def __init__(self, pathname, outputdir=None, timeout=None, user=None,
275 276 pre=None, pre_user=None, post=None, post_user=None):
276 277 super(Test, self).__init__(pathname, outputdir, timeout, user)
277 278 self.pre = pre or ''
278 279 self.pre_user = pre_user or ''
279 280 self.post = post or ''
280 281 self.post_user = post_user or ''
281 282
282 283 def __str__(self):
283 284 post_user = pre_user = ''
284 285 if len(self.pre_user):
285 286 pre_user = ' (as %s)' % (self.pre_user)
286 287 if len(self.post_user):
287 288 post_user = ' (as %s)' % (self.post_user)
288 289 return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nPre: %s%s\nPost: " \
289 290 "%s%s\nUser: %s\n" % (self.pathname, self.outputdir,
290 291 self.timeout, self.pre, pre_user, self.post, post_user,
291 292 self.user)
292 293
293 294 def verify(self, logger):
294 295 """
295 296 Check the pre/post scripts, user and Test. Omit the Test from this
296 297 run if there are any problems.
297 298 """
298 299 files = [self.pre, self.pathname, self.post]
299 300 users = [self.pre_user, self.user, self.post_user]
300 301
301 302 for f in [f for f in files if len(f)]:
302 303 if not verify_file(f):
303 304 logger.info("Warning: Test '%s' not added to this run because"
304 305 " it failed verification." % f)
305 306 return False
306 307
307 308 for user in [user for user in users if len(user)]:
308 309 if not verify_user(user, logger):
309 310 logger.info("Not adding Test '%s' to this run." %
310 311 self.pathname)
311 312 return False
312 313
313 314 return True
314 315
315 316 def run(self, logger, options):
316 317 """
317 318 Create Cmd instances for the pre/post scripts. If the pre script
318 319 doesn't pass, skip this Test. Run the post script regardless.
319 320 """
320 321 pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir,
321 322 os.path.basename(self.pre)), timeout=self.timeout,
322 323 user=self.pre_user)
323 324 test = Cmd(self.pathname, outputdir=self.outputdir,
324 325 timeout=self.timeout, user=self.user)
325 326 posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir,
326 327 os.path.basename(self.post)), timeout=self.timeout,
327 328 user=self.post_user)
328 329
329 330 cont = True
330 331 if len(pretest.pathname):
331 332 pretest.run(options)
332 333 cont = pretest.result.result is 'PASS'
333 334 pretest.log(logger, options)
334 335
335 336 if cont:
336 337 test.run(options)
337 338 else:
338 339 test.skip()
339 340
340 341 test.log(logger, options)
341 342
342 343 if len(posttest.pathname):
343 344 posttest.run(options)
344 345 posttest.log(logger, options)
345 346
346 347
347 348 class TestGroup(Test):
348 349 props = Test.props + ['tests']
349 350
350 351 def __init__(self, pathname, outputdir=None, timeout=None, user=None,
351 352 pre=None, pre_user=None, post=None, post_user=None,
352 353 tests=None):
353 354 super(TestGroup, self).__init__(pathname, outputdir, timeout, user,
354 355 pre, pre_user, post, post_user)
355 356 self.tests = tests or []
356 357
357 358 def __str__(self):
358 359 post_user = pre_user = ''
359 360 if len(self.pre_user):
360 361 pre_user = ' (as %s)' % (self.pre_user)
361 362 if len(self.post_user):
362 363 post_user = ' (as %s)' % (self.post_user)
363 364 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \
364 365 "Pre: %s%s\nPost: %s%s\nUser: %s\n" % (self.pathname,
365 366 self.outputdir, self.tests, self.timeout, self.pre, pre_user,
366 367 self.post, post_user, self.user)
367 368
368 369 def verify(self, logger):
369 370 """
370 371 Check the pre/post scripts, user and tests in this TestGroup. Omit
371 372 the TestGroup entirely, or simply delete the relevant tests in the
372 373 group, if that's all that's required.
373 374 """
374 375 # If the pre or post scripts are relative pathnames, convert to
375 376 # absolute, so they stand a chance of passing verification.
376 377 if len(self.pre) and not os.path.isabs(self.pre):
377 378 self.pre = os.path.join(self.pathname, self.pre)
378 379 if len(self.post) and not os.path.isabs(self.post):
379 380 self.post = os.path.join(self.pathname, self.post)
380 381
381 382 auxfiles = [self.pre, self.post]
382 383 users = [self.pre_user, self.user, self.post_user]
383 384
384 385 for f in [f for f in auxfiles if len(f)]:
385 386 if self.pathname != os.path.dirname(f):
386 387 logger.info("Warning: TestGroup '%s' not added to this run. "
387 388 "Auxiliary script '%s' exists in a different "
388 389 "directory." % (self.pathname, f))
389 390 return False
390 391
391 392 if not verify_file(f):
392 393 logger.info("Warning: TestGroup '%s' not added to this run. "
393 394 "Auxiliary script '%s' failed verification." %
394 395 (self.pathname, f))
395 396 return False
396 397
397 398 for user in [user for user in users if len(user)]:
398 399 if not verify_user(user, logger):
399 400 logger.info("Not adding TestGroup '%s' to this run." %
400 401 self.pathname)
401 402 return False
402 403
403 404 # If one of the tests is invalid, delete it, log it, and drive on.
404 405 for test in self.tests:
405 406 if not verify_file(os.path.join(self.pathname, test)):
406 407 del self.tests[self.tests.index(test)]
407 408 logger.info("Warning: Test '%s' removed from TestGroup '%s' "
408 409 "because it failed verification." % (test,
409 410 self.pathname))
410 411
411 412 return len(self.tests) is not 0
412 413
413 414 def run(self, logger, options):
414 415 """
415 416 Create Cmd instances for the pre/post scripts. If the pre script
416 417 doesn't pass, skip all the tests in this TestGroup. Run the post
417 418 script regardless.
418 419 """
419 420 pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir,
420 421 os.path.basename(self.pre)), timeout=self.timeout,
421 422 user=self.pre_user)
422 423 posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir,
423 424 os.path.basename(self.post)), timeout=self.timeout,
424 425 user=self.post_user)
425 426
426 427 cont = True
427 428 if len(pretest.pathname):
428 429 pretest.run(options)
429 430 cont = pretest.result.result is 'PASS'
430 431 pretest.log(logger, options)
431 432
432 433 for fname in self.tests:
433 434 test = Cmd(os.path.join(self.pathname, fname),
434 435 outputdir=os.path.join(self.outputdir, fname),
435 436 timeout=self.timeout, user=self.user)
436 437 if cont:
437 438 test.run(options)
438 439 else:
439 440 test.skip()
440 441
441 442 test.log(logger, options)
442 443
443 444 if len(posttest.pathname):
444 445 posttest.run(options)
445 446 posttest.log(logger, options)
446 447
447 448
448 449 class TestRun(object):
449 450 props = ['quiet', 'outputdir']
450 451
451 452 def __init__(self, options):
452 453 self.tests = {}
453 454 self.testgroups = {}
454 455 self.starttime = time()
455 456 self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S')
456 457 self.outputdir = os.path.join(options.outputdir, self.timestamp)
457 458 self.logger = self.setup_logging(options)
458 459 self.defaults = [
459 460 ('outputdir', BASEDIR),
460 461 ('quiet', False),
461 462 ('timeout', 60),
462 463 ('user', ''),
463 464 ('pre', ''),
464 465 ('pre_user', ''),
465 466 ('post', ''),
466 467 ('post_user', '')
467 468 ]
468 469
469 470 def __str__(self):
470 471 s = 'TestRun:\n outputdir: %s\n' % self.outputdir
471 472 s += 'TESTS:\n'
472 473 for key in sorted(self.tests.keys()):
473 474 s += '%s%s' % (self.tests[key].__str__(), '\n')
474 475 s += 'TESTGROUPS:\n'
475 476 for key in sorted(self.testgroups.keys()):
476 477 s += '%s%s' % (self.testgroups[key].__str__(), '\n')
477 478 return s
478 479
479 480 def addtest(self, pathname, options):
480 481 """
481 482 Create a new Test, and apply any properties that were passed in
482 483 from the command line. If it passes verification, add it to the
483 484 TestRun.
484 485 """
485 486 test = Test(pathname)
486 487 for prop in Test.props:
487 488 setattr(test, prop, getattr(options, prop))
488 489
489 490 if test.verify(self.logger):
490 491 self.tests[pathname] = test
491 492
492 493 def addtestgroup(self, dirname, filenames, options):
493 494 """
494 495 Create a new TestGroup, and apply any properties that were passed
495 496 in from the command line. If it passes verification, add it to the
496 497 TestRun.
497 498 """
498 499 if dirname not in self.testgroups:
499 500 testgroup = TestGroup(dirname)
500 501 for prop in Test.props:
501 502 setattr(testgroup, prop, getattr(options, prop))
502 503
503 504 # Prevent pre/post scripts from running as regular tests
504 505 for f in [testgroup.pre, testgroup.post]:
505 506 if f in filenames:
506 507 del filenames[filenames.index(f)]
507 508
508 509 self.testgroups[dirname] = testgroup
509 510 self.testgroups[dirname].tests = sorted(filenames)
510 511
511 512 testgroup.verify(self.logger)
512 513
513 514 def read(self, logger, options):
514 515 """
515 516 Read in the specified runfile, and apply the TestRun properties
516 517 listed in the 'DEFAULT' section to our TestRun. Then read each
517 518 section, and apply the appropriate properties to the Test or
518 519 TestGroup. Properties from individual sections override those set
519 520 in the 'DEFAULT' section. If the Test or TestGroup passes
520 521 verification, add it to the TestRun.
521 522 """
522 523 config = ConfigParser.RawConfigParser()
523 524 if not len(config.read(options.runfile)):
524 525 fail("Coulnd't read config file %s" % options.runfile)
525 526
526 527 for opt in TestRun.props:
527 528 if config.has_option('DEFAULT', opt):
528 529 setattr(self, opt, config.get('DEFAULT', opt))
529 530 self.outputdir = os.path.join(self.outputdir, self.timestamp)
530 531
531 532 for section in config.sections():
532 533 if 'tests' in config.options(section):
533 534 testgroup = TestGroup(section)
534 535 for prop in TestGroup.props:
535 536 try:
536 537 setattr(testgroup, prop, config.get('DEFAULT', prop))
537 538 setattr(testgroup, prop, config.get(section, prop))
538 539 except ConfigParser.NoOptionError:
539 540 pass
540 541
541 542 # Repopulate tests using eval to convert the string to a list
542 543 testgroup.tests = eval(config.get(section, 'tests'))
543 544
544 545 if testgroup.verify(logger):
545 546 self.testgroups[section] = testgroup
546 547 else:
547 548 test = Test(section)
548 549 for prop in Test.props:
549 550 try:
550 551 setattr(test, prop, config.get('DEFAULT', prop))
551 552 setattr(test, prop, config.get(section, prop))
552 553 except ConfigParser.NoOptionError:
553 554 pass
554 555 if test.verify(logger):
555 556 self.tests[section] = test
556 557
557 558 def write(self, options):
558 559 """
559 560 Create a configuration file for editing and later use. The
560 561 'DEFAULT' section of the config file is created from the
561 562 properties that were specified on the command line. Tests are
562 563 simply added as sections that inherit everything from the
563 564 'DEFAULT' section. TestGroups are the same, except they get an
564 565 option including all the tests to run in that directory.
565 566 """
566 567
567 568 defaults = dict([(prop, getattr(options, prop)) for prop, _ in
568 569 self.defaults])
569 570 config = ConfigParser.RawConfigParser(defaults)
570 571
571 572 for test in sorted(self.tests.keys()):
572 573 config.add_section(test)
573 574
574 575 for testgroup in sorted(self.testgroups.keys()):
575 576 config.add_section(testgroup)
576 577 config.set(testgroup, 'tests', self.testgroups[testgroup].tests)
577 578
578 579 try:
579 580 with open(options.template, 'w') as f:
580 581 return config.write(f)
581 582 except IOError:
582 583 fail('Could not open \'%s\' for writing.' % options.template)
583 584
584 585 def complete_outputdirs(self, options):
585 586 """
586 587 Collect all the pathnames for Tests, and TestGroups. Work
587 588 backwards one pathname component at a time, to create a unique
588 589 directory name in which to deposit test output. Tests will be able
589 590 to write output files directly in the newly modified outputdir.
590 591 TestGroups will be able to create one subdirectory per test in the
591 592 outputdir, and are guaranteed uniqueness because a group can only
592 593 contain files in one directory. Pre and post tests will create a
593 594 directory rooted at the outputdir of the Test or TestGroup in
594 595 question for their output.
595 596 """
596 597 done = False
597 598 components = 0
598 599 tmp_dict = dict(self.tests.items() + self.testgroups.items())
599 600 total = len(tmp_dict)
600 601 base = self.outputdir
601 602
602 603 while not done:
603 604 l = []
604 605 components -= 1
605 606 for testfile in tmp_dict.keys():
606 607 uniq = '/'.join(testfile.split('/')[components:]).lstrip('/')
607 608 if not uniq in l:
608 609 l.append(uniq)
609 610 tmp_dict[testfile].outputdir = os.path.join(base, uniq)
610 611 else:
611 612 break
612 613 done = total == len(l)
613 614
614 615 def setup_logging(self, options):
615 616 """
616 617 Two loggers are set up here. The first is for the logfile which
617 618 will contain one line summarizing the test, including the test
618 619 name, result, and running time. This logger will also capture the
619 620 timestamped combined stdout and stderr of each run. The second
620 621 logger is optional console output, which will contain only the one
621 622 line summary. The loggers are initialized at two different levels
622 623 to facilitate segregating the output.
623 624 """
624 625 if options.dryrun is True:
625 626 return
626 627
627 628 testlogger = logging.getLogger(__name__)
628 629 testlogger.setLevel(logging.DEBUG)
629 630
630 631 if options.cmd is not 'wrconfig':
631 632 try:
632 633 old = os.umask(0)
633 634 os.makedirs(self.outputdir, mode=0777)
634 635 os.umask(old)
635 636 except OSError, e:
636 637 fail('%s' % e)
637 638 filename = os.path.join(self.outputdir, 'log')
638 639
639 640 logfile = logging.FileHandler(filename)
640 641 logfile.setLevel(logging.DEBUG)
641 642 logfilefmt = logging.Formatter('%(message)s')
642 643 logfile.setFormatter(logfilefmt)
643 644 testlogger.addHandler(logfile)
644 645
645 646 cons = logging.StreamHandler()
646 647 cons.setLevel(logging.INFO)
647 648 consfmt = logging.Formatter('%(message)s')
648 649 cons.setFormatter(consfmt)
649 650 testlogger.addHandler(cons)
650 651
651 652 return testlogger
652 653
653 654 def run(self, options):
654 655 """
655 656 Walk through all the Tests and TestGroups, calling run().
656 657 """
657 658 try:
658 659 os.chdir(self.outputdir)
659 660 except OSError:
660 661 fail('Could not change to directory %s' % self.outputdir)
661 662 for test in sorted(self.tests.keys()):
662 663 self.tests[test].run(self.logger, options)
663 664 for testgroup in sorted(self.testgroups.keys()):
664 665 self.testgroups[testgroup].run(self.logger, options)
665 666
666 667 def summary(self):
667 668 if Result.total is 0:
668 669 return
669 670
670 671 print '\nResults Summary'
671 672 for key in Result.runresults.keys():
672 673 if Result.runresults[key] is not 0:
673 674 print '%s\t% 4d' % (key, Result.runresults[key])
674 675
675 676 m, s = divmod(time() - self.starttime, 60)
676 677 h, m = divmod(m, 60)
677 678 print '\nRunning Time:\t%02d:%02d:%02d' % (h, m, s)
678 679 print 'Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) /
679 680 float(Result.total)) * 100)
680 681 print 'Log directory:\t%s' % self.outputdir
681 682
682 683
683 684 def verify_file(pathname):
684 685 """
685 686 Verify that the supplied pathname is an executable regular file.
686 687 """
687 688 if os.path.isdir(pathname) or os.path.islink(pathname):
688 689 return False
689 690
690 691 if os.path.isfile(pathname) and os.access(pathname, os.X_OK):
691 692 return True
692 693
693 694 return False
694 695
695 696
696 697 def verify_user(user, logger):
697 698 """
698 699 Verify that the specified user exists on this system, and can execute
699 700 sudo without being prompted for a password.
700 701 """
701 702 testcmd = [SUDO, '-n', '-u', user, TRUE]
702 703 can_sudo = exists = True
703 704
704 705 if user in Cmd.verified_users:
705 706 return True
706 707
707 708 try:
708 709 _ = getpwnam(user)
709 710 except KeyError:
710 711 exists = False
711 712 logger.info("Warning: user '%s' does not exist.", user)
712 713 return False
713 714
714 715 p = Popen(testcmd)
715 716 p.wait()
716 717 if p.returncode is not 0:
717 718 logger.info("Warning: user '%s' cannot use passwordless sudo.", user)
718 719 return False
719 720 else:
720 721 Cmd.verified_users.append(user)
721 722
722 723 return True
723 724
724 725
725 726 def find_tests(testrun, options):
726 727 """
727 728 For the given list of pathnames, add files as Tests. For directories,
728 729 if do_groups is True, add the directory as a TestGroup. If False,
729 730 recursively search for executable files.
730 731 """
731 732
732 733 for p in sorted(options.pathnames):
733 734 if os.path.isdir(p):
734 735 for dirname, _, filenames in os.walk(p):
735 736 if options.do_groups:
736 737 testrun.addtestgroup(dirname, filenames, options)
737 738 else:
738 739 for f in sorted(filenames):
739 740 testrun.addtest(os.path.join(dirname, f), options)
740 741 else:
741 742 testrun.addtest(p, options)
742 743
743 744
744 745 def fail(retstr, ret=1):
745 746 print '%s: %s' % (argv[0], retstr)
746 747 exit(ret)
747 748
748 749
749 750 def options_cb(option, opt_str, value, parser):
750 751 path_options = ['runfile', 'outputdir', 'template']
751 752
752 753 if option.dest is 'runfile' and '-w' in parser.rargs or \
753 754 option.dest is 'template' and '-c' in parser.rargs:
754 755 fail('-c and -w are mutually exclusive.')
755 756
756 757 if opt_str in parser.rargs:
757 758 fail('%s may only be specified once.' % opt_str)
758 759
759 760 if option.dest is 'runfile':
760 761 parser.values.cmd = 'rdconfig'
761 762 if option.dest is 'template':
762 763 parser.values.cmd = 'wrconfig'
763 764
764 765 setattr(parser.values, option.dest, value)
765 766 if option.dest in path_options:
766 767 setattr(parser.values, option.dest, os.path.abspath(value))
767 768
768 769
769 770 def parse_args():
770 771 parser = OptionParser()
771 772 parser.add_option('-c', action='callback', callback=options_cb,
772 773 type='string', dest='runfile', metavar='runfile',
773 774 help='Specify tests to run via config file.')
774 775 parser.add_option('-d', action='store_true', default=False, dest='dryrun',
775 776 help='Dry run. Print tests, but take no other action.')
776 777 parser.add_option('-g', action='store_true', default=False,
777 778 dest='do_groups', help='Make directories TestGroups.')
778 779 parser.add_option('-o', action='callback', callback=options_cb,
779 780 default=BASEDIR, dest='outputdir', type='string',
780 781 metavar='outputdir', help='Specify an output directory.')
781 782 parser.add_option('-p', action='callback', callback=options_cb,
782 783 default='', dest='pre', metavar='script',
783 784 type='string', help='Specify a pre script.')
784 785 parser.add_option('-P', action='callback', callback=options_cb,
785 786 default='', dest='post', metavar='script',
786 787 type='string', help='Specify a post script.')
787 788 parser.add_option('-q', action='store_true', default=False, dest='quiet',
788 789 help='Silence on the console during a test run.')
789 790 parser.add_option('-t', action='callback', callback=options_cb, default=60,
790 791 dest='timeout', metavar='seconds', type='int',
791 792 help='Timeout (in seconds) for an individual test.')
792 793 parser.add_option('-u', action='callback', callback=options_cb,
793 794 default='', dest='user', metavar='user', type='string',
794 795 help='Specify a different user name to run as.')
795 796 parser.add_option('-w', action='callback', callback=options_cb,
796 797 default=None, dest='template', metavar='template',
797 798 type='string', help='Create a new config file.')
798 799 parser.add_option('-x', action='callback', callback=options_cb, default='',
799 800 dest='pre_user', metavar='pre_user', type='string',
800 801 help='Specify a user to execute the pre script.')
801 802 parser.add_option('-X', action='callback', callback=options_cb, default='',
802 803 dest='post_user', metavar='post_user', type='string',
803 804 help='Specify a user to execute the post script.')
804 805 (options, pathnames) = parser.parse_args()
805 806
806 807 if not options.runfile and not options.template:
807 808 options.cmd = 'runtests'
808 809
809 810 if options.runfile and len(pathnames):
810 811 fail('Extraneous arguments.')
811 812
812 813 options.pathnames = [os.path.abspath(path) for path in pathnames]
813 814
814 815 return options
815 816
816 817
817 818 def main(args):
818 819 options = parse_args()
819 820 testrun = TestRun(options)
820 821
821 822 if options.cmd is 'runtests':
822 823 find_tests(testrun, options)
823 824 elif options.cmd is 'rdconfig':
824 825 testrun.read(testrun.logger, options)
825 826 elif options.cmd is 'wrconfig':
826 827 find_tests(testrun, options)
827 828 testrun.write(options)
828 829 exit(0)
829 830 else:
830 831 fail('Unknown command specified')
831 832
832 833 testrun.complete_outputdirs(options)
833 834 testrun.run(options)
834 835 testrun.summary()
835 836 exit(0)
836 837
837 838
838 839 if __name__ == '__main__':
839 840 main(argv[1:])
↓ open down ↓ |
592 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX