VirtualBox

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

Last change on this file since 106089 was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.7 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: tdAudioTest.py 106061 2024-09-16 14:03:52Z 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-2024 Oracle and/or its affiliates.
15
16This file is part of VirtualBox base platform packages, as
17available from https://www.virtualbox.org.
18
19This program is free software; you can redistribute it and/or
20modify it under the terms of the GNU General Public License
21as published by the Free Software Foundation, in version 3 of the
22License.
23
24This program is distributed in the hope that it will be useful, but
25WITHOUT ANY WARRANTY; without even the implied warranty of
26MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27General Public License for more details.
28
29You should have received a copy of the GNU General Public License
30along with this program; if not, see <https://www.gnu.org/licenses>.
31
32The contents of this file may alternatively be used under the terms
33of the Common Development and Distribution License Version 1.0
34(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
35in the VirtualBox distribution, in which case the provisions of the
36CDDL are applicable instead of those of the GPL.
37
38You may elect to license modified versions of this file under the
39terms and conditions of either the GPL or the CDDL or both.
40
41SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
42"""
43__version__ = "$Revision: 106061 $"
44
45# Standard Python imports.
46from datetime import datetime
47import os
48import sys
49import subprocess
50import time
51import threading
52
53# Only the main script needs to modify the path.
54try: __file__ # pylint: disable=used-before-assignment
55except: __file__ = sys.argv[0];
56g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))));
57sys.path.append(g_ksValidationKitDir);
58
59# Validation Kit imports.
60from testdriver import reporter
61from testdriver import base
62from testdriver import vbox
63from testdriver import vboxcon;
64from testdriver import vboxtestvms
65from common import utils;
66
67# pylint: disable=unnecessary-semicolon
68
69class tdDebugSettings(object):
70 """
71 Contains local test debug settings.
72 """
73 def __init__(self, sVkatExeHst = None):
74 # Absolute path of VKAT on the host side which gets uploaded from the host.
75 self.sVkatExeHst = sVkatExeHst;
76 # Absolute path of VKAT on the guest side (which got uploaded from the host).
77 self.sVkatExeGst = None;
78
79class tdAudioTest(vbox.TestDriver):
80 """
81 Runs various audio tests.
82 """
83 def __init__(self):
84 vbox.TestDriver.__init__(self);
85 self.oTestVmSet = self.oTestVmManager.getSmokeVmSet('nat');
86 self.asGstVkatPaths = [
87 # Debugging stuff (SCP'd over to the guest).
88 '/tmp/vkat',
89 '/tmp/VBoxAudioTest',
90 'C:\\Temp\\vkat',
91 'C:\\Temp\\VBoxAudioTest',
92 # Validation Kit .ISO.
93 '${CDROM}/vboxvalidationkit/${OS/ARCH}/vkat${EXESUFF}',
94 '${CDROM}/${OS/ARCH}/vkat${EXESUFF}',
95 # Test VMs.
96 '/opt/apps/vkat',
97 '/opt/apps/VBoxAudioTest',
98 '/apps/vkat',
99 '/apps/VBoxAudioTest',
100 'C:\\Apps\\vkat${EXESUFF}',
101 'C:\\Apps\\VBoxAudioTest${EXESUFF}',
102 ## @todo VBoxAudioTest on Guest Additions?
103 ];
104 self.asTestsDef = [
105 'guest_tone_playback', 'guest_tone_recording'
106 ];
107 self.asTests = self.asTestsDef;
108 self.oDebug = tdDebugSettings();
109
110 # Optional arguments passing to VKAT when doing the actual audio tests.
111 self.asVkatTestArgs = [];
112 # Optional arguments passing to VKAT when verifying audio test sets.
113 self.asVkatVerifyArgs = [];
114
115 # Exit code of last host process execution, shared between exeuction thread and main thread.
116 # This ASSUMES that we only have one thread running at a time. Rather hacky, but does the job for now.
117 self.iThreadHstProcRc = 0;
118
119 # Enable audio debug mode.
120 #
121 # This is needed in order to load and use the Validation Kit audio driver,
122 # which in turn is being used in conjunction with the guest side to record
123 # output (guest is playing back) and injecting input (guest is recording).
124 self.asOptExtraData = [
125 'VBoxInternal2/Audio/Debug/Enabled:true',
126 ];
127
128 # Name of the running VM to use for running the test driver. Optional, and None if not being used.
129 self.sRunningVmName = None;
130
131 # Audio controller type to use.
132 # If set to None, the OS' recommended controller type will be used (defined by Main).
133 self.sAudioControllerType = None;
134
135 def showUsage(self):
136 """
137 Shows the audio test driver-specific command line options.
138 """
139 fRc = vbox.TestDriver.showUsage(self);
140 reporter.log('');
141 reporter.log('tdAudioTest Options:');
142 reporter.log(' --runningvmname <vmname>');
143 reporter.log(' --audio-tests <s1[:s2[:]]>');
144 reporter.log(' Default: %s (all)' % (':'.join(self.asTestsDef)));
145 reporter.log(' --audio-controller-type <HDA|AC97|SB16>');
146 reporter.log(' Default: recommended controller');
147 reporter.log(' --audio-debug-img <path/to/vkat>');
148 reporter.log(' --audio-test-count <number>');
149 reporter.log(' Default: 0 (means random)');
150 reporter.log(' --audio-test-timeout <ms>');
151 reporter.log(' Default: 5 minutes (300000)');
152 reporter.log(' --audio-test-tone-duration <ms>');
153 reporter.log(' Default: 0 (means random)');
154 reporter.log(' --audio-verify-max-diff-count <number>');
155 reporter.log(' Default: 0 (strict)');
156 reporter.log(' --audio-verify-max-diff-percent <0-100>');
157 reporter.log(' Default: 0 (strict)');
158 reporter.log(' --audio-verify-max-size-percent <0-100>');
159 reporter.log(' Default: 0 (strict)');
160 return fRc;
161
162 def parseOption(self, asArgs, iArg):
163 """
164 Parses the audio test driver-specific command line options.
165 """
166 if asArgs[iArg] == '--runningvmname':
167 iArg += 1;
168 if iArg >= len(asArgs):
169 raise base.InvalidOption('The "--runningvmname" needs VM name');
170
171 self.sRunningVmName = asArgs[iArg];
172 elif asArgs[iArg] == '--audio-tests':
173 iArg += 1;
174 if asArgs[iArg] == 'all': # Nice for debugging scripts.
175 self.asTests = self.asTestsDef;
176 else:
177 self.asTests = asArgs[iArg].split(':');
178 for s in self.asTests:
179 if s not in self.asTestsDef:
180 raise base.InvalidOption('The "--audio-tests" value "%s" is not valid; valid values are: %s'
181 % (s, ' '.join(self.asTestsDef)));
182 elif asArgs[iArg] == '--audio-controller-type':
183 iArg += 1;
184 if iArg >= len(asArgs):
185 raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1]));
186 if asArgs[iArg] == 'HDA' \
187 or asArgs[iArg] == 'AC97' \
188 or asArgs[iArg] == 'SB16':
189 self.sAudioControllerType = asArgs[iArg];
190 else:
191 raise base.InvalidOption('The "--audio-controller-type" value "%s" is not valid' % (asArgs[iArg]));
192 elif asArgs[iArg] == '--audio-debug-img':
193 iArg += 1;
194 if iArg >= len(asArgs):
195 raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1]));
196 self.oDebug.sVkatExeHst = asArgs[iArg];
197 elif asArgs[iArg] == '--audio-test-count' \
198 or asArgs[iArg] == '--audio-test-tone-duration' \
199 or asArgs[iArg] == '--audio-test-timeout':
200 fHandleOption = True;
201 if asArgs[iArg] == '--audio-test-timeout' \
202 and (self.fpApiVer < 7.1 or self.uRevision < 161834):
203 fHandleOption = False; # Older VKAT builds don't know about this option.
204 # Strip the "--audio-test-" prefix and keep the options as defined in VKAT,
205 # e.g. "--audio-test-count" -> "--count". That way we don't
206 # need to do any special argument translation and whatnot.
207 if fHandleOption:
208 self.asVkatTestArgs.extend(['--' + asArgs[iArg][len('--audio-test-'):]]);
209 iArg += 1;
210 if iArg >= len(asArgs):
211 raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1]));
212 if fHandleOption:
213 self.asVkatTestArgs.extend([asArgs[iArg]]);
214 elif asArgs[iArg] == '--audio-verify-max-diff-count' \
215 or asArgs[iArg] == '--audio-verify-max-diff-percent' \
216 or asArgs[iArg] == '--audio-verify-max-size-percent':
217 # Strip the "--audio-verify-" prefix and keep the options as defined in VKAT,
218 # e.g. "--audio-verify-max-diff-count" -> "--max-diff-count". That way we don't
219 # need to do any special argument translation and whatnot.
220 self.asVkatVerifyArgs.extend(['--' + asArgs[iArg][len('--audio-verify-'):]]);
221 iArg += 1;
222 if iArg >= len(asArgs):
223 raise base.InvalidOption('Option "%s" needs a value' % (asArgs[iArg - 1]));
224 self.asVkatVerifyArgs.extend([asArgs[iArg]]);
225 else:
226 return vbox.TestDriver.parseOption(self, asArgs, iArg);
227 return iArg + 1;
228
229 def prepareGuestForDebugging(self, oSession, oTxsSession, oTestVm): # pylint: disable=unused-argument
230 """
231 Prepares a guest for (manual) debugging.
232
233 This involves copying over and invoking a the locally built VKAT binary.
234 """
235
236 if self.oDebug.sVkatExeHst is None: # If no debugging enabled, bail out.
237 reporter.log('Skipping debugging');
238 return True;
239
240 self.oDebug.sVkatExeGst = oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vkat${EXESUFF}');
241
242 reporter.log('Preparing for debugging ...');
243
244 try:
245 reporter.log('Uploading "%s" to "%s" ...' % (self.oDebug.sVkatExeHst, self.oDebug.sVkatExeGst));
246 oTxsSession.syncUploadFile(self.oDebug.sVkatExeHst, self.oDebug.sVkatExeGst);
247
248 if oTestVm.isLinux():
249 oTxsSession.syncChMod(self.oDebug.sVkatExeGst, 0o755);
250 except:
251 return reporter.errorXcpt('Unable to prepare for debugging');
252
253 return True;
254
255 def actionVerify(self):
256 """
257 Verifies the test driver before running.
258 """
259 if self.sVBoxValidationKitIso is None or not os.path.isfile(self.sVBoxValidationKitIso):
260 reporter.error('Cannot find the VBoxValidationKit.iso! (%s)'
261 'Please unzip a Validation Kit build in the current directory or in some parent one.'
262 % (self.sVBoxValidationKitIso,) );
263 return False;
264 return vbox.TestDriver.actionVerify(self);
265
266 def actionConfig(self):
267 """
268 Configures the test driver before running.
269 """
270 if not self.importVBoxApi(): # So we can use the constant below.
271 return False;
272
273 # Make sure that the Validation Kit .ISO is mounted
274 # to find the VKAT (Validation Kit Audio Test) binary on it.
275 assert self.sVBoxValidationKitIso is not None;
276 return self.oTestVmSet.actionConfig(self, sDvdImage = self.sVBoxValidationKitIso);
277
278 def actionExecute(self):
279 """
280 Executes the test driver.
281 """
282
283 # Disable maximum logging line restrictions per group.
284 # This comes in handy when running this test driver in a (very) verbose mode, e.g. for debugging.
285 os.environ['VBOX_LOG_MAX_PER_GROUP'] = '0';
286 os.environ['VBOX_RELEASE_LOG_MAX_PER_GROUP'] = '0';
287 os.environ['VKAT_RELEASE_LOG_MAX_PER_GROUP'] = '0';
288
289 if self.sRunningVmName is None:
290 return self.oTestVmSet.actionExecute(self, self.testOneVmConfig);
291 return self.actionExecuteOnRunnigVM();
292
293 def actionExecuteOnRunnigVM(self):
294 """
295 Executes the tests in an already configured + running VM.
296 """
297 if not self.importVBoxApi():
298 return False;
299
300 fRc = True;
301
302 oVM = None;
303 oVirtualBox = None;
304
305 oVirtualBox = self.oVBoxMgr.getVirtualBox();
306 try:
307 oVM = oVirtualBox.findMachine(self.sRunningVmName);
308 if oVM.state != self.oVBoxMgr.constants.MachineState_Running:
309 reporter.error("Machine '%s' is not in Running state (state is %d)" % (self.sRunningVmName, oVM.state));
310 fRc = False;
311 except:
312 reporter.errorXcpt("Machine '%s' not found" % (self.sRunningVmName));
313 fRc = False;
314
315 if fRc:
316 oSession = self.openSession(oVM);
317 if oSession:
318 # Tweak this to your likings.
319 oTestVm = vboxtestvms.TestVm('runningvm', sKind = 'WindowsXP'); #sKind = 'WindowsXP' # sKind = 'Ubuntu_64'
320 (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, 30 * 1000);
321 if fRc:
322 self.doTest(oTestVm, oSession, oTxsSession);
323 else:
324 reporter.error("Unable to open session for machine '%s'" % (self.sRunningVmName));
325 fRc = False;
326
327 if oVM:
328 del oVM;
329 if oVirtualBox:
330 del oVirtualBox;
331 return fRc;
332
333 def getGstVkatLogFilePath(self, oTestVm):
334 """
335 Returns the log file path of VKAT running on the guest (daemonized).
336 """
337 return oTestVm.pathJoin(self.getGuestTempDir(oTestVm), 'vkat-guest.log');
338
339 def locateGstBinary(self, oSession, oTxsSession, asPaths):
340 """
341 Locates a guest binary on the guest by checking the paths in \a asPaths.
342 """
343 for sCurPath in asPaths:
344 reporter.log2('Checking for \"%s\" ...' % (sCurPath));
345 if self.txsIsFile(oSession, oTxsSession, sCurPath, fIgnoreErrors = True):
346 return (True, sCurPath);
347 reporter.error('Unable to find guest binary in any of these places:\n%s' % ('\n'.join(asPaths),));
348 return (False, "");
349
350 def executeHstLoop(self, sWhat, asArgs, asEnv = None, fAsAdmin = False):
351 """
352 Inner loop which handles the execution of a host binary.
353
354 Might be called synchronously in main thread or via the thread exeuction helper (asynchronous).
355
356 Returns (success status, exit code).
357 """
358 fRc = False;
359 iRc = -42;
360
361 asEnvTmp = os.environ.copy();
362 if asEnv:
363 for sEnv in asEnv:
364 sKey, sValue = sEnv.split('=');
365 reporter.log2('Setting env var \"%s\" -> \"%s\"' % (sKey, sValue));
366 os.environ[sKey] = sValue; # Also apply it to the current environment.
367 asEnvTmp[sKey] = sValue;
368
369 try:
370 # Spawn process.
371 if fAsAdmin \
372 and utils.getHostOs() != 'win':
373 oProcess = utils.sudoProcessStart(asArgs, env = asEnvTmp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT);
374 else:
375 oProcess = utils.processStart(asArgs, env = asEnvTmp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT);
376
377 if not oProcess:
378 reporter.error('Starting process for "%s" failed!' % (sWhat));
379 return fRc, iRc;
380
381 iPid = oProcess.pid;
382 self.pidFileAdd(iPid, sWhat);
383
384 while True if sys.version_info[0] < 3 else oProcess.stdout.readable(): # pylint: disable=no-member
385 try:
386 sStdOut = oProcess.stdout.readline();
387 if sStdOut \
388 and isinstance(sStdOut, str):
389 sStdOut = sStdOut.strip();
390 reporter.log('%s [stdout]: %s' % (sWhat, sStdOut.rstrip('\n'),));
391 except:
392 reporter.log('%s [stdout]: <Unable to read output>' % (sWhat,));
393 self.processEvents(0);
394 iRc = oProcess.poll();
395 if iRc is not None:
396 break;
397
398 if iRc == 0:
399 reporter.log('*** %s: exit code %d' % (sWhat, iRc));
400 else:
401 reporter.log('!*! %s: exit code %d' % (sWhat, iRc));
402
403 fRc = True;
404 self.pidFileRemove(iPid);
405 except:
406 reporter.logXcpt('Executing "%s" failed!' % (sWhat));
407
408 reporter.log('Executing \"%s\" on host %s' % (sWhat, 'done' if fRc else 'failed',));
409 return (fRc, iRc);
410
411 def executeHstThread(self, sWhat, asArgs, asEnv = None, fAsAdmin = False):
412 """
413 Thread execution helper to run a process on the host.
414 """
415 _, iRc = self.executeHstLoop(sWhat, asArgs, asEnv, fAsAdmin);
416
417 # Save thread result code.
418 self.iThreadHstProcRc = iRc;
419
420 def executeHst(self, sWhat, asArgs, asEnv = None, fAsAdmin = False, fBlocking = True, iExpectedRc = None):
421 """
422 Runs a binary (image) with optional admin (root) rights on the host and
423 waits until it terminates.
424
425 Windows currently is not supported yet running stuff as Administrator.
426
427 Returns (success status, exit code).
428 """
429 reporter.log('Executing \"%s\" on host (as admin = %s, blocking = %s, expected rc = %s)'
430 % (sWhat, fAsAdmin, fBlocking, str(iExpectedRc) if iExpectedRc is not None else '<None>'));
431 reporter.log2('Arguments: %s' % (asArgs,));
432 if asEnv:
433 reporter.log2('Environment: %s' % (asEnv,));
434
435 try: sys.stdout.flush();
436 except: pass;
437 try: sys.stderr.flush();
438 except: pass;
439
440 fRc = False;
441 iRc = -42;
442
443 if fBlocking: # Run in same thread (blocking).
444 fRc, iRc = self.executeHstLoop(sWhat, asArgs, asEnv, fAsAdmin);
445 else: # Run in separate thread (asynchronous).
446 self.iThreadHstProcRc = -42; # Initialize thread rc.
447 try:
448 oThread = threading.Thread(target = self.executeHstThread, args = [ sWhat, asArgs, asEnv, fAsAdmin ]);
449 oThread.start();
450 while oThread.join(0.1):
451 if not oThread.is_alive():
452 break;
453 self.processEvents(0);
454 reporter.log2('Thread returned exit code for "%s": %d' % (sWhat, self.iThreadHstProcRc));
455 fRc = True;
456 iRc = self.iThreadHstProcRc;
457 except:
458 reporter.logXcpt('Starting thread for "%s" failed' % (sWhat,));
459
460 # Adjust fRc if caller expected a specific exit code.
461 if iExpectedRc is not None \
462 and (iRc != iExpectedRc):
463 reporter.error('Executing \"%s\" on host failed (got exit code %d, expected %d'
464 % (sWhat, iRc, iExpectedRc,));
465 fRc = False;
466
467 reporter.log2('Got fRc = %s + exit code %d' % (fRc, iRc,));
468 return fRc, iRc;
469
470 def getWinFirewallArgsDisable(self, sOsType):
471 """
472 Returns the command line arguments for Windows OSes
473 to disable the built-in firewall (if any).
474
475 If not supported, returns an empty array.
476 """
477 if sOsType == 'vista': # pylint: disable=no-else-return
478 # Vista and up.
479 return (['netsh.exe', 'advfirewall', 'set', 'allprofiles', 'state', 'off']);
480 elif sOsType == 'xp': # Older stuff (XP / 2003).
481 return(['netsh.exe', 'firewall', 'set', 'opmode', 'mode=DISABLE']);
482 # Not supported / available.
483 return [];
484
485 def disableGstFirewall(self, oTestVm, oTxsSession):
486 """
487 Disables the firewall on a guest (if any).
488
489 Needs elevated / admin / root privileges.
490
491 Returns success status, not logged.
492 """
493 fRc = False;
494
495 asArgs = [];
496 sOsType = '';
497 if oTestVm.isWindows():
498 if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x']:
499 sOsType = 'nt3x'; # Not supported, but define it anyway.
500 elif oTestVm.sKind in ('Windows2000', 'WindowsXP', 'Windows2003'):
501 sOsType = 'xp';
502 else:
503 sOsType = 'vista';
504 asArgs = self.getWinFirewallArgsDisable(sOsType);
505 else:
506 sOsType = 'unsupported';
507
508 reporter.log('Disabling firewall on guest (type: %s) ...' % (sOsType,));
509
510 if asArgs:
511 fRc = self.txsRunTest(oTxsSession, 'Disabling guest firewall', 3 * 60 * 1000, \
512 oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), asArgs[0]), asArgs);
513 if not fRc:
514 reporter.error('Disabling firewall on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
515 else:
516 reporter.log('Firewall not available on guest, skipping');
517 fRc = True; # Not available, just skip.
518
519 return fRc;
520
521 def disableHstFirewall(self):
522 """
523 Disables the firewall on the host (if any).
524
525 Needs elevated / admin / root privileges.
526
527 Returns success status, not logged.
528 """
529 fRc = False;
530
531 asArgs = [];
532 sOsType = sys.platform;
533
534 if sOsType == 'win32':
535 reporter.log('Disabling firewall on host (type: %s) ...' % (sOsType));
536
537 ## @todo For now we ASSUME that we don't run (and don't support even) on old(er)
538 # Windows hosts than Vista.
539 asArgs = self.getWinFirewallArgsDisable('vista');
540 if asArgs:
541 fRc, _ = self.executeHst('Disabling host firewall', asArgs, fAsAdmin = True);
542 else:
543 reporter.log('Firewall not available on host, skipping');
544 fRc = True; # Not available, just skip.
545
546 return fRc;
547
548 def getLastRcFromTxs(self, oTxsSession):
549 """
550 Extracts the last exit code reported by TXS from a run before.
551 Assumes that nothing else has been run on the same TXS session in the meantime.
552 """
553 iRc = 0;
554 (_, sOpcode, abPayload) = oTxsSession.getLastReply();
555 if sOpcode.startswith('PROC NOK '): # Extract process rc
556 iRc = abPayload[0]; # ASSUMES 8-bit rc for now.
557 return iRc;
558
559 def startVkatOnGuest(self, oTestVm, oSession, oTxsSession, sTag):
560 """
561 Starts VKAT on the guest (running in background).
562 """
563 sPathTemp = self.getGuestTempDir(oTestVm);
564 sPathAudioOut = oTestVm.pathJoin(sPathTemp, 'vkat-guest-out');
565 sPathAudioTemp = oTestVm.pathJoin(sPathTemp, 'vkat-guest-temp');
566
567 reporter.log('Guest audio test temp path is \"%s\"' % (sPathAudioOut));
568 reporter.log('Guest audio test output path is \"%s\"' % (sPathAudioTemp));
569 reporter.log('Guest audio test tag is \"%s\"' % (sTag));
570
571 if self.oDebug.sVkatExeGst is None:
572 fRc, sVkatExe = self.locateGstBinary(oSession, oTxsSession, self.asGstVkatPaths);
573 else:
574 sVkatExe = self.oDebug.sVkatExeGst;
575 fRc = True;
576 if fRc:
577 reporter.log('Using VKAT on guest at \"%s\"' % (sVkatExe));
578
579 sCmd = '';
580 asArgs = [];
581
582 asArgsVkat = [ sVkatExe, 'test', '--mode', 'guest', '--probe-backends', \
583 '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, \
584 '--tag', sTag ];
585
586 asArgs.extend(asArgsVkat);
587
588 for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1.
589 asArgs.extend([ '-v' ]);
590
591 # Needed for NATed VMs.
592 asArgs.extend(['--tcp-connect-addr', '10.0.2.2' ]);
593
594 if oTestVm.sKind in 'Oracle_64':
595 #
596 # Some Linux distros have a bug / are configured (?) so that processes started by init system
597 # cannot access the PulseAudio server ("Connection refused"), for example OL 8.1.
598 #
599 # To work around this, we use the (hopefully) configured user "vbox" and run it under its behalf,
600 # as the Test Execution Service (TxS) currently does not implement impersonation yet.
601 #
602 asSU = [ '/bin/su',
603 '/usr/bin/su',
604 '/usr/local/bin/su' ];
605 fRc, sCmd = self.locateGstBinary(oSession, oTxsSession, asSU);
606 if fRc:
607 sCmdArgs = '';
608 for sArg in asArgs:
609 sCmdArgs += sArg + " ";
610 asArgs = [ sCmd, oTestVm.getTestUser(), '-c', sCmdArgs ];
611 else:
612 reporter.log('Unable to find SU on guest, falling back to regular starting ...')
613
614 if not sCmd: # Just start it with the same privileges as TxS.
615 sCmd = sVkatExe;
616
617 reporter.log2('startVkatOnGuest: sCmd=%s' % (sCmd,));
618 reporter.log2('startVkatOnGuest: asArgs=%s' % (asArgs,));
619
620 #
621 # Add own environment stuff.
622 #
623 asEnv = [];
624
625 # Write the log file to some deterministic place so TxS can retrieve it later.
626 sVkatLogFile = 'VKAT_RELEASE_LOG_DEST=file=' + self.getGstVkatLogFilePath(oTestVm);
627 asEnv.extend([ sVkatLogFile ]);
628
629 #
630 # Execute asynchronously on the guest.
631 #
632 fRc = oTxsSession.asyncExec(sCmd, asArgs, asEnv, cMsTimeout = 15 * 60 * 1000, sPrefix = '[VKAT Guest] ');
633 if fRc:
634 self.addTask(oTxsSession);
635
636 if not fRc:
637 reporter.error('VKAT on guest returned exit code error %d' % (self.getLastRcFromTxs(oTxsSession)));
638 else:
639 reporter.error('VKAT on guest not found');
640
641 return fRc;
642
643 def runTests(self, oTestVm, oSession, oTxsSession, sDesc, sTag, asTests):
644 """
645 Runs one or more tests using VKAT on the host, which in turn will
646 communicate with VKAT running on the guest and the Validation Kit
647 audio driver ATS (Audio Testing Service).
648 """
649 _ = oTestVm, oSession, oTxsSession;
650
651 sPathTemp = self.sScratchPath;
652 sPathAudioOut = os.path.join(sPathTemp, 'vkat-host-out-%s' % (sTag));
653 sPathAudioTemp = os.path.join(sPathTemp, 'vkat-host-temp-%s' % (sTag));
654
655 reporter.log('Host audio test temp path is \"%s\"' % (sPathAudioOut));
656 reporter.log('Host audio test output path is \"%s\"' % (sPathAudioTemp));
657 reporter.log('Host audio test tag is \"%s\"' % (sTag));
658
659 sVkatExe = self.getBinTool('vkat');
660
661 reporter.log('Using VKAT on host at: \"%s\"' % (sVkatExe));
662
663 reporter.testStart(sDesc);
664
665 # Build the base command line, exclude all tests by default.
666 asArgs = [ sVkatExe, 'test', '--mode', 'host', '--probe-backends',
667 '--tempdir', sPathAudioTemp, '--outdir', sPathAudioOut, '-a',
668 '--tag', sTag,
669 '--no-audio-ok', # Enables running on hosts which do not have any audio hardware.
670 '--no-verify' ]; # We do the verification separately in the step below.
671
672 for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1.
673 asArgs.extend([ '-v' ]);
674
675 if self.asVkatTestArgs:
676 asArgs.extend(self.asVkatTestArgs);
677
678 # ... and extend it with wanted tests.
679 asArgs.extend(asTests);
680
681 #
682 # Let VKAT on the host run synchronously.
683 #
684 fRc, _ = self.executeHst("VKAT Host", asArgs, iExpectedRc = 0);
685
686 reporter.testDone();
687
688 if fRc:
689 #
690 # When running the test(s) above were successful, do the verification step next.
691 # This gives us a bit more fine-grained test results in the test manager.
692 #
693 reporter.testStart('Verifying audio data');
694
695 sNameSetHst = '%s-host.tar.gz' % (sTag);
696 sPathSetHst = os.path.join(sPathAudioOut, sNameSetHst);
697 sNameSetGst = '%s-guest.tar.gz' % (sTag);
698 sPathSetGst = os.path.join(sPathAudioOut, sNameSetGst);
699
700 asArgs = [ sVkatExe, 'verify', sPathSetHst, sPathSetGst ];
701
702 for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1.
703 asArgs.extend([ '-v' ]);
704
705 if self.asVkatVerifyArgs:
706 asArgs += self.asVkatVerifyArgs;
707
708 fRc, _ = self.executeHst("VKAT Host Verify", asArgs, iExpectedRc = 0);
709 if fRc:
710 reporter.log("Verification audio data successful");
711 else:
712 #
713 # Add the test sets to the test manager for later (manual) diagnosis.
714 #
715 reporter.addLogFile(sPathSetGst, 'misc/other', 'Guest audio test set');
716 reporter.addLogFile(sPathSetHst, 'misc/other', 'Host audio test set');
717
718 reporter.error("Verification of audio data failed");
719
720 reporter.testDone();
721
722 return fRc;
723
724 def doTest(self, oTestVm, oSession, oTxsSession):
725 """
726 Executes the specified audio tests.
727 """
728
729 # Disable any OS-specific firewalls preventing VKAT / ATS to run.
730 fRc = self.disableHstFirewall();
731 fRc = self.disableGstFirewall(oTestVm, oTxsSession) and fRc;
732
733 if not fRc:
734 return False;
735
736 reporter.log("Active tests: %s" % (self.asTests,));
737
738 # Define a tag for the whole run.
739 sTag = oTestVm.sVmName + "_" + datetime.now().strftime("%Y%m%d_%H%M%S");
740
741 fRc = self.startVkatOnGuest(oTestVm, oSession, oTxsSession, sTag);
742 if fRc:
743 #
744 # Execute the tests using VKAT on the guest side (in guest mode).
745 #
746 if "guest_tone_playback" in self.asTests:
747 fRc = self.runTests(oTestVm, oSession, oTxsSession, \
748 'Guest audio playback', sTag + "_test_playback", \
749 asTests = [ '-i0' ]);
750 if "guest_tone_recording" in self.asTests:
751 fRc = fRc and self.runTests(oTestVm, oSession, oTxsSession, \
752 'Guest audio recording', sTag + "_test_recording", \
753 asTests = [ '-i1' ]);
754
755 # Cancel guest VKAT execution task summoned by startVkatOnGuest().
756 oTxsSession.cancelTask();
757
758 #
759 # Retrieve log files for diagnosis.
760 #
761 self.txsDownloadFiles(oSession, oTxsSession,
762 [ ( self.getGstVkatLogFilePath(oTestVm),
763 'vkat-guest-%s.log' % (oTestVm.sVmName,),),
764 ],
765 fIgnoreErrors = True);
766
767 # A bit of diagnosis on error.
768 ## @todo Remove this later when stuff runs stable.
769 if not fRc:
770 reporter.log('Kernel messages:');
771 sCmdDmesg = oTestVm.pathJoin(self.getGuestSystemDir(oTestVm), 'dmesg');
772 oTxsSession.syncExec(sCmdDmesg, (sCmdDmesg), fIgnoreErrors = True);
773 reporter.log('Loaded kernel modules:');
774 sCmdLsMod = oTestVm.pathJoin(self.getGuestSystemAdminDir(oTestVm), 'lsmod');
775 oTxsSession.syncExec(sCmdLsMod, (sCmdLsMod), fIgnoreErrors = True);
776
777 return fRc;
778
779 def testOneVmConfig(self, oVM, oTestVm):
780 """
781 Runs tests using one specific VM config.
782 """
783
784 reporter.testStart("Audio Testing");
785
786 fSkip = False;
787
788 if oTestVm.isWindows() \
789 and oTestVm.sKind in ('WindowsNT4', 'Windows2000'): # Too old for DirectSound and WASAPI backends.
790 reporter.log('Audio testing skipped, not implemented/available for that OS yet.');
791 fSkip = True;
792
793 if not fSkip \
794 and self.fpApiVer < 7.0:
795 reporter.log('Audio testing not available for this branch, skipping.');
796 fSkip = True;
797
798 if fSkip:
799 reporter.testDone(fSkipped = True);
800 return True;
801
802 reporter.log('Verbosity level is: %d' % (reporter.getVerbosity(),));
803
804 sVkatExe = self.getBinTool('vkat');
805
806 #
807 # Probe the backends on the host.
808 #
809 reporter.testStart('VKAT Probing');
810 asArgs = [ sVkatExe, 'enum', '--probe-backends' ];
811 for _ in range(1, reporter.getVerbosity()): # Verbosity always is initialized at 1.
812 asArgs.extend([ '-v' ]);
813 _, iRc = self.executeHst("VKAT Host Audio Probing", asArgs);
814 if iRc != 0:
815 # Not fatal, as VBox then should fall back to the NULL audio backend (also worth having as a test case).
816 reporter.log('Warning: Backend probing on host failed, no audio available (pure server installation?)');
817 # Mark the whole VM test as being skipped.
818 fSkip = True;
819 reporter.testDone();
820
821 #
822 # Reconfigure the VM.
823 #
824 fRc = True;
825 oSession = self.openSession(oVM);
826 if oSession is not None:
827
828 cVerbosity = reporter.getVerbosity();
829 if cVerbosity >= 2: # Explicitly set verbosity via extra-data when >= level 2.
830 self.asOptExtraData.extend([ 'VBoxInternal2/Audio/Debug/Level:' + str(cVerbosity) ]);
831
832 # Set extra data.
833 for sExtraData in self.asOptExtraData:
834 sKey, sValue = sExtraData.split(':');
835 reporter.log('Set extradata: %s => %s' % (sKey, sValue));
836 fRc = oSession.setExtraData(sKey, sValue) and fRc;
837 if not fRc:
838 break;
839
840 # Make sure that the VM's audio adapter is configured the way we need it to.
841 if fRc \
842 and self.fpApiVer >= 4.0:
843 enmAudioControllerType = None;
844 reporter.log('Configuring audio controller type ...');
845 if self.sAudioControllerType is None:
846 oOsType = oSession.getOsType();
847 enmAudioControllerType = oOsType.recommendedAudioController;
848 else:
849 if self.sAudioControllerType == 'HDA':
850 enmAudioControllerType = vboxcon.AudioControllerType_HDA;
851 elif self.sAudioControllerType == 'AC97':
852 enmAudioControllerType = vboxcon.AudioControllerType_AC97;
853 elif self.sAudioControllerType == 'SB16':
854 enmAudioControllerType = vboxcon.AudioControllerType_SB16;
855 assert enmAudioControllerType is not None;
856
857 # For now we're encforcing to test the HDA emulation only, regardless of
858 # what the recommended audio controller type from above was.
859 ## @todo Make other emulations work as well.
860 fEncforceHDA = True;
861
862 if fEncforceHDA:
863 enmAudioControllerType = vboxcon.AudioControllerType_HDA;
864 reporter.log('Enforcing audio controller type to HDA');
865
866 fRc = fRc and oSession.setupAudio(enmAudioControllerType,
867 fEnable = True, fEnableIn = True, fEnableOut = True);
868
869 # Save the settings.
870 fRc = fRc and oSession.saveSettings();
871 fRc = oSession.close() and fRc;
872 oSession = None;
873
874 self.logVmInfo(oVM);
875
876 else:
877 fRc = False;
878
879 if fRc:
880 reporter.testStart('Waiting for TXS');
881 oSession, oTxsSession = self.startVmAndConnectToTxsViaTcp(oTestVm.sVmName,
882 fCdWait = True,
883 cMsTimeout = 3 * 60 * 1000,
884 sFileCdWait = '${OS/ARCH}/vkat${EXESUFF}');
885 reporter.testDone();
886
887 if oSession is not None:
888
889 self.prepareGuestForDebugging(oSession, oTxsSession, oTestVm);
890
891 reporter.log('Waiting for any OS startup sounds getting played (to skip those) ...');
892 time.sleep(5);
893
894 self.addTask(oTxsSession);
895
896 fRc = self.doTest(oTestVm, oSession, oTxsSession);
897
898 # Cleanup.
899 self.removeTask(oTxsSession);
900 self.terminateVmBySession(oSession);
901
902 # Report an error in case we forgot it.
903 if not fRc:
904 reporter.error('Audio testing failed!');
905
906 reporter.testDone();
907 return fRc;
908
909 def onExit(self, iRc):
910 """
911 Exit handler for this test driver.
912 """
913 return vbox.TestDriver.onExit(self, iRc);
914
915if __name__ == '__main__':
916 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