VirtualBox

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

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

Validation Kit: Added enabling + collecting crash reporting (core dumps) for Solaris hosts [build fix].

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

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