Add ability to override the log alias directory. (#584)

diff --git a/mobly/logger.py b/mobly/logger.py
index fba6b2d..a68398e 100644
--- a/mobly/logger.py
+++ b/mobly/logger.py
@@ -187,29 +187,36 @@
             h.close()
 
 
-def create_latest_log_alias(actual_path):
+def create_latest_log_alias(actual_path, alias):
     """Creates a symlink to the latest test run logs.
 
     Args:
-        actual_path: The source directory where the latest test run's logs are.
+        actual_path: string, the source directory where the latest test run's
+            logs are.
+        alias: string, the name of the directory to contain the latest log
+            files.
     """
-    alias_path = os.path.join(os.path.dirname(actual_path), 'latest')
+    alias_path = os.path.join(os.path.dirname(actual_path), alias)
     utils.create_alias(actual_path, alias_path)
 
 
-def setup_test_logger(log_path, prefix=None, filename=None):
+def setup_test_logger(log_path, prefix=None, alias='latest'):
     """Customizes the root logger for a test run.
 
     Args:
-        log_path: Location of the report file.
-        prefix: A prefix for each log line in terminal.
-        filename: Name of the files. The default is the time the objects
-            are requested.
+        log_path: string, the location of the report file.
+        prefix: optional string, a prefix for each log line in terminal.
+        alias: optional string, The name of the alias to use for the latest log
+            directory. If a falsy value is provided, then the alias directory
+            will not be created, which is useful to save storage space when the
+            storage system (e.g. ZIP files) does not properly support
+            shortcut/symlinks.
     """
     utils.create_dir(log_path)
     _setup_test_logger(log_path, prefix)
     logging.info('Test output folder: "%s"', log_path)
-    create_latest_log_alias(log_path)
+    if alias:
+        create_latest_log_alias(log_path, alias=alias)
 
 
 def normalize_log_line_timestamp(log_line_timestamp):
diff --git a/mobly/test_runner.py b/mobly/test_runner.py
index f67dede..65e5149 100644
--- a/mobly/test_runner.py
+++ b/mobly/test_runner.py
@@ -219,16 +219,23 @@
         self._log_path = None
 
     @contextlib.contextmanager
-    def mobly_logger(self):
+    def mobly_logger(self, alias='latest'):
         """Starts and stops a logging context for a Mobly test run.
 
+        Args:
+          alias: optional string, the name of the latest log alias directory to
+              create. If a falsy value is specified, then the directory will not
+              be created.
+
         Yields:
             The host file path where the logs for the test run are stored.
         """
         self._start_time = logger.get_log_file_timestamp()
         self._log_path = os.path.join(self._log_dir, self._test_bed_name,
                                       self._start_time)
-        logger.setup_test_logger(self._log_path, self._test_bed_name)
+        logger.setup_test_logger(self._log_path,
+                                 self._test_bed_name,
+                                 alias=alias)
         try:
             yield self._log_path
         finally:
diff --git a/tests/mobly/logger_test.py b/tests/mobly/logger_test.py
index 2adc08a..2e34d46 100755
--- a/tests/mobly/logger_test.py
+++ b/tests/mobly/logger_test.py
@@ -12,8 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest
+import mock
 import pytz
+import shutil
+import tempfile
+import unittest
 
 from mobly import logger
 
@@ -21,10 +24,15 @@
 class LoggerTest(unittest.TestCase):
     """Verifies code in mobly.logger module.
     """
+    def setUp(self):
+        self.log_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        shutil.rmtree(self.log_dir)
 
     def test_epoch_to_log_line_timestamp(self):
-        actual_stamp = logger.epoch_to_log_line_timestamp(
-            1469134262116, time_zone=pytz.utc)
+        actual_stamp = logger.epoch_to_log_line_timestamp(1469134262116,
+                                                          time_zone=pytz.utc)
         self.assertEqual("07-21 20:51:02.116", actual_stamp)
 
     def test_is_valid_logline_timestamp(self):
@@ -39,6 +47,31 @@
         self.assertFalse(
             logger.is_valid_logline_timestamp("------------------"))
 
+    @mock.patch('mobly.utils.create_alias')
+    def test_create_latest_log_alias(self, mock_create_alias):
+        logger.create_latest_log_alias('fake_path', alias='latest')
+        mock_create_alias.assert_called_once_with('fake_path', 'latest')
+
+    @mock.patch('mobly.logger._setup_test_logger')
+    @mock.patch('mobly.logger.create_latest_log_alias')
+    def test_setup_test_logger_creates_log_alias(self,
+                                                 mock_create_latest_log_alias,
+                                                 mock__setup_test_logger):
+        logger.setup_test_logger(self.log_dir)
+        mock__setup_test_logger.assert_called_once_with(self.log_dir, None)
+        mock_create_latest_log_alias.assert_called_once_with(self.log_dir,
+                                                             alias='latest')
+
+    @mock.patch('mobly.logger._setup_test_logger')
+    @mock.patch('mobly.logger.create_latest_log_alias')
+    def test_setup_test_logger_creates_log_alias_with_custom_value(
+            self, mock_create_latest_log_alias, mock__setup_test_logger):
+        mock_alias = mock.MagicMock()
+        logger.setup_test_logger(self.log_dir, alias=mock_alias)
+
+        mock_create_latest_log_alias.assert_called_once_with(self.log_dir,
+                                                             alias=mock_alias)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/mobly/output_test.py b/tests/mobly/output_test.py
index 743c342..6762684 100755
--- a/tests/mobly/output_test.py
+++ b/tests/mobly/output_test.py
@@ -132,6 +132,52 @@
             win32file.GetLongPathName(logging.log_path))
         self.assertEqual(normalized_shortcut_path, normalized_logger_path)
 
+    @mock.patch('mobly.utils.create_alias')
+    def test_mobly_logger_with_default_latest_log_alias(
+            self, mock_create_alias):
+        mock_test_config = self.create_mock_test_config(
+            self.base_mock_test_config)
+        tr = test_runner.TestRunner(self.log_dir, self.test_bed_name)
+        with tr.mobly_logger():
+            pass
+        expected_alias_dir = os.path.join(self.log_dir, self.test_bed_name,
+                                          'latest')
+        mock_create_alias.assert_called_once_with(logging.log_path,
+                                                  expected_alias_dir)
+
+    @mock.patch('mobly.utils.create_alias')
+    def test_mobly_logger_with_custom_latest_log_alias(self,
+                                                       mock_create_alias):
+        mock_test_config = self.create_mock_test_config(
+            self.base_mock_test_config)
+        tr = test_runner.TestRunner(self.log_dir, self.test_bed_name)
+        with tr.mobly_logger(alias='history'):
+            pass
+        expected_alias_dir = os.path.join(self.log_dir, self.test_bed_name,
+                                          'history')
+        mock_create_alias.assert_called_once_with(logging.log_path,
+                                                  expected_alias_dir)
+
+    @mock.patch('mobly.utils.create_alias')
+    def test_mobly_logger_skips_latest_log_alias_when_none(
+            self, mock_create_alias):
+        mock_test_config = self.create_mock_test_config(
+            self.base_mock_test_config)
+        tr = test_runner.TestRunner(self.log_dir, self.test_bed_name)
+        with tr.mobly_logger(alias=None):
+            pass
+        mock_create_alias.asset_not_called()
+
+    @mock.patch('mobly.utils.create_alias')
+    def test_mobly_logger_skips_latest_log_alias_when_empty(
+            self, mock_create_alias):
+        mock_test_config = self.create_mock_test_config(
+            self.base_mock_test_config)
+        tr = test_runner.TestRunner(self.log_dir, self.test_bed_name)
+        with tr.mobly_logger(alias=''):
+            pass
+        mock_create_alias.asset_not_called()
+
     def test_logging_before_run(self):
         """Verifies the expected output files from a test run.