VirtualBox

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

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

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

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