1# -*- Mode: python; tab-width: 8; indent-tabs-mode: nil -*- 2# vim: set ts=8 sts=4 et sw=4 tw=80: 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6from __future__ import absolute_import, print_function 7 8import json 9import os 10import re 11import utils 12 13KEY_XRE = '{xre}' 14DEFAULT_DURATION = 100.0 15 16 17class Whitelist: 18 # we need to find the root dir of the profile at runtime 19 PRE_PROFILE = '' 20 21 def __init__(self, test_name, paths, path_substitutions, 22 name_substitutions, event_sources=None, init_with=None): 23 self.test_name = test_name 24 self.listmap = init_with if init_with else {} 25 self.dependent_libs = self.load_dependent_libs() \ 26 if init_with and KEY_XRE in paths else {} 27 self.paths = paths 28 self.path_substitutions = path_substitutions 29 self.name_substitutions = name_substitutions 30 self.expected_event_sources = event_sources or [] 31 32 def load(self, filename): 33 if not self.load_dependent_libs(): 34 return False 35 36 try: 37 with open(filename, 'r') as fHandle: 38 temp = json.load(fHandle) 39 40 for whitelist_name in temp: 41 self.listmap[whitelist_name.lower()] = temp[whitelist_name] 42 43 except IOError as e: 44 print("%s: %s" % (e.filename, e.strerror)) 45 return False 46 return True 47 48 def sanitize_filename(self, filename): 49 filename = filename.lower() 50 filename.replace(' (x86)', '') 51 52 for path, subst in self.path_substitutions.iteritems(): 53 parts = filename.split(path) 54 if len(parts) >= 2: 55 if self.PRE_PROFILE == '' and subst == '{profile}': 56 fname = self.sanitize_filename(parts[0]) 57 self.listmap[fname] = {} 58 # Windows can have {appdata}\local\temp\longnamedfolder 59 # or {appdata}\local\temp\longna~1 60 self.listmap[fname] = {} 61 if not fname.endswith('~1'): 62 # parse the longname into longna~1 63 dirs = fname.split('\\') 64 dirs[-1] = "%s~1" % (dirs[-1][:6]) 65 # now we want to ensure that every parent dir is 66 # added since we seem to be accessing them sometimes 67 diter = 2 68 while (diter < len(dirs)): 69 self.listmap['\\'.join(dirs[:diter])] = {} 70 diter = diter + 1 71 self.PRE_PROFILE = fname 72 73 filename = "%s%s" % (subst, path.join(parts[1:])) 74 75 for old_name, new_name in self.name_substitutions.iteritems(): 76 if isinstance(old_name, re._pattern_type): 77 filename = re.sub(old_name, new_name, filename) 78 else: 79 parts = filename.split(old_name) 80 if len(parts) >= 2: 81 filename = "%s%s" % (parts[0], new_name) 82 83 return filename.strip('/\\\ \t') 84 85 def check(self, test, file_name_index, event_source_index=None): 86 errors = {} 87 for row_key in test.iterkeys(): 88 filename = self.sanitize_filename(row_key[file_name_index]) 89 90 if filename in self.listmap: 91 if 'ignore' in self.listmap[filename] and \ 92 self.listmap[filename]['ignore']: 93 continue 94 elif filename in self.dependent_libs: 95 continue 96 elif event_source_index is not None and \ 97 row_key[event_source_index] in self.expected_event_sources: 98 continue 99 else: 100 if filename not in errors: 101 errors[filename] = [] 102 errors[filename].append(test[row_key]) 103 return errors 104 105 def checkDuration(self, test, file_name_index, file_duration_index): 106 errors = {} 107 for idx, (row_key, row_value) in utils.indexed_items(test.iteritems()): 108 if row_value[file_duration_index] > DEFAULT_DURATION: 109 filename = self.sanitize_filename(row_key[file_name_index]) 110 if filename in self.listmap and \ 111 'ignoreduration' in self.listmap[filename]: 112 # we have defined in the json manifest max values 113 # (max found value * 2) and will ignore it 114 if row_value[file_duration_index] <= \ 115 self.listmap[filename]['ignoreduration']: 116 continue 117 118 if filename not in errors: 119 errors[filename] = [] 120 errors[filename].append("Duration %s > %S" 121 % (row_value[file_duration_index]), 122 DEFAULT_DURATION) 123 return errors 124 125 def filter(self, test, file_name_index): 126 for row_key in test.keys(): 127 filename = self.sanitize_filename(row_key[file_name_index]) 128 if filename in self.listmap: 129 if 'ignore' in self.listmap[filename] and \ 130 self.listmap[filename]['ignore']: 131 del test[row_key] 132 continue 133 elif filename in self.dependent_libs: 134 del test[row_key] 135 continue 136 137 @staticmethod 138 def get_error_strings(errors): 139 error_strs = [] 140 for filename, data in errors.iteritems(): 141 for datum in data: 142 error_strs.append("File '%s' was accessed and we were not" 143 " expecting it: %r" % (filename, datum)) 144 return error_strs 145 146 def print_errors(self, error_strs): 147 for error_msg in error_strs: 148 print("TEST-UNEXPECTED-FAIL | %s | %s" % (self.test_name, 149 error_msg)) 150 151 # Note that we don't store dependent libs in listmap. This makes 152 # save_baseline cleaner. Since a baseline whitelist should not include 153 # the dependent_libs, we would need to filter them out if everything was 154 # stored in the same dict. 155 def load_dependent_libs(self): 156 filename = "%s%sdependentlibs.list" % (self.paths[KEY_XRE], 157 os.path.sep) 158 try: 159 with open(filename, 'r') as f: 160 libs = f.readlines() 161 self.dependent_libs = \ 162 {"%s%s%s" % (KEY_XRE, os.path.sep, lib.strip()): 163 {'ignore': True} for lib in libs} 164 return True 165 except IOError as e: 166 print("%s: %s" % (e.filename, e.strerror)) 167 return False 168