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