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)