VirtualBox

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

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

Audio/ValKit: tdAudioTest.py: Factored out running host binaries into executeHstBinaryAsAdmin() and also make use of it when killing host processes to catch (std[err|out]) errors. ​bugref:10008

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