VirtualBox

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

Last change on this file since 87672 was 86513, checked in by vboxsync, 4 years ago

ValKit/vbox.py: VBoxSVC failure should be checked and reported. [win fix] bugref:9841

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