fix 1.0.0.beta12 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.md +2 -2
- data/README.md +183 -210
- data/lib/fix/dsl.rb +149 -97
- data/lib/fix/environment.rb +25 -0
- data/lib/fix/level.rb +40 -0
- data/lib/fix/matcher.rb +92 -41
- data/lib/fix/node.rb +32 -0
- data/lib/fix/result.rb +36 -0
- data/lib/fix/spec.rb +167 -0
- data/lib/fix.rb +42 -12
- data/lib/kernel.rb +10 -31
- metadata +35 -19
- data/lib/fix/doc.rb +0 -24
- data/lib/fix/requirement.rb +0 -87
- data/lib/fix/run.rb +0 -55
- data/lib/fix/set.rb +0 -67
data/lib/fix/spec.rb
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "aw"
|
|
4
|
+
require "defi"
|
|
5
|
+
|
|
6
|
+
require_relative "dsl"
|
|
7
|
+
require_relative "result"
|
|
8
|
+
|
|
9
|
+
module Fix
|
|
10
|
+
# The lib directories of fix and of the gems it runs, each found by a
|
|
11
|
+
# method or a module its root file defines, not by the name of a file,
|
|
12
|
+
# which another file could bear: their frames are left out of a backtrace
|
|
13
|
+
# printed by {Spec#test}.
|
|
14
|
+
LIBRARIES = ([
|
|
15
|
+
::Aw.method(:fork!).source_location,
|
|
16
|
+
::Object.const_source_location("Defi"),
|
|
17
|
+
::Object.const_source_location("Matchi"),
|
|
18
|
+
::Spectus.method(:must).source_location
|
|
19
|
+
].map { |location| "#{::File.dirname(location.fetch(0))}/" } << "#{::File.expand_path("..", __dir__)}/").freeze
|
|
20
|
+
private_constant :LIBRARIES
|
|
21
|
+
|
|
22
|
+
# A specification: its name and its contexts, built once by {Fix.spec} and
|
|
23
|
+
# never changed. Tested against a subject with {#run} or {#test}, it is
|
|
24
|
+
# also a matcher: {#match?} tests a value, as `it MUST fix(:Name)` does.
|
|
25
|
+
#
|
|
26
|
+
# @!attribute [r] name
|
|
27
|
+
# @return [Symbol, nil] the name, nil for an anonymous specification
|
|
28
|
+
# @!attribute [r] nodes
|
|
29
|
+
# @return [Array<Fix::Node>] the contexts, each before its children
|
|
30
|
+
# @api private
|
|
31
|
+
Spec = ::Data.define(:name, :nodes) do
|
|
32
|
+
# Tests a subject against every expectation, each context in a child
|
|
33
|
+
# process, and gives the results. The block builds the subject: it runs
|
|
34
|
+
# once per expectation, where the lets live.
|
|
35
|
+
#
|
|
36
|
+
# @param fork [Boolean] whether each context runs in a child process;
|
|
37
|
+
# `false` for a debugger only
|
|
38
|
+
# @yieldreturn [Object] the subject
|
|
39
|
+
# @return [Array<Fix::Result>] in the order of the contexts, then of
|
|
40
|
+
# their expectations
|
|
41
|
+
# @raise [ArgumentError] without a block
|
|
42
|
+
# @raise [NotImplementedError] when this Ruby cannot fork
|
|
43
|
+
# @raise [Aw::ChildError] when a child died or delivered no result
|
|
44
|
+
# @raise [TypeError] when the `to_s` of a matcher is not a String; what a
|
|
45
|
+
# child raised outside an expectation is raised here again
|
|
46
|
+
def run(fork: true, &subject)
|
|
47
|
+
raise ::ArgumentError, "a block must be given" unless subject
|
|
48
|
+
|
|
49
|
+
nodes.flat_map { |node| results(node, fork, subject) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Like {#run}, writing each result on the standard output as its
|
|
53
|
+
# context ends, one line each, with the backtrace of an error below.
|
|
54
|
+
#
|
|
55
|
+
# @param fork [Boolean] as for {#run}
|
|
56
|
+
# @yieldreturn [Object] the subject
|
|
57
|
+
# @return [Boolean] whether every expectation is met
|
|
58
|
+
# @raise (see #run)
|
|
59
|
+
def test(fork: true, &subject) # rubocop:disable Naming/PredicateMethod
|
|
60
|
+
raise ::ArgumentError, "a block must be given" unless subject
|
|
61
|
+
|
|
62
|
+
nodes.map do |node|
|
|
63
|
+
results(node, fork, subject).each { |result| $stdout.puts(lines(result)) }.all?(&:passed?)
|
|
64
|
+
end.all?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Tests a value, as a matcher does: the block is called once, and its
|
|
68
|
+
# value is the subject of every expectation.
|
|
69
|
+
#
|
|
70
|
+
# @yieldreturn [Object] the value
|
|
71
|
+
# @return [Boolean] whether every expectation is met
|
|
72
|
+
# @raise (see #run)
|
|
73
|
+
def match?
|
|
74
|
+
raise ::ArgumentError, "a block must be given" unless block_given?
|
|
75
|
+
|
|
76
|
+
value = yield
|
|
77
|
+
run { value }.all?(&:passed?)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [String] `fix NAME`, or `fix an anonymous specification`
|
|
81
|
+
def to_s
|
|
82
|
+
"fix #{name || "an anonymous specification"}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
alias_method :inspect, :to_s
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# The results of a context, from a child process or not.
|
|
90
|
+
def results(node, fork, subject)
|
|
91
|
+
return [] if node.expectations.empty?
|
|
92
|
+
|
|
93
|
+
work = -> { examine(node, subject) }
|
|
94
|
+
spectus = fork ? ::Aw.fork!(&work) : work.call
|
|
95
|
+
node.expectations.zip(spectus).map do |expectation, result|
|
|
96
|
+
Result.new(location: expectation.location, chain: node.chain, result:)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The results of the expectations of a context, in the process that works
|
|
101
|
+
# on them. A process the subject forked and let run on, instead of ending
|
|
102
|
+
# it with exit!, is not that one: it leaves here without a word.
|
|
103
|
+
def examine(node, subject)
|
|
104
|
+
pid = ::Process.pid
|
|
105
|
+
node.expectations.map do |expectation|
|
|
106
|
+
result = judge(node, expectation, subject)
|
|
107
|
+
::Process.exit! unless ::Process.pid == pid
|
|
108
|
+
|
|
109
|
+
result
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# One expectation, in a new environment: the subject is built, the
|
|
114
|
+
# chain is sent, and the requirement judges what came out. A method of
|
|
115
|
+
# the chain missing on its receiver makes a MAY an info, as a method
|
|
116
|
+
# missing on the actual value does for Spectus, when the error that
|
|
117
|
+
# came out is a NoMethodError by its class name: an exception of a
|
|
118
|
+
# subclass, which Ruby does not raise for a missing method, stays an
|
|
119
|
+
# error.
|
|
120
|
+
def judge(node, expectation, subject)
|
|
121
|
+
environment = Environment.new(node.lets)
|
|
122
|
+
missing = false
|
|
123
|
+
result = expectation.requirement.call do
|
|
124
|
+
value, missing = actual(node, environment, subject)
|
|
125
|
+
value.call
|
|
126
|
+
end
|
|
127
|
+
omission = missing && expectation.requirement.level.equal?(:MAY) && result.error_class == "NoMethodError"
|
|
128
|
+
omission ? result.with(outcome: :info) : result
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The subject through the chain: a value, and whether the chain stopped
|
|
132
|
+
# on a method missing on its receiver.
|
|
133
|
+
def actual(node, environment, subject)
|
|
134
|
+
object = environment.instance_exec(&subject)
|
|
135
|
+
node.challenges.each do |challenge|
|
|
136
|
+
value = challenge.to(object)
|
|
137
|
+
return [value, missing?(value.object, object, challenge)] if value.raised?
|
|
138
|
+
|
|
139
|
+
object = value.object
|
|
140
|
+
end
|
|
141
|
+
[::Defi::Value.new(object, false), false]
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Whether an exception is a NoMethodError for the method of the
|
|
145
|
+
# challenge on the very object it was sent to, read as NameError and
|
|
146
|
+
# BasicObject do, whatever the objects say. A NoMethodError built by
|
|
147
|
+
# hand without a receiver raises ArgumentError when asked for it: it
|
|
148
|
+
# names no object, so the method is not missing on one.
|
|
149
|
+
def missing?(exception, object, challenge)
|
|
150
|
+
return false unless ::NoMethodError === exception
|
|
151
|
+
|
|
152
|
+
receiver = ::NameError.instance_method(:receiver).bind_call(exception)
|
|
153
|
+
::BasicObject.instance_method(:equal?).bind_call(receiver, object) &&
|
|
154
|
+
challenge.name.equal?(::NameError.instance_method(:name).bind_call(exception))
|
|
155
|
+
rescue ::ArgumentError
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The line of a result, then the frames of an error that are not ours,
|
|
160
|
+
# each on one line too.
|
|
161
|
+
def lines(result)
|
|
162
|
+
frames = result.outcome == :error ? (result.result.backtrace || []) : []
|
|
163
|
+
frames = frames.reject { |frame| frame.start_with?(*LIBRARIES) }
|
|
164
|
+
[result.to_s] + frames.map { |frame| " #{frame.gsub(/\R/) { "\\n" }}" }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
data/lib/fix.rb
CHANGED
|
@@ -1,22 +1,52 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
|
|
3
|
+
require_relative "fix/spec"
|
|
5
4
|
require_relative "kernel"
|
|
6
5
|
|
|
7
|
-
#
|
|
6
|
+
# Specifications with the MUST, SHOULD and MAY of RFC 2119. A specification
|
|
7
|
+
# says what a value must, should or may do, in a document free of logic;
|
|
8
|
+
# any subject is then tested against it, each context in a process of its
|
|
9
|
+
# own.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# Fix :Duck do
|
|
13
|
+
# on(:swims) { it MUST eq "Swoosh..." }
|
|
14
|
+
# on(:sings) { it MAY eq "♪... ♫..." }
|
|
15
|
+
# end
|
|
8
16
|
#
|
|
9
|
-
#
|
|
17
|
+
# Fix[:Duck].test { Duck.new } # => true
|
|
10
18
|
module Fix
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# @param name [String, Symbol] The constant name of the specifications.
|
|
19
|
+
@specs = {}.freeze
|
|
20
|
+
|
|
21
|
+
# Builds a specification, and registers it under its name once it is
|
|
22
|
+
# built: a block that raises registers nothing.
|
|
17
23
|
#
|
|
18
|
-
# @
|
|
24
|
+
# @param name [Symbol, nil] the name, or nil for an anonymous specification
|
|
25
|
+
# @yield the document, written with {Fix::Dsl}
|
|
26
|
+
# @return [Fix::Spec]
|
|
27
|
+
# @raise [ArgumentError] without a block, with a name that is not a Symbol
|
|
28
|
+
# or that is already registered, or for a mistake in a word of the
|
|
29
|
+
# document
|
|
30
|
+
# @raise [NameError] for a let named in an expectation
|
|
31
|
+
# @raise [KeyError] for a `fix(:Name)` not registered yet
|
|
32
|
+
def self.spec(name = nil, &block)
|
|
33
|
+
raise ::ArgumentError, "a block must be given" unless block
|
|
34
|
+
raise ::ArgumentError, "the name must be a Symbol" unless name.nil? || ::Symbol === name
|
|
35
|
+
|
|
36
|
+
spec = Spec.new(name:, nodes: Dsl.build(&block))
|
|
37
|
+
return spec if name.nil?
|
|
38
|
+
raise ::ArgumentError, "#{name.inspect} is already a specification" if @specs.key?(name)
|
|
39
|
+
|
|
40
|
+
@specs = @specs.merge(name => spec).freeze
|
|
41
|
+
spec
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param name [Symbol] the name of a registered specification
|
|
45
|
+
# @return [Fix::Spec]
|
|
46
|
+
# @raise [KeyError] when no specification has that name
|
|
19
47
|
def self.[](name)
|
|
20
|
-
|
|
48
|
+
@specs.fetch(name) do
|
|
49
|
+
raise ::KeyError.new("no specification named #{name.inspect}", receiver: @specs, key: name)
|
|
50
|
+
end
|
|
21
51
|
end
|
|
22
52
|
end
|
data/lib/kernel.rb
CHANGED
|
@@ -1,38 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require_relative File.join("fix", "set")
|
|
6
|
-
|
|
7
|
-
# The Kernel module.
|
|
3
|
+
# The `Fix` method, private: callable at the top level of a file, not on an
|
|
4
|
+
# object.
|
|
8
5
|
module Kernel
|
|
9
|
-
|
|
6
|
+
private
|
|
10
7
|
|
|
11
|
-
#
|
|
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 }
|
|
8
|
+
# Builds a specification: `Fix :Name do … end`, or `Fix do … end` for an
|
|
9
|
+
# anonymous one. See {Fix.spec}.
|
|
21
10
|
#
|
|
22
|
-
# @param name [
|
|
23
|
-
# @yield
|
|
24
|
-
# @
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
# @api public
|
|
29
|
-
def Fix(name = nil, &)
|
|
30
|
-
klass = ::Class.new(::Fix::Dsl)
|
|
31
|
-
klass.const_set(:CONTEXTS, [klass])
|
|
32
|
-
klass.instance_eval(&)
|
|
33
|
-
::Fix::Doc.const_set(name, klass) unless name.nil?
|
|
34
|
-
::Fix::Set.new(*klass.const_get(:CONTEXTS))
|
|
11
|
+
# @param name [Symbol, nil] the name
|
|
12
|
+
# @yield the document, written with {Fix::Dsl}
|
|
13
|
+
# @return [Fix::Spec]
|
|
14
|
+
def Fix(name = nil, &) # rubocop:disable Naming/MethodName
|
|
15
|
+
::Fix.spec(name, &)
|
|
35
16
|
end
|
|
36
|
-
|
|
37
|
-
# rubocop:enable Naming/MethodName
|
|
38
17
|
end
|
metadata
CHANGED
|
@@ -1,57 +1,73 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fix
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: aw
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: defi
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
15
29
|
requirements:
|
|
16
30
|
- - "~>"
|
|
17
31
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
32
|
+
version: '4.0'
|
|
19
33
|
type: :runtime
|
|
20
34
|
prerelease: false
|
|
21
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
36
|
requirements:
|
|
23
37
|
- - "~>"
|
|
24
38
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
39
|
+
version: '4.0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: matchi
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
29
43
|
requirements:
|
|
30
44
|
- - "~>"
|
|
31
45
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
46
|
+
version: '6.0'
|
|
33
47
|
type: :runtime
|
|
34
48
|
prerelease: false
|
|
35
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
50
|
requirements:
|
|
37
51
|
- - "~>"
|
|
38
52
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
53
|
+
version: '6.0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: spectus
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
43
57
|
requirements:
|
|
44
58
|
- - "~>"
|
|
45
59
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
60
|
+
version: '6.0'
|
|
47
61
|
type: :runtime
|
|
48
62
|
prerelease: false
|
|
49
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
64
|
requirements:
|
|
51
65
|
- - "~>"
|
|
52
66
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
54
|
-
description:
|
|
67
|
+
version: '6.0'
|
|
68
|
+
description: A specification says what a subject MUST, SHOULD or MAY do, and nothing
|
|
69
|
+
about how it is made. Any subject is then tested against it, each context in a child
|
|
70
|
+
process, and every expectation gets a result.
|
|
55
71
|
email: contact@cyril.email
|
|
56
72
|
executables: []
|
|
57
73
|
extensions: []
|
|
@@ -60,22 +76,22 @@ files:
|
|
|
60
76
|
- LICENSE.md
|
|
61
77
|
- README.md
|
|
62
78
|
- lib/fix.rb
|
|
63
|
-
- lib/fix/doc.rb
|
|
64
79
|
- lib/fix/dsl.rb
|
|
80
|
+
- lib/fix/environment.rb
|
|
81
|
+
- lib/fix/level.rb
|
|
65
82
|
- lib/fix/matcher.rb
|
|
66
|
-
- lib/fix/
|
|
67
|
-
- lib/fix/
|
|
68
|
-
- lib/fix/
|
|
83
|
+
- lib/fix/node.rb
|
|
84
|
+
- lib/fix/result.rb
|
|
85
|
+
- lib/fix/spec.rb
|
|
69
86
|
- lib/kernel.rb
|
|
70
|
-
homepage: https://fixrb.dev/
|
|
87
|
+
homepage: https://fixrb.dev/gems/fix/
|
|
71
88
|
licenses:
|
|
72
89
|
- MIT
|
|
73
90
|
metadata:
|
|
74
91
|
bug_tracker_uri: https://github.com/fixrb/fix/issues
|
|
75
92
|
documentation_uri: https://rubydoc.info/gems/fix
|
|
76
|
-
source_code_uri: https://github.com/fixrb/fix
|
|
77
|
-
wiki_uri: https://github.com/fixrb/fix/wiki
|
|
78
93
|
rubygems_mfa_required: 'true'
|
|
94
|
+
source_code_uri: https://github.com/fixrb/fix
|
|
79
95
|
rdoc_options: []
|
|
80
96
|
require_paths:
|
|
81
97
|
- lib
|
|
@@ -83,14 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
83
99
|
requirements:
|
|
84
100
|
- - ">="
|
|
85
101
|
- !ruby/object:Gem::Version
|
|
86
|
-
version: 3.
|
|
102
|
+
version: 3.3.0
|
|
87
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
104
|
requirements:
|
|
89
105
|
- - ">="
|
|
90
106
|
- !ruby/object:Gem::Version
|
|
91
107
|
version: '0'
|
|
92
108
|
requirements: []
|
|
93
|
-
rubygems_version:
|
|
109
|
+
rubygems_version: 4.0.23
|
|
94
110
|
specification_version: 4
|
|
95
|
-
summary:
|
|
111
|
+
summary: Specifications in the words of RFC 2119, tested against a subject
|
|
96
112
|
test_files: []
|
data/lib/fix/doc.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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 constant name of the specifications.
|
|
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/requirement.rb
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
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 [#match?] 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(negate: false, matcher:)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# This method mean that the definition is an absolute prohibition of the specification.
|
|
27
|
-
#
|
|
28
|
-
# @param matcher [#match?] The matcher.
|
|
29
|
-
#
|
|
30
|
-
# @return [Requirement::Required] An absolute prohibition level instance.
|
|
31
|
-
#
|
|
32
|
-
# @api public
|
|
33
|
-
def MUST_NOT(matcher)
|
|
34
|
-
::Spectus::Requirement::Required.new(negate: true, matcher:)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# This method mean that there may exist valid reasons in particular
|
|
38
|
-
# circumstances to ignore a particular item, but the full implications must be
|
|
39
|
-
# understood and carefully weighed before choosing a different course.
|
|
40
|
-
#
|
|
41
|
-
# @param matcher [#match?] The matcher.
|
|
42
|
-
#
|
|
43
|
-
# @return [Requirement::Recommended] A recommended requirement level instance.
|
|
44
|
-
#
|
|
45
|
-
# @api public
|
|
46
|
-
def SHOULD(matcher)
|
|
47
|
-
::Spectus::Requirement::Recommended.new(negate: false, matcher:)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# This method mean that there may exist valid reasons in particular
|
|
51
|
-
# circumstances when the particular behavior is acceptable or even useful, but
|
|
52
|
-
# the full implications should be understood and the case carefully weighed
|
|
53
|
-
# before implementing any behavior described with this label.
|
|
54
|
-
#
|
|
55
|
-
# @param matcher [#match?] The matcher.
|
|
56
|
-
#
|
|
57
|
-
# @return [Requirement::Recommended] A not recommended requirement level
|
|
58
|
-
# instance.
|
|
59
|
-
#
|
|
60
|
-
# @api public
|
|
61
|
-
def SHOULD_NOT(matcher)
|
|
62
|
-
::Spectus::Requirement::Recommended.new(negate: true, matcher:)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# This method mean that an item is truly optional.
|
|
66
|
-
# One vendor may choose to include the item because a particular marketplace
|
|
67
|
-
# requires it or because the vendor feels that it enhances the product while
|
|
68
|
-
# another vendor may omit the same item. An implementation which does not
|
|
69
|
-
# include a particular option must be prepared to interoperate with another
|
|
70
|
-
# implementation which does include the option, though perhaps with reduced
|
|
71
|
-
# functionality. In the same vein an implementation which does include a
|
|
72
|
-
# particular option must be prepared to interoperate with another
|
|
73
|
-
# implementation which does not include the option (except, of course, for the
|
|
74
|
-
# feature the option provides).
|
|
75
|
-
#
|
|
76
|
-
# @param matcher [#match?] The matcher.
|
|
77
|
-
#
|
|
78
|
-
# @return [Requirement::Optional] An optional requirement level instance.
|
|
79
|
-
#
|
|
80
|
-
# @api public
|
|
81
|
-
def MAY(matcher)
|
|
82
|
-
::Spectus::Requirement::Optional.new(negate: false, matcher:)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# rubocop:enable Naming/MethodName
|
|
86
|
-
end
|
|
87
|
-
end
|
data/lib/fix/run.rb
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
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::Method>] 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::Method>] 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 test(&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
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "English"
|
|
4
|
-
|
|
5
|
-
require_relative "doc"
|
|
6
|
-
require_relative "run"
|
|
7
|
-
|
|
8
|
-
module Fix
|
|
9
|
-
# Collection of specifications.
|
|
10
|
-
#
|
|
11
|
-
# @api private
|
|
12
|
-
class Set
|
|
13
|
-
# @return [Array] A list of specifications.
|
|
14
|
-
attr_reader :specs
|
|
15
|
-
|
|
16
|
-
# Load specifications from a constant name.
|
|
17
|
-
#
|
|
18
|
-
# @param name [String, Symbol] The constant name of the specifications.
|
|
19
|
-
# @return [Set] A new Set instance containing the loaded specifications.
|
|
20
|
-
#
|
|
21
|
-
# @api public
|
|
22
|
-
def self.load(name)
|
|
23
|
-
new(*Doc.fetch(name))
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Initialize a new Set with given contexts.
|
|
27
|
-
#
|
|
28
|
-
# @param contexts [Array<::Fix::Dsl>] The list of contexts document.
|
|
29
|
-
def initialize(*contexts)
|
|
30
|
-
@specs = Doc.specs(*contexts).shuffle
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Run the test suite against the provided subject.
|
|
34
|
-
#
|
|
35
|
-
# @yield The block of code to be tested
|
|
36
|
-
# @yieldreturn [Object] The result of the code being tested
|
|
37
|
-
# @return [Boolean] true if all tests pass, exits with false otherwise
|
|
38
|
-
# @raise [::SystemExit] The test set failed!
|
|
39
|
-
#
|
|
40
|
-
# @api public
|
|
41
|
-
def test(&)
|
|
42
|
-
suite_passed?(&) || ::Kernel.exit(false)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def suite_passed?(&subject)
|
|
48
|
-
specs.all? { |spec| run_spec(*spec, &subject) }
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def run_spec(env, location, requirement, challenges, &subject)
|
|
52
|
-
::Process.fork { lab(env, location, requirement, challenges, &subject) }
|
|
53
|
-
::Process.wait
|
|
54
|
-
$CHILD_STATUS.success?
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def lab(env, location, requirement, challenges, &)
|
|
58
|
-
result = Run.new(env, requirement, *challenges).test(&)
|
|
59
|
-
report!(location, result)
|
|
60
|
-
::Kernel.exit(result.passed?)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def report!(path, result)
|
|
64
|
-
puts "#{path} #{result.colored_string}"
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|