From efd4a3cae8a567a7bb6e5306fd2ff10167a0dedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 7 Dec 2020 01:09:43 +0100 Subject: [PATCH 1/4] Add usage instructions for spec runner to compiler --- src/compiler/crystal/command/spec.cr | 14 +++ src/spec.cr | 61 +++---------- src/spec/cli.cr | 128 +++++++++++++++++++++++++++ src/spec/dsl.cr | 73 --------------- 4 files changed, 154 insertions(+), 122 deletions(-) create mode 100644 src/spec/cli.cr diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index 0af8808a205a..a3652de95b82 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -8,12 +8,26 @@ # 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 OptionParser.parse(options) do |opts| opts.banner = "Usage: crystal spec [options] [files]\n\nOptions:" 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 end # Assume spec files end with ".cr" and optionally with a colon and a number diff --git a/src/spec.cr b/src/spec.cr index c8290f673d0a..1df81a9b7b8c 100644 --- a/src/spec.cr +++ b/src/spec.cr @@ -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. @@ -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 diff --git a/src/spec/cli.cr b/src/spec/cli.cr new file mode 100644 index 000000000000..fb8f67767a79 --- /dev/null +++ b/src/spec/cli.cr @@ -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 diff --git a/src/spec/dsl.cr b/src/spec/dsl.cr index bc199149a01c..f972edbdfd66 100644 --- a/src/spec/dsl.cr +++ b/src/spec/dsl.cr @@ -19,8 +19,6 @@ module Spec pending: '*', } - @@use_colors = true - # :nodoc: def self.color(str, status) if use_colors? @@ -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 @@ -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 @@ -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 @@ -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. # From 7cf1e1c85bdbba0df40ca50c133c5da0ba3e1e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 11 Dec 2020 00:31:40 +0100 Subject: [PATCH 2/4] Add runtime_options to usage line --- src/compiler/crystal/command/spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index a3652de95b82..a738283e24b8 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -16,7 +16,7 @@ class Crystal::Command private def spec compiler = new_compiler 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:" setup_simple_compiler_options compiler, opts opts.on("-h", "--help", "Show this message") do From cfc7a6f32d8cff0e0a7180db79c944c44ec4e1c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 12 Jan 2021 19:39:21 +0100 Subject: [PATCH 3/4] Remove whitespace in option flag --- src/spec/cli.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spec/cli.cr b/src/spec/cli.cr index fb8f67767a79..f0d36f45348d 100644 --- a/src/spec/cli.cr +++ b/src/spec/cli.cr @@ -64,10 +64,10 @@ module Spec 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| + 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| + 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 From 348c5f0af90dd9fae5149d0902f1c19a758fb469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 19 Jan 2021 22:53:49 +0100 Subject: [PATCH 4/4] Remove `--` from usage --- src/compiler/crystal/command/spec.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/crystal/command/spec.cr b/src/compiler/crystal/command/spec.cr index bd2e23989585..c54e4a00cc18 100644 --- a/src/compiler/crystal/command/spec.cr +++ b/src/compiler/crystal/command/spec.cr @@ -17,7 +17,7 @@ class Crystal::Command compiler = new_compiler link_flags = [] of String OptionParser.parse(options) do |opts| - opts.banner = "Usage: crystal spec [options] [files] [-- runtime_options]\n\nOptions:" + opts.banner = "Usage: crystal spec [options] [files] [runtime_options]\n\nOptions:" setup_simple_compiler_options compiler, opts opts.on("-h", "--help", "Show this message") do