minitest-proptest 0.2.1 → 0.4.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/lib/minitest/proptest/gen/value_generator.rb +9 -5
- data/lib/minitest/proptest/gen.rb +25 -36
- data/lib/minitest/proptest/property.rb +57 -54
- data/lib/minitest/proptest/version.rb +1 -1
- data/lib/minitest/proptest.rb +2 -2
- metadata +12 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 902a119e57c8158fe97d384c65cd988a396b1204689f68c0cdd88d88629ee8dc
|
|
4
|
+
data.tar.gz: 1e371e1ce98f872cda67ede73aa5f967685e1d29a3801bf115ee97f2e04829e4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92c48c115151d03833c649a76441ef6a1be44a101ad0a8c58da77350eafdb3940fe5f78d54c104a6d200b1ebf23a0716c9b13f43dda3b285c1bc7c82c7e6546b
|
|
7
|
+
data.tar.gz: c59e61044807c0c3769c6d9769d9764415a408f4191c8b04b22af59919df73a059bd9ea93536b2ab1cc44cb032bad6ded596f624b10679f4fb047275813ffa2e
|
|
@@ -8,13 +8,13 @@ module Minitest
|
|
|
8
8
|
attr_accessor :entropy
|
|
9
9
|
attr_writer :type_parameters
|
|
10
10
|
|
|
11
|
-
def self.with_shrink_function(&
|
|
12
|
-
define_method(:shrink_function, &
|
|
11
|
+
def self.with_shrink_function(&)
|
|
12
|
+
define_method(:shrink_function, &)
|
|
13
13
|
self
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def self.with_score_function(&
|
|
17
|
-
define_method(:score_function, &
|
|
16
|
+
def self.with_score_function(&)
|
|
17
|
+
define_method(:score_function, &)
|
|
18
18
|
self
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -98,7 +98,11 @@ module Minitest
|
|
|
98
98
|
def shrink_candidates
|
|
99
99
|
fs = @type_parameters.map { |x| x.method(:shrink_function) }
|
|
100
100
|
os = score
|
|
101
|
-
|
|
101
|
+
# Ensure that the end of the shrink attempt will contain the original
|
|
102
|
+
# value. This is necessary to guarantee that the shrink process
|
|
103
|
+
# produces at least one failure for the purpose of capturing variable
|
|
104
|
+
# assignment.
|
|
105
|
+
candidates = shrink_function(*fs, value) + [value]
|
|
102
106
|
candidates
|
|
103
107
|
.map { |c| [force(c).score, c] }
|
|
104
108
|
.reject { |(s, _)| s > os }
|
|
@@ -39,7 +39,7 @@ module Minitest
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def self.generator_for(klass, &
|
|
42
|
+
def self.generator_for(klass, &)
|
|
43
43
|
new_class = Class.new(ValueGenerator)
|
|
44
44
|
new_class.define_method(:initialize) do |g|
|
|
45
45
|
@entropy = ->(b = MAX_SIZE) { (@generated << g.rand(b)).last }
|
|
@@ -50,7 +50,7 @@ module Minitest
|
|
|
50
50
|
@type_parameters = []
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
new_class.define_method(:generator, &
|
|
53
|
+
new_class.define_method(:generator, &)
|
|
54
54
|
|
|
55
55
|
instance_variable_get(:@_generators)[klass] = new_class
|
|
56
56
|
self.const_set("#{klass.name}Gen".split('::').last, new_class)
|
|
@@ -184,7 +184,7 @@ module Minitest
|
|
|
184
184
|
elsif xs2.empty?
|
|
185
185
|
[[]]
|
|
186
186
|
else
|
|
187
|
-
[xs2] + list_remove.call(k,
|
|
187
|
+
[xs2] + list_remove.call(k, n - k, xs2).map { |ys| xs1 + ys }
|
|
188
188
|
end
|
|
189
189
|
end
|
|
190
190
|
|
|
@@ -224,7 +224,7 @@ module Minitest
|
|
|
224
224
|
else
|
|
225
225
|
h1 = xs1.reduce({}) { |c, e| c.merge({ e => h[e] }) }
|
|
226
226
|
h2 = xs2.reduce({}) { |c, e| c.merge({ e => h[e] }) }
|
|
227
|
-
[h1, h2] + list_remove.call(k,
|
|
227
|
+
[h1, h2] + list_remove.call(k, n - k, h2).map { |ys| h1.merge(ys.to_h) }
|
|
228
228
|
end
|
|
229
229
|
end
|
|
230
230
|
|
|
@@ -271,13 +271,13 @@ module Minitest
|
|
|
271
271
|
# random values to shrink towards 0.
|
|
272
272
|
generator_for(Integer) do
|
|
273
273
|
r = sized(MAX_SIZE)
|
|
274
|
-
if (
|
|
274
|
+
if r.nobits?(SIGN_BIT)
|
|
275
275
|
r
|
|
276
276
|
else
|
|
277
277
|
-(((r & (MAX_SIZE ^ SIGN_BIT)) - 1) ^ (MAX_SIZE ^ SIGN_BIT))
|
|
278
278
|
end
|
|
279
279
|
end.with_shrink_function do |i|
|
|
280
|
-
j = if (
|
|
280
|
+
j = if i.nobits?(SIGN_BIT)
|
|
281
281
|
i
|
|
282
282
|
else
|
|
283
283
|
-(((i & (MAX_SIZE ^ SIGN_BIT)) - 1) ^ (MAX_SIZE ^ SIGN_BIT))
|
|
@@ -287,47 +287,27 @@ module Minitest
|
|
|
287
287
|
|
|
288
288
|
generator_for(Int8) do
|
|
289
289
|
r = sized(0xff)
|
|
290
|
-
(
|
|
291
|
-
end.with_shrink_function
|
|
292
|
-
j = (i & 0x80).zero? ? i : -(((i & 0x7f) - 1) ^ 0x7f)
|
|
293
|
-
integral_shrink.call(j)
|
|
294
|
-
end
|
|
290
|
+
r.nobits?(0x80) ? r : -((r ^ 0x7f) - 0x7f)
|
|
291
|
+
end.with_shrink_function(&integral_shrink)
|
|
295
292
|
|
|
296
293
|
generator_for(Int16) do
|
|
297
294
|
r = sized(0xffff)
|
|
298
|
-
(
|
|
299
|
-
end.with_shrink_function
|
|
300
|
-
j = (i & 0x8000).zero? ? i : -(((i & 0x7fff) - 1) ^ 0x7fff)
|
|
301
|
-
integral_shrink.call(j)
|
|
302
|
-
end
|
|
295
|
+
r.nobits?(0x8000) ? r : -((r ^ 0x7fff) - 0x7fff)
|
|
296
|
+
end.with_shrink_function(&integral_shrink)
|
|
303
297
|
|
|
304
298
|
generator_for(Int32) do
|
|
305
299
|
r = sized(0xffffffff)
|
|
306
|
-
(
|
|
307
|
-
end.with_shrink_function
|
|
308
|
-
j = if (i & 0x80000000).zero?
|
|
309
|
-
i
|
|
310
|
-
else
|
|
311
|
-
-(((i & 0x7fffffff) - 1) ^ 0x7fffffff)
|
|
312
|
-
end
|
|
313
|
-
integral_shrink.call(j)
|
|
314
|
-
end
|
|
300
|
+
r.nobits?(0x80000000) ? r : -((r ^ 0x7fffffff) - 0x7fffffff)
|
|
301
|
+
end.with_shrink_function(&integral_shrink)
|
|
315
302
|
|
|
316
303
|
generator_for(Int64) do
|
|
317
304
|
r = sized(0xffffffffffffffff)
|
|
318
|
-
if (
|
|
305
|
+
if r.nobits?(0x8000000000000000)
|
|
319
306
|
r
|
|
320
307
|
else
|
|
321
|
-
-((
|
|
308
|
+
-((r ^ 0x7fffffffffffffff) - 0x7fffffffffffffff)
|
|
322
309
|
end
|
|
323
|
-
end.with_shrink_function
|
|
324
|
-
j = if (i & 0x8000000000000000).zero?
|
|
325
|
-
i
|
|
326
|
-
else
|
|
327
|
-
-(((i & 0x7fffffffffffffff) - 1) ^ 0x7fffffffffffffff)
|
|
328
|
-
end
|
|
329
|
-
integral_shrink.call(j)
|
|
330
|
-
end
|
|
310
|
+
end.with_shrink_function(&integral_shrink)
|
|
331
311
|
|
|
332
312
|
generator_for(UInt8) do
|
|
333
313
|
sized(0xff)
|
|
@@ -458,7 +438,7 @@ module Minitest
|
|
|
458
438
|
generator_for(Rational) do
|
|
459
439
|
n = sized(MAX_SIZE)
|
|
460
440
|
d = sized(MAX_SIZE - 1) + 1
|
|
461
|
-
if (
|
|
441
|
+
if n.nobits?(SIGN_BIT)
|
|
462
442
|
Rational(n, d)
|
|
463
443
|
else
|
|
464
444
|
Rational(-(((n & (MAX_SIZE ^ SIGN_BIT)) - 1) ^ (MAX_SIZE ^ SIGN_BIT)), d)
|
|
@@ -471,6 +451,15 @@ module Minitest
|
|
|
471
451
|
end.with_score_function do |_|
|
|
472
452
|
1
|
|
473
453
|
end
|
|
454
|
+
|
|
455
|
+
generator_for(Time) do
|
|
456
|
+
r = sized(0xffffffff)
|
|
457
|
+
Time.at(r.nobits?(0x80000000) ? r : -((r ^ 0x7fffffff) - 0x7fffffff))
|
|
458
|
+
end.with_shrink_function do |t|
|
|
459
|
+
integral_shrink.call(t.to_i).map(&Time.method(:at))
|
|
460
|
+
end.with_score_function do |t|
|
|
461
|
+
t.to_i.abs
|
|
462
|
+
end
|
|
474
463
|
end
|
|
475
464
|
end
|
|
476
465
|
end
|
|
@@ -7,6 +7,8 @@ module Minitest
|
|
|
7
7
|
require 'minitest/assertions'
|
|
8
8
|
include Minitest::Assertions
|
|
9
9
|
|
|
10
|
+
class InvalidProperty < StandardError; end
|
|
11
|
+
|
|
10
12
|
attr_reader :calls, :result, :status, :trivial
|
|
11
13
|
|
|
12
14
|
attr_accessor :assertions
|
|
@@ -47,7 +49,6 @@ module Minitest
|
|
|
47
49
|
@max_shrinks = max_shrinks
|
|
48
50
|
@status = Status.unknown
|
|
49
51
|
@trivial = false
|
|
50
|
-
@valid_test_case = true
|
|
51
52
|
@result = nil
|
|
52
53
|
@exception = nil
|
|
53
54
|
@calls = 0
|
|
@@ -82,7 +83,7 @@ module Minitest
|
|
|
82
83
|
elsif @status.interesting?
|
|
83
84
|
info = 'A counterexample to a property has been found after ' \
|
|
84
85
|
"#{@valid_test_cases} valid " \
|
|
85
|
-
"example#{@valid_test_cases
|
|
86
|
+
"example#{'s' if @valid_test_cases != 1}.\n"
|
|
86
87
|
var_info = if @local_variables.empty?
|
|
87
88
|
'Variables local to the property were unable ' \
|
|
88
89
|
'to be determined. This is usually a bug.'
|
|
@@ -126,40 +127,35 @@ module Minitest
|
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def where(&b)
|
|
129
|
-
|
|
130
|
+
raise InvalidProperty unless b.call
|
|
130
131
|
end
|
|
131
132
|
|
|
132
133
|
def iterate!
|
|
133
134
|
while continue_iterate? && @result.nil? && @valid_test_cases <= @max_success
|
|
134
|
-
@valid_test_case = true
|
|
135
135
|
@generated = []
|
|
136
136
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
|
137
137
|
@calls += 1
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
if @valid_test_case && success
|
|
150
|
-
@status = Status.valid if @status.unknown?
|
|
151
|
-
@valid_test_cases += 1
|
|
152
|
-
elsif @valid_test_case
|
|
139
|
+
begin
|
|
140
|
+
if instance_eval(&@test_proc)
|
|
141
|
+
@status = Status.valid if @status.unknown?
|
|
142
|
+
@valid_test_cases += 1
|
|
143
|
+
else
|
|
144
|
+
@result = @generated
|
|
145
|
+
@status = Status.interesting
|
|
146
|
+
end
|
|
147
|
+
rescue Minitest::Assertion
|
|
153
148
|
@result = @generated
|
|
154
149
|
@status = Status.interesting
|
|
150
|
+
rescue InvalidProperty
|
|
151
|
+
rescue => e
|
|
152
|
+
@status = Status.invalid
|
|
153
|
+
@exception = e
|
|
155
154
|
end
|
|
156
155
|
|
|
157
156
|
@status = Status.exhausted if @calls >= @max_success * (@max_discard_ratio + 1)
|
|
158
157
|
@trivial = true if @generated.empty?
|
|
159
158
|
end
|
|
160
|
-
rescue => e
|
|
161
|
-
@status = Status.invalid
|
|
162
|
-
@exception = e
|
|
163
159
|
end
|
|
164
160
|
|
|
165
161
|
def rerun!
|
|
@@ -181,22 +177,22 @@ module Minitest
|
|
|
181
177
|
end
|
|
182
178
|
|
|
183
179
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
false
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
if success || !@valid_test_case
|
|
196
|
-
@generated = []
|
|
197
|
-
elsif @valid_test_case
|
|
180
|
+
begin
|
|
181
|
+
if instance_eval(&@test_proc)
|
|
182
|
+
@generated = []
|
|
183
|
+
else
|
|
184
|
+
@result = @generated
|
|
185
|
+
@status = Status.interesting
|
|
186
|
+
end
|
|
187
|
+
rescue Minitest::Assertion
|
|
198
188
|
@result = @generated
|
|
199
189
|
@status = Status.interesting
|
|
190
|
+
rescue InvalidProperty
|
|
191
|
+
@generated = []
|
|
192
|
+
rescue => e
|
|
193
|
+
@result = @generated
|
|
194
|
+
@status = Status.invalid
|
|
195
|
+
@exception = e
|
|
200
196
|
end
|
|
201
197
|
|
|
202
198
|
# Clean up after we're done
|
|
@@ -215,6 +211,12 @@ module Minitest
|
|
|
215
211
|
candidates = @generated.map(&:shrink_candidates)
|
|
216
212
|
old_arbitrary = @arbitrary
|
|
217
213
|
|
|
214
|
+
# Using a TracePoint to determine variable assignments at the time of
|
|
215
|
+
# the failure only occurs within shrink! - this is a deliberate decision
|
|
216
|
+
# which eliminates all time lost in iterate! to optimize for the success
|
|
217
|
+
# case. The tradeoff is that if all shrinking fails, one additional
|
|
218
|
+
# cycle (with the values which produced the original failure) will be
|
|
219
|
+
# required.
|
|
218
220
|
local_variables = {}
|
|
219
221
|
tracepoint = TracePoint.new(:b_return) do |trace|
|
|
220
222
|
if trace.path == @filename && trace.method_id.to_s == @methodname
|
|
@@ -249,27 +251,28 @@ module Minitest
|
|
|
249
251
|
@valid_test_case = true
|
|
250
252
|
|
|
251
253
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
|
252
|
-
if to_test[run[:run]].map(&:first).reduce(&:+)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
@excption = e
|
|
263
|
-
break
|
|
264
|
-
ensure
|
|
265
|
-
tracepoint.disable
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
if !success && @valid_test_case
|
|
269
|
-
# The first hit is guaranteed to be the best scoring since the
|
|
270
|
-
# shrink candidates are pre-sorted.
|
|
254
|
+
if to_test[run[:run]].map(&:first).reduce(&:+) <= best_score
|
|
255
|
+
begin
|
|
256
|
+
tracepoint.enable
|
|
257
|
+
unless instance_eval(&@test_proc)
|
|
258
|
+
# The first hit is guaranteed to be the best scoring since the
|
|
259
|
+
# shrink candidates are pre-sorted.
|
|
260
|
+
best_generated = @generated
|
|
261
|
+
break
|
|
262
|
+
end
|
|
263
|
+
rescue Minitest::Assertion
|
|
271
264
|
best_generated = @generated
|
|
272
265
|
break
|
|
266
|
+
rescue InvalidProperty
|
|
267
|
+
# Invalid test case generated- continue
|
|
268
|
+
rescue => e
|
|
269
|
+
next unless @valid_test_case
|
|
270
|
+
|
|
271
|
+
@status = Status.invalid
|
|
272
|
+
@excption = e
|
|
273
|
+
break
|
|
274
|
+
ensure
|
|
275
|
+
tracepoint.disable
|
|
273
276
|
end
|
|
274
277
|
end
|
|
275
278
|
|
data/lib/minitest/proptest.rb
CHANGED
|
@@ -149,8 +149,8 @@ module Minitest
|
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
module Kernel
|
|
152
|
-
def generator_for(klass, &
|
|
153
|
-
::Minitest::Proptest::Gen.generator_for(klass, &
|
|
152
|
+
def generator_for(klass, &)
|
|
153
|
+
::Minitest::Proptest::Gen.generator_for(klass, &)
|
|
154
154
|
end
|
|
155
155
|
private :generator_for
|
|
156
156
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: minitest-proptest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina Wuest
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-12-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: minitest
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '5'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '7'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '5'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7'
|
|
27
33
|
description: Property testing in Minitest, a la Haskell's QuickCheck and Python's
|
|
28
34
|
Hypothesis
|
|
29
35
|
email: tina@wuest.me
|
|
@@ -54,14 +60,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
54
60
|
requirements:
|
|
55
61
|
- - ">="
|
|
56
62
|
- !ruby/object:Gem::Version
|
|
57
|
-
version: 2.
|
|
63
|
+
version: 3.2.0
|
|
58
64
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
65
|
requirements:
|
|
60
66
|
- - ">="
|
|
61
67
|
- !ruby/object:Gem::Version
|
|
62
68
|
version: '0'
|
|
63
69
|
requirements: []
|
|
64
|
-
rubygems_version: 3.
|
|
70
|
+
rubygems_version: 3.4.19
|
|
65
71
|
signing_key:
|
|
66
72
|
specification_version: 4
|
|
67
73
|
summary: Property testing in Minitest
|