Initial Commit

This commit is contained in:
Riley Schneider
2025-12-03 16:38:10 +01:00
parent c5e26bf594
commit b732d8d4b5
17680 changed files with 5977495 additions and 2 deletions

View File

@@ -0,0 +1,251 @@
package Test2::Util::Grabber;
use strict;
use warnings;
our $VERSION = '0.000139';
use Test2::Hub::Interceptor();
use Test2::EventFacet::Trace();
use Test2::API qw/test2_stack test2_ipc/;
use Test2::Util::HashBase qw/hub finished _events term_size <state <trace/;
sub init {
my $self = shift;
# Make sure we have a hub on the stack
test2_stack->top();
my $hub = test2_stack->new_hub(
class => 'Test2::Hub::Interceptor',
formatter => undef,
no_ending => 1,
);
$self->{+HUB} = $hub;
my @events;
$hub->listen(sub { push @events => $_[1] });
$self->{+_EVENTS} = \@events;
$self->{+TERM_SIZE} = $ENV{TS_TERM_SIZE};
$ENV{TS_TERM_SIZE} = 80;
my $trace = $self->{+TRACE} ||= Test2::EventFacet::Trace->new(frame => [caller(1)]);
my $state = $self->{+STATE} ||= {};
$hub->clean_inherited(trace => $trace, state => $state);
return;
}
sub flush {
my $self = shift;
my $out = [@{$self->{+_EVENTS}}];
@{$self->{+_EVENTS}} = ();
return $out;
}
sub events {
my $self = shift;
# Copy
return [@{$self->{+_EVENTS}}];
}
sub finish {
my ($self) = @_; # Do not shift;
$_[0] = undef;
if (defined $self->{+TERM_SIZE}) {
$ENV{TS_TERM_SIZE} = $self->{+TERM_SIZE};
}
else {
delete $ENV{TS_TERM_SIZE};
}
my $hub = $self->{+HUB};
$self->{+FINISHED} = 1;
test2_stack()->pop($hub);
my $trace = $self->{+TRACE} ||= Test2::EventFacet::Trace->new(frame => [caller(1)]);
my $state = $self->{+STATE} ||= {};
$hub->clean_inherited(trace => $trace, state => $state);
my $dbg = Test2::EventFacet::Trace->new(
frame => [caller(0)],
);
$hub->finalize($dbg, 1)
if !$hub->no_ending
&& !$hub->state->ended;
return $self->flush;
}
sub DESTROY {
my $self = shift;
return if $self->{+FINISHED};
test2_stack->pop($self->{+HUB});
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Grabber - Object used to temporarily intercept all events.
=head1 DESCRIPTION
Once created this object will intercept and stash all events sent to the shared
L<Test2::Hub> object. Once the object is destroyed, events will once
again be sent to the shared hub.
=head1 SYNOPSIS
use Test2 qw/Core Grab/;
my $grab = grab();
# Generate some events, they are intercepted.
ok(1, "pass");
ok(0, "fail");
my $events_a = $grab->flush;
# Generate some more events, they are intercepted.
ok(1, "pass");
ok(0, "fail");
# Same as flush, except it destroys the grab object.
my $events_b = $grab->finish;
After calling C<finish()> the grab object is destroyed and C<$grab> is set to
undef. C<$events_a> is an arrayref with the first two events. C<$events_b> is an
arrayref with the second two events.
=head1 EXPORTS
=over 4
=item $grab = grab()
This lets you intercept all events for a section of code without adding
anything to your call stack. This is useful for things that are sensitive to
changes in the stack depth.
my $grab = grab();
ok(1, 'foo');
ok(0, 'bar');
# $grab is magically undef after this.
my $events = $grab->finish;
is(@$events, 2, "grabbed two events.");
When you call C<finish()> the C<$grab> object will automagically undef itself,
but only for the reference used in the method call. If you have other
references to the C<$grab> object they will not be set to undef.
If the C<$grab> object is destroyed without calling C<finish()>, it will
automatically clean up after itself and restore the parent hub.
{
my $grab = grab();
# Things are grabbed
}
# Things are back to normal
By default the hub used has C<no_ending> set to true. This will prevent the hub
from enforcing that you issued a plan and ran at least one test. You can turn
enforcement back one like this:
$grab->hub->set_no_ending(0);
With C<no_ending> turned off, C<finish> will run the post-test checks to
enforce the plan and that tests were run. In many cases this will result in
additional events in your events array.
=back
=head1 METHODS
=over 4
=item $grab = $class->new()
Create a new grab object, immediately starts intercepting events.
=item $ar = $grab->flush()
Get an arrayref of all the events so far, clearing the grab objects internal
list.
=item $ar = $grab->events()
Get an arrayref of all events so far. Does not clear the internal list.
=item $ar = $grab->finish()
Get an arrayref of all the events, then destroy the grab object.
=item $hub = $grab->hub()
Get the hub that is used by the grab event.
=back
=head1 ENDING BEHAVIOR
By default the hub used has C<no_ending> set to true. This will prevent the hub
from enforcing that you issued a plan and ran at least one test. You can turn
enforcement back one like this:
$grab->hub->set_no_ending(0);
With C<no_ending> turned off, C<finish> will run the post-test checks to
enforce the plan and that tests were run. In many cases this will result in
additional events in your events array.
=head1 SEE ALSO
L<Test2::Tools::Intercept> - Accomplish the same thing, but using
blocks instead.
=head1 SOURCE
The source code repository for Test2 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>exodist@cpan.orgE<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

View File

@@ -0,0 +1,118 @@
package Test2::Util::Ref;
use strict;
use warnings;
our $VERSION = '0.000139';
use Scalar::Util qw/reftype blessed refaddr/;
our @EXPORT_OK = qw/rtype render_ref/;
use base 'Exporter';
sub rtype {
my ($thing) = @_;
return '' unless defined $thing;
my $rf = ref $thing;
my $rt = reftype $thing;
return '' unless $rf || $rt;
return 'REGEXP' if $rf =~ m/Regex/i;
return 'REGEXP' if $rt =~ m/Regex/i;
return $rt || '';
}
sub render_ref {
my ($in) = @_;
return 'undef' unless defined($in);
my $type = rtype($in);
return "$in" unless $type;
# Look past overloading
my $class = blessed($in) || '';
my $it = sprintf('0x%x', refaddr($in));
my $ref = "$type($it)";
return $ref unless $class;
return "$class=$ref";
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Ref - Tools for inspecting or manipulating references.
=head1 DESCRIPTION
These are used by L<Test2::Tools> to inspect, render, or manipulate references.
=head1 EXPORTS
All exports are optional. You must specify subs to import.
=over 4
=item $type = rtype($ref)
A normalization between C<Scalar::Util::reftype()> and C<ref()>.
Always returns a string.
Returns C<'REGEXP'> for regex types
Returns C<''> for non-refs
Otherwise returns what C<Scalar::Util::reftype()> returns.
=item $addr_str = render_ref($ref)
Always returns a string. For unblessed references this returns something like
C<"SCALAR(0x...)">. For blessed references it returns
C<"My::Thing=SCALAR(0x...)">. The only difference between this and C<$add_str =
"$thing"> is that it ignores any overloading to ensure it is always the ref
address.
=back
=head1 SOURCE
The source code repository for Test2-Suite 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>
=item Kent Fredric E<lt>kentnl@cpan.orgE<gt>
=back
=head1 COPYRIGHT
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<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

View File

@@ -0,0 +1,247 @@
package Test2::Util::Stash;
use strict;
use warnings;
our $VERSION = '0.000139';
use Carp qw/croak/;
use B;
our @EXPORT_OK = qw{
get_stash
get_glob
get_symbol
parse_symbol
purge_symbol
slot_to_sig sig_to_slot
};
use base 'Exporter';
my %SIGMAP = (
'&' => 'CODE',
'$' => 'SCALAR',
'%' => 'HASH',
'@' => 'ARRAY',
);
my %SLOTMAP = reverse %SIGMAP;
sub slot_to_sig { $SLOTMAP{$_[0]} || croak "unsupported slot: '$_[0]'" }
sub sig_to_slot { $SIGMAP{$_[0]} || croak "unsupported sigil: $_[0]" }
sub get_stash {
my $package = shift || caller;
no strict 'refs';
return \%{"${package}\::"};
}
sub get_glob {
my $sym = _parse_symbol(scalar(caller), @_);
no strict 'refs';
no warnings 'once';
return \*{"$sym->{package}\::$sym->{name}"};
}
sub parse_symbol { _parse_symbol(scalar(caller), @_) }
sub _parse_symbol {
my ($caller, $symbol, $package) = @_;
if (ref($symbol)) {
my $pkg = $symbol->{package};
croak "Symbol package ($pkg) and package argument ($package) do not match"
if $pkg && $package && $pkg ne $package;
$symbol->{package} ||= $caller;
return $symbol;
}
utf8::downgrade($symbol) if $] == 5.010000; # prevent crash on 5.10.0
my ($sig, $pkg, $name) = ($symbol =~ m/^(\W?)(.*::)?([^:]+)$/)
or croak "Invalid symbol: '$symbol'";
# Normalize package, '::' becomes 'main', 'Foo::' becomes 'Foo'
$pkg = $pkg
? $pkg eq '::'
? 'main'
: substr($pkg, 0, -2)
: undef;
croak "Symbol package ($pkg) and package argument ($package) do not match"
if $pkg && $package && $pkg ne $package;
$sig ||= '&';
my $type = $SIGMAP{$sig} || croak "unsupported sigil: '$sig'";
my $real_package = $package || $pkg || $caller;
return {
name => $name,
sigil => $sig,
type => $type,
symbol => "${sig}${real_package}::${name}",
package => $real_package,
};
}
sub get_symbol {
my $sym = _parse_symbol(scalar(caller), @_);
my $name = $sym->{name};
my $type = $sym->{type};
my $package = $sym->{package};
my $symbol = $sym->{symbol};
my $stash = get_stash($package);
return undef unless exists $stash->{$name};
my $glob = get_glob($sym);
return *{$glob}{$type} if $type ne 'SCALAR' && defined(*{$glob}{$type});
if ($] < 5.010) {
return undef unless defined(*{$glob}{$type});
{
local ($@, $!);
local $SIG{__WARN__} = sub { 1 };
return *{$glob}{$type} if eval "package $package; my \$y = $symbol; 1";
}
return undef unless defined *{$glob}{$type};
return *{$glob}{$type} if defined ${*{$glob}{$type}};
return undef;
}
my $sv = B::svref_2object($glob)->SV;
return *{$glob}{$type} if $sv->isa('B::SV');
return undef unless $sv->isa('B::SPECIAL');
return *{$glob}{$type} if $B::specialsv_name[$$sv] ne 'Nullsv';
return undef;
}
sub purge_symbol {
my $sym = _parse_symbol(scalar(caller), @_);
local *GLOBCLONE = *{get_glob($sym)};
delete get_stash($sym->{package})->{$sym->{name}};
my $new_glob = get_glob($sym);
for my $type (qw/CODE SCALAR HASH ARRAY FORMAT IO/) {
next if $type eq $sym->{type};
my $ref = get_symbol({type => $type, name => 'GLOBCLONE', sigil => $SLOTMAP{$type}}, __PACKAGE__);
next unless $ref;
*$new_glob = $ref;
}
return *GLOBCLONE{$sym->{type}};
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Stash - Utilities for manipulating stashes and globs.
=head1 DESCRIPTION
This is a collection of utilities for manipulating and inspecting package
stashes and globs.
=head1 EXPORTS
=over 4
=item $stash = get_stash($package)
Gets the package stash. This is the same as C<$stash = \%Package::Name::>.
=item $sym_spec = parse_symbol($symbol)
=item $sym_spec = parse_symbol($symbol, $package)
Parse a symbol name, and return a hashref with info about the symbol.
C<$symbol> can be a simple name, or a fully qualified symbol name. The sigil is
optional, and C<&> is assumed if none is provided. If C<$symbol> is fully qualified,
and C<$package> is also provided, then the package of the symbol must match the
C<$package>.
Returns a structure like this:
return {
name => 'BAZ',
sigil => '$',
type => 'SCALAR',
symbol => '&Foo::Bar::BAZ',
package => 'Foo::Bar',
};
=item $glob_ref = get_glob($symbol)
=item $glob_ref = get_glob($symbol, $package)
Get a glob ref. Arguments are the same as for C<parse_symbol>.
=item $ref = get_symbol($symbol)
=item $ref = get_symbol($symbol, $package)
Get a reference to the symbol. Arguments are the same as for C<parse_symbol>.
=item $ref = purge_symbol($symbol)
=item $ref = purge_symbol($symbol, $package)
Completely remove the symbol from the package symbol table. Arguments are the
same as for C<parse_symbol>. A reference to the removed symbol is returned.
=item $sig = slot_to_sig($slot)
Convert a slot (like 'SCALAR') to a sigil (like '$').
=item $slot = sig_to_slot($sig)
Convert a sigil (like '$') to a slot (like 'SCALAR').
=back
=head1 SOURCE
The source code repository for Test2-Suite 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>exodist@cpan.orgE<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

View File

@@ -0,0 +1,180 @@
package Test2::Util::Sub;
use strict;
use warnings;
our $VERSION = '0.000139';
use Carp qw/croak carp/;
use B();
use Sub::Info;
our @EXPORT_OK = qw{
sub_info
sub_name
gen_reader gen_writer gen_accessor
};
use base 'Exporter';
sub gen_reader {
my $field = shift;
return sub { $_[0]->{$field} };
}
sub gen_writer {
my $field = shift;
return sub { $_[0]->{$field} = $_[1] };
}
sub gen_accessor {
my $field = shift;
return sub {
my $self = shift;
($self->{$field}) = @_ if @_;
return $self->{$field};
};
}
sub sub_name {
my ($sub) = @_;
croak "sub_name requires a coderef as its only argument"
unless ref($sub) eq 'CODE';
my $cobj = B::svref_2object($sub);
my $name = $cobj->GV->NAME;
return $name;
}
sub sub_info {
carp "Test2::Util::Sub::sub_info() is deprecated, use Sub::Info::sub_info() instead";
Sub::Info::sub_info(@_);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Sub - Tools for inspecting and manipulating subs.
=head1 DESCRIPTION
Utilities used by Test2::Tools to inspect and manipulate subroutines.
=head1 EXPORTS
All exports are optional, you must specify subs to import.
=over 4
=item $name = sub_name(\&sub)
Get the name of the sub.
=item my $hr = sub_info(\&code)
This returns a hashref with information about the sub:
{
ref => \&code,
cobj => $cobj,
name => "Some::Mod::code",
file => "Some/Mod.pm",
package => "Some::Mod",
# Note: These have been adjusted based on guesswork.
start_line => 22,
end_line => 42,
lines => [22, 42],
# Not a bug, these lines are different!
all_lines => [23, 25, ..., 39, 41],
};
=over 4
=item $info->{ref} => \&code
This is the original sub passed to C<sub_info()>.
=item $info->{cobj} => $cobj
This is the c-object representation of the coderef.
=item $info->{name} => "Some::Mod::code"
This is the name of the coderef. For anonymous coderefs this may end with
C<'__ANON__'>. Also note that the package 'main' is special, and 'main::' may
be omitted.
=item $info->{file} => "Some/Mod.pm"
The file in which the sub was defined.
=item $info->{package} => "Some::Mod"
The package in which the sub was defined.
=item $info->{start_line} => 22
=item $info->{end_line} => 42
=item $info->{lines} => [22, 42]
These three fields are the I<adjusted> start line, end line, and array with both.
It is important to note that these lines have been adjusted and may not be
accurate.
The lines are obtained by walking the ops. As such, the first line is the line
of the first statement, and the last line is the line of the last statement.
This means that in multi-line subs the lines are usually off by 1. The lines
in these keys will be adjusted for you if it detects a multi-line sub.
=item $info->{all_lines} => [23, 25, ..., 39, 41]
This is an array with the lines of every statement in the sub. Unlike the other
line fields, these have not been adjusted for you.
=back
=back
=head1 SOURCE
The source code repository for Test2-Suite 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>
=item Kent Fredric E<lt>kentnl@cpan.orgE<gt>
=back
=head1 COPYRIGHT
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<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

View File

@@ -0,0 +1,199 @@
package Test2::Util::Table;
use strict;
use warnings;
our $VERSION = '0.000139';
use base 'Term::Table';
use Importer Importer => 'import';
our @EXPORT_OK = qw/table/;
our %EXPORT_GEN = (
'&term_size' => sub {
require Carp;
Carp::cluck "term_size should be imported from Test2::Util::Term, not " . __PACKAGE__;
Test2::Util::Term->can('term_size');
},
);
sub table {
my %params = @_;
$params{collapse} ||= 0;
$params{sanitize} ||= 0;
$params{mark_tail} ||= 0;
$params{show_header} ||= 0 unless $params{header} && @{$params{header}};
__PACKAGE__->new(%params)->render;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Table - Format a header and rows into a table
=head1 DESCRIPTION
This is used by some failing tests to provide diagnostics about what has gone
wrong. This module is able to generic format rows of data into tables.
=head1 SYNOPSIS
use Test2::Util::Table qw/table/;
my @table = table(
max_width => 80,
collapse => 1, # Do not show empty columns
header => [ 'name', 'age', 'hair color' ],
rows => [
[ 'Fred Flinstone', 2000000, 'black' ],
[ 'Wilma Flinstone', 1999995, 'red' ],
...,
],
);
# The @table array contains each line of the table, no newlines added.
say $_ for @table;
This prints a table like this:
+-----------------+---------+------------+
| name | age | hair color |
+-----------------+---------+------------+
| Fred Flinstone | 2000000 | black |
| Wilma Flinstone | 1999995 | red |
| ... | ... | ... |
+-----------------+---------+------------+
=head1 EXPORTS
=head2 @rows = table(...)
The function returns a list of lines, lines do not have the newline C<\n>
character appended.
Options:
=over 4
=item header => [ ... ]
If you want a header specify it here. This takes an arrayref with each columns
heading.
=item rows => [ [...], [...], ... ]
This should be an arrayref containing an arrayref per row.
=item collapse => $bool
Use this if you want to hide empty columns, that is any column that has no data
in any row. Having a header for the column will not effect collapse.
=item max_width => $num
Set the maximum width of the table, the table may not be this big, but it will
be no bigger. If none is specified it will attempt to find the width of your
terminal and use that, otherwise it falls back to C<80>.
=item sanitize => $bool
This will sanitize all the data in the table such that newlines, control
characters, and all whitespace except for ASCII 20 C<' '> are replaced with
escape sequences. This prevents newlines, tabs, and similar whitespace from
disrupting the table.
B<Note:> newlines are marked as '\n', but a newline is also inserted into the
data so that it typically displays in a way that is useful to humans.
Example:
my $field = "foo\nbar\nbaz\n";
print join "\n" => table(
sanitize => 1,
rows => [
[$field, 'col2' ],
['row2 col1', 'row2 col2']
]
);
Prints:
+-----------------+-----------+
| foo\n | col2 |
| bar\n | |
| baz\n | |
| | |
| row2 col1 | row2 col2 |
+-----------------+-----------+
So it marks the newlines by inserting the escape sequence, but it also shows
the data across as many lines as it would normally display.
=item mark_tail => $bool
This will replace the last whitespace character of any trailing whitespace with
its escape sequence. This makes it easier to notice trailing whitespace when
comparing values.
=back
=head2 my $cols = term_size()
Attempts to find the width in columns (characters) of the current terminal.
Returns 80 as a safe bet if it cannot find it another way.
=head1 NOTE ON UNICODE/WIDE CHARACTERS
Some unicode characters, such as C<婧> (C<U+5A67>) are wider than others. These
will render just fine if you C<use utf8;> as necessary, and
L<Unicode::GCString> is installed, however if the module is not installed there
will be anomalies in the table:
+-----+-----+---+
| a | b | c |
+-----+-----+---+
| 婧 | x | y |
| x | y | z |
| x | 婧 | z |
+-----+-----+---+
=head1 SOURCE
The source code repository for Test2-Suite 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>exodist@cpan.orgE<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

View File

@@ -0,0 +1,9 @@
package Test2::Util::Table::Cell;
use strict;
use warnings;
our $VERSION = '0.000139';
use base 'Term::Table::Cell';
1;

View File

@@ -0,0 +1,67 @@
package Test2::Util::Table::LineBreak;
use strict;
use warnings;
our $VERSION = '0.000139';
use base 'Term::Table::LineBreak';
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Table::LineBreak - Break up lines for use in tables.
=head1 DESCRIPTION
This is meant for internal use. This package takes long lines of text and
splits them so that they fit in table rows.
=head1 SYNOPSIS
use Test2::Util::Table::LineBreak;
my $lb = Test2::Util::Table::LineBreak->new(string => $STRING);
$lb->break($SIZE);
while (my $part = $lb->next) {
...
}
=head1 SOURCE
The source code repository for Test2-Suite 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>exodist@cpan.orgE<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

View File

@@ -0,0 +1,12 @@
package Test2::Util::Term;
use strict;
use warnings;
use Term::Table::Util qw/term_size USE_GCS USE_TERM_READKEY uni_length/;
our $VERSION = '0.000139';
use Importer Importer => 'import';
our @EXPORT_OK = qw/term_size USE_GCS USE_TERM_READKEY uni_length/;
1;

View File

@@ -0,0 +1,138 @@
package Test2::Util::Times;
use strict;
use warnings;
use List::Util qw/sum/;
our $VERSION = '0.000139';
our @EXPORT_OK = qw/render_bench render_duration/;
use base 'Exporter';
sub render_duration {
my $time;
if (@_ == 1) {
($time) = @_;
}
else {
my ($start, $end) = @_;
$time = $end - $start;
}
return sprintf('%1.5fs', $time) if $time < 10;
return sprintf('%2.4fs', $time) if $time < 60;
my $msec = substr(sprintf('%0.2f', $time - int($time)), -2, 2);
my $secs = $time % 60;
my $mins = int($time / 60) % 60;
my $hours = int($time / 60 / 60) % 24;
my $days = int($time / 60 / 60 / 24);
my @units = (qw/d h m/, '');
my $duration = '';
for my $t ($days, $hours, $mins, $secs) {
my $u = shift @units;
next unless $t || $duration;
$duration = join ':' => grep { length($_) } $duration, sprintf('%02u%s', $t, $u);
}
$duration ||= '0';
$duration .= ".$msec" if int($msec);
$duration .= 's';
return $duration;
}
sub render_bench {
my ($start, $end, $user, $system, $cuser, $csystem) = @_;
my $duration = render_duration($start, $end);
my $bench = sprintf(
"%s on wallclock (%5.2f usr %5.2f sys + %5.2f cusr %5.2f csys = %5.2f CPU)",
$duration, $user, $system, $cuser, $csystem, sum($user, $system, $cuser, $csystem),
);
$bench =~ s/\s+/ /g;
$bench =~ s/(\(|\))\s+/$1/g;
return $bench;
}
1;
=pod
=encoding UTF-8
=head1 NAME
Test2::Util::Times - Format timing/benchmark information.
=head1 DESCRIPTION
This modules exports tools for rendering timing data at the end of tests.
=head1 EXPORTS
All exports are optional. You must specify subs to import.
=over 4
=item $str = render_bench($start, $end, $user, $system, $cuser, $csystem)
=item $str = render_bench($start, time(), times())
This will produce a string like one of these (Note these numbers are completely
made up). I<Which string is used depends on the time elapsed.>
0.12345s on wallclock (0.05 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.05 CPU)
11.1234s on wallclock (0.05 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.05 CPU)
01m:54.45s on wallclock (0.05 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.05 CPU)
18h:22m:54.45s on wallclock (0.05 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.05 CPU)
04d:18h:22m:54.45s on wallclock (0.05 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.05 CPU)
The first 2 arguments are the C<$start> and C<$end> times in seconds (as
returned by C<time()> or C<Time::HiRes::time()>).
The last 4 arguments are timing information as returned by the C<times()>
function.
=back
=head1 SOURCE
The source code repository for Test2-Suite 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>exodist@cpan.orgE<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