Nuke formatting types to prep for new types

This commit is contained in:
Michael Miller 2021-05-06 22:11:38 -06:00
parent f3afd74dc5
commit 31d819e4c9
No known key found for this signature in database
GPG key ID: F9A0C5C65B162436
32 changed files with 0 additions and 1271 deletions

View file

@ -1,42 +0,0 @@
require "colorize"
module Spectator::Formatting
# Method for colorizing output.
module Color
extend self
# Symbols in `Colorize` representing result types and formatting types.
private COLORS = {
success: :green,
failure: :red,
error: :magenta,
pending: :yellow,
comment: :cyan,
}
# Colorizes some text with the success color.
def pass(text)
text.colorize(COLORS[:success])
end
# Colorizes some text with the failure color.
def failure(text)
text.colorize(COLORS[:failure])
end
# Colorizes some text with the error color.
def error(text)
text.colorize(COLORS[:error])
end
# Colorizes some text with the pending/skipped color.
def pending(text)
text.colorize(COLORS[:pending])
end
# Colorizes some text with the comment color.
def comment(text)
text.colorize(COLORS[:comment])
end
end
end

View file

@ -1,20 +0,0 @@
module Spectator::Formatting
# Produces a stringified comment for output.
private struct Comment(T)
# Creates the comment.
def initialize(@text : T)
end
# Appends the comment to the output.
def to_s(io)
io << '#'
io << ' '
io << @text
end
# Creates a colorized version of the comment.
def self.color(text)
Color.comment(new(text))
end
end
end

View file

@ -1,65 +0,0 @@
require "../example_group"
require "./formatter"
require "./suite_summary"
module Spectator::Formatting
# Produces an indented document-style output.
# Each nested group of examples increases the indent.
# Example names are output in a color based on their result.
class DocumentFormatter < Formatter
include SuiteSummary
private INDENT = " "
@previous_hierarchy = [] of ExampleGroup
# Creates the formatter.
# By default, output is sent to STDOUT.
def initialize(@io : IO = STDOUT)
end
# Does nothing when an example is started.
def start_example(example)
hierarchy = group_hierarchy(example)
tuple = hierarchy_diff(@previous_hierarchy, hierarchy)
print_sub_hierarchy(*tuple)
@previous_hierarchy = hierarchy
end
# Produces a single character output based on a result.
def end_example(example)
@previous_hierarchy.size.times { @io.print INDENT }
@io.puts example.result.accept(Color) { example }
end
# Produces a list of groups making up the hierarchy for an example.
private def group_hierarchy(example)
hierarchy = [] of ExampleGroup
group = example.group
while group.is_a?(ExampleGroup)
hierarchy << group if group.name?
group = group.group?
end
hierarchy.reverse
end
# Generates a difference between two hierarchies.
private def hierarchy_diff(first, second)
index = -1
diff = second.skip_while do |group|
index += 1
first.size > index && first[index] == group
end
{index, diff}
end
# Displays an indented hierarchy starting partially into the whole hierarchy.
private def print_sub_hierarchy(index, sub_hierarchy)
sub_hierarchy.each do |group|
index.times { @io.print INDENT }
@io.puts group.name
index += 1
end
end
end
end

View file

@ -1,60 +0,0 @@
require "./formatter"
require "./suite_summary"
module Spectator::Formatting
# Produces a single character for each example.
# A dot is output for each successful example (hence the name).
# Other characters are output for non-successful results.
# At the end of the test suite, a summary of failures and results is displayed.
class DotsFormatter < Formatter
include SuiteSummary
# Creates the formatter.
# By default, output is sent to STDOUT.
def initialize(@io : IO = STDOUT)
end
# Does nothing when an example is started.
def start_example(example)
# ...
end
# Produces a single character output based on a result.
def end_example(example)
@io.print example.result.accept(Character)
end
# Interface for `Result` to pick a character for output.
private module Character
extend self
# Characters for each of the result types.
private CHARACTERS = {
success: '.',
failure: 'F',
error: 'E',
pending: '*',
}
# Character output for a successful example.
def pass
Color.pass(CHARACTERS[:success])
end
# Character output for a failed example.
def failure
Color.failure(CHARACTERS[:failure])
end
# Character output for an errored example.
def error
Color.error(CHARACTERS[:error])
end
# Character output for a pending or skipped example.
def pending
Color.pending(CHARACTERS[:pending])
end
end
end
end

View file

@ -1,21 +0,0 @@
require "./failure_junit_test_case"
module Spectator::Formatting
# JUnit test case for a errored result.
private class ErrorJUnitTestCase < FailureJUnitTestCase
# Result for this test case.
private getter result
# Creates the JUnit test case.
def initialize(example : Example)
super
@result = example.result.as(ErrorResult)
end
# Adds the exception to the XML block.
private def content(xml)
xml.element("error", message: @result.error.message, type: @result.error.class)
super
end
end
end

View file

@ -1,126 +0,0 @@
module Spectator::Formatting
# Constructs a block of text containing information about a failed example.
#
# A failure block takes the form:
#
# ```text
# 1) Example name
# Failure: Reason or message
#
# Expected: value
# got: value
#
# # spec/source_spec.cr:42
# ```
private struct FailureBlock
# Creates the failure block.
# The *index* uniquely identifies the failure in the output.
# The *example* is the failed example.
def initialize(@index : Int32, @example : Example)
end
# Retrieves the failed result.
private def result
@example.result.as(FailResult)
end
# Creates the block of text describing the failure.
def to_s(io)
indent = Indent.new(io)
inner_indent = integer_length(@index) + 2 # +2 for ) and space after number.
indent.increase do
title(indent)
indent.increase(inner_indent) do
content(indent)
location(indent)
end
end
end
# Produces the title of the failure block.
# The line takes the form:
# ```text
# 1) Example name
# ```
private def title(indent)
indent.line(NumberedItem.new(@index, @example))
end
# Produces the main content of the failure block.
# Any failed expectations are displayed,
# then an error stacktrace if an error occurred.
private def content(indent)
unsatisfied_expectations(indent)
error_stacktrace(indent) if result.is_a?(ErrorResult)
end
# Produces a list of unsatisfied expectations and their values.
private def unsatisfied_expectations(indent)
result.expectations.reject(&.satisfied?).each do |expectation|
indent.line(Color.failure(LabeledText.new("Failure", expectation.failure_message)))
indent.line
indent.increase do
matcher_values(indent, expectation)
end
indent.line
end
end
# Produces the values list for an expectation
private def matcher_values(indent, expectation)
MatchDataValues.new(expectation.values).each do |pair|
colored_pair = if expectation.satisfied?
Color.pass(pair)
else
Color.failure(pair)
end
indent.line(colored_pair)
end
end
# Produces the stack trace for an errored result.
private def error_stacktrace(indent)
error = result.error
first_line = error.message.try(&.lines).try(&.first)
indent.line(Color.error(LabeledText.new("Error", first_line)))
indent.line
indent.increase do
loop do
display_error(indent, error)
if (next_error = error.cause)
error = next_error
else
break
end
end
end
indent.line
end
# Display a single error and its stacktrace.
private def display_error(indent, error) : Nil
indent.line(Color.error(LabeledText.new(error.class.to_s, error)))
indent.increase do
error.backtrace.each do |frame|
indent.line(Color.error(frame))
end
end
end
# Produces the location line of the failure block.
private def location(indent)
indent.line(Comment.color(@example.location))
end
# Gets the number of characters a positive integer spans in base 10.
private def integer_length(index)
count = 1
while index >= 10
index /= 10
count += 1
end
count
end
end
end

View file

@ -1,19 +0,0 @@
module Spectator::Formatting
# Produces a stringified command to run a failed test.
private struct FailureCommand
# Creates the failure command.
def initialize(@example : Example)
end
# Appends the command to the output.
def to_s(io)
io << "crystal spec "
io << @example.location
end
# Colorizes the command instance based on the result.
def self.color(example)
example.result.accept(Color) { new(example) }
end
end
end

View file

@ -1,39 +0,0 @@
require "./finished_junit_test_case"
module Spectator::Formatting
# JUnit test case for a failed result.
private class FailureJUnitTestCase < FinishedJUnitTestCase
# Result for this test case.
private getter result
# Creates the JUnit test case.
def initialize(example : Example)
super
@result = example.result.as(FailResult)
end
# Status string specific to the result type.
private def status : String
"FAIL"
end
# Adds the failed expectations to the XML block.
private def content(xml)
super
@result.expectations.reject(&.satisfied?).each do |expectation|
xml.element("failure", message: expectation.failure_message) do
expectation_values(expectation.values, xml)
end
end
end
# Adds the expectation values to the failure block.
private def expectation_values(labeled_values, xml)
labeled_values.each do |entry|
label = entry.first
value = entry.last
xml.text("#{label}: #{value}\n")
end
end
end
end

View file

@ -1,21 +0,0 @@
require "./junit_test_case"
module Spectator::Formatting
# Commonalities of all test cases that ran (success or failure).
private abstract class FinishedJUnitTestCase < JUnitTestCase
# Produces the test case XML element.
def to_xml(xml : ::XML::Builder)
xml.element("testcase", **full_attributes) do
content(xml)
end
end
# Attributes that go in the "testcase" XML element.
private def full_attributes
attributes.merge(
time: result.elapsed.total_seconds,
assertions: result.expectations.size
)
end
end
end

View file

@ -1,41 +0,0 @@
module Spectator::Formatting
# Provides a more human-friendly formatting for a time span.
# This produces a string with the minimum of
# microseconds, milliseconds, seconds, minutes, hours, or days.
private struct HumanTime
@string : String
# Creates the wrapper
def initialize(span)
@string = simplify(span)
end
# Produces the human-friendly string for a time span.
def to_s(io)
io << @string
end
# Does the actual work of converting a time span to string.
private def simplify(span)
millis = span.total_milliseconds
return "#{(millis * 1000).round.to_i} microseconds" if millis < 1
seconds = span.total_seconds
return "#{millis.round(2)} milliseconds" if seconds < 1
return "#{seconds.round(2)} seconds" if seconds < 60
int_seconds = seconds.to_i
minutes = int_seconds // 60
int_seconds %= 60
return sprintf("%i:%02i", minutes, int_seconds) if minutes < 60
hours = minutes // 60
minutes %= 60
return sprintf("%i:%02i:%02i", hours, minutes, int_seconds) if hours < 24
days = hours // 24
hours %= 24
sprintf("%i days %i:%02i:%02i", days, hours, minutes, int_seconds)
end
end
end

View file

@ -1,50 +0,0 @@
module Spectator::Formatting
# Tracks indentation for text output.
# To use, create an instance and call `#increase` when a block should be indented.
# The `#increase` method yields, so additional `#increase` and `#line` methods can be called.
# Then call `#line` to produce a line of text at the current indent.
# ```
# indent = Indent.new(io)
# indent.increase do
# indent.line("Text")
# indent.increase do
# indent.line("More text")
# end
# end
# ```
private struct Indent
# Default number of spaces to indent by.
INDENT_SIZE = 2
# Creates the identation tracker.
# The *io* is the stream to output to.
# The *indent_size* is how much (number of spaces) to indent at each level.
# The *initial_indent* is what the ident should be set to.
def initialize(@io : IO, @indent_size = INDENT_SIZE, inital_indent @indent = 0)
end
# Indents the text and yields.
def increase(&block)
increase(@indent_size, &block)
end
# Indents the text by a specified amount and yields.
def increase(amount) : Nil
@indent += amount
yield
ensure
@indent -= amount
end
# Produces an empty line.
def line
@io.puts
end
# Produces a line of indented text.
def line(text)
@indent.times { @io << ' ' }
@io.puts text
end
end
end

View file

@ -1,95 +0,0 @@
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.
# The *profile* value is not nil when profiling results should be displayed.
def end_suite(report : Report, profile : Profile?)
@json.end_array # examples
totals(report)
timing(report)
profile(profile) if profile
@json.field("result", report.failed? ? "fail" : "success")
@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(example : Example)
example.result.to_json(@json, example)
end
# Adds the totals section of the document.
private def totals(report)
@json.field("totals") do
@json.object do
@json.field("examples", report.example_count)
@json.field("success", report.successful_count)
@json.field("fail", report.failed_count)
@json.field("error", report.error_count)
@json.field("pending", report.pending_count)
@json.field("remaining", report.remaining_count)
end
end
end
# Adds the timings section of the document.
private def timing(report)
@json.field("timing") do
@json.object do
@json.field("runtime", report.runtime.total_seconds)
@json.field("examples", report.example_runtime.total_seconds)
@json.field("overhead", report.overhead_time.total_seconds)
end
end
end
# Adds the profile information to the document.
private def profile(profile)
@json.field("profile") do
@json.object do
@json.field("count", profile.size)
@json.field("time", profile.total_time.total_seconds)
@json.field("percentage", profile.percentage)
@json.field("results") do
@json.array do
profile.each do |result|
profile_entry(result)
end
end
end
end
end
end
# Adds a profile entry to the document.
private def profile_entry(example)
@json.object do
@json.field("example", example)
@json.field("time", example.result.elapsed.total_seconds)
@json.field("location", example.location)
end
end
end
end

View file

@ -1,63 +0,0 @@
require "xml"
module Spectator::Formatting
# Formatter for producing a JUnit XML report.
class JUnitFormatter < Formatter
# Name of the JUnit output file.
private JUNIT_XML_FILE = "output.xml"
# Name of the top-level test suites block.
private NAME = "Spec"
# Creates the formatter.
# By default, output is sent to STDOUT.
def initialize(output_dir : String)
path = File.join(output_dir, JUNIT_XML_FILE)
@io = File.open(path, "w")
@xml = XML::Builder.new(@io)
end
# Called when a test suite is starting to execute.
def start_suite(suite : TestSuite)
@xml.start_document(encoding: "UTF-8")
end
# Called when a test suite finishes.
# The results from the entire suite are provided.
# The *profile* value does nothing for this formatter.
def end_suite(report : Report, profile : Profile?)
test_suites_block(report)
@xml.end_document
@xml.flush
@io.close
end
# Called before a test starts.
def start_example(example : Example)
end
# Called when a test finishes.
# The result of the test is provided by *example*.
def end_example(example : Example)
end
# Creates the "testsuites" block in the XML.
private def test_suites_block(report)
@xml.element("testsuites",
tests: report.example_count,
failures: report.failed_count,
errors: report.error_count,
time: report.runtime.total_seconds,
name: NAME) do
add_test_suites(report)
end
end
# Adds all of the individual test suite blocks.
private def add_test_suites(report)
report.group_by(&.location.path).each do |path, examples|
JUnitTestSuite.new(path, examples).to_xml(@xml)
end
end
end
end

View file

@ -1,49 +0,0 @@
module Spectator::Formatting
# Base type for all JUnit test case results.
private abstract class JUnitTestCase
# Creates the JUnit test case.
def initialize(@example : Example)
end
# Produces the test case XML element.
def to_xml(xml : ::XML::Builder)
xml.element("testcase", **attributes) do
content(xml)
end
end
# Attributes that go in the "testcase" XML element.
private def attributes
{
name: example,
status: status,
classname: classname,
}
end
# Result to pull values from.
private abstract def result
# Status string specific to the result type.
private abstract def status : String
# Example for this test case.
private getter example : Example
# Adds additional content to the "testcase" XML block.
# Override this to add more content.
private def content(xml)
# ...
end
# Java-ified class name created from the spec.
private def classname
path = example.location.path
file = File.basename(path)
ext = File.extname(file)
name = file[0...-(ext.size)]
dir = path[0...-(file.size + 1)]
{dir.gsub('/', '.').underscore, name.camelcase}.join('.')
end
end
end

View file

@ -1,73 +0,0 @@
module Spectator::Formatting
# Mapping of a single spec file into a JUnit test suite.
private struct JUnitTestSuite
# Creates the JUnit test suite.
# The *path* should be the file that all results are from.
# The *examples* is a subset of all examples that share the path.
def initialize(@path : String, examples : Array(Example))
@report = Report.new(examples)
end
# Generates the XML for the test suite (and all nested test cases).
def to_xml(xml : ::XML::Builder)
xml.element("testsuite",
tests: @report.example_count,
failures: @report.failed_count,
errors: @report.error_count,
skipped: @report.pending_count,
time: @report.runtime.total_seconds,
name: name,
package: package) do
add_test_cases(xml)
end
end
# Adds the test case elements to the XML.
private def add_test_cases(xml)
@report.each do |example|
test_case = example.result.accept(JUnitTestCaseSelector) { example }
test_case.to_xml(xml)
end
end
# Java-ified name of the test suite.
private def name
file = File.basename(@path)
ext = File.extname(file)
name = file[0...-(ext.size)]
name.camelcase
end
# Java-ified package (path) of the test suite.
private def package
file = File.basename(@path)
dir = @path[0...-(file.size + 1)]
dir.gsub('/', '.').underscore
end
# Selector for creating a JUnit test case based on a result.
private module JUnitTestCaseSelector
extend self
# Creates a successful JUnit test case.
def pass(example)
SuccessfulJUnitTestCase.new(example)
end
# Creates a failure JUnit test case.
def failure(example)
FailureJUnitTestCase.new(example)
end
# Creates an error JUnit test case.
def error(example)
ErrorJUnitTestCase.new(example)
end
# Creates a skipped JUnit test case.
def pending(example)
SkippedJUnitTestCase.new(example)
end
end
end
end

View file

@ -1,15 +0,0 @@
module Spectator::Formatting
# Produces a stringified message with a prefix.
private struct LabeledText(T)
# Creates the labeled text.
def initialize(@label : String, @text : T)
end
# Appends the message to the output.
def to_s(io)
io << @label
io << ": "
io << @text
end
end
end

View file

@ -1,16 +0,0 @@
module Spectator::Formatting
# Produces the timing line in a profile block.
# This contains the length of time, and the example's location.
private struct LocationTiming
# Creates the location timing line.
def initialize(@span : Time::Span, @location : Location)
end
# Appends the location timing information to the output.
def to_s(io)
io << HumanTime.new(@span).colorize.bold
io << ' '
io << @location
end
end
end

View file

@ -1,16 +0,0 @@
module Spectator::Formatting
# A single labeled value from the `Spectator::Matchers::MatchData#value` method.
private struct MatchDataValuePair
# Creates the pair formatter.
def initialize(@key : Symbol, @value : String, @padding : Int32)
end
# Appends the pair to the output.
def to_s(io)
@padding.times { io << ' ' }
io << @key
io << ": "
io << @value
end
end
end

View file

@ -1,24 +0,0 @@
module Spectator::Formatting
# Produces a `MatchDataValuePair` for each key-value pair
# from `Spectator::Matchers::MatchData#values`.
private struct MatchDataValues
include Enumerable(Tuple(Symbol, String))
@max_key_length : Int32
# Creates the values mapper.
def initialize(@values : Array(Tuple(Symbol, String)))
@max_key_length = @values.map(&.first.to_s.size).max
end
# Yields pairs that can be printed to output.
def each
@values.each do |labeled_value|
key = labeled_value.first
key_length = key.to_s.size
padding = @max_key_length - key_length
yield MatchDataValuePair.new(key, labeled_value.last, padding)
end
end
end
end

View file

@ -1,16 +0,0 @@
module Spectator::Formatting
# Produces a stringified value with a numerical prefix.
private struct NumberedItem(T)
# Creates the numbered item.
def initialize(@number : Int32, @text : T)
end
# Appends the numbered item to the output.
def to_s(io)
io << @number
io << ')'
io << ' '
io << @text
end
end
end

View file

@ -1,28 +0,0 @@
module Spectator::Formatting
# Contents of a profile block.
private struct ProfileBlock
# Creates the block.
def initialize(@profile : Profile)
end
# Appends the block to the output.
def to_s(io)
io.puts(ProfileSummary.new(@profile))
indent = Indent.new(io)
indent.increase do
@profile.each do |example|
entry(indent, example)
end
end
end
# Adds a result entry to the output.
private def entry(indent, example)
indent.line(example)
indent.increase do
indent.line(LocationTiming.new(example.result.elapsed, example.location))
end
end
end
end

View file

@ -1,29 +0,0 @@
module Spectator::Formatting
# Top line of a profile block which gives a summary.
private struct ProfileSummary
# Creates the summary line.
def initialize(@profile : Profile)
end
# Appends the summary to the output.
def to_s(io)
io << "Top "
io << @profile.size
io << " slowest examples ("
io << human_time
io << ", "
io.printf("%.2f", percentage)
io << "% of total time):"
end
# Creates a human-friendly string for the total time.
private def human_time
HumanTime.new(@profile.total_time)
end
# Percentage (0 to 100) of total time.
private def percentage
@profile.percentage * 100
end
end
end

View file

@ -1,14 +0,0 @@
module Spectator::Formatting
# Text displayed when using a random seed.
private struct RandomSeedText
# Creates the text object.
def initialize(@seed : UInt64)
end
# Appends the command to the output.
def to_s(io)
io << "Randomized with seed "
io << @seed
end
end
end

View file

@ -1,15 +0,0 @@
module Spectator::Formatting
# Text displayed when fail-fast is enabled and tests were skipped.
private struct RemainingText
# Creates the text object.
def initialize(@count : Int32)
end
# Appends the command to the output.
def to_s(io)
io << "Text execution aborted (fail-fast) - "
io << @count
io << " examples were omitted."
end
end
end

View file

@ -1,24 +0,0 @@
module Spectator::Formatting
# Produces a stringified time span for the runtime.
private struct Runtime
# Creates the runtime instance.
def initialize(@runtime : Time::Span)
end
# Appends the runtime to the output.
# The text will be formatted as follows,
# depending on the length of time:
# ```text
# Finished in ## microseconds
# Finished in ## milliseconds
# Finished in ## seconds
# Finished in #:##
# Finished in #:##:##
# Finished in # days #:##:##
# ```
def to_s(io)
io << "Finished in "
io << HumanTime.new(@runtime)
end
end
end

View file

@ -1,27 +0,0 @@
module Spectator::Formatting
# Formatter that outputs nothing.
# Useful for testing and larger automated processes.
class SilentFormatter < Formatter
# Called when a test suite is starting to execute.
def start_suite(suite : TestSuite)
# ... crickets ...
end
# Called when a test suite finishes.
# The results from the entire suite are provided.
def end_suite(report : Report, profile : Profile?)
# ... crickets ...
end
# Called before a test starts.
def start_example(example : Example)
# ... crickets ...
end
# Called when a test finishes.
# The result of the test is provided by *example*.
def end_example(example : Example)
# ... crickets ...
end
end
end

View file

@ -1,26 +0,0 @@
require "./junit_test_case"
module Spectator::Formatting
# JUnit test case for a pending result.
private class SkippedJUnitTestCase < JUnitTestCase
# Result for this test case.
private getter result
# Creates the JUnit test case.
def initialize(example : Example)
super
@result = example.result.as(PendingResult)
end
# Status string specific to the result type.
private def status : String
"TODO"
end
# Adds the skipped tag to the XML block.
private def content(xml)
super
xml.element("skipped")
end
end
end

View file

@ -1,50 +0,0 @@
module Spectator::Formatting
# Produces a stringified stats counter from result totals.
private struct StatsCounter
# Creates the instance with each of the counters.
private def initialize(@examples : Int32, @failures : Int32, @errors : Int32, @pending : Int32, @failed : Bool)
end
# Creates the instance from the counters in a report.
def initialize(report)
initialize(report.example_count, report.failed_count, report.error_count, report.pending_count, report.failed?)
end
# Produces a colorized formatting for the stats,
# depending on the number of each type of result.
def color
if @errors > 0
Color.error(self)
elsif @failed || @failures > 0
Color.failure(self)
elsif @pending > 0
Color.pending(self)
else
Color.pass(self)
end
end
# Appends the counters to the output.
# The format will be:
# ```text
# # examples, # failures, # errors, # pending
# ```
def to_s(io)
stats.each_with_index do |stat, value, index|
io << ", " if index > 0
io << value
io << ' '
io << stat
end
end
private def stats
{
examples: @examples,
failures: @failures,
errors: @errors,
pending: @pending,
}
end
end
end

View file

@ -1,18 +0,0 @@
module Spectator::Formatting
# JUnit test case for a successful result.
private class SuccessfulJUnitTestCase < FinishedJUnitTestCase
# Result for this test case.
private getter result
# Creates the JUnit test case.
def initialize(example : Example)
super
@result = example.result.as(PassResult)
end
# Status string specific to the result type.
private def status : String
"PASS"
end
end
end

View file

@ -1,78 +0,0 @@
module Spectator::Formatting
# Mix-in for producing a human-readable summary of a test suite.
module SuiteSummary
# Does nothing when starting a test suite.
def start_suite(suite)
# ...
end
# Produces the summary of test suite from a report.
# A block describing each failure is displayed.
# At the end, the totals and runtime are printed.
# The *profile* value is not nil when profiling results should be displayed.
def end_suite(report, profile : Profile?)
if report.example_count > 0
@io.puts if is_a?(DotsFormatter)
@io.puts
end
failures(report.failures) if report.failed_count > 0
profile(profile) if profile
stats(report)
remaining(report) if report.remaining?
if report.failed?
if report.examples_ran > 0
failure_commands(report.failures)
else
@io.puts Color.failure("Failing because no tests were run (fail-blank)")
end
end
end
# Produces the failure section of the summary.
# This has a "Failures" title followed by a block for each failure.
private def failures(failures)
@io.puts "Failures:"
@io.puts
failures.each_with_index do |example, index|
@io.puts FailureBlock.new(index + 1, example)
end
end
# Produces the profiling section of the summary.
private def profile(profile)
@io.puts ProfileBlock.new(profile)
end
# Produces the statistical section of the summary.
# This contains how long the suite took to run
# and the counts for the results (total, failures, errors, and pending).
private def stats(report)
@io.puts Runtime.new(report.runtime)
@io.puts StatsCounter.new(report).color
if (seed = report.random_seed?)
@io.puts
@io.puts RandomSeedText.new(seed)
end
end
# Produces the skipped tests text if fail-fast is enabled and tests were omitted.
private def remaining(report)
text = RemainingText.new(report.remaining_count)
@io.puts Color.failure(text)
end
# Produces the failure commands section of the summary.
# This provides a set of commands the user can run
# to test just the examples that failed.
private def failure_commands(failures)
@io.puts
@io.puts "Failed examples:"
@io.puts
failures.each do |example|
@io << FailureCommand.color(example)
@io << ' '
@io.puts Comment.color(example)
end
end
end
end

View file

@ -1,58 +0,0 @@
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.
# The *profile* value is not nil when profiling results should be displayed.
def end_suite(report : Report, profile : Profile?)
@io.puts "Bail out!" if report.remaining?
profile(profile) if profile
end
# Called before a test starts.
def start_example(example : Example)
end
# Called when a test finishes.
# The result of the test is provided by *example*.
def end_example(example : Example)
@io.puts TAPTestLine.new(@index, example)
@index += 1
end
# Displays profiling information.
private def profile(profile)
@io.puts(Comment.new(ProfileSummary.new(profile)))
indent = Indent.new(@io)
indent.increase do
profile.each do |example|
profile_entry(indent, example)
end
end
end
# Adds a profile result entry to the output.
private def profile_entry(indent, example)
@io << "# "
indent.line(example)
indent.increase do
@io << "# "
indent.line(LocationTiming.new(example.result.elapsed, example.location))
end
end
end
end

View file

@ -1,33 +0,0 @@
module Spectator::Formatting
# Produces a formatted TAP test line.
private struct TAPTestLine
# Creates the test line.
def initialize(@index : Int32, @example : Example)
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?(FailResult) ? "not ok" : "ok"
end
# The result of running the example.
private def result
@example.result
end
# Indicates whether this test was skipped.
private def pending?
result.is_a?(PendingResult)
end
end
end