NAME

Test::Which - Skip tests if external programs are missing from PATH (with version checks)

VERSION

Version 0.05

SYNOPSIS

  use Test::Which 'ffmpeg' => '>=6.0', 'convert' => '>=7.1';

  # At runtime in a subtest or test body
  use Test::Which qw(which_ok);

  subtest 'needs ffmpeg' => sub {
	  which_ok 'ffmpeg' => '>=6.0' or return;
	  ... # tests that use ffmpeg
  };

DESCRIPTION

Test::Which mirrors Test::Needs but checks for executables in PATH. It can also check version constraints using a built-in heuristic that tries common version flags (--version, -version, -v, -V) and extracts version numbers from the output.

If a version is requested but cannot be determined, the requirement fails.

Key features:

  • Compile-time and runtime checking of program availability

  • Version comparison with standard operators (>=, >, <, <=, ==, !=)

  • Regular expression matching for version strings

  • Custom version flag support for non-standard programs

  • Custom version extraction for unusual output formats

  • Caching to avoid repeated program execution

  • Cross-platform support (Unix, Linux, macOS, Windows)

EXAMPLES

Basic Usage

Check for program availability without version constraints:

use Test::Which qw(which_ok);

which_ok 'perl', 'ffmpeg', 'convert';

Version Constraints

Check programs with minimum version requirements:

# String constraints with comparison operators
which_ok 'perl' => '>=5.10';
which_ok 'ffmpeg' => '>=4.0', 'convert' => '>=7.1';

# Exact version match
which_ok 'node' => '==18.0.0';

# Version range
which_ok 'python' => '>=3.8', 'python' => '<4.0';

Hashref Syntax

Use hashrefs for more complex constraints:

# String version in hashref
which_ok 'perl', { version => '>=5.10' };

# Regex matching
which_ok 'perl', { version => qr/5\.\d+/ };
which_ok 'ffmpeg', { version => qr/^[4-6]\./ };

Custom Version Flags

Some programs use non-standard flags to display version information:

# Java uses -version (single dash)
which_ok 'java', {
    version => '>=11',
    version_flag => '-version'
};

# Try multiple flags in order
which_ok 'myprogram', {
    version => '>=2.0',
    version_flag => ['--show-version', '-version', '--ver']
};

# Program prints version without any flag
which_ok 'sometool', {
    version => '>=1.0',
    version_flag => '',
    timeout => 10,	# seconds - the default is 5
};

# Windows-specific flag
which_ok 'cmd', {
    version => qr/\d+/,
    version_flag => '/?'
} if $^O eq 'MSWin32';

If version_flag is not specified, the module tries these flags in order: --version, -version, -v, -V (and /?, -? on Windows)

Custom Version Extraction

For programs with unusual version output formats:

which_ok 'myprogram', {
    version => '>=1.0',
    extractor => sub {
        my $output = shift;
        return $1 if $output =~ /Build (\d+\.\d+)/;
        return undef;
    }
};

The extractor receives the program's output and should return the version string or undef if no version could be found.

Mixed Usage

Combine different constraint types:

which_ok
    'perl' => '>=5.10',           # String constraint
    'ffmpeg',                      # No constraint
    'convert', { version => qr/^7\./ };  # Regex constraint

Compile-Time Checking

Skip entire test files if requirements aren't met:

use Test::Which 'ffmpeg' => '>=6.0', 'convert' => '>=7.1';

# Test file is skipped if either program is missing or the version is too old
# No tests below this line will run if requirements aren't met

Runtime Checking in Subtests

Check requirements for individual subtests:

use Test::Which qw(which_ok);

subtest 'video conversion' => sub {
    which_ok 'ffmpeg' => '>=4.0' or return;
    # ... tests using ffmpeg
};

subtest 'image processing' => sub {
    which_ok 'convert' => '>=7.0' or return;
    # ... tests using ImageMagick
};

Absolute Paths

You can specify absolute paths instead of searching PATH:

which_ok '/usr/local/bin/myprogram' => '>=1.0';

The program must be executable.

VERSION DETECTION

The module attempts to detect version numbers using these strategies in order:

1. Look for version near the word "version" (case-insensitive)

Matches patterns like: ffmpeg version 4.2.7, Version: 2.1.0

2. Extract dotted version from first line of output

Common for programs that print version info prominently

3. Find any dotted version number in output

Fallback for less standard formats

4. Look for single number near "version"

For programs that use simple integer versioning

5. Use any standalone number found

Last resort - least reliable

VERSION COMPARISON

Version comparison uses Perl's version module. Versions are normalized to have the same number of components before comparison to avoid version.pm's parsing quirks.

For example: - 2020.10 becomes 2020.10.0 - 2020.10.15 stays 2020.10.15 - Then they're compared correctly

Supported operators: >=, >, <=, <, =, !=

CACHING

Version detection results are cached to avoid repeated program execution. Each unique combination of program path and version flags creates a separate cache entry.

Cache benefits: - Faster repeated checks in test suites - Reduced system load - Works across multiple test files in the same process

The cache persists for the lifetime of the Perl process.

VERBOSE OUTPUT

Set environment variables to see detected versions:

TEST_WHICH_VERBOSE=1 prove -v t/mytest.t
TEST_VERBOSE=1 perl t/mytest.t
prove -v t/mytest.t  # HARNESS_IS_VERBOSE is set automatically

Output includes the detected version for each checked program:

# perl: version 5.38.0
# ffmpeg: version 6.1.1

PLATFORM SUPPORT

  • Unix/Linux/macOS: Full support for all features

  • Windows: Basic functionality supported. Complex shell features (STDERR redirection, empty flags) may have limitations.

DIAGNOSTICS

Common error messages:

Missing required program 'foo'

The program 'foo' could not be found in PATH.

Version issue for foo: no version detected

The program exists but the module couldn't extract a version number from its output. Try specifying a custom version_flag or extractor.

Version issue for foo: found 1.0 but need =2.0>

The program's version doesn't meet the constraint.

Version issue for foo: found version 1.0 but doesn't match pattern

For regex constraints, the detected version didn't match the pattern.

hashref constraint must contain 'version' key

When using hashref syntax, you must include a version key.

invalid constraint 'foo'

The version constraint string couldn't be parsed. Use formats like '=1.2.3'>, '2.0'>, or '1.5'.

FUNCTIONS/METHODS

which_ok @programs_or_pairs

Checks the named programs (with optional version constraints). If any requirement is not met, the current test or subtest is skipped via Test::Builder.

Returns true if all requirements are met, false otherwise.

SUPPORT

This module is provided as-is without any warranty.

SEE ALSO

Test::Needs - Similar module for checking Perl module availability

File::Which - Used internally to locate programs

version - Used for version comparison

Test::Builder - Used for test integration

LIMITATIONS

  • Version detection is heuristic-based and may fail for programs with unusual output formats. Use custom version_flag or extractor for such cases.

  • No built-in timeout for program execution. Hanging programs will hang tests.

  • Cache persists for process lifetime - updated programs won't be re-detected without restarting the test process.

  • Requires programs to be in PATH or specified with absolute paths.

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

LICENCE AND COPYRIGHT

Copyright 2025 Nigel Horne.

Usage is subject to licence terms.

The licence terms of this software are as follows:

  • Personal single user, single computer use: GPL2

  • All other users (including Commercial, Charity, Educational, Government) must apply in writing for a licence for use from Nigel Horne at the above e-mail.