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