VirtualBox

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

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

Audio/Validation Kit: Increased (release log) logging of VKAT running on the host. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.2 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: tdAudioTest.py 90994 2021-08-30 10:41:20Z 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: 90994 $"
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, asEnv = None, fAsAdmin = False):
219 """
220 Inner loop which handles the execution of a host binary.
221 """
222 fRc = False;
223
224 asEnvTmp = os.environ.copy();
225 if asEnv:
226 for sEnv in asEnv:
227 sKey, sValue = sEnv.split('=');
228 reporter.log2('Setting env var \"%s\" -> \"%s\"' % (sKey, sValue));
229 os.environ[sKey] = sValue; # Also apply it to the current environment.
230 asEnvTmp[sKey] = sValue;
231
232 if fAsAdmin \
233 and utils.getHostOs() != 'win':
234 oProcess = utils.sudoProcessPopen(asArgs,
235 env = asEnvTmp,
236 stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = False,
237 close_fds = False);
238 else:
239 oProcess = utils.processPopenSafe(asArgs,
240 env = asEnvTmp,
241 stdout = subprocess.PIPE, stderr = subprocess.PIPE);
242 if oProcess:
243 for line in iter(oProcess.stdout.readline, b''):
244 reporter.log('[' + sWhat + '] ' + line.decode('utf-8'));
245 iExitCode = oProcess.poll();
246 if iExitCode:
247 if iExitCode == 0:
248 reporter.error('Executing \"%s\" was successful' % (sWhat));
249 fRc = True;
250 else:
251 reporter.error('Executing \"%s\" on host returned exit code error %d' % (sWhat, iExitCode));
252
253 if not fRc:
254 reporter.error('Executing \"%s\" on host failed' % (sWhat,));
255
256 return fRc;
257
258 def executeHst(self, sWhat, asArgs, asEnv = None, fAsync = False, fAsAdmin = False):
259 """
260 Runs a binary (image) with optional admin (root) rights on the host and
261 waits until it terminates.
262
263 Windows currently is not supported yet running stuff as Administrator.
264
265 Returns success status (exit code is 0).
266 """
267 reporter.log('Executing \"%s\" on host (as admin = %s, async = %s)' % (sWhat, fAsAdmin, fAsync));
268
269 reporter.testStart(sWhat);
270
271 fRc = self.executeHstLoop(sWhat, asArgs, asEnv);
272 if fRc:
273 reporter.error('Executing \"%s\" on host done' % (sWhat,));
274 else:
275 reporter.error('Executing \"%s\" on host failed' % (sWhat,));
276
277 reporter.testDone();
278
279 return fRc;
280
281 def killHstProcessByName(self, sProcName):
282 """
283 Kills processes by their name.
284 """
285 reporter.log('Trying to kill processes named "%s"' % (sProcName,));
286 if sys.platform == 'win32':
287 sArgProcName = '\"%s.exe\"' % sProcName;
288 asArgs = [ 'taskkill', '/IM', sArgProcName, '/F' ];
289 self.executeHst('Killing process', asArgs);
290 else: # Note: killall is not available on older Debians (requires psmisc).
291 # Using the BSD syntax here; MacOS also should understand this.
292 procPs = subprocess.Popen(['ps', 'ax'], stdout=subprocess.PIPE);
293 out, err = procPs.communicate();
294 if err:
295 reporter.log('PS stderr:');
296 for sLine in err.decode('utf-8').splitlines():
297 reporter.log(sLine);
298 if out:
299 reporter.log4('PS stdout:');
300 for sLine in out.decode('utf-8').splitlines():
301 reporter.log4(sLine);
302 if sProcName in sLine:
303 pid = int(sLine.split(None, 1)[0]);
304 reporter.log('Killing PID %d' % (pid,));
305 os.kill(pid, signal.SIGKILL); # pylint: disable=no-member
306
307 def killHstVkat(self):
308 """
309 Kills VKAT (VBoxAudioTest) on the host side.
310 """
311 reporter.log('Killing stale/old VKAT processes ...');
312 self.killHstProcessByName("vkat");
313 self.killHstProcessByName("VBoxAudioTest");
314
315 def getWinFirewallArgsDisable(self, sOsType):
316 """
317 Returns the command line arguments for Windows OSes
318 to disable the built-in firewall (if any).
319
320 If not supported, returns an empty array.
321 """
322 if sOsType == 'vista': # pylint: disable=no-else-return
323 # Vista and up.
324 return (['netsh.exe', 'advfirewall', 'set', 'allprofiles', 'state', 'off']);
325 elif sOsType == 'xp': # Older stuff (XP / 2003).
326 return(['netsh.exe', 'firewall', 'set', 'opmode', 'mode=DISABLE']);
327 # Not supported / available.
328 return [];
329
330 def disableGstFirewall(self, oTestVm, oTxsSession):
331 """
332 Disables the firewall on a guest (if any).
333
334 Needs elevated / admin / root privileges.
335
336 Returns success status, not logged.
337 """
338 fRc = False;
339
340 asArgs = [];
341 sOsType = '';
342 if oTestVm.isWindows():
343 if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x']:
344 sOsType = 'nt3x'; # Not supported, but define it anyway.
345 elif oTestVm.sKind in ('Windows2000', 'WindowsXP', 'Windows2003'):
346 sOsType = 'xp';
347 else:
348 sOsType = 'vista';
349 asArgs = self.getWinFirewallArgsDisable(sOsType);
350 else:
351 sOsType = 'unsupported';
352
353 reporter.log('Disabling firewall on guest (type: %s) ...' % (sOsType,));
354
355 if asArgs:
356 fRc = self.txsRunTest(oTxsSession, 'Disabling guest firewall', 3 * 60 * 1000, \
357 oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), asArgs[0]), asArgs);
358 if not fRc:
359 reporter.error('Disabling firewall on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
360 else:
361 reporter.log('Firewall not available on guest, skipping');
362 fRc = True; # Not available, just skip.
363
364 return fRc;
365
366 def disableHstFirewall(self):
367 """
368 Disables the firewall on the host (if any).
369
370 Needs elevated / admin / root privileges.
371
372 Returns success status, not logged.
373 """
374 fRc = False;
375
376 asArgs = [];
377 sOsType = sys.platform;
378
379 if sOsType == 'win32':
380 reporter.log('Disabling firewall on host (type: %s) ...' % (sOsType));
381
382 ## @todo For now we ASSUME that we don't run (and don't support even) on old(er)
383 # Windows hosts than Vista.
384 asArgs = self.getWinFirewallArgsDisable('vista');
385 if asArgs:
386 fRc = self.executeHst('Disabling host firewall', asArgs, fAsAdmin = True);
387 else:
388 reporter.log('Firewall not available on host, skipping');
389 fRc = True; # Not available, just skip.
390
391 return fRc;
392
393 def getLastRcFromTxs(self, oTxsSession):
394 """
395 Extracts the last exit code reported by TXS from a run before.
396 Assumes that nothing else has been run on the same TXS session in the meantime.
397 """
398 iRc = 0;
399 (_, sOpcode, abPayload) = oTxsSession.getLastReply();
400 if sOpcode.startswith('PROC NOK '): # Extract process rc
401 iRc = abPayload[0]; # ASSUMES 8-bit rc for now.
402 return iRc;
403
404 def startVkatOnGuest(self, oTestVm, oSession, oTxsSession):
405 """
406 Starts VKAT on the guest (running in background).
407 """
408 sPathTemp = self.getGuestTempDir(oTestVm);
409 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-guest-out');
410 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-guest-temp');
411
412 reporter.log('Guest audio test temp path is \"%s\"' % (sPathAudioOut));
413 reporter.log('Guest audio test output path is \"%s\"' % (sPathAudioTemp));
414
415 fRc, sVkatExe = self.locateGstVkat(oSession, oTxsSession);
416 if fRc:
417 reporter.log('Using VKAT on guest at \"%s\"' % (sVkatExe));
418
419 asArgs = [ sVkatExe, 'test', '-vv', '--mode', 'guest', '--probe-backends', \
420 '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut ];
421
422 # Needed for NATed VMs.
423 asArgs.extend(['--tcp-connect-addr', '10.0.2.2' ]);
424
425 #
426 # Add own environment stuff.
427 #
428 asEnv = [];
429
430 # Enable more verbose logging for all groups. Disable later again?
431 asEnv.extend([ 'VKAT_RELEASE_LOG=all.e.l.l2.l3.f+audio_test.e.l.l2.l3.f' ]);
432
433 # Write the log file to some deterministic place so TxS can retrieve it later.
434 sVkatLogFile = 'VKAT_RELEASE_LOG_DEST=file=' + self.getGstVkatLogFilePath(oTestVm);
435 asEnv.extend([ sVkatLogFile ]);
436
437 #
438 # Execute asynchronously on the guest.
439 #
440 fRc = oTxsSession.asyncExec(sVkatExe, asArgs, asEnv, cMsTimeout = 15 * 60 * 1000);
441 if fRc:
442 self.addTask(oTxsSession);
443
444 if not fRc:
445 reporter.error('VKAT on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
446 else:
447 reporter.error('VKAT on guest not found');
448
449 return fRc;
450
451 def runTests(self, oTestVm, oSession, oTxsSession, sDesc, asTests):
452 """
453 Runs one or more tests using VKAT on the host, which in turn will
454 communicate with VKAT running on the guest and the Validation Kit
455 audio driver ATS (Audio Testing Service).
456 """
457 _ = oSession, oTxsSession;
458 sTag = uuid.uuid4();
459
460 sPathTemp = self.sScratchPath;
461 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-host-out-%s' % (sTag));
462 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-host-temp-%s' % (sTag));
463
464 reporter.log('Host audio test temp path is \"%s\"' % (sPathAudioOut));
465 reporter.log('Host audio test output path is \"%s\"' % (sPathAudioTemp));
466 reporter.log('Host audio test tag is \"%s\"' % (sTag));
467
468 reporter.testStart(sDesc);
469
470 sVkatExe = self.getBinTool('vkat');
471
472 reporter.log('Using VKAT on host at: \"%s\"' % (sVkatExe));
473
474 # Enable more verbose logging for all groups. Disable later again?
475 asEnv = [ 'VKAT_RELEASE_LOG=all.e.l.l2.l3.f+audio_test.e.l.l2.l3.f' ];
476
477 # Build the base command line, exclude all tests by default.
478 asArgs = [ sVkatExe, 'test', '-vv', '--mode', 'host', '--probe-backends', \
479 '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, '-a' ];
480
481 # ... and extend it with wanted tests.
482 asArgs.extend(asTests);
483
484 #
485 # Let VKAT on the host run synchronously.
486 #
487 fRc = self.executeHst("VKAT Host", asArgs, asEnv);
488
489 reporter.testDone();
490
491 return fRc;
492
493 def doTest(self, oTestVm, oSession, oTxsSession):
494 """
495 Executes the specified audio tests.
496 """
497
498 # Disable any OS-specific firewalls preventing VKAT / ATS to run.
499 fRc = self.disableHstFirewall();
500 fRc = self.disableGstFirewall(oTestVm, oTxsSession) and fRc;
501
502 if not fRc:
503 return False;
504
505 # First try to kill any old VKAT / VBoxAudioTest processes lurking around on the host.
506 # Might happen because of former (aborted) runs.
507 self.killHstVkat();
508
509 reporter.log("Active tests: %s" % (self.asTests,));
510
511 fRc = self.startVkatOnGuest(oTestVm, oSession, oTxsSession);
512 if fRc:
513 #
514 # Execute the tests using VKAT on the guest side (in guest mode).
515 #
516 if "guest_tone_playback" in self.asTests:
517 fRc = self.runTests(oTestVm, oSession, oTxsSession, 'Guest audio playback', asTests = [ '-i0' ]);
518 if "guest_tone_recording" in self.asTests:
519 fRc = fRc and self.runTests(oTestVm, oSession, oTxsSession, 'Guest audio recording', asTests = [ '-i1' ]);
520
521 # Cancel guest VKAT execution task summoned by startVkatOnGuest().
522 oTxsSession.cancelTask();
523
524 #
525 # Retrieve log files for diagnosis.
526 #
527 self.txsDownloadFiles(oSession, oTxsSession,
528 [ ( self.getGstVkatLogFilePath(oTestVm),
529 'vkat-guest-%s.log' % (oTestVm.sVmName,),),
530 ],
531 fIgnoreErrors = True);
532 return fRc;
533
534 def testOneVmConfig(self, oVM, oTestVm):
535 """
536 Runs tests using one specific VM config.
537 """
538
539 self.logVmInfo(oVM);
540
541 if oTestVm.isWindows() \
542 and oTestVm.sKind in ('WindowsNT4', 'Windows2000'): # Too old for DirectSound and WASAPI backends.
543 reporter.log('Audio testing skipped, not implemented/available for that OS yet.');
544 return True;
545
546 if self.fpApiVer < 7.0:
547 reporter.log('Audio testing for non-trunk builds skipped.');
548 return True;
549
550 fRc = False;
551
552 # Reconfigure the VM.
553 oSession = self.openSession(oVM);
554 if oSession is not None:
555 # Set extra data.
556 for sExtraData in self.asOptExtraData:
557 sKey, sValue = sExtraData.split(':');
558 reporter.log('Set extradata: %s => %s' % (sKey, sValue));
559 fRc = oSession.setExtraData(sKey, sValue) and fRc;
560
561 # Save the settings.
562 fRc = fRc and oSession.saveSettings();
563 fRc = oSession.close() and fRc;
564
565 reporter.testStart('Waiting for TXS');
566 oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName,
567 fCdWait = True,
568 cMsTimeout = 3 * 60 * 1000,
569 sFileCdWait = '${OS/ARCH}/vkat${EXESUFF}');
570 reporter.testDone();
571
572 if oSession is not None:
573 self.addTask(oTxsSession);
574
575 fRc = self.doTest(oTestVm, oSession, oTxsSession);
576
577 # Cleanup.
578 self.removeTask(oTxsSession);
579 self.terminateVmBySession(oSession);
580
581 return fRc;
582
583 def onExit(self, iRc):
584 """
585 Exit handler for this test driver.
586 """
587 return vbox.TestDriver.onExit(self, iRc);
588
589if __name__ == '__main__':
590 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