kosmas58-cucumber 0.3.92 → 0.3.93.1

Sign up to get free protection for your applications and to get access to all the features.
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
+