Skip to content

Commit febdbbe

Browse files
committed
fix(tests): two-pass module loading to prevent MUGJ/MVTS cross-contamination
MUGJ and MVTS share 176 routine names (V1BOA, V1AC, etc.) with different source code. The single-pass loop that registered and exec'd each module sequentially could resolve cross-routine imports (e.g. `import V1BOA1` inside V1BOA) against stale sys.modules entries from whichever suite ran first. Split into two passes: 1. Read code + register empty ModuleType shells in sys.modules 2. exec() code into each shell This guarantees every `import RoutineName` during exec() resolves to the current suite's module, eliminating order-dependent failures.
1 parent 447d541 commit febdbbe

2 files changed

Lines changed: 45 additions & 6 deletions

File tree

tests/functional/test_mugj.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,14 @@ def _load_all_mugj_routines() -> tuple[
275275
json.dump({"errors": transpile_errors}, f)
276276

277277
# Load modules from disk (each worker does this independently since
278-
# module objects aren't shared across processes)
278+
# module objects aren't shared across processes).
279+
#
280+
# Two-pass loading: MUGJ and MVTS share 176 routine names (V1BOA,
281+
# V1AC, etc.) but with different source code. Register all modules
282+
# in sys.modules first (pass 1) so that `import RoutineName` inside
283+
# exec() always resolves to this suite's module, not a stale entry
284+
# from a different suite that ran earlier in the same process.
285+
module_code: dict[str, str] = {}
279286
for source_path in all_routine_files:
280287
filename_stem = source_path.stem
281288
module_name = filename_to_module_name(filename_stem)
@@ -287,15 +294,25 @@ def _load_all_mugj_routines() -> tuple[
287294
py_path = os.path.join(cache_dir, f"{module_name}.py")
288295
try:
289296
with open(py_path) as fh:
290-
python_code = fh.read()
297+
module_code[module_name] = fh.read()
291298
module = types.ModuleType(module_name)
292299
sys.modules[module_name] = module
293-
exec(python_code, module.__dict__)
294300
routine_modules[module_name] = module
295301
except Exception as e:
296302
routine_modules[module_name] = None
297303
transpile_errors[module_name] = str(e)
298304

305+
# Pass 2: execute code into the pre-registered shells
306+
for module_name, python_code in module_code.items():
307+
module = routine_modules[module_name]
308+
if module is None:
309+
continue
310+
try:
311+
exec(python_code, module.__dict__)
312+
except Exception as e:
313+
routine_modules[module_name] = None
314+
transpile_errors[module_name] = str(e)
315+
299316
return routine_modules, transpile_errors
300317

301318

tests/functional/test_mvts.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,19 @@ def _codegen_fingerprint() -> str:
282282
json.dump({"errors": transpile_errors, "codegen_hash": codegen_hash}, f)
283283

284284
# Load modules from disk (each worker does this independently since
285-
# module objects aren't shared across processes)
285+
# module objects aren't shared across processes).
286+
#
287+
# Two-pass loading: MUGJ and MVTS share 176 routine names (V1BOA,
288+
# V1AC, etc.) but with different source code. If MUGJ ran first, its
289+
# modules are still in sys.modules. During exec(), transpiled code
290+
# uses `import V1BOA1` to call other routines — which resolves from
291+
# sys.modules. A single-pass loop that registers + execs each module
292+
# sequentially can hit MUGJ's stale entry for modules not yet processed.
293+
#
294+
# Pass 1: register empty shells so every `import RoutineName` inside
295+
# exec() resolves to the MVTS module (even if not yet populated).
296+
# Pass 2: exec() code into each shell.
297+
module_code: dict[str, str] = {}
286298
for source_path in all_routine_files:
287299
filename_stem = source_path.stem
288300
module_name = filename_to_module_name(filename_stem)
@@ -294,15 +306,25 @@ def _codegen_fingerprint() -> str:
294306
py_path = os.path.join(cache_dir, f"{module_name}.py")
295307
try:
296308
with open(py_path) as f:
297-
python_code = f.read()
309+
module_code[module_name] = f.read()
298310
module = types.ModuleType(module_name)
299311
sys.modules[module_name] = module
300-
exec(python_code, module.__dict__)
301312
routine_modules[module_name] = module
302313
except Exception as e:
303314
routine_modules[module_name] = None
304315
transpile_errors[module_name] = str(e)
305316

317+
# Pass 2: execute code into the pre-registered shells
318+
for module_name, python_code in module_code.items():
319+
module = routine_modules[module_name]
320+
if module is None:
321+
continue
322+
try:
323+
exec(python_code, module.__dict__)
324+
except Exception as e:
325+
routine_modules[module_name] = None
326+
transpile_errors[module_name] = str(e)
327+
306328
# Add cache dir to sys.path so subprocess can find routines
307329
if cache_dir not in sys.path:
308330
sys.path.insert(0, cache_dir)

0 commit comments

Comments
 (0)