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