| # Copyright 2016 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # 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 io |
| import logging |
| import mock |
| import os |
| import re |
| import shutil |
| import tempfile |
| import yaml |
| from future.tests.base import unittest |
| |
| from mobly import config_parser |
| from mobly import records |
| from mobly import signals |
| from mobly import test_runner |
| |
| from tests.lib import mock_android_device |
| from tests.lib import mock_controller |
| from tests.lib import integration_test |
| from tests.lib import integration2_test |
| from tests.lib import integration3_test |
| |
| |
| class TestRunnerTest(unittest.TestCase): |
| """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() |
| self.base_mock_test_config.test_bed_name = 'SampleTestBed' |
| self.base_mock_test_config.controller_configs = {} |
| self.base_mock_test_config.user_params = { |
| 'icecream': 42, |
| 'extra_param': 'haha' |
| } |
| self.base_mock_test_config.log_path = self.tmp_dir |
| self.log_dir = self.base_mock_test_config.log_path |
| self.test_bed_name = self.base_mock_test_config.test_bed_name |
| |
| def tearDown(self): |
| shutil.rmtree(self.tmp_dir) |
| |
| def _assertControllerInfoEqual(self, info, expected_info_dict): |
| self.assertEqual(expected_info_dict['Controller Name'], |
| info.controller_name) |
| self.assertEqual(expected_info_dict['Test Class'], info.test_class) |
| self.assertEqual(expected_info_dict['Controller Info'], |
| info.controller_info) |
| |
| def test_run_twice(self): |
| """Verifies that: |
| 1. Repeated run works properly. |
| 2. The original configuration is not altered if a test controller |
| module modifies configuration. |
| """ |
| mock_test_config = self.base_mock_test_config.copy() |
| mock_ctrlr_config_name = mock_controller.MOBLY_CONTROLLER_CONFIG_NAME |
| my_config = [{ |
| 'serial': 'xxxx', |
| 'magic': 'Magic1' |
| }, { |
| 'serial': 'xxxx', |
| 'magic': 'Magic2' |
| }] |
| mock_test_config.controller_configs[mock_ctrlr_config_name] = my_config |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| with tr.mobly_logger(): |
| tr.add_test_class(mock_test_config, |
| integration_test.IntegrationTest) |
| tr.run() |
| self.assertTrue( |
| mock_test_config.controller_configs[mock_ctrlr_config_name][0]) |
| with tr.mobly_logger(): |
| tr.run() |
| results = tr.results.summary_dict() |
| self.assertEqual(results['Requested'], 2) |
| self.assertEqual(results['Executed'], 2) |
| self.assertEqual(results['Passed'], 2) |
| expected_info_dict = { |
| 'Controller Info': [{ |
| 'MyMagic': { |
| 'magic': 'Magic1' |
| } |
| }, { |
| 'MyMagic': { |
| 'magic': 'Magic2' |
| } |
| }], |
| 'Controller Name': |
| 'MagicDevice', |
| 'Test Class': |
| 'IntegrationTest', |
| } |
| self._assertControllerInfoEqual(tr.results.controller_info[0], |
| expected_info_dict) |
| self._assertControllerInfoEqual(tr.results.controller_info[1], |
| expected_info_dict) |
| self.assertNotEqual(tr.results.controller_info[0], |
| tr.results.controller_info[1]) |
| |
| def test_summary_file_entries(self): |
| """Verifies the output summary's file format. |
| |
| This focuses on the format of the file instead of the content of |
| entries, which is covered in base_test_test. |
| """ |
| mock_test_config = self.base_mock_test_config.copy() |
| mock_ctrlr_config_name = mock_controller.MOBLY_CONTROLLER_CONFIG_NAME |
| my_config = [{ |
| 'serial': 'xxxx', |
| 'magic': 'Magic1' |
| }, { |
| 'serial': 'xxxx', |
| 'magic': 'Magic2' |
| }] |
| mock_test_config.controller_configs[mock_ctrlr_config_name] = my_config |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| with tr.mobly_logger(): |
| tr.add_test_class(mock_test_config, |
| integration_test.IntegrationTest) |
| tr.run() |
| summary_path = os.path.join(logging.log_path, |
| records.OUTPUT_FILE_SUMMARY) |
| with io.open(summary_path, 'r', encoding='utf-8') as f: |
| summary_entries = list(yaml.safe_load_all(f)) |
| self.assertEqual(len(summary_entries), 4) |
| # Verify the first entry is the list of test names. |
| self.assertEqual(summary_entries[0]['Type'], |
| records.TestSummaryEntryType.TEST_NAME_LIST.value) |
| self.assertEqual(summary_entries[1]['Type'], |
| records.TestSummaryEntryType.RECORD.value) |
| self.assertEqual(summary_entries[2]['Type'], |
| records.TestSummaryEntryType.CONTROLLER_INFO.value) |
| self.assertEqual(summary_entries[3]['Type'], |
| records.TestSummaryEntryType.SUMMARY.value) |
| |
| def test_run(self): |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| self.base_mock_test_config.controller_configs[ |
| mock_controller.MOBLY_CONTROLLER_CONFIG_NAME] = '*' |
| with tr.mobly_logger(): |
| tr.add_test_class(self.base_mock_test_config, |
| integration_test.IntegrationTest) |
| tr.run() |
| results = tr.results.summary_dict() |
| self.assertEqual(results['Requested'], 1) |
| self.assertEqual(results['Executed'], 1) |
| self.assertEqual(results['Passed'], 1) |
| self.assertEqual(len(tr.results.executed), 1) |
| record = tr.results.executed[0] |
| self.assertEqual(record.test_class, 'IntegrationTest') |
| |
| def test_run_without_mobly_logger_context(self): |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| self.base_mock_test_config.controller_configs[ |
| mock_controller.MOBLY_CONTROLLER_CONFIG_NAME] = '*' |
| tr.add_test_class(self.base_mock_test_config, |
| integration_test.IntegrationTest) |
| tr.run() |
| results = tr.results.summary_dict() |
| self.assertEqual(results['Requested'], 1) |
| self.assertEqual(results['Executed'], 1) |
| self.assertEqual(results['Passed'], 1) |
| self.assertEqual(len(tr.results.executed), 1) |
| record = tr.results.executed[0] |
| self.assertEqual(record.test_class, 'IntegrationTest') |
| |
| @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.controllers.android_device.list_adb_devices', |
| return_value=['1']) |
| @mock.patch('mobly.controllers.android_device.get_all_instances', |
| return_value=mock_android_device.get_mock_ads(1)) |
| def test_run_two_test_classes(self, mock_get_all, mock_list_adb, |
| mock_fastboot, mock_adb): |
| """Verifies that running more than one test class in one test run works |
| properly. |
| |
| This requires using a built-in controller module. Using AndroidDevice |
| module since it has all the mocks needed already. |
| """ |
| mock_test_config = self.base_mock_test_config.copy() |
| mock_ctrlr_config_name = mock_controller.MOBLY_CONTROLLER_CONFIG_NAME |
| my_config = [{ |
| 'serial': 'xxxx', |
| 'magic': 'Magic1' |
| }, { |
| 'serial': 'xxxx', |
| 'magic': 'Magic2' |
| }] |
| mock_test_config.controller_configs[mock_ctrlr_config_name] = my_config |
| mock_test_config.controller_configs['AndroidDevice'] = [{ |
| 'serial': '1' |
| }] |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| with tr.mobly_logger(): |
| tr.add_test_class(mock_test_config, |
| integration2_test.Integration2Test) |
| tr.add_test_class(mock_test_config, |
| integration_test.IntegrationTest) |
| tr.run() |
| results = tr.results.summary_dict() |
| self.assertEqual(results['Requested'], 2) |
| self.assertEqual(results['Executed'], 2) |
| self.assertEqual(results['Passed'], 2) |
| self.assertEqual(len(tr.results.executed), 2) |
| # Tag of the test class defaults to the class name. |
| record1 = tr.results.executed[0] |
| record2 = tr.results.executed[1] |
| self.assertEqual(record1.test_class, 'Integration2Test') |
| self.assertEqual(record2.test_class, 'IntegrationTest') |
| |
| def test_run_two_test_classes_different_configs_and_aliases(self): |
| """Verifies that running more than one test class in one test run with |
| different configs works properly. |
| """ |
| config1 = self.base_mock_test_config.copy() |
| config1.controller_configs[ |
| mock_controller.MOBLY_CONTROLLER_CONFIG_NAME] = [{ |
| 'serial': 'xxxx' |
| }] |
| config2 = config1.copy() |
| config2.user_params['icecream'] = 10 |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| with tr.mobly_logger(): |
| tr.add_test_class(config1, |
| integration_test.IntegrationTest, |
| name_suffix='FirstConfig') |
| tr.add_test_class(config2, |
| integration_test.IntegrationTest, |
| name_suffix='SecondConfig') |
| tr.run() |
| results = tr.results.summary_dict() |
| self.assertEqual(results['Requested'], 2) |
| self.assertEqual(results['Executed'], 2) |
| self.assertEqual(results['Passed'], 1) |
| self.assertEqual(results['Failed'], 1) |
| self.assertEqual(tr.results.failed[0].details, '10 != 42') |
| self.assertEqual(len(tr.results.executed), 2) |
| record1 = tr.results.executed[0] |
| record2 = tr.results.executed[1] |
| self.assertEqual(record1.test_class, 'IntegrationTest_FirstConfig') |
| self.assertEqual(record2.test_class, 'IntegrationTest_SecondConfig') |
| |
| def test_run_with_abort_all(self): |
| mock_test_config = self.base_mock_test_config.copy() |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| with tr.mobly_logger(): |
| tr.add_test_class(mock_test_config, |
| integration3_test.Integration3Test) |
| with self.assertRaises(signals.TestAbortAll): |
| tr.run() |
| results = tr.results.summary_dict() |
| self.assertEqual(results['Requested'], 1) |
| self.assertEqual(results['Executed'], 0) |
| self.assertEqual(results['Passed'], 0) |
| self.assertEqual(results['Failed'], 0) |
| |
| def test_add_test_class_mismatched_log_path(self): |
| tr = test_runner.TestRunner('/different/log/dir', self.test_bed_name) |
| with self.assertRaisesRegex( |
| test_runner.Error, |
| 'TestRunner\'s log folder is "/different/log/dir", but a test ' |
| r'config with a different log folder \("%s"\) was added.' % |
| re.escape(self.log_dir)): |
| tr.add_test_class(self.base_mock_test_config, |
| integration_test.IntegrationTest) |
| |
| def test_add_test_class_mismatched_test_bed_name(self): |
| tr = test_runner.TestRunner(self.log_dir, 'different_test_bed') |
| with self.assertRaisesRegex( |
| test_runner.Error, |
| 'TestRunner\'s test bed is "different_test_bed", but a test ' |
| r'config with a different test bed \("%s"\) was added.' % |
| self.test_bed_name): |
| tr.add_test_class(self.base_mock_test_config, |
| integration_test.IntegrationTest) |
| |
| def test_run_no_tests(self): |
| tr = test_runner.TestRunner(self.log_dir, self.test_bed_name) |
| with self.assertRaisesRegex(test_runner.Error, 'No tests to execute.'): |
| tr.run() |
| |
| @mock.patch('mobly.test_runner._find_test_class', |
| return_value=type('SampleTest', (), {})) |
| @mock.patch('mobly.test_runner.config_parser.load_test_config_file', |
| return_value=[config_parser.TestRunConfig()]) |
| @mock.patch('mobly.test_runner.TestRunner', return_value=mock.MagicMock()) |
| def test_main_parse_args(self, mock_test_runner, mock_config, |
| mock_find_test): |
| test_runner.main(['-c', 'some/path/foo.yaml', '-b', 'hello']) |
| mock_config.assert_called_with('some/path/foo.yaml', None) |
| |
| @mock.patch('mobly.test_runner._find_test_class', |
| return_value=integration_test.IntegrationTest) |
| @mock.patch('sys.exit') |
| def test_main(self, mock_exit, mock_find_test): |
| tmp_file_path = os.path.join(self.tmp_dir, 'config.yml') |
| with io.open(tmp_file_path, 'w', encoding='utf-8') as f: |
| f.write(u""" |
| TestBeds: |
| # A test bed where adb will find Android devices. |
| - Name: SampleTestBed |
| Controllers: |
| MagicDevice: '*' |
| TestParams: |
| icecream: 42 |
| extra_param: 'haha' |
| """) |
| test_runner.main(['-c', tmp_file_path]) |
| mock_exit.assert_not_called() |
| |
| @mock.patch('mobly.test_runner._find_test_class', |
| return_value=integration_test.IntegrationTest) |
| @mock.patch('sys.exit') |
| def test_main_with_failures(self, mock_exit, mock_find_test): |
| tmp_file_path = os.path.join(self.tmp_dir, 'config.yml') |
| with io.open(tmp_file_path, 'w', encoding='utf-8') as f: |
| f.write(u""" |
| TestBeds: |
| # A test bed where adb will find Android devices. |
| - Name: SampleTestBed |
| Controllers: |
| MagicDevice: '*' |
| """) |
| test_runner.main(['-c', tmp_file_path]) |
| mock_exit.assert_called_once_with(1) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |