VirtualBox

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

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