| #!/usr/bin/python2 |
| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| # pylint: disable-msg=C0111 |
| |
| import os, unittest |
| import mox |
| import common |
| import subprocess |
| import shutil |
| import tempfile |
| import types |
| from autotest_lib.client.common_lib import control_data |
| from autotest_lib.server import utils |
| from autotest_lib.server.cros.dynamic_suite import constants |
| from autotest_lib.server.cros.dynamic_suite import control_file_getter |
| from autotest_lib.server.cros.dynamic_suite import suite as suite_module |
| from autotest_lib.site_utils import test_runner_utils |
| |
| |
| class StartsWithList(mox.Comparator): |
| def __init__(self, start_of_list): |
| """Mox comparator which returns True if the argument |
| to the mocked function is a list that begins with the elements |
| in start_of_list. |
| """ |
| self._lhs = start_of_list |
| |
| def equals(self, rhs): |
| if len(rhs)<len(self._lhs): |
| return False |
| for (x, y) in zip(self._lhs, rhs): |
| if x != y: |
| return False |
| return True |
| |
| |
| class ContainsSublist(mox.Comparator): |
| def __init__(self, sublist): |
| """Mox comparator which returns True if the argument |
| to the mocked function is a list that contains sublist |
| as a sub-list. |
| """ |
| self._sublist = sublist |
| |
| def equals(self, rhs): |
| n = len(self._sublist) |
| if len(rhs)<n: |
| return False |
| return any((self._sublist == rhs[i:i+n]) |
| for i in xrange(len(rhs) - n + 1)) |
| |
| class DummyJob(object): |
| def __init__(self, id=1): |
| self.id = id |
| |
| class TestRunnerUnittests(mox.MoxTestBase): |
| |
| def setUp(self): |
| mox.MoxTestBase.setUp(self) |
| |
| |
| def test_fetch_local_suite(self): |
| # Deferred until fetch_local_suite knows about non-local builds. |
| pass |
| |
| |
| def _results_directory_from_results_list(self, results_list): |
| """Generate a temp directory filled with provided test results. |
| |
| @param results_list: List of results, each result is a tuple of strings |
| (test_name, test_status_message). |
| @returns: Absolute path to the results directory. |
| """ |
| global_dir = tempfile.mkdtemp() |
| for index, (test_name, test_status_message) in enumerate(results_list): |
| dir_name = '-'.join(['results', |
| "%02.f" % (index + 1), |
| test_name]) |
| local_dir = os.path.join(global_dir, dir_name) |
| os.mkdir(local_dir) |
| os.mkdir('%s/debug' % local_dir) |
| with open("%s/status.log" % local_dir, mode='w+') as status: |
| status.write(test_status_message) |
| status.flush() |
| return global_dir |
| |
| |
| def test_handle_local_result_for_good_test(self): |
| getter = self.mox.CreateMock(control_file_getter.DevServerGetter) |
| getter.get_control_file_list(suite_name=mox.IgnoreArg()).AndReturn([]) |
| job = DummyJob() |
| test = self.mox.CreateMock(control_data.ControlData) |
| test.job_retries = 5 |
| self.mox.StubOutWithMock(test_runner_utils.LocalSuite, |
| '_retry_local_result') |
| self.mox.ReplayAll() |
| suite = test_runner_utils.LocalSuite([], "tag", [], None, getter, |
| job_retry=True) |
| suite._retry_handler = suite_module.RetryHandler({job.id: test}) |
| |
| #No calls, should not be retried |
| directory = self._results_directory_from_results_list([ |
| ("dummy_Good", "GOOD: nonexistent test completed successfully")]) |
| new_id = suite.handle_local_result( |
| job.id, directory, |
| lambda log_entry, log_in_subdir=False: None) |
| self.assertIsNone(new_id) |
| shutil.rmtree(directory) |
| |
| |
| def test_handle_local_result_for_bad_test(self): |
| getter = self.mox.CreateMock(control_file_getter.DevServerGetter) |
| getter.get_control_file_list(suite_name=mox.IgnoreArg()).AndReturn([]) |
| job = DummyJob() |
| test = self.mox.CreateMock(control_data.ControlData) |
| test.job_retries = 5 |
| self.mox.StubOutWithMock(test_runner_utils.LocalSuite, |
| '_retry_local_result') |
| test_runner_utils.LocalSuite._retry_local_result( |
| job.id, mox.IgnoreArg()).AndReturn(42) |
| self.mox.ReplayAll() |
| suite = test_runner_utils.LocalSuite([], "tag", [], None, getter, |
| job_retry=True) |
| suite._retry_handler = suite_module.RetryHandler({job.id: test}) |
| |
| directory = self._results_directory_from_results_list([ |
| ("dummy_Bad", "FAIL")]) |
| new_id = suite.handle_local_result( |
| job.id, directory, |
| lambda log_entry, log_in_subdir=False: None) |
| self.assertIsNotNone(new_id) |
| shutil.rmtree(directory) |
| |
| |
| def test_generate_report_status_code_success_with_retries(self): |
| global_dir = self._results_directory_from_results_list([ |
| ("dummy_Flaky", "FAIL"), |
| ("dummy_Flaky", "GOOD: nonexistent test completed successfully")]) |
| status_code = test_runner_utils.generate_report( |
| global_dir, just_status_code=True) |
| self.assertEquals(status_code, 0) |
| shutil.rmtree(global_dir) |
| |
| |
| def test_generate_report_status_code_failure_with_retries(self): |
| global_dir = self._results_directory_from_results_list([ |
| ("dummy_Good", "GOOD: nonexistent test completed successfully"), |
| ("dummy_Bad", "FAIL"), |
| ("dummy_Bad", "FAIL")]) |
| status_code = test_runner_utils.generate_report( |
| global_dir, just_status_code=True) |
| self.assertNotEquals(status_code, 0) |
| shutil.rmtree(global_dir) |
| |
| |
| def test_get_predicate_for_test_arg(self): |
| # Assert the type signature of get_predicate_for_test(...) |
| # Because control.test_utils_wrapper calls this function, |
| # it is imperative for backwards compatilbility that |
| # the return type of the tested function does not change. |
| tests = ['dummy_test', 'e:name_expression', 'f:expression', |
| 'suite:suitename'] |
| for test in tests: |
| pred, desc = test_runner_utils.get_predicate_for_test_arg(test) |
| self.assertTrue(isinstance(pred, types.FunctionType)) |
| self.assertTrue(isinstance(desc, str)) |
| |
| def test_run_job(self): |
| class Object(): |
| pass |
| |
| autotest_path = 'htap_tsetotua' |
| autoserv_command = os.path.join(autotest_path, 'server', 'autoserv') |
| remote = 'etomer' |
| results_dir = '/tmp/fakeresults' |
| fast_mode = False |
| job1_results_dir = '/tmp/fakeresults/results-1-gilbert' |
| job2_results_dir = '/tmp/fakeresults/results-2-sullivan' |
| args = 'matey' |
| expected_args_sublist = ['--args', args] |
| experimental_keyval = {constants.JOB_EXPERIMENTAL_KEY: False} |
| |
| # Create some dummy job objects. |
| job1 = Object() |
| job2 = Object() |
| setattr(job1, 'control_type', 'cLiEnT') |
| setattr(job1, 'control_file', 'c1') |
| setattr(job1, 'id', 1) |
| setattr(job1, 'name', 'gilbert') |
| setattr(job1, 'keyvals', experimental_keyval) |
| |
| setattr(job2, 'control_type', 'Server') |
| setattr(job2, 'control_file', 'c2') |
| setattr(job2, 'id', 2) |
| setattr(job2, 'name', 'sullivan') |
| setattr(job2, 'keyvals', experimental_keyval) |
| |
| id_digits = 1 |
| |
| # Stub out subprocess.Popen and wait calls. |
| # Make them expect correct arguments. |
| def fake_readline(): |
| return b'' |
| mock_process_1 = self.mox.CreateMock(subprocess.Popen) |
| mock_process_2 = self.mox.CreateMock(subprocess.Popen) |
| fake_stdout = self.mox.CreateMock(file) |
| fake_returncode = 0 |
| mock_process_1.stdout = fake_stdout |
| mock_process_1.returncode = fake_returncode |
| mock_process_2.stdout = fake_stdout |
| mock_process_2.returncode = fake_returncode |
| |
| self.mox.StubOutWithMock(os, 'makedirs') |
| self.mox.StubOutWithMock(utils, 'write_keyval') |
| self.mox.StubOutWithMock(subprocess, 'Popen') |
| |
| os.makedirs(job1_results_dir) |
| utils.write_keyval(job1_results_dir, experimental_keyval) |
| arglist_1 = [autoserv_command, '-p', '-r', job1_results_dir, |
| '-m', remote, '--no_console_prefix', '-l', 'gilbert', |
| '-c'] |
| subprocess.Popen(mox.And(StartsWithList(arglist_1), |
| ContainsSublist(expected_args_sublist)), |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT |
| ).AndReturn(mock_process_1) |
| mock_process_1.stdout.readline().AndReturn(b'') |
| mock_process_1.wait().AndReturn(0) |
| |
| os.makedirs(job2_results_dir) |
| utils.write_keyval(job2_results_dir, experimental_keyval) |
| arglist_2 = [autoserv_command, '-p', '-r', job2_results_dir, |
| '-m', remote, '--no_console_prefix', '-l', 'sullivan', |
| '-s'] |
| subprocess.Popen(mox.And(StartsWithList(arglist_2), |
| ContainsSublist(expected_args_sublist)), |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT |
| ).AndReturn(mock_process_2) |
| mock_process_2.stdout.readline().AndReturn(b'') |
| mock_process_2.wait().AndReturn(0) |
| |
| # Test run_job. |
| self.mox.ReplayAll() |
| code, job_res = test_runner_utils.run_job( |
| job1, remote, autotest_path,results_dir, fast_mode, id_digits, |
| 0, None, args) |
| self.assertEqual(job_res, job1_results_dir) |
| self.assertEqual(code, 0) |
| code, job_res = test_runner_utils.run_job( |
| job2, remote, autotest_path, results_dir, fast_mode, id_digits, |
| 0, None, args) |
| |
| self.assertEqual(job_res, job2_results_dir) |
| self.assertEqual(code, 0) |
| self.mox.ResetAll() |
| |
| def test_perform_local_run(self): |
| afe = test_runner_utils.setup_local_afe() |
| autotest_path = 'ottotest_path' |
| suite_name = 'sweet_name' |
| test_arg = 'suite:' + suite_name |
| remote = 'remoat' |
| build = 'bild' |
| board = 'bored' |
| fast_mode = False |
| suite_control_files = ['c1', 'c2', 'c3', 'c4'] |
| results_dir = '/tmp/test_that_results_fake' |
| id_digits = 1 |
| ssh_verbosity = 2 |
| ssh_options = '-F /dev/null -i /dev/null' |
| args = 'matey' |
| ignore_deps = False |
| |
| # Fake suite objects that will be returned by fetch_local_suite |
| class fake_suite(object): |
| def __init__(self, suite_control_files, hosts): |
| self._suite_control_files = suite_control_files |
| self._hosts = hosts |
| self._jobs = [] |
| self._jobs_to_tests = {} |
| self.retry_hack = True |
| |
| def schedule(self, *args, **kwargs): |
| for control_file in self._suite_control_files: |
| job_id = afe.create_job(control_file, hosts=self._hosts) |
| self._jobs.append(job_id) |
| self._jobs_to_tests[job_id] = control_file |
| |
| def handle_local_result(self, job_id, results_dir, logger, |
| **kwargs): |
| if results_dir == "success_directory": |
| return None |
| retries = True |
| if 'retries' in kwargs: |
| retries = kwargs['retries'] |
| if retries and self.retry_hack: |
| self.retry_hack = False |
| else: |
| return None |
| control_file = self._jobs_to_tests.get(job_id) |
| job_id = afe.create_job(control_file, hosts=self._hosts) |
| self._jobs.append(job_id) |
| self._jobs_to_tests[job_id] = control_file |
| return job_id |
| |
| @property |
| def jobs(self): |
| return self._jobs |
| |
| def test_name_from_job(self, id): |
| return "" |
| |
| # Mock out scheduling of suite and running of jobs. |
| self.mox.StubOutWithMock(test_runner_utils, 'fetch_local_suite') |
| test_runner_utils.fetch_local_suite(autotest_path, mox.IgnoreArg(), |
| afe, test_arg=test_arg, remote=remote, build=build, |
| board=board, results_directory=results_dir, |
| no_experimental=False, |
| ignore_deps=ignore_deps |
| ).AndReturn(fake_suite(suite_control_files, [remote])) |
| self.mox.StubOutWithMock(test_runner_utils, 'run_job') |
| self.mox.StubOutWithMock(test_runner_utils, 'run_provisioning_job') |
| self.mox.StubOutWithMock(test_runner_utils, '_auto_detect_labels') |
| |
| test_runner_utils._auto_detect_labels(afe, remote) |
| # Test perform_local_run. Enforce that run_provisioning_job, |
| # run_job and _auto_detect_labels are called correctly. |
| test_runner_utils.run_provisioning_job( |
| 'cros-version:' + build, remote, autotest_path, |
| results_dir, fast_mode, |
| ssh_verbosity, ssh_options, |
| False, False) |
| |
| for control_file in suite_control_files: |
| test_runner_utils.run_job( |
| mox.ContainsAttributeValue('control_file', control_file), |
| remote, |
| autotest_path, |
| results_dir, |
| fast_mode, |
| id_digits, |
| ssh_verbosity, |
| ssh_options, |
| mox.StrContains(args), |
| False, |
| False, |
| {}, |
| ).AndReturn((0, '/fake/dir')) |
| self.mox.ReplayAll() |
| test_runner_utils.perform_local_run( |
| afe, autotest_path, ['suite:'+suite_name], remote, fast_mode, |
| build=build, board=board, ignore_deps=False, |
| ssh_verbosity=ssh_verbosity, ssh_options=ssh_options, |
| args=args, results_directory=results_dir) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |