VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/tests/audio/tdAudioTest.py@ 90653

Last change on this file since 90653 was 90653, checked in by vboxsync, 3 years ago

Audio/ValKit: Added initial support to tdAudioTest.py to disable the guest and host firewalls to let VKAT / ATS through (and to not make the tests hang in such cases) [pylint]. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: tdAudioTest.py 90653 2021-08-12 10:46:14Z vboxsync $
3
4"""
5AudioTest test driver which invokes the VKAT (Validation Kit Audio Test)
6binary to perform the actual audio tests.
7
8The generated test set archive on the guest will be downloaded by TXS
9to the host for later audio comparison / verification.
10"""
11
12__copyright__ = \
13"""
14Copyright (C) 2021 Oracle Corporation
15
16This file is part of VirtualBox Open Source Edition (OSE), as
17available from http://www.virtualbox.org. This file is free software;
18you can redistribute it and/or modify it under the terms of the GNU
19General Public License (GPL) as published by the Free Software
20Foundation, in version 2 as it comes in the "COPYING" file of the
21VirtualBox OSE distribution. VirtualBox OSE is distributed in the
22hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
23
24The contents of this file may alternatively be used under the terms
25of the Common Development and Distribution License Version 1.0
26(CDDL) only, as it comes in the "COPYING.CDDL" file of the
27VirtualBox OSE distribution, in which case the provisions of the
28CDDL are applicable instead of those of the GPL.
29
30You may elect to license modified versions of this file under the
31terms and conditions of either the GPL or the CDDL or both.
32"""
33__version__ = "$Revision: 90653 $"
34
35# Standard Python imports.
36import os
37import sys
38import signal
39import subprocess
40import uuid
41
42# Only the main script needs to modify the path.
43try: __file__
44except: __file__ = sys.argv[0];
45g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))));
46sys.path.append(g_ksValidationKitDir);
47
48# Validation Kit imports.
49from testdriver import reporter
50from testdriver import base
51from testdriver import vbox
52from testdriver import vboxtestvms
53from common import utils;
54
55# pylint: disable=unnecessary-semicolon
56
57class tdAudioTest(vbox.TestDriver):
58 """
59 Runs various audio tests.
60 """
61 def __init__(self):
62 vbox.TestDriver.__init__(self);
63 self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat');
64 self.asGstVkatPaths = [
65 # Debugging stuff (SCP'd over to the guest).
66 '/tmp/vkat',
67 '/tmp/VBoxAudioTest',
68 # Validation Kit .ISO.
69 '${CDROM}/vboxvalidationkit/${OS/ARCH}/vkat${EXESUFF}',
70 '${CDROM}/${OS/ARCH}/vkat${EXESUFF}',
71 ## @odo VBoxAudioTest on Guest Additions?
72 ];
73 self.asTestsDef = [
74 'guest_tone_playback', 'guest_tone_recording'
75 ];
76 self.asTests = self.asTestsDef;
77
78 # Enable audio debug mode.
79 #
80 # This is needed in order to load and use the Validation Kit audio driver,
81 # which in turn is being used in conjunction with the guest side to record
82 # output (guest is playing back) and injecting input (guest is recording).
83 self.asOptExtraData = [
84 'VBoxInternal2/Audio/Debug/Enabled:true',
85 ];
86
87 # Name of the running VM to use for running the test driver. Optional, and None if not being used.
88 self.sRunningVmName = None;
89
90 def showUsage(self):
91 """
92 Shows the audio test driver-specific command line options.
93 """
94 fRc = vbox.TestDriver.showUsage(self);
95 reporter.log('');
96 reporter.log('tdAudioTest Options:');
97 reporter.log(' --runningvmname <vmname>');
98 reporter.log(' --audio-tests <s1[:s2[:]]>');
99 reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef)));
100 return fRc;
101
102 def parseOption(self, asArgs, iArg):
103 """
104 Parses the audio test driver-specific command line options.
105 """
106 if asArgs[iArg] == '--runningvmname':
107 iArg += 1;
108 if iArg >= len(asArgs):
109 raise base.InvalidOption('The "--runningvmname" needs VM name');
110
111 self.sRunningVmName = asArgs[iArg];
112 elif asArgs[iArg] == '--audio-tests':
113 iArg += 1;
114 if asArgs[iArg] == 'all': # Nice for debugging scripts.
115 self.asTests = self.asTestsDef;
116 else:
117 self.asTests = asArgs[iArg].split(':');
118 for s in self.asTests:
119 if s not in self.asTestsDef:
120 raise base.InvalidOption('The "--audio-tests" value "%s" is not valid; valid values are: %s'
121 % (s, ' '.join(self.asTestsDef)));
122 else:
123 return vbox.TestDriver.parseOption(self, asArgs, iArg);
124 return iArg + 1;
125
126 def actionVerify(self):
127 """
128 Verifies the test driver before running.
129 """
130 if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso):
131 reporter.error('Cannot find the VBoxValidationKit.iso! (%s)'
132 'Please unzip a Validation Kit build in the current directory or in some parent one.'
133 % (self.sVBoxValidationKitIso,) );
134 return False;
135 return vbox.TestDriver.actionVerify(self);
136
137 def actionConfig(self):
138 """
139 Configures the test driver before running.
140 """
141 if not self.importVBoxApi(): # So we can use the constant below.
142 return False;
143
144 # Make sure that the Validation Kit .ISO is mounted
145 # to find the VKAT (Validation Kit Audio Test) binary on it.
146 assert self.sVBoxValidationKitIso is not None;
147 return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso);
148
149 def actionExecute(self):
150 """
151 Executes the test driver.
152 """
153 if self.sRunningVmName is None:
154 return self.oTestVmSet.actionExecute(self, self.testOneVmConfig);
155 return self.actionExecuteOnRunnigVM();
156
157 def actionExecuteOnRunnigVM(self):
158 """
159 Executes the tests in an already configured + running VM.
160 """
161 if not self.importVBoxApi():
162 return False;
163
164 fRc = True;
165
166 oVirtualBox = self.oVBoxMgr.getVirtualBox();
167 try:
168 oVM = oVirtualBox.findMachine(self.sRunningVmName);
169 if oVM.state != self.oVBoxMgr.constants.MachineState_Running:
170 reporter.error("Machine '%s' is not in Running state" % (self.sRunningVmName));
171 fRc = False;
172 except:
173 reporter.errorXcpt("Machine '%s' not found" % (self.sRunningVmName));
174 fRc = False;
175
176 if fRc:
177 oSession = self.openSession(oVM);
178 if oSession:
179 # Tweak this to your likings.
180 oTestVm = vboxtestvms.TestVm('runningvm', sKind = 'Ubuntu_64');
181 (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 30 * 1000);
182 if fRc:
183 self.doTest(oTestVm, oSession, oTxsSession);
184 else:
185 reporter.error("Unable to open session for machine '%s'" % (self.sRunningVmName));
186 fRc = False;
187
188 del oVM;
189 del oVirtualBox;
190 return fRc;
191
192 def getGstVkatLogFilePath(self, oTestVm):
193 """
194 Returns the log file path of VKAT running on the guest (daemonized).
195 """
196 return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vkat-guest-daemonized.log');
197
198 def locateGstVkat(self, oSession, oTxsSession):
199 """
200 Returns guest side path to VKAT.
201 """
202 for sVkatPath in self.asGstVkatPaths:
203 reporter.log2('Checking for VKAT at: %s ...' % (sVkatPath));
204 if self.txsIsFile(oSession, oTxsSession, sVkatPath):
205 return (True, sVkatPath);
206 reporter.error('Unable to find guest VKAT in any of these places:\n%s' % ('\n'.join(self.asGstVkatPaths),));
207 return (False, "");
208
209 def killProcessByName(self, sProcName):
210 """
211 Kills processes by their name.
212 """
213 reporter.log('Trying to kill processes named "%s"' % (sProcName,));
214 if sys.platform == 'win32':
215 os.system('taskkill /IM "%s.exe" /F' % (sProcName));
216 else: # Note: killall is not available on older Debians (requires psmisc).
217 # Using the BSD syntax here; MacOS also should understand this.
218 procPs = subprocess.Popen(['ps', 'ax'], stdout=subprocess.PIPE);
219 out, _ = procPs.communicate();
220 for sLine in out.decode("utf-8").splitlines():
221 if sProcName in sLine:
222 pid = int(sLine.split(None, 1)[0]);
223 reporter.log2('Killing PID %d' % (pid,));
224 os.kill(pid, signal.SIGKILL); # pylint: disable=no-member
225
226 def killHstVkat(self):
227 """
228 Kills VKAT (VBoxAudioTest) on the host side.
229 """
230 reporter.log('Killing stale/old VKAT processes ...');
231 self.killProcessByName("vkat");
232 self.killProcessByName("VBoxAudioTest");
233
234 def getWinFirewallArgsDisable(self, sOsType):
235 """
236 Returns the command line arguments for Windows OSes
237 to disable the built-in firewall (if any).
238
239 If not supported, returns an empty array.
240 """
241 if sOsType == 'vista': # Vista and up.
242 return (['netsh', 'advfirewall', 'set', 'allprofiles', 'state', 'off']);
243 elif sOsType == 'xp': # pylint: disable=no-else-return
244 # Older stuff (XP / 2003).
245 return(['netsh', 'firewall', 'set', 'opmode', 'mode=DISABLE']);
246 # Not supported / available.
247 return [];
248
249 def disableGstFirewall(self, oTestVm, oTxsSession):
250 """
251 Disables the firewall on a guest (if any).
252
253 Needs elevated / admin / root privileges.
254
255 Returns success status, not logged.
256 """
257 fRc = False;
258
259 asArgs = [];
260 sOsType = '';
261 if oTestVm.isWindows():
262 if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x']:
263 sOsType = 'nt3x'; # Not supported, but define it anyway.
264 elif oTestVm.sKind in ('Windows2000', 'WindowsXP', 'Windows2003'):
265 sOsType = 'xp';
266 else:
267 sOsType = 'vista';
268 asArgs = self.getWinFirewallArgsDisable(sOsType);
269 else:
270 sOsType = 'unsupported';
271
272 reporter.log('Disabling firewall on guest (type: %s) ...' % (sOsType,));
273
274 if asArgs:
275 fRc = self.txsRunTest(oTxsSession, 'Disabling guest firewall', \
276 oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), asArgs[0]), asArgs);
277 if not fRc:
278 reporter.error('Disabling firewall on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
279 else:
280 reporter.log('Firewall not available on guest, skipping');
281 fRc = True; # Not available, just skip.
282
283 return fRc;
284
285 def disableHstFirewall(self):
286 """
287 Disables the firewall on the host (if any).
288
289 Needs elevated / admin / root privileges.
290
291 Returns success status, not logged.
292 """
293 fRc = False;
294
295 asArgs = [];
296 sOsType = sys.platform;
297
298 if sOsType == 'win32':
299 reporter.log('Disabling firewall on host (type: %s) ...' % (sOsType));
300
301 ## @todo For now we ASSUME that we don't run (and don't support even) on old(er)
302 # Windows hosts than Vista.
303 asArgs = self.getWinFirewallArgsDisable('vista');
304
305 if asArgs:
306 oProcess = utils.sudoProcessPopen(asArgs, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
307 stderr=subprocess.PIPE, shell = False, close_fds = False);
308 if oProcess:
309 sOut, sErr = oProcess.communicate();
310
311 sOut = sOut.decode(sys.stdin.encoding);
312 for sLine in sOut.split('\n'):
313 reporter.log(sLine);
314
315 sErr = sErr.decode(sys.stdin.encoding);
316 for sLine in sErr.split('\n'):
317 reporter.log(sLine);
318
319 iExitCode = oProcess.poll();
320 if iExitCode != 0:
321 fRc = False;
322 else:
323 fRc = False;
324
325 if not fRc:
326 reporter.error('Disabling firewall on host returned exit code error %d' % iExitCode);
327 else:
328 reporter.log('Firewall not available on host, skipping ');
329 fRc = True; # Not available, just skip.
330
331 return fRc;
332
333 def getLastRcFromTxs(self, oTxsSession):
334 """
335 Extracts the last exit code reported by TXS from a run before.
336 Assumes that nothing else has been run on the same TXS session in the meantime.
337 """
338 iRc = 0;
339 (_, sOpcode, abPayload) = oTxsSession.getLastReply();
340 if sOpcode.startswith('PROC NOK '): # Extract process rc
341 iRc = abPayload[0]; # ASSUMES 8-bit rc for now.
342 return iRc;
343
344 def startVkatOnGuest(self, oTestVm, oSession, oTxsSession):
345 """
346 Starts VKAT on the guest (running in background).
347 """
348 sPathTemp = self.getGuestTempDir(oTestVm);
349 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-guest-out');
350 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-guest-temp');
351
352 reporter.log('Guest audio test temp path is \"%s\"' % (sPathAudioOut));
353 reporter.log('Guest audio test output path is \"%s\"' % (sPathAudioTemp));
354
355 fRc, sVkatExe = self.locateGstVkat(oSession, oTxsSession);
356 if fRc:
357 reporter.log('Using VKAT on guest at \"%s\"' % (sVkatExe));
358
359 aArgs = [ sVkatExe, 'test', '-vvvv', '--mode', 'guest', \
360 '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut ];
361 #
362 # Start VKAT in the background (daemonized) on the guest side, so that we
363 # can continue starting VKAT on the host.
364 #
365 aArgs.extend(['--daemonize']);
366
367 #
368 # Add own environment stuff.
369 #
370 aEnv = [];
371
372 # Write the log file to some deterministic place so TxS can retrieve it later.
373 sVkatLogFile = 'VKAT_RELEASE_LOG_DEST=file=' + self.getGstVkatLogFilePath(oTestVm);
374 aEnv.extend([ sVkatLogFile ]);
375
376 #
377 # Execute.
378 #
379 fRc = self.txsRunTest(oTxsSession, 'Starting VKAT on guest', 15 * 60 * 1000,
380 sVkatExe, aArgs, aEnv);
381 if not fRc:
382 reporter.error('VKAT on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
383 else:
384 reporter.error('VKAT on guest not found');
385
386 return fRc;
387
388 def runTests(self, oTestVm, oSession, oTxsSession, sDesc, sTests):
389 """
390 Runs one or more tests using VKAT on the host, which in turn will
391 communicate with VKAT running on the guest and the Validation Kit
392 audio driver ATS (Audio Testing Service).
393 """
394 _ = oSession, oTxsSession;
395 sTag = uuid.uuid4();
396
397 sPathTemp = self.sScratchPath;
398 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-host-out-%s' % (sTag));
399 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-host-temp-%s' % (sTag));
400
401 reporter.log('Host audio test temp path is \"%s\"' % (sPathAudioOut));
402 reporter.log('Host audio test output path is \"%s\"' % (sPathAudioTemp));
403 reporter.log('Host audio test tag is \"%s\"' % (sTag));
404
405 sVkatExe = self.getBinTool('vkat');
406
407 reporter.log('Using VKAT on host at: \"%s\"' % (sVkatExe));
408
409 # Build the base command line, exclude all tests by default.
410 sArgs = '%s test -vvvv --mode host --tempdir %s --outdir %s -a' \
411 % (sVkatExe, sPathAudioTemp, sPathAudioOut);
412
413 # ... and extend it with wanted tests.
414 sArgs += " " + sTests;
415
416 fRc = True;
417
418 reporter.testStart(sDesc);
419
420 #
421 # Let VKAT on the host run synchronously.
422 #
423 procVkat = subprocess.Popen(sArgs, \
424 stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True);
425 if procVkat:
426 reporter.log('VKAT on host started');
427
428 out, err = procVkat.communicate();
429 rc = procVkat.poll();
430
431 out = out.decode(sys.stdin.encoding);
432 for line in out.split('\n'):
433 reporter.log(line);
434
435 err = err.decode(sys.stdin.encoding);
436 for line in err.split('\n'):
437 reporter.log(line);
438
439 reporter.log('VKAT on host ended with exit code %d' % rc);
440 if rc != 0:
441 reporter.testFailure('VKAT on the host failed');
442 fRc = False;
443 else:
444 reporter.testFailure('VKAT on the host failed to start');
445 fRc = False;
446
447 reporter.testDone();
448
449 return fRc;
450
451 def doTest(self, oTestVm, oSession, oTxsSession):
452 """
453 Executes the specified audio tests.
454 """
455
456 # Disable any OS-specific firewalls preventing VKAT / ATS to run.
457 self.disableHstFirewall();
458 self.disableGstFirewall(oTestVm, oTxsSession);
459
460 # First try to kill any old VKAT / VBoxAudioTest processes lurking around on the host.
461 # Might happen because of former (aborted) runs.
462 self.killHstVkat();
463
464 reporter.log("Active tests: %s" % (self.asTests,));
465
466 fRc = self.startVkatOnGuest(oTestVm, oSession, oTxsSession);
467 if fRc:
468 #
469 # Execute the tests using VKAT on the guest side (in guest mode).
470 #
471 if "guest_tone_playback" in self.asTests:
472 fRc = self.runTests(oTestVm, oSession, oTxsSession, 'Guest audio playback', '-i0');
473 if "guest_tone_recording" in self.asTests:
474 fRc = fRc and self.runTests(oTestVm, oSession, oTxsSession, 'Guest audio recording', '-i1');
475
476 #
477 # Retrieve log files for diagnosis.
478 #
479 self.txsDownloadFiles(oSession, oTxsSession,
480 [ ( self.getGstVkatLogFilePath(oTestVm),
481 'vkat-guest-daemonized-%s.log' % (oTestVm.sVmName,),),
482 ],
483 fIgnoreErrors = True);
484
485 return fRc;
486
487 def testOneVmConfig(self, oVM, oTestVm):
488 """
489 Runs tests using one specific VM config.
490 """
491
492 self.logVmInfo(oVM);
493
494 if oTestVm.isWindows() \
495 and oTestVm.sKind in ('WindowsNT4', 'Windows2000'): # Too old for DirectSound and WASAPI backends.
496 reporter.log('Audio testing skipped, not implemented/available for that OS yet.');
497 return True;
498
499 fRc = False;
500
501 # Reconfigure the VM.
502 oSession = self.openSession(oVM);
503 if oSession is not None:
504 # Set extra data.
505 for sExtraData in self.asOptExtraData:
506 sKey, sValue = sExtraData.split(':');
507 reporter.log('Set extradata: %s => %s' % (sKey, sValue));
508 fRc = oSession.setExtraData(sKey, sValue) and fRc;
509
510 # Save the settings.
511 fRc = fRc and oSession.saveSettings();
512 fRc = oSession.close() and fRc;
513
514 reporter.testStart('Waiting for TXS');
515 oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName,
516 fCdWait = True,
517 cMsTimeout = 3 * 60 * 1000,
518 sFileCdWait = '${OS/ARCH}/vkat${EXESUFF}');
519 reporter.testDone();
520
521 if oSession is not None:
522 self.addTask(oTxsSession);
523
524 fRc = self.doTest(oTestVm, oSession, oTxsSession);
525
526 # Cleanup.
527 self.removeTask(oTxsSession);
528 self.terminateVmBySession(oSession);
529
530 return fRc;
531
532 def onExit(self, iRc):
533 """
534 Exit handler for this test driver.
535 """
536 return vbox.TestDriver.onExit(self, iRc);
537
538if __name__ == '__main__':
539 sys.exit(tdAudioTest().main(sys.argv))
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette