VirtualBox

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

Last change on this file since 71371 was 70575, checked in by vboxsync, 7 years ago

ValidationKit: More python 3 adjustments.

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