Rewrite logcat's create_output_excerpts to avoid dangling excerpts. (#658)

diff --git a/mobly/controllers/android_device_lib/services/logcat.py b/mobly/controllers/android_device_lib/services/logcat.py
index 2582750..0d3c94d 100644
--- a/mobly/controllers/android_device_lib/services/logcat.py
+++ b/mobly/controllers/android_device_lib/services/logcat.py
@@ -11,11 +11,10 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-import copy
 import io
 import logging
 import os
-import shutil
+import time
 
 from mobly import logger as mobly_logger
 from mobly import utils
@@ -23,6 +22,8 @@
 from mobly.controllers.android_device_lib import errors
 from mobly.controllers.android_device_lib.services import base_service
 
+CREATE_LOGCAT_FILE_TIMEOUT_SEC = 5
+
 
 class Error(errors.ServiceError):
     """Root error type for logcat service."""
@@ -62,6 +63,7 @@
         super(Logcat, self).__init__(android_device, configs)
         self._ad = android_device
         self._adb_logcat_process = None
+        self._adb_logcat_file_obj = None
         self.adb_logcat_file_path = None
         # Logcat service uses a single config obj, using singular internal
         # name: `_config`.
@@ -104,12 +106,12 @@
         .. deprecated:: 1.10
            Use :func:`create_output_excerpts` instead.
 
+        This copies logcat lines from self.adb_logcat_file_path to an excerpt
+        file, starting from the location where the previous excerpt ended.
+
         To use this feature, call this method at the end of: `setup_class`,
         `teardown_test`, and `teardown_class`.
 
-        This moves the current content of `self.adb_logcat_file_path` to the
-        log directory specific to the current test.
-
         Args:
             current_test_info: `self.current_test_info` in a Mobly test.
         """
@@ -118,8 +120,8 @@
     def create_output_excerpts(self, test_info):
         """Convenient method for creating excerpts of adb logcat.
 
-        This moves the current content of `self.adb_logcat_file_path` to the
-        log directory specific to the current test.
+        This copies logcat lines from self.adb_logcat_file_path to an excerpt
+        file, starting from the location where the previous excerpt ended.
 
         Call this method at the end of: `setup_class`, `teardown_test`, and
         `teardown_class`.
@@ -130,15 +132,19 @@
         Returns:
             List of strings, the absolute paths to excerpt files.
         """
-        self.pause()
         dest_path = test_info.output_path
         utils.create_dir(dest_path)
         filename = self._ad.generate_filename(self.OUTPUT_FILE_TYPE, test_info,
                                               'txt')
         excerpt_file_path = os.path.join(dest_path, filename)
-        shutil.move(self.adb_logcat_file_path, excerpt_file_path)
+        with io.open(excerpt_file_path, 'w', encoding='utf-8',
+                     errors='replace') as out:
+            while True:
+                line = self._adb_logcat_file_obj.readline()
+                if not line:
+                    break
+                out.write(line)
         self._ad.log.debug('logcat excerpt created at: %s', excerpt_file_path)
-        self.resume()
         return [excerpt_file_path]
 
     @property
@@ -239,6 +245,30 @@
                           self._config, new_config)
         self._config = new_config
 
+    def _open_logcat_file(self):
+        """Create a file object that points to the beginning of the logcat file.
+        Wait for the logcat file to be created by the subprocess if it doesn't
+        exist.
+        """
+        if not self._adb_logcat_file_obj:
+            start_time = time.time()
+            while not os.path.exists(self.adb_logcat_file_path):
+                if time.time() > start_time + CREATE_LOGCAT_FILE_TIMEOUT_SEC:
+                    raise Error(
+                        self._ad,
+                        'Timeout while waiting for logcat file to be created.')
+                time.sleep(1)
+            self._adb_logcat_file_obj = io.open(
+                self.adb_logcat_file_path, 'r', encoding='utf-8',
+                errors='replace')
+            self._adb_logcat_file_obj.seek(0, os.SEEK_END)
+
+    def _close_logcat_file(self):
+        """Closes and resets the logcat file object, if it exists."""
+        if self._adb_logcat_file_obj:
+            self._adb_logcat_file_obj.close()
+            self._adb_logcat_file_obj = None
+
     def start(self):
         """Starts a standing adb logcat collection.
 
@@ -248,25 +278,33 @@
         if self._config.clear_log:
             self.clear_adb_log()
         self._start()
+        self._open_logcat_file()
 
     def _start(self):
         """The actual logic of starting logcat."""
         self._enable_logpersist()
-        logcat_file_path = self._config.output_file_path
-        if not logcat_file_path:
+        if self._config.output_file_path:
+            self._close_logcat_file()
+            self.adb_logcat_file_path = self._config.output_file_path
+        if not self.adb_logcat_file_path:
             f_name = self._ad.generate_filename(self.OUTPUT_FILE_TYPE,
                                                 extension_name='txt')
             logcat_file_path = os.path.join(self._ad.log_path, f_name)
-        utils.create_dir(os.path.dirname(logcat_file_path))
-        cmd = '"%s" -s %s logcat -v threadtime %s >> "%s"' % (
+            self.adb_logcat_file_path = logcat_file_path
+        utils.create_dir(os.path.dirname(self.adb_logcat_file_path))
+        cmd = '"%s" -s %s logcat -v threadtime -T 1 %s >> "%s"' % (
             adb.ADB, self._ad.serial, self._config.logcat_params,
-            logcat_file_path)
+            self.adb_logcat_file_path)
         process = utils.start_standing_subprocess(cmd, shell=True)
         self._adb_logcat_process = process
-        self.adb_logcat_file_path = logcat_file_path
 
     def stop(self):
         """Stops the adb logcat service."""
+        self._close_logcat_file()
+        self._stop()
+
+    def _stop(self):
+        """Stops the background process for logcat."""
         if not self._adb_logcat_process:
             return
         try:
@@ -281,17 +319,8 @@
         Note: the service is unable to collect the logs when paused, if more
         logs are generated on the device than the device's log buffer can hold,
         some logs would be lost.
-
-        Clears cached adb content, so that when the service resumes, we don't
-        duplicate what's in the device's log buffer already. This helps
-        situations like USB off.
         """
-        self.stop()
-        # Clears cached adb content, so that the next time logcat is started,
-        # we won't produce duplicated logs to log file.
-        # This helps disconnection that caused by, e.g., USB off; at the
-        # cost of losing logs at disconnection caused by reboot.
-        self.clear_adb_log()
+        self._stop()
 
     def resume(self):
         """Resumes a paused logcat service."""
diff --git a/tests/mobly/controllers/android_device_lib/services/logcat_test.py b/tests/mobly/controllers/android_device_lib/services/logcat_test.py
index 7b2217d..f394239 100755
--- a/tests/mobly/controllers/android_device_lib/services/logcat_test.py
+++ b/tests/mobly/controllers/android_device_lib/services/logcat_test.py
@@ -97,10 +97,11 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     @mock.patch('mobly.logger.get_log_file_timestamp')
-    def test_start_and_stop(self, get_timestamp_mock, stop_proc_mock,
-                            start_proc_mock, create_dir_mock, FastbootProxy,
-                            MockAdbProxy):
+    def test_start_and_stop(self, get_timestamp_mock, open_logcat_mock,
+                            stop_proc_mock, start_proc_mock, create_dir_mock,
+                            FastbootProxy, MockAdbProxy):
         """Verifies the steps of collecting adb logcat on an AndroidDevice
         object, including various function calls and the expected behaviors of
         the calls.
@@ -116,7 +117,7 @@
             logging.log_path, 'AndroidDevice%s' % ad.serial,
             'logcat,%s,fakemodel,123.txt' % ad.serial)
         create_dir_mock.assert_called_with(os.path.dirname(expected_log_path))
-        adb_cmd = '"adb" -s %s logcat -v threadtime  >> %s'
+        adb_cmd = '"adb" -s %s logcat -v threadtime -T 1  >> %s'
         start_proc_mock.assert_called_with(
             adb_cmd % (ad.serial, '"%s"' % expected_log_path), shell=True)
         self.assertEqual(logcat_service.adb_logcat_file_path,
@@ -142,8 +143,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
-    def test_update_config(self, stop_proc_mock, start_proc_mock,
-                           create_dir_mock, FastbootProxy, MockAdbProxy):
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
+    def test_update_config(self, open_logcat_mock, stop_proc_mock,
+                           start_proc_mock, create_dir_mock, FastbootProxy,
+                           MockAdbProxy):
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
         logcat_service = logcat.Logcat(ad)
@@ -157,11 +160,12 @@
         logcat_service.start()
         self.assertTrue(logcat_service._adb_logcat_process)
         create_dir_mock.assert_has_calls([mock.call('some/path')])
-        expected_adb_cmd = ('"adb" -s 1 logcat -v threadtime -a -b -c >> '
+        expected_adb_cmd = ('"adb" -s 1 logcat -v threadtime -T 1 -a -b -c >> '
                             '"some/path/log.txt"')
         start_proc_mock.assert_called_with(expected_adb_cmd, shell=True)
         self.assertEqual(logcat_service.adb_logcat_file_path,
                          'some/path/log.txt')
+        logcat_service.stop()
 
     @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
                 return_value=mock_android_device.MockAdbProxy('1'))
@@ -171,9 +175,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
-    def test_update_config_while_running(self, stop_proc_mock, start_proc_mock,
-                                         create_dir_mock, FastbootProxy,
-                                         MockAdbProxy):
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
+    def test_update_config_while_running(self, open_logcat_mock, stop_proc_mock,
+                                         start_proc_mock, create_dir_mock,
+                                         FastbootProxy, MockAdbProxy):
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
         logcat_service = logcat.Logcat(ad)
@@ -185,6 +190,7 @@
                 'Logcat thread is already running, cannot start another one'):
             logcat_service.update_config(new_config)
         self.assertTrue(logcat_service.is_alive)
+        logcat_service.stop()
 
     @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
                 return_value=mock_android_device.MockAdbProxy('1'))
@@ -194,12 +200,13 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     @mock.patch(
         'mobly.controllers.android_device_lib.services.logcat.Logcat.clear_adb_log',
         return_value=mock_android_device.MockAdbProxy('1'))
-    def test_pause_and_resume(self, clear_adb_mock, stop_proc_mock,
-                              start_proc_mock, create_dir_mock, FastbootProxy,
-                              MockAdbProxy):
+    def test_pause_and_resume(self, clear_adb_mock, open_logcat_mock,
+                              stop_proc_mock, start_proc_mock, create_dir_mock,
+                              FastbootProxy, MockAdbProxy):
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
         logcat_service = logcat.Logcat(ad, logcat.Config(clear_log=True))
@@ -214,56 +221,7 @@
         logcat_service.resume()
         self.assertTrue(logcat_service.is_alive)
         clear_adb_mock.assert_not_called()
-
-    @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
-                return_value=mock_android_device.MockAdbProxy('1'))
-    @mock.patch('mobly.controllers.android_device_lib.fastboot.FastbootProxy',
-                return_value=mock_android_device.MockFastbootProxy('1'))
-    @mock.patch('mobly.utils.start_standing_subprocess',
-                return_value='process')
-    @mock.patch('mobly.utils.stop_standing_subprocess')
-    @mock.patch(
-        'mobly.controllers.android_device_lib.services.logcat.Logcat.clear_adb_log',
-        return_value=mock_android_device.MockAdbProxy('1'))
-    def test_logcat_service_create_excerpt(self, clear_adb_mock,
-                                           stop_proc_mock, start_proc_mock,
-                                           FastbootProxy, MockAdbProxy):
-        mock_serial = '1'
-        ad = android_device.AndroidDevice(serial=mock_serial)
-        logcat_service = logcat.Logcat(ad)
-        logcat_service.start()
-        FILE_CONTENT = 'Some log.\n'
-        with open(logcat_service.adb_logcat_file_path, 'w') as f:
-            f.write(FILE_CONTENT)
-        test_output_dir = os.path.join(self.tmp_dir, 'test_foo')
-        mock_record = mock.MagicMock()
-        mock_record.begin_time = 123
-        test_run_info = runtime_test_info.RuntimeTestInfo(
-            'test_foo', test_output_dir, mock_record)
-        actual_path1 = logcat_service.create_output_excerpts(test_run_info)[0]
-        expected_path1 = os.path.join(test_output_dir, 'test_foo-123',
-                                      'logcat,1,fakemodel,test_foo-123.txt')
-        self.assertEqual(expected_path1, actual_path1)
-        self.assertTrue(os.path.exists(expected_path1))
-        self.AssertFileContains(FILE_CONTENT, expected_path1)
-        self.assertFalse(os.path.exists(logcat_service.adb_logcat_file_path))
-        # Generate some new logs and do another excerpt.
-        FILE_CONTENT = 'Some more logs!!!\n'
-        with open(logcat_service.adb_logcat_file_path, 'w') as f:
-            f.write(FILE_CONTENT)
-        test_output_dir = os.path.join(self.tmp_dir, 'test_bar')
-        mock_record = mock.MagicMock()
-        mock_record.begin_time = 456
-        test_run_info = runtime_test_info.RuntimeTestInfo(
-            'test_bar', test_output_dir, mock_record)
-        actual_path2 = logcat_service.create_output_excerpts(test_run_info)[0]
-        expected_path2 = os.path.join(test_output_dir, 'test_bar-456',
-                                      'logcat,1,fakemodel,test_bar-456.txt')
-        self.assertEqual(expected_path2, actual_path2)
-        self.assertTrue(os.path.exists(expected_path2))
-        self.AssertFileContains(FILE_CONTENT, expected_path2)
-        self.AssertFileDoesNotContain(FILE_CONTENT, expected_path1)
-        self.assertFalse(os.path.exists(logcat_service.adb_logcat_file_path))
+        logcat_service.stop()
 
     @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
                 return_value=mock_android_device.MockAdbProxy('1'))
@@ -283,9 +241,16 @@
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
         logcat_service = logcat.Logcat(ad)
-        logcat_service.start()
+        logcat_service._start()
+        # Generate logs before the file pointer is created.
+        # This message will not be captured in the excerpt.
+        NOT_IN_EXCERPT = 'Not in excerpt.\n'
+        with open(logcat_service.adb_logcat_file_path, 'a') as f:
+            f.write(NOT_IN_EXCERPT)
+        # With the file pointer created, generate logs and make an excerpt.
+        logcat_service._open_logcat_file()
         FILE_CONTENT = 'Some log.\n'
-        with open(logcat_service.adb_logcat_file_path, 'w') as f:
+        with open(logcat_service.adb_logcat_file_path, 'a') as f:
             f.write(FILE_CONTENT)
         test_output_dir = os.path.join(self.tmp_dir, 'test_foo')
         mock_record = mock.MagicMock()
@@ -298,10 +263,10 @@
         self.assertEqual(actual_path1, expected_path1)
         self.assertTrue(os.path.exists(expected_path1))
         self.AssertFileContains(FILE_CONTENT, expected_path1)
-        self.assertFalse(os.path.exists(logcat_service.adb_logcat_file_path))
+        self.AssertFileDoesNotContain(NOT_IN_EXCERPT, expected_path1)
         # Generate some new logs and do another excerpt.
         FILE_CONTENT = 'Some more logs!!!\n'
-        with open(logcat_service.adb_logcat_file_path, 'w') as f:
+        with open(logcat_service.adb_logcat_file_path, 'a') as f:
             f.write(FILE_CONTENT)
         test_output_dir = os.path.join(self.tmp_dir, 'test_bar')
         mock_record = mock.MagicMock()
@@ -315,7 +280,7 @@
         self.assertTrue(os.path.exists(expected_path2))
         self.AssertFileContains(FILE_CONTENT, expected_path2)
         self.AssertFileDoesNotContain(FILE_CONTENT, expected_path1)
-        self.assertFalse(os.path.exists(logcat_service.adb_logcat_file_path))
+        logcat_service.stop()
 
     @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
                 return_value=mock_android_device.MockAdbProxy('1'))
@@ -325,11 +290,12 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     @mock.patch('mobly.logger.get_log_file_timestamp')
     def test_take_logcat_with_extra_params(self, get_timestamp_mock,
-                                           stop_proc_mock, start_proc_mock,
-                                           create_dir_mock, FastbootProxy,
-                                           MockAdbProxy):
+                                           open_logcat_mock, stop_proc_mock,
+                                           start_proc_mock, create_dir_mock,
+                                           FastbootProxy, MockAdbProxy):
         """Verifies the steps of collecting adb logcat on an AndroidDevice
         object, including various function calls and the expected behaviors of
         the calls.
@@ -347,11 +313,12 @@
             logging.log_path, 'AndroidDevice%s' % ad.serial,
             'logcat,%s,fakemodel,123.txt' % ad.serial)
         create_dir_mock.assert_called_with(os.path.dirname(expected_log_path))
-        adb_cmd = '"adb" -s %s logcat -v threadtime -b radio >> %s'
+        adb_cmd = '"adb" -s %s logcat -v threadtime -T 1 -b radio >> %s'
         start_proc_mock.assert_called_with(
             adb_cmd % (ad.serial, '"%s"' % expected_log_path), shell=True)
         self.assertEqual(logcat_service.adb_logcat_file_path,
                          expected_log_path)
+        logcat_service.stop()
 
     @mock.patch('mobly.controllers.android_device_lib.adb.AdbProxy',
                 return_value=mock_android_device.MockAdbProxy('1'))
@@ -374,10 +341,12 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     @mock.patch('mobly.logger.get_log_line_timestamp',
                 return_value=MOCK_ADB_LOGCAT_END_TIME)
-    def test_cat_adb_log(self, mock_timestamp_getter, stop_proc_mock,
-                         start_proc_mock, FastbootProxy, MockAdbProxy):
+    def test_cat_adb_log(self, mock_timestamp_getter, open_logcat_mock,
+                         stop_proc_mock, start_proc_mock, FastbootProxy,
+                         MockAdbProxy):
         """Verifies that AndroidDevice.cat_adb_log loads the correct adb log
         file, locates the correct adb log lines within the given time range,
         and writes the lines to the correct output file.
@@ -413,11 +382,13 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     @mock.patch('mobly.logger.get_log_line_timestamp',
                 return_value=MOCK_ADB_LOGCAT_END_TIME)
     def test_cat_adb_log_with_unicode(self, mock_timestamp_getter,
-                                      stop_proc_mock, start_proc_mock,
-                                      FastbootProxy, MockAdbProxy):
+                                      open_logcat_mock, stop_proc_mock,
+                                      start_proc_mock, FastbootProxy,
+                                      MockAdbProxy):
         """Verifies that AndroidDevice.cat_adb_log loads the correct adb log
         file, locates the correct adb log lines within the given time range,
         and writes the lines to the correct output file.
diff --git a/tests/mobly/controllers/android_device_test.py b/tests/mobly/controllers/android_device_test.py
index 6308833..22aa17c 100755
--- a/tests/mobly/controllers/android_device_test.py
+++ b/tests/mobly/controllers/android_device_test.py
@@ -855,9 +855,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     def test_AndroidDevice_change_log_path_with_service(
-            self, stop_proc_mock, start_proc_mock, creat_dir_mock,
-            FastbootProxy, MockAdbProxy):
+            self, open_logcat_mock, stop_proc_mock, start_proc_mock,
+            creat_dir_mock, FastbootProxy, MockAdbProxy):
         ad = android_device.AndroidDevice(serial='1')
         ad.services.logcat.start()
         new_log_path = tempfile.mkdtemp()
@@ -911,9 +912,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     def test_AndroidDevice_update_serial_with_service_running(
-            self, stop_proc_mock, start_proc_mock, creat_dir_mock,
-            FastbootProxy, MockAdbProxy):
+            self, open_logcat_mock, stop_proc_mock, start_proc_mock,
+            creat_dir_mock, FastbootProxy, MockAdbProxy):
         ad = android_device.AndroidDevice(serial='1')
         ad.services.logcat.start()
         expected_msg = '.* Cannot change device serial number when there is service running.'
@@ -1053,7 +1055,8 @@
     @mock.patch(
         'mobly.controllers.android_device_lib.snippet_client.SnippetClient')
     @mock.patch('mobly.utils.get_available_host_port')
-    def test_AndroidDevice_snippet_cleanup(self, MockGetPort,
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
+    def test_AndroidDevice_snippet_cleanup(self, open_logcat_mock, MockGetPort,
                                            MockSnippetClient, MockFastboot,
                                            MockAdbProxy):
         ad = android_device.AndroidDevice(serial='1')
@@ -1093,9 +1096,11 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
-    def test_AndroidDevice_handle_usb_disconnect(self, stop_proc_mock,
-                                                 start_proc_mock,
-                                                 FastbootProxy, MockAdbProxy):
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
+    def test_AndroidDevice_handle_usb_disconnect(self, open_logcat_mock,
+                                                 stop_proc_mock,
+                                                 start_proc_mock, FastbootProxy,
+                                                 MockAdbProxy):
         class MockService(base_service.BaseService):
             def __init__(self, device, configs=None):
                 self._alive = False
@@ -1137,8 +1142,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
-    def test_AndroidDevice_handle_reboot(self, stop_proc_mock, start_proc_mock,
-                                         FastbootProxy, MockAdbProxy):
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
+    def test_AndroidDevice_handle_reboot(self, open_logcat_mock, stop_proc_mock,
+                                         start_proc_mock, FastbootProxy,
+                                         MockAdbProxy):
         class MockService(base_service.BaseService):
             def __init__(self, device, configs=None):
                 self._alive = False
@@ -1180,9 +1187,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     def test_AndroidDevice_handle_reboot_changes_build_info(
-            self, stop_proc_mock, start_proc_mock, FastbootProxy,
-            MockAdbProxy):
+            self, open_logcat_mock, stop_proc_mock, start_proc_mock,
+            FastbootProxy, MockAdbProxy):
         ad = android_device.AndroidDevice(serial='1')
         with ad.handle_reboot():
             ad.adb.mock_properties['ro.build.type'] = 'user'
@@ -1199,9 +1207,10 @@
     @mock.patch('mobly.utils.start_standing_subprocess',
                 return_value='process')
     @mock.patch('mobly.utils.stop_standing_subprocess')
+    @mock.patch.object(logcat.Logcat, '_open_logcat_file')
     def test_AndroidDevice_handle_reboot_changes_build_info_with_caching(
-            self, stop_proc_mock, start_proc_mock, FastbootProxy,
-            MockAdbProxy):
+            self, open_logcat_mock, stop_proc_mock, start_proc_mock,
+            FastbootProxy, MockAdbProxy):
         ad = android_device.AndroidDevice(serial='1')  # Call getprops 1.
         rootable_states = [ad.is_rootable]
         with ad.handle_reboot():