• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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