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