VirtualBox

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

Last change on this file since 79450 was 79446, checked in by vboxsync, 5 years ago

ValKit/base.py: Log more when innerMain throws unexpected exceptions.

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