VirtualBox

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

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

ValKit: Pylint 2.16.2 adjustments.

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