Loading...
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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | #!/usr/bin/env python3 import argparse import copy import os import plistlib import sys def write_plist(plist_path, content): with open(plist_path, 'wb') as plist_file: plistlib.dump(content, plist_file, fmt=plistlib.FMT_BINARY) # Note: The env is a list of `key=value`, not a dictionary def has_env_var(test, env_var_name): env = test.get('ShellEnv', []) key = env_var_name + '=' # exact match required return any(v.startswith(key) for v in env) def extend_env(test, env_vars): if 'ShellEnv' in test: test['ShellEnv'].extend(env_vars) else: test['ShellEnv'] = env_vars # Disable PGM by setting `MallocProbGuard=0`, but respect existing uses def disable_pgm(test): if not has_env_var(test, 'MallocProbGuard'): extend_env(test, ['MallocProbGuard=0']) # Set MallocReportConfig=1 if not already present def enable_report_config(test): if not has_env_var(test, 'MallocReportConfig'): extend_env(test, ['MallocReportConfig=1', 'MallocDebugReport=stderr']) # Exclude tests with the following characteristic from being re-run under PGM: # - T_META_NAMESPACE("pgm"): PGM implementation tests, name starts with "libmalloc.pgm" # - T_META_ENVVAR("MallocProbGuard=X"): these tests already specify PGM state def pgm_compatible(test): if test.get('TestName', '').startswith('libmalloc.pgm'): return False if has_env_var(test, 'MallocProbGuard'): return False return True def create_allocator_test(orig_test, extension, extra_envvars=[], use_debug_dylib=False, remove_perf_tag=False): new_test = copy.deepcopy(orig_test) if 'TestName' in orig_test: orig_name = orig_test['TestName'] new_test['TestName'] = orig_name + '.' + extension if 'CanonicalName' in orig_test: # The CanonicalName for darwintests has a .<arch> suffix that we # want to keep at the end, so insert our new component just # before that orig_name = orig_test['CanonicalName'] components = orig_name.split('.') if len(components) > 1: components.insert(-1, extension) new_name = '.'.join(components) else: new_name = orig_name + '.' + extension new_test['CanonicalName'] = new_name envvars = [ 'MallocReportConfig=1', 'MallocDebugReport=stderr', 'MallocAllowInternalSecurity=1', ] envvars += extra_envvars if 'MallocProbGuard=1' not in extra_envvars and \ not has_env_var(orig_test, 'MallocProbGuard'): envvars.append('MallocProbGuard=0') if use_debug_dylib and 'perf' not in orig_test['Tags'] and \ 'no_debug' not in orig_test['Tags']: # This isn't a performance test or otherwise incompatible with # the debug variant of the library, so we can use it for extra # assert coverage. envvars.append('DYLD_IMAGE_SUFFIX=_debug') extend_env(new_test, envvars) if remove_perf_tag: tags = new_test.get('Tags', []) if 'perf' in tags: tags.remove('perf') return new_test def create_magazine_test(orig_test): return create_allocator_test(orig_test, 'magazine', extra_envvars=['MallocSecureAllocator=0']) def create_xzone_test(orig_test, add_pgm=False): envvars = [ 'MallocSecureAllocator=1', 'MallocSecureAllocatorNano=1', ] if add_pgm: if has_env_var(orig_test, 'MallocProbGuard'): sys.exit('Test %s already specifies MallocProbGuard' % orig_test.get('TestName', '<unknown>')) envvars.append('MallocProbGuard=1') return create_allocator_test(orig_test, 'xzone', extra_envvars=envvars, use_debug_dylib=True) def create_pgm_test(orig_test, timeout_risk): new_test = create_allocator_test(orig_test, 'pgm', extra_envvars=['MallocProbGuard=1'], remove_perf_tag=True) if timeout_risk: new_test['Timeout'] *= 10 else: extend_env(new_test, ['MallocProbGuardSampleRate=5']) return new_test TEST_TAG_ALL_ALLOCATORS = 'all_allocators' TEST_TAG_NO_ALLOCATOR_OVERRIDE = 'no_allocator_override' TEST_TAG_XZONE_ONLY = 'xzone_only' TEST_TAG_MAGAZINE_ONLY = 'magazine_only' def process_bats_plist(bats_plist_path, output_directory): with open(bats_plist_path, 'rb') as bats_plist_file: orig_bats_plist = plistlib.load(bats_plist_file) main_plist = copy.deepcopy(orig_bats_plist) pgm_plist = copy.deepcopy(orig_bats_plist) main_tests = [] pgm_tests = [] for orig_test in orig_bats_plist['Tests']: testname = orig_test.get('TestName', '<unknown>') if testname == 'libmalloc.xctests': main_tests.append(orig_test) continue if 'Tags' not in orig_test: sys.exit('No tags for test %s' % testname) tags = set(orig_test['Tags']) vm_tags = { 'VM_PREFERRED', 'VM_NOT_PREFERRED', 'VM_NOT_ELIGIBLE', } if len(tags & vm_tags) != 1: sys.exit('Test %s must be tagged with exactly one of %s, found %s' % (testname, str(vm_tags), str(tags & vm_tags))) allocator_tags = { TEST_TAG_ALL_ALLOCATORS, TEST_TAG_NO_ALLOCATOR_OVERRIDE, TEST_TAG_XZONE_ONLY, TEST_TAG_MAGAZINE_ONLY, } if len(tags & allocator_tags) != 1: sys.exit('Test %s must be tagged with exactly one of %s, found %s' % (testname, str(allocator_tags), str(tags & allocator_tags))) test_allocator_tag = list(tags & allocator_tags)[0] if test_allocator_tag == TEST_TAG_NO_ALLOCATOR_OVERRIDE: # These tests want to run with whatever the ambient allocator # configuration is, either because they specifically want to check # that or because they're unit tests that don't care. We make only # minimal changes to these tests. new_test = copy.deepcopy(orig_test) disable_pgm(new_test) enable_report_config(new_test) main_tests.append(new_test) if test_allocator_tag == TEST_TAG_ALL_ALLOCATORS and \ pgm_compatible(orig_test): # Tests that want to run with all allocator configurations and do # not explicitly specify their PGM state should be re-run with PGM # on top of the default allocator in the PGM suite pgm_timeout_risk_tests = { 'libmalloc.threaded_stress_fork', 'libmalloc.threaded_stress_fork_small', } timeout_risk = testname in pgm_timeout_risk_tests pgm_test = create_pgm_test(orig_test, timeout_risk) pgm_tests.append(pgm_test) if test_allocator_tag in { \ TEST_TAG_MAGAZINE_ONLY, TEST_TAG_ALL_ALLOCATORS }: magazine_test = create_magazine_test(orig_test) main_tests.append(magazine_test) if test_allocator_tag in { \ TEST_TAG_XZONE_ONLY, TEST_TAG_ALL_ALLOCATORS }: # Don't add xzone tests for arm64_32 (or any other 32-bit slices, # which we'll have to check for here should they appear in the # future) if orig_test.get('Arch', '') == 'arm64_32': continue xzone_test = create_xzone_test(orig_test) main_tests.append(xzone_test) if 'xzone_and_pgm' in tags: xzone_and_pgm_test = create_xzone_test(orig_test, add_pgm=True) main_tests.append(xzone_and_pgm_test) main_plist['Tests'] = main_tests # Rewrite the main plist in place write_plist(bats_plist_path, main_plist) pgm_plist['Tests'] = pgm_tests pgm_plist_path = os.path.join(output_directory, 'libmalloc_pgm.plist') write_plist(pgm_plist_path, pgm_plist) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Processes libmalloc BATS plist') parser.add_argument('bats_plist') parser.add_argument('output_directory') parser.add_argument('-p', '--platform', help='Platform tests are being run on ()') args = parser.parse_args() process_bats_plist(args.bats_plist, args.output_directory) |