VirtualBox

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

Last change on this file since 79132 was 79087, checked in by vboxsync, 5 years ago

ValKit: New pylint version - cleanup in progress.

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