App-Project-Doctor
view release on metacpan or search on metacpan
lib/App/Project/Doctor/Check/CpanReadiness.pm view on Meta::CPAN
message => 'Changes file has no version entries.',
file => 'Changes',
);
}
}
# MANIFEST stale-check requires 'make manifest' -- too invasive; just advise.
if ($ctx->has_file('MANIFEST')) {
push @findings, _f(
severity => 'info',
message => "MANIFEST present -- run 'make manifest' to verify it is not stale.",
);
}
# Emit a pass only when there are no errors or warnings.
my $has_problem = grep { $_->severity =~ /^(?:error|warning)$/ } @findings;
unless ($has_problem) {
push @findings, _f(
severity => 'pass',
message => 'Distribution meets basic CPAN readiness requirements.',
);
t/edge_cases.t view on Meta::CPAN
my $dir = tempdir(CLEANUP => 1);
my $ctx = $Context->new(root => $dir);
my $result;
lives_ok { $result = $ctx->perl_files('lib', 't', 'nonexistent') }
'missing dirs do not cause perl_files to die';
returns_ok( $result, { type => 'arrayref' }, 'still returns arrayref' );
is( scalar @{$result}, 0, 'empty arrayref for missing dirs' );
};
subtest 'Context::perl_files -- does not clobber outer $_' => sub {
# File::Find localises $_ internally; we verify the outer value survives.
my $ctx = _ctx(_distro('lib/A.pm' => '1;'));
local $_ = 'sentinel_value';
$ctx->perl_files;
is( $_, 'sentinel_value', '$_ unchanged by perl_files' );
};
subtest 'Context::find_files -- undef dir argument croaks' => sub {
throws_ok { _ctx()->find_files(undef) }
qr/requires a directory/,
'find_files(undef) croaks';
t/extended_tests.t view on Meta::CPAN
'lib/Bare.pm' => "package Bare;\n1;\n",
);
my $ctx = $CONTEXT->new(root => $dir);
my @f = $CHECK_POD->new->check($ctx);
my @errs = grep { $_->severity eq $SEV_ERROR } @f;
is( scalar @errs, 1, 'one error for missing POD' );
like($errs[0]->message, qr/No POD found/i, 'message says No POD found' );
ok( $errs[0]->is_fixable, 'finding carries fix coderef' );
# Apply the fix and verify POD skeleton was written (not just appended).
my $out;
open(local *STDOUT, '>', \$out) or die;
$errs[0]->fix->($ctx);
my $content = $ctx->slurp('lib/Bare.pm');
like($content, qr/=head1 NAME/, 'fix wrote =head1 NAME' );
like($content, qr/Bare/, 'fix included the package name' );
# Regression: _fix_scaffold_pod previously used '>>' (append) which produced
# a duplicate `1;` line. Verify the file has exactly one `1;` at the top level.
my @one_lines = $content =~ /^1;$/mg;
t/function.t view on Meta::CPAN
};
subtest 'Check::Base -- optional method defaults' => sub {
my $b = App::Project::Doctor::Check::Base->new;
is $b->can_fix, 0, 'can_fix defaults to 0 (not just false)';
is $b->category, 'general', 'category defaults to "general"';
is $b->order, 50, 'order defaults to 50';
};
subtest 'Check::Base -- subclass inherits and overrides' => sub {
# Create an inline subclass to verify inheritance mechanics.
{
package My::ConcreteCheck;
use parent -norequire, 'App::Project::Doctor::Check::Base';
sub name { 'Concrete' }
sub description { 'A test check.' }
sub check { () }
sub can_fix { 1 }
sub order { 99 }
}
t/function.t view on Meta::CPAN
subtest 'Check::Meta::check -- error when META parse fails' => sub {
mock_exception 'CPAN::Meta::load_file' => 'bad yaml';
my $dir = make_distro('META.yml' => 'invalid: [yaml');
my @f = App::Project::Doctor::Check::Meta->new->check(_make_ctx($dir));
my $parse_err = grep { $_->severity eq $ERR_SEV && $_->message =~ /Failed to parse/i } @f;
ok $parse_err, 'error emitted on parse failure';
restore_all 'CPAN::Meta';
};
subtest 'Check::Meta::check -- error per missing required field' => sub {
# license is intentionally absent to verify that field is required.
my $struct = {
name => 'MyDist',
version => '0.01',
author => ['Test Author'],
abstract => 'Something',
};
my $g = mock_scoped('CPAN::Meta',
load_file => sub { return bless {}, 'CPAN::Meta' },
as_struct => sub { return $struct },
);
t/integration.t view on Meta::CPAN
open(local *STDIN, '<', \$input) or die "Cannot redirect STDIN: $!";
$count = $fixer->run;
}
diag "Fixer output:\n$out" if $ENV{TEST_VERBOSE} && $out;
return $count;
}
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# 1. FINDING -> REPORT ACCUMULATION
# Strategy: Exercise the full Finding+Report pipeline by creating all four
# severity types and verifying that each filter method is independently correct.
# A passing report and a failing report are tested to ensure exit_code and the
# has_* predicates agree.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest 'finding-report accumulation across all severity types' => sub {
my $fix_called = 0;
my $report = $Report->new;
$report->add_findings(
_f(severity => 'error', message => 'E1', check_name => 'A'),
t/integration.t view on Meta::CPAN
my $doctor = $Doctor->new(path => $dir, checks => ['Security']);
my $report = $doctor->run;
my @non_security = grep { $_->check_name ne 'Security' } $report->all_findings;
is( scalar @non_security, 0, 'no findings from other check classes when checks => [Security]' );
ok( scalar($report->all_findings) > 0, 'at least one Security finding present' );
};
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# 10. SECURITY CHECK INTEGRATION
# Strategy: Run Check::Security directly on crafted distros to verify the
# strict/warnings detection produces a fixable error finding that correctly
# identifies the source file. A clean file must yield only a pass finding.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest 'security check detects missing "use strict" and produces fixable error' => sub {
require App::Project::Doctor::Check::Security;
my $dir = _make_distro(
'lib/Broken.pm' => "package Broken;\nuse warnings;\n1;\n",
);
t/integration.t view on Meta::CPAN
is( $findings[0]->severity, 'warning', 'finding is a warning, not an exception' );
ok( scalar @carped, 'carp was called to report the failure' );
diag 'warning: ' . $findings[0]->message if $ENV{TEST_VERBOSE};
diag 'carped: ' . $carped[0] if $ENV{TEST_VERBOSE} && @carped;
};
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# 12. CONCURRENT OBJECT NON-INTERFERENCE
# Strategy: Create two independent instances of the same class within the same
# test block and verify they accumulate state in isolation -- no shared %INC
# side effects, no shared internal _findings arrays between Report instances,
# and no cross-contamination between Doctor runs on different distros.
# âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
subtest 'two concurrent Report instances accumulate state independently' => sub {
my $report_a = $Report->new;
my $report_b = $Report->new;
$report_a->add_findings(_f(severity => 'error', message => 'Err A', check_name => 'A'));
$report_b->add_findings(_f(severity => 'warning', message => 'Warn B', check_name => 'B'));
( run in 0.887 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )