Skip to content

Commit 9f56749

Browse files
authored
Merge pull request #80 from plasma-umass/copilot/sub-pr-77
Add defensive check to prevent double-wrapping in spec_from_file_location wrapper
2 parents 95f67ce + c84f30d commit 9f56749

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

src/slipcover/importer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ def spec_from_file_location_wrapper(name, location=None, *, loader=None, submodu
310310
if isinstance(spec.loader, machinery.ExtensionFileLoader):
311311
return spec
312312

313+
# Skip if already wrapped - prevent double-wrapping
314+
if isinstance(spec.loader, SlipcoverLoader):
315+
return spec
316+
313317
# Check if this file should be instrumented
314318
origin = spec.origin or (str(location) if location else None)
315319
if origin and file_matcher.matches(origin):

tests/test_importer.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,62 @@ def downgrade():
450450
assert 7 in executed_lines or 8 in executed_lines, f"upgrade() lines not executed, executed: {executed_lines}"
451451

452452

453+
def test_wrap_spec_from_file_location_no_double_wrap(tmp_path, monkeypatch):
454+
"""Test that spec_from_file_location wrapper doesn't double-wrap already wrapped loaders."""
455+
import importlib.util
456+
457+
# Create a test file
458+
test_file = tmp_path / "test_module.py"
459+
test_file.write_text('''
460+
x = 1
461+
y = 2
462+
''')
463+
464+
monkeypatch.chdir(tmp_path)
465+
466+
# Set up slipcover and file matcher
467+
import slipcover as sc
468+
sci = sc.Slipcover()
469+
fm = im.FileMatcher()
470+
fm.addSource(tmp_path)
471+
472+
# Wrap spec_from_file_location
473+
im.wrap_spec_from_file_location(sci, fm)
474+
475+
# Get the current (wrapped) spec_from_file_location
476+
wrapped_spec_from_file_location = importlib.util.spec_from_file_location
477+
478+
# Call wrapped function to get a spec with a wrapped loader
479+
spec = wrapped_spec_from_file_location("test_module", test_file)
480+
assert spec is not None
481+
assert spec.loader is not None
482+
assert isinstance(spec.loader, im.SlipcoverLoader), \
483+
f"Expected SlipcoverLoader, got {type(spec.loader)}"
484+
485+
# Save the wrapped loader
486+
first_wrapper = spec.loader
487+
488+
# Simulate the defensive check scenario: pass the spec with already-wrapped loader
489+
# back through the wrapper by manipulating what the original function returns.
490+
# We do this by calling spec_from_file_location with the already-wrapped loader
491+
# as the loader parameter.
492+
spec2 = wrapped_spec_from_file_location(
493+
"test_module2", test_file, loader=first_wrapper
494+
)
495+
assert spec2 is not None
496+
assert spec2.loader is not None
497+
498+
# The defensive check should have prevented double-wrapping
499+
assert isinstance(spec2.loader, im.SlipcoverLoader), \
500+
f"Expected SlipcoverLoader, got {type(spec2.loader)}"
501+
assert spec2.loader is first_wrapper, \
502+
"Defensive check failed: loader should not be wrapped again"
503+
504+
# Verify that the original loader is not another SlipcoverLoader
505+
assert not isinstance(first_wrapper.orig_loader, im.SlipcoverLoader), \
506+
"Loader was double-wrapped: orig_loader should not be a SlipcoverLoader"
507+
508+
453509
@pytest.mark.skipif(sys.platform == 'win32', reason='Fails due to weird PermissionError')
454510
def test_wrap_spec_from_file_location_with_branch(tmp_path, monkeypatch):
455511
"""Test that files loaded via spec_from_file_location get branch coverage."""

0 commit comments

Comments
 (0)