App-makefilepl2cpanfile
view release on metacpan or search on metacpan
t/edge_cases.t view on Meta::CPAN
with_develop => 0,
)
} 'path with spaces does not trigger shell-quoting issues';
like $out, qr/Carp/, 'correct output from path with spaces';
};
subtest 'generate: path with shell-injection characters is rejected by Cannot-read guard' => sub {
# Paths like "/tmp; rm -rf /" contain shell metacharacters, but since
# Perl and Path::Tiny never pass them to a shell, they are benign file
# paths. No such file exists, so the guard croaks safely.
my $g = empty_home();
throws_ok {
App::makefilepl2cpanfile::generate(makefile => '/tmp/; rm -rf /; #.pl')
} qr/Cannot read/, 'shell-injection path is rejected (file does not exist)';
throws_ok {
App::makefilepl2cpanfile::generate(makefile => "/tmp/Make\nfile.PL")
} qr/Cannot read/, 'path with embedded newline is rejected (file does not exist)';
};
subtest 'generate: extremely long path is rejected by Cannot-read guard' => sub {
# Most filesystems cap path lengths well below PATH_MAX (4096 on Linux).
# A 5000-char path cannot correspond to an existing file; the guard must
# croak cleanly without crashing the regex or OS call.
my $g = empty_home();
my $long_path = 'M' x 5000;
throws_ok {
App::makefilepl2cpanfile::generate(makefile => $long_path)
} qr/Cannot read/, 'extremely long path causes croak gracefully';
};
# -----------------------------------------------------------------------
# SECTION 7: Upstream Failure Simulation via Test::Mockingbird
#
# These tests simulate real-world upstream failures: I/O interruptions,
# missing home directory (containers), and YAML service timeouts.
# They verify that the module propagates errors clearly without masking
# the root cause or leaving the process in a corrupted state.
# -----------------------------------------------------------------------
subtest 'upstream: Path::Tiny::slurp_utf8 dies â generate propagates the I/O error' => sub {
# Simulates a network filesystem going down between the readability check
# and the actual read, or a catastrophic hardware read error.
# Strategy: create a valid Makefile.PL (passes -f && -r), then mock
# slurp_utf8 to die so the error surfaces after the guard.
my $g_home = empty_home();
my $mf = make_mf($MF_SIMPLE);
my $g_slurp = mock_scoped 'Path::Tiny::slurp_utf8' => sub {
die "Input/output error: simulated disk failure\n";
};
throws_ok {
App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 0,
)
} qr/Input\/output error.*simulated disk failure/,
'slurp_utf8 I/O failure propagates transparently from generate()';
};
subtest 'upstream: File::HomeDir::my_home returns undef â falls back to defaults (BUG 3)' => sub {
# Simulates a container or CI job that runs under a user with no home
# directory set (e.g., nobody, or a UID with no passwd entry).
# BUG 3 (fixed): before the fix, path(undef) croaked from Path::Tiny
# with "positive-length parts" error, not a clean fallback to defaults.
my $g_home = mock_scoped 'File::HomeDir::my_home' => sub { undef };
my $mf = make_mf($MF_SIMPLE);
my $out;
lives_ok {
$out = App::makefilepl2cpanfile::generate(
makefile => "$mf",
with_develop => 1, # forces _load_develop_config call
)
} 'undef my_home does not crash (BUG 3 fixed â falls back to defaults)';
like $out, qr/Perl::Critic/,
'default develop tool present â fallback to %DEFAULT_DEVELOP works';
like $out, qr/on 'develop' => sub/,
'develop block was emitted using default tools';
diag "Output:\n$out" if $ENV{TEST_VERBOSE};
};
subtest 'upstream: YAML::Tiny simulated timeout â croak includes path and message' => sub {
# Simulates a YAML parsing backend that fails mid-operation (e.g. a remote
# config service returning an error, or a corrupt/truncated YAML file).
# The croak message must include both the config file path and the upstream
# error text so operators can diagnose the failure.
my $dir = tempdir(CLEANUP => 1);
path($dir)->child('.config')->mkpath;
path($dir)->child('.config', 'makefilepl2cpanfile.yml')->spew_utf8(
"develop:\n Perl::Critic: 0\n" # valid YAML on disk
);
my $g_home = mock_scoped 'File::HomeDir::my_home' => sub { $dir };
my $g_yaml = mock_scoped(
'YAML::Tiny::read' => sub { undef },
'YAML::Tiny::errstr' => sub { 'connection timed out after 30s' },
);
my $mf = make_mf($MF_SIMPLE);
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: Path::Tiny::is_file dies (simulated stat() failure) â propagates' => sub {
# Simulates a filesystem that has become unreachable after the home
# directory was resolved (e.g. an NFS mount that went stale between
# the my_home() call and the subsequent is_file() check on the config path).
# The error must propagate out of generate() without being swallowed.
my $g_home = empty_home();
( run in 3.069 seconds using v1.01-cache-2.11-cpan-354807fb38d )