summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/00-compile.t60
-rw-r--r--t/00-report-prereqs.dd68
-rw-r--r--t/00-report-prereqs.t184
-rw-r--r--t/archive.t91
-rw-r--r--t/determine.t129
-rw-r--r--t/dir.t115
-rw-r--r--t/dists.t219
-rw-r--r--t/file_spec.t32
-rw-r--r--t/load_meta.t55
-rw-r--r--t/module_info.t126
-rw-r--r--t/no_index.t86
-rw-r--r--t/package_versions.t55
-rw-r--r--t/struct.t69
-rw-r--r--t/tar.t29
-rw-r--r--t/zip.t21
15 files changed, 1339 insertions, 0 deletions
diff --git a/t/00-compile.t b/t/00-compile.t
new file mode 100644
index 0000000..e16fa2d
--- /dev/null
+++ b/t/00-compile.t
@@ -0,0 +1,60 @@
+use 5.006;
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.052
+
+use Test::More;
+
+plan tests => 7 + ($ENV{AUTHOR_TESTING} ? 1 : 0);
+
+my @module_files = (
+ 'Dist/Metadata.pm',
+ 'Dist/Metadata/Archive.pm',
+ 'Dist/Metadata/Dir.pm',
+ 'Dist/Metadata/Dist.pm',
+ 'Dist/Metadata/Struct.pm',
+ 'Dist/Metadata/Tar.pm',
+ 'Dist/Metadata/Zip.pm'
+);
+
+
+
+# fake home for cpan-testers
+use File::Temp;
+local $ENV{HOME} = File::Temp::tempdir( CLEANUP => 1 );
+
+
+my $inc_switch = -d 'blib' ? '-Mblib' : '-Ilib';
+
+use File::Spec;
+use IPC::Open3;
+use IO::Handle;
+
+open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!";
+
+my @warnings;
+for my $lib (@module_files)
+{
+ # see L<perlfaq8/How can I capture STDERR from an external command?>
+ my $stderr = IO::Handle->new;
+
+ my $pid = open3($stdin, '>&STDERR', $stderr, $^X, $inc_switch, '-e', "require q[$lib]");
+ binmode $stderr, ':crlf' if $^O eq 'MSWin32';
+ my @_warnings = <$stderr>;
+ waitpid($pid, 0);
+ is($?, 0, "$lib loaded ok");
+
+ if (@_warnings)
+ {
+ warn @_warnings;
+ push @warnings, @_warnings;
+ }
+}
+
+
+
+is(scalar(@warnings), 0, 'no warnings found')
+ or diag 'got warnings: ', ( Test::More->can('explain') ? Test::More::explain(\@warnings) : join("\n", '', @warnings) ) if $ENV{AUTHOR_TESTING};
+
+
diff --git a/t/00-report-prereqs.dd b/t/00-report-prereqs.dd
new file mode 100644
index 0000000..c378a45
--- /dev/null
+++ b/t/00-report-prereqs.dd
@@ -0,0 +1,68 @@
+do { my $x = {
+ 'configure' => {
+ 'requires' => {
+ 'ExtUtils::MakeMaker' => '0',
+ 'perl' => '5.006'
+ }
+ },
+ 'develop' => {
+ 'requires' => {
+ 'Archive::Any::Create' => '0.03',
+ 'Pod::Coverage::TrustPod' => '0',
+ 'Test::CPAN::Changes' => '0.19',
+ 'Test::CPAN::Meta' => '0',
+ 'Test::CPAN::Meta::JSON' => '0.16',
+ 'Test::EOL' => '0',
+ 'Test::Kwalitee' => '1.21',
+ 'Test::More' => '0.88',
+ 'Test::NoTabs' => '0',
+ 'Test::Pod' => '1.41',
+ 'Test::Pod::Coverage' => '1.08',
+ 'Test::Spelling' => '0.12',
+ 'Test::Synopsis' => '0',
+ 'Test::Version' => '1'
+ }
+ },
+ 'runtime' => {
+ 'requires' => {
+ 'Archive::Tar' => '1',
+ 'Archive::Zip' => '1.30',
+ 'CPAN::DistnameInfo' => '0.12',
+ 'CPAN::Meta' => '2.1',
+ 'Carp' => '0',
+ 'Digest' => '1.03',
+ 'Digest::MD5' => '2',
+ 'Digest::SHA' => '5',
+ 'File::Basename' => '0',
+ 'File::Find' => '0',
+ 'File::Spec::Native' => '1.002',
+ 'File::Temp' => '0.19',
+ 'List::Util' => '0',
+ 'Module::Metadata' => '0',
+ 'Path::Class' => '0.24',
+ 'Try::Tiny' => '0.09',
+ 'parent' => '0',
+ 'perl' => '5.006',
+ 'strict' => '0',
+ 'warnings' => '0'
+ }
+ },
+ 'test' => {
+ 'recommends' => {
+ 'CPAN::Meta' => '2.120900'
+ },
+ 'requires' => {
+ 'ExtUtils::MakeMaker' => '0',
+ 'File::Spec' => '0',
+ 'File::Temp' => '0.19',
+ 'IO::Handle' => '0',
+ 'IPC::Open3' => '0',
+ 'Test::Fatal' => '0',
+ 'Test::MockObject' => '1.09',
+ 'Test::More' => '0.96',
+ 'perl' => '5.006'
+ }
+ }
+ };
+ $x;
+ } \ No newline at end of file
diff --git a/t/00-report-prereqs.t b/t/00-report-prereqs.t
new file mode 100644
index 0000000..cce9245
--- /dev/null
+++ b/t/00-report-prereqs.t
@@ -0,0 +1,184 @@
+#!perl
+
+use strict;
+use warnings;
+
+# This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.021
+
+use Test::More tests => 1;
+
+use ExtUtils::MakeMaker;
+use File::Spec;
+
+# from $version::LAX
+my $lax_version_re =
+ qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )?
+ |
+ (?:\.[0-9]+) (?:_[0-9]+)?
+ ) | (?:
+ v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )?
+ |
+ (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)?
+ )
+ )/x;
+
+# hide optional CPAN::Meta modules from prereq scanner
+# and check if they are available
+my $cpan_meta = "CPAN::Meta";
+my $cpan_meta_pre = "CPAN::Meta::Prereqs";
+my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic
+
+# Verify requirements?
+my $DO_VERIFY_PREREQS = 1;
+
+sub _max {
+ my $max = shift;
+ $max = ( $_ > $max ) ? $_ : $max for @_;
+ return $max;
+}
+
+sub _merge_prereqs {
+ my ($collector, $prereqs) = @_;
+
+ # CPAN::Meta::Prereqs object
+ if (ref $collector eq $cpan_meta_pre) {
+ return $collector->with_merged_prereqs(
+ CPAN::Meta::Prereqs->new( $prereqs )
+ );
+ }
+
+ # Raw hashrefs
+ for my $phase ( keys %$prereqs ) {
+ for my $type ( keys %{ $prereqs->{$phase} } ) {
+ for my $module ( keys %{ $prereqs->{$phase}{$type} } ) {
+ $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module};
+ }
+ }
+ }
+
+ return $collector;
+}
+
+my @include = qw(
+ JSON::PP
+ JSON
+);
+
+my @exclude = qw(
+
+);
+
+# Add static prereqs to the included modules list
+my $static_prereqs = do 't/00-report-prereqs.dd';
+
+# Merge all prereqs (either with ::Prereqs or a hashref)
+my $full_prereqs = _merge_prereqs(
+ ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ),
+ $static_prereqs
+);
+
+# Add dynamic prereqs to the included modules list (if we can)
+my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
+if ( $source && $HAS_CPAN_META ) {
+ if ( my $meta = eval { CPAN::Meta->load_file($source) } ) {
+ $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs);
+ }
+}
+else {
+ $source = 'static metadata';
+}
+
+my @full_reports;
+my @dep_errors;
+my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs;
+
+# Add static includes into a fake section
+for my $mod (@include) {
+ $req_hash->{other}{modules}{$mod} = 0;
+}
+
+for my $phase ( qw(configure build test runtime develop other) ) {
+ next unless $req_hash->{$phase};
+ next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING});
+
+ for my $type ( qw(requires recommends suggests conflicts modules) ) {
+ next unless $req_hash->{$phase}{$type};
+
+ my $title = ucfirst($phase).' '.ucfirst($type);
+ my @reports = [qw/Module Want Have/];
+
+ for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) {
+ next if $mod eq 'perl';
+ next if grep { $_ eq $mod } @exclude;
+
+ my $file = $mod;
+ $file =~ s{::}{/}g;
+ $file .= ".pm";
+ my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC;
+
+ my $want = $req_hash->{$phase}{$type}{$mod};
+ $want = "undef" unless defined $want;
+ $want = "any" if !$want && $want == 0;
+
+ my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required";
+
+ if ($prefix) {
+ my $have = MM->parse_version( File::Spec->catfile($prefix, $file) );
+ $have = "undef" unless defined $have;
+ push @reports, [$mod, $want, $have];
+
+ if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) {
+ if ( $have !~ /\A$lax_version_re\z/ ) {
+ push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)";
+ }
+ elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) {
+ push @dep_errors, "$mod version '$have' is not in required range '$want'";
+ }
+ }
+ }
+ else {
+ push @reports, [$mod, $want, "missing"];
+
+ if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) {
+ push @dep_errors, "$mod is not installed ($req_string)";
+ }
+ }
+ }
+
+ if ( @reports ) {
+ push @full_reports, "=== $title ===\n\n";
+
+ my $ml = _max( map { length $_->[0] } @reports );
+ my $wl = _max( map { length $_->[1] } @reports );
+ my $hl = _max( map { length $_->[2] } @reports );
+
+ if ($type eq 'modules') {
+ splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl];
+ push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports;
+ }
+ else {
+ splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl];
+ push @full_reports, map { sprintf(" %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2]) } @reports;
+ }
+
+ push @full_reports, "\n";
+ }
+ }
+}
+
+if ( @full_reports ) {
+ diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports;
+}
+
+if ( @dep_errors ) {
+ diag join("\n",
+ "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n",
+ "The following REQUIRED prerequisites were not satisfied:\n",
+ @dep_errors,
+ "\n"
+ );
+}
+
+pass;
+
+# vim: ts=4 sts=4 sw=4 et:
diff --git a/t/archive.t b/t/archive.t
new file mode 100644
index 0000000..f75539b
--- /dev/null
+++ b/t/archive.t
@@ -0,0 +1,91 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Test::Fatal;
+
+my $mod = 'Dist::Metadata::Archive';
+eval "require $mod" or die $@;
+
+# default_file_spec
+is( $mod->default_file_spec, 'Unix', 'most archive files use unix paths' );
+
+test_constructor_errors($mod);
+
+# test file type determination
+my $base = 'corpus/Dist-Metadata-Test-NoMetaFile-0.1';
+foreach my $test (
+ [Zip => "$base.zip"],
+ [Tar => "$base.tar.gz"],
+){
+ my ($type, $file) = @$test;
+
+ my $distclass = "Dist::Metadata::$type";
+
+ # instantiate using base 'Archive' class which will determine subclass
+ my $archive = new_ok($mod => [file => $file]);
+
+ isa_ok($archive, $distclass);
+ isa_ok($archive->archive, "Archive::$type");
+
+ # file
+ is($archive->file, $file, 'dumb accessor works');
+
+ # determine_name_and_version
+ $archive->determine_name_and_version();
+ is($archive->name, 'Dist-Metadata-Test-NoMetaFile', 'name from file');
+ is($archive->version, '0.1', 'version from file');
+
+ # file_content
+ is(
+ $archive->file_content('README'),
+ qq[This "dist" is for testing Dist::Metadata.\n],
+ 'got file content without specifying root dir'
+ );
+
+ # perllocale says, "By default Perl ignores the current locale."
+
+ # find_files
+ is_deeply(
+ [sort $archive->find_files],
+ [qw(
+ Dist-Metadata-Test-NoMetaFile-0.1/README
+ Dist-Metadata-Test-NoMetaFile-0.1/lib/Dist/Metadata/Test/NoMetaFile.pm
+ Dist-Metadata-Test-NoMetaFile-0.1/lib/Dist/Metadata/Test/NoMetaFile/PM.pm
+ )],
+ 'find_files'
+ );
+
+ # list_files (no root)
+ is_deeply(
+ [sort $archive->list_files],
+ [qw(
+ README
+ lib/Dist/Metadata/Test/NoMetaFile.pm
+ lib/Dist/Metadata/Test/NoMetaFile/PM.pm
+ )],
+ 'files listed without root directory'
+ );
+
+ # root
+ is($archive->root, 'Dist-Metadata-Test-NoMetaFile-0.1', 'root dir');
+
+ # do this last so that successful new() has already loaded the distclass
+ test_constructor_errors($distclass);
+}
+
+done_testing;
+
+# required_attribute
+# file doesn't exist
+sub test_constructor_errors {
+ my $mod = shift;
+
+ my $att = 'file';
+ is( $mod->required_attribute, $att, "'$att' attribute required" );
+ my $ex = exception { $mod->new() };
+ like($ex, qr/'$att' parameter required/, "new dies without '$att'");
+
+ my $dist = new_ok( $mod, [ file => 'does-not._exist_' ] );
+ $ex = exception { $dist->archive };
+ like($ex, qr/does not exist/, 'file does not exist');
+}
diff --git a/t/determine.t b/t/determine.t
new file mode 100644
index 0000000..4f9f49b
--- /dev/null
+++ b/t/determine.t
@@ -0,0 +1,129 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+
+my $mod = 'Dist::Metadata';
+my $smod = "${mod}::Struct";
+eval "require $_" || die $@
+ for $mod, $smod;
+
+$Dist::Metadata::VERSION ||= 0; # avoid undef warnings
+
+{
+ foreach my $test (
+ [
+ '/tmp/No-Existy-1.01',
+ [],
+ ['No-Existy', '1.01'],
+ undef # same
+ ],
+ [
+ # main module: No::Existy::3 (like perl5i::2)
+ 'No-Existy-3-v2.1.3',
+ [],
+ ['No-Existy-3', 'v2.1.3'],
+ undef # same
+ ],
+ [
+ # constructor args override
+ 'No-Existy-3-v2.1.3',
+ [
+ name => 'Who-Cares'
+ ],
+ ['No-Existy-3', 'v2.1.3'],
+ ['Who-Cares', 'v2.1.3'],
+ ],
+ [
+ # constructor args override
+ 'No-Existy-3-v2.1.3',
+ [
+ name => 'Who-Cares',
+ version => 5,
+ ],
+ ['No-Existy-3', 'v2.1.3'],
+ ['Who-Cares', '5'],
+ ],
+ ){
+ my ($base, $args, $parsed, $att) = @$test;
+ $att ||= $parsed;
+ # test dir name and tar file name
+ foreach my $path ( $base, "$base.tar.gz", "$base.tgz" ){
+ my $dm = new_ok($smod, [files => {}, @$args]);
+
+ my @nv = $dm->parse_name_and_version($path);
+ is_deeply(\@nv, $parsed, 'parsed name and version');
+
+ $dm->set_name_and_version(@nv);
+ is_deeply([$dm->name, $dm->version], $att, "set dist name and version");
+ }
+ }
+}
+
+{
+ my $struct = {
+ files => {
+ 'README' => 'we need a file to establish the root dir',
+ 'lib/Bunnies.pm' => <<'BUNNIES',
+package Bunnies;
+our $VERSION = 2.3;
+
+package # comment
+ HiddenBunnies;
+our $VERSION = 2.4;
+
+package TooManyBunnies;
+our $VERSION = 2.5;
+BUNNIES
+ 'lib/Rabbit/Hole.pm' => <<'HOLE',
+package Rabbit::Hole;
+our $VERSION = '1.1';
+
+package Rabbit::Hole::Cover;
+our $VERSION = '1.1';
+HOLE
+ # Test something that doesn't match the "simile" regexp in DM:determine_packages.
+ # Module::Metadata 1.000009 will find this but for obvious reasons PAUSE would not index it.
+ # If MM stops finding this we'll have to determine if there are
+ # any other possible file names that wouldn't match the regexp.
+ 'lib/.pm' => <<'GOOFY',
+package Goofy;
+our $VERSION = '0.1';
+GOOFY
+ },
+ };
+
+ is_deeply
+ new_ok($mod, [struct => $struct, include_inner_packages => 1])->determine_packages,
+ {
+ Bunnies => { file => 'lib/Bunnies.pm', version => '2.3', },
+ TooManyBunnies => { file => 'lib/Bunnies.pm', version => '2.5', },
+ Goofy => { file => 'lib/.pm', version => '0.1', },
+ 'Rabbit::Hole' => { file => 'lib/Rabbit/Hole.pm', version => '1.1' },
+ 'Rabbit::Hole::Cover' => { file => 'lib/Rabbit/Hole.pm', version => '1.1' },
+ },
+ 'determine all (not hidden) packages';
+
+ is_deeply
+ new_ok($mod, [struct => $struct])->determine_packages,
+ {
+ Bunnies => { file => 'lib/Bunnies.pm', version => '2.3', },
+ 'Rabbit::Hole' => { file => 'lib/Rabbit/Hole.pm', version => '1.1' },
+ },
+ 'determine only "simile" packages';
+
+ {
+ my $dm = new_ok($mod, [struct => $struct]);
+ my $cpan_meta = $dm->default_metadata;
+ push @{ $cpan_meta->{no_index}{namespace} ||= [] }, 'Rabbit'; # this is only about bunnies
+
+ is_deeply
+ $dm->determine_packages($dm->meta_from_struct($cpan_meta)),
+ {
+ Bunnies => { file => 'lib/Bunnies.pm', version => '2.3', },
+ },
+ 'determine only loadable modules, minus no_index/namespace';
+ }
+
+}
+
+done_testing;
diff --git a/t/dir.t b/t/dir.t
new file mode 100644
index 0000000..81c8a64
--- /dev/null
+++ b/t/dir.t
@@ -0,0 +1,115 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Test::Fatal;
+use Test::MockObject 1.09 ();
+use Path::Class 0.24 qw(file dir);
+use File::Spec ();
+
+my $tmpdir = File::Spec->tmpdir;
+
+my $mod = 'Dist::Metadata::Dir';
+eval "require $mod" or die $@;
+
+# required_attribute
+# extract_into
+{
+ my $att = 'dir';
+ is($mod->required_attribute, $att, "'$att' attribute required");
+ my $ex = exception { $mod->new() };
+ like($ex, qr/'$att' parameter required/, "new dies without '$att'");
+
+ $ex = exception { $mod->new(dir => $tmpdir)->extract_into($tmpdir) };
+ like( $ex, qr/A directory doesn't need to be extracted/, 'no extraction' );
+}
+
+# default_file_spec
+ is( $mod->default_file_spec, 'Native', 'default to native file spec for dir' );
+
+# dir
+# file_content
+# find_files
+# physical_directory
+
+{
+ # with no root dir
+ my $path = dir( qw(corpus noroot) );
+ my $dir = $path->stringify;
+ my $dist = new_ok( $mod, [ dir => $dir ] );
+
+ test_phys_dir($dist, $dir, $path);
+
+ my @files = (
+ file( qw(lib Dist Metadata Test NoRoot PM.pm) )->stringify,
+ file( qw(lib Dist Metadata Test NoRoot.pm) )->stringify,
+ 'README'
+ );
+
+ # no root, same as below
+ is_deeply([sort $dist->find_files], [sort @files], 'all files listed (full paths)');
+ # root stripped
+ is_deeply([sort $dist->list_files], [sort @files], 'all files listed (no root)');
+}
+# with root dir
+{
+ my $path = dir( qw(corpus subdir) );
+ my $dir = $path->stringify;
+ my $dist = new_ok( $mod, [ dir => $dir ] );
+
+ test_phys_dir($dist, $dir, $path->subdir($dist->root));
+
+ my @files = (
+ file( qw(lib Dist Metadata Test SubDir PM.pm) )->stringify,
+ file( qw(lib Dist Metadata Test SubDir.pm) )->stringify,
+ 'README'
+ );
+
+ # root present
+ is_deeply([sort $dist->find_files], [sort map { file($dist->root, $_)->stringify } @files], 'all files listed (full paths)');
+ # root stripped
+ is_deeply([sort $dist->list_files], [sort @files], 'all files listed (no root)');
+}
+
+# determine_name_and_version
+{
+ my %nv = (name => 'Dist-Metadata-Test-MetaFile', version => 2.2);
+ my $dir = dir( 'corpus', join('-', @nv{qw(name version)}) );
+ my $dist = new_ok( $mod, [ dir => $dir ] );
+
+ ok(!exists($dist->{$_}), "no dist $_" )
+ for keys %nv;
+
+ $dist->determine_name_and_version;
+
+ is($dist->$_, $nv{$_}, "determined dist $_" )
+ for keys %nv;
+}
+
+done_testing;
+
+sub test_phys_dir {
+ my ($dist, $dir, $subroot) = @_;
+ $subroot = $subroot->absolute;
+
+ is( $dist->dir, $dir, 'dir attribute from constructor arg' );
+ is( $dist->physical_directory, $subroot, 'dir + root' );
+
+ is_deeply(
+ [$dist->physical_directory('README')],
+ [$subroot, $subroot->file('README')],
+ 'physical directory with adjusted file'
+ );
+
+ is(
+ $dist->file_content('README'),
+ qq[This "dist" is for testing Dist::Metadata.\n],
+ 'file content'
+ );
+
+ like(
+ exception { $dist->file_content('missing.file') },
+ qr{Failed to open file 'corpus.+\w+.+missing\.file':},
+ 'die on missing file'
+ );
+
+}
diff --git a/t/dists.t b/t/dists.t
new file mode 100644
index 0000000..aa79260
--- /dev/null
+++ b/t/dists.t
@@ -0,0 +1,219 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Path::Class 0.24 qw(file);
+
+my $mod = 'Dist::Metadata';
+eval "require $mod" or die $@;
+$Dist::Metadata::VERSION ||= 0; # quiet warnings
+
+# we may need to prepend $FindBin::Bin
+my $root = 'corpus';
+my $structs = do "$root/structs.pl";
+
+# NOTE: Portability tests report issues with file names being long
+# and containing periods, so there could be issues...
+
+foreach my $test (
+ [
+ [
+ metafile =>
+ 'Dist-Metadata-Test-MetaFile-2.2',
+ ],
+ {
+ name => 'Dist-Metadata-Test-MetaFile',
+ version => '2.2',
+ provides => {
+ 'Dist::Metadata::Test::MetaFile' => {
+ file => 'lib/Dist/Metadata/Test/MetaFile.pm',
+ version => '2.1',
+ },
+ 'Dist::Metadata::Test::MetaFile::PM' => {
+ file => 'lib/Dist/Metadata/Test/MetaFile/PM.pm',
+ version => '2.0',
+ },
+ },
+ },
+ ],
+ [
+ [
+ metafile_incomplete =>
+ 'Dist-Metadata-Test-MetaFile-Incomplete-2.1',
+ ],
+ {
+ name => 'Dist-Metadata-Test-MetaFile-Incomplete',
+ version => '2.1',
+ provides => {
+ 'Dist::Metadata::Test::MetaFile::Incomplete' => {
+ file => 'lib/Dist/Metadata/Test/MetaFile/Incomplete.pm',
+ version => '2.1',
+ },
+ },
+ },
+ ],
+ [
+ [
+ nometafile =>
+ 'Dist-Metadata-Test-NoMetaFile-0.1',
+ ],
+ {
+ name => 'Dist-Metadata-Test-NoMetaFile',
+ version => '0.1',
+ provides => {
+ 'Dist::Metadata::Test::NoMetaFile' => {
+ file => 'lib/Dist/Metadata/Test/NoMetaFile.pm',
+ version => '0.1',
+ },
+ 'Dist::Metadata::Test::NoMetaFile::PM' => {
+ file => 'lib/Dist/Metadata/Test/NoMetaFile/PM.pm',
+ version => '0.1',
+ },
+ },
+ },
+ ],
+
+ [
+ [
+ index_like_pause => 'Dist-Metadata-Test-LikePause-0.1',
+ ],
+ {
+ name => 'Dist-Metadata-Test-LikePause',
+ version => '0.1',
+ provides => {
+ 'Dist::Metadata::Test::LikePause' => {
+ file => 'lib/Dist/Metadata/Test/LikePause.pm',
+ version => '0.1',
+ },
+ },
+ },
+ ],
+
+ [
+ [
+ index_like_pause => 'Dist-Metadata-Test-LikePause-0.1',
+ ],
+ {
+ name => 'Dist-Metadata-Test-LikePause',
+ version => '0.1',
+ provides => {
+ 'Dist::Metadata::Test::LikePause' => {
+ file => 'lib/Dist/Metadata/Test/LikePause.pm',
+ version => '0.1',
+ },
+ 'ExtraPackage' => {
+ file => 'lib/Dist/Metadata/Test/LikePause.pm',
+ version => '0.2',
+ },
+ },
+ },
+ {
+ # this we should find the Extra (inner) package
+ include_inner_packages => 1,
+ },
+ ],
+
+ [
+ [
+ nometafile_dev_release =>
+ 'Dist-Metadata-Test-NoMetaFile-DevRelease-0.1_1',
+ ],
+ {
+ name => 'Dist-Metadata-Test-NoMetaFile-DevRelease',
+ version => '0.1_1',
+ provides => {
+ 'Dist::Metadata::Test::NoMetaFile::DevRelease' => {
+ file => 'lib/Dist/Metadata/Test/NoMetaFile/DevRelease.pm',
+ version => '0.1_1',
+ },
+ },
+ },
+ ],
+ [
+ [
+ subdir =>
+ 'Dist-Metadata-Test-SubDir-1.5',
+ 'subdir',
+ ],
+ {
+ name => 'Dist-Metadata-Test-SubDir',
+ version => '1.5',
+ provides => {
+ 'Dist::Metadata::Test::SubDir' => {
+ file => 'lib/Dist/Metadata/Test/SubDir.pm',
+ version => '1.1',
+ },
+ 'Dist::Metadata::Test::SubDir::PM' => {
+ file => 'lib/Dist/Metadata/Test/SubDir/PM.pm',
+ version => '1.0',
+ },
+ },
+ },
+ ],
+ [
+ 'noroot',
+ {
+ # can't guess name/version without formatted file name or root dir
+ name => 'noroot', # modified in loop
+ version => '0',
+ provides => {
+ 'Dist::Metadata::Test::NoRoot' => {
+ file => 'lib/Dist/Metadata/Test/NoRoot.pm',
+ version => '3.3',
+ },
+ 'Dist::Metadata::Test::NoRoot::PM' => {
+ file => 'lib/Dist/Metadata/Test/NoRoot/PM.pm',
+ version => '3.25',
+ },
+ },
+ },
+ ],
+){
+ my ( $dists, $exp, $opts ) = @$test;
+ $exp->{package_versions} = do {
+ my $p = $exp->{provides};
+ +{ map { ($_ => $p->{$_}{version}) } keys %$p };
+ };
+
+ $dists = [ ($dists) x 2 ]
+ unless ref $dists;
+
+ my ($key, $file, $dir) = @$dists;
+
+ $dir ||= $file;
+ $_ = "corpus/$_" for ($file, $dir);
+
+ $_ = file($root, $_)->stringify
+ for @$dists;
+
+
+
+ foreach my $args (
+ [file => "$file.tar.gz"],
+ [file => "$file.zip"],
+ [dir => $dir],
+ [struct => { files => $structs->{$key} }],
+ ){
+
+ push @{ $args }, %{ $opts || {} };
+
+ my $dm = new_ok( $mod, $args );
+ # minimal name can be determined from file or dir but not struct
+ $exp->{name} = Dist::Metadata::UNKNOWN() if $key eq 'noroot' && $args->[0] eq 'struct';
+
+ # FIXME: perl 5.6.2 weirdness: http://www.cpantesters.org/cpan/report/4297a762-a314-11e0-b62c-be5be1de4735
+ # # Failed test 'verify corpus/noroot/lib/Dist/Metadata/Test/NoRoot/PM.pm for dir corpus/noroot'
+ # # at t/dists.t line 124.
+ # # Structures begin differing at:
+ # # $got = HASH(0x11c22d0)
+ # # $expected = undef
+ is_deeply( $dm->$_, $exp->{$_}, "verify $_ for @$args" ) || dump_if_automated([$dm, $_, $exp])
+ for keys %$exp;
+ }
+}
+
+done_testing;
+
+sub dump_if_automated {
+ diag(explain(@_))
+ if $ENV{AUTOMATED_TESTING};
+}
diff --git a/t/file_spec.t b/t/file_spec.t
new file mode 100644
index 0000000..a8c0ab3
--- /dev/null
+++ b/t/file_spec.t
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+
+my $mod = 'Dist::Metadata::Struct';
+eval "require $mod" or die $@;
+
+# all these translate into "Native"
+foreach my $test (
+ [ '' => 'Native' ],
+ [ qw( File::Spec Native ) ],
+ [ qw( File::Spec::Native Native ) ],
+ [ qw( Native Native ) ],
+ [ qw( Win32 Win32 ) ],
+ [ qw( File::Spec::Win32 Win32 ) ],
+) {
+ my ( $spec, $exp ) = @$test;
+ my $dist = new_ok( $mod, [ file_spec => $spec, files => {} ] );
+ is( $dist->file_spec, $exp, "spec '$spec' => '$exp'" );
+}
+
+# test using default File::Spec
+{
+ my $dist = new_ok( $mod, [ file_spec => '', files => {
+ README => 'read me',
+ 'Module.pm' => \"package Some::Module;\nour \$VERSION = 2;",
+ } ] );
+ is_deeply( $dist->determine_packages, {'Some::Module' => { file => 'Module.pm', version => 2 }},
+ 'found package in root' );
+}
+
+done_testing;
diff --git a/t/load_meta.t b/t/load_meta.t
new file mode 100644
index 0000000..e19fcdc
--- /dev/null
+++ b/t/load_meta.t
@@ -0,0 +1,55 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Test::MockObject 1.09;
+
+my ( $default, $loaded, $created ) = (-1) x 3;
+
+Test::MockObject->new->fake_module('CPAN::Meta',
+ VERSION => sub { 2 },
+ new => sub { bless { %{ $_[1] } }, $_[0] },
+ create => sub { $created = $_[1]; shift->new(@_) },
+ #as_struct => sub { +{ %{ $_[0] } } }, # unbless
+ (map { ( $_ => sub { undef } ) } qw(name version provides)),
+ map {
+ ( "load_${_}_string" => sub { $loaded = $_[1]; $_[0]->new({loaded => $_[1]}); } )
+ } qw(json yaml)
+);
+
+my $mod = 'Dist::Metadata';
+eval "require $mod" or die $@;
+$Dist::Metadata::VERSION ||= 0; # quiet warnings
+
+foreach my $test (
+ [ json => j => { 'META.json' => 'j' } ],
+ [ yaml => y => { 'META.yml' => 'y' } ],
+
+ # usually it's spelled .yml but yaml spec suggests .yaml
+ [ yaml => y => { 'tar/META.yaml' => 'y' } ],
+
+ # json preferred
+ [ json => j => { 'tar/META.json' => 'j', 'tar/META.yaml' => 'y' } ],
+ )
+{
+ my ( $type, $content, $files ) = @$test;
+ my $struct = { files => $files };
+
+ new_ok( $mod, [ struct => $struct, determine_packages => 0 ] )->load_meta;
+ is( $loaded, $content, "loaded $type" );
+ is( $created, $default, "loaded not created" );
+}
+
+reset_vars();
+
+new_ok( $mod,
+ [ struct => { files => { 'README' => 'nevermind' } }, determine_packages => 0 ]
+)->load_meta;
+
+is( $loaded, $default, 'meta file not found, not loaded' );
+is( ref($created), 'HASH', 'hash passed to create()' );
+
+done_testing;
+
+sub reset_vars {
+ ( $loaded, $created ) = ($default) x 2;
+}
diff --git a/t/module_info.t b/t/module_info.t
new file mode 100644
index 0000000..1fb8bc3
--- /dev/null
+++ b/t/module_info.t
@@ -0,0 +1,126 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Test::Fatal;
+use Path::Class;
+
+my $mod = 'Dist::Metadata';
+eval "require $mod" or die $@;
+
+test_module_info(
+ [file => file(qw(corpus Dist-Metadata-Test-NoMetaFile-0.1.tar.gz))->stringify],
+ {
+ 'Dist::Metadata::Test::NoMetaFile' => {
+ file => 'lib/Dist/Metadata/Test/NoMetaFile.pm',
+ version => '0.1',
+ md5 => 'd4a5a07d20dd1fdad6191d5950287609',
+ sha1 => '99d1aa7e3dbaa54dc16f178a8a4d2a9ba4d33da2',
+ sha256 => '7d888a6c321041adbc1225b3ca12ae22ebfccdf221e5e3f0ccb2dec1a9c0a71a',
+ },
+ 'Dist::Metadata::Test::NoMetaFile::PM' => {
+ file => 'lib/Dist/Metadata/Test/NoMetaFile/PM.pm',
+ version => '0.1',
+ md5 => '6e8845e06e7297bc913ebf3f1447c89a',
+ sha1 => '843ce5cd5443c7ae2792f7b58e069fcab64963c8',
+ sha256 => 'bc61da45e576a43155fcf296d03f74532bfe3a410f88aeaa75ade9155f67d049',
+ },
+ },
+);
+
+test_module_info(
+ [file => file(qw(corpus Dist-Metadata-Test-MetaFile-2.2.zip))->stringify],
+ {
+ 'Dist::Metadata::Test::MetaFile' => {
+ file => 'lib/Dist/Metadata/Test/MetaFile.pm',
+ version => '2.1',
+ md5 => '95fe72abee727b584941eda6da89f049',
+ sha1 => '2c4341d7778a78702e364f2c38c6c97b8410387d',
+ sha256 => '17dbde0b5b534d2a9ff9d188133da11670e3909ce853ac333aaa6973b348701e',
+ },
+ 'Dist::Metadata::Test::MetaFile::PM' => {
+ file => 'lib/Dist/Metadata/Test/MetaFile/PM.pm',
+ version => '2.0',
+ md5 => '873b2db91af4418020350d3337f6c173',
+ sha1 => '29553e76693b13b1e3d9f4493ee9d05c4cd4f6fb',
+ sha256 => '53c79b083cb731e2f642ae409459756a483b6912b99ed61c34edbbfb483ea7d1',
+ },
+ }
+);
+
+{
+ my $args = [
+ struct => {
+ files => {
+ 'fb/lib/Foo/Bar.pm' => "package Foo::Bar;\nour \$VERSION = 13;\n",
+ 'fb/README.txt' => "anything\n",
+ }
+ }
+ ];
+ my $exp = {
+ 'Foo::Bar' => {
+ file => 'lib/Foo/Bar.pm',
+ version => '13',
+ md5 => '8642ef750b6ca0d9c9afe5db4174e009',
+ sha1 => '2a4899cefacd1defd114731fec0e58c747eb9471',
+ sha256 => '368e2f18d80a866537153885807ddf6e0733168b683b0a7ecac6d257943ac894',
+ },
+ };
+
+ test_module_info($args, $exp);
+
+ my $dm = new_ok($mod => $args);
+ my $provides = {
+ 'Who::Cares' => {
+ file => 'README.txt',
+ version => 0,
+ },
+ };
+
+ # specify our own 'provides'
+ my $mi = $dm->module_info({digest => ['MD5', 'SHA-256'], provides => $provides});
+
+ # use official names
+ my $checksums = {
+ 'MD5' => 'f5b1321af715fbd4866590170ddbe8f6',
+ 'SHA-256' => 'ce32b18ae7f79e70f7cde4cf6077ae8b4195044307a78a4ea8761ddfedf9badc',
+ };
+
+ @{ $provides->{'Who::Cares'} }{ keys %$checksums } = values %$checksums;
+
+ is_deeply $provides, $mi, 'module info with official checksum names';
+}
+
+done_testing;
+
+sub test_module_info {
+ my ($args, $info) = @_;
+ my $dm = new_ok($mod => $args);
+
+ my $p = $dm->provides;
+ {
+ my $m = $dm->module_info;
+ is_deeply $p, $m, 'provides and module_info have the same';
+ is_deeply limit_keys($info), $m, 'sanity check - no checksums';
+ }
+
+ foreach my $checksums (
+ 'md5',
+ ['sha1'],
+ [qw(md5 sha256)],
+ ){
+ is_deeply limit_keys($info, $checksums), $dm->module_info({checksum => $checksums});
+ }
+}
+
+sub limit_keys {
+ my $hash = { %{ shift() } };
+ my @keys = map { ref($_) eq 'ARRAY' ? @$_ : $_ } (qw(file version), @_);
+
+ foreach my $mod ( keys %$hash ){
+ my $info = delete $hash->{ $mod };
+ my $new = $hash->{ $mod } = {};
+ @$new{ @keys } = @$info{ @keys };
+ }
+
+ return $hash;
+}
diff --git a/t/no_index.t b/t/no_index.t
new file mode 100644
index 0000000..0443244
--- /dev/null
+++ b/t/no_index.t
@@ -0,0 +1,86 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Path::Class qw( foreign_file );
+
+my $mod = 'Dist::Metadata';
+eval "require $mod" or die $@;
+$Dist::Metadata::VERSION ||= 0; # quiet warnings
+
+# specifically test that expected paths are not indexed on various platforms
+foreach my $spec ( qw(Unix Win32 Mac) ){
+ my $dm = new_ok($mod, [struct => {
+ file_spec => $spec,
+ files => {
+ README => 'nevermind',
+ foreign_file($spec => qw(lib Mod Name.pm)) => "package Mod::Name;\nour \$VERSION = 0.11;",
+ foreign_file($spec => qw(inc No.pm)) => "package No;\nour \$VERSION = 0.11;",
+ foreign_file($spec => qw(t lib YU.pm)) => "package YU;\nour \$VERSION = 0.11;",
+ }
+ }]);
+
+ is $dm->dist->file_spec, $spec, "dist faking file spec: $spec";
+
+ is_deeply
+ [sort $dm->dist->perl_files],
+ [sort grep { !/README/ } keys %{ $dm->dist->{files} }],
+ 'perl files listed';
+
+ is_deeply
+ $dm->package_versions,
+ {'Mod::Name' => '0.11'},
+ 't and inc not indexed';
+
+ is_deeply
+ $dm->determine_packages,
+ {'Mod::Name' => {file => 'lib/Mod/Name.pm', version => '0.11'}},
+ 'determined package with translated path';
+}
+
+sub indexed_ok {
+ my ($files, $exp, $desc) = @_;
+
+ my $dm = new_ok($mod, [struct => {
+ file_spec => 'Unix',
+ files => $files,
+ }]);
+
+ is_deeply $dm->package_versions, $exp, $desc;
+}
+
+sub _pkg {
+ my ($name, $version) = @_;
+ return "package $name;\nour \$VERSION = 0.$version;\n";
+}
+
+
+indexed_ok
+ {
+ 'META.json' => <<JSON,
+{
+ "name": "X",
+ "version": "1.1",
+ "no_index": {
+ "directory": [ "notthis" ]
+ }
+}
+JSON
+ 'lib/A/B.pm' => _pkg('A::B' => 2),
+ 't/T.pm' => _pkg('T' => 3),
+ 'xt/XT.pm' => _pkg('XT' => 4),
+ 'inc/Inc.pm' => _pkg('Inc' => 5),
+ 'local/Local.pm' => _pkg('Local' => 6),
+ 'perl5/Perl5.pm' => _pkg('Perl5' => 7),
+ 'fatlib/FatLib.pm' => _pkg('FatLib' => 8),
+ 'Root.pm' => _pkg('Some::Root' => 9),
+ 'notthis/More.pm' => _pkg('Some::More' => 10),
+ 'butthis/Moar.pm' => _pkg('Moar' => 11),
+ },
+ {
+ 'A::B' => '0.2',
+ 'Some::Root' => '0.9',
+ 'Moar' => '0.11',
+ },
+ q[Merge 'always' no_index dirs with specified no_index dirs];
+
+done_testing;
diff --git a/t/package_versions.t b/t/package_versions.t
new file mode 100644
index 0000000..8af1be9
--- /dev/null
+++ b/t/package_versions.t
@@ -0,0 +1,55 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+
+my $mod = 'Dist::Metadata';
+eval "require $mod" or die $@;
+
+{
+ foreach my $test (
+ [
+ {
+ buzzwords => {
+ file => 'lib/buzzwords.pm',
+ version => '0.1',
+ },
+ },
+ {
+ buzzwords => '0.1',
+ }
+ ],
+ [
+ {
+ fulfillment_issues => {
+ file => 'lib/fulfillment_issues.pm'
+ }
+ },
+ {
+ fulfillment_issues => undef,
+ }
+ ],
+ [
+ {
+ 'Design::Patterns' => {
+ file => 'lib/Design/Patterns.pm',
+ version => 0.2
+ },
+ 'Paradigm::Shift' => {
+ file => 'lib/Paradigm/Shift.pm',
+ version => 'v1.3.5',
+ }
+ },
+ {
+ 'Design::Patterns' => 0.2,
+ 'Paradigm::Shift' => 'v1.3.5',
+ },
+ ],
+ ){
+
+ my ($provides, $exp) = @$test;
+
+ is_deeply($mod->package_versions($provides), $exp, 'package_versions');
+ }
+}
+
+done_testing;
diff --git a/t/struct.t b/t/struct.t
new file mode 100644
index 0000000..7eb265c
--- /dev/null
+++ b/t/struct.t
@@ -0,0 +1,69 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+use Test::Fatal;
+use Test::MockObject 1.09 ();
+use Path::Class 0.24 qw(file dir);
+
+my $mod = 'Dist::Metadata::Struct';
+eval "require $mod" or die $@;
+
+# required_attribute
+{
+ my $att = 'files';
+ is( $mod->required_attribute, $att, "'$att' attribute required" );
+ my $ex = exception { $mod->new() };
+ like( $ex, qr/'$att' parameter required/, "new dies without '$att'" );
+}
+
+# don't create a dependency on IO::String or IO::Scalar for this simple test.
+my $io = Test::MockObject->new({});
+$io->mock(getline => sub { 'read me' });
+
+# file_content
+# find_files
+foreach my $test (
+ [ string => 'read me' ],
+ [ scalar_ref => \'read me' ],
+ [ io => $io ],
+) {
+ my ( $type, $content ) = @$test;
+ my $dist = new_ok( $mod, [ files => { README => $content } ] );
+ is( $dist->file_content('README'), 'read me', "content returned for $type" );
+ is_deeply( [ $dist->find_files ], ['README'], 'all files listed' );
+}
+
+{
+ my $dist = new_ok( $mod, [ files => { 'root/README' => 'please', 'root/SECRET' => 'shhhh' } ] );
+ {
+ my $dir = $dist->physical_directory('README');
+ ok( -d $dir, 'phyiscal directory exists' );
+ }
+ my @dir_and_files = $dist->physical_directory('README');
+ is(scalar @dir_and_files, 2, 'list returned');
+ is($dir_and_files[1], file($dir_and_files[0], 'README'), 'full path to file');
+ ok(-e $dir_and_files[1], 'extracted file exists');
+}
+
+# default_file_spec
+# file_spec
+# find_files
+# determine_packages
+{
+ my $defspec = 'Unix';
+ my $spec = 'Win32';
+ my $dist = new_ok($mod, [file_spec => $spec, files => {
+ README => 'nevermind',
+ 'lib\\Mod\\Name.pm' => "package Mod::Name;\nour \$VERSION = 0.11;"
+ }]);
+ is( $dist->default_file_spec, $defspec, "struct defaults to $defspec" );
+ is( $dist->file_spec, $spec, "struct has custom spec: $spec" );
+
+ # TODO: should paths always come out in unix format? perhaps not if you specify an alternate...
+ is_deeply( [sort $dist->find_files], ['README', 'lib\\Mod\\Name.pm'], 'all files listed' );
+
+ is_deeply( $dist->determine_packages, {'Mod::Name' => {file => 'lib/Mod/Name.pm', version => '0.11'}},
+ 'determined package with translated path' );
+}
+
+done_testing;
diff --git a/t/tar.t b/t/tar.t
new file mode 100644
index 0000000..1c863cb
--- /dev/null
+++ b/t/tar.t
@@ -0,0 +1,29 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+
+my $mod = 'Dist::Metadata::Tar';
+eval "require $mod" or die $@;
+
+my $base = 'corpus/Dist-Metadata-Test-NoMetaFile-0.1';
+
+# test that instantiating this class directly does not negotiate type
+new_ok($mod => [file => "$base.zip"]);
+
+my $file = "$base.tar.gz";
+my $tar = new_ok($mod => [file => $file]);
+
+# file_content, and find_files tested in t/archive.t
+
+# read_archive
+isa_ok($tar->read_archive($file), 'Archive::Tar');
+
+# tar
+{
+ my $warning;
+ local $SIG{__WARN__} = sub { $warning = $_[0] };
+ isa_ok($tar->tar, 'Archive::Tar');
+ like($warning, qr/deprecated/, 'tar() works but is deprecated');
+}
+
+done_testing;
diff --git a/t/zip.t b/t/zip.t
new file mode 100644
index 0000000..59d82d4
--- /dev/null
+++ b/t/zip.t
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use Test::More 0.96;
+
+my $mod = 'Dist::Metadata::Zip';
+eval "require $mod" or die $@;
+
+my $base = 'corpus/Dist-Metadata-Test-NoMetaFile-0.1';
+
+# test that instantiating this class directly does not negotiate type
+new_ok($mod => [file => "$base.tgz"]);
+
+my $file = "$base.zip";
+my $zip = new_ok($mod => [file => $file]);
+
+# file_content, and find_files tested in t/archive.t
+
+# read_archive
+isa_ok($zip->read_archive($file), 'Archive::Zip');
+
+done_testing;