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