fix 1.0.0.beta3 → 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: 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