VirtualBox

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

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

tdAddGuestCtrl.py: testGuestCtrlCopyFrom related cleanups. Fixed python 2.x issue in vboxtestfileset.py. bugref:9151 bugref:9320

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