diff options
Diffstat (limited to 'src/VBox/ValidationKit/tests/serial')
-rw-r--r-- | src/VBox/ValidationKit/tests/serial/Makefile.kmk | 42 | ||||
-rwxr-xr-x | src/VBox/ValidationKit/tests/serial/loopback.py | 247 | ||||
-rwxr-xr-x | src/VBox/ValidationKit/tests/serial/tdSerial1.py | 337 |
3 files changed, 626 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/tests/serial/Makefile.kmk b/src/VBox/ValidationKit/tests/serial/Makefile.kmk new file mode 100644 index 00000000..ac4235fb --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/Makefile.kmk @@ -0,0 +1,42 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - Serial port. +# + +# +# Copyright (C) 2018-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL) only, as it comes in the "COPYING.CDDL" file of the +# VirtualBox OSE 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. +# + +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..a0b71a91 --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/loopback.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# $Id: loopback.py $ + +""" +VirtualBox Validation Kit - Serial loopback module. +""" + +__copyright__ = \ +""" +Copyright (C) 2018-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE 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. +""" +__version__ = "$Revision: 127855 $" + +# 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. + """ + self.oLock.acquire(); + self.fShutdown = True; + self.oLock.release(); + self.oIoPumper.shutdown(); + + def isShutdown(self): + """ + Returns whether the I/O pumping thread should shut down. + """ + self.oLock.acquire(); + fShutdown = self.fShutdown; + self.oLock.release(); + + 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..b2f0b264 --- /dev/null +++ b/src/VBox/ValidationKit/tests/serial/tdSerial1.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# $Id: tdSerial1.py $ + +""" +VirtualBox Validation Kit - Serial port testing #1. +""" + +__copyright__ = \ +""" +Copyright (C) 2018-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE 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. +""" +__version__ = "$Revision: 127855 $" + + +# 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 == 'TcpServ' or sMode == '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: + oFile = open(self.sLocation, 'rb'); + 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; + oFile.close(); + 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; + elif 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)); + |