1#!/usr/bin/python 2# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Unittests for deploy_server_local.py.""" 7 8from __future__ import print_function 9 10import mock 11import subprocess 12import unittest 13 14import common 15import deploy_server_local as dsl 16 17 18class TestDeployServerLocal(unittest.TestCase): 19 """Test deploy_server_local with commands mocked out.""" 20 21 orig_timer = dsl.SERVICE_STABILITY_TIMER 22 23 PROD_STATUS = ('\x1b[1mproject autotest/ ' 24 ' \x1b[m\x1b[1mbranch prod\x1b[m\n') 25 26 PROD_VERSIONS = '''\x1b[1mproject autotest/\x1b[m 27/usr/local/autotest 28ebb2182 29 30\x1b[1mproject autotest/site_utils/autotest_private/\x1b[m 31/usr/local/autotest/site_utils/autotest_private 3278b9626 33 34\x1b[1mproject autotest/site_utils/autotest_tools/\x1b[m 35/usr/local/autotest/site_utils/autotest_tools 36a1598f7 37''' 38 39 40 def setUp(self): 41 dsl.SERVICE_STABILITY_TIMER = 0.01 42 43 def tearDown(self): 44 dsl.SERVICE_STABILITY_TIMER = self.orig_timer 45 46 def test_strip_terminal_codes(self): 47 """Test deploy_server_local.strip_terminal_codes.""" 48 # Leave format free lines alone. 49 result = dsl.strip_terminal_codes('') 50 self.assertEqual(result, '') 51 52 result = dsl.strip_terminal_codes('This is normal text.') 53 self.assertEqual(result, 'This is normal text.') 54 55 result = dsl.strip_terminal_codes('Line1\nLine2\n') 56 self.assertEqual(result, 'Line1\nLine2\n') 57 58 result = dsl.strip_terminal_codes('Line1\nLine2\n') 59 self.assertEqual(result, 'Line1\nLine2\n') 60 61 # Test cleaning lines with formatting. 62 result = dsl.strip_terminal_codes('\x1b[1m') 63 self.assertEqual(result, '') 64 65 result = dsl.strip_terminal_codes('\x1b[m') 66 self.assertEqual(result, '') 67 68 result = dsl.strip_terminal_codes('\x1b[1mm') 69 self.assertEqual(result, 'm') 70 71 result = dsl.strip_terminal_codes(self.PROD_STATUS) 72 self.assertEqual(result, 73 'project autotest/ branch prod\n') 74 75 result = dsl.strip_terminal_codes(self.PROD_VERSIONS) 76 self.assertEqual(result, '''project autotest/ 77/usr/local/autotest 78ebb2182 79 80project autotest/site_utils/autotest_private/ 81/usr/local/autotest/site_utils/autotest_private 8278b9626 83 84project autotest/site_utils/autotest_tools/ 85/usr/local/autotest/site_utils/autotest_tools 86a1598f7 87''') 88 89 @mock.patch('subprocess.check_output', autospec=True) 90 def test_verify_repo_clean(self, run_cmd): 91 """Test deploy_server_local.verify_repo_clean. 92 93 @param run_cmd: Mock of subprocess call used. 94 """ 95 # If repo returns what we expect, exit cleanly. 96 run_cmd.return_value = 'nothing to commit (working directory clean)\n' 97 dsl.verify_repo_clean() 98 99 # If repo contains any branches (even clean ones), raise. 100 run_cmd.return_value = self.PROD_STATUS 101 with self.assertRaises(dsl.DirtyTreeException): 102 dsl.verify_repo_clean() 103 104 # If repo doesn't return what we expect, raise. 105 run_cmd.return_value = "That's a very dirty repo you've got." 106 with self.assertRaises(dsl.DirtyTreeException): 107 dsl.verify_repo_clean() 108 109 @mock.patch('subprocess.check_output', autospec=True) 110 def test_repo_versions(self, run_cmd): 111 """Test deploy_server_local.repo_versions. 112 113 @param run_cmd: Mock of subprocess call used. 114 """ 115 expected = { 116 'autotest': 117 ('/usr/local/autotest', 'ebb2182'), 118 'autotest/site_utils/autotest_private': 119 ('/usr/local/autotest/site_utils/autotest_private', '78b9626'), 120 'autotest/site_utils/autotest_tools': 121 ('/usr/local/autotest/site_utils/autotest_tools', 'a1598f7'), 122 } 123 124 run_cmd.return_value = self.PROD_VERSIONS 125 result = dsl.repo_versions() 126 self.assertEquals(result, expected) 127 128 run_cmd.assert_called_with( 129 ['repo', 'forall', '-p', '-c', 130 'pwd && git log -1 --format=%h']) 131 132 @mock.patch('subprocess.check_output', autospec=True) 133 def test_repo_sync_not_for_push_servers(self, run_cmd): 134 """Test deploy_server_local.repo_sync. 135 136 @param run_cmd: Mock of subprocess call used. 137 """ 138 dsl.repo_sync() 139 expect_cmds = [mock.call(['git', 'checkout', 'cros/prod'], stderr=-2)] 140 run_cmd.assert_has_calls(expect_cmds) 141 142 @mock.patch('subprocess.check_output', autospec=True) 143 def test_repo_sync_for_push_servers(self, run_cmd): 144 """Test deploy_server_local.repo_sync. 145 146 @param run_cmd: Mock of subprocess call used. 147 """ 148 dsl.repo_sync(update_push_servers=True) 149 expect_cmds = [mock.call(['git', 'checkout', 'cros/master'], stderr=-2)] 150 run_cmd.assert_has_calls(expect_cmds) 151 152 def test_discover_commands(self): 153 """Test deploy_server_local.discover_update_commands and 154 discover_restart_services.""" 155 # It should always be a list, and should always be callable in 156 # any local environment, though the result will vary. 157 result = dsl.discover_update_commands() 158 self.assertIsInstance(result, list) 159 160 @mock.patch('subprocess.check_output', autospec=True) 161 def test_update_command(self, run_cmd): 162 """Test deploy_server_local.update_command. 163 164 @param run_cmd: Mock of subprocess call used. 165 """ 166 # Call with a bad command name. 167 with self.assertRaises(dsl.UnknownCommandException): 168 dsl.update_command('Unknown Command') 169 self.assertFalse(run_cmd.called) 170 171 # Call with a couple valid command names. 172 dsl.update_command('apache') 173 run_cmd.assert_called_with('sudo service apache2 reload', shell=True, 174 cwd=common.autotest_dir, 175 stderr=subprocess.STDOUT) 176 177 dsl.update_command('build_externals') 178 expected_cmd = './utils/build_externals.py' 179 run_cmd.assert_called_with(expected_cmd, shell=True, 180 cwd=common.autotest_dir, 181 stderr=subprocess.STDOUT) 182 183 # Test a failed command. 184 failure = subprocess.CalledProcessError(10, expected_cmd, 'output') 185 186 run_cmd.side_effect = failure 187 with self.assertRaises(subprocess.CalledProcessError) as unstable: 188 dsl.update_command('build_externals') 189 190 @mock.patch('subprocess.check_call', autospec=True) 191 def test_restart_service(self, run_cmd): 192 """Test deploy_server_local.restart_service. 193 194 @param run_cmd: Mock of subprocess call used. 195 """ 196 # Standard call. 197 dsl.restart_service('foobar') 198 run_cmd.assert_called_with(['sudo', 'service', 'foobar', 'restart'], 199 stderr=-2) 200 201 @mock.patch('subprocess.check_output', autospec=True) 202 def test_restart_status(self, run_cmd): 203 """Test deploy_server_local.service_status. 204 205 @param run_cmd: Mock of subprocess call used. 206 """ 207 # Standard call. 208 dsl.service_status('foobar') 209 run_cmd.assert_called_with(['sudo', 'service', 'foobar', 'status']) 210 211 @mock.patch.object(dsl, 'restart_service', autospec=True) 212 def _test_restart_services(self, service_results, _restart): 213 """Helper for testing restart_services. 214 215 @param service_results: {'service_name': ['status_1', 'status_2']} 216 """ 217 # each call to service_status should return the next status value for 218 # that service. 219 with mock.patch.object(dsl, 'service_status', autospec=True, 220 side_effect=lambda n: service_results[n].pop(0)): 221 dsl.restart_services(service_results.keys()) 222 223 def test_restart_services(self): 224 """Test deploy_server_local.restart_services.""" 225 dsl.HOSTNAME = 'test_server' 226 single_stable = {'foo': ['status_ok', 'status_ok']} 227 double_stable = {'foo': ['status_a', 'status_a'], 228 'bar': ['status_b', 'status_b']} 229 230 # Verify we can handle stable services. 231 self._test_restart_services(single_stable) 232 self._test_restart_services(double_stable) 233 234 single_unstable = {'foo': ['status_ok', 'status_not_ok']} 235 triple_unstable = {'foo': ['status_a', 'status_a'], 236 'bar': ['status_b', 'status_b_not_ok'], 237 'joe': ['status_c', 'status_c_not_ok']} 238 239 # Verify we can handle unstable services and report the right failures. 240 with self.assertRaises(dsl.UnstableServices) as unstable: 241 self._test_restart_services(single_unstable) 242 self.assertEqual(unstable.exception.args[0], 243 "test_server service restart failed: ['foo']") 244 245 with self.assertRaises(dsl.UnstableServices) as unstable: 246 self._test_restart_services(triple_unstable) 247 self.assertEqual(unstable.exception.args[0], 248 "test_server service restart failed: ['bar', 'joe']") 249 250 @mock.patch('subprocess.check_output', autospec=True) 251 def test_report_changes_no_update(self, run_cmd): 252 """Test deploy_server_local.report_changes. 253 254 @param run_cmd: Mock of subprocess call used. 255 """ 256 257 before = { 258 'autotest': ('/usr/local/autotest', 'auto_before'), 259 'autotest_private': ('/dir/autotest_private', '78b9626'), 260 'other': ('/fake/unchanged', 'constant_hash'), 261 } 262 263 run_cmd.return_value = 'hash1 Fix change.\nhash2 Bad change.\n' 264 265 result = dsl.report_changes(before, None) 266 267 self.assertEqual(result, """autotest: auto_before 268autotest_private: 78b9626 269other: constant_hash 270""") 271 272 self.assertFalse(run_cmd.called) 273 274 @mock.patch('subprocess.check_output', autospec=True) 275 def test_report_changes_diff(self, run_cmd): 276 """Test deploy_server_local.report_changes. 277 278 @param run_cmd: Mock of subprocess call used. 279 """ 280 281 before = { 282 'autotest': ('/usr/local/autotest', 'auto_before'), 283 'autotest_private': ('/dir/autotest_private', '78b9626'), 284 'other': ('/fake/unchanged', 'constant_hash'), 285 } 286 287 after = { 288 'autotest': ('/usr/local/autotest', 'auto_after'), 289 'autotest_tools': ('/dir/autotest_tools', 'a1598f7'), 290 'other': ('/fake/unchanged', 'constant_hash'), 291 } 292 293 run_cmd.return_value = 'hash1 Fix change.\nhash2 Bad change.\n' 294 295 result = dsl.report_changes(before, after) 296 297 self.assertEqual(result, """autotest: 298hash1 Fix change. 299hash2 Bad change. 300 301autotest_private: 302Removed. 303 304autotest_tools: 305Added. 306 307other: 308No Change. 309""") 310 311 run_cmd.assert_called_with( 312 ['git', 'log', 'auto_before..auto_after', '--oneline'], 313 cwd='/usr/local/autotest', stderr=subprocess.STDOUT) 314 315 def test_parse_arguments(self): 316 """Test deploy_server_local.parse_arguments.""" 317 # No arguments. 318 results = dsl.parse_arguments([]) 319 self.assertDictContainsSubset( 320 {'verify': True, 'update': True, 'actions': True, 321 'report': True, 'dryrun': False, 'update_push_servers': False}, 322 vars(results)) 323 324 # Update test_push servers. 325 results = dsl.parse_arguments(['--update_push_servers']) 326 self.assertDictContainsSubset( 327 {'verify': True, 'update': True, 'actions': True, 328 'report': True, 'dryrun': False, 'update_push_servers': True}, 329 vars(results)) 330 331 # Dryrun. 332 results = dsl.parse_arguments(['--dryrun']) 333 self.assertDictContainsSubset( 334 {'verify': False, 'update': False, 'actions': True, 335 'report': True, 'dryrun': True, 'update_push_servers': False}, 336 vars(results)) 337 338 # Restart only. 339 results = dsl.parse_arguments(['--actions-only']) 340 self.assertDictContainsSubset( 341 {'verify': False, 'update': False, 'actions': True, 342 'report': False, 'dryrun': False, 343 'update_push_servers': False}, 344 vars(results)) 345 346 # All skip arguments. 347 results = dsl.parse_arguments(['--skip-verify', '--skip-update', 348 '--skip-actions', '--skip-report']) 349 self.assertDictContainsSubset( 350 {'verify': False, 'update': False, 'actions': False, 351 'report': False, 'dryrun': False, 352 'update_push_servers': False}, 353 vars(results)) 354 355 # All arguments. 356 results = dsl.parse_arguments(['--skip-verify', '--skip-update', 357 '--skip-actions', '--skip-report', 358 '--actions-only', '--dryrun', 359 '--update_push_servers']) 360 self.assertDictContainsSubset( 361 {'verify': False, 'update': False, 'actions': False, 362 'report': False, 'dryrun': True, 'update_push_servers': True}, 363 vars(results)) 364 365 366if __name__ == '__main__': 367 unittest.main() 368