VirtualBox

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

Last change on this file since 78965 was 78641, checked in by vboxsync, 6 years ago

ValKit/base.py: comments

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