VirtualBox

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

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

Validation Kit/testdriver/base.py: Use PyHANDLE.int where applicable. Needed for newer pywin32 versions with Python 3.10+.

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