diff --git a/src/spectator/command_line_arguments_config_source.cr b/src/spectator/command_line_arguments_config_source.cr index 5030345..510d453 100644 --- a/src/spectator/command_line_arguments_config_source.cr +++ b/src/spectator/command_line_arguments_config_source.cr @@ -22,6 +22,7 @@ module Spectator parser.on("-l", "--line LINE", "Run examples whose line matches LINE") { |line| raise NotImplementedError.new("-l") } parser.on("-p", "--profile", "Display the 10 slowest specs") { raise NotImplementedError.new("-p") } 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("--no-color", "Disable colored output") { raise NotImplementedError.new("--no-color") } diff --git a/src/spectator/formatting/json_formatter.cr b/src/spectator/formatting/json_formatter.cr new file mode 100644 index 0000000..66ce605 --- /dev/null +++ b/src/spectator/formatting/json_formatter.cr @@ -0,0 +1,107 @@ +require "json" +require "./formatter" + +module Spectator::Formatting + # Produces a JSON document containing the test results. + class JsonFormatter < Formatter + # Creates the formatter. + # By default, output is sent to STDOUT. + def initialize(io : IO = STDOUT) + @json = ::JSON::Builder.new(io) + end + + # Called when a test suite is starting to execute. + def start_suite(suite : TestSuite) + @json.start_document + @json.start_object + @json.string("examples") + @json.start_array + end + + # Called when a test suite finishes. + # The results from the entire suite are provided. + def end_suite(report : Report) + @json.end_array # examples + @json.end_object + 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) + result.to_json(@json) + end + end +end + +module Spectator + abstract class Result + def to_json(json : ::JSON::Builder) + json.object do + common_json_fields(json) + end + end + + private def common_json_fields(json : ::JSON::Builder) + json.field("name") { example.to_json(json) } + json.field("location") { example.source.to_json(json) } + json.field("result", to_s) + end + end + + abstract class FinishedResult < Result + def to_json(json : ::JSON::Builder) + json.object do + finished_json_fields(json) + end + end + + private def finished_json_fields(json) + common_json_fields(json) + json.field("time", elapsed.to_s) + json.field("expectations") { expectations.to_json(json) } + end + end + + abstract class Example < ExampleComponent + def to_json(json : ::JSON::Builder) + json.string(to_s) + end + end + + struct Source + def to_json(json : ::JSON::Builder) + json.string(to_s) + end + end + + module Expectations + class ExampleExpectations + def to_json(json : ::JSON::Builder) + json.array do + each &.to_json(json) + end + end + end + + class Expectation + def to_json(json : ::JSON::Builder) + json.object do + json.field("satisfied") { satisfied?.to_json(json) } + json.field("expected") { expected_message.to_json(json) } + json.field("actual") { actual_message.to_json(json) } + json.field("values") do + json.object do + values.each do |labeled_value| + json.field(labeled_value.label, labeled_value.value.to_s) + end + end + end + end + end + end + end +end