Log path changes in `BaseTestClass`. (#650)

* Add a new level of output directoy specific to test classes.
* Direct `log_path` to class-specific.
* Add a `root_output_path` pointing to the test run path
  (what `log_path` used to point to)
diff --git a/mobly/base_test.py b/mobly/base_test.py
index 2d386b1..90ff75a 100644
--- a/mobly/base_test.py
+++ b/mobly/base_test.py
@@ -18,6 +18,7 @@
 import functools
 import inspect
 import logging
+import os
 
 from future.utils import raise_with_traceback
 
@@ -71,8 +72,11 @@
             test bed config.
         current_test_info: RuntimeTestInfo, runtime information on the test
             currently being executed.
-        log_path: string, specifies the root directory for all logs written
-            by a test run.
+        root_output_path: string, storage path for output files associated with
+            the entire test run. A test run can have multiple test class
+            executions. This includes the test summary and Mobly log files.
+        log_path: string, storage path for files specific to a single test
+            class execution.
         test_bed_name: [Deprecated, use 'testbed_name' instead]
             string, the name of the test bed used by a test run.
         testbed_name: string, the name of the test bed used by a test run.
@@ -94,14 +98,16 @@
             configs: A config_parser.TestRunConfig object.
         """
         self.tests = []
-        self._class_name = self.__class__.__name__
-        if configs.test_class_name_suffix and self.TAG is None:
-            self.TAG = '%s_%s' % (self._class_name,
-                                  configs.test_class_name_suffix)
-        elif self.TAG is None:
-            self.TAG = self._class_name
+        class_identifier = self.__class__.__name__
+        if configs.test_class_name_suffix:
+            class_identifier = '%s_%s' % (class_identifier,
+                                          configs.test_class_name_suffix)
+        if self.TAG is None:
+            self.TAG = class_identifier
         # Set params.
-        self.log_path = configs.log_path
+        self.root_output_path = configs.log_path
+        self.log_path = os.path.join(self.root_output_path, class_identifier)
+        utils.create_dir(self.log_path)
         # Deprecated, use 'testbed_name'
         self.test_bed_name = configs.test_bed_name
         self.testbed_name = configs.testbed_name
@@ -809,6 +815,7 @@
         Returns:
             The test results object of this class.
         """
+        logging.log_path = self.log_path
         # Executes pre-setup procedures, like generating test methods.
         if not self._setup_generated_tests():
             return self.results
diff --git a/mobly/logger.py b/mobly/logger.py
index b3a0fb7..74f76fe 100644
--- a/mobly/logger.py
+++ b/mobly/logger.py
@@ -211,6 +211,7 @@
     log.addHandler(fh_debug)
     log.log_path = log_path
     logging.log_path = log_path
+    logging.root_output_path = log_path
 
 
 def kill_test_logger(logger):
@@ -241,6 +242,14 @@
 def setup_test_logger(log_path, prefix=None, alias='latest'):
     """Customizes the root logger for a test run.
 
+    In addition to configuring the Mobly logging handlers, this also sets two
+    attributes on the `logging` module for the output directories:
+
+    root_output_path: path to the directory for the entire test run.
+    log_path: same as `root_output_path` outside of a test class run. In the
+        context of a test class run, this is the output directory for files
+        specific to a test class.
+
     Args:
         log_path: string, the location of the report file.
         prefix: optional string, a prefix for each log line in terminal.
diff --git a/mobly/test_runner.py b/mobly/test_runner.py
index 269a6e4..05cd36b 100644
--- a/mobly/test_runner.py
+++ b/mobly/test_runner.py
@@ -189,10 +189,12 @@
         self.results: The test result object used to record the results of
             this test run.
     """
+
     class _TestRunInfo(object):
         """Identifies one test class to run, which tests to run, and config to
         run it with.
         """
+
         def __init__(self,
                      config,
                      test_class,
@@ -223,8 +225,9 @@
     def _update_log_path(self):
         """Updates the logging values with the current timestamp."""
         self._start_time = logger.get_log_file_timestamp()
-        self._log_path = os.path.join(self._log_dir, self._testbed_name,
-                                      self._start_time)
+        self._root_output_path = os.path.join(self._log_dir,
+                                              self._testbed_name,
+                                              self._start_time)
 
     @contextlib.contextmanager
     def mobly_logger(self, alias='latest'):
@@ -239,11 +242,11 @@
             The host file path where the logs for the test run are stored.
         """
         self._update_log_path()
-        logger.setup_test_logger(self._log_path,
+        logger.setup_test_logger(self._root_output_path,
                                  self._testbed_name,
                                  alias=alias)
         try:
-            yield self._log_path
+            yield self._root_output_path
         finally:
             logger.kill_test_logger(logging.getLogger())
 
@@ -318,15 +321,15 @@
 
         # Ensure the log path exists. Necessary if `run` is used outside of the
         # `mobly_logger` context.
-        utils.create_dir(self._log_path)
+        utils.create_dir(self._root_output_path)
 
         summary_writer = records.TestSummaryWriter(
-            os.path.join(self._log_path, records.OUTPUT_FILE_SUMMARY))
+            os.path.join(self._root_output_path, records.OUTPUT_FILE_SUMMARY))
         try:
             for test_run_info in self._test_run_infos:
                 # Set up the test-specific config
                 test_config = test_run_info.config.copy()
-                test_config.log_path = self._log_path
+                test_config.log_path = self._root_output_path
                 test_config.summary_writer = summary_writer
                 test_config.test_class_name_suffix = test_run_info.test_class_name_suffix
                 try:
diff --git a/tests/mobly/base_test_test.py b/tests/mobly/base_test_test.py
index 03c929c..6eb7149 100755
--- a/tests/mobly/base_test_test.py
+++ b/tests/mobly/base_test_test.py
@@ -65,6 +65,23 @@
     def tearDown(self):
         shutil.rmtree(self.tmp_dir)
 
+    def test_paths(self):
+        '''Checks the output paths set in `BaseTestClass`.'''
+        path_checker = mock.MagicMock()
+
+        class MockBaseTest(base_test.BaseTestClass):
+            def test_func(self):
+                path_checker.log_path = self.log_path
+                path_checker.root_output_path = self.root_output_path
+
+        bt_cls = MockBaseTest(self.mock_test_cls_configs)
+        bt_cls.run(test_names=["test_func"])
+        self.assertEqual(path_checker.root_output_path, self.tmp_dir)
+        self.assertTrue(os.path.exists(path_checker.root_output_path))
+        expected_log_path = os.path.join(self.tmp_dir, 'MockBaseTest')
+        self.assertEqual(path_checker.log_path, expected_log_path)
+        self.assertTrue(os.path.exists(path_checker.log_path))
+
     def test_current_test_name(self):
         class MockBaseTest(base_test.BaseTestClass):
             def test_func(self):
@@ -2176,7 +2193,7 @@
                     pass
 
     def test_log_stage_always_logs_end_statement(self):
-        instance = base_test.BaseTestClass(mock.Mock())
+        instance = base_test.BaseTestClass(self.mock_test_cls_configs)
         instance.current_test_info = mock.Mock()
         instance.current_test_info.name = 'TestClass'
 
diff --git a/tests/mobly/output_test.py b/tests/mobly/output_test.py
index 28a9808..6a8facb 100755
--- a/tests/mobly/output_test.py
+++ b/tests/mobly/output_test.py
@@ -40,6 +40,7 @@
     """This test class has unit tests for the implementation of Mobly's output
     files.
     """
+
     def setUp(self):
         self.tmp_dir = tempfile.mkdtemp()
         self.base_mock_test_config = config_parser.TestRunConfig()
@@ -195,7 +196,7 @@
             tr.add_test_class(mock_test_config,
                               integration_test.IntegrationTest)
             tr.run()
-        output_dir = logging.log_path
+        output_dir = logging.root_output_path
         (summary_file_path, debug_log_path,
          info_log_path) = self.assert_output_logs_exist(output_dir)
         self.assert_log_contents(debug_log_path,
@@ -218,10 +219,10 @@
         tr.add_test_class(mock_test_config, integration_test.IntegrationTest)
         with tr.mobly_logger():
             tr.run()
-        output_dir1 = logging.log_path
+        output_dir1 = logging.root_output_path
         with tr.mobly_logger():
             tr.run()
-        output_dir2 = logging.log_path
+        output_dir2 = logging.root_output_path
         self.assertNotEqual(output_dir1, output_dir2)
         self.assert_output_logs_exist(output_dir1)
         self.assert_output_logs_exist(output_dir2)
@@ -279,7 +280,11 @@
             tr.add_test_class(mock_test_config,
                               integration_test.IntegrationTest)
             tr.run()
-        output_dir = logging.log_path
+        expected_class_path = os.path.join(logging.root_output_path,
+                                           'IntegrationTest')
+        self.assertEqual(expected_class_path, logging.log_path)
+        os.path.exists(logging.log_path)
+        output_dir = logging.root_output_path
         (summary_file_path, debug_log_path,
          info_log_path) = self.assert_output_logs_exist(output_dir)
         summary_entries = []
@@ -303,7 +308,7 @@
                 mock_test_config,
                 teardown_class_failure_test.TearDownClassFailureTest)
             tr.run()
-        output_dir = logging.log_path
+        output_dir = logging.root_output_path
         summary_file_path = os.path.join(output_dir,
                                          records.OUTPUT_FILE_SUMMARY)
         found = False
diff --git a/tests/mobly/test_runner_test.py b/tests/mobly/test_runner_test.py
index fafdbe1..8d65336 100755
--- a/tests/mobly/test_runner_test.py
+++ b/tests/mobly/test_runner_test.py
@@ -39,6 +39,7 @@
     """This test class has unit tests for the implementation of everything
     under mobly.test_runner.
     """
+
     def setUp(self):
         self.tmp_dir = tempfile.mkdtemp()
         self.base_mock_test_config = config_parser.TestRunConfig()
@@ -134,7 +135,7 @@
             tr.add_test_class(mock_test_config,
                               integration_test.IntegrationTest)
             tr.run()
-        summary_path = os.path.join(logging.log_path,
+        summary_path = os.path.join(logging.root_output_path,
                                     records.OUTPUT_FILE_SUMMARY)
         with io.open(summary_path, 'r', encoding='utf-8') as f:
             summary_entries = list(yaml.safe_load_all(f))