VirtualBox

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

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

(C) year

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