Name

Test::NoShadow - Fail CI when two distributions provide the same Perl package, or when t/ exercises a copy other than the one under test

Version

Version 0.001.0

Synopsis

# In your CI, over a directory of checkouts:
use Test::More;
use Test::NoShadow qw(shadowed_ok lib_ok find_shadows);

shadowed_ok('/home/njh');    # fails if any Perl package is provided by two checkouts

# In a distribution's own t/, after using its modules:
use My::Module;
lib_ok('My::Module');        # fails if My::Module came from anywhere but this distribution

# Or inspect the data yourself:
my $shadows = find_shadows('/home/njh');
foreach my $package (sort keys %$shadows) {
    diag("$package: @{ $shadows->{$package} }");
}

Description

Test::NoShadow fails your test suite when two distributions provide the same Perl package, and when a distribution's tests exercise a copy other than the one under test (an older installed copy, or a vendored copy in t/).

It was written to police a workspace of many checkouts: pairs such as Schema::Validator in both schema-validator and Schema-Validator, or a vendored VWF::* inside another distribution, ship identical package names, so use can silently load the wrong code.

No other CPAN module checks for the multi-checkout variant; the closest relative, Test::CleanNamespaces, works on installed distributions only.

Two checks are provided:

Functions/Methods

Shadowed_Ok [@Dirs, [ \%Options ]]

Scans each directory (recursively) for .pm files and extracts the package names they declare. Passes if no Perl package is declared in more than one file; otherwise fails, with a diagnostic naming both providers.

With no directories given, @INC is scanned - for example:

prove -Ilib t/shadow.t

The following options are recognised in the optional hash reference:

Each top-level directory beneath the scanned directory is treated as a "provider" (in this workspace, a checkout); files directly beneath the scanned directory are labelled with their own file name. To check a single checkout for vendored copies, pass the checkout itself: its top-level directory (usually lib) becomes the provider, and copies under t/ are reported alongside copies under lib.

Returns true on success, false on failure.

Lib_Ok @Modules, [ \%Options ]

For use in the distribution's own tests, after the modules have been used. Passes if each named module was loaded from this distribution's lib or blib/lib; fails if a module was loaded from anywhere else (a shadowing copy installed in @INC), or was not loaded at all.

One test is run per module.

The following options are recognised:

Returns true if all modules passed.

Find_Shadows $Dir, [ \%Options ]

The non-TAP interface: returns a hash reference mapping each shadowed package name to a sorted array reference of the files that declare it. Accepts the same options as "shadowed_ok".

Diagnostics

Limitations

See Also

Test::Which - skip tests if external programs are missing from PATH

Test::CleanNamespaces - check for leftover subroutines in installed distributions

Test::Builder - the test framework this module is built on

Author

Nigel Horne, <njh at nigelhorne.com>

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms. If you use it, please let me know.