• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Module to deal with result cache."""
5
6from __future__ import print_function
7
8import glob
9import hashlib
10import os
11import pickle
12import re
13import tempfile
14import json
15import sys
16
17from cros_utils import command_executer
18from cros_utils import misc
19
20from image_checksummer import ImageChecksummer
21
22import results_report
23import test_flag
24
25SCRATCH_DIR = os.path.expanduser('~/cros_scratch')
26RESULTS_FILE = 'results.txt'
27MACHINE_FILE = 'machine.txt'
28AUTOTEST_TARBALL = 'autotest.tbz2'
29PERF_RESULTS_FILE = 'perf-results.txt'
30CACHE_KEYS_FILE = 'cache_keys.txt'
31
32
33class Result(object):
34  """Class for holding the results of a single test run.
35
36  This class manages what exactly is stored inside the cache without knowing
37  what the key of the cache is. For runs with perf, it stores perf.data,
38  perf.report, etc. The key generation is handled by the ResultsCache class.
39  """
40
41  def __init__(self, logger, label, log_level, machine, cmd_exec=None):
42    self.chromeos_root = label.chromeos_root
43    self._logger = logger
44    self.ce = cmd_exec or command_executer.GetCommandExecuter(
45        self._logger, log_level=log_level)
46    self.temp_dir = None
47    self.label = label
48    self.results_dir = None
49    self.log_level = log_level
50    self.machine = machine
51    self.perf_data_files = []
52    self.perf_report_files = []
53    self.results_file = []
54    self.chrome_version = ''
55    self.err = None
56    self.chroot_results_dir = ''
57    self.test_name = ''
58    self.keyvals = None
59    self.board = None
60    self.suite = None
61    self.retval = None
62    self.out = None
63
64  def CopyFilesTo(self, dest_dir, files_to_copy):
65    file_index = 0
66    for file_to_copy in files_to_copy:
67      if not os.path.isdir(dest_dir):
68        command = 'mkdir -p %s' % dest_dir
69        self.ce.RunCommand(command)
70      dest_file = os.path.join(dest_dir,
71                               ('%s.%s' % (os.path.basename(file_to_copy),
72                                           file_index)))
73      ret = self.ce.CopyFiles(file_to_copy, dest_file, recursive=False)
74      if ret:
75        raise IOError('Could not copy results file: %s' % file_to_copy)
76
77  def CopyResultsTo(self, dest_dir):
78    self.CopyFilesTo(dest_dir, self.perf_data_files)
79    self.CopyFilesTo(dest_dir, self.perf_report_files)
80    if len(self.perf_data_files) or len(self.perf_report_files):
81      self._logger.LogOutput('Perf results files stored in %s.' % dest_dir)
82
83  def GetNewKeyvals(self, keyvals_dict):
84    # Initialize 'units' dictionary.
85    units_dict = {}
86    for k in keyvals_dict:
87      units_dict[k] = ''
88    results_files = self.GetDataMeasurementsFiles()
89    for f in results_files:
90      # Make sure we can find the results file
91      if os.path.exists(f):
92        data_filename = f
93      else:
94        # Otherwise get the base filename and create the correct
95        # path for it.
96        _, f_base = misc.GetRoot(f)
97        data_filename = os.path.join(self.chromeos_root, 'chroot/tmp',
98                                     self.temp_dir, f_base)
99      if data_filename.find('.json') > 0:
100        raw_dict = dict()
101        if os.path.exists(data_filename):
102          with open(data_filename, 'r') as data_file:
103            raw_dict = json.load(data_file)
104
105        if 'charts' in raw_dict:
106          raw_dict = raw_dict['charts']
107        for k1 in raw_dict:
108          field_dict = raw_dict[k1]
109          for k2 in field_dict:
110            result_dict = field_dict[k2]
111            key = k1 + '__' + k2
112            if 'value' in result_dict:
113              keyvals_dict[key] = result_dict['value']
114            elif 'values' in result_dict:
115              values = result_dict['values']
116              if ('type' in result_dict and
117                  result_dict['type'] == 'list_of_scalar_values' and values and
118                  values != 'null'):
119                keyvals_dict[key] = sum(values) / float(len(values))
120              else:
121                keyvals_dict[key] = values
122            units_dict[key] = result_dict['units']
123      else:
124        if os.path.exists(data_filename):
125          with open(data_filename, 'r') as data_file:
126            lines = data_file.readlines()
127            for line in lines:
128              tmp_dict = json.loads(line)
129              graph_name = tmp_dict['graph']
130              graph_str = (graph_name + '__') if graph_name else ''
131              key = graph_str + tmp_dict['description']
132              keyvals_dict[key] = tmp_dict['value']
133              units_dict[key] = tmp_dict['units']
134
135    return keyvals_dict, units_dict
136
137  def AppendTelemetryUnits(self, keyvals_dict, units_dict):
138    """keyvals_dict is the dict of key-value used to generate Crosperf reports.
139
140    units_dict is a dictionary of the units for the return values in
141    keyvals_dict.  We need to associate the units with the return values,
142    for Telemetry tests, so that we can include the units in the reports.
143    This function takes each value in keyvals_dict, finds the corresponding
144    unit in the units_dict, and replaces the old value with a list of the
145    old value and the units.  This later gets properly parsed in the
146    ResultOrganizer class, for generating the reports.
147    """
148
149    results_dict = {}
150    for k in keyvals_dict:
151      # We don't want these lines in our reports; they add no useful data.
152      if k == '' or k == 'telemetry_Crosperf':
153        continue
154      val = keyvals_dict[k]
155      units = units_dict[k]
156      new_val = [val, units]
157      results_dict[k] = new_val
158    return results_dict
159
160  def GetKeyvals(self):
161    results_in_chroot = os.path.join(self.chromeos_root, 'chroot', 'tmp')
162    if not self.temp_dir:
163      self.temp_dir = tempfile.mkdtemp(dir=results_in_chroot)
164      command = 'cp -r {0}/* {1}'.format(self.results_dir, self.temp_dir)
165      self.ce.RunCommand(command, print_to_console=False)
166
167    command = ('python generate_test_report --no-color --csv %s' %
168               (os.path.join('/tmp', os.path.basename(self.temp_dir))))
169    _, out, _ = self.ce.ChrootRunCommandWOutput(
170        self.chromeos_root, command, print_to_console=False)
171    keyvals_dict = {}
172    tmp_dir_in_chroot = misc.GetInsideChrootPath(self.chromeos_root,
173                                                 self.temp_dir)
174    for line in out.splitlines():
175      tokens = re.split('=|,', line)
176      key = tokens[-2]
177      if key.startswith(tmp_dir_in_chroot):
178        key = key[len(tmp_dir_in_chroot) + 1:]
179      value = tokens[-1]
180      keyvals_dict[key] = value
181
182    # Check to see if there is a perf_measurements file and get the
183    # data from it if so.
184    keyvals_dict, units_dict = self.GetNewKeyvals(keyvals_dict)
185    if self.suite == 'telemetry_Crosperf':
186      # For telemtry_Crosperf results, append the units to the return
187      # results, for use in generating the reports.
188      keyvals_dict = self.AppendTelemetryUnits(keyvals_dict, units_dict)
189    return keyvals_dict
190
191  def GetResultsDir(self):
192    mo = re.search(r'Results placed in (\S+)', self.out)
193    if mo:
194      result = mo.group(1)
195      return result
196    raise RuntimeError('Could not find results directory.')
197
198  def FindFilesInResultsDir(self, find_args):
199    if not self.results_dir:
200      return None
201
202    command = 'find %s %s' % (self.results_dir, find_args)
203    ret, out, _ = self.ce.RunCommandWOutput(command, print_to_console=False)
204    if ret:
205      raise RuntimeError('Could not run find command!')
206    return out
207
208  def GetResultsFile(self):
209    return self.FindFilesInResultsDir('-name results-chart.json').splitlines()
210
211  def GetPerfDataFiles(self):
212    return self.FindFilesInResultsDir('-name perf.data').splitlines()
213
214  def GetPerfReportFiles(self):
215    return self.FindFilesInResultsDir('-name perf.data.report').splitlines()
216
217  def GetDataMeasurementsFiles(self):
218    result = self.FindFilesInResultsDir('-name perf_measurements').splitlines()
219    if not result:
220      result = \
221          self.FindFilesInResultsDir('-name results-chart.json').splitlines()
222    return result
223
224  def GeneratePerfReportFiles(self):
225    perf_report_files = []
226    for perf_data_file in self.perf_data_files:
227      # Generate a perf.report and store it side-by-side with the perf.data
228      # file.
229      chroot_perf_data_file = misc.GetInsideChrootPath(self.chromeos_root,
230                                                       perf_data_file)
231      perf_report_file = '%s.report' % perf_data_file
232      if os.path.exists(perf_report_file):
233        raise RuntimeError('Perf report file already exists: %s' %
234                           perf_report_file)
235      chroot_perf_report_file = misc.GetInsideChrootPath(self.chromeos_root,
236                                                         perf_report_file)
237      perf_path = os.path.join(self.chromeos_root, 'chroot', 'usr/bin/perf')
238
239      perf_file = '/usr/sbin/perf'
240      if os.path.exists(perf_path):
241        perf_file = '/usr/bin/perf'
242
243      command = ('%s report '
244                 '-n '
245                 '--symfs /build/%s '
246                 '--vmlinux /build/%s/usr/lib/debug/boot/vmlinux '
247                 '--kallsyms /build/%s/boot/System.map-* '
248                 '-i %s --stdio '
249                 '> %s' % (perf_file, self.board, self.board, self.board,
250                           chroot_perf_data_file, chroot_perf_report_file))
251      self.ce.ChrootRunCommand(self.chromeos_root, command)
252
253      # Add a keyval to the dictionary for the events captured.
254      perf_report_files.append(
255          misc.GetOutsideChrootPath(self.chromeos_root,
256                                    chroot_perf_report_file))
257    return perf_report_files
258
259  def GatherPerfResults(self):
260    report_id = 0
261    for perf_report_file in self.perf_report_files:
262      with open(perf_report_file, 'r') as f:
263        report_contents = f.read()
264        for group in re.findall(r'Events: (\S+) (\S+)', report_contents):
265          num_events = group[0]
266          event_name = group[1]
267          key = 'perf_%s_%s' % (report_id, event_name)
268          value = str(misc.UnitToNumber(num_events))
269          self.keyvals[key] = value
270
271  def PopulateFromRun(self, out, err, retval, test, suite):
272    self.board = self.label.board
273    self.out = out
274    self.err = err
275    self.retval = retval
276    self.test_name = test
277    self.suite = suite
278    self.chroot_results_dir = self.GetResultsDir()
279    self.results_dir = misc.GetOutsideChrootPath(self.chromeos_root,
280                                                 self.chroot_results_dir)
281    self.results_file = self.GetResultsFile()
282    self.perf_data_files = self.GetPerfDataFiles()
283    # Include all perf.report data in table.
284    self.perf_report_files = self.GeneratePerfReportFiles()
285    # TODO(asharif): Do something similar with perf stat.
286
287    # Grab keyvals from the directory.
288    self.ProcessResults()
289
290  def ProcessJsonResults(self):
291    # Open and parse the json results file generated by telemetry/test_that.
292    if not self.results_file:
293      raise IOError('No results file found.')
294    filename = self.results_file[0]
295    if not filename.endswith('.json'):
296      raise IOError('Attempt to call json on non-json file: %s' % filename)
297
298    if not os.path.exists(filename):
299      return {}
300
301    keyvals = {}
302    with open(filename, 'r') as f:
303      raw_dict = json.load(f)
304      if 'charts' in raw_dict:
305        raw_dict = raw_dict['charts']
306      for k, field_dict in raw_dict.iteritems():
307        for item in field_dict:
308          keyname = k + '__' + item
309          value_dict = field_dict[item]
310          if 'value' in value_dict:
311            result = value_dict['value']
312          elif 'values' in value_dict:
313            values = value_dict['values']
314            if not values:
315              continue
316            if ('type' in value_dict and
317                value_dict['type'] == 'list_of_scalar_values' and
318                values != 'null'):
319              result = sum(values) / float(len(values))
320            else:
321              result = values
322          units = value_dict['units']
323          new_value = [result, units]
324          keyvals[keyname] = new_value
325    return keyvals
326
327  def ProcessResults(self, use_cache=False):
328    # Note that this function doesn't know anything about whether there is a
329    # cache hit or miss. It should process results agnostic of the cache hit
330    # state.
331    if self.results_file and self.results_file[0].find(
332        'results-chart.json') != -1:
333      self.keyvals = self.ProcessJsonResults()
334    else:
335      if not use_cache:
336        print('\n ** WARNING **: Had to use deprecated output-method to '
337              'collect results.\n')
338      self.keyvals = self.GetKeyvals()
339    self.keyvals['retval'] = self.retval
340    # Generate report from all perf.data files.
341    # Now parse all perf report files and include them in keyvals.
342    self.GatherPerfResults()
343
344  def GetChromeVersionFromCache(self, cache_dir):
345    # Read chrome_version from keys file, if present.
346    chrome_version = ''
347    keys_file = os.path.join(cache_dir, CACHE_KEYS_FILE)
348    if os.path.exists(keys_file):
349      with open(keys_file, 'r') as f:
350        lines = f.readlines()
351        for l in lines:
352          if l.startswith('Google Chrome '):
353            chrome_version = l
354            if chrome_version.endswith('\n'):
355              chrome_version = chrome_version[:-1]
356            break
357    return chrome_version
358
359  def PopulateFromCacheDir(self, cache_dir, test, suite):
360    self.test_name = test
361    self.suite = suite
362    # Read in everything from the cache directory.
363    with open(os.path.join(cache_dir, RESULTS_FILE), 'r') as f:
364      self.out = pickle.load(f)
365      self.err = pickle.load(f)
366      self.retval = pickle.load(f)
367
368    # Untar the tarball to a temporary directory
369    self.temp_dir = tempfile.mkdtemp(
370        dir=os.path.join(self.chromeos_root, 'chroot', 'tmp'))
371
372    command = ('cd %s && tar xf %s' %
373               (self.temp_dir, os.path.join(cache_dir, AUTOTEST_TARBALL)))
374    ret = self.ce.RunCommand(command, print_to_console=False)
375    if ret:
376      raise RuntimeError('Could not untar cached tarball')
377    self.results_dir = self.temp_dir
378    self.results_file = self.GetDataMeasurementsFiles()
379    self.perf_data_files = self.GetPerfDataFiles()
380    self.perf_report_files = self.GetPerfReportFiles()
381    self.chrome_version = self.GetChromeVersionFromCache(cache_dir)
382    self.ProcessResults(use_cache=True)
383
384  def CleanUp(self, rm_chroot_tmp):
385    if rm_chroot_tmp and self.results_dir:
386      dirname, basename = misc.GetRoot(self.results_dir)
387      if basename.find('test_that_results_') != -1:
388        command = 'rm -rf %s' % self.results_dir
389      else:
390        command = 'rm -rf %s' % dirname
391      self.ce.RunCommand(command)
392    if self.temp_dir:
393      command = 'rm -rf %s' % self.temp_dir
394      self.ce.RunCommand(command)
395
396  def StoreToCacheDir(self, cache_dir, machine_manager, key_list):
397    # Create the dir if it doesn't exist.
398    temp_dir = tempfile.mkdtemp()
399
400    # Store to the temp directory.
401    with open(os.path.join(temp_dir, RESULTS_FILE), 'w') as f:
402      pickle.dump(self.out, f)
403      pickle.dump(self.err, f)
404      pickle.dump(self.retval, f)
405
406    if not test_flag.GetTestMode():
407      with open(os.path.join(temp_dir, CACHE_KEYS_FILE), 'w') as f:
408        f.write('%s\n' % self.label.name)
409        f.write('%s\n' % self.label.chrome_version)
410        f.write('%s\n' % self.machine.checksum_string)
411        for k in key_list:
412          f.write(k)
413          f.write('\n')
414
415    if self.results_dir:
416      tarball = os.path.join(temp_dir, AUTOTEST_TARBALL)
417      command = ('cd %s && '
418                 'tar '
419                 '--exclude=var/spool '
420                 '--exclude=var/log '
421                 '-cjf %s .' % (self.results_dir, tarball))
422      ret = self.ce.RunCommand(command)
423      if ret:
424        raise RuntimeError("Couldn't store autotest output directory.")
425    # Store machine info.
426    # TODO(asharif): Make machine_manager a singleton, and don't pass it into
427    # this function.
428    with open(os.path.join(temp_dir, MACHINE_FILE), 'w') as f:
429      f.write(machine_manager.machine_checksum_string[self.label.name])
430
431    if os.path.exists(cache_dir):
432      command = 'rm -rf {0}'.format(cache_dir)
433      self.ce.RunCommand(command)
434
435    command = 'mkdir -p {0} && '.format(os.path.dirname(cache_dir))
436    command += 'chmod g+x {0} && '.format(temp_dir)
437    command += 'mv {0} {1}'.format(temp_dir, cache_dir)
438    ret = self.ce.RunCommand(command)
439    if ret:
440      command = 'rm -rf {0}'.format(temp_dir)
441      self.ce.RunCommand(command)
442      raise RuntimeError('Could not move dir %s to dir %s' %
443                         (temp_dir, cache_dir))
444
445  @classmethod
446  def CreateFromRun(cls,
447                    logger,
448                    log_level,
449                    label,
450                    machine,
451                    out,
452                    err,
453                    retval,
454                    test,
455                    suite='telemetry_Crosperf'):
456    if suite == 'telemetry':
457      result = TelemetryResult(logger, label, log_level, machine)
458    else:
459      result = cls(logger, label, log_level, machine)
460    result.PopulateFromRun(out, err, retval, test, suite)
461    return result
462
463  @classmethod
464  def CreateFromCacheHit(cls,
465                         logger,
466                         log_level,
467                         label,
468                         machine,
469                         cache_dir,
470                         test,
471                         suite='telemetry_Crosperf'):
472    if suite == 'telemetry':
473      result = TelemetryResult(logger, label, log_level, machine)
474    else:
475      result = cls(logger, label, log_level, machine)
476    try:
477      result.PopulateFromCacheDir(cache_dir, test, suite)
478
479    except RuntimeError as e:
480      logger.LogError('Exception while using cache: %s' % e)
481      return None
482    return result
483
484
485class TelemetryResult(Result):
486  """Class to hold the results of a single Telemetry run."""
487
488  def __init__(self, logger, label, log_level, machine, cmd_exec=None):
489    super(TelemetryResult, self).__init__(logger, label, log_level, machine,
490                                          cmd_exec)
491
492  def PopulateFromRun(self, out, err, retval, test, suite):
493    self.out = out
494    self.err = err
495    self.retval = retval
496
497    self.ProcessResults()
498
499  # pylint: disable=arguments-differ
500  def ProcessResults(self):
501    # The output is:
502    # url,average_commit_time (ms),...
503    # www.google.com,33.4,21.2,...
504    # We need to convert to this format:
505    # {"www.google.com:average_commit_time (ms)": "33.4",
506    #  "www.google.com:...": "21.2"}
507    # Added note:  Occasionally the output comes back
508    # with "JSON.stringify(window.automation.GetResults())" on
509    # the first line, and then the rest of the output as
510    # described above.
511
512    lines = self.out.splitlines()
513    self.keyvals = {}
514
515    if lines:
516      if lines[0].startswith('JSON.stringify'):
517        lines = lines[1:]
518
519    if not lines:
520      return
521    labels = lines[0].split(',')
522    for line in lines[1:]:
523      fields = line.split(',')
524      if len(fields) != len(labels):
525        continue
526      for i in xrange(1, len(labels)):
527        key = '%s %s' % (fields[0], labels[i])
528        value = fields[i]
529        self.keyvals[key] = value
530    self.keyvals['retval'] = self.retval
531
532  def PopulateFromCacheDir(self, cache_dir, test, suite):
533    self.test_name = test
534    self.suite = suite
535    with open(os.path.join(cache_dir, RESULTS_FILE), 'r') as f:
536      self.out = pickle.load(f)
537      self.err = pickle.load(f)
538      self.retval = pickle.load(f)
539
540    self.chrome_version = \
541        super(TelemetryResult, self).GetChromeVersionFromCache(cache_dir)
542    self.ProcessResults()
543
544
545class CacheConditions(object):
546  """Various Cache condition values, for export."""
547
548  # Cache hit only if the result file exists.
549  CACHE_FILE_EXISTS = 0
550
551  # Cache hit if the checksum of cpuinfo and totalmem of
552  # the cached result and the new run match.
553  MACHINES_MATCH = 1
554
555  # Cache hit if the image checksum of the cached result and the new run match.
556  CHECKSUMS_MATCH = 2
557
558  # Cache hit only if the cached result was successful
559  RUN_SUCCEEDED = 3
560
561  # Never a cache hit.
562  FALSE = 4
563
564  # Cache hit if the image path matches the cached image path.
565  IMAGE_PATH_MATCH = 5
566
567  # Cache hit if the uuid of hard disk mataches the cached one
568
569  SAME_MACHINE_MATCH = 6
570
571
572class ResultsCache(object):
573  """Class to handle the cache for storing/retrieving test run results.
574
575  This class manages the key of the cached runs without worrying about what
576  is exactly stored (value). The value generation is handled by the Results
577  class.
578  """
579  CACHE_VERSION = 6
580
581  def __init__(self):
582    # Proper initialization happens in the Init function below.
583    self.chromeos_image = None
584    self.chromeos_root = None
585    self.test_name = None
586    self.iteration = None
587    self.test_args = None
588    self.profiler_args = None
589    self.board = None
590    self.cache_conditions = None
591    self.machine_manager = None
592    self.machine = None
593    self._logger = None
594    self.ce = None
595    self.label = None
596    self.share_cache = None
597    self.suite = None
598    self.log_level = None
599    self.show_all = None
600    self.run_local = None
601
602  def Init(self, chromeos_image, chromeos_root, test_name, iteration, test_args,
603           profiler_args, machine_manager, machine, board, cache_conditions,
604           logger_to_use, log_level, label, share_cache, suite,
605           show_all_results, run_local):
606    self.chromeos_image = chromeos_image
607    self.chromeos_root = chromeos_root
608    self.test_name = test_name
609    self.iteration = iteration
610    self.test_args = test_args
611    self.profiler_args = profiler_args
612    self.board = board
613    self.cache_conditions = cache_conditions
614    self.machine_manager = machine_manager
615    self.machine = machine
616    self._logger = logger_to_use
617    self.ce = command_executer.GetCommandExecuter(
618        self._logger, log_level=log_level)
619    self.label = label
620    self.share_cache = share_cache
621    self.suite = suite
622    self.log_level = log_level
623    self.show_all = show_all_results
624    self.run_local = run_local
625
626  def GetCacheDirForRead(self):
627    matching_dirs = []
628    for glob_path in self.FormCacheDir(self.GetCacheKeyList(True)):
629      matching_dirs += glob.glob(glob_path)
630
631    if matching_dirs:
632      # Cache file found.
633      return matching_dirs[0]
634    return None
635
636  def GetCacheDirForWrite(self, get_keylist=False):
637    cache_path = self.FormCacheDir(self.GetCacheKeyList(False))[0]
638    if get_keylist:
639      args_str = '%s_%s_%s' % (self.test_args, self.profiler_args,
640                               self.run_local)
641      version, image = results_report.ParseChromeosImage(
642          self.label.chromeos_image)
643      keylist = [
644          version, image, self.label.board, self.machine.name, self.test_name,
645          str(self.iteration), args_str
646      ]
647      return cache_path, keylist
648    return cache_path
649
650  def FormCacheDir(self, list_of_strings):
651    cache_key = ' '.join(list_of_strings)
652    cache_dir = misc.GetFilenameFromString(cache_key)
653    if self.label.cache_dir:
654      cache_home = os.path.abspath(os.path.expanduser(self.label.cache_dir))
655      cache_path = [os.path.join(cache_home, cache_dir)]
656    else:
657      cache_path = [os.path.join(SCRATCH_DIR, cache_dir)]
658
659    if len(self.share_cache):
660      for path in [x.strip() for x in self.share_cache.split(',')]:
661        if os.path.exists(path):
662          cache_path.append(os.path.join(path, cache_dir))
663        else:
664          self._logger.LogFatal('Unable to find shared cache: %s' % path)
665
666    return cache_path
667
668  def GetCacheKeyList(self, read):
669    if read and CacheConditions.MACHINES_MATCH not in self.cache_conditions:
670      machine_checksum = '*'
671    else:
672      machine_checksum = self.machine_manager.machine_checksum[self.label.name]
673    if read and CacheConditions.CHECKSUMS_MATCH not in self.cache_conditions:
674      checksum = '*'
675    elif self.label.image_type == 'trybot':
676      checksum = hashlib.md5(self.label.chromeos_image).hexdigest()
677    elif self.label.image_type == 'official':
678      checksum = '*'
679    else:
680      checksum = ImageChecksummer().Checksum(self.label, self.log_level)
681
682    if read and CacheConditions.IMAGE_PATH_MATCH not in self.cache_conditions:
683      image_path_checksum = '*'
684    else:
685      image_path_checksum = hashlib.md5(self.chromeos_image).hexdigest()
686
687    machine_id_checksum = ''
688    if read and CacheConditions.SAME_MACHINE_MATCH not in self.cache_conditions:
689      machine_id_checksum = '*'
690    else:
691      if self.machine and self.machine.name in self.label.remote:
692        machine_id_checksum = self.machine.machine_id_checksum
693      else:
694        for machine in self.machine_manager.GetMachines(self.label):
695          if machine.name == self.label.remote[0]:
696            machine_id_checksum = machine.machine_id_checksum
697            break
698
699    temp_test_args = '%s %s %s' % (self.test_args, self.profiler_args,
700                                   self.run_local)
701    test_args_checksum = hashlib.md5(temp_test_args).hexdigest()
702    return (image_path_checksum, self.test_name, str(self.iteration),
703            test_args_checksum, checksum, machine_checksum, machine_id_checksum,
704            str(self.CACHE_VERSION))
705
706  def ReadResult(self):
707    if CacheConditions.FALSE in self.cache_conditions:
708      cache_dir = self.GetCacheDirForWrite()
709      command = 'rm -rf %s' % (cache_dir,)
710      self.ce.RunCommand(command)
711      return None
712    cache_dir = self.GetCacheDirForRead()
713
714    if not cache_dir:
715      return None
716
717    if not os.path.isdir(cache_dir):
718      return None
719
720    if self.log_level == 'verbose':
721      self._logger.LogOutput('Trying to read from cache dir: %s' % cache_dir)
722    result = Result.CreateFromCacheHit(self._logger, self.log_level, self.label,
723                                       self.machine, cache_dir, self.test_name,
724                                       self.suite)
725    if not result:
726      return None
727
728    if (result.retval == 0 or
729        CacheConditions.RUN_SUCCEEDED not in self.cache_conditions):
730      return result
731
732    return None
733
734  def StoreResult(self, result):
735    cache_dir, keylist = self.GetCacheDirForWrite(get_keylist=True)
736    result.StoreToCacheDir(cache_dir, self.machine_manager, keylist)
737
738
739class MockResultsCache(ResultsCache):
740  """Class for mock testing, corresponding to ResultsCache class."""
741
742  def Init(self, *args):
743    pass
744
745  def ReadResult(self):
746    return None
747
748  def StoreResult(self, result):
749    pass
750
751
752class MockResult(Result):
753  """Class for mock testing, corresponding to Result class."""
754
755  def PopulateFromRun(self, out, err, retval, test, suite):
756    self.out = out
757    self.err = err
758    self.retval = retval
759