summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/tests/serial
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ValidationKit/tests/serial')
-rw-r--r--src/VBox/ValidationKit/tests/serial/Makefile.kmk42
-rwxr-xr-xsrc/VBox/ValidationKit/tests/serial/loopback.py247
-rwxr-xr-xsrc/VBox/ValidationKit/tests/serial/tdSerial1.py337
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));
+