experiment_runner_unittest.py revision e7c6fbf9cfd5f45dda21f2c4742bf5a62589d2ed
1#!/usr/bin/python2 2# 3# Copyright 2014 Google Inc. All Rights Reserved 4"""Tests for the experiment runner module.""" 5 6from __future__ import print_function 7 8import StringIO 9import getpass 10import os 11 12import mock 13import unittest 14 15import experiment_runner 16import experiment_status 17import machine_manager 18import config 19import test_flag 20from experiment_file import ExperimentFile 21from experiment_factory import ExperimentFactory 22from results_report import TextResultsReport 23from results_report import HTMLResultsReport 24from results_cache import Result 25 26from cros_utils import command_executer 27from cros_utils.email_sender import EmailSender 28from cros_utils.file_utils import FileUtils 29 30EXPERIMENT_FILE_1 = """ 31 board: parrot 32 remote: chromeos-parrot1.cros chromreos-parrot2.cros 33 34 benchmark: kraken { 35 suite: telemetry_Crosperf 36 iterations: 3 37 } 38 39 image1 { 40 chromeos_root: /usr/local/google/chromeos 41 chromeos_image: /usr/local/google/chromeos/src/build/images/parrot/latest/cros_image1.bin 42 } 43 44 image2 { 45 chromeos_image: /usr/local/google/chromeos/src/build/imaages/parrot/latest/cros_image2.bin 46 } 47 """ 48 49 50class FakeLogger(object): 51 """Fake logger for tests.""" 52 53 def __init__(self): 54 self.LogOutputCount = 0 55 self.LogErrorCount = 0 56 self.output_msgs = [] 57 self.error_msgs = [] 58 self.dot_count = 0 59 self.LogStartDotsCount = 0 60 self.LogEndDotsCount = 0 61 self.LogAppendDotCount = 0 62 63 def LogOutput(self, msg): 64 self.LogOutputCount += 1 65 self.output_msgs.append(msg) 66 67 def LogError(self, msg): 68 self.LogErrorCount += 1 69 self.error_msgs.append(msg) 70 71 def LogStartDots(self): 72 self.LogStartDotsCount += 1 73 self.dot_count += 1 74 75 def LogAppendDot(self): 76 self.LogAppendDotCount += 1 77 self.dot_count += 1 78 79 def LogEndDots(self): 80 self.LogEndDotsCount += 1 81 82 def Reset(self): 83 self.LogOutputCount = 0 84 self.LogErrorCount = 0 85 self.output_msgs = [] 86 self.error_msgs = [] 87 self.dot_count = 0 88 self.LogStartDotsCount = 0 89 self.LogEndDotsCount = 0 90 self.LogAppendDotCount = 0 91 92 93class ExperimentRunnerTest(unittest.TestCase): 94 """Test for experiment runner class.""" 95 96 run_counter = 0 97 mock_logger = FakeLogger() 98 mock_cmd_exec = mock.Mock(spec=command_executer.CommandExecuter) 99 100 def make_fake_experiment(self): 101 test_flag.SetTestMode(True) 102 experiment_file = ExperimentFile(StringIO.StringIO(EXPERIMENT_FILE_1)) 103 experiment = ExperimentFactory().GetExperiment(experiment_file, 104 working_directory='', 105 log_dir='') 106 return experiment 107 108 @mock.patch.object(machine_manager.MachineManager, 'AddMachine') 109 @mock.patch.object(os.path, 'isfile') 110 def setUp(self, mock_isfile, _mock_addmachine): 111 mock_isfile.return_value = True 112 self.exp = self.make_fake_experiment() 113 114 def test_init(self): 115 er = experiment_runner.ExperimentRunner(self.exp, 116 json_report=False, 117 using_schedv2=False, 118 log=self.mock_logger, 119 cmd_exec=self.mock_cmd_exec) 120 self.assertFalse(er._terminated) 121 self.assertEqual(er.STATUS_TIME_DELAY, 10) 122 123 self.exp.log_level = 'verbose' 124 er = experiment_runner.ExperimentRunner(self.exp, 125 json_report=False, 126 using_schedv2=False, 127 log=self.mock_logger, 128 cmd_exec=self.mock_cmd_exec) 129 self.assertEqual(er.STATUS_TIME_DELAY, 30) 130 131 @mock.patch.object(experiment_status.ExperimentStatus, 'GetStatusString') 132 @mock.patch.object(experiment_status.ExperimentStatus, 'GetProgressString') 133 def test_run(self, mock_progress_string, mock_status_string): 134 135 self.run_count = 0 136 self.is_complete_count = 0 137 138 def reset(): 139 self.run_count = 0 140 self.is_complete_count = 0 141 142 def FakeRun(): 143 self.run_count += 1 144 return 0 145 146 def FakeIsComplete(): 147 self.is_complete_count += 1 148 if self.is_complete_count < 3: 149 return False 150 else: 151 return True 152 153 self.mock_logger.Reset() 154 self.exp.Run = FakeRun 155 self.exp.IsComplete = FakeIsComplete 156 157 # Test 1: log_level == "quiet" 158 self.exp.log_level = 'quiet' 159 er = experiment_runner.ExperimentRunner(self.exp, 160 json_report=False, 161 using_schedv2=False, 162 log=self.mock_logger, 163 cmd_exec=self.mock_cmd_exec) 164 er.STATUS_TIME_DELAY = 2 165 mock_status_string.return_value = 'Fake status string' 166 er._Run(self.exp) 167 self.assertEqual(self.run_count, 1) 168 self.assertTrue(self.is_complete_count > 0) 169 self.assertEqual(self.mock_logger.LogStartDotsCount, 1) 170 self.assertEqual(self.mock_logger.LogAppendDotCount, 1) 171 self.assertEqual(self.mock_logger.LogEndDotsCount, 1) 172 self.assertEqual(self.mock_logger.dot_count, 2) 173 self.assertEqual(mock_progress_string.call_count, 0) 174 self.assertEqual(mock_status_string.call_count, 2) 175 self.assertEqual(self.mock_logger.output_msgs, 176 ['==============================', 'Fake status string', 177 '==============================']) 178 self.assertEqual(len(self.mock_logger.error_msgs), 0) 179 180 # Test 2: log_level == "average" 181 self.mock_logger.Reset() 182 reset() 183 self.exp.log_level = 'average' 184 mock_status_string.call_count = 0 185 er = experiment_runner.ExperimentRunner(self.exp, 186 json_report=False, 187 using_schedv2=False, 188 log=self.mock_logger, 189 cmd_exec=self.mock_cmd_exec) 190 er.STATUS_TIME_DELAY = 2 191 mock_status_string.return_value = 'Fake status string' 192 er._Run(self.exp) 193 self.assertEqual(self.run_count, 1) 194 self.assertTrue(self.is_complete_count > 0) 195 self.assertEqual(self.mock_logger.LogStartDotsCount, 1) 196 self.assertEqual(self.mock_logger.LogAppendDotCount, 1) 197 self.assertEqual(self.mock_logger.LogEndDotsCount, 1) 198 self.assertEqual(self.mock_logger.dot_count, 2) 199 self.assertEqual(mock_progress_string.call_count, 0) 200 self.assertEqual(mock_status_string.call_count, 2) 201 self.assertEqual(self.mock_logger.output_msgs, 202 ['==============================', 'Fake status string', 203 '==============================']) 204 self.assertEqual(len(self.mock_logger.error_msgs), 0) 205 206 # Test 3: log_level == "verbose" 207 self.mock_logger.Reset() 208 reset() 209 self.exp.log_level = 'verbose' 210 mock_status_string.call_count = 0 211 er = experiment_runner.ExperimentRunner(self.exp, 212 json_report=False, 213 using_schedv2=False, 214 log=self.mock_logger, 215 cmd_exec=self.mock_cmd_exec) 216 er.STATUS_TIME_DELAY = 2 217 mock_status_string.return_value = 'Fake status string' 218 mock_progress_string.return_value = 'Fake progress string' 219 er._Run(self.exp) 220 self.assertEqual(self.run_count, 1) 221 self.assertTrue(self.is_complete_count > 0) 222 self.assertEqual(self.mock_logger.LogStartDotsCount, 0) 223 self.assertEqual(self.mock_logger.LogAppendDotCount, 0) 224 self.assertEqual(self.mock_logger.LogEndDotsCount, 0) 225 self.assertEqual(self.mock_logger.dot_count, 0) 226 self.assertEqual(mock_progress_string.call_count, 2) 227 self.assertEqual(mock_status_string.call_count, 2) 228 self.assertEqual(self.mock_logger.output_msgs, 229 ['==============================', 'Fake progress string', 230 'Fake status string', '==============================', 231 '==============================', 'Fake progress string', 232 'Fake status string', '==============================']) 233 self.assertEqual(len(self.mock_logger.error_msgs), 0) 234 235 @mock.patch.object(TextResultsReport, 'GetReport') 236 def test_print_table(self, mock_report): 237 self.mock_logger.Reset() 238 mock_report.return_value = 'This is a fake experiment report.' 239 er = experiment_runner.ExperimentRunner(self.exp, 240 json_report=False, 241 using_schedv2=False, 242 log=self.mock_logger, 243 cmd_exec=self.mock_cmd_exec) 244 er._PrintTable(self.exp) 245 self.assertEqual(mock_report.call_count, 1) 246 self.assertEqual(self.mock_logger.output_msgs, 247 ['This is a fake experiment report.']) 248 249 @mock.patch.object(HTMLResultsReport, 'GetReport') 250 @mock.patch.object(TextResultsReport, 'GetReport') 251 @mock.patch.object(EmailSender, 'Attachment') 252 @mock.patch.object(EmailSender, 'SendEmail') 253 @mock.patch.object(getpass, 'getuser') 254 def test_email(self, mock_getuser, mock_emailer, mock_attachment, 255 mock_text_report, mock_html_report): 256 257 mock_getuser.return_value = 'john.smith@google.com' 258 mock_text_report.return_value = 'This is a fake text report.' 259 mock_html_report.return_value = 'This is a fake html report.' 260 261 self.mock_logger.Reset() 262 config.AddConfig('no_email', True) 263 self.exp.email_to = ['jane.doe@google.com'] 264 er = experiment_runner.ExperimentRunner(self.exp, 265 json_report=False, 266 using_schedv2=False, 267 log=self.mock_logger, 268 cmd_exec=self.mock_cmd_exec) 269 # Test 1. Config:no_email; exp.email_to set ==> no email sent 270 er._Email(self.exp) 271 self.assertEqual(mock_getuser.call_count, 0) 272 self.assertEqual(mock_emailer.call_count, 0) 273 self.assertEqual(mock_attachment.call_count, 0) 274 self.assertEqual(mock_text_report.call_count, 0) 275 self.assertEqual(mock_html_report.call_count, 0) 276 277 # Test 2. Config: email. exp.email_to set; cache hit. => send email 278 self.mock_logger.Reset() 279 config.AddConfig('no_email', False) 280 for r in self.exp.benchmark_runs: 281 r.cache_hit = True 282 er._Email(self.exp) 283 self.assertEqual(mock_getuser.call_count, 1) 284 self.assertEqual(mock_emailer.call_count, 1) 285 self.assertEqual(mock_attachment.call_count, 1) 286 self.assertEqual(mock_text_report.call_count, 1) 287 self.assertEqual(mock_html_report.call_count, 1) 288 self.assertEqual(len(mock_emailer.call_args), 2) 289 self.assertEqual(mock_emailer.call_args[0], 290 (['jane.doe@google.com', 'john.smith@google.com'], 291 ': image1 vs. image2', 292 "<pre style='font-size: 13px'>This is a fake text " 293 'report.\nResults are stored in _results.\n</pre>')) 294 self.assertTrue(type(mock_emailer.call_args[1]) is dict) 295 self.assertEqual(len(mock_emailer.call_args[1]), 2) 296 self.assertTrue('attachments' in mock_emailer.call_args[1].keys()) 297 self.assertEqual(mock_emailer.call_args[1]['msg_type'], 'html') 298 299 mock_attachment.assert_called_with('report.html', 300 'This is a fake html report.') 301 302 # Test 3. Config: email; exp.mail_to set; no cache hit. => send email 303 self.mock_logger.Reset() 304 mock_getuser.reset_mock() 305 mock_emailer.reset_mock() 306 mock_attachment.reset_mock() 307 mock_text_report.reset_mock() 308 mock_html_report.reset_mock() 309 config.AddConfig('no_email', False) 310 for r in self.exp.benchmark_runs: 311 r.cache_hit = False 312 er._Email(self.exp) 313 self.assertEqual(mock_getuser.call_count, 1) 314 self.assertEqual(mock_emailer.call_count, 1) 315 self.assertEqual(mock_attachment.call_count, 1) 316 self.assertEqual(mock_text_report.call_count, 1) 317 self.assertEqual(mock_html_report.call_count, 1) 318 self.assertEqual(len(mock_emailer.call_args), 2) 319 self.assertEqual(mock_emailer.call_args[0], 320 (['jane.doe@google.com', 'john.smith@google.com', 321 'john.smith@google.com'], ': image1 vs. image2', 322 "<pre style='font-size: 13px'>This is a fake text " 323 'report.\nResults are stored in _results.\n</pre>')) 324 self.assertTrue(type(mock_emailer.call_args[1]) is dict) 325 self.assertEqual(len(mock_emailer.call_args[1]), 2) 326 self.assertTrue('attachments' in mock_emailer.call_args[1].keys()) 327 self.assertEqual(mock_emailer.call_args[1]['msg_type'], 'html') 328 329 mock_attachment.assert_called_with('report.html', 330 'This is a fake html report.') 331 332 # Test 4. Config: email; exp.mail_to = None; no cache hit. => send email 333 self.mock_logger.Reset() 334 mock_getuser.reset_mock() 335 mock_emailer.reset_mock() 336 mock_attachment.reset_mock() 337 mock_text_report.reset_mock() 338 mock_html_report.reset_mock() 339 self.exp.email_to = [] 340 er._Email(self.exp) 341 self.assertEqual(mock_getuser.call_count, 1) 342 self.assertEqual(mock_emailer.call_count, 1) 343 self.assertEqual(mock_attachment.call_count, 1) 344 self.assertEqual(mock_text_report.call_count, 1) 345 self.assertEqual(mock_html_report.call_count, 1) 346 self.assertEqual(len(mock_emailer.call_args), 2) 347 self.assertEqual(mock_emailer.call_args[0], 348 (['john.smith@google.com'], ': image1 vs. image2', 349 "<pre style='font-size: 13px'>This is a fake text " 350 'report.\nResults are stored in _results.\n</pre>')) 351 self.assertTrue(type(mock_emailer.call_args[1]) is dict) 352 self.assertEqual(len(mock_emailer.call_args[1]), 2) 353 self.assertTrue('attachments' in mock_emailer.call_args[1].keys()) 354 self.assertEqual(mock_emailer.call_args[1]['msg_type'], 'html') 355 356 mock_attachment.assert_called_with('report.html', 357 'This is a fake html report.') 358 359 # Test 5. Config: email; exp.mail_to = None; cache hit => no email sent 360 self.mock_logger.Reset() 361 mock_getuser.reset_mock() 362 mock_emailer.reset_mock() 363 mock_attachment.reset_mock() 364 mock_text_report.reset_mock() 365 mock_html_report.reset_mock() 366 for r in self.exp.benchmark_runs: 367 r.cache_hit = True 368 er._Email(self.exp) 369 self.assertEqual(mock_getuser.call_count, 0) 370 self.assertEqual(mock_emailer.call_count, 0) 371 self.assertEqual(mock_attachment.call_count, 0) 372 self.assertEqual(mock_text_report.call_count, 0) 373 self.assertEqual(mock_html_report.call_count, 0) 374 375 @mock.patch.object(FileUtils, 'RmDir') 376 @mock.patch.object(FileUtils, 'MkDirP') 377 @mock.patch.object(FileUtils, 'WriteFile') 378 @mock.patch.object(HTMLResultsReport, 'GetReport') 379 @mock.patch.object(TextResultsReport, 'GetReport') 380 @mock.patch.object(Result, 'CopyResultsTo') 381 @mock.patch.object(Result, 'CleanUp') 382 def test_store_results(self, mock_cleanup, mock_copy, _mock_text_report, 383 mock_report, mock_writefile, mock_mkdir, mock_rmdir): 384 385 self.mock_logger.Reset() 386 self.exp.results_directory = '/usr/local/crosperf-results' 387 bench_run = self.exp.benchmark_runs[5] 388 bench_path = '/usr/local/crosperf-results/' + filter(str.isalnum, 389 bench_run.name) 390 self.assertEqual(len(self.exp.benchmark_runs), 6) 391 392 er = experiment_runner.ExperimentRunner(self.exp, 393 json_report=False, 394 using_schedv2=False, 395 log=self.mock_logger, 396 cmd_exec=self.mock_cmd_exec) 397 398 # Test 1. Make sure nothing is done if _terminated is true. 399 er._terminated = True 400 er._StoreResults(self.exp) 401 self.assertEqual(mock_cleanup.call_count, 0) 402 self.assertEqual(mock_copy.call_count, 0) 403 self.assertEqual(mock_report.call_count, 0) 404 self.assertEqual(mock_writefile.call_count, 0) 405 self.assertEqual(mock_mkdir.call_count, 0) 406 self.assertEqual(mock_rmdir.call_count, 0) 407 self.assertEqual(self.mock_logger.LogOutputCount, 0) 408 409 # Test 2. _terminated is false; everything works properly. 410 fake_result = Result(self.mock_logger, self.exp.labels[0], 'average', 411 'daisy1') 412 for r in self.exp.benchmark_runs: 413 r.result = fake_result 414 er._terminated = False 415 er._StoreResults(self.exp) 416 self.assertEqual(mock_cleanup.call_count, 6) 417 mock_cleanup.called_with(bench_run.benchmark.rm_chroot_tmp) 418 self.assertEqual(mock_copy.call_count, 6) 419 mock_copy.called_with(bench_path) 420 self.assertEqual(mock_writefile.call_count, 3) 421 self.assertEqual(len(mock_writefile.call_args_list), 3) 422 first_args = mock_writefile.call_args_list[0] 423 second_args = mock_writefile.call_args_list[1] 424 self.assertEqual(first_args[0][0], 425 '/usr/local/crosperf-results/experiment.exp') 426 self.assertEqual(second_args[0][0], 427 '/usr/local/crosperf-results/results.html') 428 self.assertEqual(mock_mkdir.call_count, 1) 429 mock_mkdir.called_with('/usr/local/crosperf-results') 430 self.assertEqual(mock_rmdir.call_count, 1) 431 mock_rmdir.called_with('/usr/local/crosperf-results') 432 self.assertEqual(self.mock_logger.LogOutputCount, 4) 433 self.assertEqual( 434 self.mock_logger.output_msgs, 435 ['Storing experiment file in /usr/local/crosperf-results.', 436 'Storing results report in /usr/local/crosperf-results.', 437 'Storing email message body in /usr/local/crosperf-results.', 438 'Storing results of each benchmark run.']) 439 440 441if __name__ == '__main__': 442 unittest.main() 443