App-makefilepl2cpanfile

 view release on metacpan or  search on metacpan

t/integration.t  view on Meta::CPAN

use strict;
use warnings;

# Integration tests for App::makefilepl2cpanfile.
#
# These tests validate end-to-end workflows that cross function boundaries:
# parse_prereqs() feeding generate(), config loading interacting with the
# develop block, develop-merge semantics, format invariants across scenarios,
# and statelessness under repeated or interleaved calls.
#
# NOTE ON OPTIONAL DEPENDENCIES
# All CPAN dependencies in this module are hard 'use' requirements — there are
# no soft/optional require paths.  Test::Without::Module is therefore not
# applicable.  The closest analog is the OPTIONAL CONFIG FILE at
# ~/.config/makefilepl2cpanfile.yml; its absent/present/malformed paths are
# exercised in dedicated subtests below.

use Test::Most;
use Test::Mockingbird;
use File::Temp qw(tempdir);
use Path::Tiny;
use Readonly;
use YAML::Tiny;

use_ok('App::makefilepl2cpanfile');

# -----------------------------------------------------------------------
# Fixture constants — shared across all subtests.
# -----------------------------------------------------------------------

# Makefile.PL with only PREREQ_PM entries; no version constraints, no comments.
Readonly my $MF_SIMPLE => <<'END';
use ExtUtils::MakeMaker;
WriteMakefile(
	PREREQ_PM => {
		'Moo'     => 0,
		'Try::Tiny' => 0,
	},
);
END

# Makefile.PL with all four simple key types.
Readonly my $MF_ALL_PHASES => <<'END';
use ExtUtils::MakeMaker;
WriteMakefile(
	CONFIGURE_REQUIRES => { 'ExtUtils::MakeMaker' => '6.64' },
	BUILD_REQUIRES     => { 'Module::Build' => '0.42' },
	TEST_REQUIRES      => { 'Test::More' => '0.98' },
	PREREQ_PM          => { 'Scalar::Util' => 0 },
);
END

# Makefile.PL with a MIN_PERL_VERSION declaration.
Readonly my $MF_MIN_PERL => <<'END';
use ExtUtils::MakeMaker;
WriteMakefile(
	MIN_PERL_VERSION => '5.010',
	PREREQ_PM => { 'Moo' => 0 },
);
END

# Makefile.PL with inline comments on dependency lines.
Readonly my $MF_COMMENTS => <<'END';
use ExtUtils::MakeMaker;
WriteMakefile(
	PREREQ_PM => {
		'HTTP::Tiny' => 0,    # used for HTTP calls
		'JSON::PP'   => '2.00', # decode API responses
	},
);
END

# Makefile.PL with a CPAN Meta Spec structured prereqs block.
Readonly my $MF_STRUCTURED => <<'END';
use ExtUtils::MakeMaker;
WriteMakefile(
	prereqs => {
		runtime => {
			requires   => { 'Moo'            => '2.00' },
			recommends => { 'Moo::XS'        => 0      },
			suggests   => { 'MooX::TypeTiny' => 0      },
		},
		test => {
			requires => { 'Test::More' => '0.98' },
		},
	},
);
END

# Makefile.PL with a META_MERGE nested prereqs block (recommends).
Readonly my $MF_META_MERGE => <<'END';
use ExtUtils::MakeMaker;
WriteMakefile(
	PREREQ_PM => { 'Carp' => 0 },
	META_MERGE => {
		prereqs => {
			runtime => {

t/integration.t  view on Meta::CPAN

		ok $out !~ /\n\n\n/,
			"[$name] output has no triple (or more) consecutive newlines";

		# Runtime deps must NOT be inside an 'on' block.
		# (Simple test: if requires 'Moo' appears, it must not be inside on 'runtime')
		ok $out !~ /on 'runtime' => sub/,
			"[$name] no 'on runtime' block (runtime emitted at top level)";
	}
};

# -----------------------------------------------------------------------
# 14. Alphabetical sort invariant within phase blocks
#
# Strategy: verify that within each relationship group, modules are emitted
# in alphabetical order regardless of the order they appear in Makefile.PL.
# -----------------------------------------------------------------------

subtest 'output format: modules sorted alphabetically within each phase' => sub {
	my $g = empty_home();

	# Declare modules in reverse alphabetical order in the Makefile.PL.
	my $mf = make_mf(<<'END');
WriteMakefile(
	PREREQ_PM => {
		'Zeta::Module' => 0,
		'Alpha::First' => 0,
		'Mu::Middle'   => 0,
	},
);
END

	my $out = App::makefilepl2cpanfile::generate(
		makefile     => "$mf",
		with_develop => 0,
	);

	diag "Output:\n$out" if $ENV{TEST_VERBOSE};

	# Extract the order of the three modules in the output.
	my @order;
	while ($out =~ /requires '(Alpha::First|Mu::Middle|Zeta::Module)'/g) {
		push @order, $1;
	}

	is_deeply \@order, [qw(Alpha::First Mu::Middle Zeta::Module)],
		'modules emitted in alphabetical order regardless of Makefile.PL order';
};

# -----------------------------------------------------------------------
# 15. Statelessness: independent generate() calls do not interfere
#
# Strategy: call generate() with two different Makefile.PLs (A and B),
# then call generate() for A again.  The first and third outputs must be
# identical, and A must differ from B.  This confirms no shared mutable
# state exists between calls.
# -----------------------------------------------------------------------

subtest 'statelessness: independent generate() calls do not interfere' => sub {
	my $g = empty_home();

	my $mf_a = make_mf($MF_SIMPLE);       # Moo + Try::Tiny
	my $mf_b = make_mf($MF_ALL_PHASES);   # completely different modules

	my $out_a1 = App::makefilepl2cpanfile::generate(
		makefile => "$mf_a", with_develop => 0
	);
	my $out_b = App::makefilepl2cpanfile::generate(
		makefile => "$mf_b", with_develop => 0
	);
	my $out_a2 = App::makefilepl2cpanfile::generate(
		makefile => "$mf_a", with_develop => 0
	);

	is   $out_a2, $out_a1, 'same Makefile.PL produces identical output on second call';
	isnt $out_b,  $out_a1, 'different Makefile.PLs produce different outputs';

	diag "A1:\n$out_a1\nB:\n$out_b" if $ENV{TEST_VERBOSE};
};

# -----------------------------------------------------------------------
# 16. Statelessness: interleaved parse_prereqs() calls
#
# Strategy: call parse_prereqs() with content A, then with empty content,
# then with content A again.  The first and third results must be deeply
# equal, and the second (empty) must return an empty hashref.
# This checks that the function holds no mutable state between calls.
# -----------------------------------------------------------------------

subtest 'statelessness: interleaved parse_prereqs() calls' => sub {
	my $content_a = $MF_ALL_PHASES;

	my $result_a1    = App::makefilepl2cpanfile::parse_prereqs($content_a);
	my $result_empty = App::makefilepl2cpanfile::parse_prereqs('');
	my $result_a2    = App::makefilepl2cpanfile::parse_prereqs($content_a);

	is_deeply $result_a2, $result_a1,
		'parse_prereqs() returns same result for same content on repeated calls';

	is_deeply $result_empty, {},
		'parse_prereqs() returns empty hashref for empty content';
};

# -----------------------------------------------------------------------
# 17. Version constraint emission: versioned and unversioned deps
#
# Strategy: a version of 0 must produce no version constraint in the
# cpanfile output; a non-zero version must produce a quoted version
# argument.  Both parse_prereqs and generate are checked end-to-end.
# -----------------------------------------------------------------------

subtest 'pipeline: versioned and unversioned deps emitted correctly' => sub {
	my $g = empty_home();

	my $mf = make_mf(<<'END');
WriteMakefile(
	PREREQ_PM => {
		'Unversioned' => 0,
		'Versioned'   => '1.23',
	},
);
END



( run in 1.512 second using v1.01-cache-2.11-cpan-6de40a662fe )