fix 1.0.0.beta3 → 1.0.0.beta7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d55ef5f2323d4329f5b62fa981130fc4027f72a44aedb4cedc1d60a8f6482a8c
4
- data.tar.gz: 4f19f7567567c70f180a1cc3cfbcbad8f907dd16154a815d1395b1fb22a3c18d
3
+ metadata.gz: 81f6a38942f7037e0250876038e1a715b9b905d5fb2186fb1b3dc3884ebee446
4
+ data.tar.gz: 76f805fc42a2683236ed85efbc888c25fadfaeb2f975177127101e4eb8ae2618
5
5
  SHA512:
6
- metadata.gz: e14033e214c24666156eed3c3c4f29699bb40c4f6404dd1ab1b8c50b626d3db36ae7986cb29cba180ca0aaac1d80f677b6750693870e9932dbe98c51e01964d9
7
- data.tar.gz: bf125f159f6f672f3d799c3c67631949d724d71cbd06137644158ac83d54cb9f9957b5e98f9f6676ab7a1381e0f5e74b773fde659bffe7e95a18d150ea9827a4
6
+ metadata.gz: c0ae0f82dd15aff8f14252631c2c8cf134bd6ce78ba814267f53e7b88a5d84ff2c7d14bb753152f51cea595f46a0284ae58d370e6e50f74fa6c6ec7bb7dcace1
7
+ data.tar.gz: 983e9f29a66982ab2cfef0548b50c064c0b63605acdb0f055da6f35cfd2153f667bf3d58975f6d274b49101d557149336e3a6c465549cfa3658f758903024617
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2020 Cyril Kato
3
+ Copyright (c) 2014-2021 Cyril Kato
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,31 +1,44 @@
1
1
  # Fix
2
2
 
3
- [![Build Status](https://api.travis-ci.org/fixrb/fix.svg?branch=master)][travis]
4
- [![Code Climate](https://codeclimate.com/github/fixrb/fix/badges/gpa.svg)][codeclimate]
5
- [![Gem Version](https://badge.fury.io/rb/fix.svg)][gem]
6
- [![Inline docs](https://inch-ci.org/github/fixrb/fix.svg?branch=master)][inchpages]
7
- [![Documentation](https://img.shields.io/:yard-docs-38c800.svg)][rubydoc]
8
- [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
3
+ [![Home](https://img.shields.io/badge/Home-fixrb.dev-00af8b)](https://fixrb.dev/)
4
+ [![Version](https://img.shields.io/github/v/tag/fixrb/fix?label=Version&logo=github)](https://github.com/fixrb/fix/releases)
5
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/fixrb/fix/main)
6
+ [![CI](https://github.com/fixrb/fix/workflows/CI/badge.svg?branch=main)](https://github.com/fixrb/fix/actions?query=workflow%3Aci+branch%3Amain)
7
+ [![RuboCop](https://github.com/fixrb/fix/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/fix/actions?query=workflow%3Arubocop+branch%3Amain)
8
+ [![License](https://img.shields.io/github/license/fixrb/fix?label=License&logo=github)](https://github.com/fixrb/fix/raw/main/LICENSE.md)
9
9
 
10
- > Specing framework for Ruby.
10
+ ⚠️ This project is still in the experimental phase. May be used at your own risk.
11
+
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.
11
20
 
12
21
  ## Installation
13
22
 
14
23
  Add this line to your application's Gemfile:
15
24
 
16
25
  ```ruby
17
- gem 'fix', '>= 1.0.0.beta3'
26
+ gem "fix", ">= 1.0.0.beta7"
18
27
  ```
19
28
 
20
29
  And then execute:
21
30
 
22
- $ bundle
31
+ ```sh
32
+ bundle
33
+ ```
23
34
 
24
35
  Or install it yourself as:
25
36
 
26
- $ gem install fix --pre
37
+ ```sh
38
+ gem install fix --pre
39
+ ```
27
40
 
28
- ## Usage
41
+ ## Example
29
42
 
30
43
  Given this app:
31
44
 
@@ -33,59 +46,74 @@ Given this app:
33
46
  # examples/duck/app.rb
34
47
  class Duck
35
48
  def walks
36
- 'Klop klop!'
49
+ "Klop klop!"
37
50
  end
38
51
 
39
52
  def swims
40
- 'Swoosh...'
53
+ "Swoosh..."
41
54
  end
42
55
 
43
56
  def quacks
44
- puts 'Quaaaaaack!'
57
+ puts "Quaaaaaack!"
45
58
  end
46
59
  end
47
60
  ```
48
61
 
49
- When you run this:
62
+ And this fixed behavior:
50
63
 
51
64
  ```ruby
52
65
  # examples/duck/fix.rb
53
- require_relative 'app'
54
- require_relative '../../lib/fix'
55
66
 
56
- @bird = Duck.new
67
+ relative "fix"
68
+
69
+ Fix :Duck do
70
+ it SHOULD be_an_instance_of :Duck
57
71
 
58
- Fix(@bird) do
59
72
  on :swims do
60
- it { MUST eql 'Swoosh...' }
73
+ it MUST be_an_instance_of :String
74
+ it MUST eql "Swoosh..."
61
75
  end
62
76
 
63
77
  on :speaks do
64
- it { MUST raise_exception NoMethodError }
78
+ it MUST raise_exception NoMethodError
65
79
  end
66
80
 
67
81
  on :sings do
68
- it { MAY eql '♪... ♫...' }
82
+ it MAY eql "♪... ♫..."
69
83
  end
70
84
  end
71
85
  ```
72
86
 
73
- Then the output should look like this:
87
+ When I run this test:
88
+
89
+ ```ruby
90
+ # examples/duck/test.rb
91
+
92
+ require_relative "app"
93
+ require_relative "fix"
94
+
95
+ Fix[:Duck].test { Duck.new }
96
+ ```
74
97
 
75
98
  ```sh
76
- ruby examples/duck/fix.rb
99
+ ruby examples/duck/test.rb
77
100
  ```
78
101
 
102
+ I should see this output:
103
+
79
104
  ```txt
80
- examples/duck/fix.rb:8: Success: expected to eql "Swoosh...".
81
- examples/duck/fix.rb:12: Success: undefined method `speaks' for #<Duck:0x00007fe3be868ea0>.
82
- examples/duck/fix.rb:16: NoMethodError: undefined method `sings' for #<Duck:0x00007fe3be868ea0>.
105
+ Success: expected #<Duck:0x00007fc55b3c6388> to be an instance of Duck.
106
+ Success: expected to eql "Swoosh...".
107
+ Success: undefined method `speaks' for #<Duck:0x00007fc55b3c46f0>.
108
+ NoMethodError: undefined method `sings' for #<Duck:0x00007fc558aab6d8>.
83
109
  ```
84
110
 
85
111
  ## Contact
86
112
 
87
- * Home page: https://fixrb.dev/
88
- * Source code: https://github.com/fixrb/fix
113
+ * Home page: [https://fixrb.dev/](https://fixrb.dev/)
114
+ * Source code: [https://github.com/fixrb/fix](https://github.com/fixrb/fix)
115
+ * API Doc: [https://rubydoc.info/gems/fix](https://rubydoc.info/gems/fix)
116
+ * Twitter: [https://twitter.com/fix\_rb](https://twitter.com/fix\_rb)
89
117
 
90
118
  ## Versioning
91
119
 
@@ -93,20 +121,13 @@ __Fix__ follows [Semantic Versioning 2.0](https://semver.org/).
93
121
 
94
122
  ## License
95
123
 
96
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
124
+ The [gem](https://rubygems.org/gems/fix) is available as open source under the terms of the [MIT License](https://github.com/fixrb/fix/raw/main/LICENSE.md).
97
125
 
98
126
  ***
99
127
 
100
128
  <p>
101
129
  This project is sponsored by:<br />
102
130
  <a href="https://sashite.com/"><img
103
- src="https://github.com/fixrb/fix/raw/master/img/sashite.png"
131
+ src="https://github.com/fixrb/fix/raw/main/img/sashite.png"
104
132
  alt="Sashite" /></a>
105
133
  </p>
106
-
107
- [travis]: https://travis-ci.org/fixrb/fix
108
- [codeclimate]: https://codeclimate.com/github/fixrb/fix
109
- [gem]: https://rubygems.org/gems/fix
110
- [inchpages]: https://inch-ci.org/github/fixrb/fix
111
- [rubydoc]: https://rubydoc.info/gems/fix/frames
112
- [gitter]: https://gitter.im/fixrb/fix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
data/lib/fix.rb CHANGED
@@ -1,25 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("fix", "set")
4
+
5
+ require_relative "kernel"
6
+
3
7
  # Namespace for the Fix framework.
4
8
  #
9
+ # @api public
5
10
  module Fix
6
- # Specs are built with this method.
11
+ # Test a built specification.
7
12
  #
8
- # @example 42 must be equal to 42
9
- # describe(42) do
10
- # it { MUST equal 42 }
11
- # end
13
+ # @example Run _Answer_ specification against `42`.
14
+ # Fix[:Answer].test(42)
12
15
  #
13
- # @param front_object [BasicObject] The front object.
14
- # @param options [Hash] Some options.
15
- # @param specs [Proc] The set of specs.
16
+ # @param name [String, Symbol] The name of the specification document.
16
17
  #
17
- # @raise [SystemExit] The result of the test.
18
- def self.describe(front_object, **lets, &block)
19
- c = Context.new(front_object, ::Defi.send(:itself), **lets)
20
- c.instance_eval(&block)
18
+ # @return [::Fix::Test] The specification document.
19
+ def self.[](name)
20
+ ::Fix::Set.load(name)
21
21
  end
22
22
  end
23
-
24
- require_relative 'kernel'
25
- require_relative File.join('fix', 'context')
data/lib/fix/doc.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fix
4
+ # Module for storing spec documents.
5
+ #
6
+ # @api private
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
23
+ end
24
+ end
data/lib/fix/dsl.rb ADDED
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "defi"
4
+
5
+ require_relative "matcher"
6
+ require_relative "requirement"
7
+
8
+ module Fix
9
+ # Abstract class for handling the domain-specific language.
10
+ #
11
+ # @api private
12
+ class Dsl
13
+ extend Matcher
14
+ extend Requirement
15
+
16
+ # Sets a user-defined property.
17
+ #
18
+ # @example
19
+ # require "fix"
20
+ #
21
+ # Fix do
22
+ # let(:name) { "Bob" }
23
+ # end
24
+ #
25
+ # @param name [String, Symbol] The name of the property.
26
+ # @param block [Proc] The content of the method to define.
27
+ #
28
+ # @return [Symbol] A private method that define the block content.
29
+ #
30
+ # @api public
31
+ def self.let(name, &block)
32
+ private define_method(name, &block)
33
+ end
34
+
35
+ # Defines an example group with user-defined properties that describes a
36
+ # unit to be tested.
37
+ #
38
+ # @example
39
+ # require "fix"
40
+ #
41
+ # Fix do
42
+ # with :password, "secret" do
43
+ # it MUST be true
44
+ # end
45
+ # end
46
+ #
47
+ # @param kwargs [Hash] The list of propreties.
48
+ # @param block [Proc] The block to define the specs.
49
+ #
50
+ # @api public
51
+ def self.with(**kwargs, &block)
52
+ klass = ::Class.new(self)
53
+ klass.const_get(:CONTEXTS) << klass
54
+ kwargs.each { |name, value| klass.let(name) { value } }
55
+ klass.instance_eval(&block)
56
+ klass
57
+ end
58
+
59
+ # Defines an example group that describes a unit to be tested.
60
+ #
61
+ # @example
62
+ # require "fix"
63
+ #
64
+ # Fix do
65
+ # on :+, 2 do
66
+ # it MUST be 42
67
+ # end
68
+ # end
69
+ #
70
+ # @param method_name [String, Symbol] The method to send to the subject.
71
+ # @param block [Proc] The block to define the specs.
72
+ #
73
+ # @api public
74
+ def self.on(method_name, *args, **kwargs, &block)
75
+ klass = ::Class.new(self)
76
+ klass.const_get(:CONTEXTS) << klass
77
+
78
+ const_set("Child#{block.object_id}", klass)
79
+
80
+ klass.define_singleton_method(:challenges) do
81
+ challenge = ::Defi.send(method_name, *args, **kwargs)
82
+ super() + [challenge]
83
+ end
84
+
85
+ klass.instance_eval(&block)
86
+ klass
87
+ end
88
+
89
+ # Defines a concrete spec definition.
90
+ #
91
+ # @example
92
+ # require "fix"
93
+ #
94
+ # Fix { it MUST be 42 }
95
+ #
96
+ # @api public
97
+ def self.it(requirement)
98
+ location = caller_locations(1, 1).fetch(0)
99
+ location = [location.path, location.lineno].join(":")
100
+
101
+ define_method("test_#{requirement.object_id}") do
102
+ [location, requirement, self.class.challenges]
103
+ end
104
+ end
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.
109
+ def self.challenges
110
+ []
111
+ end
112
+ end
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
@@ -1,9 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative File.join("fix", "doc")
4
+ require_relative File.join("fix", "dsl")
5
+ require_relative File.join("fix", "set")
6
+
7
+ # The Kernel module.
3
8
  module Kernel
4
9
  # rubocop:disable Naming/MethodName
5
- def Fix(subject = nil, **lets, &block)
6
- ::Fix.describe(subject, **lets, &block)
10
+
11
+ # Specifications are built with this method.
12
+ #
13
+ # @example Require an answer equal to 42.
14
+ # # The spec
15
+ # Fix :Answer do
16
+ # it MUST equal 42
17
+ # end
18
+ #
19
+ # # A test
20
+ # Fix[:Answer].test { 42 }
21
+ #
22
+ # @param name [String, Symbol] The name of the specification document.
23
+ # @param block [Proc] The specifications.
24
+ #
25
+ # @return [#test] The collection of specifications.
26
+ #
27
+ # @api public
28
+ def Fix(name = nil, &block)
29
+ klass = ::Class.new(::Fix::Dsl)
30
+ klass.const_set(:CONTEXTS, [klass])
31
+ klass.instance_eval(&block)
32
+ ::Fix::Doc.const_set(name, klass) unless name.nil?
33
+ ::Fix::Set.new(*klass.const_get(:CONTEXTS))
7
34
  end
8
35
  # rubocop:enable Naming/MethodName
9
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.beta3
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: 2020-01-15 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
@@ -16,70 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.0.4
19
+ version: 2.0.5
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: 2.0.4
26
+ version: 2.0.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: matchi
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: spectus
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: 3.1.2
47
+ version: 4.0.2
34
48
  type: :runtime
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: 3.1.2
54
+ version: 4.0.2
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: '2.1'
61
+ version: '0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '2.1'
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - "~>"
73
+ - - ">="
60
74
  - !ruby/object:Gem::Version
61
- version: '13.0'
75
+ version: '0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - "~>"
80
+ - - ">="
67
81
  - !ruby/object:Gem::Version
68
- version: '13.0'
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: rubocop
84
+ name: rubocop-md
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - "~>"
87
+ - - ">="
74
88
  - !ruby/object:Gem::Version
75
- version: '0.79'
89
+ version: '0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - "~>"
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
- version: '0.79'
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop-performance
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,35 +108,77 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
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'
97
153
  - !ruby/object:Gem::Dependency
98
154
  name: simplecov
99
155
  requirement: !ruby/object:Gem::Requirement
100
156
  requirements:
101
- - - "~>"
157
+ - - ">="
102
158
  - !ruby/object:Gem::Version
103
- version: '0.17'
159
+ version: '0'
104
160
  type: :development
105
161
  prerelease: false
106
162
  version_requirements: !ruby/object:Gem::Requirement
107
163
  requirements:
108
- - - "~>"
164
+ - - ">="
109
165
  - !ruby/object:Gem::Version
110
- version: '0.17'
166
+ version: '0'
111
167
  - !ruby/object:Gem::Dependency
112
168
  name: yard
113
169
  requirement: !ruby/object:Gem::Requirement
114
170
  requirements:
115
- - - "~>"
171
+ - - ">="
116
172
  - !ruby/object:Gem::Version
117
- version: '0.9'
173
+ version: '0'
118
174
  type: :development
119
175
  prerelease: false
120
176
  version_requirements: !ruby/object:Gem::Requirement
121
177
  requirements:
122
- - - "~>"
178
+ - - ">="
123
179
  - !ruby/object:Gem::Version
124
- version: '0.9'
125
- description: Specing framework for Ruby.
180
+ version: '0'
181
+ description: Specing framework.
126
182
  email: contact@cyril.email
127
183
  executables: []
128
184
  extensions: []
@@ -131,16 +187,21 @@ files:
131
187
  - LICENSE.md
132
188
  - README.md
133
189
  - lib/fix.rb
134
- - lib/fix/context.rb
135
- - lib/fix/expectation_result_not_found_error.rb
136
- - lib/fix/it.rb
137
- - lib/fix/suspicious_success_error.rb
190
+ - lib/fix/doc.rb
191
+ - lib/fix/dsl.rb
192
+ - lib/fix/matcher.rb
193
+ - lib/fix/requirement.rb
194
+ - lib/fix/run.rb
195
+ - lib/fix/set.rb
138
196
  - lib/kernel.rb
139
197
  homepage: https://fixrb.dev/
140
198
  licenses:
141
199
  - MIT
142
200
  metadata:
201
+ bug_tracker_uri: https://github.com/fixrb/fix/issues
202
+ documentation_uri: https://rubydoc.info/gems/fix
143
203
  source_code_uri: https://github.com/fixrb/fix
204
+ wiki_uri: https://github.com/fixrb/fix/wiki
144
205
  post_install_message:
145
206
  rdoc_options: []
146
207
  require_paths:
@@ -149,15 +210,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
210
  requirements:
150
211
  - - ">="
151
212
  - !ruby/object:Gem::Version
152
- version: 2.3.0
213
+ version: 2.7.0
153
214
  required_rubygems_version: !ruby/object:Gem::Requirement
154
215
  requirements:
155
216
  - - ">"
156
217
  - !ruby/object:Gem::Version
157
218
  version: 1.3.1
158
219
  requirements: []
159
- rubygems_version: 3.1.2
220
+ rubygems_version: 3.1.6
160
221
  signing_key:
161
222
  specification_version: 4
162
- summary: Specing framework for Ruby.
223
+ summary: Specing framework.
163
224
  test_files: []
data/lib/fix/context.rb DELETED
@@ -1,147 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'aw'
4
- require 'defi'
5
-
6
- module Fix
7
- # Wraps the target of challenge.
8
- class Context
9
- RESERVED_KEYWORDS = %i[
10
- alias
11
- and
12
- begin
13
- break
14
- case
15
- catch
16
- class
17
- def
18
- defined?
19
- do
20
- else
21
- elsif
22
- end
23
- ensure
24
- fail
25
- false
26
- for
27
- if
28
- in
29
- module
30
- next
31
- nil
32
- not
33
- or
34
- raise
35
- redo
36
- rescue
37
- retry
38
- return
39
- self
40
- super
41
- then
42
- throw
43
- true
44
- undef
45
- unless
46
- until
47
- when
48
- while
49
- yield
50
- ].freeze
51
-
52
- attr_reader :callable
53
-
54
- def initialize(subject, challenge, before_hooks_counter = 0, *hooks, **lets)
55
- @subject = subject
56
- @callable = challenge.to(subject)
57
- @before_hooks = hooks[0, before_hooks_counter]
58
- @after_hooks = hooks[before_hooks_counter..-1]
59
- @lets = lets
60
- end
61
-
62
- def before(&block)
63
- @before_hooks << block
64
- end
65
-
66
- def after(&block)
67
- @after_hooks << block
68
- end
69
-
70
- def let(name, &block)
71
- raise ::TypeError, "expected a Symbol, got #{name.class}" unless name.is_a?(::Symbol)
72
- raise ::NameError, "wrong method name `#{name}'" unless name.match(/\A[a-z][a-z0-9_]+[?!]?\z/)
73
- raise ::NameError, "reserved keyword name `#{name}'" if RESERVED_KEYWORDS.include?(name)
74
- raise ::NameError, "reserved method name `#{name}'" if respond_to?(name, true) && !@lets.key?(name)
75
-
76
- @lets.update(name => block.call)
77
- rescue ::SystemExit => e
78
- raise SuspiciousSuccessError, "attempt `#{name}' to bypass the tests" if e.success?
79
- raise e
80
- end
81
-
82
- def let!(name, &block)
83
- raise ::TypeError, "expected a Symbol, got #{name.class}" unless name.is_a?(::Symbol)
84
- raise ::NameError, "wrong method name `#{name}'" unless name.match(/\A[a-z][a-z0-9_]+[?!]?\z/)
85
- raise ::NameError, "reserved keyword name `#{name}'" if RESERVED_KEYWORDS.include?(name)
86
- raise ::NameError, "reserved method name `#{name}'" if respond_to?(name, true) && !@lets.key?(name)
87
-
88
- @lets.update(name => ::Aw.fork! { block.call })
89
- rescue ::SystemExit => e
90
- raise SuspiciousSuccessError, "attempt `#{name}' to bypass the tests" if e.success?
91
- raise e
92
- end
93
-
94
- # Verify the expectation.
95
- #
96
- # @param block [Proc] A spec to compare against the computed actual value.
97
- #
98
- # @return [::Spectus::Result::Pass, ::Spectus::Result::Fail] Pass or fail.
99
- def it(_message = nil, &block)
100
- print "#{block.source_location.join(':')}: "
101
- i = It.new(callable, **@lets)
102
- @before_hooks.each { |hook| i.instance_eval(&hook) }
103
- result = i.instance_eval(&block)
104
- puts result.colored_string
105
- rescue ::Spectus::Result::Fail => result
106
- abort result.colored_string
107
- ensure
108
- @after_hooks.each { |hook| i.instance_eval(&hook) }
109
- raise ExpectationResultNotFoundError, result.class.inspect unless result.is_a?(::Spectus::Result::Common)
110
- end
111
-
112
- def on(name, *args, **options, &block)
113
- if callable.raised?
114
- actual = callable
115
- challenge = ::Defi.send(:call)
116
- else
117
- actual = callable.object
118
- challenge = ::Defi.send(name, *args, **options)
119
- end
120
-
121
- o = Context.new(actual, challenge, @before_hooks.length, *@before_hooks + @after_hooks, **@lets)
122
- o.instance_eval(&block)
123
- end
124
-
125
- def with(_message = nil, **new_lets, &block)
126
- actual = callable.object
127
- challenge = ::Defi.send(:itself)
128
-
129
- c = Context.new(actual, challenge, @before_hooks.length, *@before_hooks + @after_hooks, **@lets.merge(new_lets))
130
- c.instance_eval(&block)
131
- end
132
-
133
- private
134
-
135
- def method_missing(name, *args, &block)
136
- @lets.fetch(name) { super }
137
- end
138
-
139
- def respond_to_missing?(name, include_private = false)
140
- @lets.key?(name) || super
141
- end
142
- end
143
- end
144
-
145
- require_relative 'it'
146
- require_relative 'expectation_result_not_found_error'
147
- require_relative 'suspicious_success_error'
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fix
4
- class ExpectationResultNotFoundError < ::RuntimeError; end
5
- end
data/lib/fix/it.rb DELETED
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spectus/expectation_target'
4
- require 'matchi/helper'
5
-
6
- module Fix
7
- # Wraps the target of an expectation.
8
- class It < ::Spectus::ExpectationTarget
9
- include ::Matchi::Helper
10
-
11
- # Create a new expection target
12
- #
13
- # @param callable [#call] The object to test.
14
- def initialize(callable, **lets)
15
- raise unless callable.respond_to?(:call)
16
-
17
- @callable = callable
18
- @lets = lets
19
- end
20
-
21
- private
22
-
23
- def method_missing(name, *args, &block)
24
- @lets.fetch(name) { super }
25
- end
26
-
27
- def respond_to_missing?(name, include_private = false)
28
- @lets.key?(name) || super
29
- end
30
- end
31
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fix
4
- class SuspiciousSuccessError < ::StandardError; end
5
- end