NAME

Test::WriteVariants - Dynamic generation of tests in nested combinations of contexts

SYNOPSIS

use Test::WriteVariants;

my $test_writer = Test::WriteVariants->new();

$test_writer->write_test_variants(

    # tests we want to run in various contexts
    input_tests => {
        'core/10-foo' => { require => 't/core/10-foo.t' },
        'core/20-bar' => { require => 't/core/20-bar.t' },
    },

    # one or more providers of variant contexts
    variant_providers => [
        sub {
            my ($path, $context, $tests) = @_;
            my %variants = (
                plain    => $context->new_env_var(MY_MODULE_PUREPERL => 0),
                pureperl => $context->new_env_var(MY_MODULE_PUREPERL => 1),
            );
            return %variants;
        },
        sub {
            my ($path, $context, $tests) = @_;
            my %variants = map {
                $_ => $context->new_env_var(MY_MODULE_WIBBLE => $_),
            } 1..3;
            delete $variants{3} if $context->get_env_var("MY_MODULE_PUREPERL");
            return %variants;
        },
    ],

    # where to generate the .t files that wrap the input_tests
    output_dir => 't/variants',
);

When run that generates the desired test variants:

Writing t/variants/plain/1/core/10-foo.t
Writing t/variants/plain/1/core/20-bar.t
Writing t/variants/plain/2/core/10-foo.t
Writing t/variants/plain/2/core/20-bar.t
Writing t/variants/plain/3/core/10-foo.t
Writing t/variants/plain/3/core/20-bar.t
Writing t/variants/pureperl/1/core/10-foo.t
Writing t/variants/pureperl/1/core/20-bar.t
Writing t/variants/pureperl/2/core/10-foo.t
Writing t/variants/pureperl/2/core/20-bar.t

Here's what t/variants/pureperl/2/core/20-bar.t looks like:

#!perl
$ENV{MY_MODULE_WIBBLE} = 2;
END { delete $ENV{MY_MODULE_WIBBLE} } # for VMS
$ENV{MY_MODULE_PUREPERL} = 1;
END { delete $ENV{MY_MODULE_PUREPERL} } # for VMS
require 't/core/20-bar.t';

Here's an example that uses plugins to provide the tests and the variants:

my $test_writer = Test::WriteVariants->new();

# gather set of input tests that we want to run in various contexts
# these can come from various sources, including modules and test files
my $input_tests = $test_writer->find_input_test_modules(
    search_path => [ 'DBI::TestCase' ]
);

$test_writer->write_test_variants(

    # tests we want to run in various contexts
    input_tests => $input_tests,

    # one or more providers of variant contexts
    # (these can be code refs or plugin namespaces)
    variant_providers => [
        "DBI::Test::VariantDBI",
        "DBI::Test::VariantDriver",
        "DBI::Test::VariantDBD",
    ],

    # where to generate the .t files that wrap the input_tests
    output_dir => $output_dir,
);

DESCRIPTION

Test::WriteVariants is a utility to create variants of a common test.

Given the situation - like in DBI where some tests are the same for DBI::SQL::Nano and it's drop-in replacement SQL::Statement. Or a distribution duo having a Pure-Perl and an XS variant - and the same test shall be used to ensure XS and PP version are really drop-in replacements for each other.

METHODS

new

$test_writer = Test::WriteVariants->new(%attributes);

Instanciates a Test::WriteVariants instance and sets the specified attributes, if any.

allow_dir_overwrite

$test_writer->allow_dir_overwrite($bool);
$bool = $test_writer->allow_dir_overwrite;

If the output directory already exists when tumble() is called it'll throw an exception (and warn if it wasn't created during the run). Setting allow_dir_overwrite true disables this safety check.

allow_file_overwrite

$test_writer->allow_file_overwrite($bool);
$bool = $test_writer->allow_file_overwrite;

If the test file that's about to be written already exists then write_output_files() will throw an exception. Setting allow_file_overwrite true disables this safety check.

write_test_variants

$test_writer->write_test_variants(
    input_tests => \%input_tests,
    variant_providers => \@variant_providers,
    output_dir => $output_dir,
);

Instanciates a Data::Tumbler. Sets its consumer to call:

$self->write_output_files($path, $context, $payload, $output_dir)

and sets its add_context to call:

$context->new($context, $item);

and then calls its tumble method:

$tumbler->tumble(
    $self->normalize_providers($variant_providers),
    [],
    Test::WriteVariants::Context->new(),
    $input_tests,
);

find_input_test_modules

$input_tests = $test_writer->find_input_test_modules(
    search_path => ["Helper"],
    search_dirs => "t/lib",
    test_prefix => "Extra::Helper",
    input_tests => $input_tests
);

find_input_test_files

Not yet implemented - will file .t files.

find_input_inline_tests

$input_tests = $test_writer->find_input_inline_tests(
    search_patterns => ["*.it"],
    search_dirs     => "t/inl",
    input_tests     => $input_tests
);

add_test

$test_writer->add_test(
    $input_tests,   # the \%input_tests to add the test module to
    $test_name,     # the key to use in \%input_tests
    $test_spec      # the details of the test file
);

Adds the $test_spec to %$input_tests keys by $test_name. In other words:

$input_tests->{ $test_name } = $test_spec;

An exception will be thrown if a test with $test_name already exists in %$input_tests.

This is a low-level interface that's not usually called directly. See "add_test_module".

add_test_module

$test_writer->add_test_module(
    $input_tests,     # the \%input_tests to add the test module to
    $module_name,     # the package name of the test module
    $edit_test_name   # a code ref to edit the test module name in $_
);

add_test_inline

$test_writer->add_test_inline(
    $input_tests,     # the \%input_tests to add the test module to
    $file_name,       # the file name of the test code to inline
    $edit_test_name   # a code ref to edit the test file name in $_
);

normalize_providers

$providers = $test_writer->normalize_providers($providers);

Given a reference to an array of providers, returns a reference to a new array. Any code references in the original array are passed through unchanged.

Any other value is treated as a package name and passed to Module::Pluggable::Object as a namespace search_path to find plugins. An exception is thrown if no plugins are found.

The corresponding element of the original $providers array is replaced with a new provider code reference which calls the provider_initial, provider, and provider_final methods, if present, for each plugin namespace in turn.

Normal Data::Tumbler provider subroutines are called with these arguments:

($path, $context, $tests)

and the return value is expected to be a hash. Whereas the plugin provider methods are called with these arguments:

($test_writer, $path, $context, $tests, $variants)

and the return value is ignored. The $variants argument is a reference to a hash that will be returned to Data::Tumbler and which should be edited by the plugin provider method. This allows a plugin to see, and change, the variants requested by any other plugins that have already been run for this provider.

write_output_files

$test_writer->write_output_files($path, $context, $input_tests, $output_dir);

Writes test files for each test in %$input_tests, for the given $path and $context, into the $output_dir.

The $output_dir, @$path, and key of %$input_tests are concatenated to form a file name. A ".t" is added if not already present.

Calls "get_test_file_body" to get the content of the test file, and then calls "write_file" to write it.

write_file

$test_writer->write_file($filepath, $content);

Throws an exception if $filepath already exists and "allow_file_overwrite" is not true.

Creates $filepath and writes $content to it. Creates any directories that are needed. Throws an exception on error.

get_test_file_body

$test_body = $test_writer->get_test_file_body($context, $test_spec);

XXX This should probably be a method call on an object instanciated by the find_input_test_* methods.

BUGS

Please report any bugs or feature requests to bug-Test-WriteVariants at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-WriteVariants. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Test::WriteVariants

You can also look for information at:

AUTHOR

Tim Bunce, <timb at cpan.org>

Jens Rehsack, rehsack at cpan.org

ACKNOWLEDGEMENTS

This module has been created to support DBI::Test in design and separation of concerns.

COPYRIGHT

Copyright 2014-2017 Tim Bunce and Perl5 DBI Team.

LICENSE

This program is free software; you can redistribute it and/or modify it under the terms of either:

a) the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or

b) the "Artistic License" which comes with this Kit.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the Artistic License for more details.