21st century cpan testing: cpanci
DESCRIPTION
Presented at the 2013 Pittsburgh Perl Workshop, this talk discusses the history and technology behind Mike Friedman's CPANci project.TRANSCRIPT
21st CenturyCPAN Testing
Mike Friedman(friedo)MongoDB, Inc.
Aug. 27, 2013:
, Inc.
, Inc.
makes
(the database)
, Inc.
makes
(the database)
employs
Mike Friedman(friedo)
, Inc.
makes
(the database)
employs
Mike Friedman(friedo)
WAT
What is MongoDB?
Open Source Non-relational Horizontally
Scalable
Document-oriented Fast Database
Fault-tolerant CoolSchemaless
Document-oriented
Document-oriented
JSON-like thingy:
{ "foo": "a string", "bar": 42, "baz": [ 1, 2, "narf", "poit" ], "quux": { "key_1": "w00t.", "key_2": "you get the idea", }}
{ "foo": "a string", "bar": 42, "baz": [ 1, 2, "narf", "poit" ], "quux": { "key_1": "w00t.", "key_2": "you get the idea", }}
{ "foo": "a string", "bar": 42, "baz": [ 1, 2, "narf", "poit" ], "quux": { "key_1": "w00t.", "key_2": "you get the idea", }}
{ "foo": "a string", "bar": 42, "baz": [ 1, 2, "narf", "poit" ], "quux": { "key_1": "w00t.", "key_2": "you get the idea", }}
First-Class Objects
First-Class Objects
First-Class Objects
First-Class Objects
Queryable
First-Class Objects
Queryable Indexable
First-Class Objects
Queryable Indexable Updateable
Testing CPANin the
21st Century
A lengthy series of bad ideas and stupid
questions.
Stupid Question No. 1
Stupid Question No. 1Who here uses CPAN?
Stupid Question No. 2
Stupid Question No. 2Who here is a CPAN author?
What’s this about, anyway?
What’s this about, anyway?
CPANci
A Brief History
A Brief History
•December 18, 1987
A Brief History
•December 18, 1987•Perl 1.000 released.
A Brief History
•December 18, 1987•Perl 1.000 released.•TAP invented.
The Test Anything Protocol
The Test Anything Protocol
1..42ok 1 the thing looks good!ok 2ok 3 $beer isa $drinknot ok 4 too much $beernot ok 5 $me->vomit( 'now' )...
A Brief History
A Brief History
•October 17, 1994
A Brief History
•October 17, 1994•Perl 5.000 released.
A Brief History
•October 17, 1994•Perl 5.000 released.•Perl has a module system.
# from thisrequire "funcs.pl";
# from thisrequire "funcs.pl";
# to thisuse My::Module;
# but under the hoodBEGIN { require My::Module; My::Module->import;};
A Brief History
A Brief History
•October 26, 1995
A Brief History
•October 26, 1995•CPAN established.
A Brief History
•October 26, 1995•CPAN established.•Perl modules are available.
A Brief History
A Brief History
•May 15, 1997
A Brief History
•May 15, 1997•Perl 5.004 released.
A Brief History
•May 15, 1997•Perl 5.004 released.•CPAN.pm is in the core.
# the dark art$ perl -MCPAN -e 'install Foo'
A Brief History
A Brief History
•May, 1998
A Brief History
•May, 1998•CPAN Testers conceived
A Brief History
•May, 1998•CPAN Testers conceived•Automated feedback for authors
A Brief History
A Brief History
•November 15, 2003
A Brief History
•November 15, 2003•Perl 5.6.2 released.
A Brief History
•November 15, 2003•Perl 5.6.2 released.•Test::More is in the core.
use Test::More tests => 3;
ok( 42 );is( $foo, 'my value' );isnt( 'foo', 'bar' );
A Brief History
A Brief History
•August 6, 2012
A Brief History
•August 6, 2012•Mike goes to work for 10gen
A Brief History
•August 6, 2012•Mike goes to work for 10gen MongoDB
Bad Idea No. 1
Bad Idea No. 1Come up with a cool Perl MongoDB project
to show off at YAPC!
Bad Idea No. 1Come up with a cool Perl MongoDB project
to show off at YAPC!
It'll be fun!
Bad Idea No. 1Come up with a cool Perl MongoDB project
to show off at YAPC!
It'll be fun!promise!
CPAN Testers
CPAN Testers is Wonderful and Amazing
Disadvantages:
Disadvantages:Not real time
Disadvantages:Not real timeNot consistent
Disadvantages:Not real timeNot consistentPolluted / Inconsistent
environments
Disadvantages:Not real timeNot consistentPolluted / Inconsistent
environmentsNot all versions on all platforms
Perl 5.18 runs on
GNU Hurd
Perl 5.18 runs on
GNU Hurd?
Perl 5.18 runs on
GNU Hurd?
Perl 5.18 runs on
GNU Hurd?I ♥
CPAN
I don't care.
I care about:
I care about:
I care about:
I care about:
I want Continuous Integrationfor the entire CPAN.
I want Continuous Integrationfor the entire CPAN.
For platforms I care about.
Bad Idea No. 2
CPANci.org
Bad Idea No. 2
Stupid Question No. 3
Stupid Question No. 3How can we test CPAN without the
disadvantages of CPAN Testers?
Postulate:
Every CPAN distribution must be tested in isolation, on a virgin Perl installation untouched by human hands.
I release distribution Foo-Awesome-0.0000001
I release distribution Foo-Awesome-0.0000001
use Spiffy::Module;
I release distribution Foo-Awesome-0.0000001
use Spiffy::Module;
Forgotten dependency!
I release distribution Foo-Awesome-0.0000001
use Spiffy::Module;
Forgotten dependency!
What happens?
Scenario IThe tester hath not the missing
dependency upon his box.
FAIL- mail
Scenario IIThe tester doth possess
the dependency upon his box.
EVERYTHING IS FINE.
NOT FINE.
Postulate:
Every CPAN distribution must be tested in isolation, on a virgin Perl installation untouched by human hands.
Postulate:
Every CPAN distribution must be tested in isolation, on a virgin Perl installation untouched by human hands.
So how do we do that?
perlbrew
Virtualization
Virtualization
Whoa!
Bad Idea No. 3
•Create an EC2 image
•Create an EC2 image•Put perlbrew on it
•Create an EC2 image•Put perlbrew on it•Install every Perl locally
•Create an EC2 image•Put perlbrew on it•Install every Perl locally•Boot an instance for every uploaded distribution
•Create an EC2 image•Put perlbrew on it•Install every Perl locally•Boot an instance for every uploaded distribution•Install needed dependencies for the distribution
•Create an EC2 image•Put perlbrew on it•Install every Perl locally•Boot an instance for every uploaded distribution•Install needed dependencies for the distribution•Run the tests and report the results
•Create an EC2 image•Put perlbrew on it•Install every Perl locally•Boot an instance for every uploaded distribution•Install needed dependencies for the distribution•Run the tests and report the results•Shut down the instance
Uh-oh.
Stupid Question No. 4
Stupid Question No. 4How can we do this on one instance?
local::lib
cpanminus
App::cpanminus
App::cpanminus
•Self-contained
App::cpanminus
•Self-contained•That is, the cpanm script is self-contained
App::cpanminus
•Self-contained•That is, the cpanm script is self-contained•via App::FatPacker
App::cpanminus
App::cpanminusThat means the same cpanm can be run by any perl
App::cpanminusThat means the same cpanm can be run by any perl
perlbrew switch mastercurl -L http://cpanmin.us/ | perl - App::cpanminusln -s ~/perl5/perlbrew/perls/master/bin/cpanm ./cpanm
~/perl5/perlbrew/perls/perl-5.8.9/bin/perl cpanm~/perl5/perlbrew/perls/perl-5.10.1/bin/perl cpanm~/perl5/perlbrew/perls/perl-5.12.5/bin/perl cpanm~/perl5/perlbrew/perls/perl-5.14.4/bin/perl cpanm~/perl5/perlbrew/perls/perl-5.16.3/bin/perl cpanm~/perl5/perlbrew/perls/perl-5.18.0/bin/perl cpanm~/perl5/perlbrew/perls/perl-5.19.0/bin/perl cpanm
•Use perlbrew to install a "master" perl
•Use perlbrew to install a "master" perl•Use it again to install "virgin" perls of every major version
•Use perlbrew to install a "master" perl•Use it again to install "virgin" perls of every major version•Use the master perl to install everything from CPAN that makes CPANci work
•Use perlbrew to install a "master" perl•Use it again to install "virgin" perls of every major version•Use the master perl to install everything from CPAN that makes CPANci work•For each distribution, create a temp directory
•Use perlbrew to install a "master" perl•Use it again to install "virgin" perls of every major version•Use the master perl to install everything from CPAN that makes CPANci work•For each distribution, create a temp directory•Tell cpanminus to install dependencies there, as if for local::lib
•Use perlbrew to install a "master" perl•Use it again to install "virgin" perls of every major version•Use the master perl to install everything from CPAN that makes CPANci work•For each distribution, create a temp directory•Tell cpanminus to install dependencies there, as if for local::lib•Run tests and report results
•Use perlbrew to install a "master" perl•Use it again to install "virgin" perls of every major version•Use the master perl to install everything from CPAN that makes CPANci work•For each distribution, create a temp directory•Tell cpanminus to install dependencies there, as if for local::lib•Run tests and report results•Delete temp directory, leaving each perl untouched!
What does that look like?
The "fetcher" grabs the latest distribution URLs from MetaCPAN's RSS feed.
my $xml = XML::LibXML->load_xml( string => $self->ua->get( $self->rss_base )->decoded_content);
my @dists = map { my $u = URI->new( $_->getAttribute( 'rdf:resource' ) ); $u->host( 'api.metacpan.org' ); $u->path( 'v0' . $u->path ); $u;} $xml->findnodes( '//rdf:li' );
What does that look like?
The "fetcher" grabs the latest distribution URLs from MetaCPAN's RSS feed.
my $xml = XML::LibXML->load_xml( string => $self->ua->get( $self->rss_base )->decoded_content);
my @dists = map { my $u = URI->new( $_->getAttribute( 'rdf:resource' ) ); $u->host( 'api.metacpan.org' ); $u->path( 'v0' . $u->path ); $u;} $xml->findnodes( '//rdf:li' );
What does that look like?
The "fetcher" grabs the latest distribution URLs from MetaCPAN's RSS feed.
my $xml = XML::LibXML->load_xml( string => $self->ua->get( $self->rss_base )->decoded_content);
my @dists = map { my $u = URI->new( $_->getAttribute( 'rdf:resource' ) ); $u->host( 'api.metacpan.org' ); $u->path( 'v0' . $u->path ); $u;} $xml->findnodes( '//rdf:li' );
What does that look like?
The "fetcher" grabs the latest distribution URLs from MetaCPAN's RSS feed.
my $xml = XML::LibXML->load_xml( string => $self->ua->get( $self->rss_base )->decoded_content);
my @dists = map { my $u = URI->new( $_->getAttribute( 'rdf:resource' ) ); $u->host( 'api.metacpan.org' ); $u->path( 'v0' . $u->path ); $u;} $xml->findnodes( '//rdf:li' );
What does that look like?
We retrieve the distribution metadata from the MetaCPAN JSON API and save it to
MongoDB.
foreach my $dist( @dists ) { my $fetched_data = decode_json $self->ua->get( $dist )->decoded_content;
my %ins = ( _id => 'cpan/' . $name, fetched => DateTime->now, meta => $fetched_data );
$coll->insert( \%ins );}
What does that look like?
We retrieve the distribution metadata from the MetaCPAN JSON API and save it to
MongoDB.
foreach my $dist( @dists ) { my $fetched_data = decode_json $self->ua->get( $dist )->decoded_content;
my %ins = ( _id => 'cpan/' . $name, fetched => DateTime->now, meta => $fetched_data );
$coll->insert( \%ins );}
What does that look like?
We retrieve the distribution metadata from the MetaCPAN JSON API and save it to
MongoDB.
foreach my $dist( @dists ) { my $fetched_data = decode_json $self->ua->get( $dist )->decoded_content;
my %ins = ( _id => 'cpan/' . $name, fetched => DateTime->now, meta => $fetched_data );
$coll->insert( \%ins );}
What does that look like?
We retrieve the distribution metadata from the MetaCPAN JSON API and save it to
MongoDB.
foreach my $dist( @dists ) { my $fetched_data = decode_json $self->ua->get( $dist )->decoded_content;
my %ins = ( _id => 'cpan/' . $name, fetched => DateTime->now, meta => $fetched_data );
$coll->insert( \%ins );}
What does that look like?
We retrieve the distribution metadata from the MetaCPAN JSON API and save it to
MongoDB.
foreach my $dist( @dists ) { my $fetched_data = decode_json $self->ua->get( $dist )->decoded_content;
my %ins = ( _id => 'cpan/' . $name, fetched => DateTime->now, meta => $fetched_data );
$coll->insert( \%ins );}
What does that look like?
We extract the archive in a specific "work" directoryfor each perl.
Then use a temp directory for building and installingdependencies.
foreach my $perl ( @{ $self->perls } ) { my $workdir = catdir $self->home, 'work', $perl, $dist; chdir $workdir;}
What does that look like?
We extract the archive in a specific "work" directoryfor each perl.
Then use a temp directory for building and installingdependencies.
foreach my $perl ( @{ $self->perls } ) { my $workdir = catdir $self->home, 'work', $perl, $dist; chdir $workdir;}
What does that look like?
We extract the archive in a specific "work" directoryfor each perl.
Then use a temp directory for building and installingdependencies.
foreach my $perl ( @{ $self->perls } ) { my $workdir = catdir $self->home, 'work', $perl, $dist; chdir $workdir;}
What does that look like?
We extract the archive in a specific "work" directoryfor each perl.
Then use a temp directory for building and installingdependencies.
foreach my $perl ( @{ $self->perls } ) { my $workdir = catdir $self->home, 'work', $perl, $dist; chdir $workdir;}
What does that look like?
Use a specific perl binary to run cpanm and install dependencies, with no tests, to the temp
directory.
Then we parse the cpanm log on stderr
my $plbin = catfile $self->pldir, $perl, 'bin', 'perl';my $pid = open3 $wtr, $rdr, $err, $plbin, $self->cpanm, '--installdeps', '--notest', '-L', $dist_tmp, '.';
my $results = $self->_read_cpanm_deps_log( $err );
What does that look like?
Use a specific perl binary to run cpanm and install dependencies, with no tests, to the temp
directory.
Then we parse the cpanm log on stderr
my $plbin = catfile $self->pldir, $perl, 'bin', 'perl';my $pid = open3 $wtr, $rdr, $err, $plbin, $self->cpanm, '--installdeps', '--notest', '-L', $dist_tmp, '.';
my $results = $self->_read_cpanm_deps_log( $err );
What does that look like?
Use a specific perl binary to run cpanm and install dependencies, with no tests, to the temp
directory.
Then we parse the cpanm log on stderr
my $plbin = catfile $self->pldir, $perl, 'bin', 'perl';my $pid = open3 $wtr, $rdr, $err, $plbin, $self->cpanm, '--installdeps', '--notest', '-L', $dist_tmp, '.';
my $results = $self->_read_cpanm_deps_log( $err );
What does that look like?
Use a specific perl binary to run cpanm and install dependencies, with no tests, to the temp
directory.
Then we parse the cpanm log on stderr
my $plbin = catfile $self->pldir, $perl, 'bin', 'perl';my $pid = open3 $wtr, $rdr, $err, $plbin, $self->cpanm, '--installdeps', '--notest', '-L', $dist_tmp, '.';
my $results = $self->_read_cpanm_deps_log( $err );
What does that look like?
Use a specific perl binary to run cpanm and install dependencies, with no tests, to the temp
directory.
Then we parse the cpanm log on stderr
my $plbin = catfile $self->pldir, $perl, 'bin', 'perl';my $pid = open3 $wtr, $rdr, $err, $plbin, $self->cpanm, '--installdeps', '--notest', '-L', $dist_tmp, '.';
my $results = $self->_read_cpanm_deps_log( $err );
What does that look like?
Use a specific perl binary to run cpanm and install dependencies, with no tests, to the temp
directory.
Then we parse the cpanm log on stderr
my $plbin = catfile $self->pldir, $perl, 'bin', 'perl';my $pid = open3 $wtr, $rdr, $err, $plbin, $self->cpanm, '--installdeps', '--notest', '-L', $dist_tmp, '.';
my $results = $self->_read_cpanm_deps_log( $err );
"deps" : { "log" : [ { "indent" : 0, "type" : "working-on", "line" : "--> Working on .\n" }, { "line" : "Configuring Lingua-EN-NamedEntity-1.92 ... OK\n", "type" : "config", "indent" : 1 }, { "type" : "found-deps", "indent" : 1, "line" : "==> Found dependencies: Lingua::Stem::En, DB_File, LWP::Simple\n" }, { "indent" : 1, "type" : "working-on", "line" : "--> Working on Lingua::Stem::En\n" }, { "indent" : 2, "type" : "fetch", "line" : "Fetching http://www.cpan.org/authors/id/S/SN/SNOWHARE/Lingua-Stem-0.84.tar.gz ... OK\n" },
What does that look like?
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Use a specific perl to run each test file,save the TAP output and any errors, and
use the exit status to determine if it passed.
foreach my $test( @tests ) { my @test_results; my $plbin = catfile $self->pldir, $perl, 'bin', 'perl'; my $idir = catdir $dist_tmp, 'lib', 'perl5';
my $pid = open3 $wtr, $rdr, $plbin, '-I', $idir, $test;
my ( $tap_out, $errors );
{ local $/; $tap_out = readline $rdr; $errors = readline $err; }
waitpid $pid, 0; my $passed = ( ( $? >> 8 ) == 0 ) true : false );}
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
Parse the TAP output of each test into a structure which can be saved in MongoDB
eval { my $parser = TAP::Parser->new( { source => $tap_out } ); while( my $result = $parser->next ) { push @test_results, { text => $result->as_string, ok => ( $result_is_ok ? true : false ), type => $result->type, $result->type eq 'test' ? ( number => $result->number, desc => ( $result->description =~ s/^- //r ) ) : ( ), }; }};
What does that look like?
"name" : "t/05depth.t", "lines" : [ { "ok" : true, "text" : "1..12", "type" : "plan" }, { "number" : 1, "ok" : true, "type" : "test", "desc" : "new object", "text" : "ok 1 - new object" }, { "ok" : true, "number" : 2, "desc" : "depth", "type" : "test", "text" : "ok 2 - depth" }, { "desc" : "depth", "text" : "ok 3 - depth", "type" : "test", "number" : 3, "ok" : true }, { "type" : "test", "desc" : "depth", "text" : "ok 4 - depth", "ok" : true, "number" : 4 }, { "number" : 5, "ok" : true, "text" : "ok 5 - depth", "desc" : "depth", "type" : "test" }, { "number" : 6, "ok" : true, "type" : "test", "desc" : "depth", "text" : "ok 6 - depth" },
What does that look like?
"name" : "t/05depth.t", "lines" : [ { "ok" : true, "text" : "1..12", "type" : "plan" }, { "number" : 1, "ok" : true, "type" : "test", "desc" : "new object", "text" : "ok 1 - new object" }, { "ok" : true, "number" : 2, "desc" : "depth", "type" : "test", "text" : "ok 2 - depth" }, { "desc" : "depth", "text" : "ok 3 - depth", "type" : "test", "number" : 3, "ok" : true }, { "type" : "test", "desc" : "depth", "text" : "ok 4 - depth", "ok" : true, "number" : 4 }, { "number" : 5, "ok" : true, "text" : "ok 5 - depth", "desc" : "depth", "type" : "test" }, { "number" : 6, "ok" : true, "type" : "test", "desc" : "depth", "text" : "ok 6 - depth" },
What does that look like?
"name" : "t/05depth.t", "lines" : [ { "ok" : true, "text" : "1..12", "type" : "plan" }, { "number" : 1, "ok" : true, "type" : "test", "desc" : "new object", "text" : "ok 1 - new object" }, { "ok" : true, "number" : 2, "desc" : "depth", "type" : "test", "text" : "ok 2 - depth" }, { "desc" : "depth", "text" : "ok 3 - depth", "type" : "test", "number" : 3, "ok" : true }, { "type" : "test", "desc" : "depth", "text" : "ok 4 - depth", "ok" : true, "number" : 4 }, { "number" : 5, "ok" : true, "text" : "ok 5 - depth", "desc" : "depth", "type" : "test" }, { "number" : 6, "ok" : true, "type" : "test", "desc" : "depth", "text" : "ok 6 - depth" },
•This is JSON
What does that look like?
"name" : "t/05depth.t", "lines" : [ { "ok" : true, "text" : "1..12", "type" : "plan" }, { "number" : 1, "ok" : true, "type" : "test", "desc" : "new object", "text" : "ok 1 - new object" }, { "ok" : true, "number" : 2, "desc" : "depth", "type" : "test", "text" : "ok 2 - depth" }, { "desc" : "depth", "text" : "ok 3 - depth", "type" : "test", "number" : 3, "ok" : true }, { "type" : "test", "desc" : "depth", "text" : "ok 4 - depth", "ok" : true, "number" : 4 }, { "number" : 5, "ok" : true, "text" : "ok 5 - depth", "desc" : "depth", "type" : "test" }, { "number" : 6, "ok" : true, "type" : "test", "desc" : "depth", "text" : "ok 6 - depth" },
•This is JSON•Stored in MongoDB
What does that look like?
"name" : "t/05depth.t", "lines" : [ { "ok" : true, "text" : "1..12", "type" : "plan" }, { "number" : 1, "ok" : true, "type" : "test", "desc" : "new object", "text" : "ok 1 - new object" }, { "ok" : true, "number" : 2, "desc" : "depth", "type" : "test", "text" : "ok 2 - depth" }, { "desc" : "depth", "text" : "ok 3 - depth", "type" : "test", "number" : 3, "ok" : true }, { "type" : "test", "desc" : "depth", "text" : "ok 4 - depth", "ok" : true, "number" : 4 }, { "number" : 5, "ok" : true, "text" : "ok 5 - depth", "desc" : "depth", "type" : "test" }, { "number" : 6, "ok" : true, "type" : "test", "desc" : "depth", "text" : "ok 6 - depth" },
•This is JSON•Stored in MongoDB•But it's also TAP!
What does that mean?
Beautiful Tables!
Final Thoughts
Final Thoughts
•Play with new toys.
Final Thoughts
•Play with new toys.•Think before you code.
Final Thoughts
•Play with new toys.•Think before you code.•Throw stuff away.
Final Thoughts
•Play with new toys.•Think before you code.•Throw stuff away.•Have bad ideas.
Final Thoughts
•Play with new toys.•Think before you code.•Throw stuff away.•Have bad ideas.•Ask stupid questions.
Final Thoughts
•Play with new toys.•Think before you code.•Throw stuff away.•Have bad ideas.•Ask stupid questions.•Have fun.
!