-
Notifications
You must be signed in to change notification settings - Fork 0
/
option_parser.rb
317 lines (259 loc) · 11.9 KB
/
option_parser.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html
require 'optparse'
module RSpec::Core
# @private
class Parser
def self.parse(args, source=nil)
new(args).parse(source)
end
attr_reader :original_args
def initialize(original_args)
@original_args = original_args
end
def parse(source=nil)
return { :files_or_directories_to_run => [] } if original_args.empty?
args = original_args.dup
options = args.delete('--tty') ? { :tty => true } : {}
begin
parser(options).parse!(args)
rescue OptionParser::InvalidOption => e
failure = e.message
failure << " (defined in #{source})" if source
abort "#{failure}\n\nPlease use --help for a listing of valid options"
end
options[:files_or_directories_to_run] = args
options
end
private
# rubocop:disable MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable CyclomaticComplexity
# rubocop:disable PerceivedComplexity
# rubocop:disable Metrics/BlockLength
def parser(options)
OptionParser.new do |parser|
parser.summary_width = 34
parser.banner = "Usage: rspec [options] [files or directories]\n\n"
parser.on('-I PATH', 'Specify PATH to add to $LOAD_PATH (may be used more than once).') do |dirs|
options[:libs] ||= []
options[:libs].concat(dirs.split(File::PATH_SEPARATOR))
end
parser.on('-r', '--require PATH', 'Require a file.') do |path|
options[:requires] ||= []
options[:requires] << path
end
parser.on('-O', '--options PATH', 'Specify the path to a custom options file.') do |path|
options[:custom_options_file] = path
end
parser.on('--order TYPE[:SEED]', 'Run examples by the specified order type.',
' [defined] examples and groups are run in the order they are defined',
' [rand] randomize the order of groups and examples',
' [random] alias for rand',
' [random:SEED] e.g. --order random:123') do |o|
options[:order] = o
end
parser.on('--seed SEED', Integer, 'Equivalent of --order rand:SEED.') do |seed|
options[:order] = "rand:#{seed}"
end
parser.on('--bisect[=verbose]', 'Repeatedly runs the suite in order to isolate the failures to the ',
' smallest reproducible case.') do |argument|
options[:bisect] = argument || true
options[:runner] = RSpec::Core::Invocations::Bisect.new
end
parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument|
if argument == true
value = 1
elsif argument == false || argument == 0
value = false
else
begin
value = Integer(argument)
rescue ArgumentError
RSpec.warning "Expected an integer value for `--fail-fast`, got: #{argument.inspect}", :call_site => nil
end
end
set_fail_fast(options, value)
end
parser.on('--failure-exit-code CODE', Integer,
'Override the exit code used when there are failing specs.') do |code|
options[:failure_exit_code] = code
end
parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |use_drb|
options[:drb] = use_drb
options[:runner] = RSpec::Core::Invocations::DRbWithFallback.new if use_drb
end
parser.on('--drb-port PORT', 'Port to connect to the DRb server.') do |o|
options[:drb_port] = o.to_i
end
parser.separator("\n **** Output ****\n\n")
parser.on('-f', '--format FORMATTER', 'Choose a formatter.',
' [p]rogress (default - dots)',
' [d]ocumentation (group and example names)',
' [h]tml',
' [j]son',
' [f]ailures ("file:line:reason", suitable for editors integration)',
' custom formatter class name') do |o|
options[:formatters] ||= []
options[:formatters] << [o]
end
parser.on('-o', '--out FILE',
'Write output to a file instead of $stdout. This option applies',
' to the previously specified --format, or the default format',
' if no format is specified.'
) do |o|
options[:formatters] ||= [['progress']]
options[:formatters].last << o
end
parser.on('--deprecation-out FILE', 'Write deprecation warnings to a file instead of $stderr.') do |file|
options[:deprecation_stream] = file
end
parser.on('-b', '--backtrace', 'Enable full backtrace.') do |_o|
options[:full_backtrace] = true
end
parser.on('-c', '--color', '--colour', '') do |_o|
# flag will be excluded from `--help` output because it is deprecated
options[:color] = true
options[:color_mode] = :automatic
end
parser.on('--force-color', '--force-colour', 'Force the output to be in color, even if the output is not a TTY') do |_o|
if options[:color_mode] == :off
abort "Please only use one of `--force-color` and `--no-color`"
end
options[:color_mode] = :on
end
parser.on('--no-color', '--no-colour', 'Force the output to not be in color, even if the output is a TTY') do |_o|
if options[:color_mode] == :on
abort "Please only use one of --force-color and --no-color"
end
options[:color_mode] = :off
end
parser.on('-p', '--[no-]profile [COUNT]',
'Enable profiling of examples and list the slowest examples (default: 10).') do |argument|
options[:profile_examples] = if argument.nil?
true
elsif argument == false
false
else
begin
Integer(argument)
rescue ArgumentError
RSpec.warning "Non integer specified as profile count, separate " \
"your path from options with -- e.g. " \
"`rspec --profile -- #{argument}`",
:call_site => nil
true
end
end
end
parser.on('--dry-run', 'Print the formatter output of your suite without',
' running any examples or hooks') do |_o|
options[:dry_run] = true
end
parser.on('-w', '--warnings', 'Enable ruby warnings') do
$VERBOSE = true
end
parser.separator <<-FILTERING
**** Filtering/tags ****
In addition to the following options for selecting specific files, groups, or
examples, you can select individual examples by appending the line number(s) to
the filename:
rspec path/to/a_spec.rb:37:87
You can also pass example ids enclosed in square brackets:
rspec path/to/a_spec.rb[1:5,1:6] # run the 5th and 6th examples/groups defined in the 1st group
FILTERING
parser.on('--only-failures', "Filter to just the examples that failed the last time they ran.") do
configure_only_failures(options)
end
parser.on("-n", "--next-failure", "Apply `--only-failures` and abort after one failure.",
" (Equivalent to `--only-failures --fail-fast --order defined`)") do
configure_only_failures(options)
set_fail_fast(options, 1)
options[:order] ||= 'defined'
end
parser.on('-P', '--pattern PATTERN', 'Load files matching pattern (default: "spec/**/*_spec.rb").') do |o|
if options[:pattern]
options[:pattern] += ',' + o
else
options[:pattern] = o
end
end
parser.on('--exclude-pattern PATTERN',
'Load files except those matching pattern. Opposite effect of --pattern.') do |o|
options[:exclude_pattern] = o
end
parser.on('-e', '--example STRING', "Run examples whose full nested names include STRING (may be",
" used more than once)") do |o|
(options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o))
end
parser.on('-E', '--example-matches REGEX', "Run examples whose full nested names match REGEX (may be",
" used more than once)") do |o|
(options[:full_description] ||= []) << Regexp.compile(o)
end
parser.on('-t', '--tag TAG[:VALUE]',
'Run examples with the specified tag, or exclude examples',
'by adding ~ before the tag.',
' - e.g. ~slow',
' - TAG is always converted to a symbol') do |tag|
filter_type = tag =~ /^~/ ? :exclusion_filter : :inclusion_filter
name, value = tag.gsub(/^(~@|~|@)/, '').split(':', 2)
name = name.to_sym
parsed_value = case value
when nil then true # The default value for tags is true
when 'true' then true
when 'false' then false
when 'nil' then nil
when /^:/ then value[1..-1].to_sym
when /^\d+$/ then Integer(value)
when /^\d+.\d+$/ then Float(value)
else
value
end
add_tag_filter(options, filter_type, name, parsed_value)
end
parser.on('--default-path PATH', 'Set the default path where RSpec looks for examples (can',
' be a path to a file or a directory).') do |path|
options[:default_path] = path
end
parser.separator("\n **** Utility ****\n\n")
parser.on('--init', 'Initialize your project with RSpec.') do |_cmd|
options[:runner] = RSpec::Core::Invocations::InitializeProject.new
end
parser.on('-v', '--version', 'Display the version.') do
options[:runner] = RSpec::Core::Invocations::PrintVersion.new
end
# These options would otherwise be confusing to users, so we forcibly
# prevent them from executing.
#
# * --I is too similar to -I.
# * -d was a shorthand for --debugger, which is removed, but now would
# trigger --default-path.
invalid_options = %w[-d --I]
hidden_options = invalid_options + %w[-c]
parser.on_tail('-h', '--help', "You're looking at it.") do
options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, hidden_options)
end
# This prevents usage of the invalid_options.
invalid_options.each do |option|
parser.on(option) do
raise OptionParser::InvalidOption.new
end
end
end
end
# rubocop:enable Metrics/BlockLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable MethodLength
# rubocop:enable CyclomaticComplexity
# rubocop:enable PerceivedComplexity
def add_tag_filter(options, filter_type, tag_name, value=true)
(options[filter_type] ||= {})[tag_name] = value
end
def set_fail_fast(options, value)
options[:fail_fast] = value
end
def configure_only_failures(options)
options[:only_failures] = true
add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed')
end
end
end