# 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 fnmatch import imp import os from . import commands from . import statusfile from . import utils from ..objects import testcase from variants import ALL_VARIANTS, ALL_VARIANT_FLAGS, FAST_VARIANT_FLAGS FAST_VARIANTS = set(["default", "turbofan"]) STANDARD_VARIANT = set(["default"]) class VariantGenerator(object): def __init__(self, suite, variants): self.suite = suite self.all_variants = ALL_VARIANTS & variants self.fast_variants = FAST_VARIANTS & variants self.standard_variant = STANDARD_VARIANT & variants def FilterVariantsByTest(self, testcase): result = self.all_variants if testcase.outcomes: if statusfile.OnlyStandardVariant(testcase.outcomes): return self.standard_variant if statusfile.OnlyFastVariants(testcase.outcomes): result = self.fast_variants return result def GetFlagSets(self, testcase, variant): if testcase.outcomes and statusfile.OnlyFastVariants(testcase.outcomes): return FAST_VARIANT_FLAGS[variant] else: return ALL_VARIANT_FLAGS[variant] class TestSuite(object): @staticmethod def LoadTestSuite(root, global_init=True): name = root.split(os.path.sep)[-1] f = None try: (f, pathname, description) = imp.find_module("testcfg", [root]) module = imp.load_module(name + "_testcfg", f, pathname, description) return module.GetSuite(name, root) except ImportError: # Use default if no testcfg is present. return GoogleTestSuite(name, root) finally: if f: f.close() def __init__(self, name, root): # Note: This might be called concurrently from different processes. self.name = name # string self.root = root # string containing path self.tests = None # list of TestCase objects self.rules = None # dictionary mapping test path to list of outcomes self.wildcards = None # dictionary mapping test paths to list of outcomes self.total_duration = None # float, assigned on demand def shell(self): return "d8" def suffix(self): return ".js" def status_file(self): return "%s/%s.status" % (self.root, self.name) # Used in the status file and for stdout printing. def CommonTestName(self, testcase): if utils.IsWindows(): return testcase.path.replace("\\", "/") else: return testcase.path def ListTests(self, context): raise NotImplementedError def _VariantGeneratorFactory(self): """The variant generator class to be used.""" return VariantGenerator def CreateVariantGenerator(self, variants): """Return a generator for the testing variants of this suite. Args: variants: List of variant names to be run as specified by the test runner. Returns: An object of type VariantGenerator. """ return self._VariantGeneratorFactory()(self, set(variants)) def PrepareSources(self): """Called once before multiprocessing for doing file-system operations. This should not access the network. For network access use the method below. """ pass def ReadStatusFile(self, variables): with open(self.status_file()) as f: self.rules, self.wildcards = ( statusfile.ReadStatusFile(f.read(), variables)) def ReadTestCases(self, context): self.tests = self.ListTests(context) @staticmethod def _FilterSlow(slow, mode): return (mode == "run" and not slow) or (mode == "skip" and slow) @staticmethod def _FilterPassFail(pass_fail, mode): return (mode == "run" and not pass_fail) or (mode == "skip" and pass_fail) def FilterTestCasesByStatus(self, warn_unused_rules, slow_tests="dontcare", pass_fail_tests="dontcare", variants=False): # Use only variants-dependent rules and wildcards when filtering # respective test cases and generic rules when filtering generic test # cases. if not variants: rules = self.rules[""] wildcards = self.wildcards[""] else: # We set rules and wildcards to a variant-specific version for each test # below. rules = {} wildcards = {} filtered = [] # Remember used rules as tuples of (rule, variant), where variant is "" for # variant-independent rules. used_rules = set() for t in self.tests: slow = False pass_fail = False testname = self.CommonTestName(t) variant = t.variant or "" if variants: rules = self.rules[variant] wildcards = self.wildcards[variant] if testname in rules: used_rules.add((testname, variant)) # Even for skipped tests, as the TestCase object stays around and # PrintReport() uses it. t.outcomes = t.outcomes | rules[testname] if statusfile.DoSkip(t.outcomes): continue # Don't add skipped tests to |filtered|. for outcome in t.outcomes: if outcome.startswith('Flags: '): t.flags += outcome[7:].split() slow = statusfile.IsSlow(t.outcomes) pass_fail = statusfile.IsPassOrFail(t.outcomes) skip = False for rule in wildcards: assert rule[-1] == '*' if testname.startswith(rule[:-1]): used_rules.add((rule, variant)) t.outcomes = t.outcomes | wildcards[rule] if statusfile.DoSkip(t.outcomes): skip = True break # "for rule in wildcards" slow = slow or statusfile.IsSlow(t.outcomes) pass_fail = pass_fail or statusfile.IsPassOrFail(t.outcomes) if (skip or self._FilterSlow(slow, slow_tests) or self._FilterPassFail(pass_fail, pass_fail_tests)): continue # "for t in self.tests" filtered.append(t) self.tests = filtered if not warn_unused_rules: return if not variants: for rule in self.rules[""]: if (rule, "") not in used_rules: print("Unused rule: %s -> %s (variant independent)" % ( rule, self.rules[""][rule])) for rule in self.wildcards[""]: if (rule, "") not in used_rules: print("Unused rule: %s -> %s (variant independent)" % ( rule, self.wildcards[""][rule])) else: for variant in ALL_VARIANTS: for rule in self.rules[variant]: if (rule, variant) not in used_rules: print("Unused rule: %s -> %s (variant: %s)" % ( rule, self.rules[variant][rule], variant)) for rule in self.wildcards[variant]: if (rule, variant) not in used_rules: print("Unused rule: %s -> %s (variant: %s)" % ( rule, self.wildcards[variant][rule], variant)) def FilterTestCasesByArgs(self, args): """Filter test cases based on command-line arguments. args can be a glob: asterisks in any position of the argument represent zero or more characters. Without asterisks, only exact matches will be used with the exeption of the test-suite name as argument. """ filtered = [] globs = [] for a in args: argpath = a.split('/') if argpath[0] != self.name: continue if len(argpath) == 1 or (len(argpath) == 2 and argpath[1] == '*'): return # Don't filter, run all tests in this suite. path = '/'.join(argpath[1:]) globs.append(path) for t in self.tests: for g in globs: if fnmatch.fnmatch(t.path, g): filtered.append(t) break self.tests = filtered def GetFlagsForTestCase(self, testcase, context): raise NotImplementedError def GetSourceForTest(self, testcase): return "(no source available)" def IsFailureOutput(self, testcase): return testcase.output.exit_code != 0 def IsNegativeTest(self, testcase): return False def HasFailed(self, testcase): execution_failed = self.IsFailureOutput(testcase) if self.IsNegativeTest(testcase): return not execution_failed else: return execution_failed def GetOutcome(self, testcase): if testcase.output.HasCrashed(): return statusfile.CRASH elif testcase.output.HasTimedOut(): return statusfile.TIMEOUT elif self.HasFailed(testcase): return statusfile.FAIL else: return statusfile.PASS def HasUnexpectedOutput(self, testcase): outcome = self.GetOutcome(testcase) return not outcome in (testcase.outcomes or [statusfile.PASS]) def StripOutputForTransmit(self, testcase): if not self.HasUnexpectedOutput(testcase): testcase.output.stdout = "" testcase.output.stderr = "" def CalculateTotalDuration(self): self.total_duration = 0.0 for t in self.tests: self.total_duration += t.duration return self.total_duration class StandardVariantGenerator(VariantGenerator): def FilterVariantsByTest(self, testcase): return self.standard_variant class GoogleTestSuite(TestSuite): def __init__(self, name, root): super(GoogleTestSuite, self).__init__(name, root) def ListTests(self, context): shell = os.path.abspath(os.path.join(context.shell_dir, self.shell())) if utils.IsWindows(): shell += ".exe" output = None for i in xrange(3): # Try 3 times in case of errors. cmd = ( context.command_prefix + [shell, "--gtest_list_tests"] + context.extra_flags ) output = commands.Execute(cmd) if output.exit_code == 0: break print "Test executable failed to list the tests (try %d).\n\nCmd:" % i print ' '.join(cmd) print "\nStdout:" print output.stdout print "\nStderr:" print output.stderr print "\nExit code: %d" % output.exit_code else: raise Exception("Test executable failed to list the tests.") tests = [] test_case = '' for line in output.stdout.splitlines(): test_desc = line.strip().split()[0] if test_desc.endswith('.'): test_case = test_desc elif test_case and test_desc: test = testcase.TestCase(self, test_case + test_desc) tests.append(test) tests.sort(key=lambda t: t.path) return tests def GetFlagsForTestCase(self, testcase, context): return (testcase.flags + ["--gtest_filter=" + testcase.path] + ["--gtest_random_seed=%s" % context.random_seed] + ["--gtest_print_time=0"] + context.mode_flags) def _VariantGeneratorFactory(self): return StandardVariantGenerator def shell(self): return self.name