App-makefilepl2cpanfile
view release on metacpan or search on metacpan
# parse_prereqs() â documented return structure contracts
'parse_prereqs.return.hashref' => q{Returns a HashRef},
'parse_prereqs.return.absent_phase_missing' => q{Absent phases not present in hashref},
'parse_prereqs.return.version_zero' => q{version is 0 when no minimum declared},
'parse_prereqs.return.comment_undef' => q{comment is undef when no inline comment},
'parse_prereqs.map.prereq_pm' => q{PREREQ_PM maps to runtime/requires},
'parse_prereqs.map.build_requires' => q{BUILD_REQUIRES maps to build/requires},
'parse_prereqs.map.test_requires' => q{TEST_REQUIRES maps to test/requires},
'parse_prereqs.map.configure_requires' => q{CONFIGURE_REQUIRES maps to configure/requires},
'parse_prereqs.structured.requires' => q{Structured prereqs block: requires},
'parse_prereqs.structured.recommends' => q{Structured prereqs block: recommends},
'parse_prereqs.structured.suggests' => q{Structured prereqs block: suggests},
'parse_prereqs.meta_merge.prereqs' => q{META_MERGE nested prereqs extracted},
'parse_prereqs.inline_comment' => q{Inline comment captured verbatim},
'parse_prereqs.silent.unrecognised' => q{Unrecognised content silently ignored},
);
# Helper: mark a ledger entry as covered, fail loudly if the key was never registered.
sub covered {
my ($key) = @_;
fail("Unknown ledger key '$key'") unless exists $LEDGER{$key};
delete $LEDGER{$key};
}
# -----------------------------------------------------------------------
# Shared fixture factories
# -----------------------------------------------------------------------
# Set up a redirected home directory so _load_develop_config never touches
# the developer's real ~/.config during testing. Each helper returns a
# mock_scoped guard; callers must hold it (my $g = empty_home()) so the
# mock stays active for the lifetime of the enclosing subtest block.
# A fresh tempdir representing a home dir with NO config file.
sub empty_home {
my $h = tempdir( CLEANUP => 1 );
return mock_scoped 'File::HomeDir::my_home' => sub { $h };
}
# Write a YAML config to a fake home dir and return the scoped guard.
sub home_with_config {
my ($data) = @_;
my $h = tempdir( CLEANUP => 1 );
path($h)->child('.config')->mkpath;
YAML::Tiny->new($data)
->write( path($h)->child('.config', 'makefilepl2cpanfile.yml')->stringify );
return mock_scoped 'File::HomeDir::my_home' => sub { $h };
}
# Write a Makefile.PL into a tempdir and return its path object.
sub make_mf {
my ($content) = @_;
my $dir = tempdir( CLEANUP => 1 );
my $mf = path($dir)->child('Makefile.PL');
$mf->spew_utf8($content);
return $mf;
}
Readonly my $MF_SIMPLE =>
"WriteMakefile(PREREQ_PM => { 'Try::Tiny' => 0 });\n";
Readonly my $MF_VERSIONED =>
"WriteMakefile(PREREQ_PM => { 'Moo' => '2.000' });\n";
# -----------------------------------------------------------------------
# generate() â error / message paths
# -----------------------------------------------------------------------
subtest 'generate() â croak on unreadable makefile' => sub {
my $g = empty_home();
# Missing file: the path does not exist at all.
my $missing = '/no/such/path/Makefile.PL';
throws_ok {
App::makefilepl2cpanfile::generate( makefile => $missing )
} qr/Cannot read '\Q$missing\E'/, q{exact croak message for missing file};
covered('generate.croak.cannot_read.missing');
# Directory: the path exists but is not a file.
my $dir = tempdir( CLEANUP => 1 );
throws_ok {
App::makefilepl2cpanfile::generate( makefile => $dir )
} qr/Cannot read '\Q$dir\E'/, q{exact croak message when path is a directory};
covered('generate.croak.cannot_read.directory');
diag 'generate croak paths verified' if $ENV{TEST_VERBOSE};
};
subtest 'generate() â croak on malformed YAML config' => sub {
# Config file must exist so _load_develop_config proceeds to YAML::Tiny->read.
my $g_home = home_with_config( {} ); # creates the file; content irrelevant
# mock_scoped with multiple pairs restores both methods when $g_yaml goes out
# of scope at the end of this subtest block.
my $g_yaml = mock_scoped(
'YAML::Tiny::read' => sub { undef },
'YAML::Tiny::errstr' => sub { 'synthetic YAML failure' },
);
my $mf = make_mf($MF_SIMPLE);
throws_ok {
App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 1,
)
} qr/Failed to parse .+: synthetic YAML failure/,
q{croak message includes path and YAML error string};
covered('generate.croak.failed_to_parse_yaml');
diag 'generate YAML croak verified' if $ENV{TEST_VERBOSE};
};
subtest 'generate() â carp when config has no develop key' => sub {
# Config file exists but its 'develop' key is absent.
my $g = home_with_config( { other_section => { tool => 1 } } );
my $mf = make_mf($MF_SIMPLE);
my @warnings;
local $SIG{__WARN__} = sub { push @warnings, @_ };
App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 1,
);
ok scalar @warnings > 0, 'a carp warning was issued';
like $warnings[0], qr/No 'develop' key found in .+; using defaults/,
q{carp message matches documented text exactly};
covered('generate.carp.no_develop_key');
diag 'generate carp verified' if $ENV{TEST_VERBOSE};
};
# -----------------------------------------------------------------------
# generate() â return value contract
# -----------------------------------------------------------------------
subtest 'generate() â return value is Str terminated with a single newline' => sub {
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 0,
);
ok defined $out, 'return value is defined';
ok !ref $out, 'return value is a plain scalar (Str)';
like $out, qr/\n$/, 'output ends with a newline';
ok $out !~ /\n\n$/, 'output does not end with a double newline';
covered('generate.return.string_single_newline');
diag 'generate return contract verified' if $ENV{TEST_VERBOSE};
};
# -----------------------------------------------------------------------
# generate() â default argument values
# -----------------------------------------------------------------------
subtest 'generate() â default argument: makefile is "Makefile.PL"' => sub {
my $g = empty_home();
# The POD says: default makefile is 'Makefile.PL'. We test this by
# chdir-ing to a tempdir that contains a Makefile.PL and calling generate()
# without a makefile argument.
my $dir = tempdir( CLEANUP => 1 );
path($dir)->child('Makefile.PL')->spew_utf8($MF_SIMPLE);
my $orig = Path::Tiny->cwd;
chdir $dir;
my $out;
lives_ok {
$out = App::makefilepl2cpanfile::generate( with_develop => 0 )
} 'generate() reads Makefile.PL from cwd when no makefile arg given';
like $out, qr/Try::Tiny/, 'content from default Makefile.PL appears in output';
chdir "$orig";
covered('generate.arg.default_makefile');
};
subtest 'generate() â default argument: existing is empty string' => sub {
my $g = empty_home();
# When 'existing' is omitted, no pre-existing develop entries should be
# merged (there are none to merge from an empty string).
my $mf = make_mf($MF_SIMPLE);
my $out;
lives_ok {
$out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 0,
)
} 'generate() with no existing arg lives';
# The output must contain the parsed module but no phantom develop entries
# from a non-existent pre-existing cpanfile.
like $out, qr/Try::Tiny/, 'parsed module present';
unlike $out, qr/on 'develop' => sub/, 'no develop block from empty existing';
covered('generate.arg.default_existing_empty');
};
subtest 'generate() â default argument: with_develop is 1 (true)' => sub {
my $g = empty_home(); # no config file -> uses DEFAULT_DEVELOP
my $mf = make_mf($MF_SIMPLE);
my $out = App::makefilepl2cpanfile::generate( makefile => "$mf" );
# A develop block must appear because with_develop defaults to 1.
like $out, qr/on 'develop' => sub/,
'develop block present when with_develop omitted (default true)';
covered('generate.arg.default_with_develop_true');
};
# -----------------------------------------------------------------------
# generate() â calling style variants
# -----------------------------------------------------------------------
subtest 'generate() â flat hash calling style' => sub {
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $out = App::makefilepl2cpanfile::generate( makefile => "$mf", with_develop => 0 );
like $out, qr/Try::Tiny/, 'flat hash calling style works';
covered('generate.calling.flat_hash');
};
subtest 'generate() â hashref calling style' => sub {
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $out = App::makefilepl2cpanfile::generate(
{ makefile => "$mf", with_develop => 0 }
);
like $out, qr/Try::Tiny/, 'single hashref calling style works';
covered('generate.calling.hashref');
};
# -----------------------------------------------------------------------
# generate() â with_develop behaviour
# -----------------------------------------------------------------------
subtest 'generate() â develop block injected when with_develop => 1' => sub {
# Use an empty home so default tools (Perl::Critic etc.) are injected.
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 1,
);
like $out, qr/on 'develop' => sub/, 'develop block present';
# At least one of the four default tools must appear.
like $out, qr/Perl::Critic|Devel::Cover|Test::Pod/, 'default dev tool present';
covered('generate.develop.injected_when_true');
};
subtest 'generate() â develop block suppressed when with_develop => 0' => sub {
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 0,
);
unlike $out, qr/on 'develop' => sub/, 'no develop block when with_develop is false';
covered('generate.develop.suppressed_when_false');
};
# -----------------------------------------------------------------------
# generate() â existing develop block merging (all three relationship types)
# -----------------------------------------------------------------------
subtest 'generate() â merge existing develop requires' => sub {
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $existing = "on 'develop' => sub {\n requires 'My::Linter';\n};\n";
my $out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
existing => $existing,
with_develop => 0,
);
like $out, qr/My::Linter/, 'hand-curated develop requires entry preserved';
covered('generate.develop.merge_existing_requires');
};
subtest 'generate() â merge existing develop recommends' => sub {
my $g = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $existing = "on 'develop' => sub {\n recommends 'My::NiceGUI';\n};\n";
my $out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
existing => $existing,
with_develop => 0,
);
( run in 1.022 second using v1.01-cache-2.11-cpan-2aafcb1aa8b )