VirtualBox

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

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

Valkit: Resource handling regression fix.

  • 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 72732 2018-06-29 06:53:30Z 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: 72732 $"
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 self.asRsrcs = [];
747
748
749 def showUsage(self):
750 """
751 Show usage information if any.
752
753 The default implementation only prints the name.
754 """
755 reporter.log('');
756 reporter.log('Options for sub-test driver %s:' % (self.sName,));
757 return True;
758
759 def parseOption(self, asArgs, iArg):
760 """
761 Parse an option. Override this.
762
763 @param asArgs The argument vector.
764 @param iArg The index of the current argument.
765
766 @returns The index of the next argument if consumed, @a iArg if not.
767
768 @throws InvalidOption or QuietInvalidOption on syntax error or similar.
769 """
770 _ = asArgs;
771 return iArg;
772
773
774class TestDriverBase(object): # pylint: disable=R0902
775 """
776 The base test driver.
777 """
778
779 def __init__(self):
780 self.fInterrupted = False;
781
782 # Actions.
783 self.asSpecialActions = ['extract', 'abort'];
784 self.asNormalActions = ['cleanup-before', 'verify', 'config', 'execute', 'cleanup-after' ];
785 self.asActions = [];
786 self.sExtractDstPath = None;
787
788 # Options.
789 self.fNoWipeClean = False;
790
791 # Tasks - only accessed by one thread atm, so no need for locking.
792 self.aoTasks = [];
793
794 # Host info.
795 self.sHost = utils.getHostOs();
796 self.sHostArch = utils.getHostArch();
797
798 #
799 # Get our bearings and adjust the environment.
800 #
801 if not utils.isRunningFromCheckout():
802 self.sBinPath = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch());
803 else:
804 self.sBinPath = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', utils.getHostOsDotArch(),
805 os.environ.get('KBUILD_TYPE', os.environ.get('BUILD_TYPE', 'debug')),
806 'validationkit', utils.getHostOs(), utils.getHostArch());
807 self.sOrgShell = os.environ.get('SHELL');
808 self.sOurShell = os.path.join(self.sBinPath, 'vts_shell' + exeSuff()); # No shell yet.
809 os.environ['SHELL'] = self.sOurShell;
810
811 self.sScriptPath = getDirEnv('TESTBOX_PATH_SCRIPTS');
812 if self.sScriptPath is None:
813 self.sScriptPath = os.path.abspath(os.path.join(os.getcwd(), '..'));
814 os.environ['TESTBOX_PATH_SCRIPTS'] = self.sScriptPath;
815
816 self.sScratchPath = getDirEnv('TESTBOX_PATH_SCRATCH', fTryCreate = True);
817 if self.sScratchPath is None:
818 sTmpDir = tempfile.gettempdir();
819 if sTmpDir == '/tmp': # /var/tmp is generally more suitable on all platforms.
820 sTmpDir = '/var/tmp';
821 self.sScratchPath = os.path.abspath(os.path.join(sTmpDir, 'VBoxTestTmp'));
822 if not os.path.isdir(self.sScratchPath):
823 os.makedirs(self.sScratchPath, 0o700);
824 os.environ['TESTBOX_PATH_SCRATCH'] = self.sScratchPath;
825
826 self.sTestBoxName = getEnv( 'TESTBOX_NAME', 'local');
827 self.sTestSetId = getEnv( 'TESTBOX_TEST_SET_ID', 'local');
828 self.sBuildPath = getDirEnv('TESTBOX_PATH_BUILDS');
829 self.sUploadPath = getDirEnv('TESTBOX_PATH_UPLOAD');
830 self.sResourcePath = getDirEnv('TESTBOX_PATH_RESOURCES');
831 if self.sResourcePath is None:
832 if self.sHost == 'darwin': self.sResourcePath = "/Volumes/testrsrc/";
833 elif self.sHost == 'freebsd': self.sResourcePath = "/mnt/testrsrc/";
834 elif self.sHost == 'linux': self.sResourcePath = "/mnt/testrsrc/";
835 elif self.sHost == 'os2': self.sResourcePath = "T:/";
836 elif self.sHost == 'solaris': self.sResourcePath = "/mnt/testrsrc/";
837 elif self.sHost == 'win': self.sResourcePath = "T:/";
838 else: raise GenError('unknown host OS "%s"' % (self.sHost));
839
840 # PID file for the testdriver.
841 self.sPidFile = os.path.join(self.sScratchPath, 'testdriver.pid');
842
843 # Some stuff for the log...
844 reporter.log('scratch: %s' % (self.sScratchPath,));
845
846 # Get the absolute timeout (seconds since epoch, see
847 # utils.timestampSecond()). None if not available.
848 self.secTimeoutAbs = os.environ.get('TESTBOX_TIMEOUT_ABS', None);
849 if self.secTimeoutAbs is not None:
850 self.secTimeoutAbs = long(self.secTimeoutAbs);
851 reporter.log('secTimeoutAbs: %s' % (self.secTimeoutAbs,));
852 else:
853 reporter.log('TESTBOX_TIMEOUT_ABS not found in the environment');
854
855 # Distance from secTimeoutAbs that timeouts should be adjusted to.
856 self.secTimeoutFudge = 30;
857
858 # List of sub-test drivers (SubTestDriverBase derivatives).
859 self.aoSubTstDrvs = [];
860
861 # Use the scratch path for temporary files.
862 if self.sHost in ['win', 'os2']:
863 os.environ['TMP'] = self.sScratchPath;
864 os.environ['TEMP'] = self.sScratchPath;
865 os.environ['TMPDIR'] = self.sScratchPath;
866 os.environ['IPRT_TMPDIR'] = self.sScratchPath; # IPRT/VBox specific.
867
868
869 #
870 # Resource utility methods.
871 #
872
873 def isResourceFile(self, sFile):
874 """
875 Checks if sFile is in in the resource set.
876 """
877 ## @todo need to deal with stuff in the validationkit.zip and similar.
878 asRsrcs = self.getResourceSet();
879 if sFile in asRsrcs:
880 return os.path.isfile(os.path.join(self.sResourcePath, sFile));
881 for sRsrc in asRsrcs:
882 if sFile.startswith(sRsrc):
883 sFull = os.path.join(self.sResourcePath, sRsrc);
884 if os.path.isdir(sFull):
885 return os.path.isfile(os.path.join(self.sResourcePath, sRsrc));
886 return False;
887
888 def getFullResourceName(self, sName):
889 """
890 Returns the full resource name.
891 """
892 if os.path.isabs(sName): ## @todo Hack. Need to deal properly with stuff in the validationkit.zip and similar.
893 return sName;
894 return os.path.join(self.sResourcePath, sName);
895
896 #
897 # Scratch related utility methods.
898 #
899
900 def __wipeScratchRecurse(self, sDir):
901 """
902 Deletes all file and sub-directories in sDir.
903 Returns the number of errors.
904 """
905 try:
906 asNames = os.listdir(sDir);
907 except:
908 reporter.errorXcpt('os.listdir("%s")' % (sDir));
909 return False;
910
911 cErrors = 0;
912 for sName in asNames:
913 # Build full path and lstat the object.
914 sFullName = os.path.join(sDir, sName)
915 try:
916 oStat = os.lstat(sFullName);
917 except:
918 reporter.errorXcpt('lstat("%s")' % (sFullName));
919 cErrors = cErrors + 1;
920 continue;
921
922 if stat.S_ISDIR(oStat.st_mode):
923 # Directory - recurse and try remove it.
924 cErrors = cErrors + self.__wipeScratchRecurse(sFullName);
925 try:
926 os.rmdir(sFullName);
927 except:
928 reporter.errorXcpt('rmdir("%s")' % (sFullName));
929 cErrors = cErrors + 1;
930 else:
931 # File, symlink, fifo or something - remove/unlink.
932 try:
933 os.remove(sFullName);
934 except:
935 reporter.errorXcpt('remove("%s")' % (sFullName));
936 cErrors = cErrors + 1;
937 return cErrors;
938
939 def wipeScratch(self):
940 """
941 Removes the content of the scratch directory.
942 Returns True on no errors, False + log entries on errors.
943 """
944 cErrors = self.__wipeScratchRecurse(self.sScratchPath);
945 return cErrors == 0;
946
947 #
948 # Sub-test driver related methods.
949 #
950
951 def addSubTestDriver(self, oSubTstDrv):
952 """
953 Adds a sub-test driver.
954
955 Returns True on success, false on failure.
956 """
957 assert isinstance(oSubTstDrv, SubTestDriverBase);
958 if oSubTstDrv in self.aoSubTstDrvs:
959 reporter.error('Attempt at adding sub-test driver %s twice.' % (oSubTstDrv.sName,));
960 return False;
961 self.aoSubTstDrvs.append(oSubTstDrv);
962 return True;
963
964 def showSubTstDrvUsage(self):
965 """
966 Shows the usage of the sub-test drivers.
967 """
968 for oSubTstDrv in self.aoSubTstDrvs:
969 oSubTstDrv.showUsage();
970 return True;
971
972 def subTstDrvParseOption(self, asArgs, iArgs):
973 """
974 Lets the sub-test drivers have a go at the option.
975 Returns the index of the next option if handled, otherwise iArgs.
976 """
977 for oSubTstDrv in self.aoSubTstDrvs:
978 iNext = oSubTstDrv.parseOption(asArgs, iArgs)
979 if iNext != iArgs:
980 assert iNext > iArgs;
981 assert iNext <= len(asArgs);
982 return iNext;
983 return iArgs;
984
985
986 #
987 # Task related methods.
988 #
989
990 def addTask(self, oTask):
991 """
992 Adds oTask to the task list.
993
994 Returns True if the task was added.
995
996 Returns False if the task was already in the task list.
997 """
998 if oTask in self.aoTasks:
999 return False;
1000 #reporter.log2('adding task %s' % (oTask,));
1001 self.aoTasks.append(oTask);
1002 oTask.setTaskOwner(self);
1003 #reporter.log2('tasks now in list: %d - %s' % (len(self.aoTasks), self.aoTasks));
1004 return True;
1005
1006 def removeTask(self, oTask):
1007 """
1008 Removes oTask to the task list.
1009
1010 Returns oTask on success and None on failure.
1011 """
1012 try:
1013 #reporter.log2('removing task %s' % (oTask,));
1014 self.aoTasks.remove(oTask);
1015 except:
1016 return None;
1017 else:
1018 oTask.setTaskOwner(None);
1019 #reporter.log2('tasks left: %d - %s' % (len(self.aoTasks), self.aoTasks));
1020 return oTask;
1021
1022 def removeAllTasks(self):
1023 """
1024 Removes all the task from the task list.
1025
1026 Returns None.
1027 """
1028 aoTasks = self.aoTasks;
1029 self.aoTasks = [];
1030 for oTask in aoTasks:
1031 oTask.setTaskOwner(None);
1032 return None;
1033
1034 def notifyAboutReadyTask(self, oTask):
1035 """
1036 Notificiation that there is a ready task. May be called owning the
1037 task lock, so be careful wrt deadlocks.
1038
1039 Remember to call super when overriding this.
1040 """
1041 if oTask is None: pass; # lint
1042 return None;
1043
1044 def pollTasks(self):
1045 """
1046 Polls the task to see if any of them are ready.
1047 Returns the ready task, None if none are ready.
1048 """
1049 for oTask in self.aoTasks:
1050 if oTask.pollTask():
1051 return oTask;
1052 return None;
1053
1054 def waitForTasksSleepWorker(self, cMsTimeout):
1055 """
1056 Overriable method that does the sleeping for waitForTask().
1057
1058 cMsTimeout will not be larger than 1000, so there is normally no need
1059 to do any additional splitting up of the polling interval.
1060
1061 Returns True if cMillieSecs elapsed.
1062 Returns False if some exception was raised while we waited or
1063 there turned out to be nothing to wait on.
1064 """
1065 try:
1066 self.aoTasks[0].waitForTask(cMsTimeout);
1067 return True;
1068 except Exception as oXcpt:
1069 reporter.log("waitForTasksSleepWorker: %s" % (str(oXcpt),));
1070 return False;
1071
1072 def waitForTasks(self, cMsTimeout):
1073 """
1074 Waits for any of the tasks to require attention or a KeyboardInterrupt.
1075 Returns the ready task on success, None on timeout or interrupt.
1076 """
1077 try:
1078 #reporter.log2('waitForTasks: cMsTimeout=%d' % (cMsTimeout,));
1079
1080 if cMsTimeout == 0:
1081 return self.pollTasks();
1082
1083 if not self.aoTasks:
1084 return None;
1085
1086 fMore = True;
1087 if cMsTimeout < 0:
1088 while fMore:
1089 oTask = self.pollTasks();
1090 if oTask is not None:
1091 return oTask;
1092 fMore = self.waitForTasksSleepWorker(1000);
1093 else:
1094 msStart = timestampMilli();
1095 while fMore:
1096 oTask = self.pollTasks();
1097 if oTask is not None:
1098 #reporter.log2('waitForTasks: returning %s, msStart=%d' % \
1099 # (oTask, msStart));
1100 return oTask;
1101
1102 cMsElapsed = timestampMilli() - msStart;
1103 if cMsElapsed > cMsTimeout: # not ==, we want the final waitForEvents.
1104 break;
1105 cMsSleep = cMsTimeout - cMsElapsed;
1106 if cMsSleep > 1000:
1107 cMsSleep = 1000;
1108 fMore = self.waitForTasksSleepWorker(cMsSleep);
1109 except KeyboardInterrupt:
1110 self.fInterrupted = True;
1111 reporter.errorXcpt('KeyboardInterrupt', 6);
1112 except:
1113 reporter.errorXcpt(None, 6);
1114 return None;
1115
1116 #
1117 # PID file management methods.
1118 #
1119
1120 def pidFileRead(self):
1121 """
1122 Worker that reads the PID file.
1123 Returns dictionary of PID with value (sName, fSudo), empty if no file.
1124 """
1125 dPids = {};
1126 if os.path.isfile(self.sPidFile):
1127 try:
1128 oFile = utils.openNoInherit(self.sPidFile, 'r');
1129 sContent = str(oFile.read());
1130 oFile.close();
1131 except:
1132 reporter.errorXcpt();
1133 return dPids;
1134
1135 sContent = str(sContent).strip().replace('\n', ' ').replace('\r', ' ').replace('\t', ' ');
1136 for sProcess in sContent.split(' '):
1137 asFields = sProcess.split(':');
1138 if len(asFields) == 3 and asFields[0].isdigit():
1139 try:
1140 dPids[int(asFields[0])] = (asFields[2], asFields[1] == 'sudo');
1141 except:
1142 reporter.logXcpt('sProcess=%s' % (sProcess,));
1143 else:
1144 reporter.log('%s: "%s"' % (self.sPidFile, sProcess));
1145
1146 return dPids;
1147
1148 def pidFileAdd(self, iPid, sName, fSudo = False):
1149 """
1150 Adds a PID to the PID file, creating the file if necessary.
1151 """
1152 try:
1153 oFile = utils.openNoInherit(self.sPidFile, 'a');
1154 oFile.write('%s:%s:%s\n'
1155 % ( iPid,
1156 'sudo' if fSudo else 'normal',
1157 sName.replace(' ', '_').replace(':','_').replace('\n','_').replace('\r','_').replace('\t','_'),));
1158 oFile.close();
1159 except:
1160 reporter.errorXcpt();
1161 return False;
1162 ## @todo s/log/log2/
1163 reporter.log('pidFileAdd: added %s (%#x) %s fSudo=%s (new content: %s)'
1164 % (iPid, iPid, sName, fSudo, self.pidFileRead(),));
1165 return True;
1166
1167 def pidFileRemove(self, iPid, fQuiet = False):
1168 """
1169 Removes a PID from the PID file.
1170 """
1171 dPids = self.pidFileRead();
1172 if iPid not in dPids:
1173 if not fQuiet:
1174 reporter.log('pidFileRemove could not find %s in the PID file (content: %s)' % (iPid, dPids));
1175 return False;
1176
1177 sName = dPids[iPid][0];
1178 del dPids[iPid];
1179
1180 sPid = '';
1181 for iPid2 in dPids:
1182 sPid += '%s:%s:%s\n' % (iPid2, 'sudo' if dPids[iPid2][1] else 'normal', dPids[iPid2][0]);
1183
1184 try:
1185 oFile = utils.openNoInherit(self.sPidFile, 'w');
1186 oFile.write(sPid);
1187 oFile.close();
1188 except:
1189 reporter.errorXcpt();
1190 return False;
1191 ## @todo s/log/log2/
1192 reporter.log('pidFileRemove: removed PID %d [%s] (new content: %s)' % (iPid, sName, self.pidFileRead(),));
1193 return True;
1194
1195 def pidFileDelete(self):
1196 """Creates the testdriver PID file."""
1197 if os.path.isfile(self.sPidFile):
1198 try:
1199 os.unlink(self.sPidFile);
1200 except:
1201 reporter.logXcpt();
1202 return False;
1203 ## @todo s/log/log2/
1204 reporter.log('pidFileDelete: deleted "%s"' % (self.sPidFile,));
1205 return True;
1206
1207 #
1208 # Misc helper methods.
1209 #
1210
1211 def requireMoreArgs(self, cMinNeeded, asArgs, iArg):
1212 """
1213 Checks that asArgs has at least cMinNeeded args following iArg.
1214
1215 Returns iArg + 1 if it checks out fine.
1216 Raise appropritate exception if not, ASSUMING that the current argument
1217 is found at iArg.
1218 """
1219 assert cMinNeeded >= 1;
1220 if iArg + cMinNeeded > len(asArgs):
1221 if cMinNeeded > 1:
1222 raise InvalidOption('The "%s" option takes %s values' % (asArgs[iArg], cMinNeeded,));
1223 raise InvalidOption('The "%s" option takes 1 value' % (asArgs[iArg],));
1224 return iArg + 1;
1225
1226 def getBinTool(self, sName):
1227 """
1228 Returns the full path to the given binary validation kit tool.
1229 """
1230 return os.path.join(self.sBinPath, sName) + exeSuff();
1231
1232 def adjustTimeoutMs(self, cMsTimeout, cMsMinimum = None):
1233 """
1234 Adjusts the given timeout (milliseconds) to take TESTBOX_TIMEOUT_ABS
1235 and cMsMinimum (optional) into account.
1236
1237 Returns adjusted timeout.
1238 Raises no exceptions.
1239 """
1240 if self.secTimeoutAbs is not None:
1241 cMsToDeadline = self.secTimeoutAbs * 1000 - utils.timestampMilli();
1242 if cMsToDeadline >= 0:
1243 # Adjust for fudge and enforce the minimum timeout
1244 cMsToDeadline -= self.secTimeoutFudge * 1000;
1245 if cMsToDeadline < (cMsMinimum if cMsMinimum is not None else 10000):
1246 cMsToDeadline = cMsMinimum if cMsMinimum is not None else 10000;
1247
1248 # Is the timeout beyond the (adjusted) deadline, if so change it.
1249 if cMsTimeout > cMsToDeadline:
1250 reporter.log('adjusting timeout: %s ms -> %s ms (deadline)\n' % (cMsTimeout, cMsToDeadline,));
1251 return cMsToDeadline;
1252 reporter.log('adjustTimeoutMs: cMsTimeout (%s) > cMsToDeadline (%s)' % (cMsTimeout, cMsToDeadline,));
1253 else:
1254 # Don't bother, we've passed the deadline.
1255 reporter.log('adjustTimeoutMs: ooops! cMsToDeadline=%s (%s), timestampMilli()=%s, timestampSecond()=%s'
1256 % (cMsToDeadline, cMsToDeadline*1000, utils.timestampMilli(), utils.timestampSecond()));
1257
1258 # Only enforce the minimum timeout if specified.
1259 if cMsMinimum is not None and cMsTimeout < cMsMinimum:
1260 reporter.log('adjusting timeout: %s ms -> %s ms (minimum)\n' % (cMsTimeout, cMsMinimum,));
1261 cMsTimeout = cMsMinimum;
1262
1263 return cMsTimeout;
1264
1265 def prepareResultFile(self, sName = 'results.xml'):
1266 """
1267 Given a base name (no path, but extension if required), a scratch file
1268 name is computed and any previous file removed.
1269
1270 Returns the full path to the file sName.
1271 Raises exception on failure.
1272 """
1273 sXmlFile = os.path.join(self.sScratchPath, sName);
1274 if os.path.exists(sXmlFile):
1275 os.unlink(sXmlFile);
1276 return sXmlFile;
1277
1278
1279 #
1280 # Overridable methods.
1281 #
1282
1283 def showUsage(self):
1284 """
1285 Shows the usage.
1286
1287 When overriding this, call super first.
1288 """
1289 sName = os.path.basename(sys.argv[0]);
1290 reporter.log('Usage: %s [options] <action(s)>' % (sName,));
1291 reporter.log('');
1292 reporter.log('Actions (in execution order):');
1293 reporter.log(' cleanup-before');
1294 reporter.log(' Cleanups done at the start of testing.');
1295 reporter.log(' verify');
1296 reporter.log(' Verify that all necessary resources are present.');
1297 reporter.log(' config');
1298 reporter.log(' Configure the tests.');
1299 reporter.log(' execute');
1300 reporter.log(' Execute the tests.');
1301 reporter.log(' cleanup-after');
1302 reporter.log(' Cleanups done at the end of the testing.');
1303 reporter.log('');
1304 reporter.log('Special Actions:');
1305 reporter.log(' all');
1306 reporter.log(' Alias for: %s' % (' '.join(self.asNormalActions),));
1307 reporter.log(' extract <path>');
1308 reporter.log(' Extract the test resources and put them in the specified');
1309 reporter.log(' path for off side/line testing.');
1310 reporter.log(' abort');
1311 reporter.log(' Aborts the test.');
1312 reporter.log('');
1313 reporter.log('Base Options:');
1314 reporter.log(' -h, --help');
1315 reporter.log(' Show this help message.');
1316 reporter.log(' -v, --verbose');
1317 reporter.log(' Increase logging verbosity, repeat for more logging.');
1318 reporter.log(' -d, --debug');
1319 reporter.log(' Increase the debug logging level, repeat for more info.');
1320 reporter.log(' --no-wipe-clean');
1321 reporter.log(' Do not wipe clean the scratch area during the two clean up');
1322 reporter.log(' actions. This is for facilitating nested test driver execution.');
1323 return True;
1324
1325 def parseOption(self, asArgs, iArg):
1326 """
1327 Parse an option. Override this.
1328
1329 Keyword arguments:
1330 asArgs -- The argument vector.
1331 iArg -- The index of the current argument.
1332
1333 Returns iArg if the option was not recognized.
1334 Returns the index of the next argument when something is consumed.
1335 In the event of a syntax error, a InvalidOption or QuietInvalidOption
1336 should be thrown.
1337 """
1338
1339 if asArgs[iArg] in ('--help', '-help', '-h', '-?', '/?', '/help', '/H', '-H'):
1340 self.showUsage();
1341 self.showSubTstDrvUsage();
1342 raise QuietInvalidOption();
1343
1344 # options
1345 if asArgs[iArg] in ('--verbose', '-v'):
1346 reporter.incVerbosity()
1347 elif asArgs[iArg] in ('--debug', '-d'):
1348 reporter.incDebug()
1349 elif asArgs[iArg] == '--no-wipe-clean':
1350 self.fNoWipeClean = True;
1351 elif (asArgs[iArg] == 'all' or asArgs[iArg] in self.asNormalActions) \
1352 and self.asActions in self.asSpecialActions:
1353 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1354 # actions
1355 elif asArgs[iArg] == 'all':
1356 self.asActions = [ 'all' ];
1357 elif asArgs[iArg] in self.asNormalActions:
1358 self.asActions.append(asArgs[iArg])
1359 elif asArgs[iArg] in self.asSpecialActions:
1360 if self.asActions != []:
1361 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1362 self.asActions = [ asArgs[iArg] ];
1363 # extact <destination>
1364 if asArgs[iArg] == 'extract':
1365 iArg = iArg + 1;
1366 if iArg >= len(asArgs): raise InvalidOption('The "extract" action requires a destination directory');
1367 self.sExtractDstPath = asArgs[iArg];
1368 else:
1369 return iArg;
1370 return iArg + 1;
1371
1372 def completeOptions(self):
1373 """
1374 This method is called after parsing all the options.
1375 Returns success indicator. Use the reporter to complain.
1376
1377 Overriable, call super.
1378 """
1379 return True;
1380
1381 def getResourceSet(self):
1382 """
1383 Returns a set of file and/or directory names relative to
1384 TESTBOX_PATH_RESOURCES.
1385
1386 Override this.
1387 """
1388 return [];
1389
1390 def actionExtract(self):
1391 """
1392 Handle the action that extracts the test resources for off site use.
1393 Returns a success indicator and error details with the reporter.
1394
1395 Usually no need to override this.
1396 """
1397 reporter.error('the extract action is not implemented')
1398 return False;
1399
1400 def actionVerify(self):
1401 """
1402 Handle the action that verify the test resources.
1403 Returns a success indicator and error details with the reporter.
1404
1405 There is usually no need to override this.
1406 """
1407
1408 asRsrcs = self.getResourceSet();
1409 for sRsrc in asRsrcs:
1410 # Go thru some pain to catch escape sequences.
1411 if sRsrc.find("//") >= 0:
1412 reporter.error('Double slash test resource name: "%s"' % (sRsrc));
1413 return False;
1414 if sRsrc == ".." \
1415 or sRsrc.startswith("../") \
1416 or sRsrc.find("/../") >= 0 \
1417 or sRsrc.endswith("/.."):
1418 reporter.error('Relative path in test resource name: "%s"' % (sRsrc));
1419 return False;
1420
1421 sFull = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc)));
1422 if not sFull.startswith(os.path.normpath(self.sResourcePath)):
1423 reporter.error('sFull="%s" self.sResourcePath=%s' % (sFull, self.sResourcePath));
1424 reporter.error('The resource "%s" seems to specify a relative path' % (sRsrc));
1425 return False;
1426
1427 reporter.log2('Checking for resource "%s" at "%s" ...' % (sRsrc, sFull));
1428 if os.path.isfile(sFull):
1429 try:
1430 oFile = utils.openNoInherit(sFull, "rb");
1431 oFile.close();
1432 except Exception as oXcpt:
1433 reporter.error('The file resource "%s" cannot be accessed: %s' % (sFull, oXcpt));
1434 return False;
1435 elif os.path.isdir(sFull):
1436 if not os.path.isdir(os.path.join(sFull, '.')):
1437 reporter.error('The directory resource "%s" cannot be accessed' % (sFull));
1438 return False;
1439 elif os.path.exists(sFull):
1440 reporter.error('The resource "%s" is not a file or directory' % (sFull));
1441 return False;
1442 else:
1443 reporter.error('The resource "%s" was not found' % (sFull));
1444 return False;
1445 return True;
1446
1447 def actionConfig(self):
1448 """
1449 Handle the action that configures the test.
1450 Returns True (success), False (failure) or None (skip the test),
1451 posting complaints and explanations with the reporter.
1452
1453 Override this.
1454 """
1455 return True;
1456
1457 def actionExecute(self):
1458 """
1459 Handle the action that executes the test.
1460
1461 Returns True (success), False (failure) or None (skip the test),
1462 posting complaints and explanations with the reporter.
1463
1464 Override this.
1465 """
1466 return True;
1467
1468 def actionCleanupBefore(self):
1469 """
1470 Handle the action that cleans up spills from previous tests before
1471 starting the tests. This is mostly about wiping the scratch space
1472 clean in local runs. On a testbox the testbox script will use the
1473 cleanup-after if the test is interrupted.
1474
1475 Returns True (success), False (failure) or None (skip the test),
1476 posting complaints and explanations with the reporter.
1477
1478 Override this, but call super to wipe the scratch directory.
1479 """
1480 if self.fNoWipeClean is False:
1481 self.wipeScratch();
1482 return True;
1483
1484 def actionCleanupAfter(self):
1485 """
1486 Handle the action that cleans up all spills from executing the test.
1487
1488 Returns True (success) or False (failure) posting complaints and
1489 explanations with the reporter.
1490
1491 Override this, but call super to wipe the scratch directory.
1492 """
1493 if self.fNoWipeClean is False:
1494 self.wipeScratch();
1495 return True;
1496
1497 def actionAbort(self):
1498 """
1499 Handle the action that aborts a (presumed) running testdriver, making
1500 sure to include all it's children.
1501
1502 Returns True (success) or False (failure) posting complaints and
1503 explanations with the reporter.
1504
1505 Override this, but call super to kill the testdriver script and any
1506 other process covered by the testdriver PID file.
1507 """
1508
1509 dPids = self.pidFileRead();
1510 reporter.log('The pid file contained: %s' % (dPids,));
1511
1512 #
1513 # Try convince the processes to quit with increasing impoliteness.
1514 #
1515 if sys.platform == 'win32':
1516 afnMethods = [ processInterrupt, processTerminate ];
1517 else:
1518 afnMethods = [ sendUserSignal1, processInterrupt, processTerminate, processKill ];
1519 for fnMethod in afnMethods:
1520 for iPid in dPids:
1521 fnMethod(iPid, fSudo = dPids[iPid][1]);
1522
1523 for i in range(10):
1524 if i > 0:
1525 time.sleep(1);
1526
1527 for iPid in dPids.keys():
1528 if not processExists(iPid):
1529 reporter.log('%s (%s) terminated' % (dPids[iPid][0], iPid,));
1530 self.pidFileRemove(iPid, fQuiet = True);
1531 del dPids[iPid];
1532
1533 if not dPids:
1534 reporter.log('All done.');
1535 return True;
1536
1537 if i in [4, 8]:
1538 reporter.log('Still waiting for: %s (method=%s)' % (dPids, fnMethod,));
1539
1540 reporter.log('Failed to terminate the following processes: %s' % (dPids,));
1541 return False;
1542
1543
1544 def onExit(self, iRc):
1545 """
1546 Hook for doing very important cleanups on the way out.
1547
1548 iRc is the exit code or -1 in the case of an unhandled exception.
1549 Returns nothing and shouldn't raise exceptions (will be muted+ignored).
1550 """
1551 _ = iRc;
1552 return None;
1553
1554
1555 #
1556 # main() - don't override anything!
1557 #
1558
1559 def main(self, asArgs = None):
1560 """
1561 The main function of the test driver.
1562
1563 Keyword arguments:
1564 asArgs -- The argument vector. Defaults to sys.argv.
1565
1566 Returns exit code. No exceptions.
1567 """
1568
1569 #
1570 # Wrap worker in exception handler and always call a 'finally' like
1571 # method to do crucial cleanups on the way out.
1572 #
1573 try:
1574 iRc = self.innerMain(asArgs);
1575 except:
1576 try:
1577 self.onExit(-1);
1578 except:
1579 reporter.logXcpt();
1580 raise;
1581 self.onExit(iRc);
1582 return iRc;
1583
1584
1585 def innerMain(self, asArgs = None): # pylint: disable=R0915
1586 """
1587 Exception wrapped main() worker.
1588 """
1589
1590 # parse the arguments.
1591 if asArgs is None:
1592 asArgs = list(sys.argv);
1593 iArg = 1;
1594 try:
1595 while iArg < len(asArgs):
1596 iNext = self.parseOption(asArgs, iArg);
1597 if iNext == iArg:
1598 iNext = self.subTstDrvParseOption(asArgs, iArg);
1599 if iNext == iArg:
1600 raise InvalidOption('unknown option: %s' % (asArgs[iArg]))
1601 iArg = iNext;
1602 except QuietInvalidOption as oXcpt:
1603 return rtexitcode.RTEXITCODE_SYNTAX;
1604 except InvalidOption as oXcpt:
1605 reporter.error(oXcpt.str());
1606 return rtexitcode.RTEXITCODE_SYNTAX;
1607 except:
1608 reporter.error('unexpected exception while parsing argument #%s' % (iArg));
1609 traceback.print_exc();
1610 return rtexitcode.RTEXITCODE_SYNTAX;
1611
1612 if not self.completeOptions():
1613 return rtexitcode.RTEXITCODE_SYNTAX;
1614
1615 if self.asActions == []:
1616 reporter.error('no action was specified');
1617 reporter.error('valid actions: %s' % (self.asNormalActions + self.asSpecialActions + ['all']));
1618 return rtexitcode.RTEXITCODE_SYNTAX;
1619
1620 # execte the actions.
1621 fRc = True; # Tristate - True (success), False (failure), None (skipped).
1622 asActions = self.asActions;
1623 if 'extract' in asActions:
1624 reporter.log('*** extract action ***');
1625 asActions.remove('extract');
1626 fRc = self.actionExtract();
1627 reporter.log('*** extract action completed (fRc=%s) ***' % (fRc));
1628 elif 'abort' in asActions:
1629 reporter.appendToProcessName('/abort'); # Make it easier to spot in the log.
1630 reporter.log('*** abort action ***');
1631 asActions.remove('abort');
1632 fRc = self.actionAbort();
1633 reporter.log('*** abort action completed (fRc=%s) ***' % (fRc));
1634 else:
1635 if asActions == [ 'all' ]:
1636 asActions = self.asNormalActions;
1637
1638 if 'verify' in asActions:
1639 reporter.log('*** verify action ***');
1640 asActions.remove('verify');
1641 fRc = self.actionVerify();
1642 if fRc is True: reporter.log("verified succeeded");
1643 else: reporter.log("verified failed (fRc=%s)" % (fRc,));
1644 reporter.log('*** verify action completed (fRc=%s) ***' % (fRc,));
1645
1646 if 'cleanup-before' in asActions:
1647 reporter.log('*** cleanup-before action ***');
1648 asActions.remove('cleanup-before');
1649 fRc2 = self.actionCleanupBefore();
1650 if fRc2 is not True: reporter.log("cleanup-before failed");
1651 if fRc2 is not True and fRc is True: fRc = fRc2;
1652 reporter.log('*** cleanup-before action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1653
1654 self.pidFileAdd(os.getpid(), os.path.basename(sys.argv[0]));
1655
1656 if 'config' in asActions and fRc is True:
1657 asActions.remove('config');
1658 reporter.log('*** config action ***');
1659 fRc = self.actionConfig();
1660 if fRc is True: reporter.log("config succeeded");
1661 elif fRc is None: reporter.log("config skipping test");
1662 else: reporter.log("config failed");
1663 reporter.log('*** config action completed (fRc=%s) ***' % (fRc,));
1664
1665 if 'execute' in asActions and fRc is True:
1666 asActions.remove('execute');
1667 reporter.log('*** execute action ***');
1668 fRc = self.actionExecute();
1669 if fRc is True: reporter.log("execute succeeded");
1670 elif fRc is None: reporter.log("execute skipping test");
1671 else: reporter.log("execute failed (fRc=%s)" % (fRc,));
1672 reporter.testCleanup();
1673 reporter.log('*** execute action completed (fRc=%s) ***' % (fRc,));
1674
1675 if 'cleanup-after' in asActions:
1676 reporter.log('*** cleanup-after action ***');
1677 asActions.remove('cleanup-after');
1678 fRc2 = self.actionCleanupAfter();
1679 if fRc2 is not True: reporter.log("cleanup-after failed");
1680 if fRc2 is not True and fRc is True: fRc = fRc2;
1681 reporter.log('*** cleanup-after action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1682
1683 self.pidFileRemove(os.getpid());
1684
1685 if asActions != [] and fRc is True:
1686 reporter.error('unhandled actions: %s' % (asActions,));
1687 fRc = False;
1688
1689 # Done
1690 if fRc is None:
1691 reporter.log('*****************************************');
1692 reporter.log('*** The test driver SKIPPED the test. ***');
1693 reporter.log('*****************************************');
1694 return rtexitcode.RTEXITCODE_SKIPPED;
1695 if fRc is not True:
1696 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1697 reporter.error('!!! The test driver FAILED (in case we forgot to mention it). !!!');
1698 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1699 return rtexitcode.RTEXITCODE_FAILURE;
1700 reporter.log('*******************************************');
1701 reporter.log('*** The test driver exits successfully. ***');
1702 reporter.log('*******************************************');
1703 return rtexitcode.RTEXITCODE_SUCCESS;
1704
1705# The old, deprecated name.
1706TestDriver = TestDriverBase; # pylint: disable=C0103
1707
1708
1709#
1710# Unit testing.
1711#
1712
1713# pylint: disable=C0111
1714class TestDriverBaseTestCase(unittest.TestCase):
1715 def setUp(self):
1716 self.oTstDrv = TestDriverBase();
1717 self.oTstDrv.pidFileDelete();
1718
1719 def tearDown(self):
1720 pass; # clean up scratch dir and such.
1721
1722 def testPidFile(self):
1723
1724 iPid1 = os.getpid() + 1;
1725 iPid2 = os.getpid() + 2;
1726
1727 self.assertTrue(self.oTstDrv.pidFileAdd(iPid1, 'test1'));
1728 self.assertEqual(self.oTstDrv.pidFileRead(), {iPid1:('test1',False)});
1729
1730 self.assertTrue(self.oTstDrv.pidFileAdd(iPid2, 'test2', fSudo = True));
1731 self.assertEqual(self.oTstDrv.pidFileRead(), {iPid1:('test1',False), iPid2:('test2',True)});
1732
1733 self.assertTrue(self.oTstDrv.pidFileRemove(iPid1));
1734 self.assertEqual(self.oTstDrv.pidFileRead(), {iPid2:('test2',True)});
1735
1736 self.assertTrue(self.oTstDrv.pidFileRemove(iPid2));
1737 self.assertEqual(self.oTstDrv.pidFileRead(), {});
1738
1739 self.assertTrue(self.oTstDrv.pidFileDelete());
1740
1741if __name__ == '__main__':
1742 unittest.main();
1743 # not reached.
1744
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