tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

suites.py (3897B)


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