diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/ValidationKit/tests/unittests/Makefile.kmk | 51 | ||||
-rwxr-xr-x | src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py | 1282 |
2 files changed, 1333 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/tests/unittests/Makefile.kmk b/src/VBox/ValidationKit/tests/unittests/Makefile.kmk new file mode 100644 index 00000000..1f3c23a9 --- /dev/null +++ b/src/VBox/ValidationKit/tests/unittests/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Unit Tests. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + + +INSTALLS += ValidationKitTestsUnitTests +ValidationKitTestsUnitTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsUnitTests_INST = $(INST_VALIDATIONKIT)tests/unittests/ +ValidationKitTestsUnitTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdUnitTest1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsUnitTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py new file mode 100755 index 00000000..c308d372 --- /dev/null +++ b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py @@ -0,0 +1,1282 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdUnitTest1.py $ + +""" +VirtualBox Validation Kit - Unit Tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2010-2023 Oracle and/or its affiliates. + +This file is part of VirtualBox base platform packages, as +available from https://www.virtualbox.org. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, in version 3 of the +License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see <https://www.gnu.org/licenses>. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL), a copy of it is provided in the "COPYING.CDDL" file included +in the VirtualBox distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. + +SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +""" +__version__ = "$Revision: 157454 $" + + +# Standard Python imports. +import os +import sys +import re + + +# Only the main script needs to modify the path. +try: __file__ +except: __file__ = sys.argv[0]; +g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.append(g_ksValidationKitDir) + +# Validation Kit imports. +from common import utils; +from testdriver import base; +from testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdUnitTest1(vbox.TestDriver): + """ + Unit Tests. + """ + + ## The temporary exclude list. + ## @note This shall be empty before we release 4.3! + kdTestCasesBuggyPerOs = { + 'darwin': { + 'testcase/tstX86-1': '', # 'FSTP M32R, ST0' fails; no idea why. + }, + 'linux': { + 'testcase/tstRTFileAio': '', # See xTracker #8035. + }, + 'linux.amd64': { + 'testcase/tstLdr-4': '', # failed: Failed to get bits for '/home/vbox/test/tmp/bin/testcase/tstLdrObjR0.r0'/0, + # rc=VERR_SYMBOL_VALUE_TOO_BIG. aborting test + }, + 'solaris': { + 'testcase/tstIntNet-1': '', # Fails opening rge0, probably a generic issue figuring which nic to use. + 'testcase/tstIprtList': '', # Crashes in the multithreaded test, I think. + 'testcase/tstRTCritSect': '', # Fairness/whatever issue here. + 'testcase/tstRTR0MemUserKernelDriver': '', # Failes when kernel to kernel buffers. + 'testcase/tstRTSemRW': '', # line 338: RTSemRWReleaseRead(hSemRW): got VERR_ACCESS_DENIED + 'testcase/tstRTStrAlloc': '', # VERR_NO_STR_MEMORY! + 'testcase/tstRTFileQuerySize-1': '', # VERR_DEV_IO_ERROR on /dev/null! + 'testcase/tstLow' : '', # VERR_NOT_SUPPORTED - allocating kernel memory with physical backing + # below 4GB (RTR0MemObjAllocLow) for running code (fExecutable=true) + # isn't implemented. + 'testcase/tstContiguous' : '', # VERR_NOT_SUPPORTED - allocating kernel memory with contiguous physical + # backing below 4GB (RTR0MemObjAllocCont) for running code + # (fExecutable=true) isn't implemented. + 'tstPDMQueue' : '' # VERR_NOT_SUPPORTED - running without the support driver (vboxdrv) isn't + # supported on Solaris (VMCREATE_F_DRIVERLESS/SUPR3INIT_F_DRIVERLESS). + }, + 'solaris.amd64': { + 'testcase/tstLdr-4': '', # failed: Failed to get bits for '/home/vbox/test/tmp/bin/testcase/tstLdrObjR0.r0'/0, + # rc=VERR_SYMBOL_VALUE_TOO_BIG. aborting test + }, + 'win': { + 'testcase/tstFile': '', # ?? + 'testcase/tstIntNet-1': '', # possibly same issue as solaris. + 'testcase/tstMouseImpl': '', # STATUS_ACCESS_VIOLATION + 'testcase/tstRTR0ThreadPreemptionDriver': '', # ?? + 'testcase/tstRTPath': '<4.3.51r89894', + 'testcase/tstRTPipe': '', # ?? + 'testcase/tstRTR0MemUserKernelDriver': '', # ?? + 'testcase/tstRTR0SemMutexDriver': '', # ?? + 'testcase/tstRTStrAlloc': '', # ?? + 'testcase/tstRTStrFormat': '', # ?? + 'testcase/tstRTSystemQueryOsInfo': '', # ?? + 'testcase/tstRTTemp': '', # ?? + 'testcase/tstRTTime': '', # ?? + 'testcase/tstTime-2': '', # Total time differs too much! ... delta=-10859859 + 'testcase/tstTime-4': '', # Needs to be converted to DLL; ditto for tstTime-2. + 'testcase/tstUtf8': '', # ?? + 'testcase/tstVMMR0CallHost-2': '', # STATUS_STACK_OVERFLOW + 'testcase/tstX86-1': '', # Fails on win.x86. + 'tscpasswd': '', # ?? + 'tstVMREQ': '', # ?? Same as darwin.x86? + }, + 'win.x86': { + 'testcase/tstRTR0TimerDriver': '', # See xTracker #8041. + } + }; + + kdTestCasesBuggy = { + 'testcase/tstGuestPropSvc': '', # GET_NOTIFICATION fails on testboxlin5.de.oracle.com and others. + 'testcase/tstRTProcCreateEx': '', # Seen failing on wei01-b6ka-9.de.oracle.com. + 'testcase/tstTimer': '', # Sometimes fails on linux, not important atm. + 'testcase/tstGIP-2': '', # 2015-09-10: Fails regularly. E.g. TestSetID 2744205 (testboxsh2), + # 2743961 (wei01-b6kc-6). The responsible engineer should reenable + # it once it has been fixed. + }; + + ## The permanent exclude list. + # @note Stripped of extensions! + kdTestCasesBlackList = { + 'testcase/tstClipboardX11Smoke': '', # (Old naming, deprecated) Needs X, not available on all test boxes. + 'testcase/tstClipboardGH-X11Smoke': '', # (New name) Ditto. + 'testcase/tstClipboardMockHGCM': '', # Ditto. + 'tstClipboardQt': '', # Is interactive and needs Qt, needed for Qt clipboard bugfixing. + 'testcase/tstClipboardQt': '', # In case it moves here. + 'tstDragAndDropQt': '', # Is interactive and needs Qt, needed for Qt drag'n drop bugfixing. + 'testcase/tstDragAndDropQt': '', # In case it moves here. + 'testcase/tstFileLock': '', + 'testcase/tstDisasm-2': '', # without parameters it will disassembler 1GB starting from 0 + 'testcase/tstFileAppendWin-1': '', + 'testcase/tstDir': '', # useless without parameters + 'testcase/tstDir-2': '', # useless without parameters + 'testcase/tstGlobalConfig': '', + 'testcase/tstHostHardwareLinux': '', # must be killed with CTRL-C + 'testcase/tstHttp': '', # Talks to outside servers. + 'testcase/tstRTHttp': '', # parameters required + 'testcase/tstLdr-2': '', # parameters required + 'testcase/tstLdr-3': '', # parameters required + 'testcase/tstLdr': '', # parameters required + 'testcase/tstLdrLoad': '', # parameters required + 'testcase/tstMove': '', # parameters required + 'testcase/tstRTR0Timer': '', # loads 'tstRTR0Timer.r0' + 'testcase/tstRTR0ThreadDriver': '', # loads 'tstRTR0Thread.r0' + 'testcase/tstRunTestcases': '', # that's a script like this one + 'testcase/tstRTReqPool': '', # fails sometimes, testcase buggy + 'testcase/tstRTS3': '', # parameters required + 'testcase/tstSDL': '', # graphics test + 'testcase/tstSupLoadModule': '', # Needs parameters and vboxdrv access. Covered elsewhere. + 'testcase/tstSeamlessX11': '', # graphics test + 'testcase/tstTime-3': '', # parameters required + 'testcase/tstVBoxControl': '', # works only inside a guest + 'testcase/tstVDCopy': '', # parameters required + 'testcase/tstVDFill': '', # parameters required + 'tstAnimate': '', # parameters required + 'testcase/tstAPI': '', # user interaction required + 'tstCollector': '', # takes forever + 'testcase/tstHeadless': '', # parameters required + 'tstHeadless': '', # parameters required + 'tstMicroRC': '', # required by tstMicro + 'tstVBoxDbg': '', # interactive test + 'testcase/tstTestServMgr': '', # some strange xpcom18a4 test, does not work + 'tstTestServMgr': '', # some strange xpcom18a4 test, does not work + 'tstPDMAsyncCompletion': '', # parameters required + 'testcase/tstXptDump': '', # parameters required + 'tstXptDump': '', # parameters required + 'testcase/tstnsIFileEnumerator': '', # some strange xpcom18a4 test, does not work + 'tstnsIFileEnumerator': '', # some strange xpcom18a4 test, does not work + 'testcase/tstSimpleTypeLib': '', # parameters required + 'tstSimpleTypeLib': '', # parameters required + 'testcase/tstTestAtoms': '', # additional test file (words.txt) required + 'tstTestAtoms': '', # additional test file (words.txt) required + 'testcase/tstXptLink': '', # parameters required + 'tstXptLink': '', # parameters required + 'tstXPCOMCGlue': '', # user interaction required + 'testcase/tstXPCOMCGlue': '', # user interaction required + 'testcase/tstCAPIGlue': '', # user interaction required + 'testcase/tstTestCallTemplates': '', # some strange xpcom18a4 test, segfaults + 'tstTestCallTemplates': '', # some strange xpcom18a4 test, segfaults + 'testcase/tstRTFilesystem': '', # parameters required + 'testcase/tstRTDvm': '', # parameters required + 'tstSSLCertDownloads': '', # Obsolete. + # later + 'testcase/tstIntNetR0': '', # RTSPINLOCK_FLAGS_INTERRUPT_SAFE == RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE + # slow stuff + 'testcase/tstAvl': '', # SLOW! + 'testcase/tstRTAvl': '', # SLOW! (new name) + 'testcase/tstVD': '', # 8GB fixed-sized vmdk + # failed or hang + 'testcase/tstCryptoPkcs7Verify': '', # hang + 'tstOVF': '', # hang (only ancient version, now in new place) + 'testcase/tstRTLockValidator': '', # Lock validation is not enabled for critical sections + 'testcase/tstGuestControlSvc': '', # failed: line 288: testHost(&svcTable): + # expected VINF_SUCCESS, got VERR_NOT_FOUND + 'testcase/tstRTMemEf': '', # failed w/o error message + 'testcase/tstSupSem': '', # failed: SRE Timeout Accuracy (ms) : FAILED (1 errors) + 'testcase/tstCryptoPkcs7Sign': '', # failed: 29330: + # error:02001002:lib(2):func(1):reason(2):NA:0:fopen('server.pem': '','r') + 'testcase/tstCompressionBenchmark': '', # failed: error: RTZipBlockCompress failed + # for 'RTZipBlock/LZJB' (#4): VERR_NOT_SUPPORTED + 'tstPDMAsyncCompletionStress': '', # VERR_INVALID_PARAMETER (cbSize = 0) + 'tstMicro': '', # doesn't work on solaris, fix later if we care. + 'tstVMM-HwAccm': '', # failed: Only checked AMD-V on linux + 'tstVMM-HM': '', # failed: Only checked AMD-V on linux + 'tstVMMFork': '', # failed: xtracker 6171 + 'tstTestFactory': '', # some strange xpcom18a4 test, does not work + 'testcase/tstRTSemXRoads': '', # sporadically failed: Traffic - 8 threads per direction, 10 sec : + # FAILED (8 errors) + 'tstVBoxAPILinux': '', # creates VirtualBox directories for root user because of sudo + # (should be in vbox) + 'testcase/tstVMStructDTrace': '', # This is a D-script generator. + 'tstVMStructRC': '', # This is a C-code generator. + 'tstDeviceStructSizeRC': '', # This is a C-code generator. + 'testcase/tstTSC': '', # Doesn't test anything and might fail with HT or/and too many cores. + 'testcase/tstOpenUSBDev': '', # Not a useful testcase. + 'testcase/tstX86-1': '', # Really more guest side. + 'testcase/tstX86-FpuSaveRestore': '', # Experiments, could be useful for the guest not the host. + 'tstAsmStructsRC': '', # Testcase run during build time (fails to find libstdc++.so.6 on some + # Solaris testboxes). + }; + + # Suffix exclude list. + kasSuffixBlackList = [ + '.r0', + '.gc', + '.debug', + '.rel', + '.sys', + '.ko', + '.o', + '.obj', + '.lib', + '.a', + '.so', + '.dll', + '.dylib', + '.tmp', + '.log', + '.py', + '.pyc', + '.pyo', + '.pdb', + '.dSYM', + '.sym', + '.template', + '.expected', + '.expect', + ]; + + # White list, which contains tests considered to be safe to execute, + # even on remote targets (guests). + # + # When --only-whitelist is specified, this is the only list being checked for. + kdTestCasesWhiteList = { + 'testcase/tstFile': '', + 'testcase/tstFileLock': '', + 'testcase/tstClipboardMockHGCM': '', # Requires X on Linux OSes. Execute on remote targets only (guests). + 'testcase/tstRTLocalIpc': '', + 'testcase/tstRTPathQueryInfo': '', + 'testcase/tstRTPipe': '', + 'testcase/tstRTProcCreateEx': '', + 'testcase/tstRTProcCreatePrf': '', + 'testcase/tstRTProcIsRunningByName': '', + 'testcase/tstRTProcQueryUsername': '', + 'testcase/tstRTProcWait': '', + 'testcase/tstTime-2': '', + 'testcase/tstTime-3': '', + 'testcase/tstTime-4': '', + 'testcase/tstTimer': '', + 'testcase/tstThread-1': '', + 'testcase/tstUtf8': '' + }; + + # Test dependency list -- libraries. + # Needed in order to execute testcases on remote targets which don't have a VBox installation present. + kdTestCaseDepsLibs = [ + "VBoxRT" + ]; + + ## The exclude list. + # @note Stripped extensions! + kasHardened = [ + "testcase/tstIntNet-1", + "testcase/tstR0ThreadPreemptionDriver", # VBox 4.3 + "testcase/tstRTR0ThreadPreemptionDriver", + "testcase/tstRTR0MemUserKernelDriver", + "testcase/tstRTR0SemMutexDriver", + "testcase/tstRTR0TimerDriver", + "testcase/tstRTR0ThreadDriver", + 'testcase/tstRTR0DbgKrnlInfoDriver', + "tstInt", + "tstPDMQueue", # Comment in testcase says its driverless, but it needs driver access. + "tstVMM", + "tstVMMFork", + "tstVMREQ", + 'testcase/tstCFGM', + 'testcase/tstContiguous', + 'testcase/tstGetPagingMode', + 'testcase/tstGIP-2', + 'testcase/tstInit', + 'testcase/tstLow', + 'testcase/tstMMHyperHeap', + 'testcase/tstPage', + 'testcase/tstPin', + 'testcase/tstRTTime', 'testcase/tstTime', # GIP test case. + 'testcase/tstRTTime-2', 'testcase/tstTime-2', # GIP test case. + 'testcase/tstRTTime-4', 'testcase/tstTime-4', # GIP test case. + 'testcase/tstSSM', + 'testcase/tstSupSem-Zombie', + ] + + ## Argument lists + kdArguments = { + 'testcase/tstbntest': [ '-out', os.devnull, ], # Very noisy. + }; + + + ## Status code translations. + ## @{ + kdExitCodeNames = { + 0: 'RTEXITCODE_SUCCESS', + 1: 'RTEXITCODE_FAILURE', + 2: 'RTEXITCODE_SYNTAX', + 3: 'RTEXITCODE_INIT', + 4: 'RTEXITCODE_SKIPPED', + }; + kdExitCodeNamesWin = { + -1073741515: 'STATUS_DLL_NOT_FOUND', + -1073741512: 'STATUS_ORDINAL_NOT_FOUND', + -1073741511: 'STATUS_ENTRYPOINT_NOT_FOUND', + -1073741502: 'STATUS_DLL_INIT_FAILED', + -1073741500: 'STATUS_UNHANDLED_EXCEPTION', + -1073741499: 'STATUS_APP_INIT_FAILURE', + -1073741819: 'STATUS_ACCESS_VIOLATION', + -1073741571: 'STATUS_STACK_OVERFLOW', + }; + ## @} + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self); + + # We need to set a default test VM set here -- otherwise the test + # driver base class won't let us use the "--test-vms" switch. + # + # See the "--local" switch in self.parseOption(). + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + + # Selected NIC attachment. + self.sNicAttachment = ''; + + # Session handling stuff. + # Only needed for remote tests executed by TxS. + self.oSession = None; + self.oTxsSession = None; + + self.sVBoxInstallRoot = None; + + ## Testing mode being used: + # "local": Execute unit tests locally (same host, default). + # "remote-copy": Copies unit tests from host to the remote, then executing it. + # "remote-exec": Executes unit tests right on the remote from a given source. + self.sMode = 'local'; + + self.cSkipped = 0; + self.cPassed = 0; + self.cFailed = 0; + + # The source directory where our unit tests live. + # This most likely is our out/ or some staging directory and + # also acts the source for copying over the testcases to a remote target. + self.sUnitTestsPathSrc = None; + + # Array of environment variables with NAME=VAL entries + # to be applied for testcases. + # + # This is also needed for testcases which are being executed remotely. + self.asEnv = []; + + # The destination directory our unit tests live when being + # copied over to a remote target (via TxS). + self.sUnitTestsPathDst = None; + + # The executable suffix to use for the executing the actual testcases. + # Will be re-set when executing the testcases on a remote (VM) once we know + # what type of suffix to use then (based on guest OS). + self.sExeSuff = base.exeSuff(); + + self.aiVBoxVer = (4, 3, 0, 0); + + # For testing testcase logic. + self.fDryRun = False; + self.fOnlyWhiteList = False; + + @staticmethod + def _sanitizePath(sPath): + """ + Does a little bit of sanitizing a given path by removing quoting, if any. + + This is needed because handed-in paths via command line arguments can contain variables like "${CDROM}" + which might need to get processed by TXS on the guest side first. + + Returns the sanitized path. + """ + if sPath is None: # Keep uninitialized strings as-is. + return None; + return sPath.strip('\"').strip('\''); + + def _detectPaths(self): + """ + Internal worker for actionVerify and actionExecute that detects paths. + + This sets sVBoxInstallRoot and sUnitTestsPathBase and returns True/False. + """ + + reporter.log2('Detecting paths ...'); + + # + # Do some sanity checking first. + # + if self.sMode == 'remote-exec' and not self.sUnitTestsPathSrc: # There is no way we can figure this out automatically. + reporter.error('Unit tests source must be specified explicitly for selected mode!'); + return False; + + # + # We need a VBox install (/ build) to test. + # + if False is True: ## @todo r=andy ?? + if not self.importVBoxApi(): + reporter.error('Unabled to import the VBox Python API.'); + return False; + else: + self._detectBuild(); + if self.oBuild is None: + reporter.error('Unabled to detect the VBox build.'); + return False; + + # + # Where are the files installed? + # Solaris requires special handling because of it's multi arch subdirs. + # + if not self.sVBoxInstallRoot: + self.sVBoxInstallRoot = self.oBuild.sInstallPath; + if not self.oBuild.isDevBuild() and utils.getHostOs() == 'solaris': + sArchDir = utils.getHostArch(); + if sArchDir == 'x86': sArchDir = 'i386'; + self.sVBoxInstallRoot = os.path.join(self.sVBoxInstallRoot, sArchDir); + + ## @todo r=andy Make sure the install root really exists and is accessible. + + # Add the installation root to the PATH on windows so we can get DLLs from it. + if utils.getHostOs() == 'win': + sPathName = 'PATH'; + if not sPathName in os.environ: + sPathName = 'Path'; + sPath = os.environ.get(sPathName, '.'); + if sPath and sPath[-1] != ';': + sPath += ';'; + os.environ[sPathName] = sPath + self.sVBoxInstallRoot + ';'; + else: + reporter.log2('VBox installation root already set to "%s"' % (self.sVBoxInstallRoot)); + + self.sVBoxInstallRoot = self._sanitizePath(self.sVBoxInstallRoot); + + # + # The unittests are generally not installed, so look for them. + # + if not self.sUnitTestsPathSrc: + sBinOrDist = 'dist' if utils.getHostOs() in [ 'darwin', ] else 'bin'; + asCandidates = [ + self.oBuild.sInstallPath, + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), self.oBuild.sType, sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'release', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'debug', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'strict', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'dbgopt', sBinOrDist), + os.path.join(self.sScratchPath, utils.getHostOsDotArch(), 'profile', sBinOrDist), + os.path.join(self.sScratchPath, sBinOrDist + '.' + utils.getHostArch()), + os.path.join(self.sScratchPath, sBinOrDist, utils.getHostArch()), + os.path.join(self.sScratchPath, sBinOrDist), + ]; + if utils.getHostOs() == 'darwin': + for i in range(1, len(asCandidates)): + asCandidates[i] = os.path.join(asCandidates[i], 'VirtualBox.app', 'Contents', 'MacOS'); + + for sCandidat in asCandidates: + # The path of tstVMStructSize acts as a beacon to know where all other testcases are. + sFileBeacon = os.path.join(sCandidat, 'testcase', 'tstVMStructSize' + self.sExeSuff); + reporter.log2('Searching for "%s" ...' % sFileBeacon); + if os.path.exists(sFileBeacon): + self.sUnitTestsPathSrc = sCandidat; + break + + if self.sUnitTestsPathSrc: + reporter.log('Unit test source dir path: ', self.sUnitTestsPathSrc) + else: + reporter.error('Unable to find unit test source dir. Candidates: %s' % (asCandidates,)); + if reporter.getVerbosity() >= 2: + reporter.log('Contents of "%s"' % self.sScratchPath); + for paths, dirs, files in os.walk(self.sScratchPath): + reporter.log('{} {} {}'.format(repr(paths), repr(dirs), repr(files))); + return False + + else: + reporter.log2('Unit test source dir already set to "%s"' % (self.sUnitTestsPathSrc)) + reporter.log('Unit test source dir path: ', self.sUnitTestsPathSrc) + + self.sUnitTestsPathSrc = self._sanitizePath(self.sUnitTestsPathSrc); + + return True; + + # + # Overridden methods. + # + + def showUsage(self): + """ + Shows the testdriver usage. + """ + fRc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('Unit Test #1 options:'); + reporter.log(' --dryrun'); + reporter.log(' Performs a dryrun (no tests being executed).'); + reporter.log(' --mode <local|remote-copy|remote-exec>'); + reporter.log(' Specifies the test execution mode:'); + reporter.log(' local: Locally on the same machine.'); + reporter.log(' remote-copy: On remote (guest) by copying them from the local source.'); + reporter.log(' remote-exec: On remote (guest) directly (needs unit test source).'); + reporter.log(' --only-whitelist'); + reporter.log(' Only processes the white list.'); + reporter.log(' --quick'); + reporter.log(' Very selective testing.'); + reporter.log(' --unittest-source <dir>'); + reporter.log(' Sets the unit test source to <dir>.'); + reporter.log(' Also used for remote execution.'); + reporter.log(' --vbox-install-root <dir>'); + reporter.log(' Sets the VBox install root to <dir>.'); + reporter.log(' Also used for remote execution.'); + return fRc; + + def parseOption(self, asArgs, iArg): + """ + Parses the testdriver arguments from the command line. + """ + if asArgs[iArg] == '--dryrun': + self.fDryRun = True; + elif asArgs[iArg] == '--mode': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + if asArgs[iArg] in ('local', 'remote-copy', 'remote-exec',): + self.sMode = asArgs[iArg]; + else: + raise base.InvalidOption('Argument "%s" invalid' % (asArgs[iArg])); + elif asArgs[iArg] == '--unittest-source': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.sUnitTestsPathSrc = asArgs[iArg]; + elif asArgs[iArg] == '--only-whitelist': + self.fOnlyWhiteList = True; + elif asArgs[iArg] == '--quick': + self.fOnlyWhiteList = True; + elif asArgs[iArg] == '--vbox-install-root': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.sVBoxInstallRoot = asArgs[iArg]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionVerify(self): + if not self._detectPaths(): + return False; + + if self.oTestVmSet: + return vbox.TestDriver.actionVerify(self); + + return True; + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # Do the configuring. + if self.isRemoteMode(): + if self.sNicAttachment == 'nat': eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + elif self.sNicAttachment == 'bridged': eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + else: eNic0AttachType = None; + + # Make sure to mount the Validation Kit .ISO so that TxS has the chance + # to update itself. + # + # This is necessary as a lot of our test VMs nowadays have a very old TxS + # installed which don't understand commands like uploading files to the guest. + # Uploading files is needed for this test driver, however. + # + ## @todo Get rid of this as soon as we create test VMs in a descriptive (automated) manner. + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, + sDvdImage = self.sVBoxValidationKitIso); + + return True; + + def actionExecute(self): + # Make sure vboxapi has been imported so we can execute the driver without going thru + # a former configuring step. + if not self.importVBoxApi(): + return False; + if not self._detectPaths(): + return False; + reporter.log2('Unit test source path is "%s"\n' % self.sUnitTestsPathSrc); + + if not self.sUnitTestsPathDst: + self.sUnitTestsPathDst = self.sScratchPath; + reporter.log2('Unit test destination path is "%s"\n' % self.sUnitTestsPathDst); + + if self.isRemoteMode(): # Run on a test VM (guest). + if self.fpApiVer < 7.0: ## @todo Needs Validation Kit .ISO tweaking (including the unit tests) first. + reporter.log('Remote unit tests for non-trunk builds skipped.'); + fRc = True; + else: + assert self.oTestVmSet is not None; + fRc = self.oTestVmSet.actionExecute(self, self.testOneVmConfig); + else: # Run locally (host). + self._figureVersion(); + self._makeEnvironmentChanges(); + + # If this is an ASAN build and we're on linux, make sure we've got + # libasan.so.N in the LD_LIBRARY_PATH or stuff w/o a RPATH entry + # pointing to /opt/VirtualBox will fail (like tstAsmStructs). + if self.getBuildType() == 'asan' and utils.getHostOs() in ('linux',): + sLdLibraryPath = ''; + if 'LD_LIBRARY_PATH' in os.environ: + sLdLibraryPath = os.environ['LD_LIBRARY_PATH'] + ':'; + sLdLibraryPath += self.oBuild.sInstallPath; + os.environ['LD_LIBRARY_PATH'] = sLdLibraryPath; + + fRc = self._testRunUnitTests(None); + + return fRc; + + # + # Misc. + # + def isRemoteMode(self): + """ Predicate method for checking if in any remote mode. """ + return self.sMode.startswith('remote'); + + # + # Test execution helpers. + # + + def _testRunUnitTests(self, oTestVm): + """ + Main function to execute all unit tests. + """ + + # Determine executable suffix based on selected execution mode. + if self.isRemoteMode(): # Run on a test VM (guest). + if oTestVm.isWindows(): + self.sExeSuff = '.exe'; + else: + self.sExeSuff = ''; + else: + # For local tests this already is set in __init__ + pass; + + self._testRunUnitTestsSet(oTestVm, r'^tst*', 'testcase'); + self._testRunUnitTestsSet(oTestVm, r'^tst*', '.'); + + fRc = self.cFailed == 0; + + reporter.log(''); + if self.fDryRun: + reporter.log('*********************************************************'); + reporter.log('DRY RUN - DRY RUN - DRY RUN - DRY RUN - DRY RUN - DRY RUN'); + reporter.log('*********************************************************'); + reporter.log('*********************************************************'); + reporter.log(' Target: %s' % (oTestVm.sVmName if oTestVm else 'local',)); + reporter.log(' Mode: %s' % (self.sMode,)); + reporter.log(' Exe suffix: %s' % (self.sExeSuff,)); + reporter.log('Unit tests source: %s %s' + % (self.sUnitTestsPathSrc, '(on remote)' if self.sMode == 'remote-exec' else '',)); + reporter.log('VBox install root: %s %s' + % (self.sVBoxInstallRoot, '(on remote)' if self.sMode == 'remote-exec' else '',)); + reporter.log('*********************************************************'); + reporter.log('*** PASSED: %d' % (self.cPassed,)); + reporter.log('*** FAILED: %d' % (self.cFailed,)); + reporter.log('*** SKIPPED: %d' % (self.cSkipped,)); + reporter.log('*** TOTAL: %d' % (self.cPassed + self.cFailed + self.cSkipped,)); + + return fRc; + + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + # Simple test. + self.logVmInfo(oVM); + + if not self.fDryRun: + # Try waiting for a bit longer (5 minutes) until the CD is available to avoid running into timeouts. + self.oSession, self.oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, + fCdWait = not self.fDryRun, + cMsCdWait = 5 * 60 * 1000); + if self.oSession is None: + return False; + + self.addTask(self.oTxsSession); + + # Determine the unit tests destination path. + self.sUnitTestsPathDst = oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'testUnitTests'); + + # Run the unit tests. + self._testRunUnitTests(oTestVm); + + # Cleanup. + if self.oSession is not None: + self.removeTask(self.oTxsSession); + self.terminateVmBySession(self.oSession); + return True; + + # + # Test execution helpers. + # + + def _figureVersion(self): + """ Tries to figure which VBox version this is, setting self.aiVBoxVer. """ + try: + sVer = utils.processOutputChecked(['VBoxManage', '--version']) + + sVer = sVer.strip(); + sVer = re.sub(r'_BETA.*r', '.', sVer); + sVer = re.sub(r'_ALPHA.*r', '.', sVer); + sVer = re.sub(r'_RC.*r', '.', sVer); + sVer = re.sub('_SPB', '', sVer) + sVer = sVer.replace('r', '.'); + + self.aiVBoxVer = [int(sComp) for sComp in sVer.split('.')]; + + reporter.log('VBox version: %s' % (self.aiVBoxVer,)); + except: + reporter.logXcpt(); + return False; + return True; + + def _compareVersion(self, aiVer): + """ + Compares the give version string with the vbox version string, + returning a result similar to C strcmp(). aiVer is on the right side. + """ + cComponents = min(len(self.aiVBoxVer), len(aiVer)); + for i in range(cComponents): + if self.aiVBoxVer[i] < aiVer[i]: + return -1; + if self.aiVBoxVer[i] > aiVer[i]: + return 1; + return len(self.aiVBoxVer) - len(aiVer); + + def _isExcluded(self, sTest, dExclList): + """ Checks if the testcase is excluded or not. """ + if sTest in dExclList: + sFullExpr = dExclList[sTest].replace(' ', '').strip(); + if sFullExpr == '': + return True; + + # Consider each exclusion expression. These are generally ranges, + # either open ended or closed: "<4.3.51r12345", ">=4.3.0 && <=4.3.4". + asExprs = sFullExpr.split(';'); + for sExpr in asExprs: + + # Split it on the and operator and process each sub expression. + fResult = True; + for sSubExpr in sExpr.split('&&'): + # Split out the comparison operator and the version value. + if sSubExpr.startswith('<=') or sSubExpr.startswith('>='): + sOp = sSubExpr[:2]; + sValue = sSubExpr[2:]; + elif sSubExpr.startswith('<') or sSubExpr.startswith('>') or sSubExpr.startswith('='): + sOp = sSubExpr[:1]; + sValue = sSubExpr[1:]; + else: + sOp = sValue = ''; + + # Convert the version value, making sure we've got a valid one. + try: aiValue = [int(sComp) for sComp in sValue.replace('r', '.').split('.')]; + except: aiValue = (); + if not aiValue or len(aiValue) > 4: + reporter.error('Invalid exclusion expression for %s: "%s" [%s]' % (sTest, sSubExpr, dExclList[sTest])); + return True; + + # Do the compare. + iCmp = self._compareVersion(aiValue); + if sOp == '>=' and iCmp < 0: + fResult = False; + elif sOp == '>' and iCmp <= 0: + fResult = False; + elif sOp == '<' and iCmp >= 0: + fResult = False; + elif sOp == '>=' and iCmp < 0: + fResult = False; + reporter.log2('iCmp=%s; %s %s %s -> %s' % (iCmp, self.aiVBoxVer, sOp, aiValue, fResult)); + + # Did the expression match? + if fResult: + return True; + + return False; + + def _sudoExecuteSync(self, asArgs): + """ + Executes a sudo child process synchronously. + Returns True if the process executed successfully and returned 0, + otherwise False is returned. + """ + reporter.log2('Executing [sudo]: %s' % (asArgs, )); + if self.isRemoteMode(): + iRc = -1; ## @todo Not used remotely yet. + else: + try: + iRc = utils.sudoProcessCall(asArgs, shell = False, close_fds = False); + except: + reporter.errorXcpt(); + return False; + reporter.log('Exit code [sudo]: %s (%s)' % (iRc, asArgs)); + return iRc == 0; + + + def _logExpandString(self, sString, cVerbosity = 2): + """ + Expands a given string by asking TxS on the guest side and logs it. + Uses log level 2 by default. + + No-op if no TxS involved. + """ + if reporter.getVerbosity() < cVerbosity or self.oTxsSession is None: + return; + sStringExp = self.oTxsSession.syncExpandString(sString); + if not sStringExp: + return; + reporter.log2('_logExpandString: "%s" -> "%s"' % (sString, sStringExp)); + + def _wrapPathExists(self, sPath): + """ + Creates the directory specified sPath (including parents). + """ + reporter.log2('_wrapPathExists: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = False; + if self.isRemoteMode(): + self._logExpandString(sPath); + fRc = self.oTxsSession.syncIsDir(sPath, fIgnoreErrors = True); + if not fRc: + fRc = self.oTxsSession.syncIsFile(sPath, fIgnoreErrors = True); + else: + fRc = os.path.exists(sPath); + return fRc; + + def _wrapMkDir(self, sPath): + """ + Creates the directory specified sPath (including parents). + """ + reporter.log2('_wrapMkDir: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + fRc = self.oTxsSession.syncMkDirPath(sPath, fMode = 0o755); + else: + if utils.getHostOs() in [ 'win', 'os2' ]: + os.makedirs(sPath, 0o755); + else: + fRc = self._sudoExecuteSync(['/bin/mkdir', '-p', '-m', '0755', sPath]); + if not fRc: + reporter.log('Failed to create dir "%s".' % (sPath,)); + return fRc; + + def _wrapCopyFile(self, sSrc, sDst, iMode): + """ + Copies a file. + """ + reporter.log2('_wrapCopyFile: %s -> %s (mode: %o)' % (sSrc, sDst, iMode,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + self._logExpandString(sSrc); + self._logExpandString(sDst); + if self.sMode == 'remote-exec': + self.oTxsSession.syncCopyFile(sSrc, sDst, iMode); + else: + fRc = self.oTxsSession.syncUploadFile(sSrc, sDst); + if fRc: + fRc = self.oTxsSession.syncChMod(sDst, iMode); + else: + if utils.getHostOs() in [ 'win', 'os2' ]: + utils.copyFileSimple(sSrc, sDst); + os.chmod(sDst, iMode); + else: + fRc = self._sudoExecuteSync(['/bin/cp', sSrc, sDst]); + if fRc: + fRc = self._sudoExecuteSync(['/bin/chmod', '%o' % (iMode,), sDst]); + if fRc is not True: + raise Exception('Failed to chmod "%s".' % (sDst,)); + if not fRc: + reporter.log('Failed to copy "%s" to "%s".' % (sSrc, sDst,)); + return fRc; + + def _wrapDeleteFile(self, sPath): + """ + Deletes a file. + """ + reporter.log2('_wrapDeleteFile: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + if self.oTxsSession.syncIsFile(sPath): + fRc = self.oTxsSession.syncRmFile(sPath, fIgnoreErrors = True); + else: + if os.path.exists(sPath): + if utils.getHostOs() in [ 'win', 'os2' ]: + os.remove(sPath); + else: + fRc = self._sudoExecuteSync(['/bin/rm', sPath]); + if not fRc: + reporter.log('Failed to remove "%s".' % (sPath,)); + return fRc; + + def _wrapRemoveDir(self, sPath): + """ + Removes a directory. + """ + reporter.log2('_wrapRemoveDir: %s' % (sPath,)); + if self.fDryRun: + return True; + fRc = True; + if self.isRemoteMode(): + if self.oTxsSession.syncIsDir(sPath): + fRc = self.oTxsSession.syncRmDir(sPath, fIgnoreErrors = True); + else: + if os.path.exists(sPath): + if utils.getHostOs() in [ 'win', 'os2' ]: + os.rmdir(sPath); + else: + fRc = self._sudoExecuteSync(['/bin/rmdir', sPath]); + if not fRc: + reporter.log('Failed to remove "%s".' % (sPath,)); + return fRc; + + def _envSet(self, sName, sValue): + if self.isRemoteMode(): + # For remote execution we cache the environment block and pass it + # right when the process execution happens. + self.asEnv.append([ sName, sValue ]); + else: + os.environ[sName] = sValue; + return True; + + def _executeTestCase(self, oTestVm, sName, sFilePathAbs, sTestCaseSubDir, oDevNull): # pylint: disable=too-many-locals,too-many-statements + """ + Executes a test case. + + sFilePathAbs contains the absolute path (including OS-dependent executable suffix) of the testcase. + + Returns @c true if testcase was skipped, or @c if not. + """ + + fSkipped = False; + + # + # If hardening is enabled, some test cases and their dependencies + # needs to be copied to and execute from the source + # directory in order to work. They also have to be executed as + # root, i.e. via sudo. + # + fHardened = sName in self.kasHardened and self.sUnitTestsPathSrc != self.sVBoxInstallRoot; + fCopyToRemote = self.isRemoteMode(); + fCopyDeps = self.isRemoteMode(); + asFilesToRemove = []; # Stuff to clean up. + asDirsToRemove = []; # Ditto. + + if fHardened or fCopyToRemote: + if fCopyToRemote: + sDstDir = os.path.join(self.sUnitTestsPathDst, sTestCaseSubDir); + else: + sDstDir = os.path.join(self.sVBoxInstallRoot, sTestCaseSubDir); + if not self._wrapPathExists(sDstDir): + self._wrapMkDir(sDstDir); + asDirsToRemove.append(sDstDir); + + sSrc = sFilePathAbs; + # If the testcase source does not exist for whatever reason, just mark it as skipped + # instead of reporting an error. + if not self._wrapPathExists(sSrc): + self.cSkipped += 1; + fSkipped = True; + return fSkipped; + + sDst = os.path.join(sDstDir, os.path.basename(sFilePathAbs)); + fModeExe = 0; + fModeDeps = 0; + if not oTestVm or (oTestVm and not oTestVm.isWindows()): ## @todo NT4 does not like the chmod. Investigate this! + fModeExe = 0o755; + fModeDeps = 0o644; + self._wrapCopyFile(sSrc, sDst, fModeExe); + asFilesToRemove.append(sDst); + + # Copy required dependencies to destination. + if fCopyDeps: + for sLib in self.kdTestCaseDepsLibs: + for sSuff in [ '.dll', '.so', '.dylib' ]: + assert self.sVBoxInstallRoot is not None; + sSrc = os.path.join(self.sVBoxInstallRoot, sLib + sSuff); + if self._wrapPathExists(sSrc): + sDst = os.path.join(sDstDir, os.path.basename(sSrc)); + self._wrapCopyFile(sSrc, sDst, fModeDeps); + asFilesToRemove.append(sDst); + + # Copy any associated .dll/.so/.dylib. + for sSuff in [ '.dll', '.so', '.dylib' ]: + sSrc = os.path.splitext(sFilePathAbs)[0] + sSuff; + if os.path.exists(sSrc): + sDst = os.path.join(sDstDir, os.path.basename(sSrc)); + self._wrapCopyFile(sSrc, sDst, fModeDeps); + asFilesToRemove.append(sDst); + + # Copy any associated .r0, .rc and .gc modules. + offDriver = sFilePathAbs.rfind('Driver') + if offDriver > 0: + for sSuff in [ '.r0', 'RC.rc', 'RC.gc' ]: + sSrc = sFilePathAbs[:offDriver] + sSuff; + if os.path.exists(sSrc): + sDst = os.path.join(sDstDir, os.path.basename(sSrc)); + self._wrapCopyFile(sSrc, sDst, fModeDeps); + asFilesToRemove.append(sDst); + + sFilePathAbs = os.path.join(sDstDir, os.path.basename(sFilePathAbs)); + + # + # Set up arguments and environment. + # + asArgs = [sFilePathAbs,] + if sName in self.kdArguments: + asArgs.extend(self.kdArguments[sName]); + + sXmlFile = os.path.join(self.sUnitTestsPathDst, 'result.xml'); + + self._envSet('IPRT_TEST_OMIT_TOP_TEST', '1'); + self._envSet('IPRT_TEST_FILE', sXmlFile); + + if self._wrapPathExists(sXmlFile): + try: os.unlink(sXmlFile); + except: self._wrapDeleteFile(sXmlFile); + + # + # Execute the test case. + # + # Windows is confusing output. Trying a few things to get rid of this. + # First, flush both stderr and stdout before running the child. Second, + # assign the child stderr to stdout. If this doesn't help, we'll have + # to capture the child output. + # + reporter.log('*** Executing %s%s...' % (asArgs, ' [hardened]' if fHardened else '')); + try: sys.stdout.flush(); + except: pass; + try: sys.stderr.flush(); + except: pass; + + iRc = 0; + + if not self.fDryRun: + if fCopyToRemote: + fRc = self.txsRunTest(self.oTxsSession, sName, cMsTimeout = 30 * 60 * 1000, sExecName = asArgs[0], + asArgs = asArgs, asAddEnv = self.asEnv, fCheckSessionStatus = True); + if fRc: + iRc = 0; + else: + (_, sOpcode, abPayload) = self.oTxsSession.getLastReply(); + if sOpcode.startswith('PROC NOK '): # Extract process rc. + iRc = abPayload[0]; # ASSUMES 8-bit rc for now. + if iRc == 0: # Might happen if the testcase misses some dependencies. Set it to -42 then. + iRc = -42; + else: + iRc = -1; ## @todo + else: + oChild = None; + try: + if fHardened: + oChild = utils.sudoProcessPopen(asArgs, stdin = oDevNull, stdout = sys.stdout, stderr = sys.stdout); + else: + oChild = utils.processPopenSafe(asArgs, stdin = oDevNull, stdout = sys.stdout, stderr = sys.stdout); + except: + if sName in [ 'tstAsmStructsRC', # 32-bit, may fail to start on 64-bit linux. Just ignore. + ]: + reporter.logXcpt(); + fSkipped = True; + else: + reporter.errorXcpt(); + iRc = 1023; + oChild = None; + + if oChild is not None: + self.pidFileAdd(oChild.pid, sName, fSudo = fHardened); + iRc = oChild.wait(); + self.pidFileRemove(oChild.pid); + # + # Clean up + # + for sPath in asFilesToRemove: + self._wrapDeleteFile(sPath); + for sPath in asDirsToRemove: + self._wrapRemoveDir(sPath); + + # + # Report. + # + if os.path.exists(sXmlFile): + reporter.addSubXmlFile(sXmlFile); + if fHardened: + self._wrapDeleteFile(sXmlFile); + else: + os.unlink(sXmlFile); + + if iRc == 0: + reporter.log('*** %s: exit code %d' % (sFilePathAbs, iRc)); + self.cPassed += 1; + + elif iRc == 4: # RTEXITCODE_SKIPPED + reporter.log('*** %s: exit code %d (RTEXITCODE_SKIPPED)' % (sFilePathAbs, iRc)); + fSkipped = True; + self.cSkipped += 1; + + elif fSkipped: + reporter.log('*** %s: exit code %d (Skipped)' % (sFilePathAbs, iRc)); + self.cSkipped += 1; + + else: + sName = self.kdExitCodeNames.get(iRc, ''); + if iRc in self.kdExitCodeNamesWin and utils.getHostOs() == 'win': + sName = self.kdExitCodeNamesWin[iRc]; + if sName != '': + sName = ' (%s)' % (sName); + + if iRc != 1: + reporter.testFailure('Exit status: %d%s' % (iRc, sName)); + reporter.log( '!*! %s: exit code %d%s' % (sFilePathAbs, iRc, sName)); + else: + reporter.error('!*! %s: exit code %d%s' % (sFilePathAbs, iRc, sName)); + self.cFailed += 1; + + return fSkipped; + + def _testRunUnitTestsSet(self, oTestVm, sTestCasePattern, sTestCaseSubDir): + """ + Run subset of the unit tests set. + """ + + # Open /dev/null for use as stdin further down. + try: + oDevNull = open(os.path.devnull, 'w+'); # pylint: disable=consider-using-with,unspecified-encoding + except: + oDevNull = None; + + # Determin the host OS specific exclusion lists. + dTestCasesBuggyForHostOs = self.kdTestCasesBuggyPerOs.get(utils.getHostOs(), []); + dTestCasesBuggyForHostOs.update(self.kdTestCasesBuggyPerOs.get(utils.getHostOsDotArch(), [])); + + ## @todo Add filtering for more specific OSes (like OL server, doesn't have X installed) by adding a separate + # black list + using utils.getHostOsVersion(). + + # + # Process the file list and run everything looking like a testcase. + # + if not self.fOnlyWhiteList: + if self.sMode in ('local', 'remote-copy'): + asFiles = sorted(os.listdir(os.path.join(self.sUnitTestsPathSrc, sTestCaseSubDir))); + else: # 'remote-exec' + ## @todo Implement remote file enumeration / directory listing. + reporter.error('Sorry, no remote file enumeration implemented yet!\nUse --only-whitelist instead.'); + return; + else: + # Transform our dict into a list, where the keys are the list elements. + asFiles = list(self.kdTestCasesWhiteList.keys()); + # Make sure to only keep the list item's base name so that the iteration down below works + # with our white list without any additional modification. + asFiles = [os.path.basename(s) for s in asFiles]; + + for sFilename in asFiles: + # When executing in remote execution mode, make sure to append the executable suffix here, as + # the (white / black) lists do not contain any OS-specific executable suffixes. + if self.sMode == 'remote-exec': + sFilename = sFilename + self.sExeSuff; + # Separate base and suffix and morph the base into something we + # can use for reporting and array lookups. + sBaseName = os.path.basename(sFilename); + sName, sSuffix = os.path.splitext(sBaseName); + if sTestCaseSubDir != '.': + sName = sTestCaseSubDir + '/' + sName; + + reporter.log2('sTestCasePattern=%s, sBaseName=%s, sName=%s, sSuffix=%s, sFileName=%s' + % (sTestCasePattern, sBaseName, sName, sSuffix, sFilename,)); + + # Process white list first, if set. + if self.fOnlyWhiteList \ + and not self._isExcluded(sName, self.kdTestCasesWhiteList): + # (No testStart/Done or accounting here!) + reporter.log('%s: SKIPPED (not in white list)' % (sName,)); + continue; + + # Basic exclusion. + if not re.match(sTestCasePattern, sBaseName) \ + or sSuffix in self.kasSuffixBlackList: + reporter.log2('"%s" is not a test case.' % (sName,)); + continue; + + # When not only processing the white list, do some more checking first. + if not self.fOnlyWhiteList: + # Check if the testcase is black listed or buggy before executing it. + if self._isExcluded(sName, self.kdTestCasesBlackList): + # (No testStart/Done or accounting here!) + reporter.log('%s: SKIPPED (blacklisted)' % (sName,)); + continue; + + if self._isExcluded(sName, self.kdTestCasesBuggy): + reporter.testStart(sName); + reporter.log('%s: Skipping, buggy in general.' % (sName,)); + reporter.testDone(fSkipped = True); + self.cSkipped += 1; + continue; + + if self._isExcluded(sName, dTestCasesBuggyForHostOs): + reporter.testStart(sName); + reporter.log('%s: Skipping, buggy on %s.' % (sName, utils.getHostOs(),)); + reporter.testDone(fSkipped = True); + self.cSkipped += 1; + continue; + else: + # Passed the white list check already above. + pass; + + sFilePathAbs = os.path.normpath(os.path.join(self.sUnitTestsPathSrc, os.path.join(sTestCaseSubDir, sFilename))); + reporter.log2('sFilePathAbs=%s\n' % (sFilePathAbs,)); + reporter.testStart(sName); + try: + fSkipped = self._executeTestCase(oTestVm, sName, sFilePathAbs, sTestCaseSubDir, oDevNull); + except: + reporter.errorXcpt('!*!'); + self.cFailed += 1; + fSkipped = False; + reporter.testDone(fSkipped); + + +if __name__ == '__main__': + sys.exit(tdUnitTest1().main(sys.argv)) |