├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── guard-cucumber.gemspec ├── lib └── guard │ ├── cucumber.rb │ └── cucumber │ ├── focuser.rb │ ├── inspector.rb │ ├── notification_formatter.rb │ ├── runner.rb │ ├── templates │ └── Guardfile │ └── version.rb └── spec ├── guard ├── cucumber │ ├── focuser_spec.rb │ ├── inspector_spec.rb │ ├── notification_formatter_spec.rb │ ├── runner_spec.rb │ └── version_spec.rb └── cucumber_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | .yardoc 5 | .idea 6 | doc 7 | Gemfile.lock 8 | *.rbc 9 | .rbx 10 | 11 | ## MAC OS 12 | .DS_Store 13 | .Trashes 14 | .com.apple.timemachine.supported 15 | .fseventsd 16 | Desktop DB 17 | Desktop DF 18 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - db/schema.rb 4 | 5 | AccessorMethodName: 6 | Enabled: false 7 | 8 | ActionFilter: 9 | Enabled: false 10 | 11 | Alias: 12 | Enabled: false 13 | 14 | ArrayJoin: 15 | Enabled: false 16 | 17 | AsciiComments: 18 | Enabled: false 19 | 20 | AsciiIdentifiers: 21 | Enabled: false 22 | 23 | Attr: 24 | Enabled: false 25 | 26 | BlockNesting: 27 | Enabled: false 28 | 29 | CaseEquality: 30 | Enabled: false 31 | 32 | CharacterLiteral: 33 | Enabled: false 34 | 35 | ClassAndModuleChildren: 36 | Enabled: false 37 | 38 | ClassLength: 39 | Enabled: false 40 | 41 | ClassVars: 42 | Enabled: false 43 | 44 | CollectionMethods: 45 | PreferredMethods: 46 | find: detect 47 | reduce: inject 48 | collect: map 49 | find_all: select 50 | 51 | ColonMethodCall: 52 | Enabled: false 53 | 54 | CommentAnnotation: 55 | Enabled: false 56 | 57 | CyclomaticComplexity: 58 | Enabled: false 59 | 60 | Delegate: 61 | Enabled: false 62 | 63 | DeprecatedHashMethods: 64 | Enabled: false 65 | 66 | Documentation: 67 | Enabled: false 68 | 69 | DotPosition: 70 | EnforcedStyle: trailing 71 | 72 | DoubleNegation: 73 | Enabled: false 74 | 75 | EachWithObject: 76 | Enabled: false 77 | 78 | EmptyLiteral: 79 | Enabled: false 80 | 81 | Encoding: 82 | Enabled: false 83 | 84 | EvenOdd: 85 | Enabled: false 86 | 87 | FileName: 88 | Enabled: false 89 | 90 | FlipFlop: 91 | Enabled: false 92 | 93 | FormatString: 94 | Enabled: false 95 | 96 | GlobalVars: 97 | Enabled: false 98 | 99 | GuardClause: 100 | Enabled: false 101 | 102 | IfUnlessModifier: 103 | Enabled: false 104 | 105 | IfWithSemicolon: 106 | Enabled: false 107 | 108 | InlineComment: 109 | Enabled: false 110 | 111 | Lambda: 112 | Enabled: false 113 | 114 | LambdaCall: 115 | Enabled: false 116 | 117 | LineEndConcatenation: 118 | Enabled: false 119 | 120 | LineLength: 121 | Max: 80 122 | 123 | MethodLength: 124 | Enabled: false 125 | 126 | ModuleFunction: 127 | Enabled: false 128 | 129 | NegatedIf: 130 | Enabled: false 131 | 132 | NegatedWhile: 133 | Enabled: false 134 | 135 | Next: 136 | Enabled: false 137 | 138 | NilComparison: 139 | Enabled: false 140 | 141 | Not: 142 | Enabled: false 143 | 144 | NumericLiterals: 145 | Enabled: false 146 | 147 | OneLineConditional: 148 | Enabled: false 149 | 150 | OpMethod: 151 | Enabled: false 152 | 153 | ParameterLists: 154 | Enabled: false 155 | 156 | PercentLiteralDelimiters: 157 | Enabled: false 158 | 159 | PerlBackrefs: 160 | Enabled: false 161 | 162 | PredicateName: 163 | NamePrefixBlacklist: 164 | - is_ 165 | 166 | Proc: 167 | Enabled: false 168 | 169 | RaiseArgs: 170 | Enabled: false 171 | 172 | RegexpLiteral: 173 | Enabled: false 174 | 175 | SelfAssignment: 176 | Enabled: false 177 | 178 | SingleLineBlockParams: 179 | Enabled: false 180 | 181 | SingleLineMethods: 182 | Enabled: false 183 | 184 | SignalException: 185 | Enabled: false 186 | 187 | SpecialGlobalVars: 188 | Enabled: false 189 | 190 | StringLiterals: 191 | EnforcedStyle: double_quotes 192 | 193 | VariableInterpolation: 194 | Enabled: false 195 | 196 | Style/TrailingCommaInLiteral: 197 | Enabled: false 198 | 199 | TrivialAccessors: 200 | Enabled: false 201 | 202 | VariableInterpolation: 203 | Enabled: false 204 | 205 | WhenThen: 206 | Enabled: false 207 | 208 | WhileUntilModifier: 209 | Enabled: false 210 | 211 | WordArray: 212 | Enabled: false 213 | 214 | # Lint 215 | 216 | AmbiguousOperator: 217 | Enabled: false 218 | 219 | AmbiguousRegexpLiteral: 220 | Enabled: false 221 | 222 | AssignmentInCondition: 223 | Enabled: false 224 | 225 | ConditionPosition: 226 | Enabled: false 227 | 228 | DeprecatedClassMethods: 229 | Enabled: false 230 | 231 | ElseLayout: 232 | Enabled: false 233 | 234 | HandleExceptions: 235 | Enabled: false 236 | 237 | InvalidCharacterLiteral: 238 | Enabled: false 239 | 240 | LiteralInCondition: 241 | Enabled: false 242 | 243 | LiteralInInterpolation: 244 | Enabled: false 245 | 246 | Loop: 247 | Enabled: false 248 | 249 | ParenthesesAsGroupedExpression: 250 | Enabled: false 251 | 252 | RequireParentheses: 253 | Enabled: false 254 | 255 | UnderscorePrefixedVariableName: 256 | Enabled: false 257 | 258 | Void: 259 | Enabled: false 260 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | bundler_args: --without development 3 | rvm: 4 | - 2.2.0 5 | - 2.3.1 6 | - jruby-9.0.5.0 7 | - rbx 8 | - ruby-head 9 | matrix: 10 | allow_failures: 11 | - rvm: rbx 12 | - rvm: ruby-head 13 | branches: 14 | only: 15 | - master 16 | sudo: false 17 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --readme README.md 2 | --markup markdown 3 | --markup-provider redcarpet 4 | --title 'Guard::Cucumber Documentation' 5 | --hide-void-return 6 | --protected 7 | --private 8 | lib/**/*.rb 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # NOTE: Moved to [Github releases page](https://github.com/guard/guard-cucumber/releases). 2 | 3 | # Changelog 4 | 5 | ## 1.4.1 - Dec 27, 2013 6 | 7 | - [#45][]: Don't require Bundler to use binstubs. ([@mgarriott][]) 8 | 9 | ## 1.4.0 - Apr 1, 2013 10 | 11 | - [#8][]: Don't require Bundler to use binstubs. ([@JeanMertz][]) 12 | 13 | ## 1.3.2 - Jan 15, 2013 14 | 15 | - Do not search focus tag in files that already have a line number on rerun. 16 | 17 | ## 1.3.1 - Jan 7, 2013 18 | 19 | - [#7][]: Fix `run_all` with enabled focus option. ([@martco][]) 20 | 21 | ## 1.3.0 - Dec 28, 2012 22 | 23 | - [#6][]: Add `:focus_on` option. ([@martco][]) 24 | 25 | ## 1.2.2 - Oct 31, 2012 26 | 27 | - Add accessors for `last_failed` and `failed_path` 28 | 29 | ## 1.2.1 - Oct 31, 2012 30 | 31 | - [#36][]: `:feature_sets` defined in Guardfile is not taken in account. 32 | 33 | ## 1.2.0 - June 19, 2012 34 | 35 | - Add a command_prefix option. ([@mikefarmer][]) 36 | 37 | ## 1.1.0 - June 6, 2012 38 | 39 | - [#34][]: Implemented :feature_sets option. ([@andreassimon][]) 40 | 41 | ## 1.0.0 - June 2, 2012 42 | 43 | - Update for Guard 1.1 44 | 45 | ## 0.8.0 - Mai 8, 2012 46 | 47 | - Update for Cucucmber 1.2.0 48 | 49 | ## 0.7.5 - Jan 17, 2012 50 | 51 | - [#31][]: Allow to use binstubs. ([@dnagir][]) 52 | - [#27][]: Allow binstubs options. ([@hedgehog][]) 53 | 54 | ## 0.7.4 - Nov 9, 2011 55 | 56 | - [#24][]: Individual scenarios are no longer run if the entire feature is also scheduled to run([@oreoshake][]) 57 | 58 | ## 0.7.3 - Oct 14, 2011 59 | 60 | - [#20][]: Close rerun.txt file properly after reading the failed features. 61 | 62 | ## 0.7.2 - Oct 1, 2011 63 | 64 | - Enable :task_has_failed 65 | 66 | ## 0.7.1 - Oct 1, 2011 67 | 68 | - Disable :task_has_failed until new Guard version is available. 69 | 70 | ## 0.7.0 - Sep 30, 2011 71 | 72 | - Update for Guard 0.8 73 | 74 | ## 0.6.3 - Sep 8, 2011 75 | 76 | - Documentation updates and refactorings. 77 | 78 | ## 0.6.2 - Sep 4, 2011 79 | 80 | - [#17][]: Make sure either a valid symbol or nil is passed as image. 81 | 82 | ## 0.6.1 - Aug 17, 2011 83 | 84 | - Remember line number of features to be rerun. 85 | 86 | ## 0.6.0 - Aug 14 2011 87 | 88 | - Add `:run_all` option. 89 | 90 | ## 0.5.2 - Jul 04 2011 91 | 92 | - [#13][]: Save failed features from `#run_all`. ([@lmarburger][]) 93 | 94 | ## 0.5.1 - Jun 28 2011 95 | 96 | - [#14][]: Ensure the Guard returns a boolean status. 97 | - [#12][]: Require guard/notifier to fix exception in cucumber binary. ([@robertzx][]) 98 | - Introduces the `:change_format` option. 99 | - [#11][] Add support for failure format. ([@NickClark][]) 100 | 101 | ## 0.4.0 - Jun 06 2011 102 | 103 | - Fix empty `rerun.txt`. 104 | 105 | ## 0.3.3 - Jun 06 2011 106 | 107 | - Improve failed feature detection via `rerun.txt`. 108 | - Change the :cli option to ignore the default profile. 109 | 110 | ## 0.3.1 - May 13 2011 111 | 112 | - When stealing is desired: Porting the `:all_after_pass`, `:all_on_start` and `:keep_failed` 113 | - Let Cucumber notify the most important story: a failure, else a pending, else an undefined. ([@lorennorman][]) 114 | - Added an extra Cucumber alert naming the failed steps. ([@lorennorman][]) 115 | 116 | ## 0.3.0 - Mar 28 2011 117 | 118 | - Cucumber arguments are now passed only through the CLI option. 119 | - Use another null device on ms win. 120 | - [#5][]: made loading of notification formatter as just another formatter with `/dev/null`. ([@hron][]) 121 | 122 | ## 0.2.4 - Mar 14 2011 123 | 124 | - Implemented workaround of not loaded step definitions when using guard-spork. ([@hron][]) 125 | - Added option to pass any command to cucumber. ([@oriolgual][]) 126 | 127 | ## 0.2.3 - Jan 21 2011 128 | 129 | - Add option to include a profile argument in cucumber. ([@thecatwasnot][]) 130 | 131 | ## 0.2.2 - Dec 09 2010 132 | 133 | - Depend on the latest cucumber 0.10.0 134 | 135 | ## 0.1.0 - Oct 28 2010 136 | 137 | - Initial release. 138 | 139 | [#5]: https://github.com/netzpirat/guard-cucumber/issues/5 140 | [#6]: https://github.com/guard/guard-cucumber/issues/6 141 | [#7]: https://github.com/guard/guard-cucumber/issues/7 142 | [#8]: https://github.com/guard/guard-cucumber/issues/8 143 | [#11]: https://github.com/netzpirat/guard-cucumber/issues/11 144 | [#12]: https://github.com/netzpirat/guard-cucumber/issues/12 145 | [#13]: https://github.com/netzpirat/guard-cucumber/issues/13 146 | [#14]: https://github.com/netzpirat/guard-cucumber/issues/14 147 | [#17]: https://github.com/netzpirat/guard-cucumber/issues/17 148 | [#20]: https://github.com/netzpirat/guard-cucumber/issues/20 149 | [#24]: https://github.com/netzpirat/guard-cucumber/issues/24 150 | [#27]: https://github.com/netzpirat/guard-cucumber/issues/27 151 | [#31]: https://github.com/netzpirat/guard-cucumber/issues/31 152 | [#34]: https://github.com/netzpirat/guard-cucumber/issues/34 153 | [#36]: https://github.com/netzpirat/guard-cucumber/issues/36 154 | [#45]: https://github.com/netzpirat/guard-cucumber/issues/45 155 | [@JeanMertz]: https://github.com/JeanMertz 156 | [@NickClark]: https://github.com/NickClark 157 | [@andreassimon]: https://github.com/andreassimon 158 | [@dnagir]: https://github.com/dnagir 159 | [@hedgehog]: https://github.com/hedgehog 160 | [@hron]: https://github.com/hron 161 | [@lmarburger]: https://github.com/lmarburger 162 | [@lorennorman]: https://github.com/lorennorman 163 | [@martco]: https://github.com/martco 164 | [@mgarriott]: https://github.com/mgarriott 165 | [@mikefarmer]: https://github.com/mikefarmer 166 | [@oreoshake]: https://github.com/oreoshake 167 | [@oriolgual]: https://github.com/oriolgual 168 | [@robertzx]: https://github.com/robertzx 169 | [@thecatwasnot]: https://github.com/thecatwasnot 170 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribute to Guard::Cucumber 2 | ============================= 3 | 4 | File an issue 5 | ------------- 6 | 7 | You can report bugs and feature requests to [GitHub Issues](https://github.com/netzpirat/guard-cucumber/issues). 8 | 9 | **Please don't ask question in the issue tracker**, instead ask them on at Stack Overflow and use the 10 | [guard](http://stackoverflow.com/questions/tagged/guard) tag. 11 | 12 | Try to figure out where the issue belongs to: Is it an issue with Guard itself or with a Guard plugin you're 13 | using? 14 | 15 | When you file a bug, please try to follow these simple rules if applicable: 16 | 17 | * Make sure you've read the README carefully. 18 | * Make sure you run Guard with `bundle exec` first. 19 | * Add debug information to the issue by running Guard with the `--debug` option. 20 | * Add your `Guardfile` and `Gemfile` to the issue. 21 | * Make sure that the issue is reproducible with your description. 22 | 23 | **It's most likely that your bug gets resolved faster if you provide as much information as possible!** 24 | 25 | Development 26 | ----------- 27 | 28 | * Documentation hosted at [RubyDoc](http://rubydoc.info/github/netzpirat/guard-cucumber/master/frames). 29 | * Source hosted at [GitHub](https://github.com/netzpirat/guard-cucumber). 30 | 31 | Pull requests are very welcome! Please try to follow these simple rules if applicable: 32 | 33 | * Please create a topic branch for every separate change you make. 34 | * Make sure your patches are well tested. All specs run with `rake spec:portability` must pass. 35 | * Update the [Yard](http://yardoc.org/) documentation. 36 | * Update the [README](https://github.com/netzpirat/guard-cucumber/blob/master/README.md). 37 | * Update the [CHANGELOG](https://github.com/netzpirat/guard-cucumber/blob/master/CHANGELOG.md) for noteworthy changes. 38 | * Please **do not change** the version number. 39 | 40 | For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on 41 | `#guard` (irc.freenode.net). 42 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec development_group: :gem_build_tools 4 | 5 | group :gem_build_tools do 6 | gem "rake" 7 | end 8 | 9 | # The development group will no be 10 | # installed on Travis CI. 11 | # 12 | group :development do 13 | gem "guard-rspec", require: false 14 | gem "guard-bundler", "~> 2.0.0", require: false 15 | gem "yard", require: false 16 | gem "redcarpet", require: false 17 | gem "guard-rubocop", require: false 18 | gem "rubocop", "~> 0.39.0" 19 | gem "guard-compat", require: false 20 | end 21 | 22 | group :test do 23 | gem "rspec", "~> 3.1" 24 | end 25 | 26 | platforms :rbx do 27 | gem "racc" 28 | gem "rubysl", "~> 2.0" 29 | gem "psych" 30 | end 31 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | group :spec, halt_on_fail: true do 2 | guard :bundler do 3 | watch("Gemfile") 4 | # Uncomment next line if your Gemfile contains the `gemspec' command. 5 | watch(/^.+\.gemspec/) 6 | end 7 | 8 | guard :rspec, cmd: "bundle exec rspec", all_on_start: false do 9 | watch("spec/spec_helper.rb") { "spec" } 10 | watch(%r{spec/.+_spec.rb}) 11 | watch(%r{lib/(.+).rb}) { |m| "spec/#{m[1]}_spec.rb" } 12 | end 13 | 14 | guard :rubocop, all_on_start: false do 15 | watch(%r{.+\.rb$}) 16 | watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2013 Michael Kessler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guard::Cucumber [![Build Status](https://secure.travis-ci.org/guard/guard-cucumber.png)](http://travis-ci.org/netzpirat/guard-cucumber) 2 | 3 | Guard::Cucumber allows you to automatically run Cucumber features when files are modified. 4 | 5 | Supported Ruby versions are those tested on [Travis CI](https://travis-ci.org/guard/guard-cucumber). 6 | (NOTE: Any Ruby version earlier than 2.2.5 is outdated and likely insecure). 7 | 8 | 9 | If you have any questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net). 10 | 11 | ## Install 12 | 13 | The simplest way to install Guard is to use [Bundler](http://gembundler.com/). 14 | Please make sure to have [Guard](https://github.com/guard/guard) installed before continue. 15 | 16 | Add Guard::Cucumber to your `Gemfile`: 17 | 18 | ```bash 19 | group :development do 20 | gem 'guard-cucumber' 21 | end 22 | ``` 23 | 24 | Add the default Guard::Cucumber template to your `Guardfile` by running: 25 | 26 | ```bash 27 | $ guard init cucumber 28 | ``` 29 | 30 | ## Usage 31 | 32 | Please read the [Guard usage documentation](https://github.com/guard/guard#readme). 33 | 34 | ## Guardfile 35 | 36 | Guard::Cucumber can be adapted to all kind of projects and comes with a default template that looks like this: 37 | 38 | ```ruby 39 | guard 'cucumber' do 40 | watch(%r{^features/.+\.feature$}) 41 | watch(%r{^features/support/.+$}) { 'features' } 42 | watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' } 43 | end 44 | ``` 45 | 46 | Expressed in plain English, this configuration tells Guard::Cucumber: 47 | 48 | 1. When a file within the features directory that ends in feature is modified, just run that single feature. 49 | 2. When any file within features/support directory is modified, run all features. 50 | 3. When a file within the features/step_definitions directory that ends in \_steps.rb is modified, 51 | run the first feature that matches the name (\_steps.rb replaced by .feature) and when no feature is found, 52 | then run all features. 53 | 54 | Please read the [Guard documentation](http://github.com/guard/guard#readme) for more information about the Guardfile DSL. 55 | 56 | ## Options 57 | 58 | You can pass any of the standard Cucumber CLI options using the :cmd_additional_args option: 59 | 60 | ```ruby 61 | guard 'cucumber', :cmd_additional_args => '-c --drb --port 1234 --profile guard' 62 | ``` 63 | 64 | Former `:color`, `:drb`, `:port`, `:profile` and `:cli` options are thus deprecated and have no effect anymore. 65 | 66 | You can specify custom cucumber command to run using the :cmd option: 67 | 68 | ```ruby 69 | guard 'cucumber', :cmd => 'spring cucumber' 70 | ``` 71 | 72 | Former `:bundler`, `:bin_stubs`, `:change_format`, `:rvm` and `:command_prefix` options are thus deprecated and have no effect anymore. 73 | 74 | 75 | ### List of available options 76 | 77 | ```ruby 78 | cmd: 'spring cucumber' # Specify custom cucumber command to run, default: 'cucumber' 79 | cmd_additional_args: # Pass arbitrary Cucumber CLI arguments, 80 | '--profile guard -c' # default: '--no-profile --color --format progress --strict' 81 | 82 | feature_sets: # Use non-default feature directory/ies 83 | ['set_a', 'set_b'] # default: ['features'] 84 | 85 | notification: false # Don't display Growl (or Libnotify) notification 86 | # default: true 87 | 88 | all_after_pass: false # Don't run all features after changed features pass 89 | # default: true 90 | 91 | all_on_start: false # Don't run all the features at startup 92 | # default: true 93 | 94 | keep_failed: false # Keep failed features until they pass 95 | # default: true 96 | 97 | run_all: {cmd_additional_args: "--format pretty"} # Override any option when running all specs 98 | # default: {} 99 | 100 | focus_on: 'dev' # Focus on scenarios tagged with '@dev' 101 | # If '@dev' is on line 6 in 'foo.feature', 102 | # this example runs: 'bundle exec cucumber foo.feature:6' 103 | # default: nil 104 | ``` 105 | 106 | ## Cucumber configuration 107 | 108 | It's **very important** that you understand how Cucumber gets configured, because it's often the origin of 109 | strange behavior of guard-cucumber. 110 | 111 | Cucumber uses [cucumber.yml](https://github.com/cucumber/cucumber/wiki/cucumber.yml) for defining profiles 112 | of specific run configurations. When you pass configurations through the `:cli` option but don't include a 113 | specific profile with `--profile`, then the configurations from the `default` profile are also used. 114 | 115 | For example, when you're using the default cucumber.yml generated by [cucumber-rails](https://github.com/cucumber/cucumber-rails), 116 | then the default profile forces guard-cucumber to always run all features, because it appends the `features` folder. 117 | 118 | ### Configure Cucumber solely from Guard 119 | 120 | If you want to configure Cucumber from Guard solely, then you should pass `--no-profile` to the `:cli` option. 121 | 122 | Since guard-cucumber version 0.3.2, the default `:cmd_additional_args` options are: 123 | 124 | ```ruby 125 | cmd_additional_args: '--no-profile --color --format progress --strict' 126 | ``` 127 | 128 | This default configuration has been chosen to avoid strange behavior when mixing configurations from 129 | the cucumber.yml default profile with the guard-cucumber `:cmd_additional_args` option. 130 | 131 | You can safely remove `config/cucumber.yml`, since all configuration is done in the `Guardfile`. 132 | 133 | ### Use a separate Guard profile 134 | 135 | If you're using different profiles with Cucumber then you should create a profile for Guard in cucumber.yml, 136 | something like this: 137 | 138 | ```yaml 139 | guard: --format progress --strict --tags ~@wip 140 | ``` 141 | 142 | Now you want to make guard-cucumber use that profile by passing `--profile guard` to the `:cli`. 143 | 144 | ## Cucumber with Spork 145 | 146 | To use Guard::Cucumber with [Spork](https://github.com/timcharper/spork), you should install 147 | [Guard::Spork](https://github.com/guard/guard-spork) and use the following configuration: 148 | 149 | ```ruby 150 | guard 'spork' do 151 | watch('config/application.rb') 152 | watch('config/environment.rb') 153 | watch(%r{^config/environments/.*\.rb$}) 154 | watch(%r{^config/initializers/.*\.rb$}) 155 | watch('spec/spec_helper.rb') 156 | end 157 | 158 | guard 'cucumber', :cmd_additional_args => '--drb --format progress --no-profile' do 159 | watch(%r{^features/.+\.feature$}) 160 | watch(%r{^features/support/.+$}) { 'features' } 161 | watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' } 162 | end 163 | ``` 164 | 165 | There is a section with alternative configurations on the [Wiki](https://github.com/netzpirat/guard-cucumber/wiki/Spork-configurations). 166 | 167 | ## Cucumber with Zeus 168 | 169 | To use Guard::Cucumber with [Zeus](https://github.com/burke/zeus), just set the command prefix: 170 | ``` 171 | guard 'cucumber', :cmd => 'zeus cucumber' do 172 | ... 173 | end 174 | ``` 175 | 176 | Don't use `cmd: 'bundle exec zeus cucumber'` because Zeus recommends to avoid a bundler call. 177 | 178 | ## Cucumber with Spring 179 | 180 | To use Guard::Cucumber with [Spring](https://github.com/jonleighton/spring), just set the command prefix: 181 | ``` 182 | guard 'cucumber', :cmd => 'spring cucumber' do 183 | ... 184 | end 185 | ``` 186 | 187 | Don't use `cmd: 'bundle exec zeus cucumber'` because Spring recommends to avoid a bundler call. 188 | 189 | 190 | Issues 191 | ------ 192 | 193 | You can report issues and feature requests to [GitHub Issues](https://github.com/netzpirat/guard-cucumber/issues). Try to figure out 194 | where the issue belongs to: Is it an issue with Guard itself or with Guard::Cucumber? Please don't 195 | ask question in the issue tracker, instead join us in our [Google group](http://groups.google.com/group/guard-dev) or on 196 | `#guard` (irc.freenode.net). 197 | 198 | When you file an issue, please try to follow to these simple rules if applicable: 199 | 200 | * Make sure you run Guard with `bundle exec` first. 201 | * Add debug information to the issue by running Guard with the `--debug` option. 202 | * Add your `Guardfile` and `Gemfile` to the issue. 203 | * Make sure that the issue is reproducible with your description. 204 | 205 | ## Development 206 | 207 | - Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/guard-cucumber/master/frames). 208 | - Source hosted at [GitHub](https://github.com/netzpirat/guard-cucumber). 209 | 210 | Pull requests are very welcome! Please try to follow these simple rules if applicable: 211 | 212 | * Please create a topic branch for every separate change you make. 213 | * Make sure your patches are well tested. 214 | * Update the [Yard](http://yardoc.org/) documentation. 215 | * Update the README. 216 | * Update the CHANGELOG for noteworthy changes. 217 | * Please **do not change** the version number. 218 | 219 | For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on 220 | `#guard` (irc.freenode.net). 221 | 222 | ## Author 223 | 224 | Developed by Michael Kessler, sponsored by [FlinkFinger](http://www.flinkfinger.com). 225 | 226 | If you like Guard::Cucumber, you can watch the repository at [GitHub](https://github.com/netzpirat/guard-cucumber) 227 | and follow [@netzpirat](https://twitter.com/#!/netzpirat) on Twitter for project updates. 228 | 229 | ## Contributors 230 | 231 | See the GitHub list of [contributors](https://github.com/netzpirat/guard-cucumber/contributors). 232 | 233 | Since guard-cucumber is very close to guard-rspec, some contributions by the following authors have been 234 | incorporated into guard-cucumber: 235 | 236 | * [Andre Arko](https://github.com/indirect) 237 | * [Thibaud Guillaume-Gentil](https://github.com/thibaudgg) 238 | 239 | ## Acknowledgment 240 | 241 | The [Guard Team](https://github.com/guard/guard/contributors) for giving us such a nice pice of software 242 | that is so easy to extend, one *has* to make a plugin for it! 243 | 244 | All the authors of the numerous [Guards](http://github.com/guard) available for making the Guard ecosystem 245 | so much growing and comprehensive. 246 | 247 | ## License 248 | 249 | (The MIT License) 250 | 251 | Copyright (c) 2010-2013 Michael Kessler 252 | 253 | Permission is hereby granted, free of charge, to any person obtaining 254 | a copy of this software and associated documentation files (the 255 | 'Software'), to deal in the Software without restriction, including 256 | without limitation the rights to use, copy, modify, merge, publish, 257 | distribute, sublicense, and/or sell copies of the Software, and to 258 | permit persons to whom the Software is furnished to do so, subject to 259 | the following conditions: 260 | 261 | The above copyright notice and this permission notice shall be 262 | included in all copies or substantial portions of the Software. 263 | 264 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 265 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 266 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 267 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 268 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 269 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 270 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 271 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | 3 | require "nenv" 4 | Bundler::GemHelper.install_tasks 5 | 6 | require "rspec/core/rake_task" 7 | RSpec::Core::RakeTask.new(:spec) do |t| 8 | t.verbose = Nenv.ci? 9 | end 10 | 11 | task default: :spec 12 | 13 | require "rbconfig" 14 | namespace(:spec) do 15 | desc "Run all specs on multiple ruby versions (requires rvm)" 16 | task(:portability) do 17 | travis_config_file = File.expand_path("../.travis.yml", __FILE__) 18 | begin 19 | travis_options ||= YAML::load_file(travis_config_file) 20 | rescue => ex 21 | msg = "Travis config file '%s' could not be found: %s" 22 | puts format(msg, travis_config_file, ex.message) 23 | return 24 | end 25 | 26 | travis_options["rvm"].each do |version| 27 | system <<-BASH 28 | bash -c 'source ~/.rvm/scripts/rvm; 29 | rvm #{version}; 30 | ruby_version_string_size=`ruby -v | wc -m` 31 | echo; 32 | for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done 33 | echo; 34 | echo "`ruby -v`"; 35 | for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done 36 | echo; 37 | RBXOPT="-Xrbc.db" bundle install; 38 | RBXOPT="-Xrbc.db" bundle exec rspec spec -f doc 2>&1;' 39 | BASH 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /guard-cucumber.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "guard/cucumber/version" 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "guard-cucumber" 8 | s.version = Guard::CucumberVersion::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ["Cezary Baginski", "Michael Kessler"] 11 | s.email = ["cezary@chronomantic.net"] 12 | s.homepage = "http://github.com/guard/guard-cucumber" 13 | s.license = "MIT" 14 | s.summary = "Guard plugin for Cucumber" 15 | s.description = "Guard::Cucumber automatically run your"\ 16 | " features (much like autotest)" 17 | 18 | s.required_rubygems_version = ">= 1.3.6" 19 | 20 | s.add_dependency "cucumber", ">= 3.1" 21 | s.add_dependency "nenv", ">= 0.1" 22 | 23 | s.add_development_dependency "guard-compat", ">= 1.0" 24 | s.add_development_dependency "bundler", ">= 1.6" 25 | 26 | s.files = `git ls-files -z`.split("\x0").select do |f| 27 | /^lib\// =~ f 28 | end + %w(LICENSE README.md) 29 | 30 | s.require_paths = ["lib"] 31 | end 32 | -------------------------------------------------------------------------------- /lib/guard/cucumber.rb: -------------------------------------------------------------------------------- 1 | require "cucumber" 2 | require "guard/cucumber/version" 3 | require "guard/cucumber/runner" 4 | require "guard/cucumber/inspector" 5 | require "guard/cucumber/focuser" 6 | 7 | module Guard 8 | # The Cucumber guard that gets notifications about the following 9 | # Guard events: `start`, `stop`, `reload`, `run_all` and `run_on_change`. 10 | # 11 | class Cucumber < Plugin 12 | attr_accessor :last_failed, :failed_path 13 | 14 | KNOWN_OPTIONS = %w( 15 | cmd 16 | cmd_additional_args 17 | 18 | all_after_pass 19 | all_on_start 20 | keep_failed 21 | feature_sets 22 | 23 | run_all 24 | focus_on 25 | notification 26 | ).map(&:to_sym) 27 | 28 | # Initialize Guard::Cucumber. 29 | # 30 | # @param [Array] watchers the watchers in the Guard block 31 | # @param [Hash] options the options for the Guard 32 | # @option options [Array] :feature_sets a list of non-standard 33 | # feature directory/ies 34 | # @option options [Boolean] :notification show notifications 35 | # @option options [Boolean] :all_after_pass run all features after changed 36 | # features pass 37 | # @option options [Boolean] :all_on_start run all the features at startup 38 | # @option options [Boolean] :keep_failed Keep failed features until they 39 | # pass 40 | # @option options [Boolean] :run_all run override any option when running 41 | # all specs 42 | # @option options [Boolean] :cmd the command to run 43 | # @option options [Boolean] :cmd_additional_args additional args to append 44 | # 45 | def initialize(options = {}) 46 | super(options) 47 | 48 | @options = { 49 | all_after_pass: true, 50 | all_on_start: true, 51 | keep_failed: true, 52 | cmd: "cucumber", 53 | cmd_additional_args: "--no-profile --color --format progress --strict", 54 | feature_sets: ["features"] 55 | }.update(options) 56 | 57 | unknown_options = @options.keys - KNOWN_OPTIONS 58 | unknown_options.each do |unknown| 59 | msg = "Unknown guard-cucumber option: #{unknown.inspect}" 60 | Guard::Compat::UI.warning(msg) 61 | end 62 | 63 | @last_failed = false 64 | @failed_paths = [] 65 | end 66 | 67 | # Gets called once when Guard starts. 68 | # 69 | # @raise [:task_has_failed] when stop has failed 70 | # 71 | def start 72 | run_all if @options[:all_on_start] 73 | end 74 | 75 | # Gets called when all specs should be run. 76 | # 77 | # @raise [:task_has_failed] when stop has failed 78 | # 79 | def run_all 80 | opts = options.merge(options[:run_all] || {}) 81 | opts[:message] = "Running all features" 82 | passed = Runner.run(options[:feature_sets], opts) 83 | 84 | if passed 85 | @failed_paths = [] 86 | elsif @options[:keep_failed] 87 | @failed_paths = read_failed_features 88 | end 89 | 90 | @last_failed = !passed 91 | 92 | throw :task_has_failed unless passed 93 | end 94 | 95 | # Gets called when the Guard should reload itself. 96 | # 97 | # @raise [:task_has_failed] when stop has failed 98 | # 99 | def reload 100 | @failed_paths = [] 101 | end 102 | 103 | # Gets called when watched paths and files have changes. 104 | # 105 | # @param [Array] paths the changed paths and files 106 | # @raise [:task_has_failed] when stop has failed 107 | # 108 | def run_on_modifications(paths) 109 | paths += @failed_paths if @options[:keep_failed] 110 | paths = Inspector.clean(paths, options[:feature_sets]) 111 | 112 | options = @options 113 | 114 | msg = "Running all features" 115 | options[:message] = msg if paths.include?("features") 116 | 117 | _run(paths, options) 118 | end 119 | 120 | private 121 | 122 | # Read the failed features that from `rerun.txt` 123 | # 124 | # @see Guard::Cucumber::NotificationFormatter#write_rerun_features 125 | # @return [Array] the list of features 126 | # 127 | def read_failed_features 128 | failed = [] 129 | 130 | if File.exist?("rerun.txt") 131 | failed = File.open("rerun.txt") { |file| file.read.split(" ") } 132 | File.delete("rerun.txt") 133 | end 134 | 135 | failed 136 | end 137 | 138 | def _run(paths, options) 139 | if Runner.run(paths, options) 140 | # clean failed paths memory 141 | @failed_paths -= paths if @options[:keep_failed] 142 | # run all the specs if the changed specs failed, like autotest 143 | run_all if @last_failed && @options[:all_after_pass] 144 | return 145 | end 146 | 147 | # remember failed paths for the next change 148 | @failed_paths += read_failed_features if @options[:keep_failed] 149 | # track whether the changed feature failed for the next change 150 | @last_failed = true 151 | throw :task_has_failed 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /lib/guard/cucumber/focuser.rb: -------------------------------------------------------------------------------- 1 | require "guard/compat/plugin" 2 | 3 | module Guard 4 | class Cucumber < Plugin 5 | # The Cucumber focuser updates cucumber feature paths to 6 | # focus on sections tagged with a provided focus_tag. 7 | # 8 | # For example, if the `foo.feature` file has the provided focus tag 9 | # `@bar` on line 8, then the path will be updated using the cucumber 10 | # syntax for focusing on a section: 11 | # 12 | # foo.feature:8 13 | # 14 | # If '@bar' is found on lines 8 and 16, the path is updated as follows: 15 | # 16 | # foo.feature:8:16 17 | # 18 | # The path is not updated if it does not contain the focus tag. 19 | # 20 | module Focuser 21 | class << self 22 | # Focus the supplied paths using the provided focus tag. 23 | # 24 | # @param [Array] paths the locations of the feature files 25 | # @param [String] focus_tag the focus tag to look for in each path 26 | # @return [Array] the updated paths 27 | # 28 | def focus(paths, focus_tag) 29 | return false if paths.empty? 30 | 31 | paths.inject([]) do |updated_paths, path| 32 | focused_line_numbers = scan_path_for_focus_tag(path, focus_tag) 33 | 34 | if focused_line_numbers.empty? 35 | updated_paths << path 36 | else 37 | updated_paths << append_line_numbers_to_path( 38 | focused_line_numbers, path 39 | ) 40 | end 41 | 42 | updated_paths 43 | end 44 | end 45 | 46 | # Checks to see if the file at path contains the focus tag 47 | # It will return an empty array if the path is a directory. 48 | # 49 | # @param [String] path the file path to search 50 | # @param [String] focus_tag the focus tag to look for in each path 51 | # @return [Array] the line numbers that include the focus tag 52 | # in path 53 | # 54 | def scan_path_for_focus_tag(path, focus_tag) 55 | return [] if File.directory?(path) || path.include?(":") 56 | 57 | line_numbers = [] 58 | 59 | File.open(path, "r") do |file| 60 | while (line = file.gets) 61 | if line.include?(focus_tag) 62 | line_numbers << file.lineno 63 | end 64 | end 65 | end 66 | 67 | line_numbers 68 | end 69 | 70 | # Appends the line numbers to the path 71 | # 72 | # @param [Array] line_numbers the line numbers to append to 73 | # the path 74 | # @param [String] path the path that will receive the appended line 75 | # numbers 76 | # @return [String] the string containing the path appended with the 77 | # line number 78 | # 79 | def append_line_numbers_to_path(line_numbers, path) 80 | line_numbers.each { |num| path += ":" + num.to_s } 81 | 82 | path 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/guard/cucumber/inspector.rb: -------------------------------------------------------------------------------- 1 | require "guard/compat/plugin" 2 | 3 | module Guard 4 | class Cucumber < Plugin 5 | # The inspector verifies of the changed paths are valid 6 | # for Guard::Cucumber. 7 | # 8 | module Inspector 9 | class << self 10 | # Clean the changed paths and return only valid 11 | # Cucumber features. 12 | # 13 | # @param [Array] paths the changed paths 14 | # @param [Array] feature_sets the feature sets 15 | # @return [Array] the valid feature files 16 | # 17 | def clean(paths, sets) 18 | paths.uniq! 19 | paths.compact! 20 | paths = paths.select do |p| 21 | cucumber_file?(p, sets) || cucumber_folder?(p, sets) 22 | end 23 | paths = paths.delete_if { |p| included_in_other_path?(p, paths) } 24 | clear_cucumber_files_list 25 | paths 26 | end 27 | 28 | private 29 | 30 | # Tests if the file is the features folder. 31 | # 32 | # @param [String] path the file 33 | # @param [Array] feature_sets the feature sets 34 | # @return [Boolean] when the file is the feature folder 35 | # 36 | def cucumber_folder?(path, feature_sets) 37 | sets = feature_sets.join("|") 38 | path.match(/^\/?(#{ sets })/) && !path.match(/\..+$/) 39 | end 40 | 41 | # Tests if the file is valid. 42 | # 43 | # @param [String] path the file 44 | # @param [Array] feature_sets the feature sets 45 | # @return [Boolean] when the file valid 46 | # 47 | def cucumber_file?(path, feature_sets) 48 | cucumber_files(feature_sets).include?(path.split(":").first) 49 | end 50 | 51 | # Scans the project and keeps a list of all 52 | # feature files in the `features` directory. 53 | # 54 | # @see #clear_jasmine_specs 55 | # @param [Array] feature_sets the feature sets 56 | # @return [Array] the valid files 57 | # 58 | def cucumber_files(feature_sets) 59 | glob = "#{feature_sets.join(',')}/**/*.feature" 60 | @cucumber_files ||= Dir.glob(glob) 61 | end 62 | 63 | # Clears the list of features in this project. 64 | # 65 | def clear_cucumber_files_list 66 | @cucumber_files = nil 67 | end 68 | 69 | # Checks if the given path is already contained 70 | # in the paths list. 71 | # 72 | # @param [Sting] path the path to test 73 | # @param [Array] paths the list of paths 74 | # 75 | def included_in_other_path?(path, paths) 76 | paths = paths.select { |p| p != path } 77 | massaged = path[0...(path.index(":") || path.size)] 78 | paths.any? { |p| _path_includes(path, p, massaged) } 79 | end 80 | 81 | private 82 | 83 | def _path_includes(path, p, massaged) 84 | includes = path.include?(p) 85 | return true if includes && path.gsub(p, "").include?("/") 86 | massaged.include?(p) 87 | end 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/guard/cucumber/notification_formatter.rb: -------------------------------------------------------------------------------- 1 | # Conditionally require this because it's run outside Guard 2 | # 3 | if Object.const_defined?(:Guard) 4 | # TODO: MOVE THIS OUTSIDE THE FORMATTER!!!! 5 | # TODO: (call notify() in Guard::Cucumber, not here in formatter 6 | 7 | # If notifier is defined it's likely Guard::Compat::Plugin's stub 8 | unless Guard.const_defined?(:Notifier) 9 | require "guard" 10 | require "guard/notifier" 11 | end 12 | end 13 | 14 | require "cucumber" 15 | require "guard/compat/plugin" 16 | 17 | require "cucumber/formatter/console" 18 | require "cucumber/formatter/io" 19 | 20 | module Guard 21 | class Cucumber < Plugin 22 | # The notification formatter is a Cucumber formatter that Guard::Cucumber 23 | # passes to the Cucumber binary. It writes the `rerun.txt` file with the 24 | # failed features 25 | # an creates system notifications. 26 | # 27 | # @see https://github.com/cucumber/cucumber/wiki/Custom-Formatters 28 | # 29 | class NotificationFormatter 30 | include ::Cucumber::Formatter::Console 31 | 32 | attr_reader :step_mother 33 | 34 | # Initialize the formatter. 35 | # 36 | # @param [Cucumber::Runtime] step_mother the step mother 37 | # @param [String, IO] path_or_io the path or IO to the feature file 38 | # @param [Hash] options the options 39 | # 40 | def initialize(step_mother, _path_or_io, options) 41 | @options = options 42 | @file_names = [] 43 | @step_mother = step_mother 44 | @feature = nil 45 | end 46 | 47 | def before_background(_background) 48 | # NOTE: background.feature is nil on newer gherkin versions 49 | end 50 | 51 | def before_feature(feature) 52 | @feature = feature 53 | end 54 | 55 | # Notification after all features have completed. 56 | # 57 | # @param [Array[Cucumber::Ast::Feature]] features the ran features 58 | # 59 | def after_features(_features) 60 | notify_summary 61 | write_rerun_features if !@file_names.empty? 62 | end 63 | 64 | # Before a feature gets run. 65 | # 66 | # @param [Cucumber::Ast::FeatureElement] feature_element 67 | # 68 | def before_feature_element(_feature_element) 69 | @rerun = false 70 | # TODO: show feature element name instead? 71 | # @feature = feature_element.name 72 | end 73 | 74 | # After a feature gets run. 75 | # 76 | # @param [Cucumber::Ast::FeatureElement] feature_element 77 | # 78 | def after_feature_element(feature_element) 79 | if @rerun 80 | @file_names << feature_element.location.to_s 81 | @rerun = false 82 | end 83 | end 84 | 85 | # Gets called when a step is done. 86 | # 87 | # @param [String] keyword the keyword 88 | # @param [Cucumber::StepMatch] step_match the step match 89 | # @param [Symbol] status the status of the step 90 | # @param [Integer] source_indent the source indentation 91 | # @param [Cucumber::Ast::Background] background the feature background 92 | # @param [String] file name and line number describing where the step is 93 | # used 94 | # 95 | def step_name(_keyword, step_match, status, _src_indent, _bckgnd, _loc) 96 | return unless [:failed, :pending, :undefined].index(status) 97 | 98 | @rerun = true 99 | step_name = step_match.format_args(lambda { |param| "*#{param}*" }) 100 | 101 | options = { title: @feature.name, image: icon_for(status) } 102 | Guard::Compat::UI.notify(step_name, options) 103 | end 104 | 105 | private 106 | 107 | # Notify the user with a system notification about the 108 | # result of the feature tests. 109 | # 110 | def notify_summary 111 | # TODO: MOVE THIS OUTSIDE THE FORMATTER!!!! 112 | statuses = [:failed, :skipped, :undefined, :pending, :passed] 113 | statuses = statuses.reverse 114 | statuses.select! { |status| step_mother.steps(status).any? } 115 | 116 | messages = statuses.map { |status| status_to_message(status) } 117 | 118 | icon = statuses.reverse.detect { |status| icon_for(status) } 119 | 120 | msg = messages.reverse.join(", ") 121 | Guard::Compat::UI.notify msg, title: "Cucumber Results", image: icon 122 | end 123 | 124 | # Writes the `rerun.txt` file containing all failed features. 125 | # 126 | def write_rerun_features 127 | File.open("rerun.txt", "w") do |f| 128 | f.puts @file_names.join(" ") 129 | end 130 | end 131 | 132 | # Gives the icon name to use for the status. 133 | # 134 | # @param [Symbol] status the cucumber status 135 | # @return [Symbol] the Guard notification symbol 136 | # 137 | def icon_for(status) 138 | case status 139 | when :passed 140 | :success 141 | when :pending, :undefined, :skipped 142 | :pending 143 | when :failed 144 | :failed 145 | end 146 | end 147 | 148 | def dump_count(count, what, state = nil) 149 | [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(' ') 150 | end 151 | 152 | def status_to_message(status) 153 | len = step_mother.steps(status).length 154 | dump_count(len, "step", status.to_s) 155 | end 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/guard/cucumber/runner.rb: -------------------------------------------------------------------------------- 1 | require "guard/compat/plugin" 2 | 3 | require "guard/cucumber/focuser" 4 | 5 | module Guard 6 | class Cucumber < Plugin 7 | # The Cucumber runner handles the execution of the cucumber binary. 8 | # 9 | module Runner 10 | class << self 11 | # Run the supplied features. 12 | # 13 | # @param [Array] paths the feature files or directories 14 | # @param [Hash] options the options for the execution 15 | # @option options [Array] :feature_sets a list of non-standard 16 | # feature directory/ies 17 | # @option options [Boolean] :notification show notifications 18 | # prefix to the cucumber command. Ideal for running xvfb-run for 19 | # terminal only cucumber tests. 20 | # @return [Boolean] the status of the execution 21 | # 22 | def run(paths, options = {}) 23 | return false if paths.empty? 24 | 25 | cmd = cucumber_command(paths, options) 26 | 27 | msg1 = "Running all Cucumber features: #{cmd}" 28 | msg2 = "Running Cucumber features: #{cmd}" 29 | msg = (paths == ["features"] ? msg1 : msg2) 30 | 31 | message = options[:message] || msg 32 | 33 | paths = Focuser.focus(paths, options[:focus_on]) if options[:focus_on] 34 | cmd = cucumber_command(paths, options) 35 | 36 | Compat::UI.info message, reset: true 37 | 38 | system(cmd) 39 | end 40 | 41 | private 42 | 43 | # Assembles the Cucumber command from the passed options. 44 | # 45 | # @param [Array] paths the feature files or directories 46 | # @param [Hash] options the options for the execution 47 | # @option options [Boolean] :notification show notifications 48 | # prefix to the cucumber command. Ideal for running xvfb-run for 49 | # terminal only cucumber tests. 50 | # @return [String] the Cucumber command 51 | # 52 | def cucumber_command(paths, options) 53 | cmd = [] 54 | _add_cli_options(cmd, options[:cmd] || "cucumber") 55 | _add_notification(cmd, options) 56 | _add_cli_options(cmd, options[:cmd_additional_args]) 57 | (cmd + paths).join(" ") 58 | end 59 | 60 | # Returns a null device for all OS. 61 | # 62 | # @return [String] the name of the null device 63 | # 64 | def null_device 65 | RUBY_PLATFORM.index("mswin") ? "NUL" : "/dev/null" 66 | end 67 | 68 | private 69 | 70 | def _add_notification(cmd, options) 71 | return unless options[:notification] != false 72 | 73 | this_dir = File.dirname(__FILE__) 74 | formatter_path = File.join(this_dir, "notification_formatter.rb") 75 | notification_formatter_path = File.expand_path(formatter_path) 76 | 77 | cmd << "--require #{notification_formatter_path}" 78 | cmd << "--format Guard::Cucumber::NotificationFormatter" 79 | cmd << "--out #{null_device}" 80 | cmd << (options[:feature_sets] || ["features"]).map do |path| 81 | "--require #{path}" 82 | end.join(" ") 83 | end 84 | 85 | def _add_cli_options(cmd, cli) 86 | cmd << cli if cli 87 | end 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/guard/cucumber/templates/Guardfile: -------------------------------------------------------------------------------- 1 | cucumber_options = { 2 | # Below are examples overriding defaults 3 | 4 | # cmd: 'bin/cucumber', 5 | # cmd_additional_args: '--profile guard', 6 | 7 | # all_after_pass: false, 8 | # all_on_start: false, 9 | # keep_failed: false, 10 | # feature_sets: ['features/frontend', 'features/experimental'], 11 | 12 | # run_all: { cmd_additional_args: '--profile guard_all' }, 13 | # focus_on: { 'wip' }, # @wip 14 | # notification: false 15 | } 16 | 17 | guard "cucumber", cucumber_options do 18 | watch(%r{^features/.+\.feature$}) 19 | watch(%r{^features/support/.+$}) { "features" } 20 | 21 | watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |m| 22 | Dir[File.join("**/#{m[1]}.feature")][0] || "features" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/guard/cucumber/version.rb: -------------------------------------------------------------------------------- 1 | module Guard 2 | module CucumberVersion 3 | # Guard::Cucumber version that is used for the Gem specification 4 | VERSION = "3.0.0".freeze 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/guard/cucumber/focuser_spec.rb: -------------------------------------------------------------------------------- 1 | require "guard/compat/test/helper" 2 | 3 | require "guard/cucumber/focuser" 4 | 5 | RSpec.describe Guard::Cucumber::Focuser do 6 | let(:focuser) { Guard::Cucumber::Focuser } 7 | let(:focus_tag) { "@focus" } 8 | let(:null_device) { RUBY_PLATFORM.index("mswin") ? "NUL" : "/dev/null" } 9 | 10 | let(:dir) { "features" } 11 | let(:path) { "foo.feature" } 12 | let(:path_two) { "bar.feature" } 13 | 14 | describe ".focus" do 15 | context "when passed an empty paths list" do 16 | it "returns false" do 17 | expect(focuser.focus([], "@focus")).to be_falsey 18 | end 19 | end 20 | 21 | context "when passed a paths argument" do 22 | let(:file) do 23 | StringIO.new <<-EOS 24 | @focus 25 | Scenario: Foo 26 | Given bar 27 | Scenario: Bar 28 | Given focus lorem 29 | @focus 30 | Scenario: Ipsum 31 | Given dolor 32 | EOS 33 | end 34 | 35 | let(:file_two) do 36 | StringIO.new <<-EOS 37 | @focus 38 | Scenario: Lorem 39 | Given ipsum 40 | @focus 41 | Scenario: Bar 42 | Given lorem 43 | Scenario: Dolor 44 | Given sit focus 45 | EOS 46 | end 47 | 48 | before do 49 | expect(File).to receive(:open).with(path, "r").and_yield(file) 50 | expect(File).to receive(:open).with(path_two, "r").and_yield(file_two) 51 | end 52 | 53 | it "returns an array of paths updated to focus on line numbers" do 54 | paths = [path, path_two] 55 | 56 | expect(focuser.focus(paths, focus_tag)).to eql([ 57 | "foo.feature:1:6", 58 | "bar.feature:1:4" 59 | ]) 60 | end 61 | end 62 | end 63 | 64 | describe ".scan_path_for_focus_tag" do 65 | context "file with focus tags in it" do 66 | let(:file) do 67 | StringIO.new <<-EOS 68 | @focus 69 | Scenario: Foo 70 | Given bar 71 | Scenario: Bar 72 | Given lorem 73 | @focus 74 | Scenario: Ipsum 75 | Given dolor 76 | EOS 77 | end 78 | 79 | before do 80 | expect(File).to receive(:open).with(path, "r").and_yield(file) 81 | end 82 | 83 | it "returns an array of line numbers" do 84 | expect(focuser.scan_path_for_focus_tag(path, focus_tag)).to eql([1, 6]) 85 | end 86 | end 87 | 88 | context "file without focus tags in it" do 89 | let(:file) do 90 | StringIO.new <<-EOS 91 | Scenario: Foo 92 | Given bar 93 | Scenario: Bar 94 | Given lorem 95 | Scenario: Ipsum 96 | Given dolor 97 | EOS 98 | end 99 | 100 | before do 101 | expect(File).to receive(:open).with(path, "r").and_return(file) 102 | end 103 | 104 | it "returns an empty array" do 105 | expect(focuser.scan_path_for_focus_tag(path, focus_tag)).to eql([]) 106 | end 107 | end 108 | 109 | context "file that is a directory" do 110 | before do 111 | expect(File).to receive(:directory?).with(dir).and_return(true) 112 | end 113 | 114 | it "returns an empty array" do 115 | expect(focuser.scan_path_for_focus_tag(dir, focus_tag)).to eql([]) 116 | end 117 | end 118 | 119 | context "file that has already a line number" do 120 | let(:path) { "bar.feature:12" } 121 | 122 | it "returns an empty array" do 123 | expect(File).not_to receive(:open).with(path, "r") 124 | expect(focuser.scan_path_for_focus_tag(path, focus_tag)).to eql([]) 125 | end 126 | end 127 | end 128 | 129 | describe ".append_line_numbers_to_path" do 130 | it "returns a path with line numbers appended" do 131 | line_numbers = [1, 2] 132 | returned_path = focuser.append_line_numbers_to_path(line_numbers, path) 133 | expect(returned_path).to eql(path + ":1:2") 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /spec/guard/cucumber/inspector_spec.rb: -------------------------------------------------------------------------------- 1 | require "guard/cucumber/inspector" 2 | 3 | RSpec.describe Guard::Cucumber::Inspector do 4 | let(:inspector) { Guard::Cucumber::Inspector } 5 | 6 | describe ".clean" do 7 | context "with the standard feature set" do 8 | before do 9 | allow(Dir).to receive(:glob). 10 | and_return(%w(features/a.feature features/subfolder/b.feature)) 11 | end 12 | 13 | it "removes non-feature files" do 14 | expect(inspector.clean(%w(features/a.feature b.rb), %w(features))). 15 | to eq(%w(features/a.feature)) 16 | end 17 | 18 | it "removes non-existing feature files" do 19 | expect(inspector.clean( 20 | %w(features/a.feature features/x.feature), 21 | %w(features))). 22 | to eq(%w(features/a.feature)) 23 | end 24 | 25 | it "keeps a feature folder" do 26 | expect(inspector.clean( 27 | %w(features/a.feature features/subfolder), 28 | %w(features) 29 | )).to eq(%w(features/a.feature features/subfolder)) 30 | end 31 | 32 | it "removes duplicate paths" do 33 | expect(inspector.clean( 34 | %w(features features), 35 | %w(features))).to eq(%w(features)) 36 | end 37 | 38 | it "removes individual feature tests if the path is already "\ 39 | "in paths to run" do 40 | expect(inspector.clean( 41 | %w( features/a.feature features/a.feature:10), 42 | %w(features))).to eq(%w(features/a.feature)) 43 | end 44 | 45 | it "removes feature folders included in other feature folders" do 46 | expect(inspector.clean( 47 | %w(features/subfolder features), 48 | %w(features))).to eq(%w(features)) 49 | end 50 | 51 | it "removes feature files includes in feature folder" do 52 | expect(inspector.clean( 53 | %w(features/subfolder/b.feature features), 54 | %w(features))).to eq(%w(features)) 55 | end 56 | end 57 | 58 | context "with an additional feature set" do 59 | before do 60 | allow(Dir).to receive(:glob). 61 | and_return(%w( 62 | feature_set_1/a.feature 63 | feature_set_1/subfolder/b.feature 64 | feature_set_2/c.feature 65 | feature_set_2/subfolder/d.feature)) 66 | end 67 | 68 | it "removes non-feature files" do 69 | expect(inspector.clean( 70 | %w(feature_set_1/a.feature feature_set_2/c.feature b.rb), 71 | %w(feature_set_1, feature_set_2))). 72 | to eq(%w(feature_set_1/a.feature feature_set_2/c.feature)) 73 | end 74 | 75 | it "removes non-existing feature files" do 76 | expect(inspector.clean( 77 | %w( 78 | feature_set_1/a.feature 79 | feature_set_1/x.feature 80 | feature_set_2/c.feature 81 | feature_set_2/y.feature), 82 | %w(feature_set_1 feature_set_2))). 83 | to eq(%w(feature_set_1/a.feature feature_set_2/c.feature)) 84 | end 85 | 86 | it "keeps the feature folders" do 87 | expect(inspector.clean( 88 | %w( 89 | feature_set_1/a.feature 90 | feature_set_1/subfolder 91 | feature_set_2/c.feature 92 | feature_set_2/subfolder), 93 | %w(feature_set_1 feature_set_2))). 94 | to eq(%w( 95 | feature_set_1/a.feature 96 | feature_set_1/subfolder 97 | feature_set_2/c.feature 98 | feature_set_2/subfolder)) 99 | end 100 | 101 | it "removes duplicate paths" do 102 | expect(inspector.clean( 103 | %w( 104 | feature_set_1 105 | feature_set_1 106 | feature_set_2 107 | feature_set_2), 108 | %w(feature_set_1 feature_set_2))). 109 | to eq(%w(feature_set_1 feature_set_2)) 110 | end 111 | 112 | it "removes individual feature tests if the path is already "\ 113 | "in paths to run" do 114 | expect(inspector.clean( 115 | %w( 116 | feature_set_1/a.feature 117 | feature_set_1/a.feature:10 118 | feature_set_2/c.feature 119 | feature_set_2/c.feature:25), 120 | %w(features feature_set_2))). 121 | to eq(%w(feature_set_1/a.feature feature_set_2/c.feature)) 122 | end 123 | 124 | it "removes feature folders included in other feature folders" do 125 | expect(inspector.clean( 126 | %w( 127 | feature_set_1/subfolder 128 | feature_set_1 129 | feature_set_2/subfolder 130 | feature_set_2), 131 | %w(feature_set_1 feature_set_2))). 132 | to eq(%w(feature_set_1 feature_set_2)) 133 | 134 | expect(inspector.clean( 135 | %w(feature_set_1 feature_set_2/subfolder), 136 | %w(feature_set_1 feature_set_2))). 137 | to eq(%w(feature_set_1 feature_set_2/subfolder)) 138 | 139 | expect(inspector.clean( 140 | %w(feature_set_2 feature_set_1/subfolder), 141 | %w(feature_set_1 feature_set_2))). 142 | to eq(%w(feature_set_2 feature_set_1/subfolder)) 143 | end 144 | 145 | it "removes feature files includes in feature folder" do 146 | a = %w( 147 | feature_set_1/subfolder/b.feature 148 | feature_set_1 149 | feature_set_2/subfolder/c.feature feature_set_2) 150 | 151 | b = %w( feature_set_1 feature_set_2) 152 | c = %w(feature_set_1 feature_set_2) 153 | 154 | expect(inspector.clean(a, b)).to eq(c) 155 | 156 | expect(inspector.clean( 157 | %w(feature_set_1/subfolder/b.feature feature_set_2), 158 | 159 | %w(feature_set_1 feature_set_2))). 160 | to eq(%w(feature_set_1/subfolder/b.feature feature_set_2)) 161 | 162 | expect(inspector.clean( 163 | %w(feature_set_2/subfolder/d.feature feature_set_1), 164 | %w(feature_set_1 feature_set_2))). 165 | to eq(%w( feature_set_2/subfolder/d.feature feature_set_1)) 166 | end 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /spec/guard/cucumber/notification_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require "guard/cucumber/notification_formatter" 2 | 3 | RSpec.describe Guard::Cucumber::NotificationFormatter do 4 | subject { described_class.new(mother, nil, {}) } 5 | let(:mother) { instance_double(Cucumber::Runtime) } 6 | 7 | context "after all features" do 8 | let(:step) { double("step") } 9 | 10 | before do 11 | allow(mother).to receive(:steps).with(:passed).and_return([step]) 12 | allow(mother).to receive(:steps).with(:pending).and_return([step]) 13 | allow(mother).to receive(:steps).with(:undefined).and_return([step]) 14 | allow(mother).to receive(:steps).with(:skipped).and_return([step]) 15 | allow(mother).to receive(:steps).with(:failed).and_return([step]) 16 | end 17 | 18 | it "formats the notification" do 19 | allow(Guard::Compat::UI).to receive(:notify). 20 | with("1 failed step, 1 skipped step, 1 undefined step, 1 pending " + 21 | "step, 1 passed step", title: "Cucumber Results", image: :failed) 22 | 23 | subject.after_features(nil) 24 | end 25 | end 26 | 27 | describe "#step_name" do 28 | let(:step_match) { instance_double(Cucumber::StepMatch) } 29 | let(:feature) do 30 | instance_double(Cucumber::Core::Ast::Feature, name: "feature1") 31 | end 32 | 33 | before do 34 | subject.before_feature(feature) 35 | subject.before_background(background) 36 | allow(step_match).to receive(:format_args) do |block| 37 | block.call "step_name1" 38 | end 39 | end 40 | 41 | context "when failure is in a background step" do 42 | let(:background) do 43 | instance_double(Cucumber::Formatter::LegacyApi::Ast::Background, 44 | feature: feature) 45 | end 46 | 47 | it "notifies with a valid feature name" do 48 | expect(Guard::Compat::UI).to receive(:notify). 49 | with("*step_name1*", hash_including(title: "feature1")) 50 | 51 | subject.step_name(nil, step_match, :failed, nil, nil, nil) 52 | end 53 | end 54 | 55 | # workaround for: https://github.com/cucumber/gherkin/issues/334 56 | context "with a buggy Background implementation" do 57 | let(:background) do 58 | instance_double(Cucumber::Formatter::LegacyApi::Ast::Background, 59 | feature: nil) 60 | end 61 | 62 | it "correctly gets the feature name" do 63 | expect(Guard::Compat::UI).to receive(:notify). 64 | with("*step_name1*", hash_including(title: "feature1")) 65 | 66 | subject.step_name(nil, step_match, :failed, nil, nil, nil) 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/guard/cucumber/runner_spec.rb: -------------------------------------------------------------------------------- 1 | require "guard/cucumber/runner" 2 | 3 | RSpec.describe Guard::Cucumber::Runner do 4 | let(:runner) { Guard::Cucumber::Runner } 5 | let(:null_device) { RUBY_PLATFORM.index("mswin") ? "NUL" : "/dev/null" } 6 | 7 | before do 8 | allow(Guard::Compat::UI).to receive(:info) 9 | allow(runner).to receive(:system) 10 | end 11 | 12 | describe "#run" do 13 | context "when passed an empty paths list" do 14 | it "returns false" do 15 | expect(runner.run([])).to be_falsey 16 | end 17 | end 18 | 19 | context "with a paths argument" do 20 | it "runs the given paths" do 21 | expect(runner).to receive(:system).with( 22 | /features\/foo\.feature features\/bar\.feature$/ 23 | ) 24 | runner.run(["features/foo.feature", "features/bar.feature"]) 25 | end 26 | end 27 | 28 | context "with a :feature_sets option" do 29 | it "requires each feature set" do 30 | feature_sets = ["feature_set_a", "feature_set_b"] 31 | 32 | expect(runner).to receive(:system).with( 33 | /--require feature_set_a --require feature_set_b/ 34 | ) 35 | runner.run(feature_sets, feature_sets: feature_sets) 36 | end 37 | end 38 | 39 | it "runs cucumber according to passed cmd option" do 40 | req = @lib_path.join("guard/cucumber/notification_formatter.rb") 41 | expect(runner).to receive(:system).with( 42 | "xvfb-run bundle exec cucumber "\ 43 | "--require #{req} "\ 44 | "--format Guard::Cucumber::NotificationFormatter "\ 45 | "--out #{null_device} "\ 46 | "--require features features" 47 | ) 48 | runner.run(["features"], cmd: "xvfb-run bundle exec cucumber") 49 | end 50 | 51 | context "with a :focus_on option" do 52 | it "passes the value in :focus_on to the Focuser" do 53 | paths = ["features"] 54 | focus_on_hash = { 55 | focus_on: "@focus" 56 | } 57 | 58 | expect(Guard::Cucumber::Focuser).to receive(:focus).with( 59 | paths, focus_on_hash[:focus_on] 60 | ).and_return(paths) 61 | 62 | runner.run(paths, focus_on_hash) 63 | end 64 | end 65 | 66 | context "with a :cmd_additional_args option" do 67 | it "appends the cli arguments when calling cucumber" do 68 | req = @lib_path.join("guard/cucumber/notification_formatter.rb") 69 | expect(runner).to receive(:system).with( 70 | "cucumber --require #{req} "\ 71 | "--format Guard::Cucumber::NotificationFormatter "\ 72 | "--out #{null_device} --require features "\ 73 | "--custom command "\ 74 | "features") 75 | runner.run(["features"], cmd_additional_args: "--custom command") 76 | end 77 | end 78 | 79 | context "with a :notification option" do 80 | it "does not add the guard notification listener" do 81 | expect(runner).to receive(:system).with( 82 | "cucumber features" 83 | ) 84 | runner.run(["features"], notification: false) 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/guard/cucumber/version_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Guard::CucumberVersion do 2 | describe "VERSION" do 3 | it "defines the version" do 4 | expect(Guard::CucumberVersion::VERSION).to match /\d+.\d+.\d+/ 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/guard/cucumber_spec.rb: -------------------------------------------------------------------------------- 1 | require "guard/compat/test/helper" 2 | require "guard/cucumber" 3 | 4 | RSpec.describe Guard::Cucumber do 5 | subject { Guard::Cucumber.new(options) } 6 | 7 | let(:options) { {} } 8 | let(:runner) { Guard::Cucumber::Runner } 9 | let(:msg_opts) { { message: "Running all features" } } 10 | 11 | before do 12 | allow(Dir).to receive(:glob). 13 | and_return ["features/a.feature", "features/subfolder/b.feature"] 14 | end 15 | 16 | let(:default_options) do 17 | { 18 | all_after_pass: true, 19 | all_on_start: true, 20 | keep_failed: true, 21 | cmd: "cucumber", 22 | cmd_additional_args: "--no-profile --color --format progress --strict", 23 | feature_sets: ["features"] 24 | } 25 | end 26 | 27 | describe "#initialize" do 28 | context "when no options are provided" do 29 | let(:options) { {} } 30 | 31 | it "sets a default :all_after_pass option" do 32 | expect(subject.options[:all_after_pass]).to be_truthy 33 | end 34 | 35 | it "sets a default :all_on_start option" do 36 | expect(subject.options[:all_on_start]).to be_truthy 37 | end 38 | 39 | it "sets a default :keep_failed option" do 40 | expect(subject.options[:keep_failed]).to be_truthy 41 | end 42 | 43 | it "sets a default :cmd_additional_args option" do 44 | expect(subject.options[:cmd_additional_args]). 45 | to eql "--no-profile --color --format progress --strict" 46 | end 47 | 48 | it "sets a default :feature_sets option" do 49 | expect(subject.options[:feature_sets]).to eql ["features"] 50 | end 51 | end 52 | 53 | context "with other options than the default ones" do 54 | let(:options) do 55 | { all_after_pass: false, 56 | all_on_start: false, 57 | keep_failed: false, 58 | cmd_additional_args: "--color", 59 | feature_sets: ["feature_set_a", "feature_set_b"], 60 | focus_on: "@focus" } 61 | end 62 | 63 | it "sets the provided :all_after_pass option" do 64 | expect(subject.options[:all_after_pass]).to be_falsey 65 | end 66 | 67 | it "sets the provided :all_on_start option" do 68 | expect(subject.options[:all_on_start]).to be_falsey 69 | end 70 | 71 | it "sets the provided :keep_failed option" do 72 | expect(subject.options[:keep_failed]).to be_falsey 73 | end 74 | 75 | it "sets the provided :cmd_additional_args option" do 76 | expect(subject.options[:cmd_additional_args]).to eql "--color" 77 | end 78 | 79 | it "sets the provided :feature_sets option" do 80 | expect(subject.options[:feature_sets]). 81 | to eql ["feature_set_a", "feature_set_b"] 82 | end 83 | 84 | it "sets the provided :focus_on option" do 85 | expect(subject.options[:focus_on]).to eql "@focus" 86 | end 87 | end 88 | 89 | context "when unknown options are provided" do 90 | before do 91 | allow(Guard::Compat::UI).to receive(:warning) 92 | end 93 | 94 | let(:options) { { foobar: false } } 95 | it "warns about unknown options" do 96 | expect(Guard::Compat::UI).to receive(:warning). 97 | with("Unknown guard-cucumber option: :foobar") 98 | subject 99 | end 100 | end 101 | end 102 | 103 | describe "#start" do 104 | it "calls #run_all" do 105 | expect(runner).to receive(:run). 106 | with(["features"], default_options.merge(msg_opts)).and_return(true) 107 | subject.start 108 | end 109 | 110 | context "with the :all_on_start option is false" do 111 | let(:options) { { all_on_start: false } } 112 | 113 | it "does not call #run_all" do 114 | expect(runner).not_to receive(:run). 115 | with(["features"], default_options.merge(all_on_start: false, 116 | message: "Running all 117 | features")) 118 | subject.start 119 | end 120 | end 121 | end 122 | 123 | describe "#run_all" do 124 | it "runs all features" do 125 | expect(runner).to receive(:run). 126 | with(["features"], default_options.merge(msg_opts)).and_return(true) 127 | subject.run_all 128 | end 129 | 130 | it "cleans failed memory if passed" do 131 | expect(runner).to receive(:run). 132 | with(["features/foo"], default_options).and_return(false) 133 | 134 | expect do 135 | subject.run_on_modifications(["features/foo"]) 136 | end.to throw_symbol :task_has_failed 137 | 138 | expect(runner).to receive(:run). 139 | with(["features"], default_options.merge(msg_opts)).and_return(true) 140 | 141 | expect(runner).to receive(:run). 142 | with(["features/bar"], default_options).and_return(true) 143 | 144 | subject.run_on_modifications(["features/bar"]) 145 | end 146 | 147 | it "saves failed features" do 148 | expect(runner).to receive(:run). 149 | with(["features"], default_options.merge(msg_opts)).and_return(false) 150 | expect(File).to receive(:exist?). 151 | with("rerun.txt").and_return true 152 | file = double("file") 153 | expect(file).to receive(:read).and_return "features/foo" 154 | allow(File).to receive(:open).and_yield file 155 | expect(File).to receive(:delete).with("rerun.txt") 156 | expect { subject.run_all }.to throw_symbol :task_has_failed 157 | 158 | expect(runner).to receive(:run). 159 | with(["features/bar", "features/foo"], default_options).and_return(true) 160 | expect(runner).to receive(:run). 161 | with(["features"], default_options.merge(msg_opts)).and_return(true) 162 | subject.run_on_modifications(["features/bar"]) 163 | end 164 | 165 | context "with the :feature_sets option" do 166 | non_standard_feature_set = ["a_non_standard_feature_set"] 167 | let(:options) { { feature_sets: non_standard_feature_set } } 168 | 169 | it "passes the feature sets as paths to runner" do 170 | expect(runner).to receive(:run). 171 | with(non_standard_feature_set, anything).and_return(true) 172 | 173 | subject.run_all 174 | end 175 | end 176 | 177 | context "with the :cmd_additional_args option" do 178 | let(:options) { { cmd_additional_args: "--color" } } 179 | 180 | it "directly passes :cmd_additional_args option to runner" do 181 | msg = "Running all features" 182 | opts = default_options.merge(cmd_additional_args: "--color", 183 | message: msg) 184 | 185 | expect(runner).to receive(:run).with(["features"], opts). 186 | and_return(true) 187 | subject.run_all 188 | end 189 | end 190 | 191 | context "when the :keep_failed option is false" do 192 | let(:options) { { keep_failed: false } } 193 | let(:run_options) { default_options.merge keep_failed: false } 194 | 195 | it "does not save failed features if keep_failed is disabled" do 196 | expect(runner).to receive(:run). 197 | with(["features"], run_options.merge(msg_opts)).and_return(false) 198 | 199 | expect(File).not_to receive(:exist?).with("rerun.txt") 200 | 201 | expect { subject.run_all }.to throw_symbol :task_has_failed 202 | 203 | expect(runner).to receive(:run). 204 | with(["features/bar"], run_options).and_return(true) 205 | 206 | expect(runner).to receive(:run). 207 | with(["features"], run_options.merge(msg_opts)).and_return(true) 208 | 209 | subject.run_on_modifications(["features/bar"]) 210 | end 211 | end 212 | 213 | context "with a :run_all option" do 214 | let(:options) do 215 | { 216 | cmd_additional_args: "--color", 217 | run_all: { cmd_additional_args: "--format progress" } 218 | } 219 | end 220 | 221 | it "allows the :run_all options to override the default_options" do 222 | expect(runner).to receive(:run). 223 | with( 224 | anything, 225 | hash_including(cmd_additional_args: "--format progress") 226 | ).and_return(true) 227 | 228 | subject.run_all 229 | end 230 | end 231 | end 232 | 233 | describe "#reload" do 234 | it "clears failed_path" do 235 | expect(runner).to receive(:run). 236 | with(["features/foo"], default_options).and_return(false) 237 | 238 | expect do 239 | subject.run_on_modifications(["features/foo"]) 240 | end.to throw_symbol :task_has_failed 241 | 242 | subject.reload 243 | 244 | expect(runner).to receive(:run). 245 | with(["features/bar"], default_options).and_return(true) 246 | 247 | opts = default_options.merge(msg_opts) 248 | 249 | expect(runner).to receive(:run). 250 | with(["features"], opts).and_return(true) 251 | subject.run_on_modifications(["features/bar"]) 252 | end 253 | end 254 | 255 | describe "#run_on_modifications" do 256 | it "runs cucumber with all features" do 257 | expect(runner).to receive(:run). 258 | with(["features"], default_options.merge(msg_opts)).and_return(true) 259 | subject.run_on_modifications(["features"]) 260 | end 261 | 262 | it "runs cucumber with single feature" do 263 | expect(runner).to receive(:run). 264 | with(["features/a.feature"], default_options).and_return(true) 265 | subject.run_on_modifications(["features/a.feature"]) 266 | end 267 | 268 | it "passes the matched paths to the inspector for cleanup" do 269 | allow(runner).to receive(:run).and_return(true) 270 | expect(Guard::Cucumber::Inspector).to receive(:clean). 271 | with(["features"], ["features"]).and_return ["features"] 272 | subject.run_on_modifications(["features"]) 273 | end 274 | 275 | it "calls #run_all if the changed specs pass after failing" do 276 | expect(runner).to receive(:run). 277 | with(["features/foo"], default_options).and_return(false, true) 278 | 279 | opts = default_options.merge(msg_opts) 280 | expect(runner).to receive(:run). 281 | with(["features"], opts).and_return(true) 282 | 283 | expect do 284 | subject.run_on_modifications(["features/foo"]) 285 | end.to throw_symbol :task_has_failed 286 | subject.run_on_modifications(["features/foo"]) 287 | end 288 | 289 | it "does not call #run_all if the changed specs pass without failing" do 290 | expect(runner).to receive(:run). 291 | with(["features/foo"], default_options).and_return(true) 292 | 293 | expect(runner).not_to receive(:run). 294 | with(["features"], default_options.merge(msg_opts)) 295 | 296 | subject.run_on_modifications(["features/foo"]) 297 | end 298 | 299 | context "with a :cmd_additional_args option" do 300 | let(:options) { { cmd_additional_args: "--color" } } 301 | 302 | it "directly passes the :cmd_additional_args option to the runner" do 303 | opts = default_options.merge(cmd_additional_args: "--color") 304 | opts.merge!(msg_opts) 305 | 306 | expect(runner).to receive(:run). 307 | with(["features"], opts).and_return(true) 308 | subject.run_on_modifications(["features"]) 309 | end 310 | end 311 | 312 | context "when the :all_after_pass option is false" do 313 | let(:options) { { all_after_pass: false } } 314 | 315 | it "does not call #run_all if the changed specs pass after failing "\ 316 | "but the :all_after_pass option is false" do 317 | expect(runner).to receive(:run). 318 | with(["features/foo"], default_options.merge(all_after_pass: false)). 319 | and_return(false, true) 320 | 321 | opts = default_options.merge(all_after_pass: false).merge(msg_opts) 322 | expect(runner).not_to receive(:run). 323 | with(["features"], opts) 324 | 325 | expect do 326 | subject.run_on_modifications(["features/foo"]) 327 | end.to throw_symbol :task_has_failed 328 | 329 | subject.run_on_modifications(["features/foo"]) 330 | end 331 | end 332 | 333 | context "with a rerun.txt file" do 334 | before do 335 | file = double("file") 336 | allow(file).to receive(:read).and_return "features/foo" 337 | allow(File).to receive(:open).and_yield file 338 | end 339 | 340 | it "keeps failed spec and rerun later" do 341 | expect(runner).to receive(:run). 342 | with(["features/foo"], default_options).and_return(false) 343 | expect(File).to receive(:exist?). 344 | with("rerun.txt").and_return true 345 | expect(File).to receive(:delete). 346 | with("rerun.txt") 347 | 348 | expect do 349 | subject.run_on_modifications(["features/foo"]) 350 | end.to throw_symbol :task_has_failed 351 | 352 | expect(runner).to receive(:run). 353 | with(["features/bar", "features/foo"], default_options). 354 | and_return(true) 355 | 356 | expect(runner).to receive(:run). 357 | with(["features"], default_options.merge(msg_opts)).and_return(true) 358 | 359 | subject.run_on_modifications(["features/bar"]) 360 | 361 | expect(runner).to receive(:run). 362 | with(["features/bar"], default_options).and_return(true) 363 | 364 | subject.run_on_modifications(["features/bar"]) 365 | end 366 | end 367 | end 368 | end 369 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # The `.rspec` file also contains a few flags that are not defaults but that 16 | # users commonly want. 17 | # 18 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 19 | RSpec.configure do |config| 20 | # rspec-expectations config goes here. You can use an alternate 21 | # assertion/expectation library such as wrong or the stdlib/minitest 22 | # assertions if you prefer. 23 | config.expect_with :rspec do |expectations| 24 | # This option will default to `true` in RSpec 4. It makes the `description` 25 | # and `failure_message` of custom matchers include text for helper methods 26 | # defined using `chain`, e.g.: 27 | # be_bigger_than(2).and_smaller_than(4).description 28 | # # => "be bigger than 2 and smaller than 4" 29 | # ...rather than: 30 | # # => "be bigger than 2" 31 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 32 | end 33 | 34 | # rspec-mocks config goes here. You can use an alternate test double 35 | # library (such as bogus or mocha) by changing the `mock_with` option here. 36 | config.mock_with :rspec do |mocks| 37 | # Prevents you from mocking or stubbing a method that does not exist on 38 | # a real object. This is generally recommended, and will default to 39 | # `true` in RSpec 4. 40 | mocks.verify_partial_doubles = true 41 | end 42 | 43 | # The settings below are suggested to provide a good initial experience 44 | # with RSpec, but feel free to customize to your heart's content. 45 | 46 | # These two settings work together to allow you to limit a spec run 47 | # to individual examples or groups you care about by tagging them with 48 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 49 | # get run. 50 | config.filter_run :focus 51 | config.run_all_when_everything_filtered = true 52 | 53 | # Limits the available syntax to the non-monkey patched syntax that is 54 | # recommended. 55 | config.disable_monkey_patching! 56 | 57 | # This setting enables warnings. It's recommended, but in some cases may 58 | # be too noisy due to issues in dependencies. 59 | # config.warnings = true 60 | 61 | # Many RSpec users commonly either run the entire suite or an individual 62 | # file, and it's useful to allow more verbose output when running an 63 | # individual spec file. 64 | if config.files_to_run.one? 65 | # Use the documentation formatter for detailed output, 66 | # unless a formatter has already been configured 67 | # (e.g. via a command-line flag). 68 | config.default_formatter = "doc" 69 | end 70 | 71 | # Print the 10 slowest examples and example groups at the 72 | # end of the spec run, to help surface which specs are running 73 | # particularly slow. 74 | # config.profile_examples = 10 75 | 76 | # Run specs in random order to surface order dependencies. If you find an 77 | # order dependency and want to debug it, you can fix the order by providing 78 | # the seed, which is printed after each run. 79 | # --seed 1234 80 | config.order = :random 81 | 82 | # Seed global randomization in this process using the `--seed` CLI option. 83 | # Setting this allows you to use `--seed` to deterministically reproduce 84 | # test failures related to randomization by passing the same `--seed` value 85 | # as the one that triggered the failure. 86 | Kernel.srand config.seed 87 | 88 | config.before(:each) do 89 | ENV["GUARD_ENV"] = "test" 90 | @lib_path = Pathname.new(File.expand_path("../../lib/", __FILE__)) 91 | end 92 | 93 | config.after(:each) do 94 | ENV["GUARD_ENV"] = nil 95 | end 96 | end 97 | --------------------------------------------------------------------------------