diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/ValidationKit/tests | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ValidationKit/tests')
73 files changed, 26295 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/tests/Makefile.kmk b/src/VBox/ValidationKit/tests/Makefile.kmk new file mode 100644 index 00000000..fce3b9bf --- /dev/null +++ b/src/VBox/ValidationKit/tests/Makefile.kmk @@ -0,0 +1,60 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit. +# + +# +# 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 + +# +# Include sub-makefiles. +# +include $(PATH_SUB_CURRENT)/additions/Makefile.kmk +include $(PATH_SUB_CURRENT)/api/Makefile.kmk +include $(PATH_SUB_CURRENT)/audio/Makefile.kmk +include $(PATH_SUB_CURRENT)/autostart/Makefile.kmk +include $(PATH_SUB_CURRENT)/benchmarks/Makefile.kmk +include $(PATH_SUB_CURRENT)/cpu/Makefile.kmk +include $(PATH_SUB_CURRENT)/network/Makefile.kmk +include $(PATH_SUB_CURRENT)/selftests/Makefile.kmk +include $(PATH_SUB_CURRENT)/smoketests/Makefile.kmk +include $(PATH_SUB_CURRENT)/storage/Makefile.kmk +include $(PATH_SUB_CURRENT)/teleportation/Makefile.kmk +include $(PATH_SUB_CURRENT)/unittests/Makefile.kmk +include $(PATH_SUB_CURRENT)/installation/Makefile.kmk +include $(PATH_SUB_CURRENT)/usb/Makefile.kmk +include $(PATH_SUB_CURRENT)/serial/Makefile.kmk + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/__init__.py b/src/VBox/ValidationKit/tests/__init__.py new file mode 100644 index 00000000..00fdf9ea --- /dev/null +++ b/src/VBox/ValidationKit/tests/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Just to make python 2.x happy. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-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: 155244 $" + diff --git a/src/VBox/ValidationKit/tests/additions/Makefile.kmk b/src/VBox/ValidationKit/tests/additions/Makefile.kmk new file mode 100644 index 00000000..5af122d2 --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/Makefile.kmk @@ -0,0 +1,54 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Additions 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 += ValidationKitTestsAdditions +ValidationKitTestsAdditions_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsAdditions_INST = $(INST_VALIDATIONKIT)tests/additions/ +ValidationKitTestsAdditions_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdAddBasic1.py \ + $(PATH_SUB_CURRENT)/tdAddGuestCtrl.py \ + $(PATH_SUB_CURRENT)/tdAddSharedFolders1.py + + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsAdditions_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/additions/tdAddBasic1.py b/src/VBox/ValidationKit/tests/additions/tdAddBasic1.py new file mode 100755 index 00000000..572e5048 --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/tdAddBasic1.py @@ -0,0 +1,649 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdAddBasic1.py $ + +""" +VirtualBox Validation Kit - Additions Basics #1. +""" + +__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: 155244 $" + +# Standard Python imports. +import os; +import sys; +import uuid; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + +# Sub-test driver imports. +sys.path.append(os.path.dirname(os.path.abspath(__file__))); # For sub-test drivers. +from tdAddGuestCtrl import SubTstDrvAddGuestCtrl; +from tdAddSharedFolders1 import SubTstDrvAddSharedFolders1; + + +class tdAddBasic1(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Additions Basics #1. + """ + ## @todo + # - More of the settings stuff can be and need to be generalized! + # + + def __init__(self): + vbox.TestDriver.__init__(self); + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + self.asTestsDef = ['install', 'guestprops', 'stdguestprops', 'guestcontrol', 'sharedfolders']; + self.asTests = self.asTestsDef; + self.asRsrcs = None + # The file we're going to use as a beacon to wait if the Guest Additions CD-ROM is ready. + self.sFileCdWait = ''; + # Path pointing to the Guest Additions on the (V)ISO file. + self.sGstPathGaPrefix = ''; + + self.addSubTestDriver(SubTstDrvAddGuestCtrl(self)); + self.addSubTestDriver(SubTstDrvAddSharedFolders1(self)); + + # + # Overridden methods. + # + def showUsage(self): + """ Shows this driver's command line options. """ + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAddBasic1 Options:'); + reporter.log(' --tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --quick'); + reporter.log(' Same as --virt-modes hwvirt --cpu-counts 1.'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of tests'); + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef),)); + + elif asArgs[iArg] == '--quick': + self.parseOption(['--virt-modes', 'hwvirt'], 0); + self.parseOption(['--cpu-counts', '1'], 0); + + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + if self.asRsrcs is None: + self.asRsrcs = [] + for oSubTstDrv in self.aoSubTstDrvs: + self.asRsrcs.extend(oSubTstDrv.asRsrcs) + self.asRsrcs.extend(self.oTestVmSet.getResourceSet()) + return self.asRsrcs + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + sGaIso = self.getGuestAdditionsIso(); + + # On 6.0 we merge the GAs with the ValidationKit so we can get at FsPerf. + # + # Note1: Not possible to do a double import as both images an '/OS2' dir. + # So, using same dir as with unattended VISOs for the valkit. + # + # Note2: We need to make sure that we don't change the location of the + # ValidationKit bits of the combined VISO, as this will break TXS' (TestExecService) + # automatic updating mechanism (uses hardcoded paths, e.g. "{CDROM}/linux/amd64/TestExecService"). + # + ## @todo Find a solution for testing the automatic Guest Additions updates, which also looks at {CDROM}s root. + if self.fpApiVer >= 6.0: + sGaViso = os.path.join(self.sScratchPath, 'AdditionsAndValKit.viso'); + ## @todo encode as bash cmd line: + sVisoContent = '--iprt-iso-maker-file-marker-bourne-sh %s ' \ + '--import-iso \'%s\' ' \ + '--push-iso \'%s\' ' \ + '/vboxadditions=/ ' \ + '--pop ' \ + % (uuid.uuid4(), self.sVBoxValidationKitIso, sGaIso); + reporter.log2('Using VISO combining ValKit and GAs "%s": %s' % (sVisoContent, sGaViso)); + with open(sGaViso, 'w') as oGaViso: # pylint: disable=unspecified-encoding + oGaViso.write(sVisoContent); + sGaIso = sGaViso; + + self.sGstPathGaPrefix = 'vboxadditions'; + else: + self.sGstPathGaPrefix = ''; + + + reporter.log2('Path to Guest Additions on ISO is "%s"' % self.sGstPathGaPrefix); + + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, sDvdImage = sGaIso); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + + # + # Test execution helpers. + # + + def testOneCfg(self, oVM, oTestVm): + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + fRc = False; + + self.logVmInfo(oVM); + + # We skip Linux Guest Additions testing for VBox < 6.1 for now. + fVersionIgnored = oTestVm.isLinux() and self.fpApiVer < 6.1; + + if fVersionIgnored: + reporter.log('Skipping testing for "%s" because VBox version %s is ignored' % (oTestVm.sKind, self.fpApiVer,)); + fRc = True; + else: + reporter.testStart('Waiting for TXS'); + if oTestVm.isWindows(): + self.sFileCdWait = ('%s/VBoxWindowsAdditions.exe' % (self.sGstPathGaPrefix,)); + elif oTestVm.isLinux(): + self.sFileCdWait = ('%s/VBoxLinuxAdditions.run' % (self.sGstPathGaPrefix,)); + + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True, + cMsCdWait = 5 * 60 * 1000, + sFileCdWait = self.sFileCdWait); + reporter.testDone(); + + if oSession is not None \ + and oTxsSession is not None: + self.addTask(oTxsSession); + # Do the testing. + fSkip = 'install' not in self.asTests; + reporter.testStart('Install'); + if not fSkip: + fRc, oTxsSession = self.testInstallAdditions(oSession, oTxsSession, oTestVm); + reporter.testDone(fSkip); + + if not fSkip \ + and not fRc: + reporter.log('Skipping following tests as Guest Additions were not installed successfully'); + else: + fSkip = 'guestprops' not in self.asTests; + reporter.testStart('Guest Properties'); + if not fSkip: + fRc = self.testGuestProperties(oSession, oTxsSession, oTestVm) and fRc; + reporter.testDone(fSkip); + + fSkip = 'guestcontrol' not in self.asTests; + reporter.testStart('Guest Control'); + if not fSkip: + fRc, oTxsSession = self.aoSubTstDrvs[0].testIt(oTestVm, oSession, oTxsSession); + reporter.testDone(fSkip); + + fSkip = 'sharedfolders' not in self.asTests; + reporter.testStart('Shared Folders'); + if not fSkip: + fRc, oTxsSession = self.aoSubTstDrvs[1].testIt(oTestVm, oSession, oTxsSession); + reporter.testDone(fSkip or fRc is None); + + ## @todo Save and restore test. + + ## @todo Reset tests. + + ## @todo Final test: Uninstallation. + + # Download the TxS (Test Execution Service) log. This is not fatal when not being present. + if not fRc: + self.txsDownloadFiles(oSession, oTxsSession, + [ (oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vbox-txs-release.log'), + 'vbox-txs-%s.log' % oTestVm.sVmName) ], + fIgnoreErrors = True); + + # Cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + + return fRc; + + def testInstallAdditions(self, oSession, oTxsSession, oTestVm): + """ + Tests installing the guest additions + """ + if oTestVm.isWindows(): + (fRc, oTxsSession) = self.testWindowsInstallAdditions(oSession, oTxsSession, oTestVm); + elif oTestVm.isLinux(): + (fRc, oTxsSession) = self.testLinuxInstallAdditions(oSession, oTxsSession, oTestVm); + else: + reporter.error('Guest Additions installation not implemented for %s yet! (%s)' % + (oTestVm.sKind, oTestVm.sVmName,)); + fRc = False; + + # + # Verify installation of Guest Additions using commmon bits. + # + if fRc: + # + # Check if the additions are operational. + # + try: oGuest = oSession.o.console.guest; + except: + reporter.errorXcpt('Getting IGuest failed.'); + return (False, oTxsSession); + + # Wait for the GAs to come up. + reporter.testStart('IGuest::additionsRunLevel'); + fRc = self.testIGuest_additionsRunLevel(oSession, oTestVm, oGuest); + reporter.testDone(); + + # Check the additionsVersion attribute. It must not be empty. + reporter.testStart('IGuest::additionsVersion'); + fRc = self.testIGuest_additionsVersion(oGuest) and fRc; + reporter.testDone(); + + # Check Guest Additions facilities + reporter.testStart('IGuest::getFacilityStatus'); + fRc = self.testIGuest_getFacilityStatus(oTestVm, oGuest) and fRc; + reporter.testDone(); + + # Do a bit of diagnosis on error. + if not fRc: + if oTestVm.isLinux(): + reporter.log('Boot log:'); + sCmdJournalCtl = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'journalctl'); + oTxsSession.syncExec(sCmdJournalCtl, (sCmdJournalCtl, '-b'), fIgnoreErrors = True); + reporter.log('Loaded processes:'); + sCmdPs = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'ps'); + oTxsSession.syncExec(sCmdPs, (sCmdPs, '-a', '-u', '-x'), fIgnoreErrors = True); + reporter.log('Kernel messages:'); + sCmdDmesg = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'dmesg'); + oTxsSession.syncExec(sCmdDmesg, (sCmdDmesg), fIgnoreErrors = True); + reporter.log('Loaded modules:'); + sCmdLsMod = oTestVm.pathJoin(self.getGuestSystemAdminDir(oTestVm), 'lsmod'); + oTxsSession.syncExec(sCmdLsMod, (sCmdLsMod), fIgnoreErrors = True); + elif oTestVm.isWindows() or oTestVm.isOS2(): + sShell = self.getGuestSystemShell(oTestVm); + sShellOpt = '/C' if oTestVm.isWindows() or oTestVm.isOS2() else '-c'; + reporter.log('Loaded processes:'); + oTxsSession.syncExec(sShell, (sShell, sShellOpt, "tasklist.exe", "/FO", "CSV"), fIgnoreErrors = True); + reporter.log('Listing autostart entries:'); + oTxsSession.syncExec(sShell, (sShell, sShellOpt, "wmic.exe", "startup", "get"), fIgnoreErrors = True); + reporter.log('Listing autostart entries:'); + oTxsSession.syncExec(sShell, (sShell, sShellOpt, "dir", + oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'VBox*')), + fIgnoreErrors = True); + reporter.log('Downloading logs ...'); + self.txsDownloadFiles(oSession, oTxsSession, + [ ( self.getGuestVBoxTrayClientLogFile(oTestVm), + 'ga-vboxtrayclient-%s.log' % (oTestVm.sVmName,),), + ( "C:\\Documents and Settings\\All Users\\Application Data\\Microsoft\\Dr Watson\\drwtsn32.log", + 'ga-drwatson-%s.log' % (oTestVm.sVmName,), ), + ], + fIgnoreErrors = True); + + return (fRc, oTxsSession); + + def getGuestVBoxTrayClientLogFile(self, oTestVm): + """ Gets the path on the guest for the (release) log file of VBoxTray / VBoxClient. """ + if oTestVm.isWindows(): + return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'VBoxTray.log'); + + return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'VBoxClient.log'); + + def setGuestEnvVar(self, oSession, oTxsSession, oTestVm, sName, sValue): + """ Sets a system-wide environment variable on the guest. Only supports Windows guests so far. """ + _ = oSession; + if oTestVm.isWindows(): + sPathRegExe = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'reg.exe'); + self.txsRunTest(oTxsSession, ('Set env var \"%s\"' % (sName,)), + 30 * 1000, sPathRegExe, + (sPathRegExe, 'add', + '"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"', '/v', + sName, '/t', 'REG_EXPAND_SZ', '/d', sValue, '/f')); + + def testWindowsInstallAdditions(self, oSession, oTxsSession, oTestVm): + """ + Installs the Windows guest additions using the test execution service. + Since this involves rebooting the guest, we will have to create a new TXS session. + """ + + # Set system-wide env vars to enable release logging on some applications. + self.setGuestEnvVar(oSession, oTxsSession, oTestVm, 'VBOXTRAY_RELEASE_LOG', 'all.e.l.l2.l3.f'); + self.setGuestEnvVar(oSession, oTxsSession, oTestVm, 'VBOXTRAY_RELEASE_LOG_FLAGS', 'time thread group append'); + self.setGuestEnvVar(oSession, oTxsSession, oTestVm, 'VBOXTRAY_RELEASE_LOG_DEST', + ('file=%s' % (self.getGuestVBoxTrayClientLogFile(oTestVm),))); + + # + # Install the public signing key. + # + if oTestVm.sKind not in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + fRc = self.txsRunTest(oTxsSession, 'VBoxCertUtil.exe', 1 * 60 * 1000, + '${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, + ('${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, 'add-trusted-publisher', + '${CDROM}/%s/cert/vbox-sha1.cer' % self.sGstPathGaPrefix), + fCheckSessionStatus = True); + if not fRc: + reporter.error('Error installing SHA1 certificate'); + else: + fRc = self.txsRunTest(oTxsSession, 'VBoxCertUtil.exe', 1 * 60 * 1000, + '${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, + ('${CDROM}/%s/cert/VBoxCertUtil.exe' % self.sGstPathGaPrefix, 'add-trusted-publisher', + '${CDROM}/%s/cert/vbox-sha256.cer' % self.sGstPathGaPrefix), fCheckSessionStatus = True); + if not fRc: + reporter.error('Error installing SHA256 certificate'); + + # + # Delete relevant log files. + # + # Note! On some guests the files in question still can be locked by the OS, so ignore + # deletion errors from the guest side (e.g. sharing violations) and just continue. + # + sWinDir = self.getGuestWinDir(oTestVm); + aasLogFiles = [ + ( oTestVm.pathJoin(sWinDir, 'setupapi.log'), 'ga-setupapi-%s.log' % (oTestVm.sVmName,), ), + ( oTestVm.pathJoin(sWinDir, 'setupact.log'), 'ga-setupact-%s.log' % (oTestVm.sVmName,), ), + ( oTestVm.pathJoin(sWinDir, 'setuperr.log'), 'ga-setuperr-%s.log' % (oTestVm.sVmName,), ), + ]; + + # Apply The SetupAPI logging level so that we also get the (most verbose) setupapi.dev.log file. + ## @todo !!! HACK ALERT !!! Add the value directly into the testing source image. Later. + sRegExe = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'reg.exe'); + fHaveSetupApiDevLog = self.txsRunTest(oTxsSession, 'Enabling setupapi.dev.log', 30 * 1000, + sRegExe, + (sRegExe, 'add', + '"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Setup"', + '/v', 'LogLevel', '/t', 'REG_DWORD', '/d', '0xFF'), + fCheckSessionStatus = True); + + for sGstFile, _ in aasLogFiles: + self.txsRmFile(oSession, oTxsSession, sGstFile, 10 * 1000, fIgnoreErrors = True); + + # Enable installing the optional auto-logon modules (VBoxGINA/VBoxCredProv). + # Also tell the installer to produce the appropriate log files. + sExe = '${CDROM}/%s/VBoxWindowsAdditions.exe' % self.sGstPathGaPrefix; + asArgs = [ sExe, '/S', '/l', '/with_autologon' ]; + + # Determine if we need to force installing the legacy timestamp CA to make testing succeed. + # Note: Don't force installing when the Guest Additions installer should do this automatically, + # i.e, only force it for Windows Server 2016 and up. + fForceInstallTimeStampCA = False; + if self.fpApiVer >= 6.1 \ + and oTestVm.getNonCanonicalGuestOsType() \ + in [ 'Windows2016', 'Windows2019', 'Windows2022', 'Windows11' ]: + fForceInstallTimeStampCA = True; + + # As we don't have a console command line to parse for the Guest Additions installer (only a message box) and + # unknown / unsupported parameters get ignored with silent installs anyway, we safely can add the following parameter(s) + # even if older Guest Addition installers might not support those. + if fForceInstallTimeStampCA: + asArgs.extend([ '/install_timestamp_ca' ]); + + # + # Do the actual install. + # + fRc = self.txsRunTest(oTxsSession, 'VBoxWindowsAdditions.exe', 5 * 60 * 1000, + sExe, asArgs, fCheckSessionStatus = True); + + # Add the Windows Guest Additions installer files to the files we want to download + # from the guest. Note: There won't be a install_ui.log because of the silent installation. + sGuestAddsDir = 'C:\\Program Files\\Oracle\\VirtualBox Guest Additions\\'; + aasLogFiles.append((sGuestAddsDir + 'install.log', 'ga-install-%s.log' % (oTestVm.sVmName,),)); + aasLogFiles.append((sGuestAddsDir + 'install_drivers.log', 'ga-install_drivers-%s.log' % (oTestVm.sVmName,),)); + aasLogFiles.append(('C:\\Windows\\setupapi.log', 'ga-setupapi-%s.log' % (oTestVm.sVmName,),)); + + # Note: setupapi.dev.log only is available since Windows 2000. + if fHaveSetupApiDevLog: + aasLogFiles.append(('C:\\Windows\\setupapi.dev.log', 'ga-setupapi.dev-%s.log' % (oTestVm.sVmName,),)); + + # + # Download log files. + # Ignore errors as all files above might not be present (or in different locations) + # on different Windows guests. + # + self.txsDownloadFiles(oSession, oTxsSession, aasLogFiles, fIgnoreErrors = True); + + # + # Reboot the VM and reconnect the TXS session. + # + if fRc: + reporter.testStart('Rebooting guest w/ updated Guest Additions active'); + (fRc, oTxsSession) = self.txsRebootAndReconnectViaTcp(oSession, oTxsSession, cMsTimeout = 15 * 60 * 1000); + if fRc: + pass; + else: + reporter.testFailure('Rebooting and reconnecting to TXS service failed'); + reporter.testDone(); + else: + reporter.error('Error installing Windows Guest Additions (installer returned with exit code <> 0)') + + return (fRc, oTxsSession); + + def getAdditionsInstallerResult(self, oTxsSession): + """ + Extracts the Guest Additions installer exit code from a run before. + Assumes that nothing else has been run on the same TXS session in the meantime. + """ + iRc = 0; + (_, sOpcode, abPayload) = oTxsSession.getLastReply(); + if sOpcode.startswith('PROC NOK '): # Extract process rc + iRc = abPayload[0]; # ASSUMES 8-bit rc for now. + ## @todo Parse more statuses here. + return iRc; + + def testLinuxInstallAdditions(self, oSession, oTxsSession, oTestVm): + # + # The actual install. + # Also tell the installer to produce the appropriate log files. + # + # Make sure to add "--nox11" to the makeself wrapper in order to not getting any blocking + # xterm window spawned. + fRc = self.txsRunTest(oTxsSession, 'VBoxLinuxAdditions.run', 30 * 60 * 1000, + self.getGuestSystemShell(oTestVm), + (self.getGuestSystemShell(oTestVm), + '${CDROM}/%s/VBoxLinuxAdditions.run' % self.sGstPathGaPrefix, '--nox11')); + if not fRc: + iRc = self.getAdditionsInstallerResult(oTxsSession); + # Check for rc == 0 just for completeness. + if iRc in (0, 2): # Can happen if the GA installer has detected older VBox kernel modules running and needs a reboot. + reporter.log('Guest has old(er) VBox kernel modules still running; requires a reboot'); + fRc = True; + + if not fRc: + reporter.error('Installing Linux Additions failed (isSuccess=%s, lastReply=%s, see log file for details)' + % (oTxsSession.isSuccess(), oTxsSession.getLastReply())); + + # + # Download log files. + # Ignore errors as all files above might not be present for whatever reason. + # + self.txsDownloadFiles(oSession, oTxsSession, + [('/var/log/vboxadd-install.log', 'vboxadd-install-%s.log' % oTestVm.sVmName), ], + fIgnoreErrors = True); + + # Do the final reboot to get the just installed Guest Additions up and running. + if fRc: + reporter.testStart('Rebooting guest w/ updated Guest Additions active'); + (fRc, oTxsSession) = self.txsRebootAndReconnectViaTcp(oSession, oTxsSession, cMsTimeout = 15 * 60 * 1000); + if fRc: + pass + else: + reporter.testFailure('Rebooting and reconnecting to TXS service failed'); + reporter.testDone(); + + return (fRc, oTxsSession); + + def testIGuest_additionsRunLevel(self, oSession, oTestVm, oGuest): + """ + Do run level tests. + """ + + _ = oGuest; + + if oTestVm.isWindows(): + if oTestVm.isLoggedOntoDesktop(): + eExpectedRunLevel = vboxcon.AdditionsRunLevelType_Desktop; + else: + eExpectedRunLevel = vboxcon.AdditionsRunLevelType_Userland; + else: + ## @todo VBoxClient does not have facility statuses implemented yet. + eExpectedRunLevel = vboxcon.AdditionsRunLevelType_Userland; + + return self.waitForGAs(oSession, aenmWaitForRunLevels = [ eExpectedRunLevel ]); + + def testIGuest_additionsVersion(self, oGuest): + """ + Returns False if no version string could be obtained, otherwise True + even though errors are logged. + """ + try: + sVer = oGuest.additionsVersion; + except: + reporter.errorXcpt('Getting the additions version failed.'); + return False; + reporter.log('IGuest::additionsVersion="%s"' % (sVer,)); + + if sVer.strip() == '': + reporter.error('IGuest::additionsVersion is empty.'); + return False; + + if sVer != sVer.strip(): + reporter.error('IGuest::additionsVersion is contains spaces: "%s".' % (sVer,)); + + asBits = sVer.split('.'); + if len(asBits) < 3: + reporter.error('IGuest::additionsVersion does not contain at least tree dot separated fields: "%s" (%d).' + % (sVer, len(asBits))); + + ## @todo verify the format. + return True; + + def checkFacilityStatus(self, oGuest, eFacilityType, sDesc, fMustSucceed = True): + """ + Prints the current status of a Guest Additions facility. + + Return success status. + """ + + fRc = True; + + try: + eStatus, tsLastUpdatedMs = oGuest.getFacilityStatus(eFacilityType); + except: + if fMustSucceed: + reporter.errorXcpt('Getting facility status for "%s" failed' % (sDesc,)); + fRc = False; + else: + if eStatus == vboxcon.AdditionsFacilityStatus_Inactive: + sStatus = "INACTIVE"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Paused: + sStatus = "PAUSED"; + elif eStatus == vboxcon.AdditionsFacilityStatus_PreInit: + sStatus = "PREINIT"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Init: + sStatus = "INIT"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Active: + sStatus = "ACTIVE"; + elif eStatus == vboxcon.AdditionsFacilityStatus_Terminating: + sStatus = "TERMINATING"; + fRc = not fMustSucceed; + elif eStatus == vboxcon.AdditionsFacilityStatus_Terminated: + sStatus = "TERMINATED"; + fRc = not fMustSucceed; + elif eStatus == vboxcon.AdditionsFacilityStatus_Failed: + sStatus = "FAILED"; + fRc = not fMustSucceed; + elif eStatus == vboxcon.AdditionsFacilityStatus_Unknown: + sStatus = "UNKNOWN"; + fRc = not fMustSucceed; + else: + sStatus = "???"; + fRc = not fMustSucceed; + + reporter.log('Guest Additions facility "%s": %s (last updated: %sms)' % (sDesc, sStatus, str(tsLastUpdatedMs))); + if fMustSucceed \ + and not fRc: + reporter.error('Guest Additions facility "%s" did not report expected status (is "%s")' % (sDesc, sStatus)); + + return fRc; + + def testIGuest_getFacilityStatus(self, oTestVm, oGuest): + """ + Checks Guest Additions facilities for their status. + + Returns success status. + """ + + reporter.testStart('Status VBoxGuest Driver'); + fRc = self.checkFacilityStatus(oGuest, vboxcon.AdditionsFacilityType_VBoxGuestDriver, "VBoxGuest Driver"); + reporter.testDone(); + + reporter.testStart('Status VBoxService'); + fRc = self.checkFacilityStatus(oGuest, vboxcon.AdditionsFacilityType_VBoxService, "VBoxService") and fRc; + reporter.testDone(); + + if oTestVm.isWindows(): + if oTestVm.isLoggedOntoDesktop(): + ## @todo VBoxClient does not have facility statuses implemented yet. + reporter.testStart('Status VBoxTray / VBoxClient'); + fRc = self.checkFacilityStatus(oGuest, vboxcon.AdditionsFacilityType_VBoxTrayClient, + "VBoxTray / VBoxClient") and fRc; + reporter.testDone(); + ## @todo Add more. + + return fRc; + + def testGuestProperties(self, oSession, oTxsSession, oTestVm): + """ + Test guest properties. + """ + _ = oSession; _ = oTxsSession; _ = oTestVm; + return True; + +if __name__ == '__main__': + sys.exit(tdAddBasic1().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py b/src/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py new file mode 100755 index 00000000..0b4ef4bf --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py @@ -0,0 +1,5503 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# pylint: disable=too-many-lines + +""" +VirtualBox Validation Kit - Guest Control 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: 155473 $" + +# Standard Python imports. +import errno +import os +import random +import string +import struct +import sys +import threading +import time + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import testfileset; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestfileset; +from testdriver import vboxwrappers; +from common import utils; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int # pylint: disable=redefined-builtin,invalid-name + xrange = range; # pylint: disable=redefined-builtin,invalid-name + +def limitString(sString, cLimit = 128): + """ + Returns a string with ellipsis ("...") when exceeding the specified limit. + Useful for toning down logging. By default strings will be shortened at 128 characters. + """ + if not isinstance(sString, str): + sString = str(sString); + cLen = len(sString); + if not cLen: + return ''; + return (sString[:cLimit] + '...[%d more]' % (cLen - cLimit)) if cLen > cLimit else sString; + +class GuestStream(bytearray): + """ + Class for handling a guest process input/output stream. + + @todo write stdout/stderr tests. + """ + def appendStream(self, stream, convertTo = '<b'): + """ + Appends and converts a byte sequence to this object; + handy for displaying a guest stream. + """ + self.extend(struct.pack(convertTo, stream)); + + +class tdCtxCreds(object): + """ + Provides credentials to pass to the guest. + """ + def __init__(self, sUser = None, sPassword = None, sDomain = None): + self.oTestVm = None; + self.sUser = sUser; + self.sPassword = sPassword; + self.sDomain = sDomain; + + def applyDefaultsIfNotSet(self, oTestVm): + """ + Applies credential defaults, based on the test VM (guest OS), if + no credentials were set yet. + + Returns success status. + """ + self.oTestVm = oTestVm; + if not self.oTestVm: + reporter.log('VM object is invalid -- did VBoxSVC or a client crash?'); + return False; + + if self.sUser is None: + self.sUser = self.oTestVm.getTestUser(); + + if self.sPassword is None: + self.sPassword = self.oTestVm.getTestUserPassword(self.sUser); + + if self.sDomain is None: + self.sDomain = ''; + + return True; + +class tdTestGuestCtrlBase(object): + """ + Base class for all guest control tests. + + Note: This test ASSUMES that working Guest Additions + were installed and running on the guest to be tested. + """ + def __init__(self, oCreds = None): + self.oGuest = None; ##< IGuest. + self.oTestVm = None; + self.oCreds = oCreds ##< type: tdCtxCreds + self.timeoutMS = 30 * 1000; ##< 30s timeout + self.oGuestSession = None; ##< IGuestSession reference or None. + + def setEnvironment(self, oSession, oTxsSession, oTestVm): + """ + Sets the test environment required for this test. + + Returns success status. + """ + _ = oTxsSession; + + fRc = True; + try: + self.oGuest = oSession.o.console.guest; + self.oTestVm = oTestVm; + except: + fRc = reporter.errorXcpt(); + + if self.oCreds is None: + self.oCreds = tdCtxCreds(); + + fRc = fRc and self.oCreds.applyDefaultsIfNotSet(self.oTestVm); + + if not fRc: + reporter.log('Error setting up Guest Control testing environment!'); + + return fRc; + + def uploadLogData(self, oTstDrv, aData, sFileName, sDesc): + """ + Uploads (binary) data to a log file for manual (later) inspection. + """ + reporter.log('Creating + uploading log data file "%s"' % sFileName); + sHstFileName = os.path.join(oTstDrv.sScratchPath, sFileName); + try: + with open(sHstFileName, "wb") as oCurTestFile: + oCurTestFile.write(aData); + except: + return reporter.error('Unable to create temporary file for "%s"' % (sDesc,)); + return reporter.addLogFile(sHstFileName, 'misc/other', sDesc); + + def createSession(self, sName, fIsError = True): + """ + Creates (opens) a guest session. + Returns (True, IGuestSession) on success or (False, None) on failure. + """ + if self.oGuestSession is None: + if sName is None: + sName = "<untitled>"; + + reporter.log('Creating session "%s" ...' % (sName,)); + try: + self.oGuestSession = self.oGuest.createSession(self.oCreds.sUser, + self.oCreds.sPassword, + self.oCreds.sDomain, + sName); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Creating a guest session "%s" failed; sUser="%s", pw="%s", sDomain="%s":' + % (sName, self.oCreds.sUser, self.oCreds.sPassword, self.oCreds.sDomain)); + return (False, None); + + tsStartMs = base.timestampMilli(); + while base.timestampMilli() - tsStartMs < self.timeoutMS: + reporter.log('Waiting for session "%s" to start within %dms...' % (sName, self.timeoutMS)); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + waitResult = self.oGuestSession.waitForArray(aeWaitFor, self.timeoutMS); + + # Log session status changes. + if waitResult is vboxcon.GuestSessionWaitResult_Status: + reporter.log('Session "%s" indicated status change (status is now %d)' \ + % (sName, self.oGuestSession.status)); + if self.oGuestSession.status is vboxcon.GuestSessionStatus_Started: + # We indicate an error here, as we intentionally waited for the session start + # in the wait call above and got back a status change instead. + reporter.error('Session "%s" successfully started (thru status change)' % (sName,)); + break; + continue; # Continue waiting for the session to start. + + # + # Be nice to Guest Additions < 4.3: They don't support session handling and + # therefore return WaitFlagNotSupported. + # + if waitResult not in (vboxcon.GuestSessionWaitResult_Start, \ + vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErr(fIsError, 'Session did not start successfully, returned wait result: %d' \ + % (waitResult,)); + return (False, None); + reporter.log('Session "%s" successfully started' % (sName,)); + + # + # Make sure that the test VM configuration and Guest Control use the same path separator style for the guest. + # + sGstSep = '\\' if self.oGuestSession.pathStyle is vboxcon.PathStyle_DOS else '/'; + if self.oTestVm.pathSep() != sGstSep: + reporter.error('Configured test VM uses a different path style (%s) than Guest Control (%s)' \ + % (self.oTestVm.pathSep(), sGstSep)); + break; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Waiting for guest session "%s" (usr=%s;pw=%s;dom=%s) to start failed:' + % (sName, self.oCreds.sUser, self.oCreds.sPassword, self.oCreds.sDomain,)); + return (False, None); + else: + reporter.log('Warning: Session already set; this is probably not what you want'); + return (True, self.oGuestSession); + + def setSession(self, oGuestSession): + """ + Sets the current guest session and closes + an old one if necessary. + """ + if self.oGuestSession is not None: + self.closeSession(); + self.oGuestSession = oGuestSession; + return self.oGuestSession; + + def closeSession(self, fIsError = True): + """ + Closes the guest session. + """ + if self.oGuestSession is not None: + try: + sName = self.oGuestSession.name; + except: + return reporter.errorXcpt(); + + reporter.log('Closing session "%s" ...' % (sName,)); + try: + self.oGuestSession.close(); + self.oGuestSession = None; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Closing guest session "%s" failed:' % (sName,)); + return False; + return True; + +class tdTestCopyFrom(tdTestGuestCtrlBase): + """ + Test for copying files from the guest to the host. + """ + def __init__(self, sSrc = "", sDst = "", oCreds = None, afFlags = None, oSrc = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sSrc = sSrc; + self.sDst = sDst; + self.afFlags = afFlags; + self.oSrc = oSrc # type: testfileset.TestFsObj + if oSrc and not sSrc: + self.sSrc = oSrc.sPath; + +class tdTestCopyFromDir(tdTestCopyFrom): + + def __init__(self, sSrc = "", sDst = "", oCreds = None, afFlags = None, oSrc = None, fIntoDst = False): + tdTestCopyFrom.__init__(self, sSrc, sDst, oCreds, afFlags, oSrc); + self.fIntoDst = fIntoDst; # hint to the verification code that sDst == oSrc, rather than sDst+oSrc.sNAme == oSrc. + +class tdTestCopyFromFile(tdTestCopyFrom): + pass; + +class tdTestRemoveHostDir(object): + """ + Test step that removes a host directory tree. + """ + def __init__(self, sDir): + self.sDir = sDir; + + def execute(self, oTstDrv, oVmSession, oTxsSession, oTestVm, sMsgPrefix): + _ = oTstDrv; _ = oVmSession; _ = oTxsSession; _ = oTestVm; _ = sMsgPrefix; + if os.path.exists(self.sDir): + if base.wipeDirectory(self.sDir) != 0: + return False; + try: + os.rmdir(self.sDir); + except: + return reporter.errorXcpt('%s: sDir=%s' % (sMsgPrefix, self.sDir,)); + return True; + + + +class tdTestCopyTo(tdTestGuestCtrlBase): + """ + Test for copying files from the host to the guest. + """ + def __init__(self, sSrc = "", sDst = "", oCreds = None, afFlags = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sSrc = sSrc; + self.sDst = sDst; + self.afFlags = afFlags; + +class tdTestCopyToFile(tdTestCopyTo): + pass; + +class tdTestCopyToDir(tdTestCopyTo): + pass; + +class tdTestDirCreate(tdTestGuestCtrlBase): + """ + Test for directoryCreate call. + """ + def __init__(self, sDirectory = "", oCreds = None, fMode = 0, afFlags = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sDirectory = sDirectory; + self.fMode = fMode; + self.afFlags = afFlags; + +class tdTestDirCreateTemp(tdTestGuestCtrlBase): + """ + Test for the directoryCreateTemp call. + """ + def __init__(self, sDirectory = "", sTemplate = "", oCreds = None, fMode = 0, fSecure = False): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sDirectory = sDirectory; + self.sTemplate = sTemplate; + self.fMode = fMode; + self.fSecure = fSecure; + +class tdTestDirOpen(tdTestGuestCtrlBase): + """ + Test for the directoryOpen call. + """ + def __init__(self, sDirectory = "", oCreds = None, sFilter = "", afFlags = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sDirectory = sDirectory; + self.sFilter = sFilter; + self.afFlags = afFlags or []; + +class tdTestDirRead(tdTestDirOpen): + """ + Test for the opening, reading and closing a certain directory. + """ + def __init__(self, sDirectory = "", oCreds = None, sFilter = "", afFlags = None): + tdTestDirOpen.__init__(self, sDirectory, oCreds, sFilter, afFlags); + +class tdTestExec(tdTestGuestCtrlBase): + """ + Specifies exactly one guest control execution test. + Has a default timeout of 5 minutes (for safety). + """ + def __init__(self, sCmd = "", asArgs = None, aEnv = None, afFlags = None, # pylint: disable=too-many-arguments + timeoutMS = 5 * 60 * 1000, oCreds = None, fWaitForExit = True): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sCmd = sCmd; + self.asArgs = asArgs if asArgs is not None else [sCmd,]; + self.aEnv = aEnv; + self.afFlags = afFlags or []; + self.timeoutMS = timeoutMS; + self.fWaitForExit = fWaitForExit; + self.uExitStatus = 0; + self.iExitCode = 0; + self.cbStdOut = 0; + self.cbStdErr = 0; + self.sBuf = ''; + +class tdTestFileExists(tdTestGuestCtrlBase): + """ + Test for the file exists API call (fileExists). + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestFileRemove(tdTestGuestCtrlBase): + """ + Test querying guest file information. + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestRemoveBase(tdTestGuestCtrlBase): + """ + Removal base. + """ + def __init__(self, sPath, fRcExpect = True, oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sPath = sPath; + self.fRcExpect = fRcExpect; + + def execute(self, oSubTstDrv): + """ + Executes the test, returns True/False. + """ + _ = oSubTstDrv; + return True; + + def checkRemoved(self, sType): + """ Check that the object was removed using fObjExists. """ + try: + fExists = self.oGuestSession.fsObjExists(self.sPath, False); + except: + return reporter.errorXcpt('fsObjExists failed on "%s" after deletion (type: %s)' % (self.sPath, sType)); + if fExists: + return reporter.error('fsObjExists says "%s" still exists after deletion (type: %s)!' % (self.sPath, sType)); + return True; + +class tdTestRemoveFile(tdTestRemoveBase): + """ + Remove a single file. + """ + def __init__(self, sPath, fRcExpect = True, oCreds = None): + tdTestRemoveBase.__init__(self, sPath, fRcExpect, oCreds); + + def execute(self, oSubTstDrv): + reporter.log2('Deleting file "%s" ...' % (limitString(self.sPath),)); + try: + if oSubTstDrv.oTstDrv.fpApiVer >= 5.0: + self.oGuestSession.fsObjRemove(self.sPath); + else: + self.oGuestSession.fileRemove(self.sPath); + except: + reporter.maybeErrXcpt(self.fRcExpect, 'Removing "%s" failed' % (self.sPath,)); + return not self.fRcExpect; + if not self.fRcExpect: + return reporter.error('Expected removing "%s" to failed, but it succeeded' % (self.sPath,)); + + return self.checkRemoved('file'); + +class tdTestRemoveDir(tdTestRemoveBase): + """ + Remove a single directory if empty. + """ + def __init__(self, sPath, fRcExpect = True, oCreds = None): + tdTestRemoveBase.__init__(self, sPath, fRcExpect, oCreds); + + def execute(self, oSubTstDrv): + _ = oSubTstDrv; + reporter.log2('Deleting directory "%s" ...' % (limitString(self.sPath),)); + try: + self.oGuestSession.directoryRemove(self.sPath); + except: + reporter.maybeErrXcpt(self.fRcExpect, 'Removing "%s" (as a directory) failed' % (self.sPath,)); + return not self.fRcExpect; + if not self.fRcExpect: + return reporter.error('Expected removing "%s" (dir) to failed, but it succeeded' % (self.sPath,)); + + return self.checkRemoved('directory'); + +class tdTestRemoveTree(tdTestRemoveBase): + """ + Recursively remove a directory tree. + """ + def __init__(self, sPath, afFlags = None, fRcExpect = True, fNotExist = False, oCreds = None): + tdTestRemoveBase.__init__(self, sPath, fRcExpect, oCreds = None); + self.afFlags = afFlags if afFlags is not None else []; + self.fNotExist = fNotExist; # Hack for the ContentOnly scenario where the dir does not exist. + + def execute(self, oSubTstDrv): + reporter.log2('Deleting tree "%s" ...' % (limitString(self.sPath),)); + try: + oProgress = self.oGuestSession.directoryRemoveRecursive(self.sPath, self.afFlags); + except: + reporter.maybeErrXcpt(self.fRcExpect, 'Removing directory tree "%s" failed (afFlags=%s)' + % (self.sPath, self.afFlags)); + return not self.fRcExpect; + + oWrappedProgress = vboxwrappers.ProgressWrapper(oProgress, oSubTstDrv.oTstDrv.oVBoxMgr, oSubTstDrv.oTstDrv, + "remove-tree: %s" % (self.sPath,)); + oWrappedProgress.wait(); + if not oWrappedProgress.isSuccess(): + oWrappedProgress.logResult(fIgnoreErrors = not self.fRcExpect); + return not self.fRcExpect; + if not self.fRcExpect: + return reporter.error('Expected removing "%s" (tree) to failed, but it succeeded' % (self.sPath,)); + + if vboxcon.DirectoryRemoveRecFlag_ContentAndDir not in self.afFlags and not self.fNotExist: + # Cannot use directoryExists here as it is buggy. + try: + if oSubTstDrv.oTstDrv.fpApiVer >= 5.0: + oFsObjInfo = self.oGuestSession.fsObjQueryInfo(self.sPath, False); + else: + oFsObjInfo = self.oGuestSession.fileQueryInfo(self.sPath); + eType = oFsObjInfo.type; + except: + return reporter.errorXcpt('sPath=%s' % (self.sPath,)); + if eType != vboxcon.FsObjType_Directory: + return reporter.error('Found file type %d, expected directory (%d) for %s after rmtree/OnlyContent' + % (eType, vboxcon.FsObjType_Directory, self.sPath,)); + return True; + + return self.checkRemoved('tree'); + + +class tdTestFileStat(tdTestGuestCtrlBase): + """ + Test querying guest file information. + """ + def __init__(self, sFile = "", oCreds = None, cbSize = 0, eFileType = 0): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + self.cbSize = cbSize; + self.eFileType = eFileType; + +class tdTestFileIO(tdTestGuestCtrlBase): + """ + Test for the IGuestFile object. + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestFileQuerySize(tdTestGuestCtrlBase): + """ + Test for the file size query API call (fileQuerySize). + """ + def __init__(self, sFile = "", oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + +class tdTestFileOpen(tdTestGuestCtrlBase): + """ + Tests opening a guest files. + """ + def __init__(self, sFile = "", eAccessMode = None, eAction = None, eSharing = None, + fCreationMode = 0o660, oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sFile = sFile; + self.eAccessMode = eAccessMode if eAccessMode is not None else vboxcon.FileAccessMode_ReadOnly; + self.eAction = eAction if eAction is not None else vboxcon.FileOpenAction_OpenExisting; + self.eSharing = eSharing if eSharing is not None else vboxcon.FileSharingMode_All; + self.fCreationMode = fCreationMode; + self.afOpenFlags = []; + self.oOpenedFile = None; + + def toString(self): + """ Get a summary string. """ + return 'eAccessMode=%s eAction=%s sFile=%s' % (self.eAccessMode, self.eAction, self.sFile); + + def doOpenStep(self, fExpectSuccess): + """ + Does the open step, putting the resulting file in oOpenedFile. + """ + try: + self.oOpenedFile = self.oGuestSession.fileOpenEx(self.sFile, self.eAccessMode, self.eAction, + self.eSharing, self.fCreationMode, self.afOpenFlags); + except: + reporter.maybeErrXcpt(fExpectSuccess, 'fileOpenEx(%s, %s, %s, %s, %s, %s)' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + return False; + return True; + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + """ Overridden by children to do more testing. """ + _ = fExpectSuccess; _ = oSubTst; + return True; + + def doCloseStep(self): + """ Closes the file. """ + if self.oOpenedFile: + try: + self.oOpenedFile.close(); + except: + return reporter.errorXcpt('close([%s, %s, %s, %s, %s, %s])' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + self.oOpenedFile = None; + return True; + + def doSteps(self, fExpectSuccess, oSubTst): + """ Do the tests. """ + fRc = self.doOpenStep(fExpectSuccess); + if fRc is True: + fRc = self.doStepsOnOpenedFile(fExpectSuccess, oSubTst); + if self.oOpenedFile: + fRc = self.doCloseStep() and fRc; + return fRc; + + +class tdTestFileOpenCheckSize(tdTestFileOpen): + """ + Opens a file and checks the size. + """ + def __init__(self, sFile = "", eAccessMode = None, eAction = None, eSharing = None, + fCreationMode = 0o660, cbOpenExpected = 0, oCreds = None): + tdTestFileOpen.__init__(self, sFile, eAccessMode, eAction, eSharing, fCreationMode, oCreds); + self.cbOpenExpected = cbOpenExpected; + + def toString(self): + return 'cbOpenExpected=%s %s' % (self.cbOpenExpected, tdTestFileOpen.toString(self),); + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + # + # Call parent. + # + fRc = tdTestFileOpen.doStepsOnOpenedFile(self, fExpectSuccess, oSubTst); + + # + # Check the size. Requires 6.0 or later (E_NOTIMPL in 5.2). + # + if oSubTst.oTstDrv.fpApiVer >= 6.0: + try: + oFsObjInfo = self.oOpenedFile.queryInfo(); + except: + return reporter.errorXcpt('queryInfo([%s, %s, %s, %s, %s, %s])' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + if oFsObjInfo is None: + return reporter.error('IGuestFile::queryInfo returned None'); + try: + cbFile = oFsObjInfo.objectSize; + except: + return reporter.errorXcpt(); + if cbFile != self.cbOpenExpected: + return reporter.error('Wrong file size after open (%d): %s, expected %s (file %s) (#1)' + % (self.eAction, cbFile, self.cbOpenExpected, self.sFile)); + + try: + cbFile = self.oOpenedFile.querySize(); + except: + return reporter.errorXcpt('querySize([%s, %s, %s, %s, %s, %s])' + % (self.sFile, self.eAccessMode, self.eAction, self.eSharing, + self.fCreationMode, self.afOpenFlags,)); + if cbFile != self.cbOpenExpected: + return reporter.error('Wrong file size after open (%d): %s, expected %s (file %s) (#2)' + % (self.eAction, cbFile, self.cbOpenExpected, self.sFile)); + + return fRc; + + +class tdTestFileOpenAndWrite(tdTestFileOpen): + """ + Opens the file and writes one or more chunks to it. + + The chunks are a list of tuples(offset, bytes), where offset can be None + if no seeking should be performed. + """ + def __init__(self, sFile = "", eAccessMode = None, eAction = None, eSharing = None, # pylint: disable=too-many-arguments + fCreationMode = 0o660, atChunks = None, fUseAtApi = False, abContent = None, oCreds = None): + tdTestFileOpen.__init__(self, sFile, eAccessMode if eAccessMode is not None else vboxcon.FileAccessMode_WriteOnly, + eAction, eSharing, fCreationMode, oCreds); + assert atChunks is not None; + self.atChunks = atChunks # type: list(tuple(int,bytearray)) + self.fUseAtApi = fUseAtApi; + self.fAppend = ( eAccessMode in (vboxcon.FileAccessMode_AppendOnly, vboxcon.FileAccessMode_AppendRead) + or eAction == vboxcon.FileOpenAction_AppendOrCreate); + self.abContent = abContent # type: bytearray + + def toString(self): + sChunks = ', '.join('%s LB %s' % (tChunk[0], len(tChunk[1]),) for tChunk in self.atChunks); + sApi = 'writeAt' if self.fUseAtApi else 'write'; + return '%s [%s] %s' % (sApi, sChunks, tdTestFileOpen.toString(self),); + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + # + # Call parent. + # + fRc = tdTestFileOpen.doStepsOnOpenedFile(self, fExpectSuccess, oSubTst); + + # + # Do the writing. + # + for offFile, abBuf in self.atChunks: + if self.fUseAtApi: + # + # writeAt: + # + assert offFile is not None; + reporter.log2('writeAt(%s, %s bytes)' % (offFile, len(abBuf),)); + if self.fAppend: + if self.abContent is not None: # Try avoid seek as it updates the cached offset in GuestFileImpl. + offExpectAfter = len(self.abContent); + else: + try: + offSave = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + offExpectAfter = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_End); + self.oOpenedFile.seek(offSave, vboxcon.FileSeekOrigin_Begin); + except: + return reporter.errorXcpt(); + offExpectAfter += len(abBuf); + else: + offExpectAfter = offFile + len(abBuf); + + try: + cbWritten = self.oOpenedFile.writeAt(offFile, abBuf, 30*1000); + except: + return reporter.errorXcpt('writeAt(%s, %s bytes)' % (offFile, len(abBuf),)); + + else: + # + # write: + # + if self.fAppend: + if self.abContent is not None: # Try avoid seek as it updates the cached offset in GuestFileImpl. + offExpectAfter = len(self.abContent); + else: + try: + offSave = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + offExpectAfter = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_End); + self.oOpenedFile.seek(offSave, vboxcon.FileSeekOrigin_Begin); + except: + return reporter.errorXcpt('seek(0,End)'); + if offFile is not None: + try: + self.oOpenedFile.seek(offFile, vboxcon.FileSeekOrigin_Begin); + except: + return reporter.errorXcpt('seek(%s,Begin)' % (offFile,)); + else: + try: + offFile = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + return reporter.errorXcpt(); + if not self.fAppend: + offExpectAfter = offFile; + offExpectAfter += len(abBuf); + + reporter.log2('write(%s bytes @ %s)' % (len(abBuf), offFile,)); + try: + cbWritten = self.oOpenedFile.write(abBuf, 30*1000); + except: + return reporter.errorXcpt('write(%s bytes @ %s)' % (len(abBuf), offFile)); + + # + # Check how much was written, ASSUMING nothing we push thru here is too big: + # + if cbWritten != len(abBuf): + fRc = reporter.errorXcpt('Wrote less than expected: %s out of %s, expected all to be written' + % (cbWritten, len(abBuf),)); + if not self.fAppend: + offExpectAfter -= len(abBuf) - cbWritten; + + # + # Update the file content tracker if we've got one and can: + # + if self.abContent is not None: + if cbWritten < len(abBuf): + abBuf = abBuf[:cbWritten]; + + # + # In append mode, the current file offset shall be disregarded and the + # write always goes to the end of the file, regardless of writeAt or write. + # Note that RTFileWriteAt only naturally behaves this way on linux and + # (probably) windows, so VBoxService makes that behaviour generic across + # all OSes. + # + if self.fAppend: + reporter.log2('len(self.abContent)=%s + %s' % (len(self.abContent), cbWritten, )); + self.abContent.extend(abBuf); + else: + if offFile is None: + offFile = offExpectAfter - cbWritten; + reporter.log2('len(self.abContent)=%s + %s @ %s' % (len(self.abContent), cbWritten, offFile, )); + if offFile > len(self.abContent): + self.abContent.extend(bytearray(offFile - len(self.abContent))); + self.abContent[offFile:offFile + cbWritten] = abBuf; + reporter.log2('len(self.abContent)=%s' % (len(self.abContent),)); + + # + # Check the resulting file offset with IGuestFile::offset. + # + try: + offApi = self.oOpenedFile.offset; # Must be gotten first! + offSeek = self.oOpenedFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + fRc = reporter.errorXcpt(); + else: + reporter.log2('offApi=%s offSeek=%s offExpectAfter=%s' % (offApi, offSeek, offExpectAfter,)); + if offSeek != offExpectAfter: + fRc = reporter.error('Seek offset is %s, expected %s after %s bytes write @ %s (offApi=%s)' + % (offSeek, offExpectAfter, len(abBuf), offFile, offApi,)); + if offApi != offExpectAfter: + fRc = reporter.error('IGuestFile::offset is %s, expected %s after %s bytes write @ %s (offSeek=%s)' + % (offApi, offExpectAfter, len(abBuf), offFile, offSeek,)); + # for each chunk - end + return fRc; + + +class tdTestFileOpenAndCheckContent(tdTestFileOpen): + """ + Opens the file and checks the content using the read API. + """ + def __init__(self, sFile = "", eSharing = None, abContent = None, cbContentExpected = None, oCreds = None): + tdTestFileOpen.__init__(self, sFile = sFile, eSharing = eSharing, oCreds = oCreds); + self.abContent = abContent # type: bytearray + self.cbContentExpected = cbContentExpected; + + def toString(self): + return 'check content %s (%s) %s' % (len(self.abContent), self.cbContentExpected, tdTestFileOpen.toString(self),); + + def doStepsOnOpenedFile(self, fExpectSuccess, oSubTst): + # + # Call parent. + # + fRc = tdTestFileOpen.doStepsOnOpenedFile(self, fExpectSuccess, oSubTst); + + # + # Check the expected content size. + # + if self.cbContentExpected is not None: + if len(self.abContent) != self.cbContentExpected: + fRc = reporter.error('Incorrect abContent size: %s, expected %s' + % (len(self.abContent), self.cbContentExpected,)); + + # + # Read the file and compare it with the content. + # + offFile = 0; + while True: + try: + abChunk = self.oOpenedFile.read(512*1024, 30*1000); + except: + return reporter.errorXcpt('read(512KB) @ %s' % (offFile,)); + cbChunk = len(abChunk); + if cbChunk == 0: + if offFile != len(self.abContent): + fRc = reporter.error('Unexpected EOF @ %s, len(abContent)=%s' % (offFile, len(self.abContent),)); + break; + if offFile + cbChunk > len(self.abContent): + fRc = reporter.error('File is larger than expected: at least %s bytes, expected %s bytes' + % (offFile + cbChunk, len(self.abContent),)); + elif not utils.areBytesEqual(abChunk, self.abContent[offFile:(offFile + cbChunk)]): + fRc = reporter.error('Mismatch in range %s LB %s!' % (offFile, cbChunk,)); + offFile += cbChunk; + + return fRc; + + +class tdTestSession(tdTestGuestCtrlBase): + """ + Test the guest session handling. + """ + def __init__(self, sUser = None, sPassword = None, sDomain = None, sSessionName = ""): + tdTestGuestCtrlBase.__init__(self, oCreds = tdCtxCreds(sUser, sPassword, sDomain)); + self.sSessionName = sSessionName; + + def getSessionCount(self, oVBoxMgr): + """ + Helper for returning the number of currently + opened guest sessions of a VM. + """ + if self.oGuest is None: + return 0; + try: + aoSession = oVBoxMgr.getArray(self.oGuest, 'sessions') + except: + reporter.errorXcpt('sSessionName: %s' % (self.sSessionName,)); + return 0; + return len(aoSession); + + +class tdTestSessionEx(tdTestGuestCtrlBase): + """ + Test the guest session. + """ + def __init__(self, aoSteps = None, enmUser = None): + tdTestGuestCtrlBase.__init__(self); + assert enmUser is None; # For later. + self.enmUser = enmUser; + self.aoSteps = aoSteps if aoSteps is not None else []; + + def execute(self, oTstDrv, oVmSession, oTxsSession, oTestVm, sMsgPrefix): + """ + Executes the test. + """ + # + # Create a session. + # + assert self.enmUser is None; # For later. + self.oCreds = tdCtxCreds(); + fRc = self.setEnvironment(oVmSession, oTxsSession, oTestVm); + if not fRc: + return False; + reporter.log2('%s: %s steps' % (sMsgPrefix, len(self.aoSteps),)); + fRc, oCurSession = self.createSession(sMsgPrefix); + if fRc is True: + # + # Execute the tests. + # + try: + fRc = self.executeSteps(oTstDrv, oCurSession, sMsgPrefix); + except: + fRc = reporter.errorXcpt('%s: Unexpected exception executing test steps' % (sMsgPrefix,)); + + # + # Close the session. + # + fRc2 = self.closeSession(); + if fRc2 is False: + fRc = reporter.error('%s: Session could not be closed' % (sMsgPrefix,)); + else: + fRc = reporter.error('%s: Session creation failed' % (sMsgPrefix,)); + return fRc; + + def executeSteps(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes just the steps. + Returns True on success, False on test failure. + """ + fRc = True; + for (i, oStep) in enumerate(self.aoSteps): + fRc2 = oStep.execute(oTstDrv, oGstCtrlSession, sMsgPrefix + ', step #%d' % i); + if fRc2 is True: + pass; + elif fRc2 is None: + reporter.log('%s: skipping remaining %d steps' % (sMsgPrefix, len(self.aoSteps) - i - 1,)); + break; + else: + fRc = False; + return fRc; + + @staticmethod + def executeListTestSessions(aoTests, oTstDrv, oVmSession, oTxsSession, oTestVm, sMsgPrefix): + """ + Works thru a list of tdTestSessionEx object. + """ + fRc = True; + for (i, oCurTest) in enumerate(aoTests): + try: + fRc2 = oCurTest.execute(oTstDrv, oVmSession, oTxsSession, oTestVm, '%s / %#d' % (sMsgPrefix, i,)); + if fRc2 is not True: + fRc = False; + except: + fRc = reporter.errorXcpt('%s: Unexpected exception executing test #%d' % (sMsgPrefix, i ,)); + + return (fRc, oTxsSession); + + +class tdSessionStepBase(object): + """ + Base class for the guest control session test steps. + """ + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the test step. + + Returns True on success. + Returns False on failure (must be reported as error). + Returns None if to skip the remaining steps. + """ + _ = oTstDrv; + _ = oGstCtrlSession; + return reporter.error('%s: Missing execute implementation: %s' % (sMsgPrefix, self,)); + + +class tdStepRequireMinimumApiVer(tdSessionStepBase): + """ + Special test step which will cause executeSteps to skip the remaining step + if the VBox API is too old: + """ + def __init__(self, fpMinApiVer): + self.fpMinApiVer = fpMinApiVer; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ Returns None if API version is too old, otherwise True. """ + if oTstDrv.fpApiVer >= self.fpMinApiVer: + return True; + _ = oGstCtrlSession; + _ = sMsgPrefix; + return None; # Special return value. Don't use elsewhere. + + +# +# Scheduling Environment Changes with the Guest Control Session. +# + +class tdStepSessionSetEnv(tdSessionStepBase): + """ + Guest session environment: schedule putenv + """ + def __init__(self, sVar, sValue, hrcExpected = 0): + self.sVar = sVar; + self.sValue = sValue; + self.hrcExpected = hrcExpected; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionSetEnv: sVar=%s sValue=%s hrcExpected=%#x' % (self.sVar, self.sValue, self.hrcExpected,)); + try: + if oTstDrv.fpApiVer >= 5.0: + oGstCtrlSession.environmentScheduleSet(self.sVar, self.sValue); + else: + oGstCtrlSession.environmentSet(self.sVar, self.sValue); + except vbox.ComException as oXcpt: + # Is this an expected failure? + if vbox.ComError.equal(oXcpt, self.hrcExpected): + return True; + return reporter.errorXcpt('%s: Expected hrc=%#x (%s) got %#x (%s) instead (setenv %s=%s)' + % (sMsgPrefix, self.hrcExpected, vbox.ComError.toString(self.hrcExpected), + vbox.ComError.getXcptResult(oXcpt), + vbox.ComError.toString(vbox.ComError.getXcptResult(oXcpt)), + self.sVar, self.sValue,)); + except: + return reporter.errorXcpt('%s: Unexpected exception in tdStepSessionSetEnv::execute (%s=%s)' + % (sMsgPrefix, self.sVar, self.sValue,)); + + # Should we succeed? + if self.hrcExpected != 0: + return reporter.error('%s: Expected hrcExpected=%#x, got S_OK (putenv %s=%s)' + % (sMsgPrefix, self.hrcExpected, self.sVar, self.sValue,)); + return True; + +class tdStepSessionUnsetEnv(tdSessionStepBase): + """ + Guest session environment: schedule unset. + """ + def __init__(self, sVar, hrcExpected = 0): + self.sVar = sVar; + self.hrcExpected = hrcExpected; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionUnsetEnv: sVar=%s hrcExpected=%#x' % (self.sVar, self.hrcExpected,)); + try: + if oTstDrv.fpApiVer >= 5.0: + oGstCtrlSession.environmentScheduleUnset(self.sVar); + else: + oGstCtrlSession.environmentUnset(self.sVar); + except vbox.ComException as oXcpt: + # Is this an expected failure? + if vbox.ComError.equal(oXcpt, self.hrcExpected): + return True; + return reporter.errorXcpt('%s: Expected hrc=%#x (%s) got %#x (%s) instead (unsetenv %s)' + % (sMsgPrefix, self.hrcExpected, vbox.ComError.toString(self.hrcExpected), + vbox.ComError.getXcptResult(oXcpt), + vbox.ComError.toString(vbox.ComError.getXcptResult(oXcpt)), + self.sVar,)); + except: + return reporter.errorXcpt('%s: Unexpected exception in tdStepSessionUnsetEnv::execute (%s)' + % (sMsgPrefix, self.sVar,)); + + # Should we succeed? + if self.hrcExpected != 0: + return reporter.error('%s: Expected hrcExpected=%#x, got S_OK (unsetenv %s)' + % (sMsgPrefix, self.hrcExpected, self.sVar,)); + return True; + +class tdStepSessionBulkEnv(tdSessionStepBase): + """ + Guest session environment: Bulk environment changes. + """ + def __init__(self, asEnv = None, hrcExpected = 0): + self.asEnv = asEnv if asEnv is not None else []; + self.hrcExpected = hrcExpected; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionBulkEnv: asEnv=%s hrcExpected=%#x' % (self.asEnv, self.hrcExpected,)); + try: + if oTstDrv.fpApiVer >= 5.0: + oTstDrv.oVBoxMgr.setArray(oGstCtrlSession, 'environmentChanges', self.asEnv); + else: + oTstDrv.oVBoxMgr.setArray(oGstCtrlSession, 'environment', self.asEnv); + except vbox.ComException as oXcpt: + # Is this an expected failure? + if vbox.ComError.equal(oXcpt, self.hrcExpected): + return True; + return reporter.errorXcpt('%s: Expected hrc=%#x (%s) got %#x (%s) instead (asEnv=%s)' + % (sMsgPrefix, self.hrcExpected, vbox.ComError.toString(self.hrcExpected), + vbox.ComError.getXcptResult(oXcpt), + vbox.ComError.toString(vbox.ComError.getXcptResult(oXcpt)), + self.asEnv,)); + except: + return reporter.errorXcpt('%s: Unexpected exception writing the environmentChanges property (asEnv=%s).' + % (sMsgPrefix, self.asEnv)); + return True; + +class tdStepSessionClearEnv(tdStepSessionBulkEnv): + """ + Guest session environment: clears the scheduled environment changes. + """ + def __init__(self): + tdStepSessionBulkEnv.__init__(self); + + +class tdStepSessionCheckEnv(tdSessionStepBase): + """ + Check the currently scheduled environment changes of a guest control session. + """ + def __init__(self, asEnv = None): + self.asEnv = asEnv if asEnv is not None else []; + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Executes the step. + Returns True on success, False on test failure. + """ + reporter.log2('tdStepSessionCheckEnv: asEnv=%s' % (self.asEnv,)); + + # + # Get the environment change list. + # + try: + if oTstDrv.fpApiVer >= 5.0: + asCurEnv = oTstDrv.oVBoxMgr.getArray(oGstCtrlSession, 'environmentChanges'); + else: + asCurEnv = oTstDrv.oVBoxMgr.getArray(oGstCtrlSession, 'environment'); + except: + return reporter.errorXcpt('%s: Unexpected exception reading the environmentChanges property.' % (sMsgPrefix,)); + + # + # Compare it with the expected one by trying to remove each expected value + # and the list anything unexpected. + # + fRc = True; + asCopy = list(asCurEnv); # just in case asCurEnv is immutable + for sExpected in self.asEnv: + try: + asCopy.remove(sExpected); + except: + fRc = reporter.error('%s: Expected "%s" to be in the resulting environment' % (sMsgPrefix, sExpected,)); + for sUnexpected in asCopy: + fRc = reporter.error('%s: Unexpected "%s" in the resulting environment' % (sMsgPrefix, sUnexpected,)); + + if fRc is not True: + reporter.log2('%s: Current environment: %s' % (sMsgPrefix, asCurEnv)); + return fRc; + + +# +# File system object statistics (i.e. stat()). +# + +class tdStepStat(tdSessionStepBase): + """ + Stats a file system object. + """ + def __init__(self, sPath, hrcExpected = 0, fFound = True, fFollowLinks = True, enmType = None, oTestFsObj = None): + self.sPath = sPath; + self.hrcExpected = hrcExpected; + self.fFound = fFound; + self.fFollowLinks = fFollowLinks; + self.enmType = enmType if enmType is not None else vboxcon.FsObjType_File; + self.cbExactSize = None; + self.cbMinSize = None; + self.oTestFsObj = oTestFsObj # type: testfileset.TestFsObj + + def execute(self, oTstDrv, oGstCtrlSession, sMsgPrefix): + """ + Execute the test step. + """ + reporter.log2('tdStepStat: sPath=%s enmType=%s hrcExpected=%s fFound=%s fFollowLinks=%s' + % (limitString(self.sPath), self.enmType, self.hrcExpected, self.fFound, self.fFollowLinks,)); + + # Don't execute non-file tests on older VBox version. + if oTstDrv.fpApiVer >= 5.0 or self.enmType == vboxcon.FsObjType_File or not self.fFound: + # + # Call the API. + # + try: + if oTstDrv.fpApiVer >= 5.0: + oFsInfo = oGstCtrlSession.fsObjQueryInfo(self.sPath, self.fFollowLinks); + else: + oFsInfo = oGstCtrlSession.fileQueryInfo(self.sPath); + except vbox.ComException as oXcpt: + ## @todo: The error reporting in the API just plain sucks! Most of the errors are + ## VBOX_E_IPRT_ERROR and there seems to be no way to distinguish between + ## non-existing files/path and a lot of other errors. Fix API and test! + if not self.fFound: + return True; + if vbox.ComError.equal(oXcpt, self.hrcExpected): # Is this an expected failure? + return True; + return reporter.errorXcpt('%s: Unexpected exception for exiting path "%s" (enmType=%s, hrcExpected=%s):' + % (sMsgPrefix, self.sPath, self.enmType, self.hrcExpected,)); + except: + return reporter.errorXcpt('%s: Unexpected exception in tdStepStat::execute (%s)' + % (sMsgPrefix, self.sPath,)); + if oFsInfo is None: + return reporter.error('%s: "%s" got None instead of IFsObjInfo instance!' % (sMsgPrefix, self.sPath,)); + + # + # Check type expectations. + # + try: + enmType = oFsInfo.type; + except: + return reporter.errorXcpt('%s: Unexpected exception in reading "IFsObjInfo::type"' % (sMsgPrefix,)); + if enmType != self.enmType: + return reporter.error('%s: "%s" has type %s, expected %s' + % (sMsgPrefix, self.sPath, enmType, self.enmType)); + + # + # Check size expectations. + # Note! This is unicode string here on windows, for some reason. + # long long mapping perhaps? + # + try: + cbObject = long(oFsInfo.objectSize); + except: + return reporter.errorXcpt('%s: Unexpected exception in reading "IFsObjInfo::objectSize"' + % (sMsgPrefix,)); + if self.cbExactSize is not None \ + and cbObject != self.cbExactSize: + return reporter.error('%s: "%s" has size %s bytes, expected %s bytes' + % (sMsgPrefix, self.sPath, cbObject, self.cbExactSize)); + if self.cbMinSize is not None \ + and cbObject < self.cbMinSize: + return reporter.error('%s: "%s" has size %s bytes, expected as least %s bytes' + % (sMsgPrefix, self.sPath, cbObject, self.cbMinSize)); + return True; + +class tdStepStatDir(tdStepStat): + """ Checks for an existing directory. """ + def __init__(self, sDirPath, oTestDir = None): + tdStepStat.__init__(self, sPath = sDirPath, enmType = vboxcon.FsObjType_Directory, oTestFsObj = oTestDir); + +class tdStepStatDirEx(tdStepStatDir): + """ Checks for an existing directory given a TestDir object. """ + def __init__(self, oTestDir): # type: (testfileset.TestDir) + tdStepStatDir.__init__(self, oTestDir.sPath, oTestDir); + +class tdStepStatFile(tdStepStat): + """ Checks for an existing file """ + def __init__(self, sFilePath = None, oTestFile = None): + tdStepStat.__init__(self, sPath = sFilePath, enmType = vboxcon.FsObjType_File, oTestFsObj = oTestFile); + +class tdStepStatFileEx(tdStepStatFile): + """ Checks for an existing file given a TestFile object. """ + def __init__(self, oTestFile): # type: (testfileset.TestFile) + tdStepStatFile.__init__(self, oTestFile.sPath, oTestFile); + +class tdStepStatFileSize(tdStepStat): + """ Checks for an existing file of a given expected size.. """ + def __init__(self, sFilePath, cbExactSize = 0): + tdStepStat.__init__(self, sPath = sFilePath, enmType = vboxcon.FsObjType_File); + self.cbExactSize = cbExactSize; + +class tdStepStatFileNotFound(tdStepStat): + """ Checks for an existing directory. """ + def __init__(self, sPath): + tdStepStat.__init__(self, sPath = sPath, fFound = False); + +class tdStepStatPathNotFound(tdStepStat): + """ Checks for an existing directory. """ + def __init__(self, sPath): + tdStepStat.__init__(self, sPath = sPath, fFound = False); + + +# +# +# + +class tdTestSessionFileRefs(tdTestGuestCtrlBase): + """ + Tests session file (IGuestFile) reference counting. + """ + def __init__(self, cRefs = 0): + tdTestGuestCtrlBase.__init__(self); + self.cRefs = cRefs; + +class tdTestSessionDirRefs(tdTestGuestCtrlBase): + """ + Tests session directory (IGuestDirectory) reference counting. + """ + def __init__(self, cRefs = 0): + tdTestGuestCtrlBase.__init__(self); + self.cRefs = cRefs; + +class tdTestSessionProcRefs(tdTestGuestCtrlBase): + """ + Tests session process (IGuestProcess) reference counting. + """ + def __init__(self, cRefs = 0): + tdTestGuestCtrlBase.__init__(self); + self.cRefs = cRefs; + +class tdTestUpdateAdditions(tdTestGuestCtrlBase): + """ + Test updating the Guest Additions inside the guest. + """ + def __init__(self, sSrc = "", asArgs = None, afFlags = None, oCreds = None): + tdTestGuestCtrlBase.__init__(self, oCreds = oCreds); + self.sSrc = sSrc; + self.asArgs = asArgs; + self.afFlags = afFlags; + +class tdTestResult(object): + """ + Base class for test results. + """ + def __init__(self, fRc = False): + ## The overall test result. + self.fRc = fRc; + +class tdTestResultFailure(tdTestResult): + """ + Base class for test results. + """ + def __init__(self): + tdTestResult.__init__(self, fRc = False); + +class tdTestResultSuccess(tdTestResult): + """ + Base class for test results. + """ + def __init__(self): + tdTestResult.__init__(self, fRc = True); + +class tdTestResultDirRead(tdTestResult): + """ + Test result for reading guest directories. + """ + def __init__(self, fRc = False, cFiles = 0, cDirs = 0, cOthers = None): + tdTestResult.__init__(self, fRc = fRc); + self.cFiles = cFiles; + self.cDirs = cDirs; + self.cOthers = cOthers; + +class tdTestResultExec(tdTestResult): + """ + Holds a guest process execution test result, + including the exit code, status + afFlags. + """ + def __init__(self, fRc = False, uExitStatus = 500, iExitCode = 0, sBuf = None, cbBuf = 0, cbStdOut = None, cbStdErr = None): + tdTestResult.__init__(self); + ## The overall test result. + self.fRc = fRc; + ## Process exit stuff. + self.uExitStatus = uExitStatus; + self.iExitCode = iExitCode; + ## Desired buffer length returned back from stdout/stderr. + self.cbBuf = cbBuf; + ## Desired buffer result from stdout/stderr. Use with caution! + self.sBuf = sBuf; + self.cbStdOut = cbStdOut; + self.cbStdErr = cbStdErr; + +class tdTestResultFileStat(tdTestResult): + """ + Test result for stat'ing guest files. + """ + def __init__(self, fRc = False, + cbSize = 0, eFileType = 0): + tdTestResult.__init__(self, fRc = fRc); + self.cbSize = cbSize; + self.eFileType = eFileType; + ## @todo Add more information. + +class tdTestResultFileReadWrite(tdTestResult): + """ + Test result for reading + writing guest directories. + """ + def __init__(self, fRc = False, + cbProcessed = 0, offFile = 0, abBuf = None): + tdTestResult.__init__(self, fRc = fRc); + self.cbProcessed = cbProcessed; + self.offFile = offFile; + self.abBuf = abBuf; + +class tdTestResultSession(tdTestResult): + """ + Test result for guest session counts. + """ + def __init__(self, fRc = False, cNumSessions = 0): + tdTestResult.__init__(self, fRc = fRc); + self.cNumSessions = cNumSessions; + +class tdDebugSettings(object): + """ + Contains local test debug settings. + """ + def __init__(self, sVBoxServiceExeHst = None): + self.sVBoxServiceExeHst = sVBoxServiceExeHst; + self.sGstVBoxServiceLogPath = ''; + +class SubTstDrvAddGuestCtrl(base.SubTestDriverBase): + """ + Sub-test driver for executing guest control (VBoxService, IGuest) tests. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'add-guest-ctrl', 'Guest Control'); + + ## @todo base.TestBase. + self.asTestsDef = [ + 'debug', + 'session_basic', 'session_env', 'session_file_ref', 'session_dir_ref', 'session_proc_ref', 'session_reboot', + 'exec_basic', 'exec_timeout', + 'dir_create', 'dir_create_temp', 'dir_read', + 'file_open', 'file_remove', 'file_stat', 'file_read', 'file_write', + 'copy_to', 'copy_from', + 'update_additions' + ]; + self.asTests = self.asTestsDef; + self.fSkipKnownBugs = False; + self.oTestFiles = None # type: vboxtestfileset.TestFileSet + self.oDebug = tdDebugSettings(); + self.sPathVBoxServiceExeGst = ''; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--add-guest-ctrl-tests': + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + if asArgs[iArg] == 'all': # Nice for debugging scripts. + self.asTests = self.asTestsDef; + else: + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--add-guest-ctrl-tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef))); + return iNext; + if asArgs[iArg] == '--add-guest-ctrl-skip-known-bugs': + self.fSkipKnownBugs = True; + return iArg + 1; + if asArgs[iArg] == '--no-add-guest-ctrl-skip-known-bugs': + self.fSkipKnownBugs = False; + return iArg + 1; + if asArgs[iArg] == '--add-guest-ctrl-debug-img': + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + self.oDebug.sVBoxServiceExeHst = asArgs[iArg]; + return iNext; + return iArg; + + def showUsage(self): + base.SubTestDriverBase.showUsage(self); + reporter.log(' --add-guest-ctrl-tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --add-guest-ctrl-skip-known-bugs'); + reporter.log(' Skips known bugs. Default: --no-add-guest-ctrl-skip-known-bugs'); + reporter.log('Debugging:'); + reporter.log(' --add-guest-ctrl-debug-img'); + reporter.log(' Sets VBoxService image to deploy for debugging'); + return True; + + def testIt(self, oTestVm, oSession, oTxsSession): + """ + Executes the test. + + Returns fRc, oTxsSession. The latter may have changed. + """ + + self.sPathVBoxServiceExeGst = oTestVm.pathJoin(self.oTstDrv.getGuestSystemAdminDir(oTestVm), 'VBoxService') \ + + base.exeSuff(); + + reporter.log("Active tests: %s" % (self.asTests,)); + + # The tests. Must-succeed tests should be first. + atTests = [ + ( True, self.prepareGuestForDebugging, None, 'Manual Debugging',), + ( True, self.prepareGuestForTesting, None, 'Preparations',), + ( True, self.testGuestCtrlSession, 'session_basic', 'Session Basics',), + ( True, self.testGuestCtrlExec, 'exec_basic', 'Execution',), + ( False, self.testGuestCtrlExecTimeout, 'exec_timeout', 'Execution Timeouts',), + ( False, self.testGuestCtrlSessionEnvironment, 'session_env', 'Session Environment',), + ( False, self.testGuestCtrlSessionFileRefs, 'session_file_ref', 'Session File References',), + #( False, self.testGuestCtrlSessionDirRefs, 'session_dir_ref', 'Session Directory References',), + ( False, self.testGuestCtrlSessionProcRefs, 'session_proc_ref', 'Session Process References',), + ( False, self.testGuestCtrlDirCreate, 'dir_create', 'Creating directories',), + ( False, self.testGuestCtrlDirCreateTemp, 'dir_create_temp', 'Creating temporary directories',), + ( False, self.testGuestCtrlDirRead, 'dir_read', 'Reading directories',), + ( False, self.testGuestCtrlCopyTo, 'copy_to', 'Copy to guest',), + ( False, self.testGuestCtrlCopyFrom, 'copy_from', 'Copy from guest',), + ( False, self.testGuestCtrlFileStat, 'file_stat', 'Querying file information (stat)',), + ( False, self.testGuestCtrlFileOpen, 'file_open', 'File open',), + ( False, self.testGuestCtrlFileRead, 'file_read', 'File read',), + ( False, self.testGuestCtrlFileWrite, 'file_write', 'File write',), + ( False, self.testGuestCtrlFileRemove, 'file_remove', 'Removing files',), # Destroys prepped files. + ( False, self.testGuestCtrlUpdateAdditions, 'update_additions', 'Updating Guest Additions',), + ]; + + if not self.fSkipKnownBugs: + atTests.extend([ + ## @todo Seems to (mainly?) fail on Linux guests, primarily running with systemd as service supervisor. + # Needs to be investigated and fixed. + ( False, self.testGuestCtrlSessionReboot, 'session_reboot', 'Session w/ Guest Reboot',), # May zap /tmp. + ]); + + fRc = True; + for fMustSucceed, fnHandler, sShortNm, sTestNm in atTests: + + # If for whatever reason the VM object became invalid, bail out. + if not oTestVm: + reporter.error('Test VM object invalid (VBoxSVC or client process crashed?), aborting tests'); + fRc = False; + break; + + reporter.testStart(sTestNm); + if sShortNm is None or sShortNm in self.asTests: + # Returns (fRc, oTxsSession, oSession) - but only the first one is mandatory. + aoResult = fnHandler(oSession, oTxsSession, oTestVm); + if aoResult is None or isinstance(aoResult, bool): + fRcTest = aoResult; + else: + fRcTest = aoResult[0]; + if len(aoResult) > 1: + oTxsSession = aoResult[1]; + if len(aoResult) > 2: + oSession = aoResult[2]; + assert len(aoResult) == 3; + else: + fRcTest = None; + + if fRcTest is False and reporter.testErrorCount() == 0: + fRcTest = reporter.error('Buggy test! Returned False w/o logging the error!'); + if reporter.testDone(fRcTest is None)[1] != 0: + fRcTest = False; + fRc = False; + + # Stop execution if this is a must-succeed test and it failed. + if fRcTest is False and fMustSucceed is True: + reporter.log('Skipping any remaining tests since the previous one failed.'); + break; + + # Upload VBoxService logs on failure. + if reporter.testErrorCount() > 0 \ + and self.oDebug.sGstVBoxServiceLogPath: + sVBoxServiceLogsTarGz = 'ga-vboxservice-logs-%s.tar.gz' % oTestVm.sVmName; + sGstVBoxServiceLogsTarGz = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), sVBoxServiceLogsTarGz); + if self.oTstDrv.txsPackFile(oSession, oTxsSession, \ + sGstVBoxServiceLogsTarGz, self.oDebug.sGstVBoxServiceLogPath, fIgnoreErrors = True): + self.oTstDrv.txsDownloadFiles(oSession, oTxsSession, [ (sGstVBoxServiceLogsTarGz, sVBoxServiceLogsTarGz) ], \ + fIgnoreErrors = True); + + return (fRc, oTxsSession); + + def prepareGuestForDebugging(self, oSession, oTxsSession, oTestVm): # pylint: disable=unused-argument + """ + Prepares a guest for (manual) debugging. + + This involves copying over and invoking a the locally built VBoxService binary. + """ + + if self.oDebug.sVBoxServiceExeHst is None: # If no debugging enabled, bail out. + reporter.log('Skipping debugging'); + return True; + + reporter.log('Preparing for debugging ...'); + + try: + self.vboxServiceControl(oTxsSession, oTestVm, fStart = False); + + self.oTstDrv.sleep(5); # Fudge factor -- wait until the service stopped. + + reporter.log('Uploading "%s" to "%s" ...' % (self.oDebug.sVBoxServiceExeHst, self.sPathVBoxServiceExeGst)); + oTxsSession.syncUploadFile(self.oDebug.sVBoxServiceExeHst, self.sPathVBoxServiceExeGst); + + if oTestVm.isLinux(): + oTxsSession.syncChMod(self.sPathVBoxServiceExeGst, 0o755); + + self.vboxServiceControl(oTxsSession, oTestVm, fStart = True); + + self.oTstDrv.sleep(5); # Fudge factor -- wait until the service is ready. + + except: + return reporter.errorXcpt('Unable to prepare for debugging'); + + return True; + + # + # VBoxService handling. + # + def vboxServiceControl(self, oTxsSession, oTestVm, fStart): + """ + Controls VBoxService on the guest by starting or stopping the service. + Returns success indicator. + """ + + fRc = True; + + if oTestVm.isWindows(): + sPathSC = os.path.join(self.oTstDrv.getGuestSystemDir(oTestVm), 'sc.exe'); + if fStart is True: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Starting VBoxService', 30 * 1000, \ + sPathSC, (sPathSC, 'start', 'VBoxService')); + else: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Stopping VBoxService', 30 * 1000, \ + sPathSC, (sPathSC, 'stop', 'VBoxService')); + elif oTestVm.isLinux(): + sPathService = "/sbin/rcvboxadd-service"; + if fStart is True: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Starting VBoxService', 30 * 1000, \ + sPathService, (sPathService, 'start')); + else: + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Stopping VBoxService', 30 * 1000, \ + sPathService, (sPathService, 'stop')); + else: + reporter.log('Controlling VBoxService not supported for this guest yet'); + + return fRc; + + def waitForGuestFacility(self, oSession, eFacilityType, sDesc, + eFacilityStatus, cMsTimeout = 30 * 1000): + """ + Waits for a guest facility to enter a certain status. + By default the "Active" status is being used. + + Returns success status. + """ + + reporter.log('Waiting for Guest Additions facility "%s" to change to status %s (%dms timeout)...' + % (sDesc, str(eFacilityStatus), cMsTimeout)); + + fRc = False; + + eStatusOld = None; + tsStart = base.timestampMilli(); + while base.timestampMilli() - tsStart < cMsTimeout: + try: + eStatus, _ = oSession.o.console.guest.getFacilityStatus(eFacilityType); + reporter.log('Current status is %s' % (str(eStatus))); + if eStatusOld is None: + eStatusOld = eStatus; + except: + reporter.errorXcpt('Getting facility status failed'); + break; + if eStatus != eStatusOld: + reporter.log('Status changed to %s' % (str(eStatus))); + eStatusOld = eStatus; + if eStatus == eFacilityStatus: + fRc = True; + break; + self.oTstDrv.sleep(5); # Do some busy waiting. + + if not fRc: + reporter.error('Waiting for Guest Additions facility "%s" timed out' % (sDesc)); + else: + reporter.log('Guest Additions facility "%s" reached requested status %s after %dms' + % (sDesc, str(eFacilityStatus), base.timestampMilli() - tsStart)); + + return fRc; + + # + # Guest test files. + # + + def prepareGuestForTesting(self, oSession, oTxsSession, oTestVm): + """ + Prepares the VM for testing, uploading a bunch of files and stuff via TXS. + Returns success indicator. + """ + _ = oSession; + + # + # Make sure the temporary directory exists. + # + for sDir in [self.oTstDrv.getGuestTempDir(oTestVm), ]: + if oTxsSession.syncMkDirPath(sDir, 0o777) is not True: + return reporter.error('Failed to create directory "%s"!' % (sDir,)); + + # Query the TestExecService (TXS) version first to find out on what we run. + fGotTxsVer = self.oTstDrv.txsVer(oSession, oTxsSession, 30 * 100, fIgnoreErrors = True); + + # Whether to enable verbose logging for VBoxService. + fEnableVerboseLogging = False; + + # On Windows guests we always can enable verbose logging. + if oTestVm.isWindows(): + fEnableVerboseLogging = True; + + # Old TxS versions had a bug which caused an infinite loop when executing stuff containing "$xxx", + # so check if we got the version here first and skip enabling verbose logging nonetheless if needed. + if not fGotTxsVer: + reporter.log('Too old TxS service running') + fEnableVerboseLogging = False; + + # + # Enable VBoxService verbose logging. + # + reporter.log('Enabling verbose VBoxService logging: %s' % (fEnableVerboseLogging)); + if fEnableVerboseLogging: + self.oDebug.sGstVBoxServiceLogPath = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), "VBoxService"); + if oTxsSession.syncMkDirPath(self.oDebug.sGstVBoxServiceLogPath, 0o777) is not True: + return reporter.error('Failed to create directory "%s"!' % (self.oDebug.sGstVBoxServiceLogPath,)); + sPathLogFile = oTestVm.pathJoin(self.oDebug.sGstVBoxServiceLogPath, 'VBoxService.log'); + + reporter.log('VBoxService logs will be stored in "%s"' % (self.oDebug.sGstVBoxServiceLogPath,)); + + fRestartVBoxService = False; + if oTestVm.isWindows(): + sPathRegExe = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'reg.exe'); + sImagePath = '%s -vvvv --logfile %s' % (self.sPathVBoxServiceExeGst, sPathLogFile); + fRestartVBoxService = self.oTstDrv.txsRunTest(oTxsSession, 'Enabling VBoxService verbose logging (via registry)', + 30 * 1000, + sPathRegExe, + (sPathRegExe, 'add', + 'HKLM\\SYSTEM\\CurrentControlSet\\Services\\VBoxService', + '/v', 'ImagePath', '/t', 'REG_SZ', '/d', sImagePath, '/f')); + elif oTestVm.isLinux(): + sPathSed = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'sed'); + fRestartVBoxService = self.oTstDrv.txsRunTest(oTxsSession, 'Enabling VBoxService verbose logging', 30 * 1000, + sPathSed, + (sPathSed, '-i', '-e', 's/' + '\\$2 \\$3' + '/' + '\\$2 \\$3 -vvvv --logfile \\/var\\/tmp\\/VBoxService\\/VBoxService.log' + '/g', + '/sbin/rcvboxadd-service')); + else: + reporter.log('Verbose logging for VBoxService not supported for this guest yet'); + + if fRestartVBoxService: + self.vboxServiceControl(oTxsSession, oTestVm, fStart = False); + self.oTstDrv.sleep(5); + self.vboxServiceControl(oTxsSession, oTestVm, fStart = True); + else: + reporter.testStart('Waiting for VBoxService to get started'); + fRc = self.waitForGuestFacility(oSession, vboxcon.AdditionsFacilityType_VBoxService, "VBoxService", + vboxcon.AdditionsFacilityStatus_Active); + reporter.testDone(); + if not fRc: + return False; + + # + # Generate and upload some random files and dirs to the guest. + # Note! Make sure we don't run into too-long-path issues when using + # the test files on the host if. + # + cchGst = len(self.oTstDrv.getGuestTempDir(oTestVm)) + 1 + len('addgst-1') + 1; + cchHst = len(self.oTstDrv.sScratchPath) + 1 + len('copyto/addgst-1') + 1; + cchMaxPath = 230; + if cchHst > cchGst: + cchMaxPath -= cchHst - cchGst; + reporter.log('cchMaxPath=%s (cchHst=%s, cchGst=%s)' % (cchMaxPath, cchHst, cchGst,)); + self.oTestFiles = vboxtestfileset.TestFileSet(oTestVm, + self.oTstDrv.getGuestTempDir(oTestVm), 'addgst-1', + # Make sure that we use a lowest common denominator across all supported + # platforms, to make testing the randomly generated file paths work + # reliably. + cchMaxPath = cchMaxPath, asCompatibleWith = [ ('cross') ]); + return self.oTestFiles.upload(oTxsSession, self.oTstDrv); + + + # + # gctrlXxxx stuff. + # + + def gctrlCopyFileFrom(self, oGuestSession, oTest, fExpected): + """ + Helper function to copy a single file from the guest to the host. + """ + + # As we pass-in randomly generated file names, the source sometimes can be empty, which + # in turn will result in a (correct) error by the API. Simply skip this function then. + if not oTest.sSrc: + reporter.log2('Skipping guest file "%s"' % (limitString(oTest.sSrc))); + return fExpected; + + # + # Do the copying. + # + reporter.log2('Copying guest file "%s" to host "%s"' % (limitString(oTest.sSrc), limitString(oTest.sDst))); + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyFromGuest(oTest.sSrc, oTest.sDst, oTest.afFlags); + else: + oCurProgress = oGuestSession.copyFrom(oTest.sSrc, oTest.sDst, oTest.afFlags); + except: + reporter.maybeErrXcpt(fExpected, 'Copy from exception for sSrc="%s", sDst="%s":' % (oTest.sSrc, oTest.sDst,)); + return False; + if oCurProgress is None: + return reporter.error('No progress object returned'); + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlFileCopyFrom"); + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fExpected); + return False; + + # + # Check the result if we can. + # + if oTest.oSrc: + assert isinstance(oTest.oSrc, testfileset.TestFile); + sDst = oTest.sDst; + if os.path.isdir(sDst): + sDst = os.path.join(sDst, oTest.oSrc.sName); + try: + oFile = open(sDst, 'rb'); # pylint: disable=consider-using-with + except: + # Don't report expected non-existing paths / files as an error. + return reporter.maybeErrXcpt(fExpected, 'open(%s) failed during verfication (file / path not found)' % (sDst,)); + fEqual = oTest.oSrc.equalFile(oFile); + oFile.close(); + if not fEqual: + return reporter.error('Content differs for "%s"' % (sDst,)); + + return True; + + def __compareTestDir(self, oDir, sHostPath): # type: (testfileset.TestDir, str) -> bool + """ + Recursively compare the content of oDir and sHostPath. + + Returns True on success, False + error logging on failure. + + Note! This ASSUMES that nothing else was copied to sHostPath! + """ + # + # First check out all the entries and files in the directory. + # + dLeftUpper = dict(oDir.dChildrenUpper); + try: + asEntries = os.listdir(sHostPath); + except: + return reporter.errorXcpt('os.listdir(%s) failed' % (sHostPath,)); + + fRc = True; + for sEntry in asEntries: + sEntryUpper = sEntry.upper(); + if sEntryUpper not in dLeftUpper: + fRc = reporter.error('Unexpected entry "%s" in "%s"' % (sEntry, sHostPath,)); + else: + oFsObj = dLeftUpper[sEntryUpper]; + del dLeftUpper[sEntryUpper]; + + if isinstance(oFsObj, testfileset.TestFile): + sFilePath = os.path.join(sHostPath, oFsObj.sName); + try: + oFile = open(sFilePath, 'rb'); # pylint: disable=consider-using-with + except: + fRc = reporter.errorXcpt('open(%s) failed during verfication' % (sFilePath,)); + else: + fEqual = oFsObj.equalFile(oFile); + oFile.close(); + if not fEqual: + fRc = reporter.error('Content differs for "%s"' % (sFilePath,)); + + # List missing entries: + for sKey in dLeftUpper: + oEntry = dLeftUpper[sKey]; + fRc = reporter.error('%s: Missing %s "%s" (src path: %s)' + % (sHostPath, oEntry.sName, + 'file' if isinstance(oEntry, testfileset.TestFile) else 'directory', oEntry.sPath)); + + # + # Recurse into subdirectories. + # + for oFsObj in oDir.aoChildren: + if isinstance(oFsObj, testfileset.TestDir): + fRc = self.__compareTestDir(oFsObj, os.path.join(sHostPath, oFsObj.sName)) and fRc; + return fRc; + + def gctrlCopyDirFrom(self, oGuestSession, oTest, fExpected): + """ + Helper function to copy a directory from the guest to the host. + """ + + # As we pass-in randomly generated directories, the source sometimes can be empty, which + # in turn will result in a (correct) error by the API. Simply skip this function then. + if not oTest.sSrc: + reporter.log2('Skipping guest dir "%s"' % (limitString(oTest.sSrc))); + return fExpected; + + # + # Do the copying. + # + reporter.log2('Copying guest dir "%s" to host "%s"' % (limitString(oTest.sSrc), limitString(oTest.sDst))); + try: + if self.oTstDrv.fpApiVer >= 7.0: + ## @todo Make the following new flags implicit for 7.0 for now. Develop dedicated tests for this later and remove. + if not oTest.afFlags: + oTest.afFlags = [ vboxcon.DirectoryCopyFlag_Recursive, ]; + elif vboxcon.DirectoryCopyFlag_Recursive not in oTest.afFlags: + oTest.afFlags.append(vboxcon.DirectoryCopyFlag_Recursive); + ## @todo Ditto. + if not oTest.afFlags: + oTest.afFlags = [ vboxcon.DirectoryCopyFlag_FollowLinks, ]; + elif vboxcon.DirectoryCopyFlag_FollowLinks not in oTest.afFlags: + oTest.afFlags.append(vboxcon.DirectoryCopyFlag_FollowLinks); + oCurProgress = oGuestSession.directoryCopyFromGuest(oTest.sSrc, oTest.sDst, oTest.afFlags); + except: + reporter.maybeErrXcpt(fExpected, 'Copy dir from exception for sSrc="%s", sDst="%s":' % (oTest.sSrc, oTest.sDst,)); + return False; + if oCurProgress is None: + return reporter.error('No progress object returned'); + + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlDirCopyFrom"); + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fExpected); + return False; + + # + # Check the result if we can. + # + if oTest.oSrc: + assert isinstance(oTest.oSrc, testfileset.TestDir); + sDst = oTest.sDst; + if oTest.fIntoDst: + return self.__compareTestDir(oTest.oSrc, os.path.join(sDst, oTest.oSrc.sName)); + oDummy = testfileset.TestDir(None, 'dummy'); + oDummy.aoChildren = [oTest.oSrc,] + oDummy.dChildrenUpper = { oTest.oSrc.sName.upper(): oTest.oSrc, }; + return self.__compareTestDir(oDummy, sDst); + return True; + + def gctrlCopyFileTo(self, oGuestSession, sSrc, sDst, afFlags, fIsError): + """ + Helper function to copy a single file from the host to the guest. + + afFlags is either None or an array of vboxcon.DirectoryCopyFlag_Xxxx values. + """ + reporter.log2('Copying host file "%s" to guest "%s" (flags %s)' % (limitString(sSrc), limitString(sDst), afFlags)); + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyToGuest(sSrc, sDst, afFlags); + else: + oCurProgress = oGuestSession.copyTo(sSrc, sDst, afFlags); + except: + reporter.maybeErrXcpt(fIsError, 'sSrc=%s sDst=%s' % (sSrc, sDst,)); + return False; + + if oCurProgress is None: + return reporter.error('No progress object returned'); + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlCopyFileTo"); + + try: + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fIsError); + return False; + except: + reporter.maybeErrXcpt(fIsError, 'Wait exception for sSrc="%s", sDst="%s":' % (sSrc, sDst)); + return False; + return True; + + def gctrlCopyDirTo(self, oGuestSession, sSrc, sDst, afFlags, fIsError): + """ + Helper function to copy a directory (tree) from the host to the guest. + + afFlags is either None or an array of vboxcon.DirectoryCopyFlag_Xxxx values. + """ + reporter.log2('Copying host directory "%s" to guest "%s" (flags %s)' % (limitString(sSrc), limitString(sDst), afFlags)); + try: + if self.oTstDrv.fpApiVer >= 7.0: + ## @todo Make the following new flags implicit for 7.0 for now. Develop dedicated tests for this later and remove. + if not afFlags: + afFlags = [ vboxcon.DirectoryCopyFlag_Recursive, ]; + elif vboxcon.DirectoryCopyFlag_Recursive not in afFlags: + afFlags.append(vboxcon.DirectoryCopyFlag_Recursive); + ## @todo Ditto. + if not afFlags: + afFlags = [vboxcon.DirectoryCopyFlag_FollowLinks,]; + elif vboxcon.DirectoryCopyFlag_FollowLinks not in afFlags: + afFlags.append(vboxcon.DirectoryCopyFlag_FollowLinks); + oCurProgress = oGuestSession.directoryCopyToGuest(sSrc, sDst, afFlags); + except: + reporter.maybeErrXcpt(fIsError, 'sSrc=%s sDst=%s' % (sSrc, sDst,)); + return False; + + if oCurProgress is None: + return reporter.error('No progress object returned'); + oProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "gctrlCopyFileTo"); + + try: + oProgress.wait(); + if not oProgress.isSuccess(): + oProgress.logResult(fIgnoreErrors = not fIsError); + return False; + except: + reporter.maybeErrXcpt(fIsError, 'Wait exception for sSrc="%s", sDst="%s":' % (sSrc, sDst)); + return False; + return True; + + def gctrlCreateDir(self, oTest, oRes, oGuestSession): + """ + Helper function to create a guest directory specified in the current test. + """ + reporter.log2('Creating directory "%s"' % (limitString(oTest.sDirectory),)); + try: + oGuestSession.directoryCreate(oTest.sDirectory, oTest.fMode, oTest.afFlags); + except: + reporter.maybeErrXcpt(oRes.fRc, 'Failed to create "%s" fMode=%o afFlags=%s' + % (oTest.sDirectory, oTest.fMode, oTest.afFlags,)); + return not oRes.fRc; + if oRes.fRc is not True: + return reporter.error('Did not expect to create directory "%s"!' % (oTest.sDirectory,)); + + # Check if the directory now exists. + try: + if self.oTstDrv.fpApiVer >= 5.0: + fDirExists = oGuestSession.directoryExists(oTest.sDirectory, False); + else: + fDirExists = oGuestSession.directoryExists(oTest.sDirectory); + except: + return reporter.errorXcpt('directoryExists failed on "%s"!' % (oTest.sDirectory,)); + if not fDirExists: + return reporter.errorXcpt('directoryExists returned False on "%s" after directoryCreate succeeded!' + % (oTest.sDirectory,)); + return True; + + def gctrlReadDirTree(self, oTest, oGuestSession, fIsError, sSubDir = None): + """ + Helper function to recursively read a guest directory tree specified in the current test. + """ + sDir = oTest.sDirectory; + sFilter = oTest.sFilter; + afFlags = oTest.afFlags; + oTestVm = oTest.oCreds.oTestVm; + sCurDir = oTestVm.pathJoin(sDir, sSubDir) if sSubDir else sDir; + + fRc = True; # Be optimistic. + cDirs = 0; # Number of directories read. + cFiles = 0; # Number of files read. + cOthers = 0; # Other files. + + # Open the directory: + reporter.log2('Directory="%s", filter="%s", afFlags="%s"' % (limitString(sCurDir), sFilter, afFlags)); + try: + oCurDir = oGuestSession.directoryOpen(sCurDir, sFilter, afFlags); + except: + reporter.maybeErrXcpt(fIsError, 'sCurDir=%s sFilter=%s afFlags=%s' % (sCurDir, sFilter, afFlags,)) + return (False, 0, 0, 0); + + # Read the directory. + while fRc is True: + try: + oFsObjInfo = oCurDir.read(); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.VBOX_E_OBJECT_NOT_FOUND): + if self.oTstDrv.fpApiVer > 5.2: + reporter.errorXcpt('Error reading directory "%s":' % (sCurDir,)); + else: + # Unlike fileOpen, directoryOpen will not fail if the directory does not exist. + reporter.maybeErrXcpt(fIsError, 'Error reading directory "%s":' % (sCurDir,)); + fRc = False; + else: + reporter.log2('\tNo more directory entries for "%s"' % (limitString(sCurDir),)); + break; + + try: + sName = oFsObjInfo.name; + eType = oFsObjInfo.type; + except: + fRc = reporter.errorXcpt(); + break; + + if sName in ('.', '..', ): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('Wrong type for "%s": %d, expected %d (Directory)' + % (sName, eType, vboxcon.FsObjType_Directory)); + elif eType == vboxcon.FsObjType_Directory: + reporter.log2(' Directory "%s"' % limitString(oFsObjInfo.name)); + aSubResult = self.gctrlReadDirTree(oTest, oGuestSession, fIsError, + oTestVm.pathJoin(sSubDir, sName) if sSubDir else sName); + fRc = aSubResult[0]; + cDirs += aSubResult[1] + 1; + cFiles += aSubResult[2]; + cOthers += aSubResult[3]; + elif eType is vboxcon.FsObjType_File: + reporter.log4(' File "%s"' % oFsObjInfo.name); + cFiles += 1; + elif eType is vboxcon.FsObjType_Symlink: + reporter.log4(' Symlink "%s" -- not tested yet' % oFsObjInfo.name); + cOthers += 1; + elif oTestVm.isWindows() \ + or oTestVm.isOS2() \ + or eType not in (vboxcon.FsObjType_Fifo, vboxcon.FsObjType_DevChar, vboxcon.FsObjType_DevBlock, + vboxcon.FsObjType_Socket, vboxcon.FsObjType_WhiteOut): + fRc = reporter.error('Directory "%s" contains invalid directory entry "%s" (type %d)' % + (sCurDir, oFsObjInfo.name, oFsObjInfo.type,)); + else: + cOthers += 1; + + # Close the directory + try: + oCurDir.close(); + except: + fRc = reporter.errorXcpt('sCurDir=%s' % (sCurDir)); + + return (fRc, cDirs, cFiles, cOthers); + + def gctrlReadDirTree2(self, oGuestSession, oDir): # type: (testfileset.TestDir) -> bool + """ + Helper function to recursively read a guest directory tree specified in the current test. + """ + + # + # Process the directory. + # + + # Open the directory: + try: + oCurDir = oGuestSession.directoryOpen(oDir.sPath, '', None); + except: + return reporter.errorXcpt('sPath=%s' % (oDir.sPath,)); + + # Read the directory. + dLeftUpper = dict(oDir.dChildrenUpper); + cDot = 0; + cDotDot = 0; + fRc = True; + while True: + try: + oFsObjInfo = oCurDir.read(); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.VBOX_E_OBJECT_NOT_FOUND): + fRc = reporter.errorXcpt('Error reading directory "%s":' % (oDir.sPath,)); + break; + + try: + sName = oFsObjInfo.name; + eType = oFsObjInfo.type; + cbFile = oFsObjInfo.objectSize; + ## @todo check further attributes. + except: + fRc = reporter.errorXcpt(); + break; + + # '.' and '..' entries are not present in oDir.aoChildren, so special treatment: + if sName in ('.', '..', ): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('Wrong type for "%s": %d, expected %d (Directory)' + % (sName, eType, vboxcon.FsObjType_Directory)); + if sName == '.': cDot += 1; + else: cDotDot += 1; + else: + # Find the child and remove it from the dictionary. + sNameUpper = sName.upper(); + oFsObj = dLeftUpper.get(sNameUpper); + if oFsObj is None: + fRc = reporter.error('Unknown object "%s" found in "%s" (type %s, size %s)!' + % (sName, oDir.sPath, eType, cbFile,)); + else: + del dLeftUpper[sNameUpper]; + + # Check type + if isinstance(oFsObj, testfileset.TestDir): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('%s: expected directory (%d), got eType=%d!' + % (oFsObj.sPath, vboxcon.FsObjType_Directory, eType,)); + elif isinstance(oFsObj, testfileset.TestFile): + if eType != vboxcon.FsObjType_File: + fRc = reporter.error('%s: expected file (%d), got eType=%d!' + % (oFsObj.sPath, vboxcon.FsObjType_File, eType,)); + else: + fRc = reporter.error('%s: WTF? type=%s' % (oFsObj.sPath, type(oFsObj),)); + + # Check the name. + if oFsObj.sName != sName: + fRc = reporter.error('%s: expected name "%s", got "%s" instead!' % (oFsObj.sPath, oFsObj.sName, sName,)); + + # Check the size if a file. + if isinstance(oFsObj, testfileset.TestFile) and cbFile != oFsObj.cbContent: + fRc = reporter.error('%s: expected size %s, got %s instead!' % (oFsObj.sPath, oFsObj.cbContent, cbFile,)); + + ## @todo check timestamps and attributes. + + # Close the directory + try: + oCurDir.close(); + except: + fRc = reporter.errorXcpt('oDir.sPath=%s' % (oDir.sPath,)); + + # Any files left over? + for sKey in dLeftUpper: + oFsObj = dLeftUpper[sKey]; + fRc = reporter.error('%s: Was not returned! (%s)' % (oFsObj.sPath, type(oFsObj),)); + + # Check the dot and dot-dot counts. + if cDot != 1: + fRc = reporter.error('%s: Found %s "." entries, expected exactly 1!' % (oDir.sPath, cDot,)); + if cDotDot != 1: + fRc = reporter.error('%s: Found %s ".." entries, expected exactly 1!' % (oDir.sPath, cDotDot,)); + + # + # Recurse into subdirectories using info from oDir. + # + for oFsObj in oDir.aoChildren: + if isinstance(oFsObj, testfileset.TestDir): + fRc = self.gctrlReadDirTree2(oGuestSession, oFsObj) and fRc; + + return fRc; + + def gctrlExecDoTest(self, i, oTest, oRes, oGuestSession): + """ + Wrapper function around gctrlExecute to provide more sanity checking + when needed in actual execution tests. + """ + reporter.log('Testing #%d, cmd="%s" ...' % (i, oTest.sCmd)); + fRcExec = self.gctrlExecute(oTest, oGuestSession, oRes.fRc); + if fRcExec == oRes.fRc: + fRc = True; + if fRcExec is True: + # Compare exit status / code on successful process execution. + if oTest.uExitStatus != oRes.uExitStatus \ + or oTest.iExitCode != oRes.iExitCode: + fRc = reporter.error('Test #%d (%s) failed: Got exit status + code %d,%d, expected %d,%d' + % (i, oTest.asArgs, oTest.uExitStatus, oTest.iExitCode, + oRes.uExitStatus, oRes.iExitCode)); + + # Compare test / result buffers on successful process execution. + if oTest.sBuf is not None and oRes.sBuf is not None: + if not utils.areBytesEqual(oTest.sBuf, oRes.sBuf): + fRc = reporter.error('Test #%d (%s) failed: Got buffer\n%s (%d bytes), expected\n%s (%d bytes)' + % (i, oTest.asArgs, + map(hex, map(ord, oTest.sBuf)), len(oTest.sBuf), + map(hex, map(ord, oRes.sBuf)), len(oRes.sBuf))); + reporter.log2('Test #%d passed: Buffers match (%d bytes)' % (i, len(oRes.sBuf))); + elif oRes.sBuf and not oTest.sBuf: + fRc = reporter.error('Test #%d (%s) failed: Got no buffer data, expected\n%s (%dbytes)' % + (i, oTest.asArgs, map(hex, map(ord, oRes.sBuf)), len(oRes.sBuf),)); + + if oRes.cbStdOut is not None and oRes.cbStdOut != oTest.cbStdOut: + fRc = reporter.error('Test #%d (%s) failed: Got %d bytes of stdout data, expected %d' + % (i, oTest.asArgs, oTest.cbStdOut, oRes.cbStdOut)); + if oRes.cbStdErr is not None and oRes.cbStdErr != oTest.cbStdErr: + fRc = reporter.error('Test #%d (%s) failed: Got %d bytes of stderr data, expected %d' + % (i, oTest.asArgs, oTest.cbStdErr, oRes.cbStdErr)); + else: + fRc = reporter.error('Test #%d (%s) failed: Got %s, expected %s' % (i, oTest.asArgs, fRcExec, oRes.fRc)); + return fRc; + + def gctrlExecute(self, oTest, oGuestSession, fIsError): # pylint: disable=too-many-statements + """ + Helper function to execute a program on a guest, specified in the current test. + + Note! This weirdo returns results (process exitcode and status) in oTest. + """ + fRc = True; # Be optimistic. + + # Reset the weird result stuff: + oTest.cbStdOut = 0; + oTest.cbStdErr = 0; + oTest.sBuf = ''; + oTest.uExitStatus = 0; + oTest.iExitCode = 0; + + ## @todo Compare execution timeouts! + #tsStart = base.timestampMilli(); + + try: + reporter.log2('Using session user=%s, sDomain=%s, name=%s, timeout=%d' + % (oGuestSession.user, oGuestSession.domain, oGuestSession.name, oGuestSession.timeout,)); + except: + return reporter.errorXcpt(); + + # + # Start the process: + # + reporter.log2('Executing sCmd=%s, afFlags=%s, timeoutMS=%d, asArgs=%s, asEnv=%s' + % (oTest.sCmd, oTest.afFlags, oTest.timeoutMS, limitString(oTest.asArgs), limitString(oTest.aEnv),)); + try: + oProcess = oGuestSession.processCreate(oTest.sCmd, + oTest.asArgs if self.oTstDrv.fpApiVer >= 5.0 else oTest.asArgs[1:], + oTest.aEnv, oTest.afFlags, oTest.timeoutMS); + except: + reporter.maybeErrXcpt(fIsError, 'type=%s, asArgs=%s' % (type(oTest.asArgs), oTest.asArgs,)); + return False; + if oProcess is None: + return reporter.error('oProcess is None! (%s)' % (oTest.asArgs,)); + + #time.sleep(5); # try this if you want to see races here. + + # Wait for the process to start properly: + reporter.log2('Process start requested, waiting for start (%dms) ...' % (oTest.timeoutMS,)); + iPid = -1; + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Start, ]; + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, oTest.timeoutMS); + except: + reporter.maybeErrXcpt(fIsError, 'waitforArray failed for asArgs=%s' % (oTest.asArgs,)); + fRc = False; + else: + try: + eStatus = oProcess.status; + iPid = oProcess.PID; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + else: + reporter.log2('Wait result returned: %d, current process status is: %d' % (eWaitResult, eStatus,)); + + # + # Wait for the process to run to completion if necessary. + # + # Note! The above eWaitResult return value can be ignored as it will + # (mostly) reflect the process status anyway. + # + if eStatus == vboxcon.ProcessStatus_Started: + + # What to wait for: + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate, ]; + if vboxcon.ProcessCreateFlag_WaitForStdOut in oTest.afFlags: + aeWaitFor.append(vboxcon.ProcessWaitForFlag_StdOut); + if vboxcon.ProcessCreateFlag_WaitForStdErr in oTest.afFlags: + aeWaitFor.append(vboxcon.ProcessWaitForFlag_StdErr); + ## @todo Add vboxcon.ProcessWaitForFlag_StdIn. + + reporter.log2('Process (PID %d) started, waiting for termination (%dms), aeWaitFor=%s ...' + % (iPid, oTest.timeoutMS, aeWaitFor)); + acbFdOut = [0,0,0]; + while True: + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, oTest.timeoutMS); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + break; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + break; + #reporter.log2('Wait returned: %d' % (eWaitResult,)); + + # Process output: + for eFdResult, iFd, sFdNm in [ (vboxcon.ProcessWaitResult_StdOut, 1, 'stdout'), + (vboxcon.ProcessWaitResult_StdErr, 2, 'stderr'), ]: + if eWaitResult in (eFdResult, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + try: + abBuf = oProcess.read(iFd, 64 * 1024, oTest.timeoutMS); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + except: + reporter.maybeErrXcpt(fIsError, 'asArgs=%s' % (oTest.asArgs,)); + else: + if abBuf: + reporter.log2('Process (PID %d) got %d bytes of %s data (type: %s)' + % (iPid, len(abBuf), sFdNm, type(abBuf))); + if reporter.getVerbosity() >= 4: + sBuf = ''; + if sys.version_info >= (2, 7): + if isinstance(abBuf, memoryview): ## @todo Why is this happening? + abBuf = abBuf.tobytes(); + sBuf = abBuf.decode("utf-8"); + if sys.version_info <= (2, 7): + if isinstance(abBuf, buffer): # (for 3.0+) pylint: disable=undefined-variable + sBuf = str(abBuf); + for sLine in sBuf.splitlines(): + reporter.log4('%s: %s' % (sFdNm, sLine)); + acbFdOut[iFd] += len(abBuf); + oTest.sBuf = abBuf; ## @todo Figure out how to uniform + append! + + ## Process input (todo): + #if eWaitResult in (vboxcon.ProcessWaitResult_StdIn, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + # reporter.log2('Process (PID %d) needs stdin data' % (iPid,)); + + # Termination or error? + if eWaitResult in (vboxcon.ProcessWaitResult_Terminate, + vboxcon.ProcessWaitResult_Error, + vboxcon.ProcessWaitResult_Timeout,): + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + reporter.log2('Process (PID %d) reported terminate/error/timeout: %d, status: %d' + % (iPid, eWaitResult, eStatus,)); + break; + + # End of the wait loop. + _, oTest.cbStdOut, oTest.cbStdErr = acbFdOut; + + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + reporter.log2('Final process status (PID %d) is: %d' % (iPid, eStatus)); + reporter.log2('Process (PID %d) %d stdout, %d stderr' % (iPid, oTest.cbStdOut, oTest.cbStdErr)); + + # + # Get the final status and exit code of the process. + # + try: + oTest.uExitStatus = oProcess.status; + oTest.iExitCode = oProcess.exitCode; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (oTest.asArgs,)); + reporter.log2('Process (PID %d) has exit code: %d; status: %d ' % (iPid, oTest.iExitCode, oTest.uExitStatus)); + return fRc; + + def testGuestCtrlSessionEnvironment(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests the guest session environment changes. + """ + aoTests = [ + # Check basic operations. + tdTestSessionEx([ # Initial environment is empty. + tdStepSessionCheckEnv(), + # Check clearing empty env. + tdStepSessionClearEnv(), + tdStepSessionCheckEnv(), + # Check set. + tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionCheckEnv(['FOO=BAR',]), + tdStepRequireMinimumApiVer(5.0), # 4.3 can't cope with the remainder. + tdStepSessionClearEnv(), + tdStepSessionCheckEnv(), + # Check unset. + tdStepSessionUnsetEnv('BAR'), + tdStepSessionCheckEnv(['BAR']), + tdStepSessionClearEnv(), + tdStepSessionCheckEnv(), + # Set + unset. + tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionCheckEnv(['FOO=BAR',]), + tdStepSessionUnsetEnv('FOO'), + tdStepSessionCheckEnv(['FOO']), + # Bulk environment changes (via attrib) (shall replace existing 'FOO'). + tdStepSessionBulkEnv( ['PATH=/bin:/usr/bin', 'TMPDIR=/var/tmp', 'USER=root']), + tdStepSessionCheckEnv(['PATH=/bin:/usr/bin', 'TMPDIR=/var/tmp', 'USER=root']), + ]), + tdTestSessionEx([ # Check that setting the same value several times works. + tdStepSessionSetEnv('FOO','BAR'), + tdStepSessionCheckEnv([ 'FOO=BAR',]), + tdStepSessionSetEnv('FOO','BAR2'), + tdStepSessionCheckEnv([ 'FOO=BAR2',]), + tdStepSessionSetEnv('FOO','BAR3'), + tdStepSessionCheckEnv([ 'FOO=BAR3',]), + tdStepRequireMinimumApiVer(5.0), # 4.3 can't cope with the remainder. + # Add a little unsetting to the mix. + tdStepSessionSetEnv('BAR', 'BEAR'), + tdStepSessionCheckEnv([ 'FOO=BAR3', 'BAR=BEAR',]), + tdStepSessionUnsetEnv('FOO'), + tdStepSessionCheckEnv([ 'FOO', 'BAR=BEAR',]), + tdStepSessionSetEnv('FOO','BAR4'), + tdStepSessionCheckEnv([ 'FOO=BAR4', 'BAR=BEAR',]), + # The environment is case sensitive. + tdStepSessionSetEnv('foo','BAR5'), + tdStepSessionCheckEnv([ 'FOO=BAR4', 'BAR=BEAR', 'foo=BAR5']), + tdStepSessionUnsetEnv('foo'), + tdStepSessionCheckEnv([ 'FOO=BAR4', 'BAR=BEAR', 'foo']), + ]), + tdTestSessionEx([ # Bulk settings merges stuff, last entry standing. + tdStepSessionBulkEnv(['FOO=bar', 'foo=bar', 'FOO=doofus', 'TMPDIR=/tmp', 'foo=bar2']), + tdStepSessionCheckEnv(['FOO=doofus', 'TMPDIR=/tmp', 'foo=bar2']), + tdStepRequireMinimumApiVer(5.0), # 4.3 is buggy! + tdStepSessionBulkEnv(['2=1+1', 'FOO=doofus2', ]), + tdStepSessionCheckEnv(['2=1+1', 'FOO=doofus2' ]), + ]), + # Invalid variable names. + tdTestSessionEx([ + tdStepSessionSetEnv('', 'FOO', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepRequireMinimumApiVer(5.0), # 4.3 is too relaxed checking input! + tdStepSessionBulkEnv(['', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionSetEnv('FOO=', 'BAR', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + ]), + # A bit more weird keys/values. + tdTestSessionEx([ tdStepSessionSetEnv('$$$', ''), + tdStepSessionCheckEnv([ '$$$=',]), ]), + tdTestSessionEx([ tdStepSessionSetEnv('$$$', '%%%'), + tdStepSessionCheckEnv([ '$$$=%%%',]), + ]), + tdTestSessionEx([ tdStepRequireMinimumApiVer(5.0), # 4.3 is buggy! + tdStepSessionSetEnv(u'ß$%ß&', ''), + tdStepSessionCheckEnv([ u'ß$%ß&=',]), + ]), + # Misc stuff. + tdTestSessionEx([ tdStepSessionSetEnv('FOO', ''), + tdStepSessionCheckEnv(['FOO=',]), + ]), + tdTestSessionEx([ tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionCheckEnv(['FOO=BAR',]) + ],), + tdTestSessionEx([ tdStepSessionSetEnv('FOO', 'BAR'), + tdStepSessionSetEnv('BAR', 'BAZ'), + tdStepSessionCheckEnv([ 'FOO=BAR', 'BAR=BAZ',]), + ]), + ]; + # Leading '=' in the name is okay for windows guests in 6.1 and later (for driver letter CWDs). + if (self.oTstDrv.fpApiVer < 6.1 and self.oTstDrv.fpApiVer >= 5.0) or not oTestVm.isWindows(): + aoTests.append(tdTestSessionEx([tdStepSessionSetEnv('=', '===', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionSetEnv('=FOO', 'BAR', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionBulkEnv(['=', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionBulkEnv(['=FOO', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionBulkEnv(['=D:=D:/tmp', 'foo=bar'], vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + tdStepSessionSetEnv('=D:', 'D:/temp', vbox.ComError.E_INVALIDARG), + tdStepSessionCheckEnv(), + ])); + elif self.oTstDrv.fpApiVer >= 6.1 and oTestVm.isWindows(): + aoTests.append(tdTestSessionEx([tdStepSessionSetEnv('=D:', 'D:/tmp'), + tdStepSessionCheckEnv(['=D:=D:/tmp',]), + tdStepSessionBulkEnv(['=D:=D:/temp', '=FOO', 'foo=bar']), + tdStepSessionCheckEnv(['=D:=D:/temp', '=FOO', 'foo=bar']), + tdStepSessionUnsetEnv('=D:'), + tdStepSessionCheckEnv(['=D:', '=FOO', 'foo=bar']), + ])); + + return tdTestSessionEx.executeListTestSessions(aoTests, self.oTstDrv, oSession, oTxsSession, oTestVm, 'SessionEnv'); + + def testGuestCtrlSession(self, oSession, oTxsSession, oTestVm): + """ + Tests the guest session handling. + """ + + # + # Tests: + # + atTests = [ + # Invalid parameters. + [ tdTestSession(sUser = ''), tdTestResultSession() ], + # User account without a passwort - forbidden. + [ tdTestSession(sPassword = "" ), tdTestResultSession() ], + # Various wrong credentials. + # Note! Only windows cares about sDomain, the other guests ignores it. + # Note! On Guest Additions < 4.3 this always succeeds because these don't + # support creating dedicated sessions. Instead, guest process creation + # then will fail. See note below. + [ tdTestSession(sPassword = 'bar'), tdTestResultSession() ], + [ tdTestSession(sUser = 'foo', sPassword = 'bar'), tdTestResultSession() ], + [ tdTestSession(sPassword = 'bar', sDomain = 'boo'), tdTestResultSession() ], + [ tdTestSession(sUser = 'foo', sPassword = 'bar', sDomain = 'boo'), tdTestResultSession() ], + ]; + if oTestVm.isWindows(): # domain is ignored elsewhere. + atTests.append([ tdTestSession(sDomain = 'boo'), tdTestResultSession() ]); + + # Finally, correct credentials. + atTests.append([ tdTestSession(), tdTestResultSession(fRc = True, cNumSessions = 1) ]); + + # + # Run the tests. + # + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestSession + oCurRes = tTest[1] # type: tdTestResult + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + reporter.log('Testing #%d, user="%s", sPassword="%s", sDomain="%s" ...' + % (i, oCurTest.oCreds.sUser, oCurTest.oCreds.sPassword, oCurTest.oCreds.sDomain)); + sCurGuestSessionName = 'testGuestCtrlSession: Test #%d' % (i,); + fRc2, oCurGuestSession = oCurTest.createSession(sCurGuestSessionName, fIsError = oCurRes.fRc); + + # See note about < 4.3 Guest Additions above. + uProtocolVersion = 2; + if oCurGuestSession is not None: + try: + uProtocolVersion = oCurGuestSession.protocolVersion; + except: + fRc = reporter.errorXcpt('Test #%d' % (i,)); + + if uProtocolVersion >= 2 and fRc2 is not oCurRes.fRc: + fRc = reporter.error('Test #%d failed: Session creation failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc,)); + + if fRc2 and oCurGuestSession is None: + fRc = reporter.error('Test #%d failed: no session object' % (i,)); + fRc2 = False; + + if fRc2: + if uProtocolVersion >= 2: # For Guest Additions < 4.3 getSessionCount() always will return 1. + cCurSessions = oCurTest.getSessionCount(self.oTstDrv.oVBoxMgr); + if cCurSessions != oCurRes.cNumSessions: + fRc = reporter.error('Test #%d failed: Session count does not match: Got %d, expected %d' + % (i, cCurSessions, oCurRes.cNumSessions)); + try: + sObjName = oCurGuestSession.name; + except: + fRc = reporter.errorXcpt('Test #%d' % (i,)); + else: + if sObjName != sCurGuestSessionName: + fRc = reporter.error('Test #%d failed: Session name does not match: Got "%s", expected "%s"' + % (i, sObjName, sCurGuestSessionName)); + fRc2 = oCurTest.closeSession(); + if fRc2 is False: + fRc = reporter.error('Test #%d failed: Session could not be closed' % (i,)); + + if fRc is False: + return (False, oTxsSession); + + # + # Multiple sessions. + # + cMaxGuestSessions = 31; # Maximum number of concurrent guest session allowed. + # Actually, this is 32, but we don't test session 0. + aoMultiSessions = {}; + reporter.log2('Opening multiple guest tsessions at once ...'); + for i in xrange(cMaxGuestSessions + 1): + aoMultiSessions[i] = tdTestSession(sSessionName = 'MultiSession #%d' % (i,)); + fRc = aoMultiSessions[i].setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + + cCurSessions = aoMultiSessions[i].getSessionCount(self.oTstDrv.oVBoxMgr); + reporter.log2('MultiSession test #%d count is %d' % (i, cCurSessions)); + if cCurSessions != i: + return (reporter.error('MultiSession count is %d, expected %d' % (cCurSessions, i)), oTxsSession); + fRc2, _ = aoMultiSessions[i].createSession('MultiSession #%d' % (i,), i < cMaxGuestSessions); + if fRc2 is not True: + if i < cMaxGuestSessions: + return (reporter.error('MultiSession #%d test failed' % (i,)), oTxsSession); + reporter.log('MultiSession #%d exceeded concurrent guest session count, good' % (i,)); + break; + + cCurSessions = aoMultiSessions[i].getSessionCount(self.oTstDrv.oVBoxMgr); + if cCurSessions is not cMaxGuestSessions: + return (reporter.error('Final session count %d, expected %d ' % (cCurSessions, cMaxGuestSessions,)), oTxsSession); + + reporter.log2('Closing MultiSessions ...'); + for i in xrange(cMaxGuestSessions): + # Close this session: + oClosedGuestSession = aoMultiSessions[i].oGuestSession; + fRc2 = aoMultiSessions[i].closeSession(); + cCurSessions = aoMultiSessions[i].getSessionCount(self.oTstDrv.oVBoxMgr) + reporter.log2('MultiSession #%d count is %d' % (i, cCurSessions,)); + if fRc2 is False: + fRc = reporter.error('Closing MultiSession #%d failed' % (i,)); + elif cCurSessions != cMaxGuestSessions - (i + 1): + fRc = reporter.error('Expected %d session after closing #%d, got %d instead' + % (cMaxGuestSessions - (i + 1), cCurSessions, i,)); + assert aoMultiSessions[i].oGuestSession is None or not fRc2; + ## @todo any way to check that the session is closed other than the 'sessions' attribute? + + # Try check that none of the remaining sessions got closed. + try: + aoGuestSessions = self.oTstDrv.oVBoxMgr.getArray(atTests[0][0].oGuest, 'sessions'); + except: + return (reporter.errorXcpt('i=%d/%d' % (i, cMaxGuestSessions,)), oTxsSession); + if oClosedGuestSession in aoGuestSessions: + fRc = reporter.error('i=%d/%d: %s should not be in %s' + % (i, cMaxGuestSessions, oClosedGuestSession, aoGuestSessions)); + if i + 1 < cMaxGuestSessions: # Not sure what xrange(2,2) does... + for j in xrange(i + 1, cMaxGuestSessions): + if aoMultiSessions[j].oGuestSession not in aoGuestSessions: + fRc = reporter.error('i=%d/j=%d/%d: %s should be in %s' + % (i, j, cMaxGuestSessions, aoMultiSessions[j].oGuestSession, aoGuestSessions)); + ## @todo any way to check that they work? + + ## @todo Test session timeouts. + + return (fRc, oTxsSession); + + def testGuestCtrlSessionFileRefs(self, oSession, oTxsSession, oTestVm): + """ + Tests the guest session file reference handling. + """ + + # Find a file to play around with: + sFile = self.oTstDrv.getGuestSystemFileForReading(oTestVm); + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + # Number of stale guest files to create. + cStaleFiles = 10; + + # + # Start a session. + # + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlSessionFileRefs"); + eWaitResult = oGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Open guest files and "forget" them (stale entries). + # For them we don't have any references anymore intentionally. + # + reporter.log2('Opening stale files'); + fRc = True; + for i in xrange(0, cStaleFiles): + try: + if self.oTstDrv.fpApiVer >= 5.0: + oGuestSession.fileOpen(sFile, vboxcon.FileAccessMode_ReadOnly, vboxcon.FileOpenAction_OpenExisting, 0); + else: + oGuestSession.fileOpen(sFile, "r", "oe", 0); + # Note: Use a timeout in the call above for not letting the stale processes + # hanging around forever. This can happen if the installed Guest Additions + # do not support terminating guest processes. + except: + fRc = reporter.errorXcpt('Opening stale file #%d failed:' % (i,)); + break; + + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + else: + if cFiles != cStaleFiles: + fRc = reporter.error('Got %d stale files, expected %d' % (cFiles, cStaleFiles)); + + if fRc is True: + # + # Open non-stale files and close them again. + # + reporter.log2('Opening non-stale files'); + aoFiles = []; + for i in xrange(0, cStaleFiles): + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurFile = oGuestSession.fileOpen(sFile, vboxcon.FileAccessMode_ReadOnly, + vboxcon.FileOpenAction_OpenExisting, 0); + else: + oCurFile = oGuestSession.fileOpen(sFile, "r", "oe", 0); + aoFiles.append(oCurFile); + except: + fRc = reporter.errorXcpt('Opening non-stale file #%d failed:' % (i,)); + break; + + # Check the count. + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + else: + if cFiles != cStaleFiles * 2: + fRc = reporter.error('Got %d total files, expected %d' % (cFiles, cStaleFiles * 2)); + + # Close them. + reporter.log2('Closing all non-stale files again ...'); + for i, oFile in enumerate(aoFiles): + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('Closing non-stale file #%d failed:' % (i,)); + + # Check the count again. + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + # Here we count the stale files (that is, files we don't have a reference + # anymore for) and the opened and then closed non-stale files (that we still keep + # a reference in aoFiles[] for). + if cFiles != cStaleFiles: + fRc = reporter.error('Got %d total files, expected %d' % (cFiles, cStaleFiles)); + + # + # Check that all (referenced) non-stale files are now in the "closed" state. + # + reporter.log2('Checking statuses of all non-stale files ...'); + for i, oFile in enumerate(aoFiles): + try: + eFileStatus = aoFiles[i].status; + except: + fRc = reporter.errorXcpt('Checking status of file #%d failed:' % (i,)); + else: + if eFileStatus != vboxcon.FileStatus_Closed: + fRc = reporter.error('Non-stale file #%d has status %d, expected %d' + % (i, eFileStatus, vboxcon.FileStatus_Closed)); + + if fRc is True: + reporter.log2('All non-stale files closed'); + + try: cFiles = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'files')); + except: fRc = reporter.errorXcpt(); + else: reporter.log2('Final guest session file count: %d' % (cFiles,)); + + # + # Now try to close the session and see what happens. + # Note! Session closing is why we've been doing all the 'if fRc is True' stuff above rather than returning. + # + reporter.log2('Closing guest session ...'); + try: + oGuestSession.close(); + except: + fRc = reporter.errorXcpt('Testing for stale processes failed:'); + + return (fRc, oTxsSession); + + #def testGuestCtrlSessionDirRefs(self, oSession, oTxsSession, oTestVm): + # """ + # Tests the guest session directory reference handling. + # """ + + # fRc = True; + # return (fRc, oTxsSession); + + def testGuestCtrlSessionProcRefs(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals,too-many-statements + """ + Tests the guest session process reference handling. + """ + + sShell = self.oTstDrv.getGuestSystemShell(oTestVm); + asArgs = [sShell,]; + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + # Number of guest processes per group to create. + cProcsPerGroup = 10; + + # + # Start a session. + # + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlSessionProcRefs"); + eWaitResult = oGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Fire off forever-running processes and "forget" them (stale entries). + # For them we don't have any references anymore intentionally. + # + reporter.log('Starting stale processes...'); + fRc = True; + for i in xrange(0, cProcsPerGroup): + try: + reporter.log2('Starting stale process #%d...' % (i)); + oGuestSession.processCreate(sShell, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], [], + [ vboxcon.ProcessCreateFlag_WaitForStdOut ], 30 * 1000); + # Note: Not keeping a process reference from the created process above is intentional and part of the test! + + # Note: Use a timeout in the call above for not letting the stale processes + # hanging around forever. This can happen if the installed Guest Additions + # do not support terminating guest processes. + except: + fRc = reporter.errorXcpt('Creating stale process #%d failed:' % (i,)); + break; + + if fRc: + reporter.log2('Starting stale processes successful'); + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + reporter.log2('Proccess count is: %d' % (cProcs)); + if cProcs != cProcsPerGroup: + fRc = reporter.error('Got %d stale processes, expected %d (stale)' % (cProcs, cProcsPerGroup)); + + if fRc: + # + # Fire off non-stale processes and wait for termination. + # + if oTestVm.isWindows() or oTestVm.isOS2(): + asArgs = [ sShell, '/C', 'dir', '/S', self.oTstDrv.getGuestSystemDir(oTestVm), ]; + else: + asArgs = [ sShell, '-c', 'ls -la ' + self.oTstDrv.getGuestSystemDir(oTestVm), ]; + reporter.log('Starting non-stale processes...'); + aoProcs = []; + for i in xrange(0, cProcsPerGroup): + try: + reporter.log2('Starting non-stale process #%d...' % (i)); + oCurProc = oGuestSession.processCreate(sShell, asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], [], 0); # Infinite timeout. + aoProcs.append(oCurProc); + except: + fRc = reporter.errorXcpt('Creating non-stale process #%d failed:' % (i,)); + break; + + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + reporter.log2('Proccess count is: %d' % (cProcs)); + + reporter.log('Waiting for non-stale processes to terminate...'); + for i, oProcess in enumerate(aoProcs): + try: + reporter.log('Waiting for non-stale process #%d...' % (i)); + eWaitResult = oProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate, ], 30 * 1000); + eProcessStatus = oProcess.status; + except: + fRc = reporter.errorXcpt('Waiting for non-stale process #%d failed:' % (i,)); + else: + if eProcessStatus != vboxcon.ProcessStatus_TerminatedNormally: + fRc = reporter.error('Waiting for non-stale processes #%d resulted in status %d, expected %d (wr=%d)' + % (i, eProcessStatus, vboxcon.ProcessStatus_TerminatedNormally, eWaitResult)); + if fRc: + reporter.log('All non-stale processes ended successfully'); + + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + reporter.log2('Proccess count is: %d' % (cProcs)); + + # Here we count the stale processes (that is, processes we don't have a reference + # anymore for) and the started + ended non-stale processes (that we still keep + # a reference in aoProcesses[] for). + cProcsExpected = cProcsPerGroup * 2; + if cProcs != cProcsExpected: + fRc = reporter.error('Got %d total processes, expected %d (stale vs. non-stale)' \ + % (cProcs, cProcsExpected)); + # + # Fire off non-stale blocking processes which are terminated via terminate(). + # + if oTestVm.isWindows() or oTestVm.isOS2(): + sCmd = sShell; + asArgs = [ sCmd, '/C', 'pause']; + else: + sCmd = '/usr/bin/yes'; + asArgs = [ sCmd ]; + reporter.log('Starting blocking processes...'); + aoProcs = []; + for i in xrange(0, cProcsPerGroup): + try: + reporter.log2('Starting blocking process #%d...' % (i)); + oCurProc = oGuestSession.processCreate(sCmd, asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], [], 30 * 1000); + # Note: Use a timeout in the call above for not letting the stale processes + # hanging around forever. This can happen if the installed Guest Additions + # do not support terminating guest processes. + try: + reporter.log('Waiting for blocking process #%d getting started...' % (i)); + eWaitResult = oCurProc.waitForArray([ vboxcon.ProcessWaitForFlag_Start, ], 30 * 1000); + eProcessStatus = oCurProc.status; + except: + fRc = reporter.errorXcpt('Waiting for blocking process #%d failed:' % (i,)); + else: + if eProcessStatus != vboxcon.ProcessStatus_Started: + fRc = reporter.error('Waiting for blocking processes #%d resulted in status %d, expected %d (wr=%d)' + % (i, eProcessStatus, vboxcon.ProcessStatus_Started, eWaitResult)); + aoProcs.append(oCurProc); + except: + fRc = reporter.errorXcpt('Creating blocking process #%d failed:' % (i,)); + break; + + if fRc: + reporter.log2('Starting blocking processes successful'); + + reporter.log2('Terminating blocking processes...'); + for i, oProcess in enumerate(aoProcs): + try: + reporter.log('Terminating blocking process #%d...' % (i)); + oProcess.terminate(); + except: # Termination might not be supported, just skip and log it. + reporter.logXcpt('Termination of blocking process #%d failed, skipped:' % (i,)); + if self.oTstDrv.fpApiVer >= 6.1: # Termination is supported since 5.2 or so. + fRc = False; + if fRc: + reporter.log('All blocking processes were terminated successfully'); + + try: cProcs = len(self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes')); + except: fRc = reporter.errorXcpt(); + else: + # There still should be 20 processes because we just terminated the 10 blocking ones above. + cProcsExpected = cProcsPerGroup * 2; + if cProcs != cProcsExpected: + fRc = reporter.error('Got %d total processes, expected %d (final)' % (cProcs, cProcsExpected)); + reporter.log2('Final guest session processes count: %d' % (cProcs,)); + + if not fRc: + aoProcs = self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes'); + for i, oProc in enumerate(aoProcs): + try: + aoArgs = self.oTstDrv.oVBoxMgr.getArray(oProc, 'arguments'); + reporter.log('Process %d (\'%s\') still around, status is %d' \ + % (i, ' '.join([str(x) for x in aoArgs]), oProc.status)); + except: + reporter.errorXcpt('Process lookup failed:'); + # + # Now try to close the session and see what happens. + # + reporter.log('Closing guest session ...'); + try: + oGuestSession.close(); + except: + fRc = reporter.errorXcpt('Closing session for testing process references failed:'); + + return (fRc, oTxsSession); + + def testGuestCtrlExec(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals,too-many-statements + """ + Tests the basic execution feature. + """ + + # Paths: + sVBoxControl = None; ## @todo Get path of installed Guest Additions. Later. + sShell = self.oTstDrv.getGuestSystemShell(oTestVm); + sShellOpt = '/C' if oTestVm.isWindows() or oTestVm.isOS2() else '-c'; + sSystemDir = self.oTstDrv.getGuestSystemDir(oTestVm); + sFileForReading = self.oTstDrv.getGuestSystemFileForReading(oTestVm); + if oTestVm.isWindows() or oTestVm.isOS2(): + sImageOut = self.oTstDrv.getGuestSystemShell(oTestVm); + if oTestVm.isWindows(): + sVBoxControl = "C:\\Program Files\\Oracle\\VirtualBox Guest Additions\\VBoxControl.exe"; + else: + sImageOut = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'ls'); + if oTestVm.isLinux(): ## @todo check solaris and darwin. + sVBoxControl = "/usr/bin/VBoxControl"; # Symlink + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + atInvalid = [ + # Invalid parameters. + [ tdTestExec(), tdTestResultExec() ], + # Non-existent / invalid image. + [ tdTestExec(sCmd = "non-existent"), tdTestResultExec() ], + [ tdTestExec(sCmd = "non-existent2"), tdTestResultExec() ], + # Use an invalid format string. + [ tdTestExec(sCmd = "%$%%%&"), tdTestResultExec() ], + # More stuff. + [ tdTestExec(sCmd = u"ƒ‰‹ˆ÷‹¸"), tdTestResultExec() ], + [ tdTestExec(sCmd = "???://!!!"), tdTestResultExec() ], + [ tdTestExec(sCmd = "<>!\\"), tdTestResultExec() ], + # Enable as soon as ERROR_BAD_DEVICE is implemented. + #[ tdTestExec(sCmd = "CON", tdTestResultExec() ], + ]; + + atExec = []; + if oTestVm.isWindows() or oTestVm.isOS2(): + atExec += [ + # Basic execution. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sFileForReading ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir + '\\nonexist.dll' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', '/wrongparam' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + [ tdTestExec(sCmd = sShell, asArgs = [ sShell, sShellOpt, 'wrongcommand' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + # StdOut. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', 'stdout-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + # StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', 'stderr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + # StdOut + StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'dir', '/S', 'stdouterr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 1) ], + ]; + # atExec.extend([ + # FIXME: Failing tests. + # Environment variables. + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_NONEXIST' ], + # tdTestResultExec(fRc = True, iExitCode = 1) ] + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'windir' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'windir=C:\\WINDOWS\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR', 'TEST_BAZ=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ] + + ## @todo Create some files (or get files) we know the output size of to validate output length! + ## @todo Add task which gets killed at some random time while letting the guest output something. + #]; + else: + atExec += [ + # Basic execution. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '-R', sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sFileForReading ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '--wrong-parameter' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/non/existent' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + [ tdTestExec(sCmd = sShell, asArgs = [ sShell, sShellOpt, 'wrongcommand' ]), + tdTestResultExec(fRc = True, iExitCode = 127) ], + # StdOut. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, 'stdout-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + # StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, 'stderr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + # StdOut + StdErr. + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, sSystemDir ]), + tdTestResultExec(fRc = True) ], + [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, 'stdouterr-non-existing' ]), + tdTestResultExec(fRc = True, iExitCode = 2) ], + ]; + # atExec.extend([ + # FIXME: Failing tests. + # Environment variables. + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_NONEXIST' ], + # tdTestResultExec(fRc = True, iExitCode = 1) ] + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'windir' ], + # + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'windir=C:\\WINDOWS\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ], + # [ tdTestExec(sCmd = sImageOut, asArgs = [ sImageOut, '/C', 'set', 'TEST_FOO' ], + # aEnv = [ 'TEST_FOO=BAR', 'TEST_BAZ=BAR' ], + # afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, vboxcon.ProcessCreateFlag_WaitForStdErr ]), + # tdTestResultExec(fRc = True, sBuf = 'TEST_FOO=BAR\r\n') ] + + ## @todo Create some files (or get files) we know the output size of to validate output length! + ## @todo Add task which gets killed at some random time while letting the guest output something. + #]; + + # + #for iExitCode in xrange(0, 127): + # atExec.append([ tdTestExec(sCmd = sShell, asArgs = [ sShell, sShellOpt, 'exit %s' % iExitCode ]), + # tdTestResultExec(fRc = True, iExitCode = iExitCode) ]); + + if sVBoxControl \ + and self.oTstDrv.fpApiVer >= 6.0: # Investigate with this doesn't work on (<) 5.2. + # Paths with spaces on windows. + atExec.append([ tdTestExec(sCmd = sVBoxControl, asArgs = [ sVBoxControl, 'version' ], + afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr ]), + tdTestResultExec(fRc = True) ]); + + # Test very long arguments. Be careful when tweaking this to not break the tests. + # Regarding paths: + # - We have RTPATH_BIG_MAX (64K) + # - MSDN says 32K for CreateFileW() + # - On Windows, each path component must not be longer than MAX_PATH (260), see + # https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits + # + # Old(er) Windows OSes tend to crash in cmd.exe, so skip this on these OSes. + if self.oTstDrv.fpApiVer >= 6.1 \ + and oTestVm.sKind not in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + sEndMarker = '--end-marker'; + if oTestVm.isWindows() \ + or oTestVm.isOS2(): + sCmd = sShell; + else: + sCmd = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'echo'); + + for _ in xrange(0, 16): + if oTestVm.isWindows() \ + or oTestVm.isOS2(): + asArgs = [ sShell, sShellOpt, "echo" ]; + else: + asArgs = [ sCmd ]; + + # Append a random number of arguments with random length. + for _ in xrange(0, self.oTestFiles.oRandom.randrange(1, 64)): + asArgs.append(''.join(random.choice(string.ascii_lowercase) + for _ in range(self.oTestFiles.oRandom.randrange(1, 196)))); + + asArgs.append(sEndMarker); + + reporter.log2('asArgs=%s (%d args), type=%s' % (limitString(asArgs), len(asArgs), type(asArgs))); + + ## @todo Check limits; on Ubuntu with 256KB IPRT returns VERR_NOT_IMPLEMENTED. + # Use a higher timeout (15 min) than usual for these long checks. + atExec.append([ tdTestExec(sCmd, asArgs, + afFlags = [ vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr ], + timeoutMS = 15 * 60 * 1000), + tdTestResultExec(fRc = True) ]); + + # Build up the final test array for the first batch. + atTests = atInvalid + atExec; + + # + # First batch: One session per guest process. + # + reporter.log('One session per guest process ...'); + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestExec + oCurRes = tTest[1] # type: tdTestResultExec + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc2, oCurGuestSession = oCurTest.createSession('testGuestCtrlExec: Test #%d' % (i,)); + if fRc2 is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + fRc = self.gctrlExecDoTest(i, oCurTest, oCurRes, oCurGuestSession) and fRc; + fRc = oCurTest.closeSession() and fRc; + + reporter.log('Execution of all tests done, checking for stale sessions'); + + # No sessions left? + try: + aSessions = self.oTstDrv.oVBoxMgr.getArray(oSession.o.console.guest, 'sessions'); + except: + fRc = reporter.errorXcpt(); + else: + cSessions = len(aSessions); + if cSessions != 0: + fRc = reporter.error('Found %d stale session(s), expected 0:' % (cSessions,)); + for (i, aSession) in enumerate(aSessions): + try: reporter.log(' Stale session #%d ("%s")' % (aSession.id, aSession.name)); + except: reporter.errorXcpt(); + + if fRc is not True: + return (fRc, oTxsSession); + + reporter.log('Now using one guest session for all tests ...'); + + # + # Second batch: One session for *all* guest processes. + # + + # Create session. + reporter.log('Creating session for all tests ...'); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + oGuest = oSession.o.console.guest; + oCurGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, + 'testGuestCtrlExec: One session for all tests'); + except: + return (reporter.errorXcpt(), oTxsSession); + + try: + eWaitResult = oCurGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + fRc = reporter.errorXcpt('Waiting for guest session to start failed:'); + else: + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + fRc = reporter.error('Session did not start successfully, returned wait result: %d' % (eWaitResult,)); + else: + reporter.log('Session successfully started'); + + # Do the tests within this session. + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestExec + oCurRes = tTest[1] # type: tdTestResultExec + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc = self.gctrlExecDoTest(i, oCurTest, oCurRes, oCurGuestSession); + if fRc is False: + break; + + # Close the session. + reporter.log2('Closing guest session ...'); + try: + oCurGuestSession.close(); + oCurGuestSession = None; + except: + fRc = reporter.errorXcpt('Closing guest session failed:'); + + # No sessions left? + reporter.log('Execution of all tests done, checking for stale sessions again'); + try: cSessions = len(self.oTstDrv.oVBoxMgr.getArray(oSession.o.console.guest, 'sessions')); + except: fRc = reporter.errorXcpt(); + else: + if cSessions != 0: + fRc = reporter.error('Found %d stale session(s), expected 0' % (cSessions,)); + return (fRc, oTxsSession); + + def threadForTestGuestCtrlSessionReboot(self, oGuestProcess): + """ + Thread routine which waits for the stale guest process getting terminated (or some error) + while the main test routine reboots the guest. It then compares the expected guest process result + and logs an error if appropriate. + """ + reporter.log('Waiting for process to get terminated at reboot ...'); + try: + eWaitResult = oGuestProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate ], 5 * 60 * 1000); + except: + return reporter.errorXcpt('waitForArray failed'); + try: + eStatus = oGuestProcess.status + except: + return reporter.errorXcpt('failed to get status (wait result %d)' % (eWaitResult,)); + + if eWaitResult == vboxcon.ProcessWaitResult_Terminate and eStatus == vboxcon.ProcessStatus_Down: + reporter.log('Stale process was correctly terminated (status: down)'); + return True; + + return reporter.error('Process wait across reboot failed: eWaitResult=%d, expected %d; eStatus=%d, expected %d' + % (eWaitResult, vboxcon.ProcessWaitResult_Terminate, eStatus, vboxcon.ProcessStatus_Down,)); + + def testGuestCtrlSessionReboot(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests guest object notifications when a guest gets rebooted / shutdown. + + These notifications gets sent from the guest sessions in order to make API clients + aware of guest session changes. + + To test that we create a stale guest process and trigger a reboot of the guest. + """ + + ## @todo backport fixes to 6.0 and maybe 5.2 + if self.oTstDrv.fpApiVer <= 6.0: + reporter.log('Skipping: Required fixes not yet backported!'); + return None; + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + fRebooted = False; + fRc = True; + + # + # Start a session. + # + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlSessionReboot"); + eWaitResult = oGuestSession.waitForArray(aeWaitFor, 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Create a process. + # + # That process will also be used later to see if the session cleanup worked upon reboot. + # + sImage = self.oTstDrv.getGuestSystemShell(oTestVm); + asArgs = [ sImage, ]; + aEnv = []; + afFlags = []; + try: + oGuestProcess = oGuestSession.processCreate(sImage, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], aEnv, afFlags, + 30 * 1000); + except: + fRc = reporter.error('Failed to start shell process (%s)' % (sImage,)); + else: + try: + eWaitResult = oGuestProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Start ], 30 * 1000); + except: + fRc = reporter.errorXcpt('Waiting for shell process (%s) to start failed' % (sImage,)); + else: + # Check the result and state: + try: eStatus = oGuestProcess.status; + except: fRc = reporter.errorXcpt('Waiting for shell process (%s) to start failed' % (sImage,)); + else: + reporter.log2('Starting process wait result returned: %d; Process status is: %d' % (eWaitResult, eStatus,)); + if eWaitResult != vboxcon.ProcessWaitResult_Start: + fRc = reporter.error('wait for ProcessWaitForFlag_Start failed: %d, expected %d (Start)' + % (eWaitResult, vboxcon.ProcessWaitResult_Start,)); + elif eStatus != vboxcon.ProcessStatus_Started: + fRc = reporter.error('Unexpected process status after startup: %d, wanted %d (Started)' + % (eStatus, vboxcon.ProcessStatus_Started,)); + else: + # Create a thread that waits on the process to terminate + reporter.log('Creating reboot thread ...'); + oThreadReboot = threading.Thread(target = self.threadForTestGuestCtrlSessionReboot, + args = (oGuestProcess,), + name = ('threadForTestGuestCtrlSessionReboot')); + oThreadReboot.setDaemon(True); # pylint: disable=deprecated-method + oThreadReboot.start(); + + # Do the reboot. + reporter.log('Rebooting guest and reconnecting TXS ...'); + (oSession, oTxsSession) = self.oTstDrv.txsRebootAndReconnectViaTcp(oSession, oTxsSession, + cMsTimeout = 3 * 60000); + if oSession \ + and oTxsSession: + # Set reboot flag (needed later for session closing). + fRebooted = True; + else: + reporter.error('Rebooting via TXS failed'); + try: oGuestProcess.terminate(); + except: reporter.logXcpt(); + fRc = False; + + reporter.log('Waiting for thread to finish ...'); + oThreadReboot.join(); + + # Check that the guest session now still has the formerly guest process object created above, + # but with the "down" status now (because of guest reboot). + try: + aoGuestProcs = self.oTstDrv.oVBoxMgr.getArray(oGuestSession, 'processes'); + if len(aoGuestProcs) == 1: + enmProcSts = aoGuestProcs[0].status; + if enmProcSts != vboxcon.ProcessStatus_Down: + fRc = reporter.error('Old guest process (before reboot) has status %d, expected %s' \ + % (enmProcSts, vboxcon.ProcessStatus_Down)); + else: + fRc = reporter.error('Old guest session (before reboot) has %d processes registered, expected 1' \ + % (len(aoGuestProcs))); + except: + fRc = reporter.errorXcpt(); + # + # Try make sure we don't leave with a stale process on failure. + # + try: oGuestProcess.terminate(); + except: reporter.logXcpt(); + + # + # Close the session. + # + reporter.log2('Closing guest session ...'); + try: + oGuestSession.close(); + except: + # Closing the guest session will fail when the guest reboot has been triggered, + # as the session object will be cleared on a guest reboot. + if fRebooted: + reporter.logXcpt('Closing guest session failed, good (guest rebooted)'); + else: # ... otherwise this (still) should succeed. Report so if it doesn't. + reporter.errorXcpt('Closing guest session failed'); + + return (fRc, oTxsSession); + + def testGuestCtrlExecTimeout(self, oSession, oTxsSession, oTestVm): + """ + Tests handling of timeouts of started guest processes. + """ + + sShell = self.oTstDrv.getGuestSystemShell(oTestVm); + + # Use credential defaults. + oCreds = tdCtxCreds(); + oCreds.applyDefaultsIfNotSet(oTestVm); + + # + # Create a session. + # + try: + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(oCreds.sUser, oCreds.sPassword, oCreds.sDomain, "testGuestCtrlExecTimeout"); + eWaitResult = oGuestSession.waitForArray([ vboxcon.GuestSessionWaitForFlag_Start, ], 30 * 1000); + except: + return (reporter.errorXcpt(), oTxsSession); + + # Be nice to Guest Additions < 4.3: They don't support session handling and therefore return WaitFlagNotSupported. + if eWaitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + return (reporter.error('Session did not start successfully - wait error: %d' % (eWaitResult,)), oTxsSession); + reporter.log('Session successfully started'); + + # + # Create a process which never terminates and should timeout when + # waiting for termination. + # + fRc = True; + try: + oCurProcess = oGuestSession.processCreate(sShell, [sShell,] if self.oTstDrv.fpApiVer >= 5.0 else [], + [], [], 30 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + reporter.log('Waiting for process 1 being started ...'); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Start ], 30 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + if eWaitResult != vboxcon.ProcessWaitResult_Start: + fRc = reporter.error('Waiting for process 1 to start failed, got status %d' % (eWaitResult,)); + else: + for msWait in (1, 32, 2000,): + reporter.log('Waiting for process 1 to time out within %sms ...' % (msWait,)); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate, ], msWait); + except: + fRc = reporter.errorXcpt(); + break; + if eWaitResult != vboxcon.ProcessWaitResult_Timeout: + fRc = reporter.error('Waiting for process 1 did not time out in %sms as expected: %d' + % (msWait, eWaitResult,)); + break; + reporter.log('Waiting for process 1 timed out in %u ms, good' % (msWait,)); + + try: + oCurProcess.terminate(); + except: + reporter.errorXcpt(); + oCurProcess = None; + + # + # Create another process that doesn't terminate, but which will be killed by VBoxService + # because it ran out of execution time (3 seconds). + # + try: + oCurProcess = oGuestSession.processCreate(sShell, [sShell,] if self.oTstDrv.fpApiVer >= 5.0 else [], + [], [], 3 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + reporter.log('Waiting for process 2 being started ...'); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Start ], 30 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + if eWaitResult != vboxcon.ProcessWaitResult_Start: + fRc = reporter.error('Waiting for process 2 to start failed, got status %d' % (eWaitResult,)); + else: + reporter.log('Waiting for process 2 to get killed for running out of execution time ...'); + try: + eWaitResult = oCurProcess.waitForArray([ vboxcon.ProcessWaitForFlag_Terminate, ], 15 * 1000); + except: + fRc = reporter.errorXcpt(); + else: + if eWaitResult != vboxcon.ProcessWaitResult_Timeout: + fRc = reporter.error('Waiting for process 2 did not time out when it should, got wait result %d' + % (eWaitResult,)); + else: + reporter.log('Waiting for process 2 did not time out, good: %s' % (eWaitResult,)); + try: + eStatus = oCurProcess.status; + except: + fRc = reporter.errorXcpt(); + else: + if eStatus != vboxcon.ProcessStatus_TimedOutKilled: + fRc = reporter.error('Status of process 2 wrong; excepted %d, got %d' + % (vboxcon.ProcessStatus_TimedOutKilled, eStatus)); + else: + reporter.log('Status of process 2 is TimedOutKilled (%d) is it should be.' + % (vboxcon.ProcessStatus_TimedOutKilled,)); + try: + oCurProcess.terminate(); + except: + reporter.logXcpt(); + oCurProcess = None; + + # + # Clean up the session. + # + try: + oGuestSession.close(); + except: + fRc = reporter.errorXcpt(); + + return (fRc, oTxsSession); + + def testGuestCtrlDirCreate(self, oSession, oTxsSession, oTestVm): + """ + Tests creation of guest directories. + """ + + sScratch = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'testGuestCtrlDirCreate'); + + atTests = [ + # Invalid stuff. + [ tdTestDirCreate(sDirectory = '' ), tdTestResultFailure() ], + # More unusual stuff. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin('..', '.') ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin('..', '..') ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '../' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '../../' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '/' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '/..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '/../' ), tdTestResultFailure() ], + ]; + if oTestVm.isWindows() or oTestVm.isOS2(): + atTests.extend([ + [ tdTestDirCreate(sDirectory = 'C:\\' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:\\..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:\\..\\' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/.' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/./' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/..' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = 'C:/../' ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = '\\\\uncrulez\\foo' ), tdTestResultFailure() ], + ]); + atTests.extend([ + # Existing directories and files. + [ tdTestDirCreate(sDirectory = self.oTstDrv.getGuestSystemDir(oTestVm) ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = self.oTstDrv.getGuestSystemShell(oTestVm) ), tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = self.oTstDrv.getGuestSystemFileForReading(oTestVm) ), tdTestResultFailure() ], + # Creating directories. + [ tdTestDirCreate(sDirectory = sScratch ), tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo', 'bar', 'baz'), + afFlags = (vboxcon.DirectoryCreateFlag_Parents,) ), tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo', 'bar', 'baz'), + afFlags = (vboxcon.DirectoryCreateFlag_Parents,) ), tdTestResultSuccess() ], + # Try format strings as directories. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo%sbar%sbaz%d' )), tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, '%f%%boo%%bar%RI32' )), tdTestResultSuccess() ], + # Long random names. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(36, 28))), + tdTestResultSuccess() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(140, 116))), + tdTestResultSuccess() ], + # Too long names. ASSUMES a guests has a 255 filename length limitation. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(2048, 256))), + tdTestResultFailure() ], + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, self.oTestFiles.generateFilenameEx(2048, 256))), + tdTestResultFailure() ], + # Missing directory in path. + [ tdTestDirCreate(sDirectory = oTestVm.pathJoin(sScratch, 'foo1', 'bar') ), tdTestResultFailure() ], + ]); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestDirCreate + oCurRes = tTest[1] # type: tdTestResult + reporter.log('Testing #%d, sDirectory="%s" ...' % (i, limitString(oCurTest.sDirectory))); + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirCreate: Test #%d' % (i,)); + if fRc is False: + return reporter.error('Test #%d failed: Could not create session' % (i,)); + + fRc = self.gctrlCreateDir(oCurTest, oCurRes, oCurGuestSession); + + fRc = oCurTest.closeSession() and fRc; + if fRc is False: + fRc = reporter.error('Test #%d failed' % (i,)); + + return (fRc, oTxsSession); + + def testGuestCtrlDirCreateTemp(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests creation of temporary directories. + """ + + sSystemDir = self.oTstDrv.getGuestSystemDir(oTestVm); + atTests = [ + # Invalid stuff (template must have one or more trailin 'X'es (upper case only), or a cluster of three or more). + [ tdTestDirCreateTemp(sDirectory = ''), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sDirectory = sSystemDir, fMode = 1234), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'xXx', sDirectory = sSystemDir, fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'xxx', sDirectory = sSystemDir, fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXx', sDirectory = sSystemDir, fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'bar', sDirectory = 'whatever', fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'foo', sDirectory = 'it is not used', fMode = 0o700), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'X,so', sDirectory = 'pointless test', fMode = 0o700), tdTestResultFailure() ], + # Non-existing stuff. + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', + sDirectory = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'non', 'existing')), + tdTestResultFailure() ], + # Working stuff: + [ tdTestDirCreateTemp(sTemplate = 'X', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm)), + tdTestResultFailure() ], + ]; + + if self.oTstDrv.fpApiVer >= 7.0: + # Weird mode set. + atTests.extend([ + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o42333), + tdTestResultFailure() ] + ]); + # Same as working stuff above, but with a different mode set. + atTests.extend([ + [ tdTestDirCreateTemp(sTemplate = 'X', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fMode = 0o777), + tdTestResultFailure() ] + ]); + # Same as working stuff above, but with secure mode set. + atTests.extend([ + [ tdTestDirCreateTemp(sTemplate = 'X', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'XXXXXXX', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), + fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), + fSecure = True), + tdTestResultFailure() ], + [ tdTestDirCreateTemp(sTemplate = 'tmpXXXtst', sDirectory = self.oTstDrv.getGuestTempDir(oTestVm), + fSecure = True), + tdTestResultFailure() ] + ]); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestDirCreateTemp + oCurRes = tTest[1] # type: tdTestResult + reporter.log('Testing #%d, sTemplate="%s", fMode=%#o, path="%s", secure="%s" ...' % + (i, oCurTest.sTemplate, oCurTest.fMode, oCurTest.sDirectory, oCurTest.fSecure)); + + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirCreateTemp: Test #%d' % (i,)); + if fRc is False: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + sDirTemp = ''; + try: + sDirTemp = oCurGuestSession.directoryCreateTemp(oCurTest.sTemplate, oCurTest.fMode, + oCurTest.sDirectory, oCurTest.fSecure); + except: + if oCurRes.fRc is True: + fRc = reporter.errorXcpt('Creating temp directory "%s" failed:' % (oCurTest.sDirectory,)); + else: + reporter.logXcpt('Creating temp directory "%s" failed expectedly, skipping:' % (oCurTest.sDirectory,)); + else: + reporter.log2('Temporary directory is: "%s"' % (limitString(sDirTemp),)); + if not sDirTemp: + fRc = reporter.error('Resulting directory is empty!'); + else: + ## @todo This does not work for some unknown reason. + #try: + # if self.oTstDrv.fpApiVer >= 5.0: + # fExists = oCurGuestSession.directoryExists(sDirTemp, False); + # else: + # fExists = oCurGuestSession.directoryExists(sDirTemp); + #except: + # fRc = reporter.errorXcpt('sDirTemp=%s' % (sDirTemp,)); + #else: + # if fExists is not True: + # fRc = reporter.error('Test #%d failed: Temporary directory "%s" does not exists (%s)' + # % (i, sDirTemp, fExists)); + try: + oFsObjInfo = oCurGuestSession.fsObjQueryInfo(sDirTemp, False); + eType = oFsObjInfo.type; + except: + fRc = reporter.errorXcpt('sDirTemp="%s"' % (sDirTemp,)); + else: + reporter.log2('%s: eType=%s (dir=%d)' % (limitString(sDirTemp), eType, vboxcon.FsObjType_Directory,)); + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('Temporary directory "%s" not created as a directory: eType=%d' + % (sDirTemp, eType)); + fRc = oCurTest.closeSession() and fRc; + return (fRc, oTxsSession); + + def testGuestCtrlDirRead(self, oSession, oTxsSession, oTestVm): + """ + Tests opening and reading (enumerating) guest directories. + """ + + sSystemDir = self.oTstDrv.getGuestSystemDir(oTestVm); + atTests = [ + # Invalid stuff. + [ tdTestDirRead(sDirectory = ''), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = sSystemDir, afFlags = [ 1234 ]), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = sSystemDir, sFilter = '*.foo'), tdTestResultDirRead() ], + # Non-existing stuff. + [ tdTestDirRead(sDirectory = oTestVm.pathJoin(sSystemDir, 'really-no-such-subdir')), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = oTestVm.pathJoin(sSystemDir, 'non', 'existing')), tdTestResultDirRead() ], + ]; + + if oTestVm.isWindows() or oTestVm.isOS2(): + atTests.extend([ + # More unusual stuff. + [ tdTestDirRead(sDirectory = 'z:\\'), tdTestResultDirRead() ], + [ tdTestDirRead(sDirectory = '\\\\uncrulez\\foo'), tdTestResultDirRead() ], + ]); + + # Read the system directory (ASSUMES at least 5 files in it): + # Windows 7+ has inaccessible system32/com/dmp directory that screws up this test, so skip it on windows: + if not oTestVm.isWindows(): + atTests.append([ tdTestDirRead(sDirectory = sSystemDir), + tdTestResultDirRead(fRc = True, cFiles = -5, cDirs = None) ]); + ## @todo trailing slash + + # Read from the test file set. + atTests.extend([ + [ tdTestDirRead(sDirectory = self.oTestFiles.oEmptyDir.sPath), + tdTestResultDirRead(fRc = True, cFiles = 0, cDirs = 0, cOthers = 0) ], + [ tdTestDirRead(sDirectory = self.oTestFiles.oManyDir.sPath), + tdTestResultDirRead(fRc = True, cFiles = len(self.oTestFiles.oManyDir.aoChildren), cDirs = 0, cOthers = 0) ], + [ tdTestDirRead(sDirectory = self.oTestFiles.oTreeDir.sPath), + tdTestResultDirRead(fRc = True, cFiles = self.oTestFiles.cTreeFiles, cDirs = self.oTestFiles.cTreeDirs, + cOthers = self.oTestFiles.cTreeOthers) ], + ]); + + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestExec + oCurRes = tTest[1] # type: tdTestResultDirRead + + reporter.log('Testing #%d, dir="%s" ...' % (i, oCurTest.sDirectory)); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirRead: Test #%d' % (i,)); + if fRc is not True: + break; + (fRc2, cDirs, cFiles, cOthers) = self.gctrlReadDirTree(oCurTest, oCurGuestSession, oCurRes.fRc); + fRc = oCurTest.closeSession() and fRc; + + reporter.log2('Test #%d: Returned %d directories, %d files total' % (i, cDirs, cFiles)); + if fRc2 is oCurRes.fRc: + if fRc2 is True: + if oCurRes.cFiles is None: + pass; # ignore + elif oCurRes.cFiles >= 0 and cFiles != oCurRes.cFiles: + fRc = reporter.error('Test #%d failed: Got %d files, expected %d' % (i, cFiles, oCurRes.cFiles)); + elif oCurRes.cFiles < 0 and cFiles < -oCurRes.cFiles: + fRc = reporter.error('Test #%d failed: Got %d files, expected at least %d' + % (i, cFiles, -oCurRes.cFiles)); + if oCurRes.cDirs is None: + pass; # ignore + elif oCurRes.cDirs >= 0 and cDirs != oCurRes.cDirs: + fRc = reporter.error('Test #%d failed: Got %d directories, expected %d' % (i, cDirs, oCurRes.cDirs)); + elif oCurRes.cDirs < 0 and cDirs < -oCurRes.cDirs: + fRc = reporter.error('Test #%d failed: Got %d directories, expected at least %d' + % (i, cDirs, -oCurRes.cDirs)); + if oCurRes.cOthers is None: + pass; # ignore + elif oCurRes.cOthers >= 0 and cOthers != oCurRes.cOthers: + fRc = reporter.error('Test #%d failed: Got %d other types, expected %d' % (i, cOthers, oCurRes.cOthers)); + elif oCurRes.cOthers < 0 and cOthers < -oCurRes.cOthers: + fRc = reporter.error('Test #%d failed: Got %d other types, expected at least %d' + % (i, cOthers, -oCurRes.cOthers)); + + else: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc)); + + + # + # Go over a few directories in the test file set and compare names, + # types and sizes rather than just the counts like we did above. + # + if fRc is True: + oCurTest = tdTestDirRead(); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if fRc: + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlDirRead: gctrlReadDirTree2'); + if fRc is True: + for oDir in (self.oTestFiles.oEmptyDir, self.oTestFiles.oManyDir, self.oTestFiles.oTreeDir): + reporter.log('Checking "%s" ...' % (oDir.sPath,)); + fRc = self.gctrlReadDirTree2(oCurGuestSession, oDir) and fRc; + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileRemove(self, oSession, oTxsSession, oTestVm): + """ + Tests removing guest files. + """ + + # + # Create a directory with a few files in it using TXS that we'll use for the initial tests. + # + asTestDirs = [ + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-1'), # [0] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-1', 'subdir-1'), # [1] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-1', 'subdir-1', 'subsubdir-1'), # [2] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-2'), # [3] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-2', 'subdir-2'), # [4] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-2', 'subdir-2', 'subsbudir-2'), # [5] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-3'), # [6] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-4'), # [7] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-5'), # [8] + oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'rmtestdir-5', 'subdir-5'), # [9] + ] + asTestFiles = [ + oTestVm.pathJoin(asTestDirs[0], 'file-0'), # [0] + oTestVm.pathJoin(asTestDirs[0], 'file-1'), # [1] + oTestVm.pathJoin(asTestDirs[0], 'file-2'), # [2] + oTestVm.pathJoin(asTestDirs[1], 'file-3'), # [3] - subdir-1 + oTestVm.pathJoin(asTestDirs[1], 'file-4'), # [4] - subdir-1 + oTestVm.pathJoin(asTestDirs[2], 'file-5'), # [5] - subsubdir-1 + oTestVm.pathJoin(asTestDirs[3], 'file-6'), # [6] - rmtestdir-2 + oTestVm.pathJoin(asTestDirs[4], 'file-7'), # [7] - subdir-2 + oTestVm.pathJoin(asTestDirs[5], 'file-8'), # [8] - subsubdir-2 + ]; + for sDir in asTestDirs: + if oTxsSession.syncMkDir(sDir, 0o777) is not True: + return reporter.error('Failed to create test dir "%s"!' % (sDir,)); + for sFile in asTestFiles: + if oTxsSession.syncUploadString(sFile, sFile, 0o666) is not True: + return reporter.error('Failed to create test file "%s"!' % (sFile,)); + + # + # Tear down the directories and files. + # + aoTests = [ + # Negative tests first: + tdTestRemoveFile(asTestDirs[0], fRcExpect = False), + tdTestRemoveDir(asTestDirs[0], fRcExpect = False), + tdTestRemoveDir(asTestFiles[0], fRcExpect = False), + tdTestRemoveFile(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-file'), fRcExpect = False), + tdTestRemoveDir(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-dir'), fRcExpect = False), + tdTestRemoveFile(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-dir', 'no-file'), fRcExpect = False), + tdTestRemoveDir(oTestVm.pathJoin(self.oTestFiles.oEmptyDir.sPath, 'no-such-dir', 'no-subdir'), fRcExpect = False), + tdTestRemoveTree(asTestDirs[0], afFlags = [], fRcExpect = False), # Only removes empty dirs, this isn't empty. + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_None,], fRcExpect = False), # ditto + # Empty paths: + tdTestRemoveFile('', fRcExpect = False), + tdTestRemoveDir('', fRcExpect = False), + tdTestRemoveTree('', fRcExpect = False), + # Now actually remove stuff: + tdTestRemoveDir(asTestDirs[7], fRcExpect = True), + tdTestRemoveFile(asTestDirs[6], fRcExpect = False), + tdTestRemoveDir(asTestDirs[6], fRcExpect = True), + tdTestRemoveFile(asTestFiles[0], fRcExpect = True), + tdTestRemoveFile(asTestFiles[0], fRcExpect = False), + # 17: + tdTestRemoveTree(asTestDirs[8], fRcExpect = True), # Removes empty subdirs and leaves the dir itself. + tdTestRemoveDir(asTestDirs[8], fRcExpect = True), + tdTestRemoveTree(asTestDirs[3], fRcExpect = False), # Have subdirs & files, + tdTestRemoveTree(asTestDirs[3], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentOnly,], fRcExpect = True), + tdTestRemoveDir(asTestDirs[3], fRcExpect = True), + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentAndDir,], fRcExpect = True), + # No error if already delete (RTDirRemoveRecursive artifact). + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentAndDir,], fRcExpect = True), + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentOnly,], + fNotExist = True, fRcExpect = True), + tdTestRemoveTree(asTestDirs[0], afFlags = [vboxcon.DirectoryRemoveRecFlag_None,], fNotExist = True, fRcExpect = True), + ]; + + # + # Execution loop + # + fRc = True; + for (i, oTest) in enumerate(aoTests): # int, tdTestRemoveBase + reporter.log('Testing #%d, path="%s" %s ...' % (i, oTest.sPath, oTest.__class__.__name__)); + fRc = oTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oTest.createSession('testGuestCtrlFileRemove: Test #%d' % (i,)); + if fRc is False: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + fRc = oTest.execute(self) and fRc; + fRc = oTest.closeSession() and fRc; + + if fRc is True: + oCurTest = tdTestDirRead(); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if fRc: + fRc, oCurGuestSession = oCurTest.createSession('remove final'); + if fRc is True: + + # + # Delete all the files in the many subdir of the test set. + # + reporter.log('Deleting the file in "%s" ...' % (self.oTestFiles.oManyDir.sPath,)); + for oFile in self.oTestFiles.oManyDir.aoChildren: + reporter.log2('"%s"' % (limitString(oFile.sPath),)); + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurGuestSession.fsObjRemove(oFile.sPath); + else: + oCurGuestSession.fileRemove(oFile.sPath); + except: + fRc = reporter.errorXcpt('Removing "%s" failed' % (oFile.sPath,)); + + # Remove the directory itself to verify that we've removed all the files in it: + reporter.log('Removing the directory "%s" ...' % (self.oTestFiles.oManyDir.sPath,)); + try: + oCurGuestSession.directoryRemove(self.oTestFiles.oManyDir.sPath); + except: + fRc = reporter.errorXcpt('Removing directory "%s" failed' % (self.oTestFiles.oManyDir.sPath,)); + + # + # Recursively delete the entire test file tree from the root up. + # + # Note! On unix we cannot delete the root dir itself since it is residing + # in /var/tmp where only the owner may delete it. Root is the owner. + # + if oTestVm.isWindows() or oTestVm.isOS2(): + afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentAndDir,]; + else: + afFlags = [vboxcon.DirectoryRemoveRecFlag_ContentOnly,]; + try: + oProgress = oCurGuestSession.directoryRemoveRecursive(self.oTestFiles.oRoot.sPath, afFlags); + except: + fRc = reporter.errorXcpt('Removing tree "%s" failed' % (self.oTestFiles.oRoot.sPath,)); + else: + oWrappedProgress = vboxwrappers.ProgressWrapper(oProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, + "remove-tree-root: %s" % (self.oTestFiles.oRoot.sPath,)); + reporter.log2('waiting ...') + oWrappedProgress.wait(); + reporter.log2('isSuccess=%s' % (oWrappedProgress.isSuccess(),)); + if not oWrappedProgress.isSuccess(): + fRc = oWrappedProgress.logResult(); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileStat(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests querying file information through stat. + """ + + # Basic stuff, existing stuff. + aoTests = [ + tdTestSessionEx([ + tdStepStatDir('.'), + tdStepStatDir('..'), + tdStepStatDir(self.oTstDrv.getGuestTempDir(oTestVm)), + tdStepStatDir(self.oTstDrv.getGuestSystemDir(oTestVm)), + tdStepStatDirEx(self.oTestFiles.oRoot), + tdStepStatDirEx(self.oTestFiles.oEmptyDir), + tdStepStatDirEx(self.oTestFiles.oTreeDir), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatDirEx(self.oTestFiles.chooseRandomDirFromTree()), + tdStepStatFile(self.oTstDrv.getGuestSystemFileForReading(oTestVm)), + tdStepStatFile(self.oTstDrv.getGuestSystemShell(oTestVm)), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + tdStepStatFileEx(self.oTestFiles.chooseRandomFile()), + ]), + ]; + + # None existing stuff. + sSysDir = self.oTstDrv.getGuestSystemDir(oTestVm); + sSep = oTestVm.pathSep(); + aoTests += [ + tdTestSessionEx([ + tdStepStatFileNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory')), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory') + sSep), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', '.')), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', 'NoSuchFileOrSubDirectory')), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', 'NoSuchFileOrSubDirectory') + sSep), + tdStepStatPathNotFound(oTestVm.pathJoin(sSysDir, 'NoSuchFileOrDirectory', 'NoSuchFileOrSubDirectory', '.')), + #tdStepStatPathNotFound('N:\\'), # ASSUMES nothing mounted on N:! + #tdStepStatPathNotFound('\\\\NoSuchUncServerName\\NoSuchShare'), + ]), + ]; + # Invalid parameter check. + aoTests += [ tdTestSessionEx([ tdStepStat('', vbox.ComError.E_INVALIDARG), ]), ]; + + # + # Execute the tests. + # + fRc, oTxsSession = tdTestSessionEx.executeListTestSessions(aoTests, self.oTstDrv, oSession, oTxsSession, + oTestVm, 'FsStat'); + # + # Test the full test file set. + # + if self.oTstDrv.fpApiVer < 5.0: + return (fRc, oTxsSession); + + oTest = tdTestGuestCtrlBase(); + fRc = oTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + return (False, oTxsSession); + fRc2, oGuestSession = oTest.createSession('FsStat on TestFileSet'); + if fRc2 is not True: + return (False, oTxsSession); + + for oFsObj in self.oTestFiles.dPaths.values(): + reporter.log2('testGuestCtrlFileStat: %s sPath=%s' + % ('file' if isinstance(oFsObj, testfileset.TestFile) else 'dir ', limitString(oFsObj.sPath),)); + + # Query the information: + try: + oFsInfo = oGuestSession.fsObjQueryInfo(oFsObj.sPath, False); + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: fsObjQueryInfo trouble!' % (oFsObj.sPath, type(oFsObj),)); + continue; + if oFsInfo is None: + fRc = reporter.error('sPath=%s type=%s: No info object returned!' % (oFsObj.sPath, type(oFsObj),)); + continue; + + # Check attributes: + try: + eType = oFsInfo.type; + cbObject = oFsInfo.objectSize; + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: attribute access trouble!' % (oFsObj.sPath, type(oFsObj),)); + continue; + + if isinstance(oFsObj, testfileset.TestFile): + if eType != vboxcon.FsObjType_File: + fRc = reporter.error('sPath=%s type=file: eType=%s, expected %s!' + % (oFsObj.sPath, eType, vboxcon.FsObjType_File)); + if cbObject != oFsObj.cbContent: + fRc = reporter.error('sPath=%s type=file: cbObject=%s, expected %s!' + % (oFsObj.sPath, cbObject, oFsObj.cbContent)); + fFileExists = True; + fDirExists = False; + elif isinstance(oFsObj, testfileset.TestDir): + if eType != vboxcon.FsObjType_Directory: + fRc = reporter.error('sPath=%s type=dir: eType=%s, expected %s!' + % (oFsObj.sPath, eType, vboxcon.FsObjType_Directory)); + fFileExists = False; + fDirExists = True; + else: + fRc = reporter.error('sPath=%s type=%s: Unexpected oFsObj type!' % (oFsObj.sPath, type(oFsObj),)); + continue; + + # Check the directoryExists and fileExists results too. + try: + fExistsResult = oGuestSession.fileExists(oFsObj.sPath, False); + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: fileExists trouble!' % (oFsObj.sPath, type(oFsObj),)); + else: + if fExistsResult != fFileExists: + fRc = reporter.error('sPath=%s type=%s: fileExists returned %s, expected %s!' + % (oFsObj.sPath, type(oFsObj), fExistsResult, fFileExists)); + try: + fExistsResult = oGuestSession.directoryExists(oFsObj.sPath, False); + except: + fRc = reporter.errorXcpt('sPath=%s type=%s: directoryExists trouble!' % (oFsObj.sPath, type(oFsObj),)); + else: + if fExistsResult != fDirExists: + fRc = reporter.error('sPath=%s type=%s: directoryExists returned %s, expected %s!' + % (oFsObj.sPath, type(oFsObj), fExistsResult, fDirExists)); + + fRc = oTest.closeSession() and fRc; + return (fRc, oTxsSession); + + def testGuestCtrlFileOpen(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests opening guest files. + """ + if self.oTstDrv.fpApiVer < 5.0: + reporter.log('Skipping because of pre 5.0 API'); + return None; + + # + # Paths. + # + sTempDir = self.oTstDrv.getGuestTempDir(oTestVm); + sFileForReading = self.oTstDrv.getGuestSystemFileForReading(oTestVm); + asFiles = [ + oTestVm.pathJoin(sTempDir, 'file-open-0'), + oTestVm.pathJoin(sTempDir, 'file-open-1'), + oTestVm.pathJoin(sTempDir, 'file-open-2'), + oTestVm.pathJoin(sTempDir, 'file-open-3'), + oTestVm.pathJoin(sTempDir, 'file-open-4'), + ]; + asNonEmptyFiles = [ + oTestVm.pathJoin(sTempDir, 'file-open-10'), + oTestVm.pathJoin(sTempDir, 'file-open-11'), + oTestVm.pathJoin(sTempDir, 'file-open-12'), + oTestVm.pathJoin(sTempDir, 'file-open-13'), + ]; + sContent = 'abcdefghijklmnopqrstuvwxyz0123456789'; + for sFile in asNonEmptyFiles: + if oTxsSession.syncUploadString(sContent, sFile, 0o666) is not True: + return reporter.error('Failed to create "%s" via TXS' % (sFile,)); + + # + # The tests. + # + atTests = [ + # Invalid stuff. + [ tdTestFileOpen(sFile = ''), tdTestResultFailure() ], + # Wrong open mode. + [ tdTestFileOpen(sFile = sFileForReading, eAccessMode = -1), tdTestResultFailure() ], + # Wrong disposition. + [ tdTestFileOpen(sFile = sFileForReading, eAction = -1), tdTestResultFailure() ], + # Non-existing file or path. + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir')), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir'), + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir'), + eAccessMode = vboxcon.FileAccessMode_WriteOnly, + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-file-or-dir'), + eAccessMode = vboxcon.FileAccessMode_ReadWrite, + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = oTestVm.pathJoin(sTempDir, 'no-such-dir', 'no-such-file')), tdTestResultFailure() ], + ]; + if self.oTstDrv.fpApiVer > 5.2: # Fixed since 6.0. + atTests.extend([ + # Wrong type: + [ tdTestFileOpen(sFile = self.oTstDrv.getGuestTempDir(oTestVm)), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = self.oTstDrv.getGuestSystemDir(oTestVm)), tdTestResultFailure() ], + ]); + atTests.extend([ + # O_EXCL and such: + [ tdTestFileOpen(sFile = sFileForReading, eAction = vboxcon.FileOpenAction_CreateNew, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultFailure() ], + [ tdTestFileOpen(sFile = sFileForReading, eAction = vboxcon.FileOpenAction_CreateNew), tdTestResultFailure() ], + # Open a file. + [ tdTestFileOpen(sFile = sFileForReading), tdTestResultSuccess() ], + [ tdTestFileOpen(sFile = sFileForReading, + eAction = vboxcon.FileOpenAction_OpenOrCreate), tdTestResultSuccess() ], + # Create a new file. + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_CreateNew, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_CreateNew, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultFailure() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_OpenExisting, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_CreateOrReplace, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_OpenExistingTruncated, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[0], eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + # Open or create a new file. + [ tdTestFileOpenCheckSize(sFile = asFiles[1], eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + # Create or replace a new file. + [ tdTestFileOpenCheckSize(sFile = asFiles[2], eAction = vboxcon.FileOpenAction_CreateOrReplace, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + # Create and append to file (weird stuff). + [ tdTestFileOpenCheckSize(sFile = asFiles[3], eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asFiles[4], eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + # Open the non-empty files in non-destructive modes. + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], cbOpenExpected = len(sContent)), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], cbOpenExpected = len(sContent), + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], cbOpenExpected = len(sContent), + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_OpenOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_ReadWrite), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], cbOpenExpected = len(sContent), + eAction = vboxcon.FileOpenAction_AppendOrCreate, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + + # Now the destructive stuff: + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[0], eAccessMode = vboxcon.FileAccessMode_WriteOnly, + eAction = vboxcon.FileOpenAction_OpenExistingTruncated), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[1], eAccessMode = vboxcon.FileAccessMode_WriteOnly, + eAction = vboxcon.FileOpenAction_CreateOrReplace), tdTestResultSuccess() ], + [ tdTestFileOpenCheckSize(sFile = asNonEmptyFiles[2], eAction = vboxcon.FileOpenAction_CreateOrReplace, + eAccessMode = vboxcon.FileAccessMode_WriteOnly), tdTestResultSuccess() ], + ]); + + # + # Do the testing. + # + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestFileOpen + oCurRes = tTest[1] # type: tdTestResult + + reporter.log('Testing #%d: %s - sFile="%s", eAccessMode=%d, eAction=%d, (%s, %s, %s) ...' + % (i, oCurTest.__class__.__name__, oCurTest.sFile, oCurTest.eAccessMode, oCurTest.eAction, + oCurTest.eSharing, oCurTest.fCreationMode, oCurTest.afOpenFlags,)); + + oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oCurTest.createSession('testGuestCtrlFileOpen: Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + fRc2 = oCurTest.doSteps(oCurRes.fRc, self); + if fRc2 != oCurRes.fRc: + fRc = reporter.error('Test #%d result mismatch: Got %s, expected %s' % (i, fRc2, oCurRes.fRc,)); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileRead(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-branches,too-many-statements + """ + Tests reading from guest files. + """ + if self.oTstDrv.fpApiVer < 5.0: + reporter.log('Skipping because of pre 5.0 API'); + return None; + + # + # Do everything in one session. + # + oTest = tdTestGuestCtrlBase(); + fRc = oTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + return (False, oTxsSession); + fRc2, oGuestSession = oTest.createSession('FsStat on TestFileSet'); + if fRc2 is not True: + return (False, oTxsSession); + + # + # Create a really big zero filled, up to 1 GiB, adding it to the list of + # files from the set. + # + # Note! This code sucks a bit because we don't have a working setSize nor + # any way to figure out how much free space there is in the guest. + # + aoExtraFiles = []; + sBigName = self.oTestFiles.generateFilenameEx(); + sBigPath = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, sBigName); + fRc = True; + try: + oFile = oGuestSession.fileOpenEx(sBigPath, vboxcon.FileAccessMode_ReadWrite, vboxcon.FileOpenAction_CreateOrReplace, + vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('sBigName=%s' % (sBigPath,)); + else: + # Does setSize work now? + fUseFallback = True; + try: + oFile.setSize(0); + oFile.setSize(64); + fUseFallback = False; + except: + reporter.logXcpt(); + + # Grow the file till we hit trouble, typical VERR_DISK_FULL, then + # reduce the file size if we have a working setSize. + cbBigFile = 0; + while cbBigFile < (1024 + 32)*1024*1024: + if not fUseFallback: + cbBigFile += 16*1024*1024; + try: + oFile.setSize(cbBigFile); + except Exception: + reporter.logXcpt('cbBigFile=%s' % (sBigPath,)); + try: + cbBigFile -= 16*1024*1024; + oFile.setSize(cbBigFile); + except: + reporter.logXcpt('cbBigFile=%s' % (sBigPath,)); + break; + else: + cbBigFile += 32*1024*1024; + try: + oFile.seek(cbBigFile, vboxcon.FileSeekOrigin_Begin); + oFile.write(bytearray(1), 60*1000); + except: + reporter.logXcpt('cbBigFile=%s' % (sBigPath,)); + break; + try: + cbBigFile = oFile.seek(0, vboxcon.FileSeekOrigin_End); + except: + fRc = reporter.errorXcpt('sBigName=%s' % (sBigPath,)); + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('sBigName=%s' % (sBigPath,)); + if fRc is True: + reporter.log('Big file: %s bytes: %s' % (cbBigFile, sBigPath,)); + aoExtraFiles.append(testfileset.TestFileZeroFilled(None, sBigPath, cbBigFile)); + else: + try: + oGuestSession.fsObjRemove(sBigPath); + except: + reporter.errorXcpt('fsObjRemove(sBigName=%s)' % (sBigPath,)); + + # + # Open and read all the files in the test file set. + # + for oTestFile in aoExtraFiles + self.oTestFiles.aoFiles: # type: testfileset.TestFile + reporter.log2('Test file: %s bytes, "%s" ...' % (oTestFile.cbContent, limitString(oTestFile.sPath),)); + + # + # Open it: + # + try: + oFile = oGuestSession.fileOpenEx(oTestFile.sPath, vboxcon.FileAccessMode_ReadOnly, + vboxcon.FileOpenAction_OpenExisting, vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('sPath=%s' % (oTestFile.sPath, )); + continue; + + # + # Read the file in different sized chunks: + # + if oTestFile.cbContent < 128: + acbChunks = xrange(1,128); + elif oTestFile.cbContent < 1024: + acbChunks = (2048, 127, 63, 32, 29, 17, 16, 15, 9); + elif oTestFile.cbContent < 8*1024*1024: + acbChunks = (128*1024, 32*1024, 8191, 255); + else: + acbChunks = (768*1024, 128*1024); + + reporter.log2('Chunked reads'); + + for cbChunk in acbChunks: + # Read the whole file straight thru: + #if oTestFile.cbContent >= 1024*1024: reporter.log2('... cbChunk=%s' % (cbChunk,)); + offFile = 0; + cReads = 0; + while offFile <= oTestFile.cbContent: + try: + abRead = oFile.read(cbChunk, 30*1000); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbChunk=%s cbContent=%s' + % (oTestFile.sPath, offFile, cbChunk, oTestFile.cbContent)); + break; + cbRead = len(abRead); + if cbRead == 0 and offFile == oTestFile.cbContent: + break; + if cbRead <= 0: + fRc = reporter.error('%s @%s: cbRead=%s, cbContent=%s' + % (oTestFile.sPath, offFile, cbRead, oTestFile.cbContent)); + break; + if not oTestFile.equalMemory(abRead, offFile): + fRc = reporter.error('%s: read mismatch @ %s LB %s' % (oTestFile.sPath, offFile, cbRead)); + break; + offFile += cbRead; + cReads += 1; + if cReads > 8192: + break; + + # Seek to start of file. + try: + offFile = oFile.seek(0, vboxcon.FileSeekOrigin_Begin); + except: + fRc = reporter.errorXcpt('%s: error seeking to start of file' % (oTestFile.sPath,)); + break; + if offFile != 0: + fRc = reporter.error('%s: seek to start of file returned %u, expected 0' % (oTestFile.sPath, offFile)); + break; + + # + # Random reads. + # + reporter.log2('Random reads (seek)'); + for _ in xrange(8): + offFile = self.oTestFiles.oRandom.randrange(0, oTestFile.cbContent + 1024); + cbToRead = self.oTestFiles.oRandom.randrange(1, min(oTestFile.cbContent + 256, 768*1024)); + #if oTestFile.cbContent >= 1024*1024: reporter.log2('... %s LB %s' % (offFile, cbToRead,)); + + try: + offActual = oFile.seek(offFile, vboxcon.FileSeekOrigin_Begin); + except: + fRc = reporter.errorXcpt('%s: error seeking to %s' % (oTestFile.sPath, offFile)); + break; + if offActual != offFile: + fRc = reporter.error('%s: seek(%s,Begin) -> %s, expected %s' + % (oTestFile.sPath, offFile, offActual, offFile)); + break; + + try: + abRead = oFile.read(cbToRead, 30*1000); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + cbRead = 0; + else: + cbRead = len(abRead); + if not oTestFile.equalMemory(abRead, offFile): + fRc = reporter.error('%s: random read mismatch @ %s LB %s' % (oTestFile.sPath, offFile, cbRead,)); + + try: + offActual = oFile.offset; + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#1)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: IFile.offset is %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#1)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + try: + offActual = oFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#1)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: seek(0,cur) -> %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#1)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + + # + # Random reads using readAt. + # + reporter.log2('Random reads (readAt)'); + for _ in xrange(12): + offFile = self.oTestFiles.oRandom.randrange(0, oTestFile.cbContent + 1024); + cbToRead = self.oTestFiles.oRandom.randrange(1, min(oTestFile.cbContent + 256, 768*1024)); + #if oTestFile.cbContent >= 1024*1024: reporter.log2('... %s LB %s (readAt)' % (offFile, cbToRead,)); + + try: + abRead = oFile.readAt(offFile, cbToRead, 30*1000); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + cbRead = 0; + else: + cbRead = len(abRead); + if not oTestFile.equalMemory(abRead, offFile): + fRc = reporter.error('%s: random readAt mismatch @ %s LB %s' % (oTestFile.sPath, offFile, cbRead,)); + + try: + offActual = oFile.offset; + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#2)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: IFile.offset is %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#2)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + + try: + offActual = oFile.seek(0, vboxcon.FileSeekOrigin_Current); + except: + fRc = reporter.errorXcpt('%s: offFile=%s cbToRead=%s cbContent=%s (#2)' + % (oTestFile.sPath, offFile, cbToRead, oTestFile.cbContent)); + else: + if offActual != offFile + cbRead: + fRc = reporter.error('%s: seek(0,cur) -> %s, expected %s (offFile=%s cbToRead=%s cbRead=%s) (#2)' + % (oTestFile.sPath, offActual, offFile + cbRead, offFile, cbToRead, cbRead)); + + # + # A few negative things. + # + + # Zero byte reads -> E_INVALIDARG. + reporter.log2('Zero byte reads'); + try: + abRead = oFile.read(0, 30*1000); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.E_INVALIDARG): + fRc = reporter.errorXcpt('read(0,30s) did not raise E_INVALIDARG as expected!'); + else: + fRc = reporter.error('read(0,30s) did not fail!'); + + try: + abRead = oFile.readAt(0, 0, 30*1000); + except Exception as oXcpt: + if vbox.ComError.notEqual(oXcpt, vbox.ComError.E_INVALIDARG): + fRc = reporter.errorXcpt('readAt(0,0,30s) did not raise E_INVALIDARG as expected!'); + else: + fRc = reporter.error('readAt(0,0,30s) did not fail!'); + + # See what happens when we read 1GiB. We should get a max of 1MiB back. + ## @todo Document this behaviour in VirtualBox.xidl. + reporter.log2('1GB reads'); + try: + oFile.seek(0, vboxcon.FileSeekOrigin_Begin); + except: + fRc = reporter.error('seek(0)'); + try: + abRead = oFile.read(1024*1024*1024, 30*1000); + except: + fRc = reporter.errorXcpt('read(1GiB,30s)'); + else: + if len(abRead) != min(oTestFile.cbContent, 1024*1024): + fRc = reporter.error('Expected read(1GiB,30s) to return %s bytes, got %s bytes instead' + % (min(oTestFile.cbContent, 1024*1024), len(abRead),)); + + try: + abRead = oFile.readAt(0, 1024*1024*1024, 30*1000); + except: + fRc = reporter.errorXcpt('readAt(0,1GiB,30s)'); + else: + if len(abRead) != min(oTestFile.cbContent, 1024*1024): + reporter.error('Expected readAt(0, 1GiB,30s) to return %s bytes, got %s bytes instead' + % (min(oTestFile.cbContent, 1024*1024), len(abRead),)); + + # + # Check stat info on the file as well as querySize. + # + if self.oTstDrv.fpApiVer > 5.2: + try: + oFsObjInfo = oFile.queryInfo(); + except: + fRc = reporter.errorXcpt('%s: queryInfo()' % (oTestFile.sPath,)); + else: + if oFsObjInfo is None: + fRc = reporter.error('IGuestFile::queryInfo returned None'); + else: + try: + cbFile = oFsObjInfo.objectSize; + except: + fRc = reporter.errorXcpt(); + else: + if cbFile != oTestFile.cbContent: + fRc = reporter.error('%s: queryInfo returned incorrect file size: %s, expected %s' + % (oTestFile.sPath, cbFile, oTestFile.cbContent)); + + try: + cbFile = oFile.querySize(); + except: + fRc = reporter.errorXcpt('%s: querySize()' % (oTestFile.sPath,)); + else: + if cbFile != oTestFile.cbContent: + fRc = reporter.error('%s: querySize returned incorrect file size: %s, expected %s' + % (oTestFile.sPath, cbFile, oTestFile.cbContent)); + + # + # Use seek to test the file size and do a few other end-relative seeks. + # + try: + cbFile = oFile.seek(0, vboxcon.FileSeekOrigin_End); + except: + fRc = reporter.errorXcpt('%s: seek(0,End)' % (oTestFile.sPath,)); + else: + if cbFile != oTestFile.cbContent: + fRc = reporter.error('%s: seek(0,End) returned incorrect file size: %s, expected %s' + % (oTestFile.sPath, cbFile, oTestFile.cbContent)); + if oTestFile.cbContent > 0: + for _ in xrange(5): + offSeek = self.oTestFiles.oRandom.randrange(oTestFile.cbContent + 1); + try: + offFile = oFile.seek(-offSeek, vboxcon.FileSeekOrigin_End); + except: + fRc = reporter.errorXcpt('%s: seek(%s,End)' % (oTestFile.sPath, -offSeek,)); + else: + if offFile != oTestFile.cbContent - offSeek: + fRc = reporter.error('%s: seek(%s,End) returned incorrect offset: %s, expected %s (cbContent=%s)' + % (oTestFile.sPath, -offSeek, offSeek, oTestFile.cbContent - offSeek, + oTestFile.cbContent,)); + + # + # Close it and we're done with this file. + # + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('%s: error closing the file' % (oTestFile.sPath,)); + + # + # Clean up. + # + for oTestFile in aoExtraFiles: + try: + oGuestSession.fsObjRemove(sBigPath); + except: + fRc = reporter.errorXcpt('fsObjRemove(%s)' % (sBigPath,)); + + fRc = oTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + + def testGuestCtrlFileWrite(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests writing to guest files. + """ + if self.oTstDrv.fpApiVer < 5.0: + reporter.log('Skipping because of pre 5.0 API'); + return None; + + # + # The test file and its content. + # + sFile = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'gctrl-write-1'); + abContent = bytearray(0); + + # + # The tests. + # + def randBytes(cbHowMany): + """ Returns an bytearray of random bytes. """ + return bytearray(self.oTestFiles.oRandom.getrandbits(8) for _ in xrange(cbHowMany)); + + aoTests = [ + # Write at end: + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_CreateNew, abContent = abContent, + atChunks = [(None, randBytes(1)), (None, randBytes(77)), (None, randBytes(98)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 1+77+98), # 176 + # Appending: + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_AppendOrCreate, abContent = abContent, + atChunks = [(None, randBytes(255)), (None, randBytes(33)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 176 + 255+33), # 464 + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_AppendOrCreate, abContent = abContent, + atChunks = [(10, randBytes(44)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 464 + 44), # 508 + # Write within existing: + tdTestFileOpenAndWrite(sFile = sFile, eAction = vboxcon.FileOpenAction_OpenExisting, abContent = abContent, + atChunks = [(0, randBytes(1)), (50, randBytes(77)), (255, randBytes(199)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 508), + # Writing around and over the end: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, + atChunks = [(500, randBytes(9)), (508, randBytes(15)), (512, randBytes(12)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 512+12), + + # writeAt appending: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, + atChunks = [(0, randBytes(23)), (6, randBytes(1018)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 6+1018), # 1024 + # writeAt within existing: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, + atChunks = [(1000, randBytes(23)), (1, randBytes(990)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 1024), + # writeAt around and over the end: + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, + atChunks = [(1024, randBytes(63)), (1080, randBytes(968)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 1080+968), # 2048 + + # writeAt beyond the end (gap is filled with zeros): + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, fUseAtApi = True, atChunks = [(3070, randBytes(2)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 3072), + # write beyond the end (gap is filled with zeros): + tdTestFileOpenAndWrite(sFile = sFile, abContent = abContent, atChunks = [(4090, randBytes(6)),]), + tdTestFileOpenAndCheckContent(sFile = sFile, abContent = abContent, cbContentExpected = 4096), + ]; + + for (i, oCurTest) in enumerate(aoTests): + reporter.log('Testing #%d: %s ...' % (i, oCurTest.toString(),)); + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oCurTest.createSession('testGuestCtrlFileWrite: Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + fRc2 = oCurTest.doSteps(True, self); + if fRc2 is not True: + fRc = reporter.error('Test #%d failed!' % (i,)); + + fRc = oCurTest.closeSession() and fRc; + + # + # Cleanup + # + if oTxsSession.syncRmFile(sFile) is not True: + fRc = reporter.error('Failed to remove write-test file: %s' % (sFile, )); + + return (fRc, oTxsSession); + + @staticmethod + def __generateFile(sName, cbFile): + """ Helper for generating a file with a given size. """ + with open(sName, 'wb') as oFile: + while cbFile > 0: + cb = cbFile if cbFile < 256*1024 else 256*1024; + oFile.write(bytearray(random.getrandbits(8) for _ in xrange(cb))); + cbFile -= cb; + return True; + + def testGuestCtrlCopyTo(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests copying files from host to the guest. + """ + + # + # Paths and test files. + # + sScratchHst = os.path.join(self.oTstDrv.sScratchPath, 'copyto'); + sScratchTestFilesHst = os.path.join(sScratchHst, self.oTestFiles.sSubDir); + sScratchEmptyDirHst = os.path.join(sScratchTestFilesHst, self.oTestFiles.oEmptyDir.sName); + sScratchNonEmptyDirHst = self.oTestFiles.chooseRandomDirFromTree().buildPath(sScratchHst, os.path.sep); + sScratchTreeDirHst = os.path.join(sScratchTestFilesHst, self.oTestFiles.oTreeDir.sName); + + sScratchGst = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'copyto'); + sScratchDstDir1Gst = oTestVm.pathJoin(sScratchGst, 'dstdir1'); + sScratchDstDir2Gst = oTestVm.pathJoin(sScratchGst, 'dstdir2'); + sScratchDstDir3Gst = oTestVm.pathJoin(sScratchGst, 'dstdir3'); + sScratchDstDir4Gst = oTestVm.pathJoin(sScratchGst, 'dstdir4'); + sScratchDotDotDirGst = oTestVm.pathJoin(sScratchGst, '..'); + #sScratchGstNotExist = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'no-such-file-or-directory'); + sScratchHstNotExist = os.path.join(self.oTstDrv.sScratchPath, 'no-such-file-or-directory'); + sScratchGstPathNotFound = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'no-such-directory', 'or-file'); + #sScratchHstPathNotFound = os.path.join(self.oTstDrv.sScratchPath, 'no-such-directory', 'or-file'); + + if oTestVm.isWindows() or oTestVm.isOS2(): + sScratchGstInvalid = "?*|<invalid-name>"; + else: + sScratchGstInvalid = None; + if utils.getHostOs() in ('win', 'os2'): + sScratchHstInvalid = "?*|<invalid-name>"; + else: + sScratchHstInvalid = None; + + for sDir in (sScratchGst, sScratchDstDir1Gst, sScratchDstDir2Gst, sScratchDstDir3Gst, sScratchDstDir4Gst): + if oTxsSession.syncMkDir(sDir, 0o777) is not True: + return reporter.error('TXS failed to create directory "%s"!' % (sDir,)); + + # Put the test file set under sScratchHst. + if os.path.exists(sScratchHst): + if base.wipeDirectory(sScratchHst) != 0: + return reporter.error('Failed to wipe "%s"' % (sScratchHst,)); + else: + try: + os.mkdir(sScratchHst); + except: + return reporter.errorXcpt('os.mkdir(%s)' % (sScratchHst, )); + if self.oTestFiles.writeToDisk(sScratchHst) is not True: + return reporter.error('Filed to write test files to "%s" on the host!' % (sScratchHst,)); + + # If for whatever reason the directory tree does not exist on the host, let us know. + # Copying an non-existing tree *will* fail the tests which otherwise should succeed! + assert os.path.exists(sScratchTreeDirHst); + + # Generate a test file in 32MB to 64 MB range. + sBigFileHst = os.path.join(self.oTstDrv.sScratchPath, 'gctrl-random.data'); + cbBigFileHst = random.randrange(32*1024*1024, 64*1024*1024); + reporter.log('cbBigFileHst=%s' % (cbBigFileHst,)); + cbLeft = cbBigFileHst; + try: + self.__generateFile(sBigFileHst, cbBigFileHst); + except: + return reporter.errorXcpt('sBigFileHst=%s cbBigFileHst=%s cbLeft=%s' % (sBigFileHst, cbBigFileHst, cbLeft,)); + reporter.log('cbBigFileHst=%s' % (cbBigFileHst,)); + + # Generate an empty file on the host that we can use to save space in the guest. + sEmptyFileHst = os.path.join(self.oTstDrv.sScratchPath, 'gctrl-empty.data'); + try: + open(sEmptyFileHst, "wb").close(); # pylint: disable=consider-using-with + except: + return reporter.errorXcpt('sEmptyFileHst=%s' % (sEmptyFileHst,)); + + # os.path.join() is too clever for "..", so we just build up the path here ourselves. + sScratchDotDotFileHst = sScratchHst + os.path.sep + '..' + os.path.sep + 'gctrl-empty.data'; + + # + # Tests. + # + atTests = [ + # Nothing given: + [ tdTestCopyToFile(), tdTestResultFailure() ], + [ tdTestCopyToDir(), tdTestResultFailure() ], + # Only source given: + [ tdTestCopyToFile(sSrc = sBigFileHst), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst), tdTestResultFailure() ], + # Only destination given: + [ tdTestCopyToFile(sDst = oTestVm.pathJoin(sScratchGst, 'dstfile')), tdTestResultFailure() ], + [ tdTestCopyToDir( sDst = sScratchGst), tdTestResultFailure() ], + # Both given, but invalid flags. + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGst, afFlags = [ 0x40000000, ] ), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst, sDst = sScratchGst, afFlags = [ 0x40000000, ] ), + tdTestResultFailure() ], + ]; + atTests.extend([ + # Non-existing source, but no destination: + [ tdTestCopyToFile(sSrc = sScratchHstNotExist), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchHstNotExist), tdTestResultFailure() ], + # Valid sources, but destination path not found: + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGstPathNotFound), tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst, sDst = sScratchGstPathNotFound), tdTestResultFailure() ], + # Valid destination, but source file/dir not found: + [ tdTestCopyToFile(sSrc = sScratchHstNotExist, sDst = oTestVm.pathJoin(sScratchGst, 'dstfile')), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchHstNotExist, sDst = sScratchGst), tdTestResultFailure() ], + # Wrong type: + [ tdTestCopyToFile(sSrc = sScratchEmptyDirHst, sDst = oTestVm.pathJoin(sScratchGst, 'dstfile')), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sBigFileHst, sDst = sScratchGst), tdTestResultFailure() ], + ]); + # Invalid characters in destination or source path: + if sScratchGstInvalid is not None: + atTests.extend([ + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, sScratchGstInvalid)), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = sScratchEmptyDirHst, sDst = oTestVm.pathJoin(sScratchGst, sScratchGstInvalid)), + tdTestResultFailure() ], + ]); + if sScratchHstInvalid is not None: + atTests.extend([ + [ tdTestCopyToFile(sSrc = os.path.join(self.oTstDrv.sScratchPath, sScratchHstInvalid), sDst = sScratchGst), + tdTestResultFailure() ], + [ tdTestCopyToDir( sSrc = os.path.join(self.oTstDrv.sScratchPath, sScratchHstInvalid), sDst = sScratchGst), + tdTestResultFailure() ], + ]); + + # + # Single file handling. + # + atTests.extend([ + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat')), + tdTestResultSuccess() ], + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat')), # Overwrite + tdTestResultSuccess() ], + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat')), # Overwrite + tdTestResultSuccess() ], + ]); + if self.oTstDrv.fpApiVer > 5.2: # Copying files into directories via Main is supported only 6.0 and later. + atTests.extend([ + # Should succeed, as the file isn't there yet on the destination. + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGst + oTestVm.pathSep()), tdTestResultSuccess() ], + # Overwrite the existing file. + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = sScratchGst + oTestVm.pathSep()), tdTestResultSuccess() ], + # Same file, but with a different name on the destination. + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = oTestVm.pathJoin(sScratchGst, os.path.split(sBigFileHst)[1])), + tdTestResultSuccess() ], # Overwrite + ]); + + if oTestVm.isWindows(): + # Copy to a Windows alternative data stream (ADS). + atTests.extend([ + [ tdTestCopyToFile(sSrc = sBigFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat:ADS-Test')), + tdTestResultSuccess() ], + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = oTestVm.pathJoin(sScratchGst, 'HostGABig.dat:ADS-Test')), + tdTestResultSuccess() ], + ]); + + # + # Directory handling. + # + if self.oTstDrv.fpApiVer > 5.2: # Copying directories via Main is supported only in versions > 5.2. + atTests.extend([ + # Without a trailing slash added to the destination this should fail, + # as the destination directory already exists. + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir1Gst), tdTestResultFailure() ], + # Same existing host directory, but this time with DirectoryCopyFlag_CopyIntoExisting set. + # This should copy the contents of oEmptyDirGst to sScratchDstDir1Gst (empty, but anyway). + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir1Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Try again. + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir1Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # With a trailing slash added to the destination, copy the empty guest directory + # (should end up as sScratchDstDir2Gst/empty): + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir2Gst + oTestVm.pathSep()), + tdTestResultSuccess() ], + # Repeat -- this time it should fail, as the destination directory already exists (and + # DirectoryCopyFlag_CopyIntoExisting is not specified): + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir2Gst + oTestVm.pathSep()), + tdTestResultFailure() ], + # Add the DirectoryCopyFlag_CopyIntoExisting flag being set and it should work (again). + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = sScratchDstDir2Gst + oTestVm.pathSep(), + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Copy with a different destination name just for the heck of it: + [ tdTestCopyToDir(sSrc = sScratchEmptyDirHst, sDst = oTestVm.pathJoin(sScratchDstDir2Gst, 'empty2')), + tdTestResultSuccess() ], + ]); + atTests.extend([ + # Now the same using a directory with files in it: + [ tdTestCopyToDir(sSrc = sScratchNonEmptyDirHst, sDst = sScratchDstDir3Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Again. + [ tdTestCopyToDir(sSrc = sScratchNonEmptyDirHst, sDst = sScratchDstDir3Gst, + afFlags = [vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + atTests.extend([ + # Copy the entire test tree: + [ tdTestCopyToDir(sSrc = sScratchTreeDirHst, sDst = sScratchDstDir4Gst + oTestVm.pathSep()), + tdTestResultSuccess() ], + # Again, should fail this time. + [ tdTestCopyToDir(sSrc = sScratchTreeDirHst, sDst = sScratchDstDir4Gst + oTestVm.pathSep()), + tdTestResultFailure() ], + # Works again, as DirectoryCopyFlag_CopyIntoExisting is specified. + [ tdTestCopyToDir(sSrc = sScratchTreeDirHst, sDst = sScratchDstDir4Gst + oTestVm.pathSep(), + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + # + # Dotdot path handling. + # + if self.oTstDrv.fpApiVer >= 6.1: + atTests.extend([ + # Test if copying stuff from a host dotdot ".." directory works. + [ tdTestCopyToFile(sSrc = sScratchDotDotFileHst, sDst = sScratchDstDir1Gst + oTestVm.pathSep()), + tdTestResultSuccess() ], + # Test if copying stuff from the host to a guest's dotdot ".." directory works. + # That should fail on destinations. + [ tdTestCopyToFile(sSrc = sEmptyFileHst, sDst = sScratchDotDotDirGst), tdTestResultFailure() ], + ]); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0]; # tdTestCopyTo + oCurRes = tTest[1]; # tdTestResult + reporter.log('Testing #%d, sSrc=%s, sDst=%s, afFlags=%s ...' + % (i, limitString(oCurTest.sSrc), limitString(oCurTest.sDst), oCurTest.afFlags)); + + oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, oCurGuestSession = oCurTest.createSession('testGuestCtrlCopyTo: Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + fRc2 = False; + if isinstance(oCurTest, tdTestCopyToFile): + fRc2 = self.gctrlCopyFileTo(oCurGuestSession, oCurTest.sSrc, oCurTest.sDst, oCurTest.afFlags, oCurRes.fRc); + else: + fRc2 = self.gctrlCopyDirTo(oCurGuestSession, oCurTest.sSrc, oCurTest.sDst, oCurTest.afFlags, oCurRes.fRc); + if fRc2 is not oCurRes.fRc: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc)); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + def testGuestCtrlCopyFrom(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests copying files from guest to the host. + """ + + reporter.log2('Entered'); + + # + # Paths. + # + sScratchHst = os.path.join(self.oTstDrv.sScratchPath, "testGctrlCopyFrom"); + sScratchDstDir1Hst = os.path.join(sScratchHst, "dstdir1"); + sScratchDstDir2Hst = os.path.join(sScratchHst, "dstdir2"); + sScratchDstDir3Hst = os.path.join(sScratchHst, "dstdir3"); + sScratchDstDir4Hst = os.path.join(sScratchHst, "dstdir4"); + # os.path.join() is too clever for "..", so we just build up the path here ourselves. + sScratchDotDotDirHst = sScratchHst + os.path.sep + '..' + os.path.sep; + oExistingFileGst = self.oTestFiles.chooseRandomFile(); + oNonEmptyDirGst = self.oTestFiles.chooseRandomDirFromTree(fNonEmpty = True); + oTreeDirGst = self.oTestFiles.oTreeDir; + oEmptyDirGst = self.oTestFiles.oEmptyDir; + + if oTestVm.isWindows() or oTestVm.isOS2(): + sScratchGstInvalid = "?*|<invalid-name>"; + else: + sScratchGstInvalid = None; + if utils.getHostOs() in ('win', 'os2'): + sScratchHstInvalid = "?*|<invalid-name>"; + else: + sScratchHstInvalid = None; + + sScratchDotDotDirGst = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), '..'); + + if os.path.exists(sScratchHst): + if base.wipeDirectory(sScratchHst) != 0: + return reporter.error('Failed to wipe "%s"' % (sScratchHst,)); + else: + try: + os.mkdir(sScratchHst); + except: + return reporter.errorXcpt('os.mkdir(%s)' % (sScratchHst, )); + + reporter.log2('Creating host sub dirs ...'); + + for sSubDir in (sScratchDstDir1Hst, sScratchDstDir2Hst, sScratchDstDir3Hst, sScratchDstDir4Hst): + try: + os.mkdir(sSubDir); + except: + return reporter.errorXcpt('os.mkdir(%s)' % (sSubDir, )); + + reporter.log2('Defining tests ...'); + + # + # Bad parameter tests. + # + atTests = [ + # Missing both source and destination: + [ tdTestCopyFromFile(), tdTestResultFailure() ], + [ tdTestCopyFromDir(), tdTestResultFailure() ], + # Missing source. + [ tdTestCopyFromFile(sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sDst = sScratchHst), tdTestResultFailure() ], + # Missing destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = self.oTestFiles.oManyDir.sPath), tdTestResultFailure() ], + # Invalid flags: + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'somefile'), afFlags = [0x40000000]), + tdTestResultFailure() ], + [ tdTestCopyFromDir( oSrc = oEmptyDirGst, sDst = os.path.join(sScratchHst, 'somedir'), afFlags = [ 0x40000000] ), + tdTestResultFailure() ], + # Non-existing sources: + [ tdTestCopyFromFile(sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-file-or-directory'), + sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-file-or-directory'), + sDst = os.path.join(sScratchHst, 'somedir')), tdTestResultFailure() ], + [ tdTestCopyFromFile(sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-directory', 'no-such-file'), + sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, 'no-such-directory', 'no-such-subdir'), + sDst = os.path.join(sScratchHst, 'somedir')), tdTestResultFailure() ], + # Non-existing destinations: + [ tdTestCopyFromFile(oSrc = oExistingFileGst, + sDst = os.path.join(sScratchHst, 'no-such-directory', 'somefile') ), tdTestResultFailure() ], + [ tdTestCopyFromDir( oSrc = oEmptyDirGst, sDst = os.path.join(sScratchHst, 'no-such-directory', 'somedir') ), + tdTestResultFailure() ], + [ tdTestCopyFromFile(oSrc = oExistingFileGst, + sDst = os.path.join(sScratchHst, 'no-such-directory-slash' + os.path.sep)), + tdTestResultFailure() ], + # Wrong source type: + [ tdTestCopyFromFile(oSrc = oNonEmptyDirGst, sDst = os.path.join(sScratchHst, 'somefile') ), tdTestResultFailure() ], + [ tdTestCopyFromDir(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'somedir') ), tdTestResultFailure() ], + ]; + # Bogus names: + if sScratchHstInvalid: + atTests.extend([ + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, sScratchHstInvalid)), + tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = self.oTestFiles.oManyDir.sPath, sDst = os.path.join(sScratchHst, sScratchHstInvalid)), + tdTestResultFailure() ], + ]); + if sScratchGstInvalid: + atTests.extend([ + [ tdTestCopyFromFile(sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, sScratchGstInvalid), + sDst = os.path.join(sScratchHst, 'somefile')), tdTestResultFailure() ], + [ tdTestCopyFromDir( sSrc = oTestVm.pathJoin(self.oTestFiles.oRoot.sPath, sScratchGstInvalid), + sDst = os.path.join(sScratchHst, 'somedir')), tdTestResultFailure() ], + ]); + + # + # Single file copying. + # + atTests.extend([ + # Should succeed, as the file isn't there yet on the destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'copyfile1')), tdTestResultSuccess() ], + # Overwrite the existing file. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'copyfile1')), tdTestResultSuccess() ], + # Same file, but with a different name on the destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = os.path.join(sScratchHst, 'copyfile2')), tdTestResultSuccess() ], + ]); + + if self.oTstDrv.fpApiVer > 5.2: # Copying files into directories via Main is supported only 6.0 and later. + # Copy into a directory. + atTests.extend([ + # This should fail, as sScratchHst exists and is a directory. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchHst), tdTestResultFailure() ], + # Same existing host directory, but this time with a trailing slash. + # This should succeed, as the file isn't there yet on the destination. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchHst + os.path.sep), tdTestResultSuccess() ], + # Overwrite the existing file. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchHst + os.path.sep), tdTestResultSuccess() ], + ]); + + # + # Directory handling. + # + if self.oTstDrv.fpApiVer > 5.2: # Copying directories via Main is supported only in versions > 5.2. + atTests.extend([ + # Without a trailing slash added to the destination this should fail, + # as the destination directory already exist. + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = sScratchDstDir1Hst), tdTestResultFailure() ], + # Same existing host directory, but this time with DirectoryCopyFlag_CopyIntoExisting set. + # This should copy the contents of oEmptyDirGst to sScratchDstDir1Hst (empty, but anyway). + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = sScratchDstDir1Hst, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Try again. + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = sScratchDstDir1Hst, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # With a trailing slash added to the destination, copy the empty guest directory + # (should end up as sScratchHst/empty): + [ tdTestCopyFromDir(oSrc = oEmptyDirGst, sDst = sScratchDstDir2Hst + os.path.sep), tdTestResultSuccess() ], + # Repeat -- this time it should fail, as the destination directory already exists (and + # DirectoryCopyFlag_CopyIntoExisting is not specified): + [ tdTestCopyFromDir(oSrc = oEmptyDirGst, sDst = sScratchDstDir2Hst + os.path.sep), tdTestResultFailure() ], + # Add the DirectoryCopyFlag_CopyIntoExisting flag being set and it should work (again). + [ tdTestCopyFromDir(oSrc = oEmptyDirGst, sDst = sScratchDstDir2Hst + os.path.sep, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + # Copy with a different destination name just for the heck of it: + [ tdTestCopyFromDir(sSrc = oEmptyDirGst.sPath, sDst = os.path.join(sScratchDstDir2Hst, 'empty2'), + fIntoDst = True), + tdTestResultSuccess() ], + ]); + atTests.extend([ + # Now the same using a directory with files in it: + [ tdTestCopyFromDir(oSrc = oNonEmptyDirGst, sDst = sScratchDstDir3Hst + os.path.sep), tdTestResultSuccess() ], + # Again. + [ tdTestCopyFromDir(oSrc = oNonEmptyDirGst, sDst = sScratchDstDir3Hst, fIntoDst = True, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + atTests.extend([ + # Copy the entire test tree: + [ tdTestCopyFromDir(oSrc = oTreeDirGst, sDst = sScratchDstDir4Hst + os.path.sep), tdTestResultSuccess() ], + # Again, should fail this time. + [ tdTestCopyFromDir(oSrc = oTreeDirGst, sDst = sScratchDstDir4Hst + os.path.sep), tdTestResultFailure() ], + # Works again, as DirectoryCopyFlag_CopyIntoExisting is specified. + [ tdTestCopyFromDir(oSrc = oTreeDirGst, sDst = sScratchDstDir4Hst + os.path.sep, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), tdTestResultSuccess() ], + ]); + # + # Dotdot path handling. + # + if self.oTstDrv.fpApiVer >= 6.1: + atTests.extend([ + # Test if copying stuff from a guest dotdot ".." directory works. + [ tdTestCopyFromDir(sSrc = sScratchDotDotDirGst, sDst = sScratchDstDir1Hst + os.path.sep, + afFlags = [ vboxcon.DirectoryCopyFlag_CopyIntoExisting, ]), + tdTestResultFailure() ], + # Test if copying stuff from the guest to a host's dotdot ".." directory works. + # That should fail on destinations. + [ tdTestCopyFromFile(oSrc = oExistingFileGst, sDst = sScratchDotDotDirHst), tdTestResultFailure() ], + ]); + + reporter.log2('Executing tests ...'); + + # + # Execute the tests. + # + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] + oCurRes = tTest[1] # type: tdTestResult + if isinstance(oCurTest, tdTestCopyFrom): + reporter.log('Testing #%d, %s: sSrc="%s", sDst="%s", afFlags="%s" ...' + % (i, "directory" if isinstance(oCurTest, tdTestCopyFromDir) else "file", + limitString(oCurTest.sSrc), limitString(oCurTest.sDst), oCurTest.afFlags,)); + else: + reporter.log('Testing #%d, tdTestRemoveHostDir "%s" ...' % (i, oCurTest.sDir,)); + if isinstance(oCurTest, tdTestCopyFromDir) and self.oTstDrv.fpApiVer < 6.0: + reporter.log('Skipping directoryCopyFromGuest test, not implemented in %s' % (self.oTstDrv.fpApiVer,)); + continue; + + if isinstance(oCurTest, tdTestRemoveHostDir): + fRc = oCurTest.execute(self.oTstDrv, oSession, oTxsSession, oTestVm, 'testing #%d' % (i,)); + else: + fRc = oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc2, oCurGuestSession = oCurTest.createSession('testGuestCtrlCopyFrom: Test #%d' % (i,)); + if fRc2 is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + if isinstance(oCurTest, tdTestCopyFromFile): + fRc2 = self.gctrlCopyFileFrom(oCurGuestSession, oCurTest, oCurRes.fRc); + else: + fRc2 = self.gctrlCopyDirFrom(oCurGuestSession, oCurTest, oCurRes.fRc); + + if fRc2 != oCurRes.fRc: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc2, oCurRes.fRc)); + + fRc = oCurTest.closeSession() and fRc; + + return (fRc, oTxsSession); + + def testGuestCtrlUpdateAdditions(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals + """ + Tests updating the Guest Additions inside the guest. + + """ + + ## @todo currently disabled everywhere. + if self.oTstDrv.fpApiVer < 100.0: + reporter.log("Skipping updating GAs everywhere for now..."); + return None; + + # Skip test for updating Guest Additions if we run on a too old (Windows) guest. + ## + ## @todo make it work everywhere! + ## + if oTestVm.sKind in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + reporter.log("Skipping updating GAs on old windows vm (sKind=%s)" % (oTestVm.sKind,)); + return (None, oTxsSession); + if oTestVm.isOS2(): + reporter.log("Skipping updating GAs on OS/2 guest"); + return (None, oTxsSession); + + sVBoxValidationKitIso = self.oTstDrv.sVBoxValidationKitIso; + if not os.path.isfile(sVBoxValidationKitIso): + return reporter.log('Validation Kit .ISO not found at "%s"' % (sVBoxValidationKitIso,)); + + sScratch = os.path.join(self.oTstDrv.sScratchPath, "testGctrlUpdateAdditions"); + try: + os.makedirs(sScratch); + except OSError as e: + if e.errno != errno.EEXIST: + return reporter.error('Failed: Unable to create scratch directory \"%s\"' % (sScratch,)); + reporter.log('Scratch path is: %s' % (sScratch,)); + + atTests = []; + if oTestVm.isWindows(): + atTests.extend([ + # Source is missing. + [ tdTestUpdateAdditions(sSrc = ''), tdTestResultFailure() ], + + # Wrong flags. + [ tdTestUpdateAdditions(sSrc = self.oTstDrv.getGuestAdditionsIso(), + afFlags = [ 1234 ]), tdTestResultFailure() ], + + # Non-existing .ISO. + [ tdTestUpdateAdditions(sSrc = "non-existing.iso"), tdTestResultFailure() ], + + # Wrong .ISO. + [ tdTestUpdateAdditions(sSrc = sVBoxValidationKitIso), tdTestResultFailure() ], + + # The real thing. + [ tdTestUpdateAdditions(sSrc = self.oTstDrv.getGuestAdditionsIso()), + tdTestResultSuccess() ], + # Test the (optional) installer arguments. This will extract the + # installer into our guest's scratch directory. + [ tdTestUpdateAdditions(sSrc = self.oTstDrv.getGuestAdditionsIso(), + asArgs = [ '/extract', '/D=' + sScratch ]), + tdTestResultSuccess() ] + # Some debg ISO. Only enable locally. + #[ tdTestUpdateAdditions( + # sSrc = "V:\\Downloads\\VBoxGuestAdditions-r80354.iso"), + # tdTestResultSuccess() ] + ]); + else: + reporter.log('No OS-specific tests for non-Windows yet!'); + + fRc = True; + for (i, tTest) in enumerate(atTests): + oCurTest = tTest[0] # type: tdTestUpdateAdditions + oCurRes = tTest[1] # type: tdTestResult + reporter.log('Testing #%d, sSrc="%s", afFlags="%s" ...' % (i, oCurTest.sSrc, oCurTest.afFlags,)); + + oCurTest.setEnvironment(oSession, oTxsSession, oTestVm); + if not fRc: + break; + fRc, _ = oCurTest.createSession('Test #%d' % (i,)); + if fRc is not True: + fRc = reporter.error('Test #%d failed: Could not create session' % (i,)); + break; + + try: + oCurProgress = oCurTest.oGuest.updateGuestAdditions(oCurTest.sSrc, oCurTest.asArgs, oCurTest.afFlags); + except: + reporter.maybeErrXcpt(oCurRes.fRc, 'Updating Guest Additions exception for sSrc="%s", afFlags="%s":' + % (oCurTest.sSrc, oCurTest.afFlags,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "gctrlUpGA"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = not oCurRes.fRc); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + + oCurTest.closeSession(); + if fRc is oCurRes.fRc: + if fRc: + ## @todo Verify if Guest Additions were really updated (build, revision, ...). + ## @todo r=bird: Not possible since you're installing the same GAs as before... + ## Maybe check creation dates on certain .sys/.dll/.exe files? + pass; + else: + fRc = reporter.error('Test #%d failed: Got %s, expected %s' % (i, fRc, oCurRes.fRc)); + break; + + return (fRc, oTxsSession); + + + +class tdAddGuestCtrl(vbox.TestDriver): # pylint: disable=too-many-instance-attributes,too-many-public-methods + """ + Guest control using VBoxService on the guest. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + self.asRsrcs = None; + self.fQuick = False; # Don't skip lengthly tests by default. + self.addSubTestDriver(SubTstDrvAddGuestCtrl(self)); + + # + # Overridden methods. + # + def showUsage(self): + """ + Shows the testdriver usage. + """ + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAddGuestCtrl Options:'); + reporter.log(' --quick'); + reporter.log(' Same as --virt-modes hwvirt --cpu-counts 1.'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + """ + Parses the testdriver arguments from the command line. + """ + if asArgs[iArg] == '--quick': + self.parseOption(['--virt-modes', 'hwvirt'], 0); + self.parseOption(['--cpu-counts', '1'], 0); + self.fQuick = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + sGaIso = self.getGuestAdditionsIso(); + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, sDvdImage = sGaIso); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + # + # Test execution helpers. + # + def testOneCfg(self, oVM, oTestVm): # pylint: disable=too-many-statements + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + + self.logVmInfo(oVM); + + fRc = True; + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = False); + reporter.log("TxsSession: %s" % (oTxsSession,)); + if oSession is not None: + fRc, oTxsSession = self.aoSubTstDrvs[0].testIt(oTestVm, oSession, oTxsSession); + self.terminateVmBySession(oSession); + else: + fRc = False; + return fRc; + + def onExit(self, iRc): + return vbox.TestDriver.onExit(self, iRc); + + def gctrlReportError(self, progress): + """ + Helper function to report an error of a + given progress object. + """ + if progress is None: + reporter.log('No progress object to print error for'); + else: + errInfo = progress.errorInfo; + if errInfo: + reporter.log('%s' % (errInfo.text,)); + return False; + + def gctrlGetRemainingTime(self, msTimeout, msStart): + """ + Helper function to return the remaining time (in ms) + based from a timeout value and the start time (both in ms). + """ + if msTimeout == 0: + return 0xFFFFFFFE; # Wait forever. + msElapsed = base.timestampMilli() - msStart; + if msElapsed > msTimeout: + return 0; # No time left. + return msTimeout - msElapsed; + + def testGuestCtrlManual(self, oSession, oTxsSession, oTestVm): # pylint: disable=too-many-locals,too-many-statements,unused-argument,unused-variable + """ + For manually testing certain bits. + """ + + reporter.log('Manual testing ...'); + fRc = True; + + sUser = 'Administrator'; + sPassword = 'password'; + + oGuest = oSession.o.console.guest; + oGuestSession = oGuest.createSession(sUser, + sPassword, + "", "Manual Test"); + + aWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start ]; + _ = oGuestSession.waitForArray(aWaitFor, 30 * 1000); + + sCmd = self.getGuestSystemShell(oTestVm); + asArgs = [ sCmd, '/C', 'dir', '/S', 'c:\\windows' ]; + aEnv = []; + afFlags = []; + + for _ in xrange(100): + oProc = oGuestSession.processCreate(sCmd, asArgs if self.fpApiVer >= 5.0 else asArgs[1:], + aEnv, afFlags, 30 * 1000); + + aWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate ]; + _ = oProc.waitForArray(aWaitFor, 30 * 1000); + + oGuestSession.close(); + oGuestSession = None; + + time.sleep(5); + + oSession.o.console.PowerDown(); + + return (fRc, oTxsSession); + +if __name__ == '__main__': + sys.exit(tdAddGuestCtrl().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py b/src/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py new file mode 100755 index 00000000..c50ee4d3 --- /dev/null +++ b/src/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +VirtualBox Validation Kit - Shared Folders #1. +""" + +__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: 155244 $" + +# Standard Python imports. +import os +import shutil +import sys + +# 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 testdriver import reporter; +from testdriver import base; +from common import utils; + + +class SubTstDrvAddSharedFolders1(base.SubTestDriverBase): + """ + Sub-test driver for executing shared folders tests. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'add-shared-folders', 'Shared Folders'); + + self.asTestsDef = [ 'fsperf', ]; + self.asTests = self.asTestsDef; + self.asExtraArgs = []; + self.asGstFsPerfPaths = [ + '${CDROM}/vboxvalidationkit/${OS/ARCH}/FsPerf${EXESUFF}', + '${CDROM}/${OS/ARCH}/FsPerf${EXESUFF}', + '${TXSDIR}/${OS/ARCH}/FsPerf${EXESUFF}', + '${TXSDIR}/FsPerf${EXESUFF}', + 'E:/vboxvalidationkit/${OS/ARCH}/FsPerf${EXESUFF}', + ]; + self.sGuestSlash = ''; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--add-shared-folders-tests': # 'add' as in 'additions', not the verb. + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + if asArgs[iArg] == 'all': + self.asTests = self.asTestsDef; + else: + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--add-shared-folders-tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef))); + return iNext; + if asArgs[iArg] == '--add-shared-folders-extra-arg': + iArg += 1; + iNext = self.oTstDrv.requireMoreArgs(1, asArgs, iArg); + self.asExtraArgs.append(asArgs[iArg]); + return iNext; + return iArg; + + def showUsage(self): + base.SubTestDriverBase.showUsage(self); + reporter.log(' --add-shared-folders-tests <t1[:t2[:]]>'); + reporter.log(' Default: all (%s)' % (':'.join(self.asTestsDef))); + reporter.log(' --add-shared-folders-extra-arg <fsperf-arg>'); + reporter.log(' Adds an extra FsPerf argument. Can be repeated.'); + + return True; + + def mountShareEx(self, oSession, oTxsSession, sShareName, sHostPath, sGuestMountPoint, fMustSucceed): + """ + Automount a shared folder in the guest, extended version. + + Returns success status, based on fMustSucceed. + """ + reporter.testStart('Automounting "%s"' % (sShareName,)); + + reporter.log2('Creating shared folder "%s" at "%s" ...' % (sShareName, sGuestMountPoint)); + try: + oConsole = oSession.o.console; + oConsole.createSharedFolder(sShareName, sHostPath, True, True, sGuestMountPoint); + except: + if fMustSucceed: + reporter.errorXcpt('createSharedFolder(%s,%s,True,True,%s)' % (sShareName, sHostPath, sGuestMountPoint)); + else: + reporter.log('createSharedFolder(%s,%s,True,True,%s) failed, good' % (sShareName, sHostPath, sGuestMountPoint)); + reporter.testDone(); + return False is fMustSucceed; + + # Check whether we can see the shared folder now. Retry for 30 seconds. + msStart = base.timestampMilli(); + while True: + fRc = oTxsSession.syncIsDir(sGuestMountPoint + self.sGuestSlash + 'candle.dir'); + reporter.log2('candle.dir check -> %s' % (fRc,)); + if fRc is fMustSucceed: + break; + if base.timestampMilli() - msStart > 30000: + reporter.error('Shared folder mounting timed out!'); + break; + self.oTstDrv.sleep(1); + + reporter.testDone(); + + return fRc == fMustSucceed; + + def mountShare(self, oSession, oTxsSession, sShareName, sHostPath, sGuestMountPoint): + """ + Automount a shared folder in the guest. + + Returns success status. + """ + return self.mountShareEx(oSession, oTxsSession, sShareName, sHostPath, sGuestMountPoint, fMustSucceed = True); + + def unmountShareEx(self, oSession, oTxsSession, sShareName, sGuestMountPoint, fMustSucceed): + """ + Unmounts a shared folder in the guest. + + Returns success status, based on fMustSucceed. + """ + reporter.log2('Autounmount'); + try: + oConsole = oSession.o.console; + oConsole.removeSharedFolder(sShareName); + except: + if fMustSucceed: + reporter.errorXcpt('removeSharedFolder(%s)' % (sShareName,)); + else: + reporter.log('removeSharedFolder(%s)' % (sShareName,)); + reporter.testDone(); + return False is fMustSucceed; + + # Check whether the shared folder is gone on the guest now. Retry for 30 seconds. + msStart = base.timestampMilli(); + while True: + fRc = oTxsSession.syncIsDir(sGuestMountPoint + self.sGuestSlash + 'candle.dir'); + reporter.log2('candle.dir check -> %s' % (fRc,)); + if fRc is not fMustSucceed: + break; + if base.timestampMilli() - msStart > 30000: + reporter.error('Shared folder unmounting timed out!'); + fRc = False; + break; + self.oTstDrv.sleep(1); + + reporter.testDone(); + + return fRc is not fMustSucceed; + + def unmountShare(self, oSession, oTxsSession, sShareName, sGuestMountPoint): + """ + Unmounts a shared folder in the guest, extended version. + + Returns success status, based on fMustSucceed. + """ + return self.unmountShareEx(oSession, oTxsSession, sShareName, sGuestMountPoint, fMustSucceed = True); + + def testIt(self, oTestVm, oSession, oTxsSession): + """ + Executes the test. + + Returns fRc, oTxsSession. The latter may have changed. + """ + reporter.log("Active tests: %s" % (self.asTests,)); + + # + # Skip the test if before 6.0 + # + if self.oTstDrv.fpApiVer < 6.0: + reporter.log('Requires 6.0 or later (for now)'); + return (None, oTxsSession); + + # Guess a free mount point inside the guest. + if oTestVm.isWindows() or oTestVm.isOS2(): + self.sGuestSlash = '\\'; + else: + self.sGuestSlash = '/'; + + # + # Create the host directory to share. Empty except for a 'candle.dir' subdir + # that we use to check that it mounted correctly. + # + sShareName1 = 'shfl1'; + sShareHostPath1 = os.path.join(self.oTstDrv.sScratchPath, sShareName1); + reporter.log2('Creating shared host folder "%s"...' % (sShareHostPath1,)); + if os.path.exists(sShareHostPath1): + try: shutil.rmtree(sShareHostPath1); + except: return (reporter.errorXcpt('shutil.rmtree(%s)' % (sShareHostPath1,)), oTxsSession); + try: os.mkdir(sShareHostPath1); + except: return (reporter.errorXcpt('os.mkdir(%s)' % (sShareHostPath1,)), oTxsSession); + try: os.mkdir(os.path.join(sShareHostPath1, 'candle.dir')); + except: return (reporter.errorXcpt('os.mkdir(%s)' % (sShareHostPath1,)), oTxsSession); + + # Guess a free mount point inside the guest. + if oTestVm.isWindows() or oTestVm.isOS2(): + sMountPoint1 = 'V:'; + else: + sMountPoint1 = '/mnt/' + sShareName1; + + fRc = self.mountShare(oSession, oTxsSession, sShareName1, sShareHostPath1, sMountPoint1); + if fRc is not True: + return (False, oTxsSession); # skip the remainder if we cannot auto mount the folder. + + # + # Run FsPerf inside the guest. + # + fSkip = 'fsperf' not in self.asTests; + if fSkip is False: + cMbFree = utils.getDiskUsage(sShareHostPath1); + if cMbFree >= 16: + reporter.log2('Free space: %u MBs' % (cMbFree,)); + else: + reporter.log('Skipping FsPerf because only %u MB free on %s' % (cMbFree, sShareHostPath1,)); + fSkip = True; + if fSkip is False: + # Common arguments: + asArgs = ['FsPerf', '-d', sMountPoint1 + self.sGuestSlash + 'fstestdir-1', '-s8']; + + # Skip part of mmap on older windows systems without CcCoherencyFlushAndPurgeCache (>= w7). + reporter.log2('oTestVm.sGuestOsType=%s' % (oTestVm.sGuestOsType,)); + if oTestVm.getNonCanonicalGuestOsType() \ + in [ 'WindowsNT3x', 'WindowsNT4', 'Windows2000', 'WindowsXP', 'WindowsXP_64', 'Windows2003', + 'Windows2003_64', 'WindowsVista', 'WindowsVista_64', 'Windows2008', 'Windows2008_64']: + asArgs.append('--no-mmap-coherency'); + + # Configure I/O block sizes according to guest memory size: + cbMbRam = 128; + try: cbMbRam = oSession.o.machine.memorySize; + except: reporter.errorXcpt(); + reporter.log2('cbMbRam=%s' % (cbMbRam,)); + asArgs.append('--set-block-size=1'); + asArgs.append('--add-block-size=512'); + asArgs.append('--add-block-size=4096'); + asArgs.append('--add-block-size=16384'); + asArgs.append('--add-block-size=65536'); + asArgs.append('--add-block-size=1048576'); # 1 MiB + if cbMbRam >= 512: + asArgs.append('--add-block-size=33554432'); # 32 MiB + if cbMbRam >= 768: + asArgs.append('--add-block-size=134217728'); # 128 MiB + + # Putting lots (10000) of files in a single directory causes issues on OS X + # (HFS+ presumably, though could be slow disks) and some linuxes (slow disks, + # maybe ext2/3?). So, generally reduce the file count to 4096 everywhere + # since we're not here to test the host file systems, and 3072 on macs. + if utils.getHostOs() in [ 'darwin', ]: + asArgs.append('--many-files=3072'); + elif utils.getHostOs() in [ 'linux', ]: + asArgs.append('--many-files=4096'); + + # Add the extra arguments from the command line and kick it off: + asArgs.extend(self.asExtraArgs); + + # Run FsPerf: + reporter.log2('Starting guest FsPerf (%s)...' % (asArgs,)); + sFsPerfPath = self._locateGstFsPerf(oTxsSession); + + ## @todo For some odd reason the combined GA/VaKit .ISO (by IPRT/fs/isomakercmd) + # sometimes (?) contains FsPerf as non-executable (-r--r--r-- 1 root root) on Linux. + # + # So work around this for now by copying the desired FsPerf binary to the temp directory, + # make it executable and execute it from there. + fISOMakerCmdIsBuggy = oTestVm.isLinux(); + if fISOMakerCmdIsBuggy: + sFsPerfPathTemp = oTestVm.pathJoin(self.oTstDrv.getGuestTempDir(oTestVm), 'FsPerf${EXESUFF}'); + if oTestVm.isWindows() \ + or oTestVm.isOS2(): + sCopy = self.oTstDrv.getGuestSystemShell(); + sCopyArgs = ( sCopy, "/C", "copy", "/Y", sFsPerfPath, sFsPerfPathTemp ); + else: + sCopy = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'cp'); + sCopyArgs = ( sCopy, "-a", "-v", sFsPerfPath, sFsPerfPathTemp ); + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Copying FsPerf', 60 * 1000, + sCopy, sCopyArgs, fCheckSessionStatus = True); + fRc = fRc and oTxsSession.syncChMod(sFsPerfPathTemp, 0o755); + if fRc: + sFsPerfPath = sFsPerfPathTemp; + + fRc = self.oTstDrv.txsRunTest(oTxsSession, 'Running FsPerf', 90 * 60 * 1000, sFsPerfPath, asArgs, + fCheckSessionStatus = True); + reporter.log2('FsPerf -> %s' % (fRc,)); + if fRc: + # Do a bit of diagnosis to find out why this failed. + if not oTestVm.isWindows() \ + and not oTestVm.isOS2(): + sCmdLs = oTestVm.pathJoin(self.oTstDrv.getGuestSystemDir(oTestVm), 'ls'); + oTxsSession.syncExec(sCmdLs, (sCmdLs, "-al", sFsPerfPath), fIgnoreErrors = True); + oTxsSession.syncExec(sCmdLs, (sCmdLs, "-al", "-R", "/opt"), fIgnoreErrors = True); + oTxsSession.syncExec(sCmdLs, (sCmdLs, "-al", "-R", "/media/cdrom"), fIgnoreErrors = True); + + sTestDir = os.path.join(sShareHostPath1, 'fstestdir-1'); + if os.path.exists(sTestDir): + fRc = reporter.errorXcpt('test directory lingers: %s' % (sTestDir,)); + try: shutil.rmtree(sTestDir); + except: fRc = reporter.errorXcpt('shutil.rmtree(%s)' % (sTestDir,)); + else: + reporter.testStart('FsPerf'); + reporter.testDone(fSkip or fRc is None); + + # + # Check if auto-unmounting works. + # + if fRc is True: + fRc = self.unmountShare(oSession, oTxsSession, sShareName1, sMountPoint1); + + ## @todo Add tests for multiple automount shares, random unmounting, reboot test. + + return (fRc, oTxsSession); + + def _locateGstFsPerf(self, oTxsSession): + """ + Returns guest side path to FsPerf. + """ + for sFsPerfPath in self.asGstFsPerfPaths: + if oTxsSession.syncIsFile(sFsPerfPath): + reporter.log('Using FsPerf at "%s"' % (sFsPerfPath,)); + return sFsPerfPath; + reporter.log('Unable to find guest FsPerf in any of these places: %s' % ('\n '.join(self.asGstFsPerfPaths),)); + return self.asGstFsPerfPaths[0]; + + + +if __name__ == '__main__': + reporter.error('Cannot run standalone, use tdAddBasic1.py'); + sys.exit(1); diff --git a/src/VBox/ValidationKit/tests/api/Makefile.kmk b/src/VBox/ValidationKit/tests/api/Makefile.kmk new file mode 100644 index 00000000..2598e51a --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/Makefile.kmk @@ -0,0 +1,72 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - API 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 += ValidationKitTestsApi +ValidationKitTestsApi_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsApi_INST = $(INST_VALIDATIONKIT)tests/api/ +ValidationKitTestsApi_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdApi1.py \ + $(PATH_SUB_CURRENT)/tdAppliance1.py \ + $(PATH_SUB_CURRENT)/tdCloneMedium1.py \ + $(PATH_SUB_CURRENT)/tdCreateVMWithDefaults1.py \ + $(PATH_SUB_CURRENT)/tdMoveMedium1.py \ + $(PATH_SUB_CURRENT)/tdMoveVm1.py \ + $(PATH_SUB_CURRENT)/tdPython1.py \ + $(PATH_SUB_CURRENT)/tdTreeDepth1.py + +ifndef VBOX_OSE + ValidationKitTestsApi_EXEC_SOURCES += \ + $(PATH_SUB_CURRENT)/tdCloud1.py \ + $(PATH_SUB_CURRENT)/tdOciConnection1.py \ + $(PATH_SUB_CURRENT)/tdOciExport1.py \ + $(PATH_SUB_CURRENT)/tdOciImage1.py \ + $(PATH_SUB_CURRENT)/tdOciImport1.py \ + $(PATH_SUB_CURRENT)/tdOciInstance1.py \ + $(PATH_SUB_CURRENT)/tdOciProfile1.py +endif +ValidationKitTestsApi_SOURCES := \ + $(wildcard \ + $(PATH_SUB_CURRENT)/*.ova \ + ) + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsApi_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/api/__init__.py b/src/VBox/ValidationKit/tests/api/__init__.py new file mode 100644 index 00000000..db7ec260 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# $Id: __init__.py $ + +""" +Just to make python 2.x happy. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-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: 155244 $" diff --git a/src/VBox/ValidationKit/tests/api/tdApi1.py b/src/VBox/ValidationKit/tests/api/tdApi1.py new file mode 100755 index 00000000..2aae19c8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdApi1.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdApi1.py $ + +""" +VirtualBox Validation Kit - API Test wrapper #1 combining all API sub-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: 155244 $" + + +# Standard Python imports. +import os +import sys + +# 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 testdriver import vbox + + +class tdApi1(vbox.TestDriver): + """ + API Test wrapper #1. + """ + + def __init__(self, aoSubTestDriverClasses = None): + vbox.TestDriver.__init__(self) + for oSubTestDriverClass in aoSubTestDriverClasses: + self.addSubTestDriver(oSubTestDriverClass(self)); + + # + # Overridden methods. + # + + def actionConfig(self): + """ + Import the API. + """ + if not self.importVBoxApi(): + return False + return True + + def actionExecute(self): + """ + Execute the testcase, i.e. all sub-tests. + """ + fRc = True; + for oSubTstDrv in self.aoSubTstDrvs: + if oSubTstDrv.fEnabled: + fRc = oSubTstDrv.testIt() and fRc; + return fRc; + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdPython1 import SubTstDrvPython1; # pylint: disable=relative-import + from tdAppliance1 import SubTstDrvAppliance1; # pylint: disable=relative-import + from tdMoveMedium1 import SubTstDrvMoveMedium1; # pylint: disable=relative-import + from tdTreeDepth1 import SubTstDrvTreeDepth1; # pylint: disable=relative-import + from tdMoveVm1 import SubTstDrvMoveVm1; # pylint: disable=relative-import + from tdCloneMedium1 import SubTstDrvCloneMedium1;# pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvPython1, SubTstDrvAppliance1, SubTstDrvMoveMedium1, + SubTstDrvTreeDepth1, SubTstDrvMoveVm1, SubTstDrvCloneMedium1]).main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova Binary files differnew file mode 100644 index 00000000..aba10dbb --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..19c2c4b9 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova Binary files differnew file mode 100644 index 00000000..66a173aa --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..8027ce64 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova Binary files differnew file mode 100644 index 00000000..7fbf44ee --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..2434f37e --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova Binary files differnew file mode 100644 index 00000000..a6549b15 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem new file mode 100644 index 00000000..c155d659 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem @@ -0,0 +1,74 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALRXOpwrjjFzFZtb +aTtB+3kNrgrqYkye6CqvrOODZwXsljr/pduYj6bYh5OdznAFqgeeCpkqEf/2p5mD +XlqE4cDoL2opLk7LrdmVDqX9rUkGcGf6hwTp1EAllESBIUt6LdSbL7Y5goYq1E++ +/fKSYRIniCbWp9ahTLH8hjDcw48tAgMBAAECgYAiNl4vHHA4X13dAEWBcW4UtAyt +k3Ocl5Tx7Cv/aYFU9WI2xSMg+ttdyrxFu+1bASgVk9zs27dYeOGo1OxEfesZzQkT +mbzvYCdYk9wAWKXQwpp78HZyEsKVipIxO+riH9ph7SFQBzB5NoADPoqwahOmeQQW +sE3oTJRa9O+JR3muXQJBAOOt4dj+1Rwmdy83j9uOLLfO75l2pJd/hq5gN7+eNFks +R68cbhFGkrOU13dVLquyqxAoaKc2PgZS+RfGRrohjm8CQQDKxeuqYXyrYBU4jNAS +TgcR4lbb8HiC1AyQrdiJVtH4qLpqk92M7muHXZQGOXRizHQMrRDY+PcgLoFAnRzJ +j0ojAkEA3rqL5ivlbtRyY86G/NHpDSdzXT2jZlFq/8tAvkOWEmYu+i9lvaC8gtFo +t2Stc2olzni5aFq38pfY9lkRd6S8IQJBAJe3LJPnqwrish4Epa38eae07P5U1yY0 +GE6r9EcWEbZ2MDyL9Al9XjEDIDzkAiPmC7JsTx24cda/VPAOXbqlnncCQQCCevzg +rrnTz0/3iLWkxowiKCE1EvbVALDxPwHi0zvjXbGZs8BodLxeChpJzFImbxRVAIrp +WvZOuLqhAKjL7OOT +-----END PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14180722474914962380 (0xc4cc0bf54fb45bcc) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=DE, ST=Example, L=For Instance, O=Beispiel GmbH, CN=beispiel.example.org + Validity + Not Before: Feb 1 01:45:27 2016 GMT + Not After : Jan 24 01:45:27 2046 GMT + Subject: C=DE, ST=Example, L=For Instance, O=Beispiel GmbH, CN=beispiel.example.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:b4:57:3a:9c:2b:8e:31:73:15:9b:5b:69:3b:41: + fb:79:0d:ae:0a:ea:62:4c:9e:e8:2a:af:ac:e3:83: + 67:05:ec:96:3a:ff:a5:db:98:8f:a6:d8:87:93:9d: + ce:70:05:aa:07:9e:0a:99:2a:11:ff:f6:a7:99:83: + 5e:5a:84:e1:c0:e8:2f:6a:29:2e:4e:cb:ad:d9:95: + 0e:a5:fd:ad:49:06:70:67:fa:87:04:e9:d4:40:25: + 94:44:81:21:4b:7a:2d:d4:9b:2f:b6:39:82:86:2a: + d4:4f:be:fd:f2:92:61:12:27:88:26:d6:a7:d6:a1: + 4c:b1:fc:86:30:dc:c3:8f:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 02:0A:B0:BC:21:63:C1:50:16:1E:8D:B7:F4:B0:1C:48:D8:E1:0A:2A + X509v3 Authority Key Identifier: + keyid:02:0A:B0:BC:21:63:C1:50:16:1E:8D:B7:F4:B0:1C:48:D8:E1:0A:2A + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + 13:f1:33:5f:c7:d9:6c:20:a4:eb:f2:e5:e2:b5:e0:e8:b6:9c: + c7:62:4a:39:53:83:11:98:cf:11:3d:58:09:d8:38:78:71:16: + d4:24:cc:c8:2e:5a:2b:d3:94:6a:dc:ae:62:e7:81:6a:5f:04: + 84:ba:55:8c:dc:6b:ff:aa:78:4f:37:8e:fd:ba:b5:d1:27:83: + 47:29:30:92:63:85:53:f0:b1:b9:f4:c7:a8:b1:48:44:4e:30: + 6f:50:d3:35:14:87:59:d0:f8:ed:da:07:60:6c:de:6d:53:53: + 3d:d7:03:97:1f:6b:13:ce:92:49:20:57:4f:b0:87:30:76:66: + d3:43 +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAMTMC/VPtFvMMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV +BAYTAkRFMRAwDgYDVQQIDAdFeGFtcGxlMRUwEwYDVQQHDAxGb3IgSW5zdGFuY2Ux +FjAUBgNVBAoMDUJlaXNwaWVsIEdtYkgxHTAbBgNVBAMMFGJlaXNwaWVsLmV4YW1w +bGUub3JnMB4XDTE2MDIwMTAxNDUyN1oXDTQ2MDEyNDAxNDUyN1owbTELMAkGA1UE +BhMCREUxEDAOBgNVBAgMB0V4YW1wbGUxFTATBgNVBAcMDEZvciBJbnN0YW5jZTEW +MBQGA1UECgwNQmVpc3BpZWwgR21iSDEdMBsGA1UEAwwUYmVpc3BpZWwuZXhhbXBs +ZS5vcmcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALRXOpwrjjFzFZtbaTtB ++3kNrgrqYkye6CqvrOODZwXsljr/pduYj6bYh5OdznAFqgeeCpkqEf/2p5mDXlqE +4cDoL2opLk7LrdmVDqX9rUkGcGf6hwTp1EAllESBIUt6LdSbL7Y5goYq1E++/fKS +YRIniCbWp9ahTLH8hjDcw48tAgMBAAGjUDBOMB0GA1UdDgQWBBQCCrC8IWPBUBYe +jbf0sBxI2OEKKjAfBgNVHSMEGDAWgBQCCrC8IWPBUBYejbf0sBxI2OEKKjAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBABPxM1/H2WwgpOvy5eK14Oi2nMdi +SjlTgxGYzxE9WAnYOHhxFtQkzMguWivTlGrcrmLngWpfBIS6VYzca/+qeE83jv26 +tdEng0cpMJJjhVPwsbn0x6ixSEROMG9Q0zUUh1nQ+O3aB2Bs3m1TUz3XA5cfaxPO +kkkgV0+whzB2ZtND +-----END CERTIFICATE----- diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..6a21a19b --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova Binary files differnew file mode 100644 index 00000000..8e6e2e2e --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova Binary files differnew file mode 100644 index 00000000..d5a92eb2 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova Binary files differnew file mode 100644 index 00000000..6480e6d5 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem new file mode 100644 index 00000000..1b682b0d --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem @@ -0,0 +1,134 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAzYlueHZlbSqlLrVAnCyQx9xgnO7jSjK6wXqlbxkl0QZLyDnh +vNowZYnu7YZI3V+RuVzYbczlEr1qZpUeO8NF04mK1kFPJRinRiuiOtih+WwvDQd7 +7RZJhjoAcfEeogt1OzOZg8QKF1AMGlUgk5zWma4VIS75ZM6G1b77qS3rge6L9NwZ +gthyQ1PvddYue4MNEpK+2ekZZq/GGZvd+yNP0Wq7kzcmydCgCF9EdLCZwt4BHmOS +ZM6BUxKv+PINZgO7Wp2tBBGigg9Mf/NXofjMHp7RI16xa2W9Q/Ct2A6rb5SAB10U +ddrtxiI+F+Jv7lNkHOCLbju7cEs+Y6XIbt1l7aVqBFpgo6cwFZ7DdLPHZkgb50hx +dnNdwncOR1syspTF544hbmkcb/8+weq+qgsD/57b/ZGrwb8ruByDz0LZiXzxxBRe +w9OhHZGFnuC0D1ciIvpifKe4R/kLcEgVW7yYp4W1BAZgiBJPgXArJTlUBrxamzsQ +m6N7GxlP+vTCny7IJsQ1KWu5BqkPv0T1+zD5vUT9/lAa+XBdDHhYVm0adr4dXIef +6ws+0zbpz8DyaD/Bg9KvRKdnziVc+pKwkWOVrFvZ6NvBex0i8Bgh4FrpkdjmrZMZ +uuCdex0CEayuYexgUqyMg9m0GwGqVNG6mVB4cCOg0rayaqEKrbtUSxNaD/MCAwEA +AQKCAgAnjPGQBqBf0Fv2z/P92WmGu/ZvXFyqU3aycmpRJZKsVTzR66lvkMDNWSx7 +0mJFDvXYqHAROOM/pulJkho+P8Y4/XeU4P5c0hCmJRFTp4oLl/C53h3PsoE1bgXV +5yMQ5YmKedRpkZirgcDCdG0PWpfE/MWeHA7rgf5aNSTyGh7+YqvV02CpWAMsx4MF +ttA8/ivOziQhhIRZySsilGazw8jBMHulyXASV63jzok6txzvbY7jjR+HfGFQXgE0 +s0c2wTMVLdA0Pzx5MH51BJtxVJHato7h8n/LfclcwHyDXddJYlb8k8GzKAynGsG+ +ENmfD7btA5xw+teHtULtI+KcysepCeVXW7Fr7PFJe1XOx1c24WrFpjeGdnG3PdqA +Y9YbjMCD/GpCUICla3N6iRDG3imY+DUWcExVjUo6TUbQ4ofQT4WF7+2Yzg6MRqYu +41jgF+voCXS4BH8e+ngEjd8VLHma7FPuWujodCERsvxZ7aLXBcZCH1Lje9Y8a79x +RGX83pVHTWFRyo4wKBpRUz+hwH7MjYdfE2Q+Zl153pXw3au9jX8PUMhx9swWPGRw +CvLPwtTwrezzdFeXg4wfJwWnvqhKIViery4z3rdjh5Zke/9+7gXmFbZ8w+QUkXXq +cnBEJySTvpI4B9abpcEI4OO84L/A6ENDO10W4ZGfm5aD2m0vIQKCAQEA6nZAmsSr +T1CcgrWNcY6civ8o5cMF8fM9ib5QvibRlJN9dpcZPr6+Pp2UPCkjVowk/FybP1Kw +sqA0aXOL3VR03YpLrqdUDpe9YiFCa3ttDX0pTOdU9QW2RbOrNQVurKhzNETd37cJ +0RYBOHJY/RxA0F8l16wSr1jTO/SRbWkFJYZS/OaH1B6gCxz6H1IgfKJddrvFZsd8 +6PnLtqsPnsXuxugqHu3ENddRqiiSNyGnqpGu0jQpPBrQEQQN5o8/X9rfNfAobChH +Nii0va7MHudwaTTo2ytUzEV+v0kueMNU9YXgJgxzSzf1/VQC+HoNI4Vbs9XCbOWV +z2FVBwXM+0cIMQKCAQEA4Gr1pQAymJq/9p2fiWSR8TVdrYCwvAnBu8QSqSzweNZC +SRt7Nkhhljm7M2DuUOioMaFAT9CnyD2nJyKSAqqOngb0vo7C53UzgFu+QFMtUx/Z +t/C96WcLYrErXfnB/sTq0Gp4tpoo1S3FZecabhlZyv1O08WXiNCcEX1VMQz2+iF9 ++n2PjFuEomeWHxBWCwfB9/S0pKVHP/7833sQ8Dfsm2EwtPy3v58ln3UguaUDIe5V +r0TmKr8kDrYgbw1ZpBOd9Wbbvj3vIN34OPoSIrcar67DCp7qYq77LYAhngfdQGed +MUMsbU1dODWf0kwP7mncTr3mPByQUHa3ImjvJPv1YwKCAQBYa04Dz8U2/RR46pSz +zW9Vr9Ixi7GTRALiDkaO3z7MRC7daTAZDH/cRzre0TjFa8aK8TWO1NVUF7yMRAnr +5uzHm17dN7coZasC9b4BoKNIofnQSbEtUgEiGhanwSuyqzf+7zWpJ3LpSd4d9ml+ +0ofSzP8NbZQCUoIeqyWo2CEbvKNRQnLY2M/MQRpGc4dS2TxcCYXxM6v0hDeB5NLY +MpbQpj80OMB0+YWPoQs7BVMgrR37obYnN4ld0WSYnU7uDDF/OtlTqIDqeMFogyHx +SaCH3G8wMBAjlNWut59x5WAF033re2iDZlA7P9J6+DQ6QBGMKUHQJWiws2kIY/Sg +knIRAoIBAQCzlkR/Rxo2LthhZR/PFfEIQrl1Z9+GipRDSxPX2AOT33nqARjnhqK5 +UfexlOcBTj2SgcTyWjp6LoQ9+Bc6FPzODyj5+UqVaJ/PHxuvZCCIPZu/6+I+Dlz5 +HGhk6sJIu5JhOGLjVZhJiDhIZNkstBK8M1tKcvvh23aZNF/hQcu+vOCQfLxMCMyq +HhTvROZmK04Yu/V3MGBFISuBN32FjmtEqFEO9JGiwZuc8GFAzoEkPRLKkGtUV+Nl +9m8cD2XlvGESib5djjh3Z8oE5nFu4HJ1lne0XxmX4QlWDwxX51kx+fi7/FJoIZnw +qlD8PCwfkQ1g4eyFvCHskiPZYHnHce2bAoIBACW40wFIPiDRhjNywEi4miN6P+Xs +rlM9csmxw2GG6Z4c8H/Z4SNBRQmnj/F6PHsar6SGy9WlR9mNeR2XBn4Pyf/cNjTQ +bKzV5wPRm6P81SQjhIz4Mxdx1S30AeF1LdagWFiq0on7oRTH2SeKQspdpNeiTDbC +sBw4SVDKmGZYGZcuT3BdvvZFEW4qncSYuUM7l9bTmsbzid/v8zn/XDQrpdPYnptD +ljJETKQzlyrJLtTbyFlo3Osf1N4408u3rqhpw2SgKdyMiHndhxkF869Vycll+VMz +SzPU0wI62BIPWHDBJLnxGBTa+4kUSxP+oDvCfVYCmDcfDRew3MWCK9emJnU= +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 1 (0x1) + Signature Algorithm: sha512WithRSAEncryption + Issuer: C=DE, ST=Example, L=For Instance, O=Beispiel GmbH, CN=beispiel.example.org + Validity + Not Before: Feb 15 14:27:56 2016 GMT + Not After : Feb 2 14:27:56 2066 GMT + Subject: C=DE, ST=Instance-Example, L=Examplecity, O=For Instance GmbH, OU=The Example Unit, CN=subcert.example.org/emailAddress=subcert@example.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (4096 bit) + Modulus: + 00:cd:89:6e:78:76:65:6d:2a:a5:2e:b5:40:9c:2c: + 90:c7:dc:60:9c:ee:e3:4a:32:ba:c1:7a:a5:6f:19: + 25:d1:06:4b:c8:39:e1:bc:da:30:65:89:ee:ed:86: + 48:dd:5f:91:b9:5c:d8:6d:cc:e5:12:bd:6a:66:95: + 1e:3b:c3:45:d3:89:8a:d6:41:4f:25:18:a7:46:2b: + a2:3a:d8:a1:f9:6c:2f:0d:07:7b:ed:16:49:86:3a: + 00:71:f1:1e:a2:0b:75:3b:33:99:83:c4:0a:17:50: + 0c:1a:55:20:93:9c:d6:99:ae:15:21:2e:f9:64:ce: + 86:d5:be:fb:a9:2d:eb:81:ee:8b:f4:dc:19:82:d8: + 72:43:53:ef:75:d6:2e:7b:83:0d:12:92:be:d9:e9: + 19:66:af:c6:19:9b:dd:fb:23:4f:d1:6a:bb:93:37: + 26:c9:d0:a0:08:5f:44:74:b0:99:c2:de:01:1e:63: + 92:64:ce:81:53:12:af:f8:f2:0d:66:03:bb:5a:9d: + ad:04:11:a2:82:0f:4c:7f:f3:57:a1:f8:cc:1e:9e: + d1:23:5e:b1:6b:65:bd:43:f0:ad:d8:0e:ab:6f:94: + 80:07:5d:14:75:da:ed:c6:22:3e:17:e2:6f:ee:53: + 64:1c:e0:8b:6e:3b:bb:70:4b:3e:63:a5:c8:6e:dd: + 65:ed:a5:6a:04:5a:60:a3:a7:30:15:9e:c3:74:b3: + c7:66:48:1b:e7:48:71:76:73:5d:c2:77:0e:47:5b: + 32:b2:94:c5:e7:8e:21:6e:69:1c:6f:ff:3e:c1:ea: + be:aa:0b:03:ff:9e:db:fd:91:ab:c1:bf:2b:b8:1c: + 83:cf:42:d9:89:7c:f1:c4:14:5e:c3:d3:a1:1d:91: + 85:9e:e0:b4:0f:57:22:22:fa:62:7c:a7:b8:47:f9: + 0b:70:48:15:5b:bc:98:a7:85:b5:04:06:60:88:12: + 4f:81:70:2b:25:39:54:06:bc:5a:9b:3b:10:9b:a3: + 7b:1b:19:4f:fa:f4:c2:9f:2e:c8:26:c4:35:29:6b: + b9:06:a9:0f:bf:44:f5:fb:30:f9:bd:44:fd:fe:50: + 1a:f9:70:5d:0c:78:58:56:6d:1a:76:be:1d:5c:87: + 9f:eb:0b:3e:d3:36:e9:cf:c0:f2:68:3f:c1:83:d2: + af:44:a7:67:ce:25:5c:fa:92:b0:91:63:95:ac:5b: + d9:e8:db:c1:7b:1d:22:f0:18:21:e0:5a:e9:91:d8: + e6:ad:93:19:ba:e0:9d:7b:1d:02:11:ac:ae:61:ec: + 60:52:ac:8c:83:d9:b4:1b:01:aa:54:d1:ba:99:50: + 78:70:23:a0:d2:b6:b2:6a:a1:0a:ad:bb:54:4b:13: + 5a:0f:f3 + Exponent: 65537 (0x10001) + Signature Algorithm: sha512WithRSAEncryption + 2d:1c:49:41:1a:4f:dc:d8:5c:b1:fa:ca:53:38:86:26:6e:56: + a5:6d:2e:1d:0d:74:64:0e:89:c3:3a:c7:1d:01:6e:d1:93:b2: + c9:37:01:6a:ae:31:42:96:05:d7:df:fd:01:f8:bc:f3:f3:4c: + cd:75:ae:16:00:61:78:f2:67:c5:b1:76:76:16:39:ba:d2:6b: + 09:ad:99:2b:22:ce:56:89:4d:08:ca:8c:76:4c:50:6b:83:c9: + 46:9b:f5:9f:2d:e2:7f:e5:72:aa:76:56:c4:67:83:45:26:b7: + e2:ae:f7:1e:61:c9:aa:2e:8d:b8:59:42:84:37:25:c8:16:92: + d6:d5 +-----BEGIN CERTIFICATE----- +MIIEGjCCA4MCAQEwDQYJKoZIhvcNAQENBQAwbTELMAkGA1UEBhMCREUxEDAOBgNV +BAgMB0V4YW1wbGUxFTATBgNVBAcMDEZvciBJbnN0YW5jZTEWMBQGA1UECgwNQmVp +c3BpZWwgR21iSDEdMBsGA1UEAwwUYmVpc3BpZWwuZXhhbXBsZS5vcmcwIBcNMTYw +MjE1MTQyNzU2WhgPMjA2NjAyMDIxNDI3NTZaMIG3MQswCQYDVQQGEwJERTEZMBcG +A1UECAwQSW5zdGFuY2UtRXhhbXBsZTEUMBIGA1UEBwwLRXhhbXBsZWNpdHkxGjAY +BgNVBAoMEUZvciBJbnN0YW5jZSBHbWJIMRkwFwYDVQQLDBBUaGUgRXhhbXBsZSBV +bml0MRwwGgYDVQQDDBNzdWJjZXJ0LmV4YW1wbGUub3JnMSIwIAYJKoZIhvcNAQkB +FhNzdWJjZXJ0QGV4YW1wbGUub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAzYlueHZlbSqlLrVAnCyQx9xgnO7jSjK6wXqlbxkl0QZLyDnhvNowZYnu +7YZI3V+RuVzYbczlEr1qZpUeO8NF04mK1kFPJRinRiuiOtih+WwvDQd77RZJhjoA +cfEeogt1OzOZg8QKF1AMGlUgk5zWma4VIS75ZM6G1b77qS3rge6L9NwZgthyQ1Pv +ddYue4MNEpK+2ekZZq/GGZvd+yNP0Wq7kzcmydCgCF9EdLCZwt4BHmOSZM6BUxKv ++PINZgO7Wp2tBBGigg9Mf/NXofjMHp7RI16xa2W9Q/Ct2A6rb5SAB10UddrtxiI+ +F+Jv7lNkHOCLbju7cEs+Y6XIbt1l7aVqBFpgo6cwFZ7DdLPHZkgb50hxdnNdwncO +R1syspTF544hbmkcb/8+weq+qgsD/57b/ZGrwb8ruByDz0LZiXzxxBRew9OhHZGF +nuC0D1ciIvpifKe4R/kLcEgVW7yYp4W1BAZgiBJPgXArJTlUBrxamzsQm6N7GxlP ++vTCny7IJsQ1KWu5BqkPv0T1+zD5vUT9/lAa+XBdDHhYVm0adr4dXIef6ws+0zbp +z8DyaD/Bg9KvRKdnziVc+pKwkWOVrFvZ6NvBex0i8Bgh4FrpkdjmrZMZuuCdex0C +EayuYexgUqyMg9m0GwGqVNG6mVB4cCOg0rayaqEKrbtUSxNaD/MCAwEAATANBgkq +hkiG9w0BAQ0FAAOBgQAtHElBGk/c2Fyx+spTOIYmblalbS4dDXRkDonDOscdAW7R +k7LJNwFqrjFClgXX3/0B+Lzz80zNda4WAGF48mfFsXZ2Fjm60msJrZkrIs5WiU0I +yox2TFBrg8lGm/WfLeJ/5XKqdlbEZ4NFJrfirvceYcmqLo24WUKENyXIFpLW1Q== +-----END CERTIFICATE----- diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova Binary files differnew file mode 100644 index 00000000..48e49059 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1.py b/src/VBox/ValidationKit/tests/api/tdAppliance1.py new file mode 100755 index 00000000..a92d8b42 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdAppliance1.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdAppliance1.py $ + +""" +VirtualBox Validation Kit - IAppliance Test #1 +""" + +__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: 155244 $" + + +# Standard Python imports. +import os +import sys +import tarfile + +# 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 testdriver import base; +from testdriver import reporter; +from testdriver import vboxwrappers; + + +class SubTstDrvAppliance1(base.SubTestDriverBase): + """ + Sub-test driver for IAppliance Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'appliance', 'Applicance'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + fRc = True; + + # Import a set of simple OVAs. + # Note! Manifests generated by ovftool 4.0.0 does not include the ovf, while the ones b 4.1.0 does. + for sOva in ( + # t1 is a plain VM without any disks, ovftool 4.0 export from fusion + 'tdAppliance1-t1.ova', + # t2 is a plain VM with one disk. Both 4.0 and 4.1.0 exports. + 'tdAppliance1-t2.ova', + 'tdAppliance1-t2-ovftool-4.1.0.ova', + # t3 is a VM with one gzipped disk and selecting SHA256 on the ovftool cmdline (--compress=9 --shaAlgorithm=sha256). + 'tdAppliance1-t3.ova', + 'tdAppliance1-t3-ovftool-4.1.0.ova', + # t4 is a VM with with two gzipped disk, SHA256 and a (self) signed manifest (--privateKey=./tdAppliance1-t4.pem). + 'tdAppliance1-t4.ova', + 'tdAppliance1-t4-ovftool-4.1.0.ova', + # t5 is a VM with with one gzipped disk, SHA1 and a manifest signed by a valid (2016) DigiCert code signing cert. + 'tdAppliance1-t5.ova', + 'tdAppliance1-t5-ovftool-4.1.0.ova', + # t6 is a VM with with one gzipped disk, SHA1 and a manifest signed by a certificate issued by the t4 certificate, + # thus it should be impossible to establish a trusted path to a root CA. + 'tdAppliance1-t6.ova', + 'tdAppliance1-t6-ovftool-4.1.0.ova', + # t7 is based on tdAppliance1-t2-ovftool-4.1.0.ova and has modified to have an invalid InstanceID as well as an + # extra readme file. It was tarred up using bsdtar 2.4.12 on windows, so it uses a slightly different tar format and + # have different file attributes. + 'tdAppliance1-t7-bad-instance.ova', + ): + reporter.testStart(sOva); + try: + fRc = self.testImportOva(os.path.join(g_ksValidationKitDir, 'tests', 'api', sOva)) and fRc; + fRc = self.testImportOvaAsOvf(os.path.join(g_ksValidationKitDir, 'tests', 'api', sOva)) and fRc; + except: + reporter.errorXcpt(); + fRc = False; + fRc = reporter.testDone() and fRc; + + ## @todo more stuff + return fRc; + + # + # Test execution helpers. + # + + def testImportOva(self, sOva): + """ xxx """ + oVirtualBox = self.oTstDrv.oVBoxMgr.getVirtualBox(); + + # + # Import it as OVA. + # + try: + oAppliance = oVirtualBox.createAppliance(); + except: + return reporter.errorXcpt('IVirtualBox::createAppliance failed'); + + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance.read(sOva), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'read "%s"' % (sOva,)); + except: + return reporter.errorXcpt('IAppliance::read("%s") failed' % (sOva,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + try: + oAppliance.interpret(); + except: + return reporter.errorXcpt('IAppliance::interpret() failed on "%s"' % (sOva,)); + + # + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance.importMachines([]), + self.oTstDrv.oVBoxMgr, self.oTstDrv, 'importMachines "%s"' % (sOva,)); + except: + return reporter.errorXcpt('IAppliance::importMachines failed on "%s"' % (sOva,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + # + # Export the + # + ## @todo do more with this OVA. Like untaring it and loading it as an OVF. Export it and import it again. + + return True; + + def testImportOvaAsOvf(self, sOva): + """ + Unpacks the OVA into a subdirectory in the scratch area and imports it as an OVF. + """ + oVirtualBox = self.oTstDrv.oVBoxMgr.getVirtualBox(); + + sTmpDir = os.path.join(self.oTstDrv.sScratchPath, os.path.split(sOva)[1] + '-ovf'); + sOvf = os.path.join(sTmpDir, os.path.splitext(os.path.split(sOva)[1])[0] + '.ovf'); + + # + # Unpack + # + try: + os.mkdir(sTmpDir, 0o755); + oTarFile = tarfile.open(sOva, 'r:*'); # No 'with' support in 2.6. pylint: disable=consider-using-with + oTarFile.extractall(sTmpDir); + oTarFile.close(); + except: + return reporter.errorXcpt('Unpacking "%s" to "%s" for OVF style importing failed' % (sOvf, sTmpDir,)); + + # + # Import. + # + try: + oAppliance2 = oVirtualBox.createAppliance(); + except: + return reporter.errorXcpt('IVirtualBox::createAppliance failed (#2)'); + + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance2.read(sOvf), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'read "%s"' % (sOvf,)); + except: + return reporter.errorXcpt('IAppliance::read("%s") failed' % (sOvf,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + try: + oAppliance2.interpret(); + except: + return reporter.errorXcpt('IAppliance::interpret() failed on "%s"' % (sOvf,)); + + try: + oProgress = vboxwrappers.ProgressWrapper(oAppliance2.importMachines([]), + self.oTstDrv.oVBoxMgr, self.oTstDrv, 'importMachines "%s"' % (sOvf,)); + except: + return reporter.errorXcpt('IAppliance::importMachines failed on "%s"' % (sOvf,)); + oProgress.wait(); + if oProgress.logResult() is False: + return False; + + return True; + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tests.api.tdApi1 import tdApi1; + sys.exit(tdApi1([SubTstDrvAppliance1]).main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/api/tdCloneMedium1.py b/src/VBox/ValidationKit/tests/api/tdCloneMedium1.py new file mode 100755 index 00000000..056fb2eb --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdCloneMedium1.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdCloneMedium1.py $ + +""" +VirtualBox Validation Kit - Clone Medium Test #1 +""" + +__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: 155244 $" + + +# Standard Python imports. +import os +import sys + +# 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 testdriver import base +from testdriver import reporter +from testdriver import vboxcon +from testdriver import vboxwrappers + + +class SubTstDrvCloneMedium1(base.SubTestDriverBase): + """ + Sub-test driver for Clone Medium Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'clone-medium', 'Move Medium'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + + return self.testAll() + + # + # Test execution helpers. + # + + def createTestMedium(self, oVM, sPathSuffix, sFmt = 'VDI', cbSize = 1024*1024, data = None): + assert oVM is not None + + oSession = self.oTstDrv.openSession(oVM) + + if oSession is None: + return False + + # + # Create Medium Object + # + + sBaseHdd1Path = os.path.join(self.oTstDrv.sScratchPath, sPathSuffix) + sBaseHdd1Fmt = sFmt + cbBaseHdd1Size = cbSize + + try: + oBaseHdd1 = oSession.createBaseHd(sBaseHdd1Path, sBaseHdd1Fmt, cbBaseHdd1Size) + except: + return reporter.errorXcpt('createBaseHd failed') + + oMediumIOBaseHdd1 = oBaseHdd1.openForIO(True, "") + + if data: + cbWritten = oMediumIOBaseHdd1.write(0, data) + + if cbWritten != 1: + return reporter.error("Failed writing to test hdd.") + + oMediumIOBaseHdd1.close() + + return oBaseHdd1 + + def cloneMedium(self, oSrcHd, oTgtHd): + """ + Clones medium into target medium. + """ + try: + oProgressCom = oSrcHd.cloneTo(oTgtHd, (vboxcon.MediumVariant_Standard, ), None); + except: + reporter.errorXcpt('failed to clone medium %s to %s' % (oSrcHd.name, oTgtHd.name)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'clone base disk %s to %s' % (oSrcHd.name, oTgtHd.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def resizeAndCloneMedium(self, oSrcHd, oTgtHd, cbTgtSize): + """ + Clones medium into target medium. + """ + + try: + oProgressCom = oSrcHd.resizeAndCloneTo(oTgtHd, cbTgtSize, (vboxcon.MediumVariant_Standard, ), None); + except: + reporter.errorXcpt('failed to resize and clone medium %s to %s and to size %d' \ + % (oSrcHd.name, oTgtHd.name, cbTgtSize)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'resize and clone base disk %s to %s and to size %d' \ + % (oSrcHd.name, oTgtHd.name, cbTgtSize)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def deleteVM(self, oVM): + try: + oVM.unregister(vboxcon.CleanupMode_DetachAllReturnNone); + except: + reporter.logXcpt(); + + try: + oProgressCom = oVM.deleteConfig([]); + except: + reporter.logXcpt(); + else: + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'Delete VM %s' % (oVM.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + + return None; + + # + # Tests + # + + def testCloneOnly(self): + """ + Tests cloning mediums only. No resize. + """ + + reporter.testStart("testCloneOnly") + + oVM = self.oTstDrv.createTestVM('test-medium-clone-only', 1, None, 4) + + hd1 = self.createTestMedium(oVM, "hd1-cloneonly", data=[0xdeadbeef]) + hd2 = self.createTestMedium(oVM, "hd2-cloneonly") + + if not self.cloneMedium(hd1, hd2): + return False + + oMediumIOhd1 = hd1.openForIO(True, "") + dataHd1 = oMediumIOhd1.read(0, 4) + oMediumIOhd1.close() + + oMediumIOhd2 = hd2.openForIO(True, "") + dataHd2 = oMediumIOhd2.read(0, 4) + oMediumIOhd2.close() + + if dataHd1 != dataHd2: + reporter.testFailure("Data read is unexpected.") + + self.deleteVM(oVM) + + reporter.testDone() + return True + + def testResizeAndClone(self): + """ + Tests resizing and cloning mediums only. + """ + + reporter.testStart("testResizeAndClone") + + oVM = self.oTstDrv.createTestVM('test-medium-clone-only', 1, None, 4) + + hd1 = self.createTestMedium(oVM, "hd1-resizeandclone", data=[0xdeadbeef]) + hd2 = self.createTestMedium(oVM, "hd2-resizeandclone") + + if not (hasattr(hd1, "resizeAndCloneTo") and callable(getattr(hd1, "resizeAndCloneTo"))): + self.deleteVM(oVM) + reporter.testDone() + return True + + if not self.resizeAndCloneMedium(hd1, hd2, 1024*1024*2): + return False + + oMediumIOhd1 = hd1.openForIO(True, "") + dataHd1 = oMediumIOhd1.read(0, 4) + oMediumIOhd1.close() + + oMediumIOhd2 = hd2.openForIO(True, "") + dataHd2 = oMediumIOhd2.read(0, 4) + oMediumIOhd2.close() + + if dataHd1 != dataHd2: + reporter.testFailure("Data read is unexpected.") + + if hd2.logicalSize not in (hd1.logicalSize, 1024*1024*2): + reporter.testFailure("Target medium did not resize.") + + self.deleteVM(oVM) + + reporter.testDone() + return True + + def testAll(self): + return self.testCloneOnly() & self.testResizeAndClone() + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvCloneMedium1]).main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py b/src/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py new file mode 100755 index 00000000..21892abb --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdCreateVMWithDefaults1.py $ + +""" +VirtualBox Validation Kit - Create VM with IMachine::applyDefaults() Test +""" + +__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: 155244 $" + + +# Standard Python imports. +import os +import sys + +# 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 testdriver import base +from testdriver import reporter; +from testdriver import vboxcon; + + +class SubTstDrvCreateVMWithDefaults1(base.SubTestDriverBase): + """ + Sub-test driver for VM Move Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'create-vm-with-defaults', 'Create VMs with defaults'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + reporter.log('ValidationKit folder is "%s"' % (g_ksValidationKitDir,)) + reporter.testStart(self.sTestName); + fRc = self.testCreateVMWithDefaults(); + reporter.testDone(); + return fRc; + + + def createVMWithDefaults(self, sGuestType): + sName = 'testvm_%s' % (sGuestType) + # create VM manually, because the self.createTestVM() makes registration inside + # the method, but IMachine::applyDefault() must be called before machine is registered + try: + if self.oTstDrv.fpApiVer >= 4.2: # Introduces grouping (third parameter, empty for now). + oVM = self.oTstDrv.oVBox.createMachine("", sName, [], + self.oTstDrv.tryFindGuestOsId(sGuestType), + "") + elif self.oTstDrv.fpApiVer >= 4.0: + oVM = self.oTstDrv.oVBox.createMachine("", sName, + self.oTstDrv.tryFindGuestOsId(sGuestType), + "", False) + elif self.oTstDrv.fpApiVer >= 3.2: + oVM = self.oTstDrv.oVBox.createMachine(sName, + self.oTstDrv.tryFindGuestOsId(sGuestType), + "", "", False) + else: + oVM = self.oTstDrv.oVBox.createMachine(sName, + self.oTstDrv.tryFindGuestOsId(sGuestType), + "", "") + try: + oVM.saveSettings() + except: + reporter.logXcpt() + if self.oTstDrv.fpApiVer >= 4.0: + try: + if self.oTstDrv.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]) + else: + oProgress = oVM.delete(None); + self.oTstDrv.waitOnProgress(oProgress) + except: + reporter.logXcpt() + else: + try: oVM.deleteSettings() + except: reporter.logXcpt() + raise + except: + reporter.errorXcpt('failed to create vm "%s"' % (sName)) + return None + + if oVM is None: + return False + + # apply settings + fRc = True + try: + if self.oTstDrv.fpApiVer >= 6.1: + oVM.applyDefaults('') + oVM.saveSettings(); + self.oTstDrv.oVBox.registerMachine(oVM) + except: + reporter.logXcpt() + fRc = False + + # Some errors from applyDefaults can be observed only after further settings saving. + # Change and save the size of the VM RAM as simple setting change. + oSession = self.oTstDrv.openSession(oVM) + if oSession is None: + fRc = False + + if fRc: + try: + oSession.memorySize = 4096 + oSession.saveSettings(True) + except: + reporter.logXcpt() + fRc = False + + # delete VM + try: + oVM.unregister(vboxcon.CleanupMode_DetachAllReturnNone) + except: + reporter.logXcpt() + + if self.oTstDrv.fpApiVer >= 4.0: + try: + if self.oTstDrv.fpApiVer >= 4.3: + oProgress = oVM.deleteConfig([]) + else: + oProgress = oVM.delete(None) + self.oTstDrv.waitOnProgress(oProgress) + + except: + reporter.logXcpt() + + else: + try: oVM.deleteSettings() + except: reporter.logXcpt() + + return fRc + + def testCreateVMWithDefaults(self): + """ + Test create VM with defaults. + """ + if not self.oTstDrv.importVBoxApi(): + return reporter.error('importVBoxApi'); + + # Get the guest OS types. + try: + aoGuestTypes = self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox, 'guestOSTypes') + if aoGuestTypes is None or not aoGuestTypes: + return reporter.error('No guest OS types'); + except: + return reporter.errorXcpt(); + + # Create VMs with defaults for each of the guest types. + fRc = True + for oGuestType in aoGuestTypes: + try: + sGuestType = oGuestType.id; + except: + fRc = reporter.errorXcpt(); + else: + reporter.testStart(sGuestType); + fRc = self.createVMWithDefaults(sGuestType) and fRc; + reporter.testDone(); + return fRc + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvCreateVMWithDefaults1]).main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/api/tdMoveMedium1.py b/src/VBox/ValidationKit/tests/api/tdMoveMedium1.py new file mode 100755 index 00000000..ca3f3891 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdMoveMedium1.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdMoveMedium1.py $ + +""" +VirtualBox Validation Kit - Medium Move Test #1 +""" + +__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: 155244 $" + + +# Standard Python imports. +import os +import sys + +# 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 testdriver import base +from testdriver import reporter +from testdriver import vboxcon +from testdriver import vboxwrappers + + +class SubTstDrvMoveMedium1(base.SubTestDriverBase): + """ + Sub-test driver for Medium Move Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'move-medium', 'Move Medium'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + return self.testMediumMove() + + # + # Test execution helpers. + # + + def moveTo(self, sLocation, aoMediumAttachments): + for oAttachment in aoMediumAttachments: + try: + oMedium = oAttachment.medium + reporter.log('Move medium "%s" to "%s"' % (oMedium.name, sLocation,)) + except: + reporter.errorXcpt('failed to get the medium from the IMediumAttachment "%s"' % (oAttachment)) + + if self.oTstDrv.fpApiVer >= 5.3 and self.oTstDrv.uRevision > 124748: + try: + oProgress = vboxwrappers.ProgressWrapper(oMedium.moveTo(sLocation), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'move "%s"' % (oMedium.name,)); + except: + return reporter.errorXcpt('Medium::moveTo("%s") for medium "%s" failed' % (sLocation, oMedium.name,)); + else: + try: + oProgress = vboxwrappers.ProgressWrapper(oMedium.setLocation(sLocation), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'move "%s"' % (oMedium.name,)); + except: + return reporter.errorXcpt('Medium::setLocation("%s") for medium "%s" failed' % (sLocation, oMedium.name,)); + + + oProgress.wait() + if oProgress.logResult() is False: + return False + return True + + def checkLocation(self, sLocation, aoMediumAttachments, asFiles): + fRc = True + for oAttachment in aoMediumAttachments: + sFilePath = os.path.join(sLocation, asFiles[oAttachment.port]) + sActualFilePath = oAttachment.medium.location + if os.path.abspath(sFilePath) != os.path.abspath(sActualFilePath): + reporter.log('medium location expected to be "%s" but is "%s"' % (sFilePath, sActualFilePath)) + fRc = False; + if not os.path.exists(sFilePath): + reporter.log('medium file does not exist at "%s"' % (sFilePath,)) + fRc = False; + return fRc + + def testMediumMove(self): + """ + Test medium moving. + """ + reporter.testStart('medium moving') + + try: ## @todo r=bird: Bad 'ing style. + oVM = self.oTstDrv.createTestVM('test-medium-move', 1, None, 4) + assert oVM is not None + + # create hard disk images, one for each file-based backend, using the first applicable extension + fRc = True + oSession = self.oTstDrv.openSession(oVM) + aoDskFmts = self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox.systemProperties, 'mediumFormats') + asFiles = [] + for oDskFmt in aoDskFmts: + aoDskFmtCaps = self.oTstDrv.oVBoxMgr.getArray(oDskFmt, 'capabilities') + if vboxcon.MediumFormatCapabilities_File not in aoDskFmtCaps \ + or vboxcon.MediumFormatCapabilities_CreateDynamic not in aoDskFmtCaps: + continue + (asExts, aTypes) = oDskFmt.describeFileExtensions() + for i in range(0, len(asExts)): #pylint: disable=consider-using-enumerate + if aTypes[i] is vboxcon.DeviceType_HardDisk: + sExt = '.' + asExts[i] + break + if sExt is None: + fRc = False + break + sFile = 'Test' + str(len(asFiles)) + sExt + sHddPath = os.path.join(self.oTstDrv.sScratchPath, sFile) + oHd = oSession.createBaseHd(sHddPath, sFmt=oDskFmt.id, cb=1024*1024) + if oHd is None: + fRc = False + break + + # attach HDD, IDE controller exists by default, but we use SATA just in case + sController='SATA Controller' + fRc = fRc and oSession.attachHd(sHddPath, sController, iPort = len(asFiles), + fImmutable=False, fForceResource=False) + if fRc: + asFiles.append(sFile) + + fRc = fRc and oSession.saveSettings() + + #create temporary subdirectory in the current working directory + sOrigLoc = self.oTstDrv.sScratchPath + sNewLoc = os.path.join(sOrigLoc, 'newLocation') + os.mkdir(sNewLoc, 0o775) + + aoMediumAttachments = oVM.getMediumAttachmentsOfController(sController) + #case 1. Only path without file name, with trailing separator + fRc = self.moveTo(sNewLoc + os.sep, aoMediumAttachments) and fRc + fRc = self.checkLocation(sNewLoc, aoMediumAttachments, asFiles) and fRc + + #case 2. Only path without file name, without trailing separator + fRc = self.moveTo(sOrigLoc, aoMediumAttachments) and fRc + fRc = self.checkLocation(sOrigLoc, aoMediumAttachments, asFiles) and fRc + + #case 3. Path with file name + #The case supposes that user has passed a destination path with a file name but hasn't added an extension/suffix + #to this destination file. User supposes that the extension would be added automatically and to be the same as + #for the original file. Difficult case, apparently this case should follow mv(1) logic + #and the file name is processed as folder name (aka mv(1) logic). + #Be discussed. + fRc = self.moveTo(os.path.join(sNewLoc, 'newName'), aoMediumAttachments) and fRc + asNewFiles = ['newName' + os.path.splitext(s)[1] for s in asFiles] + fRc = self.checkLocation(os.path.join(sNewLoc, 'newName'), aoMediumAttachments, asFiles) and fRc + + #after the case the destination path must be corrected + sNewLoc = os.path.join(sNewLoc, 'newName') + + #case 4. Only file name + fRc = self.moveTo('onlyMediumName', aoMediumAttachments) and fRc + asNewFiles = ['onlyMediumName' + os.path.splitext(s)[1] for s in asFiles] + if self.oTstDrv.fpApiVer >= 5.3: + fRc = self.checkLocation(sNewLoc, aoMediumAttachments, asNewFiles) and fRc + else: + fRc = self.checkLocation(sNewLoc, aoMediumAttachments, + [s.replace('.hdd', '.parallels') for s in asNewFiles]) and fRc + + #case 5. Move all files from a snapshot + fRc = fRc and oSession.takeSnapshot('Snapshot1') + if fRc: + aoMediumAttachments = oVM.getMediumAttachmentsOfController(sController) + asSnapFiles = [os.path.basename(o.medium.name) for o in aoMediumAttachments] + fRc = self.moveTo(sOrigLoc, aoMediumAttachments) and fRc + fRc = self.checkLocation(sOrigLoc, aoMediumAttachments, asSnapFiles) and fRc + + fRc = oSession.close() and fRc + except: + reporter.errorXcpt() + + return reporter.testDone()[1] == 0 + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvMoveMedium1]).main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/api/tdMoveVm1.py b/src/VBox/ValidationKit/tests/api/tdMoveVm1.py new file mode 100755 index 00000000..3bf058d5 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdMoveVm1.py @@ -0,0 +1,768 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# "$Id: tdMoveVm1.py $" + +""" +VirtualBox Validation Kit - VM Move Test #1 +""" + +__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: 155244 $" + +# Standard Python imports. +import os +import sys +import shutil +from collections import defaultdict + +# 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 testdriver import base +from testdriver import reporter +from testdriver import vboxcon +from testdriver import vboxwrappers +from tdMoveMedium1 import SubTstDrvMoveMedium1; # pylint: disable=relative-import + + +# @todo r=aeichner: The whole path handling/checking needs fixing to also work on Windows +# The current quick workaround is to spill os.path.normcase() all over the place when +# constructing paths. I'll leave the real fix to the original author... +class SubTstDrvMoveVm1(base.SubTestDriverBase): + """ + Sub-test driver for VM Move Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'move-vm', 'Move VM'); + + # Note! Hardcoded indexing in test code further down. + self.asRsrcs = [ + os.path.join('5.3','isos','tdMoveVM1.iso'), + os.path.join('5.3','floppy','tdMoveVM1.img') + ]; + + self.asImagesNames = [] + self.dsKeys = { + 'StandardImage': 'SATA Controller', + 'ISOImage': 'IDE Controller', + 'FloppyImage': 'Floppy Controller', + 'SettingsFile': 'Settings File', + 'LogFile': 'Log File', + 'SavedStateFile': 'Saved State File', + 'SnapshotFile': 'Snapshot File' + }; + + def testIt(self): + """ + Execute the sub-testcase. + """ + reporter.testStart(self.sTestName); + reporter.log('ValidationKit folder is "%s"' % (g_ksValidationKitDir,)) + fRc = self.testVMMove(); + return reporter.testDone(fRc is None)[1] == 0; + + # + # Test execution helpers. + # + + def createTestMachine(self): + """ + Document me here, not with hashes above. + """ + oVM = self.oTstDrv.createTestVM('test-vm-move', 1, None, 4) + if oVM is None: + return None + + # create hard disk images, one for each file-based backend, using the first applicable extension + fRc = True + oSession = self.oTstDrv.openSession(oVM) + aoDskFmts = self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox.systemProperties, 'mediumFormats') + + for oDskFmt in aoDskFmts: + aoDskFmtCaps = self.oTstDrv.oVBoxMgr.getArray(oDskFmt, 'capabilities') + if vboxcon.MediumFormatCapabilities_File not in aoDskFmtCaps \ + or vboxcon.MediumFormatCapabilities_CreateDynamic not in aoDskFmtCaps: + continue + (asExts, aTypes) = oDskFmt.describeFileExtensions() + for i in range(0, len(asExts)): # pylint: disable=consider-using-enumerate + if aTypes[i] is vboxcon.DeviceType_HardDisk: + sExt = '.' + asExts[i] + break + if sExt is None: + fRc = False + break + sFile = 'test-vm-move' + str(len(self.asImagesNames)) + sExt + sHddPath = os.path.join(self.oTstDrv.sScratchPath, sFile) + oHd = oSession.createBaseHd(sHddPath, sFmt=oDskFmt.id, cb=1024*1024) + if oHd is None: + fRc = False + break + + # attach HDD, IDE controller exists by default, but we use SATA just in case + sController = self.dsKeys['StandardImage'] + fRc = fRc and oSession.attachHd(sHddPath, sController, iPort = len(self.asImagesNames), + fImmutable=False, fForceResource=False) + if fRc: + self.asImagesNames.append(sFile) + + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc + + if fRc is False: + oVM = None + + return oVM + + def moveVMToLocation(self, sLocation, oVM): + """ + Document me here, not with hashes above. + """ + fRc = True + try: + + ## @todo r=bird: Too much unncessary crap inside try clause. Only oVM.moveTo needs to be here. + ## Though, you could make an argument for oVM.name too, perhaps. + + # move machine + reporter.log('Moving machine "%s" to the "%s"' % (oVM.name, sLocation)) + sType = 'basic' + oProgress = vboxwrappers.ProgressWrapper(oVM.moveTo(sLocation, sType), self.oTstDrv.oVBoxMgr, self.oTstDrv, + 'moving machine "%s"' % (oVM.name,)) + + except: + return reporter.errorXcpt('Machine::moveTo("%s") for machine "%s" failed' % (sLocation, oVM.name,)) + + oProgress.wait() + if oProgress.logResult() is False: + fRc = False + reporter.log('Progress object returned False') + else: + fRc = True + + return fRc + + def checkLocation(self, oMachine, dsReferenceFiles): + """ + Document me. + + Prerequisites: + 1. All standard images are attached to SATA controller + 2. All ISO images are attached to IDE controller + 3. All floppy images are attached to Floppy controller + 4. The type defaultdict from collection is used here (some sort of multimap data structure) + 5. The dsReferenceFiles parameter here is the structure defaultdict(set): + [ + ('StandardImage': ['somedisk.vdi', 'somedisk.vmdk',...]), + ('ISOImage': ['somedisk_1.iso','somedisk_2.iso',...]), + ('FloppyImage': ['somedisk_1.img','somedisk_2.img',...]), + ('SnapshotFile': ['snapshot file 1','snapshot file 2', ...]), + ('SettingsFile', ['setting file',...]), + ('SavedStateFile': ['state file 1','state file 2',...]), + ('LogFile': ['log file 1','log file 2',...]), + ] + """ + + fRc = True + + for sKey, sValue in self.dsKeys.items(): + aActuals = set() + aReferences = set() + + # Check standard images locations, ISO files locations, floppy images locations, snapshots files locations + if sKey in ('StandardImage', 'ISOImage', 'FloppyImage',): + aReferences = dsReferenceFiles[sKey] + if aReferences: + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sValue) ##@todo r=bird: API call, try-except! + for oAttachment in aoMediumAttachments: + aActuals.add(os.path.normcase(oAttachment.medium.location)) + + elif sKey == 'SnapshotFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals = self.__getSnapshotsFiles(oMachine) + + # Check setting file location + elif sKey == 'SettingsFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals.add(os.path.normcase(oMachine.settingsFilePath)) + + # Check saved state files location + elif sKey == 'SavedStateFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals = self.__getStatesFiles(oMachine) + + # Check log files location + elif sKey == 'LogFile': + aReferences = dsReferenceFiles[sKey] + if aReferences: + aActuals = self.__getLogFiles(oMachine) + + if aActuals: + reporter.log('Check %s' % (sKey)) + intersection = aReferences.intersection(aActuals) + for eachItem in intersection: + reporter.log('Item location "%s" is correct' % (eachItem)) + + difference = aReferences.difference(aActuals) + for eachItem in difference: + reporter.log('Item location "%s" isn\'t correct' % (eachItem)) + + reporter.log('####### Reference locations: #######') + for eachItem in aActuals: + reporter.log(' "%s"' % (eachItem)) + + if len(intersection) != len(aActuals): + reporter.log('Not all items in the right location. Check it.') + fRc = False + + return fRc + + def checkAPIVersion(self): + return self.oTstDrv.fpApiVer >= 5.3; + + @staticmethod + def __safeListDir(sDir): + """ Wrapper around os.listdir that returns empty array instead of exceptions. """ + try: + return os.listdir(sDir); + except: + return []; + + def __getStatesFiles(self, oMachine, fPrint = False): + asStateFilesList = set() + sFolder = oMachine.snapshotFolder; + for sFile in self.__safeListDir(sFolder): + if sFile.endswith(".sav"): + sFullPath = os.path.normcase(os.path.join(sFolder, sFile)); + asStateFilesList.add(sFullPath) + if fPrint is True: + reporter.log("State file is %s" % (sFullPath)) + return asStateFilesList + + def __getSnapshotsFiles(self, oMachine, fPrint = False): + asSnapshotsFilesList = set() + sFolder = oMachine.snapshotFolder + for sFile in self.__safeListDir(sFolder): + if sFile.endswith(".sav") is False: + sFullPath = os.path.normcase(os.path.join(sFolder, sFile)); + asSnapshotsFilesList.add(sFullPath) + if fPrint is True: + reporter.log("Snapshot file is %s" % (sFullPath)) + return asSnapshotsFilesList + + def __getLogFiles(self, oMachine, fPrint = False): + asLogFilesList = set() + sFolder = oMachine.logFolder + for sFile in self.__safeListDir(sFolder): + if sFile.endswith(".log"): + sFullPath = os.path.normcase(os.path.join(sFolder, sFile)); + asLogFilesList.add(sFullPath) + if fPrint is True: + reporter.log("Log file is %s" % (sFullPath)) + return asLogFilesList + + + def __testScenario_2(self, oSession, oMachine, sNewLoc, sOldLoc): + """ + All disks attached to VM are located inside the VM's folder. + There are no any snapshots and logs. + """ + + sController = self.dsKeys['StandardImage'] + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sController) + oSubTstDrvMoveMedium1Instance = SubTstDrvMoveMedium1(self.oTstDrv) + oSubTstDrvMoveMedium1Instance.moveTo(sOldLoc, aoMediumAttachments) + + del oSubTstDrvMoveMedium1Instance + + dsReferenceFiles = defaultdict(set) + + for s in self.asImagesNames: + reporter.log('"%s"' % (s,)) + dsReferenceFiles['StandardImage'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + s)) + + sSettingFile = os.path.join(sNewLoc, os.path.join(oMachine.name, oMachine.name + '.vbox')) + dsReferenceFiles['SettingsFile'].add(os.path.normcase(sSettingFile)) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 2nd scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 2nd scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + + fRes = oSession.saveSettings() + if fRes is False: + reporter.log('2nd scenario: Couldn\'t save machine settings') + + return fRc + + def __testScenario_3(self, oSession, oMachine, sNewLoc): + """ + There are snapshots + """ + + # At moment, it's used only one snapshot due to the difficulty to get + # all attachments of the machine (i.e. not only attached at moment) + cSnap = 1 + + for counter in range(1,cSnap+1): + strSnapshot = 'Snapshot' + str(counter) + fRc = oSession.takeSnapshot(strSnapshot) + if fRc is False: + reporter.testFailure('3rd scenario: Can\'t take snapshot "%s"' % (strSnapshot,)) + + dsReferenceFiles = defaultdict(set) + + sController = self.dsKeys['StandardImage'] + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sController) + if fRc is True: + for oAttachment in aoMediumAttachments: + sRes = oAttachment.medium.location.rpartition(os.sep) + dsReferenceFiles['SnapshotFile'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + + 'Snapshots' + os.sep + sRes[2])) + + sSettingFile = os.path.join(sNewLoc, os.path.join(oMachine.name, oMachine.name + '.vbox')) + dsReferenceFiles['SettingsFile'].add(os.path.normcase(sSettingFile)) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 3rd scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 3rd scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + + fRes = oSession.saveSettings() + if fRes is False: + reporter.log('3rd scenario: Couldn\'t save machine settings') + + return fRc + + def __testScenario_4(self, oMachine, sNewLoc): + """ + There are one or more save state files in the snapshots folder + and some files in the logs folder. + Here we run VM, next stop it in the "save" state. + And next move VM + """ + + # Run VM and get new Session object. + oSession = self.oTstDrv.startVm(oMachine); + if not oSession: + return False; + + # Some time interval should be here for not closing VM just after start. + self.oTstDrv.waitForTasks(1000); + + if oMachine.state != self.oTstDrv.oVBoxMgr.constants.MachineState_Running: + reporter.log("Machine '%s' is not Running" % (oMachine.name)) + fRc = False + + # Call Session::saveState(), already closes session unless it failed. + fRc = oSession.saveState() + if fRc is True: + reporter.log("Machine is in saved state") + + fRc = self.oTstDrv.terminateVmBySession(oSession) + + if fRc is True: + # Create a new Session object for moving VM. + oSession = self.oTstDrv.openSession(oMachine) + + # Always clear before each scenario. + dsReferenceFiles = defaultdict(set) + + asLogs = self.__getLogFiles(oMachine) + for sFile in asLogs: + sRes = sFile.rpartition(os.sep) + dsReferenceFiles['LogFile'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + + 'Logs' + os.sep + sRes[2])) + + asStates = self.__getStatesFiles(oMachine) + for sFile in asStates: + sRes = sFile.rpartition(os.sep) + dsReferenceFiles['SavedStateFile'].add(os.path.normcase(sNewLoc + os.sep + oMachine.name + os.sep + + 'Snapshots' + os.sep + sRes[2])) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 4th scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 4th scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + + # cleaning up: get rid of saved state + fRes = oSession.discardSavedState(True) + if fRes is False: + reporter.log('4th scenario: Failed to discard the saved state of machine') + + fRes = oSession.close() + if fRes is False: + reporter.log('4th scenario: Couldn\'t close machine session') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 4th scenario: Terminate machine by session failed... !!!!!!!!!!!!!!!!!!') + + return fRc + + def __testScenario_5(self, oMachine, sNewLoc, sOldLoc): + """ + There is an ISO image (.iso) attached to the VM. + Prerequisites - there is IDE Controller and there are no any images attached to it. + """ + + fRc = True + sISOImageName = 'tdMoveVM1.iso' + + # Always clear before each scenario. + dsReferenceFiles = defaultdict(set) + + # Create a new Session object. + oSession = self.oTstDrv.openSession(oMachine) + + sISOLoc = self.asRsrcs[0] # '5.3/isos/tdMoveVM1.iso' + reporter.log("sHost is '%s', sResourcePath is '%s'" % (self.oTstDrv.sHost, self.oTstDrv.sResourcePath)) + sISOLoc = self.oTstDrv.getFullResourceName(sISOLoc) + reporter.log("sISOLoc is '%s'" % (sISOLoc,)) + + if not os.path.exists(sISOLoc): + reporter.log('ISO file does not exist at "%s"' % (sISOLoc,)) + fRc = False + + # Copy ISO image from the common resource folder into machine folder. + shutil.copy(sISOLoc, sOldLoc) + + # Attach ISO image to the IDE controller. + if fRc is True: + # Set actual ISO location. + sISOLoc = sOldLoc + os.sep + sISOImageName + reporter.log("sISOLoc is '%s'" % (sISOLoc,)) + if not os.path.exists(sISOLoc): + reporter.log('ISO file does not exist at "%s"' % (sISOLoc,)) + fRc = False + + sController=self.dsKeys['ISOImage'] + aoMediumAttachments = oMachine.getMediumAttachmentsOfController(sController) + iPort = len(aoMediumAttachments) + fRc = oSession.attachDvd(sISOLoc, sController, iPort, iDevice = 0) + dsReferenceFiles['ISOImage'].add(os.path.normcase(os.path.join(os.path.join(sNewLoc, oMachine.name), sISOImageName))) + + if fRc is True: + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 5th scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 5th scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 5th scenario: Attach ISO image failed... !!!!!!!!!!!!!!!!!!') + + # Detach ISO image. + fRes = oSession.detachHd(sController, iPort, 0) + if fRes is False: + reporter.log('5th scenario: Couldn\'t detach image from the controller %s ' + 'port %s device %s' % (sController, iPort, 0)) + + fRes = oSession.saveSettings() + if fRes is False: + reporter.log('5th scenario: Couldn\'t save machine settings') + + fRes = oSession.close() + if fRes is False: + reporter.log('5th scenario: Couldn\'t close machine session') + + return fRc + + def __testScenario_6(self, oMachine, sNewLoc, sOldLoc): + """ + There is a floppy image (.img) attached to the VM. + Prerequisites - there is Floppy Controller and there are no any images attached to it. + """ + + fRc = True + + # Always clear before each scenario. + dsReferenceFiles = defaultdict(set) + + # Create a new Session object. + oSession = self.oTstDrv.openSession(oMachine) + + sFloppyLoc = self.asRsrcs[1] # '5.3/floppy/tdMoveVM1.img' + sFloppyLoc = self.oTstDrv.getFullResourceName(sFloppyLoc) + + if not os.path.exists(sFloppyLoc): + reporter.log('Floppy disk does not exist at "%s"' % (sFloppyLoc,)) + fRc = False + + # Copy floppy image from the common resource folder into machine folder. + shutil.copy(sFloppyLoc, sOldLoc) + + # Attach floppy image. + if fRc is True: + # Set actual floppy location. + sFloppyImageName = 'tdMoveVM1.img' + sFloppyLoc = sOldLoc + os.sep + sFloppyImageName + sController=self.dsKeys['FloppyImage'] + fRc = fRc and oSession.attachFloppy(sFloppyLoc, sController, 0, 0) + dsReferenceFiles['FloppyImage'].add(os.path.normcase(os.path.join(os.path.join(sNewLoc, oMachine.name), + sFloppyImageName))) + + if fRc is True: + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 6th scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 6th scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 6th scenario: Attach floppy image failed... !!!!!!!!!!!!!!!!!!') + + # Detach floppy image. + fRes = oSession.detachHd(sController, 0, 0) + if fRes is False: + reporter.log('6th scenario: Couldn\'t detach image from the controller %s port %s device %s' % (sController, 0, 0)) + + fRes = oSession.saveSettings() + if fRes is False: + reporter.testFailure('6th scenario: Couldn\'t save machine settings') + + fRes = oSession.close() + if fRes is False: + reporter.log('6th scenario: Couldn\'t close machine session') + return fRc + + + def testVMMove(self): + """ + Test machine moving. + """ + if not self.oTstDrv.importVBoxApi(): + return False + + fSupported = self.checkAPIVersion() + reporter.log('ValidationKit folder is "%s"' % (g_ksValidationKitDir,)) + + if fSupported is False: + reporter.log('API version %s is too old. Just skip this test.' % (self.oTstDrv.fpApiVer)) + return None; + reporter.log('API version is "%s".' % (self.oTstDrv.fpApiVer)) + + # Scenarios + # 1. All disks attached to VM are located outside the VM's folder. + # There are no any snapshots and logs. + # In this case only VM setting file should be moved (.vbox file) + # + # 2. All disks attached to VM are located inside the VM's folder. + # There are no any snapshots and logs. + # + # 3. There are snapshots. + # + # 4. There are one or more save state files in the snapshots folder + # and some files in the logs folder. + # + # 5. There is an ISO image (.iso) attached to the VM. + # + # 6. There is a floppy image (.img) attached to the VM. + # + # 7. There are shareable disk and immutable disk attached to the VM. + + try: ## @todo r=bird: Would be nice to use sub-tests here for each scenario, however + ## this try/catch as well as lots of return points makes that very hard. + ## Big try/catch stuff like this should be avoided. + # Create test machine. + oMachine = self.createTestMachine() + if oMachine is None: + reporter.error('Failed to create test machine') + + # Create temporary subdirectory in the current working directory. + sOrigLoc = self.oTstDrv.sScratchPath + sBaseLoc = os.path.join(sOrigLoc, 'moveFolder') + os.mkdir(sBaseLoc, 0o775) + + # lock machine + # get session machine + oSession = self.oTstDrv.openSession(oMachine) + fRc = True + + sNewLoc = sBaseLoc + os.sep + + dsReferenceFiles = defaultdict(set) + + # + # 1. case: + # + # All disks attached to VM are located outside the VM's folder. + # There are no any snapshots and logs. + # In this case only VM setting file should be moved (.vbox file) + # + reporter.log("Scenario #1:"); + for s in self.asImagesNames: + reporter.log('"%s"' % (s,)) + dsReferenceFiles['StandardImage'].add(os.path.normcase(os.path.join(sOrigLoc, s))) + + sSettingFile = os.path.normcase(os.path.join(sNewLoc, os.path.join(oMachine.name, oMachine.name + '.vbox'))) + dsReferenceFiles['SettingsFile'].add(sSettingFile) + + fRc = self.moveVMToLocation(sNewLoc, oSession.o.machine) + + if fRc is True: + fRc = self.checkLocation(oSession.o.machine, dsReferenceFiles) + if fRc is False: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 1st scenario: Check locations failed... !!!!!!!!!!!!!!!!!!') + return False; + else: + reporter.testFailure('!!!!!!!!!!!!!!!!!! 1st scenario: Move VM failed... !!!!!!!!!!!!!!!!!!') + return False; + + fRc = oSession.saveSettings() + if fRc is False: + reporter.testFailure('1st scenario: Couldn\'t save machine settings') + + # + # 2. case: + # + # All disks attached to VM are located inside the VM's folder. + # There are no any snapshots and logs. + # + reporter.log("Scenario #2:"); + sOldLoc = sNewLoc + oMachine.name + os.sep + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_2nd_scenario') + os.mkdir(sNewLoc, 0o775) + + fRc = self.__testScenario_2(oSession, oMachine, sNewLoc, sOldLoc) + if fRc is False: + return False; + + # + # 3. case: + # + # There are snapshots. + # + reporter.log("Scenario #3:"); + sOldLoc = sNewLoc + oMachine.name + os.sep + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_3rd_scenario') + os.mkdir(sNewLoc, 0o775) + + fRc = self.__testScenario_3(oSession, oMachine, sNewLoc) + if fRc is False: + return False; + + # + # 4. case: + # + # There are one or more save state files in the snapshots folder + # and some files in the logs folder. + # Here we run VM, next stop it in the "save" state. + # And next move VM + # + reporter.log("Scenario #4:"); + sOldLoc = sNewLoc + oMachine.name + os.sep + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_4th_scenario') + os.mkdir(sNewLoc, 0o775) + + # Close Session object because after starting VM we get new instance of session + fRc = oSession.close() and fRc + if fRc is False: + reporter.log('Couldn\'t close machine session') + + del oSession + + fRc = self.__testScenario_4(oMachine, sNewLoc) + if fRc is False: + return False; + + # + # 5. case: + # + # There is an ISO image (.iso) attached to the VM. + # Prerequisites - there is IDE Controller and there are no any images attached to it. + # + reporter.log("Scenario #5:"); + sOldLoc = sNewLoc + os.sep + oMachine.name + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_5th_scenario') + os.mkdir(sNewLoc, 0o775) + fRc = self.__testScenario_5(oMachine, sNewLoc, sOldLoc) + if fRc is False: + return False; + + # + # 6. case: + # + # There is a floppy image (.img) attached to the VM. + # Prerequisites - there is Floppy Controller and there are no any images attached to it. + # + reporter.log("Scenario #6:"); + sOldLoc = sNewLoc + os.sep + oMachine.name + sNewLoc = os.path.join(sOrigLoc, 'moveFolder_6th_scenario') + os.mkdir(sNewLoc, 0o775) + fRc = self.__testScenario_6(oMachine, sNewLoc, sOldLoc) + if fRc is False: + return False; + +# # +# # 7. case: +# # +# # There are shareable disk and immutable disk attached to the VM. +# # +# reporter.log("Scenario #7:"); +# fRc = fRc and oSession.saveSettings() +# if fRc is False: +# reporter.log('Couldn\'t save machine settings') +# + + assert fRc is True + except: + reporter.errorXcpt() + + return fRc; + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvMoveVm1]).main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/api/tdPython1.py b/src/VBox/ValidationKit/tests/api/tdPython1.py new file mode 100755 index 00000000..dd9dffc7 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdPython1.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdPython1.py $ + +""" +VirtualBox Validation Kit - Python Bindings Test #1 +""" + +__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: 155244 $" + + +# Standard Python imports. +import os +import sys +import time +import threading + +# 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 testdriver import base; +from testdriver import reporter; + + +class SubTstDrvPython1(base.SubTestDriverBase): + """ + Sub-test driver for Python Bindings Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'python-binding', 'Python bindings'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + return self.testEventQueueWaiting() \ + and self.testEventQueueInterrupt(); + + # + # Test execution helpers. + # + + def testEventQueueWaitingThreadProc(self): + """ Thread procedure for checking that waitForEvents fails when not called by the main thread. """ + try: + rc2 = self.oTstDrv.oVBoxMgr.waitForEvents(0); + except: + return True; + reporter.error('waitForEvents() returned "%s" when called on a worker thread, expected exception.' % (rc2,)); + return False; + + def testEventQueueWaiting(self): + """ + Test event queue waiting. + """ + reporter.testStart('waitForEvents'); + + # Check return values and such. + for cMsTimeout in (0, 1, 2, 3, 256, 1000, 0): + iLoop = 0; + while True: + try: + rc = self.oTstDrv.oVBoxMgr.waitForEvents(cMsTimeout); + except: + reporter.errorXcpt(); + break; + if not isinstance(rc, int): + reporter.error('waitForEvents returns non-integer type'); + break; + if rc == 1: + break; + if rc != 0: + reporter.error('waitForEvents returns "%s", expected 0 or 1' % (rc,)); + break; + iLoop += 1; + if iLoop > 10240: + reporter.error('waitForEvents returns 0 (success) %u times. ' + 'Expected 1 (timeout/interrupt) after a call or two.' + % (iLoop,)); + break; + if reporter.testErrorCount() != 0: + break; + + # Check that we get an exception when trying to call the method from + # a different thread. + reporter.log('If running a debug build, you will see an ignored assertion now. Please ignore it.') + sVBoxAssertSaved = os.environ.get('VBOX_ASSERT', 'breakpoint'); + os.environ['VBOX_ASSERT'] = 'ignore'; + oThread = threading.Thread(target=self.testEventQueueWaitingThreadProc); + oThread.start(); + oThread.join(); + os.environ['VBOX_ASSERT'] = sVBoxAssertSaved; + + return reporter.testDone()[1] == 0; + + def interruptWaitEventsThreadProc(self): + """ Thread procedure that's used for waking up the main thread. """ + time.sleep(2); + try: + rc2 = self.oTstDrv.oVBoxMgr.interruptWaitEvents(); + except: + reporter.errorXcpt(); + else: + if rc2 is True: + return True; + reporter.error('interruptWaitEvents returned "%s" when called from other thread, expected True' % (rc2,)); + return False; + + def testEventQueueInterrupt(self): + """ + Test interrupting an event queue wait. + """ + reporter.testStart('interruptWait'); + + # interrupt ourselves first and check the return value. + for i in range(0, 10): + try: + rc = self.oTstDrv.oVBoxMgr.interruptWaitEvents(); + except: + reporter.errorXcpt(); + break; + if rc is not True: + reporter.error('interruptWaitEvents returned "%s" expected True' % (rc,)); + break + + if reporter.testErrorCount() == 0: + # + # Interrupt a waitForEvents call. + # + # This test ASSUMES that no other events are posted to the thread's + # event queue once we've drained it. Also ASSUMES the box is + # relatively fast and not too busy because we're timing sensitive. + # + for i in range(0, 4): + # Try quiesce the event queue. + for _ in range(1, 100): + self.oTstDrv.oVBoxMgr.waitForEvents(0); + + # Create a thread that will interrupt us in 2 seconds. + try: + oThread = threading.Thread(target=self.interruptWaitEventsThreadProc); + oThread.setDaemon(False); # pylint: disable=deprecated-method + except: + reporter.errorXcpt(); + break; + + cMsTimeout = 20000; + if i == 2: + cMsTimeout = -1; + elif i == 3: + cMsTimeout = -999999; + + # Do the wait. + oThread.start(); + msNow = base.timestampMilli(); + try: + rc = self.oTstDrv.oVBoxMgr.waitForEvents(cMsTimeout); + except: + reporter.errorXcpt(); + else: + msElapsed = base.timestampMilli() - msNow; + + # Check the return code and elapsed time. + if not isinstance(rc, int): + reporter.error('waitForEvents returns non-integer type after %u ms, expected 1' % (msElapsed,)); + elif rc != 1: + reporter.error('waitForEvents returned "%s" after %u ms, expected 1' % (rc, msElapsed)); + if msElapsed > 15000: + reporter.error('waitForEvents after %u ms, expected just above 2-3 seconds' % (msElapsed,)); + elif msElapsed < 100: + reporter.error('waitForEvents after %u ms, expected more than 100 ms.' % (msElapsed,)); + + oThread.join(); + oThread = None; + if reporter.testErrorCount() != 0: + break; + reporter.log('Iteration %u was successful...' % (i + 1,)); + return reporter.testDone()[1] == 0; + + +if __name__ == '__main__': + from tests.api.tdApi1 import tdApi1; + sys.exit(tdApi1([SubTstDrvPython1]).main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py b/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py new file mode 100755 index 00000000..e69a7d17 --- /dev/null +++ b/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdTreeDepth1.py $ + +""" +VirtualBox Validation Kit - Medium and Snapshot Tree Depth Test #1 +""" + +__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: 156148 $" + + +# Standard Python imports. +import os +import sys +import random + +# 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 testdriver import base +from testdriver import reporter +from testdriver import vboxcon + + +class SubTstDrvTreeDepth1(base.SubTestDriverBase): + """ + Sub-test driver for Medium and Snapshot Tree Depth Test #1. + """ + + def __init__(self, oTstDrv): + base.SubTestDriverBase.__init__(self, oTstDrv, 'tree-depth', 'Media and Snapshot tree depths'); + + def testIt(self): + """ + Execute the sub-testcase. + """ + return self.testMediumTreeDepth() \ + and self.testSnapshotTreeDepth() + + # + # Test execution helpers. + # + + def testMediumTreeDepth(self): + """ + Test medium tree depth. + """ + reporter.testStart('mediumTreeDepth') + + try: + oVBox = self.oTstDrv.oVBoxMgr.getVirtualBox() + oVM = self.oTstDrv.createTestVM('test-medium', 1, None, 4) + assert oVM is not None + + # create chain with up to 64 disk images (medium tree depth limit) + fRc = True + oSession = self.oTstDrv.openSession(oVM) + cImages = random.randrange(1, 64); + reporter.log('Creating chain with %d disk images' % (cImages)) + for i in range(1, cImages + 1): + sHddPath = os.path.join(self.oTstDrv.sScratchPath, 'Test' + str(i) + '.vdi') + if i == 1: + oHd = oSession.createBaseHd(sHddPath, cb=1024*1024) + else: + oHd = oSession.createDiffHd(oHd, sHddPath) + if oHd is None: + fRc = False + break + + # modify the VM config, attach HDD + fRc = fRc and oSession.attachHd(sHddPath, sController='SATA Controller', fImmutable=False, fForceResource=False) + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc + ## @todo r=klaus: count known hard disk images, should be cImages + + # unregister, making sure the images are closed + sSettingsFile = oVM.settingsFilePath + fDetachAll = random.choice([False, True]) + if fDetachAll: + reporter.log('unregistering VM, DetachAll style') + else: + reporter.log('unregistering VM, UnregisterOnly style') + self.oTstDrv.forgetTestMachine(oVM) + if fDetachAll: + aoHDs = oVM.unregister(vboxcon.CleanupMode_DetachAllReturnHardDisksOnly) + for oHD in aoHDs: + oHD.close() + aoHDs = None + else: + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + # If there is no base image (expected) then there are no leftover + # child images either. Can be changed later once the todos above + # and below are resolved. + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d initial base images, expected %d' % (cBaseImages, 0)); + + # re-register to test loading of settings + reporter.log('opening VM %s, testing config reading' % (sSettingsFile)) + if self.oTstDrv.fpApiVer >= 7.0: + # Needs a password parameter since 7.0. + oVM = oVBox.openMachine(sSettingsFile, "") + else: + oVM = oVBox.openMachine(sSettingsFile) + oVBox.registerMachine(oVM); + ## @todo r=klaus: count known hard disk images, should be cImages + + reporter.log('unregistering VM') + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d base images after unregistering, expected %d' % (cBaseImages, 0)); + + except: + reporter.errorXcpt() + + return reporter.testDone()[1] == 0 + + def testSnapshotTreeDepth(self): + """ + Test snapshot tree depth. + """ + reporter.testStart('snapshotTreeDepth') + + try: + oVBox = self.oTstDrv.oVBoxMgr.getVirtualBox() + oVM = self.oTstDrv.createTestVM('test-snap', 1, None, 4) + assert oVM is not None + + # modify the VM config, create and attach empty HDD + oSession = self.oTstDrv.openSession(oVM) + sHddPath = os.path.join(self.oTstDrv.sScratchPath, 'TestSnapEmpty.vdi') + fRc = True + fRc = fRc and oSession.createAndAttachHd(sHddPath, cb=1024*1024, sController='SATA Controller', fImmutable=False) + fRc = fRc and oSession.saveSettings() + + # take up to 200 snapshots (250 is the snapshot tree depth limit (settings.h:SETTINGS_SNAPSHOT_DEPTH_MAX)) + cSnapshots = random.randrange(1, 200); + reporter.log('Taking %d snapshots' % (cSnapshots)) + for i in range(1, cSnapshots + 1): + fRc = fRc and oSession.takeSnapshot('Snapshot ' + str(i)) + fRc = oSession.close() and fRc + oSession = None + reporter.log('API reports %i snapshots' % (oVM.snapshotCount)) + fRc = fRc and oVM.snapshotCount == cSnapshots + if oVM.snapshotCount != cSnapshots: + reporter.error('Got %d initial snapshots, expected %d' % (oVM.snapshotCount, cSnapshots)); + + # unregister, making sure the images are closed + sSettingsFile = oVM.settingsFilePath + fDetachAll = random.choice([False, True]) + if fDetachAll: + reporter.log('unregistering VM, DetachAll style') + else: + reporter.log('unregistering VM, UnregisterOnly style') + self.oTstDrv.forgetTestMachine(oVM) + if fDetachAll: + aoHDs = oVM.unregister(vboxcon.CleanupMode_DetachAllReturnHardDisksOnly) + for oHD in aoHDs: + oHD.close() + aoHDs = None + else: + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + # If there is no base image (expected) then there are no leftover + # child images either. Can be changed later once the todos above + # and below are resolved. + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d initial base images, expected %d' % (cBaseImages, 0)); + + # re-register to test loading of settings + reporter.log('opening VM %s, testing config reading' % (sSettingsFile)) + if self.oTstDrv.fpApiVer >= 7.0: + # Needs a password parameter since 7.0. + oVM = oVBox.openMachine(sSettingsFile, "") + else: + oVM = oVBox.openMachine(sSettingsFile) + oVBox.registerMachine(oVM); + reporter.log('API reports %i snapshots' % (oVM.snapshotCount)) + fRc = fRc and oVM.snapshotCount == cSnapshots + if oVM.snapshotCount != cSnapshots: + reporter.error('Got %d snapshots after re-registering, expected %d' % (oVM.snapshotCount, cSnapshots)); + + reporter.log('unregistering VM') + oVM.unregister(vboxcon.CleanupMode_UnregisterOnly) + oVM = None + + cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(oVBox, 'hardDisks')) + reporter.log('API reports %i base images' % (cBaseImages)) + fRc = fRc and cBaseImages == 0 + if cBaseImages != 0: + reporter.error('Got %d base images after unregistering, expected %d' % (cBaseImages, 0)); + except: + reporter.errorXcpt() + + return reporter.testDone()[1] == 0 + + +if __name__ == '__main__': + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + from tdApi1 import tdApi1; # pylint: disable=relative-import + sys.exit(tdApi1([SubTstDrvTreeDepth1]).main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/audio/Makefile.kmk b/src/VBox/ValidationKit/tests/audio/Makefile.kmk new file mode 100644 index 00000000..977cf454 --- /dev/null +++ b/src/VBox/ValidationKit/tests/audio/Makefile.kmk @@ -0,0 +1,50 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Audio tests. +# + +# +# Copyright (C) 2021-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 += ValidationKitTestsAudio +ValidationKitTestsAudio_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsAudio_INST = $(INST_VALIDATIONKIT)tests/audio/ +ValidationKitTestsAudio_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdAudioTest.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsAudio_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/audio/tdAudioTest.py b/src/VBox/ValidationKit/tests/audio/tdAudioTest.py new file mode 100755 index 00000000..67a1bcb5 --- /dev/null +++ b/src/VBox/ValidationKit/tests/audio/tdAudioTest.py @@ -0,0 +1,823 @@ +# -*- coding: utf-8 -*- +# $Id: tdAudioTest.py $ + +""" +AudioTest test driver which invokes the VKAT (Validation Kit Audio Test) +binary to perform the actual audio tests. + +The generated test set archive on the guest will be downloaded by TXS +to the host for later audio comparison / verification. +""" + +__copyright__ = \ +""" +Copyright (C) 2021-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: 155244 $" + +# Standard Python imports. +from datetime import datetime +import os +import sys +import subprocess +import time +import threading + +# 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 testdriver import reporter +from testdriver import base +from testdriver import vbox +from testdriver import vboxcon; +from testdriver import vboxtestvms +from common import utils; + +# pylint: disable=unnecessary-semicolon + +class tdAudioTest(vbox.TestDriver): + """ + Runs various audio tests. + """ + def __init__(self): + vbox.TestDriver.__init__(self); + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat'); + self.asGstVkatPaths = [ + # Debugging stuff (SCP'd over to the guest). + '/tmp/vkat', + '/tmp/VBoxAudioTest', + 'C:\\Temp\\vkat', + 'C:\\Temp\\VBoxAudioTest', + # Validation Kit .ISO. + '${CDROM}/vboxvalidationkit/${OS/ARCH}/vkat${EXESUFF}', + '${CDROM}/${OS/ARCH}/vkat${EXESUFF}', + # Test VMs. + '/opt/apps/vkat', + '/opt/apps/VBoxAudioTest', + '/apps/vkat', + '/apps/VBoxAudioTest', + 'C:\\Apps\\vkat${EXESUFF}', + 'C:\\Apps\\VBoxAudioTest${EXESUFF}', + ## @todo VBoxAudioTest on Guest Additions? + ]; + self.asTestsDef = [ + 'guest_tone_playback', 'guest_tone_recording' + ]; + self.asTests = self.asTestsDef; + + # Optional arguments passing to VKAT when doing the actual audio tests. + self.asVkatTestArgs = []; + # Optional arguments passing to VKAT when verifying audio test sets. + self.asVkatVerifyArgs = []; + + # Exit code of last host process execution, shared between exeuction thread and main thread. + # This ASSUMES that we only have one thread running at a time. Rather hacky, but does the job for now. + self.iThreadHstProcRc = 0; + + # Enable audio debug mode. + # + # This is needed in order to load and use the Validation Kit audio driver, + # which in turn is being used in conjunction with the guest side to record + # output (guest is playing back) and injecting input (guest is recording). + self.asOptExtraData = [ + 'VBoxInternal2/Audio/Debug/Enabled:true', + ]; + + # Name of the running VM to use for running the test driver. Optional, and None if not being used. + self.sRunningVmName = None; + + # Audio controller type to use. + # If set to None, the OS' recommended controller type will be used (defined by Main). + self.sAudioControllerType = None; + + def showUsage(self): + """ + Shows the audio test driver-specific command line options. + """ + fRc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAudioTest Options:'); + reporter.log(' --runningvmname <vmname>'); + reporter.log(' --audio-tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --audio-controller-type <HDA|AC97|SB16>'); + reporter.log(' Default: recommended controller'); + reporter.log(' --audio-test-count <number>'); + reporter.log(' Default: 0 (means random)'); + reporter.log(' --audio-test-tone-duration <ms>'); + reporter.log(' Default: 0 (means random)'); + reporter.log(' --audio-verify-max-diff-count <number>'); + reporter.log(' Default: 0 (strict)'); + reporter.log(' --audio-verify-max-diff-percent <0-100>'); + reporter.log(' Default: 0 (strict)'); + reporter.log(' --audio-verify-max-size-percent <0-100>'); + reporter.log(' Default: 0 (strict)'); + return fRc; + + def parseOption(self, asArgs, iArg): + """ + Parses the audio test driver-specific command line options. + """ + if asArgs[iArg] == '--runningvmname': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--runningvmname" needs VM name'); + + self.sRunningVmName = asArgs[iArg]; + elif asArgs[iArg] == '--audio-tests': + iArg += 1; + if asArgs[iArg] == 'all': # Nice for debugging scripts. + self.asTests = self.asTestsDef; + else: + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--audio-tests" value "%s" is not valid; valid values are: %s' + % (s, ' '.join(self.asTestsDef))); + elif asArgs[iArg] == '--audio-controller-type': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + if asArgs[iArg] == 'HDA' \ + or asArgs[iArg] == 'AC97' \ + or asArgs[iArg] == 'SB16': + self.sAudioControllerType = asArgs[iArg]; + else: + raise base.InvalidOption('The "--audio-controller-type" value "%s" is not valid' % (asArgs[iArg])); + elif asArgs[iArg] == '--audio-test-count' \ + or asArgs[iArg] == '--audio-test-tone-duration': + # Strip the "--audio-test-" prefix and keep the options as defined in VKAT, + # e.g. "--audio-test-count" -> "--count". That way we don't + # need to do any special argument translation and whatnot. + self.asVkatTestArgs.extend(['--' + asArgs[iArg][len('--audio-test-'):]]); + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.asVkatTestArgs.extend([asArgs[iArg]]); + elif asArgs[iArg] == '--audio-verify-max-diff-count' \ + or asArgs[iArg] == '--audio-verify-max-diff-percent' \ + or asArgs[iArg] == '--audio-verify-max-size-percent': + # Strip the "--audio-verify-" prefix and keep the options as defined in VKAT, + # e.g. "--audio-verify-max-diff-count" -> "--max-diff-count". That way we don't + # need to do any special argument translation and whatnot. + self.asVkatVerifyArgs.extend(['--' + asArgs[iArg][len('--audio-verify-'):]]); + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1])); + self.asVkatVerifyArgs.extend([asArgs[iArg]]); + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionVerify(self): + """ + Verifies the test driver before running. + """ + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + """ + Configures the test driver before running. + """ + if not self.importVBoxApi(): # So we can use the constant below. + return False; + + # Make sure that the Validation Kit .ISO is mounted + # to find the VKAT (Validation Kit Audio Test) binary on it. + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Executes the test driver. + """ + + # Disable maximum logging line restrictions per group. + # This comes in handy when running this test driver in a (very) verbose mode, e.g. for debugging. + os.environ['VBOX_LOG_MAX_PER_GROUP'] = '0'; + os.environ['VBOX_RELEASE_LOG_MAX_PER_GROUP'] = '0'; + os.environ['VKAT_RELEASE_LOG_MAX_PER_GROUP'] = '0'; + + if self.sRunningVmName is None: + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig); + return self.actionExecuteOnRunnigVM(); + + def actionExecuteOnRunnigVM(self): + """ + Executes the tests in an already configured + running VM. + """ + if not self.importVBoxApi(): + return False; + + fRc = True; + + oVM = None; + oVirtualBox = None; + + oVirtualBox = self.oVBoxMgr.getVirtualBox(); + try: + oVM = oVirtualBox.findMachine(self.sRunningVmName); + if oVM.state != self.oVBoxMgr.constants.MachineState_Running: + reporter.error("Machine '%s' is not in Running state (state is %d)" % (self.sRunningVmName, oVM.state)); + fRc = False; + except: + reporter.errorXcpt("Machine '%s' not found" % (self.sRunningVmName)); + fRc = False; + + if fRc: + oSession = self.openSession(oVM); + if oSession: + # Tweak this to your likings. + oTestVm = vboxtestvms.TestVm('runningvm', sKind = 'WindowsXP'); #sKind = 'WindowsXP' # sKind = 'Ubuntu_64' + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 30 * 1000); + if fRc: + self.doTest(oTestVm, oSession, oTxsSession); + else: + reporter.error("Unable to open session for machine '%s'" % (self.sRunningVmName)); + fRc = False; + + if oVM: + del oVM; + if oVirtualBox: + del oVirtualBox; + return fRc; + + def getGstVkatLogFilePath(self, oTestVm): + """ + Returns the log file path of VKAT running on the guest (daemonized). + """ + return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vkat-guest.log'); + + def locateGstBinary(self, oSession, oTxsSession, asPaths): + """ + Locates a guest binary on the guest by checking the paths in \a asPaths. + """ + for sCurPath in asPaths: + reporter.log2('Checking for \"%s\" ...' % (sCurPath)); + if self.txsIsFile(oSession, oTxsSession, sCurPath, fIgnoreErrors = True): + return (True, sCurPath); + reporter.error('Unable to find guest binary in any of these places:\n%s' % ('\n'.join(asPaths),)); + return (False, ""); + + def executeHstLoop(self, sWhat, asArgs, asEnv = None, fAsAdmin = False): + """ + Inner loop which handles the execution of a host binary. + + Might be called synchronously in main thread or via the thread exeuction helper (asynchronous). + """ + fRc = False; + + asEnvTmp = os.environ.copy(); + if asEnv: + for sEnv in asEnv: + sKey, sValue = sEnv.split('='); + reporter.log2('Setting env var \"%s\" -> \"%s\"' % (sKey, sValue)); + os.environ[sKey] = sValue; # Also apply it to the current environment. + asEnvTmp[sKey] = sValue; + + try: + # Spawn process. + if fAsAdmin \ + and utils.getHostOs() != 'win': + oProcess = utils.sudoProcessStart(asArgs, env = asEnvTmp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); + else: + oProcess = utils.processStart(asArgs, env = asEnvTmp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT); + + if not oProcess: + reporter.error('Starting process for "%s" failed!' % (sWhat)); + return False; + + iPid = oProcess.pid; + self.pidFileAdd(iPid, sWhat); + + iRc = 0; + + # For Python 3.x we provide "real-time" output. + if sys.version_info[0] >= 3: + while oProcess.stdout.readable(): # pylint: disable=no-member + sStdOut = oProcess.stdout.readline(); + if sStdOut: + sStdOut = sStdOut.strip(); + reporter.log('%s: %s' % (sWhat, sStdOut)); + iRc = oProcess.poll(); + if iRc is not None: + break; + else: + # For Python 2.x it's too much hassle to set the file descriptor options (O_NONBLOCK) and stuff, + # so just use communicate() here and dump everythiong all at once when finished. + sStdOut = oProcess.communicate(); + if sStdOut: + reporter.log('%s: %s' % (sWhat, sStdOut)); + iRc = oProcess.poll(); + + if iRc == 0: + reporter.log('*** %s: exit code %d' % (sWhat, iRc)); + fRc = True; + else: + reporter.log('!*! %s: exit code %d' % (sWhat, iRc)); + + self.pidFileRemove(iPid); + + # Save thread result code. + self.iThreadHstProcRc = iRc; + + except: + reporter.logXcpt('Executing "%s" failed!' % (sWhat)); + + return fRc; + + def executeHstThread(self, sWhat, asArgs, asEnv = None, fAsAdmin = False): + """ + Thread execution helper to run a process on the host. + """ + fRc = self.executeHstLoop(sWhat, asArgs, asEnv, fAsAdmin); + if fRc: + reporter.log('Executing \"%s\" on host done' % (sWhat,)); + else: + reporter.log('Executing \"%s\" on host failed' % (sWhat,)); + + def executeHst(self, sWhat, asArgs, asEnv = None, fAsAdmin = False): + """ + Runs a binary (image) with optional admin (root) rights on the host and + waits until it terminates. + + Windows currently is not supported yet running stuff as Administrator. + + Returns success status (exit code is 0). + """ + reporter.log('Executing \"%s\" on host (as admin = %s)' % (sWhat, fAsAdmin)); + + try: sys.stdout.flush(); + except: pass; + try: sys.stderr.flush(); + except: pass; + + # Initialize thread rc. + self.iThreadHstProcRc = -42; + + try: + oThread = threading.Thread(target = self.executeHstThread, args = [ sWhat, asArgs, asEnv, fAsAdmin ]); + oThread.start(); + while oThread.join(0.1): + if not oThread.is_alive(): + break; + self.processEvents(0); + reporter.log2('Thread returned exit code for "%s": %d' % (sWhat, self.iThreadHstProcRc)); + except: + reporter.logXcpt('Starting thread for "%s" failed' % (sWhat,)); + + return self.iThreadHstProcRc == 0; + + def getWinFirewallArgsDisable(self, sOsType): + """ + Returns the command line arguments for Windows OSes + to disable the built-in firewall (if any). + + If not supported, returns an empty array. + """ + if sOsType == 'vista': # pylint: disable=no-else-return + # Vista and up. + return (['netsh.exe', 'advfirewall', 'set', 'allprofiles', 'state', 'off']); + elif sOsType == 'xp': # Older stuff (XP / 2003). + return(['netsh.exe', 'firewall', 'set', 'opmode', 'mode=DISABLE']); + # Not supported / available. + return []; + + def disableGstFirewall(self, oTestVm, oTxsSession): + """ + Disables the firewall on a guest (if any). + + Needs elevated / admin / root privileges. + + Returns success status, not logged. + """ + fRc = False; + + asArgs = []; + sOsType = ''; + if oTestVm.isWindows(): + if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x']: + sOsType = 'nt3x'; # Not supported, but define it anyway. + elif oTestVm.sKind in ('Windows2000', 'WindowsXP', 'Windows2003'): + sOsType = 'xp'; + else: + sOsType = 'vista'; + asArgs = self.getWinFirewallArgsDisable(sOsType); + else: + sOsType = 'unsupported'; + + reporter.log('Disabling firewall on guest (type: %s) ...' % (sOsType,)); + + if asArgs: + fRc = self.txsRunTest(oTxsSession, 'Disabling guest firewall', 3 * 60 * 1000, \ + oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), asArgs[0]), asArgs); + if not fRc: + reporter.error('Disabling firewall on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession))); + else: + reporter.log('Firewall not available on guest, skipping'); + fRc = True; # Not available, just skip. + + return fRc; + + def disableHstFirewall(self): + """ + Disables the firewall on the host (if any). + + Needs elevated / admin / root privileges. + + Returns success status, not logged. + """ + fRc = False; + + asArgs = []; + sOsType = sys.platform; + + if sOsType == 'win32': + reporter.log('Disabling firewall on host (type: %s) ...' % (sOsType)); + + ## @todo For now we ASSUME that we don't run (and don't support even) on old(er) + # Windows hosts than Vista. + asArgs = self.getWinFirewallArgsDisable('vista'); + if asArgs: + fRc = self.executeHst('Disabling host firewall', asArgs, fAsAdmin = True); + else: + reporter.log('Firewall not available on host, skipping'); + fRc = True; # Not available, just skip. + + return fRc; + + def getLastRcFromTxs(self, oTxsSession): + """ + Extracts the last exit code reported by TXS from a run before. + Assumes that nothing else has been run on the same TXS session in the meantime. + """ + iRc = 0; + (_, sOpcode, abPayload) = oTxsSession.getLastReply(); + if sOpcode.startswith('PROC NOK '): # Extract process rc + iRc = abPayload[0]; # ASSUMES 8-bit rc for now. + return iRc; + + def startVkatOnGuest(self, oTestVm, oSession, oTxsSession, sTag): + """ + Starts VKAT on the guest (running in background). + """ + sPathTemp = self.getGuestTempDir(oTestVm); + sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-guest-out'); + sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-guest-temp'); + + reporter.log('Guest audio test temp path is \"%s\"' % (sPathAudioOut)); + reporter.log('Guest audio test output path is \"%s\"' % (sPathAudioTemp)); + reporter.log('Guest audio test tag is \"%s\"' % (sTag)); + + fRc, sVkatExe = self.locateGstBinary(oSession, oTxsSession, self.asGstVkatPaths); + if fRc: + reporter.log('Using VKAT on guest at \"%s\"' % (sVkatExe)); + + sCmd = ''; + asArgs = []; + + asArgsVkat = [ sVkatExe, 'test', '--mode', 'guest', '--probe-backends', \ + '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, \ + '--tag', sTag ]; + + asArgs.extend(asArgsVkat); + + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + + # Needed for NATed VMs. + asArgs.extend(['--tcp-connect-addr', '10.0.2.2' ]); + + if oTestVm.sKind in 'Oracle_64': + # + # Some Linux distros have a bug / are configured (?) so that processes started by init system + # cannot access the PulseAudio server ("Connection refused"), for example OL 8.1. + # + # To work around this, we use the (hopefully) configured user "vbox" and run it under its behalf, + # as the Test Execution Service (TxS) currently does not implement impersonation yet. + # + asSU = [ '/bin/su', + '/usr/bin/su', + '/usr/local/bin/su' ]; + fRc, sCmd = self.locateGstBinary(oSession, oTxsSession, asSU); + if fRc: + sCmdArgs = ''; + for sArg in asArgs: + sCmdArgs += sArg + " "; + asArgs = [ sCmd, oTestVm.getTestUser(), '-c', sCmdArgs ]; + else: + reporter.log('Unable to find SU on guest, falling back to regular starting ...') + + if not sCmd: # Just start it with the same privileges as TxS. + sCmd = sVkatExe; + + reporter.log2('startVkatOnGuest: sCmd=%s' % (sCmd,)); + reporter.log2('startVkatOnGuest: asArgs=%s' % (asArgs,)); + + # + # Add own environment stuff. + # + asEnv = []; + + # Write the log file to some deterministic place so TxS can retrieve it later. + sVkatLogFile = 'VKAT_RELEASE_LOG_DEST=file=' + self.getGstVkatLogFilePath(oTestVm); + asEnv.extend([ sVkatLogFile ]); + + # + # Execute asynchronously on the guest. + # + fRc = oTxsSession.asyncExec(sCmd, asArgs, asEnv, cMsTimeout = 15 * 60 * 1000, sPrefix = '[VKAT Guest] '); + if fRc: + self.addTask(oTxsSession); + + if not fRc: + reporter.error('VKAT on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession))); + else: + reporter.error('VKAT on guest not found'); + + return fRc; + + def runTests(self, oTestVm, oSession, oTxsSession, sDesc, sTag, asTests): + """ + Runs one or more tests using VKAT on the host, which in turn will + communicate with VKAT running on the guest and the Validation Kit + audio driver ATS (Audio Testing Service). + """ + _ = oTestVm, oSession, oTxsSession; + + sPathTemp = self.sScratchPath; + sPathAudioOut = os.path.join(sPathTemp, 'vkat-host-out-%s' % (sTag)); + sPathAudioTemp = os.path.join(sPathTemp, 'vkat-host-temp-%s' % (sTag)); + + reporter.log('Host audio test temp path is \"%s\"' % (sPathAudioOut)); + reporter.log('Host audio test output path is \"%s\"' % (sPathAudioTemp)); + reporter.log('Host audio test tag is \"%s\"' % (sTag)); + + reporter.testStart(sDesc); + + sVkatExe = self.getBinTool('vkat'); + + reporter.log('Using VKAT on host at: \"%s\"' % (sVkatExe)); + + # Build the base command line, exclude all tests by default. + asArgs = [ sVkatExe, 'test', '--mode', 'host', '--probe-backends', + '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, '-a', + '--tag', sTag, + '--no-audio-ok', # Enables running on hosts which do not have any audio hardware. + '--no-verify' ]; # We do the verification separately in the step below. + + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + + if self.asVkatTestArgs: + asArgs += self.asVkatTestArgs; + + # ... and extend it with wanted tests. + asArgs.extend(asTests); + + # + # Let VKAT on the host run synchronously. + # + fRc = self.executeHst("VKAT Host", asArgs); + + reporter.testDone(); + + if fRc: + # + # When running the test(s) above were successful, do the verification step next. + # This gives us a bit more fine-grained test results in the test manager. + # + reporter.testStart('Verifying audio data'); + + sNameSetHst = '%s-host.tar.gz' % (sTag); + sPathSetHst = os.path.join(sPathAudioOut, sNameSetHst); + sNameSetGst = '%s-guest.tar.gz' % (sTag); + sPathSetGst = os.path.join(sPathAudioOut, sNameSetGst); + + asArgs = [ sVkatExe, 'verify', sPathSetHst, sPathSetGst ]; + + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + + if self.asVkatVerifyArgs: + asArgs += self.asVkatVerifyArgs; + + fRc = self.executeHst("VKAT Host Verify", asArgs); + if fRc: + reporter.log("Verification audio data successful"); + else: + # + # Add the test sets to the test manager for later (manual) diagnosis. + # + reporter.addLogFile(sPathSetGst, 'misc/other', 'Guest audio test set'); + reporter.addLogFile(sPathSetHst, 'misc/other', 'Host audio test set'); + + reporter.error("Verification of audio data failed"); + + reporter.testDone(); + + return fRc; + + def doTest(self, oTestVm, oSession, oTxsSession): + """ + Executes the specified audio tests. + """ + + # Disable any OS-specific firewalls preventing VKAT / ATS to run. + fRc = self.disableHstFirewall(); + fRc = self.disableGstFirewall(oTestVm, oTxsSession) and fRc; + + if not fRc: + return False; + + reporter.log("Active tests: %s" % (self.asTests,)); + + # Define a tag for the whole run. + sTag = oTestVm.sVmName + "_" + datetime.now().strftime("%Y%m%d_%H%M%S"); + + fRc = self.startVkatOnGuest(oTestVm, oSession, oTxsSession, sTag); + if fRc: + # + # Execute the tests using VKAT on the guest side (in guest mode). + # + if "guest_tone_playback" in self.asTests: + fRc = self.runTests(oTestVm, oSession, oTxsSession, \ + 'Guest audio playback', sTag + "_test_playback", \ + asTests = [ '-i0' ]); + if "guest_tone_recording" in self.asTests: + fRc = fRc and self.runTests(oTestVm, oSession, oTxsSession, \ + 'Guest audio recording', sTag + "_test_recording", \ + asTests = [ '-i1' ]); + + # Cancel guest VKAT execution task summoned by startVkatOnGuest(). + oTxsSession.cancelTask(); + + # + # Retrieve log files for diagnosis. + # + self.txsDownloadFiles(oSession, oTxsSession, + [ ( self.getGstVkatLogFilePath(oTestVm), + 'vkat-guest-%s.log' % (oTestVm.sVmName,),), + ], + fIgnoreErrors = True); + + # A bit of diagnosis on error. + ## @todo Remove this later when stuff runs stable. + if not fRc: + reporter.log('Kernel messages:'); + sCmdDmesg = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'dmesg'); + oTxsSession.syncExec(sCmdDmesg, (sCmdDmesg), fIgnoreErrors = True); + reporter.log('Loaded kernel modules:'); + sCmdLsMod = oTestVm.pathJoin(self.getGuestSystemAdminDir(oTestVm), 'lsmod'); + oTxsSession.syncExec(sCmdLsMod, (sCmdLsMod), fIgnoreErrors = True); + + return fRc; + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs tests using one specific VM config. + """ + + self.logVmInfo(oVM); + + reporter.testStart("Audio Testing"); + + fSkip = False; + + if oTestVm.isWindows() \ + and oTestVm.sKind in ('WindowsNT4', 'Windows2000'): # Too old for DirectSound and WASAPI backends. + reporter.log('Audio testing skipped, not implemented/available for that OS yet.'); + fSkip = True; + + if not fSkip \ + and self.fpApiVer < 7.0: + reporter.log('Audio testing for non-trunk builds skipped.'); + fSkip = True; + + if not fSkip: + sVkatExe = self.getBinTool('vkat'); + asArgs = [ sVkatExe, 'enum', '--probe-backends' ]; + for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1. + asArgs.extend([ '-v' ]); + fRc = self.executeHst("VKAT Host Audio Probing", asArgs); + if not fRc: + # Not fatal, as VBox then should fall back to the NULL audio backend (also worth having as a test case). + reporter.log('Warning: Backend probing on host failed, no audio available (pure server installation?)'); + + if fSkip: + reporter.testDone(fSkipped = True); + return True; + + # Reconfigure the VM. + oSession = self.openSession(oVM); + if oSession is not None: + + cVerbosity = reporter.getVerbosity(); + if cVerbosity >= 2: # Explicitly set verbosity via extra-data when >= level 2. + self.asOptExtraData.extend([ 'VBoxInternal2/Audio/Debug/Level:' + str(cVerbosity) ]); + + # Set extra data. + for sExtraData in self.asOptExtraData: + sKey, sValue = sExtraData.split(':'); + reporter.log('Set extradata: %s => %s' % (sKey, sValue)); + fRc = oSession.setExtraData(sKey, sValue) and fRc; + + # Make sure that the VM's audio adapter is configured the way we need it to. + if self.fpApiVer >= 4.0: + enmAudioControllerType = None; + reporter.log('Configuring audio controller type ...'); + if self.sAudioControllerType is None: + oOsType = oSession.getOsType(); + enmAudioControllerType = oOsType.recommendedAudioController; + else: + if self.sAudioControllerType == 'HDA': + enmAudioControllerType = vboxcon.AudioControllerType_HDA; + elif self.sAudioControllerType == 'AC97': + enmAudioControllerType = vboxcon.AudioControllerType_AC97; + elif self.sAudioControllerType == 'SB16': + enmAudioControllerType = vboxcon.AudioControllerType_SB16; + assert enmAudioControllerType is not None; + + # For now we're encforcing to test the HDA emulation only, regardless of + # what the recommended audio controller type from above was. + ## @todo Make other emulations work as well. + fEncforceHDA = True; + + if fEncforceHDA: + enmAudioControllerType = vboxcon.AudioControllerType_HDA; + reporter.log('Enforcing audio controller type to HDA'); + + reporter.log('Setting user-defined audio controller type to %d' % (enmAudioControllerType)); + oSession.setupAudio(enmAudioControllerType, + fEnable = True, fEnableIn = True, fEnableOut = True); + + # Save the settings. + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc; + + reporter.testStart('Waiting for TXS'); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, + fCdWait = True, + cMsTimeout = 3 * 60 * 1000, + sFileCdWait = '${OS/ARCH}/vkat${EXESUFF}'); + reporter.testDone(); + + reporter.log('Waiting for any OS startup sounds getting played (to skip those) ...'); + time.sleep(5); + + if oSession is not None: + self.addTask(oTxsSession); + + fRc = self.doTest(oTestVm, oSession, oTxsSession); + + # Cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + + reporter.testDone(); + return fRc; + + def onExit(self, iRc): + """ + Exit handler for this test driver. + """ + return vbox.TestDriver.onExit(self, iRc); + +if __name__ == '__main__': + sys.exit(tdAudioTest().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py b/src/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py new file mode 100755 index 00000000..55502b16 --- /dev/null +++ b/src/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# $Id: tdGuestHostTimings.py $ + +""" +???????? +""" + +__copyright__ = \ +""" +Copyright (C) 2012-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: 155244 $" + + +import os +import sys +import time +import subprocess +import re +import time + +# 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 testdriver import reporter +from testdriver import base +from testdriver import vbox +from testdriver import vboxcon +from testdriver import vboxtestvms + +class tdGuestHostTimings(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + + def __init__(self): + vbox.TestDriver.__init__(self); + self.sSessionTypeDef = 'gui'; + + self.oTestVmSet = self.oTestVmManager.getStandardVmSet('nat') ## ??? + + # Use the command line "--test-vms mw7x64 execute" to run the only "mw7x64" VM + oTestVm = vboxtestvms.TestVm('mw7x64', oSet = self.oTestVmSet, sHd = 'mw7x64.vdi', + sKind = 'Windows7', acCpusSup = range(1, 2), fIoApic = True, sFirmwareType = 'bios', + asParavirtModesSup = ['hyperv'], asVirtModesSup = ['hwvirt-np'], + sHddControllerType = 'SATA Controller'); + + self.oTestVmSet.aoTestVms.append(oTestVm); + + self.sVMname = None + + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdGuestHostTimings Options:'); + reporter.log(' --runningvmname <vmname>'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--runningvmname': + iArg += 1 + if iArg >= len(asArgs): + raise base.InvalidOption('The "----runningvmname" needs VM name') + + self.sVMname = asArgs[iArg] + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + return iArg + 1 + + def actionConfig(self): + return True + + def actionExecute(self): + #self.sTempPathHost = os.environ.get("IPRT_TMPDIR") + self.sTempPathHost = os.path.normpath(os.environ.get("TEMP") + "/VBoxAudioValKit") + + if self.sVMname is None: + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + else: + return self.actionExecuteOnRunnigVM() + + def doTest(self, oSession): + oConsole = oSession.console + oGuest = oConsole.guest + + sOSTypeId = oGuest.OSTypeId.lower() + if sOSTypeId.find("win") == -1 : + reporter.log("Only Windows guests are currently supported") + reporter.testDone() + return True + + oGuestSession = oGuest.createSession("Administrator", "password", "", "Audio Validation Kit") + guestSessionWaitResult = oGuestSession.waitFor(self.oVBoxMgr.constants.GuestSessionWaitResult_Start, 2000) + reporter.log("guestSessionWaitResult = %d" % guestSessionWaitResult) + + for duration in range(3, 6): + reporter.testStart("Checking for duration of " + str(duration) + " seconds") + sPathToPlayer = "D:\\win\\" + ("amd64" if (sOSTypeId.find('_64') >= 0) else "x86") + "\\ntPlayToneWaveX.exe" + oProcess = oGuestSession.processCreate(sPathToPlayer, ["xxx0", "--total-duration-in-secs", str(duration)], [], [], 0) + processWaitResult = oProcess.waitFor(self.oVBoxMgr.constants.ProcessWaitForFlag_Start, 1000) + reporter.log("Started: pid %d, waitResult %d" % (oProcess.PID, processWaitResult)) + + processWaitResult = oProcess.waitFor(self.oVBoxMgr.constants.ProcessWaitForFlag_Terminate, 2 * duration * 1000) + reporter.log("Terminated: pid %d, waitResult %d" % (oProcess.PID, processWaitResult)) + time.sleep(1) # Give audio backend sometime to save a stream to .wav file + + absFileName = self.seekLatestAudioFileName(oGuestSession, duration) + + if absFileName is None: + reporter.testFailure("Unable to find audio file") + continue + + reporter.log("Checking audio file '" + absFileName + "'") + + diff = self.checkGuestHostTimings(absFileName + ".timing") + if diff is not None: + if diff > 0.0: # Guest sends data quicker than a host can play + if diff > 0.01: # 1% is probably good threshold here + reporter.testFailure("Guest sends audio buffers too quickly") + else: + diff = -diff; # Much worse case: guest sends data very slow, host feels starvation + if diff > 0.005: # 0.5% is probably good threshold here + reporter.testFailure("Guest sends audio buffers too slowly") + + reporter.testDone() + else: + reporter.testFailure("Unable to parse a file with timings") + + oGuestSession.close() + + del oGuest + del oConsole + + return True + + def testOneVmConfig(self, oVM, oTestVm): + #self.logVmInfo(oVM) + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, + fCdWait = True, + cMsTimeout = 60 * 1000) + if oSession is not None and oTxsSession is not None: + # Wait until guest reported success + reporter.log('Guest started. Connection to TXS service established.') + self.doTest(oSessionWrapper.o) + + return True + + def actionExecuteOnRunnigVM(self): + if not self.importVBoxApi(): + return False; + + oVirtualBox = self.oVBoxMgr.getVirtualBox() + oMachine = oVirtualBox.findMachine(self.sVMname) + + if oMachine == None: + reporter.log("Machine '%s' is unknown" % (oMachine.name)) + return False + + if oMachine.state != self.oVBoxMgr.constants.MachineState_Running: + reporter.log("Machine '%s' is not Running" % (oMachine.name)) + return False + + oSession = self.oVBoxMgr.mgr.getSessionObject(oVirtualBox) + oMachine.lockMachine(oSession, self.oVBoxMgr.constants.LockType_Shared) + + self.doTest(oSession); + + oSession.unlockMachine() + + del oSession + del oMachine + del oVirtualBox + return True + + def seekLatestAudioFileName(self, guestSession, duration): + + listOfFiles = os.listdir(self.sTempPathHost) + # Assuming that .wav files are named like 2016-11-15T12_08_27.669573100Z.wav by VBOX audio backend + # So that sorting by name = sorting by creation date + listOfFiles.sort(reverse = True) + + for fileName in listOfFiles: + if not fileName.endswith(".wav"): + continue + + absFileName = os.path.join(self.sTempPathHost, fileName) + + # Ignore too small wav files (usually uncompleted audio streams) + statInfo = os.stat(absFileName) + if statInfo.st_size > 100: + return absFileName + + return + + def checkGuestHostTimings(self, absFileName): + with open(absFileName) as f: + for line_terminated in f: + line = line_terminated.rstrip('\n') + + reporter.log("Last line is: " + line) + matchObj = re.match( r'(\d+) (\d+)', line, re.I) + if matchObj: + hostTime = int(matchObj.group(1)) + guestTime = int(matchObj.group(2)) + + diff = float(guestTime - hostTime) / hostTime + return diff + + return + +if __name__ == '__main__': + sys.exit(tdGuestHostTimings().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/autostart/Makefile.kmk b/src/VBox/ValidationKit/tests/autostart/Makefile.kmk new file mode 100644 index 00000000..72793ef4 --- /dev/null +++ b/src/VBox/ValidationKit/tests/autostart/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Autostart. +# + +# +# Copyright (C) 2013-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 += ValidationKitTestsAutostart +ValidationKitTestsAutostart_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsAutostart_INST = $(INST_VALIDATIONKIT)tests/autostart/ +ValidationKitTestsAutostart_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdAutostart1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsAutostart_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/autostart/tdAutostart1.py b/src/VBox/ValidationKit/tests/autostart/tdAutostart1.py new file mode 100755 index 00000000..813c7eb7 --- /dev/null +++ b/src/VBox/ValidationKit/tests/autostart/tdAutostart1.py @@ -0,0 +1,1443 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Autostart testcase using <please-tell-what-I-am-doing>. +""" + +__copyright__ = \ +""" +Copyright (C) 2013-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__ = "$Id: tdAutostart1.py $" + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestvms; +from testdriver import vboxwrappers; + +class VBoxManageStdOutWrapper(object): + """ Parser for VBoxManage list runningvms """ + + def __init__(self): + self.sVmRunning = ''; + + def __del__(self): + self.close(); + + def close(self): + """file.close""" + return; + + def read(self, cb): + """file.read""" + _ = cb; + return ""; + + def write(self, sText): + """VBoxManage stdout write""" + if sText is None: + return None; + try: sText = str(sText); # pylint: disable=redefined-variable-type + except: pass; + asLines = sText.splitlines(); + for sLine in asLines: + sLine = sLine.strip(); + reporter.log('Logging: ' + sLine); + # Extract the value + idxVmNameStart = sLine.find('"'); + if idxVmNameStart == -1: + raise Exception('VBoxManageStdOutWrapper: Invalid output'); + idxVmNameStart += 1; + idxVmNameEnd = idxVmNameStart; + while sLine[idxVmNameEnd] != '"': + idxVmNameEnd += 1; + self.sVmRunning = sLine[idxVmNameStart:idxVmNameEnd]; + reporter.log('Logging: ' + self.sVmRunning); + return None; + +class tdAutostartOs(vboxtestvms.BaseTestVm): + """ + Base autostart helper class to provide common methods. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + vboxtestvms.BaseTestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind); + self.oTstDrv = oTstDrv; + self.sHdd = sHdd; + self.eNic0Type = eNic0Type; + self.cMbRam = cMbRam; + self.cCpus = cCpus; + self.fPae = fPae; + self.sGuestAdditionsIso = sGuestAdditionsIso; + self.asTestBuildDirs = oTstDrv.asTestBuildDirs; + self.sVBoxInstaller = ""; + self.asVirtModesSup = ['hwvirt-np',]; + self.asParavirtModesSup = ['default',]; + + def _findFile(self, sRegExp, asTestBuildDirs): + """ + Returns a filepath based on the given regex and paths to look into + or None if no matching file is found. + """ + oRegExp = re.compile(sRegExp); + for sTestBuildDir in asTestBuildDirs: + try: + #return most recent file if there are several ones matching the pattern + asFiles = [s for s in os.listdir(sTestBuildDir) + if os.path.isfile(os.path.join(sTestBuildDir, s))]; + asFiles = (s for s in asFiles + if oRegExp.match(os.path.basename(s)) + and os.path.exists(sTestBuildDir + '/' + s)); + asFiles = sorted(asFiles, reverse = True, + key = lambda s, sTstBuildDir = sTestBuildDir: os.path.getmtime(os.path.join(sTstBuildDir, s))); + if asFiles: + return sTestBuildDir + '/' + asFiles[0]; + except: + pass; + reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, ','.join(asTestBuildDirs))); + return None; + + def _createAutostartCfg(self, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Creates a autostart config for VirtualBox + """ + sVBoxCfg = 'default_policy=' + sDefaultPolicy + '\n'; + for sUserAllow in asUserAllow: + sVBoxCfg = sVBoxCfg + sUserAllow + ' = {\n allow = true\n }\n'; + for sUserDeny in asUserDeny: + sVBoxCfg = sVBoxCfg + sUserDeny + ' = {\n allow = false\n }\n'; + return sVBoxCfg; + + def _waitAdditionsIsRunning(self, oGuest, fWaitTrayControl): + """ + Check is the additions running + """ + cAttempt = 0; + fRc = False; + while cAttempt < 30: + fRc = oGuest.additionsRunLevel in [vboxcon.AdditionsRunLevelType_Userland, + vboxcon.AdditionsRunLevelType_Desktop]; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxService); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc and not fWaitTrayControl: + break; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxTrayClient); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc: + break; + self.oTstDrv.sleep(10); + cAttempt += 1; + return fRc; + + def createSession(self, oSession, sName, sUser, sPassword, cMsTimeout = 10 * 1000, fIsError = True): + """ + Creates (opens) a guest session. + Returns (True, IGuestSession) on success or (False, None) on failure. + """ + oGuest = oSession.o.console.guest; + if sName is None: + sName = "<untitled>"; + reporter.log('Creating session "%s" ...' % (sName,)); + try: + oGuestSession = oGuest.createSession(sUser, sPassword, '', sName); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Creating a guest session "%s" failed; sUser="%s", pw="%s"' + % (sName, sUser, sPassword)); + return (False, None); + reporter.log('Waiting for session "%s" to start within %dms...' % (sName, cMsTimeout)); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + waitResult = oGuestSession.waitForArray(aeWaitFor, cMsTimeout); + # + # Be nice to Guest Additions < 4.3: They don't support session handling and + # therefore return WaitFlagNotSupported. + # + if waitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErr(fIsError, 'Session did not start successfully, returned wait result: %d' % (waitResult,)); + return (False, None); + reporter.log('Session "%s" successfully started' % (sName,)); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Waiting for guest session "%s" (usr=%s;pw=%s) to start failed:' + % (sName, sUser, sPassword,)); + return (False, None); + return (True, oGuestSession); + + def closeSession(self, oGuestSession, fIsError = True): + """ + Closes the guest session. + """ + if oGuestSession is not None: + try: + sName = oGuestSession.name; + except: + return reporter.errorXcpt(); + reporter.log('Closing session "%s" ...' % (sName,)); + try: + oGuestSession.close(); + oGuestSession = None; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Closing guest session "%s" failed:' % (sName,)); + return False; + return True; + + def guestProcessExecute(self, oGuestSession, sTestName, cMsTimeout, sExecName, asArgs = (), + fGetStdOut = True, fIsError = True): + """ + Helper function to execute a program on a guest, specified in the current test. + Returns (True, ProcessStatus, ProcessExitCode, ProcessStdOutBuffer) on success or (False, 0, 0, None) on failure. + """ + _ = sTestName; + fRc = True; # Be optimistic. + reporter.log2('Using session user=%s, name=%s, timeout=%d' + % (oGuestSession.user, oGuestSession.name, oGuestSession.timeout,)); + # + # Start the process: + # + reporter.log2('Executing sCmd=%s, timeoutMS=%d, asArgs=%s' + % (sExecName, cMsTimeout, asArgs, )); + fTaskFlags = []; + if fGetStdOut: + fTaskFlags = [vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr]; + try: + oProcess = oGuestSession.processCreate(sExecName, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], fTaskFlags, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'asArgs=%s' % (asArgs,)); + return (False, 0, 0, None); + if oProcess is None: + return (reporter.error('oProcess is None! (%s)' % (asArgs,)), 0, 0, None); + #time.sleep(5); # try this if you want to see races here. + # Wait for the process to start properly: + reporter.log2('Process start requested, waiting for start (%dms) ...' % (cMsTimeout,)); + iPid = -1; + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Start, ]; + aBuf = None; + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'waitforArray failed for asArgs=%s' % (asArgs,)); + fRc = False; + else: + try: + eStatus = oProcess.status; + iPid = oProcess.PID; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + else: + reporter.log2('Wait result returned: %d, current process status is: %d' % (eWaitResult, eStatus,)); + # + # Wait for the process to run to completion if necessary. + # + # Note! The above eWaitResult return value can be ignored as it will + # (mostly) reflect the process status anyway. + # + if eStatus == vboxcon.ProcessStatus_Started: + # What to wait for: + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate, + vboxcon.ProcessWaitForFlag_StdOut, + vboxcon.ProcessWaitForFlag_StdErr]; + reporter.log2('Process (PID %d) started, waiting for termination (%dms), aeWaitFor=%s ...' + % (iPid, cMsTimeout, aeWaitFor)); + acbFdOut = [0,0,0]; + while True: + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + break; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + break; + reporter.log2('Wait returned: %d' % (eWaitResult,)); + # Process output: + for eFdResult, iFd, sFdNm in [ (vboxcon.ProcessWaitResult_StdOut, 1, 'stdout'), + (vboxcon.ProcessWaitResult_StdErr, 2, 'stderr'), ]: + if eWaitResult in (eFdResult, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + reporter.log2('Reading %s ...' % (sFdNm,)); + try: + abBuf = oProcess.read(iFd, 64 * 1024, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + except: + pass; ## @todo test for timeouts and fail on anything else! + else: + if abBuf: + reporter.log2('Process (PID %d) got %d bytes of %s data' % (iPid, len(abBuf), sFdNm,)); + acbFdOut[iFd] += len(abBuf); + ## @todo Figure out how to uniform + append! + sBuf = ''; + if sys.version_info >= (2, 7) and isinstance(abBuf, memoryview): + abBuf = abBuf.tobytes(); + sBuf = abBuf.decode("utf-8"); + else: + sBuf = str(abBuf); + if aBuf: + aBuf += sBuf; + else: + aBuf = sBuf; + ## Process input (todo): + #if eWaitResult in (vboxcon.ProcessWaitResult_StdIn, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + # reporter.log2('Process (PID %d) needs stdin data' % (iPid,)); + # Termination or error? + if eWaitResult in (vboxcon.ProcessWaitResult_Terminate, + vboxcon.ProcessWaitResult_Error, + vboxcon.ProcessWaitResult_Timeout,): + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) reported terminate/error/timeout: %d, status: %d' + % (iPid, eWaitResult, eStatus,)); + break; + # End of the wait loop. + _, cbStdOut, cbStdErr = acbFdOut; + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Final process status (PID %d) is: %d' % (iPid, eStatus)); + reporter.log2('Process (PID %d) %d stdout, %d stderr' % (iPid, cbStdOut, cbStdErr)); + # + # Get the final status and exit code of the process. + # + try: + uExitStatus = oProcess.status; + iExitCode = oProcess.exitCode; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) has exit code: %d; status: %d ' % (iPid, iExitCode, uExitStatus)); + return (fRc, uExitStatus, iExitCode, aBuf); + + def uploadString(self, oGuestSession, sSrcString, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + oFile = oGuestSession.fileOpenEx(sDst, vboxcon.FileAccessMode_ReadWrite, vboxcon.FileOpenAction_CreateOrReplace, + vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not create and open the file %s' % sDst); + else: + try: + oFile.write(bytearray(sSrcString), 60*1000); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not write the string into the file %s' % sDst); + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not close the file %s' % sDst); + return fRc; + + def uploadFile(self, oGuestSession, sSrc, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyToGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyTo(sSrc, sDst, [0]); + except: + reporter.maybeErrXcpt(True, 'Upload file exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "uploadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + return fRc; + + def downloadFile(self, oGuestSession, sSrc, sDst, fIgnoreErrors = False): + """ + Get a file (sSrc) from the guest storing it on the host (sDst). + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyFromGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyFrom(sSrc, sDst, [0]); + except: + if not fIgnoreErrors: + reporter.errorXcpt('Download file exception for sSrc="%s":' % (sSrc,)); + else: + reporter.log('warning: Download file exception for sSrc="%s":' % (sSrc,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "downloadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors); + fRc = False; + else: + if not fIgnoreErrors: + reporter.error('No progress object returned'); + else: + reporter.log('warning: No progress object returned'); + fRc = False; + return fRc; + + def downloadFiles(self, oGuestSession, asFiles, fIgnoreErrors = False): + """ + Convenience function to get files from the guest and stores it + into the scratch directory for later (manual) review. + Returns True on success. + Returns False on failure, logged. + """ + fRc = True; + for sGstFile in asFiles: + ## @todo r=bird: You need to use the guest specific path functions here. + ## Best would be to add basenameEx to common/pathutils.py. See how joinEx + ## is used by BaseTestVm::pathJoin and such. + sTmpFile = os.path.join(self.oTstDrv.sScratchPath, 'tmp-' + os.path.basename(sGstFile)); + reporter.log2('Downloading file "%s" to "%s" ...' % (sGstFile, sTmpFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sTmpFile); + except: pass; + ## @todo Check for already existing files on the host and create a new + # name for the current file to download. + fRc = self.downloadFile(oGuestSession, sGstFile, sTmpFile, fIgnoreErrors); + if fRc: + reporter.addLogFile(sTmpFile, 'misc/other', 'guest - ' + sGstFile); + else: + if fIgnoreErrors is not True: + reporter.error('error downloading file "%s" to "%s"' % (sGstFile, sTmpFile)); + return fRc; + reporter.log('warning: file "%s" was not downloaded, ignoring.' % (sGstFile,)); + return True; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, '/sbin/ifconfig', + ['ifconfig',], + False, False); + return fRc; + + def waitVmIsReady(self, oSession, fWaitTrayControl): + """ + Waits the VM is ready after start or reboot. + Returns result (true or false) and guest session obtained + """ + _ = fWaitTrayControl; + # Give the VM a time to reboot + self.oTstDrv.sleep(30); + # Waiting the VM is ready. + # To do it, one will try to open the guest session and start the guest process in loop + if not self._waitAdditionsIsRunning(oSession.o.console.guest, False): + return (False, None); + cAttempt = 0; + oGuestSession = None; + fRc = False; + while cAttempt < 30: + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, False); + if fRc: + fRc = self._checkVmIsReady(oGuestSession); + if fRc: + break; + self.closeSession(oGuestSession, False); + self.oTstDrv.sleep(10); + cAttempt += 1; + return (fRc, oGuestSession); + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'reboot'], + False, True); + if not fRc: + reporter.error('Calling the reboot utility failed'); + return fRc; + + def rebootVMAndCheckReady(self, oSession, oGuestSession): + """ + Reboot the VM and wait the VM is ready. + Returns result and guest session obtained after reboot + """ + reporter.testStart('Reboot VM and wait for readiness'); + fRc = self._rebootVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + if not fRc: + reporter.error('VM is not ready after reboot'); + reporter.testDone(); + return (fRc, oGuestSession); + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'poweroff'], + False, True); + if not fRc: + reporter.error('Calling the poweroff utility failed'); + return fRc; + + def powerDownVM(self, oGuestSession): + """ + Power down the VM by calling guest process without wating + the VM is really powered off. Also, closes the guest session. + It helps the terminateBySession to stop the VM without aborting. + """ + if oGuestSession is None: + return False; + reporter.testStart('Power down the VM'); + fRc = self._powerDownVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if not fRc: + reporter.error('Power down the VM failed'); + reporter.testDone(); + return fRc; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oSession; + _ = oGuestSession; + _ = oVM; + reporter.error('Not implemented'); + return False; + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + _ = oGuestSession; + reporter.error('Not implemented'); + return False; + + def configureAutostart(self, oGuestSession, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Configures the autostart feature in the guest. + """ + _ = oGuestSession; + _ = sDefaultPolicy; + _ = asUserAllow; # pylint: disable=redefined-variable-type + _ = asUserDeny; + reporter.error('Not implemented'); + return False; + + def createUser(self, oGuestSession, sUser): + """ + Create a new user with the given name + """ + _ = oGuestSession; + _ = sUser; + reporter.error('Not implemented'); + return False; + + def checkForRunningVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Check for VM running in the guest after autostart. + Due to the sUser is created whithout password, + all calls will be perfomed using 'sudo -u sUser' + """ + _ = oSession; + _ = oGuestSession; + _ = sUser; + _ = sVmName; + reporter.error('Not implemented'); + return False; + + def getResourceSet(self): + asRet = []; + if not os.path.isabs(self.sHdd): + asRet.append(self.sHdd); + return asRet; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Creates the VM. + Returns Wrapped VM object on success, None on failure. + """ + _ = eNic0AttachType; + _ = sDvdImage; + return oTestDrv.createTestVM(self.sVmName, self.iGroup, self.sHdd, sKind = self.sKind, \ + fIoApic = True, eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = self.eNic0Type, cMbRam = self.cMbRam, \ + sHddControllerType = "SATA Controller", fPae = self.fPae, \ + cCpus = self.cCpus, sDvdImage = self.sGuestAdditionsIso); + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): + _ = eNic0AttachType; + _ = sDvdImage; + fRc = True; + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(True); + fRc = fRc and oSession.enableNestedPaging(True); + fRc = fRc and oSession.enableNestedHwVirt(True); + # disable 3D until the error is fixed. + fRc = fRc and oSession.setAccelerate3DEnabled(False); + fRc = fRc and oSession.setVRamSize(256); + fRc = fRc and oSession.setVideoControllerType(vboxcon.GraphicsControllerType_VBoxSVGA); + fRc = fRc and oSession.enableUsbOhci(True); + fRc = fRc and oSession.enableUsbHid(True); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + return oVM if fRc else None; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Current test uses precofigured VMs. This override disables any changes in the machine. + # + _ = cCpus; + _ = sVirtMode; + _ = sParavirtMode; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is None: + return (False, None); + return (True, oVM); + +class tdAutostartOsLinux(tdAutostartOs): + """ + Autostart support methods for Linux guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + try: self.sVBoxInstaller = '^VirtualBox-.*\\.run$'; + except: pass; + return; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Install guest additions in the guest. + """ + reporter.testStart('Install Guest Additions'); + fRc = False; + # Install Kernel headers, which are required for actually installing the Linux Additions. + if oVM.OSTypeId.startswith('Debian') \ + or oVM.OSTypeId.startswith('Ubuntu'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', + 'linux-headers-generic'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', 'build-essential', + 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + elif oVM.OSTypeId.startswith('OL') \ + or oVM.OSTypeId.startswith('Oracle') \ + or oVM.OSTypeId.startswith('RHEL') \ + or oVM.OSTypeId.startswith('Redhat') \ + or oVM.OSTypeId.startswith('Cent'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'kernel-headers'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'make', 'automake', 'gcc', + 'kernel-devel', 'dkms', 'bzip2', 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + else: + reporter.error('Installing Linux Additions for the "%s" is not supported yet' % oVM.OSTypeId); + fRc = False; + if fRc: + # + # The actual install. + # Also tell the installer to produce the appropriate log files. + # + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing guest additions', + 10 * 60 *1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/sh', + '/media/cdrom/VBoxLinuxAdditions.run'], + False, True); + if fRc: + # Due to the GA updates as separate process the above function returns before + # the actual installation finished. So just wait until the GA installed + fRc = self.closeSession(oGuestSession); + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + # Download log files. + # Ignore errors as all files above might not be present for whatever reason. + # + if fRc: + asLogFile = []; + asLogFile.append('/var/log/vboxadd-install.log'); + self.downloadFiles(oGuestSession, asLogFile, fIgnoreErrors = True); + else: + reporter.error('Installing guest additions failed: Error occured during vbox installer execution') + if fRc: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if not fRc: + reporter.error('Reboot after installing GuestAdditions failed'); + reporter.testDone(); + return (fRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + '/tmp/' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the vbox installer into guest VM failed'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Allowing execution for the vbox installer', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '755', + '/tmp/' + os.path.basename(sTestBuild)], + False, True); + if not fRc: + reporter.error('Allowing execution for the vbox installer failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 240 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', + '/tmp/' + os.path.basename(sTestBuild),], + False, True); + if not fRc: + reporter.error('Installing VBox failed'); + reporter.testDone(); + return fRc; + + def configureAutostart(self, oGuestSession, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Configures the autostart feature in the guest. + """ + reporter.testStart('Configure autostart'); + # Create autostart database directory writeable for everyone + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Creating autostart database', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/mkdir', '-m', '1777', '/etc/vbox/autostart.d'], + False, True); + if not fRc: + reporter.error('Creating autostart database failed'); + # Create /etc/default/virtualbox + if fRc: + sVBoxCfg = 'VBOXAUTOSTART_CONFIG=/etc/vbox/autostart.cfg\n' \ + + 'VBOXAUTOSTART_DB=/etc/vbox/autostart.d\n'; + fRc = self.uploadString(oGuestSession, sVBoxCfg, '/tmp/virtualbox'); + if not fRc: + reporter.error('Upload to /tmp/virtualbox failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Moving to destination', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/mv', '/tmp/virtualbox', + '/etc/default/virtualbox'], + False, True); + if not fRc: + reporter.error('Moving the /tmp/virtualbox to destination failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Setting permissions', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '644', + '/etc/default/virtualbox'], + False, True); + if not fRc: + reporter.error('Setting permissions for the virtualbox failed'); + if fRc: + sVBoxCfg = self._createAutostartCfg(sDefaultPolicy, asUserAllow, asUserDeny); + fRc = self.uploadString(oGuestSession, sVBoxCfg, '/tmp/autostart.cfg'); + if not fRc: + reporter.error('Upload to /tmp/autostart.cfg failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Moving to destination', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/mv', '/tmp/autostart.cfg', + '/etc/vbox/autostart.cfg'], + False, True); + if not fRc: + reporter.error('Moving the /tmp/autostart.cfg to destination failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Setting permissions', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '644', + '/etc/vbox/autostart.cfg'], + False, True); + if not fRc: + reporter.error('Setting permissions for the autostart.cfg failed'); + reporter.testDone(); + return fRc; + + def createUser(self, oGuestSession, sUser): + """ + Create a new user with the given name + """ + reporter.testStart('Create user %s' % sUser); + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Creating new user', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/usr/sbin/useradd', '-m', '-U', + sUser], False, True); + if not fRc: + reporter.error('Create user %s failed' % sUser); + reporter.testDone(); + return fRc; + + # pylint: enable=too-many-arguments + def createTestVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Create a test VM in the guest and enable autostart. + Due to the sUser is created whithout password, + all calls will be perfomed using 'sudo -u sUser' + """ + _ = oSession; + reporter.testStart('Create test VM for user %s' % sUser); + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Configuring autostart database', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', '/opt/VirtualBox/VBoxManage', + 'setproperty', 'autostartdbpath', '/etc/vbox/autostart.d'], + False, True); + if not fRc: + reporter.error('Configuring autostart database failed'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Create VM ' + sVmName, + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', + '/opt/VirtualBox/VBoxManage', 'createvm', + '--name', sVmName, '--register'], False, True); + if not fRc: + reporter.error('Create VM %s failed' % sVmName); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Enabling autostart for test VM', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', + '/opt/VirtualBox/VBoxManage', 'modifyvm', + sVmName, '--autostart-enabled', 'on'], False, True); + if not fRc: + reporter.error('Enabling autostart for %s failed' % sVmName); + reporter.testDone(); + return fRc; + + def checkForRunningVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Check for VM running in the guest after autostart. + Due to the sUser is created whithout password, + all calls will be perfomed using 'sudo -u sUser' + """ + self.oTstDrv.sleep(30); + _ = oSession; + reporter.testStart('Check the VM %s is running for user %s' % (sVmName, sUser)); + (fRc, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check for running VM', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '-u', sUser, '-H', + '/opt/VirtualBox/VBoxManage', + 'list', 'runningvms'], True, True); + if not fRc: + reporter.error('Checking the VM %s is running for user %s failed' % (sVmName, sUser)); + else: + bufWrapper = VBoxManageStdOutWrapper(); + bufWrapper.write(aBuf); + fRc = bufWrapper.sVmRunning == sVmName; + reporter.testDone(); + return fRc; + +class tdAutostartOsDarwin(tdAutostartOs): + """ + Autostart support methods for Darwin guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + raise base.GenError('Testing the autostart functionality for Darwin is not implemented'); + +class tdAutostartOsSolaris(tdAutostartOs): + """ + Autostart support methods for Solaris guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + raise base.GenError('Testing the autostart functionality for Solaris is not implemented'); + +class tdAutostartOsWin(tdAutostartOs): + """ + Autostart support methods for Windows guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None): + tdAutostartOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso); + try: self.sVBoxInstaller = '^VirtualBox-.*\\.(exe|msi)$'; + except: pass; + return; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, 'C:\\Windows\\System32\\ipconfig.exe', + ['C:\\Windows\\System32\\ipconfig.exe',], + False, False); + return fRc; + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/r', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/s', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oVM; + reporter.testStart('Install Guest Additions'); + asLogFiles = []; + fRc = self.closeSession(oGuestSession, True); # pychecker hack. + try: + oCurProgress = oSession.o.console.guest.updateGuestAdditions(self.sGuestAdditionsIso, ['/l',], None); + except: + reporter.maybeErrXcpt(True, 'Updating Guest Additions exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "installAdditions"); + oWrapperProgress.wait(cMsTimeout = 10 * 60 * 1000); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + #--------------------------------------- + # + ## + ## Install the public signing key. + ## + # + #self.oTstDrv.sleep(60 * 2); + # + #if oVM.OSTypeId not in ('WindowsNT4', 'Windows2000', 'WindowsXP', 'Windows2003'): + # (fRc, _, _, _) = \ + # self.guestProcessExecute(oGuestSession, 'Installing SHA1 certificate', + # 60 * 1000, 'D:\\cert\\VBoxCertUtil.exe', + # ['D:\\cert\\VBoxCertUtil.exe', 'add-trusted-publisher', + # 'D:\\cert\\vbox-sha1.cer'], + # False, True); + # if not fRc: + # reporter.error('Error installing SHA1 certificate'); + # else: + # (fRc, _, _, _) = \ + # self.guestProcessExecute(oGuestSession, 'Installing SHA1 certificate', + # 60 * 1000, 'D:\\cert\\VBoxCertUtil.exe', + # ['D:\\cert\\VBoxCertUtil.exe', 'add-trusted-publisher', + # 'D:\\cert\\vbox-sha256.cer'], + # False, True); + # if not fRc: + # reporter.error('Error installing SHA256 certificate'); + # + #(fRc, _, _, _) = \ + # self.guestProcessExecute(oGuestSession, 'Installing GA', + # 60 * 1000, 'D:\\VBoxWindowsAdditions.exe', + # ['D:\\VBoxWindowsAdditions.exe', '/S', '/l', + # '/no_vboxservice_exit'], + # False, True); + # + #if fRc: + # # Due to the GA updates as separate process the above function returns before + # # the actual installation finished. So just wait until the GA installed + # fRc = self.closeSession(oGuestSession, True); + # if fRc: + # (fRc, oGuestSession) = self.waitVmIsReady(oSession, False, False); + #--------------------------------------- + # Store the result and try download logs anyway. + fGaRc = fRc; + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, True); + if fRc is True: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if fRc is True: + # Add the Windows Guest Additions installer files to the files we want to download + # from the guest. + sGuestAddsDir = 'C:/Program Files/Oracle/VirtualBox Guest Additions/'; + asLogFiles.append(sGuestAddsDir + 'install.log'); + # Note: There won't be a install_ui.log because of the silent installation. + asLogFiles.append(sGuestAddsDir + 'install_drivers.log'); + # Download log files. + # Ignore errors as all files above might not be present (or in different locations) + # on different Windows guests. + # + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + else: + reporter.error('Reboot after installing GuestAdditions failed'); + else: + reporter.error('Create session for user vbox after GA updating failed'); + reporter.testDone(); + return (fRc and fGaRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + # Used windows image already contains the C:\Temp + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + 'C:\\Temp\\' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the installing into guest VM failed'); + else: + if sTestBuild.endswith('.msi'): + sLogFile = 'C:/Temp/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Windows\\System32\\msiexec.exe', + ['msiexec', '/quiet', '/norestart', '/i', + 'C:\\Temp\\' + os.path.basename(sTestBuild), + '/lv', sLogFile], + False, True); + if not fRc: + reporter.error('Installing the VBox from msi installer failed'); + else: + sLogFile = 'C:/Temp/Virtualbox/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Temp\\' + os.path.basename(sTestBuild), + ['C:\\Temp\\' + os.path.basename(sTestBuild), '-vvvv', + '--silent', '--logging', + '--msiparams', 'REBOOT=ReallySuppress'], + False, True); + if not fRc: + reporter.error('Installing the VBox failed'); + else: + (_, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check installation', + 240 * 1000, 'C:\\Windows\\System32\\cmd.exe', + ['c:\\Windows\\System32\\cmd.exe', '/c', + 'dir', 'C:\\Program Files\\Oracle\\VirtualBox\\*.*'], + True, True); + reporter.log('Content of VirtualBxox folder:'); + reporter.log(str(aBuf)); + asLogFiles = [sLogFile,]; + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + reporter.testDone(); + return fRc; + + def configureAutostart(self, oGuestSession, sDefaultPolicy = 'allow', asUserAllow = (), asUserDeny = ()): + """ + Configures the autostart feature in the guest. + """ + reporter.testStart('Configure autostart'); + # Create autostart database directory writeable for everyone + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Setting the autostart environment variable', + 30 * 1000, 'C:\\Windows\\System32\\reg.exe', + ['reg', 'add', + 'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', + '/v', 'VBOXAUTOSTART_CONFIG', '/d', + 'C:\\ProgramData\\autostart.cfg', '/f'], + False, True); + if fRc: + sVBoxCfg = self._createAutostartCfg(sDefaultPolicy, asUserAllow, asUserDeny); + fRc = self.uploadString(oGuestSession, sVBoxCfg, 'C:\\ProgramData\\autostart.cfg'); + if not fRc: + reporter.error('Upload the autostart.cfg failed'); + else: + reporter.error('Setting the autostart environment variable failed'); + reporter.testDone(); + return fRc; + + def createTestVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Create a test VM in the guest and enable autostart. + """ + _ = oGuestSession; + reporter.testStart('Create test VM for user %s' % sUser); + fRc, oGuestSession = self.createSession(oSession, 'Session for user: %s' % (sUser,), + sUser, 'password', 10 * 1000, True); + if not fRc: + reporter.error('Create session for user %s failed' % sUser); + else: + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Create VM ' + sVmName, + 30 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', 'createvm', + '--name', sVmName, '--register'], False, True); + if not fRc: + reporter.error('Create VM %s for user %s failed' % (sVmName, sUser)); + else: + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Enabling autostart for test VM', + 30 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + 'modifyvm', sVmName, '--autostart-enabled', 'on'], False, True); + if not fRc: + reporter.error('Enabling autostart for VM %s for user %s failed' % (sVmName, sUser)); + if fRc: + fRc = self.uploadString(oGuestSession, 'password', 'C:\\ProgramData\\password.cfg'); + if not fRc: + reporter.error('Upload the password.cfg failed'); + if fRc: + (fRc, _, _, _) = \ + self.guestProcessExecute(oGuestSession, 'Install autostart service for the user', + 30 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxAutostartSvc.exe', + ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxAutostartSvc.exe', + 'install', '--user=' + sUser, + '--password-file=C:\\ProgramData\\password.cfg'], + False, True); + if not fRc: + reporter.error('Install autostart service for user %s failed' % sUser); + fRc1 = self.closeSession(oGuestSession, True); + if not fRc1: + reporter.error('Closing session for user %s failed' % sUser); + fRc = fRc1 and fRc and True; # pychecker hack. + reporter.testDone(); + return fRc; + + def checkForRunningVM(self, oSession, oGuestSession, sUser, sVmName): + """ + Check for VM running in the guest after autostart. + """ + self.oTstDrv.sleep(30); + _ = oGuestSession; + reporter.testStart('Check the VM %s is running for user %s' % (sVmName, sUser)); + fRc, oGuestSession = self.createSession(oSession, 'Session for user: %s' % (sUser,), + sUser, 'password', 10 * 1000, True); + if not fRc: + reporter.error('Create session for user %s failed' % sUser); + else: + (fRc, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check for running VM', + 60 * 1000, 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + [ 'C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe', + 'list', 'runningvms' ], True, True); + if not fRc: + reporter.error('Checking the VM %s is running for user %s failed' % (sVmName, sUser)); + else: + bufWrapper = VBoxManageStdOutWrapper(); + bufWrapper.write(aBuf); + fRc = bufWrapper.sVmRunning == sVmName; + fRc1 = self.closeSession(oGuestSession, True); + if not fRc1: + reporter.error('Closing session for user %s failed' % sUser); + fRc = fRc1 and fRc and True; # pychecker hack. + reporter.testDone(); + return fRc; + + def createUser(self, oGuestSession, sUser): + """ + Create a new user with the given name + """ + reporter.testStart('Create user %s' % sUser); + # Create user + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Creating user %s to run a VM' % sUser, + 30 * 1000, 'C:\\Windows\\System32\\net.exe', + ['net', 'user', sUser, 'password', '/add' ], False, True); + if not fRc: + reporter.error('Creating user %s to run a VM failed' % sUser); + # Add the user to Administrators group + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Adding the user %s to Administrators group' % sUser, + 30 * 1000, 'C:\\Windows\\System32\\net.exe', + ['net', 'localgroup', 'Administrators', sUser, '/add' ], False, True); + if not fRc: + reporter.error('Adding the user %s to Administrators group failed' % sUser); + #Allow the user to logon as service + if fRc: + sSecPolicyEditor = """ +$sUser = '%s' +$oUser = New-Object System.Security.Principal.NTAccount("$sUser") +$oSID = $oUser.Translate([System.Security.Principal.SecurityIdentifier]) +$sExportFile = 'C:\\Temp\\cfg.inf' +$sSecDb = 'C:\\Temp\\secedt.sdb' +$sImportFile = 'C:\\Temp\\newcfg.inf' +secedit /export /cfg $sExportFile +$sCurrServiceLogonRight = Get-Content -Path $sExportFile | + Where-Object {$_ -Match 'SeServiceLogonRight'} +$asFileContent = @' +[Unicode] +Unicode=yes +[System Access] +[Event Audit] +[Registry Values] +[Version] +signature="$CHICAGO$" +Revision=1 +[Profile Description] +Description=GrantLogOnAsAService security template +[Privilege Rights] +{0}*{1} +'@ -f $( + if($sCurrServiceLogonRight){"$sCurrServiceLogonRight,"} + else{'SeServiceLogonRight = '} + ), $oSid.Value +Set-Content -Path $sImportFile -Value $asFileContent +secedit /import /db $sSecDb /cfg $sImportFile +secedit /configure /db $sSecDb +Remove-Item -Path $sExportFile +Remove-Item -Path $sSecDb +Remove-Item -Path $sImportFile + """ % (sUser,); + fRc = self.uploadString(oGuestSession, sSecPolicyEditor, 'C:\\Temp\\adjustsec.ps1'); + if not fRc: + reporter.error('Upload the adjustsec.ps1 failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Setting the "Logon as service" policy to the user %s' % sUser, + 300 * 1000, 'C:\\Windows\\System32\\cmd.exe', + ['cmd.exe', '/c', "type C:\\Temp\\adjustsec.ps1 | powershell -"], + False, True); + if not fRc: + reporter.error('Setting the "Logon as service" policy to the user %s failed' % sUser); + try: + oGuestSession.fsObjRemove('C:\\Temp\\adjustsec.ps1'); + except: + fRc = reporter.errorXcpt('Removing policy script failed'); + reporter.testDone(); + return fRc; + +class tdAutostart(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Autostart testcase. + """ + ksOsLinux = 'tst-linux' + ksOsWindows = 'tst-win' + ksOsDarwin = 'tst-darwin' + ksOsSolaris = 'tst-solaris' + ksOsFreeBSD = 'tst-freebsd' + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asTestVMsDef = [self.ksOsWindows, self.ksOsLinux]; #[self.ksOsLinux, self.ksOsWindows]; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + ## @todo r=bird: The --test-build-dirs option as primary way to get the installation files to test + ## is not an acceptable test practice as we don't know wtf you're testing. See defect for more. + self.asTestBuildDirs = [os.path.join(self.sScratchPath, 'bin'),]; + self.sGuestAdditionsIso = None; #'D:/AlexD/TestBox/TestAdditionalFiles/VBoxGuestAdditions_6.1.2.iso'; + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, acCpus = [2], asVirtModes = ['hwvirt-np',], fIgnoreSkippedVm = True); + # pylint: disable=line-too-long + self.asTestVmClasses = { + 'win' : tdAutostartOsWin(oSet, self, self.ksOsWindows, 'Windows7_64', \ + '6.0/windows7piglit/windows7piglit.vdi', eNic0Type = None, cMbRam = 2048, \ + cCpus = 2, fPae = True, sGuestAdditionsIso = self.getGuestAdditionsIso()), + 'linux' : tdAutostartOsLinux(oSet, self, self.ksOsLinux, 'Ubuntu_64', \ + '6.0/ub1804piglit/ub1804piglit.vdi', eNic0Type = None, \ + cMbRam = 2048, cCpus = 2, fPae = None, sGuestAdditionsIso = self.getGuestAdditionsIso()), + 'solaris' : None, #'tdAutostartOsSolaris', + 'darwin' : None #'tdAutostartOsDarwin' + }; + oSet.aoTestVms.extend([oTestVm for oTestVm in self.asTestVmClasses.values() if oTestVm is not None]); + sOs = self.getBuildOs(); + if sOs in self.asTestVmClasses: + for oTestVM in oSet.aoTestVms: + if oTestVM is not None: + oTestVM.fSkip = oTestVM != self.asTestVmClasses[sOs]; + + # pylint: enable=line-too-long + self.oTestVmSet = oSet; + + # + # Overridden methods. + # + + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAutostart Options:'); + reporter.log(' --test-build-dirs <path1[,path2[,...]]>'); + reporter.log(' The list of directories with VirtualBox distros. Overrides default path.'); + reporter.log(' Default path is $TESTBOX_SCRATCH_PATH/bin.'); + reporter.log(' --vbox-<os>-build <path>'); + reporter.log(' The path to vbox build for the specified OS.'); + reporter.log(' The OS can be one of "win", "linux", "solaris" and "darwin".'); + reporter.log(' This option alse enables corresponding VM for testing.'); + reporter.log(' (Default behaviour is testing only VM having host-like OS.)'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--test-build-dirs': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-build-dirs" takes a path argument'); + self.asTestBuildDirs = asArgs[iArg].split(','); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.asTestBuildDirs = self.asTestBuildDirs; + elif asArgs[iArg] in [ '--vbox-%s-build' % sKey for sKey in self.asTestVmClasses]: + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "%s" take a path argument' % (asArgs[iArg - 1],)); + oMatch = re.match("--vbox-([^-]+)-build", asArgs[iArg - 1]); + if oMatch is not None: + sOs = oMatch.group(1); + oTestVm = self.asTestVmClasses.get(sOs); + if oTestVm is not None: + oTestVm.sTestBuild = asArgs[iArg]; + oTestVm.fSkip = False; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testAutostartOneCfg) + + # + # Test execution helpers. + # + def testAutostartOneCfg(self, oVM, oTestVm): + # Reconfigure the VM + fRc = True; + self.logVmInfo(oVM); + oSession = self.startVmByName(oTestVm.sVmName); + if oSession is not None: + sTestUserAllow = 'test1'; + sTestUserDeny = 'test2'; + sTestVmName = 'TestVM'; + #wait the VM is ready after starting + (fRc, oGuestSession) = oTestVm.waitVmIsReady(oSession, True); + #install fresh guest additions + if fRc: + (fRc, oGuestSession) = oTestVm.installAdditions(oSession, oGuestSession, oVM); + # Create two new users + fRc = fRc and oTestVm.createUser(oGuestSession, sTestUserAllow); + fRc = fRc and oTestVm.createUser(oGuestSession, sTestUserDeny); + if fRc is True: + # Install VBox first + fRc = oTestVm.installVirtualBox(oGuestSession); + if fRc is True: + fRc = oTestVm.configureAutostart(oGuestSession, 'allow', (sTestUserAllow,), (sTestUserDeny,)); + if fRc is True: + # Create a VM with autostart enabled in the guest for both users + fRc = oTestVm.createTestVM(oSession, oGuestSession, sTestUserAllow, sTestVmName); + fRc = fRc and oTestVm.createTestVM(oSession, oGuestSession, sTestUserDeny, sTestVmName); + if fRc is True: + # Reboot the guest + (fRc, oGuestSession) = oTestVm.rebootVMAndCheckReady(oSession, oGuestSession); + if fRc is True: + # Fudge factor - Allow the guest VMs to finish starting up. + self.sleep(60); + fRc = oTestVm.checkForRunningVM(oSession, oGuestSession, sTestUserAllow, sTestVmName); + if fRc is True: + fRc = oTestVm.checkForRunningVM(oSession, oGuestSession, sTestUserDeny, sTestVmName); + if fRc is True: + reporter.error('Test VM is running inside the guest for denied user'); + fRc = not fRc; + else: + reporter.error('Test VM is not running inside the guest for allowed user'); + else: + reporter.error('Rebooting the guest failed'); + else: + reporter.error('Creating test VM failed'); + else: + reporter.error('Configuring autostart in the guest failed'); + else: + reporter.error('Installing VirtualBox in the guest failed'); + else: + reporter.error('Creating test users failed'); + if oGuestSession is not None: + try: oTestVm.powerDownVM(oGuestSession); + except: pass; + try: self.terminateVmBySession(oSession); + except: pass; + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + return fRc; + +if __name__ == '__main__': + sys.exit(tdAutostart().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk b/src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk new file mode 100644 index 00000000..fb89639d --- /dev/null +++ b/src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Benchmarks. +# + +# +# 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 += ValidationKitTestsBenchmarks +ValidationKitTestsBenchmarks_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsBenchmarks_INST = $(INST_VALIDATIONKIT)tests/benchmarks/ +ValidationKitTestsBenchmarks_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdBenchmark1.py \ + $(PATH_SUB_CURRENT)/tdBenchmark2.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsBenchmarks_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py new file mode 100755 index 00000000..33f34cc6 --- /dev/null +++ b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdBenchmark1.py $ + +""" +VirtualBox Validation Kit - Test that runs various benchmarks. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxtestvms; + + +class tdBenchmark1(vbox.TestDriver): + """ + Benchmark #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + oTestVm = vboxtestvms.BootSectorTestVm(self.oTestVmSet, 'tst-bs-test1', + os.path.join(self.sVBoxBootSectors, 'bootsector2-test1.img')); + self.oTestVmSet.aoTestVms.append(oTestVm); + + + # + # Overridden methods. + # + + + def actionConfig(self): + self._detectValidationKit(); + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + + + # + # Test execution helpers. + # + + def testOneCfg(self, oVM, oTestVm): + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + fRc = False; + + sXmlFile = self.prepareResultFile(); + asEnv = [ 'IPRT_TEST_FILE=' + sXmlFile]; + + self.logVmInfo(oVM); + oSession = self.startVm(oVM, sName = oTestVm.sVmName, asEnv = asEnv); + if oSession is not None: + cMsTimeout = 15*60*1000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = self.adjustTimeoutMs(180 * 60000); + + oRc = self.waitForTasks(cMsTimeout); + if oRc == oSession: + fRc = oSession.assertPoweredOff(); + else: + reporter.error('oRc=%s, expected %s' % (oRc, oSession)); + + reporter.addSubXmlFile(sXmlFile); + self.terminateVmBySession(oSession); + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdBenchmark1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py new file mode 100755 index 00000000..4eda0ddf --- /dev/null +++ b/src/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdBenchmark2.py $ + +""" +VirtualBox Validation Kit - Test that runs various benchmarks. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestvms; + + +class tdBenchmark2(vbox.TestDriver): + """ + Benchmark #2 - Memory. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + oTestVm = vboxtestvms.BootSectorTestVm(self.oTestVmSet, 'tst-bs-memalloc-1', + os.path.join(self.sVBoxBootSectors, 'bs3-memalloc-1.img')); + self.oTestVmSet.aoTestVms.append(oTestVm); + + + # + # Overridden methods. + # + + + def actionConfig(self): + self._detectValidationKit(); + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + return self.oTestVmSet.actionExecute(self, self.testOneCfg); + + + + # + # Test execution helpers. + # + + def testOneCfg(self, oVM, oTestVm): + """ + Runs the specified VM thru the tests. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + fRc = False; + + # + # Determin the RAM configurations we want to test. + # + cMbMaxGuestRam = self.oVBox.systemProperties.maxGuestRAM; + cMbHostAvail = self.oVBox.host.memoryAvailable; + cMbHostTotal = self.oVBox.host.memorySize; + reporter.log('cMbMaxGuestRam=%s cMbHostAvail=%s cMbHostTotal=%s' % (cMbMaxGuestRam, cMbHostAvail, cMbHostTotal,)); + + cMbHostAvail -= cMbHostAvail // 7; # Rough 14% safety/overhead margin. + if cMbMaxGuestRam < cMbHostAvail: + # Currently: 2048 GiB, 1536 GiB, 1024 GiB, 512 GiB, 256 GiB, 128 GiB, 64 GiB, 32 GiB + acMbRam = [ cMbMaxGuestRam, cMbMaxGuestRam // 4 * 3, cMbMaxGuestRam // 2, cMbMaxGuestRam // 4, + cMbMaxGuestRam // 8, cMbMaxGuestRam // 16 ]; + if acMbRam[-1] > 64*1024: + acMbRam.append(64*1024); + if acMbRam[-1] > 32*1024: + acMbRam.append(32*1024); + elif cMbHostAvail > 8*1024: + # First entry is available memory rounded down to the nearest 8 GiB + cMbHostAvail = cMbHostAvail & ~(8 * 1024 - 1); + acMbRam = [ cMbHostAvail, ]; + + # The remaining entries are powers of two below that, up to 6 of these stopping at 16 GiB. + cMb = 8*1024; + while cMb < cMbHostAvail: + cMb *= 2; + while len(acMbRam) < 7 and cMb > 16 * 1024: + cMb //= 2; + acMbRam.append(cMb); + elif cMbHostAvail >= 16000 and cMbHostAvail > 7168: + # Desperate attempt at getting some darwin testruns too. We've got two + # with 16 GiB and they usually end up with just short of 8GiB of free RAM. + acMbRam = [7168,]; + else: + reporter.log("Less than 8GB of host RAM available for VMs, skipping test"); + return None; + reporter.log("RAM configurations: %s" % (acMbRam)); + + # Large pages only work with nested paging. + afLargePages = [False, ]; + try: + if oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_NestedPaging): + afLargePages = [True, False]; + except: + return reporter.errorXcpt("Failed to get HWVirtExPropertyType_NestedPaging"); + + # + # Test the RAM configurations. + # + for fLargePages in afLargePages: + sLargePages = 'large pages' if fLargePages is True else 'no large pages'; + for cMbRam in acMbRam: + reporter.testStart('%s MiB, %s' % (cMbRam, sLargePages)); + + # Reconfigure the VM: + fRc = False + oSession = self.openSession(oVM); + if oSession: + fRc = oSession.setRamSize(cMbRam); + fRc = oSession.setLargePages(fLargePages) and fRc; + if fRc: + fRc = oSession.saveSettings(); + if not fRc: + oSession.discardSettings(True); + oSession.close(); + if fRc: + # Set up the result file + sXmlFile = self.prepareResultFile(); + asEnv = [ 'IPRT_TEST_FILE=' + sXmlFile, ]; + + # Do the test: + self.logVmInfo(oVM); + oSession = self.startVm(oVM, sName = oTestVm.sVmName, asEnv = asEnv); + if oSession is not None: + cMsTimeout = 15 * 60000 + cMbRam // 168; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = self.adjustTimeoutMs(180 * 60000 + cMbRam // 168); + + oRc = self.waitForTasks(cMsTimeout); + if oRc == oSession: + fRc = oSession.assertPoweredOff(); + else: + reporter.error('oRc=%s, expected %s' % (oRc, oSession)); + + reporter.addSubXmlFile(sXmlFile); + self.terminateVmBySession(oSession); + else: + reporter.errorXcpt("Failed to set memory size to %s MiB or setting largePages to %s" % (cMbRam, fLargePages)); + reporter.testDone(); + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdBenchmark2().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/cpu/Makefile.kmk b/src/VBox/ValidationKit/tests/cpu/Makefile.kmk new file mode 100644 index 00000000..b72fa098 --- /dev/null +++ b/src/VBox/ValidationKit/tests/cpu/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - CPU 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 += ValidationKitTestsCpu +ValidationKitTestsCpu_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsCpu_INST = $(INST_VALIDATIONKIT)tests/cpu/ +ValidationKitTestsCpu_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdCpuPae1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsCpu_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/cpu/tdCpuPae1.py b/src/VBox/ValidationKit/tests/cpu/tdCpuPae1.py new file mode 100755 index 00000000..b0d6c342 --- /dev/null +++ b/src/VBox/ValidationKit/tests/cpu/tdCpuPae1.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdCpuPae1.py $ + +""" +VirtualBox Validation Kit - Catch PAE not enabled. + +Test that switching into PAE mode when it isn't enable, check that it produces +the right runtime error. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; + + +class tdCpuPae1ConsoleCallbacks(vbox.ConsoleEventHandlerBase): + """ + For catching the PAE runtime error. + """ + def __init__(self, dArgs): + oTstDrv = dArgs['oTstDrv']; + oVBoxMgr = dArgs['oVBoxMgr']; _ = oVBoxMgr; + + vbox.ConsoleEventHandlerBase.__init__(self, dArgs, 'tdCpuPae1'); + self.oTstDrv = oTstDrv; + + def onRuntimeError(self, fFatal, sErrId, sMessage): + """ Verify the error. """ + reporter.log('onRuntimeError: fFatal=%s sErrId="%s" sMessage="%s"' % (fFatal, sErrId, sMessage)); + if sErrId != 'PAEmode': + reporter.testFailure('sErrId=%s, expected PAEmode' % (sErrId,)); + elif fFatal is not True: + reporter.testFailure('fFatal=%s, expected True' % (fFatal,)); + else: + self.oTstDrv.fCallbackSuccess = True; + self.oTstDrv.fCallbackFired = True; + self.oVBoxMgr.interruptWaitEvents(); + return None; + + +class tdCpuPae1(vbox.TestDriver): + """ + PAE Test #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asSkipTests = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + self.fCallbackFired = False; + self.fCallbackSuccess = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdCpuPae1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --quick'); + reporter.log(' Shorthand for: --virt-modes raw --cpu-counts 1 32'); + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--quick': + self.asVirtModes = ['raw',]; + self.acCpus = [1,]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + return []; + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure a VM with the PAE bootsector as floppy image. + # + + oVM = self.createTestVM('tst-bs-pae', 2, sKind = 'Other', fVirtEx = False, fPae = False, \ + sFloppy = os.path.join(self.sVBoxBootSectors, 'bootsector-pae.img') ); + if oVM is None: + return False; + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.test1(); + + + + # + # Test execution helpers. + # + + def test1OneCfg(self, oVM, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.setupBootLogo(True, 2500); # Race avoidance fudge. + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Zap the state (used by the callback). + self.fCallbackFired = False; + self.fCallbackSuccess = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession = self.startVm(oVM) + if oSession is not None: + # Set up a callback for catching the runtime error. !Races the guest bootup! + oConsoleCallbacks = oSession.registerDerivedEventHandler(tdCpuPae1ConsoleCallbacks, {'oTstDrv':self,}) + + fRc = False; + if oConsoleCallbacks is not None: + # Wait for 30 seconds for something to finish. + tsStart = base.timestampMilli(); + while base.timestampMilli() - tsStart < 30000: + oTask = self.waitForTasks(1000); + if oTask is not None: + break; + if self.fCallbackFired: + break; + if not self.fCallbackFired: + reporter.testFailure('the callback did not fire'); + fRc = self.fCallbackSuccess; + + # cleanup. + oConsoleCallbacks.unregister(); + self.terminateVmBySession(oSession) #, fRc); + else: + fRc = False; + return fRc; + + + def test1(self): + """ + Executes test #1 - Negative API testing. + + ASSUMES that the VMs are + """ + reporter.testStart('Test 1'); + oVM = self.getVmByName('tst-bs-pae'); + + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + self.test1OneCfg(oVM, cCpus, fHwVirt, fNestedPaging); + + reporter.testDone(); + reporter.testDone(); + + return reporter.testDone()[1] == 0; + + + +if __name__ == '__main__': + sys.exit(tdCpuPae1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/installation/Makefile.kmk b/src/VBox/ValidationKit/tests/installation/Makefile.kmk new file mode 100644 index 00000000..b901296f --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Automatic guest OS installation 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 += ValidationKitInstallationTests +ValidationKitInstallationTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitInstallationTests_INST = $(INST_VALIDATIONKIT)tests/installation/ +ValidationKitInstallationTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdGuestOsInstTest1.py \ + $(PATH_SUB_CURRENT)/tdGuestOsInstOs2.py \ + $(PATH_SUB_CURRENT)/tdGuestOsUnattendedInst1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitInstallationTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py new file mode 100755 index 00000000..caba4e76 --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdGuestOsInstOs2.py $ + +""" +VirtualBox Validation Kit - OS/2 install 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: 155244 $" + + +# Standard Python imports. +import os +import sys + + +# 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 testdriver import vbox +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon + + +class tdGuestOsInstOs2(vbox.TestDriver): + """ + OS/2 unattended installation. + + Scenario: + - Create new VM that corresponds specified installation ISO image + - Create HDD that corresponds to OS type that will be installed + - Set VM boot order: HDD, Floppy, ISO + - Start VM: sinse there is no OS installed on HDD, VM will booted from floppy + - After first reboot VM will continue installation from HDD automatically + - Wait for incomming TCP connection (guest should initiate such a + connection in case installation has been completed successfully) + """ + + ksSataController = 'SATA Controller' + ksIdeController = 'IDE Controller' + + # VM parameters required to run ISO image. + # Format: (cBytesHdd, sKind) + kaoVmParams = { + 'acp2-txs.iso': ( 2*1024*1024*1024, 'OS2', ksIdeController ), + 'mcp2-txs.iso': ( 2*1024*1024*1024, 'OS2', ksIdeController ), + } + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + + self.sVmName = 'TestVM' + self.sHddName = 'TestHdd.vdi' + self.sIso = None + self.sFloppy = None + self.sIsoPathBase = os.path.join(self.sResourcePath, '4.2', 'isos') + self.fEnableIOAPIC = True + self.cCpus = 1 + self.fEnableNestedPaging = True + self.fEnablePAE = False + self.asExtraData = [] + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(' --install-iso <ISO file name>') + reporter.log(' --cpus <# CPUs>') + reporter.log(' --no-ioapic') + reporter.log(' --no-nested-paging') + reporter.log(' --pae') + reporter.log(' --set-extradata <key>:value') + reporter.log(' Set VM extra data. This command line option might be used multiple times.') + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + if asArgs[iArg] == '--install-iso': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--install-iso" option requires an argument') + self.sIso = asArgs[iArg] + elif asArgs[iArg] == '--cpus': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpus" option requires an argument') + self.cCpus = int(asArgs[iArg]) + elif asArgs[iArg] == '--no-ioapic': + self.fEnableIOAPIC = False + elif asArgs[iArg] == '--no-nested-paging': + self.fEnableNestedPaging = False + elif asArgs[iArg] == '--pae': + self.fEnablePAE = True + elif asArgs[iArg] == '--extra-mem': + self.fEnablePAE = True + elif asArgs[iArg] == '--set-extradata': + iArg = self.requireMoreArgs(1, asArgs, iArg) + self.asExtraData.append(asArgs[iArg]) + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + + return iArg + 1 + + def actionConfig(self): + """ + Configure pre-conditions. + """ + + if not self.importVBoxApi(): + return False + + assert self.sIso is not None + if self.sIso not in self.kaoVmParams: + reporter.log('Error: unknown ISO image specified: %s' % self.sIso) + return False + + # Get VM params specific to ISO image + cBytesHdd, sKind, sController = self.kaoVmParams[self.sIso] + + # Create VM itself + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT + self.sIso = os.path.join(self.sIsoPathBase, self.sIso) + assert os.path.isfile(self.sIso) + + self.sFloppy = os.path.join(self.sIsoPathBase, os.path.splitext(self.sIso)[0] + '.img') + + oVM = self.createTestVM(self.sVmName, 1, sKind = sKind, sDvdImage = self.sIso, cCpus = self.cCpus, + sFloppy = self.sFloppy, eNic0AttachType = eNic0AttachType) + assert oVM is not None + + oSession = self.openSession(oVM) + + # Create HDD + sHddPath = os.path.join(self.sScratchPath, self.sHddName) + fRc = True + if sController == self.ksSataController: + fRc = oSession.setStorageControllerType(vboxcon.StorageControllerType_IntelAhci, sController) + + fRc = fRc and oSession.createAndAttachHd(sHddPath, cb = cBytesHdd, + sController = sController, iPort = 0, fImmutable=False) + if sController == self.ksSataController: + fRc = fRc and oSession.setStorageControllerPortCount(sController, 1) + + # Set proper boot order + fRc = fRc and oSession.setBootOrder(1, vboxcon.DeviceType_HardDisk) + fRc = fRc and oSession.setBootOrder(2, vboxcon.DeviceType_Floppy) + + # Enable HW virt + fRc = fRc and oSession.enableVirtEx(True) + + # Enable I/O APIC + fRc = fRc and oSession.enableIoApic(self.fEnableIOAPIC) + + # Enable Nested Paging + fRc = fRc and oSession.enableNestedPaging(self.fEnableNestedPaging) + + # Enable PAE + fRc = fRc and oSession.enablePae(self.fEnablePAE) + + # Remote desktop + oSession.setupVrdp(True) + + # Set extra data + if self.asExtraData: + for sExtraData in self.asExtraData: + try: + sKey, sValue = sExtraData.split(':') + except ValueError: + raise base.InvalidOption('Invalid extradata specified: %s' % sExtraData) + reporter.log('Set extradata: %s => %s' % (sKey, sValue)) + fRc = fRc and oSession.setExtraData(sKey, sValue) + + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() + assert fRc is True + + return vbox.TestDriver.actionConfig(self) + + def actionExecute(self): + """ + Execute the testcase itself. + """ + if not self.importVBoxApi(): + return False + return self.testDoInstallGuestOs() + + # + # Test execution helpers. + # + + def testDoInstallGuestOs(self): + """ + Install guest OS and wait for result + """ + reporter.testStart('Installing %s' % (os.path.basename(self.sIso),)) + + cMsTimeout = 40*60000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = 180 * 60000; # will be adjusted down. + + oSession, _ = self.startVmAndConnectToTxsViaTcp(self.sVmName, fCdWait = False, cMsTimeout = cMsTimeout) + if oSession is not None: + # Wait until guest reported success + reporter.log('Guest reported success') + reporter.testDone() + fRc = self.terminateVmBySession(oSession) + return fRc is True + reporter.error('Installation of %s has failed' % (self.sIso,)) + reporter.testDone() + return False + +if __name__ == '__main__': + sys.exit(tdGuestOsInstOs2().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py new file mode 100755 index 00000000..9c5c4ceb --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py @@ -0,0 +1,409 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdGuestOsInstTest1.py $ + +""" +VirtualBox Validation Kit - Guest OS installation 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: 155244 $" + + +# Standard Python imports. +import os +import sys + + +# 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 testdriver import vbox; +from testdriver import base; +from testdriver import reporter; +from testdriver import vboxcon; +from testdriver import vboxtestvms; + + +class InstallTestVm(vboxtestvms.TestVm): + """ Installation test VM. """ + + ## @name The primary controller, to which the disk will be attached. + ## @{ + ksScsiController = 'SCSI Controller' + ksSataController = 'SATA Controller' + ksIdeController = 'IDE Controller' + ## @} + + ## @name VM option flags (OR together). + ## @{ + kf32Bit = 0x01; + kf64Bit = 0x02; + # most likely for ancient Linux kernels assuming that AMD processors have always an I/O-APIC + kfReqIoApic = 0x10; + kfReqIoApicSmp = 0x20; + kfReqPae = 0x40; + kfIdeIrqDelay = 0x80; + kfUbuntuNewAmdBug = 0x100; + kfNoWin81Paravirt = 0x200; + ## @} + + ## IRQ delay extra data config for win2k VMs. + kasIdeIrqDelay = [ 'VBoxInternal/Devices/piix3ide/0/Config/IRQDelay:1', ]; + + ## Install ISO path relative to the testrsrc root. + ksIsoPathBase = os.path.join('4.2', 'isos'); + + + def __init__(self, oSet, sVmName, sKind, sInstallIso, sHdCtrlNm, cGbHdd, fFlags): + vboxtestvms.TestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind, sHddControllerType = sHdCtrlNm, + fRandomPvPMode = (fFlags & self.kfNoWin81Paravirt) == 0); + self.sDvdImage = os.path.join(self.ksIsoPathBase, sInstallIso); + self.cGbHdd = cGbHdd; + self.fInstVmFlags = fFlags; + if fFlags & self.kfReqPae: + self.fPae = True; + if fFlags & (self.kfReqIoApic | self.kfReqIoApicSmp): + self.fIoApic = True; + + # Tweaks + self.iOptRamAdjust = 0; + self.asExtraData = []; + if fFlags & self.kfIdeIrqDelay: + self.asExtraData = self.kasIdeIrqDelay; + + def detatchAndDeleteHd(self, oTestDrv): + """ + Detaches and deletes the HD. + Returns success indicator, error info logged. + """ + fRc = False; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is not None: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + (fRc, oHd) = oSession.detachHd(self.sHddControllerType, iPort = 0, iDevice = 0); + if fRc is True and oHd is not None: + fRc = oSession.saveSettings(); + fRc = fRc and oTestDrv.oVBox.deleteHdByMedium(oHd); + fRc = fRc and oSession.saveSettings(); # Necessary for media reg? + fRc = oSession.close() and fRc; + return fRc; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Do the standard reconfig in the base class first, it'll figure out + # if we can run the VM as requested. + # + (fRc, oVM) = vboxtestvms.TestVm.getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode); + + # + # Make sure there is no HD from the previous run attached nor taking + # up storage on the host. + # + if fRc is True: + fRc = self.detatchAndDeleteHd(oTestDrv); + + # + # Check for ubuntu installer vs. AMD host CPU. + # + if fRc is True and (self.fInstVmFlags & self.kfUbuntuNewAmdBug): + if self.isHostCpuAffectedByUbuntuNewAmdBug(oTestDrv): + return (None, None); # (skip) + + # + # Make adjustments to the default config, and adding a fresh HD. + # + if fRc is True: + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + if self.sHddControllerType == self.ksSataController: + fRc = fRc and oSession.setStorageControllerType(vboxcon.StorageControllerType_IntelAhci, + self.sHddControllerType); + fRc = fRc and oSession.setStorageControllerPortCount(self.sHddControllerType, 1); + elif self.sHddControllerType == self.ksScsiController: + fRc = fRc and oSession.setStorageControllerType(vboxcon.StorageControllerType_LsiLogic, + self.sHddControllerType); + try: + sHddPath = os.path.join(os.path.dirname(oVM.settingsFilePath), + '%s-%s-%s.vdi' % (self.sVmName, sVirtMode, cCpus,)); + except: + reporter.errorXcpt(); + sHddPath = None; + fRc = False; + + fRc = fRc and oSession.createAndAttachHd(sHddPath, + cb = self.cGbHdd * 1024*1024*1024, + sController = self.sHddControllerType, + iPort = 0, + fImmutable = False); + + # Set proper boot order + fRc = fRc and oSession.setBootOrder(1, vboxcon.DeviceType_HardDisk) + fRc = fRc and oSession.setBootOrder(2, vboxcon.DeviceType_DVD) + + # Adjust memory if requested. + if self.iOptRamAdjust != 0: + fRc = fRc and oSession.setRamSize(oSession.o.machine.memorySize + self.iOptRamAdjust); + + # Set extra data + for sExtraData in self.asExtraData: + try: + sKey, sValue = sExtraData.split(':') + except ValueError: + raise base.InvalidOption('Invalid extradata specified: %s' % sExtraData) + reporter.log('Set extradata: %s => %s' % (sKey, sValue)) + fRc = fRc and oSession.setExtraData(sKey, sValue) + + # Other variations? + + # Save the settings. + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc; + else: + fRc = False; + if fRc is not True: + oVM = None; + + # Done. + return (fRc, oVM) + + + + + +class tdGuestOsInstTest1(vbox.TestDriver): + """ + Guest OS installation tests. + + Scenario: + - Create new VM that corresponds specified installation ISO image. + - Create HDD that corresponds to OS type that will be installed. + - Boot VM from ISO image (i.e. install guest OS). + - Wait for incomming TCP connection (guest should initiate such a + connection in case installation has been completed successfully). + """ + + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + self.fLegacyOptions = False; + assert self.fEnableVrdp; # in parent driver. + + # + # Our install test VM set. + # + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, fIgnoreSkippedVm = True); + oSet.aoTestVms.extend([ + # pylint: disable=line-too-long + InstallTestVm(oSet, 'tst-fedora4', 'Fedora', 'fedora4-txs.iso', InstallTestVm.ksIdeController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-fedora5', 'Fedora', 'fedora5-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae | InstallTestVm.kfReqIoApicSmp), + InstallTestVm(oSet, 'tst-fedora6', 'Fedora', 'fedora6-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-fedora7', 'Fedora', 'fedora7-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-fedora9', 'Fedora', 'fedora9-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-fedora18-64', 'Fedora_64', 'fedora18-x64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-fedora18', 'Fedora', 'fedora18-txs.iso', InstallTestVm.ksScsiController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-ols6', 'Oracle', 'ols6-i386-txs.iso', InstallTestVm.ksSataController, 12, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-ols6-64', 'Oracle_64', 'ols6-x86_64-txs.iso', InstallTestVm.ksSataController, 12, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-rhel5', 'RedHat', 'rhel5-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-suse102', 'OpenSUSE', 'opensuse102-txs.iso', InstallTestVm.ksIdeController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqIoApic), + ## @todo InstallTestVm(oSet, 'tst-ubuntu606', 'Ubuntu', 'ubuntu606-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + ## @todo InstallTestVm(oSet, 'tst-ubuntu710', 'Ubuntu', 'ubuntu710-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-ubuntu804', 'Ubuntu', 'ubuntu804-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqPae | InstallTestVm.kfReqIoApic), + InstallTestVm(oSet, 'tst-ubuntu804-64', 'Ubuntu_64', 'ubuntu804-amd64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-ubuntu904', 'Ubuntu', 'ubuntu904-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-ubuntu904-64', 'Ubuntu_64', 'ubuntu904-amd64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + #InstallTestVm(oSet, 'tst-ubuntu1404', 'Ubuntu', 'ubuntu1404-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfUbuntuNewAmdBug | InstallTestVm.kfReqPae), bird: Is 14.04 one of the 'older ones'? + InstallTestVm(oSet, 'tst-ubuntu1404', 'Ubuntu', 'ubuntu1404-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-ubuntu1404-64','Ubuntu_64', 'ubuntu1404-amd64-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-debian7', 'Debian', 'debian-7.0.0-txs.iso', InstallTestVm.ksSataController, 8, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-debian7-64', 'Debian_64', 'debian-7.0.0-x64-txs.iso', InstallTestVm.ksScsiController, 8, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-vista-64', 'WindowsVista_64', 'vista-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-vista-32', 'WindowsVista', 'vista-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w7-64', 'Windows7_64', 'win7-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-w7-32', 'Windows7', 'win7-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w2k3', 'Windows2003', 'win2k3ent-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w2k', 'Windows2000', 'win2ksp0-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit | InstallTestVm.kfIdeIrqDelay), + InstallTestVm(oSet, 'tst-w2ksp4', 'Windows2000', 'win2ksp4-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit | InstallTestVm.kfIdeIrqDelay), + InstallTestVm(oSet, 'tst-wxp', 'WindowsXP', 'winxppro-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-wxpsp2', 'WindowsXP', 'winxpsp2-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-wxp64', 'WindowsXP_64', 'winxp64-txs.iso', InstallTestVm.ksIdeController, 25, InstallTestVm.kf64Bit), + ## @todo disable paravirt for Windows 8.1 guests as long as it's not fixed in the code + InstallTestVm(oSet, 'tst-w81-32', 'Windows81', 'win81-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit), + InstallTestVm(oSet, 'tst-w81-64', 'Windows81_64', 'win81-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + InstallTestVm(oSet, 'tst-w10-32', 'Windows10', 'win10-x86-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf32Bit | InstallTestVm.kfReqPae), + InstallTestVm(oSet, 'tst-w10-64', 'Windows10_64', 'win10-x64-txs.iso', InstallTestVm.ksSataController, 25, InstallTestVm.kf64Bit), + # pylint: enable=line-too-long + ]); + self.oTestVmSet = oSet; + + + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(''); + reporter.log('tdGuestOsInstTest1 options:'); + reporter.log(' --ioapic, --no-ioapic'); + reporter.log(' Enable or disable the I/O apic.'); + reporter.log(' Default: --ioapic'); + reporter.log(' --pae, --no-pae'); + reporter.log(' Enable or disable PAE support for 32-bit guests.'); + reporter.log(' Default: Guest dependent.'); + reporter.log(' --ram-adjust <MBs>') + reporter.log(' Adjust the VM ram size by the given delta. Both negative and positive'); + reporter.log(' values are accepted.'); + reporter.log(' --set-extradata <key>:value') + reporter.log(' Set VM extra data. This command line option might be used multiple times.') + reporter.log('obsolete:'); + reporter.log(' --nested-paging, --no-nested-paging'); + reporter.log(' --raw-mode'); + reporter.log(' --cpus <# CPUs>'); + reporter.log(' --install-iso <ISO file name>'); + + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + + if False is True: + pass; + elif asArgs[iArg] == '--ioapic': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fIoApic = True; + elif asArgs[iArg] == '--no-ioapic': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fIoApic = False; + elif asArgs[iArg] == '--pae': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fPae = True; + elif asArgs[iArg] == '--no-pae': + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fPae = False; + elif asArgs[iArg] == '--ram-adjust': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.iOptRamAdjust = int(asArgs[iArg]); + elif asArgs[iArg] == '--set-extradata': + iArg = self.requireMoreArgs(1, asArgs, iArg) + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.asExtraData.append(asArgs[iArg]); + + # legacy, to be removed once TM is reconfigured. + elif asArgs[iArg] == '--install-iso': + self.legacyOptions(); + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.fSkip = os.path.basename(oTestVm.sDvdImage) != asArgs[iArg]; + elif asArgs[iArg] == '--cpus': + self.legacyOptions(); + iArg = self.requireMoreArgs(1, asArgs, iArg); + self.oTestVmSet.acCpus = [ int(asArgs[iArg]), ]; + elif asArgs[iArg] == '--raw-mode': + self.legacyOptions(); + self.oTestVmSet.asVirtModes = [ 'raw', ]; + elif asArgs[iArg] == '--nested-paging': + self.legacyOptions(); + self.oTestVmSet.asVirtModes = [ 'hwvirt-np', ]; + elif asArgs[iArg] == '--no-nested-paging': + self.legacyOptions(); + self.oTestVmSet.asVirtModes = [ 'hwvirt', ]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + + return iArg + 1 + + def legacyOptions(self): + """ Enables legacy option mode. """ + if not self.fLegacyOptions: + self.fLegacyOptions = True; + self.oTestVmSet.asVirtModes = [ 'hwvirt', ]; + self.oTestVmSet.acCpus = [ 1, ]; + return True; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self, eNic0AttachType = vboxcon.NetworkAttachmentType_NAT); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + def testOneVmConfig(self, oVM, oTestVm): + """ + Install guest OS and wait for result + """ + + self.logVmInfo(oVM) + reporter.testStart('Installing %s' % (oTestVm.sVmName,)) + + cMsTimeout = 40*60000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = 180 * 60000; # will be adjusted down. + + oSession, _ = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = False, cMsTimeout = cMsTimeout); + if oSession is not None: + # The guest has connected to TXS, so we're done (for now anyways). + reporter.log('Guest reported success') + ## @todo Do save + restore. + + reporter.testDone() + fRc = self.terminateVmBySession(oSession) + return fRc is True + + reporter.error('Installation of %s has failed' % (oTestVm.sVmName,)) + oTestVm.detatchAndDeleteHd(self); # Save space. + reporter.testDone() + return False + +if __name__ == '__main__': + sys.exit(tdGuestOsInstTest1().main(sys.argv)) + diff --git a/src/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py b/src/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py new file mode 100755 index 00000000..cdc2ef59 --- /dev/null +++ b/src/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py @@ -0,0 +1,769 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdGuestOsUnattendedInst1.py $ + +""" +VirtualBox Validation Kit - Guest OS unattended installation 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: 155244 $" + + +# Standard Python imports. +import copy; +import os; +import sys; + + +# 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 testdriver import vbox; +from testdriver import base; +from testdriver import reporter; +from testdriver import vboxcon; +from testdriver import vboxtestvms; +from common import utils; + +# Sub-test driver imports. +sys.path.append(os.path.join(g_ksValidationKitDir, 'tests', 'additions')); +from tdAddGuestCtrl import SubTstDrvAddGuestCtrl; +from tdAddSharedFolders1 import SubTstDrvAddSharedFolders1; + + +class UnattendedVm(vboxtestvms.BaseTestVm): + """ Unattended Installation test VM. """ + + ## @name VM option flags (OR together). + ## @{ + kfUbuntuAvx2Crash = 0x0001; ##< Disables AVX2 as ubuntu 16.04 think it means AVX512 is available and compiz crashes. + kfNoGAs = 0x0002; ##< No guest additions installation possible. + kfKeyFile = 0x0004; ##< ISO requires a .key file containing the product key. + kfNeedCom1 = 0x0008; ##< Need serial port, typically for satifying the windows kernel debugger. + + kfIdeIrqDelay = 0x1000; + kfUbuntuNewAmdBug = 0x2000; + kfNoWin81Paravirt = 0x4000; + kfAvoidNetwork = 0x8000; + ## @} + + ## kfUbuntuAvx2Crash: Extra data that disables AVX2. + kasUbuntuAvx2Crash = [ '/CPUM/IsaExts/AVX2:0', ]; + + ## IRQ delay extra data config for win2k VMs. + kasIdeIrqDelay = [ 'VBoxInternal/Devices/piix3ide/0/Config/IRQDelay:1', ]; + + def __init__(self, oSet, sVmName, sKind, sInstallIso, fFlags = 0): + vboxtestvms.BaseTestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind, + fRandomPvPModeCrap = (fFlags & self.kfNoWin81Paravirt) == 0); + self.sInstallIso = sInstallIso; + self.fInstVmFlags = fFlags; + + # Adjustments over the defaults. + self.iOptRamAdjust = 0; + self.fOptIoApic = None; + self.fOptPae = None; + self.fOptInstallAdditions = False; + self.asOptExtraData = []; + if fFlags & self.kfUbuntuAvx2Crash: + self.asOptExtraData += self.kasUbuntuAvx2Crash; + if fFlags & self.kfIdeIrqDelay: + self.asOptExtraData += self.kasIdeIrqDelay; + if fFlags & self.kfNeedCom1: + self.fCom1RawFile = True; + + def _unattendedConfigure(self, oIUnattended, oTestDrv): # type: (Any, vbox.TestDriver) -> bool + """ + Configures the unattended install object. + + The ISO attribute has been set and detectIsoOS has been done, the rest of the + setup is done here. + + Returns True on success, False w/ errors logged on failure. + """ + + # + # Make it install the TXS. + # + try: oIUnattended.installTestExecService = True; + except: return reporter.errorXcpt(); + try: oIUnattended.validationKitIsoPath = oTestDrv.sVBoxValidationKitIso; + except: return reporter.errorXcpt(); + oTestDrv.processPendingEvents(); + + # + # Avoid using network during unattended install (stalls Debian installs). + # + if self.fInstVmFlags & UnattendedVm.kfAvoidNetwork: + try: oIUnattended.avoidUpdatesOverNetwork = True; + except: return reporter.errorXcpt(); + + # + # Install GAs? + # + if self.fOptInstallAdditions: + if (self.fInstVmFlags & self.kfNoGAs) == 0: + try: oIUnattended.installGuestAdditions = True; + except: return reporter.errorXcpt(); + try: oIUnattended.additionsIsoPath = oTestDrv.getGuestAdditionsIso(); + except: return reporter.errorXcpt(); + oTestDrv.processPendingEvents(); + else: + reporter.log("Warning! Ignoring request to install Guest Additions as kfNoGAs is set!"); + + # + # Product key? + # + if self.fInstVmFlags & UnattendedVm.kfKeyFile: + sKeyFile = ''; + sKey = ''; + try: + sKeyFile = oIUnattended.isoPath + '.key'; + oFile = utils.openNoInherit(sKeyFile); + for sLine in oFile: + sLine = sLine.strip(); + if sLine and not sLine.startswith(';') and not sLine.startswith('#') and not sLine.startswith('//'): + sKey = sLine; + break; + oFile.close(); + except: + return reporter.errorXcpt('sKeyFile=%s' % (sKeyFile,)); + if not sKey: + return reporter.error('No key in keyfile (%s)!' % (sKeyFile,)); + try: oIUnattended.productKey = sKey; + except: return reporter.errorXcpt(); + + return True; + + def _unattendedDoIt(self, oIUnattended, oVM, oTestDrv): # type: (Any, Any, vbox.TestDriver) -> bool + """ + Does the unattended installation preparing, media construction and VM reconfiguration. + + Returns True on success, False w/ errors logged on failure. + """ + + # Associate oVM with the installer: + try: + oIUnattended.machine = oVM; + except: + return reporter.errorXcpt(); + oTestDrv.processPendingEvents(); + + # Prepare and log it: + try: + oIUnattended.prepare(); + except: + return reporter.errorXcpt("IUnattended.prepare failed"); + oTestDrv.processPendingEvents(); + + reporter.log('IUnattended attributes after prepare():'); + self._unattendedLogIt(oIUnattended, oTestDrv); + + # Create media: + try: + oIUnattended.constructMedia(); + except: + return reporter.errorXcpt("IUnattended.constructMedia failed"); + oTestDrv.processPendingEvents(); + + # Reconfigure the VM: + try: + oIUnattended.reconfigureVM(); + except: + return reporter.errorXcpt("IUnattended.reconfigureVM failed"); + oTestDrv.processPendingEvents(); + + return True; + + def _unattendedLogIt(self, oIUnattended, oTestDrv): + """ + Logs the attributes of the unattended installation object. + """ + fRc = True; + asAttribs = ( 'isoPath', 'user', 'password', 'fullUserName', 'productKey', 'additionsIsoPath', 'installGuestAdditions', + 'validationKitIsoPath', 'installTestExecService', 'timeZone', 'locale', 'language', 'country', 'proxy', + 'packageSelectionAdjustments', 'hostname', 'auxiliaryBasePath', 'imageIndex', 'machine', + 'scriptTemplatePath', 'postInstallScriptTemplatePath', 'postInstallCommand', + 'extraInstallKernelParameters', 'detectedOSTypeId', 'detectedOSVersion', 'detectedOSLanguages', + 'detectedOSFlavor', 'detectedOSHints', ); + for sAttrib in asAttribs: + try: + oValue = getattr(oIUnattended, sAttrib); + except: + fRc = reporter.errorXcpt('sAttrib=%s' % sAttrib); + else: + reporter.log('%s: %s' % (sAttrib.rjust(32), oValue,)); + oTestDrv.processPendingEvents(); + return fRc; + + + # + # Overriden methods. + # + + def getResourceSet(self): + asRet = []; + if not os.path.isabs(self.sInstallIso): + asRet.append(self.sInstallIso); + if self.fInstVmFlags & UnattendedVm.kfKeyFile: + asRet.append(self.sInstallIso + '.key'); + return asRet; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + # + # Use HostOnly networking for ubuntu and debian VMs to prevent them from + # downloading updates and doing database updates during installation. + # We want predicable results. + # + if eNic0AttachType is None: + if self.isLinux() \ + and ( 'ubuntu' in self.sKind.lower() + or 'debian' in self.sKind.lower()): + eNic0AttachType = vboxcon.NetworkAttachmentType_HostOnly; + + # Also use it for windows xp to prevent it from ever going online. + if self.sKind in ('WindowsXP','WindowsXP_64',): + eNic0AttachType = vboxcon.NetworkAttachmentType_HostOnly; + + # + # Use host-only networks instead of host-only adapters for trunk builds on Mac OS. + # + if eNic0AttachType == vboxcon.NetworkAttachmentType_HostOnly \ + and utils.getHostOs() == 'darwin' \ + and oTestDrv.fpApiVer >= 7.0: + eNic0AttachType = vboxcon.NetworkAttachmentType_HostOnlyNetwork; + + return vboxtestvms.BaseTestVm._createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage); # pylint: disable=protected-access + + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): + # + # Adjust the ram, I/O APIC and stuff. + # + oSession = oTestDrv.openSession(oVM); + if oSession is None: + return None; + + fRc = True; + + ## Set proper boot order - IUnattended::reconfigureVM does this, doesn't it? + #fRc = fRc and oSession.setBootOrder(1, vboxcon.DeviceType_HardDisk) + #fRc = fRc and oSession.setBootOrder(2, vboxcon.DeviceType_DVD) + + # Adjust memory if requested. + if self.iOptRamAdjust != 0: + try: cMbRam = oSession.o.machine.memorySize; + except: fRc = reporter.errorXcpt(); + else: + fRc = oSession.setRamSize(cMbRam + self.iOptRamAdjust) and fRc; + + # I/O APIC: + if self.fOptIoApic is not None: + fRc = oSession.enableIoApic(self.fOptIoApic) and fRc; + + # I/O APIC: + if self.fOptPae is not None: + fRc = oSession.enablePae(self.fOptPae) and fRc; + + # Set extra data + for sExtraData in self.asOptExtraData: + sKey, sValue = sExtraData.split(':'); + reporter.log('Set extradata: %s => %s' % (sKey, sValue)) + fRc = oSession.setExtraData(sKey, sValue) and fRc; + + # Save the settings. + fRc = fRc and oSession.saveSettings() + fRc = oSession.close() and fRc; + + return oVM if fRc else None; + + def _skipVmTest(self, oTestDrv, oVM): + _ = oVM; + # + # Check for ubuntu installer vs. AMD host CPU. + # + if self.fInstVmFlags & self.kfUbuntuNewAmdBug: + if self.isHostCpuAffectedByUbuntuNewAmdBug(oTestDrv): + return True; + + return vboxtestvms.BaseTestVm._skipVmTest(self, oTestDrv, oVM); # pylint: disable=protected-access + + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Do the standard reconfig in the base class first, it'll figure out + # if we can run the VM as requested. + # + (fRc, oVM) = vboxtestvms.BaseTestVm.getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode); + if fRc is True: + # + # Make sure there is no HD from the previous run attached nor taking + # up storage on the host. + # + fRc = self.recreateRecommendedHdd(oVM, oTestDrv); + if fRc is True: + # + # Set up unattended installation. + # + try: + oIUnattended = oTestDrv.oVBox.createUnattendedInstaller(); + except: + fRc = reporter.errorXcpt(); + if fRc is True: + fRc = self.unattendedDetectOs(oIUnattended, oTestDrv); + if fRc is True: + fRc = self._unattendedConfigure(oIUnattended, oTestDrv); + if fRc is True: + fRc = self._unattendedDoIt(oIUnattended, oVM, oTestDrv); + + # Done. + return (fRc, oVM) + + def isLoggedOntoDesktop(self): + # + # Normally all unattended installations should end up on the desktop. + # An exception is a minimal install, but we currently don't support that. + # + return True; + + def getTestUser(self): + # Default unattended installation user (parent knowns its password). + return 'vboxuser'; + + + # + # Our methods. + # + + def unattendedDetectOs(self, oIUnattended, oTestDrv): # type: (Any, vbox.TestDriver) -> bool + """ + Does the detectIsoOS operation and checks that the detect OSTypeId matches. + + Returns True on success, False w/ errors logged on failure. + """ + + # + # Point the installer at the ISO and do the detection. + # + sInstallIso = self.sInstallIso + if not os.path.isabs(sInstallIso): + sInstallIso = os.path.join(oTestDrv.sResourcePath, sInstallIso); + + try: + oIUnattended.isoPath = sInstallIso; + except: + return reporter.errorXcpt('sInstallIso=%s' % (sInstallIso,)); + + try: + oIUnattended.detectIsoOS(); + except: + if oTestDrv.oVBoxMgr.xcptIsNotEqual(None, oTestDrv.oVBoxMgr.statuses.E_NOTIMPL): + return reporter.errorXcpt('sInstallIso=%s' % (sInstallIso,)); + + # + # Get and log the result. + # + # Note! Current (6.0.97) fails with E_NOTIMPL even if it does some work. + # + try: + sDetectedOSTypeId = oIUnattended.detectedOSTypeId; + sDetectedOSVersion = oIUnattended.detectedOSVersion; + sDetectedOSFlavor = oIUnattended.detectedOSFlavor; + sDetectedOSLanguages = oIUnattended.detectedOSLanguages; + sDetectedOSHints = oIUnattended.detectedOSHints; + except: + return reporter.errorXcpt('sInstallIso=%s' % (sInstallIso,)); + + reporter.log('detectIsoOS result for "%s" (vm %s):' % (sInstallIso, self.sVmName)); + reporter.log(' DetectedOSTypeId: %s' % (sDetectedOSTypeId,)); + reporter.log(' DetectedOSVersion: %s' % (sDetectedOSVersion,)); + reporter.log(' DetectedOSFlavor: %s' % (sDetectedOSFlavor,)); + reporter.log(' DetectedOSLanguages: %s' % (sDetectedOSLanguages,)); + reporter.log(' DetectedOSHints: %s' % (sDetectedOSHints,)); + + # + # Check if the OS type matches. + # + if self.sKind != sDetectedOSTypeId: + return reporter.error('sInstallIso=%s: DetectedOSTypeId is %s, expected %s' + % (sInstallIso, sDetectedOSTypeId, self.sKind)); + + return True; + + +class tdGuestOsInstTest1(vbox.TestDriver): + """ + Unattended Guest OS installation tests using IUnattended. + + Scenario: + - Create a new VM with default settings using IMachine::applyDefaults. + - Setup unattended installation using IUnattended. + - Start the VM and do the installation. + - Wait for TXS to report for service. + - If installing GAs: + - Wait for GAs to report operational runlevel. + - Save & restore state. + - If installing GAs: + - Test guest properties (todo). + - Test guest controls. + - Test shared folders. + """ + + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + self.fLegacyOptions = False; + assert self.fEnableVrdp; # in parent driver. + + # + # Our install test VM set. + # + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, fIgnoreSkippedVm = True); + # pylint: disable=line-too-long + oSet.aoTestVms.extend([ + # + # Windows. The older windows ISOs requires a keyfile (for xp sp3 + # pick a key from the PID.INF file on the ISO). + # + UnattendedVm(oSet, 'tst-xp-32', 'WindowsXP', '6.0/uaisos/en_winxp_pro_x86_build2600_iso.img', UnattendedVm.kfKeyFile), # >=2GiB + UnattendedVm(oSet, 'tst-xpsp2-32', 'WindowsXP', '6.0/uaisos/en_winxp_pro_with_sp2.iso', UnattendedVm.kfKeyFile), # >=2GiB + UnattendedVm(oSet, 'tst-xpsp3-32', 'WindowsXP', '6.0/uaisos/en_windows_xp_professional_with_service_pack_3_x86_cd_x14-80428.iso', UnattendedVm.kfKeyFile), # >=2GiB + UnattendedVm(oSet, 'tst-xp-64', 'WindowsXP_64', '6.0/uaisos/en_win_xp_pro_x64_vl.iso', UnattendedVm.kfKeyFile), # >=3GiB + UnattendedVm(oSet, 'tst-xpsp2-64', 'WindowsXP_64', '6.0/uaisos/en_win_xp_pro_x64_with_sp2_vl_x13-41611.iso', UnattendedVm.kfKeyFile), # >=3GiB + #fixme: UnattendedVm(oSet, 'tst-xpchk-64', 'WindowsXP_64', '6.0/uaisos/en_windows_xp_professional_x64_chk.iso', UnattendedVm.kfKeyFile | UnattendedVm.kfNeedCom1), # >=3GiB + # No key files needed: + UnattendedVm(oSet, 'tst-vista-32', 'WindowsVista', '6.0/uaisos/en_windows_vista_ee_x86_dvd_vl_x13-17271.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-vista-64', 'WindowsVista_64', '6.0/uaisos/en_windows_vista_enterprise_x64_dvd_vl_x13-17316.iso'), # >=8GiB + UnattendedVm(oSet, 'tst-vistasp1-32', 'WindowsVista', '6.0/uaisos/en_windows_vista_enterprise_with_service_pack_1_x86_dvd_x14-55954.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-vistasp1-64', 'WindowsVista_64', '6.0/uaisos/en_windows_vista_enterprise_with_service_pack_1_x64_dvd_x14-55934.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-vistasp2-32', 'WindowsVista', '6.0/uaisos/en_windows_vista_enterprise_sp2_x86_dvd_342329.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-vistasp2-64', 'WindowsVista_64', '6.0/uaisos/en_windows_vista_enterprise_sp2_x64_dvd_342332.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w7-32', 'Windows7', '6.0/uaisos/en_windows_7_enterprise_x86_dvd_x15-70745.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w7-64', 'Windows7_64', '6.0/uaisos/en_windows_7_enterprise_x64_dvd_x15-70749.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w7sp1-32', 'Windows7', '6.0/uaisos/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w7sp1-64', 'Windows7_64', '6.0/uaisos/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso'), # >=8GiB + UnattendedVm(oSet, 'tst-w8-32', 'Windows8', '6.0/uaisos/en_windows_8_enterprise_x86_dvd_917587.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w8-64', 'Windows8_64', '6.0/uaisos/en_windows_8_enterprise_x64_dvd_917522.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w81-32', 'Windows81', '6.0/uaisos/en_windows_8_1_enterprise_x86_dvd_2791510.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-w81-64', 'Windows81_64', '6.0/uaisos/en_windows_8_1_enterprise_x64_dvd_2791088.iso'), # >=8GiB + UnattendedVm(oSet, 'tst-w10-1507-32', 'Windows10', '6.0/uaisos/en_windows_10_pro_10240_x86_dvd.iso'), # >=6GiB + UnattendedVm(oSet, 'tst-w10-1507-64', 'Windows10_64', '6.0/uaisos/en_windows_10_pro_10240_x64_dvd.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w10-1511-32', 'Windows10', '6.0/uaisos/en_windows_10_enterprise_version_1511_updated_feb_2016_x86_dvd_8378870.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1511-64', 'Windows10_64', '6.0/uaisos/en_windows_10_enterprise_version_1511_x64_dvd_7224901.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w10-1607-32', 'Windows10', '6.0/uaisos/en_windows_10_enterprise_version_1607_updated_jul_2016_x86_dvd_9060097.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1607-64', 'Windows10_64', '6.0/uaisos/en_windows_10_enterprise_version_1607_updated_jul_2016_x64_dvd_9054264.iso'), # >=9GiB + UnattendedVm(oSet, 'tst-w10-1703-32', 'Windows10', '6.0/uaisos/en_windows_10_enterprise_version_1703_updated_march_2017_x86_dvd_10188981.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1703-64', 'Windows10_64', '6.0/uaisos/en_windows_10_enterprise_version_1703_updated_march_2017_x64_dvd_10189290.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1709-32', 'Windows10', '6.0/uaisos/en_windows_10_multi-edition_vl_version_1709_updated_sept_2017_x86_dvd_100090759.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1709-64', 'Windows10_64', '6.0/uaisos/en_windows_10_multi-edition_vl_version_1709_updated_sept_2017_x64_dvd_100090741.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1803-32', 'Windows10', '6.0/uaisos/en_windows_10_business_editions_version_1803_updated_march_2018_x86_dvd_12063341.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1803-64', 'Windows10_64', '6.0/uaisos/en_windows_10_business_editions_version_1803_updated_march_2018_x64_dvd_12063333.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1809-32', 'Windows10', '6.0/uaisos/en_windows_10_business_edition_version_1809_updated_sept_2018_x86_dvd_2f92403b.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1809-64', 'Windows10_64', '6.0/uaisos/en_windows_10_business_edition_version_1809_updated_sept_2018_x64_dvd_f0b7dc68.iso'), # >=10GiB + UnattendedVm(oSet, 'tst-w10-1903-32', 'Windows10', '6.0/uaisos/en_windows_10_business_editions_version_1903_x86_dvd_ca4f0f49.iso'), # >=7GiB + UnattendedVm(oSet, 'tst-w10-1903-64', 'Windows10_64', '6.0/uaisos/en_windows_10_business_editions_version_1903_x64_dvd_37200948.iso'), # >=10GiB + # + # Ubuntu + # + ## @todo 15.10 fails with grub install error. + #UnattendedVm(oSet, 'tst-ubuntu-15.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-15.10-desktop-amd64.iso'), + UnattendedVm(oSet, 'tst-ubuntu-16.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04-desktop-amd64.iso', # ~5GiB + UnattendedVm.kfUbuntuAvx2Crash), + UnattendedVm(oSet, 'tst-ubuntu-16.04-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.1-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.1-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.1-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.1-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.2-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.2-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.2-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.2-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.3-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.3-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.3-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.3-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.4-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.4-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.4-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.4-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.5-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.5-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.5-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.5-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.6-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.04.6-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.04.6-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.04.6-desktop-i386.iso'), # >=4.5GiB + UnattendedVm(oSet, 'tst-ubuntu-16.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-16.10-desktop-amd64.iso'), # >=5.5GiB + ## @todo 16.10-32 doesn't ask for an IP, so it always fails. + #UnattendedVm(oSet, 'tst-ubuntu-16.10-32', 'Ubuntu', '6.0/uaisos/ubuntu-16.10-desktop-i386.iso'), # >=5.5GiB? + UnattendedVm(oSet, 'tst-ubuntu-17.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-17.04-desktop-amd64.iso'), # >=5GiB + UnattendedVm(oSet, 'tst-ubuntu-17.04-32', 'Ubuntu', '6.0/uaisos/ubuntu-17.04-desktop-i386.iso'), # >=4.5GiB + ## @todo ubuntu 17.10, 18.04 & 18.10 do not work. They misses all the the build tools (make, gcc, perl, ++) + ## and has signed kmods: + UnattendedVm(oSet, 'tst-ubuntu-17.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-17.10-desktop-amd64.iso', # >=4Gib + UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-ubuntu-18.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-18.04-desktop-amd64.iso', # >=6GiB + UnattendedVm.kfNoGAs), + # 18.10 hangs reading install DVD during "starting partitioner..." + #UnattendedVm(oSet, 'tst-ubuntu-18.10-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-18.10-desktop-amd64.iso', + # UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-ubuntu-19.04-64', 'Ubuntu_64', '6.0/uaisos/ubuntu-19.04-desktop-amd64.iso', # >=6GiB + UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-debian-9.3-64', 'Debian_64', '6.0/uaisos/debian-9.3.0-amd64-DVD-1.iso', # >=6GiB? + UnattendedVm.kfAvoidNetwork | UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-debian-9.4-64', 'Debian_64', '6.0/uaisos/debian-9.4.0-amd64-DVD-1.iso', # >=6GiB? + UnattendedVm.kfAvoidNetwork | UnattendedVm.kfNoGAs), + UnattendedVm(oSet, 'tst-debian-10.0-64', 'Debian_64', '6.0/uaisos/debian-10.0.0-amd64-DVD-1.iso', # >=6GiB? + UnattendedVm.kfAvoidNetwork), + # + # OS/2. + # + UnattendedVm(oSet, 'tst-acp2', 'OS2Warp45', '7.0/uaisos/acp2_us_cd2.iso'), # ~400MiB + ## @todo mcp2 too? + ]); + # pylint: enable=line-too-long + self.oTestVmSet = oSet; + + # For option parsing: + self.aoSelectedVms = oSet.aoTestVms # type: list(UnattendedVm) + + # Number of VMs to test in parallel: + self.cInParallel = 1; + + # Whether to do the save-and-restore test. + self.fTestSaveAndRestore = True; + + # + # Sub-test drivers. + # + self.addSubTestDriver(SubTstDrvAddSharedFolders1(self)); + self.addSubTestDriver(SubTstDrvAddGuestCtrl(self)); + + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(''); + reporter.log('tdGuestOsUnattendedInst1 options:'); + reporter.log(' --parallel <num>'); + reporter.log(' Number of VMs to test in parallel.'); + reporter.log(' Default: 1'); + reporter.log(''); + reporter.log(' Options for working on selected test VMs:'); + reporter.log(' --select <vm1[:vm2[:..]]>'); + reporter.log(' Selects a test VM for the following configuration alterations.'); + reporter.log(' Default: All possible test VMs'); + reporter.log(' --copy <old-vm>=<new-vm>'); + reporter.log(' Creates and selects <new-vm> as a copy of <old-vm>.'); + reporter.log(' --guest-type <guest-os-type>'); + reporter.log(' Sets the guest-os type of the currently selected test VM.'); + reporter.log(' --install-iso <ISO file name>'); + reporter.log(' Sets ISO image to use for the selected test VM.'); + reporter.log(' --ram-adjust <MBs>'); + reporter.log(' Adjust the VM ram size by the given delta. Both negative and positive'); + reporter.log(' values are accepted.'); + reporter.log(' --max-cpus <# CPUs>'); + reporter.log(' Sets the maximum number of guest CPUs for the selected VM.'); + reporter.log(' --set-extradata <key>:value'); + reporter.log(' Set VM extra data for the selected VM. Can be repeated.'); + reporter.log(' --ioapic, --no-ioapic'); + reporter.log(' Enable or disable the I/O apic for the selected VM.'); + reporter.log(' --pae, --no-pae'); + reporter.log(' Enable or disable PAE support (32-bit guests only) for the selected VM.'); + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + + if asArgs[iArg] == '--parallel': + iArg = self.requireMoreArgs(1, asArgs, iArg); + self.cInParallel = int(asArgs[iArg]); + if self.cInParallel <= 0: + self.cInParallel = 1; + elif asArgs[iArg] == '--select': + iArg = self.requireMoreArgs(1, asArgs, iArg); + self.aoSelectedVms = []; + for sTestVm in asArgs[iArg].split(':'): + oTestVm = self.oTestVmSet.findTestVmByName(sTestVm); + if not oTestVm: + raise base.InvalidOption('Unknown test VM: %s' % (sTestVm,)); + self.aoSelectedVms.append(oTestVm); + elif asArgs[iArg] == '--copy': + iArg = self.requireMoreArgs(1, asArgs, iArg); + asNames = asArgs[iArg].split('='); + if len(asNames) != 2 or not asNames[0] or not asNames[1]: + raise base.InvalidOption('The --copy option expects value on the form "old=new": %s' % (asArgs[iArg],)); + oOldTestVm = self.oTestVmSet.findTestVmByName(asNames[0]); + if not oOldTestVm: + raise base.InvalidOption('Unknown test VM: %s' % (asNames[0],)); + oNewTestVm = copy.deepcopy(oOldTestVm); + oNewTestVm.sVmName = asNames[1]; + self.oTestVmSet.aoTestVms.append(oNewTestVm); + self.aoSelectedVms = [oNewTestVm]; + elif asArgs[iArg] == '--guest-type': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.sKind = asArgs[iArg]; + elif asArgs[iArg] == '--install-iso': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.sInstallIso = asArgs[iArg]; + elif asArgs[iArg] == '--ram-adjust': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.iOptRamAdjust = int(asArgs[iArg]); + elif asArgs[iArg] == '--max-cpus': + iArg = self.requireMoreArgs(1, asArgs, iArg); + for oTestVm in self.aoSelectedVms: + oTestVm.iOptMaxCpus = int(asArgs[iArg]); + elif asArgs[iArg] == '--set-extradata': + iArg = self.requireMoreArgs(1, asArgs, iArg) + sExtraData = asArgs[iArg]; + try: _, _ = sExtraData.split(':'); + except: raise base.InvalidOption('Invalid extradata specified: %s' % (sExtraData, )); + for oTestVm in self.aoSelectedVms: + oTestVm.asOptExtraData.append(sExtraData); + elif asArgs[iArg] == '--ioapic': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptIoApic = True; + elif asArgs[iArg] == '--no-ioapic': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptIoApic = False; + elif asArgs[iArg] == '--pae': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptPae = True; + elif asArgs[iArg] == '--no-pae': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptPae = False; + elif asArgs[iArg] == '--install-additions': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptInstallAdditions = True; + elif asArgs[iArg] == '--no-install-additions': + for oTestVm in self.aoSelectedVms: + oTestVm.fOptInstallAdditions = False; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + def testOneVmConfig(self, oVM, oTestVm): # type: (Any, UnattendedVm) -> bool + """ + Install guest OS and wait for result + """ + + self.logVmInfo(oVM) + reporter.testStart('Installing %s%s' % (oTestVm.sVmName, ' with GAs' if oTestVm.fOptInstallAdditions else '')) + + cMsTimeout = 40*60000; + if not reporter.isLocal(): ## @todo need to figure a better way of handling timeouts on the testboxes ... + cMsTimeout = 180 * 60000; # will be adjusted down. + + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = False, cMsTimeout = cMsTimeout); + #oSession = self.startVmByName(oTestVm.sVmName); # (for quickly testing waitForGAs) + if oSession is not None: + # The guest has connected to TXS. + reporter.log('Guest reported success via TXS.'); + reporter.testDone(); + + fRc = True; + # Kudge: GAs doesn't come up correctly, so we have to reboot the guest first: + # Looks like VBoxService isn't there. + if oTestVm.fOptInstallAdditions: + reporter.testStart('Rebooting'); + fRc, oTxsSession = self.txsRebootAndReconnectViaTcp(oSession, oTxsSession); + reporter.testDone(); + + # If we're installing GAs, wait for them to come online: + if oTestVm.fOptInstallAdditions and fRc is True: + reporter.testStart('Guest additions'); + aenmRunLevels = [vboxcon.AdditionsRunLevelType_Userland,]; + if oTestVm.isLoggedOntoDesktop(): + aenmRunLevels.append(vboxcon.AdditionsRunLevelType_Desktop); + fRc = self.waitForGAs(oSession, cMsTimeout = cMsTimeout / 2, aenmWaitForRunLevels = aenmRunLevels, + aenmWaitForActive = (vboxcon.AdditionsFacilityType_VBoxGuestDriver, + vboxcon.AdditionsFacilityType_VBoxService,)); + reporter.testDone(); + + # Now do a save & restore test: + if fRc is True and self.fTestSaveAndRestore: + fRc, oSession, oTxsSession = self.testSaveAndRestore(oSession, oTxsSession, oTestVm); + + # Test GAs if requested: + if oTestVm.fOptInstallAdditions and fRc is True: + for oSubTstDrv in self.aoSubTstDrvs: + if oSubTstDrv.fEnabled: + reporter.testStart(oSubTstDrv.sTestName); + fRc2, oTxsSession = oSubTstDrv.testIt(oTestVm, oSession, oTxsSession); + reporter.testDone(fRc2 is None); + if fRc2 is False: + fRc = False; + + if oSession is not None: + fRc = self.terminateVmBySession(oSession) and fRc; + return fRc is True + + reporter.error('Installation of %s has failed' % (oTestVm.sVmName,)) + #oTestVm.detatchAndDeleteHd(self); # Save space. + reporter.testDone() + return False + + def testSaveAndRestore(self, oSession, oTxsSession, oTestVm): + """ + Tests saving and restoring the VM. + """ + _ = oTestVm; + reporter.testStart('Save'); + ## @todo + reporter.testDone(); + reporter.testStart('Restore'); + ## @todo + reporter.testDone(); + return (True, oSession, oTxsSession); + +if __name__ == '__main__': + sys.exit(tdGuestOsInstTest1().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/network/Makefile.kmk b/src/VBox/ValidationKit/tests/network/Makefile.kmk new file mode 100644 index 00000000..c77a0d87 --- /dev/null +++ b/src/VBox/ValidationKit/tests/network/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Network 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 += ValidationKitTestsNetwork +ValidationKitTestsNetwork_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsNetwork_INST = $(INST_VALIDATIONKIT)tests/network/ +ValidationKitTestsNetwork_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdNetBenchmark1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsNetwork_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/network/tdNetBenchmark1.py b/src/VBox/ValidationKit/tests/network/tdNetBenchmark1.py new file mode 100755 index 00000000..c1dc9602 --- /dev/null +++ b/src/VBox/ValidationKit/tests/network/tdNetBenchmark1.py @@ -0,0 +1,633 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdNetBenchmark1.py $ + +""" +VirtualBox Validation Kit - Networking benchmark #1. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import socket +import sys; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdNetBenchmark1(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Networking benchmark #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.sLocalName = socket.getfqdn(); + self.sLocalIP = None + self.sRemoteName = None; + self.sRemoteIP = None; + self.sGuestName = None; + self.sGuestIP = None; + self.oGuestToGuestVM = None; + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.asTestVMsDef = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + self.asNicTypesDef = ['E1000', 'PCNet', 'Virtio',]; + self.asNicTypes = self.asNicTypesDef; + self.sNicAttachmentDef = 'bridged'; + self.sNicAttachment = self.sNicAttachmentDef; + self.asSetupsDef = ['g2h', 'g2r', 'g2g',]; + self.asSetups = self.asSetupsDef; + self.cSecsRunDef = 30; + self.cSecsRun = self.cSecsRunDef; + self.asTestsDef = ['tcp-latency', 'tcp-throughput', 'udp-latency', 'udp-throughput', 'tbench']; + self.asTests = self.asTestsDef + self.acbLatencyPktsDef = [32, 1024, 4096, 8192, 65536,]; + self.acbLatencyPkts = self.acbLatencyPktsDef + self.acbThroughputPktsDef = [8192, 65536]; + self.acbThroughputPkts = self.acbThroughputPktsDef + + try: self.sLocalName = socket.gethostbyname(self.sLocalName); + except: pass; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdNetBenchmark1 Options:'); + reporter.log(' --remote-host <hostname|address>'); + reporter.log(' --local-host <hostname|address>'); + reporter.log(' --guest-host <hostname|address>'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --nic-types <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asNicTypes))); + reporter.log(' --nic-attachment <bridged|nat>'); + reporter.log(' Default: %s' % (self.sNicAttachmentDef)); + reporter.log(' --setups <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asSetupsDef))); + reporter.log(' --secs-per-run <seconds>'); + reporter.log(' Default: %s' % (self.cSecsRunDef)); + reporter.log(' --tests <s1[:s2[:]]>'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --latency-sizes <size1[:size2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(str(cb) for cb in self.acbLatencyPktsDef))); # pychecker bug? + reporter.log(' --throughput-sizes <size1[:size2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(str(cb) for cb in self.acbThroughputPktsDef))); # pychecker bug? + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --quick'); + reporter.log(' Shorthand for: --virt-modes hwvirt --cpu-counts 1 --secs-per-run 5 --latency-sizes 32'); + reporter.log(' --throughput-sizes 8192 --test-vms tst-rhel5:tst-win2k3ent:tst-sol10'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--remote-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--remote-host" takes an IP address or a hostname'); + self.sRemoteName = asArgs[iArg]; + elif asArgs[iArg] == '--local-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--local-host" takes an IP address or a hostname'); + self.sLocalName = asArgs[iArg]; + elif asArgs[iArg] == '--guest-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--guest-host" takes an IP address or a hostname'); + self.sGuestName = asArgs[iArg]; + elif asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--nic-types': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--nic-types" takes a colon separated list of NIC types'); + self.asNicTypes = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--nic-attachment': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--nic-attachment" takes an argument'); + self.sNicAttachment = asArgs[iArg]; + if self.sNicAttachment not in ('bridged', 'nat'): + raise base.InvalidOption('The "--nic-attachment" value "%s" is not supported. Valid values are: bridged, nat' \ + % (self.sNicAttachment)); + elif asArgs[iArg] == '--setups': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--setups" takes a colon separated list of setups'); + self.asSetups = asArgs[iArg].split(':'); + for s in self.asSetups: + if s not in self.asSetupsDef: + raise base.InvalidOption('The "--setups" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asSetupsDef))); + elif asArgs[iArg] == '--secs-per-run': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--secs-per-run" takes second count'); + try: self.cSecsRun = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--secs-per-run" value "%s" is not an integer' % (self.cSecsRun,)); + if self.cSecsRun <= 0: + raise base.InvalidOption('The "--secs-per-run" value "%s" is zero or negative.' % (self.cSecsRun,)); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of tests'); + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + raise base.InvalidOption('The "--tests" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestsDef))); + elif asArgs[iArg] == '--latency-sizes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--latency-sizes" takes a colon separated list of sizes'); + self.acbLatencyPkts = []; + for s in asArgs[iArg].split(':'): + try: cb = int(s); + except: raise base.InvalidOption('The "--latency-sizes" value "%s" is not an integer' % (s,)); + if cb <= 0: raise base.InvalidOption('The "--latency-sizes" value "%s" is zero or negative' % (s,)); + self.acbLatencyPkts.append(cb); + elif asArgs[iArg] == '--throughput-sizes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--throughput-sizes" takes a colon separated list of sizes'); + self.acbThroughputPkts = []; + for s in asArgs[iArg].split(':'): + try: cb = int(s); + except: raise base.InvalidOption('The "--throughput-sizes" value "%s" is not an integer' % (s,)); + if cb <= 0: raise base.InvalidOption('The "--throughput-sizes" value "%s" is zero or negative' % (s,)); + self.acbThroughputPkts.append(cb); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--quick': + self.cSecsRun = 5; + self.asVirtModes = ['hwvirt',]; + self.acCpus = [1,]; + self.acbLatencyPkts = [32,]; + self.acbThroughputPkts = [8192,]; + self.asTestVMs = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10',]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + # Resolve any names we've been given. + self.sLocalIP = base.tryGetHostByName(self.sLocalName); + self.sRemoteIP = base.tryGetHostByName(self.sRemoteName); + self.sGuestIP = base.tryGetHostByName(self.sGuestName); + + reporter.log('Local IP : %s' % (self.sLocalIP)); + reporter.log('Remote IP: %s' % (self.sRemoteIP)); + if self.sGuestIP is None: + reporter.log('Guest IP : use tst-guest2guest'); + else: + reporter.log('Guest IP : %s' % (self.sGuestIP)); + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-rhel5' in self.asTestVMs or 'g2g' in self.asSetups: + self.asRsrcs.append('3.0/tcp/rhel5.vdi'); + if 'tst-rhel5-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/rhel5-64.vdi'); + if 'tst-sles11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11.vdi'); + if 'tst-sles11-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11-64.vdi'); + if 'tst-oel' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel.vdi'); + if 'tst-oel-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel-64.vdi'); + if 'tst-win2k3ent' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-acpi.vdi'); + if 'tst-win2k3ent-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-64.vdi'); + if 'tst-win2k8' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k8.vdi'); + if 'tst-sol10' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris10.vdi'); + if 'tst-sol11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris11.vdi'); + return self.asRsrcs; + + def actionConfig(self): + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxValidationKit.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxTestSuite.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuite.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKit.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuite.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/testsuite/VBoxTestSuite.iso'; + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # Guest to Guest VM. + if self.sGuestName is None and 'g2g' in self.asSetups: + oVM = self.createTestVM('tst-guest2guest', 0, '3.0/tcp/rhel5.vdi', sKind = 'RedHat', fIoApic = True, \ + eNic0Type = vboxcon.NetworkAdapterType_I82545EM, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged, \ + fVirtEx = True, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + self.oGuestToGuestVM = oVM; + + # + # Configure the VMs we're going to use. + # + eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + if self.sNicAttachment == 'nat': + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + + # Linux VMs + if 'tst-rhel5' in self.asTestVMs: + oVM = self.createTestVM('tst-rhel5', 1, '3.0/tcp/rhel5.vdi', sKind = 'RedHat', fIoApic = True, \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-rhel5-64' in self.asTestVMs: + oVM = self.createTestVM('tst-rhel5-64', 1, '3.0/tcp/rhel5-64.vdi', sKind = 'RedHat_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11' in self.asTestVMs: + oVM = self.createTestVM('tst-sles11', 1, '3.0/tcp/sles11.vdi', sKind = 'OpenSUSE', fIoApic = True, \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11-64' in self.asTestVMs: + oVM = self.createTestVM('tst-sles11-64', 1, '3.0/tcp/sles11-64.vdi', sKind = 'OpenSUSE_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel' in self.asTestVMs: + oVM = self.createTestVM('tst-oel', 1, '3.0/tcp/oel.vdi', sKind = 'Oracle', fIoApic = True, \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel-64' in self.asTestVMs: + oVM = self.createTestVM('tst-oel-64', 1, '3.0/tcp/oel-64.vdi', sKind = 'Oracle_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Windows VMs + if 'tst-win2k3ent' in self.asTestVMs: + oVM = self.createTestVM('tst-win2k3ent', 1, '3.0/tcp/win2k3ent-acpi.vdi', sKind = 'Windows2003', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k3ent-64' in self.asTestVMs: + oVM = self.createTestVM('tst-win2k3ent-64', 1, '3.0/tcp/win2k3ent-64.vdi', sKind = 'Windows2003_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k8' in self.asTestVMs: + oVM = self.createTestVM('tst-win2k8', 1, '3.0/tcp/win2k8.vdi', sKind = 'Windows2008_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Solaris VMs + if 'tst-sol10' in self.asTestVMs: + oVM = self.createTestVM('tst-sol10', 1, '3.0/tcp/solaris10.vdi', sKind = 'Solaris_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sol11' in self.asTestVMs: + oVM = self.createTestVM('tst-sol11', 1, '3.0/tcp/os2009-11.vdi', sKind = 'Solaris_64', \ + eNic0AttachType = eNic0AttachType, sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + + # + # Test execution helpers. + # + + def test1RunTestProgs(self, oTxsSession, fRc, sTestName, sAddr): + """ + Runs all the test programs against one 'server' machine. + """ + reporter.testStart(sTestName); + + reporter.testStart('TCP latency'); + if fRc and 'tcp-latency' in self.asTests and sAddr is not None: + for cbPkt in self.acbLatencyPkts: + fRc = self.txsRunTest(oTxsSession, '%u bytes' % (cbPkt), self.cSecsRun * 1000 * 4, + '${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}', + ('NetPerf', '--client', sAddr, '--interval', self.cSecsRun, '--len', cbPkt, + '--mode', 'latency')); + if not fRc: + break; + reporter.testDone(); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('TCP throughput'); + if fRc and 'tcp-throughput' in self.asTests and sAddr is not None: + for cbPkt in self.acbThroughputPkts: + fRc = self.txsRunTest(oTxsSession, '%u bytes' % (cbPkt), self.cSecsRun * 2 * 1000 * 4, + '${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}', + ('NetPerf', '--client', sAddr, '--interval', self.cSecsRun, '--len', cbPkt, + '--mode', 'throughput')); + if not fRc: + break; + reporter.testDone(); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('UDP latency'); + if fRc and 'udp-latency' in self.asTests and sAddr is not None: + ## @todo Netperf w/UDP. + reporter.testDone(fSkipped = True); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('UDP throughput'); + if fRc and 'udp-throughput' in self.asTests and sAddr is not None: + ## @todo Netperf w/UDP. + reporter.testDone(fSkipped = True); + else: + reporter.testDone(fSkipped = True); + + reporter.testStart('tbench'); + if fRc and 'tbench' in self.asTests and sAddr is not None: + ## @todo tbench. + reporter.testDone(fSkipped = True); + else: + reporter.testDone(fSkipped = True); + + reporter.testDone(not fRc); + return fRc; + + def test1OneCfg(self, sVmName, eNicType, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.setNicType(eNicType); + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(sVmName, fCdWait = True); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + # Benchmark #1 - guest <-> host. + if 'g2h' in self.asSetups: + self.test1RunTestProgs(oTxsSession, fRc, 'guest <-> host', self.sLocalIP); + + # Benchmark #2 - guest <-> host. + if 'g2r' in self.asSetups: + self.test1RunTestProgs(oTxsSession, fRc, 'guest <-> remote', self.sRemoteIP); + + # Benchmark #3 - guest <-> guest. + if 'g2g' in self.asSetups: + self.test1RunTestProgs(oTxsSession, fRc, 'guest <-> guest', self.sGuestIP); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + else: + fRc = False; + return fRc; + + def test1OneVM(self, sVmName, asSkipNicTypes = (), asSupVirtModes = None, rSupCpus = range(1, 256)): + """ + Runs one VM thru the various configurations. + """ + if asSupVirtModes is None: + asSupVirtModes = self.asVirtModes; + + reporter.testStart(sVmName); + fRc = True; + for sNicType in self.asNicTypes: + if sNicType in asSkipNicTypes: + continue; + reporter.testStart(sNicType); + + if sNicType == 'E1000': + eNicType = vboxcon.NetworkAdapterType_I82545EM; + elif sNicType == 'PCNet': + eNicType = vboxcon.NetworkAdapterType_Am79C973; + elif sNicType == 'Virtio': + eNicType = vboxcon.NetworkAdapterType_Virtio; + else: + eNicType = None; + + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + if cCpus not in rSupCpus: + continue; + if sVirtMode not in asSupVirtModes: + continue; + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + fRc = self.test1OneCfg(sVmName, eNicType, cCpus, fHwVirt, fNestedPaging) and fRc and True; # pychecker hack. + + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + return fRc; + + def test1(self): + """ + Executes test #1. + """ + + # Start the VM for the guest to guest testing, if required. + fRc = True; + if 'g2g' in self.asSetups and self.sGuestName is None: + self.oGuestToGuestSess, self.oGuestToGuestTxs = self.startVmAndConnectToTxsViaTcp('tst-guest2guest', fCdWait = True); + if self.oGuestToGuestSess is None: + return False; + self.sGuestIP = self.oGuestToGuestSess.getPrimaryIp(); + reporter.log('tst-guest2guest IP: %s' % (self.sGuestIP)); + + # Start the test servers on it. + fRc = self.oGuestToGuestTxs.syncExec('${CDROM}/${OS/ARCH}/NetPerf${EXESUFF}', + ('NetPerf', '--server', '--daemonize'), fWithTestPipe=False); + + # Loop thru the test VMs. + if fRc: + for sVM in self.asTestVMs: + # figure args. + asSkipNicTypes = []; + if sVM not in ('tst-sles11', 'tst-sles11-64'): + asSkipNicTypes.append('Virtio'); + if sVM in ('tst-sol11', 'tst-sol10'): + asSkipNicTypes.append('PCNet'); + asSupVirtModes = None; + if sVM in ('tst-sol11', 'tst-sol10'): # 64-bit only + asSupVirtModes = ('hwvirt', 'hwvirt-np',); + + # run test on the VM. + if not self.test1OneVM(sVM, asSkipNicTypes, asSupVirtModes): + fRc = False; + + # Kill the guest to guest VM and clean up the state. + if self.oGuestToGuestSess is not None: + self.terminateVmBySession(self.oGuestToGuestSess); + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.sGuestIP = None; + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdNetBenchmark1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/selftests/Makefile.kmk b/src/VBox/ValidationKit/tests/selftests/Makefile.kmk new file mode 100644 index 00000000..946f10b8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/Makefile.kmk @@ -0,0 +1,54 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Testsuite & TestManager 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 += ValidationKitTestsSelfTests +ValidationKitTestsSelfTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsSelfTests_INST = $(INST_VALIDATIONKIT)tests/selftests/ +ValidationKitTestsSelfTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdSelfTest1.py \ + $(PATH_SUB_CURRENT)/tdSelfTest2.py \ + $(PATH_SUB_CURRENT)/tdSelfTest3.py \ + $(PATH_SUB_CURRENT)/tdSelfTest4.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSelfTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest1.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest1.py new file mode 100755 index 00000000..3509f3e6 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest1.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest1.py $ + +""" +Test Manager Self Test - Dummy Test Driver. +""" + +from __future__ import print_function; + +__copyright__ = \ +""" +Copyright (C) 2012-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: 155244 $" + + +import sys; +import os; + +print('dummydriver.py: hello world!'); +print('dummydriver.py: args: %s' % (sys.argv,)); + +print('dummydriver.py: environment:'); +for sVar in sorted(os.environ.keys()): # pylint: disable=consider-iterating-dictionary + print('%s=%s' % (sVar, os.environ[sVar])); + +if sys.argv[-1] in [ 'all', 'execute' ]: + + import time; + + for i in range(10, 1, -1): + print('dummydriver.py: %u...', i); + sys.stdout.flush(); + time.sleep(1); + print('dummydriver.py: ...0! done'); + +sys.exit(0); + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest2.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest2.py new file mode 100755 index 00000000..6c0e12b8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest2.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest2.py $ + +""" +Test Manager / Suite Self Test #2 - Everything should succeed. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 reporter; +from testdriver.base import TestDriverBase; + + +class tdSelfTest2(TestDriverBase): + """ + Test Manager / Suite Self Test #2 - Everything should succeed. + """ + + def __init__(self): + TestDriverBase.__init__(self); + + + def actionExecute(self): + reporter.testStart('reporter.testXXXX API'); + reporter.testValue('value-name1', 123456789, 'ms'); + + reporter.testStart('subtest'); + reporter.testValue('value-name2', 11223344, 'times'); + reporter.testDone(); + + reporter.testStart('subtest2'); + reporter.testValue('value-name3', 39, 'sec'); + reporter.testValue('value-name4', 42, 'ns'); + reporter.testDone(); + + reporter.testStart('subtest3'); + reporter.testDone(fSkipped = True); + + # No spaces in XML. + reporter.testStart('subtest4'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write('<?xml version="1.0" encoding="UTF-8" ?>'); + oSubXmlFile.write('<Test timestamp="%s" name="foobar1">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="sub1">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Passed timestamp="%s"/>' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('</Test>'); + oSubXmlFile.write('<End timestamp="%s" errors="0"/>' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('</Test>'); + oSubXmlFile.close(); + oSubXmlFile = None; + reporter.testDone(); + + # Spaces + funny line endings. + reporter.testStart('subtest5'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write('<?xml version="1.0" encoding="UTF-8" ?>\r\n'); + oSubXmlFile.write('<Test timestamp="%s" name="foobar2">\n\n\t\n\r\n' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="sub2">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write(' <Passed timestamp="%s"/>\n' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write(' </Test>\n'); + oSubXmlFile.write(' <End timestamp="%s" errors="0"/>\r' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('</Test>'); + oSubXmlFile.close(); + reporter.testDone(); + + # A few long log times for WUI log testing. + reporter.log('long line: asdfasdfljkasdlfkjasldkfjlaksdfjl asdlfkjasdlkfjalskdfjlaksdjfa falkjaldkjfalskdjflaksdjf ' \ + 'lajksdflkjasdlfkjalsdfj asldfkjlaskdjflaksdjflaksdjflkj asdlfkjalsdkfjalsdkjflaksdj fasdlfkj ' \ + 'asdlkfj aljkasdflkj alkjdsf lakjsdf'); + reporter.log('long line: asdfasdfljkasdlfkjasldkfjlaksdfjl asdlfkjasdlkfjalskdfjlaksdjfa falkjaldkjfalskdjflaksdjf ' \ + 'lajksdflkjasdlfkjalsdfj asldfkjlaskdjflaksdjflaksdjflkj asdlfkjalsdkfjalsdkjflaksdj fasdlfkj ' \ + 'asdlkfj aljkasdflkj alkjdsf lakjsdf'); + reporter.log('long line: asdfasdfljkasdlfkjasldkfjlaksdfjl asdlfkjasdlkfjalskdfjlaksdjfa falkjaldkjfalskdjflaksdjf ' \ + 'lajksdflkjasdlfkjalsdfj asldfkjlaskdjflaksdjflaksdjflkj asdlfkjalsdkfjalsdkjflaksdj fasdlfkj ' \ + 'asdlkfj aljkasdflkj alkjdsf lakjsdf'); + + # Upload a file. + reporter.addLogFile(__file__, sKind = 'log/release/vm'); + + reporter.testDone(); + return True; + + +if __name__ == '__main__': + sys.exit(tdSelfTest2().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest3.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest3.py new file mode 100755 index 00000000..1044dff0 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest3.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest3.py $ + +""" +Test Manager / Suite Self Test #3 - Bad XML input and other Failures. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 reporter; +from testdriver.base import TestDriverBase; + + +class tdSelfTest3(TestDriverBase): + """ + Test Manager / Suite Self Test #3 - Bad XML input and other Failures. + """ + + def __init__(self): + TestDriverBase.__init__(self); + + + def actionExecute(self): + + # Testing PushHint/PopHint. + reporter.testStart('Negative XML #1'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write('<Test timestamp="%s" name="foobar3">\n\n\t\n\r\n' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="sub3">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.write('<Test timestamp="%s" name="subsub1">' % (utils.getIsoTimestamp(),)); + oSubXmlFile.close(); + reporter.testDone(); + + # Missing end, like we had with IRPT at one time. + reporter.testStart('Negative XML #2 (IPRT)'); + oSubXmlFile = reporter.FileWrapperTestPipe(); + oSubXmlFile.write(""" +<?xml version="1.0" encoding="UTF-8" ?> +<Test timestamp="2013-05-29T08:59:05.930602000Z" name="tstRTGetOpt"> + <Test timestamp="2013-05-29T08:59:05.930656000Z" name="Basics"> + <Passed timestamp="2013-05-29T08:59:05.930756000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.930995000Z" name="RTGetOpt - IPv4"> + <Passed timestamp="2013-05-29T08:59:05.931036000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931161000Z" name="RTGetOpt - MAC Address"> + <Passed timestamp="2013-05-29T08:59:05.931194000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931313000Z" name="RTGetOpt - Option w/ Index"> + <Passed timestamp="2013-05-29T08:59:05.931357000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931475000Z" name="RTGetOptFetchValue"> + <Passed timestamp="2013-05-29T08:59:05.931516000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931640000Z" name="RTGetOpt - bool on/off"> + <Passed timestamp="2013-05-29T08:59:05.931687000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931807000Z" name="Standard options"> + <Passed timestamp="2013-05-29T08:59:05.931843000Z"/> + </Test> + <Test timestamp="2013-05-29T08:59:05.931963000Z" name="Options first"> + <Passed timestamp="2013-05-29T08:59:05.932035000Z"/> + </Test> +"""); + oSubXmlFile.close(); + oSubXmlFile = None; + reporter.testDone(); + + # The use of testFailure. + reporter.testStart('Using testFailure()'); + reporter.testValue('value-name3', 12345678, 'times'); + reporter.testFailure('failure detail message'); + reporter.testDone(); + + return True; + + +if __name__ == '__main__': + sys.exit(tdSelfTest3().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/selftests/tdSelfTest4.py b/src/VBox/ValidationKit/tests/selftests/tdSelfTest4.py new file mode 100755 index 00000000..c1eb60b6 --- /dev/null +++ b/src/VBox/ValidationKit/tests/selftests/tdSelfTest4.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSelfTest4.py $ + +""" +Test Manager / Suite Self Test #4 - Testing result overflow handling. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver.base import TestDriverBase, InvalidOption; + + +class tdSelfTest4(TestDriverBase): + """ + Test Manager / Suite Self Test #4 - Testing result overflow handling. + """ + + ## Valid tests. + kasValidTests = [ 'immediate-sub-tests', 'total-sub-tests', 'immediate-values', 'total-values', 'immediate-messages']; + + def __init__(self): + TestDriverBase.__init__(self); + self.sOptWhich = 'immediate-sub-tests'; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--test': + iArg = self.requireMoreArgs(1, asArgs, iArg); + if asArgs[iArg] not in self.kasValidTests: + raise InvalidOption('Invalid test name "%s". Must be one of: %s' + % (asArgs[iArg], ', '.join(self.kasValidTests),)); + self.sOptWhich = asArgs[iArg]; + else: + return TestDriverBase.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionExecute(self): + # Too many immediate sub-tests. + if self.sOptWhich == 'immediate-sub-tests': + reporter.testStart('Too many immediate sub-tests (negative)'); + for i in range(1024): + reporter.testStart('subsub%d' % i); + reporter.testDone(); + # Too many sub-tests in total. + elif self.sOptWhich == 'total-sub-tests': + reporter.testStart('Too many sub-tests (negative)'); + # 32 * 256 = 2^(5+8) = 2^13 = 8192. + for i in range(32): + reporter.testStart('subsub%d' % i); + for j in range(256): + reporter.testStart('subsubsub%d' % j); + reporter.testDone(); + reporter.testDone(); + # Too many immediate values. + elif self.sOptWhich == 'immediate-values': + reporter.testStart('Too many immediate values (negative)'); + for i in range(512): + reporter.testValue('value%d' % i, i, 'times'); + # Too many values in total. + elif self.sOptWhich == 'total-values': + reporter.testStart('Too many sub-tests (negative)'); + for i in range(256): + reporter.testStart('subsub%d' % i); + for j in range(64): + reporter.testValue('value%d' % j, i * 10000 + j, 'times'); + reporter.testDone(); + # Too many failure reasons (only immediate since the limit is extremely low). + elif self.sOptWhich == 'immediate-messages': + reporter.testStart('Too many immediate messages (negative)'); + for i in range(16): + reporter.testFailure('Detail %d' % i); + else: + reporter.testStart('Unknown test %s' % (self.sOptWhich,)); + reporter.error('Invalid test selected: %s' % (self.sOptWhich,)); + reporter.testDone(); + return True; + + +if __name__ == '__main__': + sys.exit(tdSelfTest4().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/serial/Makefile.kmk b/src/VBox/ValidationKit/tests/serial/Makefile.kmk new file mode 100644 index 00000000..363e10f8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Serial port. +# + +# +# Copyright (C) 2018-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 += ValidationKitTestsSerial +ValidationKitTestsSerial_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsSerial_INST = $(INST_VALIDATIONKIT)tests/serial/ +ValidationKitTestsSerial_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdSerial1.py \ + $(PATH_SUB_CURRENT)/loopback.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSerial_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/serial/loopback.py b/src/VBox/ValidationKit/tests/serial/loopback.py new file mode 100755 index 00000000..b5ad831a --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/loopback.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# $Id: loopback.py $ + +""" +VirtualBox Validation Kit - Serial loopback module. +""" + +__copyright__ = \ +""" +Copyright (C) 2018-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: 155244 $" + +# Standard Python imports. +#import os; +import socket; +import threading; + + +g_ksLoopbackTcpServ = 'TcpServ'; +g_ksLoopbackTcpClient = 'TcpClient'; +g_ksLoopbackNamedPipeServ = 'NamedPipeServ'; +g_ksLoopbackNamedPipeClient = 'NamedPipeClient'; + +class SerialLoopbackTcpServ(object): + """ + Handler for a server TCP style connection. + """ + def __init__(self, sLocation, iTimeout): + sHost, sPort = sLocation.split(':'); + self.oConn = None; + self.oSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); + self.oSock.settimeout(iTimeout); + self.oSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1); + self.oSock.bind((sHost, int(sPort))); + self.oSock.listen(1); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + if self.oSock is not None: + self.oSock.close(); + self.oSock = None; + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + self.oSock.close(); + self.oSock = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + if self.oConn is None: + oConn, _ = self.oSock.accept(); + self.oConn = oConn; + else: + abData = self.oConn.recv(1024); # pylint: disable=no-member + if abData is not None: + self.oConn.send(abData); # pylint: disable=no-member + except: + pass; + +class SerialLoopbackTcpClient(object): + """ + Handler for a client TCP style connection. + """ + def __init__(self, sLocation, iTimeout): + sHost, sPort = sLocation.split(':'); + self.oConn = socket.socket(socket.AF_INET, socket.SOCK_STREAM); + self.oConn.connect((sHost, int(sPort))); + self.oConn.settimeout(iTimeout); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + abData = self.oConn.recv(1024); + if abData is not None: + self.oConn.send(abData); + except: + pass; + +class SerialLoopbackNamedPipeServ(object): + """ + Handler for a named pipe server style connection. + """ + def __init__(self, sLocation, iTimeout): + self.oConn = None; + self.oSock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); # pylint: disable=no-member + self.oSock.settimeout(iTimeout); + self.oSock.bind(sLocation); + self.oSock.listen(1); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + if self.oSock is not None: + self.oSock.close(); + self.oSock = None; + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + self.oSock.close(); + self.oSock = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + if self.oConn is None: + oConn, _ = self.oSock.accept(); + self.oConn = oConn; + else: + abData = self.oConn.recv(1024); # pylint: disable=no-member + if abData is not None: + self.oConn.send(abData); # pylint: disable=no-member + except: + pass; + +class SerialLoopbackNamedPipeClient(object): + """ + Handler for a named pipe client style connection. + """ + def __init__(self, sLocation, iTimeout): + self.oConn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); # pylint: disable=no-member + self.oConn.connect(sLocation); + self.oConn.settimeout(iTimeout); + self.iTimeout = iTimeout; + + def __del__(self): + if self.oConn is not None: + self.oConn.close(); + + def shutdown(self): + if self.oConn is not None: + self.oConn.close(); + self.oConn = None; + + def pumpIo(self): + """ + Main I/O pumping routine. + """ + try: + abData = self.oConn.recv(1024); + if abData is not None: + self.oConn.send(abData); + except: + pass; + +class SerialLoopback(object): + """ + Serial port loopback module working with TCP and named pipes. + """ + + def __init__(self, sType, sLocation): + self.fShutdown = False; + self.sType = sType; + self.sLocation = sLocation; + self.oLock = threading.Lock(); + self.oThread = threading.Thread(target=self.threadWorker, args=(), name=('SerLoopback')); + + if sType == g_ksLoopbackTcpServ: + self.oIoPumper = SerialLoopbackTcpServ(sLocation, 0.5); + self.oThread.start(); + elif sType == g_ksLoopbackNamedPipeServ: + self.oIoPumper = SerialLoopbackNamedPipeServ(sLocation, 0.5); # pylint: disable=redefined-variable-type + self.oThread.start(); + + def connect(self): + """ + Connects to the server for a client type version. + """ + fRc = True; + try: + if self.sType == g_ksLoopbackTcpClient: + self.oIoPumper = SerialLoopbackTcpClient(self.sLocation, 0.5); + elif self.sType == g_ksLoopbackNamedPipeClient: + self.oIoPumper = SerialLoopbackNamedPipeClient(self.sLocation, 0.5); # pylint: disable=redefined-variable-type + except: + fRc = False; + else: + self.oThread.start(); + return fRc; + + def shutdown(self): + """ + Shutdown any connection and wait for it to become idle. + """ + with self.oLock: + self.fShutdown = True; + self.oIoPumper.shutdown(); + + def isShutdown(self): + """ + Returns whether the I/O pumping thread should shut down. + """ + with self.oLock: + fShutdown = self.fShutdown; + + return fShutdown; + + def threadWorker(self): + """ + The threaded worker. + """ + while not self.isShutdown(): + self.oIoPumper.pumpIo(); + diff --git a/src/VBox/ValidationKit/tests/serial/tdSerial1.py b/src/VBox/ValidationKit/tests/serial/tdSerial1.py new file mode 100755 index 00000000..8f5f3ab7 --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/tdSerial1.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSerial1.py $ + +""" +VirtualBox Validation Kit - Serial port testing #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2018-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: 155244 $" + + +# Standard Python imports. +import os; +import random; +import string; +import struct; +import sys; + +# 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 testdriver import base; +from testdriver import reporter; +from testdriver import vbox; +from testdriver import vboxcon; + +import loopback; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class tdSerial1(vbox.TestDriver): + """ + VBox serial port testing #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oTestVmSet = self.oTestVmManager.selectSet(self.oTestVmManager.kfGrpStdSmoke); + self.asSerialModesDef = ['RawFile', 'Tcp', 'TcpServ', 'NamedPipe', 'NamedPipeServ', 'HostDev']; + self.asSerialModes = self.asSerialModesDef; + self.asSerialTestsDef = ['Write', 'ReadWrite']; + self.asSerialTests = self.asSerialTestsDef; + self.asUartsDef = ['16450', '16550A', '16750']; + self.asUarts = self.asUartsDef; + self.oLoopback = None; + self.sLocation = None; + self.fVerboseTest = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdSerial1 Options:'); + reporter.log(' --serial-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asSerialModesDef))); + reporter.log(' --serial-tests <t1[:t2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asSerialTestsDef))); + reporter.log(' --uarts <u1[:u2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asUartsDef))); + reporter.log(' --verbose-test'); + reporter.log(' Whether to enable verbose output when running the'); + reporter.log(' test utility inside the VM'); + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--serial-modes': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--serial-modes" takes a colon separated list of serial port modes to test'); + self.asSerialModes = asArgs[iArg].split(':'); + for s in self.asSerialModes: + if s not in self.asSerialModesDef: + reporter.log('warning: The "--serial-modes" value "%s" is not a valid serial port mode.' % (s)); + elif asArgs[iArg] == '--serial-tests': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--serial-tests" takes a colon separated list of serial port tests'); + self.asSerialTests = asArgs[iArg].split(':'); + for s in self.asSerialTests: + if s not in self.asSerialTestsDef: + reporter.log('warning: The "--serial-tests" value "%s" is not a valid serial port test.' % (s)); + elif asArgs[iArg] == '--aurts': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--uarts" takes a colon separated list of uarts to test'); + self.asUarts = asArgs[iArg].split(':'); + for s in self.asUarts: + if s not in self.asUartsDef: + reporter.log('warning: The "--uarts" value "%s" is not a valid uart.' % (s)); + elif asArgs[iArg] == '--verbose-test': + iArg += 1; + self.fVerboseTest = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + + return iArg + 1; + + def actionVerify(self): + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + + # + # Test execution helpers. + # + + def _generateRawPortFilename(self, oTestDrv, oTestVm, sInfix, sSuffix): + """ Generates a raw port filename. """ + random.seed(); + sRandom = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10)); + return os.path.join(oTestDrv.sScratchPath, oTestVm.sVmName + sInfix + sRandom + sSuffix); + + def setupSerialMode(self, oSession, oTestVm, sMode): + """ + Sets up the serial mode. + """ + fRc = True; + fServer = False; + sLocation = None; + ePortMode = vboxcon.PortMode_Disconnected; + if sMode == 'RawFile': + sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out'); + ePortMode = vboxcon.PortMode_RawFile; + elif sMode == 'Tcp': + sLocation = '127.0.0.1:1234'; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackTcpServ, sLocation); + ePortMode = vboxcon.PortMode_TCP; + elif sMode == 'TcpServ': + fServer = True; + sLocation = '1234'; + ePortMode = vboxcon.PortMode_TCP; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackTcpClient, '127.0.0.1:1234'); + elif sMode == 'NamedPipe': + sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out'); + ePortMode = vboxcon.PortMode_HostPipe; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackNamedPipeServ, sLocation); + elif sMode == 'NamedPipeServ': + fServer = True; + sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out'); + ePortMode = vboxcon.PortMode_HostPipe; + self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackNamedPipeClient, sLocation); + elif sMode == 'HostDev': + sLocation = '/dev/ttyUSB0'; + ePortMode = vboxcon.PortMode_HostDevice; + else: + reporter.log('warning, invalid mode %s given' % (sMode, )); + fRc = False; + + if fRc: + fRc = oSession.changeSerialPortAttachment(0, ePortMode, sLocation, fServer); + if fRc and sMode in ('TcpServ', 'NamedPipeServ',): + self.sleep(2); # Fudge to allow the TCP server to get started. + fRc = self.oLoopback.connect(); + if not fRc: + reporter.log('Failed to connect to %s' % (sLocation, )); + self.sLocation = sLocation; + + return fRc; + + def testWrite(self, oSession, oTxsSession, oTestVm, sMode): + """ + Does a simple write test verifying the output. + """ + _ = oSession; + + reporter.testStart('Write'); + tupCmdLine = ('SerialTest', '--tests', 'write', '--txbytes', '1048576'); + if self.fVerboseTest: + tupCmdLine += ('--verbose',); + if oTestVm.isWindows(): + tupCmdLine += ('--device', r'\\.\COM1',); + elif oTestVm.isLinux(): + tupCmdLine += ('--device', r'/dev/ttyS0',); + + fRc = self.txsRunTest(oTxsSession, 'SerialTest', 3600 * 1000, \ + '${CDROM}/${OS/ARCH}/SerialTest${EXESUFF}', tupCmdLine); + if not fRc: + reporter.testFailure('Running serial test utility failed'); + elif sMode == 'RawFile': + # Open serial port and verify + cLast = 0; + try: + with open(self.sLocation, 'rb') as oFile: + sFmt = '=I'; + cBytes = 4; + for i in xrange(1048576 // 4): + _ = i; + sData = oFile.read(cBytes); + tupUnpacked = struct.unpack(sFmt, sData); + cLast = cLast + 1; + if tupUnpacked[0] != cLast: + reporter.testFailure('Corruption detected, expected counter value %s, got %s' + % (cLast + 1, tupUnpacked[0],)); + break; + except: + reporter.logXcpt(); + reporter.testFailure('Verifying the written data failed'); + reporter.testDone(); + return fRc; + + def testReadWrite(self, oSession, oTxsSession, oTestVm): + """ + Does a simple write test verifying the output. + """ + _ = oSession; + + reporter.testStart('ReadWrite'); + tupCmdLine = ('SerialTest', '--tests', 'readwrite', '--txbytes', '1048576'); + if self.fVerboseTest: + tupCmdLine += ('--verbose',); + if oTestVm.isWindows(): + tupCmdLine += ('--device', r'\\.\COM1',); + elif oTestVm.isLinux(): + tupCmdLine += ('--device', r'/dev/ttyS0',); + + fRc = self.txsRunTest(oTxsSession, 'SerialTest', 600 * 1000, \ + '${CDROM}/${OS/ARCH}/SerialTest${EXESUFF}', tupCmdLine); + if not fRc: + reporter.testFailure('Running serial test utility failed'); + + reporter.testDone(); + return fRc; + + def isModeCompatibleWithTest(self, sMode, sTest): + """ + Returns whether the given port mode and test combination is + supported for testing. + """ + if sMode == 'RawFile' and sTest == 'ReadWrite': + return False; + if sMode != 'RawFile' and sTest == 'Write': + return False; + return True; + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + for sUart in self.asUarts: + reporter.testStart(sUart); + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.enableSerialPort(0); + + fRc = fRc and oSession.setExtraData("VBoxInternal/Devices/serial/0/Config/UartType", "string:" + sUart); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc; + oSession = None; + else: + fRc = False; + + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True); + if oSession is not None: + self.addTask(oTxsSession); + + for sMode in self.asSerialModes: + reporter.testStart(sMode); + fRc = self.setupSerialMode(oSession, oTestVm, sMode); + if fRc: + for sTest in self.asSerialTests: + # Skip tests which don't work with the current mode. + if self.isModeCompatibleWithTest(sMode, sTest): + if sTest == 'Write': + fRc = self.testWrite(oSession, oTxsSession, oTestVm, sMode); + if sTest == 'ReadWrite': + fRc = self.testReadWrite(oSession, oTxsSession, oTestVm); + if self.oLoopback is not None: + self.oLoopback.shutdown(); + self.oLoopback = None; + + reporter.testDone(); + + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + reporter.testDone(); + + return fRc; + +if __name__ == '__main__': + sys.exit(tdSerial1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py b/src/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py new file mode 100755 index 00000000..2407e92e --- /dev/null +++ b/src/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +VMM Guest OS boot 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: 155244 $" + + +# Standard Python imports. +import os +import sys +import time + + +# 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 testdriver import vbox +from testdriver import base +from testdriver import reporter +from testdriver import vboxcon + + +class tdGuestOsBootTest1(vbox.TestDriver): + """ + VMM Unit Tests Set. + + Scenario: + - Create VM that corresponds to Guest OS pre-installed on selected HDD + - Start VM and wait for TXS server connection (which is started after Guest successfully booted) + """ + + ksSataController = 'SATA Controller' + ksIdeController = 'IDE Controller' + + # VM parameters required to run HDD image. + # Format: { HDD image filename: (sKind, HDD controller type) } + kaoVmParams = { + 't-win80.vdi': ( 'Windows 8 (64 bit)', ksSataController ), + } + + # List of platforms which are able to suspend and resume host automatically. + # In order to add new platform, self._SuspendResume() should be adapted. + kasSuspendAllowedPlatforms = ( 'darwin' ) + + kcMsVmStartLimit = 5 * 60000 + kcMsVmShutdownLimit = 1 * 60000 + + def __init__(self): + """ + Reinitialize child class instance. + """ + vbox.TestDriver.__init__(self) + + self.sVmName = 'TestVM' + self.sHddName = None + self.sHddPathBase = os.path.join(self.sResourcePath, '4.2', 'nat', 'win80') + self.oVM = None + + # TODO: that should be moved to some common place + self.fEnableIOAPIC = True + self.cCpus = 1 + self.fEnableNestedPaging = True + self.fEnablePAE = False + self.fSuspendHost = False + self.cSecSuspendTime = 60 + self.cShutdownIters = 1 + self.fExtraVm = False + self.sExtraVmName = "TestVM-Extra" + self.oExtraVM = None + self.fLocalCatch = False + + # + # Overridden methods. + # + + def showUsage(self): + """ + Extend usage info + """ + rc = vbox.TestDriver.showUsage(self) + reporter.log(' --boot-hdd <HDD image file name>') + + reporter.log(' --cpus <# CPUs>') + reporter.log(' --no-ioapic') + reporter.log(' --no-nested-paging') + reporter.log(' --pae') + reporter.log(' --suspend-host') + reporter.log(' --suspend-time <sec>') + reporter.log(' --shutdown-iters <# iters>') + reporter.log(' --extra-vm') + reporter.log(' --local-catch') + return rc + + def parseOption(self, asArgs, iArg): + """ + Extend standard options set + """ + if asArgs[iArg] == '--boot-hdd': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--boot-hdd" option requires an argument') + self.sHddName = asArgs[iArg] + + elif asArgs[iArg] == '--cpus': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpus" option requires an argument') + self.cCpus = int(asArgs[iArg]) + elif asArgs[iArg] == '--no-ioapic': + self.fEnableIOAPIC = False + elif asArgs[iArg] == '--no-nested-paging': + self.fEnableNestedPaging = False + elif asArgs[iArg] == '--pae': + self.fEnablePAE = True + elif asArgs[iArg] == '--suspend-host': + self.fSuspendHost = True + elif asArgs[iArg] == '--suspend-time': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--suspend-time" option requires an argument') + self.cSecSuspendTime = int(asArgs[iArg]) + elif asArgs[iArg] == '--shutdown-iters': + iArg += 1 + if iArg >= len(asArgs): raise base.InvalidOption('The "--shutdown-iters" option requires an argument') + self.cShutdownIters = int(asArgs[iArg]) + elif asArgs[iArg] == '--extra-vm': + self.fExtraVm = True + elif asArgs[iArg] == '--local-catch': + self.fLocalCatch = True + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg) + + return iArg + 1 + + def getResourceSet(self): + """ + Returns a set of file and/or directory names relative to + TESTBOX_PATH_RESOURCES. + """ + return [os.path.join(self.sHddPathBase, sRsrc) for sRsrc in self.kaoVmParams]; + + def _addVM(self, sVmName, sNicTraceFile=None): + """ + Create VM + """ + # Get VM params specific to HDD image + sKind, sController = self.kaoVmParams[self.sHddName] + + # Create VM itself + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT + sHddPath = os.path.join(self.sHddPathBase, self.sHddName) + assert os.path.isfile(sHddPath) + + oVM = \ + self.createTestVM(sVmName, 1, sKind=sKind, cCpus=self.cCpus, + eNic0AttachType=eNic0AttachType, sDvdImage = self.sVBoxValidationKitIso) + assert oVM is not None + + oSession = self.openSession(oVM) + + # Attach an HDD + fRc = oSession.attachHd(sHddPath, sController, fImmutable=True) + + # Enable HW virt + fRc = fRc and oSession.enableVirtEx(True) + + # Enable I/O APIC + fRc = fRc and oSession.enableIoApic(self.fEnableIOAPIC) + + # Enable Nested Paging + fRc = fRc and oSession.enableNestedPaging(self.fEnableNestedPaging) + + # Enable PAE + fRc = fRc and oSession.enablePae(self.fEnablePAE) + + if (sNicTraceFile is not None): + fRc = fRc and oSession.setNicTraceEnabled(True, sNicTraceFile) + + # Remote desktop + oSession.setupVrdp(True) + + fRc = fRc and oSession.saveSettings() + fRc = fRc and oSession.close() + assert fRc is True + + return oVM + + def actionConfig(self): + """ + Configure pre-conditions. + """ + + if not self.importVBoxApi(): + return False + + # Save time: do not start VM if there is no way to suspend host + if (self.fSuspendHost is True and sys.platform not in self.kasSuspendAllowedPlatforms): + reporter.log('Platform [%s] is not in the list of supported platforms' % sys.platform) + return False + + assert self.sHddName is not None + if self.sHddName not in self.kaoVmParams: + reporter.log('Error: unknown HDD image specified: %s' % self.sHddName) + return False + + if (self.fExtraVm is True): + self.oExtraVM = self._addVM(self.sExtraVmName) + + self.oVM = self._addVM(self.sVmName) + + return vbox.TestDriver.actionConfig(self) + + def _SuspendResume(self, cSecTimeout): + """ + Put host into sleep and automatically resume it after specified timeout. + """ + fRc = False + + if (sys.platform == 'darwin'): + tsStart = time.time() + fRc = os.system("/usr/bin/pmset relative wake %d" % self.cSecSuspendTime) + fRc |= os.system("/usr/bin/pmset sleepnow") + # Wait for host to wake up + while ((time.time() - tsStart) < self.cSecSuspendTime): + self.sleep(0.1) + + return fRc == 0 + + def _waitKeyboardInterrupt(self): + """ + Idle loop until user press CTRL+C + """ + reporter.log('[LOCAL CATCH]: waiting for keyboard interrupt') + while (True): + try: + self.sleep(1) + except KeyboardInterrupt: + reporter.log('[LOCAL CATCH]: keyboard interrupt occurred') + break + + def actionExecute(self): + """ + Execute the testcase itself. + """ + #self.logVmInfo(self.oVM) + + reporter.testStart('SHUTDOWN GUEST') + + cIter = 0 + fRc = True + + if (self.fExtraVm is True): + oExtraSession, oExtraTxsSession = self.startVmAndConnectToTxsViaTcp(self.sExtraVmName, + fCdWait=False, + cMsTimeout=self.kcMsVmStartLimit) + if oExtraSession is None or oExtraTxsSession is None: + reporter.error('Unable to start extra VM.') + if (self.fLocalCatch is True): + self._waitKeyboardInterrupt() + reporter.testDone() + return False + + while (cIter < self.cShutdownIters): + + cIter += 1 + + reporter.log("Starting iteration #%d." % cIter) + + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(self.sVmName, + fCdWait=False, + cMsTimeout=self.kcMsVmStartLimit) + if oSession is not None and oTxsSession is not None: + # Wait until guest reported success + reporter.log('Guest started. Connection to TXS service established.') + + if (self.fSuspendHost is True): + reporter.log("Disconnect form TXS.") + fRc = fRc and self.txsDisconnect(oSession, oTxsSession) + if (fRc is not True): + reporter.log("Disconnect form TXS failed.") + else: + reporter.log('Put host to sleep and resume it automatically after %d seconds.' % self.cSecSuspendTime) + fRc = fRc and self._SuspendResume(self.cSecSuspendTime) + if (fRc is True): + reporter.log("Sleep/resume success.") + else: + reporter.log("Sleep/resume failed.") + reporter.log("Re-connect to TXS in 10 seconds.") + self.sleep(10) + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 2 * 60 * 10000) + if (fRc is not True): + reporter.log("Re-connect to TXS failed.") + + if (fRc is True): + reporter.log('Attempt to shutdown guest.') + fRc = fRc and oTxsSession.syncShutdown(cMsTimeout=(4 * 60 * 1000)) + if (fRc is True): + reporter.log('Shutdown request issued successfully.') + self.waitOnDirectSessionClose(self.oVM, self.kcMsVmShutdownLimit) + reporter.log('Shutdown %s.' % ('success' if fRc is True else 'failed')) + else: + reporter.error('Shutdown request failed.') + + # Do not terminate failing VM in order to catch it. + if (fRc is not True and self.fLocalCatch is True): + self._waitKeyboardInterrupt() + break + + fRc = fRc and self.terminateVmBySession(oSession) + reporter.log('VM terminated.') + + else: + reporter.error('Guest did not start (iteration %d of %d)' % (cIter, self.cShutdownIters)) + fRc = False + + # Stop if fail + if (fRc is not True): + break + + # Local catch at the end. + if (self.fLocalCatch is True): + reporter.log("Test completed. Waiting for user to press CTRL+C.") + self._waitKeyboardInterrupt() + + if (self.fExtraVm is True): + fRc = fRc and self.terminateVmBySession(oExtraSession) + + reporter.testDone() + return fRc is True + +if __name__ == '__main__': + sys.exit(tdGuestOsBootTest1().main(sys.argv)) diff --git a/src/VBox/ValidationKit/tests/smoketests/Makefile.kmk b/src/VBox/ValidationKit/tests/smoketests/Makefile.kmk new file mode 100644 index 00000000..74e46f1d --- /dev/null +++ b/src/VBox/ValidationKit/tests/smoketests/Makefile.kmk @@ -0,0 +1,52 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Smoke 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 += ValidationKitTestsSmokeTests +ValidationKitTestsSmokeTests_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsSmokeTests_INST = $(INST_VALIDATIONKIT)tests/smoketests/ +ValidationKitTestsSmokeTests_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdSmokeTest1.py \ + $(PATH_SUB_CURRENT)/tdExoticOrAncient1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSmokeTests_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py b/src/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py new file mode 100755 index 00000000..392aa849 --- /dev/null +++ b/src/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdExoticOrAncient1.py $ + +""" +VirtualBox Validation Kit - Exotic and/or ancient OSes #1. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import vbox; + + +class tdExoticOrAncient1(vbox.TestDriver): + """ + VBox exotic and/or ancient OSes #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oTestVmSet = self.oTestVmManager.selectSet( self.oTestVmManager.kfGrpAncient + | self.oTestVmManager.kfGrpExotic); + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + return rc; + + def parseOption(self, asArgs, iArg): + return vbox.TestDriver.parseOption(self, asArgs, iArg); + + def actionVerify(self): + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + def actionConfig(self): + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + + # + # Test execution helpers. + # + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + # Simple test. + self.logVmInfo(oVM); + if oTestVm.fGrouping & self.oTestVmManager.kfGrpNoTxs: + sResult = self.runVmAndMonitorComRawFile(oTestVm.sVmName, oTestVm.sCom1RawFile); + ## @todo sResult = self.runVmAndMonitorComRawFile(oTestVm.sVmName, oTestVm.getCom1RawFile()); + return sResult == 'PASSED'; + oSession, _ = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True); + if oSession is not None: + return self.terminateVmBySession(oSession); + return False; + +if __name__ == '__main__': + sys.exit(tdExoticOrAncient1().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py b/src/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py new file mode 100755 index 00000000..f2477430 --- /dev/null +++ b/src/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSmokeTest1.py $ + +""" +VirtualBox Validation Kit - Smoke Test #1. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdSmokeTest1(vbox.TestDriver): + """ + VBox Smoke Test #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oTestVmSet = self.oTestVmManager.getSmokeVmSet(); + self.sNicAttachmentDef = 'mixed'; + self.sNicAttachment = self.sNicAttachmentDef; + self.fQuick = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('Smoke Test #1 options:'); + reporter.log(' --nic-attachment <bridged|nat|mixed>'); + reporter.log(' Default: %s' % (self.sNicAttachmentDef)); + reporter.log(' --quick'); + reporter.log(' Very selective testing.') + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--nic-attachment': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--nic-attachment" takes an argument'); + self.sNicAttachment = asArgs[iArg]; + if self.sNicAttachment not in ('bridged', 'nat', 'mixed'): + raise base.InvalidOption('The "--nic-attachment" value "%s" is not supported. Valid values are: bridged, nat' \ + % (self.sNicAttachment)); + elif asArgs[iArg] == '--quick': + # Disable all but a few VMs and configurations. + for oTestVm in self.oTestVmSet.aoTestVms: + if oTestVm.sVmName == 'tst-win2k3ent': # 32-bit paging + oTestVm.asVirtModesSup = [ 'hwvirt' ]; + oTestVm.acCpusSup = range(1, 2); + elif oTestVm.sVmName == 'tst-rhel5': # 32-bit paging + oTestVm.asVirtModesSup = [ 'raw' ]; + oTestVm.acCpusSup = range(1, 2); + elif oTestVm.sVmName == 'tst-win2k8': # 64-bit + oTestVm.asVirtModesSup = [ 'hwvirt-np' ]; + oTestVm.acCpusSup = range(1, 2); + elif oTestVm.sVmName == 'tst-sol10-64': # SMP, 64-bit + oTestVm.asVirtModesSup = [ 'hwvirt' ]; + oTestVm.acCpusSup = range(2, 3); + elif oTestVm.sVmName == 'tst-sol10': # SMP, 32-bit + oTestVm.asVirtModesSup = [ 'hwvirt-np' ]; + oTestVm.acCpusSup = range(2, 3); + elif oTestVm.sVmName == 'tst-nsthwvirt-ubuntu-64': # Nested hw.virt, 64-bit + oTestVm.asVirtModesSup = [ 'hwvirt-np' ]; + oTestVm.acCpusSup = range(1, 2); + else: + oTestVm.fSkip = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def actionVerify(self): + if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso): + reporter.error('Cannot find the VBoxValidationKit.iso! (%s)' + 'Please unzip a Validation Kit build in the current directory or in some parent one.' + % (self.sVBoxValidationKitIso,) ); + return False; + return vbox.TestDriver.actionVerify(self); + + 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.sNicAttachment == 'nat': eNic0AttachType = vboxcon.NetworkAttachmentType_NAT; + elif self.sNicAttachment == 'bridged': eNic0AttachType = vboxcon.NetworkAttachmentType_Bridged; + else: eNic0AttachType = None; + assert self.sVBoxValidationKitIso is not None; + return self.oTestVmSet.actionConfig(self, eNic0AttachType = eNic0AttachType, sDvdImage = self.sVBoxValidationKitIso); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testOneVmConfig) + + + # + # Test execution helpers. + # + + def testOneVmConfig(self, oVM, oTestVm): + """ + Runs the specified VM thru test #1. + """ + + # Simple test. + self.logVmInfo(oVM); + # Try waiting for a bit longer (15 minutes) until the CD is available to avoid running into timeouts. + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName, fCdWait = True, cMsCdWait = 15 * 60 * 1000); + if oSession is not None: + self.addTask(oTxsSession); + + ## @todo do some quick tests: save, restore, execute some test program, shut down the guest. + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + return True; + return None; + +if __name__ == '__main__': + sys.exit(tdSmokeTest1().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/storage/Makefile.kmk b/src/VBox/ValidationKit/tests/storage/Makefile.kmk new file mode 100644 index 00000000..bcb88ec8 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/Makefile.kmk @@ -0,0 +1,56 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Storage Tests. +# + +# +# Copyright (C) 2012-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 += ValidationKitTestsStorage +ValidationKitTestsStorage_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsStorage_INST = $(INST_VALIDATIONKIT)tests/storage/ +ValidationKitTestsStorage_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdStorageBenchmark1.py \ + $(PATH_SUB_CURRENT)/tdStorageSnapshotMerging1.py \ + $(PATH_SUB_CURRENT)/tdStorageStress1.py \ + $(PATH_SUB_CURRENT)/tdStorageRawDrive1.py \ + $(PATH_SUB_CURRENT)/remoteexecutor.py \ + $(PATH_SUB_CURRENT)/storagecfg.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsStorage_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/storage/remoteexecutor.py b/src/VBox/ValidationKit/tests/storage/remoteexecutor.py new file mode 100755 index 00000000..7f9b1532 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/remoteexecutor.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +# $Id: remoteexecutor.py $ + +""" +VirtualBox Validation Kit - Storage benchmark, test execution helpers. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-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: 155244 $" + + +# Standard Python imports. +import array; +import os; +import shutil; +import sys; +if sys.version_info[0] >= 3: + from io import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +else: + from StringIO import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +import subprocess; + +# Validation Kit imports. +from common import utils; +from testdriver import reporter; + + + +class StdInOutBuffer(object): + """ Standard input output buffer """ + + def __init__(self, sInput = None): + self.sInput = StringIO(); + if sInput is not None: + self.sInput.write(self._toString(sInput)); + self.sInput.seek(0); + self.sOutput = ''; + + def _toString(self, sText): + """ + Converts any possible array to + a string. + """ + if isinstance(sText, array.array): + try: + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + return str(sText.tostring()); # pylint: disable=no-member + return str(sText.tobytes()); + except: + pass; + elif isinstance(sText, bytes): + return sText.decode('utf-8'); + + return sText; + + def read(self, cb): + """file.read""" + return self.sInput.read(cb); + + def write(self, sText): + """file.write""" + self.sOutput += self._toString(sText); + return None; + + def getOutput(self): + """ + Returns the output of the buffer. + """ + return self.sOutput; + + def close(self): + """ file.close """ + return; + +class RemoteExecutor(object): + """ + Helper for executing tests remotely through TXS or locally + """ + + def __init__(self, oTxsSession = None, asBinaryPaths = None, sScratchPath = None): + self.oTxsSession = oTxsSession; + self.asPaths = asBinaryPaths; + self.sScratchPath = sScratchPath; + if self.asPaths is None: + self.asPaths = [ ]; + + def _getBinaryPath(self, sBinary): + """ + Returns the complete path of the given binary if found + from the configured search path or None if not found. + """ + for sPath in self.asPaths: + sFile = sPath + '/' + sBinary; + if self.isFile(sFile): + return sFile; + return sBinary; + + def _sudoExecuteSync(self, asArgs, sInput): + """ + Executes a sudo child process synchronously. + Returns a tuple [True, 0] if the process executed successfully + and returned 0, otherwise [False, rc] is returned. + """ + reporter.log('Executing [sudo]: %s' % (asArgs, )); + reporter.flushall(); + fRc = True; + sOutput = ''; + sError = ''; + try: + oProcess = utils.sudoProcessPopen(asArgs, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.PIPE, shell = False, close_fds = False); + + sOutput, sError = oProcess.communicate(sInput); + iExitCode = oProcess.poll(); + + if iExitCode != 0: + fRc = False; + except: + reporter.errorXcpt(); + fRc = False; + reporter.log('Exit code [sudo]: %s (%s)' % (fRc, asArgs)); + return (fRc, str(sOutput), str(sError)); + + def _execLocallyOrThroughTxs(self, sExec, asArgs, sInput, cMsTimeout): + """ + Executes the given program locally or through TXS based on the + current config. + """ + fRc = False; + sOutput = None; + if self.oTxsSession is not None: + reporter.log('Executing [remote]: %s %s %s' % (sExec, asArgs, sInput)); + reporter.flushall(); + oStdOut = StdInOutBuffer(); + oStdErr = StdInOutBuffer(); + oTestPipe = reporter.FileWrapperTestPipe(); + oStdIn = None; + if sInput is not None: + oStdIn = StdInOutBuffer(sInput); + else: + oStdIn = '/dev/null'; # pylint: disable=redefined-variable-type + fRc = self.oTxsSession.syncExecEx(sExec, (sExec,) + asArgs, + oStdIn = oStdIn, oStdOut = oStdOut, + oStdErr = oStdErr, oTestPipe = oTestPipe, + cMsTimeout = cMsTimeout); + sOutput = oStdOut.getOutput(); + sError = oStdErr.getOutput(); + if fRc is False: + reporter.log('Exit code [remote]: %s (stdout: %s stderr: %s)' % (fRc, sOutput, sError)); + else: + reporter.log('Exit code [remote]: %s' % (fRc,)); + else: + fRc, sOutput, sError = self._sudoExecuteSync([sExec, ] + list(asArgs), sInput); + return (fRc, sOutput, sError); + + def execBinary(self, sExec, asArgs, sInput = None, cMsTimeout = 3600000): + """ + Executes the given binary with the given arguments + providing some optional input through stdin and + returning whether the process exited successfully and the output + in a string. + """ + + fRc = True; + sOutput = None; + sError = None; + sBinary = self._getBinaryPath(sExec); + if sBinary is not None: + fRc, sOutput, sError = self._execLocallyOrThroughTxs(sBinary, asArgs, sInput, cMsTimeout); + else: + fRc = False; + return (fRc, sOutput, sError); + + def execBinaryNoStdOut(self, sExec, asArgs, sInput = None): + """ + Executes the given binary with the given arguments + providing some optional input through stdin and + returning whether the process exited successfully. + """ + fRc, _, _ = self.execBinary(sExec, asArgs, sInput); + return fRc; + + def copyFile(self, sLocalFile, sFilename, cMsTimeout = 30000): + """ + Copies the local file to the remote destination + if configured + + Returns a file ID which can be used as an input parameter + to execBinary() resolving to the real filepath on the remote side + or locally. + """ + sFileId = None; + if self.oTxsSession is not None: + sFileId = '${SCRATCH}/' + sFilename; + fRc = self.oTxsSession.syncUploadFile(sLocalFile, sFileId, cMsTimeout); + if not fRc: + sFileId = None; + else: + sFileId = self.sScratchPath + '/' + sFilename; + try: + shutil.copy(sLocalFile, sFileId); + except: + sFileId = None; + + return sFileId; + + def copyString(self, sContent, sFilename, cMsTimeout = 30000): + """ + Creates a file remotely or locally with the given content. + + Returns a file ID which can be used as an input parameter + to execBinary() resolving to the real filepath on the remote side + or locally. + """ + sFileId = None; + if self.oTxsSession is not None: + sFileId = '${SCRATCH}/' + sFilename; + fRc = self.oTxsSession.syncUploadString(sContent, sFileId, cMsTimeout); + if not fRc: + sFileId = None; + else: + sFileId = self.sScratchPath + '/' + sFilename; + try: + with open(sFileId, 'wb') as oFile: + oFile.write(sContent); + except: + sFileId = None; + + return sFileId; + + def mkDir(self, sDir, fMode = 0o700, cMsTimeout = 30000): + """ + Creates a new directory at the given location. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncMkDir(sDir, fMode, cMsTimeout); + elif not os.path.isdir(sDir): + fRc = os.mkdir(sDir, fMode); + + return fRc; + + def rmDir(self, sDir, cMsTimeout = 30000): + """ + Removes the given directory. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncRmDir(sDir, cMsTimeout); + else: + fRc = self.execBinaryNoStdOut('rmdir', (sDir,)); + + return fRc; + + def rmTree(self, sDir, cMsTimeout = 30000): + """ + Recursively removes all files and sub directories including the given directory. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncRmTree(sDir, cMsTimeout); + else: + try: + shutil.rmtree(sDir, ignore_errors=True); + except: + fRc = False; + + return fRc; + + def isFile(self, sPath, cMsTimeout = 30000): + """ + Checks that the given file exists. + """ + fRc = True; + if self.oTxsSession is not None: + fRc = self.oTxsSession.syncIsFile(sPath, cMsTimeout); + else: + try: + fRc = os.path.isfile(sPath); + except: + fRc = False; + + return fRc; diff --git a/src/VBox/ValidationKit/tests/storage/storagecfg.py b/src/VBox/ValidationKit/tests/storage/storagecfg.py new file mode 100755 index 00000000..c6bb2266 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/storagecfg.py @@ -0,0 +1,681 @@ +# -*- coding: utf-8 -*- +# $Id: storagecfg.py $ + +""" +VirtualBox Validation Kit - Storage test configuration API. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-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: 155244 $" + +# Standard Python imports. +import os; +import re; + + +class StorageDisk(object): + """ + Class representing a disk for testing. + """ + + def __init__(self, sPath, fRamDisk = False): + self.sPath = sPath; + self.fUsed = False; + self.fRamDisk = fRamDisk; + + def getPath(self): + """ + Return the disk path. + """ + return self.sPath; + + def isUsed(self): + """ + Returns whether the disk is currently in use. + """ + return self.fUsed; + + def isRamDisk(self): + """ + Returns whether the disk objecthas a RAM backing. + """ + return self.fRamDisk; + + def setUsed(self, fUsed): + """ + Sets the used flag for the disk. + """ + if fUsed: + if self.fUsed: + return False; + + self.fUsed = True; + else: + self.fUsed = fUsed; + + return True; + +class StorageConfigOs(object): + """ + Base class for a single hosts OS storage configuration. + """ + + def _getDisksMatchingRegExpWithPath(self, sPath, sRegExp): + """ + Adds new disks to the config matching the given regular expression. + """ + + lstDisks = []; + oRegExp = re.compile(sRegExp); + asFiles = os.listdir(sPath); + for sFile in asFiles: + if oRegExp.match(os.path.basename(sFile)) and os.path.exists(sPath + '/' + sFile): + lstDisks.append(StorageDisk(sPath + '/' + sFile)); + + return lstDisks; + +class StorageConfigOsSolaris(StorageConfigOs): + """ + Class implementing the Solaris specifics for a storage configuration. + """ + + def __init__(self): + StorageConfigOs.__init__(self); + self.idxRamDisk = 0; + + def _getActivePoolsStartingWith(self, oExec, sPoolIdStart): + """ + Returns a list of pools starting with the given ID or None on failure. + """ + lstPools = None; + fRc, sOutput, _ = oExec.execBinary('zpool', ('list', '-H')); + if fRc: + lstPools = []; + asPools = sOutput.splitlines(); + for sPool in asPools: + if sPool.startswith(sPoolIdStart): + # Extract the whole name and add it to the list. + asItems = sPool.split('\t'); + lstPools.append(asItems[0]); + return lstPools; + + def _getActiveVolumesInPoolStartingWith(self, oExec, sPool, sVolumeIdStart): + """ + Returns a list of active volumes for the given pool starting with the given + identifier or None on failure. + """ + lstVolumes = None; + fRc, sOutput, _ = oExec.execBinary('zfs', ('list', '-H')); + if fRc: + lstVolumes = []; + asVolumes = sOutput.splitlines(); + for sVolume in asVolumes: + if sVolume.startswith(sPool + '/' + sVolumeIdStart): + # Extract the whole name and add it to the list. + asItems = sVolume.split('\t'); + lstVolumes.append(asItems[0]); + return lstVolumes; + + def getDisksMatchingRegExp(self, sRegExp): + """ + Returns a list of disks matching the regular expression. + """ + return self._getDisksMatchingRegExpWithPath('/dev/dsk', sRegExp); + + def getMntBase(self): + """ + Returns the mountpoint base for the host. + """ + return '/pools'; + + def createStoragePool(self, oExec, sPool, asDisks, sRaidLvl): + """ + Creates a new storage pool with the given disks and the given RAID level. + """ + sZPoolRaid = None; + if len(asDisks) > 1 and (sRaidLvl == 'raid5' or sRaidLvl is None): + sZPoolRaid = 'raidz'; + + fRc = True; + if sZPoolRaid is not None: + fRc = oExec.execBinaryNoStdOut('zpool', ('create', '-f', sPool, sZPoolRaid,) + tuple(asDisks)); + else: + fRc = oExec.execBinaryNoStdOut('zpool', ('create', '-f', sPool,) + tuple(asDisks)); + + return fRc; + + def createVolume(self, oExec, sPool, sVol, sMountPoint, cbVol = None): + """ + Creates and mounts a filesystem at the given mountpoint using the + given pool and volume IDs. + """ + fRc = True; + if cbVol is not None: + fRc = oExec.execBinaryNoStdOut('zfs', ('create', '-o', 'mountpoint='+sMountPoint, '-V', cbVol, sPool + '/' + sVol)); + else: + fRc = oExec.execBinaryNoStdOut('zfs', ('create', '-o', 'mountpoint='+sMountPoint, sPool + '/' + sVol)); + + # @todo Add proper parameters to set proper owner:group ownership, the testcase broke in r133060 for Solaris + # because ceating directories is now done using the python mkdir API instead of calling 'sudo mkdir...'. + # No one noticed though because testboxstor1 went out of action before... + # Will get fixed as soon as I'm back home. + if fRc: + fRc = oExec.execBinaryNoStdOut('chmod', ('777', sMountPoint)); + + return fRc; + + def destroyVolume(self, oExec, sPool, sVol): + """ + Destroys the given volume. + """ + fRc = oExec.execBinaryNoStdOut('zfs', ('destroy', sPool + '/' + sVol)); + return fRc; + + def destroyPool(self, oExec, sPool): + """ + Destroys the given storage pool. + """ + fRc = oExec.execBinaryNoStdOut('zpool', ('destroy', sPool)); + return fRc; + + def cleanupPoolsAndVolumes(self, oExec, sPoolIdStart, sVolIdStart): + """ + Cleans up any pools and volumes starting with the name in the given + parameters. + """ + fRc = True; + lstPools = self._getActivePoolsStartingWith(oExec, sPoolIdStart); + if lstPools is not None: + for sPool in lstPools: + lstVolumes = self._getActiveVolumesInPoolStartingWith(oExec, sPool, sVolIdStart); + if lstVolumes is not None: + # Destroy all the volumes first + for sVolume in lstVolumes: + fRc2 = oExec.execBinaryNoStdOut('zfs', ('destroy', sVolume)); + if not fRc2: + fRc = fRc2; + + # Destroy the pool + fRc2 = self.destroyPool(oExec, sPool); + if not fRc2: + fRc = fRc2; + else: + fRc = False; + else: + fRc = False; + + return fRc; + + def createRamDisk(self, oExec, cbRamDisk): + """ + Creates a RAM backed disk with the given size. + """ + oDisk = None; + sRamDiskName = 'ramdisk%u' % (self.idxRamDisk,); + fRc, _ , _ = oExec.execBinary('ramdiskadm', ('-a', sRamDiskName, str(cbRamDisk))); + if fRc: + self.idxRamDisk += 1; + oDisk = StorageDisk('/dev/ramdisk/%s' % (sRamDiskName, ), True); + + return oDisk; + + def destroyRamDisk(self, oExec, oDisk): + """ + Destroys the given ramdisk object. + """ + sRamDiskName = os.path.basename(oDisk.getPath()); + return oExec.execBinaryNoStdOut('ramdiskadm', ('-d', sRamDiskName)); + +class StorageConfigOsLinux(StorageConfigOs): + """ + Class implementing the Linux specifics for a storage configuration. + """ + + def __init__(self): + StorageConfigOs.__init__(self); + self.dSimplePools = { }; # Simple storage pools which don't use lvm (just one partition) + self.dMounts = { }; # Pool/Volume to mountpoint mapping. + + def _getDmRaidLevelFromLvl(self, sRaidLvl): + """ + Converts our raid level indicators to something mdadm can understand. + """ + if sRaidLvl is None or sRaidLvl == 'raid0': + return 'stripe'; + if sRaidLvl == 'raid5': + return '5'; + if sRaidLvl == 'raid1': + return 'mirror'; + return 'stripe'; + + def getDisksMatchingRegExp(self, sRegExp): + """ + Returns a list of disks matching the regular expression. + """ + return self._getDisksMatchingRegExpWithPath('/dev/', sRegExp); + + def getMntBase(self): + """ + Returns the mountpoint base for the host. + """ + return '/mnt'; + + def createStoragePool(self, oExec, sPool, asDisks, sRaidLvl): + """ + Creates a new storage pool with the given disks and the given RAID level. + """ + fRc = True; + if len(asDisks) == 1 and sRaidLvl is None: + # Doesn't require LVM, put into the simple pools dictionary so we can + # use it when creating a volume later. + self.dSimplePools[sPool] = asDisks[0]; + else: + # If a RAID is required use dm-raid first to create one. + asLvmPvDisks = asDisks; + fRc = oExec.execBinaryNoStdOut('mdadm', ('--create', '/dev/md0', '--assume-clean', + '--level=' + self._getDmRaidLevelFromLvl(sRaidLvl), + '--raid-devices=' + str(len(asDisks))) + tuple(asDisks)); + if fRc: + # /dev/md0 is the only block device to use for our volume group. + asLvmPvDisks = [ '/dev/md0' ]; + + # Create a physical volume on every disk first. + for sLvmPvDisk in asLvmPvDisks: + fRc = oExec.execBinaryNoStdOut('pvcreate', (sLvmPvDisk, )); + if not fRc: + break; + + if fRc: + # Create volume group with all physical volumes included + fRc = oExec.execBinaryNoStdOut('vgcreate', (sPool, ) + tuple(asLvmPvDisks)); + return fRc; + + def createVolume(self, oExec, sPool, sVol, sMountPoint, cbVol = None): + """ + Creates and mounts a filesystem at the given mountpoint using the + given pool and volume IDs. + """ + fRc = True; + sBlkDev = None; + if sPool in self.dSimplePools: + sDiskPath = self.dSimplePools.get(sPool); + if sDiskPath.find('zram') != -1: + sBlkDev = sDiskPath; + else: + # Create a partition with the requested size + sFdiskScript = ';\n'; # Single partition filling everything + if cbVol is not None: + sFdiskScript = ',' + str(cbVol // 512) + '\n'; # Get number of sectors + fRc = oExec.execBinaryNoStdOut('sfdisk', ('--no-reread', '--wipe', 'always', '-q', '-f', sDiskPath), \ + sFdiskScript); + if fRc: + if sDiskPath.find('nvme') != -1: + sBlkDev = sDiskPath + 'p1'; + else: + sBlkDev = sDiskPath + '1'; + else: + if cbVol is None: + fRc = oExec.execBinaryNoStdOut('lvcreate', ('-l', '100%FREE', '-n', sVol, sPool)); + else: + fRc = oExec.execBinaryNoStdOut('lvcreate', ('-L', str(cbVol), '-n', sVol, sPool)); + if fRc: + sBlkDev = '/dev/mapper' + sPool + '-' + sVol; + + if fRc is True and sBlkDev is not None: + # Create a filesystem and mount it + fRc = oExec.execBinaryNoStdOut('mkfs.ext4', ('-F', '-F', sBlkDev,)); + fRc = fRc and oExec.mkDir(sMountPoint); + fRc = fRc and oExec.execBinaryNoStdOut('mount', (sBlkDev, sMountPoint)); + if fRc: + self.dMounts[sPool + '/' + sVol] = sMountPoint; + return fRc; + + def destroyVolume(self, oExec, sPool, sVol): + """ + Destroys the given volume. + """ + # Unmount first + sMountPoint = self.dMounts[sPool + '/' + sVol]; + fRc = oExec.execBinaryNoStdOut('umount', (sMountPoint,)); + self.dMounts.pop(sPool + '/' + sVol); + oExec.rmDir(sMountPoint); + if sPool in self.dSimplePools: + # Wipe partition table + sDiskPath = self.dSimplePools.get(sPool); + if sDiskPath.find('zram') == -1: + fRc = oExec.execBinaryNoStdOut('sfdisk', ('--no-reread', '--wipe', 'always', '-q', '-f', '--delete', \ + sDiskPath)); + else: + fRc = oExec.execBinaryNoStdOut('lvremove', (sPool + '/' + sVol,)); + return fRc; + + def destroyPool(self, oExec, sPool): + """ + Destroys the given storage pool. + """ + fRc = True; + if sPool in self.dSimplePools: + self.dSimplePools.pop(sPool); + else: + fRc = oExec.execBinaryNoStdOut('vgremove', (sPool,)); + return fRc; + + def cleanupPoolsAndVolumes(self, oExec, sPoolIdStart, sVolIdStart): + """ + Cleans up any pools and volumes starting with the name in the given + parameters. + """ + # @todo: Needs implementation, for LVM based configs a similar approach can be used + # as for Solaris. + _ = oExec; + _ = sPoolIdStart; + _ = sVolIdStart; + return True; + + def createRamDisk(self, oExec, cbRamDisk): + """ + Creates a RAM backed disk with the given size. + """ + # Make sure the ZRAM module is loaded. + oDisk = None; + fRc = oExec.execBinaryNoStdOut('modprobe', ('zram',)); + if fRc: + fRc, sOut, _ = oExec.execBinary('zramctl', ('--raw', '-f', '-s', str(cbRamDisk))); + if fRc: + oDisk = StorageDisk(sOut.rstrip(), True); + + return oDisk; + + def destroyRamDisk(self, oExec, oDisk): + """ + Destroys the given ramdisk object. + """ + return oExec.execBinaryNoStdOut('zramctl', ('-r', oDisk.getPath())); + +## @name Host disk config types. +## @{ +g_ksDiskCfgStatic = 'StaticDir'; +g_ksDiskCfgRegExp = 'RegExp'; +g_ksDiskCfgList = 'DiskList'; +## @} + +class DiskCfg(object): + """ + Host disk configuration. + """ + + def __init__(self, sTargetOs, sCfgType, oDisks): + self.sTargetOs = sTargetOs; + self.sCfgType = sCfgType; + self.oDisks = oDisks; + + def getTargetOs(self): + return self.sTargetOs; + + def getCfgType(self): + return self.sCfgType; + + def isCfgStaticDir(self): + return self.sCfgType == g_ksDiskCfgStatic; + + def isCfgRegExp(self): + return self.sCfgType == g_ksDiskCfgRegExp; + + def isCfgList(self): + return self.sCfgType == g_ksDiskCfgList; + + def getDisks(self): + return self.oDisks; + +class StorageCfg(object): + """ + Storage configuration helper class taking care of the different host OS. + """ + + def __init__(self, oExec, oDiskCfg): + self.oExec = oExec; + self.lstDisks = [ ]; # List of disks present in the system. + self.dPools = { }; # Dictionary of storage pools. + self.dVols = { }; # Dictionary of volumes. + self.iPoolId = 0; + self.iVolId = 0; + self.oDiskCfg = oDiskCfg; + + fRc = True; + oStorOs = None; + if oDiskCfg.getTargetOs() == 'solaris': + oStorOs = StorageConfigOsSolaris(); + elif oDiskCfg.getTargetOs() == 'linux': + oStorOs = StorageConfigOsLinux(); # pylint: disable=redefined-variable-type + elif not oDiskCfg.isCfgStaticDir(): + # For unknown hosts only allow a static testing directory we don't care about setting up + fRc = False; + + if fRc: + self.oStorOs = oStorOs; + if oDiskCfg.isCfgRegExp(): + self.lstDisks = oStorOs.getDisksMatchingRegExp(oDiskCfg.getDisks()); + elif oDiskCfg.isCfgList(): + # Assume a list of of disks and add. + for sDisk in oDiskCfg.getDisks(): + self.lstDisks.append(StorageDisk(sDisk)); + elif oDiskCfg.isCfgStaticDir(): + if not os.path.exists(oDiskCfg.getDisks()): + self.oExec.mkDir(oDiskCfg.getDisks(), 0o700); + + def __del__(self): + self.cleanup(); + self.oDiskCfg = None; + + def cleanup(self): + """ + Cleans up any created storage configs. + """ + + if not self.oDiskCfg.isCfgStaticDir(): + # Destroy all volumes first. + for sMountPoint in list(self.dVols.keys()): # pylint: disable=consider-iterating-dictionary + self.destroyVolume(sMountPoint); + + # Destroy all pools. + for sPool in list(self.dPools.keys()): # pylint: disable=consider-iterating-dictionary + self.destroyStoragePool(sPool); + + self.dVols.clear(); + self.dPools.clear(); + self.iPoolId = 0; + self.iVolId = 0; + + def getRawDisk(self): + """ + Returns a raw disk device from the list of free devices for use. + """ + + for oDisk in self.lstDisks: + if oDisk.isUsed() is False: + oDisk.setUsed(True); + return oDisk.getPath(); + + return None; + + def getUnusedDiskCount(self): + """ + Returns the number of unused disks. + """ + + cDisksUnused = 0; + for oDisk in self.lstDisks: + if not oDisk.isUsed(): + cDisksUnused += 1; + + return cDisksUnused; + + def createStoragePool(self, cDisks = 0, sRaidLvl = None, + cbPool = None, fRamDisk = False): + """ + Create a new storage pool + """ + lstDisks = [ ]; + fRc = True; + sPool = None; + + if not self.oDiskCfg.isCfgStaticDir(): + if fRamDisk: + oDisk = self.oStorOs.createRamDisk(self.oExec, cbPool); + if oDisk is not None: + lstDisks.append(oDisk); + cDisks = 1; + else: + if cDisks == 0: + cDisks = self.getUnusedDiskCount(); + + for oDisk in self.lstDisks: + if not oDisk.isUsed(): + oDisk.setUsed(True); + lstDisks.append(oDisk); + if len(lstDisks) == cDisks: + break; + + # Enough drives to satisfy the request? + if len(lstDisks) == cDisks: + # Create a list of all device paths + lstDiskPaths = [ ]; + for oDisk in lstDisks: + lstDiskPaths.append(oDisk.getPath()); + + # Find a name for the pool + sPool = 'pool' + str(self.iPoolId); + self.iPoolId += 1; + + fRc = self.oStorOs.createStoragePool(self.oExec, sPool, lstDiskPaths, sRaidLvl); + if fRc: + self.dPools[sPool] = lstDisks; + else: + self.iPoolId -= 1; + else: + fRc = False; + + # Cleanup in case of error. + if not fRc: + for oDisk in lstDisks: + oDisk.setUsed(False); + if oDisk.isRamDisk(): + self.oStorOs.destroyRamDisk(self.oExec, oDisk); + else: + sPool = 'StaticDummy'; + + return fRc, sPool; + + def destroyStoragePool(self, sPool): + """ + Destroys the storage pool with the given ID. + """ + + fRc = True; + + if not self.oDiskCfg.isCfgStaticDir(): + lstDisks = self.dPools.get(sPool); + if lstDisks is not None: + fRc = self.oStorOs.destroyPool(self.oExec, sPool); + if fRc: + # Mark disks as unused + self.dPools.pop(sPool); + for oDisk in lstDisks: + oDisk.setUsed(False); + if oDisk.isRamDisk(): + self.oStorOs.destroyRamDisk(self.oExec, oDisk); + else: + fRc = False; + + return fRc; + + def createVolume(self, sPool, cbVol = None): + """ + Creates a new volume from the given pool returning the mountpoint. + """ + + fRc = True; + sMountPoint = None; + if not self.oDiskCfg.isCfgStaticDir(): + if sPool in self.dPools: + sVol = 'vol' + str(self.iVolId); + sMountPoint = self.oStorOs.getMntBase() + '/' + sVol; + self.iVolId += 1; + fRc = self.oStorOs.createVolume(self.oExec, sPool, sVol, sMountPoint, cbVol); + if fRc: + self.dVols[sMountPoint] = (sVol, sPool); + else: + self.iVolId -= 1; + else: + fRc = False; + else: + sMountPoint = self.oDiskCfg.getDisks(); + + return fRc, sMountPoint; + + def destroyVolume(self, sMountPoint): + """ + Destroy the volume at the given mount point. + """ + + fRc = True; + if not self.oDiskCfg.isCfgStaticDir(): + sVol, sPool = self.dVols.get(sMountPoint); + if sVol is not None: + fRc = self.oStorOs.destroyVolume(self.oExec, sPool, sVol); + if fRc: + self.dVols.pop(sMountPoint); + else: + fRc = False; + + return fRc; + + def mkDirOnVolume(self, sMountPoint, sDir, fMode = 0o700): + """ + Creates a new directory on the volume pointed to by the given mount point. + """ + return self.oExec.mkDir(sMountPoint + '/' + sDir, fMode); + + def cleanupLeftovers(self): + """ + Tries to cleanup any leftover pools and volumes from a failed previous run. + """ + if not self.oDiskCfg.isCfgStaticDir(): + return self.oStorOs.cleanupPoolsAndVolumes(self.oExec, 'pool', 'vol'); + + fRc = True; + if os.path.exists(self.oDiskCfg.getDisks()): + for sEntry in os.listdir(self.oDiskCfg.getDisks()): + fRc = fRc and self.oExec.rmTree(os.path.join(self.oDiskCfg.getDisks(), sEntry)); + + return fRc; diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py b/src/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py new file mode 100755 index 00000000..17dc09b2 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py @@ -0,0 +1,1469 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdStorageBenchmark1.py $ + +""" +VirtualBox Validation Kit - Storage benchmark. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-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: 155244 $" + + +# Standard Python imports. +import os; +import socket; +import sys; +if sys.version_info[0] >= 3: + from io import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias +else: + from StringIO import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias + +# 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 constants; +from common import utils; +from testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxwrappers; + +import remoteexecutor; +import storagecfg; + + +class FioTest(object): + """ + Flexible I/O tester testcase. + """ + + kdHostIoEngine = { + 'solaris': ('solarisaio', False), + 'linux': ('libaio', True) + }; + + def __init__(self, oExecutor, dCfg = None): + self.oExecutor = oExecutor; + self.sCfgFileId = None; + self.dCfg = dCfg; + self.sError = None; + self.sResult = None; + + def prepare(self, cMsTimeout = 30000): + """ Prepares the testcase """ + reporter.testStart('Fio'); + + sTargetOs = self.dCfg.get('TargetOs', 'linux'); + sIoEngine, fDirectIo = self.kdHostIoEngine.get(sTargetOs); + if sIoEngine is None: + return False; + + cfgBuf = StringIO(); + cfgBuf.write('[global]\n'); + cfgBuf.write('bs=' + str(self.dCfg.get('RecordSize', 4096)) + '\n'); + cfgBuf.write('ioengine=' + sIoEngine + '\n'); + cfgBuf.write('iodepth=' + str(self.dCfg.get('QueueDepth', 32)) + '\n'); + cfgBuf.write('size=' + str(self.dCfg.get('TestsetSize', 2147483648)) + '\n'); + if fDirectIo: + cfgBuf.write('direct=1\n'); + else: + cfgBuf.write('direct=0\n'); + cfgBuf.write('directory=' + self.dCfg.get('FilePath', '/mnt') + '\n'); + cfgBuf.write('filename=fio.test.file'); + + cfgBuf.write('[seq-write]\n'); + cfgBuf.write('rw=write\n'); + cfgBuf.write('stonewall\n'); + + cfgBuf.write('[rand-write]\n'); + cfgBuf.write('rw=randwrite\n'); + cfgBuf.write('stonewall\n'); + + cfgBuf.write('[seq-read]\n'); + cfgBuf.write('rw=read\n'); + cfgBuf.write('stonewall\n'); + + cfgBuf.write('[rand-read]\n'); + cfgBuf.write('rw=randread\n'); + cfgBuf.write('stonewall\n'); + + self.sCfgFileId = self.oExecutor.copyString(cfgBuf.getvalue(), 'aio-test', cMsTimeout); + return self.sCfgFileId is not None; + + def run(self, cMsTimeout = 30000): + """ Runs the testcase """ + fRc, sOutput, sError = self.oExecutor.execBinary('fio', (self.sCfgFileId,), cMsTimeout = cMsTimeout); + if fRc: + self.sResult = sOutput; + else: + self.sError = ('Binary: fio\n' + + '\nOutput:\n\n' + + sOutput + + '\nError:\n\n' + + sError); + return fRc; + + def cleanup(self): + """ Cleans up any leftovers from the testcase. """ + reporter.testDone(); + return True; + + def reportResult(self): + """ + Reports the test results to the test manager. + """ + return True; + + def getErrorReport(self): + """ + Returns the error report in case the testcase failed. + """ + return self.sError; + +class IozoneTest(object): + """ + I/O zone testcase. + """ + def __init__(self, oExecutor, dCfg = None): + self.oExecutor = oExecutor; + self.sResult = None; + self.sError = None; + self.lstTests = [ ('initial writers', 'FirstWrite'), + ('rewriters', 'Rewrite'), + ('re-readers', 'ReRead'), + ('stride readers', 'StrideRead'), + ('reverse readers', 'ReverseRead'), + ('random readers', 'RandomRead'), + ('mixed workload', 'MixedWorkload'), + ('random writers', 'RandomWrite'), + ('pwrite writers', 'PWrite'), + ('pread readers', 'PRead'), + ('fwriters', 'FWrite'), + ('freaders', 'FRead'), + ('readers', 'FirstRead')]; + self.sRecordSize = str(int(dCfg.get('RecordSize', 4096) / 1024)); + self.sTestsetSize = str(int(dCfg.get('TestsetSize', 2147483648) / 1024)); + self.sQueueDepth = str(int(dCfg.get('QueueDepth', 32))); + self.sFilePath = dCfg.get('FilePath', '/mnt/iozone'); + self.fDirectIo = True; + + sTargetOs = dCfg.get('TargetOs'); + if sTargetOs == 'solaris': + self.fDirectIo = False; + + def prepare(self, cMsTimeout = 30000): + """ Prepares the testcase """ + reporter.testStart('IoZone'); + _ = cMsTimeout; + return True; # Nothing to do. + + def run(self, cMsTimeout = 30000): + """ Runs the testcase """ + tupArgs = ('-r', self.sRecordSize, '-s', self.sTestsetSize, \ + '-t', '1', '-T', '-F', self.sFilePath + '/iozone.tmp'); + if self.fDirectIo: + tupArgs += ('-I',); + fRc, sOutput, sError = self.oExecutor.execBinary('iozone', tupArgs, cMsTimeout = cMsTimeout); + if fRc: + self.sResult = sOutput; + else: + self.sError = ('Binary: iozone\n' + + '\nOutput:\n\n' + + sOutput + + '\nError:\n\n' + + sError); + return fRc; + + def cleanup(self): + """ Cleans up any leftovers from the testcase. """ + reporter.testDone(); + return True; + + def reportResult(self): + """ + Reports the test results to the test manager. + """ + + fRc = True; + if self.sResult is not None: + try: + asLines = self.sResult.splitlines(); + for sLine in asLines: + sLine = sLine.strip(); + if sLine.startswith('Children') is True: + # Extract the value + idxValue = sLine.rfind('='); + if idxValue == -1: + raise Exception('IozoneTest: Invalid state'); + + idxValue += 1; + while sLine[idxValue] == ' ': + idxValue += 1; + + # Get the reported value, cut off after the decimal point + # it is not supported by the testmanager yet and is not really + # relevant anyway. + idxValueEnd = idxValue; + while sLine[idxValueEnd].isdigit(): + idxValueEnd += 1; + + for sNeedle, sTestVal in self.lstTests: + if sLine.rfind(sNeedle) != -1: + reporter.testValue(sTestVal, sLine[idxValue:idxValueEnd], + constants.valueunit.g_asNames[constants.valueunit.KILOBYTES_PER_SEC]); + break; + except: + fRc = False; + else: + fRc = False; + + return fRc; + + def getErrorReport(self): + """ + Returns the error report in case the testcase failed. + """ + return self.sError; + +class IoPerfTest(object): + """ + IoPerf testcase. + """ + def __init__(self, oExecutor, dCfg = None): + self.oExecutor = oExecutor; + self.sResult = None; + self.sError = None; + self.sRecordSize = str(dCfg.get('RecordSize', 4094)); + self.sTestsetSize = str(dCfg.get('TestsetSize', 2147483648)); + self.sQueueDepth = str(dCfg.get('QueueDepth', 32)); + self.sFilePath = dCfg.get('FilePath', '/mnt'); + self.fDirectIo = True; + self.asGstIoPerfPaths = [ + '${CDROM}/vboxvalidationkit/${OS/ARCH}/IoPerf${EXESUFF}', + '${CDROM}/${OS/ARCH}/IoPerf${EXESUFF}', + ]; + + sTargetOs = dCfg.get('TargetOs'); + if sTargetOs == 'solaris': + self.fDirectIo = False; + + def _locateGstIoPerf(self): + """ + Returns guest side path to FsPerf. + """ + for sIoPerfPath in self.asGstIoPerfPaths: + if self.oExecutor.isFile(sIoPerfPath): + return sIoPerfPath; + reporter.log('Unable to find guest FsPerf in any of these places: %s' % ('\n '.join(self.asGstIoPerfPaths),)); + return self.asGstIoPerfPaths[0]; + + def prepare(self, cMsTimeout = 30000): + """ Prepares the testcase """ + _ = cMsTimeout; + return True; # Nothing to do. + + def run(self, cMsTimeout = 30000): + """ Runs the testcase """ + tupArgs = ('--block-size', self.sRecordSize, '--test-set-size', self.sTestsetSize, \ + '--maximum-requests', self.sQueueDepth, '--dir', self.sFilePath + '/ioperfdir-1'); + if self.fDirectIo: + tupArgs += ('--use-cache', 'off'); + fRc, sOutput, sError = self.oExecutor.execBinary(self._locateGstIoPerf(), tupArgs, cMsTimeout = cMsTimeout); + if fRc: + self.sResult = sOutput; + else: + if sError is None: + sError = ''; + if sOutput is None: + sOutput = ''; + self.sError = ('Binary: IoPerf\n' + + '\nOutput:\n\n' + + sOutput + + '\nError:\n\n' + + sError); + return fRc; + + def cleanup(self): + """ Cleans up any leftovers from the testcase. """ + return True; + + def reportResult(self): + """ + Reports the test results to the test manager. + """ + # Should be done using the test pipe already. + return True; + + def getErrorReport(self): + """ + Returns the error report in case the testcase failed. + """ + return self.sError; + +class StorTestCfgMgr(object): + """ + Manages the different testcases. + """ + + def __init__(self, aasTestLvls, aasTestsBlacklist, fnIsCfgSupported = None): + self.aasTestsBlacklist = aasTestsBlacklist; + self.at4TestLvls = []; + self.iTestLvl = 0; + self.fnIsCfgSupported = fnIsCfgSupported; + for asTestLvl in aasTestLvls: + if isinstance(asTestLvl, tuple): + asTestLvl, fSubTestStartAuto, fnTestFmt = asTestLvl; + self.at4TestLvls.append((0, fSubTestStartAuto, fnTestFmt, asTestLvl)); + else: + self.at4TestLvls.append((0, True, None, asTestLvl)); + + self.at4TestLvls.reverse(); + + # Get the first non blacklisted test. + asTestCfg = self.getCurrentTestCfg(); + while asTestCfg and self.isTestCfgBlacklisted(asTestCfg): + asTestCfg = self.advanceTestCfg(); + + iLvl = 0; + for sCfg in asTestCfg: + sSubTest = self.getTestIdString(sCfg, iLvl); + if sSubTest is not None: + reporter.testStart('%s' % (sSubTest,)); + iLvl += 1; + + def __del__(self): + # Make sure the tests are marked as done. + while self.iTestLvl < len(self.at4TestLvls): + reporter.testDone(); + self.iTestLvl += 1; + + def getTestIdString(self, oCfg, iLvl): + """ + Returns a potentially formatted string for the test name. + """ + + # The order of the test levels is reversed so get the level starting + # from the end. + _, fSubTestStartAuto, fnTestFmt, _ = self.at4TestLvls[len(self.at4TestLvls) - 1 - iLvl]; + if not fSubTestStartAuto: + return None; + if fnTestFmt is not None: + return fnTestFmt(oCfg); + return oCfg; + + def isTestCfgBlacklisted(self, asTestCfg): + """ + Returns whether the given test config is black listed. + """ + fBlacklisted = False; + + for asTestBlacklist in self.aasTestsBlacklist: + iLvl = 0; + fBlacklisted = True; + while iLvl < len(asTestBlacklist) and iLvl < len(asTestCfg): + if asTestBlacklist[iLvl] != asTestCfg[iLvl] and asTestBlacklist[iLvl] != '*': + fBlacklisted = False; + break; + + iLvl += 1; + + if not fBlacklisted and self.fnIsCfgSupported is not None: + fBlacklisted = not self.fnIsCfgSupported(asTestCfg); + + return fBlacklisted; + + def advanceTestCfg(self): + """ + Advances to the next test config and returns it as an + array of strings or an empty config if there is no test left anymore. + """ + iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg = self.at4TestLvls[self.iTestLvl]; + iTestCfg += 1; + self.at4TestLvls[self.iTestLvl] = (iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg); + while iTestCfg == len(asTestCfg) and self.iTestLvl < len(self.at4TestLvls): + self.at4TestLvls[self.iTestLvl] = (0, fSubTestStartAuto, fnTestFmt, asTestCfg); + self.iTestLvl += 1; + if self.iTestLvl < len(self.at4TestLvls): + iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg = self.at4TestLvls[self.iTestLvl]; + iTestCfg += 1; + self.at4TestLvls[self.iTestLvl] = (iTestCfg, fSubTestStartAuto, fnTestFmt, asTestCfg); + if iTestCfg < len(asTestCfg): + self.iTestLvl = 0; + break; + else: + break; # We reached the end of our tests. + + return self.getCurrentTestCfg(); + + def getCurrentTestCfg(self): + """ + Returns the current not black listed test config as an array of strings. + """ + asTestCfg = []; + + if self.iTestLvl < len(self.at4TestLvls): + for t4TestLvl in self.at4TestLvls: + iTestCfg, _, _, asTestLvl = t4TestLvl; + asTestCfg.append(asTestLvl[iTestCfg]); + + asTestCfg.reverse() + + return asTestCfg; + + def getNextTestCfg(self): + """ + Returns the next not blacklisted test config or an empty list if + there is no test left. + """ + asTestCfgCur = self.getCurrentTestCfg(); + + asTestCfg = self.advanceTestCfg(); + while asTestCfg and self.isTestCfgBlacklisted(asTestCfg): + asTestCfg = self.advanceTestCfg(); + + # Compare the current and next config and close the approriate test + # categories. + #reporter.testDone(fSkippedLast); + if asTestCfg: + idxSame = 0; + while asTestCfgCur[idxSame] == asTestCfg[idxSame]: + idxSame += 1; + + for i in range(idxSame, len(asTestCfg) - 1): + reporter.testDone(); + + for i in range(idxSame, len(asTestCfg)): + sSubTest = self.getTestIdString(asTestCfg[i], i); + if sSubTest is not None: + reporter.testStart('%s' % (sSubTest,)); + + else: + # No more tests, mark all tests as done + for i in range(0, len(asTestCfgCur) - 1): + reporter.testDone(); + + return asTestCfg; + +class tdStorageBenchmark(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Storage benchmark. + """ + + # Global storage configs for the testbox + kdStorageCfgs = { + # Testbox configs (Flag whether to test raw mode on the testbox, disk configuration) + 'testboxstor1.de.oracle.com': (True, storagecfg.DiskCfg('solaris', storagecfg.g_ksDiskCfgRegExp, r'c[3-9]t\dd0\Z')), + # Windows testbox doesn't return testboxstor2.de.oracle.com from socket.getfqdn() + 'testboxstor2': (False, storagecfg.DiskCfg('win', storagecfg.g_ksDiskCfgStatic, 'D:\\StorageTest')), + + # Local test configs for the testcase developer + 'adaris': (True, storagecfg.DiskCfg('linux', storagecfg.g_ksDiskCfgStatic, \ + '/home/alexander/StorageScratch')), + 'daedalus': (True, storagecfg.DiskCfg('darwin', storagecfg.g_ksDiskCfgStatic, \ + '/Volumes/VirtualBox/Testsuite/StorageScratch')), + 'windows10': (True, storagecfg.DiskCfg('win', storagecfg.g_ksDiskCfgStatic, \ + 'L:\\Testsuite\\StorageTest')), + }; + + # Available test sets. + kdTestSets = { + # Mostly for developing and debugging the testcase. + 'Fast': { + 'RecordSize': 65536, + 'TestsetSize': 104857600, # 100 MiB + 'QueueDepth': 32, + 'DiskSizeGb': 2 + }, + # For quick functionality tests where benchmark results are not required. + 'Functionality': { + 'RecordSize': 65536, + 'TestsetSize': 2147483648, # 2 GiB + 'QueueDepth': 32, + 'DiskSizeGb': 10 + }, + # For benchmarking the I/O stack. + 'Benchmark': { + 'RecordSize': 65536, + 'TestsetSize': 21474836480, # 20 Gib + 'QueueDepth': 32, + 'DiskSizeGb': 30 + }, + # For stress testing which takes a lot of time. + 'Stress': { + 'RecordSize': 65536, + 'TestsetSize': 2199023255552, # 2 TiB + 'QueueDepth': 32, + 'DiskSizeGb': 10000 + }, + }; + + # Dictionary mapping the virtualization mode mnemonics to a little less cryptic + # strings used in test descriptions. + kdVirtModeDescs = { + 'raw' : 'Raw-mode', + 'hwvirt' : 'HwVirt', + 'hwvirt-np' : 'NestedPaging' + }; + + kdHostIoCacheDescs = { + 'default' : 'HostCacheDef', + 'hostiocache' : 'HostCacheOn', + 'no-hostiocache' : 'HostCacheOff' + }; + + # Password ID for encryption. + ksPwId = 'EncPwId'; + + # Array indexes for the test configs. + kiVmName = 0; + kiStorageCtrl = 1; + kiHostIoCache = 2; + kiDiskFmt = 3; + kiDiskVar = 4; + kiCpuCount = 5; + kiVirtMode = 6; + kiTestSet = 7; + kiIoTest = 8; + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asTestVMsDef = ['tst-storage', 'tst-storage32']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef; + self.acCpusDef = [1, 2]; + self.acCpus = self.acCpusDef; + self.asStorageCtrlsDef = ['AHCI', 'IDE', 'LsiLogicSAS', 'LsiLogic', 'BusLogic', 'NVMe', 'VirtIoScsi']; + self.asStorageCtrls = self.asStorageCtrlsDef; + self.asHostIoCacheDef = ['default', 'hostiocache', 'no-hostiocache']; + self.asHostIoCache = self.asHostIoCacheDef; + self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD', 'QED', 'Parallels', 'QCOW', 'iSCSI']; + self.asDiskFormats = self.asDiskFormatsDef; + self.asDiskVariantsDef = ['Dynamic', 'Fixed', 'DynamicSplit2G', 'FixedSplit2G', 'Network']; + self.asDiskVariants = self.asDiskVariantsDef; + self.asTestsDef = ['ioperf']; + self.asTests = self.asTestsDef; + self.asTestSetsDef = ['Fast', 'Functionality', 'Benchmark', 'Stress']; + self.asTestSets = self.asTestSetsDef; + self.asIscsiTargetsDef = [ ]; # @todo: Configure one target for basic iSCSI testing + self.asIscsiTargets = self.asIscsiTargetsDef; + self.cDiffLvlsDef = 0; + self.cDiffLvls = self.cDiffLvlsDef; + self.fTestHost = False; + self.fUseScratch = False; + self.fRecreateStorCfg = True; + self.fReportBenchmarkResults = True; + self.fTestRawMode = False; + self.oStorCfg = None; + self.sIoLogPathDef = self.sScratchPath; + self.sIoLogPath = self.sIoLogPathDef; + self.fIoLog = False; + self.fUseRamDiskDef = False; + self.fUseRamDisk = self.fUseRamDiskDef; + self.fEncryptDiskDef = False; + self.fEncryptDisk = self.fEncryptDiskDef; + self.sEncryptPwDef = 'TestTestTest'; + self.sEncryptPw = self.sEncryptPwDef; + self.sEncryptAlgoDef = 'AES-XTS256-PLAIN64'; + self.sEncryptAlgo = self.sEncryptAlgoDef; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdStorageBenchmark1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --storage-ctrls <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asStorageCtrlsDef))); + reporter.log(' --host-io-cache <setting1[:setting2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asHostIoCacheDef))); + reporter.log(' --disk-formats <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskFormatsDef))); + reporter.log(' --disk-variants <variant1[:variant2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskVariantsDef))); + reporter.log(' --iscsi-targets <target1[:target2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asIscsiTargetsDef))); + reporter.log(' --tests <test1[:test2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asTestsDef))); + reporter.log(' --test-sets <set1[:set2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asTestSetsDef))); + reporter.log(' --diff-levels <number of diffs>'); + reporter.log(' Default: %s' % (self.cDiffLvlsDef)); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --test-host'); + reporter.log(' Do all configured tests on the host first and report the results'); + reporter.log(' to get a baseline'); + reporter.log(' --use-scratch'); + reporter.log(' Use the scratch directory for testing instead of setting up'); + reporter.log(' fresh volumes on dedicated disks (for development)'); + reporter.log(' --always-wipe-storage-cfg'); + reporter.log(' Recreate the host storage config before each test'); + reporter.log(' --dont-wipe-storage-cfg'); + reporter.log(' Don\'t recreate the host storage config before each test'); + reporter.log(' --report-benchmark-results'); + reporter.log(' Report all benchmark results'); + reporter.log(' --dont-report-benchmark-results'); + reporter.log(' Don\'t report any benchmark results'); + reporter.log(' --io-log-path <path>'); + reporter.log(' Default: %s' % (self.sIoLogPathDef)); + reporter.log(' --enable-io-log'); + reporter.log(' Whether to enable I/O logging for each test'); + reporter.log(' --use-ramdisk'); + reporter.log(' Default: %s' % (self.fUseRamDiskDef)); + reporter.log(' --encrypt-disk'); + reporter.log(' Default: %s' % (self.fEncryptDiskDef)); + reporter.log(' --encrypt-password'); + reporter.log(' Default: %s' % (self.sEncryptPwDef)); + reporter.log(' --encrypt-algorithm'); + reporter.log(' Default: %s' % (self.sEncryptAlgoDef)); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--storage-ctrls': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--storage-ctrls" takes a colon separated list of Storage controller types'); + self.asStorageCtrls = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--host-io-cache': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--host-io-cache" takes a colon separated list of I/O cache settings'); + self.asHostIoCache = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-formats': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-formats" takes a colon separated list of disk formats'); + self.asDiskFormats = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-variants': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--disk-variants" takes a colon separated list of disk variants'); + self.asDiskVariants = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--iscsi-targets': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--iscsi-targets" takes a colon separated list of iscsi targets'); + self.asIscsiTargets = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of tests to run'); + self.asTests = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--test-sets': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-sets" takes a colon separated list of test sets'); + self.asTestSets = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--diff-levels': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--diff-levels" takes an integer'); + try: self.cDiffLvls = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--diff-levels" value "%s" is not an integer' % (asArgs[iArg],)); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--test-host': + self.fTestHost = True; + elif asArgs[iArg] == '--use-scratch': + self.fUseScratch = True; + elif asArgs[iArg] == '--always-wipe-storage-cfg': + self.fRecreateStorCfg = True; + elif asArgs[iArg] == '--dont-wipe-storage-cfg': + self.fRecreateStorCfg = False; + elif asArgs[iArg] == '--report-benchmark-results': + self.fReportBenchmarkResults = True; + elif asArgs[iArg] == '--dont-report-benchmark-results': + self.fReportBenchmarkResults = False; + elif asArgs[iArg] == '--io-log-path': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--io-log-path" takes a path argument'); + self.sIoLogPath = asArgs[iArg]; + elif asArgs[iArg] == '--enable-io-log': + self.fIoLog = True; + elif asArgs[iArg] == '--use-ramdisk': + self.fUseRamDisk = True; + elif asArgs[iArg] == '--encrypt-disk': + self.fEncryptDisk = True; + elif asArgs[iArg] == '--encrypt-password': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--encrypt-password" takes a string'); + self.sEncryptPw = asArgs[iArg]; + elif asArgs[iArg] == '--encrypt-algorithm': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--encrypt-algorithm" takes a string'); + self.sEncryptAlgo = asArgs[iArg]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-storage' in self.asTestVMs: + self.asRsrcs.append('5.0/storage/tst-storage.vdi'); + if 'tst-storage32' in self.asTestVMs: + self.asRsrcs.append('5.0/storage/tst-storage32.vdi'); + + return self.asRsrcs; + + def actionConfig(self): + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the VMs we're going to use. + # + + # Linux VMs + if 'tst-storage' in self.asTestVMs: + oVM = self.createTestVM('tst-storage', 1, '5.0/storage/tst-storage.vdi', sKind = 'ArchLinux_64', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = vboxcon.NetworkAdapterType_Am79C973, \ + sDvdImage = self.sVBoxValidationKitIso); + if oVM is None: + return False; + + if 'tst-storage32' in self.asTestVMs: + oVM = self.createTestVM('tst-storage32', 1, '5.0/storage/tst-storage32.vdi', sKind = 'ArchLinux', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = vboxcon.NetworkAdapterType_Am79C973, \ + sDvdImage = self.sVBoxValidationKitIso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + + # + # Test execution helpers. + # + + def prepareStorage(self, oStorCfg, fRamDisk = False, cbPool = None): + """ + Prepares the host storage for disk images or direct testing on the host. + """ + # Create a basic pool with the default configuration. + sMountPoint = None; + fRc, sPoolId = oStorCfg.createStoragePool(cbPool = cbPool, fRamDisk = fRamDisk); + if fRc: + fRc, sMountPoint = oStorCfg.createVolume(sPoolId); + if not fRc: + sMountPoint = None; + oStorCfg.cleanup(); + + return sMountPoint; + + def cleanupStorage(self, oStorCfg): + """ + Cleans up any created storage space for a test. + """ + return oStorCfg.cleanup(); + + def getGuestDisk(self, oSession, oTxsSession, eStorageController): + """ + Gets the path of the disk in the guest to use for testing. + """ + lstDisks = None; + + # The naming scheme for NVMe is different and we don't have + # to query the guest for unformatted disks here because the disk with the OS + # is not attached to a NVMe controller. + if eStorageController == vboxcon.StorageControllerType_NVMe: + lstDisks = [ '/dev/nvme0n1' ]; + else: + # Find a unformatted disk (no partition). + # @todo: This is a hack because LIST and STAT are not yet implemented + # in TXS (get to this eventually) + lstBlkDev = [ '/dev/sda', '/dev/sdb' ]; + for sBlkDev in lstBlkDev: + fRc = oTxsSession.syncExec('/usr/bin/ls', ('ls', sBlkDev + '1')); + if not fRc: + lstDisks = [ sBlkDev ]; + break; + + _ = oSession; + return lstDisks; + + def mountValidationKitIso(self, oVmExec): + """ + Hack to get the vlaidation kit ISO mounted in the guest as it was left out + originally and I don't feel like respinning the disk image. + """ + fRc = oVmExec.mkDir('/media'); + if fRc: + fRc = oVmExec.mkDir('/media/cdrom'); + if fRc: + fRc = oVmExec.execBinaryNoStdOut('mount', ('/dev/sr0', '/media/cdrom')); + + return fRc; + + def getDiskFormatVariantsForTesting(self, sDiskFmt, asVariants): + """ + Returns a list of disk variants for testing supported by the given + disk format and selected for testing. + """ + lstDskFmts = self.oVBoxMgr.getArray(self.oVBox.systemProperties, 'mediumFormats'); + for oDskFmt in lstDskFmts: + if oDskFmt.id == sDiskFmt: + lstDskVariants = []; + lstCaps = self.oVBoxMgr.getArray(oDskFmt, 'capabilities'); + + if vboxcon.MediumFormatCapabilities_CreateDynamic in lstCaps \ + and 'Dynamic' in asVariants: + lstDskVariants.append('Dynamic'); + + if vboxcon.MediumFormatCapabilities_CreateFixed in lstCaps \ + and 'Fixed' in asVariants: + lstDskVariants.append('Fixed'); + + if vboxcon.MediumFormatCapabilities_CreateSplit2G in lstCaps \ + and vboxcon.MediumFormatCapabilities_CreateDynamic in lstCaps \ + and 'DynamicSplit2G' in asVariants: + lstDskVariants.append('DynamicSplit2G'); + + if vboxcon.MediumFormatCapabilities_CreateSplit2G in lstCaps \ + and vboxcon.MediumFormatCapabilities_CreateFixed in lstCaps \ + and 'FixedSplit2G' in asVariants: + lstDskVariants.append('FixedSplit2G'); + + if vboxcon.MediumFormatCapabilities_TcpNetworking in lstCaps \ + and 'Network' in asVariants: + lstDskVariants.append('Network'); # Solely for iSCSI to get a non empty list + + return lstDskVariants; + + return []; + + def convDiskToMediumVariant(self, sDiskVariant): + """ + Returns a tuple of medium variant flags matching the given disk variant. + """ + tMediumVariant = None; + if sDiskVariant == 'Dynamic': + tMediumVariant = (vboxcon.MediumVariant_Standard, ); + elif sDiskVariant == 'Fixed': + tMediumVariant = (vboxcon.MediumVariant_Fixed, ); + elif sDiskVariant == 'DynamicSplit2G': + tMediumVariant = (vboxcon.MediumVariant_Standard, vboxcon.MediumVariant_VmdkSplit2G); + elif sDiskVariant == 'FixedSplit2G': + tMediumVariant = (vboxcon.MediumVariant_Fixed, vboxcon.MediumVariant_VmdkSplit2G); + + return tMediumVariant; + + def getStorageCtrlFromName(self, sStorageCtrl): + """ + Resolves the storage controller string to the matching constant. + """ + eStorageCtrl = None; + + if sStorageCtrl == 'AHCI': + eStorageCtrl = vboxcon.StorageControllerType_IntelAhci; + elif sStorageCtrl == 'IDE': + eStorageCtrl = vboxcon.StorageControllerType_PIIX4; + elif sStorageCtrl == 'LsiLogicSAS': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogicSas; + elif sStorageCtrl == 'LsiLogic': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogic; + elif sStorageCtrl == 'BusLogic': + eStorageCtrl = vboxcon.StorageControllerType_BusLogic; + elif sStorageCtrl == 'NVMe': + eStorageCtrl = vboxcon.StorageControllerType_NVMe; + elif sStorageCtrl == 'VirtIoScsi': + eStorageCtrl = vboxcon.StorageControllerType_VirtioSCSI; + + return eStorageCtrl; + + def getStorageDriverFromEnum(self, eStorageCtrl, fHardDisk): + """ + Returns the appropriate driver name for the given storage controller + and a flag whether the driver has the generic SCSI driver attached. + """ + if eStorageCtrl == vboxcon.StorageControllerType_IntelAhci: + if fHardDisk: + return ('ahci', False); + return ('ahci', True); + if eStorageCtrl == vboxcon.StorageControllerType_PIIX4: + return ('piix3ide', False); + if eStorageCtrl == vboxcon.StorageControllerType_LsiLogicSas: + return ('lsilogicsas', True); + if eStorageCtrl == vboxcon.StorageControllerType_LsiLogic: + return ('lsilogicscsi', True); + if eStorageCtrl == vboxcon.StorageControllerType_BusLogic: + return ('buslogic', True); + if eStorageCtrl == vboxcon.StorageControllerType_NVMe: + return ('nvme', False); + if eStorageCtrl == vboxcon.StorageControllerType_VirtioSCSI: + return ('virtio-scsi', True); + + return ('<invalid>', False); + + def isTestCfgSupported(self, asTestCfg): + """ + Returns whether a specific test config is supported. + """ + + # Check whether the disk variant is supported by the selected format. + asVariants = self.getDiskFormatVariantsForTesting(asTestCfg[self.kiDiskFmt], [ asTestCfg[self.kiDiskVar] ]); + if not asVariants: + return False; + + # For iSCSI check whether we have targets configured. + if asTestCfg[self.kiDiskFmt] == 'iSCSI' and not self.asIscsiTargets: + return False; + + # Check for virt mode, CPU count and selected VM. + if asTestCfg[self.kiVirtMode] == 'raw' \ + and ( asTestCfg[self.kiCpuCount] > 1 \ + or asTestCfg[self.kiVmName] == 'tst-storage' \ + or not self.fTestRawMode): + return False; + + # IDE does not support the no host I/O cache setting + if asTestCfg[self.kiHostIoCache] == 'no-hostiocache' \ + and asTestCfg[self.kiStorageCtrl] == 'IDE': + return False; + + return True; + + def fnFormatCpuString(self, cCpus): + """ + Formats the CPU count to be readable. + """ + if cCpus == 1: + return '1 cpu'; + return '%u cpus' % (cCpus); + + def fnFormatVirtMode(self, sVirtMode): + """ + Formats the virtualization mode to be a little less cryptic for use in test + descriptions. + """ + return self.kdVirtModeDescs[sVirtMode]; + + def fnFormatHostIoCache(self, sHostIoCache): + """ + Formats the host I/O cache mode to be a little less cryptic for use in test + descriptions. + """ + return self.kdHostIoCacheDescs[sHostIoCache]; + + def testBenchmark(self, sTargetOs, sBenchmark, sMountpoint, oExecutor, dTestSet, \ + cMsTimeout = 3600000): + """ + Runs the given benchmark on the test host. + """ + + dTestSet['FilePath'] = sMountpoint; + dTestSet['TargetOs'] = sTargetOs; + + oTst = None; + if sBenchmark == 'iozone': + oTst = IozoneTest(oExecutor, dTestSet); + elif sBenchmark == 'fio': + oTst = FioTest(oExecutor, dTestSet); # pylint: disable=redefined-variable-type + elif sBenchmark == 'ioperf': + oTst = IoPerfTest(oExecutor, dTestSet); # pylint: disable=redefined-variable-type + + if oTst is not None: + fRc = oTst.prepare(); + if fRc: + fRc = oTst.run(cMsTimeout); + if fRc: + if self.fReportBenchmarkResults: + fRc = oTst.reportResult(); + else: + reporter.testFailure('Running the testcase failed'); + reporter.addLogString(oTst.getErrorReport(), sBenchmark + '.log', + 'log/release/client', 'Benchmark raw output'); + else: + reporter.testFailure('Preparing the testcase failed'); + + oTst.cleanup(); + + return fRc; + + def createHd(self, oSession, sDiskFormat, sDiskVariant, iDiffLvl, oHdParent, \ + sDiskPath, cbDisk): + """ + Creates a new disk with the given parameters returning the medium object + on success. + """ + + oHd = None; + if sDiskFormat == "iSCSI" and iDiffLvl == 0: + listNames = []; + listValues = []; + listValues = self.asIscsiTargets[0].split('|'); + listNames.append('TargetAddress'); + listNames.append('TargetName'); + listNames.append('LUN'); + + if self.fpApiVer >= 5.0: + oHd = oSession.oVBox.createMedium(sDiskFormat, sDiskPath, vboxcon.AccessMode_ReadWrite, \ + vboxcon.DeviceType_HardDisk); + else: + oHd = oSession.oVBox.createHardDisk(sDiskFormat, sDiskPath); + oHd.type = vboxcon.MediumType_Normal; + oHd.setProperties(listNames, listValues); + else: + if iDiffLvl == 0: + tMediumVariant = self.convDiskToMediumVariant(sDiskVariant); + oHd = oSession.createBaseHd(sDiskPath + '/base.img', sDiskFormat, cbDisk, \ + cMsTimeout = 3600 * 1000, tMediumVariant = tMediumVariant); + else: + sDiskPath = sDiskPath + '/diff_%u.img' % (iDiffLvl); + oHd = oSession.createDiffHd(oHdParent, sDiskPath, None); + + if oHd is not None and iDiffLvl == 0 and self.fEncryptDisk: + try: + oIProgress = oHd.changeEncryption('', self.sEncryptAlgo, self.sEncryptPw, self.ksPwId); + oProgress = vboxwrappers.ProgressWrapper(oIProgress, self.oVBoxMgr, self, 'Encrypting "%s"' % (sDiskPath,)); + oProgress.wait(60*60000); # Wait for up to one hour, fixed disks take longer to encrypt. + if oProgress.logResult() is False: + raise base.GenError('Encrypting disk "%s" failed' % (sDiskPath, )); + except: + reporter.errorXcpt('changeEncryption("%s","%s","%s") failed on "%s"' \ + % ('', self.sEncryptAlgo, self.sEncryptPw, oSession.sName) ); + self.oVBox.deleteHdByMedium(oHd); + oHd = None; + else: + reporter.log('Encrypted "%s"' % (sDiskPath,)); + + return oHd; + + def startVmAndConnect(self, sVmName): + """ + Our own implementation of startVmAndConnectToTxsViaTcp to make it possible + to add passwords to a running VM when encryption is used. + """ + oSession = self.startVmByName(sVmName); + if oSession is not None: + # Add password to the session in case encryption is used. + fRc = True; + if self.fEncryptDisk: + try: + if self.fpApiVer >= 7.0: + oSession.o.console.addEncryptionPassword(self.ksPwId, self.sEncryptPw, False); + else: + oSession.o.console.addDiskEncryptionPassword(self.ksPwId, self.sEncryptPw, False); + except: + reporter.logXcpt(); + fRc = False; + + # Connect to TXS. + if fRc: + reporter.log2('startVmAndConnect: Started(/prepared) "%s", connecting to TXS ...' % (sVmName,)); + (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 15*60000, fNatForwardingForTxs = True); + if fRc is True: + if fRc is True: + # Success! + return (oSession, oTxsSession); + else: + reporter.error('startVmAndConnect: txsDoConnectViaTcp failed'); + # If something went wrong while waiting for TXS to be started - take VM screenshot before terminate it + + self.terminateVmBySession(oSession); + + return (None, None); + + def testOneCfg(self, sVmName, eStorageController, sHostIoCache, sDiskFormat, # pylint: disable=too-many-arguments,too-many-locals,too-many-statements + sDiskVariant, sDiskPath, cCpus, sIoTest, sVirtMode, sTestSet): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + dTestSet = self.kdTestSets.get(sTestSet); + cbDisk = dTestSet.get('DiskSizeGb') * 1024*1024*1024; + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + + fRc = True; + if sDiskFormat == 'iSCSI': + sDiskPath = self.asIscsiTargets[0]; + elif self.fUseScratch: + sDiskPath = self.sScratchPath; + else: + # If requested recreate the storage space to start with a clean config + # for benchmarks + if self.fRecreateStorCfg: + sMountPoint = self.prepareStorage(self.oStorCfg, self.fUseRamDisk, 2 * cbDisk); + if sMountPoint is not None: + # Create a directory where every normal user can write to. + self.oStorCfg.mkDirOnVolume(sMountPoint, 'test', 0o777); + sDiskPath = sMountPoint + '/test'; + else: + fRc = False; + reporter.testFailure('Failed to prepare storage for VM'); + + if not fRc: + return fRc; + + lstDisks = []; # List of disks we have to delete afterwards. + + for iDiffLvl in range(self.cDiffLvls + 1): + sIoLogFile = None; + + if iDiffLvl == 0: + reporter.testStart('Base'); + else: + reporter.testStart('Diff %u' % (iDiffLvl)); + + # Reconfigure the VM + oSession = self.openSession(oVM); + if oSession is not None: + # + # Disable audio controller which shares the interrupt line with the BusLogic controller and is suspected to cause + # rare test failures because the device initialization fails. + # + fRc = oSession.setupAudio(vboxcon.AudioControllerType_AC97, False); + # Attach HD + fRc = fRc and oSession.ensureControllerAttached(self.controllerTypeToName(eStorageController)); + fRc = fRc and oSession.setStorageControllerType(eStorageController, + self.controllerTypeToName(eStorageController)); + + if sHostIoCache == 'hostiocache': + fRc = fRc and oSession.setStorageControllerHostIoCache(self.controllerTypeToName(eStorageController), True); + elif sHostIoCache == 'no-hostiocache': + fRc = fRc and oSession.setStorageControllerHostIoCache(self.controllerTypeToName(eStorageController), False); + + iDevice = 0; + if eStorageController in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + iDevice = 1; # Master is for the OS. + + oHdParent = None; + if iDiffLvl > 0: + oHdParent = lstDisks[0]; + oHd = self.createHd(oSession, sDiskFormat, sDiskVariant, iDiffLvl, oHdParent, sDiskPath, cbDisk); + if oHd is not None: + lstDisks.insert(0, oHd); + try: + if oSession.fpApiVer >= 4.0: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 0, iDevice, vboxcon.DeviceType_HardDisk, oHd); + else: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 0, iDevice, vboxcon.DeviceType_HardDisk, oHd.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (self.controllerTypeToName(eStorageController), 1, 0, oHd.id, oSession.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sDiskPath, oSession.sName)); + else: + fRc = False; + + # Set up the I/O logging config if enabled + if fRc and self.fIoLog: + try: + oSession.o.machine.setExtraData('VBoxInternal2/EnableDiskIntegrityDriver', '1'); + + iLun = 0; + if eStorageController in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + iLun = 1 + sDrv, fDrvScsi = self.getStorageDriverFromEnum(eStorageController, True); + if fDrvScsi: + sCfgmPath = 'VBoxInternal/Devices/%s/0/LUN#%u/AttachedDriver/Config' % (sDrv, iLun); + else: + sCfgmPath = 'VBoxInternal/Devices/%s/0/LUN#%u/Config' % (sDrv, iLun); + + sIoLogFile = '%s/%s.iolog' % (self.sIoLogPath, sDrv); + print(sCfgmPath); + print(sIoLogFile); + oSession.o.machine.setExtraData('%s/IoLog' % (sCfgmPath,), sIoLogFile); + except: + reporter.logXcpt(); + + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnect(sVmName); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + # Prepare the storage on the guest + lstBinaryPaths = ['/bin', '/sbin', '/usr/bin', '/usr/sbin' ]; + oExecVm = remoteexecutor.RemoteExecutor(oTxsSession, lstBinaryPaths, '${SCRATCH}'); + fRc = self.mountValidationKitIso(oExecVm); + if fRc: + oGstDiskCfg = storagecfg.DiskCfg('linux', storagecfg.g_ksDiskCfgList, + self.getGuestDisk(oSession, oTxsSession, eStorageController)); + oStorCfgVm = storagecfg.StorageCfg(oExecVm, oGstDiskCfg); + + iTry = 0; + while iTry < 3: + sMountPoint = self.prepareStorage(oStorCfgVm); + if sMountPoint is not None: + reporter.log('Prepared storage on %s try' % (iTry + 1,)); + break; + iTry = iTry + 1; + self.sleep(5); + + if sMountPoint is not None: + # 3 hours max (Benchmark and QED takes a lot of time) + self.testBenchmark('linux', sIoTest, sMountPoint, oExecVm, dTestSet, cMsTimeout = 3 * 3600 * 1000); + self.cleanupStorage(oStorCfgVm); + else: + reporter.testFailure('Failed to prepare storage for the guest benchmark'); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession); + + # Add the I/O log if it exists and the test failed + if reporter.testErrorCount() > 0 \ + and sIoLogFile is not None \ + and os.path.exists(sIoLogFile): + reporter.addLogFile(sIoLogFile, 'misc/other', 'I/O log'); + os.remove(sIoLogFile); + else: + reporter.testFailure('Failed to mount validation kit ISO'); + + else: + fRc = False; + + # Remove disk + oSession = self.openSession(oVM); + if oSession is not None: + try: + oSession.o.machine.detachDevice(self.controllerTypeToName(eStorageController), 0, iDevice); + + # Remove storage controller if it is not an IDE controller. + if eStorageController not in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + oSession.o.machine.removeStorageController(self.controllerTypeToName(eStorageController)); + + oSession.saveSettings(); + oSession.saveSettings(); + oSession.close(); + oSession = None; + except: + reporter.errorXcpt('failed to detach/delete disk %s from storage controller' % (sDiskPath)); + else: + fRc = False; + + reporter.testDone(); + + # Delete all disks + for oHd in lstDisks: + self.oVBox.deleteHdByMedium(oHd); + + # Cleanup storage area + if sDiskFormat != 'iSCSI' and not self.fUseScratch and self.fRecreateStorCfg: + self.cleanupStorage(self.oStorCfg); + + return fRc; + + def testStorage(self, sDiskPath = None): + """ + Runs the storage testcase through the selected configurations + """ + + aasTestCfgs = []; + aasTestCfgs.insert(self.kiVmName, self.asTestVMs); + aasTestCfgs.insert(self.kiStorageCtrl, self.asStorageCtrls); + aasTestCfgs.insert(self.kiHostIoCache, (self.asHostIoCache, True, self.fnFormatHostIoCache)); + aasTestCfgs.insert(self.kiDiskFmt, self.asDiskFormats); + aasTestCfgs.insert(self.kiDiskVar, self.asDiskVariants); + aasTestCfgs.insert(self.kiCpuCount, (self.acCpus, True, self.fnFormatCpuString)); + aasTestCfgs.insert(self.kiVirtMode, (self.asVirtModes, True, self.fnFormatVirtMode)); + aasTestCfgs.insert(self.kiTestSet, self.asTestSets); + aasTestCfgs.insert(self.kiIoTest, (self.asTests, False, None)); + + aasTestsBlacklist = []; + aasTestsBlacklist.append(['tst-storage', 'BusLogic']); # 64bit Linux is broken with BusLogic + + oTstCfgMgr = StorTestCfgMgr(aasTestCfgs, aasTestsBlacklist, self.isTestCfgSupported); + + fRc = True; + asTestCfg = oTstCfgMgr.getCurrentTestCfg(); + while asTestCfg: + fRc = self.testOneCfg(asTestCfg[self.kiVmName], self.getStorageCtrlFromName(asTestCfg[self.kiStorageCtrl]), \ + asTestCfg[self.kiHostIoCache], asTestCfg[self.kiDiskFmt], asTestCfg[self.kiDiskVar], + sDiskPath, asTestCfg[self.kiCpuCount], asTestCfg[self.kiIoTest], \ + asTestCfg[self.kiVirtMode], asTestCfg[self.kiTestSet]) and fRc and True; # pychecker hack. + + asTestCfg = oTstCfgMgr.getNextTestCfg(); + + return fRc; + + def test1(self): + """ + Executes test #1. + """ + + fRc = True; + tupTstCfg = self.kdStorageCfgs.get(socket.getfqdn().lower()); + if tupTstCfg is None: + tupTstCfg = self.kdStorageCfgs.get(socket.gethostname().lower()); + + # Test the host first if requested + if tupTstCfg is not None or self.fUseScratch: + self.fTestRawMode = tupTstCfg[0]; + oDiskCfg = tupTstCfg[1]; + lstBinaryPaths = ['/bin', '/sbin', '/usr/bin', '/usr/sbin', \ + '/opt/csw/bin', '/usr/ccs/bin', '/usr/sfw/bin']; + oExecutor = remoteexecutor.RemoteExecutor(None, lstBinaryPaths, self.sScratchPath); + if not self.fUseScratch: + self.oStorCfg = storagecfg.StorageCfg(oExecutor, oDiskCfg); + + # Try to cleanup any leftovers from a previous run first. + fRc = self.oStorCfg.cleanupLeftovers(); + if not fRc: + reporter.error('Failed to cleanup any leftovers from a previous run'); + + if self.fTestHost: + reporter.testStart('Host'); + if self.fUseScratch: + sMountPoint = self.sScratchPath; + else: + sMountPoint = self.prepareStorage(self.oStorCfg); + if sMountPoint is not None: + for sIoTest in self.asTests: + for sTestSet in self.asTestSets: + reporter.testStart(sTestSet); + dTestSet = self.kdTestSets.get(sTestSet); + self.testBenchmark(utils.getHostOs(), sIoTest, sMountPoint, oExecutor, dTestSet); + reporter.testDone(); + self.cleanupStorage(self.oStorCfg); + else: + reporter.testFailure('Failed to prepare host storage'); + fRc = False; + reporter.testDone(); + else: + # Create the storage space first if it is not done before every test. + sMountPoint = None; + if self.fUseScratch: + sMountPoint = self.sScratchPath; + elif not self.fRecreateStorCfg: + reporter.testStart('Create host storage'); + sMountPoint = self.prepareStorage(self.oStorCfg); + if sMountPoint is None: + reporter.testFailure('Failed to prepare host storage'); + fRc = False; + self.oStorCfg.mkDirOnVolume(sMountPoint, 'test', 0o777); + sMountPoint = sMountPoint + '/test'; + reporter.testDone(); + + if fRc: + # Run the storage tests. + if not self.testStorage(sMountPoint): + fRc = False; + + if not self.fRecreateStorCfg and not self.fUseScratch: + self.cleanupStorage(self.oStorCfg); + else: + reporter.testFailure('Could not get disk configuration for host: %s' % (socket.getfqdn().lower())); + fRc = False; + + return fRc; + +if __name__ == '__main__': + sys.exit(tdStorageBenchmark().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py b/src/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py new file mode 100755 index 00000000..482453de --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py @@ -0,0 +1,1692 @@ + +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +VirtualBox Validation Kit - VMDK raw disk tests. +""" + +__copyright__ = \ +""" +Copyright (C) 2013-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__ = "$Id: tdStorageRawDrive1.py $" + +# Standard Python imports. +import os; +import re; +import sys; + +# 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 reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxtestvms; +from testdriver import vboxwrappers; + + +class tdStorageRawDriveOs(vboxtestvms.BaseTestVm): + """ + Base autostart helper class to provide common methods. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + vboxtestvms.BaseTestVm.__init__(self, sVmName, oSet = oSet, sKind = sKind); + self.oTstDrv = oTstDrv; + self.sHdd = sHdd; + self.eNic0Type = eNic0Type; + self.cMbRam = cMbRam; + self.cCpus = cCpus; + self.fPae = fPae; + self.sGuestAdditionsIso = sGuestAdditionsIso; + self.asTestBuildDirs = oTstDrv.asTestBuildDirs; + self.sVBoxInstaller = ""; + self.sVMDKPath='/home/vbox/vmdk'; + self.asVirtModesSup = ['hwvirt-np',]; + self.asParavirtModesSup = ['default',]; + self.sBootSector = sBootSector; + self.sPathDelimiter = '/'; + + # Had to move it here from oTestDrv because the output is platform-dependent + self.asHdds = \ + { '6.1/storage/t-mbr.vdi' : + { + 'Header' : + { + #Drive: /dev/sdb + 'Model' : '"ATA VBOX HARDDISK"', + 'UUID' : '62d4f394-0000-0000-0000-000000000000', + 'Size' : '2.0GiB', + 'Sector Size' : '512 bytes', + 'Scheme' : 'MBR', + }, + 'Partitions' : + { + 'Partitions' : + [ + '$(1) 07 10.0MiB 1.0MiB 0/ 32/33 1/102/37 no IFS', + '$(2) 83 10.0MiB 11.0MiB 5/ 93/33 11/ 29/14 no Linux', + '$(3) 07 10.0MiB 21.0MiB 2/172/43 3/242/47 no IFS', + '$(4) 07 10.0MiB 32.0MiB 4/ 20/17 5/ 90/21 no IFS', + '$(5) 83 10.0MiB 43.0MiB 5/122/54 6/192/58 no Linux', + '$(6) 07 10.0MiB 54.0MiB 6/225/28 8/ 40/32 no IFS', + '$(7) 83 10.0MiB 65.0MiB 8/ 73/ 2 9/143/ 6 no Linux', + '$(8) 07 1.9GiB 76.0MiB 9/175/39 260/243/47 no IFS', + ], + 'PartitionNumbers' : [1, 2, 3, 5, 6, 7, 8, 9], + }, + } , + '6.1/storage/t-gpt.vdi' : + { + 'Header' : + { + #Drive: /dev/sdc + 'Model' : '"ATA VBOX HARDDISK"', + 'UUID' : '7b642ab1-9d44-b844-a860-ce71e0686274', + 'Size' : '2.0GiB', + 'Sector Size' : '512 bytes', + 'Scheme' : 'GPT', + }, + 'Partitions' : + { + 'Partitions' : + [ + '$(1) WindowsBasicData 560b261d-081f-fb4a-8df8-c64fffcb2bd1 10.0MiB 1.0MiB off', + '$(2) LinuxData 629f66be-0254-7c4f-a328-cc033e4de124 10.0MiB 11.0MiB off', + '$(3) WindowsBasicData d3f56c96-3b28-7f44-a53d-85b8bc93bd91 10.0MiB 21.0MiB off', + '$(4) LinuxData 27c0f5ad-74c8-d54f-835f-06e51b3f10ef 10.0MiB 31.0MiB off', + '$(5) WindowsBasicData 6cf1fdf0-b2ae-3849-9cfa-c056f9d8b722 10.0MiB 41.0MiB off', + '$(6) LinuxData 017bcbed-8b96-be4d-925a-2f872194fbe6 10.0MiB 51.0MiB off', + '$(7) WindowsBasicData af6c4f89-8fc3-5049-9d98-3e2e98061073 10.0MiB 61.0MiB off', + '$(8) LinuxData 9704d7cd-810f-4d44-ac78-432ebc16143f 10.0MiB 71.0MiB off', + '$(9) WindowsBasicData a05f8e09-f9e7-5b4e-bb4e-e9f8fde3110e 1.9GiB 81.0MiB off', + ], + 'PartitionNumbers' : [1, 2, 3, 4, 5, 6, 7, 8, 9], + }, + + } + }; + self.asActions = \ + [ + { + 'action' : 'whole drive', + 'options' : [], + 'data-crc' : {}, + 'createType' : 'fullDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : ['RW 0 FLAT "$(disk)" 0',], + '6.1/storage/t-gpt.vdi' : ['RW 0 FLAT "$(disk)" 0',], + }, + }, + { + 'action' : '1 partition', + 'options' : ['--property', 'Partitions=1'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '2 partitions', + 'options' : ['--property', 'Partitions=1,$(4)'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 FLAT "$(disk)" 65536', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 FLAT "$(disk)" 63488', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '1 partition with boot sector', + 'options' : ['--property', 'Partitions=1', + '--property-file', 'BootSector=$(bootsector)'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 3980784439, + '6.1/storage/t-gpt.vdi' : 1152317131, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '2 partitions with boot sector', + 'options' : ['--property', 'Partitions=1,$(4)', + '--property-file', 'BootSector=$(bootsector)'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 3980784439, + '6.1/storage/t-gpt.vdi' : 1152317131, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 FLAT "$(disk)" 65536', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(disk)" 2048', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 FLAT "$(disk)" 63488', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '1 partition with relative names', + 'options' : ['--property', 'Partitions=1', '--property', 'Relative=1'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + { + 'action' : '2 partitions with relative names', + 'options' : ['--property', 'Partitions=1,$(4)', '--property', 'Relative=1'], + 'data-crc' : {'6.1/storage/t-mbr.vdi' : 2681429243, + '6.1/storage/t-gpt.vdi' : 1391394051, + }, + 'createType' : 'partitionedDevice', + 'extents' : { '6.1/storage/t-mbr.vdi' : + ['RW 2048 FLAT "vmdktest-pt.vmdk" 0', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 2048', + 'RW 20480 FLAT "$(part)$(4)" 0', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 4096', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 6144', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 8192', + 'RW 20480 ZERO', + 'RW 2048 FLAT "vmdktest-pt.vmdk" 10240', + 'RW 4036608 ZERO', + 'RW 36028797014771712 ZERO', + ], + '6.1/storage/t-gpt.vdi' : + ['RW 1 FLAT "vmdktest-pt.vmdk" 0', + 'RW 2047 FLAT "vmdktest-pt.vmdk" 1', + 'RW 20480 FLAT "$(part)1" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 FLAT "$(part)$(4)" 0', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 20480 ZERO', + 'RW 4026368 ZERO', + 'RW 36028797014771712 ZERO', + ], + }, + }, + ]; + + + def _findFile(self, sRegExp, asTestBuildDirs): + """ + Returns a filepath based on the given regex and paths to look into + or None if no matching file is found. + """ + oRegExp = re.compile(sRegExp); + for sTestBuildDir in asTestBuildDirs: + try: + #return most recent file if there are several ones matching the pattern + asFiles = [s for s in os.listdir(sTestBuildDir) + if os.path.isfile(os.path.join(sTestBuildDir, s))]; + asFiles = (s for s in asFiles + if oRegExp.match(os.path.basename(s)) + and os.path.exists(sTestBuildDir + '/' + s)); + asFiles = sorted(asFiles, reverse = True, + key = lambda s, sTstBuildDir = sTestBuildDir: os.path.getmtime(os.path.join(sTstBuildDir, s))); + if asFiles: + return sTestBuildDir + '/' + asFiles[0]; + except: + pass; + reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, ','.join(asTestBuildDirs))); + return None; + + def _waitAdditionsIsRunning(self, oGuest, fWaitTrayControl): + """ + Check is the additions running + """ + cAttempt = 0; + fRc = False; + while cAttempt < 30: + fRc = oGuest.additionsRunLevel in [vboxcon.AdditionsRunLevelType_Userland, + vboxcon.AdditionsRunLevelType_Desktop]; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxService); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc and not fWaitTrayControl: + break; + if fRc: + eServiceStatus, _ = oGuest.getFacilityStatus(vboxcon.AdditionsFacilityType_VBoxTrayClient); + fRc = eServiceStatus == vboxcon.AdditionsFacilityStatus_Active; + if fRc: + break; + self.oTstDrv.sleep(10); + cAttempt += 1; + return fRc; + + def createSession(self, oSession, sName, sUser, sPassword, cMsTimeout = 10 * 1000, fIsError = True): + """ + Creates (opens) a guest session. + Returns (True, IGuestSession) on success or (False, None) on failure. + """ + oGuest = oSession.o.console.guest; + if sName is None: + sName = "<untitled>"; + reporter.log('Creating session "%s" ...' % (sName,)); + try: + oGuestSession = oGuest.createSession(sUser, sPassword, '', sName); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Creating a guest session "%s" failed; sUser="%s", pw="%s"' + % (sName, sUser, sPassword)); + return (False, None); + reporter.log('Waiting for session "%s" to start within %dms...' % (sName, cMsTimeout)); + aeWaitFor = [ vboxcon.GuestSessionWaitForFlag_Start, ]; + try: + waitResult = oGuestSession.waitForArray(aeWaitFor, cMsTimeout); + # + # Be nice to Guest Additions < 4.3: They don't support session handling and + # therefore return WaitFlagNotSupported. + # + if waitResult not in (vboxcon.GuestSessionWaitResult_Start, vboxcon.GuestSessionWaitResult_WaitFlagNotSupported): + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErr(fIsError, 'Session did not start successfully, returned wait result: %d' % (waitResult,)); + return (False, None); + reporter.log('Session "%s" successfully started' % (sName,)); + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Waiting for guest session "%s" (usr=%s;pw=%s) to start failed:' + % (sName, sUser, sPassword,)); + return (False, None); + return (True, oGuestSession); + + def closeSession(self, oGuestSession, fIsError = True): + """ + Closes the guest session. + """ + if oGuestSession is not None: + try: + sName = oGuestSession.name; + except: + return reporter.errorXcpt(); + reporter.log('Closing session "%s" ...' % (sName,)); + try: + oGuestSession.close(); + oGuestSession = None; + except: + # Just log, don't assume an error here (will be done in the main loop then). + reporter.maybeErrXcpt(fIsError, 'Closing guest session "%s" failed:' % (sName,)); + return False; + return True; + + def guestProcessExecute(self, oGuestSession, sTestName, cMsTimeout, sExecName, asArgs = (), + fGetStdOut = True, fIsError = True): + """ + Helper function to execute a program on a guest, specified in the current test. + Returns (True, ProcessStatus, ProcessExitCode, ProcessStdOutBuffer) on success or (False, 0, 0, None) on failure. + """ + _ = sTestName; + fRc = True; # Be optimistic. + reporter.log2('Using session user=%s, name=%s, timeout=%d' + % (oGuestSession.user, oGuestSession.name, oGuestSession.timeout,)); + # + # Start the process: + # + reporter.log2('Executing sCmd=%s, timeoutMS=%d, asArgs=%s' + % (sExecName, cMsTimeout, asArgs, )); + fTaskFlags = []; + if fGetStdOut: + fTaskFlags = [vboxcon.ProcessCreateFlag_WaitForStdOut, + vboxcon.ProcessCreateFlag_WaitForStdErr]; + try: + oProcess = oGuestSession.processCreate(sExecName, + asArgs if self.oTstDrv.fpApiVer >= 5.0 else asArgs[1:], + [], fTaskFlags, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'asArgs=%s' % (asArgs,)); + return (False, 0, 0, None); + if oProcess is None: + return (reporter.error('oProcess is None! (%s)' % (asArgs,)), 0, 0, None); + #time.sleep(5); # try this if you want to see races here. + # Wait for the process to start properly: + reporter.log2('Process start requested, waiting for start (%dms) ...' % (cMsTimeout,)); + iPid = -1; + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Start, ]; + aBuf = None; + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except: + reporter.maybeErrXcpt(fIsError, 'waitforArray failed for asArgs=%s' % (asArgs,)); + fRc = False; + else: + try: + eStatus = oProcess.status; + iPid = oProcess.PID; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + else: + reporter.log2('Wait result returned: %d, current process status is: %d' % (eWaitResult, eStatus,)); + # + # Wait for the process to run to completion if necessary. + # + # Note! The above eWaitResult return value can be ignored as it will + # (mostly) reflect the process status anyway. + # + if eStatus == vboxcon.ProcessStatus_Started: + # What to wait for: + aeWaitFor = [ vboxcon.ProcessWaitForFlag_Terminate, + vboxcon.ProcessWaitForFlag_StdOut, + vboxcon.ProcessWaitForFlag_StdErr]; + reporter.log2('Process (PID %d) started, waiting for termination (%dms), aeWaitFor=%s ...' + % (iPid, cMsTimeout, aeWaitFor)); + acbFdOut = [0,0,0]; + while True: + try: + eWaitResult = oProcess.waitForArray(aeWaitFor, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + break; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + break; + reporter.log2('Wait returned: %d' % (eWaitResult,)); + # Process output: + for eFdResult, iFd, sFdNm in [ (vboxcon.ProcessWaitResult_StdOut, 1, 'stdout'), + (vboxcon.ProcessWaitResult_StdErr, 2, 'stderr'), ]: + if eWaitResult in (eFdResult, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + reporter.log2('Reading %s ...' % (sFdNm,)); + try: + abBuf = oProcess.read(iFd, 64 * 1024, cMsTimeout); + except KeyboardInterrupt: # Not sure how helpful this is, but whatever. + reporter.error('Process (PID %d) execution interrupted' % (iPid,)); + try: oProcess.close(); + except: pass; + except: + pass; ## @todo test for timeouts and fail on anything else! + else: + if abBuf: + reporter.log2('Process (PID %d) got %d bytes of %s data' % (iPid, len(abBuf), sFdNm,)); + acbFdOut[iFd] += len(abBuf); + ## @todo Figure out how to uniform + append! + sBuf = ''; + if sys.version_info >= (2, 7) and isinstance(abBuf, memoryview): + abBuf = abBuf.tobytes(); + sBuf = abBuf.decode("utf-8"); + else: + sBuf = str(abBuf); + if aBuf: + aBuf += sBuf; + else: + aBuf = sBuf; + ## Process input (todo): + #if eWaitResult in (vboxcon.ProcessWaitResult_StdIn, vboxcon.ProcessWaitResult_WaitFlagNotSupported): + # reporter.log2('Process (PID %d) needs stdin data' % (iPid,)); + # Termination or error? + if eWaitResult in (vboxcon.ProcessWaitResult_Terminate, + vboxcon.ProcessWaitResult_Error, + vboxcon.ProcessWaitResult_Timeout,): + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) reported terminate/error/timeout: %d, status: %d' + % (iPid, eWaitResult, eStatus,)); + break; + # End of the wait loop. + _, cbStdOut, cbStdErr = acbFdOut; + try: eStatus = oProcess.status; + except: fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Final process status (PID %d) is: %d' % (iPid, eStatus)); + reporter.log2('Process (PID %d) %d stdout, %d stderr' % (iPid, cbStdOut, cbStdErr)); + # + # Get the final status and exit code of the process. + # + try: + uExitStatus = oProcess.status; + iExitCode = oProcess.exitCode; + except: + fRc = reporter.errorXcpt('asArgs=%s' % (asArgs,)); + reporter.log2('Process (PID %d) has exit code: %d; status: %d ' % (iPid, iExitCode, uExitStatus)); + return (fRc, uExitStatus, iExitCode, aBuf); + + def uploadString(self, oGuestSession, sSrcString, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + oFile = oGuestSession.fileOpenEx(sDst, vboxcon.FileAccessMode_ReadWrite, vboxcon.FileOpenAction_CreateOrReplace, + vboxcon.FileSharingMode_All, 0, []); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not create and open the file %s' % sDst); + else: + try: + oFile.write(bytearray(sSrcString), 60*1000); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not write the string into the file %s' % sDst); + try: + oFile.close(); + except: + fRc = reporter.errorXcpt('Upload string failed. Could not close the file %s' % sDst); + return fRc; + + def uploadFile(self, oGuestSession, sSrc, sDst): + """ + Upload the string into guest. + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyToGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyTo(sSrc, sDst, [0]); + except: + reporter.maybeErrXcpt(True, 'Upload file exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, self.oTstDrv, "uploadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + return fRc; + + def downloadFile(self, oGuestSession, sSrc, sDst, fIgnoreErrors = False): + """ + Get a file (sSrc) from the guest storing it on the host (sDst). + """ + fRc = True; + try: + if self.oTstDrv.fpApiVer >= 5.0: + oCurProgress = oGuestSession.fileCopyFromGuest(sSrc, sDst, [0]); + else: + oCurProgress = oGuestSession.copyFrom(sSrc, sDst, [0]); + except: + if not fIgnoreErrors: + reporter.errorXcpt('Download file exception for sSrc="%s":' % (sSrc,)); + else: + reporter.log('warning: Download file exception for sSrc="%s":' % (sSrc,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "downloadFile"); + oWrapperProgress.wait(); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors); + fRc = False; + else: + if not fIgnoreErrors: + reporter.error('No progress object returned'); + else: + reporter.log('warning: No progress object returned'); + fRc = False; + return fRc; + + def downloadFiles(self, oGuestSession, asFiles, fIgnoreErrors = False): + """ + Convenience function to get files from the guest and stores it + into the scratch directory for later (manual) review. + Returns True on success. + Returns False on failure, logged. + """ + fRc = True; + for sGstFile in asFiles: + ## @todo r=bird: You need to use the guest specific path functions here. + ## Best would be to add basenameEx to common/pathutils.py. See how joinEx + ## is used by BaseTestVm::pathJoin and such. + sTmpFile = os.path.join(self.oTstDrv.sScratchPath, 'tmp-' + os.path.basename(sGstFile)); + reporter.log2('Downloading file "%s" to "%s" ...' % (sGstFile, sTmpFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sTmpFile); + except: pass; + ## @todo Check for already existing files on the host and create a new + # name for the current file to download. + fRc = self.downloadFile(oGuestSession, sGstFile, sTmpFile, fIgnoreErrors); + if fRc: + reporter.addLogFile(sTmpFile, 'misc/other', 'guest - ' + sGstFile); + else: + if fIgnoreErrors is not True: + reporter.error('error downloading file "%s" to "%s"' % (sGstFile, sTmpFile)); + return fRc; + reporter.log('warning: file "%s" was not downloaded, ignoring.' % (sGstFile,)); + return True; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, '/sbin/ifconfig', + ['ifconfig',], + False, False); + return fRc; + + def waitVmIsReady(self, oSession, fWaitTrayControl): + """ + Waits the VM is ready after start or reboot. + Returns result (true or false) and guest session obtained + """ + _ = fWaitTrayControl; + # Give the VM a time to reboot + self.oTstDrv.sleep(30); + # Waiting the VM is ready. + # To do it, one will try to open the guest session and start the guest process in loop + if not self._waitAdditionsIsRunning(oSession.o.console.guest, False): + return (False, None); + cAttempt = 0; + oGuestSession = None; + fRc = False; + while cAttempt < 30: + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, False); + if fRc: + fRc = self._checkVmIsReady(oGuestSession); + if fRc: + break; + self.closeSession(oGuestSession, False); + self.oTstDrv.sleep(10); + cAttempt += 1; + return (fRc, oGuestSession); + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'reboot'], + False, True); + if not fRc: + reporter.error('Calling the reboot utility failed'); + return fRc; + + def rebootVMAndCheckReady(self, oSession, oGuestSession): + """ + Reboot the VM and wait the VM is ready. + Returns result and guest session obtained after reboot + """ + reporter.testStart('Reboot VM and wait for readiness'); + fRc = self._rebootVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + if not fRc: + reporter.error('VM is not ready after reboot'); + reporter.testDone(); + return (fRc, oGuestSession); + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, '/usr/bin/sudo', + ['sudo', 'poweroff'], + False, True); + if not fRc: + reporter.error('Calling the poweroff utility failed'); + return fRc; + + def powerDownVM(self, oGuestSession): + """ + Power down the VM by calling guest process without wating + the VM is really powered off. Also, closes the guest session. + It helps the terminateBySession to stop the VM without aborting. + """ + if oGuestSession is None: + return False; + reporter.testStart('Power down the VM'); + fRc = self._powerDownVM(oGuestSession); + fRc = self.closeSession(oGuestSession, True) and fRc and True; # pychecker hack. + if not fRc: + reporter.error('Power down the VM failed'); + reporter.testDone(); + return fRc; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oSession; + _ = oGuestSession; + _ = oVM; + reporter.error('Not implemented'); + return False; + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + _ = oGuestSession; + reporter.error('Not implemented'); + return False; + + def getResourceSet(self): + asRet = []; + if not os.path.isabs(self.sHdd): + asRet.append(self.sHdd); + return asRet; + + def _createVmDoIt(self, oTestDrv, eNic0AttachType, sDvdImage): + """ + Creates the VM. + Returns Wrapped VM object on success, None on failure. + """ + _ = eNic0AttachType; + _ = sDvdImage; + return oTestDrv.createTestVM(self.sVmName, self.iGroup, self.sHdd, sKind = self.sKind, \ + fIoApic = True, eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = self.eNic0Type, cMbRam = self.cMbRam, \ + sHddControllerType = "SATA Controller", fPae = self.fPae, \ + cCpus = self.cCpus, sDvdImage = self.sGuestAdditionsIso); + + def _createVmPost(self, oTestDrv, oVM, eNic0AttachType, sDvdImage): + _ = eNic0AttachType; + _ = sDvdImage; + fRc = True; + oSession = oTestDrv.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(True); + # nested paging doesn't need for the test + #fRc = fRc and oSession.enableNestedPaging(True); + #fRc = fRc and oSession.enableNestedHwVirt(True); + # disable 3D until the error is fixed. + fRc = fRc and oSession.setAccelerate3DEnabled(False); + fRc = fRc and oSession.setVRamSize(256); + fRc = fRc and oSession.setVideoControllerType(vboxcon.GraphicsControllerType_VBoxSVGA); + fRc = fRc and oSession.enableUsbOhci(True); + fRc = fRc and oSession.enableUsbHid(True); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + return oVM if fRc else None; + + def getReconfiguredVm(self, oTestDrv, cCpus, sVirtMode, sParavirtMode = None): + # + # Current test uses precofigured VMs. This override disables any changes in the machine. + # + _ = cCpus; + _ = sVirtMode; + _ = sParavirtMode; + oVM = oTestDrv.getVmByName(self.sVmName); + if oVM is None: + return (False, None); + return (True, oVM); + + def reattachHdd(self, oVM, sHdd, asHdds): + """ + Attach required hdd and remove all others from asHdds list. + """ + reporter.testStart("Reattach hdd"); + oSession = self.oTstDrv.openSession(oVM); + fRc = False; + if oSession is not None: + # for simplicity and because we are using VMs having "SATA controller" + # we will add the hdds to only "SATA controller" + iPortNew = 0; + fFound = False; + try: + aoAttachments = self.oTstDrv.oVBox.oVBoxMgr.getArray(oVM, 'mediumAttachments'); + except: + fRc = reporter.errorXcpt(); + else: + for oAtt in aoAttachments: + try: + sCtrl = oAtt.controller + iPort = oAtt.port; + iDev = oAtt.device; + eType = oAtt.type; + except: + fRc = reporter.errorXcpt(); + break; + + fDetached = False; + if eType == vboxcon.DeviceType_HardDisk: + oMedium = oVM.getMedium(sCtrl, iPort, iDev); + if oMedium.location.endswith(sHdd): + fRc = True; + fFound = True; + break; + for sHddVar in asHdds: + if oMedium.location.endswith(sHddVar) \ + or oMedium.parent is not None and oMedium.parent.location.endswith(sHddVar) : + (fRc, oOldHd) = oSession.detachHd(sCtrl, iPort, iDev); + if fRc and oOldHd is not None: + fRc = oSession.saveSettings(); + if oMedium.parent is not None: + fRc = fRc and self.oTstDrv.oVBox.deleteHdByMedium(oOldHd); + else: + fRc = fRc and oOldHd.close(); + fRc = fRc and oSession.saveSettings(); + fDetached = True; + if not fDetached and sCtrl == 'SATA Controller' and iPort + 1 > iPortNew: + iPortNew = iPort + 1; + if not fFound: + fRc = oSession.attachHd(sHdd, 'SATA Controller', iPortNew, 0); + if fRc: + fRc = oSession.saveSettings(); + else: + oSession.discadSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack + else: + reporter.error("Open session for '%s' failed" % self.sVmName); + fRc = False; + reporter.testDone(); + return fRc; + + def _callVBoxManage(self, oGuestSession, sTestName, cMsTimeout, asArgs = (), + fGetStdOut = True, fIsError = True): + return self.guestProcessExecute(oGuestSession, sTestName, + cMsTimeout, '/usr/bin/sudo', + ['/usr/bin/sudo', '/opt/VirtualBox/VBoxManage'] + asArgs, fGetStdOut, fIsError); + + def listHostDrives(self, oGuestSession, sHdd): + """ + Define path of the specified drive using 'VBoxManage list hostdrives'. + """ + reporter.testStart("List host drives"); + sDrive = None; + (fRc, _, _, aBuf) = self._callVBoxManage(oGuestSession, 'List host drives', 60 * 1000, + ['list', 'hostdrives'], True, True); + if not fRc: + reporter.error('List host drives in the VM %s failed' % (self.sVmName, )); + else: + if aBuf is None: + fRc = reporter.error('"List host drives" output is empty for the VM %s' % (self.sVmName, )); + else: + asHddData = self.asHdds[sHdd]; + + try: aBuf = str(aBuf); # pylint: disable=redefined-variable-type + except: pass; + asLines = aBuf.splitlines(); + oRegExp = re.compile(r'^\s*([^:]+)\s*:\s*(.+)\s*$'); + + # pylint: disable=no-init + class ParseState(object): + kiNothing = 0; + kiDrive = 1; + kiPartition = 2; + + iParseState = ParseState.kiNothing; + asKeysNotFound = asHddData['Header'].keys(); + idxPartition = 0; + for sLine in asLines: + if not sLine or sLine.startswith('#') or sLine.startswith("\n"): + continue; + oMatch = oRegExp.match(sLine); + if oMatch is not None: + sKey = oMatch.group(1); + sValue = oMatch.group(2); + if sKey is not None and sKey == 'Drive': + # we found required disk if we found all required disk info and partitions + if sDrive and not asKeysNotFound and idxPartition >= len(asHddData['Partitions']['Partitions']): + break; + sDrive = sValue; + iParseState = ParseState.kiDrive; + asKeysNotFound = asKeysNotFound = asHddData['Header'].keys(); + idxPartition = 0; + continue; + if iParseState == ParseState.kiDrive: + if sLine.strip().startswith('Partitions:'): + iParseState = ParseState.kiPartition; + continue; + if oMatch is None or sKey is None: + continue; + if sKey in asHddData['Header'].keys() and asHddData['Header'][sKey] == sValue: + asKeysNotFound.remove(sKey); + continue; + if iParseState == ParseState.kiPartition: + if idxPartition < len(asHddData['Partitions']['Partitions']): + sPart = asHddData['Partitions']['Partitions'][idxPartition]; + sPart = sPart.replace('$(' + str(idxPartition + 1) + ')', + str(asHddData['Partitions']['PartitionNumbers'][idxPartition])); + if sLine.strip() == sPart: + idxPartition += 1; + continue; + fRc = sDrive and not asKeysNotFound and idxPartition >= len(asHddData['Partitions']['Partitions']); + if fRc: + reporter.log("Path to the drive '%s' in the VM '%s': %s " % (sHdd, self.sVmName, sDrive)); + else: + reporter.error("Path to drive '%s' not found in the VM '%s'" % (sHdd, self.sVmName)); + reporter.testDone(); + return (fRc, sDrive); + + def convertDiskToPartitionPrefix(self, sDisk): + return sDisk; + + def checkVMDKDescriptor(self, asDescriptor, sHdd, sRawDrive, asAction): + """ + Check VMDK descriptor of the disk created + """ + if asDescriptor is None \ + or asDescriptor[0] != '# Disk DescriptorFile' \ + and asDescriptor[0] != '# Disk Descriptor File' \ + and asDescriptor[0] != '#Disk Descriptor File' \ + and asDescriptor[0] != '#Disk DescriptorFile': + return reporter.error("VMDK descriptor has invalid format"); + + # pylint: disable=no-init + class DescriptorParseState(object): + kiHeader = 1; + kiExtent = 2; + kiDatabase = 3; + + asHddData = self.asHdds[sHdd]; + iParseState = DescriptorParseState.kiHeader; + + asHeader = { 'version' : '1', + 'CID' : '*', + 'parentCID' : 'ffffffff', + 'createType' : '$' + }; + + asDatabase = { 'ddb.virtualHWVersion' : '4', + 'ddb.adapterType' : 'ide', + 'ddb.uuid.image' : '*', + 'ddb.uuid.parent' : '00000000-0000-0000-0000-000000000000', + 'ddb.uuid.modification' : '00000000-0000-0000-0000-000000000000', + 'ddb.uuid.parentmodification' : '00000000-0000-0000-0000-000000000000' + }; + + oRegExp = re.compile(r'^\s*([^=]+)\s*=\s*\"*([^\"]+)\"*\s*$'); + iExtentIdx = 0; + + for sLine in asDescriptor: + if not sLine or sLine.startswith('#') or sLine.startswith("\n"): + continue; + + if iParseState == DescriptorParseState.kiHeader: + if sLine.startswith('ddb.'): + return reporter.error("VMDK descriptor has invalid order of sections"); + if sLine.startswith("RW") \ + or sLine.startswith("RDONLY") \ + or sLine.startswith("NOACCESS"): + iParseState = DescriptorParseState.kiExtent; + else: + oMatch = oRegExp.match(sLine); + if oMatch is None: + return reporter.error("VMDK descriptor contains lines in invalid form"); + sKey = oMatch.group(1).strip(); + sValue = oMatch.group(2).strip(); + if sKey not in asHeader: + return reporter.error("VMDK descriptor has invalid format"); + sDictValue = asHeader[sKey]; + if sDictValue == '$': + sDictValue = asAction[sKey]; + if sDictValue not in ('*', sValue): + return reporter.error("VMDK descriptor has value which was not expected"); + continue; + + if iParseState == DescriptorParseState.kiExtent: + if sLine.startswith('ddb.'): + iParseState = DescriptorParseState.kiDatabase; + else: + if not sLine.startswith("RW") \ + and not sLine.startswith("RDONLY") \ + and not sLine.startswith("NOACCESS"): + return reporter.error("VMDK descriptor has invalid order of sections"); + sExtent = asAction['extents'][sHdd][iExtentIdx]; + sExtent = sExtent.replace('$(disk)', sRawDrive); + sExtent = sExtent.replace('$(part)', self.convertDiskToPartitionPrefix(sRawDrive)); + sExtent = re.sub(r'\$\((\d+)\)', + lambda oMatch: str(asHddData['Partitions']['PartitionNumbers'][int(oMatch.group(1)) - 1]), + sExtent); + if sExtent != sLine.strip(): + return reporter.error("VMDK descriptor has invalid order of sections"); + iExtentIdx += 1; + continue; + + if iParseState == DescriptorParseState.kiDatabase: + if not sLine.startswith('ddb.'): + return reporter.error("VMDK descriptor has invalid order of sections"); + oMatch = oRegExp.match(sLine); + if oMatch is None: + return reporter.error("VMDK descriptor contains lines in invalid form"); + sKey = oMatch.group(1).strip(); + sValue = oMatch.group(2).strip(); + if sKey not in asDatabase: + return reporter.error("VMDK descriptor has invalid format"); + sDictValue = asDatabase[sKey]; + if sDictValue not in ('*', sValue): + return reporter.error("VMDK descriptor has value which was not expected"); + continue; + return iParseState == DescriptorParseState.kiDatabase; + + def _setPermissionsToVmdkFiles(self, oGuestSession): + """ + Sets 0644 permissions to all files in the self.sVMDKPath allowing reading them by 'vbox' user. + """ + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Allowing reading of the VMDK content by vbox user', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '644', + self.sVMDKPath + '/vmdktest.vmdk', self.sVMDKPath + '/vmdktest-pt.vmdk'], + False, True); + return fRc; + + def createDrives(self, oGuestSession, sHdd, sRawDrive): + """ + Creates VMDK Raw file and check correctness + """ + reporter.testStart("Create VMDK disks"); + asHddData = self.asHdds[sHdd]; + fRc = True; + try: oGuestSession.directoryCreate(self.sVMDKPath, 0o777, (vboxcon.DirectoryCreateFlag_Parents,)); + except: fRc = reporter.errorXcpt('Create directory for VMDK files failed in the VM %s' % (self.sVmName)); + if fRc: + sBootSectorGuestPath = self.sVMDKPath + self.sPathDelimiter + 't-bootsector.bin'; + try: fExists = oGuestSession.fileExists(sBootSectorGuestPath, False); + except: fExists = False; + if not fExists: + sBootSectorPath = self.oTstDrv.getFullResourceName(self.sBootSector); + fRc = self.uploadFile(oGuestSession, sBootSectorPath, sBootSectorGuestPath); + + for action in self.asActions: + reporter.testStart("Create VMDK disk: %s" % action["action"]); + asOptions = action['options']; + asOptions = [option.replace('$(bootsector)', sBootSectorGuestPath) for option in asOptions]; + asOptions = [re.sub(r'\$\((\d+)\)', + lambda oMatch: str(asHddData['Partitions']['PartitionNumbers'][int(oMatch.group(1)) - 1]), + option) + for option in asOptions]; + (fRc, _, _, _) = self._callVBoxManage(oGuestSession, 'Create VMDK disk', 60 * 1000, + ['createmedium', '--filename', + self.sVMDKPath + self.sPathDelimiter + 'vmdktest.vmdk', + '--format', 'VMDK', '--variant', 'RawDisk', + '--property', 'RawDrive=%s' % (sRawDrive,) ] + asOptions, + False, True); + if not fRc: + reporter.error('Create VMDK raw drive variant "%s" failed in the VM %s' % (action["action"], self.sVmName)); + else: + fRc = self._setPermissionsToVmdkFiles(oGuestSession); + if not fRc: + reporter.error('Setting permissions to VMDK files failed'); + else: + sSrcFile = self.sVMDKPath + self.sPathDelimiter + 'vmdktest.vmdk'; + sDstFile = os.path.join(self.oTstDrv.sScratchPath, 'guest-vmdktest.vmdk'); + reporter.log2('Downloading file "%s" to "%s" ...' % (sSrcFile, sDstFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sDstFile); + except: pass; + fRc = self.downloadFile(oGuestSession, sSrcFile, sDstFile, False); + if not fRc: + reporter.error('Download vmdktest.vmdk from guest to host failed'); + else: + with open(sDstFile) as oFile: # pylint: disable=unspecified-encoding + asDescriptor = [row.strip() for row in oFile]; + if not asDescriptor: + fRc = reporter.error('Reading vmdktest.vmdk from guest filed'); + else: + fRc = self.checkVMDKDescriptor(asDescriptor, sHdd, sRawDrive, action); + if not fRc: + reporter.error('Cheking vmdktest.vmdk from guest filed'); + elif action['data-crc']: + sSrcFile = self.sVMDKPath + self.sPathDelimiter + 'vmdktest-pt.vmdk'; + sDstFile = os.path.join(self.oTstDrv.sScratchPath, 'guest-vmdktest-pt.vmdk'); + reporter.log2('Downloading file "%s" to "%s" ...' % (sSrcFile, sDstFile)); + # First try to remove (unlink) an existing temporary file, as we don't truncate the file. + try: os.unlink(sDstFile); + except: pass; + fRc = self.downloadFile(oGuestSession, sSrcFile, sDstFile, False); + if not fRc: + reporter.error('Download vmdktest-pt.vmdk from guest to host failed'); + else: + uResCrc32 = utils.calcCrc32OfFile(sDstFile); + if uResCrc32 != action['data-crc'][sHdd]: + fRc = reporter.error('vmdktest-pt.vmdk does not match what was expected'); + (fRc1, _, _, _) = self._callVBoxManage(oGuestSession, 'Delete VMDK disk', 60 * 1000, + ['closemedium', + self.sVMDKPath + self.sPathDelimiter + 'vmdktest.vmdk', + '--delete'], + False, True); + if not fRc1: + reporter.error('Delete VMDK raw drive variant "%s" failed in the VM %s' % + (action["action"], self.sVmName)); + fRc = fRc and fRc1; + reporter.testDone(); + if not fRc: + break; + else: + reporter.error('Create %s dir failed in the VM %s' % (self.sVMDKPath, self.sVmName)); + + reporter.testDone(); + return fRc; + + +class tdStorageRawDriveOsLinux(tdStorageRawDriveOs): + """ + Autostart support methods for Linux guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + self.sVBoxInstaller = '^VirtualBox-.*\\.run$'; + return; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Install guest additions in the guest. + """ + reporter.testStart('Install Guest Additions'); + fRc = False; + # Install Kernel headers, which are required for actually installing the Linux Additions. + if oVM.OSTypeId.startswith('Debian') \ + or oVM.OSTypeId.startswith('Ubuntu'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', + 'linux-headers-generic'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/apt-get', + ['/usr/bin/apt-get', 'install', '-y', 'build-essential', + 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + elif oVM.OSTypeId.startswith('OL') \ + or oVM.OSTypeId.startswith('Oracle') \ + or oVM.OSTypeId.startswith('RHEL') \ + or oVM.OSTypeId.startswith('Redhat') \ + or oVM.OSTypeId.startswith('Cent'): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Kernel headers', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'kernel-headers'], + False, True); + if not fRc: + reporter.error('Error installing Kernel headers'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing Guest Additions depdendencies', + 5 * 60 *1000, '/usr/bin/yum', + ['/usr/bin/yum', '-y', 'install', 'make', 'automake', 'gcc', + 'kernel-devel', 'dkms', 'bzip2', 'perl'], False, True); + if not fRc: + reporter.error('Error installing additional installer dependencies'); + else: + reporter.error('Installing Linux Additions for the "%s" is not supported yet' % oVM.OSTypeId); + fRc = False; + if fRc: + # + # The actual install. + # Also tell the installer to produce the appropriate log files. + # + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing guest additions', + 10 * 60 *1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/sh', + '/media/cdrom/VBoxLinuxAdditions.run'], + False, True); + if fRc: + # Due to the GA updates as separate process the above function returns before + # the actual installation finished. So just wait until the GA installed + fRc = self.closeSession(oGuestSession); + if fRc: + (fRc, oGuestSession) = self.waitVmIsReady(oSession, False); + # Download log files. + # Ignore errors as all files above might not be present for whatever reason. + # + if fRc: + asLogFile = []; + asLogFile.append('/var/log/vboxadd-install.log'); + self.downloadFiles(oGuestSession, asLogFile, fIgnoreErrors = True); + else: + reporter.error('Installing guest additions failed: Error occured during vbox installer execution') + if fRc: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if not fRc: + reporter.error('Reboot after installing GuestAdditions failed'); + reporter.testDone(); + return (fRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + '/tmp/' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the vbox installer into guest VM failed'); + else: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, + 'Allowing execution for the vbox installer', + 30 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', '/bin/chmod', '755', + '/tmp/' + os.path.basename(sTestBuild)], + False, True); + if not fRc: + reporter.error('Allowing execution for the vbox installer failed'); + if fRc: + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 240 * 1000, '/usr/bin/sudo', + ['/usr/bin/sudo', + '/tmp/' + os.path.basename(sTestBuild),], + False, True); + if not fRc: + reporter.error('Installing VBox failed'); + reporter.testDone(); + return fRc; + +class tdStorageRawDriveOsDarwin(tdStorageRawDriveOs): + """ + Autostart support methods for Darwin guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + raise base.GenError('Testing the autostart functionality for Darwin is not implemented'); + +class tdStorageRawDriveOsSolaris(tdStorageRawDriveOs): + """ + Autostart support methods for Solaris guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + raise base.GenError('Testing the autostart functionality for Solaris is not implemented'); + +class tdStorageRawDriveOsWin(tdStorageRawDriveOs): + """ + Autostart support methods for Windows guests. + """ + # pylint: disable=too-many-arguments + def __init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type = None, cMbRam = None, \ + cCpus = 1, fPae = None, sGuestAdditionsIso = None, sBootSector = None): + tdStorageRawDriveOs.__init__(self, oSet, oTstDrv, sVmName, sKind, sHdd, eNic0Type, cMbRam, \ + cCpus, fPae, sGuestAdditionsIso, sBootSector); + self.sVBoxInstaller = r'^VirtualBox-.*\.(exe|msi)$'; + self.sVMDKPath=r'C:\Temp\vmdk'; + self.sPathDelimiter = '\\'; + self.asHdds['6.1/storage/t-mbr.vdi']['Header']['Model'] = '"VBOX HARDDISK"'; + self.asHdds['6.1/storage/t-gpt.vdi']['Header']['Model'] = '"VBOX HARDDISK"'; + self.asHdds['6.1/storage/t-mbr.vdi']['Partitions']['PartitionNumbers'] = [1, 2, 3, 4, 5, 6, 7, 8]; + return; + + def _checkVmIsReady(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Start a guest process', + 30 * 1000, 'C:\\Windows\\System32\\ipconfig.exe', + ['C:\\Windows\\System32\\ipconfig.exe',], + False, False); + return fRc; + + def _rebootVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Reboot the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/r', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def _powerDownVM(self, oGuestSession): + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Power down the VM', + 30 * 1000, 'C:\\Windows\\System32\\shutdown.exe', + ['C:\\Windows\\System32\\shutdown.exe', '/f', + '/s', '/t', '0'], + False, True); + if not fRc: + reporter.error('Calling the shutdown utility failed'); + return fRc; + + def _callVBoxManage(self, oGuestSession, sTestName, cMsTimeout, asArgs = (), + fGetStdOut = True, fIsError = True): + return self.guestProcessExecute(oGuestSession, sTestName, + cMsTimeout, r'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe', + [r'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe',] + asArgs, fGetStdOut, fIsError); + + def _setPermissionsToVmdkFiles(self, oGuestSession): + """ + Sets 0644 permissions to all files in the self.sVMDKPath allowing reading them by 'vbox' user. + """ + _ = oGuestSession; + # It is not required in case of Windows + return True; + + def installAdditions(self, oSession, oGuestSession, oVM): + """ + Installs the Windows guest additions using the test execution service. + """ + _ = oVM; + reporter.testStart('Install Guest Additions'); + asLogFiles = []; + fRc = self.closeSession(oGuestSession, True); # pychecker hack. + try: + oCurProgress = oSession.o.console.guest.updateGuestAdditions(self.sGuestAdditionsIso, ['/l',], None); + except: + reporter.maybeErrXcpt(True, 'Updating Guest Additions exception for sSrc="%s":' + % (self.sGuestAdditionsIso,)); + fRc = False; + else: + if oCurProgress is not None: + oWrapperProgress = vboxwrappers.ProgressWrapper(oCurProgress, self.oTstDrv.oVBoxMgr, + self.oTstDrv, "installAdditions"); + oWrapperProgress.wait(cMsTimeout = 10 * 60 * 1000); + if not oWrapperProgress.isSuccess(): + oWrapperProgress.logResult(fIgnoreErrors = False); + fRc = False; + else: + fRc = reporter.error('No progress object returned'); + + # Store the result and try download logs anyway. + fGaRc = fRc; + fRc, oGuestSession = self.createSession(oSession, 'Session for user: vbox', + 'vbox', 'password', 10 * 1000, True); + if fRc is True: + (fRc, oGuestSession) = self.rebootVMAndCheckReady(oSession, oGuestSession); + if fRc is True: + # Add the Windows Guest Additions installer files to the files we want to download + # from the guest. + sGuestAddsDir = 'C:/Program Files/Oracle/VirtualBox Guest Additions/'; + asLogFiles.append(sGuestAddsDir + 'install.log'); + # Note: There won't be a install_ui.log because of the silent installation. + asLogFiles.append(sGuestAddsDir + 'install_drivers.log'); + # Download log files. + # Ignore errors as all files above might not be present (or in different locations) + # on different Windows guests. + # + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + else: + reporter.error('Reboot after installing GuestAdditions failed'); + else: + reporter.error('Create session for user vbox after GA updating failed'); + reporter.testDone(); + return (fRc and fGaRc, oGuestSession); + + def installVirtualBox(self, oGuestSession): + """ + Install VirtualBox in the guest. + """ + reporter.testStart('Install Virtualbox into the guest VM'); + # Used windows image already contains the C:\Temp + sTestBuild = self._findFile(self.sVBoxInstaller, self.asTestBuildDirs); + reporter.log("Virtualbox install file: %s" % os.path.basename(sTestBuild)); + fRc = sTestBuild is not None; + if fRc: + fRc = self.uploadFile(oGuestSession, sTestBuild, + 'C:\\Temp\\' + os.path.basename(sTestBuild)); + else: + reporter.error("VirtualBox install package is not defined"); + + if not fRc: + reporter.error('Upload the installing into guest VM failed'); + else: + if sTestBuild.endswith('.msi'): + sLogFile = 'C:/Temp/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Windows\\System32\\msiexec.exe', + ['msiexec', '/quiet', '/norestart', '/i', + 'C:\\Temp\\' + os.path.basename(sTestBuild), + '/lv', sLogFile], + False, True); + if not fRc: + reporter.error('Installing the VBox from msi installer failed'); + else: + sLogFile = 'C:/Temp/Virtualbox/VBoxInstallLog.txt'; + (fRc, _, _, _) = self.guestProcessExecute(oGuestSession, 'Installing VBox', + 600 * 1000, 'C:\\Temp\\' + os.path.basename(sTestBuild), + ['C:\\Temp\\' + os.path.basename(sTestBuild), '-vvvv', + '--silent', '--logging', + '--msiparams', 'REBOOT=ReallySuppress'], + False, True); + if not fRc: + reporter.error('Installing the VBox failed'); + else: + (_, _, _, aBuf) = self.guestProcessExecute(oGuestSession, 'Check installation', + 240 * 1000, 'C:\\Windows\\System32\\cmd.exe', + ['c:\\Windows\\System32\\cmd.exe', '/c', + 'dir', 'C:\\Program Files\\Oracle\\VirtualBox\\*.*'], + True, True); + reporter.log('Content of VirtualBxox folder:'); + reporter.log(str(aBuf)); + asLogFiles = [sLogFile,]; + self.downloadFiles(oGuestSession, asLogFiles, fIgnoreErrors = True); + reporter.testDone(); + return fRc; + + def convertDiskToPartitionPrefix(self, sDisk): + # Convert \\.\PhysicalDriveX into \\.\HarddiskXPartition + oMatch = re.match(r'^\\\\.\\PhysicalDrive(\d+)$', sDisk); + if oMatch is None: + return None; + return r'\\.\Harddisk' + oMatch.group(1) + 'Partition'; + +class tdStorageRawDrive(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Autostart testcase. + """ + ksOsLinux = 'tst-linux'; + ksOsWindows = 'tst-win'; + ksOsDarwin = 'tst-darwin'; + ksOsSolaris = 'tst-solaris'; + ksOsFreeBSD = 'tst-freebsd'; + ksBootSectorPath = '6.1/storage/t-bootsector.bin'; + kasHdds = ['6.1/storage/t-gpt.vdi', '6.1/storage/t-mbr.vdi']; + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asSkipVMs = []; + ## @todo r=bird: The --test-build-dirs option as primary way to get the installation files to test + ## is not an acceptable test practice as we don't know wtf you're testing. See defect for more. + self.asTestBuildDirs = [os.path.join(self.sScratchPath, 'bin'),]; + self.sGuestAdditionsIso = None; #'D:/AlexD/TestBox/TestAdditionalFiles/VBoxGuestAdditions_6.1.2.iso'; + oSet = vboxtestvms.TestVmSet(self.oTestVmManager, acCpus = [2], asVirtModes = ['hwvirt-np',], fIgnoreSkippedVm = True); + # pylint: disable=line-too-long + self.asTestVmClasses = { + 'win' : None, #tdStorageRawDriveOsWin(oSet, self, self.ksOsWindows, 'Windows7_64', \ + #'6.0/windows7piglit/windows7piglit.vdi', eNic0Type = None, cMbRam = 2048, \ + #cCpus = 2, fPae = True, sGuestAdditionsIso = self.getGuestAdditionsIso(), + #sBootSector = self.ksBootSectorPath), + 'linux' : tdStorageRawDriveOsLinux(oSet, self, self.ksOsLinux, 'Ubuntu_64', \ + '6.0/ub1804piglit/ub1804piglit.vdi', eNic0Type = None, \ + cMbRam = 2048, cCpus = 2, fPae = None, sGuestAdditionsIso = self.getGuestAdditionsIso(), + sBootSector = self.ksBootSectorPath), + 'solaris' : None, #'tdAutostartOsSolaris', + 'darwin' : None #'tdAutostartOsDarwin' + }; + oSet.aoTestVms.extend([oTestVm for oTestVm in self.asTestVmClasses.values() if oTestVm is not None]); + sOs = self.getBuildOs(); + if sOs in self.asTestVmClasses: + for oTestVM in oSet.aoTestVms: + if oTestVM is not None: + oTestVM.fSkip = oTestVM != self.asTestVmClasses[sOs]; + # pylint: enable=line-too-long + self.oTestVmSet = oSet; + + # + # Overridden methods. + # + + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdAutostart Options:'); + reporter.log(' --test-build-dirs <path1[,path2[,...]]>'); + reporter.log(' The list of directories with VirtualBox distros. Overrides default path.'); + reporter.log(' Default path is $TESTBOX_SCRATCH_PATH/bin.'); + reporter.log(' --vbox-<os>-build <path>'); + reporter.log(' The path to vbox build for the specified OS.'); + reporter.log(' The OS can be one of "win", "linux", "solaris" and "darwin".'); + reporter.log(' This option alse enables corresponding VM for testing.'); + reporter.log(' (Default behaviour is testing only VM having host-like OS.)'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--test-build-dirs': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-build-dirs" takes a path argument'); + self.asTestBuildDirs = asArgs[iArg].split(','); + for oTestVm in self.oTestVmSet.aoTestVms: + oTestVm.asTestBuildDirs = self.asTestBuildDirs; + elif asArgs[iArg] in [ '--vbox-%s-build' % sKey for sKey in self.asTestVmClasses]: + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "%s" take a path argument' % (asArgs[iArg - 1],)); + oMatch = re.match("--vbox-([^-]+)-build", asArgs[iArg - 1]); + if oMatch is not None: + sOs = oMatch.group(1); + oTestVm = self.asTestVmClasses.get(sOs); + if oTestVm is not None: + oTestVm.sTestBuild = asArgs[iArg]; + oTestVm.fSkip = False; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + asRsrcs = self.kasHdds[:]; + asRsrcs.extend([self.ksBootSectorPath,]); + asRsrcs.extend(vbox.TestDriver.getResourceSet(self)); + return asRsrcs; + + def actionConfig(self): + if not self.importVBoxApi(): # So we can use the constant below. + return False; + return self.oTestVmSet.actionConfig(self); + + def actionExecute(self): + """ + Execute the testcase. + """ + return self.oTestVmSet.actionExecute(self, self.testAutostartOneVfg) + + # + # Test execution helpers. + # + def testAutostartOneVfg(self, oVM, oTestVm): + fRc = True; + self.logVmInfo(oVM); + + for sHdd in self.kasHdds: + reporter.testStart('%s with %s disk' % ( oTestVm.sVmName, sHdd)) + fRc = oTestVm.reattachHdd(oVM, sHdd, self.kasHdds); + if fRc: + oSession = self.startVmByName(oTestVm.sVmName); + if oSession is not None: + (fRc, oGuestSession) = oTestVm.waitVmIsReady(oSession, True); + if fRc: + if fRc: + (fRc, oGuestSession) = oTestVm.installAdditions(oSession, oGuestSession, oVM); + if fRc: + fRc = oTestVm.installVirtualBox(oGuestSession); + if fRc: + (fRc, sRawDrive) = oTestVm.listHostDrives(oGuestSession, sHdd); + if fRc: + fRc = oTestVm.createDrives(oGuestSession, sHdd, sRawDrive); + if not fRc: + reporter.error('Create VMDK raw drives failed'); + else: + reporter.error('List host drives failed'); + else: + reporter.error('Installing VirtualBox in the guest failed'); + else: + reporter.error('Creating Guest Additions failed'); + else: + reporter.error('Waiting for start VM failed'); + if oGuestSession is not None: + try: oTestVm.powerDownVM(oGuestSession); + except: pass; + try: self.terminateVmBySession(oSession); + except: pass; + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + else: + reporter.error('Attaching %s to %s failed' % (sHdd, oTestVm.sVmName)); + reporter.testDone(); + return fRc; + +if __name__ == '__main__': + sys.exit(tdStorageRawDrive().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py b/src/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py new file mode 100755 index 00000000..d6904114 --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdStorageSnapshotMerging1.py $ + +""" +VirtualBox Validation Kit - Storage snapshotting and merging testcase. +""" + +__copyright__ = \ +""" +Copyright (C) 2013-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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; +from testdriver import vboxwrappers; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +class tdStorageSnapshot(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Storage benchmark. + """ + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oGuestToGuestVM = None; + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.asStorageCtrlsDef = ['AHCI']; + self.asStorageCtrls = self.asStorageCtrlsDef; + #self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD', 'QED', 'Parallels', 'QCOW', 'iSCSI']; + self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD']; + self.asDiskFormats = self.asDiskFormatsDef; + self.sRndData = os.urandom(100*1024*1024); + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdStorageSnapshot1 Options:'); + reporter.log(' --storage-ctrls <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asStorageCtrls))); + reporter.log(' --disk-formats <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskFormats))); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--storage-ctrls': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--storage-ctrls" takes a colon separated list of Storage controller types'); + self.asStorageCtrls = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-formats': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-formats" takes a colon separated list of disk formats'); + self.asDiskFormats = asArgs[iArg].split(':'); + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = ['5.3/storage/mergeMedium/t-orig.vdi', + '5.3/storage/mergeMedium/t-fixed.vdi', + '5.3/storage/mergeMedium/t-resized.vdi']; + return self.asRsrcs; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + def resizeMedium(self, oMedium, cbNewSize): + if oMedium.deviceType is not vboxcon.DeviceType_HardDisk: + return False; + + if oMedium.type is not vboxcon.MediumType_Normal: + return False; + + #currently only VDI can be resizable. Medium variant is not checked, because testcase creates disks itself + oMediumFormat = oMedium.mediumFormat; + if oMediumFormat.id != 'VDI': + return False; + + cbCurrSize = oMedium.logicalSize; + # currently reduce is not supported + if cbNewSize < cbCurrSize: + return False; + + try: + oProgressCom = oMedium.resize(cbNewSize); + except: + reporter.logXcpt('IMedium::resize failed on %s' % (oMedium.name)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oVBox.oTstDrv, + 'Resize medium %s' % (oMedium.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def getMedium(self, oVM, sController): + oMediumAttachments = oVM.getMediumAttachmentsOfController(sController); + + for oAttachment in oMediumAttachments: + oMedium = oAttachment.medium; + if oMedium.deviceType is not vboxcon.DeviceType_HardDisk: + continue; + if oMedium.type is not vboxcon.MediumType_Normal: + continue; + return oMedium; + + return None; + + def getSnapshotMedium(self, oSnapshot, sController): + oVM = oSnapshot.machine; + oMedium = self.getMedium(oVM, sController); + + aoMediumChildren = self.oVBoxMgr.getArray(oMedium, 'children') + if aoMediumChildren is None or not aoMediumChildren: + return None; + + for oChildMedium in aoMediumChildren: + for uSnapshotId in oChildMedium.getSnapshotIds(oVM.id): + if uSnapshotId == oVM.id: + return oChildMedium; + + return None; + + def openMedium(self, sHd, fImmutable = False): + """ + Opens medium in readonly mode. + Returns Medium object on success and None on failure. Error information is logged. + """ + sFullName = self.oVBox.oTstDrv.getFullResourceName(sHd); + try: + oHd = self.oVBox.findHardDisk(sFullName); + except: + try: + if self.fpApiVer >= 4.1: + oHd = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_HardDisk, vboxcon.AccessMode_ReadOnly, False); + elif self.fpApiVer >= 4.0: + oHd = self.oVBox.openMedium(sFullName, vboxcon.DeviceType_HardDisk, vboxcon.AccessMode_ReadOnly); + else: + oHd = self.oVBox.openHardDisk(sFullName, vboxcon.AccessMode_ReadOnly, False, "", False, ""); + + except: + reporter.errorXcpt('failed to open hd "%s"' % (sFullName)); + return None; + + try: + if fImmutable: + oHd.type = vboxcon.MediumType_Immutable; + else: + oHd.type = vboxcon.MediumType_Normal; + + except: + if fImmutable: + reporter.errorXcpt('failed to set hd "%s" immutable' % (sHd)); + else: + reporter.errorXcpt('failed to set hd "%s" normal' % (sHd)); + + return None; + + return oHd; + + def cloneMedium(self, oSrcHd, oTgtHd): + """ + Clones medium into target medium. + """ + try: + oProgressCom = oSrcHd.cloneTo(oTgtHd, (vboxcon.MediumVariant_Standard, ), None); + except: + reporter.errorXcpt('failed to clone medium %s to %s' % (oSrcHd.name, oTgtHd.name)); + return False; + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oVBox.oTstDrv, + 'clone base disk %s to %s' % (oSrcHd.name, oTgtHd.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + return True; + + def deleteVM(self, oVM): + try: + oVM.unregister(vboxcon.CleanupMode_DetachAllReturnNone); + except: + reporter.logXcpt(); + + if self.fpApiVer >= 4.0: + try: + if self.fpApiVer >= 4.3: + oProgressCom = oVM.deleteConfig([]); + else: + oProgressCom = oVM.delete(None); + except: + reporter.logXcpt(); + else: + oProgress = vboxwrappers.ProgressWrapper(oProgressCom, self.oVBoxMgr, self.oVBox.oTstDrv, + 'Delete VM %s' % (oVM.name)); + oProgress.wait(cMsTimeout = 15*60*1000); # 15 min + oProgress.logResult(); + else: + try: oVM.deleteSettings(); + except: reporter.logXcpt(); + + return None; + + # + # Test execution helpers. + # + + def test1OneCfg(self, eStorageController, oDskFmt): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + + (asExts, aTypes) = oDskFmt.describeFileExtensions() + for i in range(0, len(asExts)): #pylint: disable=consider-using-enumerate + if aTypes[i] is vboxcon.DeviceType_HardDisk: + sExt = '.' + asExts[i] + break + + if sExt is None: + return False; + + oOrigBaseHd = self.openMedium('5.3/storage/mergeMedium/t-orig.vdi'); + if oOrigBaseHd is None: + return False; + + #currently only VDI can be resizable. Medium variant is not checked, because testcase creates disks itself + fFmtDynamic = oDskFmt.id == 'VDI'; + sOrigWithDiffHd = '5.3/storage/mergeMedium/t-fixed.vdi' + uOrigCrc = long(0x7a417cbb); + + if fFmtDynamic: + sOrigWithDiffHd = '5.3/storage/mergeMedium/t-resized.vdi'; + uOrigCrc = long(0xa8f5daa3); + + oOrigWithDiffHd = self.openMedium(sOrigWithDiffHd); + if oOrigWithDiffHd is None: + return False; + + oVM = self.createTestVM('testvm', 1, None); + if oVM is None: + return False; + + sController = self.controllerTypeToName(eStorageController); + + # Reconfigure the VM + oSession = self.openSession(oVM); + if oSession is None: + return False; + # Attach HD + + fRc = True; + sFile = 't-base' + sExt; + sHddPath = os.path.join(self.oVBox.oTstDrv.sScratchPath, sFile); + oHd = oSession.createBaseHd(sHddPath, sFmt=oDskFmt.id, cb=oOrigBaseHd.logicalSize, + cMsTimeout = 15 * 60 * 1000); # 15 min + #if oSession.createBaseHd can't create disk because it exists, oHd will point to some stub object anyway + fRc = fRc and oHd is not None and (oHd.logicalSize == oOrigBaseHd.logicalSize); + fRc = fRc and self.cloneMedium(oOrigBaseHd, oHd); + + fRc = fRc and oSession.ensureControllerAttached(sController); + fRc = fRc and oSession.setStorageControllerType(eStorageController, sController); + fRc = fRc and oSession.saveSettings(); + fRc = fRc and oSession.attachHd(sHddPath, sController, iPort = 0, fImmutable=False, fForceResource=False) + + if fRc: + oSession.takeSnapshot('Base snapshot'); + oSnapshot = oSession.findSnapshot('Base snapshot'); + + if oSnapshot is not None: + oSnapshotMedium = self.getSnapshotMedium(oSnapshot, sController); + fRc = oSnapshotMedium is not None; + + if fFmtDynamic: + fRc = fRc and self.resizeMedium(oSnapshotMedium, oOrigWithDiffHd.logicalSize); + fRc = fRc and self.cloneMedium(oOrigWithDiffHd, oSnapshotMedium); + fRc = fRc and oSession.deleteSnapshot(oSnapshot.id, cMsTimeout = 120 * 1000); + + if fRc: + # disk for result test by checksum + sResFilePath = os.path.join(self.oVBox.oTstDrv.sScratchPath, 't_res.vmdk'); + sResFilePathRaw = os.path.join(self.oVBox.oTstDrv.sScratchPath, 't_res-flat.vmdk'); + oResHd = oSession.createBaseHd(sResFilePath, sFmt='VMDK', cb=oOrigWithDiffHd.logicalSize, + tMediumVariant = (vboxcon.MediumVariant_Fixed, ), + cMsTimeout = 15 * 60 * 1000); # 15 min + fRc = oResHd is not None; + fRc = fRc and self.cloneMedium(oHd, oResHd); + + uResCrc32 = long(0); + if fRc: + uResCrc32 = long(utils.calcCrc32OfFile(sResFilePathRaw)); + if uResCrc32 == uOrigCrc: + reporter.log('Snapshot merged successfully. Crc32 is correct'); + fRc = True; + else: + reporter.error('Snapshot merging failed. Crc32 is invalid'); + fRc = False; + + self.oVBox.deleteHdByMedium(oResHd); + + if oSession is not None: + if oHd is not None: + oSession.detachHd(sController, iPort = 0, iDevice = 0); + + oSession.saveSettings(fClose = True); + if oHd is not None: + self.oVBox.deleteHdByMedium(oHd); + + self.deleteVM(oVM); + return fRc; + + def test1(self): + """ + Executes test #1 thru the various configurations. + """ + if not self.importVBoxApi(): + return False; + + sVmName = 'testvm'; + reporter.testStart(sVmName); + + aoDskFmts = self.oVBoxMgr.getArray(self.oVBox.systemProperties, 'mediumFormats') + if aoDskFmts is None or not aoDskFmts: + return False; + + fRc = True; + for sStorageCtrl in self.asStorageCtrls: + reporter.testStart(sStorageCtrl); + if sStorageCtrl == 'AHCI': + eStorageCtrl = vboxcon.StorageControllerType_IntelAhci; + elif sStorageCtrl == 'IDE': + eStorageCtrl = vboxcon.StorageControllerType_PIIX4; + elif sStorageCtrl == 'LsiLogicSAS': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogicSas; + elif sStorageCtrl == 'LsiLogic': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogic; + elif sStorageCtrl == 'BusLogic': + eStorageCtrl = vboxcon.StorageControllerType_BusLogic; + else: + eStorageCtrl = None; + + for oDskFmt in aoDskFmts: + if oDskFmt.id in self.asDiskFormats: + reporter.testStart('%s' % (oDskFmt.id)); + fRc = self.test1OneCfg(eStorageCtrl, oDskFmt); + reporter.testDone(); + if not fRc: + break; + + reporter.testDone(); + if not fRc: + break; + + reporter.testDone(); + return fRc; + +if __name__ == '__main__': + sys.exit(tdStorageSnapshot().main(sys.argv)); diff --git a/src/VBox/ValidationKit/tests/storage/tdStorageStress1.py b/src/VBox/ValidationKit/tests/storage/tdStorageStress1.py new file mode 100755 index 00000000..55beb6fd --- /dev/null +++ b/src/VBox/ValidationKit/tests/storage/tdStorageStress1.py @@ -0,0 +1,513 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Storage testcase using xfstests. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-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__ = "$Id: tdStorageStress1.py $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + + +class tdStorageStress(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + Storage testcase. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.oGuestToGuestVM = None; + self.oGuestToGuestSess = None; + self.oGuestToGuestTxs = None; + self.asTestVMsDef = ['tst-debian']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + self.asStorageCtrlsDef = ['AHCI', 'IDE', 'LsiLogicSAS', 'LsiLogic', 'BusLogic']; + self.asStorageCtrls = self.asStorageCtrlsDef; + self.asDiskFormatsDef = ['VDI', 'VMDK', 'VHD', 'QED', 'Parallels', 'QCOW']; + self.asDiskFormats = self.asDiskFormatsDef; + self.asTestsDef = ['xfstests']; + self.asTests = self.asTestsDef; + self.asGuestFs = ['xfs', 'ext4', 'btrfs']; + self.asGuestFsDef = self.asGuestFs; + self.asIscsiTargetsDef = ['aurora|iqn.2011-03.home.aurora:aurora.storagebench|1']; + self.asIscsiTargets = self.asIscsiTargetsDef; + self.asDirsDef = ['/run/media/alexander/OWCSSD/alexander', \ + '/run/media/alexander/CrucialSSD/alexander', \ + '/run/media/alexander/HardDisk/alexander', \ + '/home/alexander']; + self.asDirs = self.asDirsDef; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdStorageBenchmark1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --storage-ctrls <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asStorageCtrls))); + reporter.log(' --disk-formats <type1[:type2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDiskFormats))); + reporter.log(' --disk-dirs <path1[:path2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asDirs))); + reporter.log(' --iscsi-targets <target1[:target2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asIscsiTargets))); + reporter.log(' --tests <test1[:test2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asTests))); + reporter.log(' --guest-fs <fs1[:fs2[:...]]>'); + reporter.log(' Default: %s' % (':'.join(self.asGuestFs))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--storage-ctrls': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--storage-ctrls" takes a colon separated list of Storage controller types'); + self.asStorageCtrls = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-formats': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-formats" takes a colon separated list of disk formats'); + self.asDiskFormats = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--disk-dirs': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--disk-dirs" takes a colon separated list of directories'); + self.asDirs = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--iscsi-targets': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--iscsi-targets" takes a colon separated list of iscsi targets'); + self.asIscsiTargets = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes a colon separated list of disk formats'); + self.asTests = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--guest-fs': + iArg += 1; + if iArg >= len(asArgs): + raise base.InvalidOption('The "--guest-fs" takes a colon separated list of filesystem identifiers'); + self.asGuestFs = asArgs[iArg].split(':'); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-debian' in self.asTestVMs: + self.asRsrcs.append('4.2/storage/debian.vdi'); + + return self.asRsrcs; + + def actionConfig(self): + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), + '../../VBoxValidationKitStorIo.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), + '../../VBoxValidationKitStorIo.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKitStorIo.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuiteStorIo.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKitStorIo.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuiteStorIo.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/VirtualBox/VBoxValidationKitStorIo.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/VirtualBox/VBoxTestSuiteStorIo.iso'; + + + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the VMs we're going to use. + # + + # Linux VMs + if 'tst-debian' in self.asTestVMs: + oVM = self.createTestVM('tst-debian', 1, '4.2/storage/debian.vdi', sKind = 'Debian_64', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + eNic0Type = vboxcon.NetworkAdapterType_Am79C973, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.test1(); + return fRc; + + + # + # Test execution helpers. + # + + def test1RunTestProgs(self, oSession, oTxsSession, fRc, sTestName, sGuestFs): + """ + Runs all the test programs on the test machine. + """ + _ = oSession; + + reporter.testStart(sTestName); + + sMkfsCmd = 'mkfs.' + sGuestFs; + + # Prepare test disks, just create filesystem without partition + reporter.testStart('Preparation'); + fRc = fRc and self.txsRunTest(oTxsSession, 'Create FS 1', 60000, \ + '/sbin/' + sMkfsCmd, + (sMkfsCmd, '/dev/sdb')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Create FS 2', 60000, \ + '/sbin/' + sMkfsCmd, + (sMkfsCmd, '/dev/sdc')); + + # Create test and scratch directory + fRc = fRc and self.txsRunTest(oTxsSession, 'Create /mnt/test', 10000, \ + '/bin/mkdir', + ('mkdir', '/mnt/test')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Create /mnt/scratch', 10000, \ + '/bin/mkdir', + ('mkdir', '/mnt/scratch')); + + # Mount test and scratch directory. + fRc = fRc and self.txsRunTest(oTxsSession, 'Mount /mnt/test', 10000, \ + '/bin/mount', + ('mount', '/dev/sdb','/mnt/test')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Mount /mnt/scratch', 10000, \ + '/bin/mount', + ('mount', '/dev/sdc','/mnt/scratch')); + + fRc = fRc and self.txsRunTest(oTxsSession, 'Copying xfstests', 10000, \ + '/bin/cp', + ('cp', '-r','${CDROM}/${OS.ARCH}/xfstests', '/tmp')); + + reporter.testDone(); + + # Run xfstests (this sh + cd crap is required because the cwd for the script must be in the root + # of the xfstests directory...) + reporter.testStart('xfstests'); + if fRc and 'xfstests' in self.asTests: + fRc = self.txsRunTest(oTxsSession, 'xfstests', 3600000, + '/bin/sh', + ('sh', '-c', '(cd /tmp/xfstests && ./check -g auto)'), + ('TEST_DIR=/mnt/test', 'TEST_DEV=/dev/sdb', 'SCRATCH_MNT=/mnt/scratch', 'SCRATCH_DEV=/dev/sdc', + 'FSTYP=' + sGuestFs)); + reporter.testDone(); + else: + reporter.testDone(fSkipped = True); + + reporter.testDone(not fRc); + return fRc; + + # pylint: disable=too-many-arguments + + def test1OneCfg(self, sVmName, eStorageController, sDiskFormat, sDiskPath1, sDiskPath2, \ + sGuestFs, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + # Attach HD + fRc = oSession.ensureControllerAttached(self.controllerTypeToName(eStorageController)); + fRc = fRc and oSession.setStorageControllerType(eStorageController, self.controllerTypeToName(eStorageController)); + + if sDiskFormat == "iSCSI": + listNames = []; + listValues = []; + listValues = sDiskPath1.split('|'); + listNames.append('TargetAddress'); + listNames.append('TargetName'); + listNames.append('LUN'); + + if self.fpApiVer >= 5.0: + oHd = oSession.oVBox.createMedium(sDiskFormat, sDiskPath1, vboxcon.AccessMode_ReadWrite, \ + vboxcon.DeviceType_HardDisk); + else: + oHd = oSession.oVBox.createHardDisk(sDiskFormat, sDiskPath1); + oHd.type = vboxcon.MediumType_Normal; + oHd.setProperties(listNames, listValues); + + # Attach it. + if fRc is True: + try: + if oSession.fpApiVer >= 4.0: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 1, 0, vboxcon.DeviceType_HardDisk, oHd); + else: + oSession.o.machine.attachDevice(self.controllerTypeToName(eStorageController), + 1, 0, vboxcon.DeviceType_HardDisk, oHd.id); + except: + reporter.errorXcpt('attachDevice("%s",%s,%s,HardDisk,"%s") failed on "%s"' \ + % (self.controllerTypeToName(eStorageController), 1, 0, oHd.id, oSession.sName) ); + fRc = False; + else: + reporter.log('attached "%s" to %s' % (sDiskPath1, oSession.sName)); + else: + fRc = fRc and oSession.createAndAttachHd(sDiskPath1, sDiskFormat, self.controllerTypeToName(eStorageController), + cb = 10*1024*1024*1024, iPort = 1, fImmutable = False); + fRc = fRc and oSession.createAndAttachHd(sDiskPath2, sDiskFormat, self.controllerTypeToName(eStorageController), + cb = 10*1024*1024*1024, iPort = 2, fImmutable = False); + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(sVmName, fCdWait = False, fNatForwardingForTxs = True); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + fRc = self.test1RunTestProgs(oSession, oTxsSession, fRc, 'stress testing', sGuestFs); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + + # Remove disk + oSession = self.openSession(oVM); + if oSession is not None: + try: + oSession.o.machine.detachDevice(self.controllerTypeToName(eStorageController), 1, 0); + oSession.o.machine.detachDevice(self.controllerTypeToName(eStorageController), 2, 0); + + # Remove storage controller if it is not an IDE controller. + if eStorageController not in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,): + oSession.o.machine.removeStorageController(self.controllerTypeToName(eStorageController)); + + oSession.saveSettings(); + oSession.oVBox.deleteHdByLocation(sDiskPath1); + oSession.oVBox.deleteHdByLocation(sDiskPath2); + oSession.saveSettings(); + oSession.close(); + oSession = None; + except: + reporter.errorXcpt('failed to detach/delete disks %s and %s from storage controller' % \ + (sDiskPath1, sDiskPath2)); + else: + fRc = False; + else: + fRc = False; + return fRc; + + def test1OneVM(self, sVmName): + """ + Runs one VM thru the various configurations. + """ + reporter.testStart(sVmName); + fRc = True; + for sStorageCtrl in self.asStorageCtrls: + reporter.testStart(sStorageCtrl); + + if sStorageCtrl == 'AHCI': + eStorageCtrl = vboxcon.StorageControllerType_IntelAhci; + elif sStorageCtrl == 'IDE': + eStorageCtrl = vboxcon.StorageControllerType_PIIX4; + elif sStorageCtrl == 'LsiLogicSAS': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogicSas; + elif sStorageCtrl == 'LsiLogic': + eStorageCtrl = vboxcon.StorageControllerType_LsiLogic; + elif sStorageCtrl == 'BusLogic': + eStorageCtrl = vboxcon.StorageControllerType_BusLogic; + else: + eStorageCtrl = None; + + for sDiskFormat in self.asDiskFormats: + reporter.testStart('%s' % (sDiskFormat,)); + + asPaths = self.asDirs; + + for sDir in asPaths: + reporter.testStart('%s' % (sDir,)); + + sPathDisk1 = sDir + "/disk1.disk"; + sPathDisk2 = sDir + "/disk2.disk"; + + for sGuestFs in self.asGuestFs: + reporter.testStart('%s' % (sGuestFs,)); + + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus,)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + fRc = self.test1OneCfg(sVmName, eStorageCtrl, sDiskFormat, sPathDisk1, sPathDisk2, \ + sGuestFs, cCpus, fHwVirt, fNestedPaging) and fRc and True; + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + return fRc; + + def test1(self): + """ + Executes test #1. + """ + + # Loop thru the test VMs. + for sVM in self.asTestVMs: + # run test on the VM. + if not self.test1OneVM(sVM): + fRc = False; + else: + fRc = True; + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdStorageStress().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/teleportation/Makefile.kmk b/src/VBox/ValidationKit/tests/teleportation/Makefile.kmk new file mode 100644 index 00000000..f01e9d25 --- /dev/null +++ b/src/VBox/ValidationKit/tests/teleportation/Makefile.kmk @@ -0,0 +1,51 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Teleportation. +# + +# +# 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 += ValidationKitTestsTeleportation +ValidationKitTestsTeleportation_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsTeleportation_INST = $(INST_VALIDATIONKIT)testcases/cpu/ +ValidationKitTestsTeleportation_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdTeleportLocal1.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsTeleportation_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py b/src/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py new file mode 100755 index 00000000..ca739865 --- /dev/null +++ b/src/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py @@ -0,0 +1,963 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdTeleportLocal1.py $ + +""" +VirtualBox Validation Kit - Local teleportation testdriver. +""" + +__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: 155244 $" + + +# Standard Python imports. +import os; +import sys; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; + + +class tdTeleportLocal1(vbox.TestDriver): + """ + Local Teleportation Test #1. + """ + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + + self.asTestsDef = ['test1', 'test2']; + self.asTests = ['test1', 'test2']; + self.asSkipTests = []; + self.asTestVMsDef = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw',] + self.asVirtModes = self.asVirtModesDef + self.acCpusDef = [1, 2,] + self.acCpus = self.acCpusDef; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdTeleportLocal1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --tests <test1[:test2[:...]]>'); + reporter.log(' Run the specified tests.'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef))); + reporter.log(' --skip-tests <test1[:test2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --quick'); + reporter.log(' Shorthand for: --virt-modes hwvirt --cpu-counts 1'); + reporter.log(' --test-vms tst-rhel5:tst-win2k3ent:tst-sol10'); + return rc; + + def parseOption(self, asArgs, iArg): + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + if asArgs[iArg]: + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + else: + self.asTestVMs = []; + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--skip-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--tests" takes colon separated list'); + self.asTests = asArgs[iArg].split(':'); + for s in self.asTests: + if s not in self.asTestsDef: + reporter.log('warning: The "--tests" value "%s" does not specify any of our tests.' % (s)); + elif asArgs[iArg] == '--skip-tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-tests" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipTests: + if s not in self.asTestsDef: + reporter.log('warning: The "--skip-tests" value "%s" does not specify any of our tests.' % (s)); + elif asArgs[iArg] == '--quick': + self.asVirtModes = ['hwvirt',]; + self.acCpus = [1,]; + #self.asTestVMs = ['tst-rhel5', 'tst-win2k3ent', 'tst-sol10',]; + self.asTestVMs = ['tst-rhel5', ]; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test VM list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + # Remove skipped tests from the test list. + for sTest in self.asSkipTests: + try: self.asTests.remove(sTest); + except: pass; + + # If no test2, then no test VMs. + if 'test2' not in self.asTests: + self.asTestVMs = []; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + if 'tst-rhel5' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/rhel5.vdi'); + if 'tst-rhel5-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/rhel5-64.vdi'); + if 'tst-sles11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11.vdi'); + if 'tst-sles11-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/sles11-64.vdi'); + if 'tst-oel' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel.vdi'); + if 'tst-oel-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/oel-64.vdi'); + if 'tst-win2k3ent' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-acpi.vdi'); + if 'tst-win2k3ent-64' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k3ent-64.vdi'); + if 'tst-win2k8' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/win2k8.vdi'); + if 'tst-sol10' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris10.vdi'); + if 'tst-sol11' in self.asTestVMs: + self.asRsrcs.append('3.0/tcp/solaris11.vdi'); + return self.asRsrcs; + + def actionConfig(self): + ## @todo actionConfig() and getResourceSet() are working on the same + # set of VMs as tdNetBenchmark1, creating common base class would be + # a good idea. + + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxValidationKit.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxTestSuite.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuite.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKit.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuite.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is not None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/testsuite/VBoxTestSuite.iso'; + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the empty VMs we're going to use for the first tests. + # + + if 'test1' in self.asTests: + #oVM = self.createTestVMs('tst-empty-hwvirt', 0, sKind = 'Other', fVirtEx = True); + #if oVM is None: + # return False; + + oVM = self.createTestVMs('tst-empty-raw', 2, sKind = 'Other', fVirtEx = False); + if oVM is None: + return False; + + # + # Configure the VMs we're going to use for the last test. + # + + # Linux VMs + if 'tst-rhel5' in self.asTestVMs: + oVM = self.createTestVMs('tst-rhel5', 1, '3.0/tcp/rhel5.vdi', sKind = 'RedHat', fIoApic = True, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-rhel5-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-rhel5-64', 1, '3.0/tcp/rhel5-64.vdi', sKind = 'RedHat_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11' in self.asTestVMs: + oVM = self.createTestVMs('tst-sles11', 1, '3.0/tcp/sles11.vdi', sKind = 'OpenSUSE', fIoApic = True, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sles11-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-sles11-64', 1, '3.0/tcp/sles11-64.vdi', sKind = 'OpenSUSE_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel' in self.asTestVMs: + oVM = self.createTestVMs('tst-oel', 1, '3.0/tcp/oel.vdi', sKind = 'Oracle', fIoApic = True, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-oel-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-oel-64', 1, '3.0/tcp/oel-64.vdi', sKind = 'Oracle_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Windows VMs + if 'tst-win2k3ent' in self.asTestVMs: + oVM = self.createTestVMs('tst-win2k3ent', 1, '3.0/tcp/win2k3ent-acpi.vdi', sKind = 'Windows2003', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k3ent-64' in self.asTestVMs: + oVM = self.createTestVMs('tst-win2k3ent-64', 1, '3.0/tcp/win2k3ent-64.vdi', sKind = 'Windows2003_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-win2k8' in self.asTestVMs: + oVM = self.createTestVMs('tst-win2k8', 1, '3.0/tcp/win2k8.vdi', sKind = 'Windows2008_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + # Solaris VMs + if 'tst-sol10' in self.asTestVMs: + oVM = self.createTestVMs('tst-sol10', 1, '3.0/tcp/solaris10.vdi', sKind = 'Solaris_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + if 'tst-sol11' in self.asTestVMs: + oVM = self.createTestVMs('tst-sol11', 1, '3.0/tcp/os2009-11.vdi', sKind = 'Solaris_64', \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = 'test1' not in self.asTests or self.test1(); + if fRc: fRc = 'test2' not in self.asTests or self.test2(); + return fRc; + + + # + # Test config helpers. + # + + def createTestVMs(self, sName, iGroup, *tArgs, **dKeywordArgs): + """ + Wrapper around vbox.createTestVM for creating two VMs, the source + (sName-1) and target (sName-2). + + Returns the 2nd VM object on success, None on failure. + """ + sName1 = sName + '-1'; + oVM = self.createTestVM(sName1, iGroup * 2, *tArgs, **dKeywordArgs); + if oVM is not None: + sName2 = sName + '-2'; + oVM = self.createTestVM(sName2, iGroup * 2 + 1, *tArgs, **dKeywordArgs); + return oVM; + + + # + # Test execution helpers. + # + + def test2Teleport(self, oVmSrc, oSessionSrc, oVmDst): + """ + Attempts a teleportation. + + Returns the input parameters for the next test2Teleport call (source + and destiation are switched around). The input session is closed and + removed from the task list, while the return session is in the list. + """ + + # Enable the teleporter of the VM. + oSession = self.openSession(oVmDst); + fRc = oSession is not None + if fRc: + fRc = oSession.enableTeleporter(); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + if fRc: + # Start the destination VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + + # Do the teleportation. + try: + uDstPort = oVmDst.teleporterPort; + except: + reporter.logXcpt(); + uDstPort = 6502; + oProgressSrc = oSessionSrc.teleport('localhost', uDstPort, 'password', 1024); + if oProgressSrc is not None: + oProgressSrc.wait(); + if oProgressSrc.isSuccess(): + oProgressDst.wait(); + if oProgressSrc.isSuccess() and oProgressDst.isSuccess(): + + # Terminate the source VM. + self.terminateVmBySession(oSessionSrc, oProgressSrc); + + # Return with the source and destination swapped. + return oVmDst, oSessionDst, oVmSrc; + + # Failure / bail out. + oProgressSrc.logResult(); + oProgressDst.logResult(); + self.terminateVmBySession(oSessionDst, oProgressDst); + return oVmSrc, oSessionSrc, oVmDst; + + def test2OneCfg(self, sVmBaseName, cCpus, fHwVirt, fNestedPaging): + """ + Runs the specified VM thru test #1. + """ + + # Reconfigure the source VM. + oVmSrc = self.getVmByName(sVmBaseName + '-1'); + fRc = True; + oSession = self.openSession(oVmSrc); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.setupTeleporter(False, uPort=6501, sPassword='password'); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Reconfigure the destination VM. + oVmDst = self.getVmByName(sVmBaseName + '-2'); + oSession = self.openSession(oVmDst); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(fHwVirt); + fRc = fRc and oSession.enableNestedPaging(fNestedPaging); + fRc = fRc and oSession.setCpuCount(cCpus); + fRc = fRc and oSession.setupTeleporter(True, uPort=6502, sPassword='password'); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Simple test. + if fRc is True: + self.logVmInfo(oVmSrc); + self.logVmInfo(oVmDst); + + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + # Simple back and forth to test the ice... + cTeleportations = 0; + oVmSrc, oSessionSrc, oVmDst = self.test2Teleport(oVmSrc, oSessionSrc, oVmDst); + if reporter.testErrorCount() == 0: + cTeleportations += 1; + oVmSrc, oSessionSrc, oVmDst = self.test2Teleport(oVmSrc, oSessionSrc, oVmDst); + + # Teleport back and forth for a while. + msStart = base.timestampMilli(); + while reporter.testErrorCount() == 0: + cTeleportations += 1; + if oSessionSrc.txsTryConnectViaTcp(2500, 'localhost') is True: + break; + oVmSrc, oSessionSrc, oVmDst = self.test2Teleport(oVmSrc, oSessionSrc, oVmDst); + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > 5*60000: + reporter.testFailure('TXS did not show up after %u min of teleporting (%u)...' \ + % (cMsElapsed / 60000.0, cTeleportations)); + break; + + # Clean up the source VM. + self.terminateVmBySession(oSessionSrc) + return None; + + def test2OneVM(self, sVmBaseName, asSupVirtModes = None, rSupCpus = range(1, 256)): + """ + Runs one VM (a pair really) thru the various configurations. + """ + if asSupVirtModes is None: + asSupVirtModes = self.asVirtModes; + + reporter.testStart(sVmBaseName); + for cCpus in self.acCpus: + if cCpus == 1: reporter.testStart('1 cpu'); + else: reporter.testStart('%u cpus' % (cCpus)); + + for sVirtMode in self.asVirtModes: + if sVirtMode == 'raw' and cCpus > 1: + continue; + if cCpus not in rSupCpus: + continue; + if sVirtMode not in asSupVirtModes: + continue; + hsVirtModeDesc = {}; + hsVirtModeDesc['raw'] = 'Raw-mode'; + hsVirtModeDesc['hwvirt'] = 'HwVirt'; + hsVirtModeDesc['hwvirt-np'] = 'NestedPaging'; + reporter.testStart(hsVirtModeDesc[sVirtMode]); + + fHwVirt = sVirtMode != 'raw'; + fNestedPaging = sVirtMode == 'hwvirt-np'; + self.test2OneCfg(sVmBaseName, cCpus, fHwVirt, fNestedPaging); + + reporter.testDone(); + reporter.testDone(); + return reporter.testDone()[1] == 0; + + def test2(self): + """ + Executes test #2. + """ + + # Loop thru the test VMs. + fRc = True; + for sVM in self.asTestVMs: + # figure args. + asSupVirtModes = None; + if sVM in ('tst-sol11', 'tst-sol10'): # 64-bit only + asSupVirtModes = ['hwvirt', 'hwvirt-np',]; + + # run test on the VM. + if not self.test2OneVM(sVM, asSupVirtModes): + fRc = False; + + return fRc; + # + # Test #1 + # + + def test1ResetVmConfig(self, oVM, fTeleporterEnabled = False): + """ + Resets the teleportation config for the specified VM. + Returns True on success, False on failure. + """ + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.setupTeleporter(fTeleporterEnabled, uPort=6502, sPassword='password'); + fRc = fRc and oSession.saveSettings(); + if not oSession.close(): fRc = False; + oSession = None; + else: + fRc = False; + return fRc; + + def test1Sub7(self, oVmSrc, oVmDst): + """ + Test the password check. + """ + reporter.testStart('Bad password'); + if self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) \ + and self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True): + # Start the target VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + tsPasswords = ('password-bad', 'passwor', 'pass', 'p', '', 'Password', ); + for sPassword in tsPasswords: + reporter.testStart(sPassword); + oProgressSrc = oSessionSrc.teleport('localhost', 6502, sPassword); + if oProgressSrc: + oProgressSrc.wait(); + reporter.log('src: %s' % oProgressSrc.stringifyResult()); + if oProgressSrc.isSuccess(): + reporter.testFailure('IConsole::teleport succeeded with bad password "%s"' % sPassword); + elif oProgressSrc.getErrInfoResultCode() != vbox.ComError.E_FAIL: + reporter.testFailure('IConsole::teleport returns %s instead of E_FAIL' \ + % (vbox.ComError.toString(oProgressSrc.getErrInfoResultCode()),)); + elif oProgressSrc.getErrInfoText() != 'Invalid password': + reporter.testFailure('IConsole::teleport returns "%s" instead of "Invalid password"' \ + % (oProgressSrc.getErrInfoText(),)); + elif oProgressDst.isCompleted(): + reporter.testFailure('Destination completed unexpectedly after bad password "%s"' \ + % sPassword); + else: + reporter.testFailure('IConsole::teleport failed with password "%s"' % sPassword); + if reporter.testDone()[1] != 0: + break; + self.terminateVmBySession(oSessionSrc, oProgressSrc); + self.terminateVmBySession(oSessionDst, oProgressDst); + else: + reporter.testFailure('reconfig failed'); + return reporter.testDone()[1] == 0; + + def test1Sub6(self, oVmSrc, oVmDst): + """ + Misconfigure the target VM and check that teleportation fails with the + same status and message on both ends. + xTracker: #4813 + """ + reporter.testStart('Misconfiguration & error message'); + if self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) \ + and self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True): + # Give the source a bit more RAM. + oSession = self.openSession(oVmSrc); + if oSession is not None: + try: cbMB = oVmSrc.memorySize + 4; + except: cbMB = 1; fRc = False; + fRc = oSession.setRamSize(cbMB); + if not oSession.saveSettings(): fRc = False; + if not oSession.close(): fRc = False; + oSession = None; + else: + fRc = False; + if fRc: + # Start the target VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + # Try teleport. + oProgressSrc = oSessionSrc.teleport('localhost', 6502, 'password'); + if oProgressSrc: + oProgressSrc.wait(); + oProgressDst.wait(); + + reporter.log('src: %s' % oProgressSrc.stringifyResult()); + reporter.log('dst: %s' % oProgressDst.stringifyResult()); + + # Make sure it failed. + if oProgressSrc.isSuccess() and oProgressDst.isSuccess(): + reporter.testFailure('The teleporation did not fail as expected'); + + # Compare the results. + if oProgressSrc.getResult() != oProgressDst.getResult(): + reporter.testFailure('Result differs - src=%s dst=%s' \ + % (vbox.ComError.toString(oProgressSrc.getResult()),\ + vbox.ComError.toString(oProgressDst.getResult()))); + elif oProgressSrc.getErrInfoResultCode() != oProgressDst.getErrInfoResultCode(): + reporter.testFailure('ErrorInfo::resultCode differs - src=%s dst=%s' \ + % (vbox.ComError.toString(oProgressSrc.getErrInfoResultCode()),\ + vbox.ComError.toString(oProgressDst.getErrInfoResultCode()))); + elif oProgressSrc.getErrInfoText() != oProgressDst.getErrInfoText(): + reporter.testFailure('ErrorInfo::text differs - src="%s" dst="%s"' \ + % (oProgressSrc.getErrInfoText(), oProgressDst.getErrInfoText())); + + self.terminateVmBySession(oSessionSrc, oProgressSrc); + self.terminateVmBySession(oSessionDst, oProgressDst); + self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) + self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True); + else: + reporter.testFailure('reconfig #2 failed'); + else: + reporter.testFailure('reconfig #1 failed'); + return reporter.testDone()[1] == 0; + + def test1Sub5(self, oVmSrc, oVmDst): + """ + Test that basic teleporting works. + xTracker: #4749 + """ + reporter.testStart('Simple teleportation'); + for cSecsX2 in range(0, 10): + if self.test1ResetVmConfig(oVmSrc, fTeleporterEnabled = False) \ + and self.test1ResetVmConfig(oVmDst, fTeleporterEnabled = True): + # Start the target VM. + oSessionDst, oProgressDst = self.startVmEx(oVmDst, fWait = False); + if oSessionDst is not None: + if oProgressDst.waitForOperation(iOperation = -3) == 0: + # Start the source VM. + oSessionSrc = self.startVm(oVmSrc); + if oSessionSrc is not None: + self.sleep(cSecsX2 / 2); + # Try teleport. + oProgressSrc = oSessionSrc.teleport('localhost', 6502, 'password'); + if oProgressSrc: + oProgressSrc.wait(); + oProgressDst.wait(); + + self.terminateVmBySession(oSessionSrc, oProgressSrc); + self.terminateVmBySession(oSessionDst, oProgressDst); + else: + reporter.testFailure('reconfig failed'); + return reporter.testDone()[1] == 0; + + def test1Sub4(self, oVM): + """ + Test that we can start and cancel a teleportation target. + (No source VM trying to connect here.) + xTracker: #4965 + """ + reporter.testStart('openRemoteSession cancel'); + for cSecsX2 in range(0, 10): + if self.test1ResetVmConfig(oVM, fTeleporterEnabled = True): + oSession, oProgress = self.startVmEx(oVM, fWait = False); + if oSession is not None: + self.sleep(cSecsX2 / 2); + oProgress.cancel(); + oProgress.wait(); + self.terminateVmBySession(oSession, oProgress); + else: + reporter.testFailure('reconfig failed'); + return reporter.testDone()[1] == 0; + + def test1Sub3(self, oVM): + """ + Test that starting a teleportation target VM will fail if we give it + a bad address to bind to. + """ + reporter.testStart('bad IMachine::teleporterAddress'); + + # re-configure it with a bad bind-to address. + fRc = False; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = oSession.setupTeleporter(True, uPort=6502, sAddress='no.such.hostname.should.ever.exist.duh'); + if not oSession.saveSettings(fClose=True): fRc = False; + oSession = None; + if fRc: + # Try start it. + oSession, oProgress = self.startVmEx(oVM, fWait = False); + if oSession is not None: + oProgress.wait(); + ## TODO: exact error code and look for the IPRT right string. + if not oProgress.isCompleted() or oProgress.getResult() >= 0: + reporter.testFailure('%s' % (oProgress.stringifyResult(),)); + self.terminateVmBySession(oSession, oProgress); + + # put back the old teleporter setup. + self.test1ResetVmConfig(oVM, fTeleporterEnabled = True); + else: + reporter.testFailure('reconfig #1 failed'); + return reporter.testDone()[1] == 0; + + # test1Sub2 - start + + def test1Sub2SetEnabled(self, oSession, fEnabled): + """ This should never fail.""" + try: + oSession.o.machine.teleporterEnabled = fEnabled; + except: + reporter.testFailureXcpt('machine.teleporterEnabled=%s' % (fEnabled,)); + return False; + try: + fNew = oSession.o.machine.teleporterEnabled; + except: + reporter.testFailureXcpt(); + return False; + if fNew != fEnabled: + reporter.testFailure('machine.teleporterEnabled=%s but afterwards it is actually %s' % (fEnabled, fNew)); + return False; + return True; + + def test1Sub2SetPassword(self, oSession, sPassword): + """ This should never fail.""" + try: + oSession.o.machine.teleporterPassword = sPassword; + except: + reporter.testFailureXcpt('machine.teleporterPassword=%s' % (sPassword,)); + return False; + try: + sNew = oSession.o.machine.teleporterPassword; + except: + reporter.testFailureXcpt(); + return False; + if sNew != sPassword: + reporter.testFailure('machine.teleporterPassword="%s" but afterwards it is actually "%s"' % (sPassword, sNew)); + return False; + return True; + + def test1Sub2SetPort(self, oSession, uPort, fInvalid = False): + """ This can fail, thus fInvalid.""" + if not fInvalid: + uOld = uPort; + else: + try: uOld = oSession.o.machine.teleporterPort; + except: return reporter.testFailureXcpt(); + + try: + oSession.o.machine.teleporterPort = uPort; + except Exception as oXcpt: + if not fInvalid or vbox.ComError.notEqual(oXcpt, vbox.ComError.E_INVALIDARG): + return reporter.testFailureXcpt('machine.teleporterPort=%u' % (uPort,)); + else: + if fInvalid: + return reporter.testFailureXcpt('machine.teleporterPort=%u succeeded unexpectedly' % (uPort,)); + + try: uNew = oSession.o.machine.teleporterPort; + except: return reporter.testFailureXcpt(); + if uNew != uOld: + if not fInvalid: + reporter.testFailure('machine.teleporterPort=%u but afterwards it is actually %u' % (uPort, uNew)); + else: + reporter.testFailure('machine.teleporterPort is %u after failure, expected %u' % (uNew, uOld)); + return False; + return True; + + def test1Sub2SetAddress(self, oSession, sAddress): + """ This should never fail.""" + try: + oSession.o.machine.teleporterAddress = sAddress; + except: + reporter.testFailureXcpt('machine.teleporterAddress=%s' % (sAddress,)); + return False; + try: + sNew = oSession.o.machine.teleporterAddress; + except: + reporter.testFailureXcpt(); + return False; + if sNew != sAddress: + reporter.testFailure('machine.teleporterAddress="%s" but afterwards it is actually "%s"' % (sAddress, sNew)); + return False; + return True; + + def test1Sub2(self, oVM): + """ + Test the attributes, making sure that we get exceptions on bad values. + """ + reporter.testStart('IMachine::teleport*'); + + # Save the original teleporter attributes for the discard test. + try: + sOrgAddress = oVM.teleporterAddress; + uOrgPort = oVM.teleporterPort; + sOrgPassword = oVM.teleporterPassword; + fOrgEnabled = oVM.teleporterEnabled; + except: + reporter.testFailureXcpt(); + else: + # Open a session and start messing with the properties. + oSession = self.openSession(oVM); + if oSession is not None: + # Anything goes for the address. + reporter.testStart('teleporterAddress'); + self.test1Sub2SetAddress(oSession, ''); + self.test1Sub2SetAddress(oSession, '1'); + self.test1Sub2SetAddress(oSession, 'Anything goes! ^&$@!$%^'); + reporter.testDone(); + + # The port is restricted to {0..65535}. + reporter.testStart('teleporterPort'); + for uPort in range(0, 1000) + range(16000, 17000) + range(32000, 33000) + range(65000, 65536): + if not self.test1Sub2SetPort(oSession, uPort): + break; + self.processPendingEvents(); + reporter.testDone(); + + reporter.testStart('teleporterPort negative'); + self.test1Sub2SetPort(oSession, 65536, True); + self.test1Sub2SetPort(oSession, 999999, True); + reporter.testDone(); + + # Anything goes for the password. + reporter.testStart('teleporterPassword'); + self.test1Sub2SetPassword(oSession, 'password'); + self.test1Sub2SetPassword(oSession, ''); + self.test1Sub2SetPassword(oSession, '1'); + self.test1Sub2SetPassword(oSession, 'Anything goes! ^&$@!$%^'); + reporter.testDone(); + + # Just test that it works. + reporter.testStart('teleporterEnabled'); + self.test1Sub2SetEnabled(oSession, True); + self.test1Sub2SetEnabled(oSession, True); + self.test1Sub2SetEnabled(oSession, False); + self.test1Sub2SetEnabled(oSession, False); + reporter.testDone(); + + # Finally, discard the changes, close the session and check + # that we're back to the originals. + if not oSession.discardSettings(True): + reporter.testFailure('Failed to discard settings & close the session') + else: + reporter.testFailure('Failed to open VM session') + try: + if oVM.teleporterAddress != sOrgAddress: reporter.testFailure('Rollback failed for teleporterAddress'); + if oVM.teleporterPort != uOrgPort: reporter.testFailure('Rollback failed for teleporterPort'); + if oVM.teleporterPassword != sOrgPassword: reporter.testFailure('Rollback failed for teleporterPassword'); + if oVM.teleporterEnabled != fOrgEnabled: reporter.testFailure('Rollback failed for teleporterEnabled'); + except: + reporter.testFailureXcpt(); + return reporter.testDone()[1] != 0; + + # test1Sub1 - start + + def test1Sub1DoTeleport(self, oSession, sHostname, uPort, sPassword, cMsMaxDowntime, hrcExpected, sTestName): + """ Do a bad IConsole::teleport call and check the result.""" + reporter.testStart(sTestName); + fRc = False; + try: + oProgress = oSession.o.console.teleport(sHostname, uPort, sPassword, cMsMaxDowntime); + except vbox.ComException as oXcpt: + if vbox.ComError.equal(oXcpt, hrcExpected): + fRc = True; + else: + reporter.testFailure('hresult %s, expected %s' \ + % (vbox.ComError.toString(oXcpt.hresult), + vbox.ComError.toString(hrcExpected))); + except Exception as oXcpt: + reporter.testFailure('Unexpected exception %s' % (oXcpt)); + else: + reporter.testFailure('Unpexected success'); + oProgress.cancel(); + oProgress.wait(); + reporter.testDone(); + return fRc; + + def test1Sub1(self, oVM): + """ Test simple IConsole::teleport() failure paths. """ + reporter.testStart('IConsole::teleport'); + oSession = self.startVm(oVM); + if oSession: + self.test1Sub1DoTeleport(oSession, 'localhost', 65536, 'password', 10000, + vbox.ComError.E_INVALIDARG, 'Bad port value 65536'); + self.test1Sub1DoTeleport(oSession, 'localhost', 0, 'password', 10000, + vbox.ComError.E_INVALIDARG, 'Bad port value 0'); + self.test1Sub1DoTeleport(oSession, 'localhost', 5000, 'password', 0, + vbox.ComError.E_INVALIDARG, 'Bad max downtime'); + self.test1Sub1DoTeleport(oSession, '', 5000, 'password', 10000, + vbox.ComError.E_INVALIDARG, 'No hostname'); + self.test1Sub1DoTeleport(oSession, 'no.such.hostname.should.ever.exist.duh', 5000, 'password', 0, + vbox.ComError.E_INVALIDARG, 'Non-existing host'); + + self.terminateVmBySession(oSession) + else: + reporter.testFailure('startVm'); + return reporter.testDone()[1] == 0; + + + def test1(self): + """ + Executes test #1 - Negative API testing. + + ASSUMES that the VMs are + """ + reporter.testStart('Test 1'); + + # Get the VMs. + #oVmHwVirt1 = self.getVmByName('tst-empty-hwvirt-1'); + #oVmHwVirt2 = self.getVmByName('tst-empty-hwvirt-2'); + oVmRaw1 = self.getVmByName('tst-empty-raw-1'); + oVmRaw2 = self.getVmByName('tst-empty-raw-2'); + + # Reset their teleportation related configuration. + fRc = True; + #for oVM in (oVmHwVirt1, oVmHwVirt2, oVmRaw1, oVmRaw2): + for oVM in (oVmRaw1, oVmRaw2): + if not self.test1ResetVmConfig(oVM): fRc = False; + + # Do the testing (don't both with fRc on the subtests). + if fRc: + self.test1Sub1(oVmRaw1); + self.test1Sub2(oVmRaw2); + self.test1Sub3(oVmRaw2); + self.test1Sub4(oVmRaw2); + self.processPendingEvents(); + self.test1Sub5(oVmRaw1, oVmRaw2); + self.test1Sub6(oVmRaw1, oVmRaw2); + self.test1Sub7(oVmRaw1, oVmRaw2); + else: + reporter.testFailure('Failed to reset the VM configs') + return reporter.testDone()[1] == 0; + + + +if __name__ == '__main__': + sys.exit(tdTeleportLocal1().main(sys.argv)); + 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)) diff --git a/src/VBox/ValidationKit/tests/usb/Makefile.kmk b/src/VBox/ValidationKit/tests/usb/Makefile.kmk new file mode 100644 index 00000000..3a4741cc --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/Makefile.kmk @@ -0,0 +1,53 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - USB. +# + +# +# Copyright (C) 2014-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 += ValidationKitTestsUsb +ValidationKitTestsUsb_TEMPLATE = VBoxValidationKitR3 +ValidationKitTestsUsb_INST = $(INST_VALIDATIONKIT)tests/usb/ +ValidationKitTestsUsb_EXEC_SOURCES := \ + $(PATH_SUB_CURRENT)/tdUsb1.py \ + $(PATH_SUB_CURRENT)/usbgadget.py \ + $(PATH_SUB_CURRENT)/tst-utsgadget.py + +VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsUsb_EXEC_SOURCES) + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/tests/usb/tdUsb1.py b/src/VBox/ValidationKit/tests/usb/tdUsb1.py new file mode 100755 index 00000000..9dd20ebf --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/tdUsb1.py @@ -0,0 +1,590 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdUsb1.py $ + +""" +VirtualBox Validation Kit - USB testcase and benchmark. +""" + +__copyright__ = \ +""" +Copyright (C) 2014-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: 155244 $" + + +# Standard Python imports. +import os; +import sys; +import socket; + +# 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 testdriver import reporter; +from testdriver import base; +from testdriver import vbox; +from testdriver import vboxcon; + +# USB gadget control import +import usbgadget; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + xrange = range; # pylint: disable=redefined-builtin,invalid-name + + +class tdUsbBenchmark(vbox.TestDriver): # pylint: disable=too-many-instance-attributes + """ + USB benchmark. + """ + + # The available test devices + # + # The first key is the hostname of the host the test is running on. + # It contains a new dictionary with the attached gadgets based on the + # USB speed we want to test (Low, Full, High, Super). + # The parameters consist of the hostname of the gadget in the network + # and the hardware type. + kdGadgetParams = { + 'adaris': { + 'Low': ('usbtest.de.oracle.com', None), + 'Full': ('usbtest.de.oracle.com', None), + 'High': ('usbtest.de.oracle.com', None), + 'Super': ('usbtest.de.oracle.com', None) + }, + }; + + # Mappings of USB controllers to supported USB device speeds. + kdUsbSpeedMappings = { + 'OHCI': ['Low', 'Full'], + 'EHCI': ['High'], + 'XHCI': ['Low', 'Full', 'High', 'Super'] + }; + + # Tests currently disabled because they fail, need investigation. + kdUsbTestsDisabled = { + 'Low': [24], + 'Full': [24], + 'High': [24], + 'Super': [24] + }; + + def __init__(self): + vbox.TestDriver.__init__(self); + self.asRsrcs = None; + self.asTestVMsDef = ['tst-arch']; + self.asTestVMs = self.asTestVMsDef; + self.asSkipVMs = []; + self.asVirtModesDef = ['hwvirt', 'hwvirt-np', 'raw']; + self.asVirtModes = self.asVirtModesDef; + self.acCpusDef = [1, 2,]; + self.acCpus = self.acCpusDef; + self.asUsbCtrlsDef = ['OHCI', 'EHCI', 'XHCI']; + self.asUsbCtrls = self.asUsbCtrlsDef; + self.asUsbSpeedDef = ['Low', 'Full', 'High', 'Super']; + self.asUsbSpeed = self.asUsbSpeedDef; + self.asUsbTestsDef = ['Compliance', 'Reattach']; + self.asUsbTests = self.asUsbTestsDef; + self.cUsbReattachCyclesDef = 100; + self.cUsbReattachCycles = self.cUsbReattachCyclesDef; + self.sHostname = socket.gethostname().lower(); + self.sGadgetHostnameDef = 'usbtest.de.oracle.com'; + self.uGadgetPortDef = None; + self.sUsbCapturePathDef = self.sScratchPath; + self.sUsbCapturePath = self.sUsbCapturePathDef; + self.fUsbCapture = False; + + # + # Overridden methods. + # + def showUsage(self): + rc = vbox.TestDriver.showUsage(self); + reporter.log(''); + reporter.log('tdUsb1 Options:'); + reporter.log(' --virt-modes <m1[:m2[:]]'); + reporter.log(' Default: %s' % (':'.join(self.asVirtModesDef))); + reporter.log(' --cpu-counts <c1[:c2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.acCpusDef))); + reporter.log(' --test-vms <vm1[:vm2[:...]]>'); + reporter.log(' Test the specified VMs in the given order. Use this to change'); + reporter.log(' the execution order or limit the choice of VMs'); + reporter.log(' Default: %s (all)' % (':'.join(self.asTestVMsDef))); + reporter.log(' --skip-vms <vm1[:vm2[:...]]>'); + reporter.log(' Skip the specified VMs when testing.'); + reporter.log(' --usb-ctrls <u1[:u2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.asUsbCtrlsDef))); + reporter.log(' --usb-speed <s1[:s2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.asUsbSpeedDef))); + reporter.log(' --usb-tests <s1[:s2[:]]'); + reporter.log(' Default: %s' % (':'.join(str(c) for c in self.asUsbTestsDef))); + reporter.log(' --usb-reattach-cycles <cycles>'); + reporter.log(' Default: %s' % (self.cUsbReattachCyclesDef)); + reporter.log(' --hostname: <hostname>'); + reporter.log(' Default: %s' % (self.sHostname)); + reporter.log(' --default-gadget-host <hostname>'); + reporter.log(' Default: %s' % (self.sGadgetHostnameDef)); + reporter.log(' --default-gadget-port <port>'); + reporter.log(' Default: %s' % (6042)); + reporter.log(' --usb-capture-path <path>'); + reporter.log(' Default: %s' % (self.sUsbCapturePathDef)); + reporter.log(' --usb-capture'); + reporter.log(' Whether to capture the USB traffic for each test'); + return rc; + + def parseOption(self, asArgs, iArg): # pylint: disable=too-many-branches,too-many-statements + if asArgs[iArg] == '--virt-modes': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--virt-modes" takes a colon separated list of modes'); + self.asVirtModes = asArgs[iArg].split(':'); + for s in self.asVirtModes: + if s not in self.asVirtModesDef: + raise base.InvalidOption('The "--virt-modes" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asVirtModesDef))); + elif asArgs[iArg] == '--cpu-counts': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--cpu-counts" takes a colon separated list of cpu counts'); + self.acCpus = []; + for s in asArgs[iArg].split(':'): + try: c = int(s); + except: raise base.InvalidOption('The "--cpu-counts" value "%s" is not an integer' % (s,)); + if c <= 0: raise base.InvalidOption('The "--cpu-counts" value "%s" is zero or negative' % (s,)); + self.acCpus.append(c); + elif asArgs[iArg] == '--test-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--test-vms" takes colon separated list'); + self.asTestVMs = asArgs[iArg].split(':'); + for s in self.asTestVMs: + if s not in self.asTestVMsDef: + raise base.InvalidOption('The "--test-vms" value "%s" is not valid; valid values are: %s' \ + % (s, ' '.join(self.asTestVMsDef))); + elif asArgs[iArg] == '--skip-vms': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--skip-vms" takes colon separated list'); + self.asSkipVMs = asArgs[iArg].split(':'); + for s in self.asSkipVMs: + if s not in self.asTestVMsDef: + reporter.log('warning: The "--test-vms" value "%s" does not specify any of our test VMs.' % (s)); + elif asArgs[iArg] == '--usb-ctrls': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-ctrls" takes a colon separated list of USB controllers'); + self.asUsbCtrls = asArgs[iArg].split(':'); + for s in self.asUsbCtrls: + if s not in self.asUsbCtrlsDef: + reporter.log('warning: The "--usb-ctrls" value "%s" is not a valid USB controller.' % (s)); + elif asArgs[iArg] == '--usb-speed': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-speed" takes a colon separated list of USB speeds'); + self.asUsbSpeed = asArgs[iArg].split(':'); + for s in self.asUsbSpeed: + if s not in self.asUsbSpeedDef: + reporter.log('warning: The "--usb-speed" value "%s" is not a valid USB speed.' % (s)); + elif asArgs[iArg] == '--usb-tests': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-tests" takes a colon separated list of USB tests'); + self.asUsbTests = asArgs[iArg].split(':'); + for s in self.asUsbTests: + if s not in self.asUsbTestsDef: + reporter.log('warning: The "--usb-tests" value "%s" is not a valid USB test.' % (s)); + elif asArgs[iArg] == '--usb-reattach-cycles': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-reattach-cycles" takes cycle count'); + try: self.cUsbReattachCycles = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--usb-reattach-cycles" value "%s" is not an integer' \ + % (asArgs[iArg],)); + if self.cUsbReattachCycles <= 0: + raise base.InvalidOption('The "--usb-reattach-cycles" value "%s" is zero or negative.' \ + % (self.cUsbReattachCycles,)); + elif asArgs[iArg] == '--hostname': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--hostname" takes a hostname'); + self.sHostname = asArgs[iArg]; + elif asArgs[iArg] == '--default-gadget-host': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--default-gadget-host" takes a hostname'); + self.sGadgetHostnameDef = asArgs[iArg]; + elif asArgs[iArg] == '--default-gadget-port': + iArg += 1; + if iArg >= len(asArgs): raise base.InvalidOption('The "--default-gadget-port" takes port number'); + try: self.uGadgetPortDef = int(asArgs[iArg]); + except: raise base.InvalidOption('The "--default-gadget-port" value "%s" is not an integer' \ + % (asArgs[iArg],)); + if self.uGadgetPortDef <= 0: + raise base.InvalidOption('The "--default-gadget-port" value "%s" is zero or negative.' \ + % (self.uGadgetPortDef,)); + elif asArgs[iArg] == '--usb-capture-path': + if iArg >= len(asArgs): raise base.InvalidOption('The "--usb-capture-path" takes a path argument'); + self.sUsbCapturePath = asArgs[iArg]; + elif asArgs[iArg] == '--usb-capture': + self.fUsbCapture = True; + else: + return vbox.TestDriver.parseOption(self, asArgs, iArg); + return iArg + 1; + + def completeOptions(self): + # Remove skipped VMs from the test list. + for sVM in self.asSkipVMs: + try: self.asTestVMs.remove(sVM); + except: pass; + + return vbox.TestDriver.completeOptions(self); + + def getResourceSet(self): + # Construct the resource list the first time it's queried. + if self.asRsrcs is None: + self.asRsrcs = []; + + if 'tst-arch' in self.asTestVMs: + self.asRsrcs.append('4.2/usb/tst-arch.vdi'); + + return self.asRsrcs; + + def actionConfig(self): + + # Some stupid trickery to guess the location of the iso. ## fixme - testsuite unzip ++ + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxValidationKit.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../VBoxTestSuite.iso')); + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/mnt/ramdisk/vbox/svn/trunk/testsuite/VBoxTestSuite.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sCur = os.getcwd(); + for i in range(0, 10): + sVBoxValidationKit_iso = os.path.join(sCur, 'validationkit/VBoxValidationKit.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sVBoxValidationKit_iso = os.path.join(sCur, 'testsuite/VBoxTestSuite.iso'); + if os.path.isfile(sVBoxValidationKit_iso): + break; + sCur = os.path.abspath(os.path.join(sCur, '..')); + if i is None: pass; # shut up pychecker/pylint. + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/validationkit/VBoxValidationKit.iso'; + if not os.path.isfile(sVBoxValidationKit_iso): + sVBoxValidationKit_iso = '/home/bird/testsuite/VBoxTestSuite.iso'; + + # Make sure vboxapi has been imported so we can use the constants. + if not self.importVBoxApi(): + return False; + + # + # Configure the VMs we're going to use. + # + + # Linux VMs + if 'tst-arch' in self.asTestVMs: + oVM = self.createTestVM('tst-arch', 1, '4.2/usb/tst-arch.vdi', sKind = 'ArchLinux_64', fIoApic = True, \ + eNic0AttachType = vboxcon.NetworkAttachmentType_NAT, \ + sDvdImage = sVBoxValidationKit_iso); + if oVM is None: + return False; + + return True; + + def actionExecute(self): + """ + Execute the testcase. + """ + fRc = self.testUsb(); + return fRc; + + def getGadgetParams(self, sHostname, sSpeed): + """ + Returns the gadget hostname and port from the + given hostname the test is running on and device speed we want to test. + """ + kdGadgetsConfigured = self.kdGadgetParams.get(sHostname); + if kdGadgetsConfigured is not None: + return kdGadgetsConfigured.get(sSpeed); + + return (self.sGadgetHostnameDef, self.uGadgetPortDef); + + def getCaptureFilePath(self, sUsbCtrl, sSpeed): + """ + Returns capture filename from the given data. + """ + + return '%s%s%s-%s.pcap' % (self.sUsbCapturePath, os.sep, sUsbCtrl, sSpeed); + + def attachUsbDeviceToVm(self, oSession, sVendorId, sProductId, iBusId, + sCaptureFile = None): + """ + Attaches the given USB device to the VM either via a filter + or directly if capturing the USB traffic is enabled. + + Returns True on success, False on failure. + """ + fRc = False; + if sCaptureFile is None: + fRc = oSession.addUsbDeviceFilter('Compliance device', sVendorId = sVendorId, sProductId = sProductId, \ + sPort = format(iBusId, 'x')); + else: + # Search for the correct device in the USB device list waiting for some time + # to let it appear. + iVendorId = int(sVendorId, 16); + iProductId = int(sProductId, 16); + + # Try a few times to give VBoxSVC a chance to detect the new device. + for _ in xrange(5): + fFound = False; + aoUsbDevs = self.oVBoxMgr.getArray(self.oVBox.host, 'USBDevices'); + for oUsbDev in aoUsbDevs: + if oUsbDev.vendorId == iVendorId \ + and oUsbDev.productId == iProductId \ + and oUsbDev.port == iBusId: + fFound = True; + fRc = oSession.attachUsbDevice(oUsbDev.id, sCaptureFile); + break; + + if fFound: + break; + + # Wait a moment until the next try. + self.sleep(1); + + if fRc: + # Wait a moment to let the USB device appear + self.sleep(9); + + return fRc; + + # + # Test execution helpers. + # + def testUsbCompliance(self, oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile = None): + """ + Test VirtualBoxs USB stack in a VM. + """ + # Get configured USB test devices from hostname we are running on + sGadgetHost, uGadgetPort = self.getGadgetParams(self.sHostname, sSpeed); + + oUsbGadget = usbgadget.UsbGadget(); + reporter.log('Connecting to UTS: ' + sGadgetHost); + fRc = oUsbGadget.connectTo(30 * 1000, sGadgetHost, uPort = uGadgetPort, fTryConnect = True); + if fRc is True: + reporter.log('Connect succeeded'); + self.oVBox.host.addUSBDeviceSource('USBIP', sGadgetHost, sGadgetHost + (':%s' % oUsbGadget.getUsbIpPort()), [], []); + + fSuperSpeed = False; + if sSpeed == 'Super': + fSuperSpeed = True; + + # Create test device gadget and a filter to attach the device automatically. + fRc = oUsbGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest, fSuperSpeed); + if fRc is True: + iBusId, _ = oUsbGadget.getGadgetBusAndDevId(); + fRc = self.attachUsbDeviceToVm(oSession, '0525', 'a4a0', iBusId, sCaptureFile); + if fRc is True: + tupCmdLine = ('UsbTest', ); + # Exclude a few tests which hang and cause a timeout, need investigation. + lstTestsExclude = self.kdUsbTestsDisabled.get(sSpeed); + for iTestExclude in lstTestsExclude: + tupCmdLine = tupCmdLine + ('--exclude', str(iTestExclude)); + + fRc = self.txsRunTest(oTxsSession, 'UsbTest', 3600 * 1000, \ + '${CDROM}/${OS/ARCH}/UsbTest${EXESUFF}', tupCmdLine); + if not fRc: + reporter.testFailure('Running USB test utility failed'); + else: + reporter.testFailure('Failed to attach USB device to VM'); + oUsbGadget.disconnectFrom(); + else: + reporter.testFailure('Failed to impersonate test device'); + + self.oVBox.host.removeUSBDeviceSource(sGadgetHost); + else: + reporter.log('warning: Failed to connect to USB gadget'); + fRc = None + + _ = sUsbCtrl; + return fRc; + + def testUsbReattach(self, oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile = None): # pylint: disable=unused-argument + """ + Tests that rapid connect/disconnect cycles work. + """ + # Get configured USB test devices from hostname we are running on + sGadgetHost, uGadgetPort = self.getGadgetParams(self.sHostname, sSpeed); + + oUsbGadget = usbgadget.UsbGadget(); + reporter.log('Connecting to UTS: ' + sGadgetHost); + fRc = oUsbGadget.connectTo(30 * 1000, sGadgetHost, uPort = uGadgetPort, fTryConnect = True); + if fRc is True: + self.oVBox.host.addUSBDeviceSource('USBIP', sGadgetHost, sGadgetHost + (':%s' % oUsbGadget.getUsbIpPort()), [], []); + + fSuperSpeed = False; + if sSpeed == 'Super': + fSuperSpeed = True; + + # Create test device gadget and a filter to attach the device automatically. + fRc = oUsbGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest, fSuperSpeed); + if fRc is True: + iBusId, _ = oUsbGadget.getGadgetBusAndDevId(); + fRc = self.attachUsbDeviceToVm(oSession, '0525', 'a4a0', iBusId, sCaptureFile); + if fRc is True: + + # Wait a moment to let the USB device appear + self.sleep(3); + + # Do a rapid disconnect reconnect cycle. Wait a second before disconnecting + # again or it will happen so fast that the VM can't attach the new device. + # @todo: Get rid of the constant wait and use an event to get notified when + # the device was attached. + for iCycle in xrange (0, self.cUsbReattachCycles): + fRc = oUsbGadget.disconnectUsb(); + fRc = fRc and oUsbGadget.connectUsb(); + if not fRc: + reporter.testFailure('Reattach cycle %s failed on the gadget device' % (iCycle)); + break; + self.sleep(1); + + else: + reporter.testFailure('Failed to create USB device filter'); + + oUsbGadget.disconnectFrom(); + else: + reporter.testFailure('Failed to impersonate test device'); + else: + reporter.log('warning: Failed to connect to USB gadget'); + fRc = None + + return fRc; + + def testUsbOneCfg(self, sVmName, sUsbCtrl, sSpeed, sUsbTest): + """ + Runs the specified VM thru one specified test. + + Returns a success indicator on the general test execution. This is not + the actual test result. + """ + oVM = self.getVmByName(sVmName); + + # Reconfigure the VM + fRc = True; + oSession = self.openSession(oVM); + if oSession is not None: + fRc = fRc and oSession.enableVirtEx(True); + fRc = fRc and oSession.enableNestedPaging(True); + + # Make sure controllers are disabled initially. + fRc = fRc and oSession.enableUsbOhci(False); + fRc = fRc and oSession.enableUsbEhci(False); + fRc = fRc and oSession.enableUsbXhci(False); + + if sUsbCtrl == 'OHCI': + fRc = fRc and oSession.enableUsbOhci(True); + elif sUsbCtrl == 'EHCI': + fRc = fRc and oSession.enableUsbEhci(True); + elif sUsbCtrl == 'XHCI': + fRc = fRc and oSession.enableUsbXhci(True); + fRc = fRc and oSession.saveSettings(); + fRc = oSession.close() and fRc and True; # pychecker hack. + oSession = None; + else: + fRc = False; + + # Start up. + if fRc is True: + self.logVmInfo(oVM); + oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(sVmName, fCdWait = False, fNatForwardingForTxs = False); + if oSession is not None: + self.addTask(oTxsSession); + + # Fudge factor - Allow the guest to finish starting up. + self.sleep(5); + + sCaptureFile = None; + if self.fUsbCapture: + sCaptureFile = self.getCaptureFilePath(sUsbCtrl, sSpeed); + + if sUsbTest == 'Compliance': + fRc = self.testUsbCompliance(oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile); + elif sUsbTest == 'Reattach': + fRc = self.testUsbReattach(oSession, oTxsSession, sUsbCtrl, sSpeed, sCaptureFile); + + # cleanup. + self.removeTask(oTxsSession); + self.terminateVmBySession(oSession) + + # Add the traffic dump if it exists and the test failed + if reporter.testErrorCount() > 0 \ + and sCaptureFile is not None \ + and os.path.exists(sCaptureFile): + reporter.addLogFile(sCaptureFile, 'misc/other', 'USB traffic dump'); + else: + fRc = False; + return fRc; + + def testUsbForOneVM(self, sVmName): + """ + Runs one VM thru the various configurations. + """ + fRc = False; + reporter.testStart(sVmName); + for sUsbCtrl in self.asUsbCtrls: + reporter.testStart(sUsbCtrl) + for sUsbSpeed in self.asUsbSpeed: + asSupportedSpeeds = self.kdUsbSpeedMappings.get(sUsbCtrl); + if sUsbSpeed in asSupportedSpeeds: + reporter.testStart(sUsbSpeed) + for sUsbTest in self.asUsbTests: + reporter.testStart(sUsbTest) + fRc = self.testUsbOneCfg(sVmName, sUsbCtrl, sUsbSpeed, sUsbTest); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + reporter.testDone(); + return fRc; + + def testUsb(self): + """ + Executes USB test. + """ + + reporter.log("Running on host: " + self.sHostname); + + # Loop thru the test VMs. + for sVM in self.asTestVMs: + # run test on the VM. + fRc = self.testUsbForOneVM(sVM); + + return fRc; + + + +if __name__ == '__main__': + sys.exit(tdUsbBenchmark().main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/usb/tst-utsgadget.py b/src/VBox/ValidationKit/tests/usb/tst-utsgadget.py new file mode 100755 index 00000000..03245e00 --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/tst-utsgadget.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# $Id: tst-utsgadget.py $ + +""" +Simple testcase for usbgadget2.py. +""" + +__copyright__ = \ +""" +Copyright (C) 2016-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: 155244 $" + +# Standard python imports. +import sys + +# Validation Kit imports. +sys.path.insert(0, '.'); +sys.path.insert(0, '..'); +sys.path.insert(0, '../..'); +from common import utils; +from testdriver import reporter; +import usbgadget; + + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +g_cTests = 0; +g_cFailures = 0 + +def boolRes(rc, fExpect = True): + """Checks a boolean result.""" + global g_cTests, g_cFailures; + g_cTests = g_cTests + 1; + if isinstance(rc, bool): + if rc == fExpect: + return 'PASSED'; + g_cFailures = g_cFailures + 1; + return 'FAILED'; + +def stringRes(rc, sExpect): + """Checks a string result.""" + global g_cTests, g_cFailures; + g_cTests = g_cTests + 1; + if utils.isString(rc): + if rc == sExpect: + return 'PASSED'; + g_cFailures = g_cFailures + 1; + return 'FAILED'; + +def main(asArgs): # pylint: disable=missing-docstring,too-many-locals,too-many-statements + cMsTimeout = long(30*1000); + sAddress = 'localhost'; + uPort = None; + fStdTests = True; + + i = 1; + while i < len(asArgs): + if asArgs[i] == '--hostname': + sAddress = asArgs[i + 1]; + i = i + 2; + elif asArgs[i] == '--port': + uPort = int(asArgs[i + 1]); + i = i + 2; + elif asArgs[i] == '--timeout': + cMsTimeout = long(asArgs[i + 1]); + i = i + 2; + elif asArgs[i] == '--help': + print('tst-utsgadget.py [--hostname <addr|name>] [--port <num>] [--timeout <cMS>]'); + return 0; + else: + print('Unknown argument: %s' % (asArgs[i],)); + return 2; + + oGadget = usbgadget.UsbGadget(); + if uPort is None: + rc = oGadget.connectTo(cMsTimeout, sAddress); + else: + rc = oGadget.connectTo(cMsTimeout, sAddress, uPort = uPort); + if rc is False: + print('connectTo failed'); + return 1; + + if fStdTests: + rc = oGadget.getUsbIpPort() is not None; + print('%s: getUsbIpPort() -> %s' % (boolRes(rc), oGadget.getUsbIpPort(),)); + + rc = oGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest); + print('%s: impersonate()' % (boolRes(rc),)); + + rc = oGadget.disconnectUsb(); + print('%s: disconnectUsb()' % (boolRes(rc),)); + + rc = oGadget.connectUsb(); + print('%s: connectUsb()' % (boolRes(rc),)); + + rc = oGadget.clearImpersonation(); + print('%s: clearImpersonation()' % (boolRes(rc),)); + + # Test super speed (and therefore passing configuration items) + rc = oGadget.impersonate(usbgadget.g_ksGadgetImpersonationTest, True); + print('%s: impersonate(, True)' % (boolRes(rc),)); + + rc = oGadget.clearImpersonation(); + print('%s: clearImpersonation()' % (boolRes(rc),)); + + # Done + rc = oGadget.disconnectFrom(); + print('%s: disconnectFrom() -> %s' % (boolRes(rc), rc,)); + + if g_cFailures != 0: + print('tst-utsgadget.py: %u out of %u test failed' % (g_cFailures, g_cTests,)); + return 1; + print('tst-utsgadget.py: all %u tests passed!' % (g_cTests,)); + return 0; + + +if __name__ == '__main__': + reporter.incVerbosity(); + reporter.incVerbosity(); + reporter.incVerbosity(); + reporter.incVerbosity(); + sys.exit(main(sys.argv)); + diff --git a/src/VBox/ValidationKit/tests/usb/usbgadget.py b/src/VBox/ValidationKit/tests/usb/usbgadget.py new file mode 100755 index 00000000..0a7c4805 --- /dev/null +++ b/src/VBox/ValidationKit/tests/usb/usbgadget.py @@ -0,0 +1,1478 @@ +# -*- coding: utf-8 -*- +# $Id: usbgadget.py $ +# pylint: disable=too-many-lines + +""" +UTS (USB Test Service) client. +""" +__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: 155244 $" + +# Standard Python imports. +import array +import errno +import select +import socket +import sys; +import threading +import time +import zlib + +# Validation Kit imports. +from common import utils; +from testdriver import base; +from testdriver import reporter; +from testdriver.base import TdTaskBase; + +# Python 3 hacks: +if sys.version_info[0] >= 3: + long = int; # pylint: disable=redefined-builtin,invalid-name + + +## @name USB gadget impersonation string constants. +## @{ +g_ksGadgetImpersonationInvalid = 'Invalid'; +g_ksGadgetImpersonationTest = 'Test'; +g_ksGadgetImpersonationMsd = 'Msd'; +g_ksGadgetImpersonationWebcam = 'Webcam'; +g_ksGadgetImpersonationEther = 'Ether'; +## @} + +## @name USB gadget type used in the UTS protocol. +## @{ +g_kiGadgetTypeTest = 1; +## @} + +## @name USB gadget access methods used in the UTS protocol. +## @{ +g_kiGadgetAccessUsbIp = 1; +## @} + +## @name USB gadget config types. +## @{ +g_kiGadgetCfgTypeBool = 1; +g_kiGadgetCfgTypeString = 2; +g_kiGadgetCfgTypeUInt8 = 3; +g_kiGadgetCfgTypeUInt16 = 4; +g_kiGadgetCfgTypeUInt32 = 5; +g_kiGadgetCfgTypeUInt64 = 6; +g_kiGadgetCfgTypeInt8 = 7; +g_kiGadgetCfgTypeInt16 = 8; +g_kiGadgetCfgTypeInt32 = 9; +g_kiGadgetCfgTypeInt64 = 10; +## @} + +# +# Helpers for decoding data received from the UTS. +# These are used both the Session and Transport classes. +# + +def getU64(abData, off): + """Get a U64 field.""" + return abData[off] \ + + abData[off + 1] * 256 \ + + abData[off + 2] * 65536 \ + + abData[off + 3] * 16777216 \ + + abData[off + 4] * 4294967296 \ + + abData[off + 5] * 1099511627776 \ + + abData[off + 6] * 281474976710656 \ + + abData[off + 7] * 72057594037927936; + +def getU32(abData, off): + """Get a U32 field.""" + return abData[off] \ + + abData[off + 1] * 256 \ + + abData[off + 2] * 65536 \ + + abData[off + 3] * 16777216; + +def getU16(abData, off): + """Get a U16 field.""" + return abData[off] \ + + abData[off + 1] * 256; + +def getU8(abData, off): + """Get a U8 field.""" + return abData[off]; + +def getSZ(abData, off, sDefault = None): + """ + Get a zero-terminated string field. + Returns sDefault if the string is invalid. + """ + cchStr = getSZLen(abData, off); + if cchStr >= 0: + abStr = abData[off:(off + cchStr)]; + try: + return abStr.tostring().decode('utf_8'); + except: + reporter.errorXcpt('getSZ(,%u)' % (off)); + return sDefault; + +def getSZLen(abData, off): + """ + Get the length of a zero-terminated string field, in bytes. + Returns -1 if off is beyond the data packet or not properly terminated. + """ + cbData = len(abData); + if off >= cbData: + return -1; + + offCur = off; + while abData[offCur] != 0: + offCur = offCur + 1; + if offCur >= cbData: + return -1; + + return offCur - off; + +def isValidOpcodeEncoding(sOpcode): + """ + Checks if the specified opcode is valid or not. + Returns True on success. + Returns False if it is invalid, details in the log. + """ + sSet1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + sSet2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ "; + if len(sOpcode) != 8: + reporter.error("invalid opcode length: %s" % (len(sOpcode))); + return False; + for i in range(0, 1): + if sSet1.find(sOpcode[i]) < 0: + reporter.error("invalid opcode char #%u: %s" % (i, sOpcode)); + return False; + for i in range(2, 7): + if sSet2.find(sOpcode[i]) < 0: + reporter.error("invalid opcode char #%u: %s" % (i, sOpcode)); + return False; + return True; + +# +# Helper for encoding data sent to the UTS. +# + +def u32ToByteArray(u32): + """Encodes the u32 value as a little endian byte (B) array.""" + return array.array('B', \ + ( u32 % 256, \ + (u32 // 256) % 256, \ + (u32 // 65536) % 256, \ + (u32 // 16777216) % 256) ); + +def u16ToByteArray(u16): + """Encodes the u16 value as a little endian byte (B) array.""" + return array.array('B', \ + ( u16 % 256, \ + (u16 // 256) % 256) ); + +def u8ToByteArray(uint8): + """Encodes the u8 value as a little endian byte (B) array.""" + return array.array('B', (uint8 % 256)); + +def zeroByteArray(cb): + """Returns an array with the given size containing 0.""" + abArray = array.array('B', (0, )); + cb = cb - 1; + for i in range(cb): # pylint: disable=unused-variable + abArray.append(0); + return abArray; + +def strToByteArry(sStr): + """Encodes the string as a little endian byte (B) array including the terminator.""" + abArray = array.array('B'); + sUtf8 = sStr.encode('utf_8'); + for ch in sUtf8: + abArray.append(ord(ch)); + abArray.append(0); + return abArray; + +def cfgListToByteArray(lst): + """Encodes the given config list as a little endian byte (B) array.""" + abArray = array.array('B'); + if lst is not None: + for t3Item in lst: + # Encode they key size + abArray.extend(u32ToByteArray(len(t3Item[0]) + 1)); # Include terminator + abArray.extend(u32ToByteArray(t3Item[1])) # Config type + abArray.extend(u32ToByteArray(len(t3Item[2]) + 1)); # Value size including temrinator. + abArray.extend(u32ToByteArray(0)); # Reserved field. + + abArray.extend(strToByteArry(t3Item[0])); + abArray.extend(strToByteArry(t3Item[2])); + + return abArray; + +class TransportBase(object): + """ + Base class for the transport layer. + """ + + def __init__(self, sCaller): + self.sDbgCreated = '%s: %s' % (utils.getTimePrefix(), sCaller); + self.fDummy = 0; + self.abReadAheadHdr = array.array('B'); + + def toString(self): + """ + Stringify the instance for logging and debugging. + """ + return '<%s: abReadAheadHdr=%s, sDbgCreated=%s>' % (type(self).__name__, self.abReadAheadHdr, self.sDbgCreated); + + def __str__(self): + return self.toString(); + + def cancelConnect(self): + """ + Cancels any pending connect() call. + Returns None; + """ + return None; + + def connect(self, cMsTimeout): + """ + Quietly attempts to connect to the UTS. + + Returns True on success. + Returns False on retryable errors (no logging). + Returns None on fatal errors with details in the log. + + Override this method, don't call super. + """ + _ = cMsTimeout; + return False; + + def disconnect(self, fQuiet = False): + """ + Disconnect from the UTS. + + Returns True. + + Override this method, don't call super. + """ + _ = fQuiet; + return True; + + def sendBytes(self, abBuf, cMsTimeout): + """ + Sends the bytes in the buffer abBuf to the UTS. + + Returns True on success. + Returns False on failure and error details in the log. + + Override this method, don't call super. + + Remarks: len(abBuf) is always a multiple of 16. + """ + _ = abBuf; _ = cMsTimeout; + return False; + + def recvBytes(self, cb, cMsTimeout, fNoDataOk): + """ + Receive cb number of bytes from the UTS. + + Returns the bytes (array('B')) on success. + Returns None on failure and error details in the log. + + Override this method, don't call super. + + Remarks: cb is always a multiple of 16. + """ + _ = cb; _ = cMsTimeout; _ = fNoDataOk; + return None; + + def isConnectionOk(self): + """ + Checks if the connection is OK. + + Returns True if it is. + Returns False if it isn't (caller should call diconnect). + + Override this method, don't call super. + """ + return True; + + def isRecvPending(self, cMsTimeout = 0): + """ + Checks if there is incoming bytes, optionally waiting cMsTimeout + milliseconds for something to arrive. + + Returns True if there is, False if there isn't. + + Override this method, don't call super. + """ + _ = cMsTimeout; + return False; + + def sendMsgInt(self, sOpcode, cMsTimeout, abPayload = array.array('B')): + """ + Sends a message (opcode + encoded payload). + + Returns True on success. + Returns False on failure and error details in the log. + """ + # Fix + check the opcode. + if len(sOpcode) < 2: + reporter.fatal('sendMsgInt: invalid opcode length: %d (\"%s\")' % (len(sOpcode), sOpcode)); + return False; + sOpcode = sOpcode.ljust(8); + if not isValidOpcodeEncoding(sOpcode): + reporter.fatal('sendMsgInt: invalid opcode encoding: \"%s\"' % (sOpcode)); + return False; + + # Start construct the message. + cbMsg = 16 + len(abPayload); + abMsg = array.array('B'); + abMsg.extend(u32ToByteArray(cbMsg)); + abMsg.extend((0, 0, 0, 0)); # uCrc32 + try: + abMsg.extend(array.array('B', \ + ( ord(sOpcode[0]), \ + ord(sOpcode[1]), \ + ord(sOpcode[2]), \ + ord(sOpcode[3]), \ + ord(sOpcode[4]), \ + ord(sOpcode[5]), \ + ord(sOpcode[6]), \ + ord(sOpcode[7]) ) ) ); + if abPayload: + abMsg.extend(abPayload); + except: + reporter.fatalXcpt('sendMsgInt: packing problem...'); + return False; + + # checksum it, padd it and send it off. + uCrc32 = zlib.crc32(abMsg[8:]); + abMsg[4:8] = u32ToByteArray(uCrc32); + + while len(abMsg) % 16: + abMsg.append(0); + + reporter.log2('sendMsgInt: op=%s len=%d to=%d' % (sOpcode, len(abMsg), cMsTimeout)); + return self.sendBytes(abMsg, cMsTimeout); + + def recvMsg(self, cMsTimeout, fNoDataOk = False): + """ + Receives a message from the UTS. + + Returns the message three-tuple: length, opcode, payload. + Returns (None, None, None) on failure and error details in the log. + """ + + # Read the header. + if self.abReadAheadHdr: + assert(len(self.abReadAheadHdr) == 16); + abHdr = self.abReadAheadHdr; + self.abReadAheadHdr = array.array('B'); + else: + abHdr = self.recvBytes(16, cMsTimeout, fNoDataOk); # (virtual method) # pylint: disable=assignment-from-none + if abHdr is None: + return (None, None, None); + if len(abHdr) != 16: + reporter.fatal('recvBytes(16) returns %d bytes!' % (len(abHdr))); + return (None, None, None); + + # Unpack and validate the header. + cbMsg = getU32(abHdr, 0); + uCrc32 = getU32(abHdr, 4); + sOpcode = abHdr[8:16].tostring().decode('ascii'); + + if cbMsg < 16: + reporter.fatal('recvMsg: message length is out of range: %s (min 16 bytes)' % (cbMsg)); + return (None, None, None); + if cbMsg > 1024*1024: + reporter.fatal('recvMsg: message length is out of range: %s (max 1MB)' % (cbMsg)); + return (None, None, None); + if not isValidOpcodeEncoding(sOpcode): + reporter.fatal('recvMsg: invalid opcode \"%s\"' % (sOpcode)); + return (None, None, None); + + # Get the payload (if any), dropping the padding. + abPayload = array.array('B'); + if cbMsg > 16: + if cbMsg % 16: + cbPadding = 16 - (cbMsg % 16); + else: + cbPadding = 0; + abPayload = self.recvBytes(cbMsg - 16 + cbPadding, cMsTimeout, False); # pylint: disable=assignment-from-none + if abPayload is None: + self.abReadAheadHdr = abHdr; + if not fNoDataOk : + reporter.log('recvMsg: failed to recv payload bytes!'); + return (None, None, None); + + while cbPadding > 0: + abPayload.pop(); + cbPadding = cbPadding - 1; + + # Check the CRC-32. + if uCrc32 != 0: + uActualCrc32 = zlib.crc32(abHdr[8:]); + if cbMsg > 16: + uActualCrc32 = zlib.crc32(abPayload, uActualCrc32); + uActualCrc32 = uActualCrc32 & 0xffffffff; + if uCrc32 != uActualCrc32: + reporter.fatal('recvMsg: crc error: expected %s, got %s' % (hex(uCrc32), hex(uActualCrc32))); + return (None, None, None); + + reporter.log2('recvMsg: op=%s len=%d' % (sOpcode, len(abPayload))); + return (cbMsg, sOpcode, abPayload); + + def sendMsg(self, sOpcode, cMsTimeout, aoPayload = ()): + """ + Sends a message (opcode + payload tuple). + + Returns True on success. + Returns False on failure and error details in the log. + Returns None if you pass the incorrectly typed parameters. + """ + # Encode the payload. + abPayload = array.array('B'); + for o in aoPayload: + try: + if utils.isString(o): + # the primitive approach... + sUtf8 = o.encode('utf_8'); + for ch in sUtf8: + abPayload.append(ord(ch)) + abPayload.append(0); + elif isinstance(o, long): + if o < 0 or o > 0xffffffff: + reporter.fatal('sendMsg: uint32_t payload is out of range: %s' % (hex(o))); + return None; + abPayload.extend(u32ToByteArray(o)); + elif isinstance(o, int): + if o < 0 or o > 0xffffffff: + reporter.fatal('sendMsg: uint32_t payload is out of range: %s' % (hex(o))); + return None; + abPayload.extend(u32ToByteArray(o)); + elif isinstance(o, array.array): + abPayload.extend(o); + else: + reporter.fatal('sendMsg: unexpected payload type: %s (%s) (aoPayload=%s)' % (type(o), o, aoPayload)); + return None; + except: + reporter.fatalXcpt('sendMsg: screwed up the encoding code...'); + return None; + return self.sendMsgInt(sOpcode, cMsTimeout, abPayload); + + +class Session(TdTaskBase): + """ + A USB Test Service (UTS) client session. + """ + + def __init__(self, oTransport, cMsTimeout, cMsIdleFudge, fTryConnect = False): + """ + Construct a UTS session. + + This starts by connecting to the UTS and will enter the signalled state + when connected or the timeout has been reached. + """ + TdTaskBase.__init__(self, utils.getCallerName()); + self.oTransport = oTransport; + self.sStatus = ""; + self.cMsTimeout = 0; + self.fErr = True; # Whether to report errors as error. + self.msStart = 0; + self.oThread = None; + self.fnTask = self.taskDummy; + self.aTaskArgs = None; + self.oTaskRc = None; + self.t3oReply = (None, None, None); + self.fScrewedUpMsgState = False; + self.fTryConnect = fTryConnect; + + if not self.startTask(cMsTimeout, False, "connecting", self.taskConnect, (cMsIdleFudge,)): + raise base.GenError("startTask failed"); + + def __del__(self): + """Make sure to cancel the task when deleted.""" + self.cancelTask(); + + def toString(self): + return '<%s fnTask=%s, aTaskArgs=%s, sStatus=%s, oTaskRc=%s, cMsTimeout=%s,' \ + ' msStart=%s, fTryConnect=%s, fErr=%s, fScrewedUpMsgState=%s, t3oReply=%s oTransport=%s, oThread=%s>' \ + % (TdTaskBase.toString(self), self.fnTask, self.aTaskArgs, self.sStatus, self.oTaskRc, self.cMsTimeout, + self.msStart, self.fTryConnect, self.fErr, self.fScrewedUpMsgState, self.t3oReply, self.oTransport, self.oThread); + + def taskDummy(self): + """Place holder to catch broken state handling.""" + raise Exception(); + + def startTask(self, cMsTimeout, fIgnoreErrors, sStatus, fnTask, aArgs = ()): + """ + Kicks of a new task. + + cMsTimeout: The task timeout in milliseconds. Values less than + 500 ms will be adjusted to 500 ms. This means it is + OK to use negative value. + sStatus: The task status. + fnTask: The method that'll execute the task. + aArgs: Arguments to pass to fnTask. + + Returns True on success, False + error in log on failure. + """ + if not self.cancelTask(): + reporter.maybeErr(not fIgnoreErrors, 'utsclient.Session.startTask: failed to cancel previous task.'); + return False; + + # Change status and make sure we're the + self.lockTask(); + if self.sStatus != "": + self.unlockTask(); + reporter.maybeErr(not fIgnoreErrors, 'utsclient.Session.startTask: race.'); + return False; + self.sStatus = "setup"; + self.oTaskRc = None; + self.t3oReply = (None, None, None); + self.resetTaskLocked(); + self.unlockTask(); + + self.cMsTimeout = max(cMsTimeout, 500); + self.fErr = not fIgnoreErrors; + self.fnTask = fnTask; + self.aTaskArgs = aArgs; + self.oThread = threading.Thread(target=self.taskThread, args=(), name=('UTS-%s' % (sStatus))); + self.oThread.setDaemon(True); # pylint: disable=deprecated-method + self.msStart = base.timestampMilli(); + + self.lockTask(); + self.sStatus = sStatus; + self.unlockTask(); + self.oThread.start(); + + return True; + + def cancelTask(self, fSync = True): + """ + Attempts to cancel any pending tasks. + Returns success indicator (True/False). + """ + self.lockTask(); + + if self.sStatus == "": + self.unlockTask(); + return True; + if self.sStatus == "setup": + self.unlockTask(); + return False; + if self.sStatus == "cancelled": + self.unlockTask(); + return False; + + reporter.log('utsclient: cancelling "%s"...' % (self.sStatus)); + if self.sStatus == 'connecting': + self.oTransport.cancelConnect(); + + self.sStatus = "cancelled"; + oThread = self.oThread; + self.unlockTask(); + + if not fSync: + return False; + + oThread.join(61.0); + + if sys.version_info < (3, 9, 0): + # Removed since Python 3.9. + return oThread.isAlive(); # pylint: disable=no-member + return oThread.is_alive(); + + def taskThread(self): + """ + The task thread function. + This does some housekeeping activities around the real task method call. + """ + if not self.isCancelled(): + try: + fnTask = self.fnTask; + oTaskRc = fnTask(*self.aTaskArgs); + except: + reporter.fatalXcpt('taskThread', 15); + oTaskRc = None; + else: + reporter.log('taskThread: cancelled already'); + + self.lockTask(); + + reporter.log('taskThread: signalling task with status "%s", oTaskRc=%s' % (self.sStatus, oTaskRc)); + self.oTaskRc = oTaskRc; + self.oThread = None; + self.sStatus = ''; + self.signalTaskLocked(); + + self.unlockTask(); + return None; + + def isCancelled(self): + """Internal method for checking if the task has been cancelled.""" + self.lockTask(); + sStatus = self.sStatus; + self.unlockTask(); + if sStatus == "cancelled": + return True; + return False; + + def hasTimedOut(self): + """Internal method for checking if the task has timed out or not.""" + cMsLeft = self.getMsLeft(); + if cMsLeft <= 0: + return True; + return False; + + def getMsLeft(self, cMsMin = 0, cMsMax = -1): + """Gets the time left until the timeout.""" + cMsElapsed = base.timestampMilli() - self.msStart; + if cMsElapsed < 0: + return cMsMin; + cMsLeft = self.cMsTimeout - cMsElapsed; + if cMsLeft <= cMsMin: + return cMsMin; + if cMsLeft > cMsMax > 0: + return cMsMax + return cMsLeft; + + def recvReply(self, cMsTimeout = None, fNoDataOk = False): + """ + Wrapper for TransportBase.recvMsg that stashes the response away + so the client can inspect it later on. + """ + if cMsTimeout is None: + cMsTimeout = self.getMsLeft(500); + cbMsg, sOpcode, abPayload = self.oTransport.recvMsg(cMsTimeout, fNoDataOk); + self.lockTask(); + self.t3oReply = (cbMsg, sOpcode, abPayload); + self.unlockTask(); + return (cbMsg, sOpcode, abPayload); + + def recvAck(self, fNoDataOk = False): + """ + Receives an ACK or error response from the UTS. + + Returns True on success. + Returns False on timeout or transport error. + Returns (sOpcode, sDetails) tuple on failure. The opcode is stripped + and there are always details of some sort or another. + """ + cbMsg, sOpcode, abPayload = self.recvReply(None, fNoDataOk); + if cbMsg is None: + return False; + sOpcode = sOpcode.strip() + if sOpcode == "ACK": + return True; + return (sOpcode, getSZ(abPayload, 16, sOpcode)); + + def recvAckLogged(self, sCommand, fNoDataOk = False): + """ + Wrapper for recvAck and logging. + Returns True on success (ACK). + Returns False on time, transport error and errors signalled by UTS. + """ + rc = self.recvAck(fNoDataOk); + if rc is not True and not fNoDataOk: + if rc is False: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s transport error' % (sCommand)); + else: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s response was %s: %s' % (sCommand, rc[0], rc[1])); + rc = False; + return rc; + + def recvTrueFalse(self, sCommand): + """ + Receives a TRUE/FALSE response from the UTS. + Returns True on TRUE, False on FALSE and None on error/other (logged). + """ + cbMsg, sOpcode, abPayload = self.recvReply(); + if cbMsg is None: + reporter.maybeErr(self.fErr, 'recvAckLogged: %s transport error' % (sCommand)); + return None; + + sOpcode = sOpcode.strip() + if sOpcode == "TRUE": + return True; + if sOpcode == "FALSE": + return False; + reporter.maybeErr(self.fErr, 'recvAckLogged: %s response was %s: %s' % \ + (sCommand, sOpcode, getSZ(abPayload, 16, sOpcode))); + return None; + + def sendMsg(self, sOpcode, aoPayload = (), cMsTimeout = None): + """ + Wrapper for TransportBase.sendMsg that inserts the correct timeout. + """ + if cMsTimeout is None: + cMsTimeout = self.getMsLeft(500); + return self.oTransport.sendMsg(sOpcode, cMsTimeout, aoPayload); + + def asyncToSync(self, fnAsync, *aArgs): + """ + Wraps an asynchronous task into a synchronous operation. + + Returns False on failure, task return status on success. + """ + rc = fnAsync(*aArgs); + if rc is False: + reporter.log2('asyncToSync(%s): returns False (#1)' % (fnAsync)); + return rc; + + rc = self.waitForTask(self.cMsTimeout + 5000); + if rc is False: + reporter.maybeErrXcpt(self.fErr, 'asyncToSync: waitForTask failed...'); + self.cancelTask(); + #reporter.log2('asyncToSync(%s): returns False (#2)' % (fnAsync, rc)); + return False; + + rc = self.getResult(); + #reporter.log2('asyncToSync(%s): returns %s' % (fnAsync, rc)); + return rc; + + # + # Connection tasks. + # + + def taskConnect(self, cMsIdleFudge): + """Tries to connect to the UTS""" + while not self.isCancelled(): + reporter.log2('taskConnect: connecting ...'); + rc = self.oTransport.connect(self.getMsLeft(500)); + if rc is True: + reporter.log('taskConnect: succeeded'); + return self.taskGreet(cMsIdleFudge); + if rc is None: + reporter.log2('taskConnect: unable to connect'); + return None; + if self.hasTimedOut(): + reporter.log2('taskConnect: timed out'); + if not self.fTryConnect: + reporter.maybeErr(self.fErr, 'taskConnect: timed out'); + return False; + time.sleep(self.getMsLeft(1, 1000) / 1000.0); + if not self.fTryConnect: + reporter.maybeErr(self.fErr, 'taskConnect: cancelled'); + return False; + + def taskGreet(self, cMsIdleFudge): + """Greets the UTS""" + sHostname = socket.gethostname().lower(); + cbFill = 68 - len(sHostname) - 1; + rc = self.sendMsg("HOWDY", ((1 << 16) | 0, 0x1, len(sHostname), sHostname, zeroByteArray(cbFill))); + if rc is True: + rc = self.recvAckLogged("HOWDY", self.fTryConnect); + if rc is True: + while cMsIdleFudge > 0: + cMsIdleFudge -= 1000; + time.sleep(1); + else: + self.oTransport.disconnect(self.fTryConnect); + return rc; + + def taskBye(self): + """Says goodbye to the UTS""" + rc = self.sendMsg("BYE"); + if rc is True: + rc = self.recvAckLogged("BYE"); + self.oTransport.disconnect(); + return rc; + + # + # Gadget tasks. + # + + def taskGadgetCreate(self, iGadgetType, iGadgetAccess, lstCfg = None): + """Creates a new gadget on UTS""" + cCfgItems = 0; + if lstCfg is not None: + cCfgItems = len(lstCfg); + fRc = self.sendMsg("GDGTCRT", (iGadgetType, iGadgetAccess, cCfgItems, 0, cfgListToByteArray(lstCfg))); + if fRc is True: + fRc = self.recvAckLogged("GDGTCRT"); + return fRc; + + def taskGadgetDestroy(self, iGadgetId): + """Destroys the given gadget handle on UTS""" + fRc = self.sendMsg("GDGTDTOR", (iGadgetId, zeroByteArray(12))); + if fRc is True: + fRc = self.recvAckLogged("GDGTDTOR"); + return fRc; + + def taskGadgetConnect(self, iGadgetId): + """Connects the given gadget handle on UTS""" + fRc = self.sendMsg("GDGTCNCT", (iGadgetId, zeroByteArray(12))); + if fRc is True: + fRc = self.recvAckLogged("GDGTCNCT"); + return fRc; + + def taskGadgetDisconnect(self, iGadgetId): + """Disconnects the given gadget handle from UTS""" + fRc = self.sendMsg("GDGTDCNT", (iGadgetId, zeroByteArray(12))); + if fRc is True: + fRc = self.recvAckLogged("GDGTDCNT"); + return fRc; + + # + # Public methods - generic task queries + # + + def isSuccess(self): + """Returns True if the task completed successfully, otherwise False.""" + self.lockTask(); + sStatus = self.sStatus; + oTaskRc = self.oTaskRc; + self.unlockTask(); + if sStatus != "": + return False; + if oTaskRc is False or oTaskRc is None: + return False; + return True; + + def getResult(self): + """ + Returns the result of a completed task. + Returns None if not completed yet or no previous task. + """ + self.lockTask(); + sStatus = self.sStatus; + oTaskRc = self.oTaskRc; + self.unlockTask(); + if sStatus != "": + return None; + return oTaskRc; + + def getLastReply(self): + """ + Returns the last reply three-tuple: cbMsg, sOpcode, abPayload. + Returns a None, None, None three-tuple if there was no last reply. + """ + self.lockTask(); + t3oReply = self.t3oReply; + self.unlockTask(); + return t3oReply; + + # + # Public methods - connection. + # + + def asyncDisconnect(self, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a disconnect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "bye", self.taskBye); + + def syncDisconnect(self, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncDisconnect, cMsTimeout, fIgnoreErrors); + + # + # Public methods - gadget API + # + + def asyncGadgetCreate(self, iGadgetType, iGadgetAccess, lstCfg = None, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget create task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetCreate", self.taskGadgetCreate, \ + (iGadgetType, iGadgetAccess, lstCfg)); + + def syncGadgetCreate(self, iGadgetType, iGadgetAccess, lstCfg = None, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetCreate, iGadgetType, iGadgetAccess, lstCfg, cMsTimeout, fIgnoreErrors); + + def asyncGadgetDestroy(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget destroy task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetDestroy", self.taskGadgetDestroy, \ + (iGadgetId, )); + + def syncGadgetDestroy(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetDestroy, iGadgetId, cMsTimeout, fIgnoreErrors); + + def asyncGadgetConnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget connect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetConnect", self.taskGadgetConnect, \ + (iGadgetId, )); + + def syncGadgetConnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetConnect, iGadgetId, cMsTimeout, fIgnoreErrors); + + def asyncGadgetDisconnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """ + Initiates a gadget disconnect task. + + Returns True on success, False on failure (logged). + + The task returns True on success and False on failure. + """ + return self.startTask(cMsTimeout, fIgnoreErrors, "GadgetDisconnect", self.taskGadgetDisconnect, \ + (iGadgetId, )); + + def syncGadgetDisconnect(self, iGadgetId, cMsTimeout = 30000, fIgnoreErrors = False): + """Synchronous version.""" + return self.asyncToSync(self.asyncGadgetDisconnect, iGadgetId, cMsTimeout, fIgnoreErrors); + + +class TransportTcp(TransportBase): + """ + TCP transport layer for the UTS client session class. + """ + + def __init__(self, sHostname, uPort): + """ + Save the parameters. The session will call us back to make the + connection later on its worker thread. + """ + TransportBase.__init__(self, utils.getCallerName()); + self.sHostname = sHostname; + self.uPort = uPort if uPort is not None else 6042; + self.oSocket = None; + self.oWakeupW = None; + self.oWakeupR = None; + self.fConnectCanceled = False; + self.fIsConnecting = False; + self.oCv = threading.Condition(); + self.abReadAhead = array.array('B'); + + def toString(self): + return '<%s sHostname=%s, uPort=%s, oSocket=%s,'\ + ' fConnectCanceled=%s, fIsConnecting=%s, oCv=%s, abReadAhead=%s>' \ + % (TransportBase.toString(self), self.sHostname, self.uPort, self.oSocket, + self.fConnectCanceled, self.fIsConnecting, self.oCv, self.abReadAhead); + + def __isInProgressXcpt(self, oXcpt): + """ In progress exception? """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt[0] == errno.EINPROGRESS: + return True; + except: pass; + try: + if oXcpt[0] == errno.EWOULDBLOCK: + return True; + if utils.getHostOs() == 'win' and oXcpt[0] == errno.WSAEWOULDBLOCK: # pylint: disable=no-member + return True; + except: pass; + except: + pass; + return False; + + def __isWouldBlockXcpt(self, oXcpt): + """ Would block exception? """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt[0] == errno.EWOULDBLOCK: + return True; + except: pass; + try: + if oXcpt[0] == errno.EAGAIN: + return True; + except: pass; + except: + pass; + return False; + + def __isConnectionReset(self, oXcpt): + """ Connection reset by Peer or others. """ + try: + if isinstance(oXcpt, socket.error): + try: + if oXcpt[0] == errno.ECONNRESET: + return True; + except: pass; + try: + if oXcpt[0] == errno.ENETRESET: + return True; + except: pass; + except: + pass; + return False; + + def _closeWakeupSockets(self): + """ Closes the wakup sockets. Caller should own the CV. """ + oWakeupR = self.oWakeupR; + self.oWakeupR = None; + if oWakeupR is not None: + oWakeupR.close(); + + oWakeupW = self.oWakeupW; + self.oWakeupW = None; + if oWakeupW is not None: + oWakeupW.close(); + + return None; + + def cancelConnect(self): + # This is bad stuff. + self.oCv.acquire(); + reporter.log2('TransportTcp::cancelConnect: fIsConnecting=%s oSocket=%s' % (self.fIsConnecting, self.oSocket)); + self.fConnectCanceled = True; + if self.fIsConnecting: + oSocket = self.oSocket; + self.oSocket = None; + if oSocket is not None: + reporter.log2('TransportTcp::cancelConnect: closing the socket'); + oSocket.close(); + + oWakeupW = self.oWakeupW; + self.oWakeupW = None; + if oWakeupW is not None: + reporter.log2('TransportTcp::cancelConnect: wakeup call'); + try: oWakeupW.send('cancelled!\n'); + except: reporter.logXcpt(); + try: oWakeupW.shutdown(socket.SHUT_WR); + except: reporter.logXcpt(); + oWakeupW.close(); + self.oCv.release(); + + def _connectAsClient(self, oSocket, oWakeupR, cMsTimeout): + """ Connects to the UTS server as client. """ + + # Connect w/ timeouts. + rc = None; + try: + oSocket.connect((self.sHostname, self.uPort)); + rc = True; + except socket.error as oXcpt: + iRc = oXcpt.errno; + if self.__isInProgressXcpt(oXcpt): + # Do the actual waiting. + reporter.log2('TransportTcp::connect: operation in progress (%s)...' % (oXcpt,)); + try: + ttRc = select.select([oWakeupR], [oSocket], [oSocket, oWakeupR], cMsTimeout / 1000.0); + if len(ttRc[1]) + len(ttRc[2]) == 0: + raise socket.error(errno.ETIMEDOUT, 'select timed out'); + iRc = oSocket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR); + rc = iRc == 0; + except socket.error as oXcpt2: + iRc = oXcpt2.errno; + except: + iRc = -42; + reporter.fatalXcpt('socket.select() on connect failed'); + + if rc is True: + pass; + elif iRc in (errno.ECONNREFUSED, errno.EHOSTUNREACH, errno.EINTR, errno.ENETDOWN, errno.ENETUNREACH, errno.ETIMEDOUT): + rc = False; # try again. + else: + if iRc != errno.EBADF or not self.fConnectCanceled: + reporter.fatalXcpt('socket.connect((%s,%s)) failed; iRc=%s' % (self.sHostname, self.uPort, iRc)); + reporter.log2('TransportTcp::connect: rc=%s iRc=%s' % (rc, iRc)); + except: + reporter.fatalXcpt('socket.connect((%s,%s)) failed' % (self.sHostname, self.uPort)); + return rc; + + + def connect(self, cMsTimeout): + # Create a non-blocking socket. + reporter.log2('TransportTcp::connect: cMsTimeout=%s sHostname=%s uPort=%s' % (cMsTimeout, self.sHostname, self.uPort)); + try: + oSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0); + except: + reporter.fatalXcpt('socket.socket() failed'); + return None; + try: + oSocket.setblocking(0); + except: + oSocket.close(); + reporter.fatalXcpt('socket.socket() failed'); + return None; + + # Create wakeup socket pair for unix (select doesn't wake up on socket close on Linux). + oWakeupR = None; + oWakeupW = None; + if hasattr(socket, 'socketpair'): + try: (oWakeupR, oWakeupW) = socket.socketpair(); # pylint: disable=no-member + except: reporter.logXcpt('socket.socketpair() failed'); + + # Update the state. + self.oCv.acquire(); + rc = None; + if not self.fConnectCanceled: + self.oSocket = oSocket; + self.oWakeupW = oWakeupW; + self.oWakeupR = oWakeupR; + self.fIsConnecting = True; + self.oCv.release(); + + # Try connect. + if oWakeupR is None: + oWakeupR = oSocket; # Avoid select failure. + rc = self._connectAsClient(oSocket, oWakeupR, cMsTimeout); + oSocket = None; + + # Update the state and cleanup on failure/cancel. + self.oCv.acquire(); + if rc is True and self.fConnectCanceled: + rc = False; + self.fIsConnecting = False; + + if rc is not True: + if self.oSocket is not None: + self.oSocket.close(); + self.oSocket = None; + self._closeWakeupSockets(); + self.oCv.release(); + + reporter.log2('TransportTcp::connect: returning %s' % (rc,)); + return rc; + + def disconnect(self, fQuiet = False): + if self.oSocket is not None: + self.abReadAhead = array.array('B'); + + # Try a shutting down the socket gracefully (draining it). + try: + self.oSocket.shutdown(socket.SHUT_WR); + except: + if not fQuiet: + reporter.error('shutdown(SHUT_WR)'); + try: + self.oSocket.setblocking(0); # just in case it's not set. + sData = "1"; + while sData: + sData = self.oSocket.recv(16384); + except: + pass; + + # Close it. + self.oCv.acquire(); + try: self.oSocket.setblocking(1); + except: pass; + self.oSocket.close(); + self.oSocket = None; + else: + self.oCv.acquire(); + self._closeWakeupSockets(); + self.oCv.release(); + + def sendBytes(self, abBuf, cMsTimeout): + if self.oSocket is None: + reporter.error('TransportTcp.sendBytes: No connection.'); + return False; + + # Try send it all. + try: + cbSent = self.oSocket.send(abBuf); + if cbSent == len(abBuf): + return True; + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.sendBytes: %s bytes' % (len(abBuf))); + return False; + cbSent = 0; + + # Do a timed send. + msStart = base.timestampMilli(); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + reporter.error('TranportTcp.sendBytes: %s bytes timed out (1)' % (len(abBuf))); + break; + + # wait. + try: + ttRc = select.select([], [self.oSocket], [self.oSocket], (cMsTimeout - cMsElapsed) / 1000.0); + if ttRc[2] and not ttRc[1]: + reporter.error('TranportTcp.sendBytes: select returned with exception'); + break; + if not ttRc[1]: + reporter.error('TranportTcp.sendBytes: %s bytes timed out (2)' % (len(abBuf))); + break; + except: + reporter.errorXcpt('TranportTcp.sendBytes: select failed'); + break; + + # Try send more. + try: + cbSent += self.oSocket.send(abBuf[cbSent:]); + if cbSent == len(abBuf): + return True; + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.sendBytes: %s bytes' % (len(abBuf))); + break; + + return False; + + def __returnReadAheadBytes(self, cb): + """ Internal worker for recvBytes. """ + assert(len(self.abReadAhead) >= cb); + abRet = self.abReadAhead[:cb]; + self.abReadAhead = self.abReadAhead[cb:]; + return abRet; + + def recvBytes(self, cb, cMsTimeout, fNoDataOk): + if self.oSocket is None: + reporter.error('TransportTcp.recvBytes(%s,%s): No connection.' % (cb, cMsTimeout)); + return None; + + # Try read in some more data without bothering with timeout handling first. + if len(self.abReadAhead) < cb: + try: + abBuf = self.oSocket.recv(cb - len(self.abReadAhead)); + if abBuf: + self.abReadAhead.extend(array.array('B', abBuf)); + except Exception as oXcpt: + if not self.__isWouldBlockXcpt(oXcpt): + reporter.errorXcpt('TranportTcp.recvBytes: 0/%s bytes' % (cb,)); + return None; + + if len(self.abReadAhead) >= cb: + return self.__returnReadAheadBytes(cb); + + # Timeout loop. + msStart = base.timestampMilli(); + while True: + cMsElapsed = base.timestampMilli() - msStart; + if cMsElapsed > cMsTimeout: + if not fNoDataOk or self.abReadAhead: + reporter.error('TranportTcp.recvBytes: %s/%s bytes timed out (1)' % (len(self.abReadAhead), cb)); + break; + + # Wait. + try: + ttRc = select.select([self.oSocket], [], [self.oSocket], (cMsTimeout - cMsElapsed) / 1000.0); + if ttRc[2] and not ttRc[0]: + reporter.error('TranportTcp.recvBytes: select returned with exception'); + break; + if not ttRc[0]: + if not fNoDataOk or self.abReadAhead: + reporter.error('TranportTcp.recvBytes: %s/%s bytes timed out (2) fNoDataOk=%s' + % (len(self.abReadAhead), cb, fNoDataOk)); + break; + except: + reporter.errorXcpt('TranportTcp.recvBytes: select failed'); + break; + + # Try read more. + try: + abBuf = self.oSocket.recv(cb - len(self.abReadAhead)); + if not abBuf: + reporter.error('TranportTcp.recvBytes: %s/%s bytes (%s) - connection has been shut down' + % (len(self.abReadAhead), cb, fNoDataOk)); + self.disconnect(); + return None; + + self.abReadAhead.extend(array.array('B', abBuf)); + + except Exception as oXcpt: + reporter.log('recv => exception %s' % (oXcpt,)); + if not self.__isWouldBlockXcpt(oXcpt): + if not fNoDataOk or not self.__isConnectionReset(oXcpt) or self.abReadAhead: + reporter.errorXcpt('TranportTcp.recvBytes: %s/%s bytes (%s)' % (len(self.abReadAhead), cb, fNoDataOk)); + break; + + # Done? + if len(self.abReadAhead) >= cb: + return self.__returnReadAheadBytes(cb); + + #reporter.log('recv => None len(self.abReadAhead) -> %d' % (len(self.abReadAhead), )); + return None; + + def isConnectionOk(self): + if self.oSocket is None: + return False; + try: + ttRc = select.select([], [], [self.oSocket], 0.0); + if ttRc[2]: + return False; + + self.oSocket.send(array.array('B')); # send zero bytes. + except: + return False; + return True; + + def isRecvPending(self, cMsTimeout = 0): + try: + ttRc = select.select([self.oSocket], [], [], cMsTimeout / 1000.0); + if not ttRc[0]: + return False; + except: + pass; + return True; + + +class UsbGadget(object): + """ + USB Gadget control class using the USBT Test Service to talk to the external + board behaving like a USB device. + """ + + def __init__(self): + self.oUtsSession = None; + self.sImpersonation = g_ksGadgetImpersonationInvalid; + self.idGadget = None; + self.iBusId = None; + self.iDevId = None; + self.iUsbIpPort = None; + + def clearImpersonation(self): + """ + Removes the current impersonation of the gadget. + """ + fRc = True; + + if self.idGadget is not None: + fRc = self.oUtsSession.syncGadgetDestroy(self.idGadget); + self.idGadget = None; + self.iBusId = None; + self.iDevId = None; + + return fRc; + + def disconnectUsb(self): + """ + Disconnects the USB gadget from the host. (USB connection not network + connection used for control) + """ + return self.oUtsSession.syncGadgetDisconnect(self.idGadget); + + def connectUsb(self): + """ + Connect the USB gadget to the host. + """ + return self.oUtsSession.syncGadgetConnect(self.idGadget); + + def impersonate(self, sImpersonation, fSuperSpeed = False): + """ + Impersonate a given device. + """ + + # Clear any previous impersonation + self.clearImpersonation(); + self.sImpersonation = sImpersonation; + + fRc = False; + if sImpersonation == g_ksGadgetImpersonationTest: + lstCfg = []; + if fSuperSpeed is True: + lstCfg.append( ('Gadget/SuperSpeed', g_kiGadgetCfgTypeBool, 'true') ); + fDone = self.oUtsSession.syncGadgetCreate(g_kiGadgetTypeTest, g_kiGadgetAccessUsbIp, lstCfg); + if fDone is True and self.oUtsSession.isSuccess(): + # Get the gadget ID. + _, _, abPayload = self.oUtsSession.getLastReply(); + + fRc = True; + self.idGadget = getU32(abPayload, 16); + self.iBusId = getU32(abPayload, 20); + self.iDevId = getU32(abPayload, 24); + else: + reporter.log('Invalid or unsupported impersonation'); + + return fRc; + + def getUsbIpPort(self): + """ + Returns the port the USB/IP server is listening on if requested, + None if USB/IP is not supported. + """ + return self.iUsbIpPort; + + def getGadgetBusAndDevId(self): + """ + Returns the bus ad device ID of the gadget as a tuple. + """ + return (self.iBusId, self.iDevId); + + def connectTo(self, cMsTimeout, sHostname, uPort = None, fUsbIpSupport = True, cMsIdleFudge = 0, fTryConnect = False): + """ + Connects to the specified target device. + Returns True on Success. + Returns False otherwise. + """ + fRc = True; + + # @todo + if fUsbIpSupport is False: + return False; + + reporter.log2('openTcpSession(%s, %s, %s, %s)' % \ + (cMsTimeout, sHostname, uPort, cMsIdleFudge)); + try: + oTransport = TransportTcp(sHostname, uPort); + self.oUtsSession = Session(oTransport, cMsTimeout, cMsIdleFudge, fTryConnect); + + if self.oUtsSession is not None: + fDone = self.oUtsSession.waitForTask(30*1000); + reporter.log('connect: waitForTask -> %s, result %s' % (fDone, self.oUtsSession.getResult())); + if fDone is True and self.oUtsSession.isSuccess(): + # Parse the reply. + _, _, abPayload = self.oUtsSession.getLastReply(); + + if getU32(abPayload, 20) is g_kiGadgetAccessUsbIp: + fRc = True; + self.iUsbIpPort = getU32(abPayload, 24); + else: + reporter.log('Gadget doesn\'t support access over USB/IP despite being requested'); + fRc = False; + else: + fRc = False; + else: + fRc = False; + except: + reporter.errorXcpt(None, 15); + return False; + + return fRc; + + def disconnectFrom(self): + """ + Disconnects from the target device. + """ + fRc = True; + + self.clearImpersonation(); + if self.oUtsSession is not None: + fRc = self.oUtsSession.syncDisconnect(); + + return fRc; |