summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/ValidationKit/tests
parentInitial commit. (diff)
downloadvirtualbox-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')
-rw-r--r--src/VBox/ValidationKit/tests/Makefile.kmk60
-rw-r--r--src/VBox/ValidationKit/tests/__init__.py40
-rw-r--r--src/VBox/ValidationKit/tests/additions/Makefile.kmk54
-rwxr-xr-xsrc/VBox/ValidationKit/tests/additions/tdAddBasic1.py649
-rwxr-xr-xsrc/VBox/ValidationKit/tests/additions/tdAddGuestCtrl.py5503
-rwxr-xr-xsrc/VBox/ValidationKit/tests/additions/tdAddSharedFolders1.py361
-rw-r--r--src/VBox/ValidationKit/tests/api/Makefile.kmk72
-rw-r--r--src/VBox/ValidationKit/tests/api/__init__.py39
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdApi1.py99
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ovabin0 -> 9216 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ovabin0 -> 77312 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ovabin0 -> 77312 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ovabin0 -> 9728 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ovabin0 -> 9728 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ovabin0 -> 116224 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ovabin0 -> 116224 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t4.pem74
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ovabin0 -> 112640 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ovabin0 -> 112640 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ovabin0 -> 218624 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ovabin0 -> 218624 bytes
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t6.pem134
-rw-r--r--src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ovabin0 -> 78336 bytes
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdAppliance1.py217
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdCloneMedium1.py243
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdCreateVMWithDefaults1.py203
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdMoveMedium1.py217
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdMoveVm1.py768
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdPython1.py220
-rwxr-xr-xsrc/VBox/ValidationKit/tests/api/tdTreeDepth1.py249
-rw-r--r--src/VBox/ValidationKit/tests/audio/Makefile.kmk50
-rwxr-xr-xsrc/VBox/ValidationKit/tests/audio/tdAudioTest.py823
-rwxr-xr-xsrc/VBox/ValidationKit/tests/audio/tdGuestHostTimings.py240
-rw-r--r--src/VBox/ValidationKit/tests/autostart/Makefile.kmk51
-rwxr-xr-xsrc/VBox/ValidationKit/tests/autostart/tdAutostart1.py1443
-rw-r--r--src/VBox/ValidationKit/tests/benchmarks/Makefile.kmk52
-rwxr-xr-xsrc/VBox/ValidationKit/tests/benchmarks/tdBenchmark1.py122
-rwxr-xr-xsrc/VBox/ValidationKit/tests/benchmarks/tdBenchmark2.py195
-rw-r--r--src/VBox/ValidationKit/tests/cpu/Makefile.kmk51
-rwxr-xr-xsrc/VBox/ValidationKit/tests/cpu/tdCpuPae1.py264
-rw-r--r--src/VBox/ValidationKit/tests/installation/Makefile.kmk52
-rwxr-xr-xsrc/VBox/ValidationKit/tests/installation/tdGuestOsInstOs2.py258
-rwxr-xr-xsrc/VBox/ValidationKit/tests/installation/tdGuestOsInstTest1.py409
-rwxr-xr-xsrc/VBox/ValidationKit/tests/installation/tdGuestOsUnattendedInst1.py769
-rw-r--r--src/VBox/ValidationKit/tests/network/Makefile.kmk51
-rwxr-xr-xsrc/VBox/ValidationKit/tests/network/tdNetBenchmark1.py633
-rw-r--r--src/VBox/ValidationKit/tests/selftests/Makefile.kmk54
-rwxr-xr-xsrc/VBox/ValidationKit/tests/selftests/tdSelfTest1.py66
-rwxr-xr-xsrc/VBox/ValidationKit/tests/selftests/tdSelfTest2.py131
-rwxr-xr-xsrc/VBox/ValidationKit/tests/selftests/tdSelfTest3.py125
-rwxr-xr-xsrc/VBox/ValidationKit/tests/selftests/tdSelfTest4.py125
-rw-r--r--src/VBox/ValidationKit/tests/serial/Makefile.kmk52
-rwxr-xr-xsrc/VBox/ValidationKit/tests/serial/loopback.py255
-rwxr-xr-xsrc/VBox/ValidationKit/tests/serial/tdSerial1.py345
-rwxr-xr-xsrc/VBox/ValidationKit/tests/shutdown/tdGuestOsShutdown1.py367
-rw-r--r--src/VBox/ValidationKit/tests/smoketests/Makefile.kmk52
-rwxr-xr-xsrc/VBox/ValidationKit/tests/smoketests/tdExoticOrAncient1.py124
-rwxr-xr-xsrc/VBox/ValidationKit/tests/smoketests/tdSmokeTest1.py173
-rw-r--r--src/VBox/ValidationKit/tests/storage/Makefile.kmk56
-rwxr-xr-xsrc/VBox/ValidationKit/tests/storage/remoteexecutor.py314
-rwxr-xr-xsrc/VBox/ValidationKit/tests/storage/storagecfg.py681
-rwxr-xr-xsrc/VBox/ValidationKit/tests/storage/tdStorageBenchmark1.py1469
-rwxr-xr-xsrc/VBox/ValidationKit/tests/storage/tdStorageRawDrive1.py1692
-rwxr-xr-xsrc/VBox/ValidationKit/tests/storage/tdStorageSnapshotMerging1.py414
-rwxr-xr-xsrc/VBox/ValidationKit/tests/storage/tdStorageStress1.py513
-rw-r--r--src/VBox/ValidationKit/tests/teleportation/Makefile.kmk51
-rwxr-xr-xsrc/VBox/ValidationKit/tests/teleportation/tdTeleportLocal1.py963
-rw-r--r--src/VBox/ValidationKit/tests/unittests/Makefile.kmk51
-rwxr-xr-xsrc/VBox/ValidationKit/tests/unittests/tdUnitTest1.py1282
-rw-r--r--src/VBox/ValidationKit/tests/usb/Makefile.kmk53
-rwxr-xr-xsrc/VBox/ValidationKit/tests/usb/tdUsb1.py590
-rwxr-xr-xsrc/VBox/ValidationKit/tests/usb/tst-utsgadget.py154
-rwxr-xr-xsrc/VBox/ValidationKit/tests/usb/usbgadget.py1478
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
new file mode 100644
index 00000000..aba10dbb
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t1.ova
Binary files differ
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
new file mode 100644
index 00000000..19c2c4b9
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2-ovftool-4.1.0.ova
Binary files differ
diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova
new file mode 100644
index 00000000..66a173aa
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t2.ova
Binary files differ
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
new file mode 100644
index 00000000..8027ce64
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3-ovftool-4.1.0.ova
Binary files differ
diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova
new file mode 100644
index 00000000..7fbf44ee
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t3.ova
Binary files differ
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
new file mode 100644
index 00000000..2434f37e
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4-ovftool-4.1.0.ova
Binary files differ
diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova
new file mode 100644
index 00000000..a6549b15
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t4.ova
Binary files differ
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
new file mode 100644
index 00000000..6a21a19b
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5-ovftool-4.1.0.ova
Binary files differ
diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova
new file mode 100644
index 00000000..8e6e2e2e
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t5.ova
Binary files differ
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
new file mode 100644
index 00000000..d5a92eb2
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6-ovftool-4.1.0.ova
Binary files differ
diff --git a/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova
new file mode 100644
index 00000000..6480e6d5
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t6.ova
Binary files differ
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
new file mode 100644
index 00000000..48e49059
--- /dev/null
+++ b/src/VBox/ValidationKit/tests/api/tdAppliance1-t7-bad-instance.ova
Binary files differ
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;