Put duplicated device-getting code into a loop

cfm_helper.extract_peripherals() contains a bunch of logic that gets
repeated for each type of device (cameras, speakers, etc).
Safer to keep it in a loop.

BUG=None
TEST=cfm_helper_unittest

Change-Id: I23a9abe87a7f47f19c9e44c651795f21426287fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1936955
Tested-by: Greg Edelston <gredelston@google.com>
Reviewed-by: Ryan Snyder <turg@google.com>
Reviewed-by: Denis Tosic <dtosic@google.com>
Commit-Queue: Greg Edelston <gredelston@google.com>
diff --git a/client/common_lib/cros/manual/cfm_helper.py b/client/common_lib/cros/manual/cfm_helper.py
index 270fbe6..cdda7b0 100644
--- a/client/common_lib/cros/manual/cfm_helper.py
+++ b/client/common_lib/cros/manual/cfm_helper.py
@@ -9,6 +9,7 @@
 import logging
 import re
 import time
+import common
 from autotest_lib.client.common_lib.cros.manual import get_usb_devices
 from autotest_lib.client.common_lib.cros import power_cycle_usb_util
 
@@ -102,26 +103,14 @@
     @param usb_data: dict extracted from output of "usb-devices"
     """
     peripheral_map = {}
-
-    speaker_list = get_usb_devices.get_speakers(usb_data)
-    camera_list = get_usb_devices.get_cameras(usb_data)
-    display_list = get_usb_devices.get_display_mimo(usb_data)
-    controller_list = get_usb_devices.get_controller_mimo(usb_data)
-    for pid_vid, device_count in speaker_list.iteritems():
-        if device_count > 0:
-            peripheral_map[pid_vid] = device_count
-
-    for pid_vid, device_count in camera_list.iteritems():
-        if device_count > 0:
-            peripheral_map[pid_vid] = device_count
-
-    for pid_vid, device_count in controller_list.iteritems():
-        if device_count > 0:
-            peripheral_map[pid_vid] = device_count
-
-    for pid_vid, device_count in display_list.iteritems():
-        if device_count > 0:
-            peripheral_map[pid_vid] = device_count
+    get_devices_funcs = (get_usb_devices.get_speakers,
+            get_usb_devices.get_cameras, get_usb_devices.get_display_mimo,
+            get_usb_devices.get_controller_mimo)
+    for get_devices in get_devices_funcs:
+        device_list = get_devices(usb_data)
+        for pid_vid, device_count in device_list.iteritems():
+            if device_count > 0:
+                peripheral_map[pid_vid] = device_count
 
     for pid_vid, device_count in peripheral_map.iteritems():
         logging.info('---device: %s (%s), count: %d',
diff --git a/client/common_lib/cros/manual/cfm_helper_unittest.py b/client/common_lib/cros/manual/cfm_helper_unittest.py
new file mode 100644
index 0000000..148bc04
--- /dev/null
+++ b/client/common_lib/cros/manual/cfm_helper_unittest.py
@@ -0,0 +1,64 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file
+"""Tests for cfm_helper.py."""
+
+import unittest
+
+import cfm_helper
+import get_usb_devices
+
+SPEAKERS = 'speakers'
+CAMERAS = 'cameras'
+DISPLAY_MIMO = 'display_mimo'
+CONTROLLER_MIMO = 'controller_mimo'
+DEVICE_TYPES = (SPEAKERS, CAMERAS, DISPLAY_MIMO, CONTROLLER_MIMO)
+
+
+class TestExtractPeripherals(unittest.TestCase):
+    """Test cfm_helper.extract_peripherals()"""
+
+    def create_mock_device_getter(self, device_list):
+        """Mock a function to take usb_data and return device_list"""
+
+        def mock_device_getter(usb_data):
+            """Return the specified device_list, ignoring usb_data."""
+            return device_list
+
+        return mock_device_getter
+
+    def setUp(self):
+        """
+        Mock the various get_devices functions so that each one returns a
+        key-value pair.
+        In extract_peripherals(), the key represents the pid_vid, and the value
+        represents the device_count.
+        For these testing purposes, we use the device_type ('cameras', etc.)
+        as the key, and a number 1-4 as the device_count.
+        (If we used a device_count of 0 it would be ignored; hence, 1-4.)
+
+        """
+        self.original_funcs = {}
+        for i in range(len(DEVICE_TYPES)):
+            device_type = DEVICE_TYPES[i]
+            mock = self.create_mock_device_getter({device_type: i + 1})
+            setattr(cfm_helper.get_usb_devices, 'get_%s' % device_type, mock)
+
+    def runTest(self):
+        """Verify that all 4 peripheral-types are extracted."""
+        peripherals = cfm_helper.extract_peripherals_for_cfm(None)
+        self.assertEqual(len(peripherals), 4)
+        for i in range(len(DEVICE_TYPES)):
+            device_type = DEVICE_TYPES[i]
+            self.assertEqual(peripherals[device_type], i + 1)
+
+    def tearDown(self):
+        """Restore the original functions, for the sake of other tests."""
+        for device_type in DEVICE_TYPES:
+            original_func = getattr(get_usb_devices, 'get_%s' % device_type)
+            setattr(cfm_helper.get_usb_devices, 'get_%s' % device_type,
+                    original_func)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/client/common_lib/cros/manual/common.py b/client/common_lib/cros/manual/common.py
new file mode 100644
index 0000000..849be4d
--- /dev/null
+++ b/client/common_lib/cros/manual/common.py
@@ -0,0 +1,8 @@
+import os, sys
+dirname = os.path.dirname(sys.modules[__name__].__file__)
+client_dir = os.path.abspath(os.path.join(dirname, "..", "..", ".."))
+sys.path.insert(0, client_dir)
+import setup_modules
+sys.path.pop(0)
+setup_modules.setup(base_path=client_dir,
+                    root_module_name="autotest_lib.client")