hypothesis-specs 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'helix_runtime'
4
+ require 'hypothesis-ruby-core/native'
5
+
6
+ module Hypothesis
7
+ class Engine
8
+ attr_reader :current_source
9
+ attr_accessor :is_find
10
+
11
+ def initialize(options)
12
+ seed = Random.rand(2**64 - 1)
13
+ @core_engine = HypothesisCoreEngine.new(
14
+ seed, options.fetch(:max_examples)
15
+ )
16
+ end
17
+
18
+ def run
19
+ loop do
20
+ core = @core_engine.new_source
21
+ break if core.nil?
22
+ @current_source = TestCase.new(core)
23
+ begin
24
+ result = yield(@current_source)
25
+ if is_find && result
26
+ @core_engine.finish_interesting(core)
27
+ else
28
+ @core_engine.finish_valid(core)
29
+ end
30
+ rescue UnsatisfiedAssumption
31
+ @core_engine.finish_invalid(core)
32
+ rescue DataOverflow
33
+ @core_engine.finish_overflow(core)
34
+ rescue Exception
35
+ raise if is_find
36
+ @core_engine.finish_interesting(core)
37
+ end
38
+ end
39
+ @current_source = nil
40
+ core = @core_engine.failing_example
41
+ if core.nil?
42
+ raise Unsatisfiable if @core_engine.was_unsatisfiable
43
+ return
44
+ end
45
+
46
+ if is_find
47
+ @current_source = TestCase.new(core, record_draws: true)
48
+ yield @current_source
49
+ else
50
+ @current_source = TestCase.new(core, print_draws: true)
51
+
52
+ begin
53
+ yield @current_source
54
+ rescue Exception => e
55
+ givens = @current_source.print_log
56
+ given_str = givens.each_with_index.map do |(name, s), i|
57
+ name = "##{i + 1}" if name.nil?
58
+ "Given #{name}: #{s}"
59
+ end.to_a
60
+
61
+ if e.respond_to? :hypothesis_data
62
+ e.hypothesis_data[0] = given_str
63
+ else
64
+ original_to_s = e.to_s
65
+ original_inspect = e.inspect
66
+
67
+ class <<e
68
+ attr_accessor :hypothesis_data
69
+
70
+ def to_s
71
+ ['', hypothesis_data[0], '', hypothesis_data[1]].join("\n")
72
+ end
73
+
74
+ def inspect
75
+ ['', hypothesis_data[0], '', hypothesis_data[2]].join("\n")
76
+ end
77
+ end
78
+ e.hypothesis_data = [given_str, original_to_s, original_inspect]
79
+ end
80
+ raise e
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hypothesis
4
+ # A generic superclass for all errors thrown by
5
+ # Hypothesis.
6
+ class HypothesisError < RuntimeError
7
+ end
8
+
9
+ # Indicates that Hypothesis was not able to find
10
+ # enough valid examples for the test to be meaningful.
11
+ # (Currently this is only thrown if Hypothesis did not
12
+ # find *any* valid examples).
13
+ class Unsatisfiable < HypothesisError
14
+ end
15
+
16
+ # Indicates that the Hypothesis API has been used
17
+ # incorrectly in some manner.
18
+ class UsageError < HypothesisError
19
+ end
20
+
21
+ # @!visibility private
22
+ class UnsatisfiedAssumption < HypothesisError
23
+ end
24
+
25
+ # @!visibility private
26
+ class DataOverflow < HypothesisError
27
+ end
28
+ end
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @!visibility private
4
+ class HypothesisCoreRepeatValues
5
+ def should_continue(source)
6
+ result = _should_continue(source.wrapped_data)
7
+ raise Hypothesis::DataOverflow if result.nil?
8
+ result
9
+ end
10
+ end
11
+
12
+ module Hypothesis
13
+ class <<self
14
+ include Hypothesis
15
+ end
16
+
17
+ # A Possible describes a range of valid values that
18
+ # can result from a call to {Hypothesis#any}.
19
+ # This class should not be subclassed directly, but
20
+ # instead should always be constructed using methods
21
+ # from {Hypothesis::Possibilities}.
22
+ class Possible
23
+ # @!visibility private
24
+ include Hypothesis
25
+
26
+ # A Possible value constructed by passing one of these
27
+ # Possible values to the provided block.
28
+ #
29
+ # e.g. the Possible values of `integers.map { |i| i * 2 }`
30
+ # are all even integers.
31
+ #
32
+ # @return [Possible]
33
+ # @yield A possible value of self.
34
+ def map
35
+ Implementations::CompositePossible.new do
36
+ yield any(self)
37
+ end
38
+ end
39
+
40
+ alias collect map
41
+
42
+ # One of these Possible values selected such that
43
+ # the block returns a true value for it.
44
+ #
45
+ # e.g. the Possible values of
46
+ # `integers.filter { |i| i % 2 == 0}` are all even
47
+ # integers (but will typically be less efficient
48
+ # than the one suggested in {Possible#map}.
49
+ #
50
+ # @note Similar warnings to {Hypothesis#assume} apply
51
+ # here: If the condition is difficult to satisfy this
52
+ # may impact the performance and quality of your
53
+ # testing.
54
+ #
55
+ # @return [Possible]
56
+ # @yield A possible value of self.
57
+ def select
58
+ Implementations::CompositePossible.new do
59
+ result = nil
60
+ 4.times do |i|
61
+ assume(i < 3)
62
+ result = any self
63
+ break if yield(result)
64
+ end
65
+ result
66
+ end
67
+ end
68
+
69
+ alias filter select
70
+
71
+ # @!visibility private
72
+ module Implementations
73
+ # @!visibility private
74
+ class CompositePossible < Possible
75
+ def initialize(block = nil, &implicit)
76
+ @block = block || implicit
77
+ end
78
+
79
+ # @!visibility private
80
+ def provide(&block)
81
+ (@block || block).call
82
+ end
83
+ end
84
+
85
+ # @!visibility private
86
+ class PossibleFromCore < Possible
87
+ def initialize(core_possible)
88
+ @core_possible = core_possible
89
+ end
90
+
91
+ # @!visibility private
92
+ def provide
93
+ data = World.current_engine.current_source
94
+ result = @core_possible.provide(data.wrapped_data)
95
+ raise Hypothesis::DataOverflow if result.nil?
96
+ result
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ # A module of many common {Possible} implementations.
103
+ # Rather than subclassing Possible yourself you should use
104
+ # methods from this module to construct Possible values.`
105
+ #
106
+ # You can use methods from this module by including
107
+ # Hypothesis::Possibilities in your tests, or by calling them
108
+ # on the module object directly.
109
+ #
110
+ # Most methods in this module that return a Possible have
111
+ # two names: A singular and a plural name. These are
112
+ # simply aliases and are identical in every way, but are
113
+ # provided to improve readability. For example
114
+ # `any integer` reads better than `given integers`
115
+ # but `arrays(of: integers)` reads better than
116
+ # `arrays(of: integer)`.
117
+ module Possibilities
118
+ include Hypothesis
119
+
120
+ class <<self
121
+ include Possibilities
122
+ end
123
+
124
+ # built_as lets you chain multiple Possible values together,
125
+ # by providing whatever value results from its block.
126
+ #
127
+ # For example the following provides a array plus some
128
+ # element from that array:
129
+ #
130
+ # ```ruby
131
+ # built_as do
132
+ # ls = any array(of: integers)
133
+ # # Or min_size: 1 above, but this shows use of
134
+ # # assume
135
+ # assume ls.size > 0
136
+ # i = any element_of(ls)
137
+ # [ls, i]
138
+ # ```
139
+ #
140
+ # @return [Possible] A Possible whose possible values are
141
+ # any result from the passed block.
142
+ def built_as(&block)
143
+ Hypothesis::Possible::Implementations::CompositePossible.new(block)
144
+ end
145
+
146
+ alias values_built_as built_as
147
+
148
+ # A Possible boolean value
149
+ # @return [Possible]
150
+ def booleans
151
+ integers(min: 0, max: 1).map { |i| i == 1 }
152
+ end
153
+
154
+ alias boolean booleans
155
+
156
+ # A Possible unicode codepoint.
157
+ # @return [Possible]
158
+ # @param min [Integer] The smallest codepoint to provide
159
+ # @param max [Integer] The largest codepoint to provide
160
+ def codepoints(min: 1, max: 1_114_111)
161
+ base = integers(min: min, max: max)
162
+ if min <= 126
163
+ from(integers(min: min, max: [126, max].min), base)
164
+ else
165
+ base
166
+ end
167
+ end
168
+
169
+ alias codepoint codepoints
170
+
171
+ # A Possible String
172
+ # @return [Possible]
173
+ # @param codepoints [Possible, nil] The Possible codepoints
174
+ # that can be found in the string. If nil,
175
+ # will default to self.codepoints. These
176
+ # will be further filtered to ensure the generated string is
177
+ # valid.
178
+ # @param min_size [Integer] The smallest valid length for a
179
+ # provided string
180
+ # @param max_size [Integer] The smallest valid length for a
181
+ # provided string
182
+ def strings(codepoints: nil, min_size: 0, max_size: 10)
183
+ codepoints = self.codepoints if codepoints.nil?
184
+ codepoints = codepoints.select do |i|
185
+ begin
186
+ [i].pack('U*').codepoints
187
+ true
188
+ rescue ArgumentError
189
+ false
190
+ end
191
+ end
192
+ arrays(of: codepoints, min_size: min_size, max_size: max_size).map do |ls|
193
+ ls.pack('U*')
194
+ end
195
+ end
196
+
197
+ alias string strings
198
+
199
+ # A Possible Hash, where all possible values have a fixed
200
+ # shape.
201
+ # This is used for hashes where you know exactly what the
202
+ # keys are, and different keys may have different possible values.
203
+ # For example, hashes_of_shape(a: integers, b: booleans)
204
+ # will give you values like `{a: 11, b: false}`.
205
+ # @return [Possible]
206
+ # @param hash [Hash] A hash describing the values to provide.
207
+ # The keys will be present unmodified in the provided hashes,
208
+ # mapping to their Possible value in the result.
209
+ def hashes_of_shape(hash)
210
+ built_as do
211
+ result = {}
212
+ hash.each { |k, v| result[k] = any(v) }
213
+ result
214
+ end
215
+ end
216
+
217
+ alias hash_of_shape hashes_of_shape
218
+
219
+ # A Possible Hash of variable shape.
220
+ # @return [Possible]
221
+ # @param keys [Possible] the possible keys
222
+ # @param values [Possible] the possible values
223
+ def hashes_with(keys:, values:, min_size: 0, max_size: 10)
224
+ built_as do
225
+ result = {}
226
+ rep = HypothesisCoreRepeatValues.new(
227
+ min_size, max_size, (min_size + max_size) * 0.5
228
+ )
229
+ source = World.current_engine.current_source
230
+ while rep.should_continue(source)
231
+ key = any keys
232
+ if result.include?(key)
233
+ rep.reject
234
+ else
235
+ result[key] = any values
236
+ end
237
+ end
238
+ result
239
+ end
240
+ end
241
+
242
+ alias hash_with hashes_with
243
+
244
+ # A Possible Arrays of a fixed shape.
245
+ # This is used for arrays where you know exactly how many
246
+ # elements there are, and different values may be possible
247
+ # at different positions.
248
+ # For example, arrays_of_shape(strings, integers)
249
+ # will give you values like ["a", 1]
250
+ # @return [Possible]
251
+ # @param elements [Array<Possible>] A variable number of Possible.
252
+ # values. The provided array will have this many values, with
253
+ # each value possible for the corresponding argument. If elements
254
+ # contains an array it will be flattened first, so e.g.
255
+ # arrays_of_shape(a, b) is equivalent to arrays_of_shape([a, b])
256
+ def arrays_of_shape(*elements)
257
+ elements = elements.flatten
258
+ built_as do
259
+ elements.map { |e| any e }.to_a
260
+ end
261
+ end
262
+
263
+ alias array_of_shape arrays_of_shape
264
+
265
+ # A Possible Array of variable shape.
266
+ # This is used for arrays where all of the elements come from
267
+ # the size may vary and the same values are possible at any position.
268
+ # For example, arrays(booleans) might provide [false, true, false].
269
+ # @return [Possible]
270
+ # @param of [Possible] The possible elements of the array.
271
+ # @param min_size [Integer] The smallest valid size of a provided array
272
+ # @param max_size [Integer] The largest valid size of a provided array
273
+ def arrays(of:, min_size: 0, max_size: 10)
274
+ built_as do
275
+ result = []
276
+ rep = HypothesisCoreRepeatValues.new(
277
+ min_size, max_size, (min_size + max_size) * 0.5
278
+ )
279
+ source = World.current_engine.current_source
280
+ result.push any(of) while rep.should_continue(source)
281
+ result
282
+ end
283
+ end
284
+
285
+ alias array arrays
286
+
287
+ # A Possible where the possible values are any one of a number
288
+ # of other possible values.
289
+ # For example, from(strings, integers) could provide either of "a"
290
+ # or 1.
291
+ # @note This has a slightly non-standard aliasing. It reads more
292
+ # nicely if you write `any from(a, b, c)` but e.g.
293
+ # `arrays(of: mix_of(a, b, c))`.
294
+ #
295
+ # @return [Possible]
296
+ # @param components [Array<Possible>] A number of Possible values,
297
+ # where the result will include any value possible from any of
298
+ # them. If components contains an
299
+ # array it will be flattened first, so e.g. from(a, b)
300
+ # is equivalent to from([a, b])
301
+ def from(*components)
302
+ components = components.flatten
303
+ indexes = from_hypothesis_core(
304
+ HypothesisCoreBoundedIntegers.new(components.size - 1)
305
+ )
306
+ built_as do
307
+ i = any indexes
308
+ any components[i]
309
+ end
310
+ end
311
+
312
+ alias mix_of from
313
+
314
+ # A Possible where any one of a fixed array of values is possible.
315
+ # @note these values are provided as is, so if the provided
316
+ # values are mutated in the test you should be careful to make
317
+ # sure each test run gets a fresh value (if you use this Possible
318
+ # in line in the test you don't need to worry about this, this
319
+ # is only a problem if you define the Possible outside of your
320
+ # hypothesis block).
321
+ # @return [Possible]
322
+ # @param values [Enumerable] A collection of possible values.
323
+ def element_of(values)
324
+ values = values.to_a
325
+ indexes = from_hypothesis_core(
326
+ HypothesisCoreBoundedIntegers.new(values.size - 1)
327
+ )
328
+ built_as do
329
+ values.fetch(any(indexes))
330
+ end
331
+ end
332
+
333
+ alias elements_of element_of
334
+
335
+ # A Possible integer
336
+ # @return [Possible]
337
+ # @param min [Integer] The smallest value integer to provide.
338
+ # @param max [Integer] The largest value integer to provide.
339
+ def integers(min: nil, max: nil)
340
+ base = from_hypothesis_core HypothesisCoreIntegers.new
341
+ if min.nil? && max.nil?
342
+ base
343
+ elsif min.nil?
344
+ built_as { max - any(base).abs }
345
+ elsif max.nil?
346
+ built_as { min + any(base).abs }
347
+ else
348
+ bounded = from_hypothesis_core(
349
+ HypothesisCoreBoundedIntegers.new(max - min)
350
+ )
351
+ if min.zero?
352
+ bounded
353
+ else
354
+ built_as { min + any(bounded) }
355
+ end
356
+ end
357
+ end
358
+
359
+ alias integer integers
360
+
361
+ private
362
+
363
+ def from_hypothesis_core(core)
364
+ Hypothesis::Possible::Implementations::PossibleFromCore.new(
365
+ core
366
+ )
367
+ end
368
+ end
369
+ end