Support multiple connected Android devices in low bandwidth audio test

Previously it was assumed that exactly one device is connected.
Now adb will get an argument with the device ID taken from the runner
script's stdout.

BUG=webrtc:7229
TBR=kjellander@webrtc.org
NOTRY=true

Review-Url: https://codereview.webrtc.org/2783343003
Cr-Commit-Position: refs/heads/master@{#17580}
diff --git a/webrtc/audio/test/low_bandwidth_audio_test.py b/webrtc/audio/test/low_bandwidth_audio_test.py
index 58243b1..8f40681 100755
--- a/webrtc/audio/test/low_bandwidth_audio_test.py
+++ b/webrtc/audio/test/low_bandwidth_audio_test.py
@@ -68,16 +68,42 @@
   return pesq_path
 
 
-def _GetFile(file_path, out_dir, android=False, adb_path=None):
+def ExtractTestRuns(lines, echo=False):
+  """Extracts information about tests from the output of a test runner.
+
+  Produces tuples (android_device, test_name, reference_file, degraded_file).
+  """
+  for line in lines:
+    if echo:
+      sys.stdout.write(line)
+
+    # Output from Android has a prefix with the device name.
+    android_prefix_re = r'(?:I\b.+\brun_tests_on_device\((.+?)\)\s*)?'
+    test_re = r'^' + android_prefix_re + r'TEST (\w+) ([^ ]+?) ([^ ]+?)\s*$'
+
+    match = re.search(test_re, line)
+    if match:
+      yield match.groups()
+
+
+def _GetFile(file_path, out_dir, move=False,
+             android=False, adb_prefix=('adb',)):
   out_file_name = os.path.basename(file_path)
   out_file_path = os.path.join(out_dir, out_file_name)
 
   if android:
-    # Pull the file from the connected Android device
-    adb_command = [adb_path, 'pull', file_path, out_dir]
+    # Pull the file from the connected Android device.
+    adb_command = adb_prefix + ('pull', file_path, out_dir)
     subprocess.check_call(_LogCommand(adb_command))
+    if move:
+      # Remove that file.
+      adb_command = adb_prefix + ('shell', 'rm', file_path)
+      subprocess.check_call(_LogCommand(adb_command))
   elif os.path.abspath(file_path) != os.path.abspath(out_file_path):
-    shutil.copy(file_path, out_file_path)
+    if move:
+      shutil.move(file_path, out_file_path)
+    else:
+      shutil.copy(file_path, out_file_path)
 
   return out_file_path
 
@@ -101,46 +127,47 @@
   test_process = subprocess.Popen(_LogCommand(test_command),
                                   stdout=subprocess.PIPE)
 
-  for line in iter(test_process.stdout.readline, ''):
-    # Echo the output to screen.
-    sys.stdout.write(line)
+  try:
+    lines = iter(test_process.stdout.readline, '')
+    for result in ExtractTestRuns(lines, echo=True):
+      (android_device, test_name, reference_file, degraded_file) = result
 
-    # Extract specific lines that contain information about produced files.
-    # Output from Android has a prefix, need to skip it.
-    match = re.search(r'^(?:I\b.+\b)?TEST (\w+) ([^ ]+?) ([^ ]+?)\s*$', line)
-    if not match:
-      continue
-    test_name = match.group(1)
-    reference_file = _GetFile(match.group(2), out_dir,
-                              args.android, args.adb_path)
-    degraded_file = _GetFile(match.group(3), out_dir,
-                             args.android, args.adb_path)
+      adb_prefix = (args.adb_path,)
+      if android_device:
+        adb_prefix += ('-s', android_device)
 
-    # Analyze audio.
-    pesq_command = [pesq_path, '+16000',
-                    os.path.basename(reference_file),
-                    os.path.basename(degraded_file)]
-    # Need to provide paths in the current directory due to a bug in PESQ:
-    # On Mac, for some 'path/to/file.wav', if 'file.wav' is longer than
-    # 'path/to', PESQ crashes.
-    pesq_output = subprocess.check_output(_LogCommand(pesq_command),
-                                          cwd=out_dir)
+      reference_file = _GetFile(reference_file, out_dir,
+                                android=args.android, adb_prefix=adb_prefix)
+      degraded_file = _GetFile(degraded_file, out_dir, move=True,
+                               android=args.android, adb_prefix=adb_prefix)
 
-    # Find the scores in stdout of pesq.
-    match = re.search(
-        r'Prediction \(Raw MOS, MOS-LQO\):\s+=\s+([\d.]+)\s+([\d.]+)',
-        pesq_output)
-    if match:
-      raw_mos, _ = match.groups()
+      # Analyze audio.
+      pesq_command = [pesq_path, '+16000',
+                      os.path.basename(reference_file),
+                      os.path.basename(degraded_file)]
+      # Need to provide paths in the current directory due to a bug in PESQ:
+      # On Mac, for some 'path/to/file.wav', if 'file.wav' is longer than
+      # 'path/to', PESQ crashes.
+      pesq_output = subprocess.check_output(_LogCommand(pesq_command),
+                                            cwd=out_dir)
 
-      # Output a result for the perf dashboard.
-      print 'RESULT pesq_mos: %s= %s score' % (test_name, raw_mos)
-    else:
-      logging.error('PESQ: %s', pesq_output.splitlines()[-1])
+      # Find the scores in stdout of pesq.
+      match = re.search(
+          r'Prediction \(Raw MOS, MOS-LQO\):\s+=\s+([\d.]+)\s+([\d.]+)',
+          pesq_output)
+      if match:
+        raw_mos, _ = match.groups()
 
-    if args.remove:
-      os.remove(reference_file)
-      os.remove(degraded_file)
+        # Output a result for the perf dashboard.
+        print 'RESULT pesq_mos: %s= %s score' % (test_name, raw_mos)
+      else:
+        logging.error('PESQ: %s', pesq_output.splitlines()[-1])
+
+      if args.remove:
+        os.remove(reference_file)
+        os.remove(degraded_file)
+  finally:
+    test_process.terminate()
 
   return test_process.wait()