cucumber 0.3.92 → 0.3.93

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