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 run method receives a fully-constructed Test::CPAN::Health::Distribution object; checks that need network access should honour no_network.

  • _result, _skip, and _error are 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_ok in a test harness rather than at compile-time via use 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.