calificador 0.1.0 → 0.2.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/README.md +35 -16
- data/TODO.md +16 -0
- data/calificador.gemspec +54 -0
- data/lib/calificador.rb +8 -4
- data/lib/calificador/assert.rb +15 -0
- data/lib/calificador/assertor.rb +79 -35
- data/lib/calificador/build/attribute_evaluator.rb +34 -30
- data/lib/calificador/build/basic_factory.rb +195 -0
- data/lib/calificador/build/mock_factory.rb +151 -0
- data/lib/calificador/build/object_factory.rb +85 -0
- data/lib/calificador/build/trait.rb +0 -20
- data/lib/calificador/context/basic_context.rb +406 -0
- data/lib/calificador/context/class_method_context.rb +0 -0
- data/lib/calificador/{spec → context}/condition_context.rb +1 -3
- data/lib/calificador/{spec/type_context.rb → context/instance_context.rb} +5 -10
- data/lib/calificador/context/operation_context.rb +27 -0
- data/lib/calificador/context/override/argument_override.rb +73 -0
- data/lib/calificador/context/override/basic_override.rb +14 -0
- data/lib/calificador/context/override/factory_override.rb +31 -0
- data/lib/calificador/context/override/property_override.rb +61 -0
- data/lib/calificador/context/test_environment.rb +283 -0
- data/lib/calificador/{spec → context}/test_method.rb +2 -31
- data/lib/calificador/{spec → context}/test_root.rb +3 -15
- data/lib/calificador/{spec/examine_context.rb → context/type_context.rb} +7 -10
- data/lib/calificador/key.rb +27 -15
- data/lib/calificador/minitest/minitest_patches.rb +0 -2
- data/lib/calificador/test.rb +1 -3
- data/lib/calificador/test_mixin.rb +143 -139
- data/lib/calificador/util/call_formatter.rb +5 -5
- data/lib/calificador/util/core_extensions.rb +104 -79
- data/lib/calificador/util/proxy_object.rb +63 -0
- data/lib/calificador/version.rb +1 -1
- metadata +22 -42
- data/lib/calificador/build/attribute_container.rb +0 -103
- data/lib/calificador/build/factory.rb +0 -132
- data/lib/calificador/spec/basic_context.rb +0 -353
- data/lib/calificador/spec/class_method_context.rb +0 -42
- data/lib/calificador/spec/instance_method_context.rb +0 -38
- data/lib/calificador/spec/test_environment.rb +0 -141
- data/lib/calificador/spec/value_override.rb +0 -37
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calificador
|
4
|
+
module Context
|
5
|
+
module Override
|
6
|
+
# Argument override
|
7
|
+
class ArgumentOverride < BasicOverride
|
8
|
+
# Configuration proxy to configure overrides
|
9
|
+
class ConfigProxy < Util::ProxyObject
|
10
|
+
def initialize(override:)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@override = override
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def __respond_to_missing?(name:, include_all:)
|
19
|
+
METHOD_PATTERN =~ name || super
|
20
|
+
end
|
21
|
+
|
22
|
+
def __method_missing(name:, arguments:, keywords:, block:)
|
23
|
+
name = name.to_sym
|
24
|
+
|
25
|
+
if METHOD_PATTERN =~ name
|
26
|
+
::Kernel.raise ::ArgumentError, "Property method '#{name}' cannot have arguments" unless arguments.empty?
|
27
|
+
|
28
|
+
@override.add_attribute(name: name, value: block) if block
|
29
|
+
|
30
|
+
ArgumentProxy.new(override: @override, name: name)
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ArgumentProxy < Util::ProxyObject
|
38
|
+
def initialize(override:, name:)
|
39
|
+
super()
|
40
|
+
|
41
|
+
@override = override
|
42
|
+
@name = name
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](index, &block)
|
46
|
+
@override.add_attribute(name: index, value: block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :attributes
|
51
|
+
|
52
|
+
def initialize(attributes: {})
|
53
|
+
super()
|
54
|
+
|
55
|
+
@attributes = attributes.dup
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_attribute(name:, value:)
|
59
|
+
@attributes[name] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
def config(&block)
|
63
|
+
ConfigProxy.new(override: self).instance_exec(&block)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def apply(context:)
|
68
|
+
context.merge_operation_arguments(@attributes)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calificador
|
4
|
+
module Context
|
5
|
+
module Override
|
6
|
+
# Factory override
|
7
|
+
class FactoryOverride < BasicOverride
|
8
|
+
attr_reader :key, :function
|
9
|
+
|
10
|
+
def initialize(key:, function:)
|
11
|
+
raise ArgumentError, "Key must be a #{Key}, not '#{key}' (#{key.class})" unless key.is_a?(Key)
|
12
|
+
|
13
|
+
unless function.is_a?(Proc)
|
14
|
+
raise ArgumentError, "Function must be a #{Proc}, not '#{function}' (#{function.class})"
|
15
|
+
end
|
16
|
+
|
17
|
+
super()
|
18
|
+
|
19
|
+
@key = key
|
20
|
+
@function = function
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply(context:)
|
24
|
+
key = @key.with_default(context.subject_key)
|
25
|
+
factory = context.override_factory(key: key)
|
26
|
+
factory.init_with = @function
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Calificador
|
4
|
+
module Context
|
5
|
+
module Override
|
6
|
+
# Property override
|
7
|
+
class PropertyOverride < BasicOverride
|
8
|
+
# Configuration proxy to configure overrides
|
9
|
+
class ConfigProxy < Util::ProxyObject
|
10
|
+
def initialize(override:)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@override = override
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def __respond_to_missing?(name:, include_all:)
|
19
|
+
METHOD_PATTERN =~ name
|
20
|
+
end
|
21
|
+
|
22
|
+
def __method_missing(name:, arguments:, keywords:, block:)
|
23
|
+
::Kernel.raise ::ArgumentError, "Property method '#{name}' cannot have arguments" unless arguments.empty?
|
24
|
+
|
25
|
+
unless block
|
26
|
+
::Kernel.raise ::ArgumentError, "Property method '#{name}' must have a block for the property value"
|
27
|
+
end
|
28
|
+
|
29
|
+
@override.add_attribute(name: name, value: block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :key, :attributes
|
34
|
+
|
35
|
+
def initialize(key:, attributes: {})
|
36
|
+
raise ArgumentError, "Key must be a #{Key}, not '#{key}' (#{key.class})" unless key.is_a?(Key)
|
37
|
+
|
38
|
+
super()
|
39
|
+
|
40
|
+
@key = key
|
41
|
+
@attributes = attributes.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_attribute(name:, value:)
|
45
|
+
@attributes[name] = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def config(&block)
|
49
|
+
ConfigProxy.new(override: self).instance_exec(&block)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply(context:)
|
54
|
+
key = @key.with_default(context.subject_key)
|
55
|
+
factory = context.override_factory(key: key)
|
56
|
+
factory.add_overrides(attributes)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
|
5
|
+
module Calificador
|
6
|
+
module Context
|
7
|
+
# Environment to run test method
|
8
|
+
class TestEnvironment < BasicContext
|
9
|
+
# Placeholder for default values in method calls
|
10
|
+
class DefaultValue
|
11
|
+
include Singleton
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"<default>"
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :inspect, :to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
DEFAULT_VALUE = DefaultValue.instance
|
21
|
+
|
22
|
+
class Proxy < Util::ProxyObject
|
23
|
+
extend ::Forwardable
|
24
|
+
|
25
|
+
def initialize(environment:)
|
26
|
+
super()
|
27
|
+
|
28
|
+
@environment = environment
|
29
|
+
@test_instance = environment.test_instance
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert(*arguments, &body)
|
33
|
+
if arguments.empty?
|
34
|
+
@environment.assert do
|
35
|
+
instance_exec(&body)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@test_instance.assert(*arguments, &body)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ruby2_keywords :assert
|
43
|
+
|
44
|
+
def refute(*arguments, &body)
|
45
|
+
if arguments.empty?
|
46
|
+
@environment.refute do
|
47
|
+
instance_exec(&body)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
@test_instance.refute(*arguments, &body)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
ruby2_keywords :refute
|
55
|
+
|
56
|
+
def_delegator :@environment, :subject
|
57
|
+
def_delegator :@environment, :create
|
58
|
+
def_delegator :@environment, :properties
|
59
|
+
def_delegator :@environment, :arguments
|
60
|
+
|
61
|
+
def _
|
62
|
+
Context::TestEnvironment::DEFAULT_VALUE
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(*arguments, **keywords, &block)
|
66
|
+
@environment.call_operation(*arguments, **keywords, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def result
|
70
|
+
@environment.result
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def __respond_to_missing?(name:, include_all:)
|
76
|
+
@environment.operation_name ||
|
77
|
+
!@environment.lookup_named_factory(name: name).nil? ||
|
78
|
+
@test_instance.respond_to?(name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def __method_missing(name:, arguments:, keywords:, block:)
|
82
|
+
if name == @environment.operation_name
|
83
|
+
@environment.call_operation(*arguments, **keywords, &block)
|
84
|
+
else
|
85
|
+
factory = @environment.lookup_named_factory(name: name)
|
86
|
+
|
87
|
+
if factory
|
88
|
+
@environment.create_object(key: factory.key)
|
89
|
+
elsif @test_instance.respond_to?(name)
|
90
|
+
@test_instance.send(name, *arguments, **keywords, &block)
|
91
|
+
else
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def singleton_method_added(name) # rubocop:disable Lint/MissingSuper
|
98
|
+
::Kernel.raise "Adding methods (#{name}) inside test methods is not supported"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
attr_reader :test_instance, :proxy
|
103
|
+
|
104
|
+
def initialize(parent:, test_instance:, overrides: [])
|
105
|
+
raise "Parent must be a #{TestMethod}" unless parent.is_a?(TestMethod)
|
106
|
+
|
107
|
+
super(
|
108
|
+
parent: parent,
|
109
|
+
subject_key: parent.subject_key,
|
110
|
+
description: parent.method_name.to_s,
|
111
|
+
overrides: overrides
|
112
|
+
)
|
113
|
+
|
114
|
+
@test_instance = test_instance
|
115
|
+
@subject = MISSING
|
116
|
+
@result = MISSING
|
117
|
+
@created_objects = {}
|
118
|
+
@current_assertor = nil
|
119
|
+
@proxy = Proxy.new(environment: self)
|
120
|
+
end
|
121
|
+
|
122
|
+
def subject(value = MISSING)
|
123
|
+
if value.equal?(MISSING)
|
124
|
+
if @subject.equal?(MISSING)
|
125
|
+
@result = MISSING
|
126
|
+
@subject = create_subject(environment: self)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
@result = MISSING
|
130
|
+
@subject = value
|
131
|
+
end
|
132
|
+
|
133
|
+
@subject
|
134
|
+
end
|
135
|
+
|
136
|
+
def call_operation(*arguments, **keywords, &block)
|
137
|
+
effective_arguments, effective_options, effective_block = collect_arguments(
|
138
|
+
arguments: arguments,
|
139
|
+
keywords: keywords,
|
140
|
+
block: block
|
141
|
+
)
|
142
|
+
|
143
|
+
@result = if effective_arguments.empty?
|
144
|
+
if effective_options.empty?
|
145
|
+
subject.__send__(operation_name, &block)
|
146
|
+
else
|
147
|
+
subject.__send__(operation_name, **effective_options, &effective_block)
|
148
|
+
end
|
149
|
+
else
|
150
|
+
subject.__send__(operation_name, *effective_arguments, **effective_options, &effective_block)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def result
|
155
|
+
raise StandardError, "Method under test was not called yet, so there is no result" if @result == MISSING
|
156
|
+
|
157
|
+
@result
|
158
|
+
end
|
159
|
+
|
160
|
+
def create(type, trait = Key::NO_TRAIT)
|
161
|
+
create_object(key: Key[type, trait])
|
162
|
+
end
|
163
|
+
|
164
|
+
def assert(&block)
|
165
|
+
@current_assertor&.__check_triggered
|
166
|
+
@current_assertor = Assertor.new(handler: @test_instance, block: block)
|
167
|
+
end
|
168
|
+
|
169
|
+
def refute(&block)
|
170
|
+
@current_assertor&.__check_triggered
|
171
|
+
@current_assertor = Assertor.new(handler: @test_instance, negated: true, block: block)
|
172
|
+
end
|
173
|
+
|
174
|
+
def arguments(&block)
|
175
|
+
raise "Cannot override properties after method under test has been called" unless @result == EMPTY
|
176
|
+
|
177
|
+
super.then do |override|
|
178
|
+
override.apply(context: self)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def properties(type = nil, trait = Key::DEFAULT_TRAIT, &block)
|
183
|
+
raise "Cannot override properties after objects have been created" unless @created_objects.empty?
|
184
|
+
|
185
|
+
super.then do |override|
|
186
|
+
override.apply(context: self)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def create_object(key:)
|
191
|
+
@created_objects.fetch(key) do
|
192
|
+
factory = lookup_factory(key: key)
|
193
|
+
|
194
|
+
@created_objects[key] = if factory
|
195
|
+
factory.create(environment: self)
|
196
|
+
else
|
197
|
+
raise(KeyError, "No factory found for #{key}") if key.trait?
|
198
|
+
|
199
|
+
if key.type.include?(Singleton)
|
200
|
+
key.type.instance
|
201
|
+
elsif key.type.is_a?(Class)
|
202
|
+
method = key.type.method(:new)
|
203
|
+
|
204
|
+
if method.required_arguments?
|
205
|
+
raise KeyError, "Class #{key} has no default constructor, cannot create without factory"
|
206
|
+
end
|
207
|
+
|
208
|
+
key.type.send(:new)
|
209
|
+
elsif key.type.is_a?(Module)
|
210
|
+
key.type
|
211
|
+
else
|
212
|
+
raise(KeyError, "Cannot create object for #{key} without factory")
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def done(error:)
|
219
|
+
@current_assertor&.__check_triggered unless error
|
220
|
+
@current_assertor = nil
|
221
|
+
end
|
222
|
+
|
223
|
+
def run_test
|
224
|
+
if parent.expected_to_fail
|
225
|
+
passed = begin
|
226
|
+
@proxy.instance_exec(&parent.body)
|
227
|
+
true
|
228
|
+
rescue ::Minitest::Assertion => e
|
229
|
+
@test_instance.pass(e.message)
|
230
|
+
false
|
231
|
+
end
|
232
|
+
|
233
|
+
@test_instance.flunk("Expected test to fail") if passed
|
234
|
+
else
|
235
|
+
@proxy.instance_exec(&parent.body)
|
236
|
+
end
|
237
|
+
|
238
|
+
done(error: false)
|
239
|
+
rescue StandardError
|
240
|
+
done(error: true)
|
241
|
+
raise
|
242
|
+
end
|
243
|
+
|
244
|
+
def to_s
|
245
|
+
"#{self.class.name}(#{@test_instance.name})"
|
246
|
+
end
|
247
|
+
|
248
|
+
protected
|
249
|
+
|
250
|
+
def collect_arguments(arguments:, keywords:, block:)
|
251
|
+
default_arguments = operation_arguments
|
252
|
+
|
253
|
+
arguments = arguments.each_with_index.map do |argument, index|
|
254
|
+
if argument.equal?(DEFAULT_VALUE)
|
255
|
+
unless default_arguments.key?(index)
|
256
|
+
raise "Please provide a default value for positional argument ##{index} of '#{operation_name}'"
|
257
|
+
end
|
258
|
+
|
259
|
+
config = default_arguments[index]
|
260
|
+
argument = proxy.instance_exec(&config)
|
261
|
+
end
|
262
|
+
|
263
|
+
argument
|
264
|
+
end
|
265
|
+
|
266
|
+
keywords = keywords.map do |name, value|
|
267
|
+
if value.equal?(DEFAULT_VALUE)
|
268
|
+
unless default_arguments.key?(name)
|
269
|
+
raise "Please provide a default value for keyword argument '#{name}' of '#{operation_name}'"
|
270
|
+
end
|
271
|
+
|
272
|
+
config = default_arguments[name]
|
273
|
+
value = proxy.instance_exec(&config)
|
274
|
+
end
|
275
|
+
|
276
|
+
[name, value]
|
277
|
+
end.to_h
|
278
|
+
|
279
|
+
[arguments, keywords, block]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|