NAME
Test::CPAN::Health::Check - Abstract base class for all health checks
SYNOPSIS
package Test::CPAN::Health::Check::MyCheck;
use parent 'Test::CPAN::Health::Check';
sub id { return 'my_check' }
sub name { return 'My Check' }
sub description { return 'Checks something useful' }
sub weight { return 4 }
sub category { return 'quality' }
sub run {
my ($self, $dist) = @_;
# ... perform analysis on $dist ...
return $self->_result(
status => 'pass',
score => 100,
summary => 'Everything looks good',
);
}
1;
DESCRIPTION
Every health check in Test::CPAN::Health is a subclass of this base class. Subclasses must override id, name, and run. Overriding description, weight, and category is recommended.
The base class provides three protected helpers (callable only from subclasses): _result, _skip, and _error. These wrap Test::CPAN::Health::Result construction so subclasses never need to use Result directly. Access is enforced at runtime by Sub::Protected; calling them from outside the class hierarchy throws an exception.
LIMITATIONS
This is not a Moo/Moose role -- inheritance is via
use parent.The
runmethod receives a fully-constructed Test::CPAN::Health::Distribution object; checks that need network access should honourno_network._result,_skip, and_errorare protected (subclass-only) via Sub::Protected. White-box test packages that inherit from this class may call them freely; non-inheriting test code cannot.Sub::Protected uses a CHECK block to install access control. When this module is first loaded at runtime (e.g. via
use_okin a test harness rather than at compile-time viause parent), Perl emits a "Too late to run CHECK block" warning. The runtime protection still operates correctly despite this warning; it is cosmetic only.
new
PURPOSE
Construct a check instance. Subclasses do not normally need to override new; all check-level configuration (severity, network flag, cover flag) is managed here.
API SPECIFICATION
INPUT
severity integer 1..5 optional default 3
no_network scalar bool optional default 0
no_cover scalar bool optional default 0
OUTPUT
Blessed hashref of the concrete subclass.
MESSAGES
Code | Severity | Message | Resolution
------+----------+---------------------------------------+----------------------------
CHK00 | FATAL | Unknown parameter '<key>' | Remove unrecognised argument
CHK00 | FATAL | severity must be integer 1..5 | Pass integer in range
FORMAL SPECIFICATION
Pre: class isa Test::CPAN::Health::Check
0 < severity <= 5 (if supplied)
Post: self._severity = severity // 3
self._no_network = no_network // 0
self._no_cover = no_cover // 0
USAGE EXAMPLE
my $check = Test::CPAN::Health::Check::SemVer->new(no_network => 1);
id
PURPOSE
Returns a stable, lowercase_underscore string that uniquely identifies this check. Used as the hash key in Report results and in --skip/--check CLI flags.
API SPECIFICATION
INPUT
None.
OUTPUT
Non-empty scalar string; lowercase alphanumeric and underscores only.
MESSAGES
Code | Severity | Message | Resolution
------+----------+-------------------------------------------+----------------------
CHK01 | FATAL | <Class> must implement id() | Override id() in subclass
FORMAL SPECIFICATION
Pre: self isa concrete subclass that overrides id()
Post: result /= ""
result =~ /^[a-z][a-z0-9_]*$/
USAGE EXAMPLE
print $check->id; # 'sem_ver'
name
PURPOSE
Returns a short human-readable display name for use in report headers and terminal output.
API SPECIFICATION
INPUT
None.
OUTPUT
Non-empty scalar string.
MESSAGES
Code | Severity | Message | Resolution
------+----------+-------------------------------------------+----------------------
CHK02 | FATAL | <Class> must implement name() | Override name() in subclass
FORMAL SPECIFICATION
Pre: self isa concrete subclass that overrides name()
Post: result /= ""
USAGE EXAMPLE
print $check->name; # 'Semantic Versioning'
description
PURPOSE
Returns a one-sentence description of what the check measures. The default implementation returns an empty string; subclasses are encouraged to override this for tooling that exposes check metadata.
API SPECIFICATION
INPUT
None.
OUTPUT
Scalar string. Empty string is acceptable but discouraged.
MESSAGES
(none -- default returns empty string; no exception path)
FORMAL SPECIFICATION
Post: is_string(result) -- empty string is valid
USAGE EXAMPLE
print $check->description;
weight
PURPOSE
Returns the numeric weight applied to this check's score in the weighted average that produces the overall Report score. Higher weights make a check more influential. The default is 1.
API SPECIFICATION
INPUT
None.
OUTPUT
Positive integer or float.
MESSAGES
(none -- default returns 1; no exception path)
FORMAL SPECIFICATION
Post: result > 0
USAGE EXAMPLE
print $check->weight; # 8
category
PURPOSE
Returns the category string used to group checks in the report. Must be one of: packaging, quality, security, ci. The default is 'quality'.
API SPECIFICATION
INPUT
None.
OUTPUT
One of: packaging, quality, security, ci.
MESSAGES
(none -- default returns 'quality'; subclass overrides must use a valid value)
FORMAL SPECIFICATION
Post: result IN {packaging, quality, security, ci}
USAGE EXAMPLE
print $check->category; # 'quality'
run
PURPOSE
Executes the check against a distribution and returns a Result. Returns undef when the check is not applicable (e.g. CPANTesters for a dist that has never been released to CPAN).
Subclasses must override this method. The base-class implementation always croaks.
API SPECIFICATION
INPUT
dist Test::CPAN::Health::Distribution required
context Hashref of check_id => Result optional
OUTPUT
Test::CPAN::Health::Result object, or undef if not applicable.
MESSAGES
Code | Severity | Message | Resolution
------+----------+--------------------------------------------+---------------------
CHK03 | FATAL | <Class> must implement run() | Override run() in subclass
FORMAL SPECIFICATION
Pre: dist isa Test::CPAN::Health::Distribution
context is hashref (may be empty)
Post: result = undef
OR (result isa Result AND result.check_id = self.id)
SIDE EFFECTS
May perform network I/O, filesystem reads, and subprocess invocations depending on the concrete check. Honours no_network and no_cover flags to suppress optional side effects.
USAGE EXAMPLE
my $result = $check->run($dist, \%context);
print $result->summary if defined $result;
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
LICENSE AND COPYRIGHT
Copyright (C) 2025-2026 Nigel Horne.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.