1# Copyright 2017 the V8 project authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from itertools import zip_longest 6 7from ..testproc.base import ( 8 DROP_RESULT, DROP_OUTPUT, DROP_PASS_OUTPUT, DROP_PASS_STDOUT) 9from ..local import statusfile 10from ..testproc.result import Result 11 12 13OUTCOMES_PASS = [statusfile.PASS] 14OUTCOMES_FAIL = [statusfile.FAIL] 15OUTCOMES_TIMEOUT = [statusfile.TIMEOUT] 16OUTCOMES_PASS_OR_TIMEOUT = [statusfile.PASS, statusfile.TIMEOUT] 17OUTCOMES_FAIL_OR_TIMEOUT = [statusfile.FAIL, statusfile.TIMEOUT] 18OUTCOMES_FAIL_OR_PASS = [statusfile.FAIL, statusfile.PASS] 19 20 21class BaseOutProc(object): 22 def process(self, output, reduction=None): 23 has_unexpected_output = self.has_unexpected_output(output) 24 if has_unexpected_output: 25 self.regenerate_expected_files(output) 26 return self._create_result(has_unexpected_output, output, reduction) 27 28 def regenerate_expected_files(self, output): 29 return 30 31 def has_unexpected_output(self, output): 32 return self.get_outcome(output) not in self.expected_outcomes 33 34 def _create_result(self, has_unexpected_output, output, reduction): 35 """Creates Result instance. When reduction is passed it tries to drop some 36 parts of the result to save memory and time needed to send the result 37 across process boundary. None disables reduction and full result is created. 38 """ 39 if reduction == DROP_RESULT: 40 return None 41 if reduction == DROP_OUTPUT: 42 return Result(has_unexpected_output, None) 43 if not has_unexpected_output: 44 if reduction == DROP_PASS_OUTPUT: 45 return Result(has_unexpected_output, None) 46 if reduction == DROP_PASS_STDOUT: 47 return Result(has_unexpected_output, output.without_text()) 48 49 return Result(has_unexpected_output, output) 50 51 def get_outcome(self, output): 52 if output.HasCrashed(): 53 return statusfile.CRASH 54 elif output.HasTimedOut(): 55 return statusfile.TIMEOUT 56 elif self._has_failed(output): 57 return statusfile.FAIL 58 else: 59 return statusfile.PASS 60 61 def _has_failed(self, output): 62 execution_failed = self._is_failure_output(output) 63 if self.negative: 64 return not execution_failed 65 return execution_failed 66 67 def _is_failure_output(self, output): 68 return output.exit_code != 0 69 70 @property 71 def negative(self): 72 return False 73 74 @property 75 def expected_outcomes(self): 76 raise NotImplementedError() 77 78 79class Negative(object): 80 @property 81 def negative(self): 82 return True 83 84 85class PassOutProc(BaseOutProc): 86 """Output processor optimized for positive tests expected to PASS.""" 87 def has_unexpected_output(self, output): 88 return self.get_outcome(output) != statusfile.PASS 89 90 @property 91 def expected_outcomes(self): 92 return OUTCOMES_PASS 93 94 95class NegPassOutProc(Negative, PassOutProc): 96 """Output processor optimized for negative tests expected to PASS""" 97 pass 98 99 100class OutProc(BaseOutProc): 101 """Output processor optimized for positive tests with expected outcomes 102 different than a single PASS. 103 """ 104 def __init__(self, expected_outcomes): 105 self._expected_outcomes = expected_outcomes 106 107 @property 108 def expected_outcomes(self): 109 return self._expected_outcomes 110 111 # TODO(majeski): Inherit from PassOutProc in case of OUTCOMES_PASS and remove 112 # custom get/set state. 113 def __getstate__(self): 114 d = self.__dict__ 115 if self._expected_outcomes is OUTCOMES_PASS: 116 d = d.copy() 117 del d['_expected_outcomes'] 118 return d 119 120 def __setstate__(self, d): 121 if '_expected_outcomes' not in d: 122 d['_expected_outcomes'] = OUTCOMES_PASS 123 self.__dict__.update(d) 124 125 126# TODO(majeski): Override __reduce__ to make it deserialize as one instance. 127DEFAULT = PassOutProc() 128DEFAULT_NEGATIVE = NegPassOutProc() 129 130 131class ExpectedOutProc(OutProc): 132 """Output processor that has is_failure_output depending on comparing the 133 output with the expected output. 134 """ 135 def __init__(self, expected_outcomes, expected_filename, 136 regenerate_expected_files=False): 137 super(ExpectedOutProc, self).__init__(expected_outcomes) 138 self._expected_filename = expected_filename 139 self._regenerate_expected_files = regenerate_expected_files 140 141 def _is_failure_output(self, output): 142 if output.exit_code != 0: 143 return True 144 145 with open(self._expected_filename, 'r', encoding='utf-8') as f: 146 expected_lines = f.readlines() 147 148 for act_iterator in self._act_block_iterator(output): 149 for expected, actual in zip_longest( 150 self._expected_iterator(expected_lines), 151 act_iterator, 152 fillvalue='' 153 ): 154 if expected != actual: 155 return True 156 return False 157 158 def regenerate_expected_files(self, output): 159 if not self._regenerate_expected_files: 160 return 161 lines = output.stdout.splitlines() 162 with open(self._expected_filename, 'w') as f: 163 for _, line in enumerate(lines): 164 f.write(line+'\n') 165 166 def _act_block_iterator(self, output): 167 """Iterates over blocks of actual output lines.""" 168 lines = output.stdout.splitlines() 169 start_index = 0 170 found_eqeq = False 171 for index, line in enumerate(lines): 172 # If a stress test separator is found: 173 if line.startswith('=='): 174 # Iterate over all lines before a separator except the first. 175 if not found_eqeq: 176 found_eqeq = True 177 else: 178 yield self._actual_iterator(lines[start_index:index]) 179 # The next block of output lines starts after the separator. 180 start_index = index + 1 181 # Iterate over complete output if no separator was found. 182 if not found_eqeq: 183 yield self._actual_iterator(lines) 184 185 def _actual_iterator(self, lines): 186 return self._iterator(lines, self._ignore_actual_line) 187 188 def _expected_iterator(self, lines): 189 return self._iterator(lines, self._ignore_expected_line) 190 191 def _ignore_actual_line(self, line): 192 """Ignore empty lines, valgrind output, Android output and trace 193 incremental marking output. 194 """ 195 if not line: 196 return True 197 return (line.startswith('==') or 198 line.startswith('**') or 199 line.startswith('ANDROID') or 200 line.startswith('###') or 201 # Android linker warning. 202 line.startswith('WARNING: linker:') or 203 # FIXME(machenbach): The test driver shouldn't try to use slow 204 # asserts if they weren't compiled. This fails in optdebug=2. 205 line == 'Warning: unknown flag --enable-slow-asserts.' or 206 line == 'Try --help for options') 207 208 def _ignore_expected_line(self, line): 209 return not line 210 211 def _iterator(self, lines, ignore_predicate): 212 for line in lines: 213 line = line.strip() 214 if not ignore_predicate(line): 215 yield line 216