Revision control

Copy as Markdown

Other Tools

# 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)