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