fix 1.0.0.beta6 → 1.0.0.beta7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 361429a943a5149aeea4da4dd83150360db392275ec4d9d98e76969e60768dce
4
- data.tar.gz: 6df8aaff424652d4e73cb163cc1dfd973c9a4a03ec2e2d6e351db5f87b0674a8
3
+ metadata.gz: 81f6a38942f7037e0250876038e1a715b9b905d5fb2186fb1b3dc3884ebee446
4
+ data.tar.gz: 76f805fc42a2683236ed85efbc888c25fadfaeb2f975177127101e4eb8ae2618
5
5
  SHA512:
6
- metadata.gz: 24a1c58a8e0db0ef62d97881eabc8a4ff0596d82c96f1dee40fc68edffdc17f0206c6d45aa11040e3b11af35038df58ee7bca1f6190330f91b1b384304dcc304
7
- data.tar.gz: 0a5ff783abf6bce80c8ff4a3e3f2e938c004bf5cd02e8300f56650e6312638e911638ed41c9293047aa757ae73048cd412bae5b6cc031e43bab82a2f4b92e41c
6
+ metadata.gz: c0ae0f82dd15aff8f14252631c2c8cf134bd6ce78ba814267f53e7b88a5d84ff2c7d14bb753152f51cea595f46a0284ae58d370e6e50f74fa6c6ec7bb7dcace1
7
+ data.tar.gz: 983e9f29a66982ab2cfef0548b50c064c0b63605acdb0f055da6f35cfd2153f667bf3d58975f6d274b49101d557149336e3a6c465549cfa3658f758903024617
data/README.md CHANGED
@@ -9,14 +9,21 @@
9
9
 
10
10
  ⚠️ This project is still in the experimental phase. May be used at your own risk.
11
11
 
12
- ![Fix specing framework for Ruby](https://github.com/fixrb/fix/raw/main/img/fix.jpg)
12
+ ![Fix specing framework for Ruby](https://fixrb.dev/fix.webp)
13
+
14
+ ## Project goals
15
+
16
+ * Extract specs from the tests.
17
+ * Look like English documents.
18
+ * Be minimalist and easy to hack.
19
+ * Run tests quickly.
13
20
 
14
21
  ## Installation
15
22
 
16
23
  Add this line to your application's Gemfile:
17
24
 
18
25
  ```ruby
19
- gem "fix", ">= 1.0.0.beta6"
26
+ gem "fix", ">= 1.0.0.beta7"
20
27
  ```
21
28
 
22
29
  And then execute:
@@ -60,9 +67,10 @@ And this fixed behavior:
60
67
  relative "fix"
61
68
 
62
69
  Fix :Duck do
63
- it MUST be_an_instance_of :Duck
70
+ it SHOULD be_an_instance_of :Duck
64
71
 
65
72
  on :swims do
73
+ it MUST be_an_instance_of :String
66
74
  it MUST eql "Swoosh..."
67
75
  end
68
76
 
@@ -94,15 +102,12 @@ ruby examples/duck/test.rb
94
102
  I should see this output:
95
103
 
96
104
  ```txt
97
- NoMethodError: undefined method `sings' for #<Duck:0x00007fc5289bcf68>.
105
+ Success: expected #<Duck:0x00007fc55b3c6388> to be an instance of Duck.
98
106
  Success: expected to eql "Swoosh...".
99
- Success: undefined method `speaks' for #<Duck:0x00007fc5289bcf68>.
107
+ Success: undefined method `speaks' for #<Duck:0x00007fc55b3c46f0>.
108
+ NoMethodError: undefined method `sings' for #<Duck:0x00007fc558aab6d8>.
100
109
  ```
101
110
 
102
- ## Test suite
103
-
104
- __Fix__'s specifications will be [fixed here](https://github.com/fixrb/fix/blob/main/fix/) and will be tested against __Fix__'s codebase executing [test/*](https://github.com/fixrb/fix/blob/main/test/)'s files.
105
-
106
111
  ## Contact
107
112
 
108
113
  * Home page: [https://fixrb.dev/](https://fixrb.dev/)
data/lib/fix.rb CHANGED
@@ -1,24 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative File.join("fix", "doc")
4
- require_relative File.join("fix", "test")
3
+ require_relative File.join("fix", "set")
5
4
 
6
5
  require_relative "kernel"
7
6
 
8
7
  # Namespace for the Fix framework.
8
+ #
9
+ # @api public
9
10
  module Fix
10
11
  # Test a built specification.
11
12
  #
12
13
  # @example Run _Answer_ specification against `42`.
13
- # Fix[:Answer].call(42)
14
- #
15
- # @example Test _Answer_ specification against `42`.
16
- # Fix[:Answer].matches? { 42 }
14
+ # Fix[:Answer].test(42)
17
15
  #
18
16
  # @param name [String, Symbol] The name of the specification document.
19
17
  #
20
- # @return [::Fix::Dsl] The specification document.
18
+ # @return [::Fix::Test] The specification document.
21
19
  def self.[](name)
22
- Test.new(*Doc.const_get(name).const_get(:CONTEXTS))
20
+ ::Fix::Set.load(name)
23
21
  end
24
22
  end
data/lib/fix/doc.rb CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  module Fix
4
4
  # Module for storing spec documents.
5
+ #
6
+ # @api private
5
7
  module Doc
8
+ # @param name [String, Symbol] The name of the specification document.
9
+ def self.fetch(name)
10
+ const_get("#{name}::CONTEXTS")
11
+ end
12
+
13
+ # @param contexts [Array<::Fix::Dsl>] The list of contexts document.
14
+ def self.specs(*contexts)
15
+ contexts.flat_map do |context|
16
+ env = context.new
17
+
18
+ env.public_methods(false).map do |public_method|
19
+ [env] + env.public_send(public_method)
20
+ end
21
+ end
22
+ end
6
23
  end
7
24
  end
data/lib/fix/dsl.rb CHANGED
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "defi"
4
- require "matchi/helper"
5
- require "spectus"
6
4
 
7
- require_relative "test"
5
+ require_relative "matcher"
6
+ require_relative "requirement"
8
7
 
9
8
  module Fix
10
9
  # Abstract class for handling the domain-specific language.
10
+ #
11
+ # @api private
11
12
  class Dsl
12
- include ::Matchi::Helper
13
+ extend Matcher
14
+ extend Requirement
13
15
 
14
16
  # Sets a user-defined property.
15
17
  #
@@ -38,7 +40,7 @@ module Fix
38
40
  #
39
41
  # Fix do
40
42
  # with :password, "secret" do
41
- # it MUST equal true
43
+ # it MUST be true
42
44
  # end
43
45
  # end
44
46
  #
@@ -76,16 +78,8 @@ module Fix
76
78
  const_set("Child#{block.object_id}", klass)
77
79
 
78
80
  klass.define_singleton_method(:challenges) do
79
- super() + [::Defi.send(method_name, *args, **kwargs)]
80
- end
81
-
82
- def klass.initialize
83
- if subject.raised?
84
- subject
85
- else
86
- challenge = ::Defi.send(method_name, *args, **kwargs)
87
- challenge.to(subject.call)
88
- end
81
+ challenge = ::Defi.send(method_name, *args, **kwargs)
82
+ super() + [challenge]
89
83
  end
90
84
 
91
85
  klass.instance_eval(&block)
@@ -97,68 +91,23 @@ module Fix
97
91
  # @example
98
92
  # require "fix"
99
93
  #
100
- # Fix { it MUST equal 42 }
94
+ # Fix { it MUST be 42 }
101
95
  #
102
96
  # @api public
103
97
  def self.it(requirement)
104
- define_method("test_#{requirement.object_id}") { requirement }
105
- end
106
-
107
- # @todo Move this method inside "fix-its" gem.
108
- def self.its(method_name, requirement)
109
- klass = ::Class.new(self)
110
- klass.const_get(:CONTEXTS) << klass
111
-
112
- const_set("Child#{requirement.object_id}", klass)
98
+ location = caller_locations(1, 1).fetch(0)
99
+ location = [location.path, location.lineno].join(":")
113
100
 
114
- klass.define_singleton_method(:challenges) do
115
- super() + [::Defi.send(method_name)]
116
- end
117
-
118
- def klass.initialize
119
- if subject.raised?
120
- subject
121
- else
122
- challenge = ::Defi.send(method_name, *args, **kwargs)
123
- challenge.to(subject.call)
124
- end
101
+ define_method("test_#{requirement.object_id}") do
102
+ [location, requirement, self.class.challenges]
125
103
  end
126
-
127
- klass.it(requirement)
128
104
  end
129
105
 
106
+ # The list of challenges to be addressed to the object to be tested.
107
+ #
108
+ # @return [Array<Defi::Challenge>] A list of challenges.
130
109
  def self.challenges
131
110
  []
132
111
  end
133
-
134
- def self.test(&subject)
135
- Test.new(self).test(&subject)
136
- end
137
-
138
- private_class_method :challenges
139
-
140
- private
141
-
142
- attr_reader :subject
143
-
144
- def initialize(&subject)
145
- @subject = ::Defi::Value.new(&subject)
146
- end
147
-
148
- ::Matchi::Matcher.constants.each do |matcher_const|
149
- next if matcher_const.equal?(:Base)
150
-
151
- matcher_klass = ::Matchi::Matcher.const_get(matcher_const)
152
-
153
- define_singleton_method(matcher_klass.to_sym) do |*args|
154
- matcher_klass.new(*args)
155
- end
156
- end
157
-
158
- ::Spectus.methods(false).each do |method_name|
159
- define_singleton_method(method_name.upcase) do |matcher|
160
- ::Spectus.public_send(method_name, matcher)
161
- end
162
- end
163
112
  end
164
113
  end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "matchi"
4
+
5
+ module Fix
6
+ # Collection of expectation matchers.
7
+ #
8
+ # @api private
9
+ module Matcher
10
+ # Equivalence matcher
11
+ #
12
+ # @example
13
+ # matcher = eq("foo")
14
+ # matcher.matches? { "foo" } # => true
15
+ # matcher.matches? { "bar" } # => false
16
+ #
17
+ # @param expected [#eql?] An expected equivalent object.
18
+ #
19
+ # @return [#matches?] An equivalence matcher.
20
+ #
21
+ # @api public
22
+ def eq(expected)
23
+ ::Matchi::Eq.new(expected)
24
+ end
25
+
26
+ alias eql eq
27
+
28
+ # Identity matcher
29
+ #
30
+ # @example
31
+ # object = "foo"
32
+ # matcher = be(object)
33
+ # matcher.matches? { object } # => true
34
+ # matcher.matches? { "foo" } # => false
35
+ #
36
+ # @param expected [#equal?] The expected identical object.
37
+ #
38
+ # @return [#matches?] An identity matcher.
39
+ #
40
+ # @api public
41
+ def be(expected)
42
+ ::Matchi::Be.new(expected)
43
+ end
44
+
45
+ alias equal be
46
+
47
+ # Comparisons matcher
48
+ #
49
+ # @example
50
+ # matcher = be_within(1).of(41)
51
+ # matcher.matches? { 42 } # => true
52
+ # matcher.matches? { 43 } # => false
53
+ #
54
+ # @param delta [Numeric] A numeric value.
55
+ #
56
+ # @return [#matches?] A comparison matcher.
57
+ #
58
+ # @api public
59
+ def be_within(delta)
60
+ ::Matchi::BeWithin.new(delta)
61
+ end
62
+
63
+ # Regular expressions matcher
64
+ #
65
+ # @example
66
+ # matcher = match(/^foo$/)
67
+ # matcher.matches? { "foo" } # => true
68
+ # matcher.matches? { "bar" } # => false
69
+ #
70
+ # @param expected [#match] A regular expression.
71
+ #
72
+ # @return [#matches?] A regular expression matcher.
73
+ #
74
+ # @api public
75
+ def match(expected)
76
+ ::Matchi::Match.new(expected)
77
+ end
78
+
79
+ # Expecting errors matcher
80
+ #
81
+ # @example
82
+ # matcher = raise_exception(NameError)
83
+ # matcher.matches? { Boom } # => true
84
+ # matcher.matches? { true } # => false
85
+ #
86
+ # @param expected [Exception, #to_s] The expected exception name.
87
+ #
88
+ # @return [#matches?] An error matcher.
89
+ #
90
+ # @api public
91
+ def raise_exception(expected)
92
+ ::Matchi::RaiseException.new(expected)
93
+ end
94
+
95
+ # True matcher
96
+ #
97
+ # @example
98
+ # matcher = be_true
99
+ # matcher.matches? { true } # => true
100
+ # matcher.matches? { false } # => false
101
+ # matcher.matches? { nil } # => false
102
+ # matcher.matches? { 4 } # => false
103
+ #
104
+ # @return [#matches?] A `true` matcher.
105
+ #
106
+ # @api public
107
+ def be_true
108
+ be(true)
109
+ end
110
+
111
+ # False matcher
112
+ #
113
+ # @example
114
+ # matcher = be_false
115
+ # matcher.matches? { false } # => true
116
+ # matcher.matches? { true } # => false
117
+ # matcher.matches? { nil } # => false
118
+ # matcher.matches? { 4 } # => false
119
+ #
120
+ # @return [#matches?] A `false` matcher.
121
+ #
122
+ # @api public
123
+ def be_false
124
+ be(false)
125
+ end
126
+
127
+ # Nil matcher
128
+ #
129
+ # @example
130
+ # matcher = be_nil
131
+ # matcher.matches? { nil } # => true
132
+ # matcher.matches? { false } # => false
133
+ # matcher.matches? { true } # => false
134
+ # matcher.matches? { 4 } # => false
135
+ #
136
+ # @return [#matches?] A `nil` matcher.
137
+ #
138
+ # @api public
139
+ def be_nil
140
+ be(nil)
141
+ end
142
+
143
+ # Type/class matcher
144
+ #
145
+ # @example
146
+ # matcher = be_an_instance_of(String)
147
+ # matcher.matches? { "foo" } # => true
148
+ # matcher.matches? { 4 } # => false
149
+ #
150
+ # @param expected [Class, #to_s] The expected class name.
151
+ #
152
+ # @return [#matches?] A type/class matcher.
153
+ #
154
+ # @api public
155
+ def be_an_instance_of(expected)
156
+ ::Matchi::BeAnInstanceOf.new(expected)
157
+ end
158
+
159
+ # Change matcher
160
+ #
161
+ # @example
162
+ # object = []
163
+ # matcher = change(object, :length).by(1)
164
+ # matcher.matches? { object << 1 } # => true
165
+ #
166
+ # object = []
167
+ # matcher = change(object, :length).by_at_least(1)
168
+ # matcher.matches? { object << 1 } # => true
169
+ #
170
+ # object = []
171
+ # matcher = change(object, :length).by_at_most(1)
172
+ # matcher.matches? { object << 1 } # => true
173
+ #
174
+ # object = "foo"
175
+ # matcher = change(object, :to_s).from("foo").to("FOO")
176
+ # matcher.matches? { object.upcase! } # => true
177
+ #
178
+ # object = "foo"
179
+ # matcher = change(object, :to_s).to("FOO")
180
+ # matcher.matches? { object.upcase! } # => true
181
+ #
182
+ # @param object [#object_id] An object.
183
+ # @param method [Symbol] The name of a method.
184
+ # @param args [Array] A list of arguments.
185
+ # @param kwargs [Hash] A list of keyword arguments.
186
+ #
187
+ # @return [#matches?] A change matcher.
188
+ #
189
+ # @api public
190
+ def change(object, method, *args, **kwargs, &block)
191
+ ::Matchi::Change.new(object, method, *args, **kwargs, &block)
192
+ end
193
+
194
+ # Satisfy matcher
195
+ #
196
+ # @example
197
+ # matcher = satisfy { |value| value == 42 }
198
+ # matcher.matches? { 42 } # => true
199
+ #
200
+ # @param expected [Proc] A block of code.
201
+ #
202
+ # @return [#matches?] A satisfy matcher.
203
+ #
204
+ # @api public
205
+ def satisfy(&expected)
206
+ ::Matchi::Satisfy.new(&expected)
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spectus/requirement/optional"
4
+ require "spectus/requirement/recommended"
5
+ require "spectus/requirement/required"
6
+
7
+ module Fix
8
+ # Collection of expectation matchers.
9
+ #
10
+ # @api private
11
+ module Requirement
12
+ # rubocop:disable Naming/MethodName
13
+
14
+ # This method mean that the definition is an absolute requirement of the
15
+ # specification.
16
+ #
17
+ # @param matcher [#matches?] The matcher.
18
+ #
19
+ # @return [Requirement::Required] An absolute requirement level instance.
20
+ #
21
+ # @api public
22
+ def MUST(matcher)
23
+ ::Spectus::Requirement::Required.new(
24
+ isolate: false,
25
+ negate: false,
26
+ matcher: matcher
27
+ )
28
+ end
29
+
30
+ # @see MUST
31
+ #
32
+ # @api public
33
+ def MUST!(matcher)
34
+ ::Spectus::Requirement::Required.new(
35
+ isolate: true,
36
+ negate: false,
37
+ matcher: matcher
38
+ )
39
+ end
40
+
41
+ # This method mean that the definition is an absolute prohibition of the specification.
42
+ #
43
+ # @param matcher [#matches?] The matcher.
44
+ #
45
+ # @return [Requirement::Required] An absolute prohibition level instance.
46
+ #
47
+ # @api public
48
+ def MUST_NOT(matcher)
49
+ ::Spectus::Requirement::Required.new(
50
+ isolate: false,
51
+ negate: true,
52
+ matcher: matcher
53
+ )
54
+ end
55
+
56
+ # @see MUST_NOT
57
+ #
58
+ # @api public
59
+ def MUST_NOT!(matcher)
60
+ ::Spectus::Requirement::Required.new(
61
+ isolate: true,
62
+ negate: true,
63
+ matcher: matcher
64
+ )
65
+ end
66
+
67
+ # This method mean that there may exist valid reasons in particular
68
+ # circumstances to ignore a particular item, but the full implications must be
69
+ # understood and carefully weighed before choosing a different course.
70
+ #
71
+ # @param matcher [#matches?] The matcher.
72
+ #
73
+ # @return [Requirement::Recommended] A recommended requirement level instance.
74
+ #
75
+ # @api public
76
+ def SHOULD(matcher)
77
+ ::Spectus::Requirement::Recommended.new(
78
+ isolate: false,
79
+ negate: false,
80
+ matcher: matcher
81
+ )
82
+ end
83
+
84
+ # @see SHOULD
85
+ #
86
+ # @api public
87
+ def SHOULD!(matcher)
88
+ ::Spectus::Requirement::Recommended.new(
89
+ isolate: true,
90
+ negate: false,
91
+ matcher: matcher
92
+ )
93
+ end
94
+
95
+ # This method mean that there may exist valid reasons in particular
96
+ # circumstances when the particular behavior is acceptable or even useful, but
97
+ # the full implications should be understood and the case carefully weighed
98
+ # before implementing any behavior described with this label.
99
+ #
100
+ # @param matcher [#matches?] The matcher.
101
+ #
102
+ # @return [Requirement::Recommended] A not recommended requirement level
103
+ # instance.
104
+ #
105
+ # @api public
106
+ def SHOULD_NOT(matcher)
107
+ ::Spectus::Requirement::Recommended.new(
108
+ isolate: false,
109
+ negate: true,
110
+ matcher: matcher
111
+ )
112
+ end
113
+
114
+ # @see SHOULD_NOT
115
+ #
116
+ # @api public
117
+ def SHOULD_NOT!(matcher)
118
+ ::Spectus::Requirement::Recommended.new(
119
+ isolate: true,
120
+ negate: true,
121
+ matcher: matcher
122
+ )
123
+ end
124
+
125
+ # This method mean that an item is truly optional.
126
+ # One vendor may choose to include the item because a particular marketplace
127
+ # requires it or because the vendor feels that it enhances the product while
128
+ # another vendor may omit the same item. An implementation which does not
129
+ # include a particular option must be prepared to interoperate with another
130
+ # implementation which does include the option, though perhaps with reduced
131
+ # functionality. In the same vein an implementation which does include a
132
+ # particular option must be prepared to interoperate with another
133
+ # implementation which does not include the option (except, of course, for the
134
+ # feature the option provides).
135
+ #
136
+ # @param matcher [#matches?] The matcher.
137
+ #
138
+ # @return [Requirement::Optional] An optional requirement level instance.
139
+ #
140
+ # @api public
141
+ def MAY(matcher)
142
+ ::Spectus::Requirement::Optional.new(
143
+ isolate: false,
144
+ negate: false,
145
+ matcher: matcher
146
+ )
147
+ end
148
+
149
+ # @see MAY
150
+ #
151
+ # @api public
152
+ def MAY!(matcher)
153
+ ::Spectus::Requirement::Optional.new(
154
+ isolate: true,
155
+ negate: false,
156
+ matcher: matcher
157
+ )
158
+ end
159
+
160
+ # rubocop:enable Naming/MethodName
161
+ end
162
+ end
data/lib/fix/run.rb ADDED
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "expresenter/fail"
4
+
5
+ module Fix
6
+ # Run class.
7
+ #
8
+ # @api private
9
+ class Run
10
+ # @return [::Fix::Dsl] A context instance.
11
+ attr_reader :environment
12
+
13
+ # @return [::Spectus::Requirement::Base] An expectation.
14
+ attr_reader :requirement
15
+
16
+ # @return [Array<::Defi::Challenge>] A list of challenges.
17
+ attr_reader :challenges
18
+
19
+ # @param environment [::Fix::Dsl] A context instance.
20
+ # @param requirement [::Spectus::Requirement::Base] An expectation.
21
+ # @param challenges [Array<::Defi::Challenge>] A list of challenges.
22
+ def initialize(environment, requirement, *challenges)
23
+ @environment = environment
24
+ @requirement = requirement
25
+ @challenges = challenges
26
+ end
27
+
28
+ # Verify if the object checks the condition.
29
+ #
30
+ # @param subject [Proc] The block of code to be tested.
31
+ #
32
+ # @raise [::Expresenter::Fail] A failed spec exception.
33
+ # @return [::Expresenter::Pass] A passed spec instance.
34
+ #
35
+ # @see https://github.com/fixrb/expresenter
36
+ def against(&subject)
37
+ requirement.call { actual_value(&subject) }
38
+ rescue ::Expresenter::Fail => e
39
+ e
40
+ end
41
+
42
+ private
43
+
44
+ # The test's actual value.
45
+ #
46
+ # @param subject [Proc] The block of code to be tested.
47
+ #
48
+ # @return [#object_id] The actual value to be tested.
49
+ def actual_value(&subject)
50
+ challenges.inject(environment.instance_eval(&subject)) do |obj, challenge|
51
+ challenge.to(obj).call
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/fix/set.rb ADDED
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "doc"
4
+ require_relative "run"
5
+
6
+ module Fix
7
+ # Collection of specifications.
8
+ #
9
+ # @api private
10
+ class Set
11
+ # The type of result.
12
+ #
13
+ # Passed expectations can be classified as:
14
+ #
15
+ # * `success`
16
+ # * `warning`
17
+ # * `info`
18
+ #
19
+ # Failed expectations can be classified as:
20
+ #
21
+ # * `failure`
22
+ # * `error`
23
+ LOG_LEVELS = %w[
24
+ none
25
+ error
26
+ failure
27
+ warning
28
+ info
29
+ success
30
+ ].freeze
31
+
32
+ # @return [Array] A list of specifications.
33
+ attr_reader :specs
34
+
35
+ # @param name [String, Symbol] The name of the specification document.
36
+ #
37
+ # @api public
38
+ def self.load(name)
39
+ new(*Doc.fetch(name))
40
+ end
41
+
42
+ # @param contexts [Array<::Fix::Dsl>] The list of contexts document.
43
+ def initialize(*contexts)
44
+ @specs = Doc.specs(*contexts)
45
+ @passed = true
46
+ end
47
+
48
+ # @param subject [Proc] The block of code to be tested.
49
+ #
50
+ # @raise [::SystemExit] The result of the test.
51
+ #
52
+ # @api public
53
+ def test(log_level: 5, &subject)
54
+ randomize!
55
+
56
+ specs.each do |environment, location, requirement, challenges|
57
+ runner = Run.new(environment, requirement, *challenges)
58
+ result = runner.against(&subject)
59
+
60
+ failed! if result.failed?
61
+ report!(location, result, log_level: log_level)
62
+ end
63
+
64
+ exit!
65
+ end
66
+
67
+ private
68
+
69
+ def randomize!
70
+ specs.shuffle!
71
+ end
72
+
73
+ # @raise [::SystemExit] The result of the test.
74
+ def exit!
75
+ ::Kernel.exit(passed?)
76
+ end
77
+
78
+ def failed!
79
+ @passed = false
80
+ end
81
+
82
+ # @return [Boolean] The test set passed or failed.
83
+ def passed?
84
+ @passed
85
+ end
86
+
87
+ def report!(path, result, log_level:)
88
+ return unless report?(result, log_level: log_level)
89
+
90
+ puts "#{path} #{result.colored_string}"
91
+ end
92
+
93
+ def report?(result, log_level:)
94
+ LOG_LEVELS[1..log_level].any? { |name| result.public_send("#{name}?") }
95
+ end
96
+ end
97
+ end
data/lib/kernel.rb CHANGED
@@ -2,31 +2,35 @@
2
2
 
3
3
  require_relative File.join("fix", "doc")
4
4
  require_relative File.join("fix", "dsl")
5
- require_relative File.join("fix", "test")
5
+ require_relative File.join("fix", "set")
6
6
 
7
7
  # The Kernel module.
8
8
  module Kernel
9
+ # rubocop:disable Naming/MethodName
10
+
9
11
  # Specifications are built with this method.
10
12
  #
11
- # @example Fix 42 such as it must be equal to 42.
13
+ # @example Require an answer equal to 42.
14
+ # # The spec
12
15
  # Fix :Answer do
13
- # it { MUST equal 42 }
16
+ # it MUST equal 42
14
17
  # end
15
18
  #
19
+ # # A test
20
+ # Fix[:Answer].test { 42 }
21
+ #
16
22
  # @param name [String, Symbol] The name of the specification document.
17
23
  # @param block [Proc] The specifications.
18
24
  #
19
- # @return [Class] The specification document.
25
+ # @return [#test] The collection of specifications.
20
26
  #
21
- # rubocop:disable Naming/MethodName
27
+ # @api public
22
28
  def Fix(name = nil, &block)
23
29
  klass = ::Class.new(::Fix::Dsl)
24
30
  klass.const_set(:CONTEXTS, [klass])
25
31
  klass.instance_eval(&block)
26
-
27
32
  ::Fix::Doc.const_set(name, klass) unless name.nil?
28
-
29
- ::Fix::Test.new(*klass.const_get(:CONTEXTS))
33
+ ::Fix::Set.new(*klass.const_get(:CONTEXTS))
30
34
  end
31
35
  # rubocop:enable Naming/MethodName
32
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fix
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta6
4
+ version: 1.0.0.beta7
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-07-18 00:00:00.000000000 Z
11
+ date: 2021-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: defi
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.2.0
33
+ version: 3.2.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: 2.2.0
40
+ version: 3.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: spectus
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 4.0.0
47
+ version: 4.0.2
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: 4.0.0
54
+ version: 4.0.2
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -178,7 +178,7 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
- description: Specing framework for Ruby.
181
+ description: Specing framework.
182
182
  email: contact@cyril.email
183
183
  executables: []
184
184
  extensions: []
@@ -187,17 +187,19 @@ files:
187
187
  - LICENSE.md
188
188
  - README.md
189
189
  - lib/fix.rb
190
- - lib/fix/console.rb
191
190
  - lib/fix/doc.rb
192
191
  - lib/fix/dsl.rb
193
- - lib/fix/test.rb
192
+ - lib/fix/matcher.rb
193
+ - lib/fix/requirement.rb
194
+ - lib/fix/run.rb
195
+ - lib/fix/set.rb
194
196
  - lib/kernel.rb
195
197
  homepage: https://fixrb.dev/
196
198
  licenses:
197
199
  - MIT
198
200
  metadata:
199
201
  bug_tracker_uri: https://github.com/fixrb/fix/issues
200
- documentation_uri: https://rubydoc.info/gems/r_spec-clone
202
+ documentation_uri: https://rubydoc.info/gems/fix
201
203
  source_code_uri: https://github.com/fixrb/fix
202
204
  wiki_uri: https://github.com/fixrb/fix/wiki
203
205
  post_install_message:
@@ -218,5 +220,5 @@ requirements: []
218
220
  rubygems_version: 3.1.6
219
221
  signing_key:
220
222
  specification_version: 4
221
- summary: Specing framework for Ruby.
223
+ summary: Specing framework.
222
224
  test_files: []
data/lib/fix/console.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fix
4
- # Send log messages to the console.
5
- module Console
6
- # @param report [::Expresenter::Pass] Passed expectation result presenter.
7
- #
8
- # @see https://github.com/fixrb/expresenter
9
- #
10
- # @return [nil] Add a colored message to `$stdout`.
11
- def self.passed_spec(report)
12
- puts report.colored_string
13
- end
14
-
15
- # @param report [::Expresenter::Fail] Failed expectation result presenter.
16
- #
17
- # @see https://github.com/fixrb/expresenter
18
- #
19
- # @raise [SystemExit] Terminate execution immediately with colored message.
20
- def self.failed_spec(report)
21
- abort report.colored_string
22
- end
23
- end
24
- end
data/lib/fix/test.rb DELETED
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "expresenter/fail"
4
-
5
- require_relative "console"
6
- require_relative "doc"
7
-
8
- module Fix
9
- # Module for testing spec documents.
10
- class Test
11
- attr_reader :contexts
12
-
13
- def initialize(*contexts)
14
- @contexts = contexts
15
- end
16
-
17
- def test(&block)
18
- requirements(&block)
19
- exit(true)
20
- end
21
-
22
- private
23
-
24
- def requirements(&block)
25
- contexts.flat_map do |context|
26
- sandbox = context.new
27
- sandbox.public_methods(false).shuffle.map do |public_method|
28
- definition = sandbox.public_send(public_method)
29
-
30
- report = begin
31
- definition.call do
32
- front_object = instance_eval(&block)
33
-
34
- context.send(:challenges).inject(front_object) do |object, challenge|
35
- challenge.to(object).call
36
- end
37
- end
38
- rescue ::Expresenter::Fail => e
39
- e
40
- end
41
-
42
- if report.passed?
43
- Console.passed_spec report
44
- else
45
- Console.failed_spec report
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end