App-makefilepl2cpanfile
view release on metacpan or search on metacpan
post result â Str
â§ prefix(result, HEADER â "\n") â§ last(result) = '\n' ⧠¬ suffix(result, "\n\n")
â§ Eâ² = E -- nothing is written
â§ $@â² = $@ â§ $!â² = $! â§ $_â² = $_
-- every value from a file or the environment in a message is
-- printable(v): control and bidi characters as \x{..}
-- Failure cases (the function dies):
a.content = ⥠⧠(¬ regular(a.makefile) ⨠¬ readable(a.makefile)) â croak("Cannot read '" â a.makefile â "'")
a.with_develop â§ regular(cfg(E)) ⧠¬ yaml_ok(cfg(E)) â croak("Failed to parse " â cfg(E) â ": " â err)
a.with_develop â§ stat(cfg(E)) fails with errno â {ENOENT, ENOTDIR} â croak("Failed to parse " â cfg(E) â ": " â errno)
```
## State Diagram
The module keeps no state between calls: every call starts at START and
ends at RETURN or at an error. The diagram shows the states that one
call to `generate()` passes through. `parse_prereqs()` is the single
step PARSE.
```perl
lib/App/makefilepl2cpanfile.pm view on Meta::CPAN
post result â Str
â§ prefix(result, HEADER â "\n") â§ last(result) = '\n' ⧠¬ suffix(result, "\n\n")
â§ Eâ² = E -- nothing is written
â§ $@â² = $@ â§ $!â² = $! â§ $_â² = $_
-- every value from a file or the environment in a message is
-- printable(v): control and bidi characters as \x{..}
-- Failure cases (the function dies):
a.content = ⥠⧠(¬ regular(a.makefile) ⨠¬ readable(a.makefile)) â croak("Cannot read '" â a.makefile â "'")
a.with_develop â§ regular(cfg(E)) ⧠¬ yaml_ok(cfg(E)) â croak("Failed to parse " â cfg(E) â ": " â err)
a.with_develop â§ stat(cfg(E)) fails with errno â {ENOENT, ENOTDIR} â croak("Failed to parse " â cfg(E) â ": " â errno)
=head1 STATE DIAGRAM
The module keeps no state between calls: every call starts at START and
ends at RETURN or at an error. The diagram shows the states that one
call to C<generate()> passes through. C<parse_prereqs()> is the single
step PARSE.
+-------+
| START | generate(%args) is called
t/edge_cases.t view on Meta::CPAN
throws_ok {
App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 1,
)
} qr/Failed to parse .+ connection timed out/s,
'YAML upstream failure croaks with path and error message';
};
subtest 'upstream: config path cannot be examined (stat() failure) - croaks' => sub {
# Simulates a filesystem that denies access after the home directory was
# resolved (the same class of failure as an NFS mount going stale). The
# config may exist, so silently falling back to the defaults would change
# the output without explanation; the error must reach the caller. A
# real EACCES is produced by making ~/.config unsearchable.
SKIP: {
my $home = path(tempdir(CLEANUP => 1));
skip why_not('search', $home), 1 unless can_revoke_search($home);
my $cfg_dir = $home->child('.config');
$cfg_dir->mkpath;
$cfg_dir->child('makefilepl2cpanfile.yml')->spew_utf8("develop:\n X: 1\n");
my $g = mock_scoped 'File::HomeDir::my_home' => sub { "$home" };
my $mf = make_mf($MF_SIMPLE);
my $msg_eacces = do { local $! = POSIX::EACCES(); "$!" };
with_revoked(search => "$cfg_dir", sub {
throws_ok {
App::makefilepl2cpanfile::generate(makefile => "$mf", with_develop => 1)
} qr/\AFailed to parse \Q$cfg_dir\E\/makefilepl2cpanfile\.yml: \Q$msg_eacces\E at /,
'stat() failure on the config path croaks with the errno text';
});
}
};
# -----------------------------------------------------------------------
# SECTION 8: Mid-flight Hardware/OS Failure Simulation
#
# Injects POSIX errno-flavoured failures into the I/O wrapper layer
# (Path::Tiny, YAML::Tiny) AFTER the readability guards have already
# passed. This simulates hardware faults, TOCTOU races, interrupted
t/edge_cases.t view on Meta::CPAN
die "mmap: $!\n";
};
throws_ok {
App::makefilepl2cpanfile::generate(makefile => "$mf", with_develop => 0)
} qr/\Q$MSG_ENOMEM\E/,
"ENOMEM during buffer alloc propagates with canonical '$MSG_ENOMEM' string";
};
subtest 'io-failure-read: unexpected EOF - slurp_utf8 returns truncated Makefile.PL' => sub {
# Simulate a file whose size shrank between stat() and read() (a log
# rotator truncated the wrong file, or a network FS returned a stale
# inode size). The truncated string ends mid-PREREQ_PM (no closing
# brace), so generate() must return a valid header-only cpanfile, not
# a partial or malformed entry.
my $g_home = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $g_slurp = mock_scoped 'Path::Tiny::slurp_utf8' => sub {
return "WriteMakefile(PREREQ_PM => { \x27Carp\x27 =>"; # truncated mid-value
};
( run in 0.842 second using v1.01-cache-2.11-cpan-28bccf0f6e5 )