| #!/usr/bin/python2 |
| # Copyright (c) 2014 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. |
| |
| """Unittests for deploy_server_local.py.""" |
| |
| from __future__ import print_function |
| |
| import mock |
| import subprocess |
| import unittest |
| |
| import common |
| import deploy_server_local as dsl |
| |
| |
| class TestDeployServerLocal(unittest.TestCase): |
| """Test deploy_server_local with commands mocked out.""" |
| |
| orig_timer = dsl.SERVICE_STABILITY_TIMER |
| |
| PROD_STATUS = ('\x1b[1mproject autotest/ ' |
| ' \x1b[m\x1b[1mbranch prod\x1b[m\n') |
| |
| PROD_VERSIONS = '''\x1b[1mproject autotest/\x1b[m |
| /usr/local/autotest |
| ebb2182 |
| |
| \x1b[1mproject autotest/site_utils/autotest_private/\x1b[m |
| /usr/local/autotest/site_utils/autotest_private |
| 78b9626 |
| |
| \x1b[1mproject autotest/site_utils/autotest_tools/\x1b[m |
| /usr/local/autotest/site_utils/autotest_tools |
| a1598f7 |
| ''' |
| |
| |
| def setUp(self): |
| dsl.SERVICE_STABILITY_TIMER = 0.01 |
| |
| def tearDown(self): |
| dsl.SERVICE_STABILITY_TIMER = self.orig_timer |
| |
| def test_strip_terminal_codes(self): |
| """Test deploy_server_local.strip_terminal_codes.""" |
| # Leave format free lines alone. |
| result = dsl.strip_terminal_codes('') |
| self.assertEqual(result, '') |
| |
| result = dsl.strip_terminal_codes('This is normal text.') |
| self.assertEqual(result, 'This is normal text.') |
| |
| result = dsl.strip_terminal_codes('Line1\nLine2\n') |
| self.assertEqual(result, 'Line1\nLine2\n') |
| |
| result = dsl.strip_terminal_codes('Line1\nLine2\n') |
| self.assertEqual(result, 'Line1\nLine2\n') |
| |
| # Test cleaning lines with formatting. |
| result = dsl.strip_terminal_codes('\x1b[1m') |
| self.assertEqual(result, '') |
| |
| result = dsl.strip_terminal_codes('\x1b[m') |
| self.assertEqual(result, '') |
| |
| result = dsl.strip_terminal_codes('\x1b[1mm') |
| self.assertEqual(result, 'm') |
| |
| result = dsl.strip_terminal_codes(self.PROD_STATUS) |
| self.assertEqual(result, |
| 'project autotest/ branch prod\n') |
| |
| result = dsl.strip_terminal_codes(self.PROD_VERSIONS) |
| self.assertEqual(result, '''project autotest/ |
| /usr/local/autotest |
| ebb2182 |
| |
| project autotest/site_utils/autotest_private/ |
| /usr/local/autotest/site_utils/autotest_private |
| 78b9626 |
| |
| project autotest/site_utils/autotest_tools/ |
| /usr/local/autotest/site_utils/autotest_tools |
| a1598f7 |
| ''') |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_verify_repo_clean(self, run_cmd): |
| """Test deploy_server_local.verify_repo_clean. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| # If repo returns what we expect, exit cleanly. |
| run_cmd.return_value = 'nothing to commit (working directory clean)\n' |
| dsl.verify_repo_clean() |
| |
| # If repo contains any branches (even clean ones), raise. |
| run_cmd.return_value = self.PROD_STATUS |
| with self.assertRaises(dsl.DirtyTreeException): |
| dsl.verify_repo_clean() |
| |
| # If repo doesn't return what we expect, raise. |
| run_cmd.return_value = "That's a very dirty repo you've got." |
| with self.assertRaises(dsl.DirtyTreeException): |
| dsl.verify_repo_clean() |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_repo_versions(self, run_cmd): |
| """Test deploy_server_local.repo_versions. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| expected = { |
| 'autotest': |
| ('/usr/local/autotest', 'ebb2182'), |
| 'autotest/site_utils/autotest_private': |
| ('/usr/local/autotest/site_utils/autotest_private', '78b9626'), |
| 'autotest/site_utils/autotest_tools': |
| ('/usr/local/autotest/site_utils/autotest_tools', 'a1598f7'), |
| } |
| |
| run_cmd.return_value = self.PROD_VERSIONS |
| result = dsl.repo_versions() |
| self.assertEquals(result, expected) |
| |
| run_cmd.assert_called_with( |
| ['repo', 'forall', '-p', '-c', |
| 'pwd && git log -1 --format=%h']) |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_repo_sync_not_for_push_servers(self, run_cmd): |
| """Test deploy_server_local.repo_sync. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| dsl.repo_sync() |
| expect_cmds = [mock.call(['git', 'checkout', 'cros/prod'], stderr=-2)] |
| run_cmd.assert_has_calls(expect_cmds) |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_repo_sync_for_push_servers(self, run_cmd): |
| """Test deploy_server_local.repo_sync. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| dsl.repo_sync(update_push_servers=True) |
| expect_cmds = [mock.call(['git', 'checkout', 'cros/master'], stderr=-2)] |
| run_cmd.assert_has_calls(expect_cmds) |
| |
| def test_discover_commands(self): |
| """Test deploy_server_local.discover_update_commands and |
| discover_restart_services.""" |
| # It should always be a list, and should always be callable in |
| # any local environment, though the result will vary. |
| result = dsl.discover_update_commands() |
| self.assertIsInstance(result, list) |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_update_command(self, run_cmd): |
| """Test deploy_server_local.update_command. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| # Call with a bad command name. |
| with self.assertRaises(dsl.UnknownCommandException): |
| dsl.update_command('Unknown Command') |
| self.assertFalse(run_cmd.called) |
| |
| # Call with a couple valid command names. |
| dsl.update_command('apache') |
| run_cmd.assert_called_with('sudo service apache2 reload', shell=True, |
| cwd=common.autotest_dir, |
| stderr=subprocess.STDOUT) |
| |
| dsl.update_command('build_externals') |
| expected_cmd = './utils/build_externals.py' |
| run_cmd.assert_called_with(expected_cmd, shell=True, |
| cwd=common.autotest_dir, |
| stderr=subprocess.STDOUT) |
| |
| # Test a failed command. |
| failure = subprocess.CalledProcessError(10, expected_cmd, 'output') |
| |
| run_cmd.side_effect = failure |
| with self.assertRaises(subprocess.CalledProcessError) as unstable: |
| dsl.update_command('build_externals') |
| |
| @mock.patch('subprocess.check_call', autospec=True) |
| def test_restart_service(self, run_cmd): |
| """Test deploy_server_local.restart_service. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| # Standard call. |
| dsl.restart_service('foobar') |
| run_cmd.assert_called_with(['sudo', 'service', 'foobar', 'restart'], |
| stderr=-2) |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_restart_status(self, run_cmd): |
| """Test deploy_server_local.service_status. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| # Standard call. |
| dsl.service_status('foobar') |
| run_cmd.assert_called_with(['sudo', 'service', 'foobar', 'status']) |
| |
| @mock.patch.object(dsl, 'restart_service', autospec=True) |
| def _test_restart_services(self, service_results, _restart): |
| """Helper for testing restart_services. |
| |
| @param service_results: {'service_name': ['status_1', 'status_2']} |
| """ |
| # each call to service_status should return the next status value for |
| # that service. |
| with mock.patch.object(dsl, 'service_status', autospec=True, |
| side_effect=lambda n: service_results[n].pop(0)): |
| dsl.restart_services(service_results.keys()) |
| |
| def test_restart_services(self): |
| """Test deploy_server_local.restart_services.""" |
| dsl.HOSTNAME = 'test_server' |
| single_stable = {'foo': ['status_ok', 'status_ok']} |
| double_stable = {'foo': ['status_a', 'status_a'], |
| 'bar': ['status_b', 'status_b']} |
| |
| # Verify we can handle stable services. |
| self._test_restart_services(single_stable) |
| self._test_restart_services(double_stable) |
| |
| single_unstable = {'foo': ['status_ok', 'status_not_ok']} |
| triple_unstable = {'foo': ['status_a', 'status_a'], |
| 'bar': ['status_b', 'status_b_not_ok'], |
| 'joe': ['status_c', 'status_c_not_ok']} |
| |
| # Verify we can handle unstable services and report the right failures. |
| with self.assertRaises(dsl.UnstableServices) as unstable: |
| self._test_restart_services(single_unstable) |
| self.assertEqual(unstable.exception.args[0], |
| "test_server service restart failed: ['foo']") |
| |
| with self.assertRaises(dsl.UnstableServices) as unstable: |
| self._test_restart_services(triple_unstable) |
| self.assertEqual(unstable.exception.args[0], |
| "test_server service restart failed: ['bar', 'joe']") |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_report_changes_no_update(self, run_cmd): |
| """Test deploy_server_local.report_changes. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| |
| before = { |
| 'autotest': ('/usr/local/autotest', 'auto_before'), |
| 'autotest_private': ('/dir/autotest_private', '78b9626'), |
| 'other': ('/fake/unchanged', 'constant_hash'), |
| } |
| |
| run_cmd.return_value = 'hash1 Fix change.\nhash2 Bad change.\n' |
| |
| result = dsl.report_changes(before, None) |
| |
| self.assertEqual(result, """autotest: auto_before |
| autotest_private: 78b9626 |
| other: constant_hash |
| """) |
| |
| self.assertFalse(run_cmd.called) |
| |
| @mock.patch('subprocess.check_output', autospec=True) |
| def test_report_changes_diff(self, run_cmd): |
| """Test deploy_server_local.report_changes. |
| |
| @param run_cmd: Mock of subprocess call used. |
| """ |
| |
| before = { |
| 'autotest': ('/usr/local/autotest', 'auto_before'), |
| 'autotest_private': ('/dir/autotest_private', '78b9626'), |
| 'other': ('/fake/unchanged', 'constant_hash'), |
| } |
| |
| after = { |
| 'autotest': ('/usr/local/autotest', 'auto_after'), |
| 'autotest_tools': ('/dir/autotest_tools', 'a1598f7'), |
| 'other': ('/fake/unchanged', 'constant_hash'), |
| } |
| |
| run_cmd.return_value = 'hash1 Fix change.\nhash2 Bad change.\n' |
| |
| result = dsl.report_changes(before, after) |
| |
| self.assertEqual(result, """autotest: |
| hash1 Fix change. |
| hash2 Bad change. |
| |
| autotest_private: |
| Removed. |
| |
| autotest_tools: |
| Added. |
| |
| other: |
| No Change. |
| """) |
| |
| run_cmd.assert_called_with( |
| ['git', 'log', 'auto_before..auto_after', '--oneline'], |
| cwd='/usr/local/autotest', stderr=subprocess.STDOUT) |
| |
| def test_parse_arguments(self): |
| """Test deploy_server_local.parse_arguments.""" |
| # No arguments. |
| results = dsl.parse_arguments([]) |
| self.assertDictContainsSubset( |
| {'verify': True, 'update': True, 'actions': True, |
| 'report': True, 'dryrun': False, 'update_push_servers': False}, |
| vars(results)) |
| |
| # Update test_push servers. |
| results = dsl.parse_arguments(['--update_push_servers']) |
| self.assertDictContainsSubset( |
| {'verify': True, 'update': True, 'actions': True, |
| 'report': True, 'dryrun': False, 'update_push_servers': True}, |
| vars(results)) |
| |
| # Dryrun. |
| results = dsl.parse_arguments(['--dryrun']) |
| self.assertDictContainsSubset( |
| {'verify': False, 'update': False, 'actions': True, |
| 'report': True, 'dryrun': True, 'update_push_servers': False}, |
| vars(results)) |
| |
| # Restart only. |
| results = dsl.parse_arguments(['--actions-only']) |
| self.assertDictContainsSubset( |
| {'verify': False, 'update': False, 'actions': True, |
| 'report': False, 'dryrun': False, |
| 'update_push_servers': False}, |
| vars(results)) |
| |
| # All skip arguments. |
| results = dsl.parse_arguments(['--skip-verify', '--skip-update', |
| '--skip-actions', '--skip-report']) |
| self.assertDictContainsSubset( |
| {'verify': False, 'update': False, 'actions': False, |
| 'report': False, 'dryrun': False, |
| 'update_push_servers': False}, |
| vars(results)) |
| |
| # All arguments. |
| results = dsl.parse_arguments(['--skip-verify', '--skip-update', |
| '--skip-actions', '--skip-report', |
| '--actions-only', '--dryrun', |
| '--update_push_servers']) |
| self.assertDictContainsSubset( |
| {'verify': False, 'update': False, 'actions': False, |
| 'report': False, 'dryrun': True, 'update_push_servers': True}, |
| vars(results)) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |