VirtualBox

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

Last change on this file since 76938 was 76938, checked in by vboxsync, 6 years ago

Additions/linux/vboxvideo: Fix incorrect type in assignment sparse warning
bugref:8282: Additions/linux: track kernel changes to vboxvideo in our own tree

Sparse emitted the following warning:
../drivers/staging/vboxvideo/vbox_fb.c:173:27: warning: incorrect type in as

signment (different address spaces)

../drivers/staging/vboxvideo/vbox_fb.c:173:27: expected char [noderef] <a

sn:2>*screen_base

../drivers/staging/vboxvideo/vbox_fb.c:173:27: got void *virtual


The vbox_bo buffer object kernel mapping is handled by a call
to ttm_bo_kmap() prior to the assignment of bo->kmap.virtual to
info->screen_base of type char iomem*.
Casting bo->kmap.virtual to char
iomem* in this assignment fixes
the warning.


vboxvideo: Fix address space of expression removal sparse warning


Sparse emitted the following warning:
../drivers/staging/vboxvideo/vbox_main.c:64:25: warning: cast removes address space of expression


vbox->vbva_buffers iomapping is handled by calling vbox_accel_init()
from vbox_hw_init().
force attribute is used in assignment to vbva to fix the warning.


Signed-off-by: Alexander Kapshuk <alexander.kapshuk@…>
Reviewed-by: Hans de Goede <hdegoede@…>
Signed-off-by: Greg Kroah-Hartman <gregkh@…>

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

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