Test-BDD-Cucumber-0.17/0000775000175000017500000000000012246645110012615 5ustar bdrbdrTest-BDD-Cucumber-0.17/Makefile.PL0000644000175000017500000000334612246645110014573 0ustar bdrbdr use strict; use warnings; use ExtUtils::MakeMaker 6.30; my %WriteMakefileArgs = ( "ABSTRACT" => "Feature-complete Cucumber-style testing in Perl", "AUTHOR" => "['Peter Sergeant ','Ben Rogers ']", "BUILD_REQUIRES" => {}, "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => "6.30" }, "DISTNAME" => "Test-BDD-Cucumber", "EXE_FILES" => [ "bin/pherkin" ], "LICENSE" => "perl", "NAME" => "Test::BDD::Cucumber", "PREREQ_PM" => { "Clone" => 0, "File::Find::Rule" => 0, "File::Slurp" => 0, "FindBin::libs" => 0, "Getopt::Long" => 0, "List::MoreUtils" => 0, "List::Util" => 0, "Method::Signatures" => 0, "Moose" => 0, "Ouch" => 0, "Path::Class" => 0, "Storable" => 0, "Term::ANSIColor" => "3.00", "Test::Builder" => 0, "Test::Differences" => 0, "Test::More" => 0, "YAML::Syck" => 0 }, "TEST_REQUIRES" => {}, "VERSION" => "0.17", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Clone" => 0, "File::Find::Rule" => 0, "File::Slurp" => 0, "FindBin::libs" => 0, "Getopt::Long" => 0, "List::MoreUtils" => 0, "List::Util" => 0, "Method::Signatures" => 0, "Moose" => 0, "Ouch" => 0, "Path::Class" => 0, "Storable" => 0, "Term::ANSIColor" => "3.00", "Test::Builder" => 0, "Test::Differences" => 0, "Test::More" => 0, "YAML::Syck" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); Test-BDD-Cucumber-0.17/TODO0000644000175000017500000000127712246645110013312 0ustar bdrbdr - [ ] Make TAP output for scenario outlines interpolate correctly - [ ] Get passing against Cucumber-Features - [ ] comments.feature - [X] core.feature - [ ] data_tables.feature - [ ] doc_strings.feature - [ ] failing_steps.feature - [ ] hooks.feature - [X] tags.feature - [ ] undefined_steps.feature - [ ] world.feature - [ ] Add support for explicit outlines - [ ] Simplify Parser - [ ] Add a document on integrating with test suties and CI - [ ] Add a jUnit outputer - [ ] Remove Ouch - [ ] Fix indentation - [ ] Add Transforms - [ ] Allow command localization - [ ] Quoting in tables is broken - [ ] Placeholders in Pystrings - [ ] Add Unicode exmaples - [ ] Make Pherkin feature complete Test-BDD-Cucumber-0.17/t/0000775000175000017500000000000012246645110013060 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/310_auto_corpus.t0000755000175000017500000000135612246645110016201 0ustar bdrbdr#!perl use strict; use warnings; use FindBin::libs; use Test::More; use Test::Differences; use Test::DumpFeature; use Test::BDD::Cucumber::Parser; use YAML::Syck; use File::Slurp; use File::Find::Rule; my @files = @ARGV; @files = File::Find::Rule ->file()->name( '*.feature_corpus' )->in( 't/auto_corpus/' ) unless @files; for my $file ( @files ) { my $file_data = read_file( $file ); my ( $feature, $yaml ) = split(/----------DIVIDER----------/, $file_data); my $expected = Load( $yaml ); my $actual = Test::DumpFeature::dump_feature( Test::BDD::Cucumber::Parser->parse_string( $feature ) ); is_deeply( $actual, $expected, "$file matches" ) || eq_or_diff( $actual, $expected ); } done_testing(); Test-BDD-Cucumber-0.17/t/auto_corpus/0000775000175000017500000000000012246645110015423 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/auto_corpus/digest.feature_corpus0000644000175000017500000000701712246645110021655 0ustar bdrbdrFeature: Simple tests of Digest.pm As a developer planning to use Digest.pm I want to test the basic functionality of Digest.pm In order to have confidence in it Background: Given a usable Digest class Scenario: Check MD5 Given a Digest MD5 object When I've added "foo bar baz" to the object And I've added "bat ban shan" to the object Then the hex output is "bcb56b3dd4674d5d7459c95e4c8a41d5" Then the base64 output is "1B2M2Y8AsgTpgAmY7PhCfg" Scenario: Check SHA-1 Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" Examples: | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | Scenario: MD5 longer data Given a Digest MD5 object When I've added the following to the object """ Here is a chunk of text that works a bit like a HereDoc. We'll split off indenting space from the lines in it up to the indentation of the first \"\"\" """ Then the hex output is "75ad9f578e43b863590fae52d5d19ce6" ----------DIVIDER---------- --- line: 1 name: Simple tests of Digest.pm satisfaction: - As a developer planning to use Digest.pm - I want to test the basic functionality of Digest.pm - In order to have confidence in it scenarios: - background: 0 data: [] line: 9 name: Check MD5 steps: - data: ~ line: 10 text: a Digest MD5 object verb: Given verb_original: Given - data: ~ line: 11 text: I've added "foo bar baz" to the object verb: When verb_original: When - data: ~ line: 12 text: I've added "bat ban shan" to the object verb: When verb_original: And - data: ~ line: 13 text: the hex output is "bcb56b3dd4674d5d7459c95e4c8a41d5" verb: Then verb_original: Then - data: ~ line: 14 text: the base64 output is "1B2M2Y8AsgTpgAmY7PhCfg" verb: Then verb_original: Then - background: 0 data: - data: foo output: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 - data: bar output: 62cdb7020ff920e5aa642c3d4066950dd1f01f4d - data: baz output: bbe960a25ea311d21d40669e93df2003ba9b90a2 line: 16 name: Check SHA-1 steps: - data: ~ line: 17 text: a Digest SHA-1 object verb: Given verb_original: Given - data: ~ line: 18 text: I've added "" to the object verb: When verb_original: When - data: ~ line: 19 text: the hex output is "" verb: Then verb_original: Then - background: 0 data: [] line: 26 name: MD5 longer data steps: - data: ~ line: 27 text: a Digest MD5 object verb: Given verb_original: Given - data: "Here is a chunk of text that works a bit like a HereDoc. We'll split\noff indenting space from the lines in it up to the indentation of the\nfirst \"\"\"\n" line: 28 text: I've added the following to the object verb: When verb_original: When - data: ~ line: 34 text: the hex output is "75ad9f578e43b863590fae52d5d19ce6" verb: Then verb_original: Then Test-BDD-Cucumber-0.17/t/auto_corpus/calculator.feature_corpus0000644000175000017500000001560512246645110022531 0ustar bdrbdrFeature: Basic Calculator Functions In order to check I've written the Calculator class correctly As a developer I want to check some basic operations So that I can have confidence in my Calculator class. Background: Given a usable "Calculator" class Scenario: First Key Press on the Display Given a new Calculator object And having pressed 1 Then the display should show 1 Scenario: Several Key Presses on the Display Given a new Calculator object And having pressed 1 and 2 and 3 and . and 5 and 0 Then the display should show 123.50 Scenario: Pressing Clear Wipes the Display Given a new Calculator object And having pressed 1 and 2 and 3 And having pressed C Then the display should show 0 Scenario: Add as you go Given a new Calculator object And having pressed 1 and 2 and 3 and + and 4 and 5 and 6 and + Then the display should show 579 Scenario: Basic arithmetic Given a new Calculator object And having keyed And having keyed And having keyed And having pressed = Then the display should show Examples: | first | operator | second | result | | 5.0 | + | 5.0 | 10 | | 6 | / | 3 | 2 | | 10 | * | 7.550 | 75.5 | | 3 | - | 10 | -7 | Scenario: Separation of calculations Given a new Calculator object And having successfully performed the following calculations | first | operator | second | result | | 0.5 | + | 0.1 | 0.6 | | 0.01 | / | 0.01 | 1 | | 10 | * | 1 | 10 | And having pressed 3 Then the display should show 3 Scenario: Ticker Tape Given a new Calculator object And having entered the following sequence """ 1 + 2 + 3 + 4 + 5 + 6 - 100 * 13 \=\=\= + 2 = """ Then the display should show -1025 ----------DIVIDER---------- --- line: 1 name: Basic Calculator Functions satisfaction: - In order to check I've written the Calculator class correctly - As a developer I want to check some basic operations - So that I can have confidence in my Calculator class. scenarios: - background: 0 data: [] line: 9 name: First Key Press on the Display steps: - data: ~ line: 10 text: a new Calculator object verb: Given verb_original: Given - data: ~ line: 11 text: having pressed 1 verb: Given verb_original: And - data: ~ line: 12 text: the display should show 1 verb: Then verb_original: Then - background: 0 data: [] line: 14 name: Several Key Presses on the Display steps: - data: ~ line: 15 text: a new Calculator object verb: Given verb_original: Given - data: ~ line: 16 text: having pressed 1 and 2 and 3 and . and 5 and 0 verb: Given verb_original: And - data: ~ line: 17 text: the display should show 123.50 verb: Then verb_original: Then - background: 0 data: [] line: 19 name: Pressing Clear Wipes the Display steps: - data: ~ line: 20 text: a new Calculator object verb: Given verb_original: Given - data: ~ line: 21 text: having pressed 1 and 2 and 3 verb: Given verb_original: And - data: ~ line: 22 text: having pressed C verb: Given verb_original: And - data: ~ line: 23 text: the display should show 0 verb: Then verb_original: Then - background: 0 data: [] line: 25 name: Add as you go steps: - data: ~ line: 26 text: a new Calculator object verb: Given verb_original: Given - data: ~ line: 27 text: having pressed 1 and 2 and 3 and + and 4 and 5 and 6 and + verb: Given verb_original: And - data: ~ line: 28 text: the display should show 579 verb: Then verb_original: Then - background: 0 data: - first: '5.0' operator: + result: 10 second: '5.0' - first: 6 operator: / result: 2 second: 3 - first: 10 operator: "*" result: '75.5' second: '7.550' - first: 3 operator: "-" result: -7 second: 10 line: 30 name: Basic arithmetic steps: - data: ~ line: 31 text: a new Calculator object verb: Given verb_original: Given - data: ~ line: 32 text: having keyed verb: Given verb_original: And - data: ~ line: 33 text: having keyed verb: Given verb_original: And - data: ~ line: 34 text: having keyed verb: Given verb_original: And - data: ~ line: 35 text: having pressed = verb: Given verb_original: And - data: ~ line: 36 text: the display should show verb: Then verb_original: Then - background: 0 data: [] line: 44 name: Separation of calculations steps: - data: ~ line: 45 text: a new Calculator object verb: Given verb_original: Given - data: - first: '0.5' operator: + result: '0.6' second: '0.1' - first: '0.01' operator: / result: 1 second: '0.01' - first: 10 operator: "*" result: 10 second: 1 line: 46 text: having successfully performed the following calculations verb: Given verb_original: And - data: ~ line: 51 text: having pressed 3 verb: Given verb_original: And - data: ~ line: 52 text: the display should show 3 verb: Then verb_original: Then - background: 0 data: [] line: 54 name: Ticker Tape steps: - data: ~ line: 55 text: a new Calculator object verb: Given verb_original: Given - data: "1 + 2 + 3 + 4 + 5 + 6 -\n100\n* 13 === + 2 =\n" line: 56 text: having entered the following sequence verb: Given verb_original: And - data: ~ line: 62 text: the display should show -1025 verb: Then verb_original: Then Test-BDD-Cucumber-0.17/t/700_tag_processing.t0000644000175000017500000000241512246645110016642 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use FindBin::libs; use App::pherkin; use Data::Dumper; for my $test ( [ 'Single tag: -t @cow', ['@cow'], [ and => [ or => 'cow' ] ] ], [ 'Two AND tags: -t @cow -t @blue', ['@cow','@blue'], [ and => [ or => 'cow' ], [ or => 'blue' ] ] ], [ 'Two OR tags: -t @cow,@blue', ['@cow,@blue'], [ and => [ or => 'cow', 'blue' ] ] ], [ 'Two OR, one AND: -t @cow,@blue -t @moo', ['@cow,@blue','@moo'], [ and => [ or => 'cow', 'blue' ], [ or => 'moo' ] ] ], [ 'Negated tag: -t ~@cow', ['~@cow'], [ and => [ or => [ not => 'cow' ] ] ] ], [ 'Negated with OR tag: -t ~@cow,@fish', ['~@cow,@fish'], [ and => [ or => [ not => 'cow' ], 'fish' ] ] ], [ 'Negated with AND tag: -t ~@cow -t @fish', ['~@cow','@fish'], [ and => [ or => [ not => 'cow' ] ], [ or => 'fish' ] ] ], [ 'Two negated: -t ~@cow,~@fish', ['~@cow,~@fish'], [ and => [ or => [ not => 'cow' ], [ not => 'fish' ] ] ] ], ) { my ( $name, $tags, $result ) = @$test; my $tag_out = App::pherkin->new()->_process_tags( @{$tags} ); is_deeply( $tag_out, $result, "Tags: $name" ); } done_testing(); Test-BDD-Cucumber-0.17/t/regressions/0000775000175000017500000000000012246645110015423 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/regressions/010_greedy_table_parsing/0000775000175000017500000000000012246645110022154 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/regressions/010_greedy_table_parsing/test.feature0000644000175000017500000000157712246645110024520 0ustar bdrbdrFeature: Multiple Scenarios As a developer using Test::BDD::Cucumber, I want to ensure that I can use multiple Scenarios in a Feature. Scenario: First test Given a Digest MD5 object When I add "foo" to the object And I add "bar" to the object Then the results look like | method | output | | hexdigest | 3858f62230ac3c915f300c664312c63f | | b64digest | 1B2M2Y8AsgTpgAmY7PhCfg | Scenario: Same test all over again Given a Digest MD5 object When I add "foo" to the object And I add "bar" to the object Then the results look like | method | output | | hexdigest | 3858f62230ac3c915f300c664312c63f | | b64digest | 1B2M2Y8AsgTpgAmY7PhCfg | Test-BDD-Cucumber-0.17/t/regressions/010_greedy_table_parsing/test_steps.pl0000644000175000017500000000116112246645110024703 0ustar bdrbdr#!/usr/bin/perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Digest::MD5; use Method::Signatures; Given qr/a Digest MD5 object/, func($c) { $c->stash->{scenario}{digest} = Digest::MD5->new; }; When qr/I add "([^"]+)" to the object/, func($c) { $c->stash->{scenario}{digest}->add($1); }; Then qr/the results look like/, func($c) { my $data = $c->data; my $digest = $c->stash->{scenario}{digest}; foreach my $row (@{$data}) { my $func = $row->{method}; my $expect = $row->{output}; my $got = $digest->$func(); is $got, $expect, "test: $func"; } }; Test-BDD-Cucumber-0.17/t/800_regressions_010_too_few_features.t0000644000175000017500000000111012246645110022166 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Parser; my $feature = Test::BDD::Cucumber::Parser->parse_file( 'examples/digest/features/basic.feature' ); # Check that we have three scenarios my @scenarios = @{ $feature->scenarios }; for my $scenario_name ( 'Check MD5', 'Check SHA-1', 'MD5 longer data' ) { my $scenario = shift( @scenarios ); ok( $scenario, "Scenario found" ); is( $scenario->name || '', $scenario_name, "Scenario name matches: " . $scenario_name ); } ok( $feature->background, "Background section exists" ); done_testing();Test-BDD-Cucumber-0.17/t/220_tag_parsing.t0000644000175000017500000000274112246645110016130 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Parser; my $feature_with_background = <<'HEREDOC' @inherited1 @inherited2 Feature: Test Feature Conditions of satisfaction Background: Given a passing step called 'background-foo' Given a background step that sometimes passes @foo @bar Scenario: Two tags Given a passing step called 'bar' Given a passing step called 'baz' @baz Scenario: One tag Given a passing step called 'bar' Given a passing step called '' Examples: | name | | bat | | ban | | fan | HEREDOC ; my $feature_without_background = $feature_with_background; $feature_without_background =~ s/\tBackground.+?\n\n//s; for ( [ "Feature with background section", $feature_with_background ], [ "Feature without background section", $feature_without_background ], ) { my ( $name, $feature_text ) = @$_; note( $name ); my $feature = Test::BDD::Cucumber::Parser->parse_string( $feature_text ); my @scenarios = @{ $feature->scenarios }; is( @scenarios, 2, "Found two scenarios" ); my $tags_match = sub { my ( $scenario, @expected_tags ) = @_; my @found_tags = sort @{$scenario->tags}; is_deeply( \@found_tags, [sort @expected_tags], "Tags match for " . $scenario->name ); }; $tags_match->( $feature, qw/inherited1 inherited2 / ); $tags_match->( $scenarios[0], qw/inherited1 inherited2 foo bar / ); $tags_match->( $scenarios[1], qw/inherited1 inherited2 baz / ); } done_testing(); Test-BDD-Cucumber-0.17/t/old/0000775000175000017500000000000012246645110013636 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/old/030_pherkin_010_sanity.t.old0000644000175000017500000000014212246645110020564 0ustar bdrbdr#!perl use strict; use warnings; # Check that we can load a file or a feature dir in to pherkin Test-BDD-Cucumber-0.17/t/old/020_parser_020_corpus.t.old0000755000175000017500000000135612246645110020437 0ustar bdrbdr#!perl use strict; use warnings; use FindBin::libs; use Test::More; use Test::Differences; use Test::DumpFeature; use Test::BDD::Cucumber::Parser; use YAML::Syck; use File::Slurp; use File::Find::Rule; my @files = @ARGV; @files = File::Find::Rule ->file()->name( '*.feature_corpus' )->in( 't/auto_corpus/' ) unless @files; for my $file ( @files ) { my $file_data = read_file( $file ); my ( $feature, $yaml ) = split(/----------DIVIDER----------/, $file_data); my $expected = Load( $yaml ); my $actual = Test::DumpFeature::dump_feature( Test::BDD::Cucumber::Parser->parse_string( $feature ) ); is_deeply( $actual, $expected, "$file matches" ) || eq_or_diff( $actual, $expected ); } done_testing(); Test-BDD-Cucumber-0.17/t/old/900_run_features.t.old0000644000175000017500000000107112246645110017667 0ustar bdrbdr#!perl use strict; use warnings; use FindBin::libs; use Test::More; use Test::BDD::Cucumber::Loader; use Test::BDD::Cucumber::Harness::TestBuilder; my $search_dir = $ARGV[0] || './features/'; unless ( -d $search_dir ) { Test::More->builder->skip_all("No features directory"); } my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load( $search_dir ); my $harness = Test::BDD::Cucumber::Harness::TestBuilder->new(); Test::More->builder->skip_all("No feature files found") unless @features; $executor->execute( $_, $harness ) for @features; done_testing;Test-BDD-Cucumber-0.17/t/900_run_cucumber_tests.t0000644000175000017500000000102212246645110017541 0ustar bdrbdr#!perl use strict; use warnings; use FindBin::libs; use Test::More; use Test::BDD::Cucumber::Loader; use Test::BDD::Cucumber::Harness::TestBuilder; my $harness = Test::BDD::Cucumber::Harness::TestBuilder->new({ fail_skip => 1 }); for my $directory (qw! examples t/cucumber_core_features t/regressions/010_greedy_table_parsing !) { my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load( $directory ); die "No features found" unless @features; $executor->execute( $_, $harness ) for @features; } done_testing; Test-BDD-Cucumber-0.17/t/release-pod-syntax.t0000644000175000017500000000045012246645110016766 0ustar bdrbdr#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod 1.41"; plan skip_all => "Test::Pod 1.41 required for testing POD" if $@; all_pod_files_ok(); Test-BDD-Cucumber-0.17/t/cucumber_core_features/0000775000175000017500000000000012246645110017573 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/cucumber_core_features/tags.feature0000644000175000017500000000550012246645110022104 0ustar bdrbdrFeature: Tags Scenario: execute scenarios matching a tag Given a scenario tagged with "@foo" And a scenario tagged with "@bar" When Cucumber executes scenarios tagged with "@foo" # cucumber --tags @foo Then only the first scenario is executed Scenario: execute scenarios not matching a tag Given a scenario tagged with "@foo" And a scenario tagged with "@bar" When Cucumber executes scenarios not tagged with "@bar" # cucumber --tags ~@bar Then only the first scenario is executed Scenario: execute scenarios matching any of several tags (OR) Given a scenario tagged with "@bar" And a scenario tagged with "@foo" And a scenario tagged with "@baz" When Cucumber executes scenarios tagged with "@foo" or "@bar" # cucumber --tags @foo,@bar Then only the first two scenarios are executed Scenario: execute scenarios matching several tags (AND) Given a scenario tagged with "@foo" and "@bar" And a scenario tagged with "@foo" When Cucumber executes scenarios tagged with both "@foo" and "@bar" # cucumber --tags @foo --tags @bar Then only the first scenario is executed Scenario: execute scenarios not matching any tag (NOT OR NOT) Given a scenario tagged with "@foo" and "@bar" And a scenario tagged with "@bar" And a scenario tagged with "@baz" And a scenario tagged with "@foo" When Cucumber executes scenarios not tagged with "@foo" nor "@bar" # cucumber --tags ~@foo --tags ~@bar Then only the third scenario is executed Scenario: exclude scenarios matching two tags (NOT AND NOT) Given a scenario tagged with "@foo" and "@bar" And a scenario tagged with "@bar" And a scenario tagged with "@baz" And a scenario tagged with "@foo" When Cucumber executes scenarios not tagged with both "@foo" and "@bar" # cucumber --tags ~@foo,~@bar Then only the second, third and fourth scenarios are executed Scenario: with tag or without other tag Given a scenario tagged with "@foo" and "@bar" And a scenario tagged with "@baz" And a scenario tagged with "@bar" When Cucumber executes scenarios tagged with "@foo" or without "@bar" # cucumber --tags @foo,~@bar Then only the first two scenarios are executed Scenario: with tag but without two other tags Given a scenario tagged with "@foo" and "@bar" And a scenario tagged with "@foo", "@bar" and "@baz" And a scenario tagged with "@baz" When Cucumber executes scenarios tagged with "@baz" but not with both "@foo" and "@bar" # cucumber --tags @baz --tags ~@foo --tags ~@bar Then only the third scenario is executed Scenario: execute scenario with tagged feature Given a feature tagged with "@foo" And a scenario without any tags When Cucumber executes scenarios tagged with "@foo" Then the scenario is executed Test-BDD-Cucumber-0.17/t/cucumber_core_features/step_definitions/0000775000175000017500000000000012246645110023141 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/cucumber_core_features/step_definitions/core_steps.pl0000755000175000017500000001245312246645110025652 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Harness::Data; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::StepFile; use Method::Signatures; use List::Util qw/sum/; my $template = join '', (); my $step_mappings = { passing => sub { pass("Step passed") }, failing => sub { fail("Step passed") }, pending => sub { TODO: { local $TODO = 'Todo Step'; ok(0, "Todo Step") } } }; Given 'the following feature', func ($c) { # Save a new executor $c->stash->{'scenario'}->{'executor'} = Test::BDD::Cucumber::Executor->new(); # Create a feature object $c->stash->{'scenario'}->{'feature'} = Test::BDD::Cucumber::Parser->parse_string( $c->data ); }; Given qr/a scenario ("([^"]+)" )?with:/, func ($c) { my $scenario_name = $1 || ''; # Save a new executor $c->stash->{'scenario'}->{'executor'} = Test::BDD::Cucumber::Executor->new(); # Create a feature object with just that scenario inside it $c->stash->{'scenario'}->{'feature'} = Test::BDD::Cucumber::Parser->parse_string( $template . "\n\nScenario: $scenario_name\n" . $c->data ); }; Given qr/the step "([^"]+)" has a (passing|failing|pending) mapping/, func ($c) { # Add the step to our 'Step' list in the executor $c->stash->{'scenario'}->{'executor'}->add_steps( [ 'Step', $1, $step_mappings->{$2} ] ); }; When 'Cucumber runs the feature', func ($c) { $c->stash->{'scenario'}->{'harness'} = Test::BDD::Cucumber::Harness::Data->new({}); $c->stash->{'scenario'}->{'executor'}->execute( $c->stash->{'scenario'}->{'feature'}, $c->stash->{'scenario'}->{'harness'} ); }; When 'Cucumber runs the scenario with steps for a calculator', func ($c) { # FFS. "runs the scenario with steps for a calculator"?!. Cads. Lucky we're # using Perl here... $c->stash->{'scenario'}->{'executor'}->add_steps( [ 'Given', 'a calculator', func ($cc) { $cc->stash->{'scenario'}->{'calculator'} = 0; $cc->stash->{'scenario'}->{'constants'}->{'PI'} = 3.14159265; } ], [ 'When', 'the calculator computes PI', func ($cc) { $cc->stash->{'scenario'}->{'calculator'} = $cc->stash->{'scenario'}->{'constants'}->{'PI'}; } ], [ 'Then', qr/the calculator returns "?(PI|[\d\.]+)"?/, func ($cc) { my $value = $cc->stash->{'scenario'}->{'constants'}->{$1} || $1; is( $cc->stash->{'scenario'}->{'calculator'}, $value, "Correctly returned $value" ); }], [ 'Then', qr/the calculator does not return "?(PI|[\d\.]+)"?/, func ($cc) { my $value = $cc->stash->{'scenario'}->{'constants'}->{$1} || $1; isnt( $cc->stash->{'scenario'}->{'calculator'}, $value, "Correctly did not return $value" ); }], [ 'When', qr/the calculator adds up (.+)/, func ($cc) { my $numbers = $1; my @numbers = $numbers =~ m/([\d\.]+)/g; $cc->stash->{'scenario'}->{'calculator'} = sum( @numbers ); }] ); $c->stash->{'scenario'}->{'harness'} = Test::BDD::Cucumber::Harness::Data->new({}); $c->stash->{'scenario'}->{'executor'}->execute_scenario({ scenario => $c->stash->{'scenario'}->{'feature'}->scenarios->[0], feature => $c->stash->{'scenario'}->{'feature'}, feature_stash => {}, harness => $c->stash->{'scenario'}->{'harness'} }); $c->stash->{'scenario'}->{'scenario'} = $c->stash->{'scenario'}->{'harness'}->current_feature->{'scenarios'}->[0]; }; When qr/Cucumber executes the scenario( "([^"]+)")?/, func ($c) { $c->stash->{'scenario'}->{'harness'} = Test::BDD::Cucumber::Harness::Data->new({}); $c->stash->{'scenario'}->{'executor'}->execute_scenario({ scenario => $c->stash->{'scenario'}->{'feature'}->scenarios->[0], feature => $c->stash->{'scenario'}->{'feature'}, feature_stash => {}, harness => $c->stash->{'scenario'}->{'harness'} }); $c->stash->{'scenario'}->{'scenario'} = $c->stash->{'scenario'}->{'harness'}->current_feature->{'scenarios'}->[0]; }; Then 'the feature passes', func ($c) { my $harness = $c->stash->{'scenario'}->{'harness'}; my $result = $harness->feature_status( $harness->current_feature ); is( $result->result, 'passing', "Feature passes" ) || diag( $result->output ); }; Then qr/the scenario (passes|fails)/, func ($c) { my $wanted = $1; my $harness = $c->stash->{'scenario'}->{'harness'}; my $scenario = $c->stash->{'scenario'}->{'scenario'}; my $result = $harness->scenario_status( $scenario ); my $expected_result = { passes => 'passing', fails => 'failing' }->{$wanted}; is ( $result->result, $expected_result, "Step success return $expected_result" ) || diag $result->output; }; Then qr/the step "(.+)" is skipped/, func ($c) { my $harness = $c->stash->{'scenario'}->{'harness'}; my $scenario = $c->stash->{'scenario'}->{'scenario'}; my $step = $harness->find_scenario_step_by_name( $scenario, $1 ); my $result = $harness->step_status( $step ); is ( $result->result, 'pending', "Step success return 'undefined'" ) || diag $result->output; }; Then qr/the scenario is (pending|undefined)/, func ($c) { my $harness = $c->stash->{'scenario'}->{'harness'}; my $scenario = $c->stash->{'scenario'}->{'scenario'}; my $expected = $1; my $result = $harness->scenario_status( $scenario ); is( $result->result, $expected, "Scenario status is $expected" ) || diag $result->output; } __DATA__ Feature: Sample Blank Feature When interpretting the "Given a Scenario" steps, we'll use this as the base to which to add those scenarios.Test-BDD-Cucumber-0.17/t/cucumber_core_features/step_definitions/tag_steps.pl0000644000175000017500000000631112246645110025466 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Model::Scenario; use Test::BDD::Cucumber::Model::TagSpec; use Method::Signatures; my %ordinals = qw/0 first 1 second 2 third 3 fourth 4 fifth/; Given qr/a scenario tagged with (.+)/, func ($c) { my @tags = $1 =~ m/"\@(.+?)"/g; # How many scenarios have we created so far? my $count = $c->stash->{'scenario'}->{'count'}++; # Create a new one my $scenario = Test::BDD::Cucumber::Model::Scenario->new({ name => $ordinals{ $count }, tags => \@tags }); # Add it to our list my $stack = ($c->stash->{'scenario'}->{'scenarios'} ||= []); push( @$stack, $scenario ); }; # OK, seriously? I know it's meant to be natural language, and "Oh look, # business people are programmers now, lol!" but the syntax the step definition # file uses is insane. Fail. Grrrr... sub from_tagspec { my ( $c, $expr ) = @_; my @scenarios = @{ $c->stash->{'scenario'}->{'scenarios'} }; my @matched = Test::BDD::Cucumber::Model::TagSpec->new({ tags => $expr })->filter( @scenarios ); $c->stash->{'scenario'}->{'matched'} = \@matched; } When qr/^Cucumber executes scenarios (not |)tagged with (both |)"\@([^"]*)"( (and|or|nor) (without |)"\@([^"]*)")?$/, func ($c) { my ( $not_flag, $both, $tag1, $two_part, $joiner, $negate_2, $tag2 ) = @{ $c->matches }; # Normalize nor to or $joiner = 'or' if $joiner && $joiner eq 'nor'; # Negate the second tag if required $tag2 = [ not => $tag2 ] if $negate_2; # If this is two-part, create that inner atom my $inner = $two_part ? [ $joiner, $tag1, $tag2 ] : $tag1; # Create the outer part, based on if it's negated my $outer = $not_flag ? [ and => [ not => $inner ] ] : [ and => $inner ]; from_tagspec( $c, $outer ); }; # Even I, great regex master, wasn't going to tackle this one in the parser # above When qr/^Cucumber executes scenarios tagged with "\@([a-z]+)" but not with both "\@([a-z]+)" and "\@([a-z]+)"/, func ($c) { from_tagspec( $c, [ and => $1, [ not => [ and => $2, $3 ] ] ] ); }; Then qr/only the (.+) scenario( is|s are) executed/, func ($c) { my $demands = $1; my @translates_to; # Work out which scenarios we're talking about if ( $demands eq 'first two' ) { @translates_to = qw/first second/; } else { $demands =~ s/(,|and)/ /g; @translates_to = split(/\s+/, $demands); } # Work out which were executed my @executed = map { $_->name } @{ $c->stash->{'scenario'}->{'matched'} }; is_deeply( \@executed, \@translates_to, "Right scenarios executed" ); }; # This final scenario has been written in a way that is pretty specific to the # underlying implementation the author wanted. I didn't implement that way, so # I'm just going to piggy-back on it, and use the way I've implemented feature # tags... Given 'a feature tagged with "@foo"', func ($c) { my $feature = Test::BDD::Cucumber::Parser->parse_string(<<'HEREDOC' @foo Feature: Name Scenario: first Given bla HEREDOC ); $c->stash->{'scenario'}->{'scenarios'} = $feature->scenarios; }; Given 'a scenario without any tags', func ($c) {1}; Then 'the scenario is executed', func ($c) { ok( $c->stash->{'scenario'}->{'matched'}->[0], "Found an executed scenario" ); } Test-BDD-Cucumber-0.17/t/cucumber_core_features/core.feature0000644000175000017500000001065212246645110022102 0ustar bdrbdrFeature: Core: Scenarios, Steps, Mappings Cucumber is a tool for executing business-readable specifications written in Gherkin. The basic unit of both specification and execution is the Scenario. A Scenario is a list of steps, each of which representing an action performed by a user (or user agent) on the software product under development. When a Scenario is executed, its steps are applied to the software system in the order they are contained in the Scenario. Gherkin is not a programming language, so in order to execute steps written in it, Cucumber must first look up a mapping from the text of each step to a function. If such a mapping exists, the function is executed, and the result is communicated to the user. Scenario: All steps passing means the scenario passes Given a scenario with: """ When I add 4 and 5 Then the result is 9 """ And the step "I add 4 and 5" has a passing mapping And the step "the result is 9" has a passing mapping When Cucumber executes the scenario Then the scenario passes Scenario: Failing step means the scenario fails Given a scenario with: """ When I add 4 and 5 Then the result is 9 """ And the step "I add 4 and 5" has a failing mapping And the step "the result is 9" has a passing mapping When Cucumber executes the scenario Then the scenario fails And the step "the result is 9" is skipped Scenario: Pending step means the scenario is pending Given a scenario with: """ When I add 4 and 5 Then the result is 9 """ And the step "I add 4 and 5" has a pending mapping And the step "the result is 9" has a passing mapping When Cucumber executes the scenario Then the scenario is pending And the step "the result is 9" is skipped Scenario: Missing step mapping means the scenario is undefined Given a scenario with: """ When I add 4 and 5 Then the result is 9 """ And the step "the result is 9" has a passing mapping When Cucumber executes the scenario Then the scenario is undefined And the step "the result is 9" is skipped Scenario: Feature headers Given the following feature: """ Feature: a feature In order to get results As a user I want to do something """ When Cucumber runs the feature Then the feature passes Scenario: Simple flat steps Given a scenario with: """ Given a calculator When the calculator computes PI Then the calculator returns PI """ When Cucumber runs the scenario with steps for a calculator Then the scenario passes Scenario: Given, When, Then, And and But steps Given a scenario with: """ Given a calculator When the calculator adds up 1 and 2 And the calculator adds up 3 and 0.14159265 Then the calculator returns PI But the calculator does not return 3 """ When Cucumber runs the scenario with steps for a calculator Then the scenario passes Scenario: Failing steps Given a scenario with: """ Given a calculator When the calculator adds up 3 and 0.14 Then the calculator returns PI """ When Cucumber runs the scenario with steps for a calculator Then the scenario fails Scenario: Single-parameter step Given a scenario with: """ Given a calculator When the calculator computes PI Then the calculator returns "3.14159265" """ When Cucumber runs the scenario with steps for a calculator Then the scenario passes Scenario: Two-parameter step Given a scenario with: """ Given a calculator When the calculator adds up "12" and "51" Then the calculator returns "63" """ When Cucumber runs the scenario with steps for a calculator Then the scenario passes Scenario: Two-parameter step failing Given a scenario with: """ Given a calculator When the calculator adds up "12" and "51" Then the calculator returns "65" """ When Cucumber runs the scenario with steps for a calculator Then the scenario fails Scenario: Three-parameter step Given a scenario with: """ Given a calculator When the calculator adds up "3", "4" and "5" Then the calculator returns "12" """ When Cucumber runs the scenario with steps for a calculator Then the scenario passes Test-BDD-Cucumber-0.17/t/210_background_sections.t0000644000175000017500000000427312246645110017661 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Harness::Data; my $feature = Test::BDD::Cucumber::Parser->parse_string( <' Examples: | name | | bat | | ban | | fan | HEREDOC ); my $pass_until = 2; my $executor = Test::BDD::Cucumber::Executor->new(); $executor->add_steps( [ Given => qr/a passing step called '(.+)'/, sub { 1; } ], [ Given => 'a background step that sometimes passes', sub { ok( ( $pass_until && $pass_until-- ), "Still passes" ); }], ); my $harness = Test::BDD::Cucumber::Harness::Data->new(); $executor->execute( $feature, $harness ); my @scenarios = @{ $harness->features->[0]->{'scenarios'} }; # We should have four scenarios. The first one, and then the three # implied by the outline. is( @scenarios, 4, "Five scenarios found" ); # Of this, the first two should have passed, the third failed, # and the fourth skipped... my $scenario_status = sub { $harness->scenario_status( $scenarios[shift()] )->result }; is( $scenario_status->(0), 'passing', "Scenario 1 passes" ); is( $scenario_status->(1), 'passing', "Scenario 2 passes" ); is( $scenario_status->(2), 'failing', "Scenario 3 fails" ); is( $scenario_status->(3), 'pending', "Scenario 4 marked pending" ); # Third scenario should have four steps. Two from the background, # and two from definition my @steps = @{ $harness->features->[0] ->{'scenarios'}->[2] ->{'steps'} }; is( @steps, 4, "Four steps found" ); # The step pattern we should see in Scenario 3 is # Pass/Fail/Skip/Skip my $step_status = sub { $harness->step_status( $steps[shift()])->result }; is( $step_status->(0), 'passing', "Step 1 passes" ); is( $step_status->(1), 'failing', "Step 2 fails" ); is( $step_status->(2), 'pending', "Step 3 pending" ); is( $step_status->(3), 'pending', "Step 4 pending" ); done_testing();Test-BDD-Cucumber-0.17/t/lib/0000775000175000017500000000000012246645110013626 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/lib/Test/0000775000175000017500000000000012246645110014545 5ustar bdrbdrTest-BDD-Cucumber-0.17/t/lib/Test/DumpFeature.pm0000644000175000017500000000164612246645110017331 0ustar bdrbdrpackage Test::DumpFeature; use strict; use warnings; sub dump_feature { my $feature = shift; return { name => $feature->name, line => $feature->name_line->number, satisfaction => [ map { $_->content } @{ $feature->satisfaction || [] } ], scenarios => [ map { dump_scenario( $_ ) } @{ $feature->scenarios } ] }; } sub dump_scenario { my $scenario = shift; return { name => $scenario->name, line => $scenario->line->number, data => $scenario->data, background => $scenario->background, steps => [ map { dump_step( $_ ) } @{ $scenario->steps } ] }; } sub dump_step { my $step = shift; return { verb => $step->verb, text => $step->text, data => $step->data, line => $step->line->number, verb_original => $step->verb_original }; } 1; Test-BDD-Cucumber-0.17/t/release-pod-coverage.t0000644000175000017500000000076512246645110017244 0ustar bdrbdr#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod::Coverage 1.08"; plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD coverage" if $@; eval "use Pod::Coverage::TrustPod"; plan skip_all => "Pod::Coverage::TrustPod required for testing POD coverage" if $@; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); Test-BDD-Cucumber-0.17/t/230_tag_matching.t0000644000175000017500000000237312246645110016261 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Model::Scenario; use Test::BDD::Cucumber::Model::TagSpec; my @scenarios = map { my @atoms = @$_; Test::BDD::Cucumber::Model::Scenario->new({ name => shift(@atoms), tags => \@atoms }); } ( [ mercury => qw/all inner / ], [ venus => qw/all inner / ], [ earth => qw/all inner life home/ ], [ mars => qw/all inner life red / ], [ jupiter => qw/all outer gas red / ], [ saturn => qw/all outer gas/ ], [ uranus => qw/all outer gas/ ], [ nepture => qw/all outer gas/ ], [ pluto => qw/all outer fake/ ], ); for my $test ( [ "Lifers and Fakers", [ or => 'life', 'fake' ], qw/ earth mars pluto /, ], [ "Lifeless inner", [ and => [ not => 'life' ], 'inner' ], qw/ mercury venus /, ], [ "Home or Red, Inner", [ and => 'inner', [ or => 'home', 'red' ] ], qw/ earth mars /, ], [ "Home or Not Red, Inner", [ and => 'inner', [ or => 'home', [ not => 'red' ] ] ], qw/ mercury venus earth /, ] ) { my ( $name, $search, @result ) = @$test; my $tag_spec = Test::BDD::Cucumber::Model::TagSpec->new({ tags => $search }); my @matches = map { $_->name } $tag_spec->filter( @scenarios ); is_deeply( \@matches, \@result, "Matched scenario: $name" ); } done_testing(); Test-BDD-Cucumber-0.17/CHANGES0000644000175000017500000000674012246645110013615 0ustar bdrbdr----- 0.17: 01 Dec 2013 - the Calculator module should now be hidden from the PAUSE indexer - The scenario stash wasn't being reset between each Outline Scenario execution. 0.16: 01 Dec 2013 - Default behaviour from pherkin is to supress colours when outputting to not a tty; thanks (for this, and much of the stuff in 0.15) to rjp: https://github.com/sheriff/test-bdd-cucumber-perl/pull/11 - Try and use Win32::Console::ANSI if on Windows https://github.com/sheriff/test-bdd-cucumber-perl/issues/13 - Before and After Hooks now implemented highflying: https://github.com/sheriff/test-bdd-cucumber-perl/pull/15 - Step Placeholder Transform now implemented - Step line number now displayed on failing steps (TestBuilder output) - Fixed bug where results from skipped steps were not being added to the overall results - Run tagged scenarios rjp: https://github.com/sheriff/test-bdd-cucumber-perl/pull/15 highflying: https://github.com/sheriff/test-bdd-cucumber-perl/pull/10 0.15: 21 May 2013 - pherkin now accepts an output type via -o, eg: pherkin -o TestBuilder ; pherkin -o TermColor This is a partial solution to: https://github.com/sheriff/test-bdd-cucumber-perl/issues/8 - Use the original verb that the test file used https://github.com/sheriff/test-bdd-cucumber-perl/issues/9 0.14: 04 May 2013 - Actually apply the Test::Builder 1.5 stuff 0.13: 04 May 2013 - Command-line options for pherkin thanks to cursork - Reintroduced the "auto_corpus" tests, and made them work 0.12: 17 Nov 2012 - Fixed tag-related issues, thanks to Craig Caroon https://github.com/sheriff/test-bdd-cucumber-perl/issues/5 0.11: 20 May 2012 - Correct Term::ANSIColor dependency https://github.com/sheriff/test-bdd-cucumber-perl/issues/4 0.10: 02 May 2012 - Changed dependency from Clone::Fast to Clone, because the following bug stopped it being installed without a force... https://rt.cpan.org/Public/Bug/Display.html?id=65485 0.09: 28 Apr 2012 - Fixed a few spelling mistakes - Added a minimal man page to pherkin - Both as reported by intrigeri@boum.org 0.08: 23 Apr 2012 - Removed some OmniOutliner artifacts. Ooops. - Fixed a spelling mistake - Both as reported by intrigeri@boum.org 0.07: 01 Apr 2012 - Started migration away from Ouch - Added tags at a code-level (but not to pherkin, yet) 0.06: 31 Mar 2012 - Fixed up the behaviour of Background sections, to run before each and every Scenario. See: https://github.com/sheriff/test-bdd-cucumber-perl/issues/3 Bug reported by: intrigeri@boum.org - `pherkin` now returns a non-zero exit code if tests failed, as per: https://github.com/sheriff/test-bdd-cucumber-perl/issues/1 0.05: 18 Mar 2012 - Yet another feature parsing bug, relating to empty lines after scenarios 0.04: 14 Jan 2012 - Fixed a bug relating to recognizing newlines after the end of Scenario tabular data, as reported by Graham TerMarsch 0.03: 03 Jan 2012 - Unbroke the test suite :-P Left a bit too many development pieces in there - Added a new Data Harness - Tidied up the parser - Switched the harnesses to use ::Model::Result, which mirrors Cucumber's result types - Added step data to colour output - Got the core cucumber-tck thingies passing - Various bits of documentation enhancements 0.02: 20 Dec 2011 - Added extra docs, and a few tiny bug fixes Test-BDD-Cucumber-0.17/MANIFEST0000644000175000017500000000375312246645110013754 0ustar bdrbdrCHANGES LICENSE MANIFEST META.yml Makefile.PL README README.pod TODO bin/pherkin dist.ini examples/calculator/features/basic.feature examples/calculator/features/step_definitions/calculator_steps.pl examples/calculator/lib/Calculator.pm examples/digest/features/basic.feature examples/digest/features/step_definitions/basic_steps.pl examples/tagged-digest/features/basic.feature examples/tagged-digest/features/step_definitions/basic_steps.pl lib/App/pherkin.pm lib/Test/BDD/Cucumber.pm lib/Test/BDD/Cucumber/Executor.pm lib/Test/BDD/Cucumber/Harness.pm lib/Test/BDD/Cucumber/Harness/Data.pm lib/Test/BDD/Cucumber/Harness/TermColor.pm lib/Test/BDD/Cucumber/Harness/TestBuilder.pm lib/Test/BDD/Cucumber/Loader.pm lib/Test/BDD/Cucumber/Manual/Architecture.pod lib/Test/BDD/Cucumber/Manual/Integration.pod lib/Test/BDD/Cucumber/Manual/Steps.pod lib/Test/BDD/Cucumber/Manual/Tutorial.pod lib/Test/BDD/Cucumber/Model/Document.pm lib/Test/BDD/Cucumber/Model/Feature.pm lib/Test/BDD/Cucumber/Model/Line.pm lib/Test/BDD/Cucumber/Model/Result.pm lib/Test/BDD/Cucumber/Model/Scenario.pm lib/Test/BDD/Cucumber/Model/Step.pm lib/Test/BDD/Cucumber/Model/TagSpec.pm lib/Test/BDD/Cucumber/Parser.pm lib/Test/BDD/Cucumber/StepContext.pm lib/Test/BDD/Cucumber/StepFile.pm lib/Test/BDD/Cucumber/Util.pm script/make_corpus.pl t/210_background_sections.t t/220_tag_parsing.t t/230_tag_matching.t t/310_auto_corpus.t t/700_tag_processing.t t/800_regressions_010_too_few_features.t t/900_run_cucumber_tests.t t/auto_corpus/calculator.feature_corpus t/auto_corpus/digest.feature_corpus t/cucumber_core_features/core.feature t/cucumber_core_features/step_definitions/core_steps.pl t/cucumber_core_features/step_definitions/tag_steps.pl t/cucumber_core_features/tags.feature t/lib/Test/DumpFeature.pm t/old/020_parser_020_corpus.t.old t/old/030_pherkin_010_sanity.t.old t/old/900_run_features.t.old t/regressions/010_greedy_table_parsing/test.feature t/regressions/010_greedy_table_parsing/test_steps.pl t/release-pod-coverage.t t/release-pod-syntax.t Test-BDD-Cucumber-0.17/dist.ini0000644000175000017500000000112312246645110014254 0ustar bdrbdrname = Test-BDD-Cucumber version = 0.17 abstract = Feature-complete Cucumber-style testing in Perl main_module = lib/Test/BDD/Cucumber.pm author = ['Peter Sergeant ','Ben Rogers '] license = Perl_5 is_trial = 0 copyright_holder = Peter Sergeant [@Classic] [Prereqs] Clone = 0 List::Util = 0 List::MoreUtils = 0 File::Find::Rule = 0 File::Slurp = 0 FindBin::libs = 0 Getopt::Long = 0 Method::Signatures = 0 Moose = 0 Ouch = 0 Path::Class = 0 Storable = 0 Term::ANSIColor = 3.00 Test::Builder = 0 Test::Differences = 0 Test::More = 0 YAML::Syck = 0 Test-BDD-Cucumber-0.17/META.yml0000644000175000017500000000137012246645110014065 0ustar bdrbdr--- abstract: 'Feature-complete Cucumber-style testing in Perl' author: - "['Peter Sergeant ','Ben Rogers ']" build_requires: {} configure_requires: ExtUtils::MakeMaker: 6.30 dynamic_config: 0 generated_by: 'Dist::Zilla version 5.006, CPAN::Meta::Converter version 2.132830' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Test-BDD-Cucumber requires: Clone: 0 File::Find::Rule: 0 File::Slurp: 0 FindBin::libs: 0 Getopt::Long: 0 List::MoreUtils: 0 List::Util: 0 Method::Signatures: 0 Moose: 0 Ouch: 0 Path::Class: 0 Storable: 0 Term::ANSIColor: 3.00 Test::Builder: 0 Test::Differences: 0 Test::More: 0 YAML::Syck: 0 version: 0.17 Test-BDD-Cucumber-0.17/LICENSE0000644000175000017500000004366112246645110013632 0ustar bdrbdrThis software is copyright (c) 2013 by Peter Sergeant. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself 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" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2013 by Peter Sergeant. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. 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 the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2013 by Peter Sergeant. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Test-BDD-Cucumber-0.17/lib/0000775000175000017500000000000012246645110013363 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/App/0000775000175000017500000000000012246645110014103 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/App/pherkin.pm0000644000175000017500000000743212246645110016105 0ustar bdrbdrpackage App::pherkin; { $App::pherkin::VERSION = '0.17'; } use strict; use warnings; use FindBin::libs; use Getopt::Long; use Moose; has 'tags' => ( is => 'rw', isa => 'ArrayRef', required => 0 ); has 'tag_scheme' => ( is => 'rw', isa => 'ArrayRef', required => 0 ); =head1 NAME App::pherkin - Run Cucumber tests from the command line =head1 VERSION version 0.17 =head1 SYNOPSIS pherkin pherkin some/path/features/ =head1 DESCRIPTION C will search the directory specified (or C<./features/>) for feature files (any file matching C<*.feature>) and step definition files (any file matching C<*_steps.pl>), loading the step definitions and then executing the features. Steps that pass will be printed in green, those that fail in red, and those for which there is no step definition as yellow (for TODO), assuming you're using the default output harness. =cut use Test::BDD::Cucumber::Loader; =head1 METHODS =head2 run The C class, which is what the C command uses, makes use of the C method, which accepts currently a single path as a string, or nothing. Returns a L object for all steps run. =cut sub run { my ( $self, @arguments ) = @_; my ($options, @feature_files) = $self->_process_arguments(@arguments); my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load( $feature_files[0] || './features/', $self->tag_scheme ); die "No feature files found" unless @features; eval "require $options->{'harness'}" || die $@; my $harness = $options->{'harness'}->new(); $harness->startup(); my $tag_spec; if ($self->tag_scheme) { $tag_spec = Test::BDD::Cucumber::Model::TagSpec->new({ tags => $self->tag_scheme }); } $executor->execute( $_, $harness, $tag_spec ) for @features; $harness->shutdown(); return $harness->result; } sub _process_arguments { my ( $self, @args ) = @_; local @ARGV = @args; # Allow -Ilib, -bl Getopt::Long::Configure('bundling'); my $includes = []; my $tags = []; GetOptions( 'I=s@' => \$includes, 'l|lib' => \(my $add_lib), 'b|blib' => \(my $add_blib), 'o|output=s' => \(my $harness), 't|tags=s@' => \$tags, ); unshift @$includes, 'lib' if $add_lib; unshift @$includes, 'blib/lib', 'blib/arch' if $add_blib; # Munge the output harness $harness //= "TermColor"; $harness = "Test::BDD::Cucumber::Harness::$harness" unless $harness =~ m/\:\:/; lib->import(@$includes) if @$includes; # Store our TagSpecScheme $self->tag_scheme( $self->_process_tags( @{$tags} ) ); return ({ harness => $harness }, @ARGV); } sub _process_tags { my ( $self, @tags ) = @_; # This is a bit faffy and possibly suboptimal. my $tag_scheme = []; my @ands = (); # Iterate over our commandline tag strings. foreach my $tag (@tags) { my @parts = (); foreach my $part (split(',', $tag)) { # Trim any @ or ~@ from the front of the tag $part =~ s/^(~?)@//; # ~@tag => "NOT tag" => [ not => tag ] if (defined $1 and $1 eq '~') { push @parts, [ not => $part ]; } else { push @parts, $part; } } # @tag,@cow => "@tag OR @cow" => [ or => tag, cow ] # (It's simpler to always stick an 'or' on the front.) push @ands, [ or => @parts ]; } # -t @tag -t @cow => "@tag AND @cow" => [ and => tag, cow ] # (It's simpler to always stick an 'and' on the front.) $tag_scheme = [ and => @ands ]; return $tag_scheme; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/0000775000175000017500000000000012246645110014302 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/Test/BDD/0000775000175000017500000000000012246645110014673 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/0000775000175000017500000000000012246645110016440 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Parser.pm0000644000175000017500000001667712246645110020251 0ustar bdrbdrpackage Test::BDD::Cucumber::Parser; { $Test::BDD::Cucumber::Parser::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Parser - Parse Feature files =head1 VERSION version 0.17 =head1 DESCRIPTION Parse Feature files in to a set of data classes =head1 SYNOPSIS # Returns a Test::BDD::Cucumber::Model::Feature object my $feature = Test::BDD::Cucumber::Parser->parse_file( 't/data/features/basic_parse.feature' ); =head1 METHODS =head2 parse_string =head2 parse_file Both methods accept a single string as their argument, and return a L object on success. =cut use strict; use warnings; use Ouch; use Data::Dumper; use File::Slurp; use Test::BDD::Cucumber::Model::Document; use Test::BDD::Cucumber::Model::Feature; use Test::BDD::Cucumber::Model::Scenario; use Test::BDD::Cucumber::Model::Step; use Test::BDD::Cucumber::Model::TagSpec; # https://github.com/cucumber/cucumber/wiki/Multiline-Step-Arguments # https://github.com/cucumber/cucumber/wiki/Scenario-outlines sub parse_string { my ( $self, $string ) = @_; return $self->_construct( Test::BDD::Cucumber::Model::Document->new({ content => $string }) ); } sub parse_file { my ( $self, $string, $tag_scheme ) = @_; return $self->_construct( Test::BDD::Cucumber::Model::Document->new({ content => scalar( read_file $string ), filename => $string }), $tag_scheme ); } sub _construct { my ( $self, $document, $tag_scheme ) = @_; my $feature = Test::BDD::Cucumber::Model::Feature->new({ document => $document }); my @lines = $self->_remove_next_blanks( @{ $document->lines } ); $self->_extract_scenarios( $self->_extract_conditions_of_satisfaction( $self->_extract_feature_name( $feature, @lines ))); return $feature; } sub _remove_next_blanks { my ( $self, @lines ) = @_; while ($lines[0] && $lines[0]->is_blank) { shift( @lines ); } return @lines; } sub _extract_feature_name { my ( $self, $feature, @lines ) = @_; my @feature_tags = (); while ( my $line = shift( @lines ) ) { next if $line->is_comment; last if $line->is_blank; if ( $line->content =~ m/^Feature: (.+)/ ) { $feature->name( $1 ); $feature->name_line( $line ); $feature->tags( \@feature_tags ); last; # Feature-level tags } elsif ( $line->content =~ m/^\s*\@\w/ ) { my @tags = $line->content =~ m/\@([^\s]+)/g; push( @feature_tags, @tags ); } else { ouch 'parse_error', "Malformed feature line", $line; } } return $feature, $self->_remove_next_blanks( @lines ); } sub _extract_conditions_of_satisfaction { my ( $self, $feature, @lines ) = @_; while ( my $line = shift( @lines ) ) { next if $line->is_comment || $line->is_blank; if ( $line->content =~ m/^(Background:|Scenario:|@)/ ) { unshift( @lines, $line ); last; } else { push( @{ $feature->satisfaction }, $line ); } } return $feature, $self->_remove_next_blanks( @lines ); } sub _extract_scenarios { my ( $self, $feature, @lines ) = @_; my $scenarios = 0; my @scenario_tags; while ( my $line = shift( @lines ) ) { next if $line->is_comment || $line->is_blank; if ( $line->content =~ m/^(Background|Scenario)(?: Outline)?: ?(.+)?/ ) { my ( $type, $name ) = ( $1, $2 ); # Only one background section, and it must be the first if ( $scenarios++ && $type eq 'Background' ) { ouch 'parse_error', "Background not allowed after scenarios", $line; } # Create the scenario my $scenario = Test::BDD::Cucumber::Model::Scenario->new({ ( $name ? ( name => $name ) : () ), background => $type eq 'Background' ? 1 : 0, line => $line, tags => [@{$feature->tags}, @scenario_tags] }); @scenario_tags = (); # Attempt to populate it @lines = $self->_extract_steps( $feature, $scenario, @lines ); if ( $type eq 'Background' ) { $feature->background( $scenario ); } else { push( @{ $feature->scenarios }, $scenario ); } # Scenario-level tags } elsif ( $line->content =~ m/^\s*\@\w/ ) { my @tags = $line->content =~ m/\@([^\s]+)/g; push( @scenario_tags, @tags ); } else { ouch 'parse_error', "Malformed scenario line", $line; } } return $feature, $self->_remove_next_blanks( @lines ); } sub _extract_steps { my ( $self, $feature, $scenario, @lines ) = @_; my $last_verb = 'Given'; while ( my $line = shift( @lines ) ) { next if $line->is_comment; last if $line->is_blank; # Conventional step? if ( $line->content =~ m/^(Given|And|When|Then|But) (.+)/ ) { my ( $verb, $text ) = ( $1, $2 ); my $original_verb = $verb; $verb = $last_verb if lc($verb) eq 'and' or lc($verb) eq 'but'; $last_verb = $verb; my $step = Test::BDD::Cucumber::Model::Step->new({ text => $text, verb => $verb, line => $line, verb_original => $original_verb, }); @lines = $self->_extract_step_data( $feature, $scenario, $step, @lines ); push( @{ $scenario->steps }, $step ); # Outline data block... } elsif ( $line->content =~ m/^Examples:$/ ) { return $self->_extract_table( 6, $scenario, $self->_remove_next_blanks( @lines )); } else { warn $line->content; ouch 'parse_error', "Malformed step line", $line; } } return $self->_remove_next_blanks( @lines ); } sub _extract_step_data { my ( $self, $feature, $scenario, $step, @lines ) = @_; return unless @lines; if ( $lines[0]->content eq '"""' ) { return $self->_extract_multiline_string( $feature, $scenario, $step, @lines ); } elsif ( $lines[0]->content =~ m/^\s*\|/ ) { return $self->_extract_table( 6, $step, @lines ); } else { return @lines; } } sub _extract_multiline_string { my ( $self, $feature, $scenario, $step, @lines ) = @_; my $data = ''; my $start = shift( @lines ); my $indent = $start->indent; # Check we still have the minimum indentation while ( my $line = shift( @lines ) ) { if ( $line->content eq '"""' ) { $step->data( $data ); return $self->_remove_next_blanks( @lines ); } my $content = $line->content_remove_indentation( $indent ); # Unescape it $content =~ s/\\(.)/$1/g; push( @{ $step->data_as_strings }, $content ); $content .= "\n"; $data .= $content; } return; } sub _extract_table { my ( $self, $indent, $target, @lines ) = @_; my @columns; my $data = []; $target->data($data); while ( my $line = shift( @lines ) ) { next if $line->is_comment; return ($line, @lines) if index( $line->content, '|' ); my @rows = $self->_pipe_array( $line->content ); if ( $target->can('data_as_strings') ) { my $t_content = $line->content; $t_content =~ s/^\s+//; push( @{ $target->data_as_strings }, $t_content ); } if ( @columns ) { ouch 'parse_error', "Inconsistent number of rows in table", $line unless @rows == @columns; $target->columns( [ @columns ] ) if $target->can('columns'); my $i = 0; my %data_hash = map { $columns[$i++] => $_ } @rows; push( @$data, \%data_hash ); } else { @columns = @rows; } } return; } sub _pipe_array { my ( $self, $string ) = @_; my @atoms = split(/\|/, $string); shift( @atoms ); return map { $_ =~ s/^\s+//; $_ =~ s/\s+$//; $_ } @atoms; } 1; =head1 ERROR HANDLING L uses L for exception handling. Error originating in this class tend to have a code of C and a L object for data. =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Harness.pm0000644000175000017500000000667012246645110020410 0ustar bdrbdrpackage Test::BDD::Cucumber::Harness; { $Test::BDD::Cucumber::Harness::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Harness - Base class for creating harnesses =head1 VERSION version 0.17 =head1 DESCRIPTION Harnesses allow your feature files to be executed while telling the outside world about how the testing is going, and what's being tested. This is a base class for creating new harnesses. You can see L and L for examples, although if you need to interact with the results in a more exciting way, you'd be best off interacting with L. =head1 METHODS / EVENTS =cut use strict; use warnings; use Moose; has 'results' => ( is => 'ro', default => sub {[]}, isa => 'ArrayRef' ); =head2 feature =head2 feature_done Called at the start and end of feature execution respectively. Both methods accept a single argument of a L. =cut sub feature { my ( $self, $feature ) = @_; } sub feature_done { my ( $self, $feature ) = @_; } =head2 background =head2 background_done If you have a background section, then we execute it as a quasi-scenario step before each scenario. These hooks are fired before and after that, and passed in the L that represents the Background section, and a a dataset hash (although why would you use that?) =cut sub background { my ( $self, $scenario, $dataset ) = @_; } sub background_done { my ( $self, $scenario, $dataset ) = @_; } =head2 scenario =head2 scenario_done Called at the start and end of scenario execution respectively. Both methods accept a L module and a dataset hash. =cut sub scenario { my ( $self, $scenario, $dataset ) = @_; } sub scenario_done { my ( $self, $scenario, $dataset ) = @_; } =head2 step =head2 step_done Called at the start and end of step execution respectively. Both methods accept a L object. C also accepts a L object. =cut sub step { my ( $self, $context ) = @_; } sub step_done { my ($self, $context, $result) = @_; } =head2 startup =head2 shutdown Some tests will run one feature, some will run many. For this reason, you may have harnesses that have something they need to do on start (print an HTML header), that they shouldn't do at the start of every feature, or a close-down task (like running C), that again shouldn't happen on I feature close-out, just the last. Just C<$self> as the single argument for both. =cut sub startup { my $self = shift; } sub shutdown { my $self = shift; } =head2 add_result Called before C with the step's result. Expected to silently add the result in to a pool that facilitate the C method. No need to override this behaviour. =head2 result Returns a collective view on the passing status of all steps run so far, as a L object. Default implementation should be fine for all your needs. =cut sub add_result { my $self = shift; push( @{$self->results}, shift() ); } sub result { my $self = shift; return Test::BDD::Cucumber::Model::Result->from_children( @{$self->results} ); } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Util.pm0000644000175000017500000000217412246645110017715 0ustar bdrbdrpackage Test::BDD::Cucumber::Util; { $Test::BDD::Cucumber::Util::VERSION = '0.17'; } use strict; use warnings; =head1 NAME Test::BDD::Cucumber::Util - Some functions used throughout the code =head1 VERSION version 0.17 =head1 DESCRIPTION Some functions used throughout the code =head1 FUNCTIONS =head2 bs_quote =head2 bs_unquote C "makes safe" strings with backslashed characters in it, so other operations can be done on them. C goes the other way. $string = "foo \ "; $string = bs_quote( $string ); $string =~ s/<([^>]+)>/"$1"/g; $string = bs_unquote( $string ); $string eq 'foo "baz"'; =cut my $marker_start = ';;;TEST_BDD_TEMP_MARKER_OPEN;;;'; my $marker_end = ';;;TEST_BDD_TEMP_MARKER_END;;;'; sub bs_quote { my $string = shift; $string =~ s/\\(.)/${marker_start} . ord($1) . ${marker_end}/ge; return $string; } sub bs_unquote { my $string = shift; $string =~ s/$marker_start(\d+)$marker_end/chr($1)/ge; return $string; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Loader.pm0000644000175000017500000000315612246645110020207 0ustar bdrbdrpackage Test::BDD::Cucumber::Loader; { $Test::BDD::Cucumber::Loader::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Loader - Simplify loading of Step Definition and feature files =head1 VERSION version 0.17 =head1 DESCRIPTION Makes loading Step Definition files and Feature files a breeze... =head1 METHODS =head2 load Accepts a path, and returns a L object with the Step Definition files loaded, and a list of L objects. =cut use strict; use warnings; use Path::Class; use File::Find::Rule; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::StepFile(); sub load { my ( $class, $path, $tag_scheme ) = @_; my $executor = Test::BDD::Cucumber::Executor->new(); # Either load a feature or a directory... my ( $dir, $file ); if ( -f $path ) { $file = file( $path ); $dir = $file->dir; } else { $dir = dir( $path ); } # Load up the steps $executor->add_steps( Test::BDD::Cucumber::StepFile->load( $_ ) ) for File::Find::Rule ->file() ->name( '*_steps.pl' ) ->in( $dir ); # Grab the feature files my @features = map { my $file = $_; my $feature = Test::BDD::Cucumber::Parser->parse_file( $file, $tag_scheme ); } ( $file ? ($file.'') : File::Find::Rule ->file() ->name( '*.feature' ) ->in( $dir ) ); return ( $executor, @features ); } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/0000775000175000017500000000000012246645110017500 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/Step.pm0000644000175000017500000000302612246645110020750 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::Step; { $Test::BDD::Cucumber::Model::Step::VERSION = '0.17'; } use Moose; =head1 NAME Test::BDD::Cucumber::Model::Step - Model to represent a step in a scenario =head1 VERSION version 0.17 =head1 DESCRIPTION Model to represent a step in a scenario =head1 ATTRIBUTES =head2 text The text of the step, once Scenario Outlines have been applied =cut has 'text' => ( is => 'rw', isa => 'Str' ); =head2 verb =head2 verb_original The verb used for the step ('Given'/'When'/etc). C is the one that appeared in the physical file - this will sometimes be C. =cut has 'verb' => ( is => 'rw', isa => 'Str' ); has 'verb_original' => ( is => 'rw', isa => 'Str' ); =head2 line The corresponding L =cut has 'line' => ( is => 'rw', isa => 'Test::BDD::Cucumber::Model::Line' ); =head2 data Step-related data. Either a string in the case of C<"""> or an arrayref of hashrefs for a data table. =cut has 'data' => ( is => 'rw' ); =head2 data_as_strings An arrayref of strings containing the original step's data, for printing out by harnesses =cut has 'data_as_strings' => ( is => 'rw', default => sub {[]}, isa => 'ArrayRef[Str]' ); =head2 columns If data was in a table format, then the column names will be here in the order they appeared. =cut has 'columns' => ( is => 'rw', isa => 'ArrayRef[Str]' ); =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/Scenario.pm0000644000175000017500000000265312246645110021605 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::Scenario; { $Test::BDD::Cucumber::Model::Scenario::VERSION = '0.17'; } use Moose; =head1 NAME Test::BDD::Cucumber::Model::Scenario - Model to represent a scenario =head1 VERSION version 0.17 =head1 DESCRIPTION Model to represent a scenario =head1 ATTRIBUTES =head2 name The text after the C keyword =cut has 'name' => ( is => 'rw', isa => 'Str' ); =head2 steps The associated L objects =cut has 'steps' => ( is => 'rw', isa => 'ArrayRef[Test::BDD::Cucumber::Model::Step]', default => sub {[]} ); =head2 data Scenario-related data table, as an arrayref of hashrefs =cut has 'data' => ( is => 'rw', isa => 'ArrayRef[HashRef]', default => sub {[]} ); =head2 background Boolean flag to mark whether this was the background section =cut has 'background' => ( is => 'rw', isa => 'Bool', default => 0 ); =head2 line A L object corresponding to the line where the C keyword is. =cut has 'line' => ( is => 'rw', isa => 'Test::BDD::Cucumber::Model::Line' ); =head2 tags Tags that the scenario has been tagged with, and has inherited from its feature. =cut has 'tags' => ( is => 'rw', isa => 'ArrayRef[Str]', default => sub {[]} ); =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/Result.pm0000644000175000017500000000360312246645110021314 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::Result; { $Test::BDD::Cucumber::Model::Result::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Model::Result - Encapsulates a result state =head1 VERSION version 0.17 =head1 DESCRIPTION Encapsulation of result state - whether that's for a step, scenario, or feature =cut use strict; use warnings; use Moose; use Moose::Util::TypeConstraints; enum 'StepStatus', [qw( passing failing pending undefined )]; =head1 ATTRIBUTES =head2 result Enum of: C, C, C or C. C is used if there was any TODO output from a test, and C for a test that wasn't run, either due to no matching step, or because a previous step failed. =cut has 'result' => ( is => 'ro', isa => 'StepStatus', required => 1 ); =head2 output The underlying test-output that contributed to a result. =cut has 'output' => ( is => 'ro', isa => 'Str', required => 1 ); =head1 METHODS =head2 from_children Collates the Result objects you pass in, and returns one that encompasses all of them. As they may be varied, it runs through them in order of C, C, C and C - the first it finds is the overall result. The empty set passes. =cut sub from_children { my ( $class, @children ) = @_; # We'll be looking for the presence of just one of any of the # short-circuiting statuses, but we need to keep a sum of all the output. # Passing is the default state, so we cheat and say there was one of them. my %results = ( passing => 1 ); my $output; for my $child ( @children ) { # Save the status of that child $results{ $child->result }++; # Add its output $output .= $child->output . "\n" } $output .= "\n"; for my $status ( qw( failing undefined pending passing ) ) { if ( $results{$status} ) { return $class->new({ result => $status, output => $output }); } } } 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/Feature.pm0000644000175000017500000000337012246645110021432 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::Feature; { $Test::BDD::Cucumber::Model::Feature::VERSION = '0.17'; } use Moose; =head1 NAME Test::BDD::Cucumber::Model::Document - Model to represent a feature file, parsed =head1 VERSION version 0.17 =head1 DESCRIPTION Model to represent a feature file, parsed =head1 ATTRIBUTES =head2 name The text after the C keyword =cut has 'name' => ( is => 'rw', isa => 'Str' ); =head2 name_line A L object corresponding to the line the C keyword was found on =cut has 'name_line' => ( is => 'rw', isa => 'Test::BDD::Cucumber::Model::Line' ); =head2 satisfaction An arrayref of strings of the Conditions of Satisfaction =cut has 'satisfaction' => ( is => 'rw', isa => 'ArrayRef[Test::BDD::Cucumber::Model::Line]', default => sub {[]}); =head2 document The corresponding L object =cut has 'document' => ( is => 'rw', isa => 'Test::BDD::Cucumber::Model::Document' ); =head2 background The L object that was marked as the background section. =cut has 'background' => ( is => 'rw', isa => 'Test::BDD::Cucumber::Model::Scenario' ); =head2 scenarios An arrayref of the L objects that constitute the test. =cut has 'scenarios' => ( is => 'rw', isa => 'ArrayRef[Test::BDD::Cucumber::Model::Scenario]', default => sub {[]} ); =head2 tags Tags that the feature has been tagged with, and will pass on to its Scenarios. =cut has 'tags' => ( is => 'rw', isa => 'ArrayRef[Str]', default => sub {[]} ); =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/TagSpec.pm0000644000175000017500000000607312246645110021370 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::TagSpec; { $Test::BDD::Cucumber::Model::TagSpec::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Model::TagSpec - Encapsulates tag selectors =head1 VERSION version 0.17 =head1 DESCRIPTION Try and deal with the crazy-sauce tagging mechanism in a sane way. =cut use strict; use warnings; use Moose; use Clone qw/clone/; =head1 OVERVIEW Cucumber tags are all sortsa crazy. This appears to be a direct result of trying to shoe-horn the syntax in to something you can use on the command line. Because 'Cucumber' is the name of a gem, application, language, methodology etc etc etc look of disapproval. Here is some further reading on how it's meant to work: L. This is obviously a little insane. Here's how they work here, on a code level: You pass in a list of lists that look like Lisp expressions, with a function: C, C, or C. You can nest these to infinite complexity, but the parser is pretty inefficient, so don't do that. The C function accepts only one argument. I: @important AND @billing: C<[and => 'important', 'billing']> (@billing OR @WIP) AND @important: C<[ and => [ or => 'billing', 'wip' ], 'important' ]> Skipping both @todo and @wip tags: C<[ and => [ not => 'todo' ], [ not => 'wip' ] ]> =head1 ATTRIBUTES =head2 tags An arrayref representing a structure like the above. TagSet->new({ tags => [ and => 'green', 'blue', [ or => 'red', 'yellow' ], [ not => 'white' ] ] }) =cut has 'tags' => ( is => 'rw', isa => 'ArrayRef', default => sub {[]} ); =head1 METHODS =head2 filter Filter a list of Scenarios by the value of C my @matched = $tagset->filter( @scenarios ); If C is empty, no filtering is done. =cut sub filter { my ( $self, @scenarios ) = @_; my @tagset = @{ $self->tags }; return @scenarios unless @tagset; my $mode = shift @tagset; return grep { my @tags = @{ $_->tags }; my $scenario = { map { $_ => 1 } @tags }; _matches( $mode, $scenario, \@tagset ); } @scenarios; } # SCHEME ON THE BRAINZ sub _matches { my ( $mode, $scenario, $tags ) = @_; $tags = clone $tags; # If $tags is null, we have to do something... if ( @$tags == 0 ) { # True is the unit of conjunction ( $mode eq 'and' ) and return 1; # False is the unit of disjunction ( $mode eq 'or' ) and return 0; # We should never get here for anything else ( $mode eq 'not' ) and die "Doesn't make sense to ask for 'not' of empty list"; die "Don't recognize mode '$mode'"; } # Get the head and tail of $tags. We'll split off the head, and leave the # tail in $tags. my $head = shift @$tags; # Get a result from the next tag. Recurse if it's complex my $result = ref( $head ) ? _matches( shift( @$head ), $scenario, $head ) : $scenario->{ $head }; if ( $mode eq 'and' ) { $result ? return _matches( 'and', $scenario, $tags ) : return 0 } elsif ( $mode eq 'or' ) { $result ? return 1 : return _matches( 'or', $scenario, $tags ); } elsif ( $mode eq 'not' ) { return ! $result; } else { die "Don't recognize mode '$mode'"; } } 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/Document.pm0000644000175000017500000000261512246645110021616 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::Document; { $Test::BDD::Cucumber::Model::Document::VERSION = '0.17'; } use Moose; use Test::BDD::Cucumber::Model::Line; =head1 NAME Test::BDD::Cucumber::Model::Document - Model to represent a feature file on disk or in memory =head1 VERSION version 0.17 =head1 DESCRIPTION Model to represent a feature file on disk or in memory =head1 ATTRIBUTES =head2 filename The filename from which the document was loaded. =cut has 'filename' => ( is => 'ro', isa => 'Str' ); =head2 content The file contents, as a string =cut has 'content' => ( is => 'ro', isa => 'Str' ); =head2 lines The file contents, as an arrayref of L objects =cut has 'lines' => ( is => 'rw', default => sub {[]}, isa => 'ArrayRef[Test::BDD::Cucumber::Model::Line]' ); =head1 OTHER =head2 BUILD The instantiation populates C by splitting the input on newlines. =cut # Create lines sub BUILD { my $self = shift; # Reset any content that was in lines my $counter = 0; for my $line ( split(/\n/, $self->content ) ) { my $obj = Test::BDD::Cucumber::Model::Line->new({ number => ++$counter, document => $self, raw_content => $line }); push( @{ $self->lines }, $obj ); } }; =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Model/Line.pm0000644000175000017500000000441112246645110020723 0ustar bdrbdrpackage Test::BDD::Cucumber::Model::Line; { $Test::BDD::Cucumber::Model::Line::VERSION = '0.17'; } use Moose; =head1 NAME Test::BDD::Cucumber::Model::Line - Model to represent a line in a feature file =head1 VERSION version 0.17 =head1 DESCRIPTION Model to represent a line in a feature file =head1 ATTRIBUTES =head2 number The line number this line represents =cut has 'number' => ( is => 'rw', isa => 'Int' ); =head2 document The L object this line belongs to. =cut has 'document' => ( is => 'rw', isa => 'Test::BDD::Cucumber::Model::Document' ); =head2 raw_content The content of the line, unmodified =cut has 'raw_content' => ( is => 'rw', isa => 'Str' ); =head1 METHODS =head2 indent Returns the number of preceding spaces before content on a line =cut sub indent { my $self = shift; my ($indent) = $self->raw_content =~ m/^( +)/g; return length( $indent || '' ); } =head2 content Returns the line's content, with the indentation stripped =cut sub content { return _strip( $_[0]->raw_content ) } =head2 content_remove_indentation Accepts an int of number of spaces, and returns the content with exactly that many preceding spaces removed. =cut sub content_remove_indentation { my ( $self, $indent ) = @_; $indent = ' ' x $indent; my $content = $self->raw_content; $content =~ s/^$indent//; return $content; } =head2 debug_summary Returns a string with the filename and line number =cut sub debug_summary { my $self = shift; my $filename = $self->filename; return "Input: $filename line " . $self->number . ": [" . $self->raw_content . "]"; } =head2 filename Returns either the filename, or the string C<[String]> if the document was loaded from a string =cut sub filename { my $self = shift; $self->document->filename || '[String]'; } =head2 is_blank =head2 is_comment Return true if the line is either blank, or is a comment. =cut sub is_blank { return ! ( $_[0]->content =~ m/\S/ ) } sub is_comment { return scalar $_[0]->content =~ m/^\s*#/ } sub _strip { my $string = shift; $string =~ s/^\s+//; $string =~ s/\s+$//; return $string; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Executor.pm0000644000175000017500000003260712246645110020602 0ustar bdrbdrpackage Test::BDD::Cucumber::Executor; { $Test::BDD::Cucumber::Executor::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Executor - Run through Feature and Harness objects =head1 VERSION version 0.17 =head1 DESCRIPTION The Executor runs through Features, matching up the Step Lines with Step Definitions, and reporting on progress through the passed-in harness. =cut use Moose; use FindBin::libs; use Storable qw(dclone); use List::Util qw/first/; use Test::Builder; use Test::BDD::Cucumber::StepContext; use Test::BDD::Cucumber::Util; use Test::BDD::Cucumber::Model::Result; =head1 METHODS =head2 steps =head2 add_steps The attributes C is a hashref of arrayrefs, storing steps by their Verb. C takes step definitions of the item list form: ( [ Given => qr//, sub {} ], ), and populates C with them. =cut has 'steps' => ( is => 'rw', isa => 'HashRef', default => sub {{}} ); sub add_steps { my ( $self, @steps ) = @_; # Map the steps to be lower case... for ( @steps ) { my ( $verb, $match, $code ) = @$_; $verb = lc $verb; if ( $verb =~ /^(before|after)$/ ) { $code = $match; $match = qr//; } else { unless (ref( $match )) { $match =~ s/:\s*$//; $match = quotemeta( $match ); $match = qr/^$match:?/i; } } if ( $verb eq 'transform' or $verb eq 'after' ) { # Most recently defined Transform takes precedence # and After blocks need to be run in reverse order unshift( @{ $self->{'steps'}->{$verb} }, [ $match, $code ] ); } else { push( @{ $self->{'steps'}->{$verb} }, [ $match, $code ] ); } } } =head2 execute Execute accepts a feature object, a harness object, and an optional L object and for each scenario in the feature which meets the tag requirements (or all of them, if you haven't specified one), runs C. =cut sub execute { my ( $self, $feature, $harness, $tag_spec ) = @_; my $feature_stash = {}; $harness->feature( $feature ); my @background = ( $feature->background ? ( background => $feature->background ) : () ); # Get all scenarios my @scenarios = @{ $feature->scenarios() }; # Filter them by the tag spec, if we have one if ( defined $tag_spec ) { @scenarios = $tag_spec->filter( @scenarios ); } for my $scenario ( @scenarios ) { # Execute the scenario itself $self->execute_scenario({ @background, scenario => $scenario, feature => $feature, feature_stash => $feature_stash, harness => $harness }); } $harness->feature_done( $feature ); } =head2 execute_scenario Accepts a hashref of options, and executes each step in a scenario. Options: C - A L object C - A hashref that should live the lifetime of feature execution C - A L subclass object C - A L object C - An optional L object representing the Background C - We'll create a new scenario stash unless you've posted one in. This is used exclusively for giving Background sections access to the same stash as the scenario they're running before. For each step, a L object is created, and passed to C. Nothing is returned - everything is played back through the Harness interface. =cut sub execute_scenario { my ( $self, $options ) = @_; my ( $feature, $feature_stash, $harness, $outline, $background_obj, $incoming_scenario_stash, $incoming_outline_stash ) = @$options{ qw/ feature feature_stash harness scenario background scenario_stash outline_stash / }; my $is_background = $outline->background; my $harness_start = $is_background ? 'background' : 'scenario'; my $harness_stop = $is_background ? 'background_done' : 'scenario_done'; my $outline_stash = $incoming_outline_stash || { short_circuit => 0 }; # Multiply out Scenario Outlines as appropriate my @datasets = @{ $outline->data }; @datasets = ({}) unless @datasets; my $scenario_stash = $incoming_scenario_stash || {}; my %context_defaults = ( # Data portion data => '', stash => { feature => $feature_stash, step => {}, }, # Step-specific info feature => $feature, scenario => $outline, # Communicators harness => $harness, transformers => $self->{'steps'}->{'transform'} || [], ); foreach my $dataset ( @datasets ) { my $scenario_stash = $incoming_scenario_stash || {}; $context_defaults{stash}->{scenario} = $scenario_stash; # OK, back to the normal execution $harness->$harness_start( $outline, $dataset, $scenario_stash->{'longest_step_line'} ); if ( not $is_background ) { for my $before_step ( @{ $self->{'steps'}->{'before'} || [] } ) { # Set up a context my $context = Test::BDD::Cucumber::StepContext->new({ %context_defaults, verb => 'before', }); my $result = $self->dispatch( $context, $before_step, $outline_stash->{'short_circuit'} ); # If it didn't pass, short-circuit the rest unless ( $result->result eq 'passing' ) { $outline_stash->{'short_circuit'}++; } } } # Run the background if we have one. This recurses back in to # execute_scenario... if ( $background_obj ) { $self->execute_scenario({ is_background => 1, scenario => $background_obj, feature => $feature, feature_stash => $feature_stash, harness => $harness, scenario_stash => $scenario_stash, outline_stash => $outline_stash }); } foreach my $step (@{ $outline->steps }) { # Multiply out any placeholders my $text = $self->add_placeholders( $step->text, $dataset ); # Set up a context my $context = Test::BDD::Cucumber::StepContext->new({ %context_defaults, # Data portion columns => $step->columns || [], data => ref($step->data) ? dclone($step->data) : $step->data || '', # Step-specific info step => $step, verb => lc($step->verb), text => $text, }); my $result = $self->find_and_dispatch( $context, $outline_stash->{'short_circuit'} ); # If it didn't pass, short-circuit the rest unless ( $result->result eq 'passing' ) { $outline_stash->{'short_circuit'}++; } } if ( not $is_background ) { for my $after_step ( @{ $self->{'steps'}->{'after'} || [] } ) { # Set up a context my $context = Test::BDD::Cucumber::StepContext->new({ %context_defaults, verb => 'after', }); # All After steps should happen, to ensure cleanup my $result = $self->dispatch( $context, $after_step, 0 ); } } $harness->$harness_stop( $outline, $dataset ); } return; } =head2 add_placeholders Accepts a text string and a hashref, and replaces C< > with the values in the hashref, returning a string. =cut sub add_placeholders { my ( $self, $text, $dataset ) = @_; my $quoted_text = Test::BDD::Cucumber::Util::bs_quote( $text ); $quoted_text =~ s/(<([^>]+)>)/ exists $dataset->{$2} ? $dataset->{$2} : die "No mapping to placeholder $1 in: $text" /eg; return Test::BDD::Cucumber::Util::bs_unquote( $quoted_text ); } =head2 find_and_dispatch Accepts a L object, and searches through the steps that have been added to the executor object, executing against the first matching one. You can also pass in a boolean 'short-circuit' flag if the Scenario's remaining steps should be skipped. =cut sub find_and_dispatch { my ( $self, $context, $short_circuit ) = @_; # Short-circuit if we need to return $self->skip_step($context, 'pending', "Short-circuited from previous tests") if $short_circuit; # Try and find a matching step my $step = first { $context->text =~ $_->[0] } @{ $self->{'steps'}->{$context->verb} || [] }, @{ $self->{'steps'}->{'step'} || [] }; # Deal with the simple case of no-match first of all return $self->skip_step( $context, 'undefined', "No matching step definition for: " . $context->verb . ' ' . $context->text ) unless $step; return $self->dispatch( $context, $step, 0 ); } =head2 dispatch Accepts a L object, and a L object and executes it. You can also pass in a boolean 'short-circuit' flag if the Scenario's remaining steps should be skipped. =cut sub dispatch { my ( $self, $context, $step, $short_circuit ) = @_; return $self->skip_step($context, 'pending', "Short-circuited from previous tests") if $short_circuit; # Execute the step definition my ( $regular_expression, $coderef ) = @$step; # Setup what we'll pass to step_done, with out localized Test::Builder stuff my $output = ''; my $tb_return = { output => \$output, builder => Test::Builder->create() }; # Set its outputs to be self-referential $tb_return->{'builder'}->output( \$output ); $tb_return->{'builder'}->failure_output( \$output ); $tb_return->{'builder'}->todo_output( \$output ); # Make a minumum pass $tb_return->{'builder'}->ok(1, "Starting to execute step: " . $context->text ); # Say we're about to start it up $context->harness->step( $context ); # New scope for the localization my $result; { # Localize test builder local $Test::Builder::Test = $tb_return->{'builder'}; # Guarantee the $ :-/ $context->matches([ $context->text =~ $regular_expression ]); # Execute! eval { $coderef->( $context ) }; if ( $@ ) { $Test::Builder::Test->ok( 0, "Test compiled" ); $Test::Builder::Test->diag( $@ ); } # Close up the Test::Builder object $tb_return->{'builder'}->done_testing(); my $status = $self->_test_status($tb_return->{builder}); # Create the result object $result = Test::BDD::Cucumber::Model::Result->new({ result => $status, output => $output }); } # Say the step is done, and return the result. Happens outside # the above block so that we don't have the localized harness # anymore... $context->harness->add_result( $result ); $context->harness->step_done( $context, $result ); return $result; } sub _test_status { my $self = shift; my $builder = shift; my $results = $builder->can("history") ? $self->_test_status_from_history($builder) : $self->_test_status_from_details($builder); # Turn that in to a Result status return $results->{'fail'} ? 'failing' : $results->{'todo'} ? 'pending' : 'passing' ; } sub _test_status_from_details { my $self = shift; my $builder = shift; # Make a note of test status my %results = map { if ( $_->{'ok'} ) { if ( $_->{'type'} eq 'todo' || $_->{'type'} eq 'todo_skip' ) { ( todo => 1) } else { ( pass => 1 ) } } else { ( fail => 1 ) } } $builder->details; return \%results; } sub _test_status_from_history { my $self = shift; my $builder = shift; my $history = $builder->history; my %results; $results{todo} = $history->todo_count ? 1 : 0; $results{fail} = !$history->test_was_successful; $results{pass} = $history->pass_count ? 1 : 0; return \%results; } =head2 skip_step Accepts a step-context, a result-type, and a textual reason, exercises the Harness's step start and step_done methods, and returns a skipped-test result. =cut sub skip_step { my ( $self, $context, $type, $reason ) = @_; # Pretend to start step execution $context->harness->step( $context ); # Create a result object my $result = Test::BDD::Cucumber::Model::Result->new({ result => $type, output => '1..0 # SKIP ' . $reason }); # Pretend we executed it $context->harness->add_result( $result ); $context->harness->step_done( $context, $result ); return $result; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/StepContext.pm0000644000175000017500000001446512246645110021266 0ustar bdrbdrpackage Test::BDD::Cucumber::StepContext; { $Test::BDD::Cucumber::StepContext::VERSION = '0.17'; } use Moose; use List::Util qw( first ); =head1 NAME Test::BDD::Cucumber::StepContext - Data made available to step definitions =head1 VERSION version 0.17 =head1 DESCRIPTION The coderefs in Step Definitions have a single argument passed to them, a C object. This is an attribute-only class, populated by L. =head1 ATTRIBUTES =head2 columns If the step-specific data supplied is a table, the this attribute will contain the column names in the order they appeared. =cut has 'columns' => ( is => 'ro', isa => 'ArrayRef' ); =head2 _data Step-specific data. Will either be a text string in the case of a """ string, or an arrayref of hashrefs if the step had an associated table. See the C method below. =cut has '_data' => ( is => 'ro', isa => 'Str|ArrayRef', init_arg => 'data', default => '' ); =head2 stash A hash of hashes, containing three keys, C, C and C. The stash allows you to persist data across features, scenarios, or steps (although the latter is there for completeness, rather than having any useful function). =cut has 'stash' => ( is => 'ro', required => 1, isa => 'HashRef' ); =head2 feature =head2 scenario =head2 step Links to the L, L, and L objects respectively. =cut has 'feature' => ( is => 'ro', required => 1, isa => 'Test::BDD::Cucumber::Model::Feature' ); has 'scenario' => ( is => 'ro', required => 1, isa => 'Test::BDD::Cucumber::Model::Scenario' ); has 'step' => ( is => 'ro', required => 0, isa => 'Test::BDD::Cucumber::Model::Step' ); =head2 verb The lower-cased verb a Step Definition was called with. =cut has 'verb' => ( is => 'ro', required => 1, isa => 'Str' ); =head2 text The text of the step, minus the verb. Placeholders will have already been multiplied out at this point. =cut has 'text' => ( is => 'ro', required => 1, isa => 'Str', default => '' ); =head2 harness The L harness being used by the executor. =cut has 'harness' => ( is => 'ro', required => 1, isa => 'Test::BDD::Cucumber::Harness' ); =head2 matches Any matches caught by the Step Definition's regex. These are also available as C<$1>, C<$2> etc as appropriate. =cut has '_matches' => ( is => 'rw', isa => 'ArrayRef', init_arg => 'matches', default => sub { [] } ); has 'transformers' => ( is => 'ro', isa => 'ArrayRef', predicate => 'has_transformers', ); has '_transformed_matches' => ( is => 'ro', isa => 'ArrayRef', lazy => 1, builder => '_build_transformed_matches', clearer => '_clear_transformed_matches', ); has '_transformed_data' => ( is => 'ro', isa => 'Str|ArrayRef', lazy => 1, builder => '_build_transformed_data', clearer => '_clear_transformed_data', ); =head2 is_hook The harness processing the output can decide whether to shop information for this step which is actually an internal hook, i.e. a Before or After step =cut has 'is_hook' => ( is => 'ro', isa => 'Bool', lazy => 1, builder => '_build_is_hook' ); =head1 METHODS =head2 background Boolean for "is this step being run as part of the background section?". Currently implemented by asking the linked Scenario object... =cut sub background { my $self = shift; return $self->scenario->background } =head2 data See the C<_data> attribute above. Calling this method will return either the """ string, or a possibly Transform-ed set of table data. =cut sub data { my $self = shift; if ( @_ ) { $self->_data( @_ ); $self->_clear_transformed_data; return; } return $self->_transformed_data; } =head2 matches See the C<_matches> attribute above. Call this method will return the possibly Transform-ed matches . =cut sub matches { my $self = shift; if ( @_ ) { $self->_matches( @_ ); $self->_clear_transformed_matches; return; } return $self->_transformed_matches; } =head2 transform Used internally to transform data and placeholders, but it can also be called from within your Given/When/Then code. =cut sub transform { my $self = shift; my $value = shift; defined $value or return $value; TRANSFORM: for my $transformer ( @{ $self->transformers } ) { # uses the same magic as other steps # and puts any matches into $1, $2, etc. # and calls the Transform step if ( $value =~ s/$transformer->[0]/$transformer->[1]->( $self )/e ) { # if we matched then stop processing this match return $value; } } # if we're here, the value will be returned unchanged return $value; } # the builder for the is_hook attribute sub _build_is_hook { my $self = shift; return ( $self->verb eq 'before' or $self->verb eq 'after' ) ? 1 : 0; } # the builder for the _transformed_matches attribute sub _build_transformed_matches { my $self = shift; my @transformed_matches = @{ $self->_matches }; # this stops it recursing forever... # and only Transform if there are any to process if ( $self->verb ne 'transform' and $self->has_transformers ) { @transformed_matches = map { $_ = $self->transform( $_ ) } @transformed_matches; } return \@transformed_matches; } # the builder for the _transformed_data attribute sub _build_transformed_data { my $self = shift; my $transformed_data = $self->_data; # again stop recursing # only transform table data # and only Transform if there are any to process if ( $self->verb ne 'transform' and ref $transformed_data and $self->has_transformers ) { # build the string that a Transform is looking for # table:column1,column2,column3 my $table_text = 'table:' . join( ',', @{ $self->columns } ); if ( my $transformer = first { $table_text =~ $_->[0] } @{ $self->transformers } ) { # call the Transform step $transformer->[1]->( $self, $transformed_data ); } } return $transformed_data; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Manual/0000775000175000017500000000000012246645110017655 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Manual/Architecture.pod0000644000175000017500000000272512246645110023007 0ustar bdrbdrpackage Test::BDD::Cucumber::Manual::Architecture; =head1 NAME Test::BDD::Cucumber::Manual::Architecture - Structural Overview =head1 VERSION version 0.17 =head1 INTRODUCTION This short document exists to give you an idea how the different components of this distribution fit together. =head1 MODELS The core of a Cucumber-based test suite are the feature files and the step definitions files. By convention, these are saved under C and C respectively. The feature files are encapsulated by the classes in C. one to one TBCM::Feature<----------------->TBCM::Document | | +-------------------+ | | has many | has a | has many V | V TBCM::Scenario +----->TBCM::Line | ^ ^ +----------------------------+ | | has many | V | TBCM::Step---------------------------+ =head1 EXECUTOR We build up a L object, in to which we load the step definitions. We then pass this in a L object, along with a L object, which controls interaction with the outside world. =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Manual/Tutorial.pod0000644000175000017500000002435212246645110022170 0ustar bdrbdrpackage Test::BDD::Cucumber::Manual::Tutorial =head1 NAME Test::BDD::Cucumber::Manual::Tutorial - Quick Start Guide =head1 VERSION version 0.17 =head1 Introduction In this article we're going to jump straight in to using L to build some simple tests for L, a core Perl module which provides message digests. We'll create a C directory, and put our first test case in it, C in it. The contents of it are, in their entirity: # Somehow I don't see this replacing the other tests this module has... Feature: Simple tests of Digest.pm As a developer planning to use Digest.pm I want to test the basic functionality of Digest.pm In order to have confidence in it Background: Given a usable Digest class Scenario: Check MD5 Given a Digest MD5 object When I've added "foo bar baz" to the object And I've added "bat ban shan" to the object Then the hex output is "bcb56b3dd4674d5d7459c95e4c8a41d5" Then the base64 output is "1B2M2Y8AsgTpgAmY7PhCfg" Scenario: Check SHA-1 Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" Examples: | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | Scenario: MD5 longer data Given a Digest MD5 object When I've added the following to the object """ Here is a chunk of text that works a bit like a HereDoc. We'll split off indenting space from the lines in it up to the indentation of the first \"\"\" """ Then the hex output is "75ad9f578e43b863590fae52d5d19ce6" This is a complete test, and if you run L against it, you will get sane output! It just doesn't do anything ... yet. In the C we'll add a C directory, and add our first (and again, only) step definitions C file in it: #!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Method::Signatures; Given qr/a usable (\S+) class/, func ($c) { use_ok( $1 ); }; Given qr/a Digest (\S+) object/, func ($c) { my $object = Digest->new($1); ok( $object, "Object created" ); $c->stash->{'scenario'}->{'object'} = $object; }; When qr/I've added "(.+)" to the object/, func ($c) { $c->stash->{'scenario'}->{'object'}->add( $1 ); }; When "I've added the following to the object", func ($c) { $c->stash->{'scenario'}->{'object'}->add( $c->data ); }; Then qr/the (.+) output is "(.+)"/, func ($c) { my $method = {base64 => 'b64digest', 'hex' => 'hexdigest' }->{ $1 } || do { fail("Unknown output type $1"); return }; is( $c->stash->{'scenario'}->{'object'}->$method, $2 ); }; When you run L or the Test::Builder-based test which does the same thing (L), we look for a C directory, and search for step definitions files (matched by C<*_steps.pl>) and feature files (matched by C<*.feature>). The step matchers (the code that starts with C, C and C) are all loaded, and then we execture the feature files one by one. Let's step through the feature file, and look at how it matches up to the step definitions file. =head1 Name and conditions of satisfaction Feature: Simple tests of Digest.pm As a developer planning to use Digest.pm I want to test the basic functionality of Digest.pm In order to have confidence in it The first non-comment line of your feature file is a description of what you intend to do. You need to start the name itself with the string C, and that should be the first line of your file, save comments (denoted by #). Anything after that before the next new-line are your conditions of satisfaction. These aren't parsed, they're treated as human-readable text, and by convention, they're a L. =head1 Background Background: Given a usable Digest class Next up, we have the Background section. The Background is a special kind of Scenario that doesn't have an explicit name, and should occur only once in your feature file. Its steps are run before the steps of every other scenario - the harnesses distributed with this distro won't display the Background section separately, they'll just subsume the steps in to the other scenarios. This is matched by: Given qr/a usable (\S+) class/, func ($c) { use_ok( $1 ); }; C is a function exported by L that accepts two arguments: a regular expression (or a string when you don't need to do any smart matching) and a coderef. We use L to save some typing here, so note that written longer hand, the above might be written: Given( qr/a usable (\S+) class/, sub { my $c = shift; use_ok( $1 ); }; ); If you're paying attention, you might notice that C comes from L. B L B. This happens seamlessly, so you can use any L-based testing tools in your step definitions without really worrying about it. There's some more detail in L. =head1 The First Scenario... Scenario: Check MD5 Given a Digest MD5 object When I've added "foo bar baz" to the object And I've added "bat ban shan" to the object Then the hex output is "bcb56b3dd4674d5d7459c95e4c8a41d5" Then the base64 output is "1B2M2Y8AsgTpgAmY7PhCfg" The first scenario is delimited from the previous steps by a blank line, and it's called I. Scenarios are marked out using the C keyword, and just like the Background section before, it's a series of steps. These steps rely on the step before, which means we can examine the L object C<$c> a little more closely. Given qr/a Digest (\S+) object/, func ($c) { my $object = Digest->new($1); ok( $object, "Object created" ); $c->stash->{'scenario'}->{'object'} = $object; }; Creates a step definition. We create a new L object, and then use L's C function to check that worked. We then put it in the I for other steps to use. There are three stashes documented in L, C, C and C. As you might expect, C is available to all step definitions that are being executed as part of a feature, and C is available to all steps that are being executed as part of a feature. The context is the single argument that gets passed to each step, and it contains evertything that step should need to execute. We'll be looking at some of the methods you can call against it as we look at other steps, and you can find complete documentation for it here: L. This scenario also introduce several ways of starting a step, I, I, and I, as well as I. These are used to organize steps by type, with I tending to describe setup steps, I describing the key actions that you're testing, and I describing the outputs you're looking for. You can find more on this here: L. A step definition you've declared with I B match a step starting with B. You can use the keyword I to declare general matching steps in your step definition files, although it's considered bad practice. Finally, the keywords I and I are simply replaced with the verb on the line before them. =head1 Scenario Outlines Scenario: Check SHA-1 Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" Examples: | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | The next scenario adds one of the three ways you can provide structured data to your steps, using placeholders and a table. This scenario is run three times, one for each table row, and with the C< > being replaced by the appropriate row's column. These are called L. =head1 Multiline Step Arguments Scenario: MD5 longer data Given a Digest MD5 object When I've added the following to the object """ Here is a chunk of text that works a bit like a HereDoc. We'll split off indenting space from the lines in it up to the indentation of the first \"\"\" """ Then the hex output is "75ad9f578e43b863590fae52d5d19ce6" While before we were looking at structured data on a Scenario level, we can also provide it on a Step level, in two ways. Firstly, we can provide multi-line strings, as above, using a feature that is syntactically similar to Cs, and conceptually similar to HEREDOCs. The contents of the string will be available to the step definition via the C method of the I: When "I've added the following to the object", func ($c) { $c->stash->{'scenario'}->{'object'}->add( $c->data ); }; While we don't have an example of it here, you can also provide tables to your steps, which will also be available via C: Scenario: Sort Employees Given a set of employees | name | wage | hair color | | Peter | 10,000 | brown | | John | 20,000 | blond | | Joan | 30,000 | green | You can find out more about these features in the Cucumber documentation here: L. =head1 Next Steps... That's the tutorial done! You can find out more about writing steps in L, the documentation for our simple command-line tool in L, and how to integrate with L in L. =cut =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Manual/Integration.pod0000644000175000017500000000247012246645110022645 0ustar bdrbdrpackage Test::BDD::Cucumber::Manual::Integration; =head1 NAME Test::BDD::Cucumber::Manual::Integration - Integrating with Test::Builder =head1 VERSION version 0.17 =head1 DESCRIPTION How to use Test::BDD::Cucumber in your test suite =head1 OVERVIEW You may well want your Cucumber tests to be executed as part of your standard test-suite. Luckily, this is SUPER easy. =head1 WELL-COMMENTED EXAMPLE #!perl use strict; use warnings; use FindBin::libs; # This will find step definitions and feature files in the directory you point # it at below use Test::BDD::Cucumber::Loader; # This harness prints out nice TAP use Test::BDD::Cucumber::Harness::TestBuilder; # Load a directory with Cucumber files in it. It will recursively execute any # file matching .*_steps.pl as a Step file, and .*\.feature as a feature file. # The features are returned in @features, and the executor is created with the # step definitions loaded. my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load( 't/cucumber_core_features/' ); # Create a Harness to execute against. TestBuilder harness prints TAP my $harness = Test::BDD::Cucumber::Harness::TestBuilder->new({}); # For each feature found, execute it, using the Harness to print results $executor->execute( $_, $harness ) for @features; done_testing; =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Manual/Steps.pod0000644000175000017500000000522112246645110021455 0ustar bdrbdrpackage Test::BDD::Cucumber::Manual::Steps; =head1 NAME Test::BDD::Cucumber::Manual::Steps - How to write Step Definitions =head1 VERSION version 0.17 =head1 INTRODUCTION The 'code' part of a Cucumber test-suite are the Step Definition files which match steps, and execute code based on them. This document aims to give you a quick overview of those. =head1 STARTING OFF Most of your step files will want to start something like: #!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Method::Signatures; The fake shebang line gives some hints to syntax highlighters, and C and C are hopefully fairly standard at this point. Most of I Step Definition files make use of L, but you can use any L based testing module. Your step will pass its pass or fail status back to its harness via L - B, with its own localized L object. L gives us the functions C, C, C and C. These pass the step definitions to the class loading the step definitions, and specify which Step Verb should be used - C matches any. L allows us to use a small amount of syntactic sugar for the step definitions, and gives us the C keyword you'll see in a minute. =head1 STEP DEFINITIONS Given qr/I have (\d+)/, func ($c) { $c->stash->{'scenario'}->{'count'} += $1; } When "The count is an integer", func ($c) { $c->stash->{'scenario'}->{'count'} = int( $c->stash->{'scenario'}->{'count'} ); } Then qr/The count should be (\d+)/, func ($c) { is( $c->stash->{'scenario'}->{'count'}, $c->matches->[0], "Count matches" ); } Each of the exported verb functions accept a regular expression (or a string that's used as one), and a coderef. The coderef is passed a single argument, the L object. To make this a little prettier, we use L's C keyword so we're not continually typing: C. We will evaluate the regex immediately before we execute the coderef, so you can use $1, $2, $etc, although these are also available via the StepContext. =head1 NEXT STEPS How step files are loaded is discussed in L, but isn't of much interest. Of far more interest should be seeing what you have available in L... =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/StepFile.pm0000755000175000017500000000500712246645110020514 0ustar bdrbdrpackage Test::BDD::Cucumber::StepFile; { $Test::BDD::Cucumber::StepFile::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::StepFile - Functions for creating and loading Step Definitions =head1 VERSION version 0.17 =cut use strict; use warnings; use File::Find; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(Given When Then Step Transform Before After); our @definitions; =head1 DESCRIPTION Provides the Given/When/Then functions, and a method for loading Step Definition files and returning the steps. =head1 SYNOPSIS Defining steps: #!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Method::Signatures; # Allows short-hand func method Given 'something', func ($c) { print "YEAH!" } When qr/smooooth (\d+)/, func ($c) { print "YEEEHAH $1" } Then qr/something (else)/, func ($c) { print "Meh $1" } Step qr/die now/, func ($c) { die "now" } Transform qr/^(\d+)$/, func ($c) { int $1 } Before func ($c) { setup_db() } After func ($c) { teardown() } Loading steps, in a different file: use Test::BDD::Cucumber::StepFile; my @steps = Test::BDD::Cucumber::StepFile->load('filename_steps.pl'); =head1 EXPORTED FUNCTIONS =head2 Given =head2 When =head2 Then =head2 Step =head2 Transform =head2 Before =head2 After Accept a regular expression or string, and a coderef. Some cute tricks ensure that when you call the C method on a file with these statements in, these are returned to it... =cut sub Given { push( @definitions, [ Given => @_ ] ) } sub When { push( @definitions, [ When => @_ ] ) } sub Then { push( @definitions, [ Then => @_ ] ) } sub Step { push( @definitions, [ Step => @_ ] ) } sub Transform { push( @definitions, [ Transform => @_ ] ) } sub Before { push( @definitions, [ Before => @_ ] ) } sub After { push( @definitions, [ After => @_ ] ) } =head2 load Loads a file containing step definitions, and returns a list of the steps defined in it, of the form: ( [ 'Given', qr/abc/, sub { etc } ], [ 'Step', 'asdf', sub { etc } ] ) =cut sub load { my ( $class, $filename ) = @_; { local @definitions; do $filename; die "Step file [$filename] failed to load: $@" if $@; return @definitions; } } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Harness/0000775000175000017500000000000012246645110020043 5ustar bdrbdrTest-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Harness/TermColor.pm0000644000175000017500000001216212246645110022307 0ustar bdrbdrpackage Test::BDD::Cucumber::Harness::TermColor; { $Test::BDD::Cucumber::Harness::TermColor::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Harness::TermColor - Prints colorized text to the screen =head1 VERSION version 0.17 =head1 DESCRIPTION A L subclass that prints test output, colorized, to the terminal. =head1 METHODS =cut use strict; use warnings; use Moose; # Try and make the colors just work on Windows... BEGIN { if ( # We're apparently on Windows $^O =~ /MSWin32/i && # We haven't disabled coloured output for Term::ANSIColor ( ! $ENV{'ANSI_COLORS_DISABLED'} ) && # Here's a flag you can use if you really really need to turn this fall- # back behaviour off (! $ENV{'DISABLE_WIN32_FALLBACK'} ) ) { # Try and load eval "require Win32::Console::ANSI"; if ( $@ ) { print "# Install Win32::Console::ANSI to display colors properly\n"; } } } use Term::ANSIColor; use Test::BDD::Cucumber::Util; use Test::BDD::Cucumber::Model::Result; extends 'Test::BDD::Cucumber::Harness'; my $margin = 2; if ( $margin > 1 ) { print "\n" x ( $margin - 1 ); } my $current_feature; sub feature { my ( $self, $feature ) = @_; $current_feature = $feature; $self->_display({ indent => 0, color => 'bright_white', text => $feature->name, follow_up => [ map { $_->content } @{ $feature->satisfaction || [] } ], trailing => 1 }); } sub feature_done { print "\n"; } sub scenario { my ( $self, $scenario, $dataset, $longest ) = @_; my $text = "Scenario: " . color('bright_blue') . ($scenario->name || '' ); $self->_display({ indent => 2, color => 'bright_white', text => $text, follow_up => [], trailing => 0, longest_line => ($longest||0) }); } sub scenario_done { print "\n"; } sub step {} sub step_done { my ($self, $context, $result ) = @_; my $color; my $follow_up = []; my $status = $result->result; if ( $status eq 'undefined' || $status eq 'pending' ) { $color = 'yellow'; } elsif ( $status eq 'passing' ) { $color = 'green'; } else { $color = 'red'; $follow_up = [ split(/\n/, $result->{'output'} ) ]; if ( ! $context->is_hook ) { unshift @{ $follow_up }, 'step defined at ' . $context->step->line->document->filename . ' line ' . $context->step->line->number . '.'; } } my $text; if ( $context->is_hook ) { $color eq 'red' or return; $text = 'In ' . ucfirst( $context->verb ) . ' Hook'; } else { $text = $context->step->verb_original . ' ' . $context->text; } $self->_display({ indent => 4, color => $color, text => $text, highlight => 'bright_cyan', trailing => 0, follow_up => $follow_up, longest_line => $context->stash->{'scenario'}->{'longest_step_line'} }); $self->_note_step_data( $context->step ); } sub _note_step_data { my ( $self, $step ) = @_; return unless $step; my @step_data = @{ $step->data_as_strings }; return unless @step_data; my $note = sub { my ( $text, $extra_indent ) = @_; $extra_indent ||= 0; $self->_display({ indent => 6 + $extra_indent, color => 'bright_cyan', text => $text }); }; if ( ref( $step->data ) eq 'ARRAY' ) { for ( @step_data ) { $note->( $_ ); } } else { $note->('"""'); for ( @step_data ) { $note->( $_, 2 ); } $note->('"""'); } } sub _display { my ( $class, $options ) = @_; $options->{'indent'} += $margin; # Reset it all... print color 'reset'; # Print the main line print ' ' x $options->{'indent'}; # Highlight as appropriate my $color = color $options->{'color'}; if ( $options->{'highlight'} ) { my $reset = color 'reset'; my $base = color $options->{'color'}; my $hl = color $options->{'highlight'}; my $text = $base . Test::BDD::Cucumber::Util::bs_quote( $options->{'text'} ); $text =~ s/("(.+?)"|[ ^](\d[-?\d\.]*))/$reset$hl$1$reset$base/g; print Test::BDD::Cucumber::Util::bs_unquote( $text ); # Normal output } else { print color $options->{'color'}; print $options->{'text'}; } # Reset and newline print color 'reset'; print "\n"; # Print follow-up lines... for my $line ( @{ $options->{'follow_up'} || [] } ) { print color 'reset'; print ' ' x ( $options->{'indent'} + 2 ); print color $options->{'color'}; print $line; print color 'reset'; print "\n"; } print "\n" if $options->{'trailing'}; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Harness/Data.pm0000644000175000017500000000761012246645110021254 0ustar bdrbdrpackage Test::BDD::Cucumber::Harness::Data; { $Test::BDD::Cucumber::Harness::Data::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Harness::Data - Builds up an internal data representation of test passes / failures =head1 VERSION version 0.17 =head1 DESCRIPTION A L subclass which collates test data =cut use strict; use warnings; use Moose; use Test::More; use Test::BDD::Cucumber::Model::Result; extends 'Test::BDD::Cucumber::Harness'; =head1 ATTRIBUTES =head2 features An array-ref in which we store all the features executed, and completed. Until C is called, it won't be in here. =cut has 'features' => ( is => 'rw', isa => 'ArrayRef', default => sub {[]} ); =head2 current_feature =head2 current_scenario =head2 current_step The current feature/step/scenario for which we've had the starting method, but not the C<_done> method. =cut has 'current_feature' => ( is => 'rw', isa => 'HashRef', default => sub {{}} ); has 'current_scenario' => ( is => 'rw', isa => 'HashRef', default => sub {{}} ); has 'current_step' => ( is => 'rw', isa => 'HashRef', default => sub {{}} ); =head2 feature =head2 feature_done Feature hashref looks like: { object => Test::BDD::Cucumber::Model::Feature object scenarios => [] } =cut # We will keep track of where we are each time... sub feature { my ( $self, $feature ) = @_; my $feature_ref = { object => $feature, scenarios => [] }; $self->current_feature( $feature_ref ); } sub feature_done { my $self = shift; push( @{ $self->features }, $self->current_feature ); $self->current_feature({}); } =head2 scenario =head2 scenario_done Scenario hashref looks like: { object => Test::BDD::Cucumber::Model::Scenario object dataset => Data hash the scenario was invoked with steps => [], } =cut sub scenario { my ( $self, $scenario, $dataset ) = @_; my $scenario_ref = { object => $scenario, dataset => $dataset, steps => [], }; $self->current_scenario( $scenario_ref ); } sub scenario_done { my $self = shift; push( @{ $self->current_feature->{'scenarios'} }, $self->current_scenario ); $self->current_scenario({}); } =head2 step =head2 step_done Step hashref looks like: { context => Test::BDD::Cucumber::StepContext object result => Test::BDD::Cucumber::Model::Result object (after step_done) } =cut sub step { my ( $self, $step_context ) = @_; my $step_ref = { context => $step_context }; $self->current_step( $step_ref ); } sub step_done { my ($self, $context, $result) = @_; $self->current_step->{'result'} = $result; push( @{ $self->current_scenario->{'steps'} }, $self->current_step ); $self->current_step({}); } =head2 feature_status =head2 scenario_status =head2 step_status Accepting one of the data-hashes above, returns a L object representing it. If it's a Feature or a Scenario, then it returns one representing all the child objects. =cut # Status methods sub feature_status { my ( $self, $feature ) = @_; return Test::BDD::Cucumber::Model::Result->from_children( map { $self->scenario_status($_) } @{ $feature->{'scenarios'} } ); } sub scenario_status { my ( $self, $scenario ) = @_; return Test::BDD::Cucumber::Model::Result->from_children( map { $self->step_status($_) } @{ $scenario->{'steps'} } ); } sub step_status { my ($self, $step) = @_; return $step->{'result'}; } =head2 find_scenario_step_by_name Given a Scenario and a string, searches through the steps for it and returns the data-hash where the Step Object's C<<->text>> matches the string. =cut # Find a step sub find_scenario_step_by_name { my ( $self, $scenario, $name ) = @_; my ( $step ) = grep { $_->{'context'}->text eq $name } @{ $scenario->{'steps'} }; return $step; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber/Harness/TestBuilder.pm0000644000175000017500000000524412246645110022632 0ustar bdrbdrpackage Test::BDD::Cucumber::Harness::TestBuilder; { $Test::BDD::Cucumber::Harness::TestBuilder::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber::Harness::TestBuilder - Pipes step output via Test::Builder =head1 VERSION version 0.17 =head1 DESCRIPTION A L subclass whose output is sent to L. =cut use strict; use warnings; use Moose; use Test::More; extends 'Test::BDD::Cucumber::Harness'; has 'fail_skip' => ( is => 'rw', isa => 'Bool', default => 0 ); my $li = ' ' x 7; my $ni = ' ' x 4; my $si = ' ' x 9; my $di = ' ' x 17; sub feature { my ( $self, $feature ) = @_; note "${li}Feature: " . $feature->name; note "$li$ni" . $_->content for @{ $feature->satisfaction }; note ""; } sub scenario { my ( $self, $scenario, $dataset ) = @_; note "$li${ni}Scenario: " . ($scenario->name || ''); } sub scenario_done { note ""; } sub step {} sub step_done { my ($self, $context, $result) = @_; my $status = $result->result; my $step = $context->step; my $step_name; if ( $context->is_hook ) { $status ne 'undefined' and $status ne 'pending' and $status ne 'passing' or return; $step_name = 'In ' . ucfirst( $context->verb ) . ' Hook'; } else { $step_name = $si . ucfirst($step->verb_original) . ' ' . $context->text; } if ( $status eq 'undefined' || $status eq 'pending' ) { if ( $self->fail_skip ) { fail( "No matcher for: $step_name" ); $self->_note_step_data( $step ); } else { TODO: { todo_skip $step_name, 1 }; $self->_note_step_data( $step ); } } elsif ( $status eq 'passing' ) { pass( $step_name ); $self->_note_step_data( $step ); } else { fail( $step_name ); $self->_note_step_data( $step ); if ( ! $context->is_hook ) { my $step_location = ' in step at ' . $step->line->document->filename . ' line ' . $step->line->number . '.'; diag( $step_location ); } diag( $result->output ); } } sub _note_step_data { my ( $self, $step ) = @_; return unless $step; my @step_data = @{ $step->data_as_strings }; return unless @step_data; if ( ref( $step->data ) eq 'ARRAY' ) { for ( @step_data ) { note( $di . $_ ); } } else { note $di . '"""'; for ( @step_data ) { note( $di . ' ' . $_ ); } note $di . '"""'; } } sub shutdown { done_testing(); } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/lib/Test/BDD/Cucumber.pm0000644000175000017500000000502612246645110016777 0ustar bdrbdrpackage Test::BDD::Cucumber; { $Test::BDD::Cucumber::VERSION = '0.17'; } =head1 NAME Test::BDD::Cucumber - Feature-complete Cucumber-style testing in Perl =head1 VERSION version 0.17 =head1 DESCRIPTION A sane and complete Cucumber implementation in Perl =head1 QUICK LINKS L =head1 WARNING This is beta software, at best. The interface is unlikely to undergo major incompatible changes, but it's certainly possible. Do have a read of the B section below so you're not surprised when these things don't work. In almost all cases, where the behaviour of this module is different from the I Cucumber, the plan is to move it to be more similar to that. The idea is that the first 1.0 release will be the first production release and before that, you're on your own. There are many things still to add, but B using it to do Real Things already. =head1 NEXT STEPS If you are B, you'd get a pretty overview from reading our short and crunchy L. If you B, and just want to get started then you should read the L, the documentation for our command-line tool L, and L. If you B then you'd probably be more interested in our L. =head1 TEN SECOND GUIDE TO USING THIS IN YOUR CI ENVIRONMENT Don't use the command-line tool, L. Instead, look at the L document. =head1 BUGS, MISSING, AND LIMITATIONS The following things do not work in this release, although support is planned in the very near future: =over 4 =item * Localization =item * Step Argument Transforms =item * Quoting in tables is broken =item * Placeholders in pystrings is broken =item * Explicit Step Outline notation doesn't work (although step outlines are explicitly supported) =item * Unicode support is probably a bit ropey =item * Pherkin isn't really fit for purpose yet =back =head1 CODE On Github, of course: L. =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/script/0000775000175000017500000000000012246645110014121 5ustar bdrbdrTest-BDD-Cucumber-0.17/script/make_corpus.pl0000755000175000017500000000063512246645110016773 0ustar bdrbdr#!perl use strict; use warnings; use FindBin::libs; use Test::More; use Test::DumpFeature; use Test::BDD::Cucumber::Parser; use YAML::Syck; use File::Slurp; my $file_data = read_file( $ARGV[0] ); my $feature = Test::BDD::Cucumber::Parser->parse_string( $file_data ); my $feature_hash = Test::DumpFeature::dump_feature( $feature ); print $file_data . "\n----------DIVIDER----------\n" . Dump( $feature_hash );Test-BDD-Cucumber-0.17/README.pod0000644000175000017500000000471212246645110014260 0ustar bdrbdrpackage Test::BDD::Cucumber; =head1 NAME Test::BDD::Cucumber - Feature-complete Cucumber-style testing in Perl =head1 DESCRIPTION A sane and complete Cucumber implementation in Perl =head1 QUICK LINKS L =head1 WARNING This is beta software, at best. The interface is unlikely to undergo major incompatible changes, but it's certainly possible. Do have a read of the B section below so you're not surprised when these things don't work. In almost all cases, where the behaviour of this module is different from the I Cucumber, the plan is to move it to be more similar to that. The idea is that the first 1.0 release will be the first production release and before that, you're on your own. There are many things still to add, but B using it to do Real Things already. =head1 NEXT STEPS If you are B, you'd get a pretty overview from reading our short and crunchy L. If you B, and just want to get started then you should read the L, the documentation for our command-line tool L, and L. If you B then you'd probably be more interested in our L. =head1 TEN SECOND GUIDE TO USING THIS IN YOUR CI ENVIRONMENT Don't use the command-line tool, L. Instead, look at the L document. =head1 BUGS, MISSING, AND LIMITATIONS The following things do not work in this release, although support is planned in the very near future: =over 4 =item * Localization =item * Step Argument Transforms =item * Quoting in tables is broken =item * Placeholders in pystrings is broken =item * Explicit Step Outline notation doesn't work (although step outlines are explicitly supported) =item * Unicode support is probably a bit ropey =item * Pherkin isn't really fit for purpose yet =back =head1 CODE On Github, of course: L. =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Test-BDD-Cucumber-0.17/README0000644000175000017500000000047712246645110013503 0ustar bdrbdr This archive contains the distribution Test-BDD-Cucumber, version 0.17: Feature-complete Cucumber-style testing in Perl This software is copyright (c) 2013 by Peter Sergeant. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Test-BDD-Cucumber-0.17/examples/0000775000175000017500000000000012246645110014433 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/tagged-digest/0000775000175000017500000000000012246645110017143 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/tagged-digest/features/0000775000175000017500000000000012246645110020761 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/tagged-digest/features/step_definitions/0000775000175000017500000000000012246645110024327 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/tagged-digest/features/step_definitions/basic_steps.pl0000755000175000017500000000147512246645110027173 0ustar bdrbdr#!perl use strict; use warnings; use Digest; use Test::More; use Test::BDD::Cucumber::StepFile; use Method::Signatures; Given qr/a usable "(\w+)" class/, func ($c) { use_ok( $1 ); }; Given qr/a Digest (\S+) object/, func ($c) { my $object = Digest->new($1); ok( $object, "Object created" ); $c->stash->{'scenario'}->{'object'} = $object; }; When qr/I've added "(.+)" to the object/, func ($c) { $c->stash->{'scenario'}->{'object'}->add( $1 ); }; When "I've added the following to the object", func ($c) { $c->stash->{'scenario'}->{'object'}->add( $c->data ); }; Then qr/the (.+) output is "(.+)"/, func ($c) { my $method = {base64 => 'b64digest', 'hex' => 'hexdigest' }->{ $1 } || do { fail("Unknown output type $1"); return }; is( $c->stash->{'scenario'}->{'object'}->$method, $2 ); }; Test-BDD-Cucumber-0.17/examples/tagged-digest/features/basic.feature0000644000175000017500000000241112246645110023413 0ustar bdrbdr# Somehow I don't see this replacing the other tests this module has... Feature: Simple tests of Digest.pm As a developer planning to use Digest.pm I want to test the basic functionality of Digest.pm In order to have confidence in it Background: Given a usable "Digest" class @md5 Scenario: Check MD5 Given a Digest MD5 object When I've added "foo bar baz" to the object And I've added "bat ban shan" to the object Then the hex output is "bcb56b3dd4674d5d7459c95e4c8a41d5" Then the base64 output is "1B2M2Y8AsgTpgAmY7PhCfg" @sha1 Scenario: Check SHA-1 Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" Examples: | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | @md5 Scenario: MD5 longer data Given a Digest MD5 object When I've added the following to the object """ Here is a chunk of text that works a bit like a HereDoc. We'll split off indenting space from the lines in it up to the indentation of the first \"\"\" """ Then the hex output is "75ad9f578e43b863590fae52d5d19ce6" Test-BDD-Cucumber-0.17/examples/calculator/0000775000175000017500000000000012246645110016564 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/calculator/features/0000775000175000017500000000000012246645110020402 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/calculator/features/step_definitions/0000775000175000017500000000000012246645110023750 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/calculator/features/step_definitions/calculator_steps.pl0000755000175000017500000000466312246645110027666 0ustar bdrbdr#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Method::Signatures; use lib 'examples/calculator/lib/'; Before func( $c ) { use_ok( 'Calculator' ); }; After func( $c ) { # a bit contrived, as garbage collection would clear it out delete $c->stash->{'scenario'}->{'Calculator'}; ok( not exists $c->stash->{'scenario'}->{'Calculator'} ); }; my %numbers_as_words = ( __THE_NUMBER_ONE__ => 1, __THE_NUMBER_FOUR__ => 4, __THE_NUMBER_FIVE__ => 5, __THE_NUMBER_TEN__ => 10, ); sub map_word_to_number { my $word = shift; ok( $word ); ok( exists $numbers_as_words{ $word } ); return $numbers_as_words{ $word }; } Transform qr/^(__THE_NUMBER_\w+__)$/, func( $c ) { map_word_to_number( $1 ) }; Transform qr/^table:number as word$/, func( $c, $data ) { for my $row ( @{ $data } ) { $row->{'number'} = map_word_to_number( $row->{'number as word'} ); } }; Given 'a new Calculator object', func ($c) { $c->stash->{'scenario'}->{'Calculator'} = Calculator->new() }; Given qr/^having pressed (.+)/, func($c) { $c->stash->{'scenario'}->{'Calculator'}->press( $_ ) for split(/(,| and) /, $1); }; Given qr/^having keyed (.+)/, func($c) { # Make this call the having pressed my ( $value ) = @{ $c->matches }; $c->stash->{'scenario'}->{'Calculator'}->key_in( $value ); }; Given 'having successfully performed the following calculations', func ($c) { my $calculator = $c->stash->{'scenario'}->{'Calculator'}; for my $row ( @{ $c->data } ) { $calculator->key_in( $row->{'first'} ); $calculator->key_in( $row->{'operator'} ); $calculator->key_in( $row->{'second'} ); $calculator->press( '=' ); is( $calculator->display, $row->{'result'}, $row->{'first'} .' '. $row->{'operator'} .' '. $row->{'second'} ); } }; Given 'having entered the following sequence', func ($c) { $c->stash->{'scenario'}->{'Calculator'}->key_in( $c->data ); }; Given 'having added these numbers', func ($c) { for my $row ( @{ $c->data } ) { $c->stash->{'scenario'}->{'Calculator'}->key_in( $row->{number} ); $c->stash->{'scenario'}->{'Calculator'}->key_in( '+' ); } }; Then qr/^the display should show (.+)/, func($c) { my ( $value ) = @{ $c->matches }; is( $c->stash->{'scenario'}->{'Calculator'}->display, $value, "Calculator display as expected" ); }; Test-BDD-Cucumber-0.17/examples/calculator/features/basic.feature0000644000175000017500000000453512246645110023045 0ustar bdrbdrFeature: Basic Calculator Functions In order to check I've written the Calculator class correctly As a developer I want to check some basic operations So that I can have confidence in my Calculator class. Scenario: First Key Press on the Display Given a new Calculator object And having pressed 1 Then the display should show 1 Scenario: Several Key Presses on the Display Given a new Calculator object And having pressed 1 and 2 and 3 and . and 5 and 0 Then the display should show 123.50 Scenario: Pressing Clear Wipes the Display Given a new Calculator object And having pressed 1 and 2 and 3 And having pressed C Then the display should show 0 Scenario: Add as you go Given a new Calculator object And having pressed 1 and 2 and 3 and + and 4 and 5 and 6 and + Then the display should show 579 Scenario: Basic arithmetic Given a new Calculator object And having keyed And having keyed And having keyed And having pressed = Then the display should show Examples: | first | operator | second | result | | 5.0 | + | 5.0 | 10 | | 6 | / | 3 | 2 | | 10 | * | 7.550 | 75.5 | | 3 | - | 10 | -7 | Scenario: Separation of calculations Given a new Calculator object And having successfully performed the following calculations | first | operator | second | result | | 0.5 | + | 0.1 | 0.6 | | 0.01 | / | 0.01 | 1 | | 10 | * | 1 | 10 | And having pressed 3 Then the display should show 3 Scenario: Ticker Tape Given a new Calculator object And having entered the following sequence """ 1 + 2 + 3 + 4 + 5 + 6 - 100 * 13 \=\=\= + 2 = """ Then the display should show -1025 Scenario: Enter number using text Given a new Calculator object And having keyed __THE_NUMBER_FIVE__ Then the display should show 5 Scenario: Enter numbers using text Given a new Calculator object And having added these numbers | number as word | | __THE_NUMBER_FOUR__ | | __THE_NUMBER_FIVE__ | | __THE_NUMBER_ONE__ | Then the display should show 10 And the display should show __THE_NUMBER_TEN__ Test-BDD-Cucumber-0.17/examples/calculator/lib/0000775000175000017500000000000012246645110017332 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/calculator/lib/Calculator.pm0000644000175000017500000000345012246645110021761 0ustar bdrbdrpackage # hide from PAUSE indexer Calculator; use strict; use warnings; use Moose; has 'left' => ( is => 'rw', isa => 'Num', default => 0 ); has 'right' => ( is => 'rw', isa => 'Str', default => '' ); has 'operator' => ( is => 'rw', isa => 'Str', default => '+' ); has 'display' => ( is => 'rw', isa => 'Str', default => '0' ); has 'equals' => ( is => 'rw', isa => 'Str', default => '' ); sub key_in { my ( $self, $seq ) = @_; my @possible = grep {/\S/} split(//, $seq); $self->press($_) for @possible; } sub press { my ( $self, $key ) = @_; # Numbers $self->digit( $1 ) if $key =~ m/^([\d\.])$/; # Operators $self->key_operator( $1 ) if $key =~ m/^([\+\-\/\*])$/; # Equals $self->equalsign if $key eq '='; # Clear $self->clear if $key eq 'C'; } sub clear { my $self = shift; $self->left(0); $self->right(''); $self->operator('+'); $self->display('0'); $self->equals(''); } sub equalsign { my $self = shift; $self->key_operator('+'); my $result = $self->left; $self->clear(); $self->equals( $result ); $self->display( $result ); } sub digit { my ( $self, $digit ) = @_; # Deal with decimal weirdness if ( $digit eq '.' ) { return if $self->right =~ m/\./; $digit = '0.' unless length( $self->right ); } $self->right( $self->right . $digit ); $self->display( $self->right ); } sub key_operator { my ( $self, $operator ) = @_; my $cmd = $self->left . $self->operator . ( length($self->right) ? $self->right : ( length( $self->equals ) ? $self->equals : '0')); $self->right(''); $self->equals(''); $self->left( (eval $cmd) + 0 ); $self->display( $self->left ); $self->operator( $operator ); } 1; Test-BDD-Cucumber-0.17/examples/digest/0000775000175000017500000000000012246645110015712 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/digest/features/0000775000175000017500000000000012246645110017530 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/digest/features/step_definitions/0000775000175000017500000000000012246645110023076 5ustar bdrbdrTest-BDD-Cucumber-0.17/examples/digest/features/step_definitions/basic_steps.pl0000755000175000017500000000147512246645110025742 0ustar bdrbdr#!perl use strict; use warnings; use Digest; use Test::More; use Test::BDD::Cucumber::StepFile; use Method::Signatures; Given qr/a usable "(\w+)" class/, func ($c) { use_ok( $1 ); }; Given qr/a Digest (\S+) object/, func ($c) { my $object = Digest->new($1); ok( $object, "Object created" ); $c->stash->{'scenario'}->{'object'} = $object; }; When qr/I've added "(.+)" to the object/, func ($c) { $c->stash->{'scenario'}->{'object'}->add( $1 ); }; When "I've added the following to the object", func ($c) { $c->stash->{'scenario'}->{'object'}->add( $c->data ); }; Then qr/the (.+) output is "(.+)"/, func ($c) { my $method = {base64 => 'b64digest', 'hex' => 'hexdigest' }->{ $1 } || do { fail("Unknown output type $1"); return }; is( $c->stash->{'scenario'}->{'object'}->$method, $2 ); }; Test-BDD-Cucumber-0.17/examples/digest/features/basic.feature0000644000175000017500000000236312246645110022170 0ustar bdrbdr# Somehow I don't see this replacing the other tests this module has... Feature: Simple tests of Digest.pm As a developer planning to use Digest.pm I want to test the basic functionality of Digest.pm In order to have confidence in it Background: Given a usable "Digest" class Scenario: Check MD5 Given a Digest MD5 object When I've added "foo bar baz" to the object And I've added "bat ban shan" to the object Then the hex output is "bcb56b3dd4674d5d7459c95e4c8a41d5" Then the base64 output is "1B2M2Y8AsgTpgAmY7PhCfg" Scenario: Check SHA-1 Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" Examples: | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | Scenario: MD5 longer data Given a Digest MD5 object When I've added the following to the object """ Here is a chunk of text that works a bit like a HereDoc. We'll split off indenting space from the lines in it up to the indentation of the first \"\"\" """ Then the hex output is "75ad9f578e43b863590fae52d5d19ce6" Test-BDD-Cucumber-0.17/bin/0000775000175000017500000000000012246645110013365 5ustar bdrbdrTest-BDD-Cucumber-0.17/bin/pherkin0000755000175000017500000000375312246645110014761 0ustar bdrbdr#!/usr/bin/env perl =head1 NAME pherkin - Execute tests written using Test::BDD::Cucumber =head1 VERSION version 0.17 =head1 SYNOPSIS pherkin pherkin some/path/features/ =head1 DESCRIPTION C accepts a single argument of a directory name, defaulting to C<./features/> if none is specified. This directory is searched for feature files (any file matching C<*.feature>) and step definition files (any file matching C<*_steps.pl>). The step definitions are loaded, and then the features executed. Steps that pass are printed in green, those that fail in red, and those for which there is no step definition - or that are skipped as the result of a previous failure - as yellow. C will exit with a non-zero status if (and only if) the overall result is considered to be failing. =head1 OPTIONS -l, --lib Add 'lib' to @INC -b, --blib Add 'blib/lib' and 'blib/arch' to @INC -I [dir] Add given directory to @INC -o, --output Output harness to use. Default to 'TermColor'. See 'Outputs' -t, --tags @tag Run scenarios tagged with '@tag' -t, --tags @tag1,@tag2 Run scenarios tagged with '@tag1' and '@tag2' -t, --tags ~@tag Run scenarios tagged without '@tag' =head1 OUTPUTS C can output using any of the C output modules. L is the default, but L is also a reasonable option: pherkin -o TermColor some/path/feature # The default pherkin -o TestBuilder some/path/feature # Test::Builder-type text output =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2012, Peter Sergeant; Licensed under the same terms as Perl =cut # See App::pherkin for documentation use strict; use warnings; use FindBin::libs; BEGIN { if ( not -t STDOUT ) { $ENV{'ANSI_COLORS_DISABLED'} = 1; } } use App::pherkin; my $result = App::pherkin->new()->run( @ARGV ); exit( $result->result eq 'failing' );