VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testdriver/base.py@ 59747

Last change on this file since 59747 was 59425, checked in by vboxsync, 9 years ago

testdriver/base.py: Better actionConfig and actionExecute result logging.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: base.py 59425 2016-01-20 19:33:11Z vboxsync $
3# pylint: disable=C0302
4
5"""
6Base testdriver module.
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2010-2015 Oracle Corporation
12
13This file is part of VirtualBox Open Source Edition (OSE), as
14available from http://www.virtualbox.org. This file is free software;
15you can redistribute it and/or modify it under the terms of the GNU
16General Public License (GPL) as published by the Free Software
17Foundation, in version 2 as it comes in the "COPYING" file of the
18VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20
21The contents of this file may alternatively be used under the terms
22of the Common Development and Distribution License Version 1.0
23(CDDL) only, as it comes in the "COPYING.CDDL" file of the
24VirtualBox OSE distribution, in which case the provisions of the
25CDDL are applicable instead of those of the GPL.
26
27You may elect to license modified versions of this file under the
28terms and conditions of either the GPL or the CDDL or both.
29"""
30__version__ = "$Revision: 59425 $"
31
32
33# Standard Python imports.
34import os
35import os.path
36import signal
37import socket
38import stat
39import subprocess
40import sys
41import time
42import thread
43import threading
44import traceback
45import tempfile;
46import unittest;
47
48# Validation Kit imports.
49from common import utils;
50from common.constants import rtexitcode;
51from testdriver import reporter;
52if sys.platform == 'win32':
53 from testdriver import winbase;
54
55# Figure where we are.
56try: __file__
57except: __file__ = sys.argv[0];
58g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)));
59
60
61#
62# Some utility functions.
63#
64
65def exeSuff():
66 """
67 Returns the executable suffix.
68 """
69 if os.name == 'nt' or os.name == 'os2':
70 return '.exe';
71 return '';
72
73def searchPath(sExecName):
74 """
75 Searches the PATH for the specified executable name, returning the first
76 existing file/directory/whatever. The return is abspath'ed.
77 """
78 sSuff = exeSuff();
79
80 sPath = os.getenv('PATH', os.getenv('Path', os.path.defpath));
81 aPaths = sPath.split(os.path.pathsep)
82 for sDir in aPaths:
83 sFullExecName = os.path.join(sDir, sExecName);
84 if os.path.exists(sFullExecName):
85 return os.path.abspath(sFullExecName);
86 sFullExecName += sSuff;
87 if os.path.exists(sFullExecName):
88 return os.path.abspath(sFullExecName);
89 return sExecName;
90
91def getEnv(sVar, sLocalAlternative = None):
92 """
93 Tries to get an environment variable, optionally with a local run alternative.
94 Will raise an exception if sLocalAlternative is None and the variable is
95 empty or missing.
96 """
97 try:
98 sVal = os.environ.get(sVar, None);
99 if sVal is None:
100 raise GenError('environment variable "%s" is missing' % (sVar));
101 if sVal == "":
102 raise GenError('environment variable "%s" is empty' % (sVar));
103 except:
104 if sLocalAlternative is None or not reporter.isLocal():
105 raise
106 sVal = sLocalAlternative;
107 return sVal;
108
109def getDirEnv(sVar, sAlternative = None, fLocalReq = False, fTryCreate = False):
110 """
111 Tries to get an environment variable specifying a directory path.
112
113 Resolves it into an absolute path and verifies its existance before
114 returning it.
115
116 If the environment variable is empty or isn't set, or if the directory
117 doesn't exist or isn't a directory, sAlternative is returned instead.
118 If sAlternative is None, then we'll raise a GenError. For local runs we'll
119 only do this if fLocalReq is True.
120 """
121 assert sAlternative is None or fTryCreate is False;
122 try:
123 sVal = os.environ.get(sVar, None);
124 if sVal is None:
125 raise GenError('environment variable "%s" is missing' % (sVar));
126 if sVal == "":
127 raise GenError('environment variable "%s" is empty' % (sVar));
128
129 sVal = os.path.abspath(sVal);
130 if not os.path.isdir(sVal):
131 if not fTryCreate or os.path.exists(sVal):
132 reporter.error('the value of env.var. "%s" is not a dir: "%s"' % (sVar, sVal));
133 raise GenError('the value of env.var. "%s" is not a dir: "%s"' % (sVar, sVal));
134 try:
135 os.makedirs(sVal, 0700);
136 except:
137 reporter.error('makedirs failed on the value of env.var. "%s": "%s"' % (sVar, sVal));
138 raise GenError('makedirs failed on the value of env.var. "%s": "%s"' % (sVar, sVal));
139 except:
140 if sAlternative is None:
141 if reporter.isLocal() and fLocalReq:
142 raise;
143 sVal = None;
144 else:
145 sVal = os.path.abspath(sAlternative);
146 return sVal;
147
148def timestampMilli():
149 """
150 Gets a millisecond timestamp.
151 """
152 if sys.platform == 'win32':
153 return long(time.clock() * 1000);
154 return long(time.time() * 1000);
155
156def timestampNano():
157 """
158 Gets a nanosecond timestamp.
159 """
160 if sys.platform == 'win32':
161 return long(time.clock() * 1000000000);
162 return long(time.time() * 1000000000);
163
164def tryGetHostByName(sName):
165 """
166 Wrapper around gethostbyname.
167 """
168 if sName is not None:
169 try:
170 sIpAddr = socket.gethostbyname(sName);
171 except:
172 reporter.errorXcpt('gethostbyname(%s)' % (sName));
173 else:
174 if sIpAddr == '0.0.0.0':
175 reporter.error('gethostbyname(%s) -> %s' % (sName, sIpAddr));
176 raise;
177 sName = sIpAddr;
178 return sName;
179
180def processInterrupt(uPid):
181 """
182 Sends a SIGINT or equivalent to interrupt the specified process.
183 Returns True on success, False on failure.
184
185 On Windows hosts this may not work unless the process happens to be a
186 process group leader.
187 """
188 if sys.platform == 'win32':
189 fRc = winbase.processInterrupt(uPid)
190 else:
191 try:
192 os.kill(uPid, signal.SIGINT);
193 fRc = True;
194 except:
195 reporter.logXcpt('uPid=%s' % (uPid,));
196 fRc = False;
197 return fRc;
198
199def sendUserSignal1(uPid):
200 """
201 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
202 (VBoxSVC) or something.
203 Returns True on success, False on failure or if not supported (win).
204
205 On Windows hosts this may not work unless the process happens to be a
206 process group leader.
207 """
208 if sys.platform == 'win32':
209 fRc = False;
210 else:
211 try:
212 os.kill(uPid, signal.SIGUSR1); # pylint: disable=E1101
213 fRc = True;
214 except:
215 reporter.logXcpt('uPid=%s' % (uPid,));
216 fRc = False;
217 return fRc;
218
219def processTerminate(uPid):
220 """
221 Terminates the process in a nice manner (SIGTERM or equivalent).
222 Returns True on success, False on failure (logged).
223 """
224 fRc = False;
225 if sys.platform == 'win32':
226 fRc = winbase.processTerminate(uPid);
227 else:
228 try:
229 os.kill(uPid, signal.SIGTERM);
230 fRc = True;
231 except:
232 reporter.logXcpt('uPid=%s' % (uPid,));
233 return fRc;
234
235def processKill(uPid):
236 """
237 Terminates the process with extreme prejudice (SIGKILL).
238 Returns True on success, False on failure.
239 """
240 fRc = False;
241 if sys.platform == 'win32':
242 fRc = winbase.processKill(uPid);
243 else:
244 try:
245 os.kill(uPid, signal.SIGKILL); # pylint: disable=E1101
246 fRc = True;
247 except:
248 reporter.logXcpt('uPid=%s' % (uPid,));
249 return fRc;
250
251def processKillWithNameCheck(uPid, sName):
252 """
253 Like processKill(), but checks if the process name matches before killing
254 it. This is intended for killing using potentially stale pid values.
255
256 Returns True on success, False on failure.
257 """
258
259 if processCheckPidAndName(uPid, sName) is not True:
260 return False;
261 return processKill(uPid);
262
263
264def processExists(uPid):
265 """
266 Checks if the specified process exits.
267 This will only work if we can signal/open the process.
268
269 Returns True if it positively exists, False otherwise.
270 """
271 if sys.platform == 'win32':
272 fRc = winbase.processExists(uPid);
273 else:
274 try:
275 os.kill(uPid, 0);
276 fRc = True;
277 except:
278 reporter.logXcpt('uPid=%s' % (uPid,));
279 fRc = False;
280 return fRc;
281
282def processCheckPidAndName(uPid, sName):
283 """
284 Checks if a process PID and NAME matches.
285 """
286 if sys.platform == 'win32':
287 fRc = winbase.processCheckPidAndName(uPid, sName);
288 else:
289 if sys.platform in ('linux2', ):
290 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
291 elif sys.platform in ('sunos5',):
292 asPsCmd = ['/usr/bin/ps', '-p', '%u' % (uPid,), '-o', 'fname='];
293 elif sys.platform in ('darwin',):
294 asPsCmd = ['/bin/ps', '-p', '%u' % (uPid,), '-o', 'ucomm='];
295 else:
296 asPsCmd = None;
297
298 if asPsCmd is not None:
299 try:
300 oPs = subprocess.Popen(asPsCmd, stdout=subprocess.PIPE);
301 sCurName = oPs.communicate()[0];
302 iExitCode = oPs.wait();
303 except:
304 reporter.logXcpt();
305 return False;
306
307 # ps fails with non-zero exit code if the pid wasn't found.
308 if iExitCode is not 0:
309 return False;
310 if sCurName is None:
311 return False;
312 sCurName = sCurName.strip();
313 if sCurName is '':
314 return False;
315
316 if os.path.basename(sName) == sName:
317 sCurName = os.path.basename(sCurName);
318 elif os.path.basename(sCurName) == sCurName:
319 sName = os.path.basename(sName);
320
321 if sCurName != sName:
322 return False;
323
324 fRc = True;
325 return fRc;
326
327
328
329#
330# Classes
331#
332
333class GenError(Exception):
334 """
335 Exception class which only purpose it is to allow us to only catch our own
336 exceptions. Better design later.
337 """
338
339 def __init__(self, sWhat = "whatever"):
340 Exception.__init__(self);
341 self.sWhat = sWhat
342
343 def str(self):
344 """Get the message string."""
345 return self.sWhat;
346
347
348class InvalidOption(GenError):
349 """
350 Exception thrown by TestDriverBase.parseOption(). It contains the error message.
351 """
352 def __init__(self, sWhat):
353 GenError.__init__(self, sWhat);
354
355
356class QuietInvalidOption(GenError):
357 """
358 Exception thrown by TestDriverBase.parseOption(). Error already printed, just
359 return failure.
360 """
361 def __init__(self):
362 GenError.__init__(self, "");
363
364
365class TdTaskBase(object):
366 """
367 The base task.
368 """
369
370 def __init__(self, sCaller):
371 self.sDbgCreated = '%s: %s' % (utils.getTimePrefix(), sCaller);
372 self.fSignalled = False;
373 self.__oRLock = threading.RLock();
374 self.oCv = threading.Condition(self.__oRLock);
375 self.oOwner = None;
376 self.msStart = timestampMilli();
377 self.oLocker = None;
378
379 def __del__(self):
380 """In case we need it later on."""
381 pass;
382
383 def toString(self):
384 """
385 Stringifies the object, mostly as a debug aid.
386 """
387 return '<%s: fSignalled=%s, __oRLock=%s, oCv=%s, oOwner=%s, oLocker=%s, msStart=%s, sDbgCreated=%s>' \
388 % (type(self).__name__, self.fSignalled, self.__oRLock, self.oCv, repr(self.oOwner), self.oLocker, self.msStart,
389 self.sDbgCreated,);
390
391 def __str__(self):
392 return self.toString();
393
394 def lockTask(self):
395 """ Wrapper around oCv.acquire(). """
396 if True: # change to False for debugging deadlocks.
397 self.oCv.acquire();
398 else:
399 msStartWait = timestampMilli();
400 while self.oCv.acquire(0) is False:
401 if timestampMilli() - msStartWait > 30*1000:
402 reporter.error('!!! timed out waiting for %s' % (self, ));
403 traceback.print_stack();
404 reporter.logAllStacks()
405 self.oCv.acquire();
406 break;
407 time.sleep(0.5);
408 self.oLocker = thread.get_ident()
409 return None;
410
411 def unlockTask(self):
412 """ Wrapper around oCv.release(). """
413 self.oLocker = None;
414 self.oCv.release();
415 return None;
416
417 def getAgeAsMs(self):
418 """
419 Returns the number of milliseconds the task has existed.
420 """
421 return timestampMilli() - self.msStart;
422
423 def setTaskOwner(self, oOwner):
424 """
425 Sets or clears the task owner. (oOwner can be None.)
426
427 Returns the previous owner, this means None if not owned.
428 """
429 self.lockTask();
430 oOldOwner = self.oOwner;
431 self.oOwner = oOwner;
432 self.unlockTask();
433 return oOldOwner;
434
435 def signalTaskLocked(self):
436 """
437 Variant of signalTask that can be called while owning the lock.
438 """
439 fOld = self.fSignalled;
440 if not fOld:
441 reporter.log2('signalTaskLocked(%s)' % (self,));
442 self.fSignalled = True;
443 self.oCv.notifyAll()
444 if self.oOwner is not None:
445 self.oOwner.notifyAboutReadyTask(self);
446 return fOld;
447
448 def signalTask(self):
449 """
450 Signals the task, internal use only.
451
452 Returns the previous state.
453 """
454 self.lockTask();
455 fOld = self.signalTaskLocked();
456 self.unlockTask();
457 return fOld
458
459 def resetTaskLocked(self):
460 """
461 Variant of resetTask that can be called while owning the lock.
462 """
463 fOld = self.fSignalled;
464 self.fSignalled = False;
465 return fOld;
466
467 def resetTask(self):
468 """
469 Resets the task signal, internal use only.
470
471 Returns the previous state.
472 """
473 self.lockTask();
474 fOld = self.resetTaskLocked();
475 self.unlockTask();
476 return fOld
477
478 def pollTask(self, fLocked = False):
479 """
480 Poll the signal status of the task.
481 Returns True if signalled, False if not.
482
483 Override this method.
484 """
485 if not fLocked:
486 self.lockTask();
487 fState = self.fSignalled;
488 if not fLocked:
489 self.unlockTask();
490 return fState
491
492 def waitForTask(self, cMsTimeout = 0):
493 """
494 Waits for the task to be signalled.
495
496 Returns True if the task is/became ready before the timeout expired.
497 Returns False if the task is still not after cMsTimeout have elapsed.
498
499 Overriable.
500 """
501 self.lockTask();
502
503 fState = self.pollTask(True);
504 if not fState:
505 # Don't wait more than 1s. This allow lazy state polling.
506 msStart = timestampMilli();
507 while not fState:
508 cMsElapsed = timestampMilli() - msStart;
509 if cMsElapsed >= cMsTimeout:
510 break;
511
512 cMsWait = cMsTimeout - cMsElapsed
513 if cMsWait > 1000:
514 cMsWait = 1000;
515 try:
516 self.oCv.wait(cMsWait / 1000)
517 except:
518 pass;
519 fState = self.pollTask(True);
520
521 self.unlockTask();
522 return fState;
523
524
525class Process(TdTaskBase):
526 """
527 Child Process.
528 """
529
530 def __init__(self, sName, asArgs, uPid, hWin = None, uTid = None):
531 TdTaskBase.__init__(self, utils.getCallerName());
532 self.sName = sName;
533 self.asArgs = asArgs;
534 self.uExitCode = -127;
535 self.uPid = uPid;
536 self.hWin = hWin;
537 self.uTid = uTid;
538 self.sKindCrashReport = None;
539 self.sKindCrashDump = None;
540
541 def toString(self):
542 return '<%s uExitcode=%s, uPid=%s, sName=%s, asArgs=%s, hWin=%s, uTid=%s>' \
543 % (TdTaskBase.toString(self), self.uExitCode, self.uPid, self.sName, self.asArgs, self.hWin, self.uTid);
544
545 #
546 # Instantiation methods.
547 #
548
549 @staticmethod
550 def spawn(sName, *asArgsIn):
551 """
552 Similar to os.spawnl(os.P_NOWAIT,).
553
554 """
555 # Make argument array (can probably use asArgsIn directly, but wtf).
556 asArgs = [];
557 for sArg in asArgsIn:
558 asArgs.append(sArg);
559
560 # Special case: Windows.
561 if sys.platform == 'win32':
562 (uPid, hProcess, uTid) = winbase.processCreate(searchPath(sName), asArgs);
563 if uPid == -1:
564 return None;
565 return Process(sName, asArgs, uPid, hProcess, uTid);
566
567 # Unixy.
568 try:
569 uPid = os.spawnv(os.P_NOWAIT, sName, asArgs);
570 except:
571 reporter.logXcpt('sName=%s' % (sName,));
572 return None;
573 return Process(sName, asArgs, uPid);
574
575 @staticmethod
576 def spawnp(sName, *asArgsIn):
577 """
578 Similar to os.spawnlp(os.P_NOWAIT,).
579
580 """
581 return Process.spawn(searchPath(sName), *asArgsIn);
582
583 #
584 # Task methods
585 #
586
587 def pollTask(self, fLocked = False):
588 """
589 Overridden pollTask method.
590 """
591 if not fLocked:
592 self.lockTask();
593
594 fRc = self.fSignalled;
595 if not fRc:
596 if sys.platform == 'win32':
597 if winbase.processPollByHandle(self.hWin):
598 try:
599 (uPid, uStatus) = os.waitpid(self.hWin, 0);
600 if uPid == self.hWin or uPid == self.uPid:
601 self.hWin = None; # waitpid closed it, so it's now invalid.
602 uPid = self.uPid;
603 except:
604 reporter.logXcpt();
605 uPid = self.uPid;
606 uStatus = 0xffffffff;
607 else:
608 uPid = 0;
609 uStatus = 0;
610 else:
611 try:
612 (uPid, uStatus) = os.waitpid(self.uPid, os.WNOHANG); # pylint: disable=E1101
613 except:
614 reporter.logXcpt();
615 uPid = self.uPid;
616 uStatus = 0xffffffff;
617
618 # Got anything?
619 if uPid == self.uPid:
620 self.uExitCode = uStatus;
621 reporter.log('Process %u -> %u (%#x)' % (uPid, uStatus, uStatus));
622 self.signalTaskLocked();
623 if self.uExitCode != 0 and (self.sKindCrashReport is not None or self.sKindCrashDump is not None):
624 reporter.error('Process "%s" returned/crashed with a non-zero status code!! rc=%u sig=%u%s (raw=%#x)'
625 % ( self.sName, self.uExitCode >> 8, self.uExitCode & 0x7f,
626 ' w/ core' if self.uExitCode & 0x80 else '', self.uExitCode))
627 utils.processCollectCrashInfo(self.uPid, reporter.log, self._addCrashFile);
628
629 fRc = self.fSignalled;
630 if not fLocked:
631 self.unlockTask();
632 return fRc;
633
634 def _addCrashFile(self, sFile, fBinary):
635 """
636 Helper for adding a crash report or dump to the test report.
637 """
638 sKind = self.sKindCrashDump if fBinary else self.sKindCrashReport;
639 if sKind is not None:
640 reporter.addLogFile(sFile, sKind);
641 return None;
642
643
644 #
645 # Methods
646 #
647
648 def enableCrashReporting(self, sKindCrashReport, sKindCrashDump):
649 """
650 Enabling (or disables) automatic crash reporting on systems where that
651 is possible. The two file kind parameters are on the form
652 'crash/log/client' and 'crash/dump/client'. If both are None,
653 reporting will be disabled.
654 """
655 self.sKindCrashReport = sKindCrashReport;
656 self.sKindCrashDump = sKindCrashDump;
657 return True;
658
659 def isRunning(self):
660 """
661 Returns True if the process is still running, False if not.
662 """
663 return not self.pollTask();
664
665 def wait(self, cMsTimeout = 0):
666 """
667 Wait for the process to exit.
668
669 Returns True if the process exited withint the specified wait period.
670 Returns False if still running.
671 """
672 return self.waitForTask(cMsTimeout);
673
674 def getExitCode(self):
675 """
676 Returns the exit code of the process.
677 The process must have exited or the result will be wrong.
678 """
679 if self.isRunning():
680 return -127;
681 return self.uExitCode >> 8;
682
683 def interrupt(self):
684 """
685 Sends a SIGINT or equivalent to interrupt the process.
686 Returns True on success, False on failure.
687
688 On Windows hosts this may not work unless the process happens to be a
689 process group leader.
690 """
691 if sys.platform == 'win32':
692 return winbase.postThreadMesssageQuit(self.uTid);
693 return processInterrupt(self.uPid);
694
695 def sendUserSignal1(self):
696 """
697 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
698 (VBoxSVC) or something.
699 Returns True on success, False on failure.
700
701 On Windows hosts this may not work unless the process happens to be a
702 process group leader.
703 """
704 #if sys.platform == 'win32':
705 # return winbase.postThreadMesssageClose(self.uTid);
706 return sendUserSignal1(self.uPid);
707
708 def terminate(self):
709 """
710 Terminates the process in a nice manner (SIGTERM or equivalent).
711 Returns True on success, False on failure (logged).
712 """
713 if sys.platform == 'win32':
714 return winbase.processTerminateByHandle(self.hWin);
715 return processTerminate(self.uPid);
716
717 def getPid(self):
718 """ Returns the process id. """
719 return self.uPid;
720
721
722class SubTestDriverBase(object):
723 """
724 The base sub-test driver.
725
726 It helps thinking of these as units/sets/groups of tests, where the test
727 cases are (mostly) realized in python.
728
729 The sub-test drivers are subordinates of one or more test drivers. They
730 can be viewed as test code libraries that is responsible for parts of a
731 test driver run in different setups. One example would be testing a guest
732 additions component, which is applicable both to freshly installed guest
733 additions and VMs with old guest.
734
735 The test drivers invokes the sub-test drivers in a private manner during
736 test execution, but some of the generic bits are done automagically by the
737 base class: options, help, various other actions.
738 """
739
740 def __init__(self, sName, oTstDrv):
741 self.sName = sName;
742 self.oTstDrv = oTstDrv;
743
744
745 def showUsage(self):
746 """
747 Show usage information if any.
748
749 The default implementation only prints the name.
750 """
751 reporter.log('');
752 reporter.log('Options for sub-test driver %s:' % (self.sName,));
753 return True;
754
755 def parseOption(self, asArgs, iArg):
756 """
757 Parse an option. Override this.
758
759 @param asArgs The argument vector.
760 @param iArg The index of the current argument.
761
762 @returns The index of the next argument if consumed, @a iArg if not.
763
764 @throws InvalidOption or QuietInvalidOption on syntax error or similar.
765 """
766 _ = asArgs;
767 return iArg;
768
769
770class TestDriverBase(object): # pylint: disable=R0902
771 """
772 The base test driver.
773 """
774
775 def __init__(self):
776 self.fInterrupted = False;
777
778 # Actions.
779 self.asSpecialActions = ['extract', 'abort'];
780 self.asNormalActions = ['cleanup-before', 'verify', 'config', 'execute', 'cleanup-after' ];
781 self.asActions = [];
782 self.sExtractDstPath = None;
783
784 # Options.
785 self.fNoWipeClean = False;
786
787 # Tasks - only accessed by one thread atm, so no need for locking.
788 self.aoTasks = [];
789
790 # Host info.
791 self.sHost = utils.getHostOs();
792 self.sHostArch = utils.getHostArch();
793
794 #
795 # Get our bearings and adjust the environment.
796 #
797 if not utils.isRunningFromCheckout():
798 self.sBinPath = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch());
799 else:
800 self.sBinPath = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', utils.getHostOsDotArch(),
801 os.environ.get('KBUILD_TYPE', os.environ.get('BUILD_TYPE', 'debug')),
802 'validationkit', utils.getHostOs(), utils.getHostArch());
803 self.sOrgShell = os.environ.get('SHELL');
804 self.sOurShell = os.path.join(self.sBinPath, 'vts_shell' + exeSuff()); # No shell yet.
805 os.environ['SHELL'] = self.sOurShell;
806
807 self.sScriptPath = getDirEnv('TESTBOX_PATH_SCRIPTS');
808 if self.sScriptPath is None:
809 self.sScriptPath = os.path.abspath(os.path.join(os.getcwd(), '..'));
810 os.environ['TESTBOX_PATH_SCRIPTS'] = self.sScriptPath;
811
812 self.sScratchPath = getDirEnv('TESTBOX_PATH_SCRATCH', fTryCreate = True);
813 if self.sScratchPath is None:
814 sTmpDir = tempfile.gettempdir();
815 if sTmpDir == '/tmp': # /var/tmp is generally more suitable on all platforms.
816 sTmpDir = '/var/tmp';
817 self.sScratchPath = os.path.abspath(os.path.join(sTmpDir, 'VBoxTestTmp'));
818 if not os.path.isdir(self.sScratchPath):
819 os.makedirs(self.sScratchPath, 0700);
820 os.environ['TESTBOX_PATH_SCRATCH'] = self.sScratchPath;
821
822 self.sTestBoxName = getEnv( 'TESTBOX_NAME', 'local');
823 self.sTestSetId = getEnv( 'TESTBOX_TEST_SET_ID', 'local');
824 self.sBuildPath = getDirEnv('TESTBOX_PATH_BUILDS');
825 self.sUploadPath = getDirEnv('TESTBOX_PATH_UPLOAD');
826 self.sResourcePath = getDirEnv('TESTBOX_PATH_RESOURCES');
827 if self.sResourcePath is None:
828 if self.sHost == 'darwin': self.sResourcePath = "/Volumes/testrsrc/";
829 elif self.sHost == 'freebsd': self.sResourcePath = "/mnt/testrsrc/";
830 elif self.sHost == 'linux': self.sResourcePath = "/mnt/testrsrc/";
831 elif self.sHost == 'os2': self.sResourcePath = "T:/";
832 elif self.sHost == 'solaris': self.sResourcePath = "/mnt/testrsrc/";
833 elif self.sHost == 'win': self.sResourcePath = "T:/";
834 else: raise GenError('unknown host OS "%s"' % (self.sHost));
835
836 # PID file for the testdriver.
837 self.sPidFile = os.path.join(self.sScratchPath, 'testdriver.pid');
838
839 # Some stuff for the log...
840 reporter.log('scratch: %s' % (self.sScratchPath,));
841
842 # Get the absolute timeout (seconds since epoch, see
843 # utils.timestampSecond()). None if not available.
844 self.secTimeoutAbs = os.environ.get('TESTBOX_TIMEOUT_ABS', None);
845 if self.secTimeoutAbs is not None:
846 self.secTimeoutAbs = long(self.secTimeoutAbs);
847
848 # Distance from secTimeoutAbs that timeouts should be adjusted to.
849 self.secTimeoutFudge = 30;
850
851 # List of sub-test drivers (SubTestDriverBase derivatives).
852 self.aoSubTstDrvs = [];
853
854 def dump(self):
855 """
856 For debugging. --> __str__?
857 """
858 print >> sys.stderr, "testdriver.base: sBinPath = '%s'" % self.sBinPath;
859 print >> sys.stderr, "testdriver.base: sScriptPath = '%s'" % self.sScriptPath;
860 print >> sys.stderr, "testdriver.base: sScratchPath = '%s'" % self.sScratchPath;
861 print >> sys.stderr, "testdriver.base: sTestBoxName = '%s'" % self.sTestBoxName;
862 print >> sys.stderr, "testdriver.base: sBuildPath = '%s'" % self.sBuildPath;
863 print >> sys.stderr, "testdriver.base: sResourcePath = '%s'" % self.sResourcePath;
864 print >> sys.stderr, "testdriver.base: sUploadPath = '%s'" % self.sUploadPath;
865 print >> sys.stderr, "testdriver.base: sTestSetId = '%s'" % self.sTestSetId;
866 print >> sys.stderr, "testdriver.base: sHost = '%s'" % self.sHost;
867 print >> sys.stderr, "testdriver.base: sHostArch = '%s'" % self.sHostArch;
868 print >> sys.stderr, "testdriver.base: asSpecialActions = '%s'" % self.asSpecialActions;
869 print >> sys.stderr, "testdriver.base: asNormalActions = '%s'" % self.asNormalActions;
870 print >> sys.stderr, "testdriver.base: asActions = '%s'" % self.asActions;
871
872 #
873 # Resource utility methods.
874 #
875
876 def isResourceFile(self, sFile):
877 """
878 Checks if sFile is in in the resource set.
879 """
880 ## @todo need to deal with stuff in the validationkit.zip and similar.
881 asRsrcs = self.getResourceSet();
882 if sFile in asRsrcs:
883 return os.path.isfile(os.path.join(self.sResourcePath, sFile));
884 for sRsrc in asRsrcs:
885 if sFile.startswith(sRsrc):
886 sFull = os.path.join(self.sResourcePath, sRsrc);
887 if os.path.isdir(sFull):
888 return os.path.isfile(os.path.join(self.sResourcePath, sRsrc));
889 return False;
890
891 def getFullResourceName(self, sName):
892 """
893 Returns the full resource name.
894 """
895 if os.path.isabs(sName): ## @todo Hack. Need to deal properly with stuff in the validationkit.zip and similar.
896 return sName;
897 return os.path.join(self.sResourcePath, sName);
898
899 #
900 # Scratch related utility methods.
901 #
902
903 def __wipeScratchRecurse(self, sDir):
904 """
905 Deletes all file and sub-directories in sDir.
906 Returns the number of errors.
907 """
908 try:
909 asNames = os.listdir(sDir);
910 except:
911 reporter.errorXcpt('os.listdir("%s")' % (sDir));
912 return False;
913
914 cErrors = 0;
915 for sName in asNames:
916 # Build full path and lstat the object.
917 sFullName = os.path.join(sDir, sName)
918 try:
919 oStat = os.lstat(sFullName);
920 except:
921 reporter.errorXcpt('lstat("%s")' % (sFullName));
922 cErrors = cErrors + 1;
923 continue;
924
925 if stat.S_ISDIR(oStat.st_mode):
926 # Directory - recurse and try remove it.
927 cErrors = cErrors + self.__wipeScratchRecurse(sFullName);
928 try:
929 os.rmdir(sFullName);
930 except:
931 reporter.errorXcpt('rmdir("%s")' % (sFullName));
932 cErrors = cErrors + 1;
933 else:
934 # File, symlink, fifo or something - remove/unlink.
935 try:
936 os.remove(sFullName);
937 except:
938 reporter.errorXcpt('remove("%s")' % (sFullName));
939 cErrors = cErrors + 1;
940 return cErrors;
941
942 def wipeScratch(self):
943 """
944 Removes the content of the scratch directory.
945 Returns True on no errors, False + log entries on errors.
946 """
947 cErrors = self.__wipeScratchRecurse(self.sScratchPath);
948 return cErrors == 0;
949
950 #
951 # Sub-test driver related methods.
952 #
953
954 def addSubTestDriver(self, oSubTstDrv):
955 """
956 Adds a sub-test driver.
957
958 Returns True on success, false on failure.
959 """
960 assert isinstance(oSubTstDrv, SubTestDriverBase);
961 if oSubTstDrv in self.aoSubTstDrvs:
962 reporter.error('Attempt at adding sub-test driver %s twice.' % (oSubTstDrv.sName,));
963 return False;
964 self.aoSubTstDrvs.append(oSubTstDrv);
965 return True;
966
967 def showSubTstDrvUsage(self):
968 """
969 Shows the usage of the sub-test drivers.
970 """
971 for oSubTstDrv in self.aoSubTstDrvs:
972 oSubTstDrv.showUsage();
973 return True;
974
975 def subTstDrvParseOption(self, asArgs, iArgs):
976 """
977 Lets the sub-test drivers have a go at the option.
978 Returns the index of the next option if handled, otherwise iArgs.
979 """
980 for oSubTstDrv in self.aoSubTstDrvs:
981 iNext = oSubTstDrv.parseOption(asArgs, iArgs)
982 if iNext != iArgs:
983 assert iNext > iArgs;
984 assert iNext <= len(asArgs);
985 return iNext;
986 return iArgs;
987
988
989 #
990 # Task related methods.
991 #
992
993 def addTask(self, oTask):
994 """
995 Adds oTask to the task list.
996
997 Returns True if the task was added.
998
999 Returns False if the task was already in the task list.
1000 """
1001 if oTask in self.aoTasks:
1002 return False;
1003 #reporter.log2('adding task %s' % (oTask,));
1004 self.aoTasks.append(oTask);
1005 oTask.setTaskOwner(self);
1006 #reporter.log2('tasks now in list: %d - %s' % (len(self.aoTasks), self.aoTasks));
1007 return True;
1008
1009 def removeTask(self, oTask):
1010 """
1011 Removes oTask to the task list.
1012
1013 Returns oTask on success and None on failure.
1014 """
1015 try:
1016 #reporter.log2('removing task %s' % (oTask,));
1017 self.aoTasks.remove(oTask);
1018 except:
1019 return None;
1020 else:
1021 oTask.setTaskOwner(None);
1022 #reporter.log2('tasks left: %d - %s' % (len(self.aoTasks), self.aoTasks));
1023 return oTask;
1024
1025 def removeAllTasks(self):
1026 """
1027 Removes all the task from the task list.
1028
1029 Returns None.
1030 """
1031 aoTasks = self.aoTasks;
1032 self.aoTasks = [];
1033 for oTask in aoTasks:
1034 oTask.setTaskOwner(None);
1035 return None;
1036
1037 def notifyAboutReadyTask(self, oTask):
1038 """
1039 Notificiation that there is a ready task. May be called owning the
1040 task lock, so be careful wrt deadlocks.
1041
1042 Remember to call super when overriding this.
1043 """
1044 if oTask is None: pass; # lint
1045 return None;
1046
1047 def pollTasks(self):
1048 """
1049 Polls the task to see if any of them are ready.
1050 Returns the ready task, None if none are ready.
1051 """
1052 for oTask in self.aoTasks:
1053 if oTask.pollTask():
1054 return oTask;
1055 return None;
1056
1057 def waitForTasksSleepWorker(self, cMsTimeout):
1058 """
1059 Overriable method that does the sleeping for waitForTask().
1060
1061 cMsTimeout will not be larger than 1000, so there is normally no need
1062 to do any additional splitting up of the polling interval.
1063
1064 Returns True if cMillieSecs elapsed.
1065 Returns False if some exception was raised while we waited or
1066 there turned out to be nothing to wait on.
1067 """
1068 try:
1069 self.aoTasks[0].waitForTask(cMsTimeout);
1070 return True;
1071 except Exception, oXcpt:
1072 reporter.log("waitForTasksSleepWorker: %s" % (str(oXcpt),));
1073 return False;
1074
1075 def waitForTasks(self, cMsTimeout):
1076 """
1077 Waits for any of the tasks to require attention or a KeyboardInterrupt.
1078 Returns the ready task on success, None on timeout or interrupt.
1079 """
1080 try:
1081 #reporter.log2('waitForTasks: cMsTimeout=%d' % (cMsTimeout,));
1082
1083 if cMsTimeout == 0:
1084 return self.pollTasks();
1085
1086 if len(self.aoTasks) == 0:
1087 return None;
1088
1089 fMore = True;
1090 if cMsTimeout < 0:
1091 while fMore:
1092 oTask = self.pollTasks();
1093 if oTask is not None:
1094 return oTask;
1095 fMore = self.waitForTasksSleepWorker(1000);
1096 else:
1097 msStart = timestampMilli();
1098 while fMore:
1099 oTask = self.pollTasks();
1100 if oTask is not None:
1101 #reporter.log2('waitForTasks: returning %s, msStart=%d' % \
1102 # (oTask, msStart));
1103 return oTask;
1104
1105 cMsElapsed = timestampMilli() - msStart;
1106 if cMsElapsed > cMsTimeout: # not ==, we want the final waitForEvents.
1107 break;
1108 if cMsTimeout - cMsElapsed > 1000:
1109 fMore = self.waitForTasksSleepWorker(1000);
1110 else:
1111 fMore = self.waitForTasksSleepWorker(cMsTimeout - cMsElapsed);
1112 except KeyboardInterrupt:
1113 self.fInterrupted = True;
1114 reporter.errorXcpt('KeyboardInterrupt', 6);
1115 except:
1116 reporter.errorXcpt(None, 6);
1117 return None;
1118
1119 #
1120 # PID file management methods.
1121 #
1122
1123 def pidFileRead(self):
1124 """
1125 Worker that reads the PID file.
1126 Returns list of PID, empty if no file.
1127 """
1128 aiPids = [];
1129 if os.path.isfile(self.sPidFile):
1130 try:
1131 oFile = utils.openNoInherit(self.sPidFile, 'r');
1132 sContent = str(oFile.read());
1133 oFile.close();
1134 except:
1135 reporter.errorXcpt();
1136 return aiPids;
1137
1138 sContent = str(sContent).strip().replace('\n', ' ').replace('\r', ' ').replace('\t', ' ');
1139 for sPid in sContent.split(' '):
1140 if sPid.isdigit():
1141 try:
1142 aiPids.append(int(sPid));
1143 except:
1144 reporter.logXcpt('sPid=%s' % (sPid,));
1145 else:
1146 reporter.log('%s: "%s"' % (self.sPidFile, sPid));
1147
1148 return aiPids;
1149
1150 def pidFileAdd(self, iPid, fSudo = False):
1151 """
1152 Adds a PID to the PID file, creating the file if necessary.
1153 """
1154 _ = fSudo; ## @todo remember sudo (root) children.
1155 try:
1156 oFile = utils.openNoInherit(self.sPidFile, 'a');
1157 oFile.write(str(iPid) + '\n');
1158 oFile.close();
1159 except:
1160 reporter.errorXcpt();
1161 return False;
1162 reporter.log2('pidFileAdd: added PID %d (new content: %s)' % (iPid, self.pidFileRead(),));
1163 return True;
1164
1165 def pidFileRemove(self, iPid, fQuiet = False):
1166 """
1167 Removes a PID from the PID file.
1168 """
1169 aiPids = self.pidFileRead();
1170 if iPid not in aiPids:
1171 if not fQuiet:
1172 reporter.log('pidFileRemove could not find %s in the PID file (content: %s)' % (iPid, aiPids));
1173 return False;
1174
1175 aiPids.remove(iPid);
1176 sPid = '';
1177 for iPid2 in aiPids:
1178 sPid += '%s\n' % (iPid2,);
1179
1180 try:
1181 oFile = utils.openNoInherit(self.sPidFile, 'w');
1182 oFile.write(sPid);
1183 oFile.close();
1184 except:
1185 reporter.errorXcpt();
1186 return False;
1187
1188 reporter.log2('pidFileRemove: removed PID %d (new content: %s)' % (iPid, self.pidFileRead(),));
1189 return True;
1190
1191 def pidFileDelete(self):
1192 """Creates the testdriver PID file."""
1193 if os.path.isfile(self.sPidFile):
1194 try:
1195 os.unlink(self.sPidFile);
1196 except:
1197 reporter.logXcpt();
1198 return False;
1199 return True;
1200
1201 #
1202 # Misc helper methods.
1203 #
1204
1205 def requireMoreArgs(self, cMinNeeded, asArgs, iArg):
1206 """
1207 Checks that asArgs has at least cMinNeeded args following iArg.
1208
1209 Returns iArg + 1 if it checks out fine.
1210 Raise appropritate exception if not, ASSUMING that the current argument
1211 is found at iArg.
1212 """
1213 assert cMinNeeded >= 1;
1214 if iArg + cMinNeeded > len(asArgs):
1215 if cMinNeeded > 1:
1216 raise InvalidOption('The "%s" option takes %s values' % (asArgs[iArg], cMinNeeded,));
1217 raise InvalidOption('The "%s" option takes 1 value' % (asArgs[iArg],));
1218 return iArg + 1;
1219
1220 def getBinTool(self, sName):
1221 """
1222 Returns the full path to the given binary validation kit tool.
1223 """
1224 return os.path.join(self.sBinPath, sName) + exeSuff();
1225
1226 def adjustTimeoutMs(self, cMsTimeout, cMsMinimum = None):
1227 """
1228 Adjusts the given timeout (milliseconds) to take TESTBOX_TIMEOUT_ABS
1229 and cMsMinimum (optional) into account.
1230
1231 Returns adjusted timeout.
1232 Raises no exceptions.
1233 """
1234 if self.secTimeoutAbs is not None:
1235 cMsToDeadline = self.secTimeoutAbs * 1000 - utils.timestampMilli();
1236 if cMsToDeadline >= 0:
1237 # Adjust for fudge and enforce the minimum timeout
1238 cMsToDeadline -= self.secTimeoutFudge * 1000;
1239 if cMsToDeadline < (cMsMinimum if cMsMinimum is not None else 10000):
1240 cMsToDeadline = cMsMinimum if cMsMinimum is not None else 10000;
1241
1242 # Is the timeout beyond the (adjusted) deadline, if so change it.
1243 if cMsTimeout > cMsToDeadline:
1244 reporter.log('adjusting timeout: %s ms -> %s ms (deadline)\n' % (cMsTimeout, cMsToDeadline,));
1245 return cMsToDeadline;
1246
1247 #else: Don't bother, we've passed the deadline.
1248
1249 # Only enforce the minimum timeout if specified.
1250 if cMsMinimum is not None and cMsTimeout < cMsMinimum:
1251 reporter.log('adjusting timeout: %s ms -> %s ms (minimum)\n' % (cMsTimeout, cMsMinimum,));
1252 cMsTimeout = cMsMinimum;
1253
1254 return cMsTimeout;
1255
1256 def prepareResultFile(self, sName = 'results.xml'):
1257 """
1258 Given a base name (no path, but extension if required), a scratch file
1259 name is computed and any previous file removed.
1260
1261 Returns the full path to the file sName.
1262 Raises exception on failure.
1263 """
1264 sXmlFile = os.path.join(self.sScratchPath, sName);
1265 if os.path.exists(sXmlFile):
1266 os.unlink(sXmlFile);
1267 return sXmlFile;
1268
1269
1270 #
1271 # Overridable methods.
1272 #
1273
1274 def showUsage(self):
1275 """
1276 Shows the usage.
1277
1278 When overriding this, call super first.
1279 """
1280 sName = os.path.basename(sys.argv[0]);
1281 reporter.log('Usage: %s [options] <action(s)>' % (sName,));
1282 reporter.log('');
1283 reporter.log('Actions (in execution order):');
1284 reporter.log(' cleanup-before');
1285 reporter.log(' Cleanups done at the start of testing.');
1286 reporter.log(' verify');
1287 reporter.log(' Verify that all necessary resources are present.');
1288 reporter.log(' config');
1289 reporter.log(' Configure the tests.');
1290 reporter.log(' execute');
1291 reporter.log(' Execute the tests.');
1292 reporter.log(' cleanup-after');
1293 reporter.log(' Cleanups done at the end of the testing.');
1294 reporter.log('');
1295 reporter.log('Special Actions:');
1296 reporter.log(' all');
1297 reporter.log(' Alias for: %s' % (' '.join(self.asNormalActions),));
1298 reporter.log(' extract <path>');
1299 reporter.log(' Extract the test resources and put them in the specified');
1300 reporter.log(' path for off side/line testing.');
1301 reporter.log(' abort');
1302 reporter.log(' Aborts the test.');
1303 reporter.log('');
1304 reporter.log('Base Options:');
1305 reporter.log(' -h, --help');
1306 reporter.log(' Show this help message.');
1307 reporter.log(' -v, --verbose');
1308 reporter.log(' Increase logging verbosity, repeat for more logging.');
1309 reporter.log(' -d, --debug');
1310 reporter.log(' Increase the debug logging level, repeat for more info.');
1311 reporter.log(' --no-wipe-clean');
1312 reporter.log(' Do not wipe clean the scratch area during the two clean up');
1313 reporter.log(' actions. This is for facilitating nested test driver execution.');
1314 return True;
1315
1316 def parseOption(self, asArgs, iArg):
1317 """
1318 Parse an option. Override this.
1319
1320 Keyword arguments:
1321 asArgs -- The argument vector.
1322 iArg -- The index of the current argument.
1323
1324 Returns iArg if the option was not recognized.
1325 Returns the index of the next argument when something is consumed.
1326 In the event of a syntax error, a InvalidOption or QuietInvalidOption
1327 should be thrown.
1328 """
1329
1330 if asArgs[iArg] in ('--help', '-help', '-h', '-?', '/?', '/help', '/H', '-H'):
1331 self.showUsage();
1332 self.showSubTstDrvUsage();
1333 raise QuietInvalidOption();
1334
1335 # options
1336 if asArgs[iArg] in ('--verbose', '-v'):
1337 reporter.incVerbosity()
1338 elif asArgs[iArg] in ('--debug', '-d'):
1339 reporter.incDebug()
1340 elif asArgs[iArg] == '--no-wipe-clean':
1341 self.fNoWipeClean = True;
1342 elif (asArgs[iArg] == 'all' or asArgs[iArg] in self.asNormalActions) \
1343 and self.asActions in self.asSpecialActions:
1344 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1345 # actions
1346 elif asArgs[iArg] == 'all':
1347 self.asActions = [ 'all' ];
1348 elif asArgs[iArg] in self.asNormalActions:
1349 self.asActions.append(asArgs[iArg])
1350 elif asArgs[iArg] in self.asSpecialActions:
1351 if self.asActions != []:
1352 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1353 self.asActions = [ asArgs[iArg] ];
1354 # extact <destination>
1355 if asArgs[iArg] == 'extract':
1356 iArg = iArg + 1;
1357 if iArg >= len(asArgs): raise InvalidOption('The "extract" action requires a destination directory');
1358 self.sExtractDstPath = asArgs[iArg];
1359 else:
1360 return iArg;
1361 return iArg + 1;
1362
1363 def completeOptions(self):
1364 """
1365 This method is called after parsing all the options.
1366 Returns success indicator. Use the reporter to complain.
1367
1368 Overriable, call super.
1369 """
1370 return True;
1371
1372 def getResourceSet(self):
1373 """
1374 Returns a set of file and/or directory names relative to
1375 TESTBOX_PATH_RESOURCES.
1376
1377 Override this.
1378 """
1379 return [];
1380
1381 def actionExtract(self):
1382 """
1383 Handle the action that extracts the test resources for off site use.
1384 Returns a success indicator and error details with the reporter.
1385
1386 Usually no need to override this.
1387 """
1388 reporter.error('the extract action is not implemented')
1389 return False;
1390
1391 def actionVerify(self):
1392 """
1393 Handle the action that verify the test resources.
1394 Returns a success indicator and error details with the reporter.
1395
1396 There is usually no need to override this.
1397 """
1398
1399 asRsrcs = self.getResourceSet();
1400 for sRsrc in asRsrcs:
1401 # Go thru some pain to catch escape sequences.
1402 if sRsrc.find("//") >= 0:
1403 reporter.error('Double slash test resource name: "%s"' % (sRsrc));
1404 return False;
1405 if sRsrc == ".." \
1406 or sRsrc.startswith("../") \
1407 or sRsrc.find("/../") >= 0 \
1408 or sRsrc.endswith("/.."):
1409 reporter.error('Relative path in test resource name: "%s"' % (sRsrc));
1410 return False;
1411
1412 sFull = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc)));
1413 if not sFull.startswith(os.path.normpath(self.sResourcePath)):
1414 reporter.error('sFull="%s" self.sResourcePath=%s' % (sFull, self.sResourcePath));
1415 reporter.error('The resource "%s" seems to specify a relative path' % (sRsrc));
1416 return False;
1417
1418 reporter.log2('Checking for resource "%s" at "%s" ...' % (sRsrc, sFull));
1419 if os.path.isfile(sFull):
1420 try:
1421 oFile = utils.openNoInherit(sFull, "rb");
1422 oFile.close();
1423 except Exception, oXcpt:
1424 reporter.error('The file resource "%s" cannot be accessed: %s' % (sFull, oXcpt));
1425 return False;
1426 elif os.path.isdir(sFull):
1427 if not os.path.isdir(os.path.join(sFull, '.')):
1428 reporter.error('The directory resource "%s" cannot be accessed' % (sFull));
1429 return False;
1430 elif os.path.exists(sFull):
1431 reporter.error('The resource "%s" is not a file or directory' % (sFull));
1432 return False;
1433 else:
1434 reporter.error('The resource "%s" was not found' % (sFull));
1435 return False;
1436 return True;
1437
1438 def actionConfig(self):
1439 """
1440 Handle the action that configures the test.
1441 Returns True (success), False (failure) or None (skip the test),
1442 posting complaints and explanations with the reporter.
1443
1444 Override this.
1445 """
1446 return True;
1447
1448 def actionExecute(self):
1449 """
1450 Handle the action that executes the test.
1451
1452 Returns True (success), False (failure) or None (skip the test),
1453 posting complaints and explanations with the reporter.
1454
1455 Override this.
1456 """
1457 return True;
1458
1459 def actionCleanupBefore(self):
1460 """
1461 Handle the action that cleans up spills from previous tests before
1462 starting the tests. This is mostly about wiping the scratch space
1463 clean in local runs. On a testbox the testbox script will use the
1464 cleanup-after if the test is interrupted.
1465
1466 Returns True (success), False (failure) or None (skip the test),
1467 posting complaints and explanations with the reporter.
1468
1469 Override this, but call super to wipe the scratch directory.
1470 """
1471 if self.fNoWipeClean is False:
1472 self.wipeScratch();
1473 return True;
1474
1475 def actionCleanupAfter(self):
1476 """
1477 Handle the action that cleans up all spills from executing the test.
1478
1479 Returns True (success) or False (failure) posting complaints and
1480 explanations with the reporter.
1481
1482 Override this, but call super to wipe the scratch directory.
1483 """
1484 if self.fNoWipeClean is False:
1485 self.wipeScratch();
1486 return True;
1487
1488 def actionAbort(self):
1489 """
1490 Handle the action that aborts a (presumed) running testdriver, making
1491 sure to include all it's children.
1492
1493 Returns True (success) or False (failure) posting complaints and
1494 explanations with the reporter.
1495
1496 Override this, but call super to kill the testdriver script and any
1497 other process covered by the testdriver PID file.
1498 """
1499
1500 aiPids = self.pidFileRead();
1501 reporter.log('The pid file contained: %s' % (aiPids,));
1502
1503 #
1504 # Try convince the processes to quit with increasing impoliteness.
1505 #
1506 if sys.platform == 'win32':
1507 afnMethods = [ processInterrupt, processTerminate ];
1508 else:
1509 afnMethods = [ sendUserSignal1, processInterrupt, processTerminate, processKill ];
1510 for fnMethod in afnMethods:
1511 for iPid in aiPids:
1512 fnMethod(iPid);
1513
1514 for i in range(10):
1515 if i > 0:
1516 time.sleep(1);
1517
1518 for j in range(len(aiPids) - 1, -1, -1):
1519 iPid = aiPids[j];
1520 if not processExists(iPid):
1521 reporter.log('%s terminated' % (iPid,));
1522 self.pidFileRemove(iPid, fQuiet = True);
1523 aiPids.pop(j);
1524
1525 if len(aiPids) == 0:
1526 reporter.log('All done.');
1527 return True;
1528
1529 if i in [4, 8]:
1530 reporter.log('Still waiting for: %s (method=%s)' % (aiPids, fnMethod,));
1531
1532 reporter.log('Failed to terminate the following processes: %s' % (aiPids,));
1533 return False;
1534
1535
1536 def onExit(self, iRc):
1537 """
1538 Hook for doing very important cleanups on the way out.
1539
1540 iRc is the exit code or -1 in the case of an unhandled exception.
1541 Returns nothing and shouldn't raise exceptions (will be muted+ignored).
1542 """
1543 _ = iRc;
1544 return None;
1545
1546
1547 #
1548 # main() - don't override anything!
1549 #
1550
1551 def main(self, asArgs = None):
1552 """
1553 The main function of the test driver.
1554
1555 Keyword arguments:
1556 asArgs -- The argument vector. Defaults to sys.argv.
1557
1558 Returns exit code. No exceptions.
1559 """
1560
1561 #
1562 # Wrap worker in exception handler and always call a 'finally' like
1563 # method to do crucial cleanups on the way out.
1564 #
1565 try:
1566 iRc = self.innerMain(asArgs);
1567 except:
1568 try:
1569 self.onExit(-1);
1570 except:
1571 reporter.logXcpt();
1572 raise;
1573 self.onExit(iRc);
1574 return iRc;
1575
1576
1577 def innerMain(self, asArgs = None):
1578 """
1579 Exception wrapped main() worker.
1580 """
1581
1582 # parse the arguments.
1583 if asArgs is None:
1584 asArgs = list(sys.argv);
1585 iArg = 1;
1586 try:
1587 while iArg < len(asArgs):
1588 iNext = self.parseOption(asArgs, iArg);
1589 if iNext == iArg:
1590 iNext = self.subTstDrvParseOption(asArgs, iArg);
1591 if iNext == iArg:
1592 raise InvalidOption('unknown option: %s' % (asArgs[iArg]))
1593 iArg = iNext;
1594 except QuietInvalidOption, oXcpt:
1595 return rtexitcode.RTEXITCODE_SYNTAX;
1596 except InvalidOption, oXcpt:
1597 reporter.error(oXcpt.str());
1598 return rtexitcode.RTEXITCODE_SYNTAX;
1599 except:
1600 reporter.error('unexpected exception while parsing argument #%s' % (iArg));
1601 traceback.print_exc();
1602 return rtexitcode.RTEXITCODE_SYNTAX;
1603
1604 if not self.completeOptions():
1605 return rtexitcode.RTEXITCODE_SYNTAX;
1606
1607 if self.asActions == []:
1608 reporter.error('no action was specified');
1609 reporter.error('valid actions: %s' % (self.asNormalActions + self.asSpecialActions + ['all']));
1610 return rtexitcode.RTEXITCODE_SYNTAX;
1611
1612 # execte the actions.
1613 fRc = True; # Tristate - True (success), False (failure), None (skipped).
1614 asActions = self.asActions;
1615 if 'extract' in asActions:
1616 reporter.log('*** extract action ***');
1617 asActions.remove('extract');
1618 fRc = self.actionExtract();
1619 reporter.log('*** extract action completed (fRc=%s) ***' % (fRc));
1620 elif 'abort' in asActions:
1621 reporter.log('*** abort action ***');
1622 asActions.remove('abort');
1623 fRc = self.actionAbort();
1624 reporter.log('*** abort action completed (fRc=%s) ***' % (fRc));
1625 else:
1626 if asActions == [ 'all' ]:
1627 asActions = self.asNormalActions;
1628
1629 if 'verify' in asActions:
1630 reporter.log('*** verify action ***');
1631 asActions.remove('verify');
1632 fRc = self.actionVerify();
1633 if fRc is True: reporter.log("verified succeeded");
1634 else: reporter.log("verified failed (fRc=%s)" % (fRc,));
1635 reporter.log('*** verify action completed (fRc=%s) ***' % (fRc,));
1636
1637 if 'cleanup-before' in asActions:
1638 reporter.log('*** cleanup-before action ***');
1639 asActions.remove('cleanup-before');
1640 fRc2 = self.actionCleanupBefore();
1641 if fRc2 is not True: reporter.log("cleanup-before failed");
1642 if fRc2 is not True and fRc is True: fRc = fRc2;
1643 reporter.log('*** cleanup-before action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1644
1645 self.pidFileAdd(os.getpid());
1646
1647 if 'config' in asActions and fRc is True:
1648 asActions.remove('config');
1649 reporter.log('*** config action ***');
1650 fRc = self.actionConfig();
1651 if fRc is True: reporter.log("config succeeded");
1652 elif fRc is None: reporter.log("config skipping test");
1653 else: reporter.log("config failed");
1654 reporter.log('*** config action completed (fRc=%s) ***' % (fRc,));
1655
1656 if 'execute' in asActions and fRc is True:
1657 asActions.remove('execute');
1658 reporter.log('*** execute action ***');
1659 fRc = self.actionExecute();
1660 if fRc is True: reporter.log("execute succeeded");
1661 elif fRc is None: reporter.log("execute skipping test");
1662 else: reporter.log("execute failed (fRc=%s)" % (fRc,));
1663 reporter.testCleanup();
1664 reporter.log('*** execute action completed (fRc=%s) ***' % (fRc,));
1665
1666 if 'cleanup-after' in asActions:
1667 reporter.log('*** cleanup-after action ***');
1668 asActions.remove('cleanup-after');
1669 fRc2 = self.actionCleanupAfter();
1670 if fRc2 is not True: reporter.log("cleanup-after failed");
1671 if fRc2 is not True and fRc is True: fRc = fRc2;
1672 reporter.log('*** cleanup-after action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1673
1674 self.pidFileRemove(os.getpid());
1675
1676 if asActions != [] and fRc is True:
1677 reporter.error('unhandled actions: %s' % (asActions,));
1678 fRc = False;
1679
1680 # Done
1681 if fRc is None:
1682 reporter.log('*****************************************');
1683 reporter.log('*** The test driver SKIPPED the test. ***');
1684 reporter.log('*****************************************');
1685 return rtexitcode.RTEXITCODE_SKIPPED;
1686 if fRc is not True:
1687 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1688 reporter.error('!!! The test driver FAILED (in case we forgot to mention it). !!!');
1689 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1690 return rtexitcode.RTEXITCODE_FAILURE;
1691 reporter.log('*******************************************');
1692 reporter.log('*** The test driver exits successfully. ***');
1693 reporter.log('*******************************************');
1694 return rtexitcode.RTEXITCODE_SUCCESS;
1695
1696# The old, deprecated name.
1697TestDriver = TestDriverBase; # pylint: disable=C0103
1698
1699
1700#
1701# Unit testing.
1702#
1703
1704# pylint: disable=C0111
1705class TestDriverBaseTestCase(unittest.TestCase):
1706 def setUp(self):
1707 self.oTstDrv = TestDriverBase();
1708 self.oTstDrv.pidFileDelete();
1709
1710 def tearDown(self):
1711 pass; # clean up scratch dir and such.
1712
1713 def testPidFile(self):
1714 aiPids = [os.getpid() + 1, os.getpid() + 2];
1715 self.assertTrue(self.oTstDrv.pidFileAdd(aiPids[0]));
1716 self.assertEqual(self.oTstDrv.pidFileRead(), aiPids[0:1]);
1717 self.assertTrue(self.oTstDrv.pidFileAdd(aiPids[1]));
1718 self.assertEqual(self.oTstDrv.pidFileRead(), aiPids[0:2]);
1719 self.assertTrue(self.oTstDrv.pidFileDelete());
1720
1721if __name__ == '__main__':
1722 unittest.main();
1723 # not reached.
1724
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette