VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

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