From a24906664c8592f3cef0d0c531396fe3a9cb25a6 Mon Sep 17 00:00:00 2001 From: Ragmaanir Date: Thu, 19 Oct 2023 02:25:42 +0200 Subject: [PATCH] Split reporters into separate files --- spec/reporters/description_reporter_spec.cr | 57 ++++ spec/reporters/error_list_reporter_spec.cr | 54 ++++ spec/reporters/progress_reporter_spec.cr | 57 ++++ spec/reporters/summary_reporter_spec.cr | 45 ++++ spec/reporters_spec.cr | 204 -------------- spec/spec_helper.cr | 31 +-- src/microtest/reporters.cr | 250 +----------------- .../reporters/description_reporter.cr | 31 +++ .../reporters/error_list_reporter.cr | 51 ++++ src/microtest/reporters/progress_reporter.cr | 20 ++ .../reporters/slow_tests_reporter.cr | 41 +++ src/microtest/reporters/summary_reporter.cr | 45 ++++ src/microtest/reporters/terminal_reporter.cr | 64 +++++ 13 files changed, 483 insertions(+), 467 deletions(-) create mode 100644 spec/reporters/description_reporter_spec.cr create mode 100644 spec/reporters/error_list_reporter_spec.cr create mode 100644 spec/reporters/progress_reporter_spec.cr create mode 100644 spec/reporters/summary_reporter_spec.cr delete mode 100644 spec/reporters_spec.cr create mode 100644 src/microtest/reporters/description_reporter.cr create mode 100644 src/microtest/reporters/error_list_reporter.cr create mode 100644 src/microtest/reporters/progress_reporter.cr create mode 100644 src/microtest/reporters/slow_tests_reporter.cr create mode 100644 src/microtest/reporters/summary_reporter.cr create mode 100644 src/microtest/reporters/terminal_reporter.cr diff --git a/spec/reporters/description_reporter_spec.cr b/spec/reporters/description_reporter_spec.cr new file mode 100644 index 0000000..dcab1a4 --- /dev/null +++ b/spec/reporters/description_reporter_spec.cr @@ -0,0 +1,57 @@ +require "../spec_helper" + +describe Microtest::DescriptionReporter do + test "description reporter" do + result = record_test([Microtest::DescriptionReporter.new]) do + describe DescriptionReporter do + test "success" do + assert true + end + + test "failure" do + assert 3 > 5 + end + + test "error" do + raise "unexpected" + end + + test "skip" do + skip "skip this one" + end + end + end + + assert !result.success? + + # dot = Microtest::TerminalReporter::DOTS[:success] + + output = uncolor(result.stdout) + assert output.includes?("DescriptionReporterTest") + assert output.matches?(%r{ โœ“\s+\d+ .s success}) + assert output.matches?(%r{ โœ•\s+\d+ .s error}) + assert output.matches?(%r{ โœ•\s+\d+ .s failure}) + assert output.matches?(%r{ โœ“\s+\d+ .s skip}) + end + + test "description reporter abortion" do + result = record_test([Microtest::DescriptionReporter.new]) do + describe DescriptionReporter do + before do + raise "ABORTED" + end + + test "failure" do + assert false + end + end + end + + assert !result.success? + + output = uncolor(result.stdout) + + assert output.includes?("DescriptionReporterTest") + assert output.matches?(%r{ ๐Ÿ’ฅ\s+\d+ .s failure}) + end +end diff --git a/spec/reporters/error_list_reporter_spec.cr b/spec/reporters/error_list_reporter_spec.cr new file mode 100644 index 0000000..51eb98b --- /dev/null +++ b/spec/reporters/error_list_reporter_spec.cr @@ -0,0 +1,54 @@ +require "../spec_helper" + +describe Microtest::ErrorListReporter do + test "error list reporter" do + result = record_test([Microtest::ErrorListReporter.new]) do + describe Microtest do + test "success" do + assert true + end + + test "failure" do + assert 3 > 5 + end + + test "error" do + raise "unexpected" + end + + test "skip" do + skip "skip this one" + end + end + end + + assert !result.success? + + output = uncolor(result.stdout) + + assert output.matches?(%r{skip: skip this one in SPEC: spec/test.cr:16}) + assert output.matches?(%r{# 1 MicrotestTest#error SPEC: spec/test.cr:12\nunexpected\nโ”}) + assert output.matches?(%r{# 2 MicrotestTest#failure SPEC: spec/test.cr:\d+\nโ—† assert 3 > 5}) + end + + test "error list reporter abortion" do + result = record_test([Microtest::ErrorListReporter.new]) do + describe Microtest do + before do + raise "ABORTED" + end + + test "failure" do + assert false + end + end + end + + assert !result.success? + + output = uncolor(result.stdout) + + assert output.matches?(/ABORTED/) + assert output.matches?(%r{โ”— SPEC: spec/test.cr:\d+ before_hooks}) + end +end diff --git a/spec/reporters/progress_reporter_spec.cr b/spec/reporters/progress_reporter_spec.cr new file mode 100644 index 0000000..b96f5cb --- /dev/null +++ b/spec/reporters/progress_reporter_spec.cr @@ -0,0 +1,57 @@ +require "../spec_helper" + +describe Microtest::ProgressReporter do + test "progress reporter" do + result = record_test([Microtest::ProgressReporter.new]) do + describe Microtest do + test "success" do + assert true + end + + test "failure" do + assert 3 > 5 + end + + test "error" do + raise "unexpected" + end + + test "skip" do + skip "skip this one" + end + end + end + + assert !result.success? + + dot = Microtest::TerminalReporter::DOTS[:success] + + assert result.stdout == [ + dot.colorize(:green), + dot.colorize(:red), + dot.colorize(:yellow), + dot.colorize(:red), + "\n\n", + ].join + end + + test "progress reporter abortion" do + result = record_test([Microtest::ProgressReporter.new]) do + describe Microtest do + before do + raise "aborted" + end + + test "failure" do + assert false + end + end + end + + assert !result.success? + + bang = Microtest::TerminalReporter::DOTS[:abortion] + + assert result.stdout.includes?(bang.colorize(:yellow).to_s) + end +end diff --git a/spec/reporters/summary_reporter_spec.cr b/spec/reporters/summary_reporter_spec.cr new file mode 100644 index 0000000..b1812db --- /dev/null +++ b/spec/reporters/summary_reporter_spec.cr @@ -0,0 +1,45 @@ +require "../spec_helper" + +describe Microtest::SummaryReporter do + test "summary reporter" do + result = record_test([Microtest::SummaryReporter.new]) do + describe Microtest do + test "success" do + assert true + end + + test "failure" do + assert 3 > 5 + end + + test "skip" do + skip "skip this one" + end + end + end + + assert !result.success? + + output = uncolor(result.stdout) + assert output.matches?(%r{Executed 3/3 tests in \d+ยตs with seed 1}) + assert output.includes?("Success: 1, Skips: 1, Failures: 1") + end + + test "summary reporter abortion" do + result = record_test([Microtest::SummaryReporter.new]) do + describe Microtest do + before do + raise "aborted" + end + + test "failure" do + assert false + end + end + end + + assert !result.success? + + assert result.stdout.includes?("Test run was aborted by exception in hooks for \"failure\"") + end +end diff --git a/spec/reporters_spec.cr b/spec/reporters_spec.cr deleted file mode 100644 index dddb5c9..0000000 --- a/spec/reporters_spec.cr +++ /dev/null @@ -1,204 +0,0 @@ -require "./spec_helper" - -describe Microtest::Reporter do - test "progress reporter" do - result = record_test([Microtest::ProgressReporter.new]) do - describe Microtest do - test "success" do - assert true - end - - test "failure" do - assert 3 > 5 - end - - test "error" do - raise "unexpected" - end - - test "skip" do - skip "skip this one" - end - end - end - - assert !result.success? - - dot = Microtest::TerminalReporter::DOTS[:success] - - assert result.stdout == [ - dot.colorize(:green), - dot.colorize(:red), - dot.colorize(:yellow), - dot.colorize(:red), - "\n\n", - ].join - end - - test "progress reporter abortion" do - result = record_test([Microtest::ProgressReporter.new]) do - describe Microtest do - before do - raise "aborted" - end - - test "failure" do - assert false - end - end - end - - assert !result.success? - - bang = Microtest::TerminalReporter::DOTS[:abortion] - - assert result.stdout.includes?(bang.colorize(:yellow).to_s) - end - - test "description reporter" do - result = record_test([Microtest::DescriptionReporter.new]) do - describe DescriptionReporter do - test "success" do - assert true - end - - test "failure" do - assert 3 > 5 - end - - test "error" do - raise "unexpected" - end - - test "skip" do - skip "skip this one" - end - end - end - - assert !result.success? - - # dot = Microtest::TerminalReporter::DOTS[:success] - - output = uncolor(result.stdout) - assert output.includes?("DescriptionReporterTest") - assert output.matches?(%r{ โœ“\s+\d+ .s success}) - assert output.matches?(%r{ โœ•\s+\d+ .s error}) - assert output.matches?(%r{ โœ•\s+\d+ .s failure}) - assert output.matches?(%r{ โœ“\s+\d+ .s skip}) - end - - test "description reporter abortion" do - result = record_test([Microtest::DescriptionReporter.new]) do - describe DescriptionReporter do - before do - raise "ABORTED" - end - - test "failure" do - assert false - end - end - end - - assert !result.success? - - output = uncolor(result.stdout) - - assert output.includes?("DescriptionReporterTest") - assert output.matches?(%r{ ๐Ÿ’ฅ\s+\d+ .s failure}) - end - - test "error list reporter" do - result = record_test([Microtest::ErrorListReporter.new]) do - describe Microtest do - test "success" do - assert true - end - - test "failure" do - assert 3 > 5 - end - - test "error" do - raise "unexpected" - end - - test "skip" do - skip "skip this one" - end - end - end - - assert !result.success? - - output = uncolor(result.stdout) - - assert output.matches?(%r{skip: skip this one in SPEC: spec/test.cr:16}) - assert output.matches?(%r{# 1 MicrotestTest#error SPEC: spec/test.cr:12\nunexpected\nโ”}) - assert output.matches?(%r{# 2 MicrotestTest#failure SPEC: spec/test.cr:\d+\nโ—† assert 3 > 5}) - end - - test "error list reporter abortion" do - result = record_test([Microtest::ErrorListReporter.new]) do - describe Microtest do - before do - raise "ABORTED" - end - - test "failure" do - assert false - end - end - end - - assert !result.success? - - output = uncolor(result.stdout) - - assert output.matches?(/ABORTED/) - assert output.matches?(%r{โ”— SPEC: spec/test.cr:\d+ before_hooks}) - end - - test "summary reporter" do - result = record_test([Microtest::SummaryReporter.new]) do - describe Microtest do - test "success" do - assert true - end - - test "failure" do - assert 3 > 5 - end - - test "skip" do - skip "skip this one" - end - end - end - - assert !result.success? - - output = uncolor(result.stdout) - assert output.matches?(%r{Executed 3/3 tests in \d+ยตs with seed 1}) - assert output.includes?("Success: 1, Skips: 1, Failures: 1") - end - - test "summary reporter abortion" do - result = record_test([Microtest::SummaryReporter.new]) do - describe Microtest do - before do - raise "aborted" - end - - test "failure" do - assert false - end - end - end - - assert !result.success? - - assert result.stdout.includes?("Test run was aborted by exception in hooks for \"failure\"") - end -end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index c013673..b2e7be9 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -4,6 +4,7 @@ require "./helpers" include Microtest::DSL include Helpers +SPEC_ROOT = __DIR__ COLOR_REGEX = %r{\e\[\d\d?(;\d)?m} def uncolor(str) @@ -29,16 +30,16 @@ macro run_block(&block) CRYSTAL %} - stdout = IO::Memory.new - stderr = IO::Memory.new - input = IO::Memory.new({{c}}) + %stdout = IO::Memory.new + %stderr = IO::Memory.new + %input = IO::Memory.new({{c}}) - result = Process.run( - "crystal", ["run", "--no-color", "--no-codegen", "--error-trace", "--stdin-filename", "#{__DIR__}/test.cr"], - input: input, output: stdout, error: stderr + %result = Process.run( + "crystal", ["run", "--no-color", "--no-codegen", "--error-trace", "--stdin-filename", "#{SPEC_ROOT}/test.cr"], + input: %input, output: %stdout, error: %stderr ) - {result, stdout, stderr} + { %result, %stdout, %stderr} end macro record_test_json(seed = 1, &block) @@ -64,19 +65,19 @@ macro record_test(reporters = [] of Microtest::Reporter, seed = 1, &block) CRYSTAL %} - input = IO::Memory.new({{c}}) - stdout = IO::Memory.new - stderr = IO::Memory.new + %input = IO::Memory.new({{c}}) + %stdout = IO::Memory.new + %stderr = IO::Memory.new - s = Process.run( - "crystal", ["run", "--error-trace", "--stdin-filename", "#{__DIR__}/test.cr"], + %s = Process.run( + "crystal", ["run", "--error-trace", "--stdin-filename", "#{SPEC_ROOT}/test.cr"], env: {"SEED" => {{seed}}.to_s}, - input: input, output: stdout, error: stderr + input: %input, output: %stdout, error: %stderr ) - raise "Error running tests: #{stderr}" if !stderr.empty? + raise "Error running tests: #{%stderr}" if !%stderr.empty? - MicrotestStdoutResult.new(s, stdout.to_s, stderr.to_s) + MicrotestStdoutResult.new(%s, %stdout.to_s, %stderr.to_s) end Microtest.run! diff --git a/src/microtest/reporters.cr b/src/microtest/reporters.cr index 19ba2f2..e393066 100644 --- a/src/microtest/reporters.cr +++ b/src/microtest/reporters.cr @@ -1,249 +1,3 @@ require "./formatter" - -module Microtest - abstract class TerminalReporter < Reporter - alias ResultSymbols = {success: String, failure: String, skip: String, abortion: String} - alias ResultColors = {success: Symbol, failure: Symbol, skip: Symbol, abortion: Symbol} - - DEFAULT_COLORS = {success: :green, failure: :red, skip: :yellow, abortion: :yellow} - - DOT = "โ€ข" # Bullet "\u2022" - TICK = "โœ“" # Check Mark "\u2713" - CROSS = "โœ•" # Multiplication "\u2715" - SKULL = "๐Ÿ’€" - BANG = "๐Ÿ’ฅ" - - DOTS = {success: DOT, failure: DOT, skip: DOT, abortion: BANG} - TICKS = {success: TICK, failure: CROSS, skip: TICK, abortion: BANG} - - private getter t : Termart - - def initialize(io : IO = STDOUT) - super - @t = Termart.new(io, true) - end - - private def write(*args, **opts) - t.w(*args, **opts) - end - - private def writeln(*args, **opts) - t.l(*args, **opts) - end - - private def br - t.br - end - - private def flush - t.flush - end - - private def result_style( - result : TestResult, - symbols : ResultSymbols = TICKS, - colors : ResultColors = DEFAULT_COLORS - ) : Tuple(String, Symbol) - { - symbols[result.kind], - colors[result.kind], - } - end - - private def exception_to_string(ex : Exception, highlight : String? = nil) : String - String.build { |io| - io << ex.message.colorize(:red) - io << "\n" - - if b = ex.backtrace? - io << BacktracePrinter.new.call(b, highlight) - else - io << "(no backtrace)" - end - } - end - end - - class ProgressReporter < TerminalReporter - @chars : ResultSymbols - - def initialize(@chars = DOTS, io = STDOUT) - super(io) - end - - def report(result : TestResult) - symbol, color = result_style(result, @chars) - write(symbol, fg: color) - flush - end - - def finished(ctx : ExecutionContext) - br - br - end - end - - class DescriptionReporter < TerminalReporter - getter threshold : Time::Span - - def initialize(@threshold = 50.milliseconds, io = STDOUT) - super(io) - end - - def suite_started(ctx : ExecutionContext, cls : String) - br - writeln(cls, fg: :magenta, m: Colorize::Mode::Underline) - end - - def report(result : TestResult) - symbol, color = result_style(result, TICKS) - - time_text = Formatter.colorize_duration(result.duration, threshold) - - write(" ") - write(symbol, fg: color) - write(time_text.to_s) - write(" ") - write(result.test.name, fg: color) - br - end - - def finished(ctx : ExecutionContext) - br - end - end - - class ErrorListReporter < TerminalReporter - def report(result : TestResult) - end - - def finished(ctx : ExecutionContext) - ctx.skips.each do |skip| - ex = skip.exception - - write(skip.test.sanitized_name, ": ", ex.message, fg: :yellow) - write(" in ", BacktracePrinter.simplify_path(ex.file)[1], ":", ex.line, fg: :dark_gray) - br - end - - br if !ctx.skips.empty? - - ctx.failures.each_with_index do |failure, i| - print_failure(i, failure) - end - - if a = ctx.abortion_info - br - writeln(exception_to_string(a.exception)) - end - end - - private def print_failure(number : Int32, failure : TestFailure) - ex = failure.exception - test = failure.test - bold = Colorize::Mode::Bold - - write("# %-3d" % (number + 1), test.full_name, " ", fg: :red, m: bold) - - case ex - when AssertionFailure - path = BacktracePrinter.simplify_path(ex.file)[1] - write(path, ":", ex.line, fg: :light_gray, m: bold) - br - writeln(ex.message) - when UnexpectedError - path = BacktracePrinter.simplify_path(test.filename)[1] - write(path, ":", test.line_number, fg: :light_gray, m: bold) - br - writeln(exception_to_string(ex.exception, test.method_name)) - else Microtest.bug("Invalid Exception") - end - - br - end - end - - class SummaryReporter < TerminalReporter - def report(result : TestResult) - end - - def started(ctx : ExecutionContext) - @started_at = Time.local - end - - def finished(ctx : ExecutionContext) - total, unit = Formatter.format_duration(ctx.duration) - - if ctx.focus? - write("USING FOCUS:", bg: :red) - write(" ") - end - - fg = :light_blue - - write("Executed", fg: fg) - write(" #{ctx.executed_tests}/#{ctx.total_tests} ", fg: (ctx.executed_tests < ctx.total_tests) ? :red : fg) - write("tests in #{total}#{unit} with seed #{ctx.random_seed}", fg: fg) - br - - write("Success: ", ctx.total_success, fg: (:green if ctx.total_success > 0)) - write(", ") - - write("Skips: ", ctx.total_skip, fg: (:yellow if ctx.total_skip > 0)) - write(", ") - - write("Failures: ", ctx.total_failure, fg: (:red if ctx.total_failure > 0)) - br - - if ctx.manually_aborted? - br - writeln("Test run was aborted manually", fg: :white, bg: :red) - elsif a = ctx.abortion_info - br - writeln("Test run was aborted by exception in hooks for ", a.test.name.inspect, fg: :white, bg: :red) - end - - br - end - end - - class SlowTestsReporter < TerminalReporter - getter count : Int32 - getter threshold : Time::Span - - def initialize(@count = 3, @threshold = 50.milliseconds, io = STDOUT) - super(io) - end - - def report(result : TestResult) - end - - def finished(ctx : ExecutionContext) - res = ctx.results - .select { |r| r.duration >= threshold } - .sort! { |l, r| r.duration <=> l.duration } - .first(count) - - if res.empty? - num, unit = Formatter.format_duration(threshold) - writeln("No slow tests (threshold: #{num}#{unit})", fg: :dark_gray) - else - writeln("Slowest #{res.size} tests", fg: :light_blue) - br - - res.each do |r| - symbol, color = result_style(r) - - write(" ") - write(symbol, fg: color) - write(Formatter.colorize_duration(r.duration, threshold).to_s) - write(" ") - write(r.test.full_name, fg: color) - br - end - end - - br - end - end -end +require "./reporters/terminal_reporter" +require "./reporters/*" diff --git a/src/microtest/reporters/description_reporter.cr b/src/microtest/reporters/description_reporter.cr new file mode 100644 index 0000000..fb9ea83 --- /dev/null +++ b/src/microtest/reporters/description_reporter.cr @@ -0,0 +1,31 @@ +module Microtest + class DescriptionReporter < TerminalReporter + getter threshold : Time::Span + + def initialize(@threshold = 50.milliseconds, io = STDOUT) + super(io) + end + + def suite_started(ctx : ExecutionContext, cls : String) + br + writeln(cls, fg: :magenta, m: Colorize::Mode::Underline) + end + + def report(result : TestResult) + symbol, color = result_style(result, TICKS) + + time_text = Formatter.colorize_duration(result.duration, threshold) + + write(" ") + write(symbol, fg: color) + write(time_text.to_s) + write(" ") + write(result.test.name, fg: color) + br + end + + def finished(ctx : ExecutionContext) + br + end + end +end diff --git a/src/microtest/reporters/error_list_reporter.cr b/src/microtest/reporters/error_list_reporter.cr new file mode 100644 index 0000000..4c15b36 --- /dev/null +++ b/src/microtest/reporters/error_list_reporter.cr @@ -0,0 +1,51 @@ +module Microtest + class ErrorListReporter < TerminalReporter + def report(result : TestResult) + end + + def finished(ctx : ExecutionContext) + ctx.skips.each do |skip| + ex = skip.exception + + write(skip.test.sanitized_name, ": ", ex.message, fg: :yellow) + write(" in ", BacktracePrinter.simplify_path(ex.file)[1], ":", ex.line, fg: :dark_gray) + br + end + + br if !ctx.skips.empty? + + ctx.failures.each_with_index do |failure, i| + print_failure(i, failure) + end + + if a = ctx.abortion_info + br + writeln(exception_to_string(a.exception)) + end + end + + private def print_failure(number : Int32, failure : TestFailure) + ex = failure.exception + test = failure.test + bold = Colorize::Mode::Bold + + write("# %-3d" % (number + 1), test.full_name, " ", fg: :red, m: bold) + + case ex + when AssertionFailure + path = BacktracePrinter.simplify_path(ex.file)[1] + write(path, ":", ex.line, fg: :light_gray, m: bold) + br + writeln(ex.message) + when UnexpectedError + path = BacktracePrinter.simplify_path(test.filename)[1] + write(path, ":", test.line_number, fg: :light_gray, m: bold) + br + writeln(exception_to_string(ex.exception, test.method_name)) + else Microtest.bug("Invalid Exception") + end + + br + end + end +end diff --git a/src/microtest/reporters/progress_reporter.cr b/src/microtest/reporters/progress_reporter.cr new file mode 100644 index 0000000..656803e --- /dev/null +++ b/src/microtest/reporters/progress_reporter.cr @@ -0,0 +1,20 @@ +module Microtest + class ProgressReporter < TerminalReporter + @chars : ResultSymbols + + def initialize(@chars = DOTS, io = STDOUT) + super(io) + end + + def report(result : TestResult) + symbol, color = result_style(result, @chars) + write(symbol, fg: color) + flush + end + + def finished(ctx : ExecutionContext) + br + br + end + end +end diff --git a/src/microtest/reporters/slow_tests_reporter.cr b/src/microtest/reporters/slow_tests_reporter.cr new file mode 100644 index 0000000..1fe26c9 --- /dev/null +++ b/src/microtest/reporters/slow_tests_reporter.cr @@ -0,0 +1,41 @@ +module Microtest + class SlowTestsReporter < TerminalReporter + getter count : Int32 + getter threshold : Time::Span + + def initialize(@count = 3, @threshold = 50.milliseconds, io = STDOUT) + super(io) + end + + def report(result : TestResult) + end + + def finished(ctx : ExecutionContext) + res = ctx.results + .select { |r| r.duration >= threshold } + .sort! { |l, r| r.duration <=> l.duration } + .first(count) + + if res.empty? + num, unit = Formatter.format_duration(threshold) + writeln("No slow tests (threshold: #{num}#{unit})", fg: :dark_gray) + else + writeln("Slowest #{res.size} tests", fg: :light_blue) + br + + res.each do |r| + symbol, color = result_style(r) + + write(" ") + write(symbol, fg: color) + write(Formatter.colorize_duration(r.duration, threshold).to_s) + write(" ") + write(r.test.full_name, fg: color) + br + end + end + + br + end + end +end diff --git a/src/microtest/reporters/summary_reporter.cr b/src/microtest/reporters/summary_reporter.cr new file mode 100644 index 0000000..2e218c6 --- /dev/null +++ b/src/microtest/reporters/summary_reporter.cr @@ -0,0 +1,45 @@ +module Microtest + class SummaryReporter < TerminalReporter + def report(result : TestResult) + end + + def started(ctx : ExecutionContext) + @started_at = Time.local + end + + def finished(ctx : ExecutionContext) + total, unit = Formatter.format_duration(ctx.duration) + + if ctx.focus? + write("USING FOCUS:", bg: :red) + write(" ") + end + + fg = :light_blue + + write("Executed", fg: fg) + write(" #{ctx.executed_tests}/#{ctx.total_tests} ", fg: (ctx.executed_tests < ctx.total_tests) ? :red : fg) + write("tests in #{total}#{unit} with seed #{ctx.random_seed}", fg: fg) + br + + write("Success: ", ctx.total_success, fg: (:green if ctx.total_success > 0)) + write(", ") + + write("Skips: ", ctx.total_skip, fg: (:yellow if ctx.total_skip > 0)) + write(", ") + + write("Failures: ", ctx.total_failure, fg: (:red if ctx.total_failure > 0)) + br + + if ctx.manually_aborted? + br + writeln("Test run was aborted manually", fg: :white, bg: :red) + elsif a = ctx.abortion_info + br + writeln("Test run was aborted by exception in hooks for ", a.test.name.inspect, fg: :white, bg: :red) + end + + br + end + end +end diff --git a/src/microtest/reporters/terminal_reporter.cr b/src/microtest/reporters/terminal_reporter.cr new file mode 100644 index 0000000..40621b8 --- /dev/null +++ b/src/microtest/reporters/terminal_reporter.cr @@ -0,0 +1,64 @@ +module Microtest + abstract class TerminalReporter < Reporter + alias ResultSymbols = {success: String, failure: String, skip: String, abortion: String} + alias ResultColors = {success: Symbol, failure: Symbol, skip: Symbol, abortion: Symbol} + + DEFAULT_COLORS = {success: :green, failure: :red, skip: :yellow, abortion: :yellow} + + DOT = "โ€ข" # Bullet "\u2022" + TICK = "โœ“" # Check Mark "\u2713" + CROSS = "โœ•" # Multiplication "\u2715" + SKULL = "๐Ÿ’€" + BANG = "๐Ÿ’ฅ" + + DOTS = {success: DOT, failure: DOT, skip: DOT, abortion: BANG} + TICKS = {success: TICK, failure: CROSS, skip: TICK, abortion: BANG} + + private getter t : Termart + + def initialize(io : IO = STDOUT) + super + @t = Termart.new(io, true) + end + + private def write(*args, **opts) + t.w(*args, **opts) + end + + private def writeln(*args, **opts) + t.l(*args, **opts) + end + + private def br + t.br + end + + private def flush + t.flush + end + + private def result_style( + result : TestResult, + symbols : ResultSymbols = TICKS, + colors : ResultColors = DEFAULT_COLORS + ) : Tuple(String, Symbol) + { + symbols[result.kind], + colors[result.kind], + } + end + + private def exception_to_string(ex : Exception, highlight : String? = nil) : String + String.build { |io| + io << ex.message.colorize(:red) + io << "\n" + + if b = ex.backtrace? + io << BacktracePrinter.new.call(b, highlight) + else + io << "(no backtrace)" + end + } + end + end +end