VirtualBox

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

Last change on this file since 99196 was 99113, checked in by vboxsync, 20 months ago

ValidationKit/testdriver/base.py: Silence a pylint 'no-member' warning
on Windows for an os.geteuid() call added to a Solaris-specific section
of code since geteuid() doesn't exist on Windows.

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