289 lines
7.1 KiB
Perl
289 lines
7.1 KiB
Perl
package Test2::Workflow;
|
|
use strict;
|
|
use warnings;
|
|
|
|
our $VERSION = '0.000139';
|
|
|
|
our @EXPORT_OK = qw/parse_args current_build build root_build init_root build_stack/;
|
|
use base 'Exporter';
|
|
|
|
use Test2::Workflow::Build;
|
|
use Test2::Workflow::Task::Group;
|
|
use Test2::API qw/intercept/;
|
|
use Scalar::Util qw/blessed/;
|
|
|
|
sub parse_args {
|
|
my %input = @_;
|
|
my $args = delete $input{args};
|
|
my %out;
|
|
my %props;
|
|
|
|
my $caller = $out{frame} = $input{caller} || caller(defined $input{level} ? $input{level} : 1);
|
|
delete @input{qw/caller level/};
|
|
|
|
for my $arg (@$args) {
|
|
if (my $r = ref($arg)) {
|
|
if ($r eq 'HASH') {
|
|
%props = (%props, %$arg);
|
|
}
|
|
elsif ($r eq 'CODE') {
|
|
die "Code is already set, did you provide multiple code blocks at $caller->[1] line $caller->[2].\n"
|
|
if $out{code};
|
|
|
|
$out{code} = $arg
|
|
}
|
|
else {
|
|
die "Not sure what to do with $arg at $caller->[1] line $caller->[2].\n";
|
|
}
|
|
next;
|
|
}
|
|
|
|
if ($arg =~ m/^\d+$/) {
|
|
push @{$out{lines}} => $arg;
|
|
next;
|
|
}
|
|
|
|
die "Name is already set to '$out{name}', cannot set to '$arg', did you specify multiple names at $caller->[1] line $caller->[2].\n"
|
|
if $out{name};
|
|
|
|
$out{name} = $arg;
|
|
}
|
|
|
|
die "a name must be provided, and must be truthy at $caller->[1] line $caller->[2].\n"
|
|
unless $out{name};
|
|
|
|
die "a codeblock must be provided at $caller->[1] line $caller->[2].\n"
|
|
unless $out{code};
|
|
|
|
return { %props, %out, %input };
|
|
}
|
|
|
|
{
|
|
my %ROOT_BUILDS;
|
|
my @BUILD_STACK;
|
|
|
|
sub root_build { $ROOT_BUILDS{$_[0]} }
|
|
sub current_build { @BUILD_STACK ? $BUILD_STACK[-1] : undef }
|
|
sub build_stack { @BUILD_STACK }
|
|
|
|
sub init_root {
|
|
my ($pkg, %args) = @_;
|
|
$ROOT_BUILDS{$pkg} ||= Test2::Workflow::Build->new(
|
|
name => $pkg,
|
|
flat => 1,
|
|
iso => 0,
|
|
async => 0,
|
|
is_root => 1,
|
|
%args,
|
|
);
|
|
|
|
return $ROOT_BUILDS{$pkg};
|
|
}
|
|
|
|
sub build {
|
|
my %params = @_;
|
|
my $args = parse_args(%params);
|
|
|
|
my $build = Test2::Workflow::Build->new(%$args);
|
|
|
|
return $build if $args->{skip};
|
|
|
|
push @BUILD_STACK => $build;
|
|
|
|
my ($ok, $err);
|
|
my $events = intercept {
|
|
my $todo = $args->{todo} ? Test2::Todo->new(reason => $args->{todo}) : undef;
|
|
$ok = eval { $args->{code}->(); 1 };
|
|
$err = $@;
|
|
$todo->end if $todo;
|
|
};
|
|
|
|
# Clear the stash
|
|
$build->{stash} = [];
|
|
$build->set_events($events);
|
|
|
|
pop @BUILD_STACK;
|
|
|
|
unless($ok) {
|
|
my $hub = Test2::API::test2_stack->top;
|
|
my $count = @$events;
|
|
my $list = $count
|
|
? "Overview of unseen events:\n" . join "" => map " " . blessed($_) . " " . $_->trace($hub)->debug . "\n", @$events
|
|
: "";
|
|
die <<" EOT";
|
|
Exception in build '$args->{name}' with $count unseen event(s).
|
|
$err
|
|
$list
|
|
EOT
|
|
}
|
|
|
|
return $build;
|
|
}
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=encoding UTF-8
|
|
|
|
=head1 NAME
|
|
|
|
Test2::Workflow - A test workflow is a way of structuring tests using
|
|
composable units.
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
A test workflow is a way of structuring tests using composable units. A well
|
|
known example of a test workflow is L<RSPEC|http://rspec.info/>. RSPEC is
|
|
implemented using Test2::Workflow in L<Test2::Tools::Spec> along with several
|
|
extensions.
|
|
|
|
=head1 IMPORTANT CONCEPTS
|
|
|
|
=head2 BUILD
|
|
|
|
L<Test2::Workflow::Build>
|
|
|
|
A Build is used to compose tasks. Usually a build object is pushed to the stack
|
|
before running code that adds tasks to the build. Once the build sub is
|
|
complete the build is popped and returned. Usually a build is converted into a
|
|
root task or task group.
|
|
|
|
=head2 RUNNER
|
|
|
|
L<Test2::Workflow::Runner>
|
|
|
|
A runner takes the composed tasks and executes them in the proper order.
|
|
|
|
=head2 TASK
|
|
|
|
L<Test2::Workflow::Task>
|
|
|
|
A task is a unit of work to accomplish. There are 2 main types of task.
|
|
|
|
=head3 ACTION
|
|
|
|
An action is the most simple unit used in composition. An action is essentially
|
|
a name and a codeblock to run.
|
|
|
|
=head3 GROUP
|
|
|
|
A group is a task that is composed of other tasks.
|
|
|
|
=head1 EXPORTS
|
|
|
|
All exports are optional, you must request the ones you want.
|
|
|
|
=over 4
|
|
|
|
=item $parsed = parse_args(args => \@args)
|
|
|
|
=item $parsed = parse_args(args => \@args, level => $L)
|
|
|
|
=item $parsed = parse_args(args => \@args, caller => [caller($L)])
|
|
|
|
This will parse a "typical" task builders arguments. The C<@args> array MUST
|
|
contain a name (plain scalar containing text) and also a single CODE reference.
|
|
The C<@args> array MAY also contain any quantity of line numbers or hashrefs.
|
|
The resulting data structure will be a single hashref with all the provided
|
|
hashrefs squashed together, and the 'name', 'code', 'lines' and 'frame' keys
|
|
set from other arguments.
|
|
|
|
{
|
|
# All hashrefs from @args get squashed together:
|
|
%squashed_input_hashref_data,
|
|
|
|
# @args must have exactly 1 plaintext scalar that is not a number, it
|
|
# is considered the name:
|
|
name => 'name from input args'
|
|
|
|
# Integer values are treated as line numbers
|
|
lines => [ 35, 44 ],
|
|
|
|
# Exactly 1 coderef must be provided in @args:
|
|
code => \&some_code,
|
|
|
|
# 'frame' contains the 'caller' data. This may be passed in directly,
|
|
# obtained from the 'level' parameter, or automatically deduced.
|
|
frame => ['A::Package', 'a_file.pm', 42, ...],
|
|
}
|
|
|
|
=item $build = init_root($pkg, %args)
|
|
|
|
This will initialize (or return the existing) a build for the specified
|
|
package. C<%args> get passed into the L<Test2::Workflow::Build> constructor.
|
|
This uses the following defaults (which can be overridden using C<%args>):
|
|
|
|
name => $pkg,
|
|
flat => 1,
|
|
iso => 0,
|
|
async => 0,
|
|
is_root => 1,
|
|
|
|
Note that C<%args> is completely ignored if the package build has already been
|
|
initialized.
|
|
|
|
=item $build = root_build($pkg)
|
|
|
|
This will return the root build for the specified package.
|
|
|
|
=item $build = current_build()
|
|
|
|
This will return the build currently at the top of the build stack (or undef).
|
|
|
|
=item $build = build($name, \%params, sub { ... })
|
|
|
|
This will push a new build object onto the build stash then run the provided
|
|
codeblock. Once the codeblock has finished running the build will be popped off
|
|
the stack and returned.
|
|
|
|
See C<parse_args()> for details about argument processing.
|
|
|
|
=back
|
|
|
|
=head1 SEE ALSO
|
|
|
|
=over 4
|
|
|
|
=item Test2::Tools::Spec
|
|
|
|
L<Test2::Tools::Spec> is an implementation of RSPEC using this library.
|
|
|
|
=back
|
|
|
|
=head1 SOURCE
|
|
|
|
The source code repository for Test2-Workflow can be found at
|
|
F<https://github.com/Test-More/Test2-Suite/>.
|
|
|
|
=head1 MAINTAINERS
|
|
|
|
=over 4
|
|
|
|
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
|
|
|
=back
|
|
|
|
=head1 AUTHORS
|
|
|
|
=over 4
|
|
|
|
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
|
|
|
=back
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 2018 Chad Granum E<lt>exodist7@gmail.comE<gt>.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the same terms as Perl itself.
|
|
|
|
See F<http://dev.perl.org/licenses/>
|
|
|
|
=cut
|
|
|