spectus 3.3.4 → 4.0.2
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/README.md +50 -127
- data/lib/spectus.rb +195 -102
- data/lib/spectus/requirement.rb +13 -0
- data/lib/spectus/requirement/base.rb +79 -0
- data/lib/spectus/requirement/optional.rb +28 -0
- data/lib/spectus/requirement/recommended.rb +28 -0
- data/lib/spectus/requirement/required.rb +17 -0
- metadata +21 -25
- data/lib/spectus/exam.rb +0 -56
- data/lib/spectus/expectation_target.rb +0 -202
- data/lib/spectus/requirement_level/base.rb +0 -69
- data/lib/spectus/requirement_level/may.rb +0 -17
- data/lib/spectus/requirement_level/must.rb +0 -17
- data/lib/spectus/requirement_level/should.rb +0 -17
- data/lib/spectus/result.rb +0 -18
- data/lib/spectus/result/fail.rb +0 -13
- data/lib/spectus/result/pass.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45d1a638f042621d43b991c15aea5311a0b370c82f63d37c8fefa6e0a29dba8b
|
4
|
+
data.tar.gz: 5bc2409a8d4daeb2e69ca70ac3c6e7d209f4c47a40088c859b545929a2841018
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a5cea6a4c057897592889e4215963176011906d940367e2e62404302ba87bc2fa1550716aba9fe00b8896375f3cb6b4785b2c7d74338c17c3df745c3da4ffb6
|
7
|
+
data.tar.gz: 5ad609a281efbde26dacc5e57c3ec0955ce431755bd206aa2617a2bbbba7740bb6730bd22603dfc2ac3159c60f8fea003e9bb0a097ac5755bd01fbb88ac47648
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Spectus
|
2
2
|
|
3
|
-
[](https://github.com/fixrb/spectus/releases)
|
4
|
+
[](https://rubydoc.info/github/fixrb/spectus/main)
|
5
|
+
[](https://github.com/fixrb/spectus/actions?query=workflow%3Aci+branch%3Amain)
|
6
|
+
[](https://github.com/fixrb/spectus/actions?query=workflow%3Arubocop+branch%3Amain)
|
7
|
+
[](https://github.com/fixrb/spectus/raw/main/LICENSE.md)
|
6
8
|
|
7
9
|
> Expectation library with [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) requirement levels 🚥
|
8
10
|
|
@@ -26,168 +28,90 @@ Or install it yourself as:
|
|
26
28
|
gem install spectus
|
27
29
|
```
|
28
30
|
|
29
|
-
## Overview
|
30
|
-
|
31
|
-
Assuming that an expectation is an assertion that is either `true` or `false`,
|
32
|
-
qualifying it with `MUST`, `SHOULD` and `MAY`, we can draw up several scenarios:
|
33
|
-
|
34
|
-
| Requirement levels | **MUST** | **SHOULD** | **MAY** |
|
35
|
-
| ------------------------- | -------- | ---------- | ------- |
|
36
|
-
| Implemented & Matched | `true` | `true` | `true` |
|
37
|
-
| Implemented & Not matched | `false` | `true` | `false` |
|
38
|
-
| Implemented & Exception | `false` | `false` | `false` |
|
39
|
-
| Not implemented | `false` | `false` | `true` |
|
40
|
-
|
41
|
-
When an expectation is evaluated by Spectus,
|
42
|
-
|
43
|
-
* in case of a _passed_ expectation, a `Spectus::Result::Pass` instance is _returned_;
|
44
|
-
* in case of a _failed_ expectation, a `Spectus::Result::Fail` exception is _raised_.
|
45
|
-
|
46
31
|
## Usage
|
47
32
|
|
48
|
-
The __Spectus__ library is basically a module
|
33
|
+
The __Spectus__ library is basically a module defining methods that can be used to qualify expectations in specifications.
|
49
34
|
|
50
|
-
|
35
|
+
To make __Spectus__ available:
|
51
36
|
|
52
37
|
```ruby
|
53
38
|
require "spectus"
|
54
|
-
|
55
|
-
class Spec
|
56
|
-
include ::Spectus
|
57
|
-
|
58
|
-
attr_reader :subject
|
59
|
-
|
60
|
-
def initialize(subject)
|
61
|
-
@subject = subject
|
62
|
-
end
|
63
|
-
|
64
|
-
def test_a
|
65
|
-
it { subject.upcase }.MUST eql "FOO"
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_b
|
69
|
-
it { subject.blank? }.MAY be_true
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_c
|
73
|
-
it { subject.length }.SHOULD equal 42
|
74
|
-
end
|
75
|
-
end
|
76
39
|
```
|
77
40
|
|
78
|
-
|
79
|
-
t = Spec.new("foo")
|
80
|
-
|
81
|
-
t.test_a # => Spectus::Result::Pass(actual: "FOO", error: nil, expected: "FOO", got: true, matcher: :eql, negate: false, level: :MUST, valid: true)
|
82
|
-
|
83
|
-
t.test_b # => Spectus::Result::Pass(actual: nil, error: #<NoMethodError: undefined method `blank?' for "foo":String>, expected: nil, got: nil, matcher: :be_true, negate: false, level: :MAY, valid: false)
|
41
|
+
For convenience, we will also instantiate some matchers from the [Matchi library](https://github.com/fixrb/matchi):
|
84
42
|
|
85
|
-
|
43
|
+
```sh
|
44
|
+
gem install matchi
|
86
45
|
```
|
87
46
|
|
88
47
|
```ruby
|
89
|
-
|
90
|
-
|
91
|
-
t.test_a # => raises an exception:
|
92
|
-
# Traceback (most recent call last):
|
93
|
-
# 6: from ./bin/console:8:in `<main>'
|
94
|
-
# 5: from (irb):23
|
95
|
-
# 4: from (irb):11:in `test_a'
|
96
|
-
# 3: from /Users/cyril/github/fixrb/spectus/lib/spectus/expectation_target.rb:34:in `MUST'
|
97
|
-
# 2: from /Users/cyril/github/fixrb/spectus/lib/spectus/requirement_level/base.rb:38:in `call'
|
98
|
-
# 1: from /Users/cyril/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/expresenter-1.2.1/lib/expresenter/fail.rb:19:in `with'
|
99
|
-
# Spectus::Result::Fail (NoMethodError: undefined method `upcase' for 4:Integer)
|
100
|
-
|
101
|
-
t.test_b # => Spectus::Result::Pass(actual: nil, error: #<NoMethodError: undefined method `blank?' for 4:Integer>, expected: nil, got: nil, matcher: :be_true, negate: false, level: :MAY, valid: false)
|
102
|
-
|
103
|
-
t.test_c # => raises an exception:
|
104
|
-
# Traceback (most recent call last):
|
105
|
-
# 6: from ./bin/console:8:in `<main>'
|
106
|
-
# 5: from (irb):25
|
107
|
-
# 4: from (irb):19:in `test_c'
|
108
|
-
# 3: from /Users/cyril/github/fixrb/spectus/lib/spectus/expectation_target.rb:100:in `SHOULD'
|
109
|
-
# 2: from /Users/cyril/github/fixrb/spectus/lib/spectus/requirement_level/base.rb:38:in `call'
|
110
|
-
# 1: from /Users/cyril/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/expresenter-1.2.1/lib/expresenter/fail.rb:19:in `with'
|
111
|
-
# Spectus::Result::Fail (NoMethodError: undefined method `length' for 4:Integer.)
|
48
|
+
require "matchi"
|
112
49
|
```
|
113
50
|
|
114
|
-
|
51
|
+
All examples here assume that this has been done.
|
115
52
|
|
116
53
|
### Absolute Requirement
|
117
54
|
|
118
|
-
|
55
|
+
There is exactly one bat:
|
119
56
|
|
120
57
|
```ruby
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
it { "ルビー".valid_encoding? }.MUST be_true
|
126
|
-
# => Spectus::Result::Pass(actual: true, error: nil, expected: nil, got: true, matcher: :be_true, negate: false, level: :MUST, valid: true)
|
58
|
+
definition = Spectus.must Matchi::Be.new(1)
|
59
|
+
definition.call { "🦇".size }
|
60
|
+
# => Expresenter::Pass(actual: 1, definition: "be 1", error: nil, expected: 1, got: true, negate: false, level: :MUST)
|
127
61
|
```
|
128
62
|
|
129
|
-
The
|
63
|
+
The test is passed.
|
130
64
|
|
131
65
|
### Absolute Prohibition
|
132
66
|
|
133
|
-
|
67
|
+
Truth and lies:
|
134
68
|
|
135
69
|
```ruby
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
it { "foo".length }.MUST_NOT raise_exception NoMethodError
|
141
|
-
# => Spectus::Result::Pass(actual: 3, error: nil, expected: NoMethodError, got: true, matcher: :raise_exception, negate: true, level: :MUST, valid: true)
|
70
|
+
definition = Spectus.must_not Matchi::Be.new(true)
|
71
|
+
definition.call { false }
|
72
|
+
# => Expresenter::Pass(actual: false, definition: "be true", error: nil, expected: true, got: true, negate: true, level: :MUST)
|
142
73
|
```
|
143
74
|
|
144
|
-
The result of the test shows that the spec passed.
|
145
|
-
|
146
75
|
### Recommended
|
147
76
|
|
148
|
-
|
77
|
+
A well-known joke. The addition of `0.1` and `0.2` is deadly precise:
|
149
78
|
|
150
79
|
```ruby
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
it { BasicObject.superclass }.SHOULD equal NilClass
|
156
|
-
# => Spectus::Result::Pass(actual: nil, error: nil, expected: NilClass, got: false, matcher: :equal, negate: false, level: :SHOULD, valid: false)
|
80
|
+
definition = Spectus.should Matchi::Be.new(0.3)
|
81
|
+
definition.call { 0.1 + 0.2 }
|
82
|
+
# => Expresenter::Pass(actual: 0.30000000000000004, definition: "be 0.3", error: nil, expected: 0.3, got: false, negate: false, level: :SHOULD)
|
157
83
|
```
|
158
84
|
|
159
|
-
Instead of the expected `NilClass` class, its sole instance (which is `nil`) was returned.
|
160
|
-
However, because there isn't any exception, the result of the test shows that the spec passed.
|
161
|
-
|
162
85
|
### Not Recommended
|
163
86
|
|
164
|
-
|
87
|
+
This should not be wrong:
|
165
88
|
|
166
89
|
```ruby
|
167
|
-
|
90
|
+
definition = Spectus.should_not Matchi::Match.new("123456")
|
168
91
|
|
169
|
-
|
92
|
+
definition.call do
|
93
|
+
require "securerandom"
|
170
94
|
|
171
|
-
|
172
|
-
|
95
|
+
SecureRandom.hex(3)
|
96
|
+
end
|
97
|
+
# => Expresenter::Pass(actual: "bb5716", definition: "match \"123456\"", error: nil, expected: "123456", got: true, negate: true, level: :SHOULD)
|
173
98
|
```
|
174
99
|
|
175
|
-
|
100
|
+
In any case, as long as there are no exceptions, the test passes.
|
176
101
|
|
177
102
|
### Optional
|
178
103
|
|
179
|
-
|
104
|
+
An empty array is blank, right?
|
180
105
|
|
181
106
|
```ruby
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
it { "foo".blank? }.MAY be_false
|
187
|
-
# => Spectus::Result::Pass(actual: nil, error: #<NoMethodError: undefined method `blank?' for "foo":String>, expected: nil, got: nil, matcher: :be_false, negate: false, level: :MAY, valid: false)
|
107
|
+
definition = Spectus.may Matchi::Be.new(true)
|
108
|
+
definition.call { [].blank? }
|
109
|
+
# => Expresenter::Pass(actual: nil, definition: "be true", error: #<NoMethodError: undefined method `blank?' for []:Array>, expected: true, got: nil, negate: false, level: :MAY)
|
188
110
|
```
|
189
111
|
|
190
|
-
|
112
|
+
My bad! ActiveSupport was not imported. 🤦♂️
|
113
|
+
|
114
|
+
Anyways, the test passes because the exception produced is `NoMethodError`, meaning that the functionality is not implemented.
|
191
115
|
|
192
116
|
## Code Isolation
|
193
117
|
|
@@ -200,26 +124,24 @@ Because they may or may not be desired, each requirement level has 2 versions:
|
|
200
124
|
Example of test without isolation:
|
201
125
|
|
202
126
|
```ruby
|
203
|
-
|
127
|
+
greeting = "Hello, world!"
|
204
128
|
|
205
|
-
|
129
|
+
definition = Spectus.must Matchi::Eq.new("Hello, Alice!")
|
130
|
+
definition.call { greeting.gsub!("world", "Alice") }
|
131
|
+
# => Expresenter::Pass(actual: "Hello, Alice!", definition: "eq \"Hello, Alice!\"", error: nil, expected: "Hello, Alice!", got: true, negate: false, level: :MUST)
|
206
132
|
|
207
|
-
greeting = "Hello, world!"
|
208
|
-
it { greeting.gsub!("world", "Alice") }.MUST eql "Hello, Alice!"
|
209
|
-
# => Spectus::Result::Pass(actual: "Hello, Alice!", error: nil, expected: "Hello, Alice!", got: true, matcher: :eql, negate: false, level: :MUST, valid: true)
|
210
133
|
greeting # => "Hello, Alice!"
|
211
134
|
```
|
212
135
|
|
213
136
|
Example of test in isolation:
|
214
137
|
|
215
138
|
```ruby
|
216
|
-
|
139
|
+
greeting = "Hello, world!"
|
217
140
|
|
218
|
-
|
141
|
+
definition = Spectus.must! Matchi::Eq.new("Hello, Alice!")
|
142
|
+
definition.call { greeting.gsub!("world", "Alice") }
|
143
|
+
# => Expresenter::Pass(actual: "Hello, Alice!", definition: "eq \"Hello, Alice!\"", error: nil, expected: "Hello, Alice!", got: true, negate: false, level: :MUST)
|
219
144
|
|
220
|
-
greeting = "Hello, world!"
|
221
|
-
it { greeting.gsub!("world", "Alice") }.MUST! eql "Hello, Alice!"
|
222
|
-
# => Spectus::Result::Pass(actual: "Hello, Alice!", error: nil, expected: "Hello, Alice!", got: true, matcher: :eql, negate: false, level: :MUST, valid: true)
|
223
145
|
greeting # => "Hello, world!"
|
224
146
|
```
|
225
147
|
|
@@ -227,6 +149,7 @@ greeting # => "Hello, world!"
|
|
227
149
|
|
228
150
|
* Home page: https://github.com/fixrb/spectus
|
229
151
|
* Bugs/issues: https://github.com/fixrb/spectus/issues
|
152
|
+
* Blog post: https://batman.buzz/a-spectus-tutorial-expectations-with-rfc-2119-compliance-1fc769861c1
|
230
153
|
|
231
154
|
## Versioning
|
232
155
|
|
@@ -234,7 +157,7 @@ __Spectus__ follows [Semantic Versioning 2.0](https://semver.org/).
|
|
234
157
|
|
235
158
|
## License
|
236
159
|
|
237
|
-
The [gem](https://rubygems.org/gems/spectus) is available as open source under the terms of the [MIT License](https://
|
160
|
+
The [gem](https://rubygems.org/gems/spectus) is available as open source under the terms of the [MIT License](https://github.com/fixrb/spectus/raw/main/LICENSE.md).
|
238
161
|
|
239
162
|
***
|
240
163
|
|
data/lib/spectus.rb
CHANGED
@@ -1,114 +1,207 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
require_relative File.join("spectus", "expectation_target")
|
3
|
+
require_relative File.join("spectus", "requirement")
|
6
4
|
|
7
5
|
# Namespace for the Spectus library.
|
8
6
|
#
|
9
|
-
# This module defines
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
# class Spec
|
14
|
-
# include ::Spectus
|
15
|
-
#
|
16
|
-
# attr_reader :subject
|
17
|
-
#
|
18
|
-
# def initialize(subject)
|
19
|
-
# @subject = subject
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
# def test_a
|
23
|
-
# it { subject.upcase }.MUST eql "FOO"
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# def test_b
|
27
|
-
# it { subject.blank? }.MAY be_true
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
# def test_c
|
31
|
-
# it { subject.length }.SHOULD equal 42
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# t = Spec.new("foo")
|
36
|
-
# t.test_a # => Spectus::Result::Pass(actual: "FOO", error: nil, expected: "FOO", got: true, matcher: :eql, negate: false, level: :MUST, valid: true)
|
37
|
-
# t.test_b # => Spectus::Result::Pass(actual: nil, error: #<NoMethodError: undefined method `blank?' for "foo":String>, expected: nil, got: nil, matcher: :be_true, negate: false, level: :MAY, valid: false)
|
38
|
-
# t.test_c # => Spectus::Result::Pass(actual: 3, error: nil, expected: 42, got: false, matcher: :equal, negate: false, level: :SHOULD, valid: false)
|
39
|
-
#
|
40
|
-
# Or even directly used like this.
|
41
|
-
#
|
42
|
-
# @example
|
43
|
-
# require 'spectus'
|
44
|
-
#
|
45
|
-
# include Spectus
|
46
|
-
#
|
47
|
-
# it { 42 }.MUST equal 42 # => #<Spectus::Result::Pass...>
|
48
|
-
#
|
49
|
-
# It also includes a collection of expectation matchers 🤹
|
50
|
-
#
|
51
|
-
# @example Equivalence matcher
|
52
|
-
# matcher = eql("foo") # => Matchi::Matcher::Eql.new("foo")
|
53
|
-
# matcher.matches? { "foo" } # => true
|
54
|
-
# matcher.matches? { "bar" } # => false
|
55
|
-
#
|
56
|
-
# @example Identity matcher
|
57
|
-
# object = "foo"
|
58
|
-
#
|
59
|
-
# matcher = equal(object) # => Matchi::Matcher::Equal.new(object)
|
60
|
-
# matcher.matches? { object } # => true
|
61
|
-
# matcher.matches? { "foo" } # => false
|
62
|
-
#
|
63
|
-
# @example Regular expressions matcher
|
64
|
-
# matcher = match(/^foo$/) # => Matchi::Matcher::Match.new(/^foo$/)
|
65
|
-
# matcher.matches? { "foo" } # => true
|
66
|
-
# matcher.matches? { "bar" } # => false
|
67
|
-
#
|
68
|
-
# @example Expecting errors matcher
|
69
|
-
# matcher = raise_exception(NameError) # => Matchi::Matcher::RaiseException.new(NameError)
|
70
|
-
# matcher.matches? { Boom } # => true
|
71
|
-
# matcher.matches? { true } # => false
|
72
|
-
#
|
73
|
-
# @example Truth matcher
|
74
|
-
# matcher = be_true # => Matchi::Matcher::BeTrue.new
|
75
|
-
# matcher.matches? { true } # => true
|
76
|
-
# matcher.matches? { false } # => false
|
77
|
-
# matcher.matches? { nil } # => false
|
78
|
-
# matcher.matches? { 4 } # => false
|
79
|
-
#
|
80
|
-
# @example Untruth matcher
|
81
|
-
# matcher = be_false # => Matchi::Matcher::BeFalse.new
|
82
|
-
# matcher.matches? { false } # => true
|
83
|
-
# matcher.matches? { true } # => false
|
84
|
-
# matcher.matches? { nil } # => false
|
85
|
-
# matcher.matches? { 4 } # => false
|
86
|
-
#
|
87
|
-
# @example Nil matcher
|
88
|
-
# matcher = be_nil # => Matchi::Matcher::BeNil.new
|
89
|
-
# matcher.matches? { nil } # => true
|
90
|
-
# matcher.matches? { false } # => false
|
91
|
-
# matcher.matches? { true } # => false
|
92
|
-
# matcher.matches? { 4 } # => false
|
93
|
-
#
|
94
|
-
# @example Type/class matcher
|
95
|
-
# matcher = be_an_instance_of(String) # => Matchi::Matcher::BeAnInstanceOf.new(String)
|
96
|
-
# matcher.matches? { "foo" } # => true
|
97
|
-
# matcher.matches? { 4 } # => false
|
98
|
-
#
|
99
|
-
# @see https://github.com/fixrb/matchi
|
7
|
+
# This module defines methods that can be used to qualify expectations in
|
8
|
+
# specifications.
|
100
9
|
module Spectus
|
101
|
-
|
10
|
+
# This method mean that the definition is an absolute requirement of the
|
11
|
+
# specification.
|
12
|
+
#
|
13
|
+
# @example An absolute requirement definition
|
14
|
+
# require "spectus"
|
15
|
+
# require "matchi/eq"
|
16
|
+
#
|
17
|
+
# Spectus.must Matchi::Eq.new("FOO")
|
18
|
+
# # => #<MUST Matchi::Eq("FOO") isolate=false negate=false>
|
19
|
+
#
|
20
|
+
# @param matcher [#matches?] The matcher.
|
21
|
+
#
|
22
|
+
# @return [Requirement::Required] An absolute requirement level instance.
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def self.must(matcher)
|
26
|
+
Requirement::Required.new(
|
27
|
+
isolate: false,
|
28
|
+
negate: false,
|
29
|
+
matcher: matcher
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @example An absolute requirement definition with isolation
|
34
|
+
# require "spectus"
|
35
|
+
# require "matchi/eq"
|
36
|
+
#
|
37
|
+
# Spectus.must! Matchi::Eq.new("FOO")
|
38
|
+
# # => #<MUST Matchi::Eq("FOO") isolate=true negate=false>
|
39
|
+
#
|
40
|
+
# @see must
|
41
|
+
def self.must!(matcher)
|
42
|
+
Requirement::Required.new(
|
43
|
+
isolate: true,
|
44
|
+
negate: false,
|
45
|
+
matcher: matcher
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
# This method mean that the definition is an absolute prohibition of the specification.
|
50
|
+
#
|
51
|
+
# @example An absolute prohibition definition
|
52
|
+
# require "spectus"
|
53
|
+
# require "matchi/be"
|
54
|
+
#
|
55
|
+
# Spectus.must_not Matchi::Be.new(42)
|
56
|
+
# # => #<MUST Matchi::Be(42) isolate=false negate=true>
|
57
|
+
#
|
58
|
+
# @param matcher [#matches?] The matcher.
|
59
|
+
#
|
60
|
+
# @return [Requirement::Required] An absolute prohibition level instance.
|
61
|
+
def self.must_not(matcher)
|
62
|
+
Requirement::Required.new(
|
63
|
+
isolate: false,
|
64
|
+
negate: true,
|
65
|
+
matcher: matcher
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @example An absolute prohibition definition with isolation
|
70
|
+
# require "spectus"
|
71
|
+
# require "matchi/be"
|
72
|
+
#
|
73
|
+
# Spectus.must_not! Matchi::Be.new(42)
|
74
|
+
# # => #<MUST Matchi::Be(42) isolate=true negate=true>
|
75
|
+
#
|
76
|
+
# @see must_not
|
77
|
+
def self.must_not!(matcher)
|
78
|
+
Requirement::Required.new(
|
79
|
+
isolate: true,
|
80
|
+
negate: true,
|
81
|
+
matcher: matcher
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
# This method mean that there may exist valid reasons in particular
|
86
|
+
# circumstances to ignore a particular item, but the full implications must be
|
87
|
+
# understood and carefully weighed before choosing a different course.
|
88
|
+
#
|
89
|
+
# @example A recommended definition
|
90
|
+
# require "spectus"
|
91
|
+
# require "matchi/be"
|
92
|
+
#
|
93
|
+
# Spectus.should Matchi::Be.new(true)
|
94
|
+
# # => #<SHOULD Matchi::Be(true) isolate=false negate=false>
|
95
|
+
#
|
96
|
+
# @param matcher [#matches?] The matcher.
|
97
|
+
#
|
98
|
+
# @return [Requirement::Recommended] A recommended requirement level instance.
|
99
|
+
def self.should(matcher)
|
100
|
+
Requirement::Recommended.new(
|
101
|
+
isolate: false,
|
102
|
+
negate: false,
|
103
|
+
matcher: matcher
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
# @example A recommended definition with isolation
|
108
|
+
# require "spectus"
|
109
|
+
# require "matchi/be"
|
110
|
+
#
|
111
|
+
# Spectus.should! Matchi::Be.new(true)
|
112
|
+
# # => #<SHOULD Matchi::Be(true) isolate=true negate=false>
|
113
|
+
#
|
114
|
+
# @see should
|
115
|
+
def self.should!(matcher)
|
116
|
+
Requirement::Recommended.new(
|
117
|
+
isolate: true,
|
118
|
+
negate: false,
|
119
|
+
matcher: matcher
|
120
|
+
)
|
121
|
+
end
|
102
122
|
|
103
|
-
#
|
123
|
+
# This method mean that there may exist valid reasons in particular
|
124
|
+
# circumstances when the particular behavior is acceptable or even useful, but
|
125
|
+
# the full implications should be understood and the case carefully weighed
|
126
|
+
# before implementing any behavior described with this label.
|
127
|
+
#
|
128
|
+
# @example A not recommended definition
|
129
|
+
# require "spectus"
|
130
|
+
# require "matchi/raise_exception"
|
131
|
+
#
|
132
|
+
# Spectus.should_not Matchi::RaiseException.new(NoMethodError)
|
133
|
+
# # => #<SHOULD Matchi::RaiseException(NoMethodError) isolate=false negate=true>
|
134
|
+
#
|
135
|
+
# @param matcher [#matches?] The matcher.
|
136
|
+
#
|
137
|
+
# @return [Requirement::Recommended] A not recommended requirement level
|
138
|
+
# instance.
|
139
|
+
def self.should_not(matcher)
|
140
|
+
Requirement::Recommended.new(
|
141
|
+
isolate: false,
|
142
|
+
negate: true,
|
143
|
+
matcher: matcher
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @example A not recommended definition with isolation
|
148
|
+
# require "spectus"
|
149
|
+
# require "matchi/raise_exception"
|
150
|
+
#
|
151
|
+
# Spectus.should_not! Matchi::RaiseException.new(NoMethodError)
|
152
|
+
# # => #<SHOULD Matchi::RaiseException(NoMethodError) isolate=true negate=true>
|
104
153
|
#
|
105
|
-
# @
|
106
|
-
|
154
|
+
# @see should_not
|
155
|
+
def self.should_not!(matcher)
|
156
|
+
Requirement::Recommended.new(
|
157
|
+
isolate: true,
|
158
|
+
negate: true,
|
159
|
+
matcher: matcher
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
# This method mean that an item is truly optional.
|
164
|
+
# One vendor may choose to include the item because a particular marketplace
|
165
|
+
# requires it or because the vendor feels that it enhances the product while
|
166
|
+
# another vendor may omit the same item. An implementation which does not
|
167
|
+
# include a particular option must be prepared to interoperate with another
|
168
|
+
# implementation which does include the option, though perhaps with reduced
|
169
|
+
# functionality. In the same vein an implementation which does include a
|
170
|
+
# particular option must be prepared to interoperate with another
|
171
|
+
# implementation which does not include the option (except, of course, for the
|
172
|
+
# feature the option provides).
|
173
|
+
#
|
174
|
+
# @example An optional definition
|
175
|
+
# require "spectus"
|
176
|
+
# require "matchi/match"
|
177
|
+
#
|
178
|
+
# Spectus.may Matchi::Match.new(/^foo$/)
|
179
|
+
# # => #<MAY Matchi::Match(/^foo$/) isolate=false negate=false>
|
180
|
+
#
|
181
|
+
# @param matcher [#matches?] The matcher.
|
182
|
+
#
|
183
|
+
# @return [Requirement::Optional] An optional requirement level instance.
|
184
|
+
def self.may(matcher)
|
185
|
+
Requirement::Optional.new(
|
186
|
+
isolate: false,
|
187
|
+
negate: false,
|
188
|
+
matcher: matcher
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
# @example An optional definition with isolation
|
193
|
+
# require "spectus"
|
194
|
+
# require "matchi/match"
|
107
195
|
#
|
108
|
-
#
|
196
|
+
# Spectus.may! Matchi::Match.new(/^foo$/)
|
197
|
+
# # => #<MAY Matchi::Match(/^foo$/) isolate=true negate=false>
|
109
198
|
#
|
110
|
-
# @
|
111
|
-
def
|
112
|
-
|
199
|
+
# @see may
|
200
|
+
def self.may!(matcher)
|
201
|
+
Requirement::Optional.new(
|
202
|
+
isolate: true,
|
203
|
+
negate: false,
|
204
|
+
matcher: matcher
|
205
|
+
)
|
113
206
|
end
|
114
207
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spectus
|
4
|
+
# Namespace for the results.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
module Requirement
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative File.join("requirement", "required")
|
12
|
+
require_relative File.join("requirement", "recommended")
|
13
|
+
require_relative File.join("requirement", "optional")
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "expresenter"
|
4
|
+
require "test_tube"
|
5
|
+
|
6
|
+
module Spectus
|
7
|
+
# Namespace for the requirement levels.
|
8
|
+
module Requirement
|
9
|
+
# Requirement level's base class.
|
10
|
+
class Base
|
11
|
+
# Initialize the requirement level class.
|
12
|
+
#
|
13
|
+
# @param isolate [Boolean] Compute actual in a subprocess.
|
14
|
+
# @param matcher [#matches?] The matcher.
|
15
|
+
# @param negate [Boolean] Invert the matcher or not.
|
16
|
+
def initialize(isolate:, matcher:, negate:)
|
17
|
+
@isolate = isolate
|
18
|
+
@matcher = matcher
|
19
|
+
@negate = negate
|
20
|
+
end
|
21
|
+
|
22
|
+
# Test result.
|
23
|
+
#
|
24
|
+
# @raise [::Expresenter::Fail] A failed spec exception.
|
25
|
+
# @return [::Expresenter::Pass] A passed spec instance.
|
26
|
+
#
|
27
|
+
# @see https://github.com/fixrb/expresenter
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def call(&block)
|
31
|
+
test = ::TestTube.invoke(isolate: @isolate, matcher: @matcher, negate: @negate, &block)
|
32
|
+
|
33
|
+
::Expresenter.call(passed?(test)).with(
|
34
|
+
actual: test.actual,
|
35
|
+
definition: @matcher.to_s,
|
36
|
+
error: test.error,
|
37
|
+
expected: @matcher.expected,
|
38
|
+
got: test.got,
|
39
|
+
level: self.class.level,
|
40
|
+
negate: @negate
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# :nocov:
|
45
|
+
|
46
|
+
# A string containing a human-readable representation of the definition.
|
47
|
+
#
|
48
|
+
# @example The human-readable representation of an absolute requirement.
|
49
|
+
# require "spectus"
|
50
|
+
# require "matchi/be"
|
51
|
+
#
|
52
|
+
# definition = Spectus.must Matchi::Be.new(1)
|
53
|
+
# definition.inspect
|
54
|
+
# # => "#<MUST Matchi::Be(1) isolate=false negate=false>"
|
55
|
+
#
|
56
|
+
# @return [String] The human-readable representation of the definition.
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def inspect
|
60
|
+
"#<#{self.class.level} #{@matcher.inspect} isolate=#{@isolate} negate=#{@negate}>"
|
61
|
+
end
|
62
|
+
|
63
|
+
# :nocov:
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Code experiment result.
|
68
|
+
#
|
69
|
+
# @param test [::TestTube::Base] The state of the experiment.
|
70
|
+
#
|
71
|
+
# @see https://github.com/fixrb/test_tube
|
72
|
+
#
|
73
|
+
# @return [Boolean] The result of the test (passed or failed).
|
74
|
+
def passed?(test)
|
75
|
+
test.got.equal?(true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Spectus
|
6
|
+
module Requirement
|
7
|
+
# Optional requirement level.
|
8
|
+
class Optional < Base
|
9
|
+
# Key word for use in RFCs to indicate requirement levels.
|
10
|
+
#
|
11
|
+
# @return [Symbol] The requirement level.
|
12
|
+
def self.level
|
13
|
+
:MAY
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Code experiment result.
|
19
|
+
#
|
20
|
+
# @param (see Base#passed?)
|
21
|
+
#
|
22
|
+
# @return (see Base#passed?)
|
23
|
+
def passed?(test)
|
24
|
+
super || test.error.is_a?(::NoMethodError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Spectus
|
6
|
+
module Requirement
|
7
|
+
# Recommended and not recommended requirement levels.
|
8
|
+
class Recommended < Base
|
9
|
+
# Key word for use in RFCs to indicate requirement levels.
|
10
|
+
#
|
11
|
+
# @return [Symbol] The requirement level.
|
12
|
+
def self.level
|
13
|
+
:SHOULD
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Code experiment result.
|
19
|
+
#
|
20
|
+
# @param (see Base#passed?)
|
21
|
+
#
|
22
|
+
# @return (see Base#passed?)
|
23
|
+
def passed?(test)
|
24
|
+
super || test.error.nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Spectus
|
6
|
+
module Requirement
|
7
|
+
# Absolute requirement and absolute prohibition levels.
|
8
|
+
class Required < Base
|
9
|
+
# Key word for use in RFCs to indicate requirement levels.
|
10
|
+
#
|
11
|
+
# @return [Symbol] The requirement level.
|
12
|
+
def self.level
|
13
|
+
:MUST
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,59 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spectus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: expresenter
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.4.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.4.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: test_tube
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 2.1.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 2.1.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: brutal
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
48
|
-
type: :
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: bundler
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: matchi
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -187,15 +187,11 @@ files:
|
|
187
187
|
- LICENSE.md
|
188
188
|
- README.md
|
189
189
|
- lib/spectus.rb
|
190
|
-
- lib/spectus/
|
191
|
-
- lib/spectus/
|
192
|
-
- lib/spectus/
|
193
|
-
- lib/spectus/
|
194
|
-
- lib/spectus/
|
195
|
-
- lib/spectus/requirement_level/should.rb
|
196
|
-
- lib/spectus/result.rb
|
197
|
-
- lib/spectus/result/fail.rb
|
198
|
-
- lib/spectus/result/pass.rb
|
190
|
+
- lib/spectus/requirement.rb
|
191
|
+
- lib/spectus/requirement/base.rb
|
192
|
+
- lib/spectus/requirement/optional.rb
|
193
|
+
- lib/spectus/requirement/recommended.rb
|
194
|
+
- lib/spectus/requirement/required.rb
|
199
195
|
homepage: https://github.com/fixrb/spectus
|
200
196
|
licenses:
|
201
197
|
- MIT
|
data/lib/spectus/exam.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "defi"
|
4
|
-
|
5
|
-
module Spectus
|
6
|
-
# This class evaluate the expectation with the passed block.
|
7
|
-
class Exam
|
8
|
-
# Execute the untested code from the passed block against the matcher.
|
9
|
-
#
|
10
|
-
# rubocop:disable Lint/RescueException
|
11
|
-
#
|
12
|
-
# @param callable [#call] The callable object to test.
|
13
|
-
# @param isolation [Boolean] Compute actual in isolation?
|
14
|
-
# @param negate [Boolean] Positive or negative assertion?
|
15
|
-
# @param matcher [#matches?] The matcher.
|
16
|
-
def initialize(callable:, isolation:, negate:, matcher:)
|
17
|
-
@got = negate ^ matcher.matches? do
|
18
|
-
value = if isolation
|
19
|
-
send_call.to!(callable)
|
20
|
-
else
|
21
|
-
send_call.to(callable)
|
22
|
-
end
|
23
|
-
|
24
|
-
@actual = value.object
|
25
|
-
|
26
|
-
value.call
|
27
|
-
end
|
28
|
-
rescue ::Exception => e
|
29
|
-
@actual = nil
|
30
|
-
@exception = e
|
31
|
-
end
|
32
|
-
# rubocop:enable Lint/RescueException
|
33
|
-
|
34
|
-
# @return [#object_id] The actual value.
|
35
|
-
attr_reader :actual
|
36
|
-
|
37
|
-
# @return [Exception, nil] An exception.
|
38
|
-
attr_reader :exception
|
39
|
-
|
40
|
-
# @return [Boolean, nil] Report to the spec requirement level if the
|
41
|
-
# expectation is true or false.
|
42
|
-
attr_reader :got
|
43
|
-
|
44
|
-
# @return [Defi::Challenge] The challenge for the callable object.
|
45
|
-
def send_call
|
46
|
-
::Defi.send(:call)
|
47
|
-
end
|
48
|
-
|
49
|
-
# Report to the spec requirement level if the test pass or fail.
|
50
|
-
#
|
51
|
-
# @return [Boolean] Report if the test pass or fail?
|
52
|
-
def valid?
|
53
|
-
exception.nil? ? got : false
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,202 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative File.join("requirement_level", "must")
|
4
|
-
require_relative File.join("requirement_level", "should")
|
5
|
-
require_relative File.join("requirement_level", "may")
|
6
|
-
|
7
|
-
module Spectus
|
8
|
-
# Wraps the target of an expectation.
|
9
|
-
#
|
10
|
-
# @example
|
11
|
-
# it { actual value } # => ExpectationTarget wrapping the block
|
12
|
-
class ExpectationTarget
|
13
|
-
# Create a new expectation target
|
14
|
-
#
|
15
|
-
# @param callable [Proc] The object to test.
|
16
|
-
def initialize(&callable)
|
17
|
-
@callable = callable
|
18
|
-
end
|
19
|
-
|
20
|
-
# rubocop:disable Naming/MethodName
|
21
|
-
|
22
|
-
# This word, or the terms "REQUIRED" or "SHALL", mean that the
|
23
|
-
# definition is an absolute requirement of the specification.
|
24
|
-
#
|
25
|
-
# @example _Absolute requirement_ definition
|
26
|
-
# it { "foo".upcase }.MUST eql 'FOO'
|
27
|
-
#
|
28
|
-
# @param matcher [#matches?] The matcher.
|
29
|
-
#
|
30
|
-
# @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
|
31
|
-
# pass or fail.
|
32
|
-
def MUST(matcher)
|
33
|
-
RequirementLevel::Must.new(
|
34
|
-
callable: callable,
|
35
|
-
isolation: false,
|
36
|
-
negate: false,
|
37
|
-
matcher: matcher
|
38
|
-
).call
|
39
|
-
end
|
40
|
-
|
41
|
-
# @example _Absolute requirement_ definition with isolation
|
42
|
-
# it { "foo".upcase }.MUST! eql 'FOO'
|
43
|
-
#
|
44
|
-
# @see MUST
|
45
|
-
def MUST!(matcher)
|
46
|
-
RequirementLevel::Must.new(
|
47
|
-
callable: callable,
|
48
|
-
isolation: true,
|
49
|
-
negate: false,
|
50
|
-
matcher: matcher
|
51
|
-
).call
|
52
|
-
end
|
53
|
-
|
54
|
-
# This phrase, or the phrase "SHALL NOT", mean that the
|
55
|
-
# definition is an absolute prohibition of the specification.
|
56
|
-
#
|
57
|
-
# @example _Absolute prohibition_ definition
|
58
|
-
# it { "foo".size }.MUST_NOT equal 42
|
59
|
-
#
|
60
|
-
# @param matcher [#matches?] The matcher.
|
61
|
-
#
|
62
|
-
# @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
|
63
|
-
# pass or fail.
|
64
|
-
def MUST_NOT(matcher)
|
65
|
-
RequirementLevel::Must.new(
|
66
|
-
callable: callable,
|
67
|
-
isolation: false,
|
68
|
-
negate: true,
|
69
|
-
matcher: matcher
|
70
|
-
).call
|
71
|
-
end
|
72
|
-
|
73
|
-
# @example _Absolute prohibition_ definition with isolation
|
74
|
-
# it { "foo".size }.MUST_NOT! equal 42
|
75
|
-
#
|
76
|
-
# @see MUST_NOT
|
77
|
-
def MUST_NOT!(matcher)
|
78
|
-
RequirementLevel::Must.new(
|
79
|
-
callable: callable,
|
80
|
-
isolation: true,
|
81
|
-
negate: true,
|
82
|
-
matcher: matcher
|
83
|
-
).call
|
84
|
-
end
|
85
|
-
|
86
|
-
# This word, or the adjective "RECOMMENDED", mean that there
|
87
|
-
# may exist valid reasons in particular circumstances to ignore a
|
88
|
-
# particular item, but the full implications must be understood and
|
89
|
-
# carefully weighed before choosing a different course.
|
90
|
-
#
|
91
|
-
# @example _Recommended_ definition
|
92
|
-
# it { "foo".valid_encoding? }.SHOULD equal true
|
93
|
-
#
|
94
|
-
# @param matcher [#matches?] The matcher.
|
95
|
-
#
|
96
|
-
# @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
|
97
|
-
# pass or fail.
|
98
|
-
def SHOULD(matcher)
|
99
|
-
RequirementLevel::Should.new(
|
100
|
-
callable: callable,
|
101
|
-
isolation: false,
|
102
|
-
negate: false,
|
103
|
-
matcher: matcher
|
104
|
-
).call
|
105
|
-
end
|
106
|
-
|
107
|
-
# @example _Recommended_ definition with isolation
|
108
|
-
# it { "foo".valid_encoding? }.SHOULD! equal true
|
109
|
-
#
|
110
|
-
# @see SHOULD
|
111
|
-
def SHOULD!(matcher)
|
112
|
-
RequirementLevel::Should.new(
|
113
|
-
callable: callable,
|
114
|
-
isolation: true,
|
115
|
-
negate: false,
|
116
|
-
matcher: matcher
|
117
|
-
).call
|
118
|
-
end
|
119
|
-
|
120
|
-
# This phrase, or the phrase "NOT RECOMMENDED" mean that
|
121
|
-
# there may exist valid reasons in particular circumstances when the
|
122
|
-
# particular behavior is acceptable or even useful, but the full
|
123
|
-
# implications should be understood and the case carefully weighed
|
124
|
-
# before implementing any behavior described with this label.
|
125
|
-
#
|
126
|
-
# @example _Not recommended_ definition
|
127
|
-
# it { "".blank? }.SHOULD_NOT raise_exception NoMethodError
|
128
|
-
#
|
129
|
-
# @param matcher [#matches?] The matcher.
|
130
|
-
#
|
131
|
-
# @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec
|
132
|
-
# pass or fail.
|
133
|
-
def SHOULD_NOT(matcher)
|
134
|
-
RequirementLevel::Should.new(
|
135
|
-
callable: callable,
|
136
|
-
isolation: false,
|
137
|
-
negate: true,
|
138
|
-
matcher: matcher
|
139
|
-
).call
|
140
|
-
end
|
141
|
-
|
142
|
-
# @example _Not recommended_ definition with isolation
|
143
|
-
# it { "".blank? }.SHOULD_NOT! raise_exception NoMethodError
|
144
|
-
#
|
145
|
-
# @see SHOULD_NOT
|
146
|
-
def SHOULD_NOT!(matcher)
|
147
|
-
RequirementLevel::Should.new(
|
148
|
-
callable: callable,
|
149
|
-
isolation: true,
|
150
|
-
negate: true,
|
151
|
-
matcher: matcher
|
152
|
-
).call
|
153
|
-
end
|
154
|
-
|
155
|
-
# This word, or the adjective "OPTIONAL", mean that an item is
|
156
|
-
# truly optional. One vendor may choose to include the item because a
|
157
|
-
# particular marketplace requires it or because the vendor feels that
|
158
|
-
# it enhances the product while another vendor may omit the same item.
|
159
|
-
# An implementation which does not include a particular option MUST be
|
160
|
-
# prepared to interoperate with another implementation which does
|
161
|
-
# include the option, though perhaps with reduced functionality. In the
|
162
|
-
# same vein an implementation which does include a particular option
|
163
|
-
# MUST be prepared to interoperate with another implementation which
|
164
|
-
# does not include the option (except, of course, for the feature the
|
165
|
-
# option provides.)
|
166
|
-
#
|
167
|
-
# @example _Optional_ definition
|
168
|
-
# it { "foo".bar }.MAY match /^foo$/
|
169
|
-
#
|
170
|
-
# @param matcher [#matches?] The matcher.
|
171
|
-
#
|
172
|
-
# @return [Spectus::Result::Fail, Spectus::Result::Pass] Report if the spec pass or fail.
|
173
|
-
def MAY(matcher)
|
174
|
-
RequirementLevel::May.new(
|
175
|
-
callable: callable,
|
176
|
-
isolation: false,
|
177
|
-
negate: false,
|
178
|
-
matcher: matcher
|
179
|
-
).call
|
180
|
-
end
|
181
|
-
|
182
|
-
# @example _Optional_ definition with isolation
|
183
|
-
# it { "foo".bar }.MAY! match /^foo$/
|
184
|
-
#
|
185
|
-
# @see MAY
|
186
|
-
def MAY!(matcher)
|
187
|
-
RequirementLevel::May.new(
|
188
|
-
callable: callable,
|
189
|
-
isolation: true,
|
190
|
-
negate: false,
|
191
|
-
matcher: matcher
|
192
|
-
).call
|
193
|
-
end
|
194
|
-
|
195
|
-
# rubocop:enable Naming/MethodName
|
196
|
-
|
197
|
-
protected
|
198
|
-
|
199
|
-
# @return [#call] The callable object to test.
|
200
|
-
attr_reader :callable
|
201
|
-
end
|
202
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative File.join("..", "exam")
|
4
|
-
require_relative File.join("..", "result")
|
5
|
-
|
6
|
-
module Spectus
|
7
|
-
# Namespace for the requirement levels.
|
8
|
-
module RequirementLevel
|
9
|
-
# Requirement level's base class.
|
10
|
-
class Base
|
11
|
-
# Initialize the requirement level class.
|
12
|
-
#
|
13
|
-
# @param callable [#call] The callable object to test.
|
14
|
-
# @param isolation [Boolean] Compute actual in isolation?
|
15
|
-
# @param negate [Boolean] Positive or negative assertion?
|
16
|
-
# @param matcher [#matches?] The matcher.
|
17
|
-
def initialize(callable:, isolation:, negate:, matcher:)
|
18
|
-
@negate = negate
|
19
|
-
@matcher = matcher
|
20
|
-
|
21
|
-
@exam = Exam.new(
|
22
|
-
callable: callable,
|
23
|
-
isolation: isolation,
|
24
|
-
negate: negate,
|
25
|
-
matcher: matcher
|
26
|
-
)
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [#Exam] The exam.
|
30
|
-
attr_reader :exam
|
31
|
-
|
32
|
-
# @return [#matches?] The matcher that performed a boolean comparison
|
33
|
-
# between the actual value and the expected value.
|
34
|
-
attr_reader :matcher
|
35
|
-
|
36
|
-
# The result of the expectation.
|
37
|
-
#
|
38
|
-
# @raise [Spectus::Result::Fail] The expectation failed.
|
39
|
-
# @return [Spectus::Result::Pass] The expectation passed.
|
40
|
-
def call
|
41
|
-
Result.call(pass?).with(
|
42
|
-
actual: exam.actual,
|
43
|
-
error: exam.exception,
|
44
|
-
expected: matcher.expected,
|
45
|
-
got: exam.got,
|
46
|
-
negate: negate?,
|
47
|
-
valid: exam.valid?,
|
48
|
-
matcher: matcher.class.to_sym,
|
49
|
-
level: level
|
50
|
-
)
|
51
|
-
end
|
52
|
-
|
53
|
-
protected
|
54
|
-
|
55
|
-
# @return [Symbol] The requirement level.
|
56
|
-
def level
|
57
|
-
self.class.name.split("::").fetch(-1).upcase.to_sym
|
58
|
-
end
|
59
|
-
|
60
|
-
# @note The boolean comparison between the actual value and the expected
|
61
|
-
# value can be evaluated to a negative assertion.
|
62
|
-
#
|
63
|
-
# @return [Boolean] Positive or negative assertion?
|
64
|
-
def negate?
|
65
|
-
@negate
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,17 +0,0 @@
|
|
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
|
data/lib/spectus/result.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative File.join("result", "fail")
|
4
|
-
require_relative File.join("result", "pass")
|
5
|
-
|
6
|
-
module Spectus
|
7
|
-
# Namespace for the results.
|
8
|
-
module Result
|
9
|
-
# @param is_passed [Boolean] The value of an assertion.
|
10
|
-
# @return [Class<Spectus::Result::Pass>, Class<Spectus::Result::Fail>] The
|
11
|
-
# class of the result.
|
12
|
-
# @example Get the pass class result.
|
13
|
-
# call(true) # => Pass
|
14
|
-
def self.call(is_passed)
|
15
|
-
is_passed ? Pass : Fail
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/spectus/result/fail.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "expresenter/fail"
|
4
|
-
|
5
|
-
module Spectus
|
6
|
-
module Result
|
7
|
-
# The class that is responsible for reporting that the expectation is false.
|
8
|
-
#
|
9
|
-
# @see https://github.com/fixrb/expresenter/blob/v1.2.1/lib/expresenter/fail.rb
|
10
|
-
class Fail < ::Expresenter::Fail
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
data/lib/spectus/result/pass.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "expresenter/pass"
|
4
|
-
|
5
|
-
module Spectus
|
6
|
-
module Result
|
7
|
-
# The class that is responsible for reporting that the expectation is true.
|
8
|
-
#
|
9
|
-
# @see https://github.com/fixrb/expresenter/blob/v1.2.1/lib/expresenter/pass.rb
|
10
|
-
class Pass < ::Expresenter::Pass
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|