| 1 |
require 'optparse' |
| 2 |
require 'stringio' |
| 3 |
|
| 4 |
module Spec |
| 5 |
module Runner |
| 6 |
class OptionParser < ::OptionParser |
| 7 |
class << self |
| 8 |
def parse(args, err, out) |
| 9 |
parser = new(err, out) |
| 10 |
parser.parse(args) |
| 11 |
parser.options |
| 12 |
end |
| 13 |
end |
| 14 |
|
| 15 |
attr_reader :options |
| 16 |
|
| 17 |
OPTIONS = { |
| 18 |
:pattern => ["-p", "--pattern [PATTERN]","Limit files loaded to those matching this pattern. Defaults to '**/*_spec.rb'", |
| 19 |
"Separate multiple patterns with commas.", |
| 20 |
"Applies only to directories named on the command line (files", |
| 21 |
"named explicitly on the command line will be loaded regardless)."], |
| 22 |
:diff => ["-D", "--diff [FORMAT]","Show diff of objects that are expected to be equal when they are not", |
| 23 |
"Builtin formats: unified|u|context|c", |
| 24 |
"You can also specify a custom differ class", |
| 25 |
"(in which case you should also specify --require)"], |
| 26 |
:colour => ["-c", "--colour", "--color", "Show coloured (red/green) output"], |
| 27 |
:example => ["-e", "--example [NAME|FILE_NAME]", "Execute example(s) with matching name(s). If the argument is", |
| 28 |
"the path to an existing file (typically generated by a previous", |
| 29 |
"run using --format failing_examples:file.txt), then the examples", |
| 30 |
"on each line of thatfile will be executed. If the file is empty,", |
| 31 |
"all examples will be run (as if --example was not specified).", |
| 32 |
" ", |
| 33 |
"If the argument is not an existing file, then it is treated as", |
| 34 |
"an example name directly, causing RSpec to run just the example", |
| 35 |
"matching that name"], |
| 36 |
:specification => ["-s", "--specification [NAME]", "DEPRECATED - use -e instead", "(This will be removed when autotest works with -e)"], |
| 37 |
:line => ["-l", "--line LINE_NUMBER", Integer, "Execute behaviout or specification at given line.", |
| 38 |
"(does not work for dynamically generated specs)"], |
| 39 |
:format => ["-f", "--format FORMAT[:WHERE]","Specifies what format to use for output. Specify WHERE to tell", |
| 40 |
"the formatter where to write the output. All built-in formats", |
| 41 |
"expect WHERE to be a file name, and will write to STDOUT if it's", |
| 42 |
"not specified. The --format option may be specified several times", |
| 43 |
"if you want several outputs", |
| 44 |
" ", |
| 45 |
"Builtin formats for examples: ", |
| 46 |
"progress|p : Text progress", |
| 47 |
"profile|o : Text progress with profiling of 10 slowest examples", |
| 48 |
"specdoc|s : Example doc as text", |
| 49 |
"html|h : A nice HTML report", |
| 50 |
"failing_examples|e : Write all failing examples - input for --example", |
| 51 |
"failing_example_groups|g : Write all failing example groups - input for --example", |
| 52 |
" ", |
| 53 |
"Builtin formats for stories: ", |
| 54 |
"plain|p : Plain Text", |
| 55 |
"html|h : A nice HTML report", |
| 56 |
" ", |
| 57 |
"FORMAT can also be the name of a custom formatter class", |
| 58 |
"(in which case you should also specify --require to load it)"], |
| 59 |
:require => ["-r", "--require FILE", "Require FILE before running specs", |
| 60 |
"Useful for loading custom formatters or other extensions.", |
| 61 |
"If this option is used it must come before the others"], |
| 62 |
:backtrace => ["-b", "--backtrace", "Output full backtrace"], |
| 63 |
:loadby => ["-L", "--loadby STRATEGY", "Specify the strategy by which spec files should be loaded.", |
| 64 |
"STRATEGY can currently only be 'mtime' (File modification time)", |
| 65 |
"By default, spec files are loaded in alphabetical order if --loadby", |
| 66 |
"is not specified."], |
| 67 |
:reverse => ["-R", "--reverse", "Run examples in reverse order"], |
| 68 |
:timeout => ["-t", "--timeout FLOAT", "Interrupt and fail each example that doesn't complete in the", |
| 69 |
"specified time"], |
| 70 |
:heckle => ["-H", "--heckle CODE", "If all examples pass, this will mutate the classes and methods", |
| 71 |
"identified by CODE little by little and run all the examples again", |
| 72 |
"for each mutation. The intent is that for each mutation, at least", |
| 73 |
"one example *should* fail, and RSpec will tell you if this is not the", |
| 74 |
"case. CODE should be either Some::Module, Some::Class or", |
| 75 |
"Some::Fabulous#method}"], |
| 76 |
:dry_run => ["-d", "--dry-run", "Invokes formatters without executing the examples."], |
| 77 |
:options_file => ["-O", "--options PATH", "Read options from a file"], |
| 78 |
:generate_options => ["-G", "--generate-options PATH", "Generate an options file for --options"], |
| 79 |
:runner => ["-U", "--runner RUNNER", "Use a custom Runner."], |
| 80 |
:drb => ["-X", "--drb", "Run examples via DRb. (For example against script/spec_server)"], |
| 81 |
:version => ["-v", "--version", "Show version"], |
| 82 |
:help => ["-h", "--help", "You're looking at it"] |
| 83 |
} |
| 84 |
|
| 85 |
def initialize(err, out) |
| 86 |
super() |
| 87 |
@error_stream = err |
| 88 |
@out_stream = out |
| 89 |
@options = Options.new(@error_stream, @out_stream) |
| 90 |
|
| 91 |
@file_factory = File |
| 92 |
|
| 93 |
self.banner = "Usage: spec (FILE|DIRECTORY|GLOB)+ [options]" |
| 94 |
self.separator "" |
| 95 |
on(*OPTIONS[:pattern]) {|pattern| @options.filename_pattern = pattern} |
| 96 |
on(*OPTIONS[:diff]) {|diff| @options.parse_diff(diff)} |
| 97 |
on(*OPTIONS[:colour]) {@options.colour = true} |
| 98 |
on(*OPTIONS[:example]) {|example| @options.parse_example(example)} |
| 99 |
on(*OPTIONS[:specification]) {|example| @options.parse_example(example)} |
| 100 |
on(*OPTIONS[:line]) {|line_number| @options.line_number = line_number.to_i} |
| 101 |
on(*OPTIONS[:format]) {|format| @options.parse_format(format)} |
| 102 |
on(*OPTIONS[:require]) {|requires| invoke_requires(requires)} |
| 103 |
on(*OPTIONS[:backtrace]) {@options.backtrace_tweaker = NoisyBacktraceTweaker.new} |
| 104 |
on(*OPTIONS[:loadby]) {|loadby| @options.loadby = loadby} |
| 105 |
on(*OPTIONS[:reverse]) {@options.reverse = true} |
| 106 |
on(*OPTIONS[:timeout]) {|timeout| @options.timeout = timeout.to_f} |
| 107 |
on(*OPTIONS[:heckle]) {|heckle| @options.load_heckle_runner(heckle)} |
| 108 |
on(*OPTIONS[:dry_run]) {@options.dry_run = true} |
| 109 |
on(*OPTIONS[:options_file]) {|options_file| parse_options_file(options_file)} |
| 110 |
on(*OPTIONS[:generate_options]) {|options_file|} |
| 111 |
on(*OPTIONS[:runner]) {|runner| @options.user_input_for_runner = runner} |
| 112 |
on(*OPTIONS[:drb]) {} |
| 113 |
on(*OPTIONS[:version]) {parse_version} |
| 114 |
on_tail(*OPTIONS[:help]) {parse_help} |
| 115 |
end |
| 116 |
|
| 117 |
def order!(argv, &blk) |
| 118 |
@argv = argv |
| 119 |
@options.argv = @argv.dup |
| 120 |
return if parse_generate_options |
| 121 |
return if parse_drb |
| 122 |
|
| 123 |
super(@argv) do |file| |
| 124 |
@options.files << file |
| 125 |
blk.call(file) if blk |
| 126 |
end |
| 127 |
|
| 128 |
@options |
| 129 |
end |
| 130 |
|
| 131 |
protected |
| 132 |
def invoke_requires(requires) |
| 133 |
requires.split(",").each do |file| |
| 134 |
require file |
| 135 |
end |
| 136 |
end |
| 137 |
|
| 138 |
def parse_options_file(options_file) |
| 139 |
option_file_args = IO.readlines(options_file).map {|l| l.chomp.split " "}.flatten |
| 140 |
@argv.push(*option_file_args) |
| 141 |
|
| 142 |
|
| 143 |
|
| 144 |
parse_drb(@argv) |
| 145 |
end |
| 146 |
|
| 147 |
def parse_generate_options |
| 148 |
|
| 149 |
options_file = nil |
| 150 |
['-G', '--generate-options'].each do |option| |
| 151 |
if index = @argv.index(option) |
| 152 |
@argv.delete_at(index) |
| 153 |
options_file = @argv.delete_at(index) |
| 154 |
end |
| 155 |
end |
| 156 |
|
| 157 |
if options_file |
| 158 |
write_generated_options(options_file) |
| 159 |
return true |
| 160 |
else |
| 161 |
return false |
| 162 |
end |
| 163 |
end |
| 164 |
|
| 165 |
def write_generated_options(options_file) |
| 166 |
File.open(options_file, 'w') do |io| |
| 167 |
io.puts @argv.join("\n") |
| 168 |
end |
| 169 |
@out_stream.puts "\nOptions written to #{options_file}. You can now use these options with:" |
| 170 |
@out_stream.puts "spec --options #{options_file}" |
| 171 |
@options.examples_should_not_be_run |
| 172 |
end |
| 173 |
|
| 174 |
def parse_drb(argv = nil) |
| 175 |
argv ||= @options.argv |
| 176 |
is_drb = false |
| 177 |
is_drb ||= argv.delete(OPTIONS[:drb][0]) |
| 178 |
is_drb ||= argv.delete(OPTIONS[:drb][1]) |
| 179 |
return false unless is_drb |
| 180 |
@options.examples_should_not_be_run |
| 181 |
DrbCommandLine.run( |
| 182 |
self.class.parse(argv, @error_stream, @out_stream) |
| 183 |
) |
| 184 |
true |
| 185 |
end |
| 186 |
|
| 187 |
def parse_version |
| 188 |
@out_stream.puts ::Spec::VERSION::DESCRIPTION |
| 189 |
exit if stdout? |
| 190 |
end |
| 191 |
|
| 192 |
def parse_help |
| 193 |
@out_stream.puts self |
| 194 |
exit if stdout? |
| 195 |
end |
| 196 |
|
| 197 |
def stdout? |
| 198 |
@out_stream == $stdout |
| 199 |
end |
| 200 |
end |
| 201 |
end |
| 202 |
end |