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