This commit is contained in:
Michael Miller 2021-05-30 10:17:49 -06:00
parent a4042a9684
commit 877831a98b
No known key found for this signature in database
GPG key ID: FB9F12F7C646A4AD
12 changed files with 104 additions and 4 deletions

View file

@ -1,16 +1,28 @@
module Spectator::Formatting::Components
# Base type for handling indented output.
# Indents are tracked and automatically printed.
# Use `#indent` to increase the indent for the duration of a block.
# Use `#line` to produce a line with an indentation prefixing it.
abstract struct Block
# Default indent amount.
private INDENT = 2
# Creates the block.
# A default *indent* size can be specified.
def initialize(*, @indent : Int32 = INDENT)
end
# Increases the indent by the a specific *amount* for the duration of the block.
private def indent(amount = INDENT)
@indent += amount
yield
@indent -= amount
end
# Produces a line of output with an indent before it.
# The contents of the line should be generated by a block provided to this method.
# Ensure that _only_ one line is produced by the block,
# otherwise the indent will be lost.
private def line(io)
@indent.times { io << ' ' }
yield

View file

@ -1,16 +1,21 @@
require "colorize"
module Spectator::Formatting::Components
# Object that can be stringified pre-pended with a comment mark (#).
struct Comment(T)
# Default color for a comment.
private COLOR = :cyan
# Creates a comment with the specified content.
def initialize(@content : T)
end
# Creates a colored comment.
def self.colorize(content)
new(content).colorize(COLOR)
end
# Writes the comment to the output.
def to_s(io)
io << '#'
io << ' '

View file

@ -4,41 +4,51 @@ require "../../error_result"
require "./result_block"
module Spectator::Formatting::Components
# Displays information about an error result.
struct ErrorResultBlock < ResultBlock
# Creates the component.
def initialize(index : Int32, example : Example, @result : ErrorResult)
super(index, example)
end
# Content displayed on the second line of the block after the label.
private def subtitle
@result.error.message.try(&.each_line.first)
end
# Prefix for the second line of the block.
private def subtitle_label
"Error: ".colorize(:red)
end
# Display error information.
private def content(io)
# Fetch the error and message.
# If there's no message
error = @result.error
lines = error.message.try(&.lines) || {"<blank>".colorize(:purple)}
# Display the error type and first line of the message.
line(io) do
io << "#{error.class}: ".colorize(:red)
io << lines.first
end
# Display additional lines after the first if there's any.
lines.skip(1).each do |entry|
line(io) do
io << entry
end
line(io) { io << entry }
end
error.backtrace?.try do |backtrace|
# Display the backtrace if it's available.
if backtrace = error.backtrace?
indent { write_backtrace(io, backtrace) }
end
end
# Writes the backtrace entries to the output.
private def write_backtrace(io, backtrace)
backtrace.each do |entry|
# Dim entries that are outside the shard.
entry = entry.colorize.dim unless entry.starts_with?(/(src|spec)\//)
line(io) { io << entry }
end

View file

@ -1,10 +1,14 @@
require "../../example"
require "./comment"
module Spectator::Formatting::Components
# Provides syntax for running a specific example from the command-line.
struct ExampleCommand
# Creates the component with the specified example.
def initialize(@example : Example)
end
# Produces output for running the previously specified example.
def to_s(io)
io << "crystal spec "
io << @example.location

View file

@ -4,19 +4,24 @@ require "../../fail_result"
require "./result_block"
module Spectator::Formatting::Components
# Displays information about a fail result.
struct FailResultBlock < ResultBlock
# Creates the component.
def initialize(index : Int32, example : Example, @result : FailResult)
super(index, example)
end
# Content displayed on the second line of the block after the label.
private def subtitle
@result.error.message.try(&.each_line.first)
end
# Prefix for the second line of the block.
private def subtitle_label
"Failure: ".colorize(:red)
end
# Display expectation match data.
private def content(io)
# TODO: Display match data.
end

View file

@ -1,10 +1,15 @@
require "../../example"
require "./example_command"
module Spectator::Formatting::Components
# Produces a list of commands to run failed examples.
struct FailureCommandList
# Creates the component.
# Requires a set of *failures* to display commands for.
def initialize(@failures : Enumerable(Example))
end
# Produces the list of commands to run failed examples.
def to_s(io)
io.puts "Failed examples:"
io.puts

View file

@ -1,22 +1,28 @@
require "colorize"
require "../../example"
require "../../pending_result"
require "./result_block"
module Spectator::Formatting::Components
# Displays information about a pending result.
struct PendingResultBlock < ResultBlock
# Creates the component.
def initialize(index : Int32, example : Example, @result : PendingResult)
super(index, example)
end
# Content displayed on the second line of the block after the label.
private def subtitle
"No reason given" # TODO: Get reason from result.
end
# Prefix for the second line of the block.
private def subtitle_label
# TODO: Could be pending or skipped.
"Pending: ".colorize(:yellow)
end
# No content for this type of block.
private def content(io)
end
end

View file

@ -2,10 +2,13 @@ require "../../profile"
require "./runtime"
module Spectator::Formatting::Components
# Displays profiling information for slow examples.
struct Profile
# Creates the component with the specified *profile*.
def initialize(@profile : Spectator::Profile)
end
# Produces the output containing the profiling information.
def to_s(io)
io << "Top "
io << @profile.size
@ -20,6 +23,7 @@ module Spectator::Formatting::Components
end
end
# Writes a single example's timing to the output.
private def example_profile(io, example)
io << " "
io.puts example

View file

@ -3,21 +3,47 @@ require "./block"
require "./comment"
module Spectator::Formatting::Components
# Base class that displayed indexed results in block form.
# These typically take the form:
# ```text
# 1) Title
# Label: Subtitle
#
# Content
# # Source
# ```
abstract struct ResultBlock < Block
# Creates the block with the specified *index* and for the given *example*.
def initialize(@index : Int32, @example : Example)
super()
end
# Content displayed on the first line of the block.
# Will be stringified.
# By default, uses the example name.
# Can be overridden to use a different value.
private def title
@example
end
# Content displayed on the second line of the block after the label.
# Will be stringified.
private abstract def subtitle
# Prefix for the second line of the block.
# Will be stringified.
# This is typically something like "Error:" or "Failure:"
private abstract def subtitle_label
# Produces the main content of the block.
# *io* is the stream to write to.
# `#line` and `#indent` (from `Block`) should be used to maintain spacing.
private abstract def content(io)
# Writes the component's output to the specified stream.
def to_s(io)
title_line(io)
# Ident over to align with the spacing used by the index.
indent(index_digit_count + 2) do
subtitle_line(io)
io.puts
@ -26,6 +52,7 @@ module Spectator::Formatting::Components
end
end
# Produces the title line.
private def title_line(io)
line(io) do
io << @index
@ -35,6 +62,7 @@ module Spectator::Formatting::Components
end
end
# Produces the subtitle line.
private def subtitle_line(io)
line(io) do
io << subtitle_label
@ -42,6 +70,7 @@ module Spectator::Formatting::Components
end
end
# Produces the (example) source line.
private def source_line(io)
source = if (result = @example.result).responds_to?(:source)
result.source
@ -51,6 +80,7 @@ module Spectator::Formatting::Components
line(io) { io << Comment.colorize(source) }
end
# Computes the number of spaces the index takes
private def index_digit_count
(Math.log(@index.to_f + 1) / Math::LOG10).ceil.to_i
end

View file

@ -1,13 +1,16 @@
require "colorize"
require "../../report"
require "./runtime"
require "./totals"
module Spectator::Formatting::Components
# Statistics information displayed at the end of a run.
struct Stats
# Creates the component with stats from *report*.
def initialize(@report : Report)
end
# Displays the stats.
def to_s(io)
runtime(io)
totals(io)
@ -16,15 +19,18 @@ module Spectator::Formatting::Components
end
end
# Displays the time it took to run the suite.
private def runtime(io)
io << "Finished in "
io.puts Runtime.new(@report.runtime)
end
# Displays the counts for each type of result.
private def totals(io)
io.puts Totals.colorize(@report.counts)
end
# Displays the random seed.
private def random(io, seed)
io.puts "Randomized with seed: #{seed}".colorize(:cyan)
end

View file

@ -2,10 +2,14 @@ require "../../profile"
require "./runtime"
module Spectator::Formatting::Components
# Displays profiling information for slow examples in a TAP format.
# Produces output similar to `Profile`, but formatted for TAP.
struct TAPProfile
# Creates the component with the specified *profile*.
def initialize(@profile : Spectator::Profile)
end
# Produces the output containing the profiling information.
def to_s(io)
io << "# Top "
io << @profile.size
@ -20,6 +24,7 @@ module Spectator::Formatting::Components
end
end
# Writes a single example's timing to the output.
private def example_profile(io, example)
io << "# "
io.puts example

View file

@ -1,10 +1,13 @@
require "colorize"
module Spectator::Formatting::Components
# Displays counts for each type of example result (pass, fail, error, pending).
struct Totals
# Creates the component with the specified counts.
def initialize(@examples : Int32, @failures : Int32, @errors : Int32, @pending : Int32)
end
# Creates the component by pulling numbers from *counts*.
def initialize(counts)
@examples = counts.run
@failures = counts.fail
@ -12,6 +15,10 @@ module Spectator::Formatting::Components
@pending = counts.pending
end
# Creates the component, but colors it whether there were pending or failed results.
# The component will be red if there were failures (or errors),
# yellow if there were pending/skipped tests,
# and green if everything passed.
def self.colorize(counts)
totals = new(counts)
if counts.fail > 0
@ -23,6 +30,7 @@ module Spectator::Formatting::Components
end
end
# Writes the counts to the output.
def to_s(io)
io << @examples
io << " examples, "