cake-tester 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +24 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +79 -0
- data/LICENSE +373 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/bin/cake +34 -0
- data/bin/collector.rb +90 -0
- data/bin/runner.rb +133 -0
- data/bin/settings.rb +36 -0
- data/cake-tester.gemspec +18 -0
- data/lib/cake.rb +6 -0
- data/lib/contextual/child.rb +20 -0
- data/lib/contextual/context.rb +32 -0
- data/lib/contextual/group.rb +45 -0
- data/lib/contextual/node.rb +80 -0
- data/lib/contextual/parent.rb +191 -0
- data/lib/contextual/test.rb +151 -0
- data/lib/contextual/test_runner.rb +71 -0
- data/lib/expect.rb +154 -0
- data/lib/helpers/filter_settings.rb +94 -0
- data/lib/helpers/printer.rb +146 -0
- data/lib/test_failure.rb +54 -0
- data/lib/test_message.rb +23 -0
- data/lib/test_neutral.rb +31 -0
- data/lib/test_options.rb +25 -0
- data/lib/test_pass.rb +23 -0
- data/lib/test_result.rb +70 -0
- data/test/test_basic.cake.rb +12 -0
- data/test/test_context.cake.rb +102 -0
- data/test/test_expects.cake.rb +48 -0
- data/test/test_failures.cake.rb +46 -0
- data/test/test_group.cake.rb +20 -0
- data/test/test_runner.cake.rb +20 -0
- data/test/test_skip.cake.rb +37 -0
- metadata +83 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../test_failure'
|
4
|
+
require_relative '../test_neutral'
|
5
|
+
require_relative '../test_pass'
|
6
|
+
require_relative 'context'
|
7
|
+
require_relative 'node'
|
8
|
+
require_relative 'child'
|
9
|
+
|
10
|
+
# A Test is an actionable unit of testing.
|
11
|
+
class Test < Contextual::Node
|
12
|
+
include Contextual::Child
|
13
|
+
# @return [Boolean]
|
14
|
+
attr_reader :ran_successfully
|
15
|
+
|
16
|
+
# @param title [String]
|
17
|
+
# @param assertions [Proc]
|
18
|
+
# @param action [Proc]
|
19
|
+
# @param skip [Boolean]
|
20
|
+
# @param options [TestOptions]
|
21
|
+
def initialize(
|
22
|
+
title,
|
23
|
+
assertions:,
|
24
|
+
setup: nil,
|
25
|
+
teardown: nil,
|
26
|
+
action: nil,
|
27
|
+
options: nil,
|
28
|
+
skip: false
|
29
|
+
)
|
30
|
+
super(title, setup: setup, teardown: teardown, options: options, skip: skip)
|
31
|
+
@action = action
|
32
|
+
@assertions = assertions
|
33
|
+
@assert_failures = []
|
34
|
+
end
|
35
|
+
|
36
|
+
def ran_successfully=(value)
|
37
|
+
# Once this has failed, don't allow it to recover
|
38
|
+
return if @ran_successfully == false
|
39
|
+
|
40
|
+
@ran_successfully = value
|
41
|
+
end
|
42
|
+
|
43
|
+
# Should this run with the current filter settings
|
44
|
+
# @param filter_settings [FilterSettings]
|
45
|
+
# @return [Boolean]
|
46
|
+
def should_run_with_filter(filter_settings)
|
47
|
+
return filter_settings.testSearchFor == _title if filter_settings.hasTestSearchFor
|
48
|
+
return _title.include? filter_settings.testFilterTerm if filter_settings.hasTestFilterTerm
|
49
|
+
|
50
|
+
should_run_with_search_term(filter_settings)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Report results, if any
|
54
|
+
# @return [void]
|
55
|
+
def report(*)
|
56
|
+
@result.report(@parent_count)
|
57
|
+
|
58
|
+
@assert_failures.each do |assert_failure|
|
59
|
+
assert_failure.report(@parent_count)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param filter_settings [FilterSettings]
|
64
|
+
# @return [TestResult]
|
65
|
+
def get_result(filter_settings)
|
66
|
+
super
|
67
|
+
|
68
|
+
@result = run_setup
|
69
|
+
|
70
|
+
unless @result.nil?
|
71
|
+
# Setup failed, we want to bail out as soon as possible
|
72
|
+
@ran_successfully = false
|
73
|
+
return @result
|
74
|
+
end
|
75
|
+
|
76
|
+
run_action
|
77
|
+
|
78
|
+
run_assertions
|
79
|
+
|
80
|
+
teardown_failure = run_teardown
|
81
|
+
unless teardown_failure.nil?
|
82
|
+
@ran_successfully = false
|
83
|
+
@result = teardown_failure
|
84
|
+
end
|
85
|
+
|
86
|
+
# At this point, if nothing has failed, this test ran successfully
|
87
|
+
@ran_successfully = true if @ran_successfully.nil?
|
88
|
+
@result
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def run_action
|
94
|
+
return if @action.nil?
|
95
|
+
|
96
|
+
begin
|
97
|
+
value = @action.call(@context)
|
98
|
+
@context.actual = value unless value.nil?
|
99
|
+
rescue StandardError => e
|
100
|
+
@ran_successfully = false
|
101
|
+
# We want to continue and try to teardown anything we've set up
|
102
|
+
# even if it's all haywire at this point
|
103
|
+
@result = TestFailure.new(@title, 'Failed during action', e)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [TestResult]
|
108
|
+
def run_assertions
|
109
|
+
# Don't bother running assertions if we've already come up failed
|
110
|
+
return unless @result.nil?
|
111
|
+
|
112
|
+
asserts = @assertions.call(@context)
|
113
|
+
has_failed_a_test = false
|
114
|
+
assert_result = nil
|
115
|
+
|
116
|
+
asserts.each_with_index do |expect, index|
|
117
|
+
# Skip rest of assertions if an assert has failed already,
|
118
|
+
# allowing a bypass with an option flag.
|
119
|
+
if has_failed_a_test && @options.fail_on_first_expect
|
120
|
+
assert_result = AssertNeutral.new(
|
121
|
+
'Skipped: Previous assert failed.',
|
122
|
+
index
|
123
|
+
)
|
124
|
+
@assert_failures << assert_result
|
125
|
+
next
|
126
|
+
end
|
127
|
+
|
128
|
+
begin
|
129
|
+
assert_result = expect.run
|
130
|
+
rescue StandardError => e
|
131
|
+
assert_result = TestFailure.new(
|
132
|
+
@title,
|
133
|
+
'Failed while running assertions',
|
134
|
+
e
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
next unless assert_result.instance_of? AssertFailure
|
139
|
+
|
140
|
+
assert_result.index = index if asserts.length > 1
|
141
|
+
@assert_failures << assert_result
|
142
|
+
has_failed_a_test = true
|
143
|
+
end
|
144
|
+
|
145
|
+
@result = if @assert_failures.empty?
|
146
|
+
TestPass.new(@title)
|
147
|
+
else
|
148
|
+
TestFailure.new(@title, 'Assert failed.')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../helpers/filter_settings'
|
4
|
+
require_relative '../helpers/printer'
|
5
|
+
require_relative 'node'
|
6
|
+
require_relative 'parent'
|
7
|
+
|
8
|
+
# TestRunner
|
9
|
+
#
|
10
|
+
# @param title
|
11
|
+
# @param context_builder
|
12
|
+
# @param children
|
13
|
+
# @param options
|
14
|
+
# @param skip
|
15
|
+
#
|
16
|
+
# The root node for any tests. Automatically runs once created. Do not include
|
17
|
+
# TestRunners inside other Groups.
|
18
|
+
class TestRunner < Contextual::Node
|
19
|
+
include Contextual::Parent
|
20
|
+
|
21
|
+
def initialize(
|
22
|
+
title,
|
23
|
+
children = [],
|
24
|
+
setup: nil,
|
25
|
+
teardown: nil,
|
26
|
+
options: nil,
|
27
|
+
skip: false
|
28
|
+
)
|
29
|
+
super(title, setup: setup, teardown: teardown, options: options, skip: skip)
|
30
|
+
set_parent(children)
|
31
|
+
# TODO: Fetch filter settings
|
32
|
+
@filter_settings = FilterSettings.new
|
33
|
+
run_all
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def run_all
|
39
|
+
return if skip
|
40
|
+
|
41
|
+
return unless should_run_with_filter(@filter_settings)
|
42
|
+
|
43
|
+
run(@context, @filter_settings)
|
44
|
+
report(@filter_settings)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param filter_settings [FilterSettings]
|
48
|
+
def should_run_with_filter(filter_settings)
|
49
|
+
return @title == filter_settings.test_runner_search_for if filter_settings.has_test_runner_search_for
|
50
|
+
return @title.include? filter_settings.test_runner_filter_term if filter_settings.has_test_runner_filter_term
|
51
|
+
|
52
|
+
should_run_with_search_term_with_children(filter_settings)
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def report(filter_settings)
|
57
|
+
@result.report
|
58
|
+
return if @skip
|
59
|
+
|
60
|
+
report_children(filter_settings)
|
61
|
+
|
62
|
+
# Get count of successes, failures, and neutrals
|
63
|
+
message = Printer.summary(total, successes, failures, neutrals)
|
64
|
+
|
65
|
+
Printer.pass(message) if @result.instance_of? TestPass
|
66
|
+
|
67
|
+
Printer.fail(message) if @result.instance_of? TestFailure
|
68
|
+
|
69
|
+
Printer.neutral(message) if @result.instance_of? TestNeutral
|
70
|
+
end
|
71
|
+
end
|
data/lib/expect.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Contains expects in order to run assertions in a [#Test]
|
4
|
+
module Expect
|
5
|
+
# @abstract Base class for Expects. Override {#run} to implement the
|
6
|
+
# validation logic.
|
7
|
+
class ExpectBase
|
8
|
+
attr_accessor :actual, :expected
|
9
|
+
|
10
|
+
# @param [Object] actual
|
11
|
+
# @param [Object] expected
|
12
|
+
def initialize(actual:, expected: nil)
|
13
|
+
@actual = actual
|
14
|
+
@expected = expected
|
15
|
+
end
|
16
|
+
|
17
|
+
# Runs the validator and returns an AssertResult
|
18
|
+
# @return [AssertPass, AssertFailure]
|
19
|
+
def run
|
20
|
+
AssertFailure.new("No expectation defined for #{@actual}.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Checks if actual == expected
|
25
|
+
class Equals < ExpectBase
|
26
|
+
# Runs the validator and returns an AssertResult
|
27
|
+
# @return [AssertPass, AssertFailure]
|
28
|
+
def run
|
29
|
+
return AssertPass.new if @actual == @expected
|
30
|
+
|
31
|
+
@actual = @actual.nil? ? '<nil>' : @actual
|
32
|
+
@expected = @expected.nil? ? '<nil>' : @expected
|
33
|
+
AssertFailure.new("Equality failed: Expected #{@expected}, got #{@actual}.")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks if actual != expected
|
38
|
+
class IsNotEqual < ExpectBase
|
39
|
+
def initialize(actual:, not_expected:)
|
40
|
+
super(actual: actual, expected: not_expected)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Runs the validator and returns an AssertResult
|
44
|
+
# @return [AssertPass, AssertFailure]
|
45
|
+
def run
|
46
|
+
return AssertPass.new if @actual != @expected
|
47
|
+
|
48
|
+
@actual = @actual.nil? ? '<nil>' : @actual
|
49
|
+
@expected = @expected.nil? ? '<nil>' : @expected
|
50
|
+
AssertFailure.new("Inequality failed: Expected #{@expected} to not equal #{@actual}.")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Checks if actual is nil
|
55
|
+
class IsNil < ExpectBase
|
56
|
+
def initialize(actual)
|
57
|
+
super(actual: actual)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Runs the validator and returns an AssertResult
|
61
|
+
# @return [AssertPass, AssertFailure]
|
62
|
+
def run
|
63
|
+
return AssertPass.new if @actual.nil?
|
64
|
+
|
65
|
+
AssertFailure.new("IsNil failed: Expected #{@actual} to be nil.")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Checks if actual is not nil
|
70
|
+
class IsNotNil < ExpectBase
|
71
|
+
def initialize(actual)
|
72
|
+
super(actual: actual)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Runs the validator and returns an AssertResult
|
76
|
+
# @return [AssertPass, AssertFailure]
|
77
|
+
def run
|
78
|
+
return AssertPass.new unless @actual.nil?
|
79
|
+
|
80
|
+
AssertFailure.new("IsNotNil failed: Expected #{@actual} to not be nil.")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Checks if actual is truthy
|
85
|
+
class IsTrue < ExpectBase
|
86
|
+
def initialize(actual)
|
87
|
+
super(actual: actual)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Runs the validator and returns an AssertResult
|
91
|
+
# @return [AssertPass, AssertFailure]
|
92
|
+
def run
|
93
|
+
return AssertPass.new if @actual
|
94
|
+
|
95
|
+
@actual = @actual.nil? ? '<nil>' : @actual
|
96
|
+
AssertFailure.new("IsTrue failed: Expected #{@actual} to be true.")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Checks if actual is falsey
|
101
|
+
class IsFalse < ExpectBase
|
102
|
+
def initialize(actual)
|
103
|
+
super(actual: actual, expected: true)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Runs the validator and returns an AssertResult
|
107
|
+
# @return [AssertPass, AssertFailure]
|
108
|
+
def run
|
109
|
+
return AssertPass.new unless @actual
|
110
|
+
|
111
|
+
AssertFailure.new("IsFalse failed: Expected #{@actual} to be false.")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Checks if actual responds to a method
|
116
|
+
class RespondTo < ExpectBase
|
117
|
+
def initialize(actual, method)
|
118
|
+
super(actual: actual)
|
119
|
+
@method = method
|
120
|
+
end
|
121
|
+
|
122
|
+
# Runs the validator and returns an AssertResult
|
123
|
+
# @return [AssertPass, AssertFailure]
|
124
|
+
def run
|
125
|
+
return AssertPass.new if @actual.respond_to?(@method)
|
126
|
+
|
127
|
+
@actual = @actual.nil? ? '<nil>' : @actual
|
128
|
+
AssertFailure.new("RespondsTo failed: Expected #{@actual} to respond to #{@method}.")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Checks if actual does not respond to a method
|
133
|
+
class Undefined < ExpectBase
|
134
|
+
def initialize(actual, method)
|
135
|
+
super(actual: actual)
|
136
|
+
@method = method
|
137
|
+
end
|
138
|
+
|
139
|
+
# Runs the validator and returns an AssertResult
|
140
|
+
# @return [AssertPass, AssertFailure]
|
141
|
+
def run
|
142
|
+
return AssertPass.new unless @actual.methods.include?(@method)
|
143
|
+
|
144
|
+
@actual = @actual.nil? ? '<nil>' : @actual
|
145
|
+
AssertFailure.new("Undefined failed: Expected #{@actual} to not respond to #{@method}.")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Checks if actual does not respond to a method
|
150
|
+
# Synonym for Expect::Undefined
|
151
|
+
# @note (see Expect::Undefined)
|
152
|
+
class DoesNotRespondTo < Undefined
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Holds a collection of filter settings. Filter settings are normally set via
|
4
|
+
# the command line as flag options.
|
5
|
+
class FilterSettings
|
6
|
+
def initialize(
|
7
|
+
general_search_term: nil,
|
8
|
+
test_filter_term: nil,
|
9
|
+
test_search_for: nil,
|
10
|
+
group_filter_term: nil,
|
11
|
+
group_search_for: nil,
|
12
|
+
test_runner_filter_term: nil,
|
13
|
+
test_runner_search_for: nil
|
14
|
+
)
|
15
|
+
@general_search_term = general_search_term || get_from_args(FilterProps::GENERAL_SEARCH_TERM)
|
16
|
+
@test_filter_term = test_filter_term || get_from_args(FilterProps::TEST_FILTER_TERM)
|
17
|
+
@test_search_for = test_search_for || get_from_args(FilterProps::TEST_SEARCH_FOR)
|
18
|
+
@group_filter_term = group_filter_term || get_from_args(FilterProps::GROUP_FILTER_TERM)
|
19
|
+
@group_search_for = group_search_for || get_from_args(FilterProps::GROUP_SEARCH_FOR)
|
20
|
+
@test_runner_filter_term = test_runner_filter_term || get_from_args(FilterProps::TEST_RUNNER_FILTER_TERM)
|
21
|
+
@test_runner_search_for = test_runner_search_for || get_from_args(FilterProps::TEST_RUNNER_SEARCH_FOR)
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_general_search_term
|
25
|
+
!@general_search_term.nil? && @general_search_term.length.positive?
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_test_filter_term
|
29
|
+
!@test_filter_term.nil? && @test_filter_term.length.positive?
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_test_search_for
|
33
|
+
!@test_search_for.nil? && @test_search_for.length.positive?
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_group_filter_term
|
37
|
+
!@group_filter_term.nil? && @group_filter_term.length.positive?
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_group_search_for
|
41
|
+
!@group_search_for.nil? && @group_search_for.length.positive?
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_test_runner_filter_term
|
45
|
+
!@test_runner_filter_term.nil? && @test_runner_filter_term.length.positive?
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_test_runner_search_for
|
49
|
+
!@test_runner_search_for.nil? && @test_runner_search_for.length.positive?
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_not_empty
|
53
|
+
hasGeneralSearchTerm ||
|
54
|
+
hasTestFilterTerm ||
|
55
|
+
hasTestSearchFor ||
|
56
|
+
hasGroupFilterTerm ||
|
57
|
+
hasGroupSearchFor ||
|
58
|
+
hasTestRunnerFilterTerm ||
|
59
|
+
hasTestRunnerSearchFor
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_properties
|
63
|
+
props = []
|
64
|
+
|
65
|
+
props << [FilterProps::GENERAL_SEARCH_TERM, @general_search_term] if has_general_search_term
|
66
|
+
props << [FilterProps::TEST_FILTER_TERM, @test_filter_term] if has_test_filter_term
|
67
|
+
props << [FilterProps::TEST_SEARCH_FOR, @test_search_for] if has_test_search_for
|
68
|
+
props << [FilterProps::GROUP_FILTER_TERM, @group_filter_term] if has_group_filter_term
|
69
|
+
props << [FilterProps::GROUP_SEARCH_FOR, @group_search_for] if has_group_search_for
|
70
|
+
props << [FilterProps::TEST_RUNNER_FILTER_TERM, @test_runner_filter_term] if has_test_runner_filter_term
|
71
|
+
props << [FilterProps::TEST_RUNNER_SEARCH_FOR, @test_runner_search_for] if has_test_runner_search_for
|
72
|
+
|
73
|
+
props
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param [String] flag
|
77
|
+
# @return [String, Nil]
|
78
|
+
def get_from_args(flag)
|
79
|
+
index = ARGV.find_index(flag)
|
80
|
+
return ARGV[index + 1] if index && index != ARGV.length - 1
|
81
|
+
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
module FilterProps
|
87
|
+
GENERAL_SEARCH_TERM = 'generalSearchTerm'
|
88
|
+
TEST_FILTER_TERM = 'testFilterTerm'
|
89
|
+
TEST_SEARCH_FOR = 'testSearchFor'
|
90
|
+
GROUP_FILTER_TERM = 'groupFilterTerm'
|
91
|
+
GROUP_SEARCH_FOR = 'groupSearchFor'
|
92
|
+
TEST_RUNNER_FILTER_TERM = 'testRunnerFilterTerm'
|
93
|
+
TEST_RUNNER_SEARCH_FOR = 'testRunnerSearchFor'
|
94
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Printer
|
4
|
+
# Prints a message in green to the console.
|
5
|
+
#
|
6
|
+
# Parameters:
|
7
|
+
# - message: A string message to be printed.
|
8
|
+
#
|
9
|
+
# Returns:
|
10
|
+
# None
|
11
|
+
def self.pass(message)
|
12
|
+
puts "\x1B[32m#{message}\x1B[0m"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Prints a neutral message in red.
|
16
|
+
#
|
17
|
+
# Params:
|
18
|
+
# - message: The error message to be displayed.
|
19
|
+
#
|
20
|
+
# Returns:
|
21
|
+
# None
|
22
|
+
def self.fail(message)
|
23
|
+
puts "\x1B[31m#{message}\x1B[0m"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Prints a neutral message in gray.
|
27
|
+
#
|
28
|
+
# Parameters:
|
29
|
+
# - message: A string representing the message to be printed.
|
30
|
+
#
|
31
|
+
# Returns: None
|
32
|
+
def self.neutral(message)
|
33
|
+
puts "\x1B[90m#{message}\x1B[0m"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generates a summary message based on the total number of tests, the number of successful tests,
|
37
|
+
# the number of failed tests, and the number of neutral tests. The summary message is formatted
|
38
|
+
# as a box with the following structure:
|
39
|
+
#
|
40
|
+
# - Summary: -------------------
|
41
|
+
# | |
|
42
|
+
# | $total tests ran. |
|
43
|
+
# | - $successes passed. |
|
44
|
+
# | - $failures failed. |
|
45
|
+
# | - $neutrals skipped/inconclusive. |
|
46
|
+
# -------------------------------
|
47
|
+
#
|
48
|
+
# Parameters:
|
49
|
+
# - total (int): The total number of tests.
|
50
|
+
# - successes (int): The number of successful tests.
|
51
|
+
# - failures (int): The number of failed tests.
|
52
|
+
# - neutrals (int): The number of neutral tests.
|
53
|
+
#
|
54
|
+
# Returns:
|
55
|
+
# - message (str): The formatted summary message.
|
56
|
+
def self.summary(total, successes, failures, neutrals)
|
57
|
+
total_char_count = total.to_s.length
|
58
|
+
success_char_count = successes.to_s.length
|
59
|
+
failure_char_count = failures.to_s.length
|
60
|
+
neutral_char_count = neutrals.to_s.length
|
61
|
+
max_char_count = [total_char_count, success_char_count, failure_char_count, neutral_char_count].max
|
62
|
+
|
63
|
+
base_top_dash_spacer = 18
|
64
|
+
base_blank_spacer = 28
|
65
|
+
base_total_spacer = 14
|
66
|
+
base_success_spacer = 15
|
67
|
+
base_failure_spacer = 15
|
68
|
+
base_neutral_spacer = 1
|
69
|
+
box_length = 29
|
70
|
+
|
71
|
+
total_extra_space = max_char_count - total_char_count + base_total_spacer
|
72
|
+
success_extra_space = max_char_count - success_char_count + base_success_spacer
|
73
|
+
failure_extra_space = max_char_count - failure_char_count + base_failure_spacer
|
74
|
+
neutral_extra_space = max_char_count - neutral_char_count + base_neutral_spacer
|
75
|
+
|
76
|
+
# There are only two fields that might realistically overflow the box
|
77
|
+
# - total and neutrals. Since total will always be >= passed and failed
|
78
|
+
# count, we can just look at total and push out the space from there.
|
79
|
+
all_extra_space = 0
|
80
|
+
|
81
|
+
if neutral_extra_space == max_char_count && max_char_count > base_neutral_spacer
|
82
|
+
all_extra_space = max_char_count - base_neutral_spacer
|
83
|
+
elsif total_extra_space == max_char_count && max_char_count > base_total_spacer
|
84
|
+
all_extra_space = max_char_count - base_total_spacer
|
85
|
+
end
|
86
|
+
|
87
|
+
# Create a box that looks like this:
|
88
|
+
# - Summary: -------------------
|
89
|
+
# | |
|
90
|
+
# | $total tests ran. |
|
91
|
+
# | - $successes passed. |
|
92
|
+
# | - $failures failed. |
|
93
|
+
# | - $neutrals skipped/inconclusive. |
|
94
|
+
# -------------------------------
|
95
|
+
|
96
|
+
message = "\n"
|
97
|
+
message += ' - Summary: '
|
98
|
+
(base_top_dash_spacer + all_extra_space).times do
|
99
|
+
message += '-'
|
100
|
+
end
|
101
|
+
message += "\n"
|
102
|
+
|
103
|
+
# | | (blank spacer line)
|
104
|
+
message += '|'
|
105
|
+
(base_blank_spacer + all_extra_space).times do
|
106
|
+
message += ' '
|
107
|
+
end
|
108
|
+
message += "|\n"
|
109
|
+
|
110
|
+
# | $total tests ran. |
|
111
|
+
message += "| #{total} tests ran."
|
112
|
+
(total_extra_space + all_extra_space).times do
|
113
|
+
message += ' '
|
114
|
+
end
|
115
|
+
message += "|\n"
|
116
|
+
|
117
|
+
# | - $successes passed. |
|
118
|
+
message += "| - #{successes} passed."
|
119
|
+
(success_extra_space + all_extra_space).times do
|
120
|
+
message += ' '
|
121
|
+
end
|
122
|
+
message += "|\n"
|
123
|
+
|
124
|
+
# | - $failures failed. |
|
125
|
+
message += "| - #{failures} failed."
|
126
|
+
(failure_extra_space + all_extra_space).times do
|
127
|
+
message += ' '
|
128
|
+
end
|
129
|
+
message += "|\n"
|
130
|
+
|
131
|
+
# | - $neutrals skipped/inconclusive. |
|
132
|
+
message += "| - #{neutrals} skipped/inconclusive."
|
133
|
+
(neutral_extra_space + all_extra_space).times do
|
134
|
+
message += ' '
|
135
|
+
end
|
136
|
+
message += "|\n"
|
137
|
+
|
138
|
+
# -------------------------------
|
139
|
+
(box_length + all_extra_space).times do
|
140
|
+
message += '-'
|
141
|
+
end
|
142
|
+
message += "\n"
|
143
|
+
|
144
|
+
message
|
145
|
+
end
|
146
|
+
end
|
data/lib/test_failure.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_result'
|
4
|
+
require_relative 'helpers/printer'
|
5
|
+
|
6
|
+
# Test that has failed, either with a provided message or thrown error
|
7
|
+
class TestFailure < TestResult
|
8
|
+
def initialize(title, message = nil, err = nil)
|
9
|
+
super(title)
|
10
|
+
@message = message
|
11
|
+
@err = err
|
12
|
+
end
|
13
|
+
|
14
|
+
def report(spacer_count = 0)
|
15
|
+
super
|
16
|
+
Printer.fail(@spacer + format_message)
|
17
|
+
return if @err.nil?
|
18
|
+
|
19
|
+
# Extra space is to compensate for the [X]
|
20
|
+
Printer.fail("#{@spacer} #{@err}")
|
21
|
+
|
22
|
+
# Mimic the default Ruby trace. This does not need additional formatting as
|
23
|
+
# some terminals recognize this as a stack trace.
|
24
|
+
puts @err.backtrace.join("\n\t")
|
25
|
+
.sub("\n\t", ": #{@err}#{@err.class ? " (#{@err.class})" : ''}\n\t")
|
26
|
+
end
|
27
|
+
|
28
|
+
def failures
|
29
|
+
1
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def format_message
|
35
|
+
if @title.nil?
|
36
|
+
" #{@message}"
|
37
|
+
else
|
38
|
+
"[X] #{@title}: #{@message}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Assert that has failed with a message explaining why the assertion failed
|
44
|
+
class AssertFailure < AssertResult
|
45
|
+
def initialize(message)
|
46
|
+
super(message, nil)
|
47
|
+
end
|
48
|
+
|
49
|
+
def report(spacer_count)
|
50
|
+
super
|
51
|
+
|
52
|
+
Printer.fail(@spacer + format_message)
|
53
|
+
end
|
54
|
+
end
|
data/lib/test_message.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'test_result'
|
4
|
+
require_relative 'helpers/printer'
|
5
|
+
|
6
|
+
# Prints system messages, generally ran by the CLI runner
|
7
|
+
class TestMessage < TestResult
|
8
|
+
attr_accessor :message
|
9
|
+
|
10
|
+
def initialize(title, message)
|
11
|
+
super(title)
|
12
|
+
@message = message
|
13
|
+
end
|
14
|
+
|
15
|
+
def report(spacer_count = 0)
|
16
|
+
super
|
17
|
+
if message.nil?
|
18
|
+
Printer.neutral(@spacer + @title)
|
19
|
+
else
|
20
|
+
Printer.neutral("#{@spacer}#{@title} #{@message}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|