VirtualBox

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

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

Validation Kit: Fixed lots of warnings, based on pylint 2.12.2.

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

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