Basically done JSON formatter

This commit is contained in:
Michael Miller 2021-06-02 22:48:48 -06:00
parent fa3e9dd34d
commit 8f3a7c0a5a
No known key found for this signature in database
GPG Key ID: FB9F12F7C646A4AD
8 changed files with 188 additions and 41 deletions

View File

@ -20,28 +20,9 @@ module Spectator
io << "error"
end
# Adds the common JSON fields for all result types
# and fields specific to errored results.
private def add_json_fields(json : ::JSON::Builder)
super
json.field("exceptions") do
exception = error
json.array do
while exception
error_to_json(exception, json) if exception
exception = error.cause
end
end
end
end
# Adds a single exception to a JSON builder.
private def error_to_json(error : Exception, json : ::JSON::Builder)
json.object do
json.field("type", error.class.to_s)
json.field("message", error.message)
json.field("backtrace", error.backtrace)
end
# String used for the JSON status field.
private def json_status
"error"
end
end
end

View File

@ -165,8 +165,14 @@ module Spectator
# Creates the JSON representation of the example,
# which is just its name.
def to_json(json : ::JSON::Builder)
json.string(to_s)
def to_json(json : JSON::Builder)
json.object do
json.field("description", name? || "<anonymous>")
json.field("full_description", to_s)
json.field("file_path", location.path)
json.field("line_number", location.line)
@result.to_json(json) if @finished
end
end
# Creates a procsy from this example and the provided block.

View File

@ -1,3 +1,4 @@
require "json"
require "./expression"
require "./location"
@ -53,7 +54,7 @@ module Spectator
end
# Creates the JSON representation of the expectation.
def to_json(json : ::JSON::Builder)
def to_json(json : JSON::Builder)
json.object do
json.field("location") { @location.to_json(json) }
json.field("satisfied", satisfied?)
@ -64,7 +65,7 @@ module Spectator
end
# Adds failure information to a JSON structure.
private def failed_to_json(failed : Matchers::FailedMatchData, json : ::JSON::Builder)
private def failed_to_json(failed : Matchers::FailedMatchData, json : JSON::Builder)
json.field("failure", failed.failure_message)
json.field("values") do
json.object do

View File

@ -1,3 +1,4 @@
require "json"
require "./result"
module Spectator
@ -40,10 +41,23 @@ module Spectator
io << "fail"
end
# Adds all of the JSON fields for finished results and failed results.
private def add_json_fields(json : ::JSON::Builder)
# Creates a JSON object from the result information.
def to_json(json : JSON::Builder)
super
json.field("error", error.message)
json.field("status", json_status)
json.field("exception") do
json.object do
json.field("class", @error.class.name)
json.field("message", @error.message)
json.field("backtrace", @error.backtrace)
end
end
end
# String used for the JSON status field.
# Necessary for the error result to override the status, but nothing else from `#to_json`.
private def json_status
"failed"
end
end
end

View File

@ -1,6 +1,141 @@
require "json"
require "./formatter"
module Spectator::Formatting
# Produces a JSON document with results of the test suite.
class JSONFormatter < Formatter
# Creates the formatter.
# By default, output is sent to STDOUT.
def initialize(io = STDOUT)
@json = JSON::Builder.new(io)
end
# Begins the JSON document output.
def start(_notification)
@json.start_document
@json.start_object
@json.field("version", Spectator::VERSION)
# Start examples array.
@json.string("examples")
@json.start_array
end
# Begins an example object and adds common fields known before running the example.
def example_started(notification)
example = notification.example
@json.start_object
@json.field("description", example.name? || "<anonymous>")
@json.field("full_description", example)
@json.field("file_path", example.location.path)
@json.field("line_number", example.location.line)
end
# Adds fields to the example object for all result types known after the example completes.
def example_finished(notification)
example = notification.example
result = example.result
@json.field("run_time", result.elapsed.total_seconds)
@json.field("expectations") do
@json.array do
result.expectations.each(&.to_json(@json))
end
end
end
# Adds success-specific fields to an example object and closes it.
def example_passed(_notification)
@json.field("status", "passed")
@json.end_object # End example object.
end
# Adds pending-specific fields to an example object and closes it.
def example_pending(_notification)
@json.field("status", "pending")
@json.field("pending_message", "Not implemented") # TODO: Fetch pending message from result.
@json.end_object # End example object.
end
# Adds failure-specific fields to an example object and closes it.
def example_failed(notification)
example = notification.example
result = example.result
@json.field("status", "failed")
build_exception_object(result.error) if result.responds_to?(:error)
@json.end_object # End example object.
end
# Adds error-specific fields to an example object and closes it.
def example_error(notification)
example = notification.example
result = example.result
@json.field("status", "error")
build_exception_object(result.error) if result.responds_to?(:error)
@json.end_object # End example object.
end
# Adds an exception field and object to the JSON document.
private def build_exception_object(error)
@json.field("exception") do
@json.object do
@json.field("class", error.class.name)
@json.field("message", error.message)
@json.field("backtrace", error.backtrace)
end
end
end
# Marks the end of the examples array.
def stop
@json.end_array # Close examples array.
end
# Adds the profiling information to the document.
def dump_profile(notification)
profile = notification.profile
@json.field("profile") do
@json.object do
@json.field("examples") do
@json.array do
profile.each(&.to_json(@json))
end
end
@json.field("slowest", profile.max_of(&.result.elapsed).total_seconds)
@json.field("total", profile.time.total_seconds)
@json.field("percentage", profile.percentage)
end
end
end
# Adds the summary object to the document.
def dump_summary(notification)
report = notification.report
@json.field("summary") do
@json.object do
@json.field("duration", report.runtime.total_seconds)
@json.field("example_count", report.counts.total)
@json.field("failure_count", report.counts.fail)
@json.field("error_count", report.counts.error)
@json.field("pending_count", report.counts.pending)
end
end
totals = Components::Totals.new(report.counts)
@json.field("summary_line", totals.to_s)
end
# Ends the JSON document and flushes output.
def close
@json.end_object
@json.end_document
@json.flush
end
end
end

View File

@ -27,5 +27,11 @@ module Spectator
def to_s(io)
io << "pass"
end
# Creates a JSON object from the result information.
def to_json(json : JSON::Builder)
super
json.field("status", "passed")
end
end
end

View File

@ -35,5 +35,12 @@ module Spectator
def to_s(io)
io << "pending"
end
# Creates a JSON object from the result information.
def to_json(json : JSON::Builder)
super
json.field("status", "pending")
json.field("pending_message", "Not implemented") # TODO: Provide pending message.
end
end
end

View File

@ -1,3 +1,6 @@
require "json"
require "./expectation"
module Spectator
# Base class that represents the outcome of running an example.
# Sub-classes contain additional information specific to the type of result.
@ -29,19 +32,13 @@ module Spectator
end
# Creates a JSON object from the result information.
def to_json(json : ::JSON::Builder, example)
json.object do
add_json_fields(json, example)
def to_json(json : JSON::Builder)
json.field("run_time", @elapsed.total_seconds)
json.field("expectations") do
json.array do
@expectations.each(&.to_json(json))
end
end
end
# Adds the common fields for a result to a JSON builder.
private def add_json_fields(json : ::JSON::Builder, example)
json.field("name", example)
json.field("location", example.location)
json.field("result", to_s)
json.field("time", elapsed.total_seconds)
json.field("expectations", expectations)
end
end
end