| # Copyright 2012 the V8 project authors. All rights reserved. |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following |
| # disclaimer in the documentation and/or other materials provided |
| # with the distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived |
| # from this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import copy |
| import os |
| import re |
| import shlex |
| |
| from ..outproc import base as outproc |
| from ..local import command |
| from ..local import statusfile |
| from ..local import utils |
| |
| FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)") |
| |
| |
| |
| class TestCase(object): |
| def __init__(self, suite, path, name): |
| self.suite = suite # TestSuite object |
| |
| self.path = path # string, e.g. 'div-mod', 'test-api/foo' |
| self.name = name # string that identifies test in the status file |
| |
| self.variant = None # name of the used testing variant |
| self.variant_flags = [] # list of strings, flags specific to this test |
| |
| self.id = None # int, used to map result back to TestCase instance |
| self.run = 1 # The nth time this test is executed. |
| self.cmd = None |
| |
| # Fields used by the test processors. |
| self.origin = None # Test that this test is subtest of. |
| self.processor = None # Processor that created this subtest. |
| self.procid = '%s/%s' % (self.suite.name, self.name) # unique id |
| self.keep_output = False # Can output of this test be dropped |
| |
| self._statusfile_outcomes = None |
| self._expected_outcomes = None # optimization: None == [statusfile.PASS] |
| self._statusfile_flags = None |
| self._prepare_outcomes() |
| |
| def create_subtest(self, processor, subtest_id, variant=None, flags=None, |
| keep_output=False): |
| subtest = copy.copy(self) |
| subtest.origin = self |
| subtest.processor = processor |
| subtest.procid += '.%s' % subtest_id |
| subtest.keep_output = keep_output |
| if variant is not None: |
| assert self.variant is None |
| subtest.variant = variant |
| subtest.variant_flags = flags |
| subtest._prepare_outcomes() |
| return subtest |
| |
| def create_variant(self, variant, flags, procid_suffix=None): |
| """Makes a shallow copy of the object and updates variant, variant flags and |
| all fields that depend on it, e.g. expected outcomes. |
| |
| Args |
| variant - variant name |
| flags - flags that should be added to origin test's variant flags |
| procid_suffix - for multiple variants with the same name set suffix to |
| keep procid unique. |
| """ |
| other = copy.copy(self) |
| if not self.variant_flags: |
| other.variant_flags = flags |
| else: |
| other.variant_flags = self.variant_flags + flags |
| other.variant = variant |
| if procid_suffix: |
| other.procid += '[%s-%s]' % (variant, procid_suffix) |
| else: |
| other.procid += '[%s]' % variant |
| |
| other._prepare_outcomes(variant != self.variant) |
| |
| return other |
| |
| def _prepare_outcomes(self, force_update=True): |
| if force_update or self._statusfile_outcomes is None: |
| def is_flag(outcome): |
| return outcome.startswith('--') |
| def not_flag(outcome): |
| return not is_flag(outcome) |
| |
| outcomes = self.suite.statusfile.get_outcomes(self.name, self.variant) |
| self._statusfile_outcomes = filter(not_flag, outcomes) |
| self._statusfile_flags = filter(is_flag, outcomes) |
| self.expected_outcomes = ( |
| self._parse_status_file_outcomes(self._statusfile_outcomes)) |
| |
| def _parse_status_file_outcomes(self, outcomes): |
| if (statusfile.FAIL_SLOPPY in outcomes and |
| '--use-strict' not in self.variant_flags): |
| return outproc.OUTCOMES_FAIL |
| |
| expected_outcomes = [] |
| if (statusfile.FAIL in outcomes or |
| statusfile.FAIL_OK in outcomes): |
| expected_outcomes.append(statusfile.FAIL) |
| if statusfile.CRASH in outcomes: |
| expected_outcomes.append(statusfile.CRASH) |
| |
| # Do not add PASS if there is nothing else. Empty outcomes are converted to |
| # the global [PASS]. |
| if expected_outcomes and statusfile.PASS in outcomes: |
| expected_outcomes.append(statusfile.PASS) |
| |
| # Avoid creating multiple instances of a list with a single FAIL. |
| if expected_outcomes == outproc.OUTCOMES_FAIL: |
| return outproc.OUTCOMES_FAIL |
| return expected_outcomes or outproc.OUTCOMES_PASS |
| |
| @property |
| def do_skip(self): |
| return statusfile.SKIP in self._statusfile_outcomes |
| |
| @property |
| def is_slow(self): |
| return statusfile.SLOW in self._statusfile_outcomes |
| |
| @property |
| def is_fail_ok(self): |
| return statusfile.FAIL_OK in self._statusfile_outcomes |
| |
| @property |
| def is_pass_or_fail(self): |
| return (statusfile.PASS in self._statusfile_outcomes and |
| statusfile.FAIL in self._statusfile_outcomes and |
| statusfile.CRASH not in self._statusfile_outcomes) |
| |
| @property |
| def only_standard_variant(self): |
| return statusfile.NO_VARIANTS in self._statusfile_outcomes |
| |
| def get_command(self, context): |
| params = self._get_cmd_params(context) |
| env = self._get_cmd_env() |
| shell, shell_flags = self._get_shell_with_flags(context) |
| timeout = self._get_timeout(params, context.timeout) |
| return self._create_cmd(shell, shell_flags + params, env, timeout, context) |
| |
| def _get_cmd_params(self, ctx): |
| """Gets command parameters and combines them in the following order: |
| - files [empty by default] |
| - extra flags (from command line) |
| - user flags (variant/fuzzer flags) |
| - statusfile flags |
| - mode flags (based on chosen mode) |
| - source flags (from source code) [empty by default] |
| |
| The best way to modify how parameters are created is to only override |
| methods for getting partial parameters. |
| """ |
| return ( |
| self._get_files_params(ctx) + |
| self._get_extra_flags(ctx) + |
| self._get_variant_flags() + |
| self._get_statusfile_flags() + |
| self._get_mode_flags(ctx) + |
| self._get_source_flags() + |
| self._get_suite_flags(ctx) |
| ) |
| |
| def _get_cmd_env(self): |
| return {} |
| |
| def _get_files_params(self, ctx): |
| return [] |
| |
| def _get_extra_flags(self, ctx): |
| return ctx.extra_flags |
| |
| def _get_variant_flags(self): |
| return self.variant_flags |
| |
| def _get_statusfile_flags(self): |
| """Gets runtime flags from a status file. |
| |
| Every outcome that starts with "--" is a flag. |
| """ |
| return self._statusfile_flags |
| |
| def _get_mode_flags(self, ctx): |
| return ctx.mode_flags |
| |
| def _get_source_flags(self): |
| return [] |
| |
| def _get_suite_flags(self, ctx): |
| return [] |
| |
| def _get_shell_with_flags(self, ctx): |
| shell = self.get_shell() |
| shell_flags = [] |
| if shell == 'd8': |
| shell_flags.append('--test') |
| if utils.IsWindows(): |
| shell += '.exe' |
| if ctx.random_seed: |
| shell_flags.append('--random-seed=%s' % ctx.random_seed) |
| return shell, shell_flags |
| |
| def _get_timeout(self, params, timeout): |
| if "--stress-opt" in params: |
| timeout *= 4 |
| if "--noenable-vfp3" in params: |
| timeout *= 2 |
| |
| # TODO(majeski): make it slow outcome dependent. |
| timeout *= 2 |
| return timeout |
| |
| def get_shell(self): |
| return 'd8' |
| |
| def _get_suffix(self): |
| return '.js' |
| |
| def _create_cmd(self, shell, params, env, timeout, ctx): |
| return command.Command( |
| cmd_prefix=ctx.command_prefix, |
| shell=os.path.abspath(os.path.join(ctx.shell_dir, shell)), |
| args=params, |
| env=env, |
| timeout=timeout, |
| verbose=ctx.verbose |
| ) |
| |
| def _parse_source_flags(self, source=None): |
| source = source or self.get_source() |
| flags = [] |
| for match in re.findall(FLAGS_PATTERN, source): |
| flags += shlex.split(match.strip()) |
| return flags |
| |
| def is_source_available(self): |
| return self._get_source_path() is not None |
| |
| def get_source(self): |
| with open(self._get_source_path()) as f: |
| return f.read() |
| |
| def _get_source_path(self): |
| return None |
| |
| @property |
| def output_proc(self): |
| if self.expected_outcomes is outproc.OUTCOMES_PASS: |
| return outproc.DEFAULT |
| return outproc.OutProc(self.expected_outcomes) |
| |
| def __cmp__(self, other): |
| # Make sure that test cases are sorted correctly if sorted without |
| # key function. But using a key function is preferred for speed. |
| return cmp( |
| (self.suite.name, self.name, self.variant_flags), |
| (other.suite.name, other.name, other.variant_flags) |
| ) |
| |
| def __hash__(self): |
| return hash((self.suite.name, self.name, ''.join(self.variant_flags))) |
| |
| def __str__(self): |
| return self.suite.name + '/' + self.name |
| |
| # TODO(majeski): Rename `id` field or `get_id` function since they're |
| # unrelated. |
| def get_id(self): |
| return '%s/%s %s' % ( |
| self.suite.name, self.name, ' '.join(self.variant_flags)) |