VirtualBox

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

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

testdriver: More python 3 adjustments.

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