minitest-proptest 0.2.0 → 0.3.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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 236dabad0376379b518d9c29fc12f1bca0a17343c9dc85c6ff19ef94419698e8
|
4
|
+
data.tar.gz: dbe3075e50971bac885e3fcc6d3c607c9edb4a9e5bac4cc9dc1863a874ac2d5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70f655e9358ce5e198b5489e94a8cdb32d0a58b0d88393482f7faf8e911ab81a50bdc5514add06cb168f13a2c687a825725e00e59c0497937bd076ba8279bc91
|
7
|
+
data.tar.gz: 9dde2177e0f6a9458f1226abd43c5bd1b7048c3c69c6f746043fb02a4c5e9de1f5e8082bad699001f238b478528da350a63ede6d84b6058f011f9d6960217c88
|
@@ -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 }
|
@@ -287,47 +287,27 @@ module Minitest
|
|
287
287
|
|
288
288
|
generator_for(Int8) do
|
289
289
|
r = sized(0xff)
|
290
|
-
(r & 0x80).zero? ? r : -((
|
291
|
-
end.with_shrink_function
|
292
|
-
j = (i & 0x80).zero? ? i : -(((i & 0x7f) - 1) ^ 0x7f)
|
293
|
-
integral_shrink.call(j)
|
294
|
-
end
|
290
|
+
(r & 0x80).zero? ? 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
|
-
(r & 0x8000).zero? ? r : -((
|
299
|
-
end.with_shrink_function
|
300
|
-
j = (i & 0x8000).zero? ? i : -(((i & 0x7fff) - 1) ^ 0x7fff)
|
301
|
-
integral_shrink.call(j)
|
302
|
-
end
|
295
|
+
(r & 0x8000).zero? ? 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
|
-
(r & 0x80000000).zero? ? r : -((
|
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 & 0x80000000).zero? ? 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
305
|
if (r & 0x8000000000000000).zero?
|
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)
|
@@ -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 & 0x80000000).zero? ? 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
|
@@ -14,6 +16,10 @@ module Minitest
|
|
14
16
|
def initialize(
|
15
17
|
# The function which proves the property
|
16
18
|
test_proc,
|
19
|
+
# The file in which our property lives
|
20
|
+
filename,
|
21
|
+
# The method containing our property
|
22
|
+
methodname,
|
17
23
|
# Any class which provides `rand` accepting both an Integer and a Range
|
18
24
|
# is acceptable. The default value is Ruby's standard Mersenne Twister
|
19
25
|
# implementation.
|
@@ -33,6 +39,8 @@ module Minitest
|
|
33
39
|
previous_failure: []
|
34
40
|
)
|
35
41
|
@test_proc = test_proc
|
42
|
+
@filename = filename
|
43
|
+
@methodname = methodname
|
36
44
|
@random = random.call
|
37
45
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
38
46
|
@max_success = max_success
|
@@ -41,7 +49,6 @@ module Minitest
|
|
41
49
|
@max_shrinks = max_shrinks
|
42
50
|
@status = Status.unknown
|
43
51
|
@trivial = false
|
44
|
-
@valid_test_case = true
|
45
52
|
@result = nil
|
46
53
|
@exception = nil
|
47
54
|
@calls = 0
|
@@ -50,6 +57,7 @@ module Minitest
|
|
50
57
|
@generated = []
|
51
58
|
@arbitrary = nil
|
52
59
|
@previous_failure = previous_failure.to_a
|
60
|
+
@local_variables = {}
|
53
61
|
end
|
54
62
|
|
55
63
|
def run!
|
@@ -58,21 +66,6 @@ module Minitest
|
|
58
66
|
shrink!
|
59
67
|
end
|
60
68
|
|
61
|
-
def arbitrary(*classes)
|
62
|
-
if @arbitrary
|
63
|
-
@arbitrary.call(*classes)
|
64
|
-
else
|
65
|
-
a = @generator.for(*classes)
|
66
|
-
@generated << a
|
67
|
-
@status = Status.overrun unless @generated.length <= @max_size
|
68
|
-
a.value
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def where(&b)
|
73
|
-
@valid_test_case &= b.call
|
74
|
-
end
|
75
|
-
|
76
69
|
def explain
|
77
70
|
prop = if @status.valid?
|
78
71
|
'The property was proved to satsfaction across ' \
|
@@ -88,10 +81,20 @@ module Minitest
|
|
88
81
|
elsif @status.unknown?
|
89
82
|
'The property has not yet been tested.'
|
90
83
|
elsif @status.interesting?
|
91
|
-
'
|
92
|
-
|
93
|
-
|
94
|
-
|
84
|
+
info = 'A counterexample to a property has been found after ' \
|
85
|
+
"#{@valid_test_cases} valid " \
|
86
|
+
"example#{@valid_test_cases == 1 ? '' : 's'}.\n"
|
87
|
+
var_info = if @local_variables.empty?
|
88
|
+
'Variables local to the property were unable ' \
|
89
|
+
'to be determined. This is usually a bug.'
|
90
|
+
else
|
91
|
+
"The values at the time of the failure were:\n"
|
92
|
+
end
|
93
|
+
vars = @local_variables
|
94
|
+
.map { |k, v| "\t#{k}: #{v.inspect}" }
|
95
|
+
.join("\n")
|
96
|
+
|
97
|
+
info + var_info + vars
|
95
98
|
elsif @status.exhausted?
|
96
99
|
"The property was unable to generate #{@max_success} test " \
|
97
100
|
'cases before generating ' \
|
@@ -112,37 +115,47 @@ module Minitest
|
|
112
115
|
|
113
116
|
private
|
114
117
|
|
118
|
+
def arbitrary(*classes)
|
119
|
+
if @arbitrary
|
120
|
+
@arbitrary.call(*classes)
|
121
|
+
else
|
122
|
+
a = @generator.for(*classes)
|
123
|
+
@generated << a
|
124
|
+
@status = Status.overrun unless @generated.length <= @max_size
|
125
|
+
a.value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def where(&b)
|
130
|
+
raise InvalidProperty unless b.call
|
131
|
+
end
|
132
|
+
|
115
133
|
def iterate!
|
116
134
|
while continue_iterate? && @result.nil? && @valid_test_cases <= @max_success
|
117
|
-
@valid_test_case = true
|
118
135
|
@generated = []
|
119
136
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
120
137
|
@calls += 1
|
121
138
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
if @valid_test_case && success
|
133
|
-
@status = Status.valid if @status.unknown?
|
134
|
-
@valid_test_cases += 1
|
135
|
-
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
|
136
148
|
@result = @generated
|
137
149
|
@status = Status.interesting
|
150
|
+
rescue InvalidProperty
|
151
|
+
rescue => e
|
152
|
+
@status = Status.invalid
|
153
|
+
@exception = e
|
138
154
|
end
|
139
155
|
|
140
156
|
@status = Status.exhausted if @calls >= @max_success * (@max_discard_ratio + 1)
|
141
157
|
@trivial = true if @generated.empty?
|
142
158
|
end
|
143
|
-
rescue => e
|
144
|
-
@status = Status.invalid
|
145
|
-
@exception = e
|
146
159
|
end
|
147
160
|
|
148
161
|
def rerun!
|
@@ -164,22 +177,22 @@ module Minitest
|
|
164
177
|
end
|
165
178
|
|
166
179
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
false
|
176
|
-
end
|
177
|
-
end
|
178
|
-
if success || !@valid_test_case
|
179
|
-
@generated = []
|
180
|
-
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
|
181
188
|
@result = @generated
|
182
189
|
@status = Status.interesting
|
190
|
+
rescue InvalidProperty
|
191
|
+
@generated = []
|
192
|
+
rescue => e
|
193
|
+
@result = @generated
|
194
|
+
@status = Status.invalid
|
195
|
+
@exception = e
|
183
196
|
end
|
184
197
|
|
185
198
|
# Clean up after we're done
|
@@ -198,6 +211,23 @@ module Minitest
|
|
198
211
|
candidates = @generated.map(&:shrink_candidates)
|
199
212
|
old_arbitrary = @arbitrary
|
200
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.
|
220
|
+
local_variables = {}
|
221
|
+
tracepoint = TracePoint.new(:b_return) do |trace|
|
222
|
+
if trace.path == @filename && trace.method_id.to_s == @methodname
|
223
|
+
b = trace.binding
|
224
|
+
vs = b.local_variables
|
225
|
+
known = vs.to_h { |lv| [lv.to_s, b.local_variable_get(lv)] }
|
226
|
+
local_variables.delete_if { true }
|
227
|
+
local_variables.merge!(known)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
201
231
|
to_test = candidates
|
202
232
|
.map { |x| x.map { |y| [y] } }
|
203
233
|
.reduce { |c, e| c.flat_map { |a| e.map { |b| a + b } } }
|
@@ -221,36 +251,42 @@ module Minitest
|
|
221
251
|
@valid_test_case = true
|
222
252
|
|
223
253
|
@generator = ::Minitest::Proptest::Gen.new(@random)
|
224
|
-
if to_test[run[:run]].map(&:first).reduce(&:+)
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
break
|
235
|
-
end
|
236
|
-
|
237
|
-
if !success && @valid_test_case
|
238
|
-
# The first hit is guaranteed to be the best scoring due to the
|
239
|
-
# 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
|
240
264
|
best_generated = @generated
|
241
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
|
242
276
|
end
|
243
277
|
end
|
244
278
|
|
245
279
|
@calls += 1
|
246
280
|
run[:run] += 1
|
247
281
|
end
|
282
|
+
|
248
283
|
# Clean up after we're done
|
249
|
-
@generated
|
250
|
-
@result
|
251
|
-
@generator
|
252
|
-
@random
|
253
|
-
@arbitrary
|
284
|
+
@generated = best_generated
|
285
|
+
@result = best_generated
|
286
|
+
@generator = old_generator
|
287
|
+
@random = old_random
|
288
|
+
@arbitrary = old_arbitrary
|
289
|
+
@local_variables = local_variables
|
254
290
|
end
|
255
291
|
|
256
292
|
def continue_iterate?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-proptest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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: 2024-
|
11
|
+
date: 2024-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|