VirtualBox

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

Last change on this file since 90746 was 90746, checked in by vboxsync, 4 years ago

Audio/ValKit: tdAudioTest.py: Removed asyncio stuff again; requires Python >= 3.5 but some test boxes still run with 2.7.x [pylint]. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.5 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: tdAudioTest.py 90746 2021-08-19 13:40:32Z 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: 90746 $"
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 'C:\\Temp\\vkat',
69 'C:\\Temp\\VBoxAudioTest',
70 # Validation Kit .ISO.
71 '${CDROM}/vboxvalidationkit/${OS/ARCH}/vkat${EXESUFF}',
72 '${CDROM}/${OS/ARCH}/vkat${EXESUFF}',
73 # Test VMs.
74 '/opt/apps/vkat',
75 '/opt/apps/VBoxAudioTest',
76 '/apps/vkat',
77 '/apps/VBoxAudioTest',
78 'C:\\Apps\\vkat${EXESUFF}',
79 'C:\\Apps\\VBoxAudioTest${EXESUFF}',
80 ## @odo VBoxAudioTest on Guest Additions?
81 ];
82 self.asTestsDef = [
83 'guest_tone_playback', 'guest_tone_recording'
84 ];
85 self.asTests = self.asTestsDef;
86
87 # Enable audio debug mode.
88 #
89 # This is needed in order to load and use the Validation Kit audio driver,
90 # which in turn is being used in conjunction with the guest side to record
91 # output (guest is playing back) and injecting input (guest is recording).
92 self.asOptExtraData = [
93 'VBoxInternal2/Audio/Debug/Enabled:true',
94 ];
95
96 # Name of the running VM to use for running the test driver. Optional, and None if not being used.
97 self.sRunningVmName = None;
98
99 def showUsage(self):
100 """
101 Shows the audio test driver-specific command line options.
102 """
103 fRc = vbox.TestDriver.showUsage(self);
104 reporter.log('');
105 reporter.log('tdAudioTest Options:');
106 reporter.log(' --runningvmname <vmname>');
107 reporter.log(' --audio-tests <s1[:s2[:]]>');
108 reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef)));
109 return fRc;
110
111 def parseOption(self, asArgs, iArg):
112 """
113 Parses the audio test driver-specific command line options.
114 """
115 if asArgs[iArg] == '--runningvmname':
116 iArg += 1;
117 if iArg >= len(asArgs):
118 raise base.InvalidOption('The "--runningvmname" needs VM name');
119
120 self.sRunningVmName = asArgs[iArg];
121 elif asArgs[iArg] == '--audio-tests':
122 iArg += 1;
123 if asArgs[iArg] == 'all': # Nice for debugging scripts.
124 self.asTests = self.asTestsDef;
125 else:
126 self.asTests = asArgs[iArg].split(':');
127 for s in self.asTests:
128 if s not in self.asTestsDef:
129 raise base.InvalidOption('The "--audio-tests" value "%s" is not valid; valid values are: %s'
130 % (s, ' '.join(self.asTestsDef)));
131 else:
132 return vbox.TestDriver.parseOption(self, asArgs, iArg);
133 return iArg + 1;
134
135 def actionVerify(self):
136 """
137 Verifies the test driver before running.
138 """
139 if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso):
140 reporter.error('Cannot find the VBoxValidationKit.iso! (%s)'
141 'Please unzip a Validation Kit build in the current directory or in some parent one.'
142 % (self.sVBoxValidationKitIso,) );
143 return False;
144 return vbox.TestDriver.actionVerify(self);
145
146 def actionConfig(self):
147 """
148 Configures the test driver before running.
149 """
150 if not self.importVBoxApi(): # So we can use the constant below.
151 return False;
152
153 # Make sure that the Validation Kit .ISO is mounted
154 # to find the VKAT (Validation Kit Audio Test) binary on it.
155 assert self.sVBoxValidationKitIso is not None;
156 return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso);
157
158 def actionExecute(self):
159 """
160 Executes the test driver.
161 """
162 if self.sRunningVmName is None:
163 return self.oTestVmSet.actionExecute(self, self.testOneVmConfig);
164 return self.actionExecuteOnRunnigVM();
165
166 def actionExecuteOnRunnigVM(self):
167 """
168 Executes the tests in an already configured + running VM.
169 """
170 if not self.importVBoxApi():
171 return False;
172
173 fRc = True;
174
175 oVirtualBox = self.oVBoxMgr.getVirtualBox();
176 try:
177 oVM = oVirtualBox.findMachine(self.sRunningVmName);
178 if oVM.state != self.oVBoxMgr.constants.MachineState_Running:
179 reporter.error("Machine '%s' is not in Running state" % (self.sRunningVmName));
180 fRc = False;
181 except:
182 reporter.errorXcpt("Machine '%s' not found" % (self.sRunningVmName));
183 fRc = False;
184
185 if fRc:
186 oSession = self.openSession(oVM);
187 if oSession:
188 # Tweak this to your likings.
189 oTestVm = vboxtestvms.TestVm('runningvm', sKind = 'WindowsXP'); #sKind = 'WindowsXP' # sKind = 'Ubuntu_64'
190 (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 30 * 1000);
191 if fRc:
192 self.doTest(oTestVm, oSession, oTxsSession);
193 else:
194 reporter.error("Unable to open session for machine '%s'" % (self.sRunningVmName));
195 fRc = False;
196
197 del oVM;
198 del oVirtualBox;
199 return fRc;
200
201 def getGstVkatLogFilePath(self, oTestVm):
202 """
203 Returns the log file path of VKAT running on the guest (daemonized).
204 """
205 return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vkat-guest.log');
206
207 def locateGstVkat(self, oSession, oTxsSession):
208 """
209 Returns guest side path to VKAT.
210 """
211 for sVkatPath in self.asGstVkatPaths:
212 reporter.log2('Checking for VKAT at: %s ...' % (sVkatPath));
213 if self.txsIsFile(oSession, oTxsSession, sVkatPath):
214 return (True, sVkatPath);
215 reporter.error('Unable to find guest VKAT in any of these places:\n%s' % ('\n'.join(self.asGstVkatPaths),));
216 return (False, "");
217
218 def executeHstLoop(self, sWhat, asArgs, fAsAdmin = False):
219
220 fRc = False;
221
222 if fAsAdmin \
223 and utils.getHostOs() != 'win':
224 oProcess = utils.sudoProcessPopen(asArgs,
225 stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = False,
226 close_fds = False);
227 else:
228 oProcess = utils.processPopenSafe(asArgs,
229 stdout = subprocess.PIPE, stderr = subprocess.PIPE);
230 if oProcess:
231 for line in iter(oProcess.stdout.readline, b''):
232 reporter.log('[' + sWhat + '] ' + line.decode(sys.stdin.encoding));
233 iExitCode = oProcess.poll();
234 if iExitCode:
235 if iExitCode == 0:
236 reporter.error('Executing \"%s\" was successful' % (sWhat));
237 fRc = True;
238 else:
239 reporter.error('Executing \"%s\" on host returned exit code error %d' % (sWhat, iExitCode));
240
241 if not fRc:
242 reporter.error('Executing \"%s\" on host failed' % (sWhat,));
243
244 return fRc;
245
246 def executeHst(self, sWhat, asArgs, fAsync = False, fAsAdmin = False):
247 """
248 Runs a binary (image) with optional admin (root) rights on the host and
249 waits until it terminates.
250
251 Windows currently is not supported yet running stuff as Administrator.
252
253 Returns success status (exit code is 0).
254 """
255 reporter.log('Executing \"%s\" on host (as admin = %s, async = %s)' % (sWhat, fAsAdmin, fAsync));
256
257 reporter.testStart(sWhat);
258
259 fRc = self.executeHstLoop(sWhat, asArgs);
260 if fRc:
261 reporter.error('Executing \"%s\" on host done' % (sWhat,));
262 else:
263 reporter.error('Executing \"%s\" on host failed' % (sWhat,));
264
265 reporter.testDone();
266
267 return fRc;
268
269 def killHstProcessByName(self, sProcName):
270 """
271 Kills processes by their name.
272 """
273 reporter.log('Trying to kill processes named "%s"' % (sProcName,));
274 if sys.platform == 'win32':
275 sArgProcName = '\"%s.exe\"' % sProcName;
276 asArgs = [ 'taskkill', '/IM', sArgProcName, '/F' ];
277 self.executeHst('Killing process', asArgs);
278 else: # Note: killall is not available on older Debians (requires psmisc).
279 # Using the BSD syntax here; MacOS also should understand this.
280 procPs = subprocess.Popen(['ps', 'ax'], stdout=subprocess.PIPE);
281 out, _ = procPs.communicate();
282 for sLine in out.decode("utf-8").splitlines():
283 if sProcName in sLine:
284 pid = int(sLine.split(None, 1)[0]);
285 reporter.log('Killing PID %d' % (pid,));
286 os.kill(pid, signal.SIGKILL); # pylint: disable=no-member
287
288 def killHstVkat(self):
289 """
290 Kills VKAT (VBoxAudioTest) on the host side.
291 """
292 reporter.log('Killing stale/old VKAT processes ...');
293 self.killHstProcessByName("vkat");
294 self.killHstProcessByName("VBoxAudioTest");
295
296 def getWinFirewallArgsDisable(self, sOsType):
297 """
298 Returns the command line arguments for Windows OSes
299 to disable the built-in firewall (if any).
300
301 If not supported, returns an empty array.
302 """
303 if sOsType == 'vista': # pylint: disable=no-else-return
304 # Vista and up.
305 return (['netsh.exe', 'advfirewall', 'set', 'allprofiles', 'state', 'off']);
306 elif sOsType == 'xp': # Older stuff (XP / 2003).
307 return(['netsh.exe', 'firewall', 'set', 'opmode', 'mode=DISABLE']);
308 # Not supported / available.
309 return [];
310
311 def disableGstFirewall(self, oTestVm, oTxsSession):
312 """
313 Disables the firewall on a guest (if any).
314
315 Needs elevated / admin / root privileges.
316
317 Returns success status, not logged.
318 """
319 fRc = False;
320
321 asArgs = [];
322 sOsType = '';
323 if oTestVm.isWindows():
324 if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x']:
325 sOsType = 'nt3x'; # Not supported, but define it anyway.
326 elif oTestVm.sKind in ('Windows2000', 'WindowsXP', 'Windows2003'):
327 sOsType = 'xp';
328 else:
329 sOsType = 'vista';
330 asArgs = self.getWinFirewallArgsDisable(sOsType);
331 else:
332 sOsType = 'unsupported';
333
334 reporter.log('Disabling firewall on guest (type: %s) ...' % (sOsType,));
335
336 if asArgs:
337 fRc = self.txsRunTest(oTxsSession, 'Disabling guest firewall', 3 * 60 * 1000, \
338 oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), asArgs[0]), asArgs);
339 if not fRc:
340 reporter.error('Disabling firewall on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
341 else:
342 reporter.log('Firewall not available on guest, skipping');
343 fRc = True; # Not available, just skip.
344
345 return fRc;
346
347 def setGstAudioBackend(self, oTestVm, asArgs):
348 """
349 Guesses guest OS, uses an alternative (non-default) audio backends if necessary
350 and appends it to the given arguments sequence.
351
352 Returns the altered arguments sequence.
353 """
354 asArgsRet = asArgs;
355
356 # Note: Also works with the 64-bit variants (if any).
357 sOsType = oTestVm.getNonCanonicalGuestOsType();
358 if "Windows2000" in sOsType \
359 or "WindowsXP" in sOsType \
360 or "Windows2003" in sOsType \
361 or "Windows7" in sOsType:
362 ## @todo Some more here?
363 asArgsRet.extend( [ '--backend', 'directsound' ]);
364
365 ## @todo Tweak old(er) Linux'es as well to use OSS instead of PulseAudio?
366 return asArgsRet;
367
368 def disableHstFirewall(self):
369 """
370 Disables the firewall on the host (if any).
371
372 Needs elevated / admin / root privileges.
373
374 Returns success status, not logged.
375 """
376 fRc = False;
377
378 asArgs = [];
379 sOsType = sys.platform;
380
381 if sOsType == 'win32':
382 reporter.log('Disabling firewall on host (type: %s) ...' % (sOsType));
383
384 ## @todo For now we ASSUME that we don't run (and don't support even) on old(er)
385 # Windows hosts than Vista.
386 asArgs = self.getWinFirewallArgsDisable('vista');
387 if asArgs:
388 fRc = self.executeHst('Disabling host firewall', asArgs, fAsAdmin = True);
389 else:
390 reporter.log('Firewall not available on host, skipping');
391 fRc = True; # Not available, just skip.
392
393 return fRc;
394
395 def getLastRcFromTxs(self, oTxsSession):
396 """
397 Extracts the last exit code reported by TXS from a run before.
398 Assumes that nothing else has been run on the same TXS session in the meantime.
399 """
400 iRc = 0;
401 (_, sOpcode, abPayload) = oTxsSession.getLastReply();
402 if sOpcode.startswith('PROC NOK '): # Extract process rc
403 iRc = abPayload[0]; # ASSUMES 8-bit rc for now.
404 return iRc;
405
406 def startVkatOnGuest(self, oTestVm, oSession, oTxsSession):
407 """
408 Starts VKAT on the guest (running in background).
409 """
410 sPathTemp = self.getGuestTempDir(oTestVm);
411 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-guest-out');
412 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-guest-temp');
413
414 reporter.log('Guest audio test temp path is \"%s\"' % (sPathAudioOut));
415 reporter.log('Guest audio test output path is \"%s\"' % (sPathAudioTemp));
416
417 fRc, sVkatExe = self.locateGstVkat(oSession, oTxsSession);
418 if fRc:
419 reporter.log('Using VKAT on guest at \"%s\"' % (sVkatExe));
420
421 asArgs = [ sVkatExe, 'test', '-vv', '--mode', 'guest', \
422 '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut ];
423
424 # Guess the guest backend and apply the new arguments (if any).
425 asArgs = self.setGstAudioBackend(oTestVm, asArgs);
426
427 #
428 # Add own environment stuff.
429 #
430 asEnv = [];
431
432 # Write the log file to some deterministic place so TxS can retrieve it later.
433 sVkatLogFile = 'VKAT_RELEASE_LOG_DEST=file=' + self.getGstVkatLogFilePath(oTestVm);
434 asEnv.extend([ sVkatLogFile ]);
435
436 #
437 # Execute asynchronously on the guest.
438 #
439 fRc = oTxsSession.asyncExec(sVkatExe, asArgs, asEnv, cMsTimeout = 15 * 60 * 1000);
440 if fRc:
441 self.addTask(oTxsSession);
442
443 if not fRc:
444 reporter.error('VKAT on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
445 else:
446 reporter.error('VKAT on guest not found');
447
448 return fRc;
449
450 def runTests(self, oTestVm, oSession, oTxsSession, sDesc, asTests):
451 """
452 Runs one or more tests using VKAT on the host, which in turn will
453 communicate with VKAT running on the guest and the Validation Kit
454 audio driver ATS (Audio Testing Service).
455 """
456 _ = oSession, oTxsSession;
457 sTag = uuid.uuid4();
458
459 sPathTemp = self.sScratchPath;
460 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-host-out-%s' % (sTag));
461 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-host-temp-%s' % (sTag));
462
463 reporter.log('Host audio test temp path is \"%s\"' % (sPathAudioOut));
464 reporter.log('Host audio test output path is \"%s\"' % (sPathAudioTemp));
465 reporter.log('Host audio test tag is \"%s\"' % (sTag));
466
467 reporter.testStart(sDesc);
468
469 sVkatExe = self.getBinTool('vkat');
470
471 reporter.log('Using VKAT on host at: \"%s\"' % (sVkatExe));
472
473 # Build the base command line, exclude all tests by default.
474 asArgs = [ sVkatExe, 'test', '-vv', '--mode', 'host', '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, '-a' ];
475
476 # ... and extend it with wanted tests.
477 asArgs.extend(asTests);
478
479 #
480 # Let VKAT on the host run synchronously.
481 #
482 fRc = self.executeHst("VKAT Host", asArgs);
483
484 reporter.testDone();
485
486 return fRc;
487
488 def doTest(self, oTestVm, oSession, oTxsSession):
489 """
490 Executes the specified audio tests.
491 """
492
493 # Disable any OS-specific firewalls preventing VKAT / ATS to run.
494 fRc = self.disableHstFirewall();
495 fRc = self.disableGstFirewall(oTestVm, oTxsSession) and fRc;
496
497 if not fRc:
498 return False;
499
500 # First try to kill any old VKAT / VBoxAudioTest processes lurking around on the host.
501 # Might happen because of former (aborted) runs.
502 self.killHstVkat();
503
504 reporter.log("Active tests: %s" % (self.asTests,));
505
506 fRc = self.startVkatOnGuest(oTestVm, oSession, oTxsSession);
507 if fRc:
508 #
509 # Execute the tests using VKAT on the guest side (in guest mode).
510 #
511 if "guest_tone_playback" in self.asTests:
512 fRc = self.runTests(oTestVm, oSession, oTxsSession, 'Guest audio playback', asTests = [ '-i0' ]);
513 if "guest_tone_recording" in self.asTests:
514 fRc = fRc and self.runTests(oTestVm, oSession, oTxsSession, 'Guest audio recording', asTests = [ '-i1' ]);
515
516 #
517 # Retrieve log files for diagnosis.
518 #
519 self.txsDownloadFiles(oSession, oTxsSession,
520 [ ( self.getGstVkatLogFilePath(oTestVm),
521 'vkat-guest-%s.log' % (oTestVm.sVmName,),),
522 ],
523 fIgnoreErrors = True);
524
525 return fRc;
526
527 def testOneVmConfig(self, oVM, oTestVm):
528 """
529 Runs tests using one specific VM config.
530 """
531
532 self.logVmInfo(oVM);
533
534 if oTestVm.isWindows() \
535 and oTestVm.sKind in ('WindowsNT4', 'Windows2000'): # Too old for DirectSound and WASAPI backends.
536 reporter.log('Audio testing skipped, not implemented/available for that OS yet.');
537 return True;
538
539 fRc = False;
540
541 # Reconfigure the VM.
542 oSession = self.openSession(oVM);
543 if oSession is not None:
544 # Set extra data.
545 for sExtraData in self.asOptExtraData:
546 sKey, sValue = sExtraData.split(':');
547 reporter.log('Set extradata: %s => %s' % (sKey, sValue));
548 fRc = oSession.setExtraData(sKey, sValue) and fRc;
549
550 # Save the settings.
551 fRc = fRc and oSession.saveSettings();
552 fRc = oSession.close() and fRc;
553
554 reporter.testStart('Waiting for TXS');
555 oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName,
556 fCdWait = True,
557 cMsTimeout = 3 * 60 * 1000,
558 sFileCdWait = '${OS/ARCH}/vkat${EXESUFF}');
559 reporter.testDone();
560
561 if oSession is not None:
562 self.addTask(oTxsSession);
563
564 fRc = self.doTest(oTestVm, oSession, oTxsSession);
565
566 # Cleanup.
567 self.removeTask(oTxsSession);
568 self.terminateVmBySession(oSession);
569
570 return fRc;
571
572 def onExit(self, iRc):
573 """
574 Exit handler for this test driver.
575 """
576 return vbox.TestDriver.onExit(self, iRc);
577
578if __name__ == '__main__':
579 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