VirtualBox

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

Last change on this file since 61950 was 61832, checked in by vboxsync, 8 years ago

base.py: pylint 1.5.5 fixes

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