VirtualBox

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

Last change on this file since 83432 was 83294, checked in by vboxsync, 5 years ago

ValidationKit/testdriver: Fixed exceptions where removing PIDs in actionAbort() will in result in RuntimeErrors.

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