spectus 3.0.10 → 3.1.4
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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +75 -84
- data/lib/spectus.rb +3 -5
- data/lib/spectus/exam.rb +56 -0
- data/lib/spectus/expectation_target.rb +87 -70
- data/lib/spectus/requirement_level/base.rb +40 -81
- data/lib/spectus/requirement_level/may.rb +17 -0
- data/lib/spectus/requirement_level/must.rb +17 -0
- data/lib/spectus/requirement_level/should.rb +17 -0
- data/lib/spectus/result.rb +15 -0
- data/lib/spectus/result/common.rb +180 -0
- data/lib/spectus/result/fail.rb +38 -17
- data/lib/spectus/result/pass.rb +47 -22
- metadata +69 -42
- data/lib/spectus/matchers.rb +0 -33
- data/lib/spectus/report.rb +0 -92
- data/lib/spectus/requirement_level/high.rb +0 -27
- data/lib/spectus/requirement_level/low.rb +0 -27
- data/lib/spectus/requirement_level/medium.rb +0 -27
- data/lib/spectus/result/base.rb +0 -112
- data/lib/spectus/sandbox.rb +0 -61
@@ -1,109 +1,68 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'aw'
|
4
|
-
|
5
3
|
module Spectus
|
6
4
|
# Namespace for the requirement levels.
|
7
|
-
#
|
8
|
-
# @api private
|
9
|
-
#
|
10
5
|
module RequirementLevel
|
11
6
|
# Requirement level's base class.
|
12
|
-
#
|
13
7
|
class Base
|
14
8
|
# Initialize the requirement level class.
|
15
9
|
#
|
16
|
-
# @param
|
17
|
-
# @param
|
18
|
-
# @param
|
19
|
-
# @param
|
20
|
-
def initialize(
|
21
|
-
@
|
22
|
-
@
|
23
|
-
|
24
|
-
@
|
10
|
+
# @param callable [#call] The callable object to test.
|
11
|
+
# @param isolation [Boolean] Compute actual in isolation?
|
12
|
+
# @param negate [Boolean] Positive or negative assertion?
|
13
|
+
# @param matcher [#matches?] The matcher.
|
14
|
+
def initialize(callable:, isolation:, negate:, matcher:)
|
15
|
+
@negate = negate
|
16
|
+
@matcher = matcher
|
17
|
+
|
18
|
+
@exam = Exam.new(
|
19
|
+
callable: callable,
|
20
|
+
isolation: isolation,
|
21
|
+
negate: negate,
|
22
|
+
matcher: matcher
|
23
|
+
)
|
25
24
|
end
|
26
25
|
|
27
|
-
#
|
28
|
-
|
29
|
-
|
26
|
+
# @return [#Exam] The exam.
|
27
|
+
attr_reader :exam
|
28
|
+
|
29
|
+
# @return [#matches?] The matcher that performed a boolean comparison
|
30
|
+
# between the actual value and the expected value.
|
30
31
|
attr_reader :matcher
|
31
32
|
|
32
|
-
# The
|
33
|
+
# The result of the expectation.
|
33
34
|
#
|
34
|
-
# @
|
35
|
-
|
36
|
-
|
35
|
+
# @raise [Result::Fail] The expectation is `false`.
|
36
|
+
# @return [Result::Pass] The expectation is `true`.
|
37
|
+
def call
|
38
|
+
Result.call(pass?,
|
39
|
+
actual: exam.actual,
|
40
|
+
error: exam.exception,
|
41
|
+
expected: matcher.expected,
|
42
|
+
got: exam.got,
|
43
|
+
negate: negate?,
|
44
|
+
valid: exam.valid?,
|
45
|
+
matcher: matcher.class.to_sym,
|
46
|
+
level: level)
|
37
47
|
end
|
38
48
|
|
39
|
-
# @!attribute [r] subject
|
40
|
-
#
|
41
|
-
# @return [#object_id] The front object to test.
|
42
|
-
attr_reader :subject
|
43
|
-
|
44
|
-
# @!attribute [r] challenges
|
45
|
-
#
|
46
|
-
# @return [Array] A list of challenges.
|
47
|
-
attr_reader :challenges
|
48
|
-
|
49
49
|
protected
|
50
50
|
|
51
|
-
# @param state [Sandbox] The sandbox that tested the code.
|
52
|
-
#
|
53
|
-
# @return [Result::Pass] Pass the spec.
|
54
|
-
def pass!(state)
|
55
|
-
r = Report.new(matcher, negate?, state, true)
|
56
|
-
|
57
|
-
Result::Pass.new(r, *result_signature(state))
|
58
|
-
end
|
59
|
-
|
60
|
-
# @param state [Sandbox] The sandbox that tested the code.
|
61
|
-
#
|
62
|
-
# @raise [Result::Fail] Fail the spec.
|
63
|
-
def fail!(state)
|
64
|
-
r = Report.new(matcher, negate?, state, false)
|
65
|
-
|
66
|
-
raise Result::Fail.new(r, *result_signature(state)), r, caller[2..-1]
|
67
|
-
end
|
68
|
-
|
69
|
-
# @param state [Sandbox] The sandbox that tested the code.
|
70
|
-
#
|
71
|
-
# @return [Array] List of parameters.
|
72
|
-
def result_signature(state)
|
73
|
-
[
|
74
|
-
subject,
|
75
|
-
state.last_challenge,
|
76
|
-
state.actual,
|
77
|
-
matcher,
|
78
|
-
state.got,
|
79
|
-
state.exception,
|
80
|
-
level,
|
81
|
-
negate?,
|
82
|
-
state.valid?
|
83
|
-
]
|
84
|
-
end
|
85
|
-
|
86
51
|
# @return [Symbol] The requirement level.
|
87
52
|
def level
|
88
|
-
self.class.name.split(
|
53
|
+
self.class.name.split("::").fetch(-1).upcase.to_sym
|
89
54
|
end
|
90
55
|
|
91
|
-
# @
|
56
|
+
# @note The boolean comparison between the actual value and the expected
|
57
|
+
# value can be evaluated to a negative assertion.
|
92
58
|
#
|
93
|
-
# @return [
|
94
|
-
def
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
# @return [Sandbox] The sandbox.
|
99
|
-
def execute
|
100
|
-
Sandbox.new(matcher, negate?, subject, *challenges)
|
59
|
+
# @return [Boolean] Positive or negative assertion?
|
60
|
+
def negate?
|
61
|
+
@negate
|
101
62
|
end
|
102
63
|
end
|
103
64
|
end
|
104
65
|
end
|
105
66
|
|
106
|
-
require_relative File.join(
|
107
|
-
require_relative File.join(
|
108
|
-
require_relative File.join('..', 'result', 'pass')
|
109
|
-
require_relative File.join('..', 'sandbox')
|
67
|
+
require_relative File.join("..", "result")
|
68
|
+
require_relative File.join("..", "exam")
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "must"
|
4
|
+
|
5
|
+
module Spectus
|
6
|
+
module RequirementLevel
|
7
|
+
# May requirement level's class.
|
8
|
+
class May < Must
|
9
|
+
# Evaluate the expectation.
|
10
|
+
#
|
11
|
+
# @return [Boolean] Report if the low expectation pass or fail?
|
12
|
+
def pass?
|
13
|
+
super || exam.exception.is_a?(::NoMethodError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Spectus
|
6
|
+
module RequirementLevel
|
7
|
+
# Must requirement level's class.
|
8
|
+
class Must < Base
|
9
|
+
# Evaluate the expectation.
|
10
|
+
#
|
11
|
+
# @return [Boolean] Report if the high expectation pass or fail?
|
12
|
+
def pass?
|
13
|
+
exam.valid?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "must"
|
4
|
+
|
5
|
+
module Spectus
|
6
|
+
module RequirementLevel
|
7
|
+
# Should requirement level's class.
|
8
|
+
class Should < Must
|
9
|
+
# Evaluate the expectation.
|
10
|
+
#
|
11
|
+
# @return [Boolean] Report if the medium expectation pass or fail?
|
12
|
+
def pass?
|
13
|
+
super || exam.exception.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spectus
|
4
|
+
# Namespace for the results.
|
5
|
+
module Result
|
6
|
+
# @raise [Fail] A failed spec result.
|
7
|
+
# @return [Pass] A passed spec result.
|
8
|
+
def self.call(is_passed, **details)
|
9
|
+
(is_passed ? Pass : Fail).call(**details)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require_relative File.join("result", "fail")
|
15
|
+
require_relative File.join("result", "pass")
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spectus
|
4
|
+
module Result
|
5
|
+
# Common collection of methods for Result's classes.
|
6
|
+
module Common
|
7
|
+
# @return [#object_id] Returned value by the challenged subject.
|
8
|
+
attr_reader :actual
|
9
|
+
|
10
|
+
# @return [Exception, nil] Any possible raised exception.
|
11
|
+
attr_reader :error
|
12
|
+
|
13
|
+
# @return [#object_id] The expected value.
|
14
|
+
attr_reader :expected
|
15
|
+
|
16
|
+
# @return [#object_id] The result of the boolean comparison between the
|
17
|
+
# actual value and the expected value through the matcher.
|
18
|
+
attr_reader :got
|
19
|
+
|
20
|
+
# @return [#object_id] The matcher.
|
21
|
+
attr_reader :matcher
|
22
|
+
|
23
|
+
# @return [:MUST, :SHOULD, :MAY] The requirement level of the expectation.
|
24
|
+
attr_reader :level
|
25
|
+
|
26
|
+
# Common initialize method.
|
27
|
+
#
|
28
|
+
# @param actual [#object_id] Returned value by the challenged subject.
|
29
|
+
# @param error [Exception, nil] Any possible raised exception.
|
30
|
+
# @param expected [#object_id] The expected value.
|
31
|
+
# @param got [Boolean, nil] The result of the boolean comparison
|
32
|
+
# between the actual value and the expected value through the matcher.
|
33
|
+
# @param negate [Boolean] Evaluated to a negative assertion?
|
34
|
+
# @param valid [Boolean] Report if the test was true or false?
|
35
|
+
# @param matcher [Symbol] The matcher.
|
36
|
+
# @param level [:MUST, :SHOULD, :MAY] The requirement level.
|
37
|
+
def initialize(actual:, error:, expected:, got:, negate:, valid:,
|
38
|
+
matcher:, level:)
|
39
|
+
|
40
|
+
@actual = actual
|
41
|
+
@error = error
|
42
|
+
@expected = expected
|
43
|
+
@got = got
|
44
|
+
@negate = negate
|
45
|
+
@valid = valid
|
46
|
+
@matcher = matcher
|
47
|
+
@level = level
|
48
|
+
|
49
|
+
super(to_s) if failed?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Did the test pass?
|
53
|
+
#
|
54
|
+
# @return [Boolean] The spec passed or failed?
|
55
|
+
def passed?
|
56
|
+
!failed?
|
57
|
+
end
|
58
|
+
|
59
|
+
# The value of the negate instance variable.
|
60
|
+
#
|
61
|
+
# @return [Boolean] Evaluated to a negative assertion?
|
62
|
+
def negate?
|
63
|
+
@negate
|
64
|
+
end
|
65
|
+
|
66
|
+
# The state of error.
|
67
|
+
#
|
68
|
+
# @return [Boolean] The test raised an error?
|
69
|
+
def error?
|
70
|
+
!error.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
# The state of success.
|
74
|
+
#
|
75
|
+
# @return [Boolean] The test was a success?
|
76
|
+
def success?
|
77
|
+
got.equal?(true)
|
78
|
+
end
|
79
|
+
|
80
|
+
# The value of the boolean comparison between the actual value and the
|
81
|
+
# expected value.
|
82
|
+
#
|
83
|
+
# @return [Boolean] The test was true or false?
|
84
|
+
def valid?
|
85
|
+
@valid
|
86
|
+
end
|
87
|
+
|
88
|
+
# A string containing a human-readable representation of the result.
|
89
|
+
#
|
90
|
+
# @return [String] The human-readable representation of the result.
|
91
|
+
def inspect
|
92
|
+
"#{self.class}(actual: #{actual.inspect}, " \
|
93
|
+
"error: #{error.inspect}, " \
|
94
|
+
"expected: #{expected.inspect}, " \
|
95
|
+
"got: #{got.inspect}, " \
|
96
|
+
"matcher: #{matcher.inspect}, " \
|
97
|
+
"negate: #{negate?.inspect}, " \
|
98
|
+
"level: #{level.inspect}, " \
|
99
|
+
"valid: #{valid?.inspect})" \
|
100
|
+
end
|
101
|
+
|
102
|
+
# The readable definition.
|
103
|
+
#
|
104
|
+
# @return [String] A readable string of the definition.
|
105
|
+
def definition
|
106
|
+
[matcher, expected&.inspect].compact.join(" ")
|
107
|
+
end
|
108
|
+
|
109
|
+
# The negation, if any.
|
110
|
+
#
|
111
|
+
# @return [String] The negation, or an empty string.
|
112
|
+
def maybe_negate
|
113
|
+
negate? ? " not" : ""
|
114
|
+
end
|
115
|
+
|
116
|
+
# The summary of the result.
|
117
|
+
#
|
118
|
+
# @return [String] A string representing the summary of the result.
|
119
|
+
def summary
|
120
|
+
if error?
|
121
|
+
error.message
|
122
|
+
elsif actual.is_a?(::Exception)
|
123
|
+
actual.message
|
124
|
+
elsif actual == expected
|
125
|
+
"expected#{maybe_negate} to #{definition}"
|
126
|
+
else
|
127
|
+
"expected #{actual.inspect}#{maybe_negate} to #{definition}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Express the result with one colored char.
|
132
|
+
#
|
133
|
+
# @return [String] The colored char that identify the result.
|
134
|
+
def colored_char
|
135
|
+
color(char)
|
136
|
+
end
|
137
|
+
|
138
|
+
# The colored string representation of the result.
|
139
|
+
#
|
140
|
+
# @return [String] A string representing the result.
|
141
|
+
def colored_string
|
142
|
+
color(to_s)
|
143
|
+
end
|
144
|
+
|
145
|
+
# The representation of the result.
|
146
|
+
#
|
147
|
+
# @return [String] A string representing the result.
|
148
|
+
def to_s
|
149
|
+
"#{titre}: #{summary}."
|
150
|
+
end
|
151
|
+
|
152
|
+
# Titre for the result.
|
153
|
+
#
|
154
|
+
# @return [String] A string representing the titre.
|
155
|
+
def titre
|
156
|
+
if error?
|
157
|
+
error.class.name
|
158
|
+
else
|
159
|
+
to_sym.to_s.capitalize
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
def color(str)
|
166
|
+
if success?
|
167
|
+
"\e[32m#{str}\e[0m"
|
168
|
+
elsif info?
|
169
|
+
"\e[36m#{str}\e[0m"
|
170
|
+
elsif warning?
|
171
|
+
"\e[33m#{str}\e[0m"
|
172
|
+
elsif failure?
|
173
|
+
"\e[35m#{str}\e[0m"
|
174
|
+
else
|
175
|
+
"\e[31m#{str}\e[0m"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/spectus/result/fail.rb
CHANGED
@@ -1,32 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "common"
|
4
4
|
|
5
5
|
module Spectus
|
6
6
|
module Result
|
7
7
|
# The class that is responsible for reporting that the expectation is false.
|
8
8
|
class Fail < ::StandardError
|
9
|
-
include
|
9
|
+
include Common
|
10
10
|
|
11
|
-
#
|
11
|
+
# @raise [Fail] A failed spec result.
|
12
|
+
def self.call(**details)
|
13
|
+
raise new(**details)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Did the test fail?
|
12
17
|
#
|
13
|
-
# @return [Boolean] The spec
|
14
|
-
def
|
15
|
-
|
18
|
+
# @return [Boolean] The spec passed or failed?
|
19
|
+
def failed?
|
20
|
+
true
|
16
21
|
end
|
17
22
|
|
18
23
|
# The state of failure.
|
19
24
|
#
|
20
|
-
# @return [Boolean] The test was a failure
|
25
|
+
# @return [Boolean] The test was a failure?
|
21
26
|
def failure?
|
22
|
-
error
|
27
|
+
!error?
|
28
|
+
end
|
29
|
+
|
30
|
+
# The state of info.
|
31
|
+
#
|
32
|
+
# @return [Boolean] The test was an info?
|
33
|
+
def info?
|
34
|
+
false
|
23
35
|
end
|
24
36
|
|
25
|
-
# The state of
|
37
|
+
# The state of warning.
|
26
38
|
#
|
27
|
-
# @return [Boolean] The test was
|
28
|
-
def
|
29
|
-
|
39
|
+
# @return [Boolean] The test was a warning?
|
40
|
+
def warning?
|
41
|
+
false
|
30
42
|
end
|
31
43
|
|
32
44
|
# Identify the state of the result.
|
@@ -38,14 +50,23 @@ module Spectus
|
|
38
50
|
|
39
51
|
# Express the result with one char.
|
40
52
|
#
|
41
|
-
# @param color [Boolean] Enable the color.
|
42
|
-
#
|
43
53
|
# @return [String] The char that identify the result.
|
44
|
-
def
|
54
|
+
def char
|
55
|
+
if failure?
|
56
|
+
"F"
|
57
|
+
else
|
58
|
+
"E"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Express the result with one emoji.
|
63
|
+
#
|
64
|
+
# @return [String] The emoji that identify the result.
|
65
|
+
def emoji
|
45
66
|
if failure?
|
46
|
-
|
67
|
+
"❌"
|
47
68
|
else
|
48
|
-
|
69
|
+
"💥"
|
49
70
|
end
|
50
71
|
end
|
51
72
|
end
|