VirtualBox

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

Last change on this file since 94126 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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