Snap for 4628106 from 987bee56f1307432b3c391d3e51fd2558e6af4ca to pi-release

Change-Id: I4ba05c39ba34613054cd402fe2c80e5486070613
diff --git a/client/cros/input_playback/keyboard_escape b/client/cros/input_playback/keyboard_escape
new file mode 100644
index 0000000..1a5718f
--- /dev/null
+++ b/client/cros/input_playback/keyboard_escape
@@ -0,0 +1,4 @@
+E: 1518480501.798258 0001 0001 1
+E: 1518480501.798258 0000 0000 0
+E: 1518480501.945122 0001 0001 0
+E: 1518480501.945122 0000 0000 0
diff --git a/client/cros/power/power_suspend.py b/client/cros/power/power_suspend.py
index 0ebb3f9..675045c 100644
--- a/client/cros/power/power_suspend.py
+++ b/client/cros/power/power_suspend.py
@@ -237,24 +237,21 @@
         raise error.TestError('Could not find %s entry.' % name)
 
 
-    def _hwclock_ts(self, not_before, retries=3):
+    def _hwclock_ts(self, not_before):
         """Read the RTC resume timestamp saved by powerd_suspend."""
-        for retry in xrange(retries + 1):
-            early_wakeup = False
-            if os.path.exists(self.HWCLOCK_FILE):
-                # TODO(crbug.com/733773): Still fragile see bug.
-                match = re.search(r'(.+)(\.\d+)[+-]\d+:?\d+$',
-                                  utils.read_file(self.HWCLOCK_FILE), re.DOTALL)
-                if match:
-                    timeval = time.strptime(match.group(1),
-                                            "%Y-%m-%d %H:%M:%S")
-                    seconds = time.mktime(timeval)
-                    seconds += float(match.group(2))
-                    logging.debug('RTC resume timestamp read: %f', seconds)
-                    if seconds >= not_before:
-                        return seconds
-                    early_wakeup = True
-            time.sleep(0.05 * retry)
+        early_wakeup = False
+        if os.path.exists(self.HWCLOCK_FILE):
+            # TODO(crbug.com/733773): Still fragile see bug.
+            match = re.search(r'(.+)(\.\d+)[+-]\d+:?\d+$',
+                              utils.read_file(self.HWCLOCK_FILE), re.DOTALL)
+            if match:
+                timeval = time.strptime(match.group(1), "%Y-%m-%d %H:%M:%S")
+                seconds = time.mktime(timeval)
+                seconds += float(match.group(2))
+                logging.debug('RTC resume timestamp read: %f', seconds)
+                if seconds >= not_before:
+                    return seconds
+                early_wakeup = True
         if early_wakeup:
             logging.debug('Early wakeup, dumping eventlog if it exists:\n')
             elog = utils.system_output('mosys eventlog list | tail -n %d' %
@@ -509,6 +506,8 @@
             iteration = len(self.failures) + len(self.successes) + 1
             # Retry suspend in case we hit a known (whitelisted) bug
             for _ in xrange(10):
+                # Clear powerd_suspend RTC timestamp, to avoid stale results.
+                utils.open_write_close(self.HWCLOCK_FILE, '')
                 self._reset_logs()
                 utils.system('sync')
                 board_delay = self._SUSPEND_DELAY.get(self._get_board(),
diff --git a/client/site_tests/login_GuestAndActualSession/login_GuestAndActualSession.py b/client/site_tests/login_GuestAndActualSession/login_GuestAndActualSession.py
index bf6a54e..25f1fef 100644
--- a/client/site_tests/login_GuestAndActualSession/login_GuestAndActualSession.py
+++ b/client/site_tests/login_GuestAndActualSession/login_GuestAndActualSession.py
@@ -34,16 +34,13 @@
                 gobject.MainLoop())
         self._listener.listen_for_new_key_and_policy()
 
-        self._cryptohome_proxy = cryptohome.CryptohomeProxy(bus_loop)
-
 
     def run_once(self):
         owner = '[email protected]'
 
-        # TODO(cmasone): enable CryptohomeProxy to do a guest mount, then use.
         cryptohome.mount_guest()
         self._session_manager.StartSession(constants.GUEST_USER, '')
-        self._cryptohome_proxy.ensure_clean_cryptohome_for(owner)
+        cryptohome.ensure_clean_cryptohome_for(owner)
         self._session_manager.StartSession(owner, '')
         self._listener.wait_for_signals(desc='Device ownership complete.')
 
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index a8b4302..b4e1733 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -1773,9 +1773,43 @@
     @returns List of hostnames that all have the same host attribute and
              value.
     """
-    hosts = models.HostAttribute.query_objects({'attribute': attribute,
-                                                'value': value})
-    return [row.host.hostname for row in hosts if row.host.invalid == 0]
+    rows = models.HostAttribute.query_objects({'attribute': attribute,
+                                               'value': value})
+    if RESPECT_STATIC_ATTRIBUTES:
+        returned_hosts = set()
+        # Add hosts:
+        #     * Non-valid
+        #     * Exist in afe_host_attribute with given attribute.
+        #     * Don't exist in afe_static_host_attribute OR exist in
+        #       afe_static_host_attribute with the same given value.
+        for row in rows:
+            if row.host.invalid != 0:
+                continue
+
+            static_hosts = models.StaticHostAttribute.query_objects(
+                {'host_id': row.host.id, 'attribute': attribute})
+            values = [static_host.value for static_host in static_hosts]
+            if len(values) == 0 or values[0] == value:
+                returned_hosts.add(row.host.hostname)
+
+        # Add hosts:
+        #     * Non-valid
+        #     * Exist in afe_static_host_attribute with given attribute
+        #       and value
+        #     * No need to check whether each static attribute has its
+        #       corresponding entry in afe_host_attribute since it is ensured
+        #       in inventory sync.
+        static_rows = models.StaticHostAttribute.query_objects(
+                {'attribute': attribute, 'value': value})
+        for row in static_rows:
+            if row.host.invalid != 0:
+                continue
+
+            returned_hosts.add(row.host.hostname)
+
+        return list(returned_hosts)
+    else:
+        return [row.host.hostname for row in rows if row.host.invalid == 0]
 
 
 def canonicalize_suite_name(suite_name):
diff --git a/frontend/afe/rpc_interface_unittest.py b/frontend/afe/rpc_interface_unittest.py
index 017b122..bf31ccb 100755
--- a/frontend/afe/rpc_interface_unittest.py
+++ b/frontend/afe/rpc_interface_unittest.py
@@ -307,6 +307,48 @@
                            'test_attribute2': 'test_value2',
                            'static_attribute1': 'static_value2'})
 
+    def test_get_hosts_by_attribute_without_static(self):
+        host1 = models.Host.objects.create(hostname='test_host1')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        host2 = models.Host.objects.create(hostname='test_host2')
+        host2.set_attribute('test_attribute1', 'test_value1')
+
+        hosts = rpc_interface.get_hosts_by_attribute(
+                'test_attribute1', 'test_value1')
+        self.assertEquals(set(hosts),
+                          set(['test_host1', 'test_host2']))
+
+
+    def test_get_hosts_by_attribute_with_static(self):
+        host1 = models.Host.objects.create(hostname='test_host1')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        self._set_static_attribute(host1, 'test_attribute1', 'test_value1')
+        host2 = models.Host.objects.create(hostname='test_host2')
+        host2.set_attribute('test_attribute1', 'test_value1')
+        self._set_static_attribute(host2, 'test_attribute1', 'static_value1')
+        host3 = models.Host.objects.create(hostname='test_host3')
+        self._set_static_attribute(host3, 'test_attribute1', 'test_value1')
+        host4 = models.Host.objects.create(hostname='test_host4')
+        host4.set_attribute('test_attribute1', 'test_value1')
+        host5 = models.Host.objects.create(hostname='test_host5')
+        host5.set_attribute('test_attribute1', 'temp_value1')
+        self._set_static_attribute(host5, 'test_attribute1', 'test_value1')
+
+        hosts = rpc_interface.get_hosts_by_attribute(
+                'test_attribute1', 'test_value1')
+        # host1: matched, it has the same value for test_attribute1.
+        # host2: not matched, it has a new value in
+        #        afe_static_host_attributes for test_attribute1.
+        # host3: matched, it has a corresponding entry in
+        #        afe_host_attributes for test_attribute1.
+        # host4: matched, test_attribute1 is not replaced by static
+        #        attribute.
+        # host5: matched, it has an updated & matched value for
+        #        test_attribute1 in afe_static_host_attributes.
+        self.assertEquals(set(hosts),
+                          set(['test_host1', 'test_host3',
+                               'test_host4', 'test_host5']))
+
 
 class RpcInterfaceTestWithStaticLabel(ShardHeartbeatTest,
                                       frontend_test_utils.FrontendTestMixin):
@@ -690,6 +732,18 @@
         self.assertEquals(rpc_interface.ping_db(), [True])
 
 
+    def test_get_hosts_by_attribute(self):
+        host1 = models.Host.objects.create(hostname='test_host1')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        host2 = models.Host.objects.create(hostname='test_host2')
+        host2.set_attribute('test_attribute1', 'test_value1')
+
+        hosts = rpc_interface.get_hosts_by_attribute(
+                'test_attribute1', 'test_value1')
+        self.assertEquals(set(hosts),
+                          set(['test_host1', 'test_host2']))
+
+
     def test_get_hosts(self):
         hosts = rpc_interface.get_hosts()
         self._check_hostnames(hosts, [host.hostname for host in self.hosts])
diff --git a/server/site_tests/firmware_Cr50BID/firmware_Cr50BID.py b/server/site_tests/firmware_Cr50BID/firmware_Cr50BID.py
index 5c96701..4b51db6 100644
--- a/server/site_tests/firmware_Cr50BID/firmware_Cr50BID.py
+++ b/server/site_tests/firmware_Cr50BID/firmware_Cr50BID.py
@@ -281,11 +281,19 @@
             universal_rw_ver: The rw version string of the universal image
             path: The path of the image that may need to be replaced.
         """
-        dut_ver = cr50_utils.GetBinVersion(self.host, path)[1]
-        # If the universal version is lower than the DUT image, install the
-        # universal image. It has the lowest version of any image in the test,
-        # so cr50-update won't try to update cr50 at any point during the test.
-        if cr50_utils.GetNewestVersion(dut_ver, universal_rw_ver) == dut_ver:
+        if self.host.path_exists(path):
+            dut_ver = cr50_utils.GetBinVersion(self.host, path)[1]
+            # If the universal version is lower than the DUT image, install the
+            # universal image. It has the lowest version of any image in the
+            # test, so cr50-update won't try to update cr50 at any point during
+            # the test.
+            install_image = (cr50_utils.GetNewestVersion(dut_ver,
+                    universal_rw_ver) == dut_ver)
+        else:
+            # If the DUT doesn't have a file at path, install the image.
+            install_image = True
+
+        if install_image:
             # Disable rootfs verification so we can copy the image to the DUT
             self.rootfs_verification_disable()
             # Copy the universal image onto the DUT.
diff --git a/server/site_tests/firmware_Cr50Open/control b/server/site_tests/firmware_Cr50Open/control
index d5181be..5d6fe0d 100644
--- a/server/site_tests/firmware_Cr50Open/control
+++ b/server/site_tests/firmware_Cr50Open/control
@@ -26,8 +26,9 @@
     host = hosts.create_host(machine, servo_args=servo_args)
 
     iterations = int(args_dict.get("iterations", 1))
+    ccd_lockout = args_dict.get("ccd_lockout", "false").lower() == "true"
 
     job.run_test("firmware_Cr50Open", host=host, cmdline_args=args,
-                 iterations=iterations)
+                 ccd_lockout=ccd_lockout, iterations=iterations)
 
 parallel_simple(run, machines)
diff --git a/server/site_tests/firmware_Cr50Open/firmware_Cr50Open.py b/server/site_tests/firmware_Cr50Open/firmware_Cr50Open.py
index a3faa91..3e8d42d 100644
--- a/server/site_tests/firmware_Cr50Open/firmware_Cr50Open.py
+++ b/server/site_tests/firmware_Cr50Open/firmware_Cr50Open.py
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import logging
 
 from autotest_lib.client.common_lib import error
 from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
@@ -29,8 +30,24 @@
             raise error.TestNAError('Cannot test on Cr50 with old CCD version')
 
 
-    def run_once(self):
+    def run_once(self, ccd_lockout):
         """Lock CCD and then Open it."""
         self.cr50.set_ccd_level('lock')
-        self.cr50.set_ccd_level('open')
+        try:
+            self.cr50.set_ccd_level('open')
+            success = True
+        except error.TestFail, e:
+            logging.debug(e)
+            if 'Access Denied' in e.message:
+                success = False
+            else:
+                raise
+
+        ccd_status_str =  'locked out' if ccd_lockout else 'accessible'
+        # Make sure we only got 'Access Denied' when ccd is locked out and open
+        # was successful only when ccd is accessible.
+        if success == ccd_lockout:
+            raise error.TestFail('ccd open %sed with ccd %s' % ('succeed'
+                    if success else 'fail', ccd_status_str))
+        logging.info('ccd open is %s', ccd_status_str)
 
diff --git a/server/site_tests/firmware_Cr50RejectUpdate/firmware_Cr50RejectUpdate.py b/server/site_tests/firmware_Cr50RejectUpdate/firmware_Cr50RejectUpdate.py
index 1ca33a3..378b6d38 100644
--- a/server/site_tests/firmware_Cr50RejectUpdate/firmware_Cr50RejectUpdate.py
+++ b/server/site_tests/firmware_Cr50RejectUpdate/firmware_Cr50RejectUpdate.py
@@ -65,20 +65,16 @@
         Raises:
             TestFail if there is an unexpected result.
         """
-
-        # Make sure trunskd isn't running
-        cr50_utils.StopTrunksd(self.host)
+        # Copy the image to the DUT
+        self.host.send_file(path, self.TEST_PATH)
 
         # Wait for cr50 to have been up for 60 seconds, so it won't
         # automatically reject the image.
         if wait:
             self.cr50.wait_until_update_is_allowed()
 
-        # Copy the image to the DUT
-        self.host.send_file(path, self.TEST_PATH)
-
         # Try to update
-        result = self.host.run('gsctool -s %s %s' % (arg, self.TEST_PATH),
+        result = self.host.run('gsctool -a %s %s' % (arg, self.TEST_PATH),
                 ignore_status=True, ignore_timeout=True, timeout=60)
 
         # Check the result
diff --git a/server/site_tests/firmware_Cr50Testlab/control b/server/site_tests/firmware_Cr50Testlab/control
index efdc1cd..6be813a 100644
--- a/server/site_tests/firmware_Cr50Testlab/control
+++ b/server/site_tests/firmware_Cr50Testlab/control
@@ -26,8 +26,9 @@
     host = hosts.create_host(machine, servo_args=servo_args)
 
     iterations = int(args_dict.get("iterations", 1))
+    ccd_lockout = args_dict.get("ccd_lockout", "false").lower() == "true"
 
     job.run_test("firmware_Cr50Testlab", host=host, cmdline_args=args,
-                 iterations=iterations)
+                 ccd_lockout=ccd_lockout, iterations=iterations)
 
 parallel_simple(run, machines)
diff --git a/server/site_tests/firmware_Cr50Testlab/firmware_Cr50Testlab.py b/server/site_tests/firmware_Cr50Testlab/firmware_Cr50Testlab.py
index 964ecb7..dc8bfbe 100644
--- a/server/site_tests/firmware_Cr50Testlab/firmware_Cr50Testlab.py
+++ b/server/site_tests/firmware_Cr50Testlab/firmware_Cr50Testlab.py
@@ -84,8 +84,19 @@
         self.check_reset_count()
 
 
-    def run_once(self):
+    def run_once(self, ccd_lockout):
         """Try to set testlab mode from different privilege levels."""
+        # ccd testlab can only be enabled after ccd is opened. This test wont
+        # do much if we can't open the device. firmware_Cr50Open should be
+        # enough to test ccd open capabilities. Do a basic test to make sure
+        # testlab mode can't be enabled while the device is locked, then raise
+        # test NA error.
+        if ccd_lockout:
+            self.cr50.set_ccd_level('lock')
+            self.try_testlab('on', err=self.ACCESS_DENIED)
+            raise error.TestNAError('Skipping firmware_Cr50Testlab when ccd is '
+                    'locked out.')
+
         # Dummy isn't a valid mode. Make sure it fails
         self.reset_ccd()
         self.try_testlab('dummy', err=self.INVALID_PARAM)
@@ -135,3 +146,4 @@
         self.cr50.set_ccd_level('lock')
         self.try_testlab('open', err=self.ACCESS_DENIED)
         self.check_reset_count()
+        logging.info('ccd testlab is accessbile')
diff --git a/server/site_tests/firmware_Cr50Unlock/control b/server/site_tests/firmware_Cr50Unlock/control
index 05b3fd4..c94e570 100644
--- a/server/site_tests/firmware_Cr50Unlock/control
+++ b/server/site_tests/firmware_Cr50Unlock/control
@@ -24,8 +24,9 @@
     host = hosts.create_host(machine, servo_args=servo_args)
 
     iterations = int(args_dict.get("iterations", 1))
+    ccd_lockout = args_dict.get("ccd_lockout", "false").lower() == "true"
 
     job.run_test("firmware_Cr50Unlock", host=host, cmdline_args=args,
-                 iterations=iterations)
+                 ccd_lockout=ccd_lockout, iterations=iterations)
 
 parallel_simple(run, machines)
diff --git a/server/site_tests/firmware_Cr50Unlock/firmware_Cr50Unlock.py b/server/site_tests/firmware_Cr50Unlock/firmware_Cr50Unlock.py
index 0139bc2..e29f524 100644
--- a/server/site_tests/firmware_Cr50Unlock/firmware_Cr50Unlock.py
+++ b/server/site_tests/firmware_Cr50Unlock/firmware_Cr50Unlock.py
@@ -34,7 +34,11 @@
 
     def gsctool_unlock(self, unlock_allowed):
         """Unlock cr50 using the gsctool command"""
-        self.host.run('gsctool -a -U')
+        result = self.host.run('gsctool -a -U',
+                ignore_status=not unlock_allowed)
+        if not unlock_allowed and (result.exit_status != 3 or
+            'Error: rv 7, response 7' not in result.stderr):
+            raise error.TestFail('unexpected lockout result %r', result)
         self.check_unlock(unlock_allowed)
 
 
@@ -83,11 +87,15 @@
         # no matter how it is being set.
 
 
-    def run_once(self):
+    def run_once(self, ccd_lockout):
         """Verify cr50 lock behavior on v1 images and v0 images"""
+        logging.info('ccd should %sbe locked out',
+                '' if ccd_lockout else 'not ')
         if self.cr50.has_command('ccdstate'):
-            self.unlock_test(self.gsctool_unlock, True)
+            self.unlock_test(self.gsctool_unlock, not ccd_lockout)
             self.unlock_test(self.console_unlock, False)
+            logging.info('ccd unlock is %s', 'locked out' if ccd_lockout else
+                    'accessible')
         else:
             # pre-v1, cr50 cannot be unlocked. Make sure that's true
             logging.info(self.cr50.send_command_get_output('lock disable',
diff --git a/server/site_tests/firmware_DevScreenTimeout/firmware_DevScreenTimeout.py b/server/site_tests/firmware_DevScreenTimeout/firmware_DevScreenTimeout.py
index 6b127bc..f04bbf4 100644
--- a/server/site_tests/firmware_DevScreenTimeout/firmware_DevScreenTimeout.py
+++ b/server/site_tests/firmware_DevScreenTimeout/firmware_DevScreenTimeout.py
@@ -97,6 +97,10 @@
                               'mainfw_type': 'developer',
                               }))
 
+        # To add an extra reboot before the measurement
+        # to avoid TPM reset too long
+        self.switcher.simple_reboot()
+        self.switcher.wait_for_client()
         logging.info("Reboot and press Ctrl-D repeatedly.")
         self.switcher.simple_reboot()
         self.ctrl_d_repeatedly()
diff --git a/server/site_tests/firmware_FWMPDisableCCD/control b/server/site_tests/firmware_FWMPDisableCCD/control
index 62124a4..95a6602 100644
--- a/server/site_tests/firmware_FWMPDisableCCD/control
+++ b/server/site_tests/firmware_FWMPDisableCCD/control
@@ -23,7 +23,9 @@
 
 def run(machine):
     host = hosts.create_host(machine, servo_args=servo_args)
+    ccd_lockout = args_dict.get("ccd_lockout", "false").lower() == "true"
 
-    job.run_test("firmware_FWMPDisableCCD", host=host, cmdline_args=args)
+    job.run_test("firmware_FWMPDisableCCD", host=host, cmdline_args=args,
+                 ccd_lockout=ccd_lockout)
 
 parallel_simple(run, machines)
diff --git a/server/site_tests/firmware_FWMPDisableCCD/firmware_FWMPDisableCCD.py b/server/site_tests/firmware_FWMPDisableCCD/firmware_FWMPDisableCCD.py
index 00fdf1a..d9c3178 100644
--- a/server/site_tests/firmware_FWMPDisableCCD/firmware_FWMPDisableCCD.py
+++ b/server/site_tests/firmware_FWMPDisableCCD/firmware_FWMPDisableCCD.py
@@ -19,7 +19,7 @@
     FWMP_DEV_DISABLE_CCD_UNLOCK = (1 << 6)
     GSCTOOL_ERR = 'Error: rv 7, response 7'
 
-    def initialize(self, host, cmdline_args):
+    def initialize(self, host, cmdline_args, ccd_lockout):
         """Initialize servo check if cr50 exists"""
         super(firmware_FWMPDisableCCD, self).initialize(host, cmdline_args)
 
@@ -27,7 +27,7 @@
         # Test CCD if servo has access to Cr50, is running with CCD v1, and has
         # testlab mode enabled.
         self.test_ccd_unlock = (hasattr(self, 'cr50') and
-            self.cr50.has_command('ccdstate'))
+            self.cr50.has_command('ccdstate') and not ccd_lockout)
 
         logging.info('%sTesting CCD Unlock', '' if self.test_ccd_unlock else
             'Not ')
@@ -122,7 +122,7 @@
         self.cr50_check_lock_control(flags)
 
 
-    def run_once(self):
+    def run_once(self, ccd_lockout):
         """Verify FWMP disable with different flag values"""
         self.check_fwmp('0xaa00', True)
         # Verify that the flags can be changed on the same boot
diff --git a/site_utils/lab_inventory_unittest.py b/site_utils/lab_inventory_unittest.py
index 311bcdd..487fa88 100755
--- a/site_utils/lab_inventory_unittest.py
+++ b/site_utils/lab_inventory_unittest.py
@@ -1075,7 +1075,9 @@
 class CommandParsingTests(unittest.TestCase):
     """Tests for command line argument parsing in `_parse_command()`."""
 
-    _NULL_NOTIFY = ['--model-notify=', '--pool-notify=']
+    # At least one of these options must be specified on every command
+    # line; otherwise, the command line parsing will fail.
+    _REPORT_OPTIONS = ['--model-notify=', '--pool-notify=', '--repair-loops']
 
     def setUp(self):
         dirpath = '/usr/local/fubar'
@@ -1085,17 +1087,22 @@
         self._logdir = os.path.join(dirpath, lab_inventory._LOGDIR)
 
 
-    def _parse_arguments(self, argv, notify=_NULL_NOTIFY):
-        full_argv = [self._command_path] + argv + notify
+    def _parse_arguments(self, argv):
+        """Test parsing with explictly passed report options."""
+        full_argv = [self._command_path] + argv
         return lab_inventory._parse_command(full_argv)
 
 
-    def _check_non_notify_defaults(self, notify_option):
-        arguments = self._parse_arguments([], notify=[notify_option])
+    def _parse_non_report_arguments(self, argv):
+        """Test parsing for non-report command-line options."""
+        return self._parse_arguments(argv + self._REPORT_OPTIONS)
+
+
+    def _check_non_report_defaults(self, report_option):
+        arguments = self._parse_arguments([report_option])
         self.assertEqual(arguments.duration,
                          lab_inventory._DEFAULT_DURATION)
         self.assertIsNone(arguments.recommend)
-        self.assertFalse(arguments.repair_loops)
         self.assertFalse(arguments.debug)
         self.assertEqual(arguments.logdir, self._logdir)
         self.assertEqual(arguments.modelnames, [])
@@ -1103,25 +1110,45 @@
 
 
     def test_empty_arguments(self):
-        """Test that an empty argument list is an error."""
-        arguments = self._parse_arguments([], notify=[])
+        """Test that no reports requested is an error."""
+        arguments = self._parse_arguments([])
         self.assertIsNone(arguments)
 
 
     def test_argument_defaults(self):
         """Test that option defaults match expectations."""
-        arguments = self._check_non_notify_defaults(self._NULL_NOTIFY[0])
+        for report in self._REPORT_OPTIONS:
+            arguments = self._check_non_report_defaults(report)
+
+
+    def test_model_notify_defaults(self):
+        """Test defaults when `--model-notify` is specified alone."""
+        arguments = self._parse_arguments(['--model-notify='])
         self.assertEqual(arguments.model_notify, [''])
         self.assertEqual(arguments.pool_notify, [])
-        arguments = self._check_non_notify_defaults(self._NULL_NOTIFY[1])
+        self.assertFalse(arguments.repair_loops)
+
+
+    def test_pool_notify_defaults(self):
+        """Test defaults when `--pool-notify` is specified alone."""
+        arguments = self._parse_arguments(['--pool-notify='])
         self.assertEqual(arguments.model_notify, [])
         self.assertEqual(arguments.pool_notify, [''])
+        self.assertFalse(arguments.repair_loops)
+
+
+    def test_repair_loop_defaults(self):
+        """Test defaults when `--repair-loops` is specified alone."""
+        arguments = self._parse_arguments(['--repair-loops'])
+        self.assertEqual(arguments.model_notify, [])
+        self.assertEqual(arguments.pool_notify, [])
+        self.assertTrue(arguments.repair_loops)
 
 
     def test_model_arguments(self):
         """Test that non-option arguments are returned in `modelnames`."""
         modellist = ['aardvark', 'echidna']
-        arguments = self._parse_arguments(modellist)
+        arguments = self._parse_non_report_arguments(modellist)
         self.assertEqual(arguments.modelnames, modellist)
 
 
@@ -1129,19 +1156,13 @@
         """Test parsing of the `--recommend` option."""
         for opt in ['-r', '--recommend']:
             for recommend in ['5', '55']:
-                arguments = self._parse_arguments([opt, recommend])
+                arguments = self._parse_non_report_arguments([opt, recommend])
                 self.assertEqual(arguments.recommend, int(recommend))
 
 
-    def test_repair_loop_option(self):
-        """Test parsing of the `--repair-loops` option."""
-        arguments = self._parse_arguments(['--repair-loops'], notify=[])
-        self.assertTrue(arguments.repair_loops)
-
-
     def test_debug_option(self):
         """Test parsing of the `--debug` option."""
-        arguments = self._parse_arguments(['--debug'])
+        arguments = self._parse_non_report_arguments(['--debug'])
         self.assertTrue(arguments.debug)
 
 
@@ -1149,7 +1170,7 @@
         """Test parsing of the `--duration` option."""
         for opt in ['-d', '--duration']:
             for duration in ['1', '11']:
-                arguments = self._parse_arguments([opt, duration])
+                arguments = self._parse_non_report_arguments([opt, duration])
                 self.assertEqual(arguments.duration, int(duration))
 
 
@@ -1171,19 +1192,17 @@
         """
         a1 = '[email protected]'
         a2 = '[email protected]'
-        arguments = self._parse_arguments([option, a1], notify=[])
+        arguments = self._parse_arguments([option, a1])
         self.assertEqual(getlist(arguments), [a1])
-        arguments = self._parse_arguments([option, ' ' + a1 + ' '],
-                                          notify=[])
+        arguments = self._parse_arguments([option, ' ' + a1 + ' '])
         self.assertEqual(getlist(arguments), [a1])
-        arguments = self._parse_arguments([option, a1, option, a2],
-                                          notify=[])
+        arguments = self._parse_arguments([option, a1, option, a2])
         self.assertEqual(getlist(arguments), [a1, a2])
         arguments = self._parse_arguments(
-                [option, ','.join([a1, a2])], notify=[])
+                [option, ','.join([a1, a2])])
         self.assertEqual(getlist(arguments), [a1, a2])
         arguments = self._parse_arguments(
-                [option, ', '.join([a1, a2])], notify=[])
+                [option, ', '.join([a1, a2])])
         self.assertEqual(getlist(arguments), [a1, a2])
 
 
@@ -1202,7 +1221,7 @@
     def test_logdir_option(self):
         """Test parsing of the `--logdir` option."""
         logdir = '/usr/local/whatsis/logs'
-        arguments = self._parse_arguments(['--logdir', logdir])
+        arguments = self._parse_non_report_arguments(['--logdir', logdir])
         self.assertEqual(arguments.logdir, logdir)
 
 
diff --git a/site_utils/stats/mysql_stats.py b/site_utils/stats/mysql_stats.py
index 3d02248..085272e 100755
--- a/site_utils/stats/mysql_stats.py
+++ b/site_utils/stats/mysql_stats.py
@@ -19,16 +19,10 @@
 import common
 
 from autotest_lib.client.common_lib import global_config
-from autotest_lib.client.common_lib import utils
 from autotest_lib.client.common_lib.cros import retry
 
-try:
-    from chromite.lib import metrics
-    from chromite.lib import ts_mon_config
-except ImportError:
-    metrics = utils.metrics_mock
-    ts_mon_config = utils.metrics_mock
-
+from chromite.lib import metrics
+from chromite.lib import ts_mon_config
 
 AT_DIR='/usr/local/autotest'
 DEFAULT_USER = global_config.global_config.get_config_value(