pbt 0.6.0 → 0.7.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/CHANGELOG.md +6 -0
- data/README.md +25 -22
- data/lib/pbt/arbitrary/arbitrary.rb +2 -2
- data/lib/pbt/arbitrary/array_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/choose_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/constant_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/filter_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/fixed_hash_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/integer_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/map_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/one_of_arbitrary.rb +1 -1
- data/lib/pbt/arbitrary/tuple_arbitrary.rb +1 -1
- data/lib/pbt/check/property.rb +1 -1
- data/lib/pbt/stateful/property.rb +3 -101
- data/lib/pbt/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3275eafb6d07373c24f422feafa7300962213568a387cd21fbcd648c85de4193
|
|
4
|
+
data.tar.gz: 7a3b902bb323251050ec023ec8318244d777c0bc2584ad0c8bad2504d93888f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e72d6278580f540b7d0aad1aefb168c2fbc7a3e64d31a90a4929695b979d3d1c29e3dc06fb22fb0fc6d1aadfc121605176cbee7146e298201f9091c0fa2ea647
|
|
7
|
+
data.tar.gz: 8967245b883d22dfb00d79b0433acc53502e3e9ecdb07e89bae04002ce413fd67d126f695f4a4e12a965fc7adf171b5cd8e1c9fd3e6d8d28e8ff4444113cbea5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-04-04
|
|
4
|
+
|
|
5
|
+
- [Breaking change] Simplify stateful command protocol: `arguments` must now accept `state` parameter, and `applicable?` must now accept both `state` and `args` parameters
|
|
6
|
+
- Make `rng` optional in all arbitrary `generate` methods with default `Random.new` [#44](https://github.com/ohbarye/pbt/pull/44)
|
|
7
|
+
- Mark stateful testing API as stable (no longer experimental)
|
|
8
|
+
|
|
3
9
|
## [0.6.0] - 2026-03-15
|
|
4
10
|
|
|
5
11
|
- Add experimental `Pbt.stateful` API for model-based stateful property testing [#38](https://github.com/ohbarye/pbt/pull/38)
|
data/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Add this line to your application's Gemfile and run `bundle install`.
|
|
|
34
34
|
gem 'pbt'
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Of course you can install with `gem install pbt`.
|
|
38
38
|
|
|
39
39
|
## Basic Usage
|
|
40
40
|
|
|
@@ -108,34 +108,37 @@ There are many built-in arbitraries in `Pbt`. You can use them to generate rando
|
|
|
108
108
|
#### Primitives
|
|
109
109
|
|
|
110
110
|
```ruby
|
|
111
|
-
|
|
111
|
+
Pbt.integer.generate # => 42
|
|
112
|
+
Pbt.integer(min: -1, max: 8).generate # => Integer between -1 and 8
|
|
112
113
|
|
|
113
|
-
Pbt.
|
|
114
|
-
Pbt.integer(min: -1, max: 8).generate(rng) # => Integer between -1 and 8
|
|
114
|
+
Pbt.symbol.generate # => :atq
|
|
115
115
|
|
|
116
|
-
Pbt.
|
|
116
|
+
Pbt.ascii_char.generate # => "a"
|
|
117
|
+
Pbt.ascii_string.generate # => "aagjZfao"
|
|
117
118
|
|
|
118
|
-
Pbt.
|
|
119
|
-
Pbt.
|
|
119
|
+
Pbt.boolean.generate # => true or false
|
|
120
|
+
Pbt.constant(42).generate # => 42 always
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You can also pass a custom random number generator if needed:
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
```ruby
|
|
126
|
+
rng = Random.new(42) # with a specific seed for reproducibility
|
|
127
|
+
Pbt.integer.generate(rng)
|
|
123
128
|
```
|
|
124
129
|
|
|
125
130
|
#### Composites
|
|
126
131
|
|
|
127
132
|
```ruby
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Pbt.array(Pbt.integer).generate(rng) # => [121, -13141, 9825]
|
|
131
|
-
Pbt.array(Pbt.integer, max: 1, empty: true).generate(rng) # => [] or [42] etc.
|
|
133
|
+
Pbt.array(Pbt.integer).generate # => [121, -13141, 9825]
|
|
134
|
+
Pbt.array(Pbt.integer, max: 1, empty: true).generate # => [] or [42] etc.
|
|
132
135
|
|
|
133
|
-
Pbt.tuple(Pbt.symbol, Pbt.integer).generate
|
|
136
|
+
Pbt.tuple(Pbt.symbol, Pbt.integer).generate # => [:atq, 42]
|
|
134
137
|
|
|
135
|
-
Pbt.fixed_hash(x: Pbt.symbol, y: Pbt.integer).generate
|
|
136
|
-
Pbt.hash(Pbt.symbol, Pbt.integer).generate
|
|
138
|
+
Pbt.fixed_hash(x: Pbt.symbol, y: Pbt.integer).generate # => {x: :atq, y: 42}
|
|
139
|
+
Pbt.hash(Pbt.symbol, Pbt.integer).generate # => {atq: 121, ygab: -1142}
|
|
137
140
|
|
|
138
|
-
Pbt.one_of(:a, 1, 0.1).generate
|
|
141
|
+
Pbt.one_of(:a, 1, 0.1).generate # => :a or 1 or 0.1
|
|
139
142
|
````
|
|
140
143
|
|
|
141
144
|
See [ArbitraryMethods](https://github.com/ohbarye/pbt/blob/main/lib/pbt/arbitrary/arbitrary_methods.rb) module for more details.
|
|
@@ -169,11 +172,11 @@ class IncrementCommand
|
|
|
169
172
|
:increment
|
|
170
173
|
end
|
|
171
174
|
|
|
172
|
-
def arguments
|
|
175
|
+
def arguments(_state)
|
|
173
176
|
Pbt.nil
|
|
174
177
|
end
|
|
175
178
|
|
|
176
|
-
def applicable?(_state)
|
|
179
|
+
def applicable?(_state, _args)
|
|
177
180
|
true
|
|
178
181
|
end
|
|
179
182
|
|
|
@@ -220,8 +223,8 @@ end
|
|
|
220
223
|
- `model.initial_state`
|
|
221
224
|
- `model.commands(state)` -> `Array<command>`
|
|
222
225
|
- `command.name`
|
|
223
|
-
- `command.arguments` (a `Pbt` arbitrary)
|
|
224
|
-
- `command.applicable?(state)` -> `true` / `false`
|
|
226
|
+
- `command.arguments(state)` (a `Pbt` arbitrary, may depend on current model state)
|
|
227
|
+
- `command.applicable?(state, args)` -> `true` / `false`
|
|
225
228
|
- `command.next_state(state, args)` -> next model state
|
|
226
229
|
- `command.run!(sut, args)` -> command result
|
|
227
230
|
- `command.verify!(before_state:, after_state:, args:, result:, sut:)`
|
|
@@ -421,7 +424,7 @@ Once this project finishes the following, we will release v1.0.0.
|
|
|
421
424
|
- [ ] Statistics feature to aggregate generated values
|
|
422
425
|
- [ ] Decide DSL
|
|
423
426
|
- [ ] Try Fiber
|
|
424
|
-
- [
|
|
427
|
+
- [x] Stateful property-based testing (experimental, via `Pbt.stateful`)
|
|
425
428
|
|
|
426
429
|
## Development
|
|
427
430
|
|
|
@@ -11,9 +11,9 @@ module Pbt
|
|
|
11
11
|
# Generate a value of type `T`, based on the provided random number generator.
|
|
12
12
|
#
|
|
13
13
|
# @abstract
|
|
14
|
-
# @param rng [Random] Random number generator.
|
|
14
|
+
# @param rng [Random] Random number generator. Defaults to a new Random instance.
|
|
15
15
|
# @return [Object] Random value of type `T`.
|
|
16
|
-
def generate(rng)
|
|
16
|
+
def generate(rng = Random.new)
|
|
17
17
|
raise NotImplementedError
|
|
18
18
|
end
|
|
19
19
|
|
data/lib/pbt/check/property.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Pbt
|
|
|
50
50
|
#
|
|
51
51
|
# @param rng [Random]
|
|
52
52
|
# @return [Array<Step>]
|
|
53
|
-
def generate(rng)
|
|
53
|
+
def generate(rng = Random.new)
|
|
54
54
|
length = rng.rand(0..@max_steps)
|
|
55
55
|
state = @model.initial_state
|
|
56
56
|
sequence = []
|
|
@@ -207,11 +207,6 @@ module Pbt
|
|
|
207
207
|
"Pbt.stateful command protocol mismatch for #{command.class} " \
|
|
208
208
|
"(name=#{safe_command_label(command)}, missing: #{missing_methods.join(", ")}, context=#{context})"
|
|
209
209
|
end
|
|
210
|
-
|
|
211
|
-
validate_command_signature!(command, :arguments, valid_counts: [0, 1], expectation: "arguments or arguments(state)",
|
|
212
|
-
context:)
|
|
213
|
-
validate_command_signature!(command, :applicable?, valid_counts: [1, 2],
|
|
214
|
-
expectation: "applicable?(state) or applicable?(state, args)", context:)
|
|
215
210
|
end
|
|
216
211
|
|
|
217
212
|
# @param command [Object]
|
|
@@ -252,12 +247,6 @@ module Pbt
|
|
|
252
247
|
# @param context [String]
|
|
253
248
|
# @return [Object]
|
|
254
249
|
def generate_applicable_args(command, state, rng, context:)
|
|
255
|
-
unless arg_aware_command?(command)
|
|
256
|
-
return NoApplicableArgs unless applicable?(command, state, nil, context:)
|
|
257
|
-
|
|
258
|
-
return arbitrary_for(command, state, context:).generate(rng)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
250
|
arbitrary = arbitrary_for(command, state, context:)
|
|
262
251
|
|
|
263
252
|
ARG_AWARE_GENERATION_ATTEMPTS.times do
|
|
@@ -275,15 +264,7 @@ module Pbt
|
|
|
275
264
|
# @param context [String]
|
|
276
265
|
# @return [Object]
|
|
277
266
|
def arguments_for(command, state, context:)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if supports_argument_count?(method, 1)
|
|
281
|
-
command.arguments(state)
|
|
282
|
-
elsif supports_argument_count?(method, 0)
|
|
283
|
-
command.arguments
|
|
284
|
-
else
|
|
285
|
-
raise_invalid_signature!(command, :arguments, "arguments or arguments(state)", context)
|
|
286
|
-
end
|
|
267
|
+
command.arguments(state)
|
|
287
268
|
end
|
|
288
269
|
|
|
289
270
|
# @param command [Object]
|
|
@@ -303,8 +284,6 @@ module Pbt
|
|
|
303
284
|
def generate_args_for(command, arbitrary, rng)
|
|
304
285
|
arbitrary.generate(rng)
|
|
305
286
|
rescue Pbt::Arbitrary::EmptyDomainError
|
|
306
|
-
raise unless state_aware_arg_aware_command?(command)
|
|
307
|
-
|
|
308
287
|
NoApplicableArgs
|
|
309
288
|
end
|
|
310
289
|
|
|
@@ -314,84 +293,7 @@ module Pbt
|
|
|
314
293
|
# @param context [String]
|
|
315
294
|
# @return [Boolean]
|
|
316
295
|
def applicable?(command, state, args, context:)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if supports_argument_count?(method, 2)
|
|
320
|
-
command.applicable?(state, args)
|
|
321
|
-
elsif supports_argument_count?(method, 1)
|
|
322
|
-
command.applicable?(state)
|
|
323
|
-
else
|
|
324
|
-
raise_invalid_signature!(command, :applicable?, "applicable?(state) or applicable?(state, args)", context)
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# @param command [Object]
|
|
329
|
-
# @return [Boolean]
|
|
330
|
-
def arg_aware_command?(command)
|
|
331
|
-
supports_argument_count?(command.method(:applicable?), 2)
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
# @param command [Object]
|
|
335
|
-
# @return [Boolean]
|
|
336
|
-
def state_aware_arg_aware_command?(command)
|
|
337
|
-
arg_aware_command?(command) && supports_argument_count?(command.method(:arguments), 1)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# @param command [Object]
|
|
341
|
-
# @param method_name [Symbol]
|
|
342
|
-
# @param valid_counts [Array<Integer>]
|
|
343
|
-
# @param expectation [String]
|
|
344
|
-
# @param context [String]
|
|
345
|
-
# @return [void]
|
|
346
|
-
def validate_command_signature!(command, method_name, valid_counts:, expectation:, context:)
|
|
347
|
-
method = command.method(method_name)
|
|
348
|
-
return if valid_counts.any? { |count| supports_argument_count?(method, count) }
|
|
349
|
-
|
|
350
|
-
raise_invalid_signature!(command, method_name, expectation, context)
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# @param command [Object]
|
|
354
|
-
# @param method_name [Symbol]
|
|
355
|
-
# @param expectation [String]
|
|
356
|
-
# @param context [String]
|
|
357
|
-
# @return [void]
|
|
358
|
-
def raise_invalid_signature!(command, method_name, expectation, context)
|
|
359
|
-
raise Pbt::InvalidConfiguration,
|
|
360
|
-
"Pbt.stateful command protocol mismatch for #{command.class} " \
|
|
361
|
-
"(name=#{safe_command_label(command)}, invalid #{method_name} signature; expected #{expectation}, context=#{context})"
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
# @param method [Method]
|
|
365
|
-
# @param count [Integer]
|
|
366
|
-
# @return [Boolean]
|
|
367
|
-
def supports_argument_count?(method, count)
|
|
368
|
-
return false if method.parameters.any? { |kind, _name| keyword_parameter?(kind) }
|
|
369
|
-
|
|
370
|
-
required = 0
|
|
371
|
-
optional = 0
|
|
372
|
-
rest = false
|
|
373
|
-
|
|
374
|
-
method.parameters.each do |kind, _name|
|
|
375
|
-
case kind
|
|
376
|
-
when :req
|
|
377
|
-
required += 1
|
|
378
|
-
when :opt
|
|
379
|
-
optional += 1
|
|
380
|
-
when :rest
|
|
381
|
-
rest = true
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
return false if count < required
|
|
386
|
-
return true if rest
|
|
387
|
-
|
|
388
|
-
count <= required + optional
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
# @param kind [Symbol]
|
|
392
|
-
# @return [Boolean]
|
|
393
|
-
def keyword_parameter?(kind)
|
|
394
|
-
%i[keyreq key keyrest].include?(kind)
|
|
296
|
+
command.applicable?(state, args)
|
|
395
297
|
end
|
|
396
298
|
|
|
397
299
|
# @param sequence [Array<Hash, Step>]
|
data/lib/pbt/version.rb
CHANGED