diff --git a/spec/std/spec/output_spec.cr b/spec/std/spec/output_spec.cr new file mode 100644 index 000000000000..05c1ff25efbf --- /dev/null +++ b/spec/std/spec/output_spec.cr @@ -0,0 +1,189 @@ +require "spec" + +private SAMPLE_SPEC_FILE = "foo_spec.cr" + +class DummyRootContext < Spec::RootContext + @io = String::Builder.new + + property lines = {} of {String, Int32} => String + + delegate puts, print, to: @io + + def output + @io.to_s + end + + def report(result) + # Spec.formatters.each(&.report(result)) + + @results[result.kind] << result + end + + private def read_line(file, line) + lines[{file, line}]? + end +end + +class DummyFormatter < Spec::Formatter + getter results = [] of Spec::Result + + def report(result) + @results << result + end + + def finish + puts + end +end + +private class SpecEnvironment + getter root = DummyRootContext.new + getter formatters + + def initialize(@formatters = [DummyFormatter.new]) + @contexts_stack = [@root] of Spec::Context + end + + def self.run(elapsed_time = 42.milliseconds, use_color = false) + instance = new + + old_color = Spec.use_colors? + begin + Spec.use_colors = use_color + + yield instance + + instance.@root.print_results(elapsed_time) + ensure + Spec.use_colors = old_color + end + + instance.@root.output + end + + def describe(description, file, line, &block) + describe = Spec::NestedContext.new(description, file, line, @contexts_stack.last) + @contexts_stack.push describe + @formatters.each(&.push(describe)) + block.call + @formatters.each(&.pop) + @contexts_stack.pop + end + + def it(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + @formatters.each(&.before_example(description)) + + start = Time.monotonic + begin + # Spec.run_before_each_hooks + block.call + report(:success, description, file, line, Time.monotonic - start) + rescue ex : Spec::AssertionFailed + report(:fail, description, file, line, Time.monotonic - start, ex) + # Spec.abort! if Spec.fail_fast? + rescue ex + report(:error, description, file, line, Time.monotonic - start, ex) + # Spec.abort! if Spec.fail_fast? + ensure + # Spec.run_after_each_hooks + end + end + + private def report(kind, full_description, file, line, elapsed = nil, ex = nil) + result = Spec::Result.new(kind, full_description, file, line, elapsed, ex) + @contexts_stack.last.report(result) + end + + def pending(description = "assert", file = __FILE__, line = __LINE__, end_line = __END_LINE__, &block) + @formatters.each(&.before_example(description)) + + @root.report(:pending, description, file, line) + end +end + +describe "spec output" do + it "empty example" do + output = SpecEnvironment.run do |env| + env.describe "foo test", SAMPLE_SPEC_FILE, 3 do + env.it "passes a test", SAMPLE_SPEC_FILE, 4, 5 { } + end + + # env.formatters.first.as(DummyFormatter).results.map(&.kind).should eq [:success] + end + + output.should eq <<-'RESULT' + + Finished in 42.0 milliseconds + 1 examples, 0 failures, 0 errors, 0 pending + + RESULT + end + + it "failing example" do + output = SpecEnvironment.run do |env| + env.root.lines[{SAMPLE_SPEC_FILE, 5}] = "3.should eq 4" + env.describe "foo test", SAMPLE_SPEC_FILE, 3 do + env.it "passes a test", SAMPLE_SPEC_FILE, 4, 6 do + 3.should eq(4), SAMPLE_SPEC_FILE, 5 + end + end + + # env.formatters.first.as(DummyFormatter).results.map(&.kind).should eq [:success] + end + + output.should eq <<-'RESULT' + + Failures: + + 1) foo test passes a test + Failure/Error: 3.should eq 4 + + Expected: 4 + got: 3 + + # foo_spec.cr:5 + + Finished in 42.0 milliseconds + 1 examples, 1 failures, 0 errors, 0 pending + + Failed examples: + + crystal spec foo_spec.cr:4 # foo test passes a test + + RESULT + end + + it "raising example" do + output = SpecEnvironment.run do |env| + env.describe "foo test", SAMPLE_SPEC_FILE, 3 do + env.it "passes a test", SAMPLE_SPEC_FILE, 4, 5 do + raise "unexpected exception" + end + end + + # env.formatters.first.as(DummyFormatter).results.map(&.kind).should eq [:success] + end + + intro, search, rest = output.partition "unexpected exception" + + (intro + search).should eq <<-'RESULT' + + Failures: + + 1) foo test passes a test + + unexpected exception + RESULT + + callstack, search, outro = rest.partition("Finished in 42.0 milliseconds") + (search + outro).should eq <<-'RESULT' + Finished in 42.0 milliseconds + 1 examples, 0 failures, 1 errors, 0 pending + + Failed examples: + + crystal spec foo_spec.cr:4 # foo test passes a test + + RESULT + end +end diff --git a/src/spec/context.cr b/src/spec/context.cr index 66911f760a41..2f2aa77fefec 100644 --- a/src/spec/context.cr +++ b/src/spec/context.cr @@ -75,7 +75,7 @@ module Spec puts "#{(i + 1).to_s.rjust(3, ' ')}) #{fail.description}" if ex.is_a?(AssertionFailed) - source_line = Spec.read_line(ex.file, ex.line) + source_line = read_line(ex.file, ex.line) if source_line puts Spec.color(" Failure/Error: #{source_line.strip}", :error) end @@ -143,6 +143,10 @@ module Spec end end + private def read_line(file, line) + Spec.read_line(file, line) + end + @@instance = RootContext.new @@contexts_stack = [@@instance] of Context