properb 0.0.1
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 +7 -0
- data/COPYING +674 -0
- data/README.md +56 -0
- data/lib/properb/generator.rb +29 -0
- data/lib/properb/generators/array_generator.rb +75 -0
- data/lib/properb/generators/choice_generator.rb +26 -0
- data/lib/properb/generators/constant_generator.rb +13 -0
- data/lib/properb/generators/deferred_generator.rb +15 -0
- data/lib/properb/generators/frequencies_generator.rb +26 -0
- data/lib/properb/generators/integer_generator.rb +30 -0
- data/lib/properb/generators/mapped_generator.rb +14 -0
- data/lib/properb/generators/recursive_generator.rb +15 -0
- data/lib/properb/generators/selected_generator.rb +19 -0
- data/lib/properb/generators/sized_generator.rb +14 -0
- data/lib/properb/generators/tuple_generator.rb +41 -0
- data/lib/properb/generators.rb +83 -0
- data/lib/properb/rspec/example_group_methods.rb +64 -0
- data/lib/properb/rspec/example_methods.rb +40 -0
- data/lib/properb/rspec.rb +2 -0
- data/lib/properb/shrink_tree.rb +44 -0
- data/lib/properb.rb +111 -0
- metadata +83 -0
data/lib/properb.rb
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
require_relative "properb/generator"
|
|
5
|
+
require_relative "properb/generators"
|
|
6
|
+
require_relative "properb/rspec"
|
|
7
|
+
require_relative "properb/shrink_tree"
|
|
8
|
+
|
|
9
|
+
module Properb
|
|
10
|
+
# This definition is taken from RSpec, in an attempt to remain
|
|
11
|
+
# consistent with them.
|
|
12
|
+
module AllExceptionsExceptOnesWeMustNotRescue
|
|
13
|
+
# These exceptions are dangerous to rescue as rescuing them
|
|
14
|
+
# would interfere with things we should not interfere with.
|
|
15
|
+
AVOID_RESCUING = [NoMemoryError, SignalException, Interrupt, SystemExit].freeze
|
|
16
|
+
|
|
17
|
+
def self.===(exception)
|
|
18
|
+
exception.is_a?(Exception) && !AVOID_RESCUING.any? { exception.is_a?(_1) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.rspec_install(config)
|
|
23
|
+
config.extend Properb::RSpec::ExampleGroupMethods
|
|
24
|
+
config.include Properb::RSpec::ExampleMethods
|
|
25
|
+
config.extend Properb::Generators
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class TestRunner
|
|
29
|
+
def initialize(block)
|
|
30
|
+
@block = block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def run(values)
|
|
34
|
+
@block.call(values)
|
|
35
|
+
true
|
|
36
|
+
rescue Properb::RSpec::ExampleMethods::AssumptionViolated
|
|
37
|
+
# These don't count as failures, but they also need to be run
|
|
38
|
+
# again
|
|
39
|
+
false
|
|
40
|
+
rescue AllExceptionsExceptOnesWeMustNotRescue => e
|
|
41
|
+
string = values.map { |k, v| "#{k} = #{v.inspect}" }.join("\n ")
|
|
42
|
+
# Don't leave properb lines in the backtrace - they're not very useful.
|
|
43
|
+
raise e, <<~MESSAGE, e.backtrace.reject { %r{spec/properb\.rb}.match(_1) }
|
|
44
|
+
#{e.message}
|
|
45
|
+
|
|
46
|
+
with input: #{string}
|
|
47
|
+
MESSAGE
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.for_all(generators, size: 0..10, num_tests: 100, assumption_limit: 10, &block)
|
|
52
|
+
test_runner = TestRunner.new(block)
|
|
53
|
+
|
|
54
|
+
random = Random.new
|
|
55
|
+
generated = nil
|
|
56
|
+
passed = Set.new
|
|
57
|
+
generator = Generators.hashmap(generators)
|
|
58
|
+
current_size = if size.is_a?(Range)
|
|
59
|
+
size.min
|
|
60
|
+
else
|
|
61
|
+
size
|
|
62
|
+
end
|
|
63
|
+
num_tests.times do
|
|
64
|
+
result = false
|
|
65
|
+
attempt_number = 0
|
|
66
|
+
until result
|
|
67
|
+
if attempt_number >= assumption_limit
|
|
68
|
+
raise StandardError,
|
|
69
|
+
"Could not satisfy assumptions after #{assumption_limit} attempts"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
current_size += 1 if size.is_a?(Range) && size.include?(current_size + 1)
|
|
73
|
+
generated = generator.generate_value(random, current_size)
|
|
74
|
+
result = test_runner.run(generated.value)
|
|
75
|
+
attempt_number += 1
|
|
76
|
+
end
|
|
77
|
+
passed << generated.value
|
|
78
|
+
end
|
|
79
|
+
rescue AllExceptionsExceptOnesWeMustNotRescue => e
|
|
80
|
+
exception, = shrink(generated, e, passed, test_runner)
|
|
81
|
+
raise exception
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.shrink(generated, exception, passed, test_runner)
|
|
85
|
+
smallest = generated
|
|
86
|
+
while smallest
|
|
87
|
+
begin
|
|
88
|
+
current = nil
|
|
89
|
+
smallest.children.each do |child|
|
|
90
|
+
current = child
|
|
91
|
+
next if passed.include?(current.value)
|
|
92
|
+
|
|
93
|
+
if test_runner.run(current.value)
|
|
94
|
+
passed << current.value
|
|
95
|
+
else
|
|
96
|
+
# If the assumptions have failed then we don't learn anything
|
|
97
|
+
# useful. We process this node's children before continuing,
|
|
98
|
+
# in the hopes that we get some information from them instead.
|
|
99
|
+
e, current = shrink(current, exception, passed, test_runner)
|
|
100
|
+
raise e unless e == exception
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
break
|
|
104
|
+
rescue AllExceptionsExceptOnesWeMustNotRescue => e
|
|
105
|
+
smallest = current
|
|
106
|
+
exception = e
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
[exception, smallest]
|
|
110
|
+
end
|
|
111
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: properb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Carlo Zancanaro
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-11-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rspec
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3'
|
|
27
|
+
description: |
|
|
28
|
+
A property-based testing library, in the spirit of Proper in
|
|
29
|
+
Erlang, with tight integration with RSpec.
|
|
30
|
+
email:
|
|
31
|
+
- carlo@zancanaro.id.au
|
|
32
|
+
executables: []
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- COPYING
|
|
37
|
+
- README.md
|
|
38
|
+
- lib/properb.rb
|
|
39
|
+
- lib/properb/generator.rb
|
|
40
|
+
- lib/properb/generators.rb
|
|
41
|
+
- lib/properb/generators/array_generator.rb
|
|
42
|
+
- lib/properb/generators/choice_generator.rb
|
|
43
|
+
- lib/properb/generators/constant_generator.rb
|
|
44
|
+
- lib/properb/generators/deferred_generator.rb
|
|
45
|
+
- lib/properb/generators/frequencies_generator.rb
|
|
46
|
+
- lib/properb/generators/integer_generator.rb
|
|
47
|
+
- lib/properb/generators/mapped_generator.rb
|
|
48
|
+
- lib/properb/generators/recursive_generator.rb
|
|
49
|
+
- lib/properb/generators/selected_generator.rb
|
|
50
|
+
- lib/properb/generators/sized_generator.rb
|
|
51
|
+
- lib/properb/generators/tuple_generator.rb
|
|
52
|
+
- lib/properb/rspec.rb
|
|
53
|
+
- lib/properb/rspec/example_group_methods.rb
|
|
54
|
+
- lib/properb/rspec/example_methods.rb
|
|
55
|
+
- lib/properb/shrink_tree.rb
|
|
56
|
+
homepage: https://sr.ht/~czan/properb/
|
|
57
|
+
licenses:
|
|
58
|
+
- GPL-3.0-only
|
|
59
|
+
metadata:
|
|
60
|
+
allowed_push_host: https://rubygems.org
|
|
61
|
+
homepage_uri: https://sr.ht/~czan/properb/
|
|
62
|
+
source_code_uri: https://sr.ht/~czan/properb/
|
|
63
|
+
rubygems_mfa_required: 'true'
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '3.0'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 3.3.26
|
|
80
|
+
signing_key:
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Property-based testing in Ruby, with RSpec integration
|
|
83
|
+
test_files: []
|