1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
import itertools
import os
import re
import subprocess
def get_gtest_suites(args, cwd, gtest_env):
"""
Get a list of gtest suite names from a gtest program.
* args - The arguments (including executable) for the gtest program.
* cwd - The working directory to use.
* gtest_env - Additional environment variables to set.
Returns a list of the suite names.
"""
# List the tests to get the suite names
args.append("--gtest_list_tests")
env = {}
env.update(os.environ)
env.update(gtest_env)
completed_proc = subprocess.run(
args, cwd=cwd, env=env, capture_output=True, check=True, text=True
)
output = completed_proc.stdout
# Suite names are exclusively text without whitespace, and followed by
# a '.', optionally with ` #` and type parameter information. This is
# specific enough to reasonably filter out some extra strings output by
# firefox.
SUITE_REGEX = re.compile(r"(\S+).( # .*)?")
def get_suite_name(line):
match = SUITE_REGEX.fullmatch(line)
if match:
return match[1]
suites = list(
filter(lambda x: x is not None, map(get_suite_name, output.splitlines()))
)
# Remove the `--gtest_list_tests` arg that we added
args.pop()
return suites
class _JoinedSubsetOfStrings:
"""
Efficient creation of joined strings for subsets of a list of strings.
This allows creation of joined strings in O(1) instead of O(n) each time (n = list
length), with a one-time O(n) cost.
"""
def __init__(self, between, strs):
"""
Arguments:
* between - the string with which to join the strings
* strs - an iterable of strings
"""
strs = list(strs)
self._string = between.join(strs)
betweenlen = len(between)
self._offsets = list(
itertools.accumulate(map(lambda s: len(s) + betweenlen, strs), initial=0)
)
def without(self, index):
"""Create a joined string excluding the given index."""
return (
self._string[: self._offsets[index]]
+ self._string[self._offsets[index + 1] :]
)
class SuiteFilter:
def __init__(self, joined, index, suite):
self._joined = joined
self.index = index
self.suite = suite
def create(self, existing_filter=None):
"""Create a filter to only run this suite."""
if existing_filter is None or existing_filter == "*":
return f"{self.suite}.*"
else:
return (
existing_filter
+ (":" if "-" in existing_filter else "-")
+ self._joined.without(self.index)
)
def set_in_env(self, env):
"""
Set the filter to only run this suite in an environment mapping.
Returns the passed env.
"""
env["GTEST_FILTER"] = self.create(env.get("GTEST_FILTER"))
return env
def __call__(self, val):
"""
If called on a dict, creates a copy and forwards to `set_in_env`,
otherwise forwards to `create`.
"""
if isinstance(val, dict):
return self.set_in_env(val.copy())
else:
return self.create(val)
def suite_filters(suites):
"""
Form gtest filters to limit tests to a single suite.
This is a generator that yields a SuiteFilter for each suite.
Arguments:
* suites - an iterable of the suite names
"""
suites = list(suites)
joined = _JoinedSubsetOfStrings(":", map(lambda s: f"{s}.*", suites))
for i, suite in enumerate(suites):
yield SuiteFilter(joined, i, suite)
|