21st Century CPAN Testing: CPANci

Post on 17-May-2015

483 views 2 download

Tags:

description

Presented at the 2013 Pittsburgh Perl Workshop, this talk discusses the history and technology behind Mike Friedman's CPANci project.

Transcript of 21st Century CPAN Testing: CPANci

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.

!

Stupid Questions?

Mike Friedman(friedo)

friedo@mongodb.comhttps://github.com/friedo/cpanci

!