r_spec-clone 1.7.0 → 1.7.1
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 +26 -24
- data/lib/r_spec/clone/dsl.rb +9 -9
- data/lib/r_spec/clone/expectation_helper/shared.rb +6 -6
- data/lib/r_spec/clone/expectation_target/base.rb +8 -8
- data/lib/r_spec/clone/expectation_target/block.rb +3 -3
- data/lib/r_spec/clone/expectation_target/value.rb +3 -3
- data/lib/r_spec.rb +6 -6
- metadata +10 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc9ae84dcef40b1d832122a6a7bec48cf1bceb8104680cc2de36a9be584fd09d
|
4
|
+
data.tar.gz: b3d8ad619c723b0b90e82f93791f1d8d927457518e91e8ab36e324e45328fb11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5dabfdecc24dcb00bfc37a6c5f8afaa57520824ec9c8f08b86088c0f0e2fc3e3bcf9333dd0d3a21746127afa7c3bb072090145270f07c93cb318cc5dc7c9b806
|
7
|
+
data.tar.gz: 7998dfc059b0a80302ef5d123401ead7c7b2eec0bbfe64a551b8d53d3f8f1ab4f0f5624b787de685ddc1c355805a941bef3159cd2093e760c35ef9adff802ed5
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# RSpec clone
|
1
|
+
# RSpec clone <img src="https://github.com/cyril/r_spec-clone.rb/raw/main/img/logo.svg" style="height: 1em; width: 1em" />
|
2
2
|
|
3
3
|
A minimalist __RSpec clone__ with all the essentials.
|
4
4
|
|
@@ -7,33 +7,35 @@ A minimalist __RSpec clone__ with all the essentials.
|
|
7
7
|
## Status
|
8
8
|
|
9
9
|
[](https://r-spec.dev/)
|
10
|
-
[](https://github.com/cyril/r_spec-clone.rb/
|
10
|
+
[](https://github.com/cyril/r_spec-clone.rb/tags)
|
11
11
|
[](https://rubydoc.info/github/cyril/r_spec-clone.rb/main)
|
12
|
-
[](https://github.com/cyril/r_spec-clone.rb/actions?query=workflow%3Aruby+branch%3Amain)
|
13
13
|
[](https://github.com/cyril/r_spec-clone.rb/actions?query=workflow%3Arubocop+branch%3Amain)
|
14
14
|
[](https://github.com/cyril/r_spec-clone.rb/raw/main/LICENSE.md)
|
15
15
|
|
16
|
-
## Project
|
16
|
+
## Project Goals
|
17
17
|
|
18
|
-
1.
|
19
|
-
2.
|
20
|
-
3. Avoid
|
21
|
-
4. Provide the basics of
|
18
|
+
1. Maintain low code complexity to avoid false negatives and false positives.
|
19
|
+
2. Implement the loading of specifications using simple, atomic, and thread-safe Ruby primitives.
|
20
|
+
3. Avoid cluttering the interface with unnecessary alternative syntaxes.
|
21
|
+
4. Provide the basics of the RSpec DSL for writing tests.
|
22
22
|
|
23
|
-
## Some
|
23
|
+
## Some Differences
|
24
24
|
|
25
|
-
*
|
26
|
-
*
|
27
|
-
* Malicious _actual values_ cannot [
|
28
|
-
* If no `subject`
|
29
|
-
* If no described class is set, `described_class` is undefined
|
25
|
+
* Monkey-patching is not an available option.
|
26
|
+
* The framework does not use [hacks such as the `at_exit` hook](https://blog.arkency.com/2013/06/are-we-abusing-at-exit/) to trigger tests.
|
27
|
+
* Malicious _actual values_ cannot [compromise results](https://asciinema.org/a/423547?autoplay=1&speed=2).
|
28
|
+
* If no `subject` is explicitly determined, it remains undefined.
|
29
|
+
* If no described class is set, `described_class` is undefined rather than `nil`.
|
30
30
|
* Expectations cannot be added inside a `before` block.
|
31
|
-
* [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not
|
31
|
+
* [Arbitrary helper methods](https://relishapp.com/rspec/rspec-core/v/3-10/docs/helper-methods/arbitrary-helper-methods) are not accessible within examples.
|
32
32
|
* The `let` method defines a helper method rather than a memoized helper method.
|
33
|
-
* The one-liner `is_expected` syntax
|
34
|
-
* `subject`, `before
|
35
|
-
* The
|
36
|
-
*
|
33
|
+
* The one-liner `is_expected` syntax is compatible with block expectations.
|
34
|
+
* Definitions of `subject`, `before`, and `let` must precede examples.
|
35
|
+
* The `after` hook is unsupported.
|
36
|
+
* The execution of the test suite halts immediately when an error is detected.
|
37
|
+
* Each `context` block isolates its tests and any potential side effects.
|
38
|
+
* The `its` method is available without the need for external dependencies.
|
37
39
|
|
38
40
|
## Installation
|
39
41
|
|
@@ -46,7 +48,7 @@ gem "r_spec-clone"
|
|
46
48
|
And then execute:
|
47
49
|
|
48
50
|
```sh
|
49
|
-
bundle
|
51
|
+
bundle install
|
50
52
|
```
|
51
53
|
|
52
54
|
Or install it yourself as:
|
@@ -59,7 +61,7 @@ gem install r_spec-clone
|
|
59
61
|
|
60
62
|
__RSpec clone__ provides a structure for writing executable examples of how your code should behave.
|
61
63
|
|
62
|
-
Inspired by RSpec, it includes a domain
|
64
|
+
Inspired by RSpec, it includes a domain-specific language (DSL) that allows you to write examples in a way similar to plain english.
|
63
65
|
|
64
66
|
A basic spec looks something like this:
|
65
67
|
|
@@ -67,10 +69,10 @@ A basic spec looks something like this:
|
|
67
69
|
|
68
70
|
## Usage
|
69
71
|
|
70
|
-
### Anatomy of a
|
72
|
+
### Anatomy of a Spec File
|
71
73
|
|
72
|
-
To
|
73
|
-
Many projects
|
74
|
+
To utilize the `RSpec` module and its DSL, include `require "r_spec"` in your spec files.
|
75
|
+
Many projects organize these includes through a custom spec helper.
|
74
76
|
|
75
77
|
Concrete test cases are defined in `it` blocks.
|
76
78
|
An optional (but recommended) descriptive string or module indicates the purpose of the test and a block contains the main logic of the test.
|
data/lib/r_spec/clone/dsl.rb
CHANGED
@@ -79,10 +79,10 @@ module RSpec
|
|
79
79
|
# @return [Symbol] A private method that define the block content.
|
80
80
|
#
|
81
81
|
# @api public
|
82
|
-
def self.let(name,
|
82
|
+
def self.let(name, ...)
|
83
83
|
raise Error::ReservedMethod if BEFORE_METHOD.equal?(name.to_sym)
|
84
84
|
|
85
|
-
private define_method(name,
|
85
|
+
private define_method(name, ...)
|
86
86
|
end
|
87
87
|
|
88
88
|
# Sets a user-defined property named {#subject}.
|
@@ -113,8 +113,8 @@ module RSpec
|
|
113
113
|
# @return [Symbol] A {#subject} method that define the block content.
|
114
114
|
#
|
115
115
|
# @api public
|
116
|
-
def self.subject(&
|
117
|
-
let(__method__, &
|
116
|
+
def self.subject(&)
|
117
|
+
let(__method__, &)
|
118
118
|
end
|
119
119
|
|
120
120
|
# Defines an example group that describes a unit to be tested.
|
@@ -135,10 +135,10 @@ module RSpec
|
|
135
135
|
# @param block [Proc] The block to define the specs.
|
136
136
|
#
|
137
137
|
# @api public
|
138
|
-
def self.describe(const, &
|
138
|
+
def self.describe(const, &)
|
139
139
|
desc = ::Class.new(self)
|
140
140
|
desc.let(:described_class) { const } if const.is_a?(::Module)
|
141
|
-
desc.instance_eval(&
|
141
|
+
desc.instance_eval(&)
|
142
142
|
end
|
143
143
|
|
144
144
|
# :nocov:
|
@@ -273,10 +273,10 @@ module RSpec
|
|
273
273
|
# @return (see ExpectationTarget::Base#result)
|
274
274
|
#
|
275
275
|
# @api public
|
276
|
-
def self.its(attribute,
|
276
|
+
def self.its(attribute, *, **, &block)
|
277
277
|
Logger.source(*block.source_location)
|
278
|
-
example_with_attribute(attribute,
|
279
|
-
|
278
|
+
example_with_attribute(attribute, *, **).new
|
279
|
+
.instance_eval(&block)
|
280
280
|
end
|
281
281
|
|
282
282
|
# :nocov:
|
@@ -194,8 +194,8 @@ module RSpec
|
|
194
194
|
# @return [#matches?] A change matcher.
|
195
195
|
#
|
196
196
|
# @api public
|
197
|
-
def change(object, method,
|
198
|
-
::Matchi::Change.new(object, method,
|
197
|
+
def change(object, method, ...)
|
198
|
+
::Matchi::Change.new(object, method, ...)
|
199
199
|
end
|
200
200
|
|
201
201
|
# Satisfy matcher
|
@@ -209,8 +209,8 @@ module RSpec
|
|
209
209
|
# @return [#matches?] A satisfy matcher.
|
210
210
|
#
|
211
211
|
# @api public
|
212
|
-
def satisfy(&
|
213
|
-
::Matchi::Satisfy.new(&
|
212
|
+
def satisfy(&)
|
213
|
+
::Matchi::Satisfy.new(&)
|
214
214
|
end
|
215
215
|
|
216
216
|
private
|
@@ -221,10 +221,10 @@ module RSpec
|
|
221
221
|
# matcher = be_empty
|
222
222
|
# matcher.matches? { [] } # => true
|
223
223
|
# matcher.matches? { [4] } # => false
|
224
|
-
def method_missing(name,
|
224
|
+
def method_missing(name, ...)
|
225
225
|
return super unless predicate_matcher_name?(name)
|
226
226
|
|
227
|
-
::Matchi::Predicate.new(name,
|
227
|
+
::Matchi::Predicate.new(name, ...)
|
228
228
|
end
|
229
229
|
|
230
230
|
# :nocov:
|
@@ -32,7 +32,7 @@ module RSpec
|
|
32
32
|
#
|
33
33
|
# @api public
|
34
34
|
def to(matcher)
|
35
|
-
absolute_requirement(matcher
|
35
|
+
absolute_requirement(matcher:, negate: false)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Runs the given expectation, passing if `matcher` returns false.
|
@@ -47,7 +47,7 @@ module RSpec
|
|
47
47
|
#
|
48
48
|
# @api public
|
49
49
|
def not_to(matcher)
|
50
|
-
absolute_requirement(matcher
|
50
|
+
absolute_requirement(matcher:, negate: true)
|
51
51
|
end
|
52
52
|
|
53
53
|
protected
|
@@ -66,8 +66,8 @@ module RSpec
|
|
66
66
|
actual: test.actual,
|
67
67
|
error: test.error,
|
68
68
|
got: test.got,
|
69
|
-
matcher
|
70
|
-
negate:
|
69
|
+
matcher:,
|
70
|
+
negate:
|
71
71
|
)
|
72
72
|
end
|
73
73
|
|
@@ -97,12 +97,12 @@ module RSpec
|
|
97
97
|
# `Kernel.exit(false)` with a failure message written to STDERR.
|
98
98
|
def result(passed, actual:, error:, got:, matcher:, negate:)
|
99
99
|
Logger.passed_spec ::Expresenter.call(passed).with(
|
100
|
-
actual
|
100
|
+
actual:,
|
101
101
|
definition: matcher.to_s,
|
102
|
-
error
|
102
|
+
error:,
|
103
103
|
expected: matcher.expected,
|
104
|
-
got
|
105
|
-
negate
|
104
|
+
got:,
|
105
|
+
negate:,
|
106
106
|
level: :MUST
|
107
107
|
)
|
108
108
|
rescue ::Expresenter::Fail => e
|
@@ -29,9 +29,9 @@ module RSpec
|
|
29
29
|
# @raise (see Base#absolute_requirement)
|
30
30
|
def absolute_requirement(matcher:, negate:)
|
31
31
|
super(
|
32
|
-
::TestTube.invoke(
|
33
|
-
matcher
|
34
|
-
negate:
|
32
|
+
::TestTube.invoke(matcher:, negate:, &@input),
|
33
|
+
matcher:,
|
34
|
+
negate:
|
35
35
|
)
|
36
36
|
end
|
37
37
|
end
|
@@ -29,9 +29,9 @@ module RSpec
|
|
29
29
|
# @raise (see Base#absolute_requirement)
|
30
30
|
def absolute_requirement(matcher:, negate:)
|
31
31
|
super(
|
32
|
-
::TestTube.pass(@input, matcher
|
33
|
-
matcher
|
34
|
-
negate:
|
32
|
+
::TestTube.pass(@input, matcher:, negate:),
|
33
|
+
matcher:,
|
34
|
+
negate:
|
35
35
|
)
|
36
36
|
end
|
37
37
|
end
|
data/lib/r_spec.rb
CHANGED
@@ -83,8 +83,8 @@ module RSpec
|
|
83
83
|
# @param description [String] A description that usually begins with "when",
|
84
84
|
# "with" or "without".
|
85
85
|
# @param block [Proc] The block to define the specs.
|
86
|
-
def self.context(description, &
|
87
|
-
Clone::Dsl.context(description, &
|
86
|
+
def self.context(description, &)
|
87
|
+
Clone::Dsl.context(description, &)
|
88
88
|
end
|
89
89
|
|
90
90
|
# :nocov:
|
@@ -117,8 +117,8 @@ module RSpec
|
|
117
117
|
#
|
118
118
|
# @param const [Module, String] A module to include in block context.
|
119
119
|
# @param block [Proc] The block to define the specs.
|
120
|
-
def self.describe(const, &
|
121
|
-
Clone::Dsl.describe(const, &
|
120
|
+
def self.describe(const, &)
|
121
|
+
Clone::Dsl.describe(const, &)
|
122
122
|
end
|
123
123
|
|
124
124
|
# :nocov:
|
@@ -143,8 +143,8 @@ module RSpec
|
|
143
143
|
#
|
144
144
|
# @raise (see RSpec::Clone::ExpectationTarget::Base#result)
|
145
145
|
# @return (see RSpec::Clone::ExpectationTarget::Base#result)
|
146
|
-
def self.it(name = nil, &
|
147
|
-
Clone::Dsl.it(name, &
|
146
|
+
def self.it(name = nil, &)
|
147
|
+
Clone::Dsl.it(name, &)
|
148
148
|
end
|
149
149
|
|
150
150
|
# :nocov:
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: r_spec-clone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: expresenter
|
@@ -16,168 +16,42 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.4.
|
19
|
+
version: 1.4.1
|
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: 1.4.
|
26
|
+
version: 1.4.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: matchi
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 3.3.
|
33
|
+
version: 3.3.2
|
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: 3.3.
|
40
|
+
version: 3.3.2
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: test_tube
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 3.0.0
|
48
48
|
type: :runtime
|
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:
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: bundler
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rake
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rubocop-md
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: rubocop-performance
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rubocop-rake
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: rubocop-rspec
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: rubocop-thread_safety
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: simplecov
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: yard
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
54
|
+
version: 3.0.0
|
181
55
|
description: A minimalist RSpec clone with all the essentials.
|
182
56
|
email: contact@cyril.email
|
183
57
|
executables: []
|
@@ -220,14 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
220
94
|
requirements:
|
221
95
|
- - ">="
|
222
96
|
- !ruby/object:Gem::Version
|
223
|
-
version: 3.
|
97
|
+
version: 3.2.4
|
224
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
225
99
|
requirements:
|
226
100
|
- - ">="
|
227
101
|
- !ruby/object:Gem::Version
|
228
102
|
version: '0'
|
229
103
|
requirements: []
|
230
|
-
rubygems_version: 3.
|
104
|
+
rubygems_version: 3.4.19
|
231
105
|
signing_key:
|
232
106
|
specification_version: 4
|
233
107
|
summary: A minimalist RSpec clone
|