From 12d8bcb32cfd367075db32eb5998680b2ffb1eab Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 31 Mar 2021 17:20:08 -0600 Subject: [PATCH] Initial pass at removing example from Result --- src/spectator/example.cr | 7 ++--- src/spectator/fail_result.cr | 5 ++-- .../formatting/document_formatter.cr | 4 +-- src/spectator/formatting/dots_formatter.cr | 2 +- .../formatting/error_junit_test_case.cr | 3 +- src/spectator/formatting/failure_block.cr | 6 ++-- src/spectator/formatting/failure_command.cr | 4 +-- .../formatting/failure_junit_test_case.cr | 3 +- src/spectator/formatting/formatter.cr | 2 +- src/spectator/formatting/json_formatter.cr | 12 ++++---- src/spectator/formatting/junit_formatter.cr | 4 +-- src/spectator/formatting/junit_test_case.cr | 7 +++-- src/spectator/formatting/junit_test_suite.cr | 18 +++++++----- src/spectator/formatting/profile_block.cr | 10 +++---- src/spectator/formatting/silent_formatter.cr | 2 +- .../formatting/skipped_junit_test_case.cr | 3 +- .../formatting/successful_junit_test_case.cr | 3 +- src/spectator/formatting/suite_summary.cr | 10 +++---- src/spectator/formatting/tap_formatter.cr | 14 +++++----- src/spectator/formatting/tap_test_line.cr | 9 ++---- src/spectator/harness.cr | 7 ++--- src/spectator/pending_result.cr | 3 +- src/spectator/profile.cr | 8 +++--- src/spectator/report.cr | 28 ++++++++++++------- src/spectator/result.cr | 13 ++++----- src/spectator/spec/runner.cr | 8 +++--- 26 files changed, 102 insertions(+), 93 deletions(-) diff --git a/src/spectator/example.cr b/src/spectator/example.cr index 2ec9ea0..f6dd736 100644 --- a/src/spectator/example.cr +++ b/src/spectator/example.cr @@ -17,10 +17,7 @@ module Spectator getter? finished : Bool = false # Retrieves the result of the last time the example ran. - def result : Result - # TODO: Set to pending immediately (requires circular dependency between Example <-> Result removed). - @result ||= PendingResult.new(self) - end + getter result : Result = PendingResult.new # Creates the example. # An instance to run the test code in is given by *context*. @@ -62,7 +59,7 @@ module Spectator if pending? Log.debug { "Skipping example #{self} - marked pending" } - return @result = PendingResult.new(self) + return @result = PendingResult.new end previous_example = @@current diff --git a/src/spectator/fail_result.cr b/src/spectator/fail_result.cr index dfe8937..7f2a449 100644 --- a/src/spectator/fail_result.cr +++ b/src/spectator/fail_result.cr @@ -11,8 +11,9 @@ module Spectator # Creates a failure result. # The *elapsed* argument is the length of time it took to run the example. # The *error* is the exception raised that caused the failure. - def initialize(example, elapsed, @error, expectations = [] of Expectation) - super(example, elapsed, expectations) + # *expectations* stores the expectations checked in the example. + def initialize(elapsed, @error : Exception, expectations = [] of Expectation) + super(elapsed, expectations) end # Calls the `failure` method on *visitor*. diff --git a/src/spectator/formatting/document_formatter.cr b/src/spectator/formatting/document_formatter.cr index 16efdf8..af00613 100644 --- a/src/spectator/formatting/document_formatter.cr +++ b/src/spectator/formatting/document_formatter.cr @@ -27,9 +27,9 @@ module Spectator::Formatting end # 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 } - @io.puts result.accept(Color) { result.example.name } + @io.puts result.accept(Color) { example.name } end # Produces a list of groups making up the hierarchy for an example. diff --git a/src/spectator/formatting/dots_formatter.cr b/src/spectator/formatting/dots_formatter.cr index 1fd35c6..db5e12c 100644 --- a/src/spectator/formatting/dots_formatter.cr +++ b/src/spectator/formatting/dots_formatter.cr @@ -20,7 +20,7 @@ module Spectator::Formatting end # Produces a single character output based on a result. - def end_example(result) + def end_example(example, result) @io.print result.accept(Character) end diff --git a/src/spectator/formatting/error_junit_test_case.cr b/src/spectator/formatting/error_junit_test_case.cr index f1a12ce..3753972 100644 --- a/src/spectator/formatting/error_junit_test_case.cr +++ b/src/spectator/formatting/error_junit_test_case.cr @@ -7,7 +7,8 @@ module Spectator::Formatting private getter result # Creates the JUnit test case. - def initialize(@result : ErrorResult) + def initialize(example : Example, @result : ErrorResult) + super(example, @result) end # Adds the exception to the XML block. diff --git a/src/spectator/formatting/failure_block.cr b/src/spectator/formatting/failure_block.cr index e1eb053..0b4f1f5 100644 --- a/src/spectator/formatting/failure_block.cr +++ b/src/spectator/formatting/failure_block.cr @@ -16,7 +16,7 @@ module Spectator::Formatting # Creates the failure block. # The *index* uniquely identifies the failure in the output. # The *result* is the outcome of the failed example. - def initialize(@index : Int32, @result : FailResult) + def initialize(@index : Int32, @example : Example, @result : FailResult) end # Creates the block of text describing the failure. @@ -39,7 +39,7 @@ module Spectator::Formatting # 1) Example name # ``` private def title(indent) - indent.line(NumberedItem.new(@index, @result.example)) + indent.line(NumberedItem.new(@index, @example)) end # Produces the main content of the failure block. @@ -105,7 +105,7 @@ module Spectator::Formatting # Produces the location line of the failure block. private def location(indent) - indent.line(Comment.color(@result.example.location)) + indent.line(Comment.color(@example.location)) end # Gets the number of characters a positive integer spans in base 10. diff --git a/src/spectator/formatting/failure_command.cr b/src/spectator/formatting/failure_command.cr index 27c6896..1018039 100644 --- a/src/spectator/formatting/failure_command.cr +++ b/src/spectator/formatting/failure_command.cr @@ -12,8 +12,8 @@ module Spectator::Formatting end # Colorizes the command instance based on the result. - def self.color(result) - result.accept(Color) { new(result.example) } + def self.color(example, result) + result.accept(Color) { new(example) } end end end diff --git a/src/spectator/formatting/failure_junit_test_case.cr b/src/spectator/formatting/failure_junit_test_case.cr index 99181ea..27d5b51 100644 --- a/src/spectator/formatting/failure_junit_test_case.cr +++ b/src/spectator/formatting/failure_junit_test_case.cr @@ -7,7 +7,8 @@ module Spectator::Formatting private getter result # Creates the JUnit test case. - def initialize(@result : FailResult) + def initialize(example : Example, @result : FailResult) + super(example) end # Status string specific to the result type. diff --git a/src/spectator/formatting/formatter.cr b/src/spectator/formatting/formatter.cr index 9efd800..67e0ed5 100644 --- a/src/spectator/formatting/formatter.cr +++ b/src/spectator/formatting/formatter.cr @@ -22,6 +22,6 @@ module Spectator::Formatting # Called when a test finishes. # The result of the test is provided. - abstract def end_example(result : Result) + abstract def end_example(example : Example, result : Result) end end diff --git a/src/spectator/formatting/json_formatter.cr b/src/spectator/formatting/json_formatter.cr index 369a896..428c647 100644 --- a/src/spectator/formatting/json_formatter.cr +++ b/src/spectator/formatting/json_formatter.cr @@ -36,7 +36,7 @@ module Spectator::Formatting # Called when a test finishes. # The result of the test is provided. - def end_example(result : Result) + def end_example(example, result : Result) result.to_json(@json) end @@ -74,8 +74,8 @@ module Spectator::Formatting @json.field("percentage", profile.percentage) @json.field("results") do @json.array do - profile.each do |result| - profile_entry(result) + profile.each do |(example, result)| + profile_entry(example, result) end end end @@ -84,11 +84,11 @@ module Spectator::Formatting end # Adds a profile entry to the document. - private def profile_entry(result) + private def profile_entry(example, result) @json.object do - @json.field("example", result.example) + @json.field("example", example) @json.field("time", result.elapsed.total_seconds) - @json.field("location", result.example.location) + @json.field("location", example.location) end end end diff --git a/src/spectator/formatting/junit_formatter.cr b/src/spectator/formatting/junit_formatter.cr index f1dd60e..a3512d5 100644 --- a/src/spectator/formatting/junit_formatter.cr +++ b/src/spectator/formatting/junit_formatter.cr @@ -38,7 +38,7 @@ module Spectator::Formatting # Called when a test finishes. # The result of the test is provided. - def end_example(result : Result) + def end_example(example : Example, result : Result) end # Creates the "testsuites" block in the XML. @@ -55,7 +55,7 @@ module Spectator::Formatting # Adds all of the individual test suite blocks. 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) end end diff --git a/src/spectator/formatting/junit_test_case.cr b/src/spectator/formatting/junit_test_case.cr index 30eacb3..6ccc00d 100644 --- a/src/spectator/formatting/junit_test_case.cr +++ b/src/spectator/formatting/junit_test_case.cr @@ -1,6 +1,9 @@ module Spectator::Formatting # Base type for all JUnit test case results. private abstract class JUnitTestCase + def initialize(@example : Example) + end + # Produces the test case XML element. def to_xml(xml : ::XML::Builder) xml.element("testcase", **attributes) do @@ -11,7 +14,7 @@ module Spectator::Formatting # Attributes that go in the "testcase" XML element. private def attributes { - name: result.example, + name: @example.to_s, status: status, classname: classname, } @@ -31,7 +34,7 @@ module Spectator::Formatting # Java-ified class name created from the spec. private def classname - path = result.example.location.path + path = @example.location.path file = File.basename(path) ext = File.extname(file) name = file[0...-(ext.size)] diff --git a/src/spectator/formatting/junit_test_suite.cr b/src/spectator/formatting/junit_test_suite.cr index f7abc4b..ffd83cb 100644 --- a/src/spectator/formatting/junit_test_suite.cr +++ b/src/spectator/formatting/junit_test_suite.cr @@ -4,7 +4,7 @@ module Spectator::Formatting # Creates the JUnit test suite. # The *path* should be the file that all results are from. # 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) end @@ -24,8 +24,8 @@ module Spectator::Formatting # Adds the test case elements to the XML. private def add_test_cases(xml) - @report.each do |result| - test_case = result.accept(JUnitTestCaseSelector) { |r| r } + @report.each do |(example, result)| + test_case = result.accept(JUnitTestCaseSelector) { |r| {example, r} } test_case.to_xml(xml) end end @@ -51,22 +51,26 @@ module Spectator::Formatting # Creates a successful JUnit test case. def pass(result) - SuccessfulJUnitTestCase.new(result.as(PassResult)) + example, result = result + SuccessfulJUnitTestCase.new(example, result.as(PassResult)) end # Creates a failure JUnit test case. def failure(result) - FailureJUnitTestCase.new(result.as(FailResult)) + example, result = result + FailureJUnitTestCase.new(example, result.as(FailResult)) end # Creates an error JUnit test case. def error(result) - ErrorJUnitTestCase.new(result.as(ErrorResult)) + example, result = result + ErrorJUnitTestCase.new(example, result.as(ErrorResult)) end # Creates a skipped JUnit test case. def pending(result) - SkippedJUnitTestCase.new(result.as(PendingResult)) + example, result = result + SkippedJUnitTestCase.new(example, result.as(PendingResult)) end end end diff --git a/src/spectator/formatting/profile_block.cr b/src/spectator/formatting/profile_block.cr index ba7943f..1aa2336 100644 --- a/src/spectator/formatting/profile_block.cr +++ b/src/spectator/formatting/profile_block.cr @@ -11,17 +11,17 @@ module Spectator::Formatting indent = Indent.new(io) indent.increase do - @profile.each do |result| - entry(indent, result) + @profile.each do |(example, result)| + entry(indent, example, result) end end end # Adds a result entry to the output. - private def entry(indent, result) - indent.line(result.example) + private def entry(indent, example, result) + indent.line(example) indent.increase do - indent.line(LocationTiming.new(result.elapsed, result.example.location)) + indent.line(LocationTiming.new(result.elapsed, example.location)) end end end diff --git a/src/spectator/formatting/silent_formatter.cr b/src/spectator/formatting/silent_formatter.cr index 2098480..51e4176 100644 --- a/src/spectator/formatting/silent_formatter.cr +++ b/src/spectator/formatting/silent_formatter.cr @@ -20,7 +20,7 @@ module Spectator::Formatting # Called when a test finishes. # The result of the test is provided. - def end_example(result : Result) + def end_example(example : Example, result : Result) # ... crickets ... end end diff --git a/src/spectator/formatting/skipped_junit_test_case.cr b/src/spectator/formatting/skipped_junit_test_case.cr index 6b90b4d..2d70cc5 100644 --- a/src/spectator/formatting/skipped_junit_test_case.cr +++ b/src/spectator/formatting/skipped_junit_test_case.cr @@ -7,7 +7,8 @@ module Spectator::Formatting private getter result # Creates the JUnit test case. - def initialize(@result : PendingResult) + def initialize(example : Example, @result : PendingResult) + super(example) end # Status string specific to the result type. diff --git a/src/spectator/formatting/successful_junit_test_case.cr b/src/spectator/formatting/successful_junit_test_case.cr index 803ea6e..984998d 100644 --- a/src/spectator/formatting/successful_junit_test_case.cr +++ b/src/spectator/formatting/successful_junit_test_case.cr @@ -5,7 +5,8 @@ module Spectator::Formatting private getter result # Creates the JUnit test case. - def initialize(@result : PassResult) + def initialize(example : Example, @result : PassResult) + super(example) end # Status string specific to the result type. diff --git a/src/spectator/formatting/suite_summary.cr b/src/spectator/formatting/suite_summary.cr index 183a78e..59634f6 100644 --- a/src/spectator/formatting/suite_summary.cr +++ b/src/spectator/formatting/suite_summary.cr @@ -33,8 +33,8 @@ module Spectator::Formatting private def failures(failures) @io.puts "Failures:" @io.puts - failures.each_with_index do |result, index| - @io.puts FailureBlock.new(index + 1, result) + failures.each_with_index do |(example, result), index| + @io.puts FailureBlock.new(index + 1, example, result) end end @@ -68,10 +68,10 @@ module Spectator::Formatting @io.puts @io.puts "Failed examples:" @io.puts - failures.each do |result| - @io << FailureCommand.color(result) + failures.each do |(example, result)| + @io << FailureCommand.color(example, result) @io << ' ' - @io.puts Comment.color(result.example) + @io.puts Comment.color(example) end end end diff --git a/src/spectator/formatting/tap_formatter.cr b/src/spectator/formatting/tap_formatter.cr index 018d879..e81b6ba 100644 --- a/src/spectator/formatting/tap_formatter.cr +++ b/src/spectator/formatting/tap_formatter.cr @@ -28,8 +28,8 @@ module Spectator::Formatting # Called when a test finishes. # The result of the test is provided. - def end_example(result : Result) - @io.puts TAPTestLine.new(@index, result) + def end_example(example : Example, result : Result) + @io.puts TAPTestLine.new(@index, example, result) @index += 1 end @@ -39,19 +39,19 @@ module Spectator::Formatting indent = Indent.new(@io) indent.increase do - profile.each do |result| - profile_entry(indent, result) + profile.each do |example, result| + profile_entry(indent, example, result) end end end # Adds a profile result entry to the output. - private def profile_entry(indent, result) + private def profile_entry(indent, example, result) @io << "# " - indent.line(result.example) + indent.line(example) indent.increase do @io << "# " - indent.line(LocationTiming.new(result.elapsed, result.example.location)) + indent.line(LocationTiming.new(result.elapsed, example.location)) end end end diff --git a/src/spectator/formatting/tap_test_line.cr b/src/spectator/formatting/tap_test_line.cr index 089031b..4ca8a20 100644 --- a/src/spectator/formatting/tap_test_line.cr +++ b/src/spectator/formatting/tap_test_line.cr @@ -2,7 +2,7 @@ module Spectator::Formatting # Produces a formatted TAP test line. private struct TAPTestLine # Creates the test line. - def initialize(@index : Int32, @result : Result) + def initialize(@index : Int32, @example : Example, @result : Result) end # Appends the line to the output. @@ -11,7 +11,7 @@ module Spectator::Formatting io << ' ' io << @index io << " - " - io << example + io << @example io << " # skip" if pending? end @@ -20,11 +20,6 @@ module Spectator::Formatting @result.is_a?(FailResult) ? "not ok" : "ok" end - # The example that was tested. - private def example - @result.example - end - # Indicates whether this test was skipped. private def pending? @result.is_a?(PendingResult) diff --git a/src/spectator/harness.cr b/src/spectator/harness.cr index 59e8975..34bd070 100644 --- a/src/spectator/harness.cr +++ b/src/spectator/harness.cr @@ -94,14 +94,13 @@ module Spectator # Takes the *elapsed* time and a possible *error* from the test. # Returns a type of `Result`. private def translate(elapsed, error) : Result - example = Example.current # TODO: Remove this. case error when nil - PassResult.new(example, elapsed, @expectations) + PassResult.new(elapsed, @expectations) when ExpectationFailed - FailResult.new(example, elapsed, error, @expectations) + FailResult.new(elapsed, error, @expectations) else - ErrorResult.new(example, elapsed, error, @expectations) + ErrorResult.new(elapsed, error, @expectations) end end diff --git a/src/spectator/pending_result.cr b/src/spectator/pending_result.cr index 4ff64e6..3468a3a 100644 --- a/src/spectator/pending_result.cr +++ b/src/spectator/pending_result.cr @@ -7,7 +7,8 @@ module Spectator class PendingResult < Result # Creates the result. # *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 end diff --git a/src/spectator/profile.cr b/src/spectator/profile.cr index 0067f7a..4848ad0 100644 --- a/src/spectator/profile.cr +++ b/src/spectator/profile.cr @@ -1,14 +1,14 @@ module Spectator # Information about the runtimes of examples. class Profile - include Indexable(Result) + include Indexable(Tuple(Example, Result)) # Total length of time it took to run all examples in the test suite. getter total_time : Time::Span # Creates the profiling information. # 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 # Number of results in the profile. @@ -23,7 +23,7 @@ module Spectator # Length of time it took to run the results in the profile. def time - @slowest.sum(&.elapsed) + @slowest.sum(&.[1].elapsed) end # 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. def self.generate(report, size = 10) results = report.to_a - sorted_results = results.sort_by(&.elapsed) + sorted_results = results.sort_by(&.[1].elapsed) slowest = sorted_results.last(size).reverse self.new(slowest, report.example_runtime) end diff --git a/src/spectator/report.cr b/src/spectator/report.cr index e295b5b..a4fb769 100644 --- a/src/spectator/report.cr +++ b/src/spectator/report.cr @@ -3,7 +3,7 @@ require "./result" module Spectator # Outcome of all tests in a suite. class Report - include Enumerable(Result) + include Enumerable(Tuple(Example, Result)) # Total length of time it took to execute the test suite. # 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 *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. - def initialize(@results : Array(Result), @runtime, @remaining_count = 0, @fail_blank = false, @random_seed = nil) - @results.each do |result| + def initialize(@results : Array(Tuple(Example, Result)), @runtime, @remaining_count = 0, @fail_blank = false, @random_seed = nil) + @results.each do |_example, result| case result when PassResult @successful_count += 1 @@ -57,15 +57,15 @@ module Spectator # This constructor is intended for reports of subsets of results. # The *results* are from running the examples in the test suite. # The runtime is calculated from the *results*. - def initialize(results : Array(Result)) - runtime = results.sum(&.elapsed) + def initialize(results : Array(Tuple(Example, Result))) + runtime = results.sum(&.[1].elapsed) initialize(results, runtime) end # Yields each result in turn. def each - @results.each do |result| - yield result + @results.each do |entry| + yield entry end end @@ -92,19 +92,27 @@ module Spectator # Returns a set of results for all failed examples. 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 # Returns a set of results for all errored examples. 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 # Length of time it took to run just example code. # This does not include hooks, # but it does include pre- and post-conditions. def example_runtime - @results.sum(&.elapsed) + @results.sum(&.[1].elapsed) end # Length of time spent in framework processes and hooks. diff --git a/src/spectator/result.cr b/src/spectator/result.cr index 2c775ef..fccff7f 100644 --- a/src/spectator/result.cr +++ b/src/spectator/result.cr @@ -2,19 +2,16 @@ module Spectator # Base class that represents the outcome of running an example. # Sub-classes contain additional information specific to the type of result. abstract class Result - # Example that generated the result. - # TODO: Remove this. - getter example : Example - # Length of time it took to run the example. getter elapsed : Time::Span - # The assertions checked in the example. + # The expectations checked in the example. getter expectations : Enumerable(Expectation) # Creates the result. # *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 # 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. private def add_json_fields(json : ::JSON::Builder) - json.field("name", example) - json.field("location", example.location) + json.field("name", "TODO: Example name") + json.field("location", "TODO: Example location") json.field("result", to_s) json.field("time", elapsed.total_seconds) json.field("expectations", expectations) diff --git a/src/spectator/spec/runner.cr b/src/spectator/spec/runner.cr index 99ce35b..b1d48df 100644 --- a/src/spectator/spec/runner.cr +++ b/src/spectator/spec/runner.cr @@ -16,7 +16,7 @@ module Spectator @config.each_formatter(&.start_suite(@suite)) # 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 collect_results(results) end @@ -34,7 +34,7 @@ module Spectator private def collect_results(results) example_order.each do |example| result = run_example(example).as(Result) - results << result + results << {example, result} if @config.fail_fast? && result.is_a?(FailResult) example.group.call_once_after_all break @@ -60,14 +60,14 @@ module Spectator else example.run end - @config.each_formatter(&.end_example(result)) + @config.each_formatter(&.end_example(example, result)) result end # Creates a fake result for an example. private def dry_run_result(example) expectations = [] of Expectation - PassResult.new(example, Time::Span.zero, expectations) + PassResult.new(Time::Span.zero, expectations) end # Generates and returns a profile if one should be displayed.