App-makefilepl2cpanfile

 view release on metacpan or  search on metacpan

README.md  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)
```

## 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.978 second using v1.01-cache-2.11-cpan-28bccf0f6e5 )