mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Initial work on summary output
This commit is contained in:
parent
ee294a3ec2
commit
3ecb04e293
10 changed files with 307 additions and 0 deletions
8
src/spectator/formatting/components.cr
Normal file
8
src/spectator/formatting/components.cr
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
require "./components/*"
|
||||||
|
|
||||||
|
module Spectator::Formatting
|
||||||
|
# Namespace for snippets of text displayed in console output.
|
||||||
|
# These types are typically constructed and have `#to_s` called.
|
||||||
|
module Components
|
||||||
|
end
|
||||||
|
end
|
18
src/spectator/formatting/components/comment.cr
Normal file
18
src/spectator/formatting/components/comment.cr
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
struct Comment(T)
|
||||||
|
private COLOR = :cyan
|
||||||
|
|
||||||
|
def initialize(@content : T)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.colorize(content)
|
||||||
|
new(content).colorize(COLOR)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << '#'
|
||||||
|
io << ' '
|
||||||
|
io << @content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
struct ExampleFilterCommand
|
||||||
|
def initialize(@example : Example)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << "crystal spec "
|
||||||
|
io << @example.location
|
||||||
|
io << ' '
|
||||||
|
io << Comment.colorize(@example.to_s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
32
src/spectator/formatting/components/failure_block.cr
Normal file
32
src/spectator/formatting/components/failure_block.cr
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
require "../../example"
|
||||||
|
require "./comment"
|
||||||
|
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
struct FailureBlock
|
||||||
|
private INDENT = 2
|
||||||
|
|
||||||
|
def initialize(@example : Example, @index : Int32)
|
||||||
|
@result = @example.result.as(FailResult)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
2.times { io << ' ' }
|
||||||
|
io << @index
|
||||||
|
io << ')'
|
||||||
|
io << ' '
|
||||||
|
io.puts @example
|
||||||
|
indent = INDENT + index_digit_count + 2
|
||||||
|
indent.times { io << ' ' }
|
||||||
|
io << "Failure: ".colorize(:red)
|
||||||
|
io.puts @result.error.message
|
||||||
|
io.puts
|
||||||
|
# TODO: Expectation values
|
||||||
|
indent.times { io << ' ' }
|
||||||
|
io.puts Comment.colorize(@example.location) # TODO: Use location of failed expectation.
|
||||||
|
end
|
||||||
|
|
||||||
|
private def index_digit_count
|
||||||
|
(Math.log(@index.to_f + 1) / Math.log(10)).ceil.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
src/spectator/formatting/components/pending_block.cr
Normal file
28
src/spectator/formatting/components/pending_block.cr
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
require "../../example"
|
||||||
|
require "./comment"
|
||||||
|
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
struct PendingBlock
|
||||||
|
private INDENT = 2
|
||||||
|
|
||||||
|
def initialize(@example : Example, @index : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
2.times { io << ' ' }
|
||||||
|
io << @index
|
||||||
|
io << ')'
|
||||||
|
io << ' '
|
||||||
|
io.puts @example
|
||||||
|
indent = INDENT + index_digit_count + 2
|
||||||
|
indent.times { io << ' ' }
|
||||||
|
io.puts Comment.colorize("No reason given") # TODO: Get reason from result.
|
||||||
|
indent.times { io << ' ' }
|
||||||
|
io.puts Comment.colorize(@example.location) # TODO: Pending result could be triggered from another location.
|
||||||
|
end
|
||||||
|
|
||||||
|
private def index_digit_count
|
||||||
|
(Math.log(@index.to_f + 1) / Math.log(10)).ceil.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
69
src/spectator/formatting/components/runtime.cr
Normal file
69
src/spectator/formatting/components/runtime.cr
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
# Presents a human readable time span.
|
||||||
|
struct Runtime
|
||||||
|
# Creates the component.
|
||||||
|
def initialize(@span : Time::Span)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Appends the elapsed time to the output.
|
||||||
|
# The text will be formatted as follows, depending on the magnitude:
|
||||||
|
# ```text
|
||||||
|
# ## microseconds
|
||||||
|
# ## milliseconds
|
||||||
|
# ## seconds
|
||||||
|
# #:##
|
||||||
|
# #:##:##
|
||||||
|
# # days #:##:##
|
||||||
|
# ```
|
||||||
|
def to_s(io)
|
||||||
|
millis = @span.total_milliseconds
|
||||||
|
return format_micro(io, millis * 1000) if millis < 1
|
||||||
|
|
||||||
|
seconds = @span.total_seconds
|
||||||
|
return format_millis(io, millis) if seconds < 1
|
||||||
|
return format_seconds(io, seconds) if seconds < 60
|
||||||
|
|
||||||
|
minutes, seconds = seconds.divmod(60)
|
||||||
|
return format_minutes(io, minutes, seconds) if minutes < 60
|
||||||
|
|
||||||
|
hours, minutes = minutes.divmod(60)
|
||||||
|
return format_hours(io, hours, minutes, seconds) if hours < 24
|
||||||
|
|
||||||
|
days, hours = hours.divmod(24)
|
||||||
|
format_days(io, days, hours, minutes, seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Formats for microseconds.
|
||||||
|
private def format_micro(io, micros)
|
||||||
|
io << micros.round.to_i
|
||||||
|
io << " microseconds"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Formats for milliseconds.
|
||||||
|
private def format_millis(io, millis)
|
||||||
|
io << millis.round(2)
|
||||||
|
io << " milliseconds"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Formats for seconds.
|
||||||
|
private def format_seconds(io, seconds)
|
||||||
|
io << seconds.round(2)
|
||||||
|
io << " seconds"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Formats for minutes.
|
||||||
|
private def format_minutes(io, minutes, seconds)
|
||||||
|
io.printf("%i:%02i", minutes, seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Formats for hours.
|
||||||
|
private def format_hours(io, hours, minutes, seconds)
|
||||||
|
io.printf("%i:%02i:%02i", hours, minutes, seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Formats for days.
|
||||||
|
private def format_days(io, days, hours, minutes, seconds)
|
||||||
|
io.printf("%i days %i:%02i:%02i", days, hours, minutes, seconds)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
38
src/spectator/formatting/components/summary_block.cr
Normal file
38
src/spectator/formatting/components/summary_block.cr
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
require "./example_filter_command"
|
||||||
|
require "./runtime"
|
||||||
|
require "./totals"
|
||||||
|
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
# Summary information displayed at the end of a run.
|
||||||
|
struct SummaryBlock
|
||||||
|
def initialize(@report : Report)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
timing_line(io)
|
||||||
|
totals_line(io)
|
||||||
|
|
||||||
|
unless (failures = @report.failures).empty?
|
||||||
|
io.puts
|
||||||
|
failures_block(io, failures)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def timing_line(io)
|
||||||
|
io << "Finished in "
|
||||||
|
io.puts Runtime.new(@report.runtime)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def totals_line(io)
|
||||||
|
io.puts Totals.colorize(@report.counts)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def failures_block(io, failures)
|
||||||
|
io.puts "Failed examples:"
|
||||||
|
io.puts
|
||||||
|
failures.each do |failure|
|
||||||
|
io.puts ExampleFilterCommand.new(failure).colorize(:red)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
35
src/spectator/formatting/components/totals.cr
Normal file
35
src/spectator/formatting/components/totals.cr
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
module Spectator::Formatting::Components
|
||||||
|
struct Totals
|
||||||
|
def initialize(@examples : Int32, @failures : Int32, @errors : Int32, @pending : Int32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(counts)
|
||||||
|
@examples = counts.run
|
||||||
|
@failures = counts.fail
|
||||||
|
@errors = counts.error
|
||||||
|
@pending = counts.pending
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.colorize(counts)
|
||||||
|
totals = new(counts)
|
||||||
|
if counts.fail > 0
|
||||||
|
totals.colorize(:red)
|
||||||
|
elsif counts.pending > 0
|
||||||
|
totals.colorize(:yellow)
|
||||||
|
else
|
||||||
|
totals.colorize(:green)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io)
|
||||||
|
io << @examples
|
||||||
|
io << " examples, "
|
||||||
|
io << @failures
|
||||||
|
io << " failures, "
|
||||||
|
io << @errors
|
||||||
|
io << " errors, "
|
||||||
|
io << @pending
|
||||||
|
io << " pending"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,15 +1,21 @@
|
||||||
require "colorize"
|
require "colorize"
|
||||||
require "./formatter"
|
require "./formatter"
|
||||||
|
require "./summary"
|
||||||
|
|
||||||
module Spectator::Formatting
|
module Spectator::Formatting
|
||||||
# Output formatter that produces a single character for each test as it completes.
|
# Output formatter that produces a single character for each test as it completes.
|
||||||
# A '.' indicates a pass, 'F' a failure, 'E' an error, and '*' a skipped or pending test.
|
# A '.' indicates a pass, 'F' a failure, 'E' an error, and '*' a skipped or pending test.
|
||||||
class ProgressFormatter < Formatter
|
class ProgressFormatter < Formatter
|
||||||
|
include Summary
|
||||||
|
|
||||||
@pass_char : Colorize::Object(Char) = '.'.colorize(:green)
|
@pass_char : Colorize::Object(Char) = '.'.colorize(:green)
|
||||||
@fail_char : Colorize::Object(Char) = 'F'.colorize(:red)
|
@fail_char : Colorize::Object(Char) = 'F'.colorize(:red)
|
||||||
@error_char : Colorize::Object(Char) = 'E'.colorize(:red)
|
@error_char : Colorize::Object(Char) = 'E'.colorize(:red)
|
||||||
@skip_char : Colorize::Object(Char) = '*'.colorize(:yellow)
|
@skip_char : Colorize::Object(Char) = '*'.colorize(:yellow)
|
||||||
|
|
||||||
|
# Output stream to write results to.
|
||||||
|
private getter io : IO
|
||||||
|
|
||||||
# Creates the formatter.
|
# Creates the formatter.
|
||||||
def initialize(@io : IO = STDOUT)
|
def initialize(@io : IO = STDOUT)
|
||||||
end
|
end
|
||||||
|
@ -33,5 +39,10 @@ module Spectator::Formatting
|
||||||
def example_pending(_notification)
|
def example_pending(_notification)
|
||||||
@skip_char.to_s(@io)
|
@skip_char.to_s(@io)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Produces a new line after the tests complete.
|
||||||
|
def stop(_notification)
|
||||||
|
@io.puts
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
55
src/spectator/formatting/summary.cr
Normal file
55
src/spectator/formatting/summary.cr
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
require "./components"
|
||||||
|
|
||||||
|
module Spectator::Formatting
|
||||||
|
# Mix-in providing common output for summarized results.
|
||||||
|
# Implements the following methods:
|
||||||
|
# `Formatter#start_dump`, `Formatter#dump_pending`, `Formatter#dump_failures`,
|
||||||
|
# `Formatter#dump_summary`, and `Formatter#dump_profile`.
|
||||||
|
# Classes including this module must implement `#io`.
|
||||||
|
module Summary
|
||||||
|
# Stream to write results to.
|
||||||
|
private abstract def io : IO
|
||||||
|
|
||||||
|
def start_dump
|
||||||
|
io.puts
|
||||||
|
end
|
||||||
|
|
||||||
|
# Invoked after testing completes with a list of pending examples.
|
||||||
|
# This method will be called with an empty list if there were no pending (skipped) examples.
|
||||||
|
# Called after `#start_dump` and before `#dump_failures`.
|
||||||
|
def dump_pending(notification)
|
||||||
|
return if (examples = notification.examples).empty?
|
||||||
|
|
||||||
|
io.puts "Pending:"
|
||||||
|
io.puts
|
||||||
|
examples.each_with_index do |example, index|
|
||||||
|
io.puts Components::PendingBlock.new(example, index + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Invoked after testing completes with a list of failed examples.
|
||||||
|
# This method will be called with an empty list if there were no failures.
|
||||||
|
# Called after `#dump_pending` and before `#dump_summary`.
|
||||||
|
def dump_failures(notification)
|
||||||
|
return if (examples = notification.examples).empty?
|
||||||
|
|
||||||
|
io.puts "Failures:"
|
||||||
|
io.puts
|
||||||
|
examples.each_with_index do |example, index|
|
||||||
|
io.puts Components::FailureBlock.new(example, index + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Invoked after testing completes with summarized information from the test suite.
|
||||||
|
# Called after `#dump_failures` and before `#dump_profile`.
|
||||||
|
def dump_summary(notification)
|
||||||
|
io.puts Components::SummaryBlock.new(notification.report)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Invoked after testing completes with profiling information.
|
||||||
|
# This method is only called if profiling is enabled.
|
||||||
|
# Called after `#dump_summary` and before `#close`.
|
||||||
|
def dump_profile(_notification)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue