Test-BDD-Cucumber-0.87000755001750001750 015014377065 13600 5ustar00erikerik000000000000LICENSE100644001750001750 4643515014377065 14722 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87This software is copyright (c) 2025 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) 2025 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 Perl Artistic License 1.0 --- This software is Copyright (c) 2025 by Peter Sergeant. This is free software, licensed under: The Perl 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 as specified below. "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 uunet.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) give non-standard executables non-standard names, and clearly document 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. You may embed this Package's interpreter within an executable of yours (by linking); this shall be construed as a mere form of aggregation, provided that the complete Standard Version of the interpreter is so embedded. 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 whoever generated them, and may be sold commercially, and may be aggregated with this Package. If such scripts or library files are aggregated with this Package via the so-called "undump" or "unexec" methods of producing a binary executable image, then distribution of such an image shall neither be construed as a distribution of this Package nor shall it fall under the restrictions of Paragraphs 3 and 4, provided that you do not represent such an executable image as a Standard Version of this Package. 7. C subroutines (or comparably compiled subroutines in other languages) supplied by you and linked into this Package in order to emulate subroutines and variables of the language defined by this Package shall not be considered part of this Package, but are the equivalent of input as in Paragraph 6, provided these subroutines do not change the language in any way that would cause it to fail the regression tests for the language. 8. Aggregation of this Package with a commercial distribution is always permitted provided that the use of this Package is embedded; that is, when no overt attempt is made to make this Package's interfaces visible to the end user of the commercial distribution. Such use shall not be construed as a distribution of this Package. 9. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End CHANGES100644001750001750 4655115014377065 14707 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87----- 0.87 2025-05-24 [Added] - Report scenarios skipped due to tag filters as skipped, which prevents feature files with *all* scenarios filtered from being reported as failed in the TAP output (while those with only some scenarios skipped will not) - 'scenario_skip()' event added to the Test::BDD::Cucumber::Harness [Fixed] - Typo in Test::BDD::Cucumber::Model::TagSpec 0.86 2023-08-12 [Breaking] - Parser returns tags prefixed with '@' now, because that's what the Cucumber ecosystem does and what Cucumber::TagExpressions wants as input for its tag names [Fixed] - Filtering scenarios by tags regressed in 0.85 [Added] - Tests for tag-filtered scenarios 0.85 2023-08-11 [Breaking] - Tag filters now use the Cucumber Tag Expressions format (see https://cucumber.io/docs/cucumber/api/?lang=java#tag-expressions) - Minimum Perl version is now 5.14 - Scenario's `data` attribute removed (deprecated since 4 years) 0.84 2023-07-24 [Fixed] - Test failures with Perl 5.39.1 due to trying to import from File::Spec 0.83 2022-10-30 [Changed] - Updated keyword translations from upstream Cucumber project 0.82 2021-08-23 [Added] - Environment variable expansion in configuration profiles; use ${ENVVAR} anywhere in a 'pherkin.yaml' file to substitude the value from the environment. Use $${ENVVAR} to include the exact value '${ENVVAR}. 0.81 2021-07-18 [Fixed] - Remove cruft from released archive (by expanding .gitignore) 0.80 2021-07-18 [Fixed] - UTF-8 in test output double encoded - Tutorial example references `use_ok`, which does not exist in Test2::Bundle::More - Step redispatching with step data now work (with documentation) 0.79 2021-03-19 [Fixed] - Fix parallel testing support in the `prove` plugin (prove '-j' support) 0.78 2021-03-19 [Changed] - Files with DOS line endings (\r\n) no longer leave \r at the end of the line on Unix (\n line-ending systems) - Stop warning about mixed comments being disallowed after consulting the Cucumber project through their Slack channel - Moved CI to GitHub Actions, because TravisCI minutes ran out [Fixed] - Fix passing UTF-8 data from sub-process spawned by `prove` plugin - Fix formatting UTF-8 TAP output collected during step execution 0.77 2021-02-10 [Added] - New option `--version` for `pherkin` [Changed] - Even more compact storage of language definitions [Fixed] - With `prove`, no location details are reported (as they are with regular Test::More tests), unless run in verbose mode which includes all non-failing output too (gh #176) - Require YAML v1.15 to fix failures seen on cpantesters - No exit status reported for tests run by the `prove` integration 0.76 2021-02-07 [Added] - Mention the `--strict` option for `pherkin` in SYNOPSIS - Added deprecation warning to 'data' accessor in Test::BDD::Cucumber::Model::Scenario [Fixed] - Warnings when processing empty feature files or files without a text after the `Feature:` keyword - Feature and scenario descriptions missing space on concatenated lines - Location of failed test in TAP output now points to the failed step, instead of somewhere inside `TAP::Harness` [Changed] - Scenarios defined by a scenario outline (`Examples:`) are now independent as in Cucumber; before, failure of a scenario in an outline would cancel all subsequent steps *and* scenarios -- now only steps are cancelled (skipped), but subsequent scenarios are run (gh #123) - Descriptions of tests no longer contain prefixed 'In ' - Dependency YAML::Syck switched to YAML (which wraps YAML::XS or YAML::PP, whichever is available); YAML has 3x more dependencies on CPAN, increasing chances of prior availability - Language definitions now stored as Perl instead of JSON for compactness and load speed [Removed] - Dependencies on Clone, List::MoreUtils, Number::Range 0.75 2020-12-28 [Fixed] - Passing multiple tags arguments to prove correctly intersects the sets; e.g. '--feature-option tags=@wip --feature-option tags=@daily' now correctly runs stricttly the scenarios matching both @wip and @daily - Tutorial.pod incorrectly stated --tags=@tag1,~@tag2 runs scenarios tagged '@tag1' except those tagged '@tag2': it runs all tagged '@tag1' and all *not* tagged '@tag2'. - Clarified difference between step definitions, step models and step execution contexts [Added] - Explanation in 'pherkin' how to pass tag patterns - Expanded explanation in Tuturial.pod how to pass tag patterns 0.74 2020-12-12 [Fixed] - Failure exit code from 'pherkin' does not work [Changed] - Synchronized translations with upstream i18n data 0.73 2020-08-24 [Added] - Allow Example variables to be used in Scenario Outline title [Fixed] - Failure to load Test::BDD::Cucumber::StepFile; throws error 'Modification of read-only value attempted' (gh #165) 0.72 2020-08-21 [Fixed] - Shebang of 'pherkin' script not replaced on 'make install' (gh #166) - Step dispatch handles data attribute incorrectly (gh #167) - Update copyright years 0.71 2020-05-02 [Fixed] - Feature file parser crashes on empty files - 'prove' plugin doesn't run 'post_execute' hooks 0.70 2020-04-19 [Fixed] - Due to hash key randomization, incompatible column sets were reported where in fact the sets are equal (with tests) 0.69 2020-04-19 [Fixed] - Multiple examples would always report incompatible column sets - Multiple examples trigger parser error due to accessing a deprecated scenario attribute 0.68 2020-04-18 [Added] - New --strict option for `pherkin` which causes an exit value of 1 when there are 'pending' or 'missing' steps [Fixed] - Too much code was running with $/ bound to `undef` when parsing Gherkin from file - NAME section missing in two modules causing Dist::Zilla to fail insertion of VERSION section [Changed] - Some improved error messages 0.67 2019-09-26 [Changed] - Add package statement to step files of core feature tests - Move Executor from Test2::API::context() to Test2::Bundle::More (for pass, fail and done_testing) to fix seemingly random failures. Fixes #155. [Added] - Full support for package declarations in step files 0.66 2019-09-22 [Fixed] - Harnass outputs 'Scenario' and 'Feature' instead of the actual keywords from the feature file (e.g. 'Szenario') [Changed] - Dependency listing clean up - Test2::API minimum dependency updated - META.json generation -- now includes 'provides' as CPANTS wants [Added] - Scenario descriptions are now included in output - Support for multiple Examples per scenario - Support for tags on Examples - Support for description blocks in Examples [Removed] - Test files in t/old/ -- not run as tests 0.64 2019-09-15 [Fixed] - Corrected List::Util dependency failing to declare 1.33 minimum 0.63 2019-09-15 [Deprecated] - Mixing steps with comments is not allowed in Gherkin; support for mixing steps and comments will be removed in v1.0 [Changed] - Gherkin parser refactoring for readability [Added] - Support for scenario descriptions: a block of explanatory text between the `Scenario:` keyword and the step lines 0.62 2019-09-09 - Fix regression in 0.61 with `prove` plugin printing TAP on STDOUT 0.61 2019-09-07 - Renamed Test::BDD::Cucumber::Harness::TestBuilder to Test::BDD::Cucumber::Harness::TAP for consistency with the other harnesses (which have output-based names) - Add support for step functions using Test2 - Added documentation of availability of meta data defined with step functions for use by extensions to the Architecture manual - Split dependencies by Runtime vs TestRequires 0.60 2019-08-31 - Added flag to make `pherkin` check for duplicate matching step functions to help debugging - Added option to specify meta data to step functions and use it in extensions; minimally available meta data identifies the file name and line where the step function has been defined - Added missing dependencies reported by CPANTS - Don't include README anymore, as there always has been a README.pod - Cleaned up release procedure: 0.58 and 0.59 were sloppy, including additional files from the working directory - Reformatted CHANGES to allow MetaCPAN to parse it 0.59 2019-08-29 - Increase minimum Perl version to 5.10 - Updated languge support from Cucumber upstream repository, adding support for: Armenian, Aragonese, Asturian, Azerbaijani, Bosnian, Emoji, Irish, Gujarati, Georgian, Macedonian (Cyrilic), Macedonian (Latin), Mongolian and Tamil - Documentation updates and (hopefully) clarification - Fix dist.ini (and META.{yml,json}) 'author' section - Add contributors in META.{yml,json} 0.58 2019-08-22 - Test::BDD::Cucumber has a new home: https://github.com/pherkin - Rename links (issues, PRs) to point to the new home - Reap finished child processes in the `prove` plugin `TAP::Parser::Iterator::PherkinStream` - Eliminate empty lines between successive feature 0.57 2019-04-09 - Add 'match mode'; don't run steps, only check matches against defined steps in step files - Fix the 'result' argument of the 'post_step' callback of of extensions; it used to always be 'failed' 0.56 2018-04-21 - Minor upgrade to minimum required Moo version to help try and flush out some CPAN smokers failure. 0.55 2018-04-11 - Fix a parsing bug with PyStrings at the end of a scenario, via latk https://github.com/pherkin/test-bdd-cucumber-perl/pull/127 0.54 2018-04-10 - Set output layers properly to UTF8, via ivanych https://github.com/pherkin/test-bdd-cucumber-perl/pull/126 0.53 2017-06-26 - Moose -> Moo, thanks to https://github.com/vti 0.52 2017-02-13 - Removed File::Slurp @ehuelsmann - Minor test fixes @ehuelsmann 0.51 2017-02-07 - Added a TAP source-handler for Cucumber files, should also allow for parallelization - Table and PyString interpolation fixes @ ehuelsmann - Localization examples for Spanish @ Gonzalo Barco - Doc Typos fixed @ Grant McLean - Fixed up JSON output @ Tomas Pokorny 0.50 2016-04-29 - ehuelsmann added placeholders to PyStrings 0.49 2016-02-29 - Special extensions syntax in config files added by ehuelsmann 0.48 2016-02-24 - Now without cruft that was lying around in the build dir 0.47 2016-02-23 - ehuelsmann adds extra documentation on extensions https://github.com/pherkin/test-bdd-cucumber-perl/pull/82 - ehuelsmann fixes which keys we allow in configuration files https://github.com/pherkin/test-bdd-cucumber-perl/pull/81 0.46 2016-02-15 - Spelling mistakes fixed https://github.com/pherkin/test-bdd-cucumber-perl/issues/75 - Table quoting fixed https://github.com/pherkin/test-bdd-cucumber-perl/issues/50 - Extensions gains setup and teardown methods https://github.com/pherkin/test-bdd-cucumber-perl/pull/78 - Works on old Perls again: https://github.com/pherkin/test-bdd-cucumber-perl/issues/79 0.45 2016-02-11 - Removed Moose cleanliness method from Test::BDD::Cucumber::Extension 0.44 2016-02-09 - Add extensions! See Test::BDD::Cucumber::Executor and Test::BDD::Cucumber::Extensions for details. Work by ehuelsmann: https://github.com/pherkin/test-bdd-cucumber-perl/pull/66 0.41 2016-02-09 - pherkin command line options can now be read from config files, based on a patch by ehuelsmann - Scenario outline handling now works properly with i18n, thanks ehuelsmann https://github.com/pherkin/test-bdd-cucumber-perl/pull/71 - Storable dependency removed, thanks ehuelsmann https://github.com/pherkin/test-bdd-cucumber-perl/pull/69 - Various spelling mistakes fixed - thanks James McCoy 0.40 2016-01-02 - Step redispatching 0.39 2015-10-25 - Proper support for Test::Builder's BAIL_OUT() added 0.38 2015-10-25 - Fixed error message when fail_skip is set on Test::Builder harness - Made pherkin -I work again 0.37 2015-08-24 - Allow specification of extra step files and directories in `pherkin` 0.36 24 Aug 2015 - Don't require Devel::FindRef 0.35 2015-06-21 - Fixed the Test::Builder wrapping issue discussed at: https://github.com/pherkin/test-bdd-cucumber-perl/pull/61 Output from Test::Exception should now be properly captured. - Updated git repository all over the places 0.34 2015-04-21 - JSON formatter tests now Win32 compatible 0.33 2015-04-20 - 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 2014-12-23 - Colour themes for TermColor harness, fixes https://github.com/pherkin/test-bdd-cucumber-perl/issues/35 - Command-line options are now passed through https://github.com/pherkin/test-bdd-cucumber-perl/pull/49/files - Both of these are based on a patch from benningm 0.31 2014-10-09 - Specified a minimum version of File::Slurp in response to a private bug report 0.30 2014-08-27 - Use core module Digest::SHA instead of Digest::SHA1 https://github.com/pherkin/test-bdd-cucumber-perl/issues/45 0.29 2014-08-26 - Tried to fix Win32 issue again - Remove FindBin::libs - Installs cleanly on 5.8 again 0.28 2014-08-26 - Fixed the JSON outputter test on Win32 to use Path::Class 0.27 2014-08-25 - Added JSON output support, courtesy of Tomas Zemres - Some useful minor patched via Paul Cochrane - Ran the whole thing through perltidy 0.26 2014-06-21 - Fixed a bug relating to skipped steps in TermColor output https://github.com/pherkin/test-bdd-cucumber-perl/issues/40 - Changed examples/ to use C->matches 0.25 2014-06-08 - Highlight parameters properly in TermColor output using @+ and @- https://github.com/pherkin/test-bdd-cucumber-perl/issues/24 0.24 2014-06-07 - 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 2014-06-05 - Another attempt to fix up the weird regex test bug - Remove our experiment with ShareDir 0.22 2014-06-04 - 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 2014-06-03 - Now works with 5.10.0 again 0.20 2014-06-03 - Adding missed dependency from Paul Cochrane 0.19 2014-04-24 - 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 2014-04-06 - Removed Find::File dependency in StepFile.pm 0.17 2013-12-01 - 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 2013-12-01 - 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/pherkin/test-bdd-cucumber-perl/pull/11 - Try and use Win32::Console::ANSI if on Windows https://github.com/pherkin/test-bdd-cucumber-perl/issues/13 - Before and After Hooks now implemented highflying: https://github.com/pherkin/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/pherkin/test-bdd-cucumber-perl/pull/15 highflying: https://github.com/pherkin/test-bdd-cucumber-perl/pull/10 0.15 2013-05-21 - pherkin now accepts an output type via -o, eg: pherkin -o TestBuilder ; pherkin -o TermColor This is a partial solution to: https://github.com/pherkin/test-bdd-cucumber-perl/issues/8 - Use the original verb that the test file used https://github.com/pherkin/test-bdd-cucumber-perl/issues/9 0.14 2013-05-04 - Actually apply the Test::Builder 1.5 stuff 0.13 2013-05-04 - Command-line options for pherkin thanks to cursork - Reintroduced the "auto_corpus" tests, and made them work 0.12 2012-05-17 - Fixed tag-related issues, thanks to Craig Caroon https://github.com/pherkin/test-bdd-cucumber-perl/issues/5 0.11 2012-05-20 - Correct Term::ANSIColor dependency https://github.com/pherkin/test-bdd-cucumber-perl/issues/4 0.10 2012-05-02 - 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 2012-04-28 - Fixed a few spelling mistakes - Added a minimal man page to pherkin - Both as reported by intrigeri@boum.org 0.08 2012-04-23 - Removed some OmniOutliner artifacts. Ooops. - Fixed a spelling mistake - Both as reported by intrigeri@boum.org 0.07 2012-04-01 - Started migration away from Ouch - Added tags at a code-level (but not to pherkin, yet) 0.06 2012-03-31 - Fixed up the behaviour of Background sections, to run before each and every Scenario. See: https://github.com/pherkin/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/pherkin/test-bdd-cucumber-perl/issues/1 0.05 2012-03-18 - Yet another feature parsing bug, relating to empty lines after scenarios 0.04 2012-01-14 - Fixed a bug relating to recognizing newlines after the end of Scenario tabular data, as reported by Graham TerMarsch 0.03 2012-01-03 - 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 2011-12-20 - Added extra docs, and a few tiny bug fixes META.yml100644001750001750 1214015014377065 15150 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87--- abstract: 'Feature-complete Cucumber-style testing in Perl' author: - 'Peter Sergeant ' - 'Erik Huelsmann ' build_requires: File::Copy::Recursive: '0' IO::Scalar: '0' Path::Tiny: '0' Test2::V0: '0' Test::Differences: '0' Test::Exception: '0' Test::Pod: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.033, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Test-BDD-Cucumber provides: App::pherkin: file: lib/App/pherkin.pm version: '0.87' TAP::Parser::Iterator::PherkinStream: file: lib/TAP/Parser/Iterator/PherkinStream.pm version: '0.87' TAP::Parser::SourceHandler::Feature: file: lib/TAP/Parser/SourceHandler/Feature.pm version: '0.87' Test::BDD::Cucumber: file: lib/Test/BDD/Cucumber.pm version: '0.87' Test::BDD::Cucumber::Errors: file: lib/Test/BDD/Cucumber/Errors.pm version: '0.87' Test::BDD::Cucumber::Executor: file: lib/Test/BDD/Cucumber/Executor.pm version: '0.87' Test::BDD::Cucumber::Extension: file: lib/Test/BDD/Cucumber/Extension.pm version: '0.87' Test::BDD::Cucumber::Harness: file: lib/Test/BDD/Cucumber/Harness.pm version: '0.87' Test::BDD::Cucumber::Harness::Data: file: lib/Test/BDD/Cucumber/Harness/Data.pm version: '0.87' Test::BDD::Cucumber::Harness::JSON: file: lib/Test/BDD/Cucumber/Harness/JSON.pm version: '0.87' Test::BDD::Cucumber::Harness::TAP: file: lib/Test/BDD/Cucumber/Harness/TAP.pm version: '0.87' Test::BDD::Cucumber::Harness::TermColor: file: lib/Test/BDD/Cucumber/Harness/TermColor.pm version: '0.87' Test::BDD::Cucumber::Harness::TestBuilder: file: lib/Test/BDD/Cucumber/Harness/TestBuilder.pm version: '0.87' Test::BDD::Cucumber::I18N::Data: file: lib/Test/BDD/Cucumber/I18N/Data.pm version: '0.87' Test::BDD::Cucumber::I18n: file: lib/Test/BDD/Cucumber/I18n.pm version: '0.87' Test::BDD::Cucumber::Loader: file: lib/Test/BDD/Cucumber/Loader.pm version: '0.87' Test::BDD::Cucumber::Model::Dataset: file: lib/Test/BDD/Cucumber/Model/Dataset.pm version: '0.87' Test::BDD::Cucumber::Model::Document: file: lib/Test/BDD/Cucumber/Model/Document.pm version: '0.87' Test::BDD::Cucumber::Model::Feature: file: lib/Test/BDD/Cucumber/Model/Feature.pm version: '0.87' Test::BDD::Cucumber::Model::Line: file: lib/Test/BDD/Cucumber/Model/Line.pm version: '0.87' Test::BDD::Cucumber::Model::Result: file: lib/Test/BDD/Cucumber/Model/Result.pm version: '0.87' Test::BDD::Cucumber::Model::Scenario: file: lib/Test/BDD/Cucumber/Model/Scenario.pm version: '0.87' Test::BDD::Cucumber::Model::Step: file: lib/Test/BDD/Cucumber/Model/Step.pm version: '0.87' Test::BDD::Cucumber::Model::TagSpec: file: lib/Test/BDD/Cucumber/Model/TagSpec.pm version: '0.87' Test::BDD::Cucumber::Parser: file: lib/Test/BDD/Cucumber/Parser.pm version: '0.87' Test::BDD::Cucumber::StepContext: file: lib/Test/BDD/Cucumber/StepContext.pm version: '0.87' Test::BDD::Cucumber::StepFile: file: lib/Test/BDD/Cucumber/StepFile.pm version: '0.87' Test::BDD::Cucumber::Util: file: lib/Test/BDD/Cucumber/Util.pm version: '0.87' requires: Cucumber::TagExpressions: v5.0.5 File::Find::Rule: '0' JSON::MaybeXS: v1.1.0 List::Util: '1.33' Module::Runtime: '0' Moo: v2.2.2 MooX::HandlesVia: '0' Path::Class: '0' TAP::Parser::Iterator: '0' TAP::Parser::SourceHandler: '0' Term::ANSIColor: '3.00' Test2::API: '1.302087' Test2::Tools::Basic: '0' Test::More: '0' Types::Standard: '0' YAML: '1.15' perl: '5.014' resources: bugtracker: https://github.com/pherkin/test-bdd-cucumber-perl/issues repository: https://github.com/pherkin/test-bdd-cucumber-perl.git version: '0.87' x_contributors: - 'Aliaksei Palkanau ' - 'Ben Little ' - 'Ben Rogers ' - 'Chylli ' - 'DragosTrif ' - 'Gabor Szabo ' - 'Gonzalo Barco ' - 'Grant McLean ' - 'James McCoy ' - 'Lukas Atkinson ' - 'Magnus Enger ' - 'Markus Benning ' - 'Mikhail Ivanov ' - 'Mohammad S Anwar ' - 'Neil Kirsopp ' - 'Pablo Duboue ' - 'Paul Cochrane ' - 'Tomas Pokorny ' - 'Yves Lavoie ' - 'dragos trif ' - 'glauschwuffel ' - 'intrigeri ' - 'poum ' - 'rjp ' - 'vti ' - 'Étienne Mollier ' x_generated_by_perl: v5.38.2 x_serialization_backend: 'YAML::Tiny version 1.76' x_spdx_expression: 'Artistic-1.0-Perl OR GPL-1.0-or-later' MANIFEST100644001750001750 677715014377065 15033 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.033. CHANGES LICENSE MANIFEST META.json META.yml Makefile.PL README.pod bin/pherkin cpanfile dist.ini docker/Dockerfile docker/README.md 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/i18n_es/features/basic.feature examples/i18n_es/features/step_definitions/basic_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/TAP/Parser/Iterator/PherkinStream.pm lib/TAP/Parser/SourceHandler/Feature.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/TAP.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/Dataset.pm lib/Test/BDD/Cucumber/Model/Document.pm lib/Test/BDD/Cucumber/Model/Feature.pm lib/Test/BDD/Cucumber/Model/Line.pm lib/Test/BDD/Cucumber/Model/Result.pm lib/Test/BDD/Cucumber/Model/Scenario.pm lib/Test/BDD/Cucumber/Model/Step.pm lib/Test/BDD/Cucumber/Model/TagSpec.pm lib/Test/BDD/Cucumber/Parser.pm lib/Test/BDD/Cucumber/StepContext.pm lib/Test/BDD/Cucumber/StepFile.pm lib/Test/BDD/Cucumber/Util.pm script/make_corpus.pl script/update-i18n 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/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/901_examples.t t/910_run_tests_pherkin.t t/920_run_tagged_tests_pherkin.t t/author-pod-syntax.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/cucumber_tagging_feature/step_definitions/tagged_steps.pl t/cucumber_tagging_feature/tagged.feature t/harness_json/mock.feature t/harness_json/step_definitions/mock_steps.pl t/lib/Test/CucumberExtensionCount.pm t/lib/Test/CucumberExtensionMetadataVerify.pm t/lib/Test/CucumberExtensionPush.pm t/lib/Test/DumpFeature.pm t/pherkin/match-only/match.feature t/pherkin/match-only/step_definitions/match_steps.pl t/pherkin_config_files/env_replace.yaml 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/test_utf8_test_name.t cpanfile100644001750001750 171715014377065 15373 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87 requires 'perl', '5.014'; requires 'Cucumber::TagExpressions', '5.0.5'; requires 'File::Find::Rule'; requires 'JSON::MaybeXS', '1.1.0'; # List::Util 1.33 adds 'any' requires 'List::Util', '1.33'; requires 'Module::Runtime'; requires 'Moo', '2.2.2'; requires 'MooX::HandlesVia'; requires 'Path::Class'; requires 'TAP::Parser::Iterator'; requires 'TAP::Parser::SourceHandler'; requires 'Term::ANSIColor', '3.00'; # Test2::API 1.302087 adds 'pass' and 'fail' requires 'Test2::API', '1.302087'; requires 'Test2::Tools::Basic'; requires 'Test::More'; requires 'Types::Standard'; # YAML 1.15 fixes the need to have a newline at the end of the input # we used to depend on YAML::Syck which does not have that requirement requires 'YAML', '1.15'; on 'test' => sub { requires 'File::Copy::Recursive'; requires 'IO::Scalar'; requires 'Path::Tiny'; requires 'Test::Differences'; requires 'Test::Exception'; requires 'Test::Pod'; requires 'Test2::V0'; }; dist.ini100644001750001750 143015014377065 15323 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87name = Test-BDD-Cucumber version = 0.87 abstract = Feature-complete Cucumber-style testing in Perl main_module = lib/Test/BDD/Cucumber.pm author = Peter Sergeant author = Erik Huelsmann license = Perl_5 copyright_holder = Peter Sergeant [MetaResources] bugtracker.web = https://github.com/pherkin/test-bdd-cucumber-perl/issues repository.url = https://github.com/pherkin/test-bdd-cucumber-perl.git repository.web = https://github.com/pherkin/test-bdd-cucumber-perl repository.type = git [@Filter] -bundle = @Basic -remove = Readme -remove = GatherDir [PruneCruft] [PkgVersion] use_package = 1 [PodVersion] [PodSyntaxTests] [MetaJSON] [MetaProvides::Package] [ContributorsFromGit] [Git::GatherDir] [Prereqs::FromCPANfile] META.json100644001750001750 1622715014377065 15332 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87{ "abstract" : "Feature-complete Cucumber-style testing in Perl", "author" : [ "Peter Sergeant ", "Erik Huelsmann " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.033, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Test-BDD-Cucumber", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "1.41" } }, "runtime" : { "requires" : { "Cucumber::TagExpressions" : "v5.0.5", "File::Find::Rule" : "0", "JSON::MaybeXS" : "v1.1.0", "List::Util" : "1.33", "Module::Runtime" : "0", "Moo" : "v2.2.2", "MooX::HandlesVia" : "0", "Path::Class" : "0", "TAP::Parser::Iterator" : "0", "TAP::Parser::SourceHandler" : "0", "Term::ANSIColor" : "3.00", "Test2::API" : "1.302087", "Test2::Tools::Basic" : "0", "Test::More" : "0", "Types::Standard" : "0", "YAML" : "1.15", "perl" : "5.014" } }, "test" : { "requires" : { "File::Copy::Recursive" : "0", "IO::Scalar" : "0", "Path::Tiny" : "0", "Test2::V0" : "0", "Test::Differences" : "0", "Test::Exception" : "0", "Test::Pod" : "0" } } }, "provides" : { "App::pherkin" : { "file" : "lib/App/pherkin.pm", "version" : "0.87" }, "TAP::Parser::Iterator::PherkinStream" : { "file" : "lib/TAP/Parser/Iterator/PherkinStream.pm", "version" : "0.87" }, "TAP::Parser::SourceHandler::Feature" : { "file" : "lib/TAP/Parser/SourceHandler/Feature.pm", "version" : "0.87" }, "Test::BDD::Cucumber" : { "file" : "lib/Test/BDD/Cucumber.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Errors" : { "file" : "lib/Test/BDD/Cucumber/Errors.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Executor" : { "file" : "lib/Test/BDD/Cucumber/Executor.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Extension" : { "file" : "lib/Test/BDD/Cucumber/Extension.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Harness" : { "file" : "lib/Test/BDD/Cucumber/Harness.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Harness::Data" : { "file" : "lib/Test/BDD/Cucumber/Harness/Data.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Harness::JSON" : { "file" : "lib/Test/BDD/Cucumber/Harness/JSON.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Harness::TAP" : { "file" : "lib/Test/BDD/Cucumber/Harness/TAP.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Harness::TermColor" : { "file" : "lib/Test/BDD/Cucumber/Harness/TermColor.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Harness::TestBuilder" : { "file" : "lib/Test/BDD/Cucumber/Harness/TestBuilder.pm", "version" : "0.87" }, "Test::BDD::Cucumber::I18N::Data" : { "file" : "lib/Test/BDD/Cucumber/I18N/Data.pm", "version" : "0.87" }, "Test::BDD::Cucumber::I18n" : { "file" : "lib/Test/BDD/Cucumber/I18n.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Loader" : { "file" : "lib/Test/BDD/Cucumber/Loader.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Dataset" : { "file" : "lib/Test/BDD/Cucumber/Model/Dataset.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Document" : { "file" : "lib/Test/BDD/Cucumber/Model/Document.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Feature" : { "file" : "lib/Test/BDD/Cucumber/Model/Feature.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Line" : { "file" : "lib/Test/BDD/Cucumber/Model/Line.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Result" : { "file" : "lib/Test/BDD/Cucumber/Model/Result.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Scenario" : { "file" : "lib/Test/BDD/Cucumber/Model/Scenario.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::Step" : { "file" : "lib/Test/BDD/Cucumber/Model/Step.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Model::TagSpec" : { "file" : "lib/Test/BDD/Cucumber/Model/TagSpec.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Parser" : { "file" : "lib/Test/BDD/Cucumber/Parser.pm", "version" : "0.87" }, "Test::BDD::Cucumber::StepContext" : { "file" : "lib/Test/BDD/Cucumber/StepContext.pm", "version" : "0.87" }, "Test::BDD::Cucumber::StepFile" : { "file" : "lib/Test/BDD/Cucumber/StepFile.pm", "version" : "0.87" }, "Test::BDD::Cucumber::Util" : { "file" : "lib/Test/BDD/Cucumber/Util.pm", "version" : "0.87" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/pherkin/test-bdd-cucumber-perl/issues" }, "repository" : { "type" : "git", "url" : "https://github.com/pherkin/test-bdd-cucumber-perl.git", "web" : "https://github.com/pherkin/test-bdd-cucumber-perl" } }, "version" : "0.87", "x_contributors" : [ "Aliaksei Palkanau ", "Ben Little ", "Ben Rogers ", "Chylli ", "DragosTrif ", "Gabor Szabo ", "Gonzalo Barco ", "Grant McLean ", "James McCoy ", "Lukas Atkinson ", "Magnus Enger ", "Markus Benning ", "Mikhail Ivanov ", "Mohammad S Anwar ", "Neil Kirsopp ", "Pablo Duboue ", "Paul Cochrane ", "Tomas Pokorny ", "Yves Lavoie ", "dragos trif ", "glauschwuffel ", "intrigeri ", "poum ", "rjp ", "vti ", "\u00c9tienne Mollier " ], "x_generated_by_perl" : "v5.38.2", "x_serialization_backend" : "Cpanel::JSON::XS version 4.39", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } README.pod100644001750001750 727715014377065 15337 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87package Test::BDD::Cucumber; 1; # CODE ENDS =head1 NAME Test::BDD::Cucumber - Feature-complete Cucumber-style testing in Perl =head1 SYNOPSIS # Driving tests using the 'pherkin' binary that comes with the distribution $ pherkin -l -b t/ # Or choose a subset of tests to be run by selecting all scenarios tagged 'slow' $ pherkin -l -b --tags @slow t/ # Or all those /not/ tagged 'slow' $ pherkin -l -b --tags ~@slow # Fail on missing steps (by default prints as orange output and succeeds tests) $ pherkin -l -b --strict t/ # Driving tests using 'prove' integration $ prove --source Feature --ext=.feature examples/ # Driving parallel tests using 'prove' $ prove -r --source Feature -j 9 --ext=.feature t/ =head1 DESCRIPTION Cucumber for Perl, integrated with L, L and L. The implementation supports the following Gherkin keywords in feature files: C, C, C, C, C, C, C, C and C. Additionally, C can be used as a synonym for C (with C). This best maps to L, but without support for its new C and C keywords. This implementation supports the same languages as Gherkin 15.0.0 - that is, it supports exactly the same translated keywords. Behaviour of this module is similar to that, but sometimes different from the I Cucumber, the plan is to move use the same parser and behaviour. =head1 GETTING STARTED This module comes with a few introductory tutorials. =over 4 =item * L for those new to Cucumber and BDD testing =item * L to get you started writing the code run for each C, C, C step =item * L =item * L for those who want to extend or hook into feature file execution =item * Documentation of the command-line tool L =back =begin html If you have problems getting started, you can talk to the author(s) here: Chat on Gitter =end html =head1 BUGS AND LIMITATIONS For current bugs, check the issue tracer at GitHub: L One thing need specific mentioning: =over 4 =item * Due to the use of its own parser, differences probably exist in the interpretation of feature files when comparing to Cucumber. Also L for tracking this topic. =back =head1 PROJECT RESOURCES =over 4 =item * Source code repository at L =item * Bug tracker at L =item * Mailing list at L =item * Chat (Gitter) at L =item * Chat (IRC) at L =item * Website at L =back =head1 SEE ALSO L - A Gherkin parser and compiler =head1 AUTHORS Peter Sergeant C Erik Huelsmann C Ben Rodgers C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut Makefile.PL100644001750001750 442515014377065 15640 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.033. use strict; use warnings; use 5.014; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Feature-complete Cucumber-style testing in Perl", "AUTHOR" => "Peter Sergeant , Erik Huelsmann ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Test-BDD-Cucumber", "EXE_FILES" => [ "bin/pherkin" ], "LICENSE" => "perl", "MIN_PERL_VERSION" => "5.014", "NAME" => "Test::BDD::Cucumber", "PREREQ_PM" => { "Cucumber::TagExpressions" => "5.0.5", "File::Find::Rule" => 0, "JSON::MaybeXS" => "1.1.0", "List::Util" => "1.33", "Module::Runtime" => 0, "Moo" => "2.2.2", "MooX::HandlesVia" => 0, "Path::Class" => 0, "TAP::Parser::Iterator" => 0, "TAP::Parser::SourceHandler" => 0, "Term::ANSIColor" => "3.00", "Test2::API" => "1.302087", "Test2::Tools::Basic" => 0, "Test::More" => 0, "Types::Standard" => 0, "YAML" => "1.15" }, "TEST_REQUIRES" => { "File::Copy::Recursive" => 0, "IO::Scalar" => 0, "Path::Tiny" => 0, "Test2::V0" => 0, "Test::Differences" => 0, "Test::Exception" => 0, "Test::Pod" => 0 }, "VERSION" => "0.87", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Cucumber::TagExpressions" => "5.0.5", "File::Copy::Recursive" => 0, "File::Find::Rule" => 0, "IO::Scalar" => 0, "JSON::MaybeXS" => "1.1.0", "List::Util" => "1.33", "Module::Runtime" => 0, "Moo" => "2.2.2", "MooX::HandlesVia" => 0, "Path::Class" => 0, "Path::Tiny" => 0, "TAP::Parser::Iterator" => 0, "TAP::Parser::SourceHandler" => 0, "Term::ANSIColor" => "3.00", "Test2::API" => "1.302087", "Test2::Tools::Basic" => 0, "Test2::V0" => 0, "Test::Differences" => 0, "Test::Exception" => 0, "Test::More" => 0, "Test::Pod" => 0, "Types::Standard" => 0, "YAML" => "1.15" ); 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); bin000755001750001750 015014377065 14271 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87pherkin100755001750001750 1244115014377065 16041 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/bin#!perl =head1 NAME pherkin - Execute tests written using Test::BDD::Cucumber =head1 VERSION version 0.87 =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 Controlling Execution -m, --match Only match steps in from features with available ones --matching [mode] Step function multiple matches behaviour: `first` (default) selects first match, `relaxed` warns and runs first match or `strict` stops execution --strict Requires steps to be defined; fails on undefined and pending steps (steps forcing 'skip') 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 Run scenarios for which the tags satisfy the cucumber tag expression 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 --version Print the version number. -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 TAP some/path/feature # TAP text output (for e.g. prove) =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 or @tag2 is equivalent to: --steps foo/steps --steps ~/steps --output TermColor --tags '@tag1 or @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. =head2 EXTENSION CONFIGURATION Extensions named in the C section of the configuration will be loaded with the configuration from the configuration file: default: includes: # include location where extensions reside on disk - t/lib extensions: # extension with configuration Test::CucumberPush: key1: value1 key2: value2 # extension without configuration Test::CucumberPop: Notice that contrary to all other configuration parameters, the names of the extensions are not prefixed with a dash (i.e. '- t/lib' vs 'Test::CucumberPush'). The example above is the equivalent of use Test::CucumberPush; use Test::CucumberPop; Test::CucumberPush->new({ 'key1' => 'value1', 'key2' => 'value2' }); Test::CucumberPop->new(); =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; } } exit App::pherkin->new()->run(@ARGV); docker000755001750001750 015014377065 14770 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87README.md100644001750001750 70715014377065 16373 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/docker ## Use ### Requirements [docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/) ### Build From the root of the project, run ``` $VERSION= docker build -t pherkin:$VERSION --build-arg VERSION=$VERSION ./docker ``` ### Run In the directory containing your `./features/` directory: ``` docker run -it --rm \ --user $(id -u) \ --volume $PWD:/work \ --workdir /work \ pherkin:$VERSION [options] ``` t000755001750001750 015014377065 13764 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87901_examples.t100644001750001750 347015014377065 16524 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl use strict; use warnings; use Test::More; use File::Temp qw(tempdir); use File::Copy::Recursive qw(dircopy); use Path::Tiny qw(path); my $dir = 'examples'; subtest examples => sub { my @examples = grep { -d $_ } glob("$dir/*"); #diag explain \@examples; for my $example (@examples) { my $exit = system "$^X -Ilib bin/pherkin $example"; is $exit, 0, "exit code of $example"; } }; subtest exit_code_incorrect_test_case => sub { # Try to make some changes in the feature description and expect a non-zero exit-code when --strict is provided my $tempdir = tempdir( CLEANUP => 1 ); #diag $tempdir; dircopy "examples/digest", $tempdir; my $filename = "$tempdir/features/basic.feature"; { my $exit = system "$^X -Ilib bin/pherkin $tempdir"; is $exit, 0, "exit code of broken Digest example"; } my $content = path($filename)->slurp_utf8; $content =~ s/When I've added "foo bar baz" to the object/When I have added "foo bar baz" to the object/; path($filename)->spew_utf8($content); { my $exit = system "$^X -Ilib bin/pherkin $tempdir"; is $exit, 0, "exit code of broken Digest example"; } { my $exit = system "$^X -Ilib bin/pherkin --strict $tempdir"; is $exit, 256, "exit code of broken Digest example"; } }; subtest exit_code_for_bad_results => sub { my $tempdir = tempdir( CLEANUP => 1 ); dircopy "examples/digest", $tempdir; my $filename = "$tempdir/features/basic.feature"; my $content = path($filename)->slurp_utf8; $content =~ s/75ad9f578e43b863590fae52d5d19ce6/somethingelse/; path($filename)->spew_utf8($content); { my $exit = system "$^X -Ilib bin/pherkin $tempdir"; is $exit, 512, "exit code of broken Digest example"; } }; done_testing; Dockerfile100644001750001750 35715014377065 17107 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/dockerARG VERSION FROM perl:5.30 RUN cpanm Carton WORKDIR /usr/src/app RUN echo "requires 'Test::BDD::Cucumber', '${VERSION}'" > cpanfile; carton install ENV PERL5LIB=/usr/src/app/local/lib/perl5 ENTRYPOINT [ "/usr/src/app/local/bin/pherkin" ] 250_i18n_json.t100644001750001750 30015014377065 16460 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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; App000755001750001750 015014377065 15007 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/libpherkin.pm100644001750001750 4221515014377065 17171 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Appuse v5.14; use warnings; package App::pherkin 0.87; use lib; use Getopt::Long; use Module::Runtime qw(use_module module_notional_filename); use List::Util qw(max); use Pod::Usage; use FindBin qw($RealBin $Script); use YAML qw( LoadFile ); use Data::Dumper; use File::Spec; use Path::Class qw/file dir/; use Cucumber::TagExpressions; use Test::BDD::Cucumber::I18n qw(languages langdef readable_keywords keyword_to_subname); use Test::BDD::Cucumber::Loader; use Moo; use Types::Standard qw( ArrayRef Bool Str ); has 'step_paths' => ( is => 'rw', isa => ArrayRef, default => sub { [] } ); has 'extensions' => ( is => 'rw', isa => ArrayRef, default => sub { [] } ); has 'tags' => ( is => 'rw', isa => Str, default => '' ); has 'match_only' => ( is => 'rw', isa => Bool, default => 0 ); has 'matching' => ( is => 'rw', isa => Str, default => 'first'); has 'strict' => ( is => 'rw', isa => Bool, default => 0 ); has 'harness' => ( is => 'rw' ); =head1 NAME App::pherkin - Run Cucumber tests from the command line =head1 VERSION version 0.87 =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 _pre_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 ); die "No feature files found in $features_path" unless @features; $executor->matching( $self->matching ); $executor->add_extensions($_) for @{ $self->extensions }; $_->pre_execute($self) for @{ $self->extensions }; Test::BDD::Cucumber::Loader->load_steps( $executor, $_ ) for @{ $self->step_paths }; return ( $executor, @features ); } sub _post_run { my $self = shift; $_->post_execute() for reverse @{ $self->extensions }; } sub run { my ( $self, @arguments ) = @_; my ( $executor, @features ) = $self->_pre_run(@arguments); if ( $self->match_only ) { $self->_make_executor_match_only($executor) if $self->match_only; $self->_rename_feature_steps( @features ); } my $result = $self->_run_tests( $executor, @features ); $self->_post_run; return $result; } sub _run_tests { my ( $self, $executor, @features ) = @_; my $harness = $self->harness; $harness->startup(); my $tag_spec; if ( $self->tags ) { $tag_spec = Cucumber::TagExpressions->parse( $self->tags ); } $executor->execute( $_, $harness, $tag_spec ) for @features; $harness->shutdown(); my $exit_code = 0; my $result = $harness->result->result; if ($result eq 'failing') { $exit_code = 2; } elsif ($self->strict) { if ($result eq 'pending' or $result eq 'undefined') { $exit_code = 1; } } return $exit_code; } sub _initialize_harness { my ( $self, $harness_module ) = @_; my $harness_args_string = undef; ( $harness_module, $harness_args_string ) = split /\(/, $harness_module, 2; unless ( $harness_module =~ m/::/ ) { $harness_module = "Test::BDD::Cucumber::Harness::" . $harness_module; } eval { use_module($harness_module) } || die "Unable to load harness [$harness_module]: $@"; if ( $harness_args_string ) { my %harness_args; eval "%harness_args = ($harness_args_string; 1" or die $@; $self->harness( $harness_module->new( %harness_args ) ); } else { $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 _replace_helper { my $inval = shift; return $ENV{$inval} // "Environment variable $inval not defined"; } sub _resolve_envvars { my ( $config_data ) = shift; if (ref $config_data) { if (ref $config_data eq 'HASH') { return { map { $_ => _resolve_envvars( $config_data->{$_} ) } keys %$config_data }; } elsif (ref $config_data eq 'ARRAY') { return [ map { _resolve_envvars( $_ ) } @$config_data ]; } else { die 'Unhandled reference type in configuration data'; } } else { # replace (in-place) ${ENVVAR_NAME} sequences with the envvar value $config_data =~ s/(?_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); $config_data_whole = _resolve_envvars( $config_data_whole ) if $config_data_whole; } 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' unless defined $profile_name; # 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 ) { if ( $key ne 'extensions' ) { 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 { die $profile_problem->( "Option $key is a [$reftype] but can only be a HASH as '$key' is" . " a special case - see the documentation for details" ) unless $reftype eq 'HASH' && $key eq 'extensions'; push( @arguments, $key, $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|?' ], version => [ 'version' ], includes => [ 'I=s@', [] ], lib => [ 'l|lib' ], blib => [ 'b|blib' ], output => [ 'o|output=s' ], strict => [ 'strict' ], steps => [ 's|steps=s@', [] ], tags => [ 't|tags=s' ], i18n => [ 'i18n=s' ], extensions => [ 'e|extension=s@', [] ], matching => [ 'matching=s' ], match_only => [ 'm|match' ], ); GetOptions( map { my $x; $_->[1] = \$x unless defined $_->[1]; ( $_->[0] => $_->[1] ); } values %options ); my $deref = sub { my $key = shift; my $value = $options{$key}->[1]; return ( ref $value eq 'ARRAY' ) ? $value : $$value; }; if ( $deref->('version') ) { my ($vol, $dirs, $file) = File::Spec->splitpath( $0 ); my $version = $App::pherkin::VERSION || '(development)'; print "$file $version\n"; exit 0; } pod2usage( -verbose => 1, -input => "$RealBin/$Script", ) if $deref->('help'); my @parsed_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 $@; push( @parsed_extensions, [ $e, \@e_args ] ); } $options{extensions}->[1] = \@parsed_extensions; # 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 ) = @{ $options{$_} }; map { $_ => $ref } map { s/=.+//; $_ } ( split( /\|/, $key_basis ), $_ ); } keys %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 ( $key eq 'extensions' || $key eq 'extension' ) { die "Value of $key in config file expected to be HASH but isn't" if ref $value ne 'HASH'; # if the configuration of the extension is 'undef', then # none was defined. Replace it with an empty hashref, which # is what Moo's 'new()' method wants later on my @e = map { [ $_, [ $value->{$_} || {} ] ] } keys %$value; $value = \@e; my $array = $additions{ 0 + $target } ||= []; push( @$array, @$value ); print "Adding extensions near the front of $key" if $deref->('debug_profiles'); } elsif ( 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( ( defined $value ) ? $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 ( $c, $a ) = @$e; use_module $c; my $instance = $c->new(@$a); push( @{ $self->extensions }, $instance ); my $dir = file( $INC{ module_notional_filename($c) } )->dir; my @step_dirs = map { File::Spec->rel2abs( $_, $dir ) } @{ $instance->step_directories }; unshift( @{ $deref->('steps') }, @step_dirs ); } # Munge the output harness $self->_initialize_harness( $deref->('output') || "TermColor" ); # Store any extra step paths $self->step_paths( $deref->('steps') ); $self->tags( $deref->('tags') // '' ); $self->matching( $deref->('matching') ) if $deref->('matching'); # Match only? $self->match_only( $deref->('match_only') ); $self->strict( $deref->('strict') ) if $deref->('strict'); return ( pop @ARGV ); } 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 scenarioOutline 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 scenarioOutline 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; } sub _make_executor_match_only { my ($self, $executor) = @_; my $match_sub = sub { my $context = shift; $Test::Builder::Test->ok( 1, "Test matched" ); return 1; }; for my $verb ( keys %{$executor->steps} ) { for my $step_tuple ( @{ $executor->steps->{$verb} } ) { $step_tuple->[2] = $match_sub; } } return 1; } sub _rename_feature_steps { my ($self, @features) = @_; my %steps; for my $feature ( @features ) { for my $scenario ( $feature->background, @{ $feature->scenarios } ) { next unless $scenario; for my $step ( @{ $scenario->steps } ) { $steps{ $step . '' } = $step; } } } for my $step_object ( values %steps ) { $step_object->verb_original( 'MATCH MODE: ' . ( $step_object->verb_original || $step_object->verb ) ); } } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; script000755001750001750 015014377065 15025 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87update-i18n100755001750001750 265415014377065 17161 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/script#!/usr/bin/env perl use strict; use warnings; use open ':std', ':encoding(UTF-8)'; use JSON::MaybeXS qw/JSON/; open my $data_in, '<:encoding(UTF-8)', 'lib/Test/BDD/Cucumber/I18N/Data.pm'; open my $data_out, '>:encoding(UTF-8)', 'Data.pm.updated'; system 'curl https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json >languages.json'; open my $json_in, '<:encoding(UTF-8)', 'languages.json' or die "Can't download the gherkin languages JSON: $!";; my $json; { local $/ = undef; $json = <$json_in>; } close $json_in; my $langdefs = JSON()->new->decode($json); for my $lang (values %$langdefs) { for my $keyword (keys %{$lang}) { $lang->{$keyword} = join '|', grep { not m/^[*]\s*$/ } @{$lang->{$keyword}} if ref $lang->{$keyword}; $lang->{$keyword} =~ s/'/\\'/g; } } my $data_line = 'our %languages = (' . join(',', map { my $l = $langdefs->{$_}; ("'$_',{" . join(',', map { "'$_','$l->{$_}'" } sort keys %$l ) . '}') } sort keys %$langdefs ) . ");\n"; while (my $line = <$data_in>) { if ($line =~ m/^our %languages/) { print $data_out $data_line; } else { print $data_out $line; } } close $data_in; close $data_out; system q{rm languages.json}; system q{mv Data.pm.updated lib/Test/BDD/Cucumber/I18N/Data.pm}; print "Successfully regenerated language data.\n"; 220_tag_parsing.t100644001750001750 312015014377065 17166 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.t100755001750001750 145615014377065 17250 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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; 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; open(my $in, '<', $file) or die $?; binmode $in, 'utf8'; { local $/; $file_data = <$in>; } 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(); examples000755001750001750 015014377065 15337 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87pherkin.yml100644001750001750 43015014377065 17637 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.t100644001750001750 265115014377065 17326 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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(); author-pod-syntax.t100644001750001750 45415014377065 17702 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); make_corpus.pl100755001750001750 62715014377065 20022 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/script#!perl use strict; use warnings; use FindBin::libs; use Test::More; use Test::DumpFeature; use Test::BDD::Cucumber::Parser; use YAML; 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.t100644001750001750 364515014377065 17507 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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(); 500_error_formatter.t100644001750001750 737115014377065 20121 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.t100644001750001750 437415014377065 20147 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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(); test_utf8_test_name.t100644001750001750 150715014377065 20300 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/tuse strict; use warnings; use Test2::V0; use Test::BDD::Cucumber::Parser; use Test::BDD::Cucumber::Executor; use Test::BDD::Cucumber::Harness::Data; use utf8; my $feature = Test::BDD::Cucumber::Parser->parse_string( <new(); $executor->add_steps( [ Given => (qr/a passing step called '(.+)'/, {}, sub { my $json = '{ "cc":"Piteşti" }'; is(1, 1, $json ); }) ] ); my $harness = Test::BDD::Cucumber::Harness::Data->new(); $executor->execute( $feature, $harness ); my $result = $harness->feature_status($harness->features->[0])->output; like($result, qr/"Piteşti"/, "utf8 strings are ok in test name"); done_testing; BDD000755001750001750 015014377065 15577 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/TestCucumber.pm100644001750001750 734215014377065 20050 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDDpackage Test::BDD::Cucumber 0.87; 1; # CODE ENDS =head1 NAME Test::BDD::Cucumber - Feature-complete Cucumber-style testing in Perl =head1 VERSION version 0.87 =head1 SYNOPSIS # Driving tests using the 'pherkin' binary that comes with the distribution $ pherkin -l -b t/ # Or choose a subset of tests to be run by selecting all scenarios tagged 'slow' $ pherkin -l -b --tags @slow t/ # Or all those /not/ tagged 'slow' $ pherkin -l -b --tags ~@slow # Fail on missing steps (by default prints as orange output and succeeds tests) $ pherkin -l -b --strict t/ # Driving tests using 'prove' integration $ prove --source Feature --ext=.feature examples/ # Driving parallel tests using 'prove' $ prove -r --source Feature -j 9 --ext=.feature t/ =head1 DESCRIPTION Cucumber for Perl, integrated with L, L and L. The implementation supports the following Gherkin keywords in feature files: C, C, C, C, C, C, C, C and C. Additionally, C can be used as a synonym for C (with C). This best maps to L, but without support for its new C and C keywords. This implementation supports the same languages as Gherkin 15.0.0 - that is, it supports exactly the same translated keywords. Behaviour of this module is similar to that, but sometimes different from the I Cucumber, the plan is to move use the same parser and behaviour. =head1 GETTING STARTED This module comes with a few introductory tutorials. =over 4 =item * L for those new to Cucumber and BDD testing =item * L to get you started writing the code run for each C, C, C step =item * L =item * L for those who want to extend or hook into feature file execution =item * Documentation of the command-line tool L =back =begin html If you have problems getting started, you can talk to the author(s) here: Chat on Gitter =end html =head1 BUGS AND LIMITATIONS For current bugs, check the issue tracer at GitHub: L One thing need specific mentioning: =over 4 =item * Due to the use of its own parser, differences probably exist in the interpretation of feature files when comparing to Cucumber. Also L for tracking this topic. =back =head1 PROJECT RESOURCES =over 4 =item * Source code repository at L =item * Bug tracker at L =item * Mailing list at L =item * Chat (Gitter) at L =item * Chat (IRC) at L =item * Website at L =back =head1 SEE ALSO L - A Gherkin parser and compiler =head1 AUTHORS Peter Sergeant C Erik Huelsmann C Ben Rodgers C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 270_scenario_outlines.t100644001750001750 1164115014377065 20451 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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" ); $@ = undef; $feature = eval { Test::BDD::Cucumber::Parser->parse_string( <' Examples: | name | value | more | columns | | a-name | a-value | some | content | Examples: | name | value | more | columns | | b-name | b-value | some | content | HEREDOC ); }; $error = $@; ok( ! $error, "Two examples correctly parsed"); $feature = Test::BDD::Cucumber::Parser->parse_string( <" to be equal to "an | escaped" Examples: | value | | an \\| escaped | HEREDOC ); my $executor = Test::BDD::Cucumber::Executor->new(); my $harness = Test::BDD::Cucumber::Harness::Data->new(); my $tbl_value; my $expectation; $executor->add_steps( [ Given => (qr/I expect "(.*)" to be equal to "(.*)"/, {}, sub { $tbl_value = $1; $expectation = $2; }) ], ); $executor->execute($feature, $harness); ok(defined $tbl_value, "table value defined"); ok(defined $expectation, "expectation defined"); is($tbl_value, $expectation, "escaped table value equals string value"); $feature = Test::BDD::Cucumber::Parser->parse_string( <" to be equal to "an | escaped" Examples: | value | | an \\| escaped | HEREDOC ); $executor = Test::BDD::Cucumber::Executor->new(); $harness = Test::BDD::Cucumber::Harness::Data->new(); $executor->add_steps( [ Given => (qr/I expect "(.*)" to be equal to "(.*)"/, {}, sub { $tbl_value = $1; $expectation = $2; }) ], ); $executor->execute($feature, $harness); ok(defined $tbl_value, "table value defined"); ok(defined $expectation, "expectation defined"); is($tbl_value, $expectation, "escaped table value equals string value"); $feature = Test::BDD::Cucumber::Parser->parse_string( < """ Examples: | value | | the value | HEREDOC ); $executor = Test::BDD::Cucumber::Executor->new(); $harness = Test::BDD::Cucumber::Harness::Data->new(); $tbl_value = ''; $executor->add_steps( [ Given => (qr/I expect/, {}, sub { my $context = shift; chomp ($tbl_value = $context->data); }) ], ); $executor->execute($feature, $harness); ok(defined $tbl_value, "table value defined"); is($tbl_value, "Expected the value", "expected value equals table value"); $feature = Test::BDD::Cucumber::Parser->parse_string( < | Examples: | value | | the value | HEREDOC ); $executor = Test::BDD::Cucumber::Executor->new(); $harness = Test::BDD::Cucumber::Harness::Data->new(); $tbl_value = ''; $executor->add_steps( [ Given => (qr/I expect/, {}, sub { my $context = shift; $tbl_value = $context->data->[0]->{data}; }) ], ); $executor->execute($feature, $harness); ok(defined $tbl_value, "table value defined"); is($tbl_value, "the value", "expected value equals table value"); done_testing(); 910_run_tests_pherkin.t100644001750001750 55415014377065 20434 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl use strict; use warnings; use lib 't/lib'; use Test::More; use App::pherkin; use Test::CucumberExtensionMetadataVerify; my $pherkin = App::pherkin->new; push @{$pherkin->extensions}, Test::CucumberExtensionMetadataVerify->new; subtest 'Run pherkin in match-only mode', sub { $pherkin->run('-oTAP', '-m', 't/pherkin/match-only/'); }; done_testing; Test000755001750001750 015014377065 15451 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/libDumpFeature.pm100644001750001750 161415014377065 20372 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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, 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.t100644001750001750 102415014377065 20537 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.t100644001750001750 103415014377065 20612 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl use strict; use warnings; use Test::More; use Test::BDD::Cucumber::Loader; use Test::BDD::Cucumber::Harness::TAP; my $harness = Test::BDD::Cucumber::Harness::TAP->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.t100644001750001750 466015014377065 20727 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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-- ), "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 passing again... 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), 'passing', "Scenario 4 passes" ); # 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.t100644001750001750 1552715014377065 21044 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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_deeply( join(", ", sort keys %json_feature), "description, elements, id, keyword, line, name, tags, uri", "feature contains only valid keys" ); 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_deeply( join(", ", sort keys %json_scenario), "description, id, keyword, line, name, steps, tags, type", "scenario contains only valid keys" ); 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}, '', "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_deeply( join(", ", sort keys %success_step), "keyword, line, name, result", "success step contains only valid keys" ); 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}, 'passed', 'passed after failed within outline' ); # 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_json000755001750001750 015014377065 16460 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/tmock.feature100644001750001750 101515014377065 21123 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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" Cucumber000755001750001750 015014377065 17344 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDDI18n.pm100644001750001750 471115014377065 20564 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::I18n 0.87; =encoding utf8 =head1 NAME Test::BDD::Cucumber::I18N - Internationalization =head1 VERSION version 0.87 =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 utf8; use base 'Exporter'; our @EXPORT_OK = qw(languages langdef has_language readable_keywords keyword_to_subname); use Test::BDD::Cucumber::I18N::Data; =head1 METHODS =head2 languages Get codes of supported languages. =cut sub languages { return sort keys %Test::BDD::Cucumber::I18N::Data::languages; } =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) = @_; return exists $Test::BDD::Cucumber::I18N::Data::languages{$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 $Test::BDD::Cucumber::I18N::Data::languages{$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 L, and have been lifted from the Gherkin distribution. =head1 AUTHOR Gregor Goldbach C (based on the works of Pablo Duboue) =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2014-2019, Gregor Goldbach; Licensed under the same terms as Perl =cut 1; Util.pm100644001750001750 221415014377065 20756 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::Util 0.87; =head1 NAME Test::BDD::Cucumber::Util - Some functions used throughout the code =head1 VERSION version 0.87 =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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 400_app_pherkin_harnesses.t100644001750001750 146515014377065 21255 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl use strict; use warnings; use lib 'lib'; 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::TAP", "Test::BDD::Cucumber::Harness::JSON", "Test::BDD::Cucumber::Harness::JSON( json_args => { utf8 => 1, pretty => 0 } )", ); 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.t100644001750001750 563215014377065 21460 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl use strict; use warnings; use lib 't/lib'; use Cwd; 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 ( [ '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/ ], [ 'not_yaml.yaml', default => qr/YAML Error:/ ], ) { 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" ); $ENV{PHERKIN_TEST_REPLACE} = 'test'; is_deeply( [ $class->_load_config( test => $dir->file('env_replace.yaml') ) ], [ extensions => { Test => { bar => '${PHERKIN_TEST_REPLACE}', baz => [ 'test', '${PHERKIN_TEST_REPLACE}' ], foo => 'test', undefined => 'Environment variable UNDEFINED not defined', }, } ], "Replacements performed 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 => {}})', '--extension' => 'Test::CucumberExtensionPush({ id => 3, hash => {}})', ); isa_ok( $p->harness, 'Test::BDD::Cucumber::Harness::Data', 'Harness set' ); is_deeply( $p->{'step_paths'}, [ # extension loaded 3 times dir(getcwd)->subdir(qw( t lib Test extension_steps )), dir(getcwd)->subdir(qw( t lib Test extension_steps )), dir(getcwd)->subdir(qw( t lib Test extension_steps )), 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.pm100644001750001750 642115014377065 21321 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::Errors 0.87; 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.87 =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 2019-2023, Erik Huelsmann Copyright 2014-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Loader.pm100644001750001750 420515014377065 21251 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::Loader 0.87; =head1 NAME Test::BDD::Cucumber::Loader - Simplify loading of Step Definition and feature files =head1 VERSION version 0.87 =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 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 ) = @_; 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 ); } ( $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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Parser.pm100644001750001750 3552615014377065 21331 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::Parser 0.87; =head1 NAME Test::BDD::Cucumber::Parser - Parse Feature files =head1 VERSION version 0.87 =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 Test::BDD::Cucumber::Model::Dataset; 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 ) = @_; return $class->_construct( Test::BDD::Cucumber::Model::Document->new( { content => $string } ) ); } sub parse_file { my ( $class, $string ) = @_; my $content; { local $/; open(my $in, '<', $string) or die $?; binmode $in, 'utf8'; $content = <$in>; close $in or warn $?; } return $class->_construct( Test::BDD::Cucumber::Model::Document->new( { content => $content, filename => '' . $string }) ); } sub _construct { my ( $class, $document ) = @_; my $feature = Test::BDD::Cucumber::Model::Feature->new( { document => $document } ); my @lines = $class->_remove_next_blanks( @{ $document->lines } ); my $language = $class->_extract_language( \@lines ); $feature->language( $language ); my $langdef = langdef( $feature->language ) or die "Declared language '$language' not available"; my $self = bless { langdef => $langdef, _construct_matchers( $langdef ) }, $class; $self->_extract_scenarios( $self->_extract_conditions_of_satisfaction( $self->_extract_feature_name( $feature, @lines ) ) ); return $feature; } sub _construct_matchers { my ($l) = @_; my $step_line_kw_cont = join('|', map { $l->{$_} } qw/given and when then but/); my $step_line_kw_first = join('|', map { $l->{$_} } qw/given when then/); my $scenario_line_kw = join('|', map { $l->{$_} } qw/background scenario scenarioOutline/); return ( _step_line_first => qr/^($step_line_kw_first)(.+)/, _step_line_cont => qr/^($step_line_kw_cont)(.+)/, _feature_line => qr/^($l->{feature}): (.+)/, _scenario_line => qr/^($scenario_line_kw): ?(.*)?/, _examples_line => qr/^($l->{examples}): ?(.+)?$/, _table_line => qr/^\s*\|/, _tags_line => qr/\@([^\s\@]+)/, ); } sub _is_step_line { my ($self, $continuation, $line) = @_; if ($continuation) { return $line =~ $self->{_step_line_cont}; } else { return $line =~ $self->{_step_line_first}; } } sub _is_feature_line { my ($self, $line) = @_; return $line =~ $self->{_feature_line}; } sub _is_scenario_line { my ($self, $line) = @_; return $line =~ $self->{_scenario_line}; } sub _is_table_line { my ($self, $line) = @_; return $line =~ $self->{_table_line}; } sub _is_tags_line { my ($self, $line) = @_; return $line =~ $self->{_tags_line}; } sub _is_examples_line { my ($self, $line) = @_; return $line =~ $self->{_examples_line}; } 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 and @$lines and $lines->[0]->raw_content =~ m{^\s*#\s*language:\s+([^\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 ( my ($keyword, $name) = $self->_is_feature_line( $line->content ) ) { $feature->name($name); $feature->keyword_original($keyword); $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; if ( $self->_is_scenario_line( $line->content ) or $self->_is_tags_line( $line->content ) ) { unshift( @lines, $line ); last; } else { push( @{ $feature->satisfaction }, $line ); } } return $feature, $self->_remove_next_blanks(@lines); } sub _finish_scenario { my ($self, $feature, $line) = @_; # Catch Scenario outlines without examples if ( @{ $feature->scenarios } ) { my $last_scenario = $feature->scenarios->[-1]; if ( $last_scenario->keyword_original =~ m/^($self->{langdef}->{scenarioOutline})/ && !@{ $last_scenario->datasets } ) { die parse_error_from_line( "Outline scenario expects 'Examples:' section", $line || $last_scenario->line ); } } } sub _extract_scenarios { my ( $self, $feature, @lines ) = @_; my $scenarios = 0; my $langdef = $self->{langdef}; my @tags; while ( my $line = shift(@lines) ) { next if $line->is_comment || $line->is_blank; if ( my ( $type, $name ) = $self->_is_examples_line( $line->content ) ) { die q{'Examples:' line before scenario definition} unless @{$feature->scenarios}; my $dataset = Test::BDD::Cucumber::Model::Dataset->new( ( $name ? ( name => $name ) : () ), tags => ( @tags ? [ @{ $feature->scenarios->[-1]->tags }, @tags ] # Reuse the ref to the scenario tags to allow # detecting 'no dataset tags' in ::Scenario : $feature->scenarios->[-1]->tags ), line => $line, ); @tags = (); @lines = $self->_extract_examples_description( $dataset, @lines ); @lines = $self->_extract_table( 6, $dataset, $self->_remove_next_blanks(@lines) ); if (@{$feature->scenarios->[-1]->datasets}) { my $prev_ds = $feature->scenarios->[-1]->datasets->[0]; my $prev_ds_cols = join '|', sort keys %{$prev_ds->data->[0]}; my $cur_ds_cols = join '|', sort keys %{$dataset->data->[0]}; die parse_error_from_line( q{Columns of 'Examples:' not in line with } . q{previous 'Examples:' } . qq{('$prev_ds_cols' vs '$cur_ds_cols')}, $line ) if $prev_ds_cols ne $cur_ds_cols; } push @{$feature->scenarios->[-1]->datasets}, $dataset; } elsif ( ( $type, $name ) = $self->_is_scenario_line( $line->content ) ) { $self->_finish_scenario( $feature, $line ); # 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, keyword => ($type =~ m/^($langdef->{background})/ ? 'Background' : ($type =~ m/^($langdef->{scenarioOutline})/ ? 'Scenario Outline' : 'Scenario')), keyword_original => $type, line => $line, tags => [ @{ $feature->tags }, @tags ] } ); @tags = (); # Attempt to populate it @lines = $self->_extract_scenario_description($scenario, @lines); @lines = $self->_extract_steps( $feature, $scenario, @lines ); if ( $type =~ m/^($langdef->{background})/ ) { $feature->background($scenario); } else { push( @{ $feature->scenarios }, $scenario ); } # Scenario-level tags } elsif ( $line->content =~ m/^\s*\@\w/ ) { push @tags, ( $line->content =~ m/(\@[^\s\@]+)/g ); } else { die parse_error_from_line( "Malformed scenario line", $line ); } } $self->_finish_scenario( $feature ); 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]; my ( $verb, $text ); while ( @lines and ($lines[0]->is_comment or ($verb, $text) = $self->_is_step_line( 1, $lines[0]->content ) ) ) { my $line = shift @lines; next if $line->is_comment; 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; # Remove the ending space for languages that # have it, for backward compatibility $original_verb =~ s/ $//; 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 ); } return $self->_remove_next_blanks(@lines); } sub _extract_examples_description { my ( $self, $examples, @lines ) = @_; while ( my $line = shift @lines ) { next if $line->is_comment; my $content = $line->content; return ( $line, @lines ) if $self->_is_table_line( $content ) or $self->_is_examples_line( $content ) or $self->_is_tags_line( $content ) or $self->_is_scenario_line( $content ); push @{$examples->description}, $line; } return @lines; } sub _extract_scenario_description { my ( $self, $scenario, @lines ) = @_; while ( @lines and ($lines[0]->is_comment or (not $self->_is_step_line(0, $lines[0]->content) and not $self->_is_examples_line($lines[0]->content) and not $self->_is_tags_line($lines[0]->content) and not $self->_is_scenario_line($lines[0]->content) ) ) ) { push @{$scenario->description}, shift(@lines); } return @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( /(? =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut Harness.pm100644001750001750 1037515014377065 21473 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::Harness 0.87; =head1 NAME Test::BDD::Cucumber::Harness - Base class for creating harnesses =head1 VERSION version 0.87 =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 Moo; use Types::Standard qw( ArrayRef ); 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 scenario_skip Called instead of C and C when a scenario is skipped. Although the only reason for skipping scenarios currently is being excluded due to tag expressions, this may change in the future. =cut sub scenario_skip { 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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 920_run_tagged_tests_pherkin.t100644001750001750 455115014377065 21771 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t#!perl use strict; use warnings; use JSON::MaybeXS 'decode_json'; use Test::More; use App::pherkin; do { my $output = ''; open my $fh, '>', \$output; local *STDOUT = $fh; my $pherkin = App::pherkin->new; $pherkin->run('-oJSON', '--tags', '@include', 't/cucumber_tagging_feature/tagged.feature'); ok $output, 'Have JSON output'; my $data = decode_json($output); my $feature = pop @{ $data }; is $feature->{keyword}, 'Feature'; is($_->{type}, 'scenario') for (@{ $feature->{elements} }); is($_->{keyword}, 'Scenario') for (@{ $feature->{elements} }); is(scalar(@{ $feature->{elements} }), 1); }; do { my $output = ''; open my $fh, '>', \$output; local *STDOUT = $fh; my $pherkin = App::pherkin->new; $pherkin->run('-oJSON', '--tags', 'not @exclude', 't/cucumber_tagging_feature/tagged.feature'); ok $output, 'Have JSON output'; my $data = decode_json($output); my $feature = pop @{ $data }; is $feature->{keyword}, 'Feature'; is($_->{type}, 'scenario') for (@{ $feature->{elements} }); is($_->{keyword}, 'Scenario') for (@{ $feature->{elements} }); is(scalar(@{ $feature->{elements} }), 2); }; do { my $output = ''; open my $fh, '>', \$output; local *STDOUT = $fh; my $pherkin = App::pherkin->new; $pherkin->run('-oJSON', '--tags', '@include or @exclude', 't/cucumber_tagging_feature/tagged.feature'); ok $output, 'Have JSON output'; my $data = decode_json($output); my $feature = pop @{ $data }; is $feature->{keyword}, 'Feature'; is($_->{type}, 'scenario') for (@{ $feature->{elements} }); is($_->{keyword}, 'Scenario') for (@{ $feature->{elements} }); is(scalar(@{ $feature->{elements} }), 2); }; do { my $output = ''; open my $fh, '>', \$output; local *STDOUT = $fh; my $pherkin = App::pherkin->new; $pherkin->run('-oJSON', '--tags', 'not @include and not @exclude', 't/cucumber_tagging_feature/tagged.feature'); ok $output, 'Have JSON output'; my $data = decode_json($output); my $feature = pop @{ $data }; is $feature->{keyword}, 'Feature'; is($_->{type}, 'scenario') for (@{ $feature->{elements} }); is($_->{keyword}, 'Scenario') for (@{ $feature->{elements} }); is(scalar(@{ $feature->{elements} }), 1); }; done_testing; Executor.pm100644001750001750 5713515014377065 21673 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber use v5.14; use warnings; package Test::BDD::Cucumber::Executor 0.87; =head1 NAME Test::BDD::Cucumber::Executor - Run through Feature and Harness objects =head1 VERSION version 0.87 =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 Moo; use MooX::HandlesVia; use Types::Standard qw( Bool Str ArrayRef HashRef ); use List::Util qw/first any/; use Module::Runtime qw/use_module/; use utf8; use Carp qw(carp croak); use Encode (); use Test2::API qw/intercept/; # Use-ing the formatter results in a # 'loaded too late to be used globally' warning # But we only need it locally anyway. require Test2::Formatter::TAP; use Test2::Tools::Basic qw/ pass fail done_testing /; # Needed for subtest() -- we don't want to import all its functions though require Test::More; use Test::BDD::Cucumber::StepFile (); 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/; =head1 ATTRIBUTES =head2 matching The value of this attribute should be one of C (default), C and C. By default (C), the first matching step is executed immediately, terminating the search for (further) matching steps. When C is set to anything other than C, all steps are checked for matches. When set to C, a warning will be generated on multiple matches. When set to C, an exception will be thrown. =cut has matching => ( is => 'rw', isa => Str, default => 'first'); =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, default => sub { [] }, handles_via => 'Array', 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 {} ], ), Or, when metadata is specified with the step, of the form: ( [ Given => qr//, { meta => $data }, sub {} ] ), (where the hashref stores step metadata) 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, $meta, $code ); if (@$_ == 3) { ( $verb, $match, $code ) = @$_; $meta = {}; } else { ( $verb, $match, $meta, $code ) = @$_; } $verb = lc $verb; 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, $meta, $code ] ); } else { push( @{ $self->{'steps'}->{$verb} }, [ $match, $meta, $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() }; $_->pre_feature( $feature, $feature_stash ) for @{ $self->extensions }; for my $outline (@scenarios) { # Execute the scenario itself $self->execute_outline( { @background, scenario => $outline, feature => $feature, feature_stash => $feature_stash, harness => $harness, tagspec => $tag_spec, } ); } $_->post_feature( $feature, $feature_stash, 'no' ) for reverse @{ $self->extensions }; $harness->feature_done($feature); } =head2 execute_outline Accepts a hashref of options and executes each scenario definition in the scenario outline, or, lacking an outline, executes the single defined scenario. Options: C< feature > - A L object C< feature_stash > - A hashref that should live the lifetime of feature execution C< harness > - A L subclass object C< outline > - A L object C< background > - An optional L object representing the Background =cut sub _match_tags { my ($spec, @tagged_components) = @_; state $deprecation_warned = 0; if ($spec->isa('Cucumber::TagExpressions::ExpressionNode')) { return grep { $spec->evaluate( @{ $_->tags } ) } @tagged_components; } else { $deprecation_warned ||= carp 'Test::BDD::Cucumber::Model::TagSpec is deprecated; replace with Cucumber::TagExpressions'; return $spec->filter( @tagged_components ); } } sub execute_outline { my ( $self, $options ) = @_; my ( $feature, $feature_stash, $harness, $outline, $background, $tagspec ) = @$options{qw/ feature feature_stash harness scenario background tagspec /}; # Multiply out Scenario Outlines as appropriate my @datasets = @{ $outline->datasets }; if (not @datasets) { if (not $tagspec or _match_tags( $tagspec, $outline )) { $self->execute_scenario( { feature => $feature, feature_stash => $feature_stash, harness => $harness, scenario => $outline, background => $background, scenario_stash => {}, dataset => {}, }); } $harness->scenario_skip( $outline, {} ); return; } if ($tagspec) { @datasets = _match_tags( $tagspec, @datasets ); unless (@datasets) { $harness->scenario_skip( $outline, {} ); return; } } foreach my $rows (@datasets) { foreach my $row (@{$rows->data}) { my $name = $outline->{name} || ""; $name =~ s/\Q<$_>\E/$row->{$_}/g for (keys %$row); local $outline->{name} = $name; $self->execute_scenario( { feature => $feature, feature_stash => $feature_stash, harness => $harness, scenario => $outline, background => $background, scenario_stash => {}, dataset => $row, }); } } } =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 - A hashref that lives the lifetime of the scenario execution 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_steps { my ( $self, $options ) = @_; my ( $feature, $feature_stash, $harness, $outline, $scenario_stash, $scenario_state, $dataset, $context_defaults ) = @$options{ qw/ feature feature_stash harness scenario scenario_stash scenario_state dataset context_defaults / }; foreach my $step ( @{ $outline->steps } ) { # Multiply out any placeholders my $text = $self->add_placeholders( $step->text, $dataset, $step->line ); my $data = $step->data; $data = (ref $data) ? $self->add_table_placeholders( $data, $dataset, $step->line ) : (defined $data) ? $self->add_placeholders( $data, $dataset, $step->line ) : ''; # Set up a context my $context = Test::BDD::Cucumber::StepContext->new( { %$context_defaults, # Data portion columns => $step->columns || [], data => $data, # Step-specific info step => $step, verb => lc( $step->verb ), text => $text, } ); my $result = $self->find_and_dispatch( $context, $scenario_state->{'short_circuit'}, 0 ); # If it didn't pass, short-circuit the rest unless ( $result->result eq 'passing' ) { $scenario_state->{'short_circuit'}++; } } return; } sub _execute_hook_steps { my ( $self, $phase, $context_defaults, $scenario_state ) = @_; my $want_short = ($phase eq 'before'); for my $step ( @{ $self->{'steps'}->{$phase} || [] } ) { my $context = Test::BDD::Cucumber::StepContext->new( { %$context_defaults, verb => $phase, } ); my $result = $self->dispatch( $context, $step, ($want_short ? $scenario_state->{'short_circuit'} : 0), 0 ); # If it didn't pass, short-circuit the rest unless ( $result->result eq 'passing' ) { if ($want_short) { $scenario_state->{'short_circuit'} = 1; } } } return; } sub execute_scenario { my ( $self, $options ) = @_; my ( $feature, $feature_stash, $harness, $outline, $background_obj, $scenario_stash, $dataset ) = @$options{ qw/ feature feature_stash harness scenario background scenario_stash dataset / }; my $scenario_state = {}; 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'} || [], ); $context_defaults{stash}->{scenario} = $scenario_stash; $harness->scenario( $outline, $dataset, $scenario_stash->{'longest_step_line'} ); $_->pre_scenario( $outline, $feature_stash, $scenario_stash ) for @{ $self->extensions }; $self->_execute_hook_steps( 'before', \%context_defaults, $scenario_state ); if ($background_obj) { $harness->background( $outline, $dataset, $scenario_stash->{'longest_step_line'} ); $self->_execute_steps( { scenario => $background_obj, feature => $feature, feature_stash => $feature_stash, harness => $harness, scenario_stash => $scenario_stash, scenario_state => $scenario_state, context_defaults => \%context_defaults, } ); $harness->background_done( $outline, $dataset ); } $self->_execute_steps( { scenario => $outline, feature => $feature, feature_stash => $feature_stash, harness => $harness, scenario_stash => $scenario_stash, scenario_state => $scenario_state, dataset => $dataset, context_defaults => \%context_defaults, }); $self->_execute_hook_steps( 'after', \%context_defaults, $scenario_state ); $_->post_scenario( $outline, $feature_stash, $scenario_stash, $scenario_state->{'short_circuit'} ) for reverse @{ $self->extensions }; $harness->scenario_done( $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 add_table_placeholders Accepts a hash with parsed table data and a hashref, and replaces C< > with the values in the hashref, returning a copy of the parsed table hashref. =cut sub add_table_placeholders { my ($self, $tbl, $dataset, $line) = @_; my @rv = map { my $row = $_; my %inner_rv = map { $_ => $self->add_placeholders($row->{$_}, $dataset, $line) } keys %$row; \%inner_rv; } @$tbl; return \@rv; } =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 (unless C<$self->matching> indicates otherwise). 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 $stepdef; my $text = $context->text; if ($self->matching eq 'first') { $stepdef = first { $text =~ $_->[0] } @{ $self->{'steps'}->{ $context->verb } || [] }, @{ $self->{'steps'}->{'step'} || [] }; } else { my @stepdefs = grep { $text =~ $_->[0] } @{ $self->{'steps'}->{ $context->verb } || [] }, @{ $self->{'steps'}->{'step'} || [] }; if (@stepdefs > 1) { my $filename = $context->step->line->document->filename; my $line = $context->step->line->number; my $msg = join("\n ", qq(Step "$text" ($filename:$line) matches multiple step functions:), map { qq{matcher $_->[0] defined at } . (($_->[1]->{source} && $_->[1]->{line}) ? "$_->[1]->{source}:$_->[1]->{line}" : '') } @stepdefs); if ($self->matching eq 'relaxed') { warn $msg; } else { die $msg; } } $stepdef = shift @stepdefs; } # Deal with the simple case of no-match first of all unless ($stepdef) { my $message = "No matching step definition for: " . $context->verb . ' ' . $context->text; my $result = $self->skip_step( $context, 'undefined', $message, $redispatch ); return $result; } $_->pre_step( $stepdef, $context ) for @{ $self->extensions }; my $result = $self->dispatch( $context, $stepdef, 0, $redispatch ); $_->post_step( $stepdef, $context, ( $result->result ne 'passing' ), $result ) for reverse @{ $self->extensions }; return $result; } =head2 dispatch($context, $stepdef, $short_circuit, $redispatch) Accepts a L object, and a reference to a step definition triplet (verb, metadata hashref, coderef) and executes it the coderef. 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, $stepdef, $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, $meta, $coderef ) = @$stepdef; 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); my @match_locations; my $stash_keys = join ';', sort keys %{$context->stash}; # Using `intercept()`, run the step function in an isolated # environment -- this should not affect the enclosing scope # which might be a TAP::Harness scope. # # Instead, we want the tests inside this scope to map to # status values my $events = intercept { # This is a hack to make Test::More's $TODO variable work # inside the intercepted scope. ###TODO: Both intercept() and Test::More::subtest() should # be replaced by a specific Hub implementation for T::B::C Test::More::subtest( 'execute step', sub { # 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 = @+; # Store the string position of matches for highlighting @match_locations = map { [ $_, shift @ends ] } @starts; # OK, actually execute local $@; eval { no warnings 'redefine'; local *Test::BDD::Cucumber::StepFile::_S = sub { return $context->stash->{'scenario'}; }; local *Test::BDD::Cucumber::StepFile::_C = sub { return $context; }; $coderef->($context) }; if ($@) { fail("Step ran to completion", "Exception: ", $@); } else { pass("Step ran to completion"); } done_testing(); }); }; my $status = $self->_test_status( $events ); my $result = Test::BDD::Cucumber::Model::Result->new( { result => $status, # due to the hack above with the subtest inside the # interception scope, we need to grovel the subtest # from out of the other results first. output => $self->_test_output( (first { $_->isa('Test2::Event::Subtest') } @$events)->{subevents}), }); warn qq|Unsupported: Step modified C->stash instead of C->stash->{scenario} or C->stash->{feature}| if $stash_keys ne (join ';', sort keys %{$context->stash}); 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; my %range = map { $_ => 1 } map { $_->[0] .. ($_->[1] - 1) } @match_locations; # Walk the string, splitting my @parts = ( [ 0, '' ] ); for ( 0 .. ( ( length $text ) - 1 ) ) { my $to_highlight = $range{$_} || 0; my $character = substr( $text, $_, 1 ); if ( $parts[-1]->[0] != $to_highlight ) { push( @parts, [ $to_highlight, '' ] ); } $parts[-1]->[1] .= $character; } return @parts; } sub _test_output { my ($self, $events) = @_; my $fmt = Test2::Formatter::TAP->new(); open my $stdout, '>:encoding(UTF-8)', \my $out_text; my $idx = 0; $fmt->set_handles([ $stdout, $stdout ]); $self->_test_output_from_subevents($events, $fmt, \$idx); close $stdout; return Encode::decode('utf8', $out_text); } sub _test_output_from_subevents { my ($self, $events, $fmt, $idx) = @_; for my $event (@$events) { if ($event->{subevents}) { $self->_test_output_from_subevents( $event->{subevents}, $fmt, $idx); } else { $fmt->write($event, $$idx++); } } } sub _test_status { my $self = shift; my $events = shift; if (any { defined $_->{effective_pass} and ! $_->{effective_pass} } @$events) { return 'failing'; } else { return $self->_test_status_from_subevents($events) ? 'pending' : 'passing'; } } sub _test_status_from_subevents { my $self = shift; my $events = shift; for my $e (@$events) { if (exists $e->{subevents}) { $self->_test_status_from_subevents($e->{subevents}) and return 1; } elsif (defined $e->{amnesty} and $e->{effective_pass} and (not $e->{pass}) and any { $_->{tag} eq 'TODO' } @{$e->{amnesty}}) { return 1; } } return 0; } =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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; StepFile.pm100644001750001750 1133215014377065 21575 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::StepFile 0.87; =head1 NAME Test::BDD::Cucumber::StepFile - Functions for creating and loading Step Definitions =head1 VERSION version 0.87 =cut use utf8; use Carp qw/croak/; use File::Spec; use Scalar::Util qw/reftype/; 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; # or: use strict; use warnings; use Test2::V0; 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 sub _ensure_meta { my ($p, $f, $l) = caller(1); if (ref $_[1] and reftype $_[1] eq 'HASH') { $_[1]->{source} = $f; $_[1]->{line} = $l; return @_; } else { return ($_[0], { source => $f, line => $l }, $_[1]); } } # Mapped to Given, When, and Then as part of the i18n mapping below sub _Given { push( @definitions, [ Given => _ensure_meta(@_) ] ) } sub _When { push( @definitions, [ When => _ensure_meta(@_) ] ) } sub _Then { push( @definitions, [ Then => _ensure_meta(@_) ] ) } sub Step { push( @definitions, [ Step => _ensure_meta(@_) ] ) } sub Transform { push( @definitions, [ Transform => _ensure_meta(@_) ] ) } sub Before { push( @definitions, [ Before => _ensure_meta(qr//, @_) ] ) } sub After { push( @definitions, [ After => _ensure_meta(qr//, @_) ] ) } 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); next unless length $subname; { 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 # We need an extra level of indirection when we want to support step functions # loaded into their own packages (which we do, for cleanliness); the exporter # binds the subs declared below to S and C symbols in the imported-into package # That prevents us from binding a different function to these symbols at # execution time. # We *can* bind the _S and _C functions declared below. sub S { _S() } sub C { _C() } 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; # Debian Jessie with security patches requires an absolute path do File::Spec->rel2abs($filename); die "Step file [$filename] failed to load: $@" if $@; return @definitions; } } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Extension.pm100644001750001750 1023215014377065 22034 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::Extension 0.87; =head1 NAME Test::BDD::Cucumber::Extension - Abstract superclass for extensions =head1 VERSION version 0.87 =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 Moo; use Types::Standard qw( HashRef ); =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_execute($app) Invoked by C before executing any features. This callback allows generic extension setup. Reports errors by calling croak(). It is called once per C instance. Note that the C plugin for C might instantiate multiple C objects, meaning it will create multiple instances of the extensions too. As such, this callback may be called once per instance, but multiple times in a Perl image. The source handler Cs the running Perl instance in order to support the parallel testing C<-j> option. This callback will be called pre-fork. =head2 post_execute() Invoked by C after executing all features. This callback allows generic extension teardown and cleanup. Reports errors by calling croak(). Note: When the C plugin for C is used, there are no guarantees at this point that this hook is called only once. =cut sub pre_execute { return; } sub post_execute { 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 respective 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 respective 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($stepdef, $step_context) Invoked by the Executor before executing each step in $scenario. Reports errors by calling croak(). C<$stepdef> contains a reference to an array with step data: [ qr//, { meta => $data }, $code ] Feature and scenario stashes can be reached through $step_context->stash->{feature} # and $step_context->stash->{scenario} Feature, scenario and step (from the feature file) are available as $step_context->feature $step_context->scenario $step_context->step Note: B steps, so not called for skipped steps. =head2 post_step($stepdef, $step_context, $failed, $result) Invoked by the Executor after each executed step in $scenario. Reports errors by calling croak(). $failed indicates that the step has not been completed successfully; this means the step can have failed, be marked as TODO or pending (not implemented). $result is a C instance which holds the completion status of the step. Note: B 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-2023, Erik Huelsmann; Licensed under the same terms as Perl =cut 1; I18N000755001750001750 015014377065 20023 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/CucumberData.pm100644001750001750 7402715014377065 21424 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/I18Nuse v5.14; use warnings; package Test::BDD::Cucumber::I18N::Data 0.87; use utf8; =encoding utf8 =head1 NAME Test::BDD::Cucumber::I18N::Data - Cucumber language definitions =head1 VERSION version 0.87 =head1 DESCRIPTION Cucumber language definitions =head1 PROVENANCE This file is a very small wrapper around the L file from L. L reads: The MIT License (MIT) Copyright (c) Cucumber Ltd, Gaspar Nagy, Björn Rasmusson, Peter Sergeant 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 None. =cut # The below is a transformed content from # https://raw.githubusercontent.com/cucumber/cucumber/master/gherkin/gherkin-languages.json # # Refresh with: # # scripts/update-i18n # our %languages = ('af',{'and','En ','background','Agtergrond','but','Maar ','examples','Voorbeelde','feature','Funksie|Besigheid Behoefte|Vermoë','given','Gegewe ','name','Afrikaans','native','Afrikaans','rule','Regel','scenario','Voorbeeld|Situasie','scenarioOutline','Situasie Uiteensetting','then','Dan ','when','Wanneer '},'am',{'and','Եվ ','background','Կոնտեքստ','but','Բայց ','examples','Օրինակներ','feature','Ֆունկցիոնալություն|Հատկություն','given','Դիցուք ','name','Armenian','native','հայերեն','rule','Rule','scenario','Օրինակ|Սցենար','scenarioOutline','Սցենարի կառուցվացքը','then','Ապա ','when','Եթե |Երբ '},'an',{'and','Y |E ','background','Antecedents','but','Pero ','examples','Eixemplos','feature','Caracteristica','given','Dau |Dada |Daus |Dadas ','name','Aragonese','native','Aragonés','rule','Rule','scenario','Eixemplo|Caso','scenarioOutline','Esquema del caso','then','Alavez |Allora |Antonces ','when','Cuan '},'ar',{'and','و ','background','الخلفية','but','لكن ','examples','امثلة','feature','خاصية','given','بفرض ','name','Arabic','native','العربية','rule','Rule','scenario','مثال|سيناريو','scenarioOutline','سيناريو مخطط','then','اذاً |ثم ','when','متى |عندما '},'ast',{'and','Y |Ya ','background','Antecedentes','but','Peru ','examples','Exemplos','feature','Carauterística','given','Dáu |Dada |Daos |Daes ','name','Asturian','native','asturianu','rule','Rule','scenario','Exemplo|Casu','scenarioOutline','Esbozu del casu','then','Entós ','when','Cuando '},'az',{'and','Və |Həm ','background','Keçmiş|Kontekst','but','Amma |Ancaq ','examples','Nümunələr','feature','Özəllik','given','Tutaq ki |Verilir ','name','Azerbaijani','native','Azərbaycanca','rule','Rule','scenario','Nümunə|Ssenari','scenarioOutline','Ssenarinin strukturu','then','O halda ','when','Əgər |Nə vaxt ki '},'bg',{'and','И ','background','Предистория','but','Но ','examples','Примери','feature','Функционалност','given','Дадено ','name','Bulgarian','native','български','rule','Правило','scenario','Пример|Сценарий','scenarioOutline','Рамка на сценарий','then','То ','when','Когато '},'bm',{'and','Dan ','background','Latar Belakang','but','Tetapi |Tapi ','examples','Contoh','feature','Fungsi','given','Diberi |Bagi ','name','Malay','native','Bahasa Melayu','rule','Rule','scenario','Senario|Situasi|Keadaan','scenarioOutline','Kerangka Senario|Kerangka Situasi|Kerangka Keadaan|Garis Panduan Senario','then','Maka |Kemudian ','when','Apabila '},'bs',{'and','I |A ','background','Pozadina','but','Ali ','examples','Primjeri','feature','Karakteristika','given','Dato ','name','Bosnian','native','Bosanski','rule','Rule','scenario','Primjer|Scenariju|Scenario','scenarioOutline','Scenariju-obris|Scenario-outline','then','Zatim ','when','Kada '},'ca',{'and','I ','background','Rerefons|Antecedents','but','Però ','examples','Exemples','feature','Característica|Funcionalitat','given','Donat |Donada |Atès |Atesa ','name','Catalan','native','català','rule','Rule','scenario','Exemple|Escenari','scenarioOutline','Esquema de l\'escenari','then','Aleshores |Cal ','when','Quan '},'cs',{'and','A také |A ','background','Pozadí|Kontext','but','Ale ','examples','Příklady','feature','Požadavek','given','Pokud |Za předpokladu ','name','Czech','native','Česky','rule','Pravidlo','scenario','Příklad|Scénář','scenarioOutline','Náčrt Scénáře|Osnova scénáře','then','Pak ','when','Když '},'cy-GB',{'and','A ','background','Cefndir','but','Ond ','examples','Enghreifftiau','feature','Arwedd','given','Anrhegedig a ','name','Welsh','native','Cymraeg','rule','Rule','scenario','Enghraifft|Scenario','scenarioOutline','Scenario Amlinellol','then','Yna ','when','Pryd '},'da',{'and','Og ','background','Baggrund','but','Men ','examples','Eksempler','feature','Egenskab','given','Givet ','name','Danish','native','dansk','rule','Rule','scenario','Eksempel|Scenarie','scenarioOutline','Abstrakt Scenario','then','Så ','when','Når '},'de',{'and','Und ','background','Grundlage|Hintergrund|Voraussetzungen|Vorbedingungen','but','Aber ','examples','Beispiele','feature','Funktionalität|Funktion','given','Angenommen |Gegeben sei |Gegeben seien ','name','German','native','Deutsch','rule','Rule|Regel','scenario','Beispiel|Szenario','scenarioOutline','Szenariogrundriss|Szenarien','then','Dann ','when','Wenn '},'el',{'and','Και ','background','Υπόβαθρο','but','Αλλά ','examples','Παραδείγματα|Σενάρια','feature','Δυνατότητα|Λειτουργία','given','Δεδομένου ','name','Greek','native','Ελληνικά','rule','Rule','scenario','Παράδειγμα|Σενάριο','scenarioOutline','Περιγραφή Σεναρίου|Περίγραμμα Σεναρίου','then','Τότε ','when','Όταν '},'em',{'and','😂','background','💤','but','😔','examples','📓','feature','📚','given','😐','name','Emoji','native','😀','rule','Rule','scenario','🥒|📕','scenarioOutline','📖','then','🙏','when','🎬'},'en',{'and','And ','background','Background','but','But ','examples','Examples|Scenarios','feature','Feature|Business Need|Ability','given','Given ','name','English','native','English','rule','Rule','scenario','Example|Scenario','scenarioOutline','Scenario Outline|Scenario Template','then','Then ','when','When '},'en-Scouse',{'and','An ','background','Dis is what went down','but','Buh ','examples','Examples','feature','Feature','given','Givun |Youse know when youse got ','name','Scouse','native','Scouse','rule','Rule','scenario','The thing of it is','scenarioOutline','Wharrimean is','then','Dun |Den youse gotta ','when','Wun |Youse know like when '},'en-au',{'and','Too right ','background','First off','but','Yeah nah ','examples','You\'ll wanna','feature','Pretty much','given','Y\'know ','name','Australian','native','Australian','rule','Rule','scenario','Awww, look mate','scenarioOutline','Reckon it\'s like','then','But at the end of the day I reckon ','when','It\'s just unbelievable '},'en-lol',{'and','AN ','background','B4','but','BUT ','examples','EXAMPLZ','feature','OH HAI','given','I CAN HAZ ','name','LOLCAT','native','LOLCAT','rule','Rule','scenario','MISHUN','scenarioOutline','MISHUN SRSLY','then','DEN ','when','WEN '},'en-old',{'and','Ond |7 ','background','Aer|Ær','but','Ac ','examples','Se the|Se þe|Se ðe','feature','Hwaet|Hwæt','given','Thurh |Þurh |Ðurh ','name','Old English','native','Englisc','rule','Rule','scenario','Swa','scenarioOutline','Swa hwaer swa|Swa hwær swa','then','Tha |Þa |Ða |Tha the |Þa þe |Ða ðe ','when','Bæþsealf |Bæþsealfa |Bæþsealfe |Ciricæw |Ciricæwe |Ciricæwa '},'en-pirate',{'and','Aye ','background','Yo-ho-ho','but','Avast! ','examples','Dead men tell no tales','feature','Ahoy matey!','given','Gangway! ','name','Pirate','native','Pirate','rule','Rule','scenario','Heave to','scenarioOutline','Shiver me timbers','then','Let go and haul ','when','Blimey! '},'en-tx',{'and','Come hell or high water ','background','Lemme tell y\'all a story','but','Well now hold on, I\'ll you what ','examples','Now that\'s a story longer than a cattle drive in July','feature','This ain’t my first rodeo|All gussied up','given','Fixin\' to |All git out ','name','Texas','native','Texas','rule','Rule ','scenario','All hat and no cattle','scenarioOutline','Serious as a snake bite|Busy as a hound in flea season','then','There’s no tree but bears some fruit ','when','Quick out of the chute '},'eo',{'and','Kaj ','background','Fono','but','Sed ','examples','Ekzemploj','feature','Trajto','given','Donitaĵo |Komence ','name','Esperanto','native','Esperanto','rule','Rule','scenario','Ekzemplo|Scenaro|Kazo','scenarioOutline','Konturo de la scenaro|Skizo|Kazo-skizo','then','Do ','when','Se '},'es',{'and','Y |E ','background','Antecedentes','but','Pero ','examples','Ejemplos','feature','Característica|Necesidad del negocio|Requisito','given','Dado |Dada |Dados |Dadas ','name','Spanish','native','español','rule','Regla|Regla de negocio','scenario','Ejemplo|Escenario','scenarioOutline','Esquema del escenario','then','Entonces ','when','Cuando '},'et',{'and','Ja ','background','Taust','but','Kuid ','examples','Juhtumid','feature','Omadus','given','Eeldades ','name','Estonian','native','eesti keel','rule','Reegel','scenario','Juhtum|Stsenaarium','scenarioOutline','Raamjuhtum|Raamstsenaarium','then','Siis ','when','Kui '},'fa',{'and','و ','background','زمینه','but','اما ','examples','نمونه ها','feature','وِیژگی','given','با فرض ','name','Persian','native','فارسی','rule','Rule','scenario','مثال|سناریو','scenarioOutline','الگوی سناریو','then','آنگاه ','when','هنگامی '},'fi',{'and','Ja ','background','Tausta','but','Mutta ','examples','Tapaukset','feature','Ominaisuus','given','Oletetaan ','name','Finnish','native','suomi','rule','Rule','scenario','Tapaus','scenarioOutline','Tapausaihio','then','Niin ','when','Kun '},'fr',{'and','Et que |Et qu\'|Et ','background','Contexte','but','Mais que |Mais qu\'|Mais ','examples','Exemples','feature','Fonctionnalité','given','Soit |Sachant que |Sachant qu\'|Sachant |Etant donné que |Etant donné qu\'|Etant donné |Etant donnée |Etant donnés |Etant données |Étant donné que |Étant donné qu\'|Étant donné |Étant donnée |Étant donnés |Étant données ','name','French','native','français','rule','Règle','scenario','Exemple|Scénario','scenarioOutline','Plan du scénario|Plan du Scénario','then','Alors |Donc ','when','Quand |Lorsque |Lorsqu\''},'ga',{'and','Agus','background','Cúlra','but','Ach','examples','Samplaí','feature','Gné','given','Cuir i gcás go|Cuir i gcás nach|Cuir i gcás gur|Cuir i gcás nár','name','Irish','native','Gaeilge','rule','Rule','scenario','Sampla|Cás','scenarioOutline','Cás Achomair','then','Ansin','when','Nuair a|Nuair nach|Nuair ba|Nuair nár'},'gj',{'and','અને ','background','બેકગ્રાઉન્ડ','but','પણ ','examples','ઉદાહરણો','feature','લક્ષણ|વ્યાપાર જરૂર|ક્ષમતા','given','આપેલ છે ','name','Gujarati','native','ગુજરાતી','rule','Rule','scenario','ઉદાહરણ|સ્થિતિ','scenarioOutline','પરિદ્દશ્ય રૂપરેખા|પરિદ્દશ્ય ઢાંચો','then','પછી ','when','ક્યારે '},'gl',{'and','E ','background','Contexto','but','Mais |Pero ','examples','Exemplos','feature','Característica','given','Dado |Dada |Dados |Dadas ','name','Galician','native','galego','rule','Rule','scenario','Exemplo|Escenario','scenarioOutline','Esbozo do escenario','then','Entón |Logo ','when','Cando '},'he',{'and','וגם ','background','רקע','but','אבל ','examples','דוגמאות','feature','תכונה','given','בהינתן ','name','Hebrew','native','עברית','rule','כלל','scenario','דוגמא|תרחיש','scenarioOutline','תבנית תרחיש','then','אז |אזי ','when','כאשר '},'hi',{'and','और |तथा ','background','पृष्ठभूमि','but','पर |परन्तु |किन्तु ','examples','उदाहरण','feature','रूप लेख','given','अगर |यदि |चूंकि ','name','Hindi','native','हिंदी','rule','नियम','scenario','परिदृश्य','scenarioOutline','परिदृश्य रूपरेखा','then','तब |तदा ','when','जब |कदा '},'hr',{'and','I ','background','Pozadina','but','Ali ','examples','Primjeri|Scenariji','feature','Osobina|Mogućnost|Mogucnost','given','Zadan |Zadani |Zadano |Ukoliko ','name','Croatian','native','hrvatski','rule','Rule','scenario','Primjer|Scenarij','scenarioOutline','Skica|Koncept','then','Onda ','when','Kada |Kad '},'ht',{'and','Ak |Epi |E ','background','Kontèks|Istorik','but','Men ','examples','Egzanp','feature','Karakteristik|Mak|Fonksyonalite','given','Sipoze |Sipoze ke |Sipoze Ke ','name','Creole','native','kreyòl','rule','Rule','scenario','Senaryo','scenarioOutline','Plan senaryo|Plan Senaryo|Senaryo deskripsyon|Senaryo Deskripsyon|Dyagram senaryo|Dyagram Senaryo','then','Lè sa a |Le sa a ','when','Lè |Le '},'hu',{'and','És ','background','Háttér','but','De ','examples','Példák','feature','Jellemző','given','Amennyiben |Adott ','name','Hungarian','native','magyar','rule','Szabály','scenario','Példa|Forgatókönyv','scenarioOutline','Forgatókönyv vázlat','then','Akkor ','when','Majd |Ha |Amikor '},'id',{'and','Dan ','background','Dasar|Latar Belakang','but','Tapi |Tetapi ','examples','Contoh|Misal','feature','Fitur','given','Dengan |Diketahui |Diasumsikan |Bila |Jika ','name','Indonesian','native','Bahasa Indonesia','rule','Rule|Aturan','scenario','Skenario','scenarioOutline','Skenario konsep|Garis-Besar Skenario','then','Maka |Kemudian ','when','Ketika '},'is',{'and','Og ','background','Bakgrunnur','but','En ','examples','Dæmi|Atburðarásir','feature','Eiginleiki','given','Ef ','name','Icelandic','native','Íslenska','rule','Rule','scenario','Atburðarás','scenarioOutline','Lýsing Atburðarásar|Lýsing Dæma','then','Þá ','when','Þegar '},'it',{'and','E ','background','Contesto','but','Ma ','examples','Esempi','feature','Funzionalità|Esigenza di Business|Abilità','given','Dato |Data |Dati |Date ','name','Italian','native','italiano','rule','Regola','scenario','Esempio|Scenario','scenarioOutline','Schema dello scenario','then','Allora ','when','Quando '},'ja',{'and','かつ','background','背景','but','しかし|但し|ただし','examples','例|サンプル','feature','フィーチャ|機能','given','前提','name','Japanese','native','日本語','rule','Rule','scenario','シナリオ','scenarioOutline','シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ','then','ならば','when','もし'},'jv',{'and','Lan ','background','Dasar','but','Tapi |Nanging |Ananging ','examples','Conto|Contone','feature','Fitur','given','Nalika |Nalikaning ','name','Javanese','native','Basa Jawa','rule','Rule','scenario','Skenario','scenarioOutline','Konsep skenario','then','Njuk |Banjur ','when','Manawa |Menawa '},'ka',{'and','და','background','კონტექსტი','but','მაგ­რამ','examples','მაგალითები','feature','თვისება','given','მოცემული','name','Georgian','native','ქართველი','rule','Rule','scenario','მაგალითად|სცენარის','scenarioOutline','სცენარის ნიმუში','then','მაშინ','when','როდესაც'},'kn',{'and','ಮತ್ತು ','background','ಹಿನ್ನೆಲೆ','but','ಆದರೆ ','examples','ಉದಾಹರಣೆಗಳು','feature','ಹೆಚ್ಚಳ','given','ನೀಡಿದ ','name','Kannada','native','ಕನ್ನಡ','rule','Rule','scenario','ಉದಾಹರಣೆ|ಕಥಾಸಾರಾಂಶ','scenarioOutline','ವಿವರಣೆ','then','ನಂತರ ','when','ಸ್ಥಿತಿಯನ್ನು '},'ko',{'and','그리고','background','배경','but','하지만|단','examples','예','feature','기능','given','조건|먼저','name','Korean','native','한국어','rule','Rule','scenario','시나리오','scenarioOutline','시나리오 개요','then','그러면','when','만일|만약'},'lt',{'and','Ir ','background','Kontekstas','but','Bet ','examples','Pavyzdžiai|Scenarijai|Variantai','feature','Savybė','given','Duota ','name','Lithuanian','native','lietuvių kalba','rule','Rule','scenario','Pavyzdys|Scenarijus','scenarioOutline','Scenarijaus šablonas','then','Tada ','when','Kai '},'lu',{'and','an |a ','background','Hannergrond','but','awer |mä ','examples','Beispiller','feature','Funktionalitéit','given','ugeholl ','name','Luxemburgish','native','Lëtzebuergesch','rule','Rule','scenario','Beispill|Szenario','scenarioOutline','Plang vum Szenario','then','dann ','when','wann '},'lv',{'and','Un ','background','Konteksts|Situācija','but','Bet ','examples','Piemēri|Paraugs','feature','Funkcionalitāte|Fīča','given','Kad ','name','Latvian','native','latviešu','rule','Rule','scenario','Piemērs|Scenārijs','scenarioOutline','Scenārijs pēc parauga','then','Tad ','when','Ja '},'mk-Cyrl',{'and','И ','background','Контекст|Содржина','but','Но ','examples','Примери|Сценарија','feature','Функционалност|Бизнис потреба|Можност','given','Дадено |Дадена ','name','Macedonian','native','Македонски','rule','Rule','scenario','Пример|Сценарио|На пример','scenarioOutline','Преглед на сценарија|Скица|Концепт','then','Тогаш ','when','Кога '},'mk-Latn',{'and','I ','background','Kontekst|Sodrzhina','but','No ','examples','Primeri|Scenaria','feature','Funkcionalnost|Biznis potreba|Mozhnost','given','Dadeno |Dadena ','name','Macedonian (Latin)','native','Makedonski (Latinica)','rule','Rule','scenario','Scenario|Na primer','scenarioOutline','Pregled na scenarija|Skica|Koncept','then','Togash ','when','Koga '},'mn',{'and','Мөн |Тэгээд ','background','Агуулга','but','Гэхдээ |Харин ','examples','Тухайлбал','feature','Функц|Функционал','given','Өгөгдсөн нь |Анх ','name','Mongolian','native','монгол','rule','Rule','scenario','Сценар','scenarioOutline','Сценарын төлөвлөгөө','then','Тэгэхэд |Үүний дараа ','when','Хэрэв '},'mr',{'and','आणि |तसेच ','background','पार्श्वभूमी','but','पण |परंतु ','examples','उदाहरण','feature','वैशिष्ट्य|सुविधा','given','जर|दिलेल्या प्रमाणे ','name','Marathi','native','मराठी','rule','नियम','scenario','परिदृश्य','scenarioOutline','परिदृश्य रूपरेखा','then','मग |तेव्हा ','when','जेव्हा '},'ne',{'and','र |अनि ','background','पृष्ठभूमी','but','तर ','examples','उदाहरण|उदाहरणहरु','feature','सुविधा|विशेषता','given','दिइएको |दिएको |यदि ','name','Nepali','native','नेपाली','rule','नियम','scenario','परिदृश्य','scenarioOutline','परिदृश्य रूपरेखा','then','त्यसपछि |अनी ','when','जब '},'nl',{'and','En ','background','Achtergrond','but','Maar ','examples','Voorbeelden','feature','Functionaliteit','given','Gegeven |Stel ','name','Dutch','native','Nederlands','rule','Rule','scenario','Voorbeeld|Scenario','scenarioOutline','Abstract Scenario','then','Dan ','when','Als |Wanneer '},'no',{'and','Og ','background','Bakgrunn','but','Men ','examples','Eksempler','feature','Egenskap','given','Gitt ','name','Norwegian','native','norsk','rule','Regel','scenario','Eksempel|Scenario','scenarioOutline','Scenariomal|Abstrakt Scenario','then','Så ','when','Når '},'pa',{'and','ਅਤੇ ','background','ਪਿਛੋਕੜ','but','ਪਰ ','examples','ਉਦਾਹਰਨਾਂ','feature','ਖਾਸੀਅਤ|ਮੁਹਾਂਦਰਾ|ਨਕਸ਼ ਨੁਹਾਰ','given','ਜੇਕਰ |ਜਿਵੇਂ ਕਿ ','name','Panjabi','native','ਪੰਜਾਬੀ','rule','Rule','scenario','ਉਦਾਹਰਨ|ਪਟਕਥਾ','scenarioOutline','ਪਟਕਥਾ ਢਾਂਚਾ|ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ','then','ਤਦ ','when','ਜਦੋਂ '},'pl',{'and','Oraz |I ','background','Założenia','but','Ale ','examples','Przykłady','feature','Właściwość|Funkcja|Aspekt|Potrzeba biznesowa','given','Zakładając |Mając |Zakładając, że ','name','Polish','native','polski','rule','Zasada|Reguła','scenario','Przykład|Scenariusz','scenarioOutline','Szablon scenariusza','then','Wtedy ','when','Jeżeli |Jeśli |Gdy |Kiedy '},'pt',{'and','E ','background','Contexto|Cenário de Fundo|Cenario de Fundo|Fundo','but','Mas ','examples','Exemplos|Cenários|Cenarios','feature','Funcionalidade|Característica|Caracteristica','given','Dado |Dada |Dados |Dadas ','name','Portuguese','native','português','rule','Regra','scenario','Exemplo|Cenário|Cenario','scenarioOutline','Esquema do Cenário|Esquema do Cenario|Delineação do Cenário|Delineacao do Cenario','then','Então |Entao ','when','Quando '},'ro',{'and','Si |Și |Şi ','background','Context','but','Dar ','examples','Exemple','feature','Functionalitate|Funcționalitate|Funcţionalitate','given','Date fiind |Dat fiind |Dată fiind|Dati fiind |Dați fiind |Daţi fiind ','name','Romanian','native','română','rule','Rule','scenario','Exemplu|Scenariu','scenarioOutline','Structura scenariu|Structură scenariu','then','Atunci ','when','Cand |Când '},'ru',{'and','И |К тому же |Также ','background','Предыстория|Контекст','but','Но |А |Иначе ','examples','Примеры','feature','Функция|Функциональность|Функционал|Свойство','given','Допустим |Дано |Пусть ','name','Russian','native','русский','rule','Правило','scenario','Пример|Сценарий','scenarioOutline','Структура сценария|Шаблон сценария','then','То |Затем |Тогда ','when','Когда |Если '},'sk',{'and','A |A tiež |A taktiež |A zároveň ','background','Pozadie','but','Ale ','examples','Príklady','feature','Požiadavka|Funkcia|Vlastnosť','given','Pokiaľ |Za predpokladu ','name','Slovak','native','Slovensky','rule','Rule','scenario','Príklad|Scenár','scenarioOutline','Náčrt Scenáru|Náčrt Scenára|Osnova Scenára','then','Tak |Potom ','when','Keď |Ak '},'sl',{'and','In |Ter ','background','Kontekst|Osnova|Ozadje','but','Toda |Ampak |Vendar ','examples','Primeri|Scenariji','feature','Funkcionalnost|Funkcija|Možnosti|Moznosti|Lastnost|Značilnost','given','Dano |Podano |Zaradi |Privzeto ','name','Slovenian','native','Slovenski','rule','Rule','scenario','Primer|Scenarij','scenarioOutline','Struktura scenarija|Skica|Koncept|Oris scenarija|Osnutek','then','Nato |Potem |Takrat ','when','Ko |Ce |Če |Kadar '},'sr-Cyrl',{'and','И ','background','Контекст|Основа|Позадина','but','Али ','examples','Примери|Сценарији','feature','Функционалност|Могућност|Особина','given','За дато |За дате |За дати ','name','Serbian','native','Српски','rule','Правило','scenario','Пример|Сценарио|Пример','scenarioOutline','Структура сценарија|Скица|Концепт','then','Онда ','when','Када |Кад '},'sr-Latn',{'and','I ','background','Kontekst|Osnova|Pozadina','but','Ali ','examples','Primeri|Scenariji','feature','Funkcionalnost|Mogućnost|Mogucnost|Osobina','given','Za dato |Za date |Za dati ','name','Serbian (Latin)','native','Srpski (Latinica)','rule','Pravilo','scenario','Scenario|Primer','scenarioOutline','Struktura scenarija|Skica|Koncept','then','Onda ','when','Kada |Kad '},'sv',{'and','Och ','background','Bakgrund','but','Men ','examples','Exempel','feature','Egenskap','given','Givet ','name','Swedish','native','Svenska','rule','Regel','scenario','Scenario','scenarioOutline','Abstrakt Scenario|Scenariomall','then','Så ','when','När '},'ta',{'and','மேலும் |மற்றும் ','background','பின்னணி','but','ஆனால் ','examples','எடுத்துக்காட்டுகள்|காட்சிகள்|நிலைமைகளில்','feature','அம்சம்|வணிக தேவை|திறன்','given','கொடுக்கப்பட்ட ','name','Tamil','native','தமிழ்','rule','Rule','scenario','உதாரணமாக|காட்சி','scenarioOutline','காட்சி சுருக்கம்|காட்சி வார்ப்புரு','then','அப்பொழுது ','when','எப்போது '},'te',{'and','మరియు ','background','నేపథ్యం','but','కాని ','examples','ఉదాహరణలు','feature','గుణము','given','చెప్పబడినది ','name','Telugu','native','తెలుగు','rule','Rule','scenario','ఉదాహరణ|సన్నివేశం','scenarioOutline','కథనం','then','అప్పుడు ','when','ఈ పరిస్థితిలో '},'th',{'and','และ ','background','แนวคิด','but','แต่ ','examples','ชุดของตัวอย่าง|ชุดของเหตุการณ์','feature','โครงหลัก|ความต้องการทางธุรกิจ|ความสามารถ','given','กำหนดให้ ','name','Thai','native','ไทย','rule','Rule','scenario','เหตุการณ์','scenarioOutline','สรุปเหตุการณ์|โครงสร้างของเหตุการณ์','then','ดังนั้น ','when','เมื่อ '},'tlh',{'and','\'ej |latlh ','background','mo\'','but','\'ach |\'a ','examples','ghantoH|lutmey','feature','Qap|Qu\'meH \'ut|perbogh|poQbogh malja\'|laH','given','ghu\' noblu\' |DaH ghu\' bejlu\' ','name','Klingon','native','tlhIngan','rule','Rule','scenario','lut','scenarioOutline','lut chovnatlh','then','vaj ','when','qaSDI\' '},'tr',{'and','Ve ','background','Geçmiş','but','Fakat |Ama ','examples','Örnekler','feature','Özellik','given','Diyelim ki ','name','Turkish','native','Türkçe','rule','Kural','scenario','Örnek|Senaryo','scenarioOutline','Senaryo taslağı','then','O zaman ','when','Eğer ki '},'tt',{'and','Һәм |Вә ','background','Кереш','but','Ләкин |Әмма ','examples','Үрнәкләр|Мисаллар','feature','Мөмкинлек|Үзенчәлеклелек','given','Әйтик ','name','Tatar','native','Татарча','rule','Rule','scenario','Сценарий','scenarioOutline','Сценарийның төзелеше','then','Нәтиҗәдә ','when','Әгәр '},'uk',{'and','І |А також |Та ','background','Передумова','but','Але ','examples','Приклади','feature','Функціонал','given','Припустимо |Припустимо, що |Нехай |Дано ','name','Ukrainian','native','Українська','rule','Rule','scenario','Приклад|Сценарій','scenarioOutline','Структура сценарію','then','То |Тоді ','when','Якщо |Коли '},'ur',{'and','اور ','background','پس منظر','but','لیکن ','examples','مثالیں','feature','صلاحیت|کاروبار کی ضرورت|خصوصیت','given','اگر |بالفرض |فرض کیا ','name','Urdu','native','اردو','rule','Rule','scenario','منظرنامہ','scenarioOutline','منظر نامے کا خاکہ','then','پھر |تب ','when','جب '},'uz',{'and','Ва ','background','Тарих','but','Лекин |Бирок |Аммо ','examples','Мисоллар','feature','Функционал','given','Belgilangan ','name','Uzbek','native','Узбекча','rule','Rule','scenario','Сценарий','scenarioOutline','Сценарий структураси','then','Унда ','when','Агар '},'vi',{'and','Và ','background','Bối cảnh','but','Nhưng ','examples','Dữ liệu','feature','Tính năng','given','Biết |Cho ','name','Vietnamese','native','Tiếng Việt','rule','Rule','scenario','Tình huống|Kịch bản','scenarioOutline','Khung tình huống|Khung kịch bản','then','Thì ','when','Khi '},'zh-CN',{'and','而且|并且|同时','background','背景','but','但是','examples','例子','feature','功能','given','假如|假设|假定','name','Chinese simplified','native','简体中文','rule','Rule|规则','scenario','场景|剧本','scenarioOutline','场景大纲|剧本大纲','then','那么','when','当'},'zh-TW',{'and','而且|並且|同時','background','背景','but','但是','examples','例子','feature','功能','given','假如|假設|假定','name','Chinese traditional','native','繁體中文','rule','Rule','scenario','場景|劇本','scenarioOutline','場景大綱|劇本大綱','then','那麼','when','當'}); 1; match-only000755001750001750 015014377065 17477 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkinmatch.feature100644001750001750 111415014377065 22305 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkin/match-onlyFeature: Match-only testing As a test developer I want to be able to assert that all steps in my feature are matched without being required to run the entire test suite (and spend the associated amount of time waiting). As a solution to this problem, Pherkin offers the ability to run through all steps in features matching the steps without executing the step functions. Scenario: Test match only function Given a step with step function that calls die() When there are further steps with associated step functions Then I expect all steps to be matched Model000755001750001750 015014377065 20404 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/CucumberLine.pm100644001750001750 461215014377065 21774 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Line 0.87; use Moo; use Types::Standard qw( Int InstanceOf Str ); =head1 NAME Test::BDD::Cucumber::Model::Line - Model to represent a line in a feature file =head1 VERSION version 0.87 =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 => InstanceOf['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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Step.pm100644001750001750 317115014377065 22017 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Step 0.87; use Moo; use Types::Standard qw( Str ArrayRef InstanceOf ); =head1 NAME Test::BDD::Cucumber::Model::Step - Model to represent a step in a scenario =head1 VERSION version 0.87 =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 => InstanceOf['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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; auto_corpus000755001750001750 015014377065 16327 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/tdigest.feature_corpus100644001750001750 636315014377065 22726 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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 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 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 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.pm100644001750001750 141615014377065 22453 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/lib/Test#!perl package Test::CucumberExtensionPush; use Moo; use Types::Standard qw( HashRef ); 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; Harness000755001750001750 015014377065 20747 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/CucumberTAP.pm100644001750001750 1054015014377065 22111 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Harnessuse v5.14; use warnings; package Test::BDD::Cucumber::Harness::TAP 0.87; =head1 NAME Test::BDD::Cucumber::Harness::TAP - Generate results in TAP format =head1 VERSION version 0.87 =head1 DESCRIPTION A L subclass whose output is TAP (Test Anything Protocol), such as consumed by C and C. =head1 OPTIONS =head2 fail_skip Boolean - makes tests with no matcher fail =cut use Moo; use Types::Standard qw( Bool InstanceOf Int ); use Test2::API qw/context/; extends 'Test::BDD::Cucumber::Harness'; has 'fail_skip' => ( is => 'rw', isa => Bool, default => 0 ); has '_reported_results' => ( is => 'rw', isa => Int, default => 0 ); sub feature { my ( $self, $feature ) = @_; my $ctx = context(); $ctx->note(join(' ', $feature->keyword_original, ($feature->name || '') . "\n", map { $_->content } @{ $feature->satisfaction })); $ctx->release; } sub scenario { my ( $self, $scenario, $dataset ) = @_; my $ctx = context(); $ctx->note(join(' ', $scenario->keyword_original, ($scenario->name || '') . "\n", map { $_->content} @{ $scenario->description })); $ctx->release; } sub scenario_skip { my ( $self, $scenario, $dataset ) = @_; my $ctx = context(); my $name = $scenario->name; $ctx->skip("Scenario '$name' skipped due to tag filter"); $ctx->release; } sub scenario_done { } sub step { } sub step_done { my ( $self, $context, $result ) = @_; my $status = $result->result; my $step = $context->step; my $scenario = $context->scenario; my $step_name; my $ctx = context(); # when called from a 'before' or 'after' hook, we have context, but no step $ctx->trace->{frame} = [ undef, $step ? $step->line->document->filename : $scenario->line->document->filename, $step ? $step->line->number : $scenario->line->number, undef ]; if ( $context->is_hook ) { $status ne 'undefined' and $status ne 'pending' and $status ne 'passing' or do { $ctx->release; return; }; $step_name = ucfirst( $context->verb ) . ' Hook'; } else { $step_name = ucfirst( $step->verb_original ) . ' ' . $context->text; } if ( $status eq 'undefined' || $status eq 'pending' ) { if ( $self->fail_skip ) { if ( $status eq 'undefined' ) { $ctx->fail( "Matcher for: $step_name", $self->_note_step_data($step)); $self->_reported_results( $self->_reported_results + 1 ); } else { $ctx->skip( "Test skipped due to failure in previous step", $self->_note_step_data($step)); $self->_reported_results( $self->_reported_results + 1 ); } } else { $ctx->send_event( 'Skip', todo => 'pending', todo_diag => 1, reason => 'Step not implemented', pass => 0); $ctx->note($self->_note_step_data($step)); } } elsif ( $status eq 'passing' ) { $ctx->pass( $step_name ); $ctx->note($self->_note_step_data($step)); $self->_reported_results( $self->_reported_results + 1 ); } else { $ctx->fail( $step_name ); $ctx->note($self->_note_step_data($step)); $self->_reported_results( $self->_reported_results + 1 ); if ( !$context->is_hook ) { my $step_location = ' in step at ' . $step->line->document->filename . ' line ' . $step->line->number . '.'; $ctx->diag($step_location); } $ctx->diag( $result->output ); } $ctx->release; } 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' ) { return join("\n", @step_data); } else { return join('', '"""', join("\n", @step_data), '"""'); } } sub shutdown { my $self = shift; my $ctx = context(); $ctx->done_testing; $ctx->release; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; StepContext.pm100644001750001750 3070015014377065 22342 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumberuse v5.14; use warnings; package Test::BDD::Cucumber::StepContext 0.87; use Moo; use Types::Standard qw( Bool Str HashRef ArrayRef InstanceOf ); use List::Util qw( first ); =head1 NAME Test::BDD::Cucumber::StepContext - Data made available to step definitions =head1 VERSION version 0.87 =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 two keys, C, C. The stash allows you to persist data across features or scenarios. 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->{'scenario'}; $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 => InstanceOf['Test::BDD::Cucumber::Model::Feature'] ); has 'scenario' => ( is => 'ro', required => 1, isa => InstanceOf['Test::BDD::Cucumber::Model::Scenario'] ); has 'step' => ( is => 'ro', required => 0, isa => InstanceOf['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 => InstanceOf['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 => InstanceOf['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 => InstanceOf['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->[2]->( $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 passed-in data", C->data ); 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. The data in that third argument can be one of: =over =item * a string This scenario corresponds with having a C<""" ... """> string argument to the step. It's passed to the child step verbatim. =item * a hash reference (deprecated) This scenario corresponds with the third example above and has been supported historically. There is no good reason to use this type of argument passing, because there is no way for a feature to pass data to the step. When you need to use this scenario, please consider implementing a separate subroutine instead. =item * a reference to an array of hashes This scenario corresponsds with a data table argument to the step. The names of the columns are taken from the first hash in the array (the first row in the data table). No transformations are applied to the table passed in to prevent duplicate transformations being applied. =back The value of the third argument will be used as the C<< C->data >> value for the C of the child step. All values passed in, will be passed to the child without applying C declarations. That way, double transformation is prevented. 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 ]; } elsif ( ref $data eq 'ARRAY' and (scalar @{ $data } > 0) and ref $data->[0] eq 'HASH' ) { $columns = [ sort keys %{ $data->[0] } ]; } } my $new_context = $self->new( { executor => $self->executor, ( $data ? ( data => $data ) : () ), ( $data ? ( _transformed_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->[2]->( $self, $transformed_data ); } } return $transformed_data; } =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; CucumberExtensionCount.pm100644001750001750 121615014377065 22622 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/lib/Test#!perl package Test::CucumberExtensionCount; use Moo; use Types::Standard qw( HashRef ); 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_files000755001750001750 015014377065 20133 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/tnot_yaml.yaml100644001750001750 4215014377065 22735 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkin_config_files - this is just rubbish - right?readable.yaml100644001750001750 44515014377065 22701 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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: key: value tags: - tag1,tag2,tag3lib000755001750001750 015014377065 20236 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/calculatorCalculator.pm100644001750001750 350715014377065 23032 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/calculator/libpackage # hide from PAUSE indexer Calculator; use strict; use warnings; use Moo; use Types::Standard qw( Num Str ); 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; Data.pm100644001750001750 1015115014377065 22334 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Harnessuse v5.14; use warnings; package Test::BDD::Cucumber::Harness::Data 0.87; =head1 NAME Test::BDD::Cucumber::Harness::Data - Builds up an internal data representation of test passes / failures =head1 VERSION version 0.87 =head1 DESCRIPTION A L subclass which collates test data =cut use Moo; use Types::Standard qw( HashRef ArrayRef ); 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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; JSON.pm100644001750001750 1116115014377065 22236 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Harnessuse v5.14; use warnings; package Test::BDD::Cucumber::Harness::JSON 0.87; =head1 NAME Test::BDD::Cucumber::Harness::JSON - Generate results to JSON file =head1 VERSION version 0.87 =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 Moo; use Types::Standard qw( Num HashRef ArrayRef FileHandle ); 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 scenario_done { my $self = shift; $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 format_tags { my ( $self, $tags_ref ) = @_; return [ map { { name => $_ } } @$tags_ref ]; } sub format_description { my ( $self, $description ) = @_; return join "\n", map { $_->content } @{ $description }; } sub format_feature { my ( $self, $feature ) = @_; return { uri => $feature->name_line->filename, keyword => $feature->keyword_original, id => $self->_generate_stable_id( $feature->name_line ), name => $feature->name, line => $feature->name_line->number, description => $self->format_description($feature->satisfaction), tags => $self->format_tags( $feature->tags ), elements => [] }; } sub format_scenario { my ( $self, $scenario, $dataset ) = @_; return { keyword => $scenario->keyword_original, id => $self->_generate_stable_id( $scenario->line ), name => $scenario->name, line => $scenario->line->number, description => $self->format_description($scenario->description), 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.pm100644001750001750 367415014377065 22372 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Result 0.87; =head1 NAME Test::BDD::Cucumber::Model::Result - Encapsulates a result state =head1 VERSION version 0.87 =head1 DESCRIPTION Encapsulation of result state - whether that's for a step, scenario, or feature =cut use Moo; use Types::Standard qw( Enum Str ); =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 => Enum[qw( passing failing pending undefined )], 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_features000755001750001750 015014377065 20477 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/tcore.feature100644001750001750 1065215014377065 23170 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.feature100644001750001750 550015014377065 23152 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_core_featuresFeature: Tags Scenario: execute scenarios matching a tag # cucumber --tags @foo Given a scenario tagged with "@foo" And a scenario tagged with "@bar" When Cucumber executes scenarios tagged with "@foo" Then only the first scenario is executed Scenario: execute scenarios not matching a tag # cucumber --tags ~@bar Given a scenario tagged with "@foo" And a scenario tagged with "@bar" When Cucumber executes scenarios not tagged with "@bar" Then only the first scenario is executed Scenario: execute scenarios matching any of several tags (OR) # cucumber --tags @foo,@bar 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" Then only the first two scenarios are executed Scenario: execute scenarios matching several tags (AND) # cucumber --tags @foo --tags @bar Given a scenario tagged with "@foo" and "@bar" And a scenario tagged with "@foo" When Cucumber executes scenarios tagged with both "@foo" and "@bar" Then only the first scenario is executed Scenario: execute scenarios not matching any tag (NOT OR NOT) # cucumber --tags ~@foo --tags ~@bar 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" Then only the third scenario is executed Scenario: exclude scenarios matching two tags (NOT AND NOT) # cucumber --tags ~@foo,~@bar 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" Then only the second, third and fourth scenarios are executed Scenario: with tag or without other tag # cucumber --tags @foo,~@bar 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" Then only the first two scenarios are executed Scenario: with tag but without two other tags # cucumber --tags @baz --tags ~@foo --tags ~@bar 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" 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 features000755001750001750 015014377065 20434 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/digestbasic.feature100644001750001750 236315014377065 23236 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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" Manual000755001750001750 015014377065 20561 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/CucumberSteps.pod100644001750001750 1762015014377065 22551 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Steps; =encoding utf8 =head1 NAME Test::BDD::Cucumber::Manual::Steps - How to write Step Definitions =head1 VERSION version 0.87 =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 package my_step_functions_for_feature_X; use strict; use warnings; use Test::More; # 'use Test2::V0;' is also supported 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 or L based testing module. E.g. C or C. L gives us the functions C, C, C and C. =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->{'scenario'}; $stash->{'count'} = 1 } sub { S->{'count'} = 1 } We will evaluate the regex immediately before we execute the coderef, so you can use C<$1>, C<$2>, C<$etc>. Similarly you can access named matches using C<$+{match_name}>. =head2 Accessing step, scenario and feature properties Step functions have access to the various properties of the step, scenario and feature in which they're being used. This includes tags, line numbers, etc. E.g.: # Examples of step properties C->step->line->number C->step->verb C->step->original_verb # Examples of scenario properties C->scenario->name C->scenario->tags # Examples of feature properties C->feature->name C->feature->tags C->feature->language For a full review of available properties, see L, L and L respectively. =head2 Re-using step definitions 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' } ); }; For more on this topic, check the L section in the documentation for C. =head1 LOCALIZATION Both feature files and step files can be written using non-english Gherkin keywords. A german feature file could look like the example below. # 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 To see which keywords (and sub names) to use, ask pherkin about a specific language: > pherkin --i18n de | feature | "Funktionalität" | | background | "Grundlage" | ... | 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 as indicated by the '(code)' suffix. A corresponding step file specifying a step function for C, could be: #!perl use strict; use warnings; use utf8; # Interpret accented German chars in regexes and identifiers properly use Test::More; use Test::BDD::Cucumber::StepFile; Wenn qr/^ich (.+) gedrückt habe/, sub { S->{'Calculator'}->press($_) for split( /(,| und) /, C->matches->[0] ); }; For more extensive examples see F and F. =head1 ADDITIONAL STEPS Next to the steps that will be matched directly against feature file input, a number of additional step functions are supported: =over 4 =item * C and C These steps create hooks into the evaluation process of feature files. E.g. Before sub { # Run before every scenario # ... scenario set up code }; After sub { # Run after every scenario # ... scenario tear down code }; For more extensive hook functionality, see L. =item * C The C step serves to map matched values or table rows from feature file (string) input to step input values. The step takes two arguments, same as the C, C and C steps: a regular expression and a code reference. E.g. Transform qr/^(\d+)$/, sub { # transform matches of digit-only strings my $rv = $1; # ... do something with $rv return $rv; }; Transform qr/^table:col1,col2$/, sub { # transform tables with 2 columns, named col1 and col2 respectively my ($step_context, $data) = @_; # ... transform data in $data return $data; }; =back =head1 BEST PRACTICES When writing step files, it's a good idea to take a few things into account. =over 4 =item * Declare a C at the top of your step file By declaring a specific package (your own), you make sure not to step on internals of other modules. At the time of writing, the default package is C, which may lead to errors being reported in that package, even though they occur in your step file (which is confusing). The default package may change in the future and it will likely not be seeded with the content of the C package. =item * Declare a different C per step file By using different packages per step file (or group of step files), name spaces are isolated which reduces the risk of importing functions with the same name from different packages. An example where this will be the case is when some of your step files are written using C and some others are written using C -- both export a function C, but with conflicting function prototypes. =item * Don't define functions in your step file Especially step files provided by extensions. Step files may be loaded more than once, depending on the exact scenario in which C is run. When the step files are being loaded multiple times, there won't be any impact on step definition, but any function definitions will cause 'function redefined' warnings. =back =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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Dataset.pm100644001750001750 260515014377065 22472 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Dataset 0.87; use Moo; use Types::Standard qw( Str ArrayRef HashRef Bool InstanceOf ); =head1 NAME Test::BDD::Cucumber::Model::Scenario - Model to represent a scenario =head1 VERSION version 0.87 =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 description The text between the Scenario line and the first step line =cut has 'description' => ( is => 'rw', isa => ArrayRef[InstanceOf['Test::BDD::Cucumber::Model::Line']], default => sub { [] }, ); =head2 data Scenario-related data table, as an arrayref of hashrefs =cut has 'data' => ( is => 'rw', isa => ArrayRef[HashRef], default => sub { [] } ); =head2 line A L object corresponding to the line where the C keyword is. =cut has 'line' => ( is => 'rw', isa => InstanceOf['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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Feature.pm100644001750001750 435115014377065 22500 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Feature 0.87; use Moo; use Types::Standard qw( Str ArrayRef InstanceOf ); =head1 NAME Test::BDD::Cucumber::Model::Feature - Model to represent a feature file, parsed =head1 VERSION version 0.87 =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 => InstanceOf['Test::BDD::Cucumber::Model::Line'] ); =head2 satisfaction An arrayref of strings of the Conditions of Satisfaction =cut has 'satisfaction' => ( is => 'rw', isa => ArrayRef[InstanceOf['Test::BDD::Cucumber::Model::Line']], default => sub { [] } ); =head2 document The corresponding L object =cut has 'document' => ( is => 'rw', isa => InstanceOf['Test::BDD::Cucumber::Model::Document'] ); =head2 background The L object that was marked as the background section. =cut has 'background' => ( is => 'rw', isa => InstanceOf['Test::BDD::Cucumber::Model::Scenario'] ); =head2 keyword_original The keyword used in the input file; equivalent to specification keyword C. =cut has 'keyword_original' => ( is => 'rw', isa => Str ); =head2 scenarios An arrayref of the L objects that constitute the test. =cut has 'scenarios' => ( is => 'rw', isa => ArrayRef[InstanceOf['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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; TagSpec.pm100644001750001750 565315014377065 22441 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::TagSpec 0.87; =head1 NAME Test::BDD::Cucumber::Model::TagSpec - Encapsulates tag selectors =head1 VERSION version 0.87 =head1 STATUS DEPRECATED - This module's functionality has been superseded by L. A module published by the Cucumber project, with cross-implementation tests to achieve overall consistency. =head1 DESCRIPTION Try and deal with the crazy-sauce tagging mechanism in a sane way. =cut use Moo; use List::Util qw( all any ); use Types::Standard qw( ArrayRef ); =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 ) = @_; return @scenarios unless @{ $self->tags }; return grep { my @tags = @{ $_->tags }; my $scenario = { map { $_ => 1 } @tags }; _matches( $scenario, $self->tags ); } @scenarios; } sub _matches { my ( $scenario, $tagspec ) = @_; my ( $mode, @tags ) = @$tagspec; if ( $mode eq 'and' ) { return all { ref $_ ? _matches( $scenario, $_ ) : $scenario->{$_} } @tags; } elsif ( $mode eq 'or' ) { return any { ref $_ ? _matches( $scenario, $_ ) : $scenario->{$_} } @tags; } elsif ( $mode eq 'not' ) { die "'not' expects exactly one tag argument; found @tags" unless @tags == 1; return not (ref $tags[0] ? _matches( $scenario, $tags[0] ) : $scenario->{$tags[0]} ); } else { die "Unexpected tagspec operator '$mode'"; } } 1; features000755001750001750 015014377065 20404 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/i18n_debasic.feature100644001750001750 535115014377065 23206 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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 features000755001750001750 015014377065 20423 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/i18n_esbasic.feature100644001750001750 255115014377065 23224 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/i18n_es/features# language: es Característica: Prueba simple de Digest.pm Como dearrollador planeo usar Digest.pm Deseo probar la funcionalidad de Digest.pm Para estar seguro de que funciona de forma correcta Antecedentes: Dada la clase "Digest" Escenario: Verificar MD5 Dado un objeto Digest usando el algoritmo "MD5" Cuando he agregado "prueba" al objeto Entonces el resultado en hexadecimal es "c893bad68927b457dbed39460e6afd62" Cuando he agregado "prueba" al objeto Y he agregado "texto" al objeto Entonces el resultado en hexadecimal es "faea2ea80591327766b7c9ce591f9460" Entonces el resultado en base64 es "1B2M2Y8AsgTpgAmY7PhCfg" # This is an intentional comment # Entonces el resultado en hexadecimal es "9ee285740e9bbc8c72c8e0fe9e68aa8f" Escenario: Check SHA-1 Dado un objeto Digest usando el algoritmo "SHA-1" Cuando he agregado "" al objeto Entonces el resultado en hexadecimal es "" Ejemplos: | data | output | | test | a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 | | devs | 99b48da825c239c6ecd0a54ebfc11552d7ffb56f | | nogo | c42910ed3f073231e88fef0b710648684fc0ed28 | Escenario: MD5 de datos mas largos Dado un objeto Digest usando el algoritmo "MD5" Cuando he agregado los siguientes datos al objeto """ Esta es una prueba del funcionamiento de Test::BDD::Cucumber. """ Entonces el resultado hexadecimal es "00c6fff240d73810685d9b4885018a5d" SourceHandler000755001750001750 015014377065 20705 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/TAP/ParserFeature.pm100644001750001750 1015515014377065 23020 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/TAP/Parser/SourceHandleruse v5.14; use warnings; package TAP::Parser::SourceHandler::Feature 0.87; =head1 NAME TAP::Parser::SourceHandler::Feature - Test::BDD::Cucumber's prove integration =head1 VERSION version 0.87 =cut use Path::Class qw/file/; use Test2::API qw/context/; use base 'TAP::Parser::SourceHandler'; use TAP::Parser::Iterator::PherkinStream; use App::pherkin; use Test::BDD::Cucumber::Loader; use Test::BDD::Cucumber::Harness::TAP; use Path::Class qw/file/; TAP::Parser::IteratorFactory->register_handler(__PACKAGE__); sub can_handle { my ( $class, $source ) = @_; if ( $source->meta->{'is_file'} && $source->meta->{'file'}->{'basename'} =~ m/\.feature$/ ) { my $dir = $source->meta->{'file'}->{'dir'}; unless ( $source->{'pherkins'}->{$dir} ) { my $pherkin = App::pherkin->new(); # Reformulate before passing to the cmd line parser my @cmd_line; my %options = %{ $source->config_for($class) }; while ( my ( $key, $value ) = each %options ) { # --feature-option 'tags=@something' # --feature-option 'tags=@somethingelse' # # is passed in %options as: # { ..., tags => [ '@something', '@somethingelse' ], ... } # # Now unfold that back into an argument array as # --tags @something --tags @somethingelse $value = [ $value ] unless ref $value; for my $v (@$value) { # Nasty hack if ( length $key > 1 ) { push( @cmd_line, "--$key", $v ); } else { push( @cmd_line, "-$key", $v ); } } } my ( $executor, @features ) = $pherkin->_pre_run( @cmd_line, $dir ); $source->{'pherkins'}->{$dir} = { pherkin => $pherkin, executor => $executor, features => { map { ( file( $_->document->filename ) . '' ) => $_ } @features } }; } return 1; } return 0; } sub make_iterator { my ( $class, $source ) = @_; my ( $input_out_fh, $output_out_fh ); pipe $input_out_fh, $output_out_fh; binmode $output_out_fh, ':utf8'; my ( $input_err_fh, $output_err_fh ); pipe $input_err_fh, $output_err_fh; binmode $output_err_fh, ':utf8'; # Don't cache the output so prove sees it immediately # (pipes are stdio buffered by default) $output_out_fh->autoflush(1); $output_err_fh->autoflush(1); my $dir = $source->meta->{'file'}->{'dir'}; my $runtime = $source->{'pherkins'}->{$dir} || die "No pherkin instantiation for [$dir]"; my $executor = $runtime->{'executor'}; my $pherkin = $runtime->{'pherkin'}; my $pid = fork; if ($pid) { close $output_out_fh; close $output_err_fh; return TAP::Parser::Iterator::PherkinStream ->new($input_out_fh, $input_err_fh, $pherkin, $pid); } # prevent uncaught exceptions to return up the call stack # causing essentially two prove instances to run. eval { close $input_out_fh; close $input_err_fh; my $harness = Test::BDD::Cucumber::Harness::TAP->new({ fail_skip => 1 }); my $context = context(); # Without the step to set the handles TAP will end up on STDOUT/STDERR $context->hub->format->set_handles([$output_out_fh, $output_err_fh]); $context->release; $pherkin->harness($harness); my $filename = file( $dir . $source->meta->{'file'}->{'basename'} ) . ''; my $feature = $runtime->{'features'}->{$filename} || die "Feature not pre-loaded: [$filename]; have: " . ( join '; ', keys %{ $runtime->{'features'} } ); my $exit_code = $pherkin->_run_tests( $executor, $feature ); close $output_out_fh; close $output_err_fh; $pherkin->_post_run; exit $exit_code; }; exit 255; } 1; Document.pm100644001750001750 310715014377065 22661 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Document 0.87; use Moo; use Types::Standard qw( Str ArrayRef InstanceOf ); 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.87 =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[InstanceOf['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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; Scenario.pm100644001750001750 423315014377065 22647 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Modeluse v5.14; use warnings; package Test::BDD::Cucumber::Model::Scenario 0.87; use Moo; use Types::Standard qw( Str ArrayRef HashRef Bool InstanceOf ); use Carp; =head1 NAME Test::BDD::Cucumber::Model::Scenario - Model to represent a scenario =head1 VERSION version 0.87 =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 description The text between the Scenario line and the first step line =cut has 'description' => ( is => 'rw', isa => ArrayRef[InstanceOf['Test::BDD::Cucumber::Model::Line']], default => sub { [] }, ); =head2 steps The associated L objects =cut has 'steps' => ( is => 'rw', isa => ArrayRef[InstanceOf['Test::BDD::Cucumber::Model::Step']], default => sub { [] } ); =head2 datasets The dataset(s) associated with a scenario. =cut has 'datasets' => ( is => 'rw', isa => ArrayRef[InstanceOf['Test::BDD::Cucumber::Model::Dataset']], default => sub { [] } ); =head2 background Boolean flag to mark whether this was the background section =cut has 'background' => ( is => 'rw', isa => Bool, default => 0 ); =head2 keyword =head2 keyword_original The keyword used in the input file (C) and its specification equivalent (C) used to start this scenario. (I.e. C, C and C.) =cut has 'keyword' => ( is => 'rw', isa => Str ); has 'keyword_original' => ( is => 'rw', isa => Str ); =head2 line A L object corresponding to the line where the C keyword is. =cut has 'line' => ( is => 'rw', isa => InstanceOf['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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; calculator.feature_corpus100644001750001750 1471615014377065 23621 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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 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 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 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 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 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 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 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 env_replace.yaml100644001750001750 32315014377065 23420 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkin_config_filestest: extensions: Test: undefined: ${UNDEFINED} foo: ${PHERKIN_TEST_REPLACE} bar: $${PHERKIN_TEST_REPLACE} baz: - ${PHERKIN_TEST_REPLACE} - $${PHERKIN_TEST_REPLACE} Iterator000755001750001750 015014377065 17740 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/TAP/ParserPherkinStream.pm100644001750001750 547415014377065 23224 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/TAP/Parser/Iteratoruse v5.14; use warnings; package TAP::Parser::Iterator::PherkinStream 0.87; =head1 NAME TAP::Parser::Iterator::PherkinStream - Stream with TAP from async BDD process =head1 VERSION version 0.87 =cut use base 'TAP::Parser::Iterator'; use IO::Select; sub _initialize { my ($self, $out_fh, $err_fh, $pherkin, $child_pid) = @_; $self->{pherkin} = $pherkin; $self->{child_pid} = $child_pid; $self->{sel} = IO::Select->new($out_fh, $err_fh); $self->{out_fh} = $out_fh; $self->{err_fh} = $err_fh; return $self; } sub _finish { my $self = shift; $self->{pherkin}->_post_run(); if ($self->{child_pid}) { waitpid $self->{child_pid}, 0; # reap child process $self->{wait} = $?; $self->{exit} = $? >> 8; } return $self; } sub wait { shift->{wait} } sub exit { shift->{exit} } sub _next { my $self = shift; my @buf = (); my $part = ''; return sub { return shift @buf if @buf; while (my @ready = $self->{sel}->can_read) { for my $fh (@ready) { my $stderr = ''; READ: { my $got = sysread $fh, my ($chunk), 2048; if ($got == 0) { $self->{sel}->remove($fh); } elsif ($fh == $self->{err_fh}) { $stderr .= $chunk; my @lines = split(/\n/, $stderr, -1); $stderr = pop @lines; for my $line (@lines) { utf8::decode($line); print STDERR $line . "\n"; } goto READ if $got == 2048; utf8::decode($stderr) or die 'Subprocess provided non-utf8 data'; print STDERR $stderr . "\n"; } else { $part .= $chunk; push @buf, split(/\n/, $part, -1); $part = pop @buf; my $rv = shift @buf; if (defined $rv) { utf8::decode($rv) or die 'Subprocess provided non-utf8 data'; return $rv; } } } } } if ($part) { $part = ''; return $part; } $self->_finish; return; }; } sub next_raw { my $self = shift; $self->{_next} ||= $self->_next; return $self->{_next}->(); } sub get_select_handles { my $self = shift; # return our handle in case it's a socket or pipe (select()-able) return ( $self->{out_fh}, $self->{err_fh}); } 1; 800_regressions_010_too_few_features.t100644001750001750 112515014377065 23242 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.pod100644001750001750 3627115014377065 23261 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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.87 =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: 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 This scenario verifies the MD5 hash object of Digest.pm 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 This scenario verifies the SHA-1 hash object of Digest.pm 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 This scenario tests multi-line input in the MD5 hash object 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 Test2::Bundle::More; use Test::BDD::Cucumber::StepFile; Given qr/a usable (\S+) class/, sub { eval "use $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 ); }; # Note: There's no C at the end of the step file. This is # intentional. When you run L or the Test::Builder-based test which does the same thing (L<900_run_features.t|https://github.com/pherkin/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 This scenario verifies the MD5 hash object of Digest.pm 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. Between the C line and the step lines, there's a description. Any line starting with one of the step keywords (C, C or C) is considered to terminate the description block (to start the group of step lines). The 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 $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 This scenario verifies the SHA-1 hash object of Digest.pm 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. One scenario outline can have multiple C, e.g. Scenario: Check SHA-1 This scenario verifies the SHA-1 hash object of Digest.pm Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" Examples: 'foo' example This is the 'foo' examples description block. | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | Examples: other examples This description block describes the other examples. | data | output | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | =head1 Multiline Step Arguments Scenario: MD5 longer data This scenario tests multi-line input in the MD5 hash object 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 Conditional execution and tags There are times when only a subset of the scenarios in a feature should be run. To that end, C (like C) has a command line option to select the set of scenarios to be run, based on C. These tags can be inserted in a scenario before the C line, before the C line and before the C line. Tags are cumulative: tags on a feature are applied to all scenarios and examples. Here's the initial example from the tutorial with tags added. @feature-tag1 @feature-tag2 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-tag Scenario: Check SHA-1 This scenario verifies the SHA-1 hash object of Digest.pm Given a Digest SHA-1 object When I've added "" to the object Then the hex output is "" @daily-tests-tag Examples: 'foo' example This is the 'foo' examples description block. | data | output | | foo | 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33 | @weekly-tests-tag Examples: other examples This description block describes the other examples. | data | output | | bar | 62cdb7020ff920e5aa642c3d4066950dd1f01f4d | | baz | bbe960a25ea311d21d40669e93df2003ba9b90a2 | In order to run all scenarios, except the weekly tests, run: pherkin --tags=~@weekly-tests-tag In order to run all scenarios marked with C<@feature-tag1> except the weekly tests, run: pherkin --tags=@feature-tag1 --tags=~@weekly-tests-tag In order to run all scenarios marked with C<@feature-tag1> as well as those marked with C<@feature-tag2>, run: pherkin --tags=@feature-tag1,@feature-tag2 =head1 Non-English features and step files 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" | | scenarioOutline | "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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; cucumber_tagging_feature000755001750001750 015014377065 21004 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/ttagged.feature100644001750001750 25215014377065 23733 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_tagging_featureFeature: Tagged @exclude Scenario: Excluded Given a scenario @include Scenario: Included Given a scenario Scenario: Not tagged Given a scenario features000755001750001750 015014377065 21306 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/calculatorbasic.feature100644001750001750 673315014377065 24115 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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: The default Display is 0 Given a new Calculator object Then the display should show 0 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: Type expression but don't execute it Given a new Calculator object And having pressed 1 and 2 and 3 and + Then the display should show 123 Scenario: Type expression but don't execute it Given a new Calculator object And having pressed 1 and 2 and 3 and + and 4 and 5 and 6 Then the display should show 456 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: Really basic calculation Given a new Calculator object And having keyed 1+1= Then the display should show 2 Scenario: After calculation pressin new key will replace result Given a new Calculator object And having keyed 1+1= And having pressed 3 Then the display should show 3 Scenario: Basic arithmetic in one long input line Given a new Calculator object And having keyed And having pressed = Then the display should show Examples: | input | result | | 5.0+5.0 | 10 | | 6 / 3 | 2 | | 1 0 * 7 . 5 5 0 | 75.5 | | 3 - 10 | -7 | 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.pm100644001750001750 1743315014377065 23403 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Harnessuse v5.14; use warnings; package Test::BDD::Cucumber::Harness::TermColor 0.87; =head1 NAME Test::BDD::Cucumber::Harness::TermColor - Prints colorized text to the screen =head1 VERSION version 0.87 =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 Moo; use Types::Standard qw( Str HashRef FileHandle ); 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; 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->keyword_original . ': ' . ( $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->keyword_original . ': ' . color( $self->_colors->{'scenario_name'} ) . ( $scenario->name || '' ); $self->_display( { indent => 2, color => $self->_colors->{'scenario'}, text => $text, follow_up => [ map { $_->content } @{ $scenario->description || [] } ], 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'} + 4 ); 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 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; top_level_array.yaml100644001750001750 11215014377065 24320 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkin_config_files# Config files are meant to have hash at top level - default: foo: barTestBuilder.pm100644001750001750 67615014377065 23664 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Harnessuse v5.14; use warnings; package Test::BDD::Cucumber::Harness::TestBuilder 0.87; =head1 NAME Test::BDD::Cucumber::Harness::TestBuilder - Temporary redirector to TAP harness =head1 VERSION version 0.87 =head1 DESCRIPTION =cut use Test::BDD::Cucumber::Harness::TAP; use Moo; extends 'Test::BDD::Cucumber::Harness::TAP'; warn __PACKAGE__ . ' has been renamed to Test::BDD::Cucumber::Harness::TAP; TestBuilder will be removed in 1.0'; 1; Integration.pod100644001750001750 571015014377065 23713 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Integration; =head1 NAME Test::BDD::Cucumber::Manual::Integration - Test suite integration options =head1 VERSION version 0.87 =head1 DESCRIPTION How to use Test::BDD::Cucumber in your test suite =head1 OVERVIEW Test::BDD::Cucumber offers two options to integrate your tests with your test framework: 1. Integration with C which will run your .feature files as it does .t files 2. Creation of a .t file which fires off your selected .feature files (Test::Builder integration) The benefits from using the former approach is that all C's advanced features like parallel testing, randomized order, C<--state>ful runs, JUnit output, etc., are available out of the box. =head1 prove integration With Test::BDD::Cucumber installed in the Perl search path (PERL5LIB) comes the possibility to run the .feature files with a C command directly, by specifying $ prove -r --source Feature --ext=.feature --feature-option tags=~@wip t/ This command registers a C plugin named C associated with the C<.feature> extension. Additionally, it passes a tag filter to exclude @wip tagged features and scenarios from being run. When executed, the command searches the C directory recursively for files with the C<.feature> extension. For each directory holding at least one C<.feature> file, the step files are loaded from the C subdirectory. The command above will find and run I C<.feature> files. When you want to run your regular C<.t> files as well as Test::BDD::Cucumber's C<.feature> files, run the following command: $ prove -r --source Perl --ext=.t --source Feature --ext=.feature --feature-option tags=~@wip t/ =head1 Test::Builder integration -- a documented example The code below needs to be stored in a C<.t> file in the C or C directory. When done that way, the tests are integrated into C as generated from C after C. #!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::TAP; # 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. TAP harness prints TAP my $harness = Test::BDD::Cucumber::Harness::TAP->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.es100644001750001750 374715014377065 24525 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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 -1025features000755001750001750 015014377065 21665 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/tagged-digestbasic.feature100644001750001750 242115014377065 24462 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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" Architecture.pod100644001750001750 1066415014377065 24076 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/lib/Test/BDD/Cucumber/Manualpackage Test::BDD::Cucumber::Manual::Architecture; =head1 NAME Test::BDD::Cucumber::Manual::Architecture - Structural Overview =head1 VERSION version 0.87 =head1 INTRODUCTION This short document exists to give you an idea how the different components of this distribution fit together. Components fall into three categories: one for dealing with definition of the step functions (Perl code), the other for dealing with feature files. And then there's the third group, with C which links the two groups together; building on the steps to execute the features. In the first group are C and C. The second group is bigger and is comprised of =over 4 =item * C =item * C =back The third group holds - next to C: =over 4 =item * C =item * C =item * C =item * C =back Please note that the C should not be confused with C or C; this harness is TBC's own and optionally forwards events to the C harness. =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. =head2 Test::Builder While running step functions, C reroutes the flow of test events (calls to C, C, etc) to itself. Based on the collected data, the step itself is reported as a success or failure to the test driver. Confusing about this situation is that both the channel to report through to the actual test driver is an instance of Test::Builder as well as the method used to route the stream of test events to itself uses a Test::Builder instance. =head1 EXTENSION Extensions allow hooking into the execution of the steps, with pre- and post hooks for steps, scenarios, features and the entire execution. Extensions can provide additional step directories from which steps will be made available. The feature and scenario stashes are passed to the extension hooks allowing for a means of communication between the hooks and the steps. Additionally, as of I<0.60>, it's possible to define meta data for a step upon step definition like this example: Given qr/a step with meta data/ => { meta => 'data' }, sub { }; This allows step function authors to communicate with extensions, because extensions receive this meta data in their C callback: sub pre_step { my ($stepdef, $step_context) = @_; my ($verb, $metadata, $stepfn) = @$stepdef; die 'All steps should have meta -> data' unless $metadata->{meta} eq 'data'; } Extensions - when loaded by the pherkin test executor - receive their configuration from the pherkin.yaml configuration file, which works similar to L. Note: when using extensions in combination with the C plugin for C, there is no guarantee that the C and C hooks execute exactly once or even execute at all. This is a current limitation to be lifted in a future release. =head1 AUTHOR Peter Sergeant C =head1 LICENSE Copyright 2019-2023, Erik Huelsmann Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl =cut 1; 800_regressions_030_pherkin_external_libs.t100644001750001750 135215014377065 24261 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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_definitions000755001750001750 015014377065 22026 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/harness_jsonmock_steps.pl100644001750001750 70115014377065 24650 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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); }; CucumberExtensionMetadataVerify.pm100644001750001750 203115014377065 24433 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/lib/Test#!perl package Test::CucumberExtensionMetadataVerify; # Extension to verify existence of metadata use Moo; use Carp; use Scalar::Util qw( reftype ); use Test::BDD::Cucumber::Extension; extends 'Test::BDD::Cucumber::Extension'; sub pre_step { my ($self, $step, $step_context) = @_; croak 'pre_step called with incorrect number $step array elements' if scalar(@$step) != 3; croak 'pre_step called with incorrect first element type of $step array' # 5.10 reports SCALAR where the argument actually is a regexp; ignore <=5.10 if reftype $step->[0] ne 'REGEXP' and $] ge '5.012'; croak 'pre_step called with incorrect second element type of $step array' if reftype $step->[1] ne 'HASH'; croak 'pre_step called with incorrect meta data content in $step array' if exists $step->[1]->{meta} and $step->[1]->{meta} ne 'data'; croak 'pre_step called with incorrect third element type of $step array' if reftype $step->[2] ne 'CODE'; } __PACKAGE__->meta->make_immutable; 1; 800_regressions_020_term_color_skipped_steps.t100644001750001750 246615014377065 25016 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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/pherkin/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_parsing000755001750001750 015014377065 23060 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/regressionstest.feature100644001750001750 243115014377065 25554 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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: Last step with multiline string Given a Digest MD5 object When I add "foo" to the object And I add "bar" to the object Then the hexdigest looks like: """ 3858f62230ac3c915f300c664312c63f """ And the b64digest looks like: """ 1B2M2Y8AsgTpgAmY7PhCfg """ Scenario: First 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 | step_definitions000755001750001750 015014377065 23045 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkin/match-onlymatch_steps.pl100644001750001750 52315014377065 26034 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/pherkin/match-only/step_definitions use strict; use warnings; use Test::BDD::Cucumber::StepFile; Given qr/^\Qa step with step function that calls die()\E$/, sub { die "This step function calls die()"; }; When qr/^\Qthere are further steps with associated step functions\E$/, sub { }; Then qr/^\QI expect all steps to be matched\E$/, { 'meta' => 'data' }, sub { }; test_steps.pl100644001750001750 142715014377065 25756 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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"; } }; Then qr/the ((?:hex|b64)digest) looks like:/, sub { my $func = $1; my $expect = C->data; chomp $expect; my $digest = S->{digest}; my $got = $digest->$func(); is $got, $expect, "test: $func"; }; step_definitions000755001750001750 015014377065 24045 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_core_featurestag_steps.pl100644001750001750 642515014377065 26542 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_core_features/step_definitions#!perl package tag_feature_step_functions; 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.pl100755001750001750 1226615014377065 26742 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_core_features/step_definitions#!perl package core_features_steps_functions; 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], scenario_stash => {}, 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_definitions000755001750001750 015014377065 24002 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/digest/featuresbasic_steps.pl100755001750001750 136215014377065 27003 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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_definitions000755001750001750 015014377065 23771 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/i18n_es/featuresbasic_steps.pl100644001750001750 144015014377065 26764 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/i18n_es/features/step_definitions#!perl use strict; use warnings; use Digest; use Test::More; use Test::BDD::Cucumber::StepFile; Dado qr/la clase "(\w+)"/, sub { use_ok( C->matches->[0] ); }; Dado qr/un objeto Digest usando el algoritmo "(\S+)"/, sub { my $object = Digest->new( C->matches->[0] ); ok( $object, "Objecto creado" ); S->{'object'} = $object; }; Cuando qr/he agregado "(\w+)" al objeto/, sub { S->{'object'}->add( C->matches->[0] ); }; Cuando "he agregado los siguientes datos al objeto", sub { S->{'object'}->add( C->data ); }; Entonces qr/el resultado (?:en\s*)?(\w+) es "(.+)"/, sub { my ( $type, $expected ) = @{ C->matches }; my $method = { 'base64' => 'b64digest', 'hexadecimal' => 'hexdigest' }->{$type}; is( S->{'object'}->$method(), $expected ); }; step_definitions000755001750001750 015014377065 24352 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_tagging_featuretagged_steps.pl100644001750001750 21515014377065 27476 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/t/cucumber_tagging_feature/step_definitions#!perl package tag_feature_step_functions; use strict; use warnings; use Test::BDD::Cucumber::StepFile; Given qr/a scenario/, sub { }; step_definitions000755001750001750 015014377065 23752 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/i18n_de/featurescalculator_steps.pl100644001750001750 440515014377065 30021 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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_definitions000755001750001750 015014377065 25233 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/tagged-digest/featuresbasic_steps.pl100755001750001750 136115014377065 30233 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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_definitions000755001750001750 015014377065 24654 5ustar00erikerik000000000000Test-BDD-Cucumber-0.87/examples/calculator/featurescalculator_steps.pl100755001750001750 460715014377065 30732 0ustar00erikerik000000000000Test-BDD-Cucumber-0.87/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" ); };