Test-BDD-Cucumber-0.45000755000765000024 012656770421 15327 5ustar00pjlsergeantstaff000000000000TODO100644000765000024 115212656770421 16077 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45 - [ ] Simplify Parser - [ ] Add a jUnit outputer - [ ] Quoting in tables is broken - [ ] Placeholders in Pystrings - [ ] Make Pherkin feature complete Make a corpus-checker for checking parsing of In The Wild Gherkin files... - https://github.com/diaspora/diaspora/tree/master/features - https://github.com/teambox/teambox/tree/dev/features - https://github.com/rspec/rspec-core/tree/master/features - https://github.com/cucumber/cucumber/tree/master/features - https://github.com/cucumber/aruba/tree/master/features - https://github.com/wikimedia/mediawiki/tree/master/tests/browser/featuresREADME100644000765000024 60612656770421 16252 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45 This archive contains the distribution Test-BDD-Cucumber, version 0.45: Feature-complete Cucumber-style testing in Perl This software is copyright (c) 2016 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. This README file was generated by Dist::Zilla::Plugin::Readme v5.039. CHANGES100644000765000024 1744412656770421 16435 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45----- 0.45: 11 Feb 2016 - Removed Moose cleanliness method from Test::BDD::Cucumber::Extension 0.44: 09 Feb 2016 - Add extensions! See Test::BDD::Cucumber::Executor and Test::BDD::Cucumber::Extensions for details. Work by ehuelsmann: https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/66 0.41: 09 Feb 2016 - pherkin command line options can now be read from config files, based on a patch by eheulsmann - Scenario outline handling now works properly with i18n, thanks eheulsmann https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/71 - Storable dependency removed, thanks ehuelsmann https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/69 - Various spelling mistakes fixed - thanks James McCoy 0.40: 02 Jan 2016 - Step redispatching 0.39: 25 Oct 2015 - Proper support for Test::Builder's BAIL_OUT() added 0.38: 25 Oct 2015 - Fixed error message when fail_skip is set on Test::Builder harness - Made pherkin -I work again 0.37: 24 Aug 2015 - Allow specification of extra step files and directories in `pherkin` 0.36: 24 Aug 2015 - Don't require Devel::FindRef 0.35: 21 Jul 2015 - Fixed the Test::Builder wrapping issue discussed at: https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/61 Output from Test::Exception should now be properly captured. - Updated git repository all over the places 0.34: 21 Apr 2015 - JSON formatter tests now Win32 compatible 0.33: 20 Apr 2015 - JSON formatter uses filename/line based IDs rather than memory-location ones, thanks Tomas Pokorny - Minor App::pherkin refactor to make harness an attribute, thanks Tomas Pokorny - $ENV{ANSI_COLOR_DISABLED} checked for definedness, not truth in deciding whether to colourize output in some situations; thanks Tomas Pokorny - Minor typo fixes, thanks 'poum', 'Chylli' 0.32: 23 Dec 2014 - Colour themes for TermColor harness, fixes https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues/35 - Command-line options are now passed through https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/49/files - Both of these are based on a patch from benningm 0.31: 09 Oct 2014 - Specified a minimum version of File::Slurp in response to a private bug report 0.30: 27 Apr 2014 - Use core module Digest::SHA instead of Digest::SHA1 https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues/45 0.29: 26 Apr 2014 - Tried to fix Win32 issue again - Remove FindBin::libs - Installs cleanly on 5.8 again 0.28: 26 Apr 2014 - Fixed the JSON outputter test on Win32 to use Path::Class 0.27: 25 Aug 2014 - Added JSON output support, courtesy of Tomas Zemres - Some useful minor patched via Paul Cochrane - Ran the whole thing through perltidy 0.26: 21 Jun 2014 - Fixed a bug relating to skipped steps in TermColor output https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues/40 - Changed examples/ to use C->matches 0.25: 08 Jun 2014 - Highlight parameters properly in TermColor output using @+ and @- https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues/24 0.24: 07 Jun 2014 - Replacing string `eval` with block `eval` for requiring test harnesses - thanks Paul Cochrane - Module metadata now points to repository and bug tracker - thanks Peter Rabbitson - Removed Ouch 0.23: 05 Jun 2014 - Another attempt to fix up the weird regex test bug - Remove our experiment with ShareDir 0.22: 04 Jun 2014 - Some PerlCritic-ish fixes from Paul Cochrane - Updated copyrights, again from Paul Cochrane - There's some weird-ass bug with $1 not getting set properly, sometimes, on perl's older than 5.14. I can't reproduce, and virtually identical examples act differently. Also I can't reproduce it. Rewritten the test itself to go via ->matches 0.21: 03 Jun 2014 - Now works with 5.10.0 again 0.20: 03 Jun 2014 - Adding missed dependency from Paul Cochrane 0.19: 24 Apr 2014 - Removed Method::Signatures dependency - Added C and S step file shortcut subrefs - Added Internationalization support, thanks to some sterling work by Gregor Goldbach and Pablo Duboue 0.18: 06 Apr 2014 - Removed Find::File dependency in StepFile.pm 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 suppress colours when outputting to not a tty; thanks (for this, and much of the stuff in 0.15) to rjp: https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/11 - Try and use Win32::Console::ANSI if on Windows https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues/13 - Before and After Hooks now implemented highflying: https://github.com/pjlsergeant/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/pjlsergeant/test-bdd-cucumber-perl/pull/15 highflying: https://github.com/pjlsergeant/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/pjlsergeant/test-bdd-cucumber-perl/issues/8 - Use the original verb that the test file used https://github.com/pjlsergeant/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/pjlsergeant/test-bdd-cucumber-perl/issues/5 0.11: 20 May 2012 - Correct Term::ANSIColor dependency https://github.com/pjlsergeant/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/pjlsergeant/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/pjlsergeant/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 LICENSE100644000765000024 4366312656770421 16451 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45This software is copyright (c) 2016 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) 2016 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, Fifth Floor, Boston, MA 02110-1301 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) 2016 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 dist.ini100644000765000024 175012656770421 17057 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45name = Test-BDD-Cucumber version = 0.45 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 [MetaResources] bugtracker.web = https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues repository.url = https://github.com/pjlsergeant/test-bdd-cucumber-perl.git repository.web = https://github.com/pjlsergeant/test-bdd-cucumber-perl repository.type = git [@Basic] [PkgVersion] [PodVersion] [PodSyntaxTests] [Prereqs] perl = 5.008 Clone = 0 Devel::Refcount = 0 Digest::SHA = 0 List::Util = 0 List::MoreUtils = 0 File::Find::Rule = 0 File::Slurp = 9999.16 Getopt::Long = 0 IO::Scalar = 0 JSON::MaybeXS = 1.001000 Module::Runtime = 0 Moose = 0 Number::Range = 0 Path::Class = 0 Term::ANSIColor = 3.00 Test::Builder = 0 Test::Differences = 0 Test::Exception = 0 Test::More = 0 YAML::Syck = 0 META.yml100644000765000024 204112656770421 16656 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45--- abstract: 'Feature-complete Cucumber-style testing in Perl' author: - "['Peter Sergeant ','Ben Rogers ']" build_requires: {} configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 5.039, CPAN::Meta::Converter version 2.150001' 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' Devel::Refcount: '0' Digest::SHA: '0' File::Find::Rule: '0' File::Slurp: '9999.16' Getopt::Long: '0' IO::Scalar: '0' JSON::MaybeXS: '1.001000' List::MoreUtils: '0' List::Util: '0' Module::Runtime: '0' Moose: '0' Number::Range: '0' Path::Class: '0' Term::ANSIColor: '3.00' Test::Builder: '0' Test::Differences: '0' Test::Exception: '0' Test::More: '0' YAML::Syck: '0' perl: '5.008' resources: bugtracker: https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues repository: https://github.com/pjlsergeant/test-bdd-cucumber-perl.git version: '0.45' MANIFEST100644000765000024 576312656770421 16554 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.039. CHANGES LICENSE MANIFEST META.yml Makefile.PL README README.pod TODO bin/pherkin dist.ini examples/calculator/features/basic.feature examples/calculator/features/basic.feature.es 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/i18n_de/features/basic.feature examples/i18n_de/features/step_definitions/calculator_steps.pl examples/pherkin.yml 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/Errors.pm lib/Test/BDD/Cucumber/Executor.pm lib/Test/BDD/Cucumber/Extension.pm lib/Test/BDD/Cucumber/Harness.pm lib/Test/BDD/Cucumber/Harness/Data.pm lib/Test/BDD/Cucumber/Harness/JSON.pm lib/Test/BDD/Cucumber/Harness/TermColor.pm lib/Test/BDD/Cucumber/Harness/TestBuilder.pm lib/Test/BDD/Cucumber/I18N/Data.pm lib/Test/BDD/Cucumber/I18n.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/TestBuilderDelegator.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/240_localized_features.t t/250_i18n_json.t t/260_match_matcher.t t/270_scenario_outlines.t t/310_auto_corpus.t t/400_app_pherkin_harnesses.t t/500_error_formatter.t t/600_harness_json_output.t t/700_tag_processing.t t/710_configuration_profiles.t t/720_extension_tests.t t/800_regressions_010_too_few_features.t t/800_regressions_020_term_color_skipped_steps.t t/800_regressions_030_pherkin_external_libs.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/harness_json/mock.feature t/harness_json/step_definitions/mock_steps.pl t/lib/Test/CucumberExtensionCount.pm t/lib/Test/CucumberExtensionPush.pm 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/pherkin_config_files/not_yaml.yaml t/pherkin_config_files/readable.yaml t/pherkin_config_files/top_level_array.yaml t/regressions/010_greedy_table_parsing/test.feature t/regressions/010_greedy_table_parsing/test_steps.pl t/release-pod-syntax.t README.pod100644000765000024 414512656770421 17055 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45package Test::BDD::Cucumber; use strict; use warnings; 1; # CODE ENDS =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 Do have a read of the B section below so you're not surprised when 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. =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 * 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 * Pherkin isn't really fit for purpose yet =back =head1 CODE On Github, of course: L. =head1 AUTHORS Peter Sergeant C Ben Rodgers C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut bin000755000765000024 012656770421 16020 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45pherkin100755000765000024 1024712656770421 17572 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/bin#!/usr/bin/env perl =head1 NAME pherkin - Execute tests written using Test::BDD::Cucumber =head1 VERSION version 0.45 =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 Controlling @INC -l, --lib Add 'lib' to @INC -b, --blib Add 'blib/lib' and 'blib/arch' to @INC -I [dir] Add given directory to @INC Output formatting -o, --output Output harness. Defaults to 'TermColor'. See 'Outputs' -c, --theme Theme for 'TermColor'. `light` or `dark` (default) Extra Steps -s, --steps [path] Include an extra step file, or directory of step files (as identified by *_steps.pl; multiple use accepted) Tag specifications -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' Configuration profiles (see CONFIGURATION PROFILES below/`man pherkin`) -g, --config [path] A YAML file containing configuration profiles -p, --profile [name] Name of the profile to load from the above config file. Defaults to `default` --debug-profile Shows information about which profile was loaded and how and then terminates Extensions -e Extension::Module Load an extension. You can place a string in brackets at the end of the module name which will be eval'd and passed to new() for the extension. Help -h, -?, --help Print usage information. --i18n LANG List keywords for a particular language. '--i18n help' lists all languages available. =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 CONFIGURATION PROFILES You can specify sets of command line options using a YAML configuration file with named profiles in it, and the C<-g, --config> and C<-p, --profile> command line options. If you don't specify a config file, the following paths are searched (in order) for one: (contents of $ENV{'PHERKIN_CONFIG'}) .pherkin.yaml ./config/pherkin.yaml ./.config/pherkin.yaml t/.pherkin.yaml ~/.pherkin.yaml The contents of each profile is merged in as if you'd specified it on the command line. C is used if you didn't specify one. For example: default: steps: - foo/steps - ~/steps output: TermColor tags: - tag1,tag2 is equivalent to: --steps foo/steps --steps ~/steps --output TermColor --tags tag1,tag2 If you specify both command-line options, and options in a configuration file, then the command-line ones override single-value items, and are placed at the end of multi-item ones. If you specify C<--debug-profile> then information showing which profile is loaded and how is printed to STDOUT, and then `pherkin` terminates. =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2012-2014, Peter Sergeant; Licensed under the same terms as Perl =cut # See App::pherkin for documentation use strict; use warnings; use App::pherkin; BEGIN { if ( not -t STDOUT and not defined $ENV{'ANSI_COLORS_DISABLED'} ) { $ENV{'ANSI_COLORS_DISABLED'} = 1; } } my $result = App::pherkin->new()->run(@ARGV); exit( $result->result eq 'failing' ); Makefile.PL100644000765000024 375412656770421 17373 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.039. use strict; use warnings; use 5.008; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Feature-complete Cucumber-style testing in Perl", "AUTHOR" => "['Peter Sergeant ','Ben Rogers ']", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Test-BDD-Cucumber", "EXE_FILES" => [ "bin/pherkin" ], "LICENSE" => "perl", "MIN_PERL_VERSION" => "5.008", "NAME" => "Test::BDD::Cucumber", "PREREQ_PM" => { "Clone" => 0, "Devel::Refcount" => 0, "Digest::SHA" => 0, "File::Find::Rule" => 0, "File::Slurp" => "9999.16", "Getopt::Long" => 0, "IO::Scalar" => 0, "JSON::MaybeXS" => "1.001000", "List::MoreUtils" => 0, "List::Util" => 0, "Module::Runtime" => 0, "Moose" => 0, "Number::Range" => 0, "Path::Class" => 0, "Term::ANSIColor" => "3.00", "Test::Builder" => 0, "Test::Differences" => 0, "Test::Exception" => 0, "Test::More" => 0, "YAML::Syck" => 0 }, "VERSION" => "0.45", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Clone" => 0, "Devel::Refcount" => 0, "Digest::SHA" => 0, "File::Find::Rule" => 0, "File::Slurp" => "9999.16", "Getopt::Long" => 0, "IO::Scalar" => 0, "JSON::MaybeXS" => "1.001000", "List::MoreUtils" => 0, "List::Util" => 0, "Module::Runtime" => 0, "Moose" => 0, "Number::Range" => 0, "Path::Class" => 0, "Term::ANSIColor" => "3.00", "Test::Builder" => 0, "Test::Differences" => 0, "Test::Exception" => 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); t000755000765000024 012656770421 15513 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45250_i18n_json.t100644000765000024 30012656770421 20207 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::I18n qw(languages); my @languages = languages(); ok scalar @languages, 'languages can be retrieved'; done_testing; App000755000765000024 012656770421 16536 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/libpherkin.pm100644000765000024 3105212656770421 20715 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Apppackage App::pherkin; $App::pherkin::VERSION = '0.45'; use strict; use warnings; use lib; use Getopt::Long; use Module::Runtime qw(use_module); use List::Util qw(max); use Pod::Usage; use FindBin qw($RealBin $Script); use YAML::Syck; use Data::Dumper; use Path::Class qw/file dir/; use Test::BDD::Cucumber::I18n qw(languages langdef readable_keywords keyword_to_subname); use Test::BDD::Cucumber::Loader; use Moose; has 'step_paths' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } ); has 'extensions' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } ); has 'tags' => ( is => 'rw', isa => 'ArrayRef', required => 0 ); has 'tag_scheme' => ( is => 'rw', isa => 'ArrayRef', required => 0 ); has 'harness' => ( is => 'rw' ); =head1 NAME App::pherkin - Run Cucumber tests from the command line =head1 VERSION version 0.45 =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. =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 ) = @_; # localized features will have utf8 in them and options may output utf8 as well binmode STDOUT, ':utf8'; my ($features_path) = $self->_process_arguments(@arguments); $features_path ||= './features/'; my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load( $features_path, $self->tag_scheme ); die "No feature files found in $features_path" unless @features; $executor->add_extensions($_) for @{ $self->extensions }; Test::BDD::Cucumber::Loader->load_steps( $executor, $_ ) for @{ $self->step_paths }; return $self->_run_tests( $executor, @features ); } sub _run_tests { my ( $self, $executor, @features ) = @_; my $harness = $self->harness; $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 _initialize_harness { my ( $self, $harness_module ) = @_; unless ( $harness_module =~ m/::/ ) { $harness_module = "Test::BDD::Cucumber::Harness::" . $harness_module; } eval { use_module($harness_module) } || die "Unable to load harness [$harness_module]: $@"; $self->harness( $harness_module->new() ); } sub _find_config_file { my ( $self, $config_filename, $debug ) = @_; return $config_filename if $config_filename; for ( ( $ENV{'PHERKIN_CONFIG'} || () ), # Allow .yaml or .yml for all of the below map { ( "$_.yaml", "$_.yml" ) } ( # Relative locations ( map { file($_) } qw!.pherkin config/pherkin ./.config/pherkin t/.pherkin! ), # Home locations ( map { dir($_)->file('.pherkin') } grep { $_ } map { $ENV{$_} } qw/HOME USERPROFILE/ ) ) ) { return $_ if -f $_; print "No config file found in $_\n" if $debug; } return undef; } sub _load_config { my ( $self, $profile_name, $proposed_config_filename, $debug ) = @_; my $config_filename = $self->_find_config_file( $proposed_config_filename, $debug ); my $config_data_whole; # Check we can actually load some data from that file if required if ($config_filename) { print "Found [$config_filename], reading...\n" if $debug; $config_data_whole = LoadFile($config_filename); } else { if ($profile_name) { print "No configuration files found\n" if $debug; die "Profile name [$profile_name] specified, but no configuration file found (use --debug-profiles to debug)"; } else { print "No configuration files found, and no profile specified\n" if $debug; return; } } $profile_name //= 'default'; # Check the config file has the right type of data at the profile name unless ( ref $config_data_whole eq 'HASH' ) { die "Config file [$config_filename] doesn't return a hashref on parse, instead a [" . ref($config_data_whole) . ']'; } my $config_data = $config_data_whole->{$profile_name}; my $profile_problem = sub { return "Config file [$config_filename] profile [$profile_name]: " . shift(); }; unless ($config_data) { die $profile_problem->("Profile not found"); } unless ( ( my $reftype = ref $config_data ) eq 'HASH' ) { die $profile_problem->("[$reftype] but needs to be a HASH"); } print "Using profile [$profile_name]\n" if $debug; # Transform it in to an argument list my @arguments; for my $key ( sort keys %$config_data ) { my $value = $config_data->{$key}; if ( my $reftype = ref $value ) { die $profile_problem->( "Option $key is a [$reftype] but can only be a single value or ARRAY" ) unless $reftype eq 'ARRAY'; push( @arguments, $key, $_ ) for @$value; } else { push( @arguments, $key, $value ); } } if ($debug) { print "Arguments to add: " . ( join ' ', @arguments ) . "\n"; } return @arguments; } sub _process_arguments { my ( $self, @args ) = @_; local @ARGV = @args; # Allow -Ilib, -bl Getopt::Long::Configure( 'bundling', 'pass_through' ); my %options = ( # Relating to other configuration options config => ['g|config=s'], profile => ['p|profile=s'], debug_profiles => ['debug-profiles'], # Standard help => ['h|help|?'], includes => [ 'I=s@', [] ], lib => ['l|lib'], blib => ['b|blib'], output => ['o|output=s'], steps => [ 's|steps=s@', [] ], tags => [ 't|tags=s@', [] ], i18n => ['i18n=s'], extensions => [ 'e|extensions=s@', [] ], ); GetOptions( map { my $x; $_->[1] //= \$x; ( $_->[0] => $_->[1] ); } values %options ); my $deref = sub { my $key = shift; my $value = $options{$key}->[1]; return ( ref $value eq 'ARRAY' ) ? $value : $$value; }; pod2usage( -verbose => 1, -input => "$RealBin/$Script", ) if $deref->('help'); # Load the configuration file my @configuration_options = $self->_load_config( map { $deref->($_) } qw/profile config debug_profiles/ ); # Merge those configuration items # First we need a list of matching keys my %keys = map { my ( $key_basis, $ref ) = @$_; map { $_ => $ref } map { s/=.+//; $_ } split( /\|/, $key_basis ); } values %options; # Now let's go through each option. For arrays, we want the configuration # options to appear in order at the front. So if configuration had 1, 2, # and command line options were 3, 4, we want: 1, 2, 3, 4. This is not # straight forward. my %additions; while (@configuration_options) { my ($key) = shift(@configuration_options); my ($value) = shift(@configuration_options); my $target = $keys{$key} || die "Unknown configuration option [$key]"; if ( ref $target ne 'ARRAY' ) { # Only use it if we don't have something already if ( defined $$target ) { print "Ignoring $key from config file because set on cmd line as $$target\n" if $deref->('debug_profiles'); } else { $$target = $value; print "Set $key to $target from config file\n" if $deref->('debug_profiles'); } } else { my $array = $additions{ 0 + $target } ||= []; push( @$array, $value ); print "Adding $value near the front of $key\n" if $deref->('debug_profiles'); } } for my $target ( values %options ) { next unless ref $target->[1] eq 'ARRAY'; my $key = $target->[1] + 0; unshift( @{ $target->[1] }, @{ $additions{$key} || [] } ); } if ( $deref->('debug_profiles') ) { print "Values are:\n"; for ( sort keys %options ) { printf( " %16s: ", $_ ); my $value = $deref->($_); if ( ref $value ) { print join ', ', @$value; } else { print $value // '[undefined]'; } print "\n"; } exit; } if ( my $i18n = $deref->('i18n') ) { _print_langdef($i18n) unless $i18n eq 'help'; _print_languages(); } unshift @{ $deref->('includes') }, 'lib' if $deref->('lib'); unshift @{ $deref->('includes') }, 'blib/lib', 'blib/arch' if $deref->('blib'); # We may need some of the imported paths... lib->import( @{ $deref->('includes') } ); # Load any extensions for my $e ( @{ $deref->('extensions') } ) { my $e_args = "()"; $e_args = $1 if $e =~ s/\((.+)\)$//; my @e_args = eval $e_args; die "Bad arguments in [$e]: $@" if $@; use_module $e; push( @{ $self->extensions }, $e->new(@e_args) ); } # Munge the output harness $self->_initialize_harness( $deref->('output') || "TermColor" ); # Store any extra step paths $self->step_paths( $deref->('steps') ); # Store our TagSpecScheme $self->tag_scheme( $self->_process_tags( @{ $deref->('tags') } ) ); return ( pop @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; } sub _print_languages { my @languages = languages(); my $max_code_length = max map { length } @languages; my $max_name_length = max map { length( langdef($_)->{name} ) } @languages; my $max_native_length = max map { length( langdef($_)->{native} ) } @languages; my $format = "| %-${max_code_length}s | %-${max_name_length}s | %-${max_native_length}s |\n"; for my $language ( sort @languages ) { my $langdef = langdef($language); printf $format, $language, $langdef->{name}, $langdef->{native}; } exit; } sub _print_langdef { my ($language) = @_; my $langdef = langdef($language); my @keywords = qw(feature background scenario scenario_outline examples given when then and but); my $max_length = max map { length readable_keywords( $langdef->{$_} ) } @keywords; my $format = "| %-16s | %-${max_length}s |\n"; for my $keyword ( qw(feature background scenario scenario_outline examples given when then and but ) ) { printf $format, $keyword, readable_keywords( $langdef->{$keyword} ); } my $codeformat = "| %-16s | %-${max_length}s |\n"; for my $keyword (qw(given when then )) { printf $codeformat, $keyword . ' (code)', readable_keywords( $langdef->{$keyword}, \&keyword_to_subname ); } exit; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 220_tag_parsing.t100644000765000024 310712656770421 20722 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!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(); 310_auto_corpus.t100755000765000024 133412656770421 20772 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use lib 't/lib/'; 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(); examples000755000765000024 012656770421 17066 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45pherkin.yml100644000765000024 43012656770421 21366 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples# `man pherkin`, "CONFIGURATION PROFILES" default: steps: - /usr/share/perl/5.14.2/Test/BDD/Plugin/steps - ~/your-project/steps o: TermColor tags: - tag1,tag2,tag3 # Note: this file is being used by the test suite; # be sure to run tests after modifying230_tag_matching.t100644000765000024 265112656770421 21055 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!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(); script000755000765000024 012656770421 16554 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45make_corpus.pl100755000765000024 63512656770421 21550 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/script#!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); 260_match_matcher.t100644000765000024 364512656770421 21236 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; use Test::Differences; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Harness::Data; # Check that when we execute steps we get a nicely split string back for # highlighting for ( [ "Simple example", "the quick brown fox", qr/the (quick) brown (fox)/, [ [ 0 => 'the ' ], [ 1 => 'quick' ], [ 0 => ' brown ' ], [ 1 => 'fox' ], ] ], [ "Non-capture", "the quick brown fox", qr/the (?:quick) brown (fox)/, [ [ 0 => 'the quick brown ' ], [ 1 => 'fox' ], ] ], [ "Nested-capture", "the quick brown fox", qr/the (q(uic)k) brown (fox)/, [ [ 0 => 'the ' ], [ 1 => 'quick' ], [ 0 => ' brown ' ], [ 1 => 'fox' ], ] ], [ "Multi-group", "the quick brown fox", qr/the (.)+ brown (fox)/, [ [ 0 => 'the quic' ], [ 1 => 'k' ], [ 0 => ' brown ' ], [ 1 => 'fox' ], ] ], ) { my ( $test_name, $step_text, $step_re, $expected ) = @$_; # Set up a feature my $feature = Test::BDD::Cucumber::Parser->parse_string( "Feature: Foo\n\tScenario:\n\t\tGiven $step_text\n"); # Set up step definitions my $executor = Test::BDD::Cucumber::Executor->new(); $executor->add_steps( [ Given => $step_re, sub { 1; } ], ); # Instantiate the harness, and run it my $harness = Test::BDD::Cucumber::Harness::Data->new(); $executor->execute( $feature, $harness ); # Get the step result my $step = $harness->features->[0]->{'scenarios'}->[0]->{'steps'}->[0]; my $highlights = $step->{'highlights'}; is_deeply( $highlights, $expected, $test_name ) || eq_or_diff( $highlights, $expected ); } done_testing(); 700_tag_processing.t100644000765000024 245112656770421 21437 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; 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(); release-pod-syntax.t100644000765000024 45612656770421 21551 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use Test::More; use Test::Pod 1.41; all_pod_files_ok(); 500_error_formatter.t100644000765000024 737112656770421 21650 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Errors qw/parse_error_from_line/; my $feature = Test::BDD::Cucumber::Parser->parse_string( join '', () ); # Test different offsets my @tests = ( [ 0 => 1, "# 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", "", " Background:", ], [ 1 => 1, "# 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", "", " Background:", ], [ 2 => 1, "# 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", "", " Background:", ], [ 4 => 2, "Feature: Simple tests of Digest.pm", " As a developer planning to use Digest.pm", "", " Background:", ' Given a usable "Digest" class', ], [ 10 => 8, ' 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"', ], [ 12 => 9, ' 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"', ], [ 13 => 9, ' 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"', ], [ 14 => 9, ' 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"', ], ); for ( @tests ) { my ( $offset, $expected_offset, @lines ) = @$_; is_deeply( [ Test::BDD::Cucumber::Errors::_get_context_range( $feature->document, $offset ) ], [ $expected_offset, @lines ], "Offset $offset works" ) } my $make_error = parse_error_from_line( "Foo bar baz", $feature->document->lines->[1] ); is( $make_error, "-- Parse Error -- Foo bar baz at [(no filename)] line 2 thrown by: [t/500_error_formatter.t] line 95 -- [(no filename)] -- 1| # Somehow I don't see this replacing the other tests this module has... 2* Feature: Simple tests of Digest.pm 3| As a developer planning to use Digest.pm 4| "." 5| Background: --------------------- ", "Error example matches" ); done_testing(); __DATA__ # 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 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"720_extension_tests.t100644000765000024 436412656770421 21675 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use lib 't/lib'; use Test::More; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Harness::Data; use Test::CucumberExtensionCount; use Test::CucumberExtensionPush; my $executor = Test::BDD::Cucumber::Executor->new(); $executor->add_extensions( 1, 2 ); ok( scalar( @{ $executor->extensions } ) == 2, "Two extensions added" ); my $feature = Test::BDD::Cucumber::Parser->parse_string( <new(); $executor = Test::BDD::Cucumber::Executor->new(); $executor->add_steps( [ Given => qr/a passing step called '(.+)'/, sub { 1; } ], ); $executor->add_extensions($extension); my $harness = Test::BDD::Cucumber::Harness::Data->new(); $executor->execute( $feature, $harness ); is_deeply( $extension->counts, { pre_feature => 1, post_feature => 1, pre_scenario => 1, post_scenario => 1, pre_step => 2, # background step and scenario step post_step => 2, }, "Simple example: hooks called the expected number of times" ); # test nesting/unrolling of multiple extensions my $hash = {}; $executor = Test::BDD::Cucumber::Executor->new(); $executor->add_steps( [ Given => qr/a passing step called '(.+)'/, sub { 1; } ], ); $executor->add_extensions( Test::CucumberExtensionPush->new( id => 2, hash => $hash ), Test::CucumberExtensionPush->new( id => 3, hash => $hash ), ); $executor->add_extensions( Test::CucumberExtensionPush->new( id => 1, hash => $hash ), ); $harness = Test::BDD::Cucumber::Harness::Data->new(); $executor->execute( $feature, $harness ); for ( [ pre_feature => [ 1, 2, 3 ] ], [ post_feature => [ 3, 2, 1 ] ], [ pre_scenario => [ 1, 2, 3 ] ], [ post_scenario => [ 3, 2, 1 ] ], [ pre_step => [ 1, 2, 3, 1, 2, 3 ] ], # background step and scenario step [ post_step => [ 3, 2, 1, 3, 2, 1 ] ], ) { my ( $hook, $expected ) = @$_; is_deeply( $hash->{$hook}, $expected, "Ordered example: $hook in right order" ); } done_testing(); BDD000755000765000024 012656770421 17326 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/TestCucumber.pm100644000765000024 425212656770421 21574 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDDpackage Test::BDD::Cucumber; $Test::BDD::Cucumber::VERSION = '0.45'; use strict; use warnings; 1; # CODE ENDS =head1 NAME Test::BDD::Cucumber - Feature-complete Cucumber-style testing in Perl =head1 VERSION version 0.45 =head1 DESCRIPTION A sane and complete Cucumber implementation in Perl =head1 QUICK LINKS L =head1 WARNING Do have a read of the B section below so you're not surprised when 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. =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 * 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 * Pherkin isn't really fit for purpose yet =back =head1 CODE On Github, of course: L. =head1 AUTHORS Peter Sergeant C Ben Rodgers C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 270_scenario_outlines.t100644000765000024 217212656770421 22157 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use utf8; use Test::More; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Harness::Data; # If you've taken the time to explicitly declare a Scenario Outline in any # language, you need to have provided examples my $feature = eval { Test::BDD::Cucumber::Parser->parse_string( <' ชุดของตัวอย่าง: | name | | 1 | สรุปเหตุการณ์: กำหนดให้ a passing step called 'bar' กำหนดให้ a passing step called '' HEREDOC ); }; my $error = $@; ok( $error, "A parsing error was caught" ); like( $error, qr/Outline scenario expects/, "Error is about outline scenario" ); like( $error, qr/12\*/, "Error identifies correct start line" ); done_testing(); Test000755000765000024 012656770421 17200 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/libDumpFeature.pm100644000765000024 166312656770421 22125 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/lib/Testpackage 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; 240_localized_features.t100644000765000024 102412656770421 22266 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Parser; my $files = { en => 'examples/calculator/features/basic.feature', es => 'examples/calculator/features/basic.feature.es' }; for my $language ( keys %$files ) { my $feature = Test::BDD::Cucumber::Parser->parse_file( $files->{$language} ); isa_ok $feature, 'Test::BDD::Cucumber::Model::Feature', "feature in language '$language' can be parsed"; is $feature->language, $language, 'feature language'; } done_testing; 900_run_cucumber_tests.t100644000765000024 105412656770421 22343 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; 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; 210_background_sections.t100644000765000024 435512656770421 22457 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!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(); 600_harness_json_output.t100644000765000024 1500712656770421 22564 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; use IO::Scalar; use IO::Handle; use Path::Class; use JSON::MaybeXS 'decode_json'; use Test::BDD::Cucumber::Harness::JSON; use Test::BDD::Cucumber::Loader; my $DIGEST_DIR = dir(qw/ examples tagged-digest /); my $DIGEST_FEATURE_FILE = $DIGEST_DIR->file(qw/ features basic.feature /); my $DIGEST_FEATURE_FILE_RE = quotemeta($DIGEST_FEATURE_FILE); sub get_line_number { my ( $filename, $regexp ) = @_; my $fh = IO::Handle->new; open $fh, "<", $filename; while ( my $line = <$fh> ) { return $fh->input_line_number if $line =~ $regexp; } } # Run tests sub run_tests { my $json_data = ""; my $fh = new IO::Scalar \$json_data; my $harness = Test::BDD::Cucumber::Harness::JSON->new( fh => $fh ); for my $directory ( $DIGEST_DIR, 't/harness_json' ) { my ( $executor, @features ) = Test::BDD::Cucumber::Loader->load($directory); die "No features found in $directory" unless @features; $executor->execute( $_, $harness ) for @features; } $harness->shutdown(); $fh->close; return $json_data; } my $json_data = run_tests(); # Load & Check JSON output my $parsed_json = decode_json($json_data); is( ref($parsed_json), 'ARRAY', 'json file contains list of features' ); # Second run results my $second_run_json = decode_json( run_tests() ); # Test list of features my @json_features = @$parsed_json; is( scalar(@json_features), 2, "number of features matches" ); is_deeply( [ map { $_->{name} } @json_features ], [ "Simple tests of Digest.pm", "My mock feature" ], "Feature names match" ); # Test feature attributes my %json_feature = %{ $parsed_json->[0] }; is( $json_feature{keyword}, 'Feature', 'feature keyword' ); is( $json_feature{name}, 'Simple tests of Digest.pm', 'feature name' ); like( $json_feature{id}, qr{$DIGEST_FEATURE_FILE_RE:\d+$}, 'feature id matches a line in ' . $DIGEST_FEATURE_FILE ); is( $json_feature{id}, $second_run_json->[0]{id}, "Feature ID is stable" ); is( $json_feature{uri}, $DIGEST_FEATURE_FILE, 'feature uri' ); is( $json_feature{line}, get_line_number( $json_feature{uri}, 'Feature: Simple tests of Digest.pm' ), 'line number in .feature file' ); is( $json_feature{description}, "As a developer planning to use Digest.pm\n" . "I want to test the basic functionality of Digest.pm\n" . "In order to have confidence in it", 'feature description' ); is_deeply( $json_feature{tags}, [ { name => '@digest' } ], "feature tags" ); is( ref( $json_feature{elements} ), 'ARRAY', "feature has list of scenarios" ); # Test list of scenarios in feature my @feature_elements = @{ $json_feature{elements} }; is_deeply( [ map { $_->{name} } @feature_elements ], [ "Check MD5", ("Check SHA-1") x 3, # nr of examples "MD5 longer data" ], "Feature elements names match" ); # Test SHA-1 scenario attributes including second example line my %json_scenario = %{ $json_feature{elements}[2] }; is( $json_scenario{keyword}, 'Scenario', 'scenario keyword' ); is( $json_scenario{name}, 'Check SHA-1', 'scenario name' ); like( $json_scenario{id}, qr{^$DIGEST_FEATURE_FILE_RE:\d+$}, 'scenario id matches a line in ' . $DIGEST_FEATURE_FILE ); is( $json_scenario{id}, $second_run_json->[0]{elements}[2]{id}, "Scenario ID is stable" ); is( $json_scenario{line}, get_line_number( $json_feature{uri}, 'Scenario: Check SHA-1' ), 'scenario line' ); is( $json_scenario{description}, undef, "scenario description" ); is_deeply( $json_scenario{tags}, [ { name => '@digest' }, { name => '@sha1' }, ], "scenario tags" ); is( ref( $json_scenario{steps} ), 'ARRAY', "scenario has list of steps" ); # Test list of step in scenario my @json_steps = @{ $json_scenario{steps} }; is_deeply( [ map { $_->{name} } @json_steps ], [ q{a usable "Digest" class}, # Background q{a Digest SHA-1 object}, # Given q{I've added "bar" to the object}, # When q{the hex output is "62cdb7020ff920e5aa642c3d4066950dd1f01f4d"} # Then ], "Scenatio steps names match" ); # Test successful step attributes my %success_step = %{ $json_scenario{steps}[2] }; is( $success_step{keyword}, 'When', 'step keyword' ); is( $success_step{name}, q{I've added "bar" to the object}, "step name" ); is( $success_step{line}, get_line_number( $DIGEST_FEATURE_FILE, q{I've added "" to the object} ), "step line number" ); is( ref( $success_step{result} ), 'HASH', 'step has result' ); is( $success_step{result}{status}, 'passed', 'success step result status' ); like( $success_step{result}{duration}, qr/^\d+$/, 'duration in result' ); # Test failed step my %failed_scenario = %{ $parsed_json->[1]->{elements}->[0] }; is( $failed_scenario{name}, 'mock failing test' ); is( $failed_scenario{steps}[2]{name}, 'number of items is "1"' ); my $failed_result = $failed_scenario{steps}[2]{result}; is( $failed_result->{status}, 'failed', 'failed result status' ); like( $failed_result->{error_message}, qr/ got:[ ]'4' .* expected:[ ]'1' /xms, 'failed error message' ); # Test skipped step my %skipped_scenario = %{ $parsed_json->[1]->{elements}->[1] }; is( $skipped_scenario{name}, 'mock failing test' ); is( $skipped_scenario{steps}[2]{name}, 'number of items is "3"' ); my $skipped_result = $skipped_scenario{steps}[2]{result}; is( $skipped_result->{status}, 'pending', 'skipped result status' ); like( $skipped_result->{error_message}, qr/SKIP Short-circuited from previous tests/, 'skipped error message' ); # Test pending(TODO) step result my %todo_scenario = %{ $parsed_json->[1]->{elements}->[2] }; is( $todo_scenario{name}, 'mock pending test' ); is( $todo_scenario{steps}[0]{name}, 'that we receive list of items from server' ); my $todo_result = $todo_scenario{steps}[0]{result}; is( $todo_result->{status}, 'pending', 'pending result status' ); like( $todo_result->{error_message}, qr/mock TODO message/, 'pending(TODO) error message' ); # Test missing step my %missed_scenario = %{ $parsed_json->[1]->{elements}->[3] }; is( $missed_scenario{name}, 'mock missing step definition' ); is( $missed_scenario{steps}[0]{name}, 'that this step is missing' ); my $missed_result = $missed_scenario{steps}[0]{result}; is( $missed_result->{status}, 'skipped', 'missed result status' ); like( $missed_result->{error_message}, qr/No matching step definition for/, 'missed error message' ); done_testing; harness_json000755000765000024 012656770421 20207 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/tmock.feature100644000765000024 101512656770421 22652 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/harness_jsonFeature: My mock feature Scenario: mock failing test Given that we have list of items="" When calculate count Then number of items is "" Examples: | items | count | | 2,-1,4,55 | 1 | | 0,-22,33 | 3 | Scenario: mock pending test Given that we receive list of items from server When calculate count Then summary is "55" Scenario: mock missing step definition Given that this step is missing When calculate count Then summary is "55" old000755000765000024 012656770421 16271 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t900_run_features.t.old100644000765000024 107112656770421 22464 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/old#!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;Cucumber000755000765000024 012656770421 21073 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDDI18n.pm100644000765000024 567612656770421 22326 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::I18n; $Test::BDD::Cucumber::I18n::VERSION = '0.45'; =encoding utf8 =head1 NAME Test::BDD::Cucumber::I18N - Internationalization =head1 VERSION version 0.45 =head1 DESCRIPTION Internationalization of feature files and step definitions. =head1 SYNOPSIS use Test::BDD::Cucumber::I18n qw(languages has_language langdef); # get codes of supported languages my @supported_languages = languages(); # look up if a language is supported my $language_is_supported = has_language('de'); # get definition of a language my $langdef = langdef('de'); # get readable keyword definitions my $string = readable_keywords =cut use strict; use warnings; use base 'Exporter'; our @EXPORT_OK = qw(languages langdef has_language readable_keywords keyword_to_subname); use Test::BDD::Cucumber::I18N::Data; my $langdefs = _initialize_language_definitions_from_shared_json_file(); sub _initialize_language_definitions_from_shared_json_file { # Parse keywords hash for all supported languages from the JSON file my $langdefs = Test::BDD::Cucumber::I18N::Data::language_definitions(); # strip asterisks from the keyword definitions since they don't work yet for my $language ( keys %$langdefs ) { my $langdef = $langdefs->{$language}; for my $key ( keys %$langdef ) { $langdef->{$key} =~ s{\Q*|\E}{}; } } return $langdefs; } =head1 METHODS =head2 languages Get codes of supported languages. =cut sub languages { return keys %$langdefs; } =head2 has_language($language) Check if a language is supported. Takes as argument the language abbreviation defined in C. =cut sub has_language { my ($language) = @_; exists $langdefs->{$language}; } =head2 langdef($language) Get definition of a language. Takes as argument the language abbreviation defined in C. =cut sub langdef { my ($language) = @_; return unless has_language($language); return $langdefs->{$language}; } =head2 readable_keywords($string, $transform) Get readable keyword definitions. =cut sub readable_keywords { my ( $string, $transform ) = @_; my @keywords = split( /\|/, $string ); @keywords = map { $transform->($_) } @keywords if $transform; return join( ', ', map { '"' . $_ . '"' } @keywords ); } =head2 keyword_to_subname Return a keyword into a subname with non-word characters removed. =cut sub keyword_to_subname { my ($word) = @_; # remove non-word characters so we have a decent sub name $word =~ s{[^\p{Word}]}{}g; return $word; } =head1 LANGUAGES Languages are defined in a JSON-based hash in the __DATA__ section of L, and have been lifted from the Gherkin distribution. =head1 AUTHOR Gregor Goldbach C (based on the works of Pablo Duboue) =head1 LICENSE Copyright 2014, Gregor Goldbach; Licensed under the same terms as Perl =cut 1; Util.pm100644000765000024 221412656770421 22505 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Util; $Test::BDD::Cucumber::Util::VERSION = '0.45'; use strict; use warnings; =head1 NAME Test::BDD::Cucumber::Util - Some functions used throughout the code =head1 VERSION version 0.45 =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 400_app_pherkin_harnesses.t100644000765000024 132712656770421 23001 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::BDD::Cucumber::Executor; use Test::More; my @known_harnesses = ( "Data", # Short form "Test::BDD::Cucumber::Harness::TermColor", # Long form "Test::BDD::Cucumber::Harness::TestBuilder", "Test::BDD::Cucumber::Harness::JSON" ); use_ok("App::pherkin"); for my $harness (@known_harnesses) { my $app = App::pherkin->new(); my $object = $app->_initialize_harness($harness); isa_ok( $object, "Test::BDD::Cucumber::Harness", "Loaded harness by name: [$harness] -> [" . ( ref $object ) . "]" ); is( $app->harness, $object, "It is set to app->harness [$harness]" ); } done_testing(); 710_configuration_profiles.t100644000765000024 432112656770421 23201 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use lib 't/lib'; use Path::Class qw/dir/; use Test::More; use App::pherkin; use Test::Exception; my $class = "App::pherkin"; # Check _find_config_file is( $class->_find_config_file('foobar'), 'foobar', "_find_config_file passes filename through" ); { local $ENV{'PHERKIN_CONFIG'} = $0; is( $class->_find_config_file(), $0, "_find_config_file checks \$ENV{'PHERKIN_CONFIG'}" ); } # Various poorly-formed files or configs my $dir = dir('t/pherkin_config_files'); for ( [ 'not_yaml.yaml', default => qr/syntax error/ ], [ 'top_level_array.yaml', default => qr/hashref on parse, instead a \[ARRAY\]/ ], [ 'readable.yaml', arrayref => qr/\[ARRAY\] but needs to be a HASH/ ], [ 'readable.yaml', hashoption => qr/Option foo is a \[HASH\]/ ], [ 'readable.yaml', missing => qr/Profile not found/ ], ) { my ( $filename, $profile_name, $expecting ) = @$_; throws_ok { $class->_load_config( $profile_name, $dir->file($filename) ) } $expecting, "Loading $filename / $profile_name caught"; } # We can read a known-good config is_deeply( [ $class->_load_config( readable => $dir->file('readable.yaml') ) ], [ f => 1, f => 2 ], "readable/readable read OK" ); is_deeply( [ $class->_load_config( undef, $dir->file('readable.yaml') ) ], [ bar => 'baz', foo => 'bar' ], "readable/[default] read OK" ); # Empty pass-through is_deeply( [ $class->_load_config( undef, undef ) ], [], "Empty configuration passed through" ); my $p = App::pherkin->new(); $p->_process_arguments( '-g', $dir->file('readable.yaml'), '-p' => 'ehuelsmann', '--steps' => '3', '--steps' => '4', '-o' => 'Data', '-e' => 'Test::CucumberExtensionPush({ id => 2, hash => {}})', '--extensions' => 'Test::CucumberExtensionPush({ id => 3, hash => {}})', ); isa_ok( $p->harness, 'Test::BDD::Cucumber::Harness::Data', 'Harness set' ); is_deeply( $p->{'step_paths'}, [ 1, 2, 3, 4 ], 'Step paths set' ); is( $p->extensions->[0]->id, 1, "Cmdline extension 1" ); is( $p->extensions->[1]->id, 2, "Cmdline extension 2" ); is( $p->extensions->[2]->id, 3, "Config extension 3" ); done_testing(); Errors.pm100644000765000024 641612656770421 23054 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Errors; $Test::BDD::Cucumber::Errors::VERSION = '0.45'; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(parse_error_from_line); =head1 NAME Test::BDD::Cucumber::Errors - Consistently formatted errors =head1 VERSION version 0.45 =head1 DESCRIPTION Consistently formatted errors =head1 NOTE This module is not intended to help throw error classes, simply to provide helpers for consistently formatting certain errors. Most of the errors thrown in practice are errors with the input test scenarios, and it's helpful to have the location of the error and context when debugging those. Perhaps in the future these can return error objects. All current uses (2016-02-09) just pass the results straight to die, so I have decided to UTF8 encode the error message on the basis that this probably constitutes an application boundary. =head1 SYNOPSIS use Test::BDD::Cucumber::Errors qw/parse_error_from_line/; parse_error_from_line( "Your input was bad", $line ); =head1 PARSER ERRORS =head2 parse_error_from_line Generates a parser error from a L object, and error reason: parse_error_from_line( "Your input was bad", $line ); =cut sub parse_error_from_line { my ( $message, $line ) = @_; my $error = "-- Parse Error --\n\n $message\n"; $error .= " at [%s] line %d\n"; $error .= " thrown by: [%s] line %d\n\n"; $error .= "-- [%s] --\n\n"; $error .= "%s"; $error .= "\n%s\n"; # Get the caller data my ( $caller_filename, $caller_line ) = ( caller() )[ 1, 2 ]; # Get the simplistic filename and line number it occurred on my $feature_filename = $line->document->filename || "(no filename)"; my $feature_line = $line->number; # Get the context lines my ( $start_line, @lines ) = _get_context_range( $line->document, $feature_line ); my $formatted_lines; for ( 0 .. $#lines ) { my $actual_line = $start_line + $_; my $mark = ( $feature_line == $actual_line ) ? '*' : '|'; $formatted_lines .= sprintf( "% 3d%s %s\n", $actual_line, $mark, $lines[$_] ); } my $to_return = sprintf( $error, $feature_filename, $feature_line, $caller_filename, $caller_line, $feature_filename, $formatted_lines, ( '-' x ( ( length $feature_filename ) + 8 ) ) ); utf8::encode($to_return); return $to_return; } sub _get_context_range { my ( $document, $number ) = @_; # Context range my $min_range = 1; my $max_range = ( scalar @{ $document->lines } ); my @range = ( $number - 2, $number - 1, $number, $number + 1, $number + 2 ); # Push the range higher if needed while ( $range[0] < $min_range ) { @range = map { $_ + 1 } @range; } # Push the range lower if needed while ( $range[4] > $max_range ) { @range = map { $_ - 1 } @range; } # Then cut it off @range = grep { $_ >= $min_range } @range; @range = grep { $_ <= $max_range } @range; return ( $range[0], map { $document->lines->[ $_ - 1 ]->raw_content } @range ); } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Loader.pm100644000765000024 420312656770421 22776 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Loader; $Test::BDD::Cucumber::Loader::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Loader - Simplify loading of Step Definition and feature files =head1 VERSION version 0.45 =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. =head2 load_steps Accepts an L object and a string representing either a step file, or a directory containing zero or more C<*_steps.pl> files, and loads the steps in to the executor; if you've used C we'll have already scanned the feature directory for C<*_steps.pl> files. =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 $class->load_steps( $executor, $dir ); # Grab the feature files my @features = map { my $file = 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 ); } sub load_steps { my ( $class, $executor, $path ) = @_; if ( -f $path ) { $executor->add_steps( Test::BDD::Cucumber::StepFile->load($path) ); } else { $executor->add_steps( Test::BDD::Cucumber::StepFile->load($_) ) for File::Find::Rule->file()->name('*_steps.pl')->in($path); } return $class; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Parser.pm100644000765000024 2450012656770421 23046 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Parser; $Test::BDD::Cucumber::Parser::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Parser - Parse Feature files =head1 VERSION version 0.45 =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 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; use Test::BDD::Cucumber::I18n qw(langdef); use Test::BDD::Cucumber::Errors qw/parse_error_from_line/; # https://github.com/cucumber/cucumber/wiki/Multiline-Step-Arguments # https://github.com/cucumber/cucumber/wiki/Scenario-outlines sub parse_string { my ( $class, $string, $tag_scheme ) = @_; return $class->_construct( Test::BDD::Cucumber::Model::Document->new( { content => $string } ), $tag_scheme ); } sub parse_file { my ( $class, $string, $tag_scheme ) = @_; return $class->_construct( Test::BDD::Cucumber::Model::Document->new( { content => scalar( read_file( $string, { binmode => ':utf8' } ) ), filename => '' . $string } ), $tag_scheme ); } sub _construct { my ( $class, $document, $tag_scheme ) = @_; my $feature = Test::BDD::Cucumber::Model::Feature->new( { document => $document } ); my @lines = $class->_remove_next_blanks( @{ $document->lines } ); $feature->language( $class->_extract_language( \@lines ) ); my $self = { langdef => langdef( $feature->language ) }; bless $self, $class; $self->_extract_scenarios( $self->_extract_conditions_of_satisfaction( $self->_extract_feature_name( $feature, @lines ) ) ); return $feature; } sub _extract_language { my ( $self, $lines ) = @_; # return default language if we don't see the language directive on the first line return 'en' unless $lines->[0]->raw_content =~ m{^\s*#\s*language:\s+(.+)$}; # remove the language directive if we saw it ... shift @$lines; # ... and return the language it declared return $1; } 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/^(?:$self->{langdef}->{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 { die parse_error_from_line( 'Malformed feature line (expecting: /^(?:' . $self->{langdef}->{feature} . '): (.+)/', $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; my $langdef = $self->{langdef}; if ( $line->content =~ m/^((?:$langdef->{background}):|(?:$langdef->{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; my $langdef = $self->{langdef}; if ( $line->content =~ m/^((?:$langdef->{background})|(?:$langdef->{scenario})|(?:$langdef->{scenario_outline})): ?(.+)?/ ) { my ( $type, $name ) = ( $1, $2 ); # Only one background section, and it must be the first if ( $scenarios++ && $type =~ m/^($langdef->{background})/ ) { die parse_error_from_line( "Background not allowed after scenarios", $line ); } # Create the scenario my $scenario = Test::BDD::Cucumber::Model::Scenario->new( { ( $name ? ( name => $name ) : () ), background => $type =~ m/^($langdef->{background})/ ? 1 : 0, line => $line, tags => [ @{ $feature->tags }, @scenario_tags ] } ); @scenario_tags = (); # Attempt to populate it @lines = $self->_extract_steps( $feature, $scenario, @lines ); # Catch Scenario outlines without examples if ( $type =~ m/^($langdef->{scenario_outline})/ && !@{ $scenario->data } ) { die parse_error_from_line( "Outline scenario expects 'Examples:' section", $line ); } if ( $type =~ m/^($langdef->{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 { die parse_error_from_line( "Malformed scenario line", $line ); } } return $feature, $self->_remove_next_blanks(@lines); } sub _extract_steps { my ( $self, $feature, $scenario, @lines ) = @_; my $langdef = $self->{langdef}; my @givens = split( /\|/, $langdef->{given} ); my $last_verb = $givens[-1]; while ( my $line = shift(@lines) ) { next if $line->is_comment; last if $line->is_blank; # Conventional step? if ( $line->content =~ m/^((?:$langdef->{given})|(?:$langdef->{and})|(?:$langdef->{when})|(?:$langdef->{then})|(?:$langdef->{but})) (.+)/ ) { my ( $verb, $text ) = ( $1, $2 ); my $original_verb = $verb; $verb = 'Given' if $verb =~ m/^($langdef->{given})$/; $verb = 'When' if $verb =~ m/^($langdef->{when})$/; $verb = 'Then' if $verb =~ m/^($langdef->{then})$/; $verb = $last_verb if $verb =~ m/^($langdef->{and})$/ or $verb =~ m/^($langdef->{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/^($langdef->{examples}):$/ ) { return $self->_extract_table( 6, $scenario, $self->_remove_next_blanks(@lines) ); } else { die parse_error_from_line( "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) { die parse_error_from_line( "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 { my $atom = $_; $atom =~ s/^\s+//; $atom =~ s/\s+$//; $atom } @atoms; } 1; =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut Harness.pm100644000765000024 766712656770421 23214 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Harness; $Test::BDD::Cucumber::Harness::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Harness - Base class for creating harnesses =head1 VERSION version 0.45 =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 and an arrayref of arrayrefs with locations of consolidated matches, for highlighting. [ [2,5], [7,9] ] =cut sub step { my ( $self, $context ) = @_; } sub step_done { my ( $self, $context, $result ) = @_; } =head2 sub_step =head2 sub_step_done As per C and C, but for steps that have been called from other steps. None of the included harnesses respond to these methods, because generally the whole thing should be transparent, and the parent step handles passes, failures, etc. =cut sub sub_step { my ( $self, $context ) = @_; } sub 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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Executor.pm100644000765000024 4642512656770421 23422 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Executor; $Test::BDD::Cucumber::Executor::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Executor - Run through Feature and Harness objects =head1 VERSION version 0.45 =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 Clone qw(clone); use List::Util qw/first/; use List::MoreUtils qw/pairwise/; use Module::Runtime qw/use_module/; use Number::Range; use Carp qw/croak/; our @CARP_NOT; use Test::Builder; # Setup wrapping for Test::Builder use Test::BDD::Cucumber::TestBuilderDelegator; use Devel::Refcount qw/refcount/; if ( ( !$ENV{'TEST_BDD_CUCUMBER_NO_TB_WRAP_TEST'} ) && refcount($Test::Builder::Test) > 1 ) { my $ref_trace = "[Install Devel::FindRef to see these diagnostics]"; if ( eval { use_module "Devel::FindRef" } ) { $ref_trace = Devel::FindRef::track($Test::Builder::Test); } my $message = sprintf( <<'END', $ref_trace ); !!! HEY YOU !!! Test::BDD::Cucumber needs to be able to wrap $Test::Builder::Test in order to properly capture testing output. However, something else has already taken a reference to that module. You need to `use` Test::BDD::Cucumber::Executor before the other testing modules are used. Modules that appear to already have a reference are: ----- %s ----- You can safetly ignore the global $Test::Builder::Test. In almost all cases, the simple fix is the move the line that says `use Test::BDD::Cucumber::Executor` above all other `use Test::*` lines. You can also suppress this check by setting: TEST_BDD_CUCUMBER_NO_TB_WRAP_TEST=1 END croak $message; } $Test::Builder::Test = Test::BDD::Cucumber::TestBuilderDelegator->new( Test::Builder->new() ); use Test::BDD::Cucumber::StepContext; use Test::BDD::Cucumber::Util; use Test::BDD::Cucumber::Model::Result; use Test::BDD::Cucumber::Errors qw/parse_error_from_line/; has '_bail_out' => ( is => 'rw', isa => 'Bool', default => 0 ); =head1 METHODS =head2 extensions =head2 add_extensions The attributes C is an arrayref of L extensions. Extensions have their hook-functions called by the Executor at specific points in the BDD feature execution. B> adds items in FIFO using unshift()>, and are called in reverse order at the end hook; this means that if you: add_extensions( 1 ); add_extensions( 2, 3 ); The C will be called in order 2, 3, 1, and C will be called in 1, 3, 2. =cut has extensions => ( is => 'ro', isa => 'ArrayRef', traits => ['Array'], default => sub { [] }, handles => { add_extensions => 'unshift' }, ); =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); } $_->pre_feature( $feature, $feature_stash ) for @{ $self->extensions }; for my $scenario (@scenarios) { # Execute the scenario itself $self->execute_scenario( { @background, scenario => $scenario, feature => $feature, feature_stash => $feature_stash, harness => $harness } ); } $_->post_feature( $feature, $feature_stash, 'no' ) for reverse @{ $self->extensions }; $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 || {}; $outline_stash->{'short_circuit'} ||= $self->_bail_out; # Multiply out Scenario Outlines as appropriate my @datasets = @{ $outline->data }; @datasets = ( {} ) unless @datasets; my $scenario_stash = $incoming_scenario_stash || {}; my %context_defaults = ( executor => $self, # Held weakly by StepContext # 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 ) { $_->pre_scenario( $outline, $feature_stash, $scenario_stash ) for @{ $self->extensions }; 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'}, 0 ); # If it didn't pass, short-circuit the rest unless ( $result->result eq 'passing' ) { $outline_stash->{'short_circuit'} = 1; } } } # 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, $step->line ); # Set up a context my $context = Test::BDD::Cucumber::StepContext->new( { %context_defaults, # Data portion columns => $step->columns || [], data => ref( $step->data ) ? clone( $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'}, 0 ); # 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, 0 ); } $_->post_scenario( $outline, $feature_stash, $scenario_stash, $outline_stash->{'short_circuit'} ) for reverse @{ $self->extensions }; } $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, $line ) = @_; my $quoted_text = Test::BDD::Cucumber::Util::bs_quote($text); $quoted_text =~ s/(<([^>]+)>)/ exists $dataset->{$2} ? $dataset->{$2} : die parse_error_from_line( "No mapping to placeholder $1", $line ) /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, and a boolean flag to denote if it's a redispatched step. =cut sub find_and_dispatch { my ( $self, $context, $short_circuit, $redispatch ) = @_; # Short-circuit if we need to return $self->skip_step( $context, 'pending', "Short-circuited from previous tests", 0 ) 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 unless ($step) { my $message = "No matching step definition for: " . $context->verb . ' ' . $context->text; my $result = $self->skip_step( $context, 'undefined', $message, $redispatch ); return $result; } $_->pre_step( $step, $context ) for @{ $self->extensions }; my $result = $self->dispatch( $context, $step, 0, $redispatch ); $_->post_step( $step, $context, $result eq 'passing' ) for reverse @{ $self->extensions }; return $result; } =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, $redispatch ) = @_; return $self->skip_step( $context, 'pending', "Short-circuited from previous tests", $redispatch ) 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 minimum pass $tb_return->{'builder'} ->ok( 1, "Starting to execute step: " . $context->text ); my $step_name = $redispatch ? 'sub_step' : 'step'; my $step_done_name = $step_name . '_done'; # Say we're about to start it up $context->harness->$step_name($context); # Store the string position of matches for highlighting my @match_locations; # New scope for the localization my $result; { # Localize test builder local $Test::Builder::Test->{'_wraps'} = $tb_return->{'builder'}; no warnings 'redefine'; local *Test::Builder::BAIL_OUT = sub { my ( $tb, $message ) = @_; $self->_bail_out(1); local @CARP_NOT = qw(Test::More Test::BDD::Cucumber::Executor); croak("BAIL_OUT() called: $message"); }; # Execute! # Set S and C to be step-specific values before executing the step local *Test::BDD::Cucumber::StepFile::S = sub { return $context->stash->{'scenario'}; }; local *Test::BDD::Cucumber::StepFile::C = sub { return $context; }; # Take a copy of this. Turns out actually matching against it # directly causes all sorts of weird-ass heisenbugs which mst has # promised to investigate. my $text = $context->text; # Save the matches $context->matches( [ $text =~ $regular_expression ] ); # Save the location of matched subgroups for highlighting hijinks my @starts = @-; my @ends = @+; @match_locations = pairwise { [ $a, $b ] } @starts, @ends; # OK, actually 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 } ); } my @clean_matches = $self->_extract_match_strings( $context->text, \@match_locations ); @clean_matches = [ 0, $context->text ] unless @clean_matches; # 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) unless $redispatch; $context->harness->$step_done_name( $context, $result, \@clean_matches ); return $result; } sub _extract_match_strings { my ( $self, $text, $locations ) = @_; # Clean up the match locations my @match_locations = grep { ( $_->[0] != $_->[1] ) && # No zero-length matches # And nothing that matched the full string ( !( ( $_->[0] == 0 ) && ( ( $_->[1] == length $text ) ) ) ) } grep { defined $_ && ref $_ && defined $_->[0] && defined $_->[1] } @$locations; return unless @match_locations; # Consolidate overlaps my $range = Number::Range->new(); { # Don't want a complain about numbers already in range, as that's # expected for nested matches no warnings; $range->addrange( $_->[0] . '..' . ( $_->[1] - 1 ) ) for @match_locations; } # Walk the string, splitting my @parts = ( [ 0, '' ] ); for ( 0 .. ( ( length $text ) - 1 ) ) { my $to_highlight = $range->inrange($_); my $character = substr( $text, $_, 1 ); if ( $parts[-1]->[0] != $to_highlight ) { push( @parts, [ $to_highlight, '' ] ); } $parts[-1]->[1] .= $character; } return @parts; } 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, $redispatch ) = @_; my $step_name = $redispatch ? 'sub_step' : 'step'; my $step_done_name = $step_name . '_done'; # Pretend to start step execution $context->harness->$step_name($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) unless $redispatch; $context->harness->$step_done_name( $context, $result ); return $result; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; StepFile.pm100644000765000024 733112656770421 23310 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::StepFile; $Test::BDD::Cucumber::StepFile::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::StepFile - Functions for creating and loading Step Definitions =head1 VERSION version 0.45 =cut use strict; use warnings; use Carp qw/croak/; use Test::BDD::Cucumber::I18n qw(languages langdef keyword_to_subname); require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(Step Transform Before After C S); 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; Given 'something', sub { print "YEAH!" } When qr/smooooth (\d+)/, sub { print "YEEEHAH $1" } Then qr/something (else)/, sub { S->{'match'} = $1 } Step qr/die now/, sub { die "now" } Transform qr/^(\d+)$/, sub { int $1 } Before sub { setup_db() } After sub { 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 # Mapped to Given, When, and Then as part of the i18n mapping below 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 => @_ ] ) } my @SUBS; for my $language ( languages() ) { my $langdef = langdef($language); _alias_function( $langdef->{given}, \&_Given ); _alias_function( $langdef->{when}, \&_When ); _alias_function( $langdef->{then}, \&_Then ); # Hm ... in cucumber, all step defining keywords are the same. # Here, the parser replaces 'and' and 'but' with the last verb. Tricky ... # _alias_function( $langdef->{and}, \&And); # _alias_function( $langdef->{but}, \&But); } push @EXPORT, @SUBS; sub _alias_function { my ( $keywords, $f ) = @_; my @keywords = split( '\|', $keywords ); for my $word (@keywords) { # asterisks won't be aliased to any sub next if $word eq '*'; my $subname = keyword_to_subname($word); { no strict 'refs'; no warnings 'redefine'; no warnings 'once'; *$subname = $f; push @SUBS, $subname; } } } =head2 C =head2 S Return the context and the Scenario stash, respectively, B. =cut sub S { croak "You can only call `S` inside a step definition" } sub C { croak "You can only call `C` inside a step definition" } =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 020_parser_020_corpus.t.old100755000765000024 135612656770421 23234 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/old#!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(); Extension.pm100644000765000024 522212656770421 23546 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::Extension; $Test::BDD::Cucumber::Extension::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Extension - Abstract superclass for extensions =head1 VERSION version 0.45 =head1 DESCRIPTION Provides an abstract superclass for extensions. Loaded extensions will have their hook-implementations triggered at specific points during the BDD script execution. =cut use Moose; =head1 PROPERTIES =head2 config A hash, the configuration read from the config file, verbatim. Extensions should look for their own configuration in $self->config->{extensions}->{} =cut has config => ( is => 'rw', isa => 'HashRef' ); =head1 METHODS =head2 steps_directories() The returns an arrayref whose values enumerate directories (relative to the directory of the extension) which hold step files to be loaded when the extension is loaded. =cut sub step_directories { return []; } =head2 pre_feature($feature, $feature_stash) Invoked by the Executor before executing the background and feature scenarios and their respective pre-hooks. Reports errors by calling croak(). =head2 post_feature($feature, $feature_stash) Invoked by the Executor after executing the background and feature scenarios and their repective post-hooks. Reports errors by calling croak(). =cut sub pre_feature { return; } sub post_feature { return; } =head2 pre_scenario($scenario, $feature_stash, $scenario_stash) Invoked by the Executor before executing the steps in $scenario and their respective pre-hooks. Reports errors by calling croak(). =head2 post_scenario($scenario, $feature_stash, $scenario_stash, $failed) Invoked by the Executor after executing all the steps in $scenario and their repective post-hooks. Reports errors by calling croak(). $failure indicates whether any of the steps in the scenario has failed. =cut sub pre_scenario { return; } sub post_scenario { return; } =head2 pre_step($step, $step_context) Invoked by the Executor before executing each step in $scenario. Reports errors by calling croak(). Feature and scenario stashes can be reached through $step_context->{stash}->{feature} and $step_context->{stash}->{scenario} Note: *executed* steps, so not called for skipped steps. =head2 post_scenario($step, $step_context, $failed) Invoked by the Executor after each executed step in $scenario. Reports errors by calling croak(). $failure indicates whether the step has failed. Note: *executed* steps, so not called for skipped steps. =cut sub pre_step { return; } sub post_step { return; } =head1 AUTHOR Erik Huelsmann C =head1 LICENSE Copyright 2016, Erik Huelsmann; Licensed under the same terms as Perl =cut 1; I18N000755000765000024 012656770421 21552 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/CucumberData.pm100644000765000024 7134112656770421 23147 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/I18Npackage Test::BDD::Cucumber::I18N::Data; $Test::BDD::Cucumber::I18N::Data::VERSION = '0.45'; use strict; use warnings; use JSON::MaybeXS qw/decode_json/; =encoding utf8 =head1 NAME Test::BDD::Cucumber::I18N::Data - Cucumber language definitions =head1 VERSION version 0.45 =head1 DESCRIPTION Cucumber language definitions =head1 PROVENANCE This file is a very small wrapper around the L file from L. The license on that file reads: # Copyright (c) 2009-2013 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =head1 METHODS =head2 language_definitions Load and C the language definitions. =cut sub language_definitions { my $raw = join '', (); my $langdefs = decode_json($raw); return $langdefs; } # The below is a copy-paste from # https://github.com/cucumber/gherkin/blob/master/lib/gherkin/i18n.json # 7a0cea85074166614cf2c3afe6fb2010e9449bd7 1; __DATA__ { "en": { "name": "English", "native": "English", "feature": "Feature|Business Need|Ability", "background": "Background", "scenario": "Scenario", "scenario_outline": "Scenario Outline|Scenario Template", "examples": "Examples|Scenarios", "given": "*|Given", "when": "*|When", "then": "*|Then", "and": "*|And", "but": "*|But" }, "af": { "name": "Afrikaans", "native": "Afrikaans", "feature": "Funksie|Besigheid Behoefte|Vermoë", "background": "Agtergrond", "scenario": "Situasie", "scenario_outline": "Situasie Uiteensetting", "examples": "Voorbeelde", "given": "*|Gegewe", "when": "*|Wanneer", "then": "*|Dan", "and": "*|En", "but": "*|Maar" }, "ar": { "name": "Arabic", "native": "العربية", "feature": "خاصية", "background": "الخلفية", "scenario": "سيناريو", "scenario_outline": "سيناريو مخطط", "examples": "امثلة", "given": "*|بفرض", "when": "*|متى|عندما", "then": "*|اذاً|ثم", "and": "*|و", "but": "*|لكن" }, "bm": { "name": "Malay", "native": "Bahasa Melayu", "feature": "Fungsi", "background": "Latar Belakang", "scenario": "Senario|Situai|Keadaan", "scenario_outline": "Template Senario|Template Situai|Template Keadaan|Menggariskan Senario", "examples": "Contoh", "given": "*|Diberi|Bagi", "when": "*|Apabila", "then": "*|Maka|Kemudian", "and": "*|Dan", "but": "*|Tetapi|Tapi" }, "bg": { "name": "Bulgarian", "native": "български", "feature": "Функционалност", "background": "Предистория", "scenario": "Сценарий", "scenario_outline": "Рамка на сценарий", "examples": "Примери", "given": "*|Дадено", "when": "*|Когато", "then": "*|То", "and": "*|И", "but": "*|Но" }, "ca": { "name": "Catalan", "native": "català", "background": "Rerefons|Antecedents", "feature": "Característica|Funcionalitat", "scenario": "Escenari", "scenario_outline": "Esquema de l'escenari", "examples": "Exemples", "given": "*|Donat|Donada|Atès|Atesa", "when": "*|Quan", "then": "*|Aleshores|Cal", "and": "*|I", "but": "*|Però" }, "cy-GB": { "name": "Welsh", "native": "Cymraeg", "background": "Cefndir", "feature": "Arwedd", "scenario": "Scenario", "scenario_outline": "Scenario Amlinellol", "examples": "Enghreifftiau", "given": "*|Anrhegedig a", "when": "*|Pryd", "then": "*|Yna", "and": "*|A", "but": "*|Ond" }, "cs": { "name": "Czech", "native": "Česky", "feature": "Požadavek", "background": "Pozadí|Kontext", "scenario": "Scénář", "scenario_outline": "Náčrt Scénáře|Osnova scénáře", "examples": "Příklady", "given": "*|Pokud|Za předpokladu", "when": "*|Když", "then": "*|Pak", "and": "*|A také|A", "but": "*|Ale" }, "da": { "name": "Danish", "native": "dansk", "feature": "Egenskab", "background": "Baggrund", "scenario": "Scenarie", "scenario_outline": "Abstrakt Scenario", "examples": "Eksempler", "given": "*|Givet", "when": "*|Når", "then": "*|Så", "and": "*|Og", "but": "*|Men" }, "de": { "name": "German", "native": "Deutsch", "feature": "Funktionalität", "background": "Grundlage", "scenario": "Szenario", "scenario_outline": "Szenariogrundriss", "examples": "Beispiele", "given": "*|Angenommen|Gegeben sei|Gegeben seien", "when": "*|Wenn", "then": "*|Dann", "and": "*|Und", "but": "*|Aber" }, "el": { "name": "Greek", "native": "Ελληνικά", "feature": "Δυνατότητα|Λειτουργία", "background": "Υπόβαθρο", "scenario": "Σενάριο", "scenario_outline": "Περιγραφή Σεναρίου", "examples": "Παραδείγματα|Σενάρια", "given": "*|Δεδομένου", "when": "*|Όταν", "then": "*|Τότε", "and": "*|Και", "but": "*|Αλλά" }, "en-au": { "name": "Australian", "native": "Australian", "feature": "Pretty much", "background": "First off", "scenario": "Awww, look mate", "scenario_outline": "Reckon it's like", "examples": "You'll wanna", "given": "*|Y'know", "when": "*|It's just unbelievable", "then": "*|But at the end of the day I reckon", "and": "*|Too right", "but": "*|Yeah nah" }, "en-lol": { "name": "LOLCAT", "native": "LOLCAT", "feature": "OH HAI", "background": "B4", "scenario": "MISHUN", "scenario_outline": "MISHUN SRSLY", "examples": "EXAMPLZ", "given": "*|I CAN HAZ", "when": "*|WEN", "then": "*|DEN", "and": "*|AN", "but": "*|BUT" }, "en-old": { "name": "Old English", "native": "Englisc", "feature": "Hwaet|Hwæt", "background": "Aer|Ær", "scenario": "Swa", "scenario_outline": "Swa hwaer swa|Swa hwær swa", "examples": "Se the|Se þe|Se ðe", "given": "*|Thurh|Þurh|Ðurh", "when": "*|Tha|Þa|Ða", "then": "*|Tha|Þa|Ða|Tha the|Þa þe|Ða ðe", "and": "*|Ond|7", "but": "*|Ac" }, "en-pirate": { "name": "Pirate", "native": "Pirate", "feature": "Ahoy matey!", "background": "Yo-ho-ho", "scenario": "Heave to", "scenario_outline": "Shiver me timbers", "examples": "Dead men tell no tales", "given": "*|Gangway!", "when": "*|Blimey!", "then": "*|Let go and haul", "and": "*|Aye", "but": "*|Avast!" }, "en-Scouse": { "name": "Scouse", "native": "Scouse", "feature": "Feature", "background": "Dis is what went down", "scenario": "The thing of it is", "scenario_outline": "Wharrimean is", "examples": "Examples", "given": "*|Givun|Youse know when youse got", "when": "*|Wun|Youse know like when", "then": "*|Dun|Den youse gotta", "and": "*|An", "but": "*|Buh" }, "en-tx": { "name": "Texan", "native": "Texan", "feature": "Feature", "background": "Background", "scenario": "Scenario", "scenario_outline": "All y'all", "examples": "Examples", "given": "*|Given y'all", "when": "*|When y'all", "then": "*|Then y'all", "and": "*|And y'all", "but": "*|But y'all" }, "eo": { "name": "Esperanto", "native": "Esperanto", "feature": "Trajto", "background": "Fono", "scenario": "Scenaro", "scenario_outline": "Konturo de la scenaro", "examples": "Ekzemploj", "given": "*|Donitaĵo", "when": "*|Se", "then": "*|Do", "and": "*|Kaj", "but": "*|Sed" }, "es": { "name": "Spanish", "native": "español", "background": "Antecedentes", "feature": "Característica", "scenario": "Escenario", "scenario_outline": "Esquema del escenario", "examples": "Ejemplos", "given": "*|Dado|Dada|Dados|Dadas", "when": "*|Cuando", "then": "*|Entonces", "and": "*|Y", "but": "*|Pero" }, "et": { "name": "Estonian", "native": "eesti keel", "feature": "Omadus", "background": "Taust", "scenario": "Stsenaarium", "scenario_outline": "Raamstsenaarium", "examples": "Juhtumid", "given": "*|Eeldades", "when": "*|Kui", "then": "*|Siis", "and": "*|Ja", "but": "*|Kuid" }, "fa": { "name": "Persian", "native": "فارسی", "feature": "وِیژگی", "background": "زمینه", "scenario": "سناریو", "scenario_outline": "الگوی سناریو", "examples": "نمونه ها", "given": "*|با فرض", "when": "*|هنگامی", "then": "*|آنگاه", "and": "*|و", "but": "*|اما" }, "fi": { "name": "Finnish", "native": "suomi", "feature": "Ominaisuus", "background": "Tausta", "scenario": "Tapaus", "scenario_outline": "Tapausaihio", "examples": "Tapaukset", "given": "*|Oletetaan", "when": "*|Kun", "then": "*|Niin", "and": "*|Ja", "but": "*|Mutta" }, "fr": { "name": "French", "native": "français", "feature": "Fonctionnalité", "background": "Contexte", "scenario": "Scénario", "scenario_outline": "Plan du scénario|Plan du Scénario", "examples": "Exemples", "given": "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données", "when": "*|Quand|Lorsque|Lorsqu'<", "then": "*|Alors", "and": "*|Et", "but": "*|Mais" }, "gl": { "name": "Galician", "native": "galego", "background": "Contexto", "feature": "Característica", "scenario": "Escenario", "scenario_outline": "Esbozo do escenario", "examples": "Exemplos", "given": "*|Dado|Dada|Dados|Dadas", "when": "*|Cando", "then": "*|Entón|Logo", "and": "*|E", "but": "*|Mais|Pero" }, "he": { "name": "Hebrew", "native": "עברית", "feature": "תכונה", "background": "רקע", "scenario": "תרחיש", "scenario_outline": "תבנית תרחיש", "examples": "דוגמאות", "given": "*|בהינתן", "when": "*|כאשר", "then": "*|אז|אזי", "and": "*|וגם", "but": "*|אבל" }, "hi": { "name": "Hindi", "native": "हिंदी", "feature": "रूप लेख", "background": "पृष्ठभूमि", "scenario": "परिदृश्य", "scenario_outline": "परिदृश्य रूपरेखा", "examples": "उदाहरण", "given": "*|अगर|यदि|चूंकि", "when": "*|जब|कदा", "then": "*|तब|तदा", "and": "*|और|तथा", "but": "*|पर|परन्तु|किन्तु" }, "hr": { "name": "Croatian", "native": "hrvatski", "feature": "Osobina|Mogućnost|Mogucnost", "background": "Pozadina", "scenario": "Scenarij", "scenario_outline": "Skica|Koncept", "examples": "Primjeri|Scenariji", "given": "*|Zadan|Zadani|Zadano", "when": "*|Kada|Kad", "then": "*|Onda", "and": "*|I", "but": "*|Ali" }, "ht": { "name": "Creole", "native": "kreyòl", "feature": "Karakteristik|Mak|Fonksyonalite", "background": "Kontèks|Istorik", "scenario": "Senaryo", "scenario_outline": "Plan senaryo|Plan Senaryo|Senaryo deskripsyon|Senaryo Deskripsyon|Dyagram senaryo|Dyagram Senaryo", "examples": "Egzanp", "given": "*|Sipoze|Sipoze ke|Sipoze Ke", "when": "*|Lè|Le", "then": "*|Lè sa a|Le sa a", "and": "*|Ak|Epi|E", "but": "*|Men" }, "hu": { "name": "Hungarian", "native": "magyar", "feature": "Jellemző", "background": "Háttér", "scenario": "Forgatókönyv", "scenario_outline": "Forgatókönyv vázlat", "examples": "Példák", "given": "*|Amennyiben|Adott", "when": "*|Majd|Ha|Amikor", "then": "*|Akkor", "and": "*|És", "but": "*|De" }, "id": { "name": "Indonesian", "native": "Bahasa Indonesia", "feature": "Fitur", "background": "Dasar", "scenario": "Skenario", "scenario_outline": "Skenario konsep", "examples": "Contoh", "given": "*|Dengan", "when": "*|Ketika", "then": "*|Maka", "and": "*|Dan", "but": "*|Tapi" }, "is": { "name": "Icelandic", "native": "Íslenska", "feature": "Eiginleiki", "background": "Bakgrunnur", "scenario": "Atburðarás", "scenario_outline": "Lýsing Atburðarásar|Lýsing Dæma", "examples": "Dæmi|Atburðarásir", "given": "*|Ef", "when": "*|Þegar", "then": "*|Þá", "and": "*|Og", "but": "*|En" }, "it": { "name": "Italian", "native": "italiano", "feature": "Funzionalità", "background": "Contesto", "scenario": "Scenario", "scenario_outline": "Schema dello scenario", "examples": "Esempi", "given": "*|Dato|Data|Dati|Date", "when": "*|Quando", "then": "*|Allora", "and": "*|E", "but": "*|Ma" }, "ja": { "name": "Japanese", "native": "日本語", "feature": "フィーチャ|機能", "background": "背景", "scenario": "シナリオ", "scenario_outline": "シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ", "examples": "例|サンプル", "given": "*|前提<", "when": "*|もし<", "then": "*|ならば<", "and": "*|かつ<", "but": "*|しかし<|但し<|ただし<" }, "jv": { "name": "Javanese", "native": "Basa Jawa", "feature": "Fitur", "background": "Dasar", "scenario": "Skenario", "scenario_outline": "Konsep skenario", "examples": "Conto|Contone", "given": "*|Nalika|Nalikaning", "when": "*|Manawa|Menawa", "then": "*|Njuk|Banjur", "and": "*|Lan", "but": "*|Tapi|Nanging|Ananging" }, "kn": { "name": "Kannada", "native": "ಕನ್ನಡ", "background": "ಹಿನ್ನೆಲೆ", "feature": "ಹೆಚ್ಚಳ", "scenario": "ಕಥಾಸಾರಾಂಶ", "scenario_outline": "ವಿವರಣೆ", "examples": "ಉದಾಹರಣೆಗಳು", "given": "*|ನೀಡಿದ", "when": "*|ಸ್ಥಿತಿಯನ್ನು", "then": "*|ನಂತರ", "and": "*|ಮತ್ತು", "but": "*|ಆದರೆ" }, "ko": { "name": "Korean", "native": "한국어", "background": "배경", "feature": "기능", "scenario": "시나리오", "scenario_outline": "시나리오 개요", "examples": "예", "given": "*|조건<|먼저<", "when": "*|만일<|만약<", "then": "*|그러면<", "and": "*|그리고<", "but": "*|하지만<|단<" }, "lt": { "name": "Lithuanian", "native": "lietuvių kalba", "feature": "Savybė", "background": "Kontekstas", "scenario": "Scenarijus", "scenario_outline": "Scenarijaus šablonas", "examples": "Pavyzdžiai|Scenarijai|Variantai", "given": "*|Duota", "when": "*|Kai", "then": "*|Tada", "and": "*|Ir", "but": "*|Bet" }, "lu": { "name": "Luxemburgish", "native": "Lëtzebuergesch", "feature": "Funktionalitéit", "background": "Hannergrond", "scenario": "Szenario", "scenario_outline": "Plang vum Szenario", "examples": "Beispiller", "given": "*|ugeholl", "when": "*|wann", "then": "*|dann", "and": "*|an|a", "but": "*|awer|mä" }, "lv": { "name": "Latvian", "native": "latviešu", "feature": "Funkcionalitāte|Fīča", "background": "Konteksts|Situācija", "scenario": "Scenārijs", "scenario_outline": "Scenārijs pēc parauga", "examples": "Piemēri|Paraugs", "given": "*|Kad", "when": "*|Ja", "then": "*|Tad", "and": "*|Un", "but": "*|Bet" }, "nl": { "name": "Dutch", "native": "Nederlands", "feature": "Functionaliteit", "background": "Achtergrond", "scenario": "Scenario", "scenario_outline": "Abstract Scenario", "examples": "Voorbeelden", "given": "*|Gegeven|Stel", "when": "*|Als", "then": "*|Dan", "and": "*|En", "but": "*|Maar" }, "no": { "name": "Norwegian", "native": "norsk", "feature": "Egenskap", "background": "Bakgrunn", "scenario": "Scenario", "scenario_outline": "Scenariomal|Abstrakt Scenario", "examples": "Eksempler", "given": "*|Gitt", "when": "*|Når", "then": "*|Så", "and": "*|Og", "but": "*|Men" }, "pa": { "name": "Panjabi", "native": "ਪੰਜਾਬੀ", "feature": "ਖਾਸੀਅਤ|ਮੁਹਾਂਦਰਾ|ਨਕਸ਼ ਨੁਹਾਰ", "background": "ਪਿਛੋਕੜ", "scenario": "ਪਟਕਥਾ", "scenario_outline": "ਪਟਕਥਾ ਢਾਂਚਾ|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ", "examples": "ਉਦਾਹਰਨਾਂ", "given": "*|ਜੇਕਰ|ਜਿਵੇਂ ਕਿ", "when": "*|ਜਦੋਂ", "then": "*|ਤਦ", "and": "*|ਅਤੇ", "but": "*|ਪਰ" }, "pl": { "name": "Polish", "native": "polski", "feature": "Właściwość|Funkcja|Aspekt|Potrzeba biznesowa", "background": "Założenia", "scenario": "Scenariusz", "scenario_outline": "Szablon scenariusza", "examples": "Przykłady", "given": "*|Zakładając|Mając", "when": "*|Jeżeli|Jeśli|Gdy|Kiedy", "then": "*|Wtedy", "and": "*|Oraz|I", "but": "*|Ale" }, "pt": { "name": "Portuguese", "native": "português", "background": "Contexto|Cenário de Fundo|Cenario de Fundo|Fundo", "feature": "Funcionalidade|Característica|Caracteristica", "scenario": "Cenário|Cenario", "scenario_outline": "Esquema do Cenário|Esquema do Cenario|Delineação do Cenário|Delineacao do Cenario", "examples": "Exemplos|Cenários|Cenarios", "given": "*|Dado|Dada|Dados|Dadas", "when": "*|Quando", "then": "*|Então|Entao", "and": "*|E", "but": "*|Mas" }, "ro": { "name": "Romanian", "native": "română", "background": "Context", "feature": "Functionalitate|Funcționalitate|Funcţionalitate", "scenario": "Scenariu", "scenario_outline": "Structura scenariu|Structură scenariu", "examples": "Exemple", "given": "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind", "when": "*|Cand|Când", "then": "*|Atunci", "and": "*|Si|Și|Şi", "but": "*|Dar" }, "ru": { "name": "Russian", "native": "русский", "feature": "Функция|Функционал|Свойство", "background": "Предыстория|Контекст", "scenario": "Сценарий", "scenario_outline": "Структура сценария", "examples": "Примеры", "given": "*|Допустим|Дано|Пусть", "when": "*|Если|Когда", "then": "*|То|Тогда", "and": "*|И|К тому же|Также", "but": "*|Но|А" }, "sv": { "name": "Swedish", "native": "Svenska", "feature": "Egenskap", "background": "Bakgrund", "scenario": "Scenario", "scenario_outline": "Abstrakt Scenario|Scenariomall", "examples": "Exempel", "given": "*|Givet", "when": "*|När", "then": "*|Så", "and": "*|Och", "but": "*|Men" }, "sk": { "name": "Slovak", "native": "Slovensky", "feature": "Požiadavka|Funkcia|Vlastnosť", "background": "Pozadie", "scenario": "Scenár", "scenario_outline": "Náčrt Scenáru|Náčrt Scenára|Osnova Scenára", "examples": "Príklady", "given": "*|Pokiaľ|Za predpokladu", "when": "*|Keď|Ak", "then": "*|Tak|Potom", "and": "*|A|A tiež|A taktiež|A zároveň", "but": "*|Ale" }, "sl": { "name": "Slovenian", "native": "Slovenski", "feature": "Funkcionalnost|Funkcija|Možnosti|Moznosti|Lastnost|Značilnost", "background": "Kontekst|Osnova|Ozadje", "scenario": "Scenarij|Primer", "scenario_outline": "Struktura scenarija|Skica|Koncept|Oris scenarija|Osnutek", "examples": "Primeri|Scenariji", "given": "Dano|Podano|Zaradi|Privzeto", "when": "Ko|Ce|Če|Kadar", "then": "Nato|Potem|Takrat", "and": "In|Ter", "but": "Toda|Ampak|Vendar" }, "sr-Latn": { "name": "Serbian (Latin)", "native": "Srpski (Latinica)", "feature": "Funkcionalnost|Mogućnost|Mogucnost|Osobina", "background": "Kontekst|Osnova|Pozadina", "scenario": "Scenario|Primer", "scenario_outline": "Struktura scenarija|Skica|Koncept", "examples": "Primeri|Scenariji", "given": "*|Zadato|Zadate|Zatati", "when": "*|Kada|Kad", "then": "*|Onda", "and": "*|I", "but": "*|Ali" }, "sr-Cyrl": { "name": "Serbian", "native": "Српски", "feature": "Функционалност|Могућност|Особина", "background": "Контекст|Основа|Позадина", "scenario": "Сценарио|Пример", "scenario_outline": "Структура сценарија|Скица|Концепт", "examples": "Примери|Сценарији", "given": "*|Задато|Задате|Задати", "when": "*|Када|Кад", "then": "*|Онда", "and": "*|И", "but": "*|Али" }, "tl": { "name": "Telugu", "native": "తెలుగు", "feature": "గుణము", "background": "నేపథ్యం", "scenario": "సన్నివేశం", "scenario_outline": "కథనం", "examples": "ఉదాహరణలు", "given": "*|చెప్పబడినది", "when": "*|ఈ పరిస్థితిలో", "then": "*|అప్పుడు", "and": "*|మరియు", "but": "*|కాని" }, "th": { "name": "Thai", "native": "ไทย", "feature": "โครงหลัก|ความต้องการทางธุรกิจ|ความสามารถ", "background": "แนวคิด", "scenario": "เหตุการณ์", "scenario_outline": "สรุปเหตุการณ์|โครงสร้างของเหตุการณ์", "examples": "ชุดของตัวอย่าง|ชุดของเหตุการณ์", "given": "*|กำหนดให้", "when": "*|เมื่อ", "then": "*|ดังนั้น", "and": "*|และ", "but": "*|แต่" }, "tlh": { "name": "Klingon", "native": "tlhIngan", "feature": "Qap|Qu'meH 'ut|perbogh|poQbogh malja'|laH", "background": "mo'", "scenario": "lut", "scenario_outline": "lut chovnatlh", "examples": "ghantoH|lutmey", "given": "*|ghu' noblu'|DaH ghu' bejlu'", "when": "*|qaSDI'", "then": "*|vaj", "and": "*|'ej|latlh", "but": "*|'ach|'a" }, "tr": { "name": "Turkish", "native": "Türkçe", "feature": "Özellik", "background": "Geçmiş", "scenario": "Senaryo", "scenario_outline": "Senaryo taslağı", "examples": "Örnekler", "given": "*|Diyelim ki", "when": "*|Eğer ki", "then": "*|O zaman", "and": "*|Ve", "but": "*|Fakat|Ama" }, "tt": { "name": "Tatar", "native": "Татарча", "feature": "Мөмкинлек|Үзенчәлеклелек", "background": "Кереш", "scenario": "Сценарий", "scenario_outline": "Сценарийның төзелеше", "examples": "Үрнәкләр|Мисаллар", "given": "*|Әйтик", "when": "*|Әгәр", "then": "*|Нәтиҗәдә", "and": "*|Һәм|Вә", "but": "*|Ләкин|Әмма" }, "uk": { "name": "Ukrainian", "native": "Українська", "feature": "Функціонал", "background": "Передумова", "scenario": "Сценарій", "scenario_outline": "Структура сценарію", "examples": "Приклади", "given": "*|Припустимо|Припустимо, що|Нехай|Дано", "when": "*|Якщо|Коли", "then": "*|То|Тоді", "and": "*|І|А також|Та", "but": "*|Але" }, "uz": { "name": "Uzbek", "native": "Узбекча", "feature": "Функционал", "background": "Тарих", "scenario": "Сценарий", "scenario_outline": "Сценарий структураси", "examples": "Мисоллар", "given": "*|Агар", "when": "*|Агар", "then": "*|Унда", "and": "*|Ва", "but": "*|Лекин|Бирок|Аммо" }, "vi": { "name": "Vietnamese", "native": "Tiếng Việt", "feature": "Tính năng", "background": "Bối cảnh", "scenario": "Tình huống|Kịch bản", "scenario_outline": "Khung tình huống|Khung kịch bản", "examples": "Dữ liệu", "given": "*|Biết|Cho", "when": "*|Khi", "then": "*|Thì", "and": "*|Và", "but": "*|Nhưng" }, "zh-CN": { "name": "Chinese simplified", "native": "简体中文", "feature": "功能", "background": "背景", "scenario": "场景|剧本", "scenario_outline": "场景大纲|剧本大纲", "examples": "例子", "given": "*|假如<|假设<|假定<", "when": "*|当<", "then": "*|那么<", "and": "*|而且<|并且<|同时<", "but": "*|但是<" }, "zh-TW": { "name": "Chinese traditional", "native": "繁體中文", "feature": "功能", "background": "背景", "scenario": "場景|劇本", "scenario_outline": "場景大綱|劇本大綱", "examples": "例子", "given": "*|假如<|假設<|假定<", "when": "*|當<", "then": "*|那麼<", "and": "*|而且<|並且<|同時<", "but": "*|但是<" }, "ur": { "name": "Urdu", "native": "اردو", "feature": "صلاحیت|کاروبار کی ضرورت|خصوصیت", "background": "پس منظر", "scenario": "منظرنامہ", "scenario_outline": "منظر نامے کا خاکہ", "examples": "مثالیں", "given": "*|اگر|بالفرض|فرض کیا", "when": "*|جب", "then": "*|پھر|تب", "and": "*|اور", "but": "*|لیکن" } } 030_pherkin_010_sanity.t.old100644000765000024 14212656770421 23341 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/old#!perl use strict; use warnings; # Check that we can load a file or a feature dir in to pherkin Model000755000765000024 012656770421 22133 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/CucumberLine.pm100644000765000024 450312656770421 23522 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::Line; $Test::BDD::Cucumber::Model::Line::VERSION = '0.45'; use Moose; =head1 NAME Test::BDD::Cucumber::Model::Line - Model to represent a line in a feature file =head1 VERSION version 0.45 =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Step.pm100644000765000024 306312656770421 23546 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::Step; $Test::BDD::Cucumber::Model::Step::VERSION = '0.45'; use Moose; =head1 NAME Test::BDD::Cucumber::Model::Step - Model to represent a step in a scenario =head1 VERSION version 0.45 =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; auto_corpus000755000765000024 012656770421 20056 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/tdigest.feature_corpus100644000765000024 701712656770421 24452 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/auto_corpusFeature: 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 CucumberExtensionPush.pm100644000765000024 135712656770421 24206 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/lib/Test#!perl package Test::CucumberExtensionPush; use Moose; use Test::BDD::Cucumber::Extension; extends 'Test::BDD::Cucumber::Extension'; has id => ( is => 'ro' ); has hash => ( is => 'ro', isa => 'HashRef', default => sub { {} } ); sub step_directories { return ['extension_steps/']; } sub pre_feature { push @{ $_[0]->hash->{pre_feature} }, $_[0]->id; } sub post_feature { push @{ $_[0]->hash->{post_feature} }, $_[0]->id; } sub pre_scenario { push @{ $_[0]->hash->{pre_scenario} }, $_[0]->id; } sub post_scenario { push @{ $_[0]->hash->{post_scenario} }, $_[0]->id; } sub pre_step { push @{ $_[0]->hash->{pre_step} }, $_[0]->id; } sub post_step { push @{ $_[0]->hash->{post_step} }, $_[0]->id; } __PACKAGE__->meta->make_immutable; 1; StepContext.pm100644000765000024 2572112656770421 24100 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::StepContext; $Test::BDD::Cucumber::StepContext::VERSION = '0.45'; use Moose; use List::Util qw( first ); =head1 NAME Test::BDD::Cucumber::StepContext - Data made available to step definitions =head1 VERSION version 0.45 =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. When steps are run normally, C is set directly before execution to return the context; this allows you to do: sub { return C->columns } instead of: sub { my $c = shift; return $c->columns; } =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). The scenario-level stash is also available to steps by calling C, making the following two lines of code equivalent: sub { my $context = shift; my $stash = $context->stash; $stash->{'count'} = 1 } sub { S->{'count'} = 1 } =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 executor Weak reference to the L being used - this allows for step redispatch. =cut has 'executor' => ( is => 'ro', required => 1, isa => 'Test::BDD::Cucumber::Executor', weak_ref => 1 ); =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' ); =head2 parent If a step redispatches to another step, the child step will have a link back to its parent step here; otherwise undef. See L. =cut has 'parent' => ( is => 'ro', isa => 'Test::BDD::Cucumber::StepContext' ); =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 } ) { # turn off this warning so undef can be set in the following regex no warnings 'uninitialized'; # uses the same magic as other steps # and puts any matches into $1, $2, etc. # and calls the Transform step # also, if the transformer code ref returns undef, this will be coerced # into an empty string, so need to mark it as something else # and then turn it into proper undef if ( $value =~ s/$transformer->[0]/ my $value = $transformer->[1]->( $self ); defined $value ? $value : '__UNDEF__' /e ) { # if we matched then stop processing this match return $value eq '__UNDEF__' ? undef : $value; } } # if we're here, the value will be returned unchanged return $value; } =head1 Redispatching Sometimes you want to call one step from another step. You can do this via the I, using the C method. For example: Given qr/I have entered (\d+)/, sub { C->dispatch( 'Given', "I have pressed $1"); C->dispatch( 'Given', "I have pressed enter", { some => 'data' } ); }; You redispatch step will have its own, new step context with almost everything copied from the parent step context. However, specifically not copied are: C, C, the C object, and of course the C and the C. If you want to pass data to your child step, you should IDEALLY do it via the text of the step itself, or failing that, through the scenario-level stash. Otherwise it'd make more sense just to be calling some subroutine... But you B pass in a third argument - a hashref which will be used as C. If the step you dispatch to doesn't pass for any reason (can't be found, dies, fails, whatever), it'll throw an exception. This will get caught by the parent step, which will then fail, and show debugging output. B =head2 dispatch C->dispatch( 'Then', "the page has loaded successfully"); See the paragraphs immediately above this =cut sub dispatch { my ( $self, $verb, $text, $data ) = @_; my $step = Test::BDD::Cucumber::Model::Step->new( { text => $text, verb => $verb, line => Test::BDD::Cucumber::Model::Line->new( { number => $self->step->line->number, raw_content => "[Redispatched step: $verb $text]", document => $self->step->line->document, } ), } ); my $columns; if ($data) { if ( ref $data eq 'HASH' ) { $columns = [ sort keys %$data ]; } } my $new_context = $self->new( { executor => $self->executor, ( $data ? ( _data => $data ) : () ), ( $columns ? ( columns => $columns ) : () ), stash => { feature => $self->stash->{'feature'}, scenario => $self->stash->{'scenario'}, step => {}, }, feature => $self->feature, scenario => $self->scenario, harness => $self->harness, transformers => $self->transformers, step => $step, verb => lc($verb), text => $text, } ); my $result = $self->executor->find_and_dispatch( $new_context, 0, 1 ); # If it didn't pass, short-circuit the rest unless ( $result->result eq 'passing' ) { my $error = "Redispatched step didn't pass:\n"; $error .= "\tStatus: " . $result->result . "\n"; $error .= "\tOutput: " . $result->output . "\n"; $error .= "Failure to redispatch a step causes the parent to fail\n"; die $error; } return $result; } # 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 { my $match = $_; $match = $self->transform($match); } @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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; CucumberExtensionCount.pm100644000765000024 115712656770421 24355 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/lib/Test#!perl package Test::CucumberExtensionCount; use Moose; use Test::BDD::Cucumber::Extension; extends 'Test::BDD::Cucumber::Extension'; has counts => ( is => 'ro', isa => 'HashRef', default => sub { {} } ); sub step_directories { return ['extension_steps/']; } sub pre_feature { $_[0]->counts->{pre_feature}++; } sub post_feature { $_[0]->counts->{post_feature}++; } sub pre_scenario { $_[0]->counts->{pre_scenario}++; } sub post_scenario { $_[0]->counts->{post_scenario}++; } sub pre_step { $_[0]->counts->{pre_step}++; } sub post_step { $_[0]->counts->{post_step}++; } __PACKAGE__->meta->make_immutable; 1; pherkin_config_files000755000765000024 012656770421 21662 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/tnot_yaml.yaml100644000765000024 4212656770421 24464 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/pherkin_config_files - this is just rubbish - right?readable.yaml100644000765000024 40612656770421 24425 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/pherkin_config_filesdefault: foo: bar bar: baz readable: f: - 1 - 2 arrayref: - foo: bar hashoption: foo: bar: 1 ehuelsmann: steps: - 1 - 2 output: JSON extensions: Test::CucumberExtensionPush({ id => 1, hash => {}}) tags: - tag1,tag2,tag3lib000755000765000024 012656770421 21765 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculatorCalculator.pm100644000765000024 346012656770421 24557 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculator/libpackage # 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; Harness000755000765000024 012656770421 22476 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/CucumberData.pm100644000765000024 1012012656770421 24057 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Harnesspackage Test::BDD::Cucumber::Harness::Data; $Test::BDD::Cucumber::Harness::Data::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Harness::Data - Builds up an internal data representation of test passes / failures =head1 VERSION version 0.45 =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, $highlights ) = @_; $self->current_step->{'result'} = $result; $self->current_step->{'highlights'} = $highlights; 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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; JSON.pm100644000765000024 1107412656770421 23770 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Harnesspackage Test::BDD::Cucumber::Harness::JSON; $Test::BDD::Cucumber::Harness::JSON::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Harness::JSON - Generate results to JSON file =head1 VERSION version 0.45 =head1 DESCRIPTION A L subclass that generates JSON output file. So that it is possible use tools like L<"Publish pretty cucumber reports"|https://github.com/masterthought/cucumber-reporting>. =cut use Moose; use JSON::MaybeXS; use Time::HiRes qw ( time ); extends 'Test::BDD::Cucumber::Harness::Data'; =head1 CONFIGURABLE ATTRIBUTES =head2 fh A filehandle to write output to; defaults to C =cut has 'fh' => ( is => 'rw', isa => 'FileHandle', default => sub { \*STDOUT } ); =head2 json_args List of options to be passed to L's C method =cut has json_args => ( is => 'ro', isa => 'HashRef', default => sub { { utf8 => 1, pretty => 1 } } ); # has all_features => ( is => 'ro', isa => 'ArrayRef', default => sub { [] } ); has current_feature => ( is => 'rw', isa => 'HashRef' ); has current_scenario => ( is => 'rw', isa => 'HashRef' ); has step_start_at => ( is => 'rw', isa => 'Num' ); sub feature { my ( $self, $feature ) = @_; $self->current_feature( $self->format_feature($feature) ); push @{ $self->all_features }, $self->current_feature; } sub scenario { my ( $self, $scenario, $dataset ) = @_; $self->current_scenario( $self->format_scenario($scenario) ); push @{ $self->current_feature->{elements} }, $self->current_scenario; } sub step { my ( $self, $context ) = @_; $self->step_start_at( time() ); } sub step_done { my ( $self, $context, $result ) = @_; my $duration = time() - $self->step_start_at; my $step_data = $self->format_step( $context, $result, $duration ); push @{ $self->current_scenario->{steps} }, $step_data; } sub shutdown { my ($self) = @_; my $json = JSON::MaybeXS->new( %{ $self->json_args } ); my $fh = $self->fh; print $fh $json->encode( $self->all_features ); } ################################## ### Internal formating methods ### ################################## sub get_keyword { my ( $self, $line_ref ) = @_; my ($keyword) = $line_ref->content =~ /^(\w+)/; return $keyword; } sub format_tags { my ( $self, $tags_ref ) = @_; return [ map { { name => '@' . $_ } } @$tags_ref ]; } sub format_description { my ( $self, $feature ) = @_; return join "\n", map { $_->content } @{ $feature->satisfaction }; } sub format_feature { my ( $self, $feature ) = @_; return { uri => $feature->name_line->filename, keyword => $self->get_keyword( $feature->name_line ), id => $self->_generate_stable_id( $feature->name_line ), name => $feature->name, line => $feature->name_line->number, description => $self->format_description($feature), tags => $self->format_tags( $feature->tags ), elements => [] }; } sub format_scenario { my ( $self, $scenario, $dataset ) = @_; return { keyword => $self->get_keyword( $scenario->line ), id => $self->_generate_stable_id( $scenario->line ), name => $scenario->name, line => $scenario->line->number, tags => $self->format_tags( $scenario->tags ), type => $scenario->background ? 'background' : 'scenario', steps => [] }; } sub _generate_stable_id { my ( $self, $line ) = @_; return $line->filename . ":" . $line->number; } sub format_step { my ( $self, $step_context, $result, $duration ) = @_; my $step = $step_context->step; return { keyword => $step ? $step->verb_original : $step_context->verb, name => $step_context->text, line => $step ? $step->line->number : 0, result => $self->format_result( $result, $duration ) }; } my %OUTPUT_STATUS = ( passing => 'passed', failing => 'failed', pending => 'pending', undefined => 'skipped', ); sub format_result { my ( $self, $result, $duration ) = @_; return { status => "undefined" } if not $result; return { status => $OUTPUT_STATUS{ $result->result }, error_message => $result->output, defined $duration ? ( duration => int( $duration * 1_000_000_000 ) ) : (), # nanoseconds }; } =head1 SEE ALSO L L L =cut 1; Result.pm100644000765000024 401512656770421 24107 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::Result; $Test::BDD::Cucumber::Model::Result::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Model::Result - Encapsulates a result state =head1 VERSION version 0.45 =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; cucumber_core_features000755000765000024 012656770421 22226 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/tcore.feature100644000765000024 1065212656770421 24717 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/cucumber_core_featuresFeature: 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 tags.feature100644000765000024 550012656770421 24701 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/cucumber_core_featuresFeature: 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 features000755000765000024 012656770421 22163 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/digestbasic.feature100644000765000024 236312656770421 24765 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/digest/features# 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" Manual000755000765000024 012656770421 22310 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/CucumberSteps.pod100644000765000024 515012656770421 24253 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Steps; =head1 NAME Test::BDD::Cucumber::Manual::Steps - How to write Step Definitions =head1 VERSION version 0.45 =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; 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. =head1 STEP DEFINITIONS Given qr/I have (\d+)/, sub { S->{'count'} += $1; } When "The count is an integer", sub { S->{'count'} = int( S->{'count'} ); } Then qr/The count should be (\d+)/, sub { is( S->{'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. Before the subref is executed, localized definitions of C and C are set, such that the lines below are equivalent: # Access the first match sub { my $context = shift; print $context->matches->[0] } sub { C->matches->[0] } # Set a value in the scenario-level stash sub { my $context = shift; my $stash = $context->stash; $stash->{'count'} = 1 } sub { S->{'count'} = 1 } 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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Feature.pm100644000765000024 370612656770421 24232 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::Feature; $Test::BDD::Cucumber::Model::Feature::VERSION = '0.45'; use Moose; =head1 NAME Test::BDD::Cucumber::Model::Feature - Model to represent a feature file, parsed =head1 VERSION version 0.45 =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 { [] } ); =head2 language Language the feature is written in. Defaults to 'en'. =cut has 'language' => ( is => 'rw', isa => 'Str', default => sub { 'en' } ); =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; TagSpec.pm100644000765000024 633212656770421 24163 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::TagSpec; $Test::BDD::Cucumber::Model::TagSpec::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Model::TagSpec - Encapsulates tag selectors =head1 VERSION version 0.45 =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; features000755000765000024 012656770421 22133 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/i18n_debasic.feature100644000765000024 535112656770421 24735 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/i18n_de/features# language: de Funktionalität: Grundlegende Taschenrechnerfunktionen Um sicherzustellen, dass ich die Calculator-Klasse korrekt programmiert habe, möchte ich als Entwickler einige grundlegende Funktionen prüfen, damit ich beruhigt meine Calculator-Klasse verwenden kann. Szenario: Anzeige des ersten Tastendrucks Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich 1 gedrückt habe Dann ist auf der Anzeige 1 zu sehen Szenario: Anzeige mehrerer Tastendrücke Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich 1 und 2 und 3 und . und 5 und 0 gedrückt habe Dann ist auf der Anzeige 123.50 zu sehen Szenario: Taste "C" löscht die Anzeige Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich 1 und 2 und 3 gedrückt habe Wenn ich C gedrückt habe Dann ist auf der Anzeige 0 zu sehen Szenario: Addition während des Rechnens Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich 1 und 2 und 3 und + und 4 und 5 und 6 und + gedrückt habe Dann ist auf der Anzeige 579 zu sehen Szenario: Grundlegende Berechnungen Gegeben sei ein neues Objekt der Klasse Calculator Wenn die Tasten gedrückt wurden Und die Tasten gedrückt wurden Und die Tasten gedrückt wurden Und ich = gedrückt habe Dann ist auf der Anzeige zu sehen Beispiele: | first | operator | second | result | | 5.0 | + | 5.0 | 10 | | 6 | / | 3 | 2 | | 10 | * | 7.550 | 75.5 | | 3 | - | 10 | -7 | Szenario: Trennung von Berechnungen Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich erfolgreich folgende Rechnungen durchgeführt habe | first | operator | second | result | | 0.5 | + | 0.1 | 0.6 | | 0.01 | / | 0.01 | 1 | | 10 | * | 1 | 10 | Und ich 3 gedrückt habe Dann ist auf der Anzeige 3 zu sehen Szenario: Ticker Tape Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich folgende Zeichenfolge eingegeben habe """ 1 + 2 + 3 + 4 + 5 + 6 - 100 * 13 \=\=\= + 2 = """ Dann ist auf der Anzeige -1025 zu sehen Szenario: Zahlen als Text eingeben Gegeben sei ein neues Objekt der Klasse Calculator Wenn die Tasten __THE_NUMBER_FIVE__ gedrückt wurden Dann ist auf der Anzeige 5 zu sehen Szenario: Zahlen als Text eingeben Gegeben sei ein neues Objekt der Klasse Calculator Wenn ich folgende Zahlen addiert habe | number as word | | __THE_NUMBER_FOUR__ | | __THE_NUMBER_FIVE__ | | __THE_NUMBER_ONE__ | Dann ist auf der Anzeige __THE_NUMBER_TEN__ zu sehen Document.pm100644000765000024 277712656770421 24424 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::Document; $Test::BDD::Cucumber::Model::Document::VERSION = '0.45'; 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.45 =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Scenario.pm100644000765000024 265512656770421 24404 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Modelpackage Test::BDD::Cucumber::Model::Scenario; $Test::BDD::Cucumber::Model::Scenario::VERSION = '0.45'; use Moose; =head1 NAME Test::BDD::Cucumber::Model::Scenario - Model to represent a scenario =head1 VERSION version 0.45 =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; calculator.feature_corpus100644000765000024 1560512656770421 25346 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/auto_corpusFeature: 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 800_regressions_010_too_few_features.t100644000765000024 112512656770421 24771 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!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(); Tutorial.pod100644000765000024 3013412656770421 25000 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Tutorial =encoding utf8 =head1 NAME Test::BDD::Cucumber::Manual::Tutorial - Quick Start Guide =head1 VERSION version 0.45 =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; Given qr/a usable (\S+) class/, sub { use_ok( $1 ); }; Given qr/a Digest (\S+) object/, sub { my $object = Digest->new($1); ok( $object, "Object created" ); S->{'object'} = $object; }; When qr/I've added "(.+)" to the object/, sub { S->{'object'}->add( $1 ); }; When "I've added the following to the object", sub { S->{'object'}->add( C->data ); }; Then qr/the (.+) output is "(.+)"/, sub { my $method = {base64 => 'b64digest', 'hex' => 'hexdigest' }->{ $1 } || do { fail("Unknown output type $1"); return }; is( S->{'object'}->$method, $2 ); }; When you run L or the Test::Builder-based test which does the same thing (L<900_run_features.t|https://github.com/pjlsergeant/test-bdd-cucumber-perl/blob/master/t/900_run_features.t>), 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 execute 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/, sub { 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. 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/, sub { my $c = shift; 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 scenario. 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. You'll note that the code above differs from the very first example, where we made use of C and C. C is a function which returns the current context, and C is a function which returns the scenario stash. So the above can be written: Given qr/a Digest (\S+) object/, sub { my $c = shift; my $object = Digest->new($1); ok( $object, "Object created" ); S->{'object'} = $object; }; 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", sub { S->{'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 Writing features and step files in languages other than English By default, pherkin expects your features and step definitions to be written in English. Since feature files are mainly used for communication within your team, you might want to use your native language. To see a list of the languages you can used, ask pherkin what languages are supported: > pherkin --i18n help | af | Afrikaans | Afrikaans | | ar | Arabic | العربية | | bg | Bulgarian | български | | bm | Malay | Bahasa Melayu | | ca | Catalan | català | ... To see which keywords (and sub names) to use, ask pherkin about a specific language: > pherkin --i18n de | feature | "Funktionalität" | | background | "Grundlage" | | scenario | "Szenario" | | scenario_outline | "Szenariogrundriss" | | examples | "Beispiele" | | given | "Angenommen", "Gegeben sei", "Gegeben seien" | | when | "Wenn" | | then | "Dann" | | and | "Und" | | but | "Aber" | | given (code) | "Angenommen", "Gegebensei", "Gegebenseien" | | when (code) | "Wenn" | | then (code) | "Dann" | The last three lines of this list show you which sub names to use in your step file. Head over to the F directory for some examples. =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; features000755000765000024 012656770421 23035 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculatorbasic.feature100644000765000024 453512656770421 25642 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculator/featuresFeature: 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__ TermColor.pm100644000765000024 1740212656770421 25126 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Harnesspackage Test::BDD::Cucumber::Harness::TermColor; $Test::BDD::Cucumber::Harness::TermColor::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Harness::TermColor - Prints colorized text to the screen =head1 VERSION version 0.45 =head1 DESCRIPTION A L subclass that prints test output, colorized, to the terminal. =head1 CONFIGURABLE ENV =head2 ANSI_COLORS_DISABLED You can use L's C to turn off colors in the output. =cut use strict; use warnings; use Moose; use Getopt::Long; # 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::Model::Result; extends 'Test::BDD::Cucumber::Harness'; =head1 CONFIGURABLE ATTRIBUTES =head2 fh A filehandle to write output to; defaults to C =cut has 'fh' => ( is => 'rw', isa => 'FileHandle', default => sub { \*STDOUT } ); =head2 theme Name of the theme to use for colours. Defaults to `dark`. Themes are defined in the private attribute C<_themes>, and currently include `light` and `dark` =cut has theme => ( 'is' => 'ro', isa => 'Str', lazy => 1, default => sub { my $theme = 'dark'; Getopt::Long::Configure('pass_through'); GetOptions( "c|theme=s" => \$theme ); return ($theme); } ); has _themes => ( is => 'ro', isa => 'HashRef[HashRef]', lazy => 1, default => sub { { dark => { 'feature' => 'bright_white', 'scenario' => 'bright_white', 'scenario_name' => 'bright_blue', 'pending' => 'yellow', 'passing' => 'green', 'failed' => 'red', 'step_data' => 'bright_cyan', }, light => { 'feature' => 'reset', 'scenario' => 'black', 'scenario_name' => 'blue', 'pending' => 'yellow', 'passing' => 'green', 'failed' => 'red', 'step_data' => 'magenta', }, }; } ); sub _colors { my $self = shift; return $self->_themes->{ $self->theme } || die( 'Unknown color theme [' . $self->theme . ']' ); } my $margin = 2; sub BUILD { my $self = shift; my $fh = $self->fh; if ( $margin > 1 ) { print $fh "\n" x ( $margin - 1 ); } } my $current_feature; sub feature { my ( $self, $feature ) = @_; my $fh = $self->fh; $current_feature = $feature; $self->_display( { indent => 0, color => $self->_colors->{'feature'}, text => $feature->name, follow_up => [ map { $_->content } @{ $feature->satisfaction || [] } ], trailing => 1 } ); } sub feature_done { my $self = shift; my $fh = $self->fh; print $fh "\n"; } sub scenario { my ( $self, $scenario, $dataset, $longest ) = @_; my $text = "Scenario: " . color( $self->_colors->{'scenario_name'} ) . ( $scenario->name || '' ); $self->_display( { indent => 2, color => $self->_colors->{'scenario'}, text => $text, follow_up => [], trailing => 0, longest_line => ( $longest || 0 ) } ); } sub scenario_done { my $self = shift; my $fh = $self->fh; print $fh "\n"; } sub step { } sub step_done { my ( $self, $context, $result, $highlights ) = @_; my $color; my $follow_up = []; my $status = $result->result; my $failed = 0; if ( $status eq 'undefined' || $status eq 'pending' ) { $color = $self->_colors->{'pending'}; } elsif ( $status eq 'passing' ) { $color = $self->_colors->{'passing'}; } else { $failed = 1; $color = $self->_colors->{'failed'}; $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 ) { $failed or return; $text = 'In ' . ucfirst( $context->verb ) . ' Hook'; undef $highlights; } elsif ($highlights) { $text = $context->step->verb_original . ' ' . $context->text; $highlights = [ [ 0, $context->step->verb_original . ' ' ], @$highlights ]; } else { $text = $context->step->verb_original . ' ' . $context->text; $highlights = [ [ 0, $text ] ]; } $self->_display( { indent => 4, color => $color, text => $text, highlights => $highlights, highlight => $self->_colors->{'step_data'}, 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 => $self->_colors->{'step_data'}, 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 ) = @_; my $fh = ref $class ? $class->fh : \*STDOUT; $options->{'indent'} += $margin; # Reset it all... print $fh color 'reset'; # Print the main line print $fh ' ' x $options->{'indent'}; # Highlight as appropriate my $color = color $options->{'color'}; if ( $options->{'highlight'} && $options->{'highlights'} ) { my $reset = color 'reset'; my $base = color $options->{'color'}; my $hl = color $options->{'highlight'}; for ( @{ $options->{'highlights'} } ) { my ( $flag, $text ) = @$_; print $fh $reset . ( $flag ? $hl : $base ) . $text . $reset; } # Normal output } else { print $fh color $options->{'color'}; print $fh $options->{'text'}; } # Reset and newline print $fh color 'reset'; print $fh "\n"; # Print follow-up lines... for my $line ( @{ $options->{'follow_up'} || [] } ) { print $fh color 'reset'; print $fh ' ' x ( $options->{'indent'} + 2 ); print $fh color $options->{'color'}; print $fh $line; print $fh color 'reset'; print $fh "\n"; } print $fh "\n" if $options->{'trailing'}; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2011-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; top_level_array.yaml100644000765000024 11212656770421 26047 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/pherkin_config_files# Config files are meant to have hash at top level - default: foo: barTestBuilder.pm100644000765000024 570512656770421 25431 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Harnesspackage Test::BDD::Cucumber::Harness::TestBuilder; $Test::BDD::Cucumber::Harness::TestBuilder::VERSION = '0.45'; =head1 NAME Test::BDD::Cucumber::Harness::TestBuilder - Pipes step output via Test::Builder =head1 VERSION version 0.45 =head1 DESCRIPTION A L subclass whose output is sent to L. =head1 OPTIONS =head2 fail_skip Boolean - makes tests with no matcher fail =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 ) { if ( $status eq 'undefined' ) { fail("No matcher for: $step_name"); } else { fail("Test skipped due to failure in previous step"); } $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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Integration.pod100644000765000024 250512656770421 25441 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Integration; =head1 NAME Test::BDD::Cucumber::Manual::Integration - Integrating with Test::Builder =head1 VERSION version 0.45 =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; # 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; # Shutdown gracefully $harness->shutdown(); =cut 1; basic.feature.es100644000765000024 374712656770421 26254 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculator/features# language: es Característica: Funciones Básicas de Calculadora 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. Antecedentes: Dado a usable "Calculator" class Escenario: First Key Press on the Display Dado a new Calculator object Y having pressed 1 Entonces the display should show 1 Escenario: Several Key Presses on the Display Dada a new Calculator object Y having pressed 1 and 2 and 3 and . and 5 and 0 Entonces the display should show 123.50 Escenario: Pressing Clear Wipes the Display Dada a new Calculator object Y having pressed 1 and 2 and 3 Y having pressed C Entonces the display should show 0 Escenario: Add as you go Dado a new Calculator object Y having pressed 1 and 2 and 3 and + and 4 and 5 and 6 and + Entonces the display should show 579 Escenario: Basic arithmetic Dado a new Calculator object Y having keyed Y having keyed Y having keyed Y having pressed = Entonces the display should show Ejemplos: | first | operator | second | result | | 5.0 | + | 5.0 | 10 | | 6 | / | 3 | 2 | | 10 | * | 7.550 | 75.5 | | 3 | - | 10 | -7 | Escenario: Separation of calculations Dado a new Calculator object Y having successfully performed the following calculations | first | operator | second | result | | 0.5 | + | 0.1 | 0.6 | | 0.01 | / | 0.01 | 1 | | 10 | * | 1 | 10 | Y having pressed 3 Entonces the display should show 3 Escenario: Ticker Tape Dado a new Calculator object Y having entered the following sequence """ 1 + 2 + 3 + 4 + 5 + 6 - 100 * 13 \=\=\= + 2 = """ Entonces the display should show -1025features000755000765000024 012656770421 23414 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/tagged-digestbasic.feature100644000765000024 242112656770421 26211 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/tagged-digest/features# Somehow I don't see this replacing the other tests this module has... @digest 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" TestBuilderDelegator.pm100644000765000024 251212656770421 25646 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumberpackage Test::BDD::Cucumber::TestBuilderDelegator; $Test::BDD::Cucumber::TestBuilderDelegator::VERSION = '0.45'; # Original fix and investigation all by https://github.com/tomas-zemres - based # on Test::Tester::Delegate. Original patch here: # # https://github.com/pjlsergeant/test-bdd-cucumber-perl/pull/61 # # which I have unfortunately butchered in order to understand and adapt it. # Some modules - like Test::Exception - start off by taking a local reference to # the Test::Builder singleton: # # my $Tester = Test::Builder->new; # # ... which later confounds our efforts to `local`ise $Test::Builder::Test. # This module provides a wrapper that we can put in to $Test::Builder::Test # early, and that we can redirect calls to. use strict; use warnings; use vars '$AUTOLOAD'; sub new { my ( $classname, $wraps ) = @_; return bless { _wraps => $wraps }, $classname; } sub AUTOLOAD { my ($sub) = $AUTOLOAD =~ /.*::(.*?)$/; return if $sub eq "DESTROY"; my $self = shift; # The object we'll be executing against my $wraps = $self->{'_wraps'}; # Get a reference to the original my $ref = $wraps->can($sub); # Add the wrapped object to the front of @_ unshift( @_, $wraps ); # Redispatch; goto replaces the current stack frame, so it's like we were # never here... goto &$ref; } 1; Architecture.pod100644000765000024 273212656770421 25602 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Architecture; =head1 NAME Test::BDD::Cucumber::Manual::Architecture - Structural Overview =head1 VERSION version 0.45 =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-2014, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 800_regressions_030_pherkin_external_libs.t100644000765000024 135212656770421 26010 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More tests => 5; use Test::BDD::Cucumber::Harness::Data; use App::pherkin; for my $test ( [ '-l', ['lib'], '-l adds lib' ], [ '-b', [ 'blib/lib', 'blib/arch' ], '-b adds blib/lib and blib/arch' ], [ '-l -b', [ 'blib/lib', 'blib/arch', 'lib' ], '-l -lb adds lib, blib/lib and blib/arch' ], [ '-I foo -I bar', [ 'foo', 'bar' ], '-I accepts multiple arguments' ], [ '-I foo -l -I bar', [ 'lib', 'foo', 'bar' ], '-I and -l work together' ], ) { my ( $flags, $expected, $name ) = @$test; local @INC = (); my $p = App::pherkin->new(); $p->_process_arguments( split( / /, '-o Data ' . $flags ) ); is_deeply( \@INC, $expected, $name ); } step_definitions000755000765000024 012656770421 23555 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/harness_jsonmock_steps.pl100644000765000024 70112656770421 26377 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/harness_json/step_definitions#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; Given qr/we have list of items="([^"]*)"/ => sub { S->{items} = [ split /,\s*/, $1 ]; }; When 'calculate count' => sub { S->{count} = scalar @{ S->{items} }; }; Then qr/number of items is "([^"]*)"/ => sub { is( S->{count}, $1 ); }; Given qr/that we receive list of items from server/ => sub { local $TODO = "mock TODO message"; ok(0); }; 800_regressions_020_term_color_skipped_steps.t100644000765000024 245612656770421 26544 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t#!perl use strict; use warnings; use Test::More; use IO::Scalar; # Don't use the suppressing Win32 behaviour for colours BEGIN { $ENV{'DISABLE_WIN32_FALLBACK'} = 1 } use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Harness::TermColor; # https://github.com/pjlsergeant/test-bdd-cucumber-perl/issues/40 # Incorrect TermColor output for skipped tests my $feature = Test::BDD::Cucumber::Parser->parse_string( join '', () ); my $executor = Test::BDD::Cucumber::Executor->new(); $executor->add_steps( [ Given => qr/(a) f(o)o b(a)r (baz)/, sub { 1; } ], ); my $expected = <new( { fh => $fh } ); # Run the step $executor->execute( $feature, $harness ); $fh->close(); is( $actual, $expected, "Skipped tests handled appropriately" ); done_testing(); __DATA__ Feature: Foo Scenario: Bar Given a foo bar baz And a non-matching step 010_greedy_table_parsing000755000765000024 012656770421 24607 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/regressionstest.feature100644000765000024 157712656770421 27315 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/regressions/010_greedy_table_parsingFeature: 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_steps.pl100644000765000024 107412656770421 27503 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/regressions/010_greedy_table_parsing#!/usr/bin/perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use Digest::MD5; Given qr/a Digest MD5 object/, sub { S->{digest} = Digest::MD5->new; }; When qr/I add "([^"]+)" to the object/, sub { S->{digest}->add( C->matches->[0] ); }; Then qr/the results look like/, sub { my $data = C->data; my $digest = S->{digest}; foreach my $row ( @{$data} ) { my $func = $row->{method}; my $expect = $row->{output}; my $got = $digest->$func(); is $got, $expect, "test: $func"; } }; step_definitions000755000765000024 012656770421 25574 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/cucumber_core_featurestag_steps.pl100644000765000024 636012656770421 30267 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/cucumber_core_features/step_definitions#!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; my %ordinals = qw/0 first 1 second 2 third 3 fourth 4 fifth/; Given qr/a scenario tagged with (.+)/, sub { my @tags = $1 =~ m/"\@(.+?)"/g; # How many scenarios have we created so far? my $count = S->{'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 = ( S->{'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 = @{ S->{'scenarios'} }; my @matched = Test::BDD::Cucumber::Model::TagSpec->new( { tags => $expr } )->filter(@scenarios); S->{'matched'} = \@matched; } When qr/^Cucumber executes scenarios (not |)tagged with (both |)"\@([^"]*)"( (and|or|nor) (without |)"\@([^"]*)")?$/, sub { 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( shift(), $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]+)"/, sub { from_tagspec( shift(), [ and => $1, [ not => [ and => $2, $3 ] ] ] ); }; Then qr/only the (.+) scenario( is|s are) executed/, sub { 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 } @{ S->{'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"', sub { my $feature = Test::BDD::Cucumber::Parser->parse_string( <<'HEREDOC' @foo Feature: Name Scenario: first Given bla HEREDOC ); S->{'scenarios'} = $feature->scenarios; }; Given 'a scenario without any tags', sub { 1 }; Then 'the scenario is executed', sub { ok( S->{'matched'}->[0], "Found an executed scenario" ); } core_steps.pl100755000765000024 1215412656770421 30465 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/t/cucumber_core_features/step_definitions#!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 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', sub { # Save a new executor S->{'executor'} = Test::BDD::Cucumber::Executor->new(); # Create a feature object S->{'feature'} = Test::BDD::Cucumber::Parser->parse_string( C->data ); }; Given qr/a scenario ("([^"]+)" )?with:/, sub { my $scenario_name = $1 || ''; # Save a new executor S->{'executor'} = Test::BDD::Cucumber::Executor->new(); # Create a feature object with just that scenario inside it S->{'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/, sub { # Add the step to our 'Step' list in the executor S->{'executor'}->add_steps( [ 'Step', $1, $step_mappings->{$2} ] ); }; When 'Cucumber runs the feature', sub { S->{'harness'} = Test::BDD::Cucumber::Harness::Data->new( {} ); S->{'executor'}->execute( S->{'feature'}, S->{'harness'} ); }; When 'Cucumber runs the scenario with steps for a calculator', sub { # FFS. "runs the scenario with steps for a calculator"?!. Cads. Lucky we're # using Perl here... S->{'executor'}->add_steps( [ 'Given', 'a calculator', sub { S->{'calculator'} = 0; S->{'constants'}->{'PI'} = 3.14159265; } ], [ 'When', 'the calculator computes PI', sub { S->{'calculator'} = S->{'constants'}->{'PI'}; } ], [ 'Then', qr/the calculator returns "?(PI|[\d\.]+)"?/, sub { my $value = S->{'constants'}->{$1} || $1; is( S->{'calculator'}, $value, "Correctly returned $value" ); } ], [ 'Then', qr/the calculator does not return "?(PI|[\d\.]+)"?/, sub { my $value = S->{'constants'}->{$1} || $1; isnt( S->{'calculator'}, $value, "Correctly did not return $value" ); } ], [ 'When', qr/the calculator adds up (.+)/, sub { my $numbers = $1; my @numbers = $numbers =~ m/([\d\.]+)/g; S->{'calculator'} = sum(@numbers); } ] ); S->{'harness'} = Test::BDD::Cucumber::Harness::Data->new( {} ); S->{'executor'}->execute_scenario( { scenario => S->{'feature'}->scenarios->[0], feature => S->{'feature'}, feature_stash => {}, harness => S->{'harness'} } ); S->{'scenario'} = S->{'harness'}->current_feature->{'scenarios'}->[0]; }; When qr/Cucumber executes the scenario( "([^"]+)")?/, sub { S->{'harness'} = Test::BDD::Cucumber::Harness::Data->new( {} ); S->{'executor'}->execute_scenario( { scenario => S->{'feature'}->scenarios->[0], feature => S->{'feature'}, feature_stash => {}, harness => S->{'harness'} } ); S->{'scenario'} = S->{'harness'}->current_feature->{'scenarios'}->[0]; }; Then 'the feature passes', sub { my $harness = S->{'harness'}; my $result = $harness->feature_status( $harness->current_feature ); is( $result->result, 'passing', "Feature passes" ) || diag( $result->output ); }; Then qr/the scenario (passes|fails)/, sub { my $wanted = $1; my $harness = S->{'harness'}; my $scenario = S->{'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/, sub { my $harness = S->{'harness'}; my $scenario = S->{'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)/, sub { my $harness = S->{'harness'}; my $scenario = S->{'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. step_definitions000755000765000024 012656770421 25531 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/digest/featuresbasic_steps.pl100755000765000024 136212656770421 30532 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/digest/features/step_definitions#!perl use strict; use warnings; use Digest; use Test::More; use Test::BDD::Cucumber::StepFile; Given qr/a usable "(\w+)" class/, sub { use_ok( C->matches->[0] ); }; Given qr/a Digest (\S+) object/, sub { my $object = Digest->new( C->matches->[0] ); ok( $object, "Object created" ); S->{'object'} = $object; }; When qr/I've added "(.+)" to the object/, sub { S->{'object'}->add( C->matches->[0] ); }; When "I've added the following to the object", sub { S->{'object'}->add( C->data ); }; Then qr/the (.+) output is "(.+)"/, sub { my ( $type, $expected ) = @{ C->matches }; my $method = { 'base64' => 'b64digest', 'hex' => 'hexdigest' }->{$type}; is( S->{'object'}->$method, $expected ); }; step_definitions000755000765000024 012656770421 25501 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/i18n_de/featurescalculator_steps.pl100644000765000024 440512656770421 31550 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/i18n_de/features/step_definitions#!perl use strict; use warnings; use utf8; # Interpret funky German chars in regexes properly use Test::More; use Test::BDD::Cucumber::StepFile; use lib 'examples/calculator/lib/'; Before sub { use_ok('Calculator'); }; After sub { # a bit contrived, as garbage collection would clear it out delete S->{'Calculator'}; ok( not exists S->{'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_i18n { my $word = shift; ok($word); ok( exists $numbers_as_words{$word} ); return $numbers_as_words{$word}; } Transform qr/^(__THE_NUMBER_\w+__)$/, sub { map_word_to_number_i18n($1) }; Transform qr/^table:number as word$/, sub { my ( $c, $data ) = @_; for my $row ( @{$data} ) { $row->{'number'} = map_word_to_number_i18n( $row->{'number as word'} ); } }; Gegebensei 'ein neues Objekt der Klasse Calculator', sub { S->{'Calculator'} = Calculator->new(); }; Wenn qr/^ich (.+) gedrückt habe/, sub { S->{'Calculator'}->press($_) for split( /(,| und) /, C->matches->[0] ); }; Wenn qr/^die Tasten (.+) gedrückt wurden/, sub { # Make this call the having pressed my ($value) = @{ C->matches }; S->{'Calculator'}->key_in($value); }; Wenn 'ich erfolgreich folgende Rechnungen durchgeführt habe', sub { my $calculator = S->{'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'} ); } }; Wenn 'ich folgende Zeichenfolge eingegeben habe', sub { S->{'Calculator'}->key_in( C->data ); }; Wenn 'ich folgende Zahlen addiert habe', sub { for my $row ( @{ C->data } ) { S->{'Calculator'}->key_in( $row->{number} ); S->{'Calculator'}->key_in('+'); } }; Dann qr/^ist auf der Anzeige (.+) zu sehen/, sub { my ($value) = @{ C->matches }; is( S->{'Calculator'}->display, $value, "Calculator display as expected" ); }; step_definitions000755000765000024 012656770421 26762 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/tagged-digest/featuresbasic_steps.pl100755000765000024 136012656770421 31761 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/tagged-digest/features/step_definitions#!perl use strict; use warnings; use Digest; use Test::More; use Test::BDD::Cucumber::StepFile; Given qr/a usable "(\w+)" class/, sub { use_ok( C->matches->[0] ); }; Given qr/a Digest (\S+) object/, sub { my $object = Digest->new( C->matches->[0] ); ok( $object, "Object created" ); S->{'object'} = $object; }; When qr/I've added "(.+)" to the object/, sub { S->{'object'}->add( C->matches->[0] ); }; When "I've added the following to the object", sub { S->{'object'}->add( C->data ); }; Then qr/the (.+) output is "(.+)"/, sub { my ( $type, $expected ) = @{ C->matches }; my $method = { 'base64' => 'b64digest', 'hex' => 'hexdigest' }->{$type}; is( S->{'object'}->$method, $expected ); }; step_definitions000755000765000024 012656770421 26403 5ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculator/featurescalculator_steps.pl100755000765000024 460712656770421 32461 0ustar00pjlsergeantstaff000000000000Test-BDD-Cucumber-0.45/examples/calculator/features/step_definitions#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::StepFile; use lib 'examples/calculator/lib/'; Before sub { use_ok('Calculator'); }; After sub { my $c = shift; # a bit contrived, as garbage collection would clear it out delete $c->stash->{'scenario'}->{'Calculator'}; ok( ( not exists $c->stash->{'scenario'}->{'Calculator'} ), "Calculator cleaned up" ); }; 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, "Passed in a word to map [$word]" ); ok( exists $numbers_as_words{$word}, "Mapping found" ); return $numbers_as_words{$word}; } Transform qr/^(__THE_NUMBER_\w+__)$/, sub { map_word_to_number($1) }; Transform qr/^table:number as word$/, sub { my ( $c, $data ) = @_; for my $row ( @{$data} ) { $row->{'number'} = map_word_to_number( $row->{'number as word'} ); } }; Given 'a new Calculator object', sub { S->{'Calculator'} = Calculator->new(); }; Given qr/^having pressed (.+)/, sub { S->{'Calculator'}->press($_) for split( /(,| and) /, C->matches->[0] ); }; Given qr/^having keyed (.+)/, sub { # Make this call the having pressed my ($value) = @{ C->matches }; S->{'Calculator'}->key_in($value); }; Given 'having successfully performed the following calculations', sub { my $calculator = S->{'Calculator'}; for my $row ( @{ C->data } ) { C->dispatch( 'Given', 'having keyed ' . $row->{'first'} ); C->dispatch( 'Given', 'having keyed ' . $row->{'operator'} ); C->dispatch( 'Given', 'having keyed ' . $row->{'second'} ); C->dispatch( 'Given', 'having pressed =' ); is( $calculator->display, $row->{'result'}, $row->{'first'} . ' ' . $row->{'operator'} . ' ' . $row->{'second'} ); } }; Given 'having entered the following sequence', sub { S->{'Calculator'}->key_in( C->data ); }; Given 'having added these numbers', sub { for my $row ( @{ C->data } ) { S->{'Calculator'}->key_in( $row->{number} ); S->{'Calculator'}->key_in('+'); } }; Then qr/^the display should show (.+)/, sub { my ($value) = @{ C->matches }; is( S->{'Calculator'}->display, $value, "Calculator display as expected" ); };