Repository: google/mobly

Issue:
Make `AndroidDevice.take_bug_report` more useful
Right now, `AndroidDevice.take_bug_report` is meant to be used in functions like `BaseTestClass.on_fail`, because it requires a test name and a test begin time.

There's no reason to lock the usage of `AndroidDevice.take_bug_report` like this. We should generalize the usage to make it work in more situations.


Discussion:
Oh, I just realized that http://go/gh/google/mobly/pull/576 implements half of this, so the only thing that's left is giving a default test name like "bugreport" maybe

Resolving patch:
diff --git a/mobly/controllers/android_device.py b/mobly/controllers/android_device.py
--- a/mobly/controllers/android_device.py
+++ b/mobly/controllers/android_device.py
@@ -55,6 +55,9 @@
 DEFAULT_VALUE_SKIP_LOGCAT = False
 SERVICE_NAME_LOGCAT = 'logcat'
 
+# Default name for bug reports taken without a specified test name.
+DEFAULT_BUG_REPORT_NAME = 'bugreport'
+
 # Default Timeout to wait for boot completion
 DEFAULT_TIMEOUT_BOOT_COMPLETION_SECOND = 15 * 60
 
@@ -392,7 +395,9 @@ def take_bug_reports(ads, test_name, begin_time, destination=None):
 
     def take_br(test_name, begin_time, ad, destination):
         ad.take_bug_report(
-            test_name, begin_time=begin_time, destination=destination)
+            test_name=test_name,
+            begin_time=begin_time,
+            destination=destination)
 
     args = [(test_name, begin_time, ad, destination) for ad in ads]
     utils.concurrent_exec(take_br, args)
@@ -824,7 +829,7 @@ def unload_snippet(self, name):
         self.services.snippets.remove_snippet_client(name)
 
     def take_bug_report(self,
-                        test_name,
+                        test_name=None,
                         begin_time=None,
                         timeout=300,
                         destination=None):
@@ -832,6 +837,8 @@ def take_bug_report(self,
 
         Args:
             test_name: Name of the test method that triggered this bug report.
+                If not set, then this will default to
+                android_device.DEFAULT_BUG_REPORT_NAME.
             begin_time: Timestamp of when the test started. If not set, then
                 this will default to the current time.
             timeout: float, the number of seconds to wait for bugreport to
@@ -843,6 +850,8 @@ def take_bug_report(self,
           A string containing the absolute path to the bug report on the host
           machine.
         """
+        if test_name is None:
+            test_name = DEFAULT_BUG_REPORT_NAME
         if begin_time is None:
             epoch_time = utils.get_current_epoch_time()
             timestamp = mobly_logger.epoch_to_log_line_timestamp(epoch_time)


Test patch:
diff --git a/tests/mobly/controllers/android_device_test.py b/tests/mobly/controllers/android_device_test.py
--- a/tests/mobly/controllers/android_device_test.py
+++ b/tests/mobly/controllers/android_device_test.py
@@ -224,11 +224,17 @@ def test_take_bug_reports(self):
         ads = mock_android_device.get_mock_ads(3)
         android_device.take_bug_reports(ads, 'test_something', 'sometime')
         ads[0].take_bug_report.assert_called_once_with(
-            'test_something', begin_time='sometime', destination=None)
+            test_name='test_something',
+            begin_time='sometime',
+            destination=None)
         ads[1].take_bug_report.assert_called_once_with(
-            'test_something', begin_time='sometime', destination=None)
+            test_name='test_something',
+            begin_time='sometime',
+            destination=None)
         ads[2].take_bug_report.assert_called_once_with(
-            'test_something', begin_time='sometime', destination=None)
+            test_name='test_something',
+            begin_time='sometime',
+            destination=None)
 
     # Tests for android_device.AndroidDevice class.
     # These tests mock out any interaction with the OS and real android device
@@ -320,7 +326,7 @@ def test_AndroidDevice_take_bug_report(self, create_dir_mock,
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
         output_path = ad.take_bug_report(
-            'test_something', begin_time='sometime')
+            test_name='test_something', begin_time='sometime')
         expected_path = os.path.join(
             logging.log_path, 'AndroidDevice%s' % ad.serial, 'BugReports')
         create_dir_mock.assert_called_with(expected_path)
@@ -344,7 +350,8 @@ def test_AndroidDevice_take_bug_report_fail(self, create_dir_mock,
         ad = android_device.AndroidDevice(serial=mock_serial)
         expected_msg = '.* Failed to take bugreport.'
         with self.assertRaisesRegex(android_device.Error, expected_msg):
-            ad.take_bug_report('test_something', begin_time='sometime')
+            ad.take_bug_report(
+                test_name='test_something', begin_time='sometime')
 
     @mock.patch(
         'mobly.controllers.android_device_lib.adb.AdbProxy',
@@ -355,7 +362,7 @@ def test_AndroidDevice_take_bug_report_fail(self, create_dir_mock,
     @mock.patch('mobly.utils.create_dir')
     @mock.patch('mobly.utils.get_current_epoch_time')
     @mock.patch('mobly.logger.epoch_to_log_line_timestamp')
-    def test_AndroidDevice_take_bug_report_without_begin_time(
+    def test_AndroidDevice_take_bug_report_without_args(
             self, epoch_to_log_line_timestamp_mock,
             get_current_epoch_time_mock, create_dir_mock, FastbootProxy,
             MockAdbProxy):
@@ -363,7 +370,33 @@ def test_AndroidDevice_take_bug_report_without_begin_time(
         epoch_to_log_line_timestamp_mock.return_value = '05-09 17:03:49.606'
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
-        output_path = ad.take_bug_report('test_something')
+        output_path = ad.take_bug_report()
+        expected_path = os.path.join(
+            logging.log_path, 'AndroidDevice%s' % ad.serial, 'BugReports')
+        create_dir_mock.assert_called_with(expected_path)
+        epoch_to_log_line_timestamp_mock.assert_called_once_with(1557446629606)
+        self.assertEqual(output_path,
+                         os.path.join(expected_path,
+                                      'bugreport,05-09_17-03-49.606,1.zip'))
+
+    @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.create_dir')
+    @mock.patch('mobly.utils.get_current_epoch_time')
+    @mock.patch('mobly.logger.epoch_to_log_line_timestamp')
+    def test_AndroidDevice_take_bug_report_with_only_test_name(
+            self, epoch_to_log_line_timestamp_mock,
+            get_current_epoch_time_mock, create_dir_mock, FastbootProxy,
+            MockAdbProxy):
+        get_current_epoch_time_mock.return_value = 1557446629606
+        epoch_to_log_line_timestamp_mock.return_value = '05-09 17:03:49.606'
+        mock_serial = '1'
+        ad = android_device.AndroidDevice(serial=mock_serial)
+        output_path = ad.take_bug_report(test_name='test_something')
         expected_path = os.path.join(
             logging.log_path, 'AndroidDevice%s' % ad.serial, 'BugReports')
         create_dir_mock.assert_called_with(expected_path)
@@ -380,7 +413,26 @@ def test_AndroidDevice_take_bug_report_without_begin_time(
         'mobly.controllers.android_device_lib.fastboot.FastbootProxy',
         return_value=mock_android_device.MockFastbootProxy(1))
     @mock.patch('mobly.utils.create_dir')
-    def test_AndroidDevice_take_bug_report_with_positional_begin_time(
+    def test_AndroidDevice_take_bug_report_with_only_begin_time(
+            self, create_dir_mock, FastbootProxy, MockAdbProxy):
+        mock_serial = '1'
+        ad = android_device.AndroidDevice(serial=mock_serial)
+        output_path = ad.take_bug_report(begin_time='sometime')
+        expected_path = os.path.join(
+            logging.log_path, 'AndroidDevice%s' % ad.serial, 'BugReports')
+        create_dir_mock.assert_called_with(expected_path)
+        self.assertEqual(output_path,
+                         os.path.join(expected_path,
+                                      'bugreport,sometime,1.zip'))
+
+    @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.create_dir')
+    def test_AndroidDevice_take_bug_report_with_positional_args(
             self, create_dir_mock, FastbootProxy, MockAdbProxy):
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
@@ -405,7 +457,9 @@ def test_AndroidDevice_take_bug_report_with_destination(
         ad = android_device.AndroidDevice(serial=mock_serial)
         dest = tempfile.gettempdir()
         output_path = ad.take_bug_report(
-            "test_something", begin_time="sometime", destination=dest)
+            test_name="test_something",
+            begin_time="sometime",
+            destination=dest)
         expected_path = os.path.join(dest)
         create_dir_mock.assert_called_with(expected_path)
         self.assertEqual(output_path,
@@ -428,7 +482,7 @@ def test_AndroidDevice_take_bug_report_fallback(
         mock_serial = '1'
         ad = android_device.AndroidDevice(serial=mock_serial)
         output_path = ad.take_bug_report(
-            'test_something', begin_time='sometime')
+            test_name='test_something', begin_time='sometime')
         expected_path = os.path.join(
             logging.log_path, 'AndroidDevice%s' % ad.serial, 'BugReports')
         create_dir_mock.assert_called_with(expected_path)