VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette