VirtualBox

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

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

reporter.doPollWork: Added debug string to see who is calling.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 59.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: base.py 62100 2016-07-07 10:06:39Z 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: 62100 $"
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 sName = sIpAddr;
176 else:
177 reporter.error('gethostbyname(%s) -> %s' % (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 is 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.0);
517 except:
518 pass;
519 reporter.doPollWork('TdTaskBase.waitForTask');
520 fState = self.pollTask(True);
521
522 self.unlockTask();
523 return fState;
524
525
526class Process(TdTaskBase):
527 """
528 Child Process.
529 """
530
531 def __init__(self, sName, asArgs, uPid, hWin = None, uTid = None):
532 TdTaskBase.__init__(self, utils.getCallerName());
533 self.sName = sName;
534 self.asArgs = asArgs;
535 self.uExitCode = -127;
536 self.uPid = uPid;
537 self.hWin = hWin;
538 self.uTid = uTid;
539 self.sKindCrashReport = None;
540 self.sKindCrashDump = None;
541
542 def toString(self):
543 return '<%s uExitcode=%s, uPid=%s, sName=%s, asArgs=%s, hWin=%s, uTid=%s>' \
544 % (TdTaskBase.toString(self), self.uExitCode, self.uPid, self.sName, self.asArgs, self.hWin, self.uTid);
545
546 #
547 # Instantiation methods.
548 #
549
550 @staticmethod
551 def spawn(sName, *asArgsIn):
552 """
553 Similar to os.spawnl(os.P_NOWAIT,).
554
555 """
556 # Make argument array (can probably use asArgsIn directly, but wtf).
557 asArgs = [];
558 for sArg in asArgsIn:
559 asArgs.append(sArg);
560
561 # Special case: Windows.
562 if sys.platform == 'win32':
563 (uPid, hProcess, uTid) = winbase.processCreate(searchPath(sName), asArgs);
564 if uPid == -1:
565 return None;
566 return Process(sName, asArgs, uPid, hProcess, uTid);
567
568 # Unixy.
569 try:
570 uPid = os.spawnv(os.P_NOWAIT, sName, asArgs);
571 except:
572 reporter.logXcpt('sName=%s' % (sName,));
573 return None;
574 return Process(sName, asArgs, uPid);
575
576 @staticmethod
577 def spawnp(sName, *asArgsIn):
578 """
579 Similar to os.spawnlp(os.P_NOWAIT,).
580
581 """
582 return Process.spawn(searchPath(sName), *asArgsIn);
583
584 #
585 # Task methods
586 #
587
588 def pollTask(self, fLocked = False):
589 """
590 Overridden pollTask method.
591 """
592 if not fLocked:
593 self.lockTask();
594
595 fRc = self.fSignalled;
596 if not fRc:
597 if sys.platform == 'win32':
598 if winbase.processPollByHandle(self.hWin):
599 try:
600 (uPid, uStatus) = os.waitpid(self.hWin, 0);
601 if uPid == self.hWin or uPid == self.uPid:
602 self.hWin = None; # waitpid closed it, so it's now invalid.
603 uPid = self.uPid;
604 except:
605 reporter.logXcpt();
606 uPid = self.uPid;
607 uStatus = 0xffffffff;
608 else:
609 uPid = 0;
610 uStatus = 0; # pylint: disable=redefined-variable-type
611 else:
612 try:
613 (uPid, uStatus) = os.waitpid(self.uPid, os.WNOHANG); # pylint: disable=E1101
614 except:
615 reporter.logXcpt();
616 uPid = self.uPid;
617 uStatus = 0xffffffff;
618
619 # Got anything?
620 if uPid == self.uPid:
621 self.uExitCode = uStatus;
622 reporter.log('Process %u -> %u (%#x)' % (uPid, uStatus, uStatus));
623 self.signalTaskLocked();
624 if self.uExitCode != 0 and (self.sKindCrashReport is not None or self.sKindCrashDump is not None):
625 reporter.error('Process "%s" returned/crashed with a non-zero status code!! rc=%u sig=%u%s (raw=%#x)'
626 % ( self.sName, self.uExitCode >> 8, self.uExitCode & 0x7f,
627 ' w/ core' if self.uExitCode & 0x80 else '', self.uExitCode))
628 utils.processCollectCrashInfo(self.uPid, reporter.log, self._addCrashFile);
629
630 fRc = self.fSignalled;
631 if not fLocked:
632 self.unlockTask();
633 return fRc;
634
635 def _addCrashFile(self, sFile, fBinary):
636 """
637 Helper for adding a crash report or dump to the test report.
638 """
639 sKind = self.sKindCrashDump if fBinary else self.sKindCrashReport;
640 if sKind is not None:
641 reporter.addLogFile(sFile, sKind);
642 return None;
643
644
645 #
646 # Methods
647 #
648
649 def enableCrashReporting(self, sKindCrashReport, sKindCrashDump):
650 """
651 Enabling (or disables) automatic crash reporting on systems where that
652 is possible. The two file kind parameters are on the form
653 'crash/log/client' and 'crash/dump/client'. If both are None,
654 reporting will be disabled.
655 """
656 self.sKindCrashReport = sKindCrashReport;
657 self.sKindCrashDump = sKindCrashDump;
658 return True;
659
660 def isRunning(self):
661 """
662 Returns True if the process is still running, False if not.
663 """
664 return not self.pollTask();
665
666 def wait(self, cMsTimeout = 0):
667 """
668 Wait for the process to exit.
669
670 Returns True if the process exited withint the specified wait period.
671 Returns False if still running.
672 """
673 return self.waitForTask(cMsTimeout);
674
675 def getExitCode(self):
676 """
677 Returns the exit code of the process.
678 The process must have exited or the result will be wrong.
679 """
680 if self.isRunning():
681 return -127;
682 return self.uExitCode >> 8;
683
684 def interrupt(self):
685 """
686 Sends a SIGINT or equivalent to interrupt the process.
687 Returns True on success, False on failure.
688
689 On Windows hosts this may not work unless the process happens to be a
690 process group leader.
691 """
692 if sys.platform == 'win32':
693 return winbase.postThreadMesssageQuit(self.uTid);
694 return processInterrupt(self.uPid);
695
696 def sendUserSignal1(self):
697 """
698 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
699 (VBoxSVC) or something.
700 Returns True on success, False on failure.
701
702 On Windows hosts this may not work unless the process happens to be a
703 process group leader.
704 """
705 #if sys.platform == 'win32':
706 # return winbase.postThreadMesssageClose(self.uTid);
707 return sendUserSignal1(self.uPid);
708
709 def terminate(self):
710 """
711 Terminates the process in a nice manner (SIGTERM or equivalent).
712 Returns True on success, False on failure (logged).
713 """
714 if sys.platform == 'win32':
715 return winbase.processTerminateByHandle(self.hWin);
716 return processTerminate(self.uPid);
717
718 def getPid(self):
719 """ Returns the process id. """
720 return self.uPid;
721
722
723class SubTestDriverBase(object):
724 """
725 The base sub-test driver.
726
727 It helps thinking of these as units/sets/groups of tests, where the test
728 cases are (mostly) realized in python.
729
730 The sub-test drivers are subordinates of one or more test drivers. They
731 can be viewed as test code libraries that is responsible for parts of a
732 test driver run in different setups. One example would be testing a guest
733 additions component, which is applicable both to freshly installed guest
734 additions and VMs with old guest.
735
736 The test drivers invokes the sub-test drivers in a private manner during
737 test execution, but some of the generic bits are done automagically by the
738 base class: options, help, various other actions.
739 """
740
741 def __init__(self, sName, oTstDrv):
742 self.sName = sName;
743 self.oTstDrv = oTstDrv;
744
745
746 def showUsage(self):
747 """
748 Show usage information if any.
749
750 The default implementation only prints the name.
751 """
752 reporter.log('');
753 reporter.log('Options for sub-test driver %s:' % (self.sName,));
754 return True;
755
756 def parseOption(self, asArgs, iArg):
757 """
758 Parse an option. Override this.
759
760 @param asArgs The argument vector.
761 @param iArg The index of the current argument.
762
763 @returns The index of the next argument if consumed, @a iArg if not.
764
765 @throws InvalidOption or QuietInvalidOption on syntax error or similar.
766 """
767 _ = asArgs;
768 return iArg;
769
770
771class TestDriverBase(object): # pylint: disable=R0902
772 """
773 The base test driver.
774 """
775
776 def __init__(self):
777 self.fInterrupted = False;
778
779 # Actions.
780 self.asSpecialActions = ['extract', 'abort'];
781 self.asNormalActions = ['cleanup-before', 'verify', 'config', 'execute', 'cleanup-after' ];
782 self.asActions = [];
783 self.sExtractDstPath = None;
784
785 # Options.
786 self.fNoWipeClean = False;
787
788 # Tasks - only accessed by one thread atm, so no need for locking.
789 self.aoTasks = [];
790
791 # Host info.
792 self.sHost = utils.getHostOs();
793 self.sHostArch = utils.getHostArch();
794
795 #
796 # Get our bearings and adjust the environment.
797 #
798 if not utils.isRunningFromCheckout():
799 self.sBinPath = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch());
800 else:
801 self.sBinPath = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', utils.getHostOsDotArch(),
802 os.environ.get('KBUILD_TYPE', os.environ.get('BUILD_TYPE', 'debug')),
803 'validationkit', utils.getHostOs(), utils.getHostArch());
804 self.sOrgShell = os.environ.get('SHELL');
805 self.sOurShell = os.path.join(self.sBinPath, 'vts_shell' + exeSuff()); # No shell yet.
806 os.environ['SHELL'] = self.sOurShell;
807
808 self.sScriptPath = getDirEnv('TESTBOX_PATH_SCRIPTS');
809 if self.sScriptPath is None:
810 self.sScriptPath = os.path.abspath(os.path.join(os.getcwd(), '..'));
811 os.environ['TESTBOX_PATH_SCRIPTS'] = self.sScriptPath;
812
813 self.sScratchPath = getDirEnv('TESTBOX_PATH_SCRATCH', fTryCreate = True);
814 if self.sScratchPath is None:
815 sTmpDir = tempfile.gettempdir();
816 if sTmpDir == '/tmp': # /var/tmp is generally more suitable on all platforms.
817 sTmpDir = '/var/tmp';
818 self.sScratchPath = os.path.abspath(os.path.join(sTmpDir, 'VBoxTestTmp'));
819 if not os.path.isdir(self.sScratchPath):
820 os.makedirs(self.sScratchPath, 0700);
821 os.environ['TESTBOX_PATH_SCRATCH'] = self.sScratchPath;
822
823 self.sTestBoxName = getEnv( 'TESTBOX_NAME', 'local');
824 self.sTestSetId = getEnv( 'TESTBOX_TEST_SET_ID', 'local');
825 self.sBuildPath = getDirEnv('TESTBOX_PATH_BUILDS');
826 self.sUploadPath = getDirEnv('TESTBOX_PATH_UPLOAD');
827 self.sResourcePath = getDirEnv('TESTBOX_PATH_RESOURCES');
828 if self.sResourcePath is None:
829 if self.sHost == 'darwin': self.sResourcePath = "/Volumes/testrsrc/";
830 elif self.sHost == 'freebsd': self.sResourcePath = "/mnt/testrsrc/";
831 elif self.sHost == 'linux': self.sResourcePath = "/mnt/testrsrc/";
832 elif self.sHost == 'os2': self.sResourcePath = "T:/";
833 elif self.sHost == 'solaris': self.sResourcePath = "/mnt/testrsrc/";
834 elif self.sHost == 'win': self.sResourcePath = "T:/";
835 else: raise GenError('unknown host OS "%s"' % (self.sHost));
836
837 # PID file for the testdriver.
838 self.sPidFile = os.path.join(self.sScratchPath, 'testdriver.pid');
839
840 # Some stuff for the log...
841 reporter.log('scratch: %s' % (self.sScratchPath,));
842
843 # Get the absolute timeout (seconds since epoch, see
844 # utils.timestampSecond()). None if not available.
845 self.secTimeoutAbs = os.environ.get('TESTBOX_TIMEOUT_ABS', None);
846 if self.secTimeoutAbs is not None:
847 self.secTimeoutAbs = long(self.secTimeoutAbs);
848 reporter.log('secTimeoutAbs: %s' % (self.secTimeoutAbs,));
849 else:
850 reporter.log('TESTBOX_TIMEOUT_ABS not found in the environment');
851
852
853 # Distance from secTimeoutAbs that timeouts should be adjusted to.
854 self.secTimeoutFudge = 30;
855
856 # List of sub-test drivers (SubTestDriverBase derivatives).
857 self.aoSubTstDrvs = [];
858
859 def dump(self):
860 """
861 For debugging. --> __str__?
862 """
863 print >> sys.stderr, "testdriver.base: sBinPath = '%s'" % self.sBinPath;
864 print >> sys.stderr, "testdriver.base: sScriptPath = '%s'" % self.sScriptPath;
865 print >> sys.stderr, "testdriver.base: sScratchPath = '%s'" % self.sScratchPath;
866 print >> sys.stderr, "testdriver.base: sTestBoxName = '%s'" % self.sTestBoxName;
867 print >> sys.stderr, "testdriver.base: sBuildPath = '%s'" % self.sBuildPath;
868 print >> sys.stderr, "testdriver.base: sResourcePath = '%s'" % self.sResourcePath;
869 print >> sys.stderr, "testdriver.base: sUploadPath = '%s'" % self.sUploadPath;
870 print >> sys.stderr, "testdriver.base: sTestSetId = '%s'" % self.sTestSetId;
871 print >> sys.stderr, "testdriver.base: sHost = '%s'" % self.sHost;
872 print >> sys.stderr, "testdriver.base: sHostArch = '%s'" % self.sHostArch;
873 print >> sys.stderr, "testdriver.base: asSpecialActions = '%s'" % self.asSpecialActions;
874 print >> sys.stderr, "testdriver.base: asNormalActions = '%s'" % self.asNormalActions;
875 print >> sys.stderr, "testdriver.base: asActions = '%s'" % self.asActions;
876 print >> sys.stderr, "testdriver.base: secTimeoutAbs = '%s'" % self.secTimeoutAbs;
877 for sVar in sorted(os.environ.keys()):
878 print >> sys.stderr, "os.environ[%s] = '%s'" % (sVar, os.environ[sVar],);
879
880 #
881 # Resource utility methods.
882 #
883
884 def isResourceFile(self, sFile):
885 """
886 Checks if sFile is in in the resource set.
887 """
888 ## @todo need to deal with stuff in the validationkit.zip and similar.
889 asRsrcs = self.getResourceSet();
890 if sFile in asRsrcs:
891 return os.path.isfile(os.path.join(self.sResourcePath, sFile));
892 for sRsrc in asRsrcs:
893 if sFile.startswith(sRsrc):
894 sFull = os.path.join(self.sResourcePath, sRsrc);
895 if os.path.isdir(sFull):
896 return os.path.isfile(os.path.join(self.sResourcePath, sRsrc));
897 return False;
898
899 def getFullResourceName(self, sName):
900 """
901 Returns the full resource name.
902 """
903 if os.path.isabs(sName): ## @todo Hack. Need to deal properly with stuff in the validationkit.zip and similar.
904 return sName;
905 return os.path.join(self.sResourcePath, sName);
906
907 #
908 # Scratch related utility methods.
909 #
910
911 def __wipeScratchRecurse(self, sDir):
912 """
913 Deletes all file and sub-directories in sDir.
914 Returns the number of errors.
915 """
916 try:
917 asNames = os.listdir(sDir);
918 except:
919 reporter.errorXcpt('os.listdir("%s")' % (sDir));
920 return False;
921
922 cErrors = 0;
923 for sName in asNames:
924 # Build full path and lstat the object.
925 sFullName = os.path.join(sDir, sName)
926 try:
927 oStat = os.lstat(sFullName);
928 except:
929 reporter.errorXcpt('lstat("%s")' % (sFullName));
930 cErrors = cErrors + 1;
931 continue;
932
933 if stat.S_ISDIR(oStat.st_mode):
934 # Directory - recurse and try remove it.
935 cErrors = cErrors + self.__wipeScratchRecurse(sFullName);
936 try:
937 os.rmdir(sFullName);
938 except:
939 reporter.errorXcpt('rmdir("%s")' % (sFullName));
940 cErrors = cErrors + 1;
941 else:
942 # File, symlink, fifo or something - remove/unlink.
943 try:
944 os.remove(sFullName);
945 except:
946 reporter.errorXcpt('remove("%s")' % (sFullName));
947 cErrors = cErrors + 1;
948 return cErrors;
949
950 def wipeScratch(self):
951 """
952 Removes the content of the scratch directory.
953 Returns True on no errors, False + log entries on errors.
954 """
955 cErrors = self.__wipeScratchRecurse(self.sScratchPath);
956 return cErrors == 0;
957
958 #
959 # Sub-test driver related methods.
960 #
961
962 def addSubTestDriver(self, oSubTstDrv):
963 """
964 Adds a sub-test driver.
965
966 Returns True on success, false on failure.
967 """
968 assert isinstance(oSubTstDrv, SubTestDriverBase);
969 if oSubTstDrv in self.aoSubTstDrvs:
970 reporter.error('Attempt at adding sub-test driver %s twice.' % (oSubTstDrv.sName,));
971 return False;
972 self.aoSubTstDrvs.append(oSubTstDrv);
973 return True;
974
975 def showSubTstDrvUsage(self):
976 """
977 Shows the usage of the sub-test drivers.
978 """
979 for oSubTstDrv in self.aoSubTstDrvs:
980 oSubTstDrv.showUsage();
981 return True;
982
983 def subTstDrvParseOption(self, asArgs, iArgs):
984 """
985 Lets the sub-test drivers have a go at the option.
986 Returns the index of the next option if handled, otherwise iArgs.
987 """
988 for oSubTstDrv in self.aoSubTstDrvs:
989 iNext = oSubTstDrv.parseOption(asArgs, iArgs)
990 if iNext != iArgs:
991 assert iNext > iArgs;
992 assert iNext <= len(asArgs);
993 return iNext;
994 return iArgs;
995
996
997 #
998 # Task related methods.
999 #
1000
1001 def addTask(self, oTask):
1002 """
1003 Adds oTask to the task list.
1004
1005 Returns True if the task was added.
1006
1007 Returns False if the task was already in the task list.
1008 """
1009 if oTask in self.aoTasks:
1010 return False;
1011 #reporter.log2('adding task %s' % (oTask,));
1012 self.aoTasks.append(oTask);
1013 oTask.setTaskOwner(self);
1014 #reporter.log2('tasks now in list: %d - %s' % (len(self.aoTasks), self.aoTasks));
1015 return True;
1016
1017 def removeTask(self, oTask):
1018 """
1019 Removes oTask to the task list.
1020
1021 Returns oTask on success and None on failure.
1022 """
1023 try:
1024 #reporter.log2('removing task %s' % (oTask,));
1025 self.aoTasks.remove(oTask);
1026 except:
1027 return None;
1028 else:
1029 oTask.setTaskOwner(None);
1030 #reporter.log2('tasks left: %d - %s' % (len(self.aoTasks), self.aoTasks));
1031 return oTask;
1032
1033 def removeAllTasks(self):
1034 """
1035 Removes all the task from the task list.
1036
1037 Returns None.
1038 """
1039 aoTasks = self.aoTasks;
1040 self.aoTasks = [];
1041 for oTask in aoTasks:
1042 oTask.setTaskOwner(None);
1043 return None;
1044
1045 def notifyAboutReadyTask(self, oTask):
1046 """
1047 Notificiation that there is a ready task. May be called owning the
1048 task lock, so be careful wrt deadlocks.
1049
1050 Remember to call super when overriding this.
1051 """
1052 if oTask is None: pass; # lint
1053 return None;
1054
1055 def pollTasks(self):
1056 """
1057 Polls the task to see if any of them are ready.
1058 Returns the ready task, None if none are ready.
1059 """
1060 for oTask in self.aoTasks:
1061 if oTask.pollTask():
1062 return oTask;
1063 return None;
1064
1065 def waitForTasksSleepWorker(self, cMsTimeout):
1066 """
1067 Overriable method that does the sleeping for waitForTask().
1068
1069 cMsTimeout will not be larger than 1000, so there is normally no need
1070 to do any additional splitting up of the polling interval.
1071
1072 Returns True if cMillieSecs elapsed.
1073 Returns False if some exception was raised while we waited or
1074 there turned out to be nothing to wait on.
1075 """
1076 try:
1077 self.aoTasks[0].waitForTask(cMsTimeout);
1078 return True;
1079 except Exception, oXcpt:
1080 reporter.log("waitForTasksSleepWorker: %s" % (str(oXcpt),));
1081 return False;
1082
1083 def waitForTasks(self, cMsTimeout):
1084 """
1085 Waits for any of the tasks to require attention or a KeyboardInterrupt.
1086 Returns the ready task on success, None on timeout or interrupt.
1087 """
1088 try:
1089 #reporter.log2('waitForTasks: cMsTimeout=%d' % (cMsTimeout,));
1090
1091 if cMsTimeout == 0:
1092 return self.pollTasks();
1093
1094 if len(self.aoTasks) == 0:
1095 return None;
1096
1097 fMore = True;
1098 if cMsTimeout < 0:
1099 while fMore:
1100 oTask = self.pollTasks();
1101 if oTask is not None:
1102 return oTask;
1103 fMore = self.waitForTasksSleepWorker(1000);
1104 else:
1105 msStart = timestampMilli();
1106 while fMore:
1107 oTask = self.pollTasks();
1108 if oTask is not None:
1109 #reporter.log2('waitForTasks: returning %s, msStart=%d' % \
1110 # (oTask, msStart));
1111 return oTask;
1112
1113 cMsElapsed = timestampMilli() - msStart;
1114 if cMsElapsed > cMsTimeout: # not ==, we want the final waitForEvents.
1115 break;
1116 cMsSleep = cMsTimeout - cMsElapsed;
1117 if cMsSleep > 1000:
1118 cMsSleep = 1000;
1119 fMore = self.waitForTasksSleepWorker(cMsSleep);
1120 reporter.doPollWork('TestDriverBase.waitForTasks'); # shouldn't be necessary, remove when we figure why...
1121 except KeyboardInterrupt:
1122 self.fInterrupted = True;
1123 reporter.errorXcpt('KeyboardInterrupt', 6);
1124 except:
1125 reporter.errorXcpt(None, 6);
1126 return None;
1127
1128 #
1129 # PID file management methods.
1130 #
1131
1132 def pidFileRead(self):
1133 """
1134 Worker that reads the PID file.
1135 Returns list of PID, empty if no file.
1136 """
1137 aiPids = [];
1138 if os.path.isfile(self.sPidFile):
1139 try:
1140 oFile = utils.openNoInherit(self.sPidFile, 'r');
1141 sContent = str(oFile.read());
1142 oFile.close();
1143 except:
1144 reporter.errorXcpt();
1145 return aiPids;
1146
1147 sContent = str(sContent).strip().replace('\n', ' ').replace('\r', ' ').replace('\t', ' ');
1148 for sPid in sContent.split(' '):
1149 if sPid.isdigit():
1150 try:
1151 aiPids.append(int(sPid));
1152 except:
1153 reporter.logXcpt('sPid=%s' % (sPid,));
1154 else:
1155 reporter.log('%s: "%s"' % (self.sPidFile, sPid));
1156
1157 return aiPids;
1158
1159 def pidFileAdd(self, iPid, fSudo = False):
1160 """
1161 Adds a PID to the PID file, creating the file if necessary.
1162 """
1163 _ = fSudo; ## @todo remember sudo (root) children.
1164 try:
1165 oFile = utils.openNoInherit(self.sPidFile, 'a');
1166 oFile.write(str(iPid) + '\n');
1167 oFile.close();
1168 except:
1169 reporter.errorXcpt();
1170 return False;
1171 reporter.log2('pidFileAdd: added PID %d (new content: %s)' % (iPid, self.pidFileRead(),));
1172 return True;
1173
1174 def pidFileRemove(self, iPid, fQuiet = False):
1175 """
1176 Removes a PID from the PID file.
1177 """
1178 aiPids = self.pidFileRead();
1179 if iPid not in aiPids:
1180 if not fQuiet:
1181 reporter.log('pidFileRemove could not find %s in the PID file (content: %s)' % (iPid, aiPids));
1182 return False;
1183
1184 aiPids.remove(iPid);
1185 sPid = '';
1186 for iPid2 in aiPids:
1187 sPid += '%s\n' % (iPid2,);
1188
1189 try:
1190 oFile = utils.openNoInherit(self.sPidFile, 'w');
1191 oFile.write(sPid);
1192 oFile.close();
1193 except:
1194 reporter.errorXcpt();
1195 return False;
1196
1197 reporter.log2('pidFileRemove: removed PID %d (new content: %s)' % (iPid, self.pidFileRead(),));
1198 return True;
1199
1200 def pidFileDelete(self):
1201 """Creates the testdriver PID file."""
1202 if os.path.isfile(self.sPidFile):
1203 try:
1204 os.unlink(self.sPidFile);
1205 except:
1206 reporter.logXcpt();
1207 return False;
1208 return True;
1209
1210 #
1211 # Misc helper methods.
1212 #
1213
1214 def requireMoreArgs(self, cMinNeeded, asArgs, iArg):
1215 """
1216 Checks that asArgs has at least cMinNeeded args following iArg.
1217
1218 Returns iArg + 1 if it checks out fine.
1219 Raise appropritate exception if not, ASSUMING that the current argument
1220 is found at iArg.
1221 """
1222 assert cMinNeeded >= 1;
1223 if iArg + cMinNeeded > len(asArgs):
1224 if cMinNeeded > 1:
1225 raise InvalidOption('The "%s" option takes %s values' % (asArgs[iArg], cMinNeeded,));
1226 raise InvalidOption('The "%s" option takes 1 value' % (asArgs[iArg],));
1227 return iArg + 1;
1228
1229 def getBinTool(self, sName):
1230 """
1231 Returns the full path to the given binary validation kit tool.
1232 """
1233 return os.path.join(self.sBinPath, sName) + exeSuff();
1234
1235 def adjustTimeoutMs(self, cMsTimeout, cMsMinimum = None):
1236 """
1237 Adjusts the given timeout (milliseconds) to take TESTBOX_TIMEOUT_ABS
1238 and cMsMinimum (optional) into account.
1239
1240 Returns adjusted timeout.
1241 Raises no exceptions.
1242 """
1243 if self.secTimeoutAbs is not None:
1244 cMsToDeadline = self.secTimeoutAbs * 1000 - utils.timestampMilli();
1245 if cMsToDeadline >= 0:
1246 # Adjust for fudge and enforce the minimum timeout
1247 cMsToDeadline -= self.secTimeoutFudge * 1000;
1248 if cMsToDeadline < (cMsMinimum if cMsMinimum is not None else 10000):
1249 cMsToDeadline = cMsMinimum if cMsMinimum is not None else 10000;
1250
1251 # Is the timeout beyond the (adjusted) deadline, if so change it.
1252 if cMsTimeout > cMsToDeadline:
1253 reporter.log('adjusting timeout: %s ms -> %s ms (deadline)\n' % (cMsTimeout, cMsToDeadline,));
1254 return cMsToDeadline;
1255 reporter.log('adjustTimeoutMs: cMsTimeout (%s) > cMsToDeadline (%s)' % (cMsTimeout, cMsToDeadline,));
1256 else:
1257 # Don't bother, we've passed the deadline.
1258 reporter.log('adjustTimeoutMs: ooops! cMsToDeadline=%s (%s), timestampMilli()=%s, timestampSecond()=%s'
1259 % (cMsToDeadline, cMsToDeadline*1000, utils.timestampMilli(), utils.timestampSecond()));
1260
1261 # Only enforce the minimum timeout if specified.
1262 if cMsMinimum is not None and cMsTimeout < cMsMinimum:
1263 reporter.log('adjusting timeout: %s ms -> %s ms (minimum)\n' % (cMsTimeout, cMsMinimum,));
1264 cMsTimeout = cMsMinimum;
1265
1266 return cMsTimeout;
1267
1268 def prepareResultFile(self, sName = 'results.xml'):
1269 """
1270 Given a base name (no path, but extension if required), a scratch file
1271 name is computed and any previous file removed.
1272
1273 Returns the full path to the file sName.
1274 Raises exception on failure.
1275 """
1276 sXmlFile = os.path.join(self.sScratchPath, sName);
1277 if os.path.exists(sXmlFile):
1278 os.unlink(sXmlFile);
1279 return sXmlFile;
1280
1281
1282 #
1283 # Overridable methods.
1284 #
1285
1286 def showUsage(self):
1287 """
1288 Shows the usage.
1289
1290 When overriding this, call super first.
1291 """
1292 sName = os.path.basename(sys.argv[0]);
1293 reporter.log('Usage: %s [options] <action(s)>' % (sName,));
1294 reporter.log('');
1295 reporter.log('Actions (in execution order):');
1296 reporter.log(' cleanup-before');
1297 reporter.log(' Cleanups done at the start of testing.');
1298 reporter.log(' verify');
1299 reporter.log(' Verify that all necessary resources are present.');
1300 reporter.log(' config');
1301 reporter.log(' Configure the tests.');
1302 reporter.log(' execute');
1303 reporter.log(' Execute the tests.');
1304 reporter.log(' cleanup-after');
1305 reporter.log(' Cleanups done at the end of the testing.');
1306 reporter.log('');
1307 reporter.log('Special Actions:');
1308 reporter.log(' all');
1309 reporter.log(' Alias for: %s' % (' '.join(self.asNormalActions),));
1310 reporter.log(' extract <path>');
1311 reporter.log(' Extract the test resources and put them in the specified');
1312 reporter.log(' path for off side/line testing.');
1313 reporter.log(' abort');
1314 reporter.log(' Aborts the test.');
1315 reporter.log('');
1316 reporter.log('Base Options:');
1317 reporter.log(' -h, --help');
1318 reporter.log(' Show this help message.');
1319 reporter.log(' -v, --verbose');
1320 reporter.log(' Increase logging verbosity, repeat for more logging.');
1321 reporter.log(' -d, --debug');
1322 reporter.log(' Increase the debug logging level, repeat for more info.');
1323 reporter.log(' --no-wipe-clean');
1324 reporter.log(' Do not wipe clean the scratch area during the two clean up');
1325 reporter.log(' actions. This is for facilitating nested test driver execution.');
1326 return True;
1327
1328 def parseOption(self, asArgs, iArg):
1329 """
1330 Parse an option. Override this.
1331
1332 Keyword arguments:
1333 asArgs -- The argument vector.
1334 iArg -- The index of the current argument.
1335
1336 Returns iArg if the option was not recognized.
1337 Returns the index of the next argument when something is consumed.
1338 In the event of a syntax error, a InvalidOption or QuietInvalidOption
1339 should be thrown.
1340 """
1341
1342 if asArgs[iArg] in ('--help', '-help', '-h', '-?', '/?', '/help', '/H', '-H'):
1343 self.showUsage();
1344 self.showSubTstDrvUsage();
1345 raise QuietInvalidOption();
1346
1347 # options
1348 if asArgs[iArg] in ('--verbose', '-v'):
1349 reporter.incVerbosity()
1350 elif asArgs[iArg] in ('--debug', '-d'):
1351 reporter.incDebug()
1352 elif asArgs[iArg] == '--no-wipe-clean':
1353 self.fNoWipeClean = True;
1354 elif (asArgs[iArg] == 'all' or asArgs[iArg] in self.asNormalActions) \
1355 and self.asActions in self.asSpecialActions:
1356 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1357 # actions
1358 elif asArgs[iArg] == 'all':
1359 self.asActions = [ 'all' ];
1360 elif asArgs[iArg] in self.asNormalActions:
1361 self.asActions.append(asArgs[iArg])
1362 elif asArgs[iArg] in self.asSpecialActions:
1363 if self.asActions != []:
1364 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1365 self.asActions = [ asArgs[iArg] ];
1366 # extact <destination>
1367 if asArgs[iArg] == 'extract':
1368 iArg = iArg + 1;
1369 if iArg >= len(asArgs): raise InvalidOption('The "extract" action requires a destination directory');
1370 self.sExtractDstPath = asArgs[iArg];
1371 else:
1372 return iArg;
1373 return iArg + 1;
1374
1375 def completeOptions(self):
1376 """
1377 This method is called after parsing all the options.
1378 Returns success indicator. Use the reporter to complain.
1379
1380 Overriable, call super.
1381 """
1382 return True;
1383
1384 def getResourceSet(self):
1385 """
1386 Returns a set of file and/or directory names relative to
1387 TESTBOX_PATH_RESOURCES.
1388
1389 Override this.
1390 """
1391 return [];
1392
1393 def actionExtract(self):
1394 """
1395 Handle the action that extracts the test resources for off site use.
1396 Returns a success indicator and error details with the reporter.
1397
1398 Usually no need to override this.
1399 """
1400 reporter.error('the extract action is not implemented')
1401 return False;
1402
1403 def actionVerify(self):
1404 """
1405 Handle the action that verify the test resources.
1406 Returns a success indicator and error details with the reporter.
1407
1408 There is usually no need to override this.
1409 """
1410
1411 asRsrcs = self.getResourceSet();
1412 for sRsrc in asRsrcs:
1413 # Go thru some pain to catch escape sequences.
1414 if sRsrc.find("//") >= 0:
1415 reporter.error('Double slash test resource name: "%s"' % (sRsrc));
1416 return False;
1417 if sRsrc == ".." \
1418 or sRsrc.startswith("../") \
1419 or sRsrc.find("/../") >= 0 \
1420 or sRsrc.endswith("/.."):
1421 reporter.error('Relative path in test resource name: "%s"' % (sRsrc));
1422 return False;
1423
1424 sFull = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc)));
1425 if not sFull.startswith(os.path.normpath(self.sResourcePath)):
1426 reporter.error('sFull="%s" self.sResourcePath=%s' % (sFull, self.sResourcePath));
1427 reporter.error('The resource "%s" seems to specify a relative path' % (sRsrc));
1428 return False;
1429
1430 reporter.log2('Checking for resource "%s" at "%s" ...' % (sRsrc, sFull));
1431 if os.path.isfile(sFull):
1432 try:
1433 oFile = utils.openNoInherit(sFull, "rb");
1434 oFile.close();
1435 except Exception, oXcpt:
1436 reporter.error('The file resource "%s" cannot be accessed: %s' % (sFull, oXcpt));
1437 return False;
1438 elif os.path.isdir(sFull):
1439 if not os.path.isdir(os.path.join(sFull, '.')):
1440 reporter.error('The directory resource "%s" cannot be accessed' % (sFull));
1441 return False;
1442 elif os.path.exists(sFull):
1443 reporter.error('The resource "%s" is not a file or directory' % (sFull));
1444 return False;
1445 else:
1446 reporter.error('The resource "%s" was not found' % (sFull));
1447 return False;
1448 return True;
1449
1450 def actionConfig(self):
1451 """
1452 Handle the action that configures the test.
1453 Returns True (success), False (failure) or None (skip the test),
1454 posting complaints and explanations with the reporter.
1455
1456 Override this.
1457 """
1458 return True;
1459
1460 def actionExecute(self):
1461 """
1462 Handle the action that executes the test.
1463
1464 Returns True (success), False (failure) or None (skip the test),
1465 posting complaints and explanations with the reporter.
1466
1467 Override this.
1468 """
1469 return True;
1470
1471 def actionCleanupBefore(self):
1472 """
1473 Handle the action that cleans up spills from previous tests before
1474 starting the tests. This is mostly about wiping the scratch space
1475 clean in local runs. On a testbox the testbox script will use the
1476 cleanup-after if the test is interrupted.
1477
1478 Returns True (success), False (failure) or None (skip the test),
1479 posting complaints and explanations with the reporter.
1480
1481 Override this, but call super to wipe the scratch directory.
1482 """
1483 if self.fNoWipeClean is False:
1484 self.wipeScratch();
1485 return True;
1486
1487 def actionCleanupAfter(self):
1488 """
1489 Handle the action that cleans up all spills from executing the test.
1490
1491 Returns True (success) or False (failure) posting complaints and
1492 explanations with the reporter.
1493
1494 Override this, but call super to wipe the scratch directory.
1495 """
1496 if self.fNoWipeClean is False:
1497 self.wipeScratch();
1498 return True;
1499
1500 def actionAbort(self):
1501 """
1502 Handle the action that aborts a (presumed) running testdriver, making
1503 sure to include all it's children.
1504
1505 Returns True (success) or False (failure) posting complaints and
1506 explanations with the reporter.
1507
1508 Override this, but call super to kill the testdriver script and any
1509 other process covered by the testdriver PID file.
1510 """
1511
1512 aiPids = self.pidFileRead();
1513 reporter.log('The pid file contained: %s' % (aiPids,));
1514
1515 #
1516 # Try convince the processes to quit with increasing impoliteness.
1517 #
1518 if sys.platform == 'win32':
1519 afnMethods = [ processInterrupt, processTerminate ];
1520 else:
1521 afnMethods = [ sendUserSignal1, processInterrupt, processTerminate, processKill ];
1522 for fnMethod in afnMethods:
1523 for iPid in aiPids:
1524 fnMethod(iPid);
1525
1526 for i in range(10):
1527 if i > 0:
1528 time.sleep(1);
1529
1530 for j in range(len(aiPids) - 1, -1, -1):
1531 iPid = aiPids[j];
1532 if not processExists(iPid):
1533 reporter.log('%s terminated' % (iPid,));
1534 self.pidFileRemove(iPid, fQuiet = True);
1535 aiPids.pop(j);
1536
1537 if len(aiPids) == 0:
1538 reporter.log('All done.');
1539 return True;
1540
1541 if i in [4, 8]:
1542 reporter.log('Still waiting for: %s (method=%s)' % (aiPids, fnMethod,));
1543
1544 reporter.log('Failed to terminate the following processes: %s' % (aiPids,));
1545 return False;
1546
1547
1548 def onExit(self, iRc):
1549 """
1550 Hook for doing very important cleanups on the way out.
1551
1552 iRc is the exit code or -1 in the case of an unhandled exception.
1553 Returns nothing and shouldn't raise exceptions (will be muted+ignored).
1554 """
1555 _ = iRc;
1556 return None;
1557
1558
1559 #
1560 # main() - don't override anything!
1561 #
1562
1563 def main(self, asArgs = None):
1564 """
1565 The main function of the test driver.
1566
1567 Keyword arguments:
1568 asArgs -- The argument vector. Defaults to sys.argv.
1569
1570 Returns exit code. No exceptions.
1571 """
1572
1573 #
1574 # Wrap worker in exception handler and always call a 'finally' like
1575 # method to do crucial cleanups on the way out.
1576 #
1577 try:
1578 iRc = self.innerMain(asArgs);
1579 except:
1580 try:
1581 self.onExit(-1);
1582 except:
1583 reporter.logXcpt();
1584 raise;
1585 self.onExit(iRc);
1586 return iRc;
1587
1588
1589 def innerMain(self, asArgs = None): # pylint: disable=R0915
1590 """
1591 Exception wrapped main() worker.
1592 """
1593
1594 # parse the arguments.
1595 if asArgs is None:
1596 asArgs = list(sys.argv);
1597 iArg = 1;
1598 try:
1599 while iArg < len(asArgs):
1600 iNext = self.parseOption(asArgs, iArg);
1601 if iNext == iArg:
1602 iNext = self.subTstDrvParseOption(asArgs, iArg);
1603 if iNext == iArg:
1604 raise InvalidOption('unknown option: %s' % (asArgs[iArg]))
1605 iArg = iNext;
1606 except QuietInvalidOption, oXcpt:
1607 return rtexitcode.RTEXITCODE_SYNTAX;
1608 except InvalidOption, oXcpt:
1609 reporter.error(oXcpt.str());
1610 return rtexitcode.RTEXITCODE_SYNTAX;
1611 except:
1612 reporter.error('unexpected exception while parsing argument #%s' % (iArg));
1613 traceback.print_exc();
1614 return rtexitcode.RTEXITCODE_SYNTAX;
1615
1616 if not self.completeOptions():
1617 return rtexitcode.RTEXITCODE_SYNTAX;
1618
1619 if self.asActions == []:
1620 reporter.error('no action was specified');
1621 reporter.error('valid actions: %s' % (self.asNormalActions + self.asSpecialActions + ['all']));
1622 return rtexitcode.RTEXITCODE_SYNTAX;
1623
1624 # execte the actions.
1625 fRc = True; # Tristate - True (success), False (failure), None (skipped).
1626 asActions = self.asActions;
1627 if 'extract' in asActions:
1628 reporter.log('*** extract action ***');
1629 asActions.remove('extract');
1630 fRc = self.actionExtract();
1631 reporter.log('*** extract action completed (fRc=%s) ***' % (fRc));
1632 elif 'abort' in asActions:
1633 reporter.log('*** abort action ***');
1634 asActions.remove('abort');
1635 fRc = self.actionAbort();
1636 reporter.log('*** abort action completed (fRc=%s) ***' % (fRc));
1637 else:
1638 if asActions == [ 'all' ]:
1639 asActions = self.asNormalActions;
1640
1641 if 'verify' in asActions:
1642 reporter.log('*** verify action ***');
1643 asActions.remove('verify');
1644 fRc = self.actionVerify();
1645 if fRc is True: reporter.log("verified succeeded");
1646 else: reporter.log("verified failed (fRc=%s)" % (fRc,));
1647 reporter.log('*** verify action completed (fRc=%s) ***' % (fRc,));
1648
1649 if 'cleanup-before' in asActions:
1650 reporter.log('*** cleanup-before action ***');
1651 asActions.remove('cleanup-before');
1652 fRc2 = self.actionCleanupBefore();
1653 if fRc2 is not True: reporter.log("cleanup-before failed");
1654 if fRc2 is not True and fRc is True: fRc = fRc2;
1655 reporter.log('*** cleanup-before action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1656
1657 self.pidFileAdd(os.getpid());
1658
1659 if 'config' in asActions and fRc is True:
1660 asActions.remove('config');
1661 reporter.log('*** config action ***');
1662 fRc = self.actionConfig();
1663 if fRc is True: reporter.log("config succeeded");
1664 elif fRc is None: reporter.log("config skipping test");
1665 else: reporter.log("config failed");
1666 reporter.log('*** config action completed (fRc=%s) ***' % (fRc,));
1667
1668 if 'execute' in asActions and fRc is True:
1669 asActions.remove('execute');
1670 reporter.log('*** execute action ***');
1671 fRc = self.actionExecute();
1672 if fRc is True: reporter.log("execute succeeded");
1673 elif fRc is None: reporter.log("execute skipping test");
1674 else: reporter.log("execute failed (fRc=%s)" % (fRc,));
1675 reporter.testCleanup();
1676 reporter.log('*** execute action completed (fRc=%s) ***' % (fRc,));
1677
1678 if 'cleanup-after' in asActions:
1679 reporter.log('*** cleanup-after action ***');
1680 asActions.remove('cleanup-after');
1681 fRc2 = self.actionCleanupAfter();
1682 if fRc2 is not True: reporter.log("cleanup-after failed");
1683 if fRc2 is not True and fRc is True: fRc = fRc2;
1684 reporter.log('*** cleanup-after action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1685
1686 self.pidFileRemove(os.getpid());
1687
1688 if asActions != [] and fRc is True:
1689 reporter.error('unhandled actions: %s' % (asActions,));
1690 fRc = False;
1691
1692 # Done
1693 if fRc is None:
1694 reporter.log('*****************************************');
1695 reporter.log('*** The test driver SKIPPED the test. ***');
1696 reporter.log('*****************************************');
1697 return rtexitcode.RTEXITCODE_SKIPPED;
1698 if fRc is not True:
1699 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1700 reporter.error('!!! The test driver FAILED (in case we forgot to mention it). !!!');
1701 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1702 return rtexitcode.RTEXITCODE_FAILURE;
1703 reporter.log('*******************************************');
1704 reporter.log('*** The test driver exits successfully. ***');
1705 reporter.log('*******************************************');
1706 return rtexitcode.RTEXITCODE_SUCCESS;
1707
1708# The old, deprecated name.
1709TestDriver = TestDriverBase; # pylint: disable=C0103
1710
1711
1712#
1713# Unit testing.
1714#
1715
1716# pylint: disable=C0111
1717class TestDriverBaseTestCase(unittest.TestCase):
1718 def setUp(self):
1719 self.oTstDrv = TestDriverBase();
1720 self.oTstDrv.pidFileDelete();
1721
1722 def tearDown(self):
1723 pass; # clean up scratch dir and such.
1724
1725 def testPidFile(self):
1726 aiPids = [os.getpid() + 1, os.getpid() + 2];
1727 self.assertTrue(self.oTstDrv.pidFileAdd(aiPids[0]));
1728 self.assertEqual(self.oTstDrv.pidFileRead(), aiPids[0:1]);
1729 self.assertTrue(self.oTstDrv.pidFileAdd(aiPids[1]));
1730 self.assertEqual(self.oTstDrv.pidFileRead(), aiPids[0:2]);
1731 self.assertTrue(self.oTstDrv.pidFileDelete());
1732
1733if __name__ == '__main__':
1734 unittest.main();
1735 # not reached.
1736
Note: See TracBrowser for help on using the repository browser.

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