Initial pass at removing example from Result

This commit is contained in:
Michael Miller 2021-03-31 17:20:08 -06:00
parent 6d815a78d6
commit 12d8bcb32c
No known key found for this signature in database
GPG Key ID: FB9F12F7C646A4AD
26 changed files with 102 additions and 93 deletions

View File

@ -17,10 +17,7 @@ module Spectator
getter? finished : Bool = false getter? finished : Bool = false
# Retrieves the result of the last time the example ran. # Retrieves the result of the last time the example ran.
def result : Result getter result : Result = PendingResult.new
# TODO: Set to pending immediately (requires circular dependency between Example <-> Result removed).
@result ||= PendingResult.new(self)
end
# Creates the example. # Creates the example.
# An instance to run the test code in is given by *context*. # An instance to run the test code in is given by *context*.
@ -62,7 +59,7 @@ module Spectator
if pending? if pending?
Log.debug { "Skipping example #{self} - marked pending" } Log.debug { "Skipping example #{self} - marked pending" }
return @result = PendingResult.new(self) return @result = PendingResult.new
end end
previous_example = @@current previous_example = @@current

View File

@ -11,8 +11,9 @@ module Spectator
# Creates a failure result. # Creates a failure result.
# The *elapsed* argument is the length of time it took to run the example. # The *elapsed* argument is the length of time it took to run the example.
# The *error* is the exception raised that caused the failure. # The *error* is the exception raised that caused the failure.
def initialize(example, elapsed, @error, expectations = [] of Expectation) # *expectations* stores the expectations checked in the example.
super(example, elapsed, expectations) def initialize(elapsed, @error : Exception, expectations = [] of Expectation)
super(elapsed, expectations)
end end
# Calls the `failure` method on *visitor*. # Calls the `failure` method on *visitor*.

View File

@ -27,9 +27,9 @@ module Spectator::Formatting
end end
# Produces a single character output based on a result. # Produces a single character output based on a result.
def end_example(result) def end_example(example, result)
@previous_hierarchy.size.times { @io.print INDENT } @previous_hierarchy.size.times { @io.print INDENT }
@io.puts result.accept(Color) { result.example.name } @io.puts result.accept(Color) { example.name }
end end
# Produces a list of groups making up the hierarchy for an example. # Produces a list of groups making up the hierarchy for an example.

View File

@ -20,7 +20,7 @@ module Spectator::Formatting
end end
# Produces a single character output based on a result. # Produces a single character output based on a result.
def end_example(result) def end_example(example, result)
@io.print result.accept(Character) @io.print result.accept(Character)
end end

View File

@ -7,7 +7,8 @@ module Spectator::Formatting
private getter result private getter result
# Creates the JUnit test case. # Creates the JUnit test case.
def initialize(@result : ErrorResult) def initialize(example : Example, @result : ErrorResult)
super(example, @result)
end end
# Adds the exception to the XML block. # Adds the exception to the XML block.

View File

@ -16,7 +16,7 @@ module Spectator::Formatting
# Creates the failure block. # Creates the failure block.
# The *index* uniquely identifies the failure in the output. # The *index* uniquely identifies the failure in the output.
# The *result* is the outcome of the failed example. # The *result* is the outcome of the failed example.
def initialize(@index : Int32, @result : FailResult) def initialize(@index : Int32, @example : Example, @result : FailResult)
end end
# Creates the block of text describing the failure. # Creates the block of text describing the failure.
@ -39,7 +39,7 @@ module Spectator::Formatting
# 1) Example name # 1) Example name
# ``` # ```
private def title(indent) private def title(indent)
indent.line(NumberedItem.new(@index, @result.example)) indent.line(NumberedItem.new(@index, @example))
end end
# Produces the main content of the failure block. # Produces the main content of the failure block.
@ -105,7 +105,7 @@ module Spectator::Formatting
# Produces the location line of the failure block. # Produces the location line of the failure block.
private def location(indent) private def location(indent)
indent.line(Comment.color(@result.example.location)) indent.line(Comment.color(@example.location))
end end
# Gets the number of characters a positive integer spans in base 10. # Gets the number of characters a positive integer spans in base 10.

View File

@ -12,8 +12,8 @@ module Spectator::Formatting
end end
# Colorizes the command instance based on the result. # Colorizes the command instance based on the result.
def self.color(result) def self.color(example, result)
result.accept(Color) { new(result.example) } result.accept(Color) { new(example) }
end end
end end
end end

View File

@ -7,7 +7,8 @@ module Spectator::Formatting
private getter result private getter result
# Creates the JUnit test case. # Creates the JUnit test case.
def initialize(@result : FailResult) def initialize(example : Example, @result : FailResult)
super(example)
end end
# Status string specific to the result type. # Status string specific to the result type.

View File

@ -22,6 +22,6 @@ module Spectator::Formatting
# Called when a test finishes. # Called when a test finishes.
# The result of the test is provided. # The result of the test is provided.
abstract def end_example(result : Result) abstract def end_example(example : Example, result : Result)
end end
end end

View File

@ -36,7 +36,7 @@ module Spectator::Formatting
# Called when a test finishes. # Called when a test finishes.
# The result of the test is provided. # The result of the test is provided.
def end_example(result : Result) def end_example(example, result : Result)
result.to_json(@json) result.to_json(@json)
end end
@ -74,8 +74,8 @@ module Spectator::Formatting
@json.field("percentage", profile.percentage) @json.field("percentage", profile.percentage)
@json.field("results") do @json.field("results") do
@json.array do @json.array do
profile.each do |result| profile.each do |(example, result)|
profile_entry(result) profile_entry(example, result)
end end
end end
end end
@ -84,11 +84,11 @@ module Spectator::Formatting
end end
# Adds a profile entry to the document. # Adds a profile entry to the document.
private def profile_entry(result) private def profile_entry(example, result)
@json.object do @json.object do
@json.field("example", result.example) @json.field("example", example)
@json.field("time", result.elapsed.total_seconds) @json.field("time", result.elapsed.total_seconds)
@json.field("location", result.example.location) @json.field("location", example.location)
end end
end end
end end

View File

@ -38,7 +38,7 @@ module Spectator::Formatting
# Called when a test finishes. # Called when a test finishes.
# The result of the test is provided. # The result of the test is provided.
def end_example(result : Result) def end_example(example : Example, result : Result)
end end
# Creates the "testsuites" block in the XML. # Creates the "testsuites" block in the XML.
@ -55,7 +55,7 @@ module Spectator::Formatting
# Adds all of the individual test suite blocks. # Adds all of the individual test suite blocks.
private def add_test_suites(report) private def add_test_suites(report)
report.group_by(&.example.location.path).each do |path, results| report.group_by(&.[0].location.path).each do |path, results|
JUnitTestSuite.new(path, results).to_xml(@xml) JUnitTestSuite.new(path, results).to_xml(@xml)
end end
end end

View File

@ -1,6 +1,9 @@
module Spectator::Formatting module Spectator::Formatting
# Base type for all JUnit test case results. # Base type for all JUnit test case results.
private abstract class JUnitTestCase private abstract class JUnitTestCase
def initialize(@example : Example)
end
# Produces the test case XML element. # Produces the test case XML element.
def to_xml(xml : ::XML::Builder) def to_xml(xml : ::XML::Builder)
xml.element("testcase", **attributes) do xml.element("testcase", **attributes) do
@ -11,7 +14,7 @@ module Spectator::Formatting
# Attributes that go in the "testcase" XML element. # Attributes that go in the "testcase" XML element.
private def attributes private def attributes
{ {
name: result.example, name: @example.to_s,
status: status, status: status,
classname: classname, classname: classname,
} }
@ -31,7 +34,7 @@ module Spectator::Formatting
# Java-ified class name created from the spec. # Java-ified class name created from the spec.
private def classname private def classname
path = result.example.location.path path = @example.location.path
file = File.basename(path) file = File.basename(path)
ext = File.extname(file) ext = File.extname(file)
name = file[0...-(ext.size)] name = file[0...-(ext.size)]

View File

@ -4,7 +4,7 @@ module Spectator::Formatting
# Creates the JUnit test suite. # Creates the JUnit test suite.
# The *path* should be the file that all results are from. # The *path* should be the file that all results are from.
# The *results* is a subset of all results that share the path. # The *results* is a subset of all results that share the path.
def initialize(@path : String, results : Array(Result)) def initialize(@path : String, results : Array(Tuple(Example, Result)))
@report = Report.new(results) @report = Report.new(results)
end end
@ -24,8 +24,8 @@ module Spectator::Formatting
# Adds the test case elements to the XML. # Adds the test case elements to the XML.
private def add_test_cases(xml) private def add_test_cases(xml)
@report.each do |result| @report.each do |(example, result)|
test_case = result.accept(JUnitTestCaseSelector) { |r| r } test_case = result.accept(JUnitTestCaseSelector) { |r| {example, r} }
test_case.to_xml(xml) test_case.to_xml(xml)
end end
end end
@ -51,22 +51,26 @@ module Spectator::Formatting
# Creates a successful JUnit test case. # Creates a successful JUnit test case.
def pass(result) def pass(result)
SuccessfulJUnitTestCase.new(result.as(PassResult)) example, result = result
SuccessfulJUnitTestCase.new(example, result.as(PassResult))
end end
# Creates a failure JUnit test case. # Creates a failure JUnit test case.
def failure(result) def failure(result)
FailureJUnitTestCase.new(result.as(FailResult)) example, result = result
FailureJUnitTestCase.new(example, result.as(FailResult))
end end
# Creates an error JUnit test case. # Creates an error JUnit test case.
def error(result) def error(result)
ErrorJUnitTestCase.new(result.as(ErrorResult)) example, result = result
ErrorJUnitTestCase.new(example, result.as(ErrorResult))
end end
# Creates a skipped JUnit test case. # Creates a skipped JUnit test case.
def pending(result) def pending(result)
SkippedJUnitTestCase.new(result.as(PendingResult)) example, result = result
SkippedJUnitTestCase.new(example, result.as(PendingResult))
end end
end end
end end

View File

@ -11,17 +11,17 @@ module Spectator::Formatting
indent = Indent.new(io) indent = Indent.new(io)
indent.increase do indent.increase do
@profile.each do |result| @profile.each do |(example, result)|
entry(indent, result) entry(indent, example, result)
end end
end end
end end
# Adds a result entry to the output. # Adds a result entry to the output.
private def entry(indent, result) private def entry(indent, example, result)
indent.line(result.example) indent.line(example)
indent.increase do indent.increase do
indent.line(LocationTiming.new(result.elapsed, result.example.location)) indent.line(LocationTiming.new(result.elapsed, example.location))
end end
end end
end end

View File

@ -20,7 +20,7 @@ module Spectator::Formatting
# Called when a test finishes. # Called when a test finishes.
# The result of the test is provided. # The result of the test is provided.
def end_example(result : Result) def end_example(example : Example, result : Result)
# ... crickets ... # ... crickets ...
end end
end end

View File

@ -7,7 +7,8 @@ module Spectator::Formatting
private getter result private getter result
# Creates the JUnit test case. # Creates the JUnit test case.
def initialize(@result : PendingResult) def initialize(example : Example, @result : PendingResult)
super(example)
end end
# Status string specific to the result type. # Status string specific to the result type.

View File

@ -5,7 +5,8 @@ module Spectator::Formatting
private getter result private getter result
# Creates the JUnit test case. # Creates the JUnit test case.
def initialize(@result : PassResult) def initialize(example : Example, @result : PassResult)
super(example)
end end
# Status string specific to the result type. # Status string specific to the result type.

View File

@ -33,8 +33,8 @@ module Spectator::Formatting
private def failures(failures) private def failures(failures)
@io.puts "Failures:" @io.puts "Failures:"
@io.puts @io.puts
failures.each_with_index do |result, index| failures.each_with_index do |(example, result), index|
@io.puts FailureBlock.new(index + 1, result) @io.puts FailureBlock.new(index + 1, example, result)
end end
end end
@ -68,10 +68,10 @@ module Spectator::Formatting
@io.puts @io.puts
@io.puts "Failed examples:" @io.puts "Failed examples:"
@io.puts @io.puts
failures.each do |result| failures.each do |(example, result)|
@io << FailureCommand.color(result) @io << FailureCommand.color(example, result)
@io << ' ' @io << ' '
@io.puts Comment.color(result.example) @io.puts Comment.color(example)
end end
end end
end end

View File

@ -28,8 +28,8 @@ module Spectator::Formatting
# Called when a test finishes. # Called when a test finishes.
# The result of the test is provided. # The result of the test is provided.
def end_example(result : Result) def end_example(example : Example, result : Result)
@io.puts TAPTestLine.new(@index, result) @io.puts TAPTestLine.new(@index, example, result)
@index += 1 @index += 1
end end
@ -39,19 +39,19 @@ module Spectator::Formatting
indent = Indent.new(@io) indent = Indent.new(@io)
indent.increase do indent.increase do
profile.each do |result| profile.each do |example, result|
profile_entry(indent, result) profile_entry(indent, example, result)
end end
end end
end end
# Adds a profile result entry to the output. # Adds a profile result entry to the output.
private def profile_entry(indent, result) private def profile_entry(indent, example, result)
@io << "# " @io << "# "
indent.line(result.example) indent.line(example)
indent.increase do indent.increase do
@io << "# " @io << "# "
indent.line(LocationTiming.new(result.elapsed, result.example.location)) indent.line(LocationTiming.new(result.elapsed, example.location))
end end
end end
end end

View File

@ -2,7 +2,7 @@ module Spectator::Formatting
# Produces a formatted TAP test line. # Produces a formatted TAP test line.
private struct TAPTestLine private struct TAPTestLine
# Creates the test line. # Creates the test line.
def initialize(@index : Int32, @result : Result) def initialize(@index : Int32, @example : Example, @result : Result)
end end
# Appends the line to the output. # Appends the line to the output.
@ -11,7 +11,7 @@ module Spectator::Formatting
io << ' ' io << ' '
io << @index io << @index
io << " - " io << " - "
io << example io << @example
io << " # skip" if pending? io << " # skip" if pending?
end end
@ -20,11 +20,6 @@ module Spectator::Formatting
@result.is_a?(FailResult) ? "not ok" : "ok" @result.is_a?(FailResult) ? "not ok" : "ok"
end end
# The example that was tested.
private def example
@result.example
end
# Indicates whether this test was skipped. # Indicates whether this test was skipped.
private def pending? private def pending?
@result.is_a?(PendingResult) @result.is_a?(PendingResult)

View File

@ -94,14 +94,13 @@ module Spectator
# Takes the *elapsed* time and a possible *error* from the test. # Takes the *elapsed* time and a possible *error* from the test.
# Returns a type of `Result`. # Returns a type of `Result`.
private def translate(elapsed, error) : Result private def translate(elapsed, error) : Result
example = Example.current # TODO: Remove this.
case error case error
when nil when nil
PassResult.new(example, elapsed, @expectations) PassResult.new(elapsed, @expectations)
when ExpectationFailed when ExpectationFailed
FailResult.new(example, elapsed, error, @expectations) FailResult.new(elapsed, error, @expectations)
else else
ErrorResult.new(example, elapsed, error, @expectations) ErrorResult.new(elapsed, error, @expectations)
end end
end end

View File

@ -7,7 +7,8 @@ module Spectator
class PendingResult < Result class PendingResult < Result
# Creates the result. # Creates the result.
# *elapsed* is the length of time it took to run the example. # *elapsed* is the length of time it took to run the example.
def initialize(example, elapsed = Time::Span::ZERO, expectations = [] of Expectation) # *expectations* stores the expectations checked in the example.
def initialize(elapsed = Time::Span::ZERO, expectations = [] of Expectation)
super super
end end

View File

@ -1,14 +1,14 @@
module Spectator module Spectator
# Information about the runtimes of examples. # Information about the runtimes of examples.
class Profile class Profile
include Indexable(Result) include Indexable(Tuple(Example, Result))
# Total length of time it took to run all examples in the test suite. # Total length of time it took to run all examples in the test suite.
getter total_time : Time::Span getter total_time : Time::Span
# Creates the profiling information. # Creates the profiling information.
# The *slowest* results must already be sorted, longest time first. # The *slowest* results must already be sorted, longest time first.
private def initialize(@slowest : Array(Result), @total_time) private def initialize(@slowest : Array(Tuple(Example, Result)), @total_time)
end end
# Number of results in the profile. # Number of results in the profile.
@ -23,7 +23,7 @@ module Spectator
# Length of time it took to run the results in the profile. # Length of time it took to run the results in the profile.
def time def time
@slowest.sum(&.elapsed) @slowest.sum(&.[1].elapsed)
end end
# Percentage (from 0 to 1) of time the results in this profile took compared to all examples. # Percentage (from 0 to 1) of time the results in this profile took compared to all examples.
@ -34,7 +34,7 @@ module Spectator
# Produces the profile from a report. # Produces the profile from a report.
def self.generate(report, size = 10) def self.generate(report, size = 10)
results = report.to_a results = report.to_a
sorted_results = results.sort_by(&.elapsed) sorted_results = results.sort_by(&.[1].elapsed)
slowest = sorted_results.last(size).reverse slowest = sorted_results.last(size).reverse
self.new(slowest, report.example_runtime) self.new(slowest, report.example_runtime)
end end

View File

@ -3,7 +3,7 @@ require "./result"
module Spectator module Spectator
# Outcome of all tests in a suite. # Outcome of all tests in a suite.
class Report class Report
include Enumerable(Result) include Enumerable(Tuple(Example, Result))
# Total length of time it took to execute the test suite. # Total length of time it took to execute the test suite.
# This includes examples, hooks, and framework processes. # This includes examples, hooks, and framework processes.
@ -34,8 +34,8 @@ module Spectator
# The *remaining_count* is the number of tests skipped due to fail-fast. # The *remaining_count* is the number of tests skipped due to fail-fast.
# The *fail_blank* flag indicates whether it is a failure if there were no tests run. # The *fail_blank* flag indicates whether it is a failure if there were no tests run.
# The *random_seed* is the seed used for random number generation. # The *random_seed* is the seed used for random number generation.
def initialize(@results : Array(Result), @runtime, @remaining_count = 0, @fail_blank = false, @random_seed = nil) def initialize(@results : Array(Tuple(Example, Result)), @runtime, @remaining_count = 0, @fail_blank = false, @random_seed = nil)
@results.each do |result| @results.each do |_example, result|
case result case result
when PassResult when PassResult
@successful_count += 1 @successful_count += 1
@ -57,15 +57,15 @@ module Spectator
# This constructor is intended for reports of subsets of results. # This constructor is intended for reports of subsets of results.
# The *results* are from running the examples in the test suite. # The *results* are from running the examples in the test suite.
# The runtime is calculated from the *results*. # The runtime is calculated from the *results*.
def initialize(results : Array(Result)) def initialize(results : Array(Tuple(Example, Result)))
runtime = results.sum(&.elapsed) runtime = results.sum(&.[1].elapsed)
initialize(results, runtime) initialize(results, runtime)
end end
# Yields each result in turn. # Yields each result in turn.
def each def each
@results.each do |result| @results.each do |entry|
yield result yield entry
end end
end end
@ -92,19 +92,27 @@ module Spectator
# Returns a set of results for all failed examples. # Returns a set of results for all failed examples.
def failures def failures
@results.each.compact_map(&.as?(FailResult)) @results.each.compact_map do |(example, result)|
if (r = result.as?(FailResult))
{example, r}
end
end
end end
# Returns a set of results for all errored examples. # Returns a set of results for all errored examples.
def errors def errors
@results.each.compact_map(&.as?(ErrorResult)) @results.each.compact_map do |(example, result)|
if (r = result.as?(ErrorResult))
{example, r}
end
end
end end
# Length of time it took to run just example code. # Length of time it took to run just example code.
# This does not include hooks, # This does not include hooks,
# but it does include pre- and post-conditions. # but it does include pre- and post-conditions.
def example_runtime def example_runtime
@results.sum(&.elapsed) @results.sum(&.[1].elapsed)
end end
# Length of time spent in framework processes and hooks. # Length of time spent in framework processes and hooks.

View File

@ -2,19 +2,16 @@ module Spectator
# Base class that represents the outcome of running an example. # Base class that represents the outcome of running an example.
# Sub-classes contain additional information specific to the type of result. # Sub-classes contain additional information specific to the type of result.
abstract class Result abstract class Result
# Example that generated the result.
# TODO: Remove this.
getter example : Example
# Length of time it took to run the example. # Length of time it took to run the example.
getter elapsed : Time::Span getter elapsed : Time::Span
# The assertions checked in the example. # The expectations checked in the example.
getter expectations : Enumerable(Expectation) getter expectations : Enumerable(Expectation)
# Creates the result. # Creates the result.
# *elapsed* is the length of time it took to run the example. # *elapsed* is the length of time it took to run the example.
def initialize(@example, @elapsed, @expectations = [] of Expectation) # *expectations* stores the expectations checked in the example.
def initialize(@elapsed, @expectations = [] of Expectation)
end end
# Calls the corresponding method for the type of result. # Calls the corresponding method for the type of result.
@ -30,8 +27,8 @@ module Spectator
# Adds the common fields for a result to a JSON builder. # Adds the common fields for a result to a JSON builder.
private def add_json_fields(json : ::JSON::Builder) private def add_json_fields(json : ::JSON::Builder)
json.field("name", example) json.field("name", "TODO: Example name")
json.field("location", example.location) json.field("location", "TODO: Example location")
json.field("result", to_s) json.field("result", to_s)
json.field("time", elapsed.total_seconds) json.field("time", elapsed.total_seconds)
json.field("expectations", expectations) json.field("expectations", expectations)

View File

@ -16,7 +16,7 @@ module Spectator
@config.each_formatter(&.start_suite(@suite)) @config.each_formatter(&.start_suite(@suite))
# Run all examples and capture the results. # Run all examples and capture the results.
results = Array(Result).new(@suite.size) results = Array(Tuple(Example, Result)).new(@suite.size)
elapsed = Time.measure do elapsed = Time.measure do
collect_results(results) collect_results(results)
end end
@ -34,7 +34,7 @@ module Spectator
private def collect_results(results) private def collect_results(results)
example_order.each do |example| example_order.each do |example|
result = run_example(example).as(Result) result = run_example(example).as(Result)
results << result results << {example, result}
if @config.fail_fast? && result.is_a?(FailResult) if @config.fail_fast? && result.is_a?(FailResult)
example.group.call_once_after_all example.group.call_once_after_all
break break
@ -60,14 +60,14 @@ module Spectator
else else
example.run example.run
end end
@config.each_formatter(&.end_example(result)) @config.each_formatter(&.end_example(example, result))
result result
end end
# Creates a fake result for an example. # Creates a fake result for an example.
private def dry_run_result(example) private def dry_run_result(example)
expectations = [] of Expectation expectations = [] of Expectation
PassResult.new(example, Time::Span.zero, expectations) PassResult.new(Time::Span.zero, expectations)
end end
# Generates and returns a profile if one should be displayed. # Generates and returns a profile if one should be displayed.