Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add usage instructions for spec runner to compiler #10046

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/compiler/crystal/command/spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,28 @@
# directory, which usually is just `require "spec"` but could
# be anything else (for example the `minitest` shard).

# Gain access to OptionParser for spec runner to include it in the usage
# instructions.
require "spec/cli"

class Crystal::Command
private def spec
compiler = new_compiler
link_flags = [] of String
OptionParser.parse(options) do |opts|
opts.banner = "Usage: crystal spec [options] [files]\n\nOptions:"
opts.banner = "Usage: crystal spec [options] [files] [-- runtime_options]\n\nOptions:"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime_options can be passed before the file, there is no need to use -- to split them. Wouldn't that be the preferred way to advertise them?

In that line of thought I would call the "Spec runner additional options" or somehing like that.
I find it confusing that they are presented as "Runtime options"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the -- can be removed. I'd still leave it as [options] [files] [runtime_options] even though they can be mixed. Don't think there's a better way to do this and this makes at least sense from a structural PoV.

Naming is particularly hard with this one. I'm not convinced Spec runner additional options improves it, though. I would have more questions about that than Runtime options which is really just a factual description. Those are the options evaluated at runtime by the spec runner, the others are compiler options.

setup_simple_compiler_options compiler, opts

opts.on("-h", "--help", "Show this message") do
puts opts
puts

runtime_options = Spec.option_parser
runtime_options.banner = "Runtime options (passed to spec runner):"
puts runtime_options
exit
end

opts.on("--link-flags FLAGS", "Additional flags to pass to the linker") do |some_link_flags|
link_flags << some_link_flags
end
Expand Down
61 changes: 12 additions & 49 deletions src/spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec/dsl"
require "./spec/cli"

# Crystal's built-in testing library. It provides a structure for writing executable examples
# of how your code should behave. A domain specific language allows you to write them in a way similar to natural language.
Expand Down Expand Up @@ -94,61 +95,23 @@ end

Colorize.on_tty_only!

OptionParser.parse do |opts|
opts.banner = "crystal spec runner"
opts.on("-e ", "--example STRING", "run examples whose full nested names include STRING") do |pattern|
Spec.pattern = pattern
end
opts.on("-l ", "--line LINE", "run examples whose line matches LINE") do |line|
Spec.line = line.to_i
end
opts.on("-p", "--profile", "Print the 10 slowest specs") do
Spec.slowest = 10
end
opts.on("--fail-fast", "abort the run on first failure") do
Spec.fail_fast = true
end
opts.on("--location file:line", "run example at line 'line' in file 'file', multiple allowed") do |location|
if location =~ /\A(.+?)\:(\d+)\Z/
Spec.add_location $1, $2.to_i
else
STDERR.puts "location #{location} must be file:line"
exit 1
end
end
opts.on("--tag TAG", "run examples with the specified TAG, or exclude examples by adding ~ before the TAG.") do |tag|
Spec.add_tag tag
end
opts.on("--order MODE", "run examples in random order by passing MODE as 'random' or to a specific seed by passing MODE as the seed value") do |mode|
if mode == "default" || mode == "random"
Spec.order = mode
elsif seed = mode.to_u64?
Spec.order = seed
else
abort("order must be either 'default', 'random', or a numeric seed value")
end
end
opts.on("--junit_output OUTPUT_PATH", "generate JUnit XML output within the given OUTPUT_PATH") do |output_path|
junit_formatter = Spec::JUnitFormatter.file(Path.new(output_path))
# :nodoc:
#
# Implement formatter configuration.
def Spec.configure_formatter(formatter, output_path = nil)
case formatter
when "junit"
junit_formatter = Spec::JUnitFormatter.file(Path.new(output_path.not_nil!))
Spec.add_formatter(junit_formatter)
end
opts.on("-h", "--help", "show this help") do |pattern|
puts opts
exit
end
opts.on("-v", "--verbose", "verbose output") do
when "verbose"
Spec.override_default_formatter(Spec::VerboseFormatter.new)
end
opts.on("--tap", "Generate TAP output (Test Anything Protocol)") do
when "tap"
Spec.override_default_formatter(Spec::TAPFormatter.new)
end
opts.on("--no-color", "Disable colored output") do
Spec.use_colors = false
end
opts.unknown_args do |args|
end
end

Spec.option_parser.parse(ARGV)

unless ARGV.empty?
STDERR.puts "Error: unknown argument '#{ARGV.first}'"
exit 1
Expand Down
128 changes: 128 additions & 0 deletions src/spec/cli.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
require "option_parser"

# This file is included in the compiler to add usage instructions for the
# spec runner on `crystal spec --help`.

module Spec
# :nodoc:
class_property? use_colors = true

# :nodoc:
class_property pattern : Regex?

# :nodoc:
class_property line : Int32?

# :nodoc:
class_property slowest : Int32?

# :nodoc:
class_property? fail_fast = false

# :nodoc:
class_property? focus = false

# :nodoc:
def self.add_location(file, line)
locations = @@locations ||= {} of String => Array(Int32)
lines = locations[File.expand_path(file)] ||= [] of Int32
lines << line
end

# :nodoc:
def self.add_tag(tag)
if anti_tag = tag.lchop?('~')
(@@anti_tags ||= Set(String).new) << anti_tag
else
(@@tags ||= Set(String).new) << tag
end
end

# :nodoc:
class_getter randomizer_seed : UInt64?
class_getter randomizer : Random::PCG32?

# :nodoc:
def self.order=(mode)
seed =
case mode
when "default"
nil
when "random"
Random::Secure.rand(1..99999).to_u64 # 5 digits or less for simplicity
when UInt64
mode
else
raise ArgumentError.new("order must be either 'default', 'random', or a numeric seed value")
end

@@randomizer_seed = seed
@@randomizer = seed ? Random::PCG32.new(seed) : nil
end

# :nodoc:
class_property option_parser : OptionParser = begin
OptionParser.new do |opts|
opts.banner = "crystal spec runner"
opts.on("-e", "--example STRING", "run examples whose full nested names include STRING") do |pattern|
Spec.pattern = Regex.new(Regex.escape(pattern))
end
opts.on("-l", "--line LINE", "run examples whose line matches LINE") do |line|
Spec.line = line.to_i
end
opts.on("-p", "--profile", "Print the 10 slowest specs") do
Spec.slowest = 10
end
opts.on("--fail-fast", "abort the run on first failure") do
Spec.fail_fast = true
end
opts.on("--location file:line", "run example at line 'line' in file 'file', multiple allowed") do |location|
if location =~ /\A(.+?)\:(\d+)\Z/
Spec.add_location $1, $2.to_i
else
STDERR.puts "location #{location} must be file:line"
exit 1
end
end
opts.on("--tag TAG", "run examples with the specified TAG, or exclude examples by adding ~ before the TAG.") do |tag|
Spec.add_tag tag
end
opts.on("--order MODE", "run examples in random order by passing MODE as 'random' or to a specific seed by passing MODE as the seed value") do |mode|
if mode == "default" || mode == "random"
Spec.order = mode
elsif seed = mode.to_u64?
Spec.order = seed
else
abort("order must be either 'default', 'random', or a numeric seed value")
end
end
opts.on("--junit_output OUTPUT_PATH", "generate JUnit XML output within the given OUTPUT_PATH") do |output_path|
configure_formatter("junit", output_path)
end
opts.on("-h", "--help", "show this help") do |pattern|
puts opts
exit
end
opts.on("-v", "--verbose", "verbose output") do
configure_formatter("verbose")
end
opts.on("--tap", "Generate TAP output (Test Anything Protocol)") do
configure_formatter("tap")
end
opts.on("--no-color", "Disable colored output") do
Spec.use_colors = false
end
opts.unknown_args do |args|
end
end
end

# :nodoc:
#
# Blank implementation to reduce the interface of spec's option parser for
# inclusion in the compiler. This avoids depending on more of `Spec`
# module.
# The real implementation in `../spec.cr` overrides this for actual use.
def self.configure_formatter(formatter, output_path = nil)
end
end
73 changes: 0 additions & 73 deletions src/spec/dsl.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ module Spec
pending: '*',
}

@@use_colors = true

# :nodoc:
def self.color(str, status)
if use_colors?
Expand All @@ -30,15 +28,6 @@ module Spec
end
end

# :nodoc:
def self.use_colors?
@@use_colors
end

# :nodoc:
def self.use_colors=(@@use_colors)
end

# :nodoc:
class SpecError < Exception
getter file : String
Expand Down Expand Up @@ -69,46 +58,6 @@ module Spec
finish_run
end

# :nodoc:
class_getter randomizer_seed : UInt64?
class_getter randomizer : Random::PCG32?

# :nodoc:
def self.order=(mode)
seed =
case mode
when "default"
nil
when "random"
Random::Secure.rand(1..99999).to_u64 # 5 digits or less for simplicity
when UInt64
mode
else
raise ArgumentError.new("order must be either 'default', 'random', or a numeric seed value")
end

@@randomizer_seed = seed
@@randomizer = seed ? Random::PCG32.new(seed) : nil
end

# :nodoc:
def self.pattern=(pattern)
@@pattern = Regex.new(Regex.escape(pattern))
end

# :nodoc:
def self.line=(@@line : Int32)
end

# :nodoc:
def self.slowest=(@@slowest : Int32)
end

# :nodoc:
def self.slowest
@@slowest
end

# :nodoc:
def self.to_human(span : Time::Span)
total_milliseconds = span.total_milliseconds
Expand All @@ -130,22 +79,6 @@ module Spec
"#{minutes}:#{seconds < 10 ? "0" : ""}#{seconds} minutes"
end

# :nodoc:
def self.add_location(file, line)
locations = @@locations ||= {} of String => Array(Int32)
lines = locations[File.expand_path(file)] ||= [] of Int32
lines << line
end

# :nodoc:
def self.add_tag(tag)
if anti_tag = tag.lchop?('~')
(@@anti_tags ||= Set(String).new) << anti_tag
else
(@@tags ||= Set(String).new) << tag
end
end

record SplitFilter, remainder : Int32, quotient : Int32

@@split_filter : SplitFilter? = nil
Expand All @@ -159,12 +92,6 @@ module Spec
end
end

# :nodoc:
class_property? fail_fast = false

# :nodoc:
class_property? focus = false

# Instructs the spec runner to execute the given block
# before each spec in the spec suite.
#
Expand Down