VirtualBox

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

Last change on this file since 94184 was 94139, checked in by vboxsync, 3 years ago

ValKit/testdriver: pylint 2.9.6 adjustments (missed some).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.1 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: base.py 94139 2022-03-08 22:42:18Z 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: 94139 $"
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); # pylint: disable=consider-using-with
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 cMsWait = min(cMsWait, 1000);
550 try:
551 self.oCv.wait(cMsWait / 1000.0);
552 except:
553 pass;
554
555 if self.fnProcessEvents:
556 self.unlockTask();
557 self.fnProcessEvents();
558 self.lockTask();
559
560 reporter.doPollWork('TdTaskBase.waitForTask');
561 fState = self.pollTask(True);
562
563 self.unlockTask();
564
565 if self.fnProcessEvents:
566 self.fnProcessEvents();
567
568 return fState;
569
570
571class Process(TdTaskBase):
572 """
573 Child Process.
574 """
575
576 def __init__(self, sName, asArgs, uPid, hWin = None, uTid = None):
577 TdTaskBase.__init__(self, utils.getCallerName());
578 self.sName = sName;
579 self.asArgs = asArgs;
580 self.uExitCode = -127;
581 self.uPid = uPid;
582 self.hWin = hWin;
583 self.uTid = uTid;
584 self.sKindCrashReport = None;
585 self.sKindCrashDump = None;
586
587 def toString(self):
588 return '<%s uExitcode=%s, uPid=%s, sName=%s, asArgs=%s, hWin=%s, uTid=%s>' \
589 % (TdTaskBase.toString(self), self.uExitCode, self.uPid, self.sName, self.asArgs, self.hWin, self.uTid);
590
591 #
592 # Instantiation methods.
593 #
594
595 @staticmethod
596 def spawn(sName, *asArgsIn):
597 """
598 Similar to os.spawnl(os.P_NOWAIT,).
599
600 """
601 # Make argument array (can probably use asArgsIn directly, but wtf).
602 asArgs = [];
603 for sArg in asArgsIn:
604 asArgs.append(sArg);
605
606 # Special case: Windows.
607 if sys.platform == 'win32':
608 (uPid, hProcess, uTid) = winbase.processCreate(searchPath(sName), asArgs);
609 if uPid == -1:
610 return None;
611 return Process(sName, asArgs, uPid, hProcess, uTid);
612
613 # Unixy.
614 try:
615 uPid = os.spawnv(os.P_NOWAIT, sName, asArgs);
616 except:
617 reporter.logXcpt('sName=%s' % (sName,));
618 return None;
619 return Process(sName, asArgs, uPid);
620
621 @staticmethod
622 def spawnp(sName, *asArgsIn):
623 """
624 Similar to os.spawnlp(os.P_NOWAIT,).
625
626 """
627 return Process.spawn(searchPath(sName), *asArgsIn);
628
629 #
630 # Task methods
631 #
632
633 def pollTask(self, fLocked = False):
634 """
635 Overridden pollTask method.
636 """
637 if not fLocked:
638 self.lockTask();
639
640 fRc = self.fSignalled;
641 if not fRc:
642 if sys.platform == 'win32':
643 if winbase.processPollByHandle(self.hWin):
644 try:
645 (uPid, uStatus) = os.waitpid(self.hWin, 0);
646 if uPid in (self.hWin, self.uPid,):
647 self.hWin.Detach(); # waitpid closed it, so it's now invalid.
648 self.hWin = None;
649 uPid = self.uPid;
650 except:
651 reporter.logXcpt();
652 uPid = self.uPid;
653 uStatus = 0xffffffff;
654 else:
655 uPid = 0;
656 uStatus = 0; # pylint: disable=redefined-variable-type
657 else:
658 try:
659 (uPid, uStatus) = os.waitpid(self.uPid, os.WNOHANG); # pylint: disable=no-member
660 except:
661 reporter.logXcpt();
662 uPid = self.uPid;
663 uStatus = 0xffffffff;
664
665 # Got anything?
666 if uPid == self.uPid:
667 self.uExitCode = uStatus;
668 reporter.log('Process %u -> %u (%#x)' % (uPid, uStatus, uStatus));
669 self.signalTaskLocked();
670 if self.uExitCode != 0 and (self.sKindCrashReport is not None or self.sKindCrashDump is not None):
671 reporter.error('Process "%s" returned/crashed with a non-zero status code!! rc=%u sig=%u%s (raw=%#x)'
672 % ( self.sName, self.uExitCode >> 8, self.uExitCode & 0x7f,
673 ' w/ core' if self.uExitCode & 0x80 else '', self.uExitCode))
674 utils.processCollectCrashInfo(self.uPid, reporter.log, self._addCrashFile);
675
676 fRc = self.fSignalled;
677 if not fLocked:
678 self.unlockTask();
679 return fRc;
680
681 def _addCrashFile(self, sFile, fBinary):
682 """
683 Helper for adding a crash report or dump to the test report.
684 """
685 sKind = self.sKindCrashDump if fBinary else self.sKindCrashReport;
686 if sKind is not None:
687 reporter.addLogFile(sFile, sKind);
688 return None;
689
690
691 #
692 # Methods
693 #
694
695 def enableCrashReporting(self, sKindCrashReport, sKindCrashDump):
696 """
697 Enabling (or disables) automatic crash reporting on systems where that
698 is possible. The two file kind parameters are on the form
699 'crash/log/client' and 'crash/dump/client'. If both are None,
700 reporting will be disabled.
701 """
702 self.sKindCrashReport = sKindCrashReport;
703 self.sKindCrashDump = sKindCrashDump;
704 return True;
705
706 def isRunning(self):
707 """
708 Returns True if the process is still running, False if not.
709 """
710 return not self.pollTask();
711
712 def wait(self, cMsTimeout = 0):
713 """
714 Wait for the process to exit.
715
716 Returns True if the process exited withint the specified wait period.
717 Returns False if still running.
718 """
719 return self.waitForTask(cMsTimeout);
720
721 def getExitCode(self):
722 """
723 Returns the exit code of the process.
724 The process must have exited or the result will be wrong.
725 """
726 if self.isRunning():
727 return -127;
728 return self.uExitCode >> 8;
729
730 def isNormalExit(self):
731 """
732 Returns True if regular exit(), False if signal or still running.
733 """
734 if self.isRunning():
735 return False;
736 if sys.platform == 'win32':
737 return True;
738 return os.WIFEXITED(self.uExitCode); # pylint: disable=no-member
739
740 def interrupt(self):
741 """
742 Sends a SIGINT or equivalent to interrupt the process.
743 Returns True on success, False on failure.
744
745 On Windows hosts this may not work unless the process happens to be a
746 process group leader.
747 """
748 if sys.platform == 'win32':
749 return winbase.postThreadMesssageQuit(self.uTid);
750 return processInterrupt(self.uPid);
751
752 def sendUserSignal1(self):
753 """
754 Sends a SIGUSR1 or equivalent to nudge the process into shutting down
755 (VBoxSVC) or something.
756 Returns True on success, False on failure.
757
758 On Windows hosts this may not work unless the process happens to be a
759 process group leader.
760 """
761 #if sys.platform == 'win32':
762 # return winbase.postThreadMesssageClose(self.uTid);
763 return sendUserSignal1(self.uPid);
764
765 def terminate(self):
766 """
767 Terminates the process in a nice manner (SIGTERM or equivalent).
768 Returns True on success, False on failure (logged).
769 """
770 if sys.platform == 'win32':
771 return winbase.processTerminateByHandle(self.hWin);
772 return processTerminate(self.uPid);
773
774 def getPid(self):
775 """ Returns the process id. """
776 return self.uPid;
777
778
779class SubTestDriverBase(object):
780 """
781 The base sub-test driver.
782
783 It helps thinking of these as units/sets/groups of tests, where the test
784 cases are (mostly) realized in python.
785
786 The sub-test drivers are subordinates of one or more test drivers. They
787 can be viewed as test code libraries that is responsible for parts of a
788 test driver run in different setups. One example would be testing a guest
789 additions component, which is applicable both to freshly installed guest
790 additions and VMs with old guest.
791
792 The test drivers invokes the sub-test drivers in a private manner during
793 test execution, but some of the generic bits are done automagically by the
794 base class: options, help, resources, various other actions.
795 """
796
797 def __init__(self, oTstDrv, sName, sTestName):
798 self.oTstDrv = oTstDrv # type: TestDriverBase
799 self.sName = sName; # For use with options (--enable-sub-driver sName:sName2)
800 self.sTestName = sTestName; # More descriptive for passing to reporter.testStart().
801 self.asRsrcs = [] # type: List(str)
802 self.fEnabled = True; # TestDriverBase --enable-sub-driver and --disable-sub-driver.
803
804 def showUsage(self):
805 """
806 Show usage information if any.
807
808 The default implementation only prints the name.
809 """
810 reporter.log('');
811 reporter.log('Options for sub-test driver %s (%s):' % (self.sTestName, self.sName,));
812 return True;
813
814 def parseOption(self, asArgs, iArg):
815 """
816 Parse an option. Override this.
817
818 @param asArgs The argument vector.
819 @param iArg The index of the current argument.
820
821 @returns The index of the next argument if consumed, @a iArg if not.
822
823 @throws InvalidOption or QuietInvalidOption on syntax error or similar.
824 """
825 _ = asArgs;
826 return iArg;
827
828
829class TestDriverBase(object): # pylint: disable=too-many-instance-attributes
830 """
831 The base test driver.
832 """
833
834 def __init__(self):
835 self.fInterrupted = False;
836
837 # Actions.
838 self.asSpecialActions = ['extract', 'abort'];
839 self.asNormalActions = ['cleanup-before', 'verify', 'config', 'execute', 'cleanup-after' ];
840 self.asActions = [];
841 self.sExtractDstPath = None;
842
843 # Options.
844 self.fNoWipeClean = False;
845
846 # Tasks - only accessed by one thread atm, so no need for locking.
847 self.aoTasks = [];
848
849 # Host info.
850 self.sHost = utils.getHostOs();
851 self.sHostArch = utils.getHostArch();
852
853 # Skipped status modifier (see end of innerMain()).
854 self.fBadTestbox = False;
855
856 #
857 # Get our bearings and adjust the environment.
858 #
859 if not utils.isRunningFromCheckout():
860 self.sBinPath = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch());
861 else:
862 self.sBinPath = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', utils.getHostOsDotArch(),
863 os.environ.get('KBUILD_TYPE', 'debug'),
864 'validationkit', utils.getHostOs(), utils.getHostArch());
865 self.sOrgShell = os.environ.get('SHELL');
866 self.sOurShell = os.path.join(self.sBinPath, 'vts_shell' + exeSuff()); # No shell yet.
867 os.environ['SHELL'] = self.sOurShell;
868
869 self.sScriptPath = getDirEnv('TESTBOX_PATH_SCRIPTS');
870 if self.sScriptPath is None:
871 self.sScriptPath = os.path.abspath(os.path.join(os.getcwd(), '..'));
872 os.environ['TESTBOX_PATH_SCRIPTS'] = self.sScriptPath;
873
874 self.sScratchPath = getDirEnv('TESTBOX_PATH_SCRATCH', fTryCreate = True);
875 if self.sScratchPath is None:
876 sTmpDir = tempfile.gettempdir();
877 if sTmpDir == '/tmp': # /var/tmp is generally more suitable on all platforms.
878 sTmpDir = '/var/tmp';
879 self.sScratchPath = os.path.abspath(os.path.join(sTmpDir, 'VBoxTestTmp'));
880 if not os.path.isdir(self.sScratchPath):
881 os.makedirs(self.sScratchPath, 0o700);
882 os.environ['TESTBOX_PATH_SCRATCH'] = self.sScratchPath;
883
884 self.sTestBoxName = getEnv( 'TESTBOX_NAME', 'local');
885 self.sTestSetId = getEnv( 'TESTBOX_TEST_SET_ID', 'local');
886 self.sBuildPath = getDirEnv('TESTBOX_PATH_BUILDS');
887 self.sUploadPath = getDirEnv('TESTBOX_PATH_UPLOAD');
888 self.sResourcePath = getDirEnv('TESTBOX_PATH_RESOURCES');
889 if self.sResourcePath is None:
890 if self.sHost == 'darwin': self.sResourcePath = "/Volumes/testrsrc/";
891 elif self.sHost == 'freebsd': self.sResourcePath = "/mnt/testrsrc/";
892 elif self.sHost == 'linux': self.sResourcePath = "/mnt/testrsrc/";
893 elif self.sHost == 'os2': self.sResourcePath = "T:/";
894 elif self.sHost == 'solaris': self.sResourcePath = "/mnt/testrsrc/";
895 elif self.sHost == 'win': self.sResourcePath = "T:/";
896 else: raise GenError('unknown host OS "%s"' % (self.sHost));
897
898 # PID file for the testdriver.
899 self.sPidFile = os.path.join(self.sScratchPath, 'testdriver.pid');
900
901 # Some stuff for the log...
902 reporter.log('scratch: %s' % (self.sScratchPath,));
903
904 # Get the absolute timeout (seconds since epoch, see
905 # utils.timestampSecond()). None if not available.
906 self.secTimeoutAbs = os.environ.get('TESTBOX_TIMEOUT_ABS', None);
907 if self.secTimeoutAbs is not None:
908 self.secTimeoutAbs = long(self.secTimeoutAbs);
909 reporter.log('secTimeoutAbs: %s' % (self.secTimeoutAbs,));
910 else:
911 reporter.log('TESTBOX_TIMEOUT_ABS not found in the environment');
912
913 # Distance from secTimeoutAbs that timeouts should be adjusted to.
914 self.secTimeoutFudge = 30;
915
916 # List of sub-test drivers (SubTestDriverBase derivatives).
917 self.aoSubTstDrvs = [] # type: list(SubTestDriverBase)
918
919 # Use the scratch path for temporary files.
920 if self.sHost in ['win', 'os2']:
921 os.environ['TMP'] = self.sScratchPath;
922 os.environ['TEMP'] = self.sScratchPath;
923 os.environ['TMPDIR'] = self.sScratchPath;
924 os.environ['IPRT_TMPDIR'] = self.sScratchPath; # IPRT/VBox specific.
925
926
927 #
928 # Resource utility methods.
929 #
930
931 def isResourceFile(self, sFile):
932 """
933 Checks if sFile is in in the resource set.
934 """
935 ## @todo need to deal with stuff in the validationkit.zip and similar.
936 asRsrcs = self.getResourceSet();
937 if sFile in asRsrcs:
938 return os.path.isfile(os.path.join(self.sResourcePath, sFile));
939 for sRsrc in asRsrcs:
940 if sFile.startswith(sRsrc):
941 sFull = os.path.join(self.sResourcePath, sRsrc);
942 if os.path.isdir(sFull):
943 return os.path.isfile(os.path.join(self.sResourcePath, sRsrc));
944 return False;
945
946 def getFullResourceName(self, sName):
947 """
948 Returns the full resource name.
949 """
950 if os.path.isabs(sName): ## @todo Hack. Need to deal properly with stuff in the validationkit.zip and similar.
951 return sName;
952 return os.path.join(self.sResourcePath, sName);
953
954 #
955 # Scratch related utility methods.
956 #
957
958 def wipeScratch(self):
959 """
960 Removes the content of the scratch directory.
961 Returns True on no errors, False + log entries on errors.
962 """
963 cErrors = wipeDirectory(self.sScratchPath);
964 return cErrors == 0;
965
966 #
967 # Sub-test driver related methods.
968 #
969
970 def addSubTestDriver(self, oSubTstDrv):
971 """
972 Adds a sub-test driver.
973
974 Returns True on success, false on failure.
975 """
976 assert isinstance(oSubTstDrv, SubTestDriverBase);
977 if oSubTstDrv in self.aoSubTstDrvs:
978 reporter.error('Attempt at adding sub-test driver %s twice.' % (oSubTstDrv.sName,));
979 return False;
980 self.aoSubTstDrvs.append(oSubTstDrv);
981 return True;
982
983 def showSubTstDrvUsage(self):
984 """
985 Shows the usage of the sub-test drivers.
986 """
987 for oSubTstDrv in self.aoSubTstDrvs:
988 oSubTstDrv.showUsage();
989 return True;
990
991 def subTstDrvParseOption(self, asArgs, iArgs):
992 """
993 Lets the sub-test drivers have a go at the option.
994 Returns the index of the next option if handled, otherwise iArgs.
995 """
996 for oSubTstDrv in self.aoSubTstDrvs:
997 iNext = oSubTstDrv.parseOption(asArgs, iArgs)
998 if iNext != iArgs:
999 assert iNext > iArgs;
1000 assert iNext <= len(asArgs);
1001 return iNext;
1002 return iArgs;
1003
1004 def findSubTstDrvByShortName(self, sShortName):
1005 """
1006 Locates a sub-test driver by it's short name.
1007 Returns sub-test driver object reference if found, None if not.
1008 """
1009 for oSubTstDrv in self.aoSubTstDrvs:
1010 if oSubTstDrv.sName == sShortName:
1011 return oSubTstDrv;
1012 return None;
1013
1014
1015 #
1016 # Task related methods.
1017 #
1018
1019 def addTask(self, oTask):
1020 """
1021 Adds oTask to the task list.
1022
1023 Returns True if the task was added.
1024
1025 Returns False if the task was already in the task list.
1026 """
1027 if oTask in self.aoTasks:
1028 return False;
1029 #reporter.log2('adding task %s' % (oTask,));
1030 self.aoTasks.append(oTask);
1031 oTask.setTaskOwner(self);
1032 #reporter.log2('tasks now in list: %d - %s' % (len(self.aoTasks), self.aoTasks));
1033 return True;
1034
1035 def removeTask(self, oTask):
1036 """
1037 Removes oTask to the task list.
1038
1039 Returns oTask on success and None on failure.
1040 """
1041 try:
1042 #reporter.log2('removing task %s' % (oTask,));
1043 self.aoTasks.remove(oTask);
1044 except:
1045 return None;
1046 else:
1047 oTask.setTaskOwner(None);
1048 #reporter.log2('tasks left: %d - %s' % (len(self.aoTasks), self.aoTasks));
1049 return oTask;
1050
1051 def removeAllTasks(self):
1052 """
1053 Removes all the task from the task list.
1054
1055 Returns None.
1056 """
1057 aoTasks = self.aoTasks;
1058 self.aoTasks = [];
1059 for oTask in aoTasks:
1060 oTask.setTaskOwner(None);
1061 return None;
1062
1063 def notifyAboutReadyTask(self, oTask):
1064 """
1065 Notificiation that there is a ready task. May be called owning the
1066 task lock, so be careful wrt deadlocks.
1067
1068 Remember to call super when overriding this.
1069 """
1070 if oTask is None: pass; # lint
1071 return None;
1072
1073 def pollTasks(self):
1074 """
1075 Polls the task to see if any of them are ready.
1076 Returns the ready task, None if none are ready.
1077 """
1078 for oTask in self.aoTasks:
1079 if oTask.pollTask():
1080 return oTask;
1081 return None;
1082
1083 def waitForTasksSleepWorker(self, cMsTimeout):
1084 """
1085 Overridable method that does the sleeping for waitForTask().
1086
1087 cMsTimeout will not be larger than 1000, so there is normally no need
1088 to do any additional splitting up of the polling interval.
1089
1090 Returns True if cMillieSecs elapsed.
1091 Returns False if some exception was raised while we waited or
1092 there turned out to be nothing to wait on.
1093 """
1094 try:
1095 self.aoTasks[0].waitForTask(cMsTimeout);
1096 return True;
1097 except Exception as oXcpt:
1098 reporter.log("waitForTasksSleepWorker: %s" % (str(oXcpt),));
1099 return False;
1100
1101 def waitForTasks(self, cMsTimeout):
1102 """
1103 Waits for any of the tasks to require attention or a KeyboardInterrupt.
1104 Returns the ready task on success, None on timeout or interrupt.
1105 """
1106 try:
1107 #reporter.log2('waitForTasks: cMsTimeout=%d' % (cMsTimeout,));
1108
1109 if cMsTimeout == 0:
1110 return self.pollTasks();
1111
1112 if not self.aoTasks:
1113 return None;
1114
1115 fMore = True;
1116 if cMsTimeout < 0:
1117 while fMore:
1118 oTask = self.pollTasks();
1119 if oTask is not None:
1120 return oTask;
1121 fMore = self.waitForTasksSleepWorker(1000);
1122 else:
1123 msStart = timestampMilli();
1124 while fMore:
1125 oTask = self.pollTasks();
1126 if oTask is not None:
1127 #reporter.log2('waitForTasks: returning %s, msStart=%d' % \
1128 # (oTask, msStart));
1129 return oTask;
1130
1131 cMsElapsed = timestampMilli() - msStart;
1132 if cMsElapsed > cMsTimeout: # not ==, we want the final waitForEvents.
1133 break;
1134 cMsSleep = cMsTimeout - cMsElapsed;
1135 cMsSleep = min(cMsSleep, 1000);
1136 fMore = self.waitForTasksSleepWorker(cMsSleep);
1137 except KeyboardInterrupt:
1138 self.fInterrupted = True;
1139 reporter.errorXcpt('KeyboardInterrupt', 6);
1140 except:
1141 reporter.errorXcpt(None, 6);
1142 return None;
1143
1144 #
1145 # PID file management methods.
1146 #
1147
1148 def pidFileRead(self):
1149 """
1150 Worker that reads the PID file.
1151 Returns dictionary of PID with value (sName, fSudo), empty if no file.
1152 """
1153 dPids = {};
1154 if os.path.isfile(self.sPidFile):
1155 try:
1156 oFile = utils.openNoInherit(self.sPidFile, 'r');
1157 sContent = str(oFile.read());
1158 oFile.close();
1159 except:
1160 reporter.errorXcpt();
1161 return dPids;
1162
1163 sContent = str(sContent).strip().replace('\n', ' ').replace('\r', ' ').replace('\t', ' ');
1164 for sProcess in sContent.split(' '):
1165 asFields = sProcess.split(':');
1166 if len(asFields) == 3 and asFields[0].isdigit():
1167 try:
1168 dPids[int(asFields[0])] = (asFields[2], asFields[1] == 'sudo');
1169 except:
1170 reporter.logXcpt('sProcess=%s' % (sProcess,));
1171 else:
1172 reporter.log('%s: "%s"' % (self.sPidFile, sProcess));
1173
1174 return dPids;
1175
1176 def pidFileAdd(self, iPid, sName, fSudo = False):
1177 """
1178 Adds a PID to the PID file, creating the file if necessary.
1179 """
1180 try:
1181 oFile = utils.openNoInherit(self.sPidFile, 'a');
1182 oFile.write('%s:%s:%s\n'
1183 % ( iPid,
1184 'sudo' if fSudo else 'normal',
1185 sName.replace(' ', '_').replace(':','_').replace('\n','_').replace('\r','_').replace('\t','_'),));
1186 oFile.close();
1187 except:
1188 reporter.errorXcpt();
1189 return False;
1190 ## @todo s/log/log2/
1191 reporter.log('pidFileAdd: added %s (%#x) %s fSudo=%s (new content: %s)'
1192 % (iPid, iPid, sName, fSudo, self.pidFileRead(),));
1193 return True;
1194
1195 def pidFileRemove(self, iPid, fQuiet = False):
1196 """
1197 Removes a PID from the PID file.
1198 """
1199 dPids = self.pidFileRead();
1200 if iPid not in dPids:
1201 if not fQuiet:
1202 reporter.log('pidFileRemove could not find %s in the PID file (content: %s)' % (iPid, dPids));
1203 return False;
1204
1205 sName = dPids[iPid][0];
1206 del dPids[iPid];
1207
1208 sPid = '';
1209 for iPid2, tNameSudo in dPids.items():
1210 sPid += '%s:%s:%s\n' % (iPid2, 'sudo' if tNameSudo[1] else 'normal', tNameSudo[0]);
1211
1212 try:
1213 oFile = utils.openNoInherit(self.sPidFile, 'w');
1214 oFile.write(sPid);
1215 oFile.close();
1216 except:
1217 reporter.errorXcpt();
1218 return False;
1219 ## @todo s/log/log2/
1220 reporter.log('pidFileRemove: removed PID %d [%s] (new content: %s)' % (iPid, sName, self.pidFileRead(),));
1221 return True;
1222
1223 def pidFileDelete(self):
1224 """Creates the testdriver PID file."""
1225 if os.path.isfile(self.sPidFile):
1226 try:
1227 os.unlink(self.sPidFile);
1228 except:
1229 reporter.logXcpt();
1230 return False;
1231 ## @todo s/log/log2/
1232 reporter.log('pidFileDelete: deleted "%s"' % (self.sPidFile,));
1233 return True;
1234
1235 #
1236 # Misc helper methods.
1237 #
1238
1239 def requireMoreArgs(self, cMinNeeded, asArgs, iArg):
1240 """
1241 Checks that asArgs has at least cMinNeeded args following iArg.
1242
1243 Returns iArg + 1 if it checks out fine.
1244 Raise appropritate exception if not, ASSUMING that the current argument
1245 is found at iArg.
1246 """
1247 assert cMinNeeded >= 1;
1248 if iArg + cMinNeeded > len(asArgs):
1249 if cMinNeeded > 1:
1250 raise InvalidOption('The "%s" option takes %s values' % (asArgs[iArg], cMinNeeded,));
1251 raise InvalidOption('The "%s" option takes 1 value' % (asArgs[iArg],));
1252 return iArg + 1;
1253
1254 def getBinTool(self, sName):
1255 """
1256 Returns the full path to the given binary validation kit tool.
1257 """
1258 return os.path.join(self.sBinPath, sName) + exeSuff();
1259
1260 def adjustTimeoutMs(self, cMsTimeout, cMsMinimum = None):
1261 """
1262 Adjusts the given timeout (milliseconds) to take TESTBOX_TIMEOUT_ABS
1263 and cMsMinimum (optional) into account.
1264
1265 Returns adjusted timeout.
1266 Raises no exceptions.
1267 """
1268 if self.secTimeoutAbs is not None:
1269 cMsToDeadline = self.secTimeoutAbs * 1000 - utils.timestampMilli();
1270 if cMsToDeadline >= 0:
1271 # Adjust for fudge and enforce the minimum timeout
1272 cMsToDeadline -= self.secTimeoutFudge * 1000;
1273 if cMsToDeadline < (cMsMinimum if cMsMinimum is not None else 10000):
1274 cMsToDeadline = cMsMinimum if cMsMinimum is not None else 10000;
1275
1276 # Is the timeout beyond the (adjusted) deadline, if so change it.
1277 if cMsTimeout > cMsToDeadline:
1278 reporter.log('adjusting timeout: %s ms -> %s ms (deadline)\n' % (cMsTimeout, cMsToDeadline,));
1279 return cMsToDeadline;
1280 reporter.log('adjustTimeoutMs: cMsTimeout (%s) > cMsToDeadline (%s)' % (cMsTimeout, cMsToDeadline,));
1281 else:
1282 # Don't bother, we've passed the deadline.
1283 reporter.log('adjustTimeoutMs: ooops! cMsToDeadline=%s (%s), timestampMilli()=%s, timestampSecond()=%s'
1284 % (cMsToDeadline, cMsToDeadline*1000, utils.timestampMilli(), utils.timestampSecond()));
1285
1286 # Only enforce the minimum timeout if specified.
1287 if cMsMinimum is not None and cMsTimeout < cMsMinimum:
1288 reporter.log('adjusting timeout: %s ms -> %s ms (minimum)\n' % (cMsTimeout, cMsMinimum,));
1289 cMsTimeout = cMsMinimum;
1290
1291 return cMsTimeout;
1292
1293 def prepareResultFile(self, sName = 'results.xml'):
1294 """
1295 Given a base name (no path, but extension if required), a scratch file
1296 name is computed and any previous file removed.
1297
1298 Returns the full path to the file sName.
1299 Raises exception on failure.
1300 """
1301 sXmlFile = os.path.join(self.sScratchPath, sName);
1302 if os.path.exists(sXmlFile):
1303 os.unlink(sXmlFile);
1304 return sXmlFile;
1305
1306
1307 #
1308 # Overridable methods.
1309 #
1310
1311 def showUsage(self):
1312 """
1313 Shows the usage.
1314
1315 When overriding this, call super first.
1316 """
1317 sName = os.path.basename(sys.argv[0]);
1318 reporter.log('Usage: %s [options] <action(s)>' % (sName,));
1319 reporter.log('');
1320 reporter.log('Actions (in execution order):');
1321 reporter.log(' cleanup-before');
1322 reporter.log(' Cleanups done at the start of testing.');
1323 reporter.log(' verify');
1324 reporter.log(' Verify that all necessary resources are present.');
1325 reporter.log(' config');
1326 reporter.log(' Configure the tests.');
1327 reporter.log(' execute');
1328 reporter.log(' Execute the tests.');
1329 reporter.log(' cleanup-after');
1330 reporter.log(' Cleanups done at the end of the testing.');
1331 reporter.log('');
1332 reporter.log('Special Actions:');
1333 reporter.log(' all');
1334 reporter.log(' Alias for: %s' % (' '.join(self.asNormalActions),));
1335 reporter.log(' extract <path>');
1336 reporter.log(' Extract the test resources and put them in the specified');
1337 reporter.log(' path for off side/line testing.');
1338 reporter.log(' abort');
1339 reporter.log(' Aborts the test.');
1340 reporter.log('');
1341 reporter.log('Base Options:');
1342 reporter.log(' -h, --help');
1343 reporter.log(' Show this help message.');
1344 reporter.log(' -v, --verbose');
1345 reporter.log(' Increase logging verbosity, repeat for more logging.');
1346 reporter.log(' -d, --debug');
1347 reporter.log(' Increase the debug logging level, repeat for more info.');
1348 reporter.log(' --no-wipe-clean');
1349 reporter.log(' Do not wipe clean the scratch area during the two clean up');
1350 reporter.log(' actions. This is for facilitating nested test driver execution.');
1351 if self.aoSubTstDrvs:
1352 reporter.log(' --enable-sub-driver <sub1>[:..]');
1353 reporter.log(' --disable-sub-driver <sub1>[:..]');
1354 reporter.log(' Enables or disables one or more of the sub drivers: %s'
1355 % (', '.join([oSubTstDrv.sName for oSubTstDrv in self.aoSubTstDrvs]),));
1356 return True;
1357
1358 def parseOption(self, asArgs, iArg):
1359 """
1360 Parse an option. Override this.
1361
1362 Keyword arguments:
1363 asArgs -- The argument vector.
1364 iArg -- The index of the current argument.
1365
1366 Returns iArg if the option was not recognized.
1367 Returns the index of the next argument when something is consumed.
1368 In the event of a syntax error, a InvalidOption or QuietInvalidOption
1369 should be thrown.
1370 """
1371
1372 if asArgs[iArg] in ('--help', '-help', '-h', '-?', '/?', '/help', '/H', '-H'):
1373 self.showUsage();
1374 self.showSubTstDrvUsage();
1375 raise QuietInvalidOption();
1376
1377 # options
1378 if asArgs[iArg] in ('--verbose', '-v'):
1379 reporter.incVerbosity()
1380 elif asArgs[iArg] in ('--debug', '-d'):
1381 reporter.incDebug()
1382 elif asArgs[iArg] == '--no-wipe-clean':
1383 self.fNoWipeClean = True;
1384 elif asArgs[iArg] in ('--enable-sub-driver', '--disable-sub-driver') and self.aoSubTstDrvs:
1385 sOption = asArgs[iArg];
1386 iArg = self.requireMoreArgs(1, asArgs, iArg);
1387 for sSubTstDrvName in asArgs[iArg].split(':'):
1388 oSubTstDrv = self.findSubTstDrvByShortName(sSubTstDrvName);
1389 if oSubTstDrv is None:
1390 raise InvalidOption('Unknown sub-test driver given to %s: %s' % (sOption, sSubTstDrvName,));
1391 oSubTstDrv.fEnabled = sOption == '--enable-sub-driver';
1392 elif (asArgs[iArg] == 'all' or asArgs[iArg] in self.asNormalActions) \
1393 and self.asActions in self.asSpecialActions:
1394 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1395 # actions
1396 elif asArgs[iArg] == 'all':
1397 self.asActions = [ 'all' ];
1398 elif asArgs[iArg] in self.asNormalActions:
1399 self.asActions.append(asArgs[iArg])
1400 elif asArgs[iArg] in self.asSpecialActions:
1401 if self.asActions != []:
1402 raise InvalidOption('selected special action "%s" already' % (self.asActions[0], ));
1403 self.asActions = [ asArgs[iArg] ];
1404 # extact <destination>
1405 if asArgs[iArg] == 'extract':
1406 iArg = iArg + 1;
1407 if iArg >= len(asArgs): raise InvalidOption('The "extract" action requires a destination directory');
1408 self.sExtractDstPath = asArgs[iArg];
1409 else:
1410 return iArg;
1411 return iArg + 1;
1412
1413 def completeOptions(self):
1414 """
1415 This method is called after parsing all the options.
1416 Returns success indicator. Use the reporter to complain.
1417
1418 Overriable, call super.
1419 """
1420 return True;
1421
1422 def getResourceSet(self):
1423 """
1424 Returns a set of file and/or directory names relative to
1425 TESTBOX_PATH_RESOURCES.
1426
1427 Override this, call super when using sub-test drivers.
1428 """
1429 asRsrcs = [];
1430 for oSubTstDrv in self.aoSubTstDrvs:
1431 asRsrcs.extend(oSubTstDrv.asRsrcs);
1432 return asRsrcs;
1433
1434 def actionExtract(self):
1435 """
1436 Handle the action that extracts the test resources for off site use.
1437 Returns a success indicator and error details with the reporter.
1438
1439 There is usually no need to override this.
1440 """
1441 fRc = True;
1442 asRsrcs = self.getResourceSet();
1443 for iRsrc, sRsrc in enumerate(asRsrcs):
1444 reporter.log('Resource #%s: "%s"' % (iRsrc, sRsrc));
1445 sSrcPath = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc.replace('/', os.path.sep))));
1446 sDstPath = os.path.normpath(os.path.join(self.sExtractDstPath, sRsrc.replace('/', os.path.sep)));
1447
1448 sDstDir = os.path.dirname(sDstPath);
1449 if not os.path.exists(sDstDir):
1450 try: os.makedirs(sDstDir, 0o775);
1451 except: fRc = reporter.errorXcpt('Error creating directory "%s":' % (sDstDir,));
1452
1453 if os.path.isfile(sSrcPath):
1454 try: utils.copyFileSimple(sSrcPath, sDstPath);
1455 except: fRc = reporter.errorXcpt('Error copying "%s" to "%s":' % (sSrcPath, sDstPath,));
1456 elif os.path.isdir(sSrcPath):
1457 fRc = reporter.error('Extracting directories have not been implemented yet');
1458 else:
1459 fRc = reporter.error('Missing or unsupported resource type: %s' % (sSrcPath,));
1460 return fRc;
1461
1462 def actionVerify(self):
1463 """
1464 Handle the action that verify the test resources.
1465 Returns a success indicator and error details with the reporter.
1466
1467 There is usually no need to override this.
1468 """
1469
1470 asRsrcs = self.getResourceSet();
1471 for sRsrc in asRsrcs:
1472 # Go thru some pain to catch escape sequences.
1473 if sRsrc.find("//") >= 0:
1474 reporter.error('Double slash test resource name: "%s"' % (sRsrc));
1475 return False;
1476 if sRsrc == ".." \
1477 or sRsrc.startswith("../") \
1478 or sRsrc.find("/../") >= 0 \
1479 or sRsrc.endswith("/.."):
1480 reporter.error('Relative path in test resource name: "%s"' % (sRsrc));
1481 return False;
1482
1483 sFull = os.path.normpath(os.path.abspath(os.path.join(self.sResourcePath, sRsrc)));
1484 if not sFull.startswith(os.path.normpath(self.sResourcePath)):
1485 reporter.error('sFull="%s" self.sResourcePath=%s' % (sFull, self.sResourcePath));
1486 reporter.error('The resource "%s" seems to specify a relative path' % (sRsrc));
1487 return False;
1488
1489 reporter.log2('Checking for resource "%s" at "%s" ...' % (sRsrc, sFull));
1490 if os.path.isfile(sFull):
1491 try:
1492 oFile = utils.openNoInherit(sFull, "rb");
1493 oFile.close();
1494 except Exception as oXcpt:
1495 reporter.error('The file resource "%s" cannot be accessed: %s' % (sFull, oXcpt));
1496 return False;
1497 elif os.path.isdir(sFull):
1498 if not os.path.isdir(os.path.join(sFull, '.')):
1499 reporter.error('The directory resource "%s" cannot be accessed' % (sFull));
1500 return False;
1501 elif os.path.exists(sFull):
1502 reporter.error('The resource "%s" is not a file or directory' % (sFull));
1503 return False;
1504 else:
1505 reporter.error('The resource "%s" was not found' % (sFull));
1506 return False;
1507 return True;
1508
1509 def actionConfig(self):
1510 """
1511 Handle the action that configures the test.
1512 Returns True (success), False (failure) or None (skip the test),
1513 posting complaints and explanations with the reporter.
1514
1515 Override this.
1516 """
1517 return True;
1518
1519 def actionExecute(self):
1520 """
1521 Handle the action that executes the test.
1522
1523 Returns True (success), False (failure) or None (skip the test),
1524 posting complaints and explanations with the reporter.
1525
1526 Override this.
1527 """
1528 return True;
1529
1530 def actionCleanupBefore(self):
1531 """
1532 Handle the action that cleans up spills from previous tests before
1533 starting the tests. This is mostly about wiping the scratch space
1534 clean in local runs. On a testbox the testbox script will use the
1535 cleanup-after if the test is interrupted.
1536
1537 Returns True (success), False (failure) or None (skip the test),
1538 posting complaints and explanations with the reporter.
1539
1540 Override this, but call super to wipe the scratch directory.
1541 """
1542 if self.fNoWipeClean is False:
1543 self.wipeScratch();
1544 return True;
1545
1546 def actionCleanupAfter(self):
1547 """
1548 Handle the action that cleans up all spills from executing the test.
1549
1550 Returns True (success) or False (failure) posting complaints and
1551 explanations with the reporter.
1552
1553 Override this, but call super to wipe the scratch directory.
1554 """
1555 if self.fNoWipeClean is False:
1556 self.wipeScratch();
1557 return True;
1558
1559 def actionAbort(self):
1560 """
1561 Handle the action that aborts a (presumed) running testdriver, making
1562 sure to include all it's children.
1563
1564 Returns True (success) or False (failure) posting complaints and
1565 explanations with the reporter.
1566
1567 Override this, but call super to kill the testdriver script and any
1568 other process covered by the testdriver PID file.
1569 """
1570
1571 dPids = self.pidFileRead();
1572 reporter.log('The pid file contained: %s' % (dPids,));
1573
1574 #
1575 # Try convince the processes to quit with increasing impoliteness.
1576 #
1577 if sys.platform == 'win32':
1578 afnMethods = [ processInterrupt, processTerminate ];
1579 else:
1580 afnMethods = [ sendUserSignal1, processInterrupt, processTerminate, processKill ];
1581 for fnMethod in afnMethods:
1582 for iPid, tNameSudo in dPids.items():
1583 fnMethod(iPid, fSudo = tNameSudo[1]);
1584
1585 for i in range(10):
1586 if i > 0:
1587 time.sleep(1);
1588
1589 dPidsToRemove = []; # Temporary dict to append PIDs to remove later.
1590
1591 for iPid, tNameSudo in dPids.items():
1592 if not processExists(iPid):
1593 reporter.log('%s (%s) terminated' % (tNameSudo[0], iPid,));
1594 self.pidFileRemove(iPid, fQuiet = True);
1595 dPidsToRemove.append(iPid);
1596 continue;
1597
1598 # Remove PIDs from original dictionary, as removing keys from a
1599 # dictionary while iterating on it won't work and will result in a RuntimeError.
1600 for iPidToRemove in dPidsToRemove:
1601 del dPids[iPidToRemove];
1602
1603 if not dPids:
1604 reporter.log('All done.');
1605 return True;
1606
1607 if i in [4, 8]:
1608 reporter.log('Still waiting for: %s (method=%s)' % (dPids, fnMethod,));
1609
1610 reporter.log('Failed to terminate the following processes: %s' % (dPids,));
1611 return False;
1612
1613
1614 def onExit(self, iRc):
1615 """
1616 Hook for doing very important cleanups on the way out.
1617
1618 iRc is the exit code or -1 in the case of an unhandled exception.
1619 Returns nothing and shouldn't raise exceptions (will be muted+ignored).
1620 """
1621 _ = iRc;
1622 return None;
1623
1624
1625 #
1626 # main() - don't override anything!
1627 #
1628
1629 def main(self, asArgs = None):
1630 """
1631 The main function of the test driver.
1632
1633 Keyword arguments:
1634 asArgs -- The argument vector. Defaults to sys.argv.
1635
1636 Returns exit code. No exceptions.
1637 """
1638
1639 #
1640 # Wrap worker in exception handler and always call a 'finally' like
1641 # method to do crucial cleanups on the way out.
1642 #
1643 try:
1644 iRc = self.innerMain(asArgs);
1645 except:
1646 reporter.logXcpt(cFrames = None);
1647 try:
1648 self.onExit(-1);
1649 except:
1650 reporter.logXcpt();
1651 raise;
1652 self.onExit(iRc);
1653 return iRc;
1654
1655
1656 def innerMain(self, asArgs = None): # pylint: disable=too-many-statements
1657 """
1658 Exception wrapped main() worker.
1659 """
1660
1661 #
1662 # Parse the arguments.
1663 #
1664 if asArgs is None:
1665 asArgs = list(sys.argv);
1666 iArg = 1;
1667 try:
1668 while iArg < len(asArgs):
1669 iNext = self.parseOption(asArgs, iArg);
1670 if iNext == iArg:
1671 iNext = self.subTstDrvParseOption(asArgs, iArg);
1672 if iNext == iArg:
1673 raise InvalidOption('unknown option: %s' % (asArgs[iArg]))
1674 iArg = iNext;
1675 except QuietInvalidOption as oXcpt:
1676 return rtexitcode.RTEXITCODE_SYNTAX;
1677 except InvalidOption as oXcpt:
1678 reporter.error(oXcpt.str());
1679 return rtexitcode.RTEXITCODE_SYNTAX;
1680 except:
1681 reporter.error('unexpected exception while parsing argument #%s' % (iArg));
1682 traceback.print_exc();
1683 return rtexitcode.RTEXITCODE_SYNTAX;
1684
1685 if not self.completeOptions():
1686 return rtexitcode.RTEXITCODE_SYNTAX;
1687
1688 if self.asActions == []:
1689 reporter.error('no action was specified');
1690 reporter.error('valid actions: %s' % (self.asNormalActions + self.asSpecialActions + ['all']));
1691 return rtexitcode.RTEXITCODE_SYNTAX;
1692
1693 #
1694 # Execte the actions.
1695 #
1696 fRc = True; # Tristate - True (success), False (failure), None (skipped).
1697 asActions = list(self.asActions); # Must copy it or vboxinstaller.py breaks.
1698 if 'extract' in asActions:
1699 reporter.log('*** extract action ***');
1700 asActions.remove('extract');
1701 fRc = self.actionExtract();
1702 reporter.log('*** extract action completed (fRc=%s) ***' % (fRc));
1703 elif 'abort' in asActions:
1704 reporter.appendToProcessName('/abort'); # Make it easier to spot in the log.
1705 reporter.log('*** abort action ***');
1706 asActions.remove('abort');
1707 fRc = self.actionAbort();
1708 reporter.log('*** abort action completed (fRc=%s) ***' % (fRc));
1709 else:
1710 if asActions == [ 'all' ]:
1711 asActions = list(self.asNormalActions);
1712
1713 if 'verify' in asActions:
1714 reporter.log('*** verify action ***');
1715 asActions.remove('verify');
1716 fRc = self.actionVerify();
1717 if fRc is True: reporter.log("verified succeeded");
1718 else: reporter.log("verified failed (fRc=%s)" % (fRc,));
1719 reporter.log('*** verify action completed (fRc=%s) ***' % (fRc,));
1720
1721 if 'cleanup-before' in asActions:
1722 reporter.log('*** cleanup-before action ***');
1723 asActions.remove('cleanup-before');
1724 fRc2 = self.actionCleanupBefore();
1725 if fRc2 is not True: reporter.log("cleanup-before failed");
1726 if fRc2 is not True and fRc is True: fRc = fRc2;
1727 reporter.log('*** cleanup-before action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1728
1729 self.pidFileAdd(os.getpid(), os.path.basename(sys.argv[0]));
1730
1731 if 'config' in asActions and fRc is True:
1732 asActions.remove('config');
1733 reporter.log('*** config action ***');
1734 fRc = self.actionConfig();
1735 if fRc is True: reporter.log("config succeeded");
1736 elif fRc is None: reporter.log("config skipping test");
1737 else: reporter.log("config failed");
1738 reporter.log('*** config action completed (fRc=%s) ***' % (fRc,));
1739
1740 if 'execute' in asActions and fRc is True:
1741 asActions.remove('execute');
1742 reporter.log('*** execute action ***');
1743 fRc = self.actionExecute();
1744 if fRc is True: reporter.log("execute succeeded");
1745 elif fRc is None: reporter.log("execute skipping test");
1746 else: reporter.log("execute failed (fRc=%s)" % (fRc,));
1747 reporter.testCleanup();
1748 reporter.log('*** execute action completed (fRc=%s) ***' % (fRc,));
1749
1750 if 'cleanup-after' in asActions:
1751 reporter.log('*** cleanup-after action ***');
1752 asActions.remove('cleanup-after');
1753 fRc2 = self.actionCleanupAfter();
1754 if fRc2 is not True: reporter.log("cleanup-after failed");
1755 if fRc2 is not True and fRc is True: fRc = fRc2;
1756 reporter.log('*** cleanup-after action completed (fRc2=%s, fRc=%s) ***' % (fRc2, fRc,));
1757
1758 self.pidFileRemove(os.getpid());
1759
1760 if asActions != [] and fRc is True:
1761 reporter.error('unhandled actions: %s' % (asActions,));
1762 fRc = False;
1763
1764 #
1765 # Done - report the final result.
1766 #
1767 if fRc is None:
1768 if self.fBadTestbox:
1769 reporter.log('****************************************************************');
1770 reporter.log('*** The test driver SKIPPED the test because of BAD_TESTBOX. ***');
1771 reporter.log('****************************************************************');
1772 return rtexitcode.RTEXITCODE_BAD_TESTBOX;
1773 reporter.log('*****************************************');
1774 reporter.log('*** The test driver SKIPPED the test. ***');
1775 reporter.log('*****************************************');
1776 return rtexitcode.RTEXITCODE_SKIPPED;
1777 if fRc is not True:
1778 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1779 reporter.error('!!! The test driver FAILED (in case we forgot to mention it). !!!');
1780 reporter.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
1781 return rtexitcode.RTEXITCODE_FAILURE;
1782 reporter.log('*******************************************');
1783 reporter.log('*** The test driver exits successfully. ***');
1784 reporter.log('*******************************************');
1785 return rtexitcode.RTEXITCODE_SUCCESS;
1786
1787# The old, deprecated name.
1788TestDriver = TestDriverBase; # pylint: disable=invalid-name
1789
1790
1791#
1792# Unit testing.
1793#
1794
1795# pylint: disable=missing-docstring
1796class TestDriverBaseTestCase(unittest.TestCase):
1797 def setUp(self):
1798 self.oTstDrv = TestDriverBase();
1799 self.oTstDrv.pidFileDelete();
1800
1801 def tearDown(self):
1802 pass; # clean up scratch dir and such.
1803
1804 def testPidFile(self):
1805
1806 iPid1 = os.getpid() + 1;
1807 iPid2 = os.getpid() + 2;
1808
1809 self.assertTrue(self.oTstDrv.pidFileAdd(iPid1, 'test1'));
1810 self.assertEqual(self.oTstDrv.pidFileRead(), {iPid1:('test1',False)});
1811
1812 self.assertTrue(self.oTstDrv.pidFileAdd(iPid2, 'test2', fSudo = True));
1813 self.assertEqual(self.oTstDrv.pidFileRead(), {iPid1:('test1',False), iPid2:('test2',True)});
1814
1815 self.assertTrue(self.oTstDrv.pidFileRemove(iPid1));
1816 self.assertEqual(self.oTstDrv.pidFileRead(), {iPid2:('test2',True)});
1817
1818 self.assertTrue(self.oTstDrv.pidFileRemove(iPid2));
1819 self.assertEqual(self.oTstDrv.pidFileRead(), {});
1820
1821 self.assertTrue(self.oTstDrv.pidFileDelete());
1822
1823if __name__ == '__main__':
1824 unittest.main();
1825 # 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