diff --git a/README.md b/README.md index b70bc7b..7e9ce33 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ In no particular order, features that have been implemented and are planned: - [X] RSpec/Crystal Spec default - [X] JSON - [ ] JUnit - - [ ] TAP + - [X] TAP ### How it Works diff --git a/src/spectator/command_line_arguments_config_source.cr b/src/spectator/command_line_arguments_config_source.cr index 510d453..59a3347 100644 --- a/src/spectator/command_line_arguments_config_source.cr +++ b/src/spectator/command_line_arguments_config_source.cr @@ -24,7 +24,7 @@ module Spectator parser.on("--location FILE:LINE", "Run the example at line 'LINE' in the file 'FILE', multiple allowed") { |location| raise NotImplementedError.new("--location") } parser.on("--json", "Generate JSON output") { builder.formatter = Formatting::JsonFormatter.new } parser.on("--junit_output OUTPUT_DIR", "Generate JUnit XML output") { |output_dir| raise NotImplementedError.new("--juni_output") } - parser.on("--tap", "Generate TAP output (Test Anything Protocol)") { raise NotImplementedError.new("--tap") } + parser.on("--tap", "Generate TAP output (Test Anything Protocol)") { builder.formatter = Formatting::TAPFormatter.new } parser.on("--no-color", "Disable colored output") { raise NotImplementedError.new("--no-color") } end end diff --git a/src/spectator/formatting/tap_formatter.cr b/src/spectator/formatting/tap_formatter.cr new file mode 100644 index 0000000..2a832de --- /dev/null +++ b/src/spectator/formatting/tap_formatter.cr @@ -0,0 +1,34 @@ +module Spectator::Formatting + # Formatter for the "Test Anything Protocol". + # For details, see: https://testanything.org/ + class TAPFormatter < Formatter + # Creates the formatter. + # By default, output is sent to STDOUT. + def initialize(@io : IO = STDOUT) + @index = 1 + end + + # Called when a test suite is starting to execute. + def start_suite(suite : TestSuite) + @io << "1.." + @io.puts suite.size + end + + # Called when a test suite finishes. + # The results from the entire suite are provided. + def end_suite(report : Report) + @io.puts "Bail out!" if report.remaining? + end + + # Called before a test starts. + def start_example(example : Example) + end + + # Called when a test finishes. + # The result of the test is provided. + def end_example(result : Result) + @io.puts TAPTestLine.new(@index, result) + @index += 1 + end + end +end diff --git a/src/spectator/formatting/tap_test_line.cr b/src/spectator/formatting/tap_test_line.cr new file mode 100644 index 0000000..8c0e823 --- /dev/null +++ b/src/spectator/formatting/tap_test_line.cr @@ -0,0 +1,33 @@ +module Spectator::Formatting + # Produces a formatted TAP test line. + private struct TAPTestLine + # Creates the test line. + def initialize(@index : Int32, @result : Result) + end + + # Appends the line to the output. + def to_s(io) + io << status + io << ' ' + io << @index + io << " - " + io << example + io << " # skip" if pending? + end + + # The text "ok" or "not ok" depending on the result. + private def status + @result.is_a?(FailedResult) ? "not ok" : "ok" + end + + # The example that was tested. + private def example + @result.example + end + + # Indicates whether this test was skipped. + private def pending? + @result.is_a?(PendingResult) + end + end +end