Print this page
3830 SIGQUEUE_MAX's limit of 32 is too low
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
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 15 # Copyright (c) 2012 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
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 237 user = ' (run as %s)' % self.user if len(self.user) else ''
238 238 msga = 'Test: %s%s ' % (self.pathname, user)
239 239 msgb = '[%s] [%s]' % (self.result.runtime, self.result.result)
240 240 pad = ' ' * (80 - (len(msga) + len(msgb)))
241 241
242 242 # If -q is specified, only print a line for tests that didn't pass.
243 243 # This means passing tests need to be logged as DEBUG, or the one
244 244 # line summary will only be printed in the logfile for failures.
245 245 if not options.quiet:
246 246 logger.info('%s%s%s' % (msga, pad, msgb))
247 247 elif self.result.result is not 'PASS':
248 248 logger.info('%s%s%s' % (msga, pad, msgb))
249 249 else:
250 250 logger.debug('%s%s%s' % (msga, pad, msgb))
251 251
252 252 lines = self.result.stdout + self.result.stderr
253 253 for dt, line in sorted(lines):
254 254 logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line))
255 255
256 256 if len(self.result.stdout):
257 257 with open(os.path.join(self.outputdir, 'stdout'), 'w') as out:
258 258 for _, line in self.result.stdout:
259 259 os.write(out.fileno(), '%s\n' % line)
260 260 if len(self.result.stderr):
261 261 with open(os.path.join(self.outputdir, 'stderr'), 'w') as err:
262 262 for _, line in self.result.stderr:
263 263 os.write(err.fileno(), '%s\n' % line)
264 264 if len(self.result.stdout) and len(self.result.stderr):
265 265 with open(os.path.join(self.outputdir, 'merged'), 'w') as merged:
266 266 for _, line in sorted(lines):
267 267 os.write(merged.fileno(), '%s\n' % line)
268 268
269 269
270 270 class Test(Cmd):
271 271 props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
272 272 'post_user']
273 273
274 274 def __init__(self, pathname, outputdir=None, timeout=None, user=None,
275 275 pre=None, pre_user=None, post=None, post_user=None):
276 276 super(Test, self).__init__(pathname, outputdir, timeout, user)
277 277 self.pre = pre or ''
278 278 self.pre_user = pre_user or ''
279 279 self.post = post or ''
280 280 self.post_user = post_user or ''
281 281
282 282 def __str__(self):
283 283 post_user = pre_user = ''
284 284 if len(self.pre_user):
285 285 pre_user = ' (as %s)' % (self.pre_user)
286 286 if len(self.post_user):
287 287 post_user = ' (as %s)' % (self.post_user)
288 288 return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nPre: %s%s\nPost: " \
289 289 "%s%s\nUser: %s\n" % (self.pathname, self.outputdir,
290 290 self.timeout, self.pre, pre_user, self.post, post_user,
291 291 self.user)
292 292
293 293 def verify(self, logger):
294 294 """
295 295 Check the pre/post scripts, user and Test. Omit the Test from this
296 296 run if there are any problems.
297 297 """
298 298 files = [self.pre, self.pathname, self.post]
299 299 users = [self.pre_user, self.user, self.post_user]
300 300
301 301 for f in [f for f in files if len(f)]:
302 302 if not verify_file(f):
303 303 logger.info("Warning: Test '%s' not added to this run because"
304 304 " it failed verification." % f)
305 305 return False
306 306
307 307 for user in [user for user in users if len(user)]:
308 308 if not verify_user(user, logger):
309 309 logger.info("Not adding Test '%s' to this run." %
310 310 self.pathname)
311 311 return False
312 312
313 313 return True
314 314
315 315 def run(self, logger, options):
316 316 """
317 317 Create Cmd instances for the pre/post scripts. If the pre script
318 318 doesn't pass, skip this Test. Run the post script regardless.
319 319 """
320 320 pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir,
321 321 os.path.basename(self.pre)), timeout=self.timeout,
322 322 user=self.pre_user)
323 323 test = Cmd(self.pathname, outputdir=self.outputdir,
324 324 timeout=self.timeout, user=self.user)
325 325 posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir,
326 326 os.path.basename(self.post)), timeout=self.timeout,
327 327 user=self.post_user)
328 328
329 329 cont = True
330 330 if len(pretest.pathname):
331 331 pretest.run(options)
332 332 cont = pretest.result.result is 'PASS'
333 333 pretest.log(logger, options)
334 334
335 335 if cont:
336 336 test.run(options)
337 337 else:
338 338 test.skip()
339 339
340 340 test.log(logger, options)
341 341
342 342 if len(posttest.pathname):
343 343 posttest.run(options)
344 344 posttest.log(logger, options)
345 345
346 346
347 347 class TestGroup(Test):
348 348 props = Test.props + ['tests']
349 349
350 350 def __init__(self, pathname, outputdir=None, timeout=None, user=None,
351 351 pre=None, pre_user=None, post=None, post_user=None,
352 352 tests=None):
353 353 super(TestGroup, self).__init__(pathname, outputdir, timeout, user,
354 354 pre, pre_user, post, post_user)
355 355 self.tests = tests or []
356 356
357 357 def __str__(self):
358 358 post_user = pre_user = ''
359 359 if len(self.pre_user):
360 360 pre_user = ' (as %s)' % (self.pre_user)
361 361 if len(self.post_user):
362 362 post_user = ' (as %s)' % (self.post_user)
363 363 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \
364 364 "Pre: %s%s\nPost: %s%s\nUser: %s\n" % (self.pathname,
365 365 self.outputdir, self.tests, self.timeout, self.pre, pre_user,
366 366 self.post, post_user, self.user)
367 367
368 368 def verify(self, logger):
369 369 """
370 370 Check the pre/post scripts, user and tests in this TestGroup. Omit
371 371 the TestGroup entirely, or simply delete the relevant tests in the
372 372 group, if that's all that's required.
373 373 """
374 374 # If the pre or post scripts are relative pathnames, convert to
375 375 # absolute, so they stand a chance of passing verification.
376 376 if len(self.pre) and not os.path.isabs(self.pre):
377 377 self.pre = os.path.join(self.pathname, self.pre)
378 378 if len(self.post) and not os.path.isabs(self.post):
379 379 self.post = os.path.join(self.pathname, self.post)
380 380
381 381 auxfiles = [self.pre, self.post]
382 382 users = [self.pre_user, self.user, self.post_user]
383 383
384 384 for f in [f for f in auxfiles if len(f)]:
385 385 if self.pathname != os.path.dirname(f):
386 386 logger.info("Warning: TestGroup '%s' not added to this run. "
387 387 "Auxiliary script '%s' exists in a different "
388 388 "directory." % (self.pathname, f))
389 389 return False
390 390
391 391 if not verify_file(f):
392 392 logger.info("Warning: TestGroup '%s' not added to this run. "
393 393 "Auxiliary script '%s' failed verification." %
394 394 (self.pathname, f))
395 395 return False
396 396
397 397 for user in [user for user in users if len(user)]:
398 398 if not verify_user(user, logger):
399 399 logger.info("Not adding TestGroup '%s' to this run." %
400 400 self.pathname)
401 401 return False
402 402
403 403 # If one of the tests is invalid, delete it, log it, and drive on.
404 404 for test in self.tests:
405 405 if not verify_file(os.path.join(self.pathname, test)):
406 406 del self.tests[self.tests.index(test)]
407 407 logger.info("Warning: Test '%s' removed from TestGroup '%s' "
408 408 "because it failed verification." % (test,
409 409 self.pathname))
410 410
411 411 return len(self.tests) is not 0
412 412
413 413 def run(self, logger, options):
414 414 """
415 415 Create Cmd instances for the pre/post scripts. If the pre script
416 416 doesn't pass, skip all the tests in this TestGroup. Run the post
417 417 script regardless.
418 418 """
419 419 pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir,
420 420 os.path.basename(self.pre)), timeout=self.timeout,
421 421 user=self.pre_user)
422 422 posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir,
423 423 os.path.basename(self.post)), timeout=self.timeout,
424 424 user=self.post_user)
425 425
426 426 cont = True
427 427 if len(pretest.pathname):
428 428 pretest.run(options)
429 429 cont = pretest.result.result is 'PASS'
430 430 pretest.log(logger, options)
431 431
432 432 for fname in self.tests:
433 433 test = Cmd(os.path.join(self.pathname, fname),
434 434 outputdir=os.path.join(self.outputdir, fname),
435 435 timeout=self.timeout, user=self.user)
436 436 if cont:
437 437 test.run(options)
438 438 else:
439 439 test.skip()
440 440
441 441 test.log(logger, options)
442 442
443 443 if len(posttest.pathname):
444 444 posttest.run(options)
445 445 posttest.log(logger, options)
446 446
447 447
448 448 class TestRun(object):
449 449 props = ['quiet', 'outputdir']
450 450
451 451 def __init__(self, options):
452 452 self.tests = {}
453 453 self.testgroups = {}
454 454 self.starttime = time()
455 455 self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S')
456 456 self.outputdir = os.path.join(options.outputdir, self.timestamp)
457 457 self.logger = self.setup_logging(options)
458 458 self.defaults = [
459 459 ('outputdir', BASEDIR),
460 460 ('quiet', False),
461 461 ('timeout', 60),
462 462 ('user', ''),
463 463 ('pre', ''),
464 464 ('pre_user', ''),
465 465 ('post', ''),
466 466 ('post_user', '')
467 467 ]
468 468
469 469 def __str__(self):
470 470 s = 'TestRun:\n outputdir: %s\n' % self.outputdir
471 471 s += 'TESTS:\n'
472 472 for key in sorted(self.tests.keys()):
473 473 s += '%s%s' % (self.tests[key].__str__(), '\n')
474 474 s += 'TESTGROUPS:\n'
475 475 for key in sorted(self.testgroups.keys()):
476 476 s += '%s%s' % (self.testgroups[key].__str__(), '\n')
477 477 return s
478 478
479 479 def addtest(self, pathname, options):
480 480 """
481 481 Create a new Test, and apply any properties that were passed in
482 482 from the command line. If it passes verification, add it to the
483 483 TestRun.
484 484 """
485 485 test = Test(pathname)
486 486 for prop in Test.props:
487 487 setattr(test, prop, getattr(options, prop))
488 488
489 489 if test.verify(self.logger):
490 490 self.tests[pathname] = test
491 491
492 492 def addtestgroup(self, dirname, filenames, options):
493 493 """
494 494 Create a new TestGroup, and apply any properties that were passed
495 495 in from the command line. If it passes verification, add it to the
496 496 TestRun.
497 497 """
498 498 if dirname not in self.testgroups:
499 499 testgroup = TestGroup(dirname)
500 500 for prop in Test.props:
501 501 setattr(testgroup, prop, getattr(options, prop))
502 502
503 503 # Prevent pre/post scripts from running as regular tests
504 504 for f in [testgroup.pre, testgroup.post]:
505 505 if f in filenames:
506 506 del filenames[filenames.index(f)]
507 507
508 508 self.testgroups[dirname] = testgroup
509 509 self.testgroups[dirname].tests = sorted(filenames)
510 510
511 511 testgroup.verify(self.logger)
512 512
513 513 def read(self, logger, options):
514 514 """
515 515 Read in the specified runfile, and apply the TestRun properties
516 516 listed in the 'DEFAULT' section to our TestRun. Then read each
517 517 section, and apply the appropriate properties to the Test or
518 518 TestGroup. Properties from individual sections override those set
519 519 in the 'DEFAULT' section. If the Test or TestGroup passes
520 520 verification, add it to the TestRun.
521 521 """
522 522 config = ConfigParser.RawConfigParser()
523 523 if not len(config.read(options.runfile)):
524 524 fail("Coulnd't read config file %s" % options.runfile)
525 525
526 526 for opt in TestRun.props:
527 527 if config.has_option('DEFAULT', opt):
528 528 setattr(self, opt, config.get('DEFAULT', opt))
529 529 self.outputdir = os.path.join(self.outputdir, self.timestamp)
530 530
531 531 for section in config.sections():
532 532 if 'tests' in config.options(section):
533 533 testgroup = TestGroup(section)
534 534 for prop in TestGroup.props:
535 535 try:
536 536 setattr(testgroup, prop, config.get('DEFAULT', prop))
537 537 setattr(testgroup, prop, config.get(section, prop))
538 538 except ConfigParser.NoOptionError:
539 539 pass
540 540
541 541 # Repopulate tests using eval to convert the string to a list
542 542 testgroup.tests = eval(config.get(section, 'tests'))
543 543
544 544 if testgroup.verify(logger):
545 545 self.testgroups[section] = testgroup
546 546 else:
547 547 test = Test(section)
548 548 for prop in Test.props:
549 549 try:
550 550 setattr(test, prop, config.get('DEFAULT', prop))
551 551 setattr(test, prop, config.get(section, prop))
552 552 except ConfigParser.NoOptionError:
553 553 pass
554 554 if test.verify(logger):
555 555 self.tests[section] = test
556 556
557 557 def write(self, options):
558 558 """
559 559 Create a configuration file for editing and later use. The
560 560 'DEFAULT' section of the config file is created from the
561 561 properties that were specified on the command line. Tests are
562 562 simply added as sections that inherit everything from the
563 563 'DEFAULT' section. TestGroups are the same, except they get an
564 564 option including all the tests to run in that directory.
565 565 """
566 566
567 567 defaults = dict([(prop, getattr(options, prop)) for prop, _ in
568 568 self.defaults])
569 569 config = ConfigParser.RawConfigParser(defaults)
570 570
571 571 for test in sorted(self.tests.keys()):
572 572 config.add_section(test)
573 573
574 574 for testgroup in sorted(self.testgroups.keys()):
575 575 config.add_section(testgroup)
576 576 config.set(testgroup, 'tests', self.testgroups[testgroup].tests)
577 577
578 578 try:
579 579 with open(options.template, 'w') as f:
580 580 return config.write(f)
581 581 except IOError:
582 582 fail('Could not open \'%s\' for writing.' % options.template)
583 583
584 584 def complete_outputdirs(self, options):
585 585 """
586 586 Collect all the pathnames for Tests, and TestGroups. Work
587 587 backwards one pathname component at a time, to create a unique
588 588 directory name in which to deposit test output. Tests will be able
589 589 to write output files directly in the newly modified outputdir.
590 590 TestGroups will be able to create one subdirectory per test in the
591 591 outputdir, and are guaranteed uniqueness because a group can only
592 592 contain files in one directory. Pre and post tests will create a
593 593 directory rooted at the outputdir of the Test or TestGroup in
594 594 question for their output.
595 595 """
596 596 done = False
597 597 components = 0
598 598 tmp_dict = dict(self.tests.items() + self.testgroups.items())
599 599 total = len(tmp_dict)
600 600 base = self.outputdir
601 601
602 602 while not done:
603 603 l = []
604 604 components -= 1
605 605 for testfile in tmp_dict.keys():
606 606 uniq = '/'.join(testfile.split('/')[components:]).lstrip('/')
607 607 if not uniq in l:
608 608 l.append(uniq)
609 609 tmp_dict[testfile].outputdir = os.path.join(base, uniq)
610 610 else:
611 611 break
612 612 done = total == len(l)
613 613
614 614 def setup_logging(self, options):
615 615 """
616 616 Two loggers are set up here. The first is for the logfile which
617 617 will contain one line summarizing the test, including the test
618 618 name, result, and running time. This logger will also capture the
619 619 timestamped combined stdout and stderr of each run. The second
620 620 logger is optional console output, which will contain only the one
621 621 line summary. The loggers are initialized at two different levels
622 622 to facilitate segregating the output.
623 623 """
624 624 if options.dryrun is True:
625 625 return
626 626
627 627 testlogger = logging.getLogger(__name__)
628 628 testlogger.setLevel(logging.DEBUG)
629 629
630 630 if options.cmd is not 'wrconfig':
631 631 try:
632 632 old = os.umask(0)
633 633 os.makedirs(self.outputdir, mode=0777)
634 634 os.umask(old)
635 635 except OSError, e:
636 636 fail('%s' % e)
637 637 filename = os.path.join(self.outputdir, 'log')
638 638
639 639 logfile = logging.FileHandler(filename)
640 640 logfile.setLevel(logging.DEBUG)
641 641 logfilefmt = logging.Formatter('%(message)s')
642 642 logfile.setFormatter(logfilefmt)
643 643 testlogger.addHandler(logfile)
644 644
645 645 cons = logging.StreamHandler()
646 646 cons.setLevel(logging.INFO)
647 647 consfmt = logging.Formatter('%(message)s')
648 648 cons.setFormatter(consfmt)
649 649 testlogger.addHandler(cons)
650 650
651 651 return testlogger
652 652
653 653 def run(self, options):
654 654 """
655 655 Walk through all the Tests and TestGroups, calling run().
656 656 """
657 657 try:
658 658 os.chdir(self.outputdir)
659 659 except OSError:
660 660 fail('Could not change to directory %s' % self.outputdir)
661 661 for test in sorted(self.tests.keys()):
662 662 self.tests[test].run(self.logger, options)
663 663 for testgroup in sorted(self.testgroups.keys()):
664 664 self.testgroups[testgroup].run(self.logger, options)
665 665
666 666 def summary(self):
667 667 if Result.total is 0:
668 668 return
669 669
670 670 print '\nResults Summary'
671 671 for key in Result.runresults.keys():
672 672 if Result.runresults[key] is not 0:
673 673 print '%s\t% 4d' % (key, Result.runresults[key])
674 674
675 675 m, s = divmod(time() - self.starttime, 60)
676 676 h, m = divmod(m, 60)
677 677 print '\nRunning Time:\t%02d:%02d:%02d' % (h, m, s)
678 678 print 'Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) /
679 679 float(Result.total)) * 100)
680 680 print 'Log directory:\t%s' % self.outputdir
681 681
682 682
683 683 def verify_file(pathname):
684 684 """
685 685 Verify that the supplied pathname is an executable regular file.
686 686 """
687 687 if os.path.isdir(pathname) or os.path.islink(pathname):
688 688 return False
689 689
690 690 if os.path.isfile(pathname) and os.access(pathname, os.X_OK):
691 691 return True
692 692
693 693 return False
694 694
695 695
696 696 def verify_user(user, logger):
697 697 """
698 698 Verify that the specified user exists on this system, and can execute
699 699 sudo without being prompted for a password.
700 700 """
701 701 testcmd = [SUDO, '-n', '-u', user, TRUE]
702 702 can_sudo = exists = True
703 703
704 704 if user in Cmd.verified_users:
705 705 return True
706 706
↓ open down ↓ |
706 lines elided |
↑ open up ↑ |
707 707 try:
708 708 _ = getpwnam(user)
709 709 except KeyError:
710 710 exists = False
711 711 logger.info("Warning: user '%s' does not exist.", user)
712 712 return False
713 713
714 714 p = Popen(testcmd)
715 715 p.wait()
716 716 if p.returncode is not 0:
717 - logger.info("Warning: user '%s' cannot use paswordless sudo.", user)
717 + logger.info("Warning: user '%s' cannot use passwordless sudo.", user)
718 718 return False
719 719 else:
720 720 Cmd.verified_users.append(user)
721 721
722 722 return True
723 723
724 724
725 725 def find_tests(testrun, options):
726 726 """
727 727 For the given list of pathnames, add files as Tests. For directories,
728 728 if do_groups is True, add the directory as a TestGroup. If False,
729 729 recursively search for executable files.
730 730 """
731 731
732 732 for p in sorted(options.pathnames):
733 733 if os.path.isdir(p):
734 734 for dirname, _, filenames in os.walk(p):
735 735 if options.do_groups:
736 736 testrun.addtestgroup(dirname, filenames, options)
737 737 else:
738 738 for f in sorted(filenames):
739 739 testrun.addtest(os.path.join(dirname, f), options)
740 740 else:
741 741 testrun.addtest(p, options)
742 742
743 743
744 744 def fail(retstr, ret=1):
745 745 print '%s: %s' % (argv[0], retstr)
746 746 exit(ret)
747 747
748 748
749 749 def options_cb(option, opt_str, value, parser):
750 750 path_options = ['runfile', 'outputdir', 'template']
751 751
752 752 if option.dest is 'runfile' and '-w' in parser.rargs or \
753 753 option.dest is 'template' and '-c' in parser.rargs:
754 754 fail('-c and -w are mutually exclusive.')
755 755
756 756 if opt_str in parser.rargs:
757 757 fail('%s may only be specified once.' % opt_str)
758 758
759 759 if option.dest is 'runfile':
760 760 parser.values.cmd = 'rdconfig'
761 761 if option.dest is 'template':
762 762 parser.values.cmd = 'wrconfig'
763 763
764 764 setattr(parser.values, option.dest, value)
765 765 if option.dest in path_options:
766 766 setattr(parser.values, option.dest, os.path.abspath(value))
767 767
768 768
769 769 def parse_args():
770 770 parser = OptionParser()
771 771 parser.add_option('-c', action='callback', callback=options_cb,
772 772 type='string', dest='runfile', metavar='runfile',
773 773 help='Specify tests to run via config file.')
774 774 parser.add_option('-d', action='store_true', default=False, dest='dryrun',
775 775 help='Dry run. Print tests, but take no other action.')
776 776 parser.add_option('-g', action='store_true', default=False,
777 777 dest='do_groups', help='Make directories TestGroups.')
778 778 parser.add_option('-o', action='callback', callback=options_cb,
779 779 default=BASEDIR, dest='outputdir', type='string',
780 780 metavar='outputdir', help='Specify an output directory.')
781 781 parser.add_option('-p', action='callback', callback=options_cb,
782 782 default='', dest='pre', metavar='script',
783 783 type='string', help='Specify a pre script.')
784 784 parser.add_option('-P', action='callback', callback=options_cb,
785 785 default='', dest='post', metavar='script',
786 786 type='string', help='Specify a post script.')
787 787 parser.add_option('-q', action='store_true', default=False, dest='quiet',
788 788 help='Silence on the console during a test run.')
789 789 parser.add_option('-t', action='callback', callback=options_cb, default=60,
790 790 dest='timeout', metavar='seconds', type='int',
791 791 help='Timeout (in seconds) for an individual test.')
792 792 parser.add_option('-u', action='callback', callback=options_cb,
793 793 default='', dest='user', metavar='user', type='string',
794 794 help='Specify a different user name to run as.')
795 795 parser.add_option('-w', action='callback', callback=options_cb,
796 796 default=None, dest='template', metavar='template',
797 797 type='string', help='Create a new config file.')
798 798 parser.add_option('-x', action='callback', callback=options_cb, default='',
799 799 dest='pre_user', metavar='pre_user', type='string',
800 800 help='Specify a user to execute the pre script.')
801 801 parser.add_option('-X', action='callback', callback=options_cb, default='',
802 802 dest='post_user', metavar='post_user', type='string',
803 803 help='Specify a user to execute the post script.')
804 804 (options, pathnames) = parser.parse_args()
805 805
806 806 if not options.runfile and not options.template:
807 807 options.cmd = 'runtests'
808 808
809 809 if options.runfile and len(pathnames):
810 810 fail('Extraneous arguments.')
811 811
812 812 options.pathnames = [os.path.abspath(path) for path in pathnames]
813 813
814 814 return options
815 815
816 816
817 817 def main(args):
818 818 options = parse_args()
819 819 testrun = TestRun(options)
820 820
821 821 if options.cmd is 'runtests':
822 822 find_tests(testrun, options)
823 823 elif options.cmd is 'rdconfig':
824 824 testrun.read(testrun.logger, options)
825 825 elif options.cmd is 'wrconfig':
826 826 find_tests(testrun, options)
827 827 testrun.write(options)
828 828 exit(0)
829 829 else:
830 830 fail('Unknown command specified')
831 831
832 832 testrun.complete_outputdirs(options)
833 833 testrun.run(options)
834 834 testrun.summary()
835 835 exit(0)
836 836
837 837
838 838 if __name__ == '__main__':
839 839 main(argv[1:])
↓ open down ↓ |
112 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX