kosmas58-cucumber 0.3.92 → 0.3.93.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/History.txt +25 -0
  2. data/Manifest.txt +7 -0
  3. data/cucumber.yml +2 -2
  4. data/examples/i18n/pt/features/adicao.feature +4 -4
  5. data/features/html_formatter/a.html +4 -4
  6. data/features/profiles.feature +99 -0
  7. data/features/step_definitions/cucumber_steps.rb +20 -0
  8. data/features/work_in_progress.feature +1 -0
  9. data/gem_tasks/contributors.rake +4 -0
  10. data/lib/cucumber/ast/table.rb +2 -2
  11. data/lib/cucumber/cli/configuration.rb +25 -281
  12. data/lib/cucumber/cli/drb_client.rb +3 -1
  13. data/lib/cucumber/cli/main.rb +5 -7
  14. data/lib/cucumber/cli/options.rb +365 -0
  15. data/lib/cucumber/cli/profile_loader.rb +65 -0
  16. data/lib/cucumber/formatter/console.rb +1 -1
  17. data/lib/cucumber/formatter/html.rb +1 -0
  18. data/lib/cucumber/parser/feature.rb +67 -67
  19. data/lib/cucumber/parser/feature.tt +28 -1
  20. data/lib/cucumber/parser/i18n/language.rb +4 -0
  21. data/lib/cucumber/parser/table.rb +25 -25
  22. data/lib/cucumber/step_mother.rb +3 -1
  23. data/lib/cucumber/version.rb +2 -2
  24. data/lib/cucumber/webrat/table_locator.rb +1 -1
  25. data/rails_generators/cucumber/cucumber_generator.rb +6 -2
  26. data/rails_generators/cucumber/templates/cucumber +3 -2
  27. data/rails_generators/cucumber/templates/cucumber_environment.rb +7 -4
  28. data/rails_generators/cucumber/templates/de/webrat_steps.rb +9 -4
  29. data/rails_generators/cucumber/templates/en/webrat_steps.rb +4 -0
  30. data/spec/cucumber/cli/configuration_spec.rb +132 -102
  31. data/spec/cucumber/cli/main_spec.rb +14 -4
  32. data/spec/cucumber/cli/options_spec.rb +306 -0
  33. data/spec/cucumber/cli/profile_loader_spec.rb +10 -0
  34. data/spec/cucumber/formatter/html_spec.rb +18 -0
  35. data/spec/cucumber/parser/table_parser_spec.rb +1 -1
  36. data/spec/spec.opts +3 -1
  37. metadata +9 -2
@@ -11,7 +11,9 @@ module Cucumber
11
11
  # See http://redmine.ruby-lang.org/issues/show/496 as to why we specify localhost:0
12
12
  DRb.start_service("druby://localhost:0")
13
13
  feature_server = DRbObject.new_with_uri("druby://127.0.0.1:8990")
14
- feature_server.run(args, error_stream, out_stream)
14
+ cloned_args = [] # I have no idea why this is needed, but if the regular args are sent then DRb magically transforms it into a DRb object - not an array
15
+ args.each { |arg| cloned_args << arg }
16
+ feature_server.run(cloned_args, error_stream, out_stream)
15
17
  rescue DRb::DRbConnError
16
18
  raise DRbClientError, "No DRb server is running."
17
19
  end
@@ -40,7 +40,6 @@ module Cucumber
40
40
  return DRbClient.run(@args, @error_stream, @out_stream)
41
41
  rescue DRbClientError => e
42
42
  @error_stream.puts "WARNING: #{e.message} Running features locally:"
43
- configuration.parse!(@args)
44
43
  end
45
44
  end
46
45
  step_mother.options = configuration.options
@@ -63,6 +62,9 @@ module Cucumber
63
62
  step_mother.scenarios(:failed).any? ||
64
63
  (configuration.strict? && step_mother.steps(:undefined).any?)
65
64
  end
65
+ rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
66
+ @error_stream.puts e.message
67
+ true
66
68
  end
67
69
 
68
70
  def load_plain_text_features
@@ -136,13 +138,9 @@ module Cucumber
136
138
 
137
139
  def trap_interrupt
138
140
  trap('INT') do
141
+ exit!(1) if $cucumber_interrupted
139
142
  $cucumber_interrupted = true
140
- STDERR.puts "Interrupted. Waiting for current step to finish."
141
- STDERR.puts "Will suicide in 5 seconds if current step doesn't finish gracefully."
142
- Thread.new do
143
- sleep(5)
144
- exit!(1)
145
- end
143
+ STDERR.puts "\nExiting... Interrupt again to exit immediately."
146
144
  end
147
145
  end
148
146
  end
@@ -0,0 +1,365 @@
1
+ require 'cucumber/cli/profile_loader'
2
+ module Cucumber
3
+ module Cli
4
+
5
+ class Options
6
+ BUILTIN_FORMATS = {
7
+ 'html' => ['Cucumber::Formatter::Html', 'Generates a nice looking HTML report.'],
8
+ 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
9
+ 'profile' => ['Cucumber::Formatter::Profile', 'Prints the 10 slowest steps at the end.'],
10
+ 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
11
+ 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
12
+ 'usage' => ['Cucumber::Formatter::Usage', 'Prints where step definitions are used.'],
13
+ 'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'],
14
+ 'tag_cloud' => ['Cucumber::Formatter::TagCloud', 'Prints a tag cloud of tag usage.'],
15
+ 'steps' => ['Cucumber::Formatter::Steps', 'Prints location of step definitions.']
16
+ }
17
+ max = BUILTIN_FORMATS.keys.map{|s| s.length}.max
18
+ FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
19
+ " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
20
+ end) + ["FORMAT can also be the fully qualified class name of",
21
+ "your own custom formatter. If the class isn't loaded,",
22
+ "Cucumber will attempt to require a file with a relative",
23
+ "file name that is the underscore name of the class name.",
24
+ "Example: --format Foo::BarZap -> Cucumber will look for",
25
+ "foo/bar_zap.rb. You can place the file with this relative",
26
+ "path underneath your features/support directory or anywhere",
27
+ "on Ruby's LOAD_PATH, for example in a Ruby gem."
28
+ ]
29
+ DRB_FLAG = '--drb'
30
+ PROFILE_SHORT_FLAG = '-p'
31
+ PROFILE_LONG_FLAG = '--profile'
32
+
33
+ attr_reader :paths
34
+
35
+ def self.parse(args, out_stream, error_stream, options = {})
36
+ new(out_stream, error_stream, options).parse!(args)
37
+ end
38
+
39
+ def initialize(out_stream = STDOUT, error_stream = STDERR, options = {})
40
+ @out_stream = out_stream
41
+ @error_stream = error_stream
42
+
43
+ @default_profile = options[:default_profile]
44
+ @skip_profile_information = options[:skip_profile_information]
45
+ @profiles = []
46
+ @options = default_options
47
+ end
48
+
49
+ def [](key)
50
+ @options[key]
51
+ end
52
+
53
+ def []=(key, value)
54
+ @options[key] = value
55
+ end
56
+
57
+ def expanded_args_without_drb
58
+ @expanded_args_without_drb ||= (
59
+ previous_flag_was_profile = false
60
+ @expanded_args.reject do |arg|
61
+ if previous_flag_was_profile
62
+ previous_flag_was_profile = false
63
+ next true
64
+ end
65
+ if [PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG].include?(arg)
66
+ previous_flag_was_profile = true
67
+ next true
68
+ end
69
+ arg == DRB_FLAG
70
+ end
71
+ )
72
+ end
73
+
74
+ def parse!(args)
75
+ @args = args
76
+ @expanded_args = @args.dup
77
+
78
+ @args.extend(::OptionParser::Arguable)
79
+
80
+ @args.options do |opts|
81
+ opts.banner = ["Usage: cucumber [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+", "",
82
+ "Examples:",
83
+ "cucumber examples/i18n/en/features",
84
+ "cucumber --language it examples/i18n/it/features/somma.feature:6:98:113",
85
+ "cucumber -s -i http://rubyurl.com/eeCl", "", "",
86
+ ].join("\n")
87
+ opts.on("-r LIBRARY|DIR", "--require LIBRARY|DIR",
88
+ "Require files before executing the features. If this",
89
+ "option is not specified, all *.rb files that are",
90
+ "siblings or below the features will be loaded auto-",
91
+ "matically. Automatic loading is disabled when this",
92
+ "option is specified, and all loading becomes explicit.",
93
+ "Files under directories named \"support\" are always",
94
+ "loaded first.",
95
+ "This option can be specified multiple times.") do |v|
96
+ @options[:require] << v
97
+ end
98
+ opts.on("-l LANG", "--language LANG",
99
+ "Specify language for features (Default: #{@options[:lang]})",
100
+ %{Run with "--language help" to see all languages},
101
+ %{Run with "--language LANG help" to list keywords for LANG}) do |v|
102
+ if v == 'help'
103
+ list_languages_and_exit
104
+ elsif args==['help'] # I think this conditional is just cruft and can be removed
105
+ list_keywords_and_exit(v)
106
+ else
107
+ @options[:lang] = v
108
+ end
109
+ end
110
+ opts.on("-f FORMAT", "--format FORMAT",
111
+ "How to format features (Default: pretty). Available formats:",
112
+ *FORMAT_HELP) do |v|
113
+ @options[:formats] << [v, @out_stream]
114
+ end
115
+ opts.on("-o", "--out [FILE|DIR]",
116
+ "Write output to a file/directory instead of STDOUT. This option",
117
+ "applies to the previously specified --format, or the",
118
+ "default format if no format is specified. Check the specific",
119
+ "formatter's docs to see whether to pass a file or a dir.") do |v|
120
+ @options[:formats] << ['pretty', nil] if @options[:formats].empty?
121
+ @options[:formats][-1][1] = v
122
+ end
123
+ opts.on("-t TAGS", "--tags TAGS",
124
+ "Only execute the features or scenarios with the specified tags.",
125
+ "TAGS must be comma-separated without spaces. Prefix tags with ~ to",
126
+ "exclude features or scenarios having that tag. Tags can be specified",
127
+ "with or without the @ prefix.") do |v|
128
+ include_tags, exclude_tags = *parse_tags(v)
129
+ @options[:include_tags] += include_tags
130
+ @options[:exclude_tags] += exclude_tags
131
+ end
132
+ opts.on("-n NAME", "--name NAME",
133
+ "Only execute the feature elements which match part of the given name.",
134
+ "If this option is given more than once, it will match against all the",
135
+ "given names.") do |v|
136
+ @options[:name_regexps] << /#{v}/
137
+ end
138
+ opts.on("-e", "--exclude PATTERN", "Don't run feature files or require ruby files matching PATTERN") do |v|
139
+ @options[:excludes] << Regexp.new(v)
140
+ end
141
+ opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE",
142
+ "Pull commandline arguments from cucumber.yml which can be defined as",
143
+ "strings or arrays. When a 'default' profile is defined and no profile",
144
+ "is specified it is always used. (Unless disabled, see -P below.)",
145
+ "When feature files are defined in a profile and on the command line",
146
+ "then only the ones from the command line are used.") do |v|
147
+ @profiles << v
148
+ end
149
+ opts.on('-P', '--no-profile',
150
+ "Disables all profile laoding to avoid using the 'default' profile.") do |v|
151
+ @disable_profile_loading = true
152
+ end
153
+ opts.on("-c", "--[no-]color",
154
+ "Whether or not to use ANSI color in the output. Cucumber decides",
155
+ "based on your platform and the output destination if not specified.") do |v|
156
+ Term::ANSIColor.coloring = v
157
+ end
158
+ opts.on("-d", "--dry-run", "Invokes formatters without executing the steps.",
159
+ "This also omits the loading of your support/env.rb file if it exists.",
160
+ "Implies --quiet.") do
161
+ @options[:dry_run] = true
162
+ @quiet = true
163
+ end
164
+ opts.on("-a", "--autoformat DIRECTORY",
165
+ "Reformats (pretty prints) feature files and write them to DIRECTORY.",
166
+ "Be careful if you choose to overwrite the originals.",
167
+ "Implies --dry-run --formatter pretty.") do |directory|
168
+ @options[:autoformat] = directory
169
+ Term::ANSIColor.coloring = false
170
+ @options[:dry_run] = true
171
+ @quiet = true
172
+ end
173
+ opts.on("-m", "--no-multiline",
174
+ "Don't print multiline strings and tables under steps.") do
175
+ @options[:no_multiline] = true
176
+ end
177
+ opts.on("-s", "--no-source",
178
+ "Don't print the file and line of the step definition with the steps.") do
179
+ @options[:source] = false
180
+ end
181
+ opts.on("-i", "--no-snippets", "Don't print snippets for pending steps.") do
182
+ @options[:snippets] = false
183
+ end
184
+ opts.on("-q", "--quiet", "Alias for --no-snippets --no-source.") do
185
+ @quiet = true
186
+ end
187
+ opts.on("-b", "--backtrace", "Show full backtrace for all errors.") do
188
+ Exception.cucumber_full_backtrace = true
189
+ end
190
+ opts.on("-S", "--strict", "Fail if there are any undefined steps.") do
191
+ @options[:strict] = true
192
+ end
193
+ opts.on("-w", "--wip", "Fail if there are any passing scenarios.") do
194
+ @options[:wip] = true
195
+ end
196
+ opts.on("-v", "--verbose", "Show the files and features loaded.") do
197
+ @options[:verbose] = true
198
+ end
199
+ opts.on("-g", "--guess", "Guess best match for Ambiguous steps.") do
200
+ @options[:guess] = true
201
+ end
202
+ opts.on("-x", "--expand", "Expand Scenario Outline Tables in output.") do
203
+ @options[:expand] = true
204
+ end
205
+ opts.on("--no-diff", "Disable diff output on failing expectations.") do
206
+ @options[:diff_enabled] = false
207
+ end
208
+ opts.on(DRB_FLAG, "Run features against a DRb server. (i.e. with the spork gem)") do
209
+ @options[:drb] = true
210
+ end
211
+ opts.on_tail("--version", "Show version.") do
212
+ @out_stream.puts VERSION::STRING
213
+ Kernel.exit
214
+ end
215
+ opts.on_tail("-h", "--help", "You're looking at it.") do
216
+ @out_stream.puts opts.help
217
+ Kernel.exit
218
+ end
219
+ end.parse!
220
+
221
+ if @quiet
222
+ @options[:snippets] = @options[:source] = false
223
+ else
224
+ @options[:snippets] = true if @options[:snippets].nil?
225
+ @options[:source] = true if @options[:source].nil?
226
+ end
227
+
228
+
229
+ extract_environment_variables
230
+ @options[:paths] = @args.dup #whatver is left over
231
+
232
+ merge_profiles
233
+ print_profile_information
234
+
235
+ self
236
+ end
237
+
238
+
239
+
240
+ protected
241
+
242
+ attr_reader :options, :profiles, :expanded_args
243
+ protected :options, :profiles, :expanded_args
244
+
245
+ def non_stdout_formats
246
+ @options[:formats].select {|format, output| output != @out_stream }
247
+ end
248
+
249
+ private
250
+
251
+ def extract_environment_variables
252
+ @args.delete_if do |arg|
253
+ if arg =~ /^(\w+)=(.*)$/
254
+ @options[:env_vars][$1] = $2
255
+ true
256
+ end
257
+ end
258
+ end
259
+
260
+ def parse_tags(tag_string)
261
+ tag_names = tag_string.split(",")
262
+ excludes, includes = tag_names.partition{|tag| tag =~ /^~/}
263
+ excludes = excludes.map{|tag| tag[1..-1]}
264
+
265
+ # Strip @
266
+ includes = includes.map{|tag| Ast::Tags.strip_prefix(tag)}
267
+ excludes = excludes.map{|tag| Ast::Tags.strip_prefix(tag)}
268
+ [includes, excludes]
269
+ end
270
+
271
+ def disable_profile_loading?
272
+ @disable_profile_loading
273
+ end
274
+
275
+ def merge_profiles
276
+ if @disable_profile_loading
277
+ @out_stream.puts "Disabling profiles..."
278
+ return
279
+ end
280
+
281
+ @profiles << @default_profile if default_profile_should_be_used?
282
+
283
+ @profiles.each do |profile|
284
+ profile_args = profile_loader.args_from(profile)
285
+ reverse_merge(
286
+ Options.parse(profile_args, @out_stream, @error_stream, :skip_profile_information => true)
287
+ )
288
+ end
289
+
290
+ end
291
+
292
+ def default_profile_should_be_used?
293
+ @profiles.empty? &&
294
+ profile_loader.cucumber_yml_defined? &&
295
+ profile_loader.has_profile?(@default_profile)
296
+ end
297
+
298
+ def profile_loader
299
+ @profile_loader ||= ProfileLoader.new
300
+ end
301
+
302
+ def reverse_merge(other_options)
303
+ @options = other_options.options.merge(@options)
304
+ @options[:require] += other_options[:require]
305
+ @options[:include_tags] += other_options[:include_tags]
306
+ @options[:exclude_tags] += other_options[:exclude_tags]
307
+ @options[:env_vars] = other_options[:env_vars].merge(@options[:env_vars])
308
+ @options[:paths] = other_options[:paths] if @options[:paths].empty?
309
+ @options[:source] &= other_options[:source]
310
+ @options[:snippets] &= other_options[:snippets]
311
+
312
+ @profiles += other_options.profiles
313
+ @expanded_args += other_options.expanded_args
314
+
315
+ if @options[:formats].empty?
316
+ @options[:formats] = other_options[:formats]
317
+ else
318
+ @options[:formats] += other_options.non_stdout_formats
319
+ end
320
+
321
+ self
322
+ end
323
+
324
+ # TODO: Move to Language
325
+ def list_keywords_and_exit(lang)
326
+ unless Cucumber::LANGUAGES[lang]
327
+ raise("No language with key #{lang}")
328
+ end
329
+ LanguageHelpFormatter.list_keywords(@out_stream, lang)
330
+ Kernel.exit
331
+ end
332
+
333
+ def list_languages_and_exit
334
+ LanguageHelpFormatter.list_languages(@out_stream)
335
+ Kernel.exit
336
+ end
337
+
338
+ def print_profile_information
339
+ return if @skip_profile_information || @profiles.empty?
340
+ profiles_sentence = ''
341
+ profiles_sentence = @profiles.size == 1 ? @profiles.first :
342
+ "#{@profiles[0...-1].join(', ')} and #{@profiles.last}"
343
+
344
+ @out_stream.puts "Using the #{profiles_sentence} profile#{'s' if @profiles.size> 1}..."
345
+ end
346
+
347
+ def default_options
348
+ {
349
+ :strict => false,
350
+ :require => [],
351
+ :lang => nil,
352
+ :dry_run => false,
353
+ :formats => [],
354
+ :excludes => [],
355
+ :include_tags => [],
356
+ :exclude_tags => [],
357
+ :name_regexps => [],
358
+ :env_vars => {},
359
+ :diff_enabled => true
360
+ }
361
+ end
362
+ end
363
+
364
+ end
365
+ end
@@ -0,0 +1,65 @@
1
+ module Cucumber
2
+ module Cli
3
+
4
+ class ProfileLoader
5
+
6
+ def args_from(profile)
7
+ unless cucumber_yml.has_key?(profile)
8
+ raise(ProfileNotFound, <<-END_OF_ERROR)
9
+ Could not find profile: '#{profile}'
10
+
11
+ Defined profiles in cucumber.yml:
12
+ * #{cucumber_yml.keys.join("\n * ")}
13
+ END_OF_ERROR
14
+ end
15
+
16
+ args_from_yml = cucumber_yml[profile] || ''
17
+
18
+ case(args_from_yml)
19
+ when String
20
+ raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was blank. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml =~ /^\s*$/
21
+ args_from_yml = args_from_yml.split(' ')
22
+ when Array
23
+ raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was empty. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml.empty?
24
+ else
25
+ raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was a #{args_from_yml.class}. It must be a String or Array"
26
+ end
27
+ args_from_yml
28
+ end
29
+
30
+ def has_profile?(profile)
31
+ cucumber_yml.has_key?(profile)
32
+ end
33
+
34
+ def cucumber_yml_defined?
35
+ @defined ||= File.exist?('cucumber.yml')
36
+ end
37
+
38
+ private
39
+
40
+ def cucumber_yml
41
+ return @cucumber_yml if @cucumber_yml
42
+ unless cucumber_yml_defined?
43
+ raise(ProfilesNotDefinedError,"cucumber.yml was not found. Please refer to cucumber's documentation on defining profiles in cucumber.yml. You must define a 'default' profile to use the cucumber command without any arguments.\nType 'cucumber --help' for usage.\n")
44
+ end
45
+
46
+ require 'yaml'
47
+ begin
48
+ @cucumber_yml = YAML::load(IO.read('cucumber.yml'))
49
+ rescue StandardError => e
50
+ raise(YmlLoadError,"cucumber.yml was found, but could not be parsed. Please refer to cucumber's documentation on correct profile usage.\n")
51
+ end
52
+
53
+ if @cucumber_yml.nil? || !@cucumber_yml.is_a?(Hash)
54
+ raise(YmlLoadError,"cucumber.yml was found, but was blank or malformed. Please refer to cucumber's documentation on correct profile usage.\n")
55
+ end
56
+
57
+ return @cucumber_yml
58
+ end
59
+
60
+
61
+
62
+ end
63
+ end
64
+ end
65
+