pbt 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +27 -8
- data/Rakefile +12 -0
- data/benchmark/README.md +46 -40
- data/benchmark/success_cpu_bound.rb +0 -3
- data/lib/pbt/check/configuration.rb +4 -1
- data/lib/pbt/check/property.rb +5 -1
- data/lib/pbt/check/rspec_adapter/integration.rb +64 -0
- data/lib/pbt/check/rspec_adapter/predicate_block_inspector.rb +47 -0
- data/lib/pbt/check/rspec_adapter/property_extension.rb +72 -0
- data/lib/pbt/check/runner_methods.rb +42 -10
- data/lib/pbt/reporter/run_details_reporter.rb +2 -0
- data/lib/pbt/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8415f37c2819e6370e7d418cb96d8ae2467fec24cfca1e64088c925874dc0c34
|
4
|
+
data.tar.gz: f6b38e54ec98fa669cc379c6c181e210d5fab02fc076a343e941aa17cb5be476
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf614cf7100cbbf13f197dbc2a088e43c8b01fac31eaddc8d24ce5f1da7ed14140e3c59e3cafa6b0a22eee7014965c99df444d90dc70bcfbf07862f077ebb738
|
7
|
+
data.tar.gz: df70247adc89586f9ebb813427fe3efdcfd5ba93deea286dea8f5120dae92bc293f6a7f7ff2bcb2a5bdd263ed7e5108d7c358c4a96eb978e037143f02356a1db
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.0] - 2024-04-21
|
4
|
+
|
5
|
+
- Add experimental_ractor_rspec_integration mode. Be careful, it's quite experimental.
|
6
|
+
- Fix a bug: consider a case when a backtrace is nil.
|
7
|
+
- Allow to pass a predicate block keyword arguments with destruction.
|
8
|
+
|
3
9
|
## [0.2.0] - 2024-04-17
|
4
10
|
|
5
11
|
- Add verbose mode. It's useful to debug the test case.
|
data/README.md
CHANGED
@@ -32,12 +32,6 @@ Add this line to your application's Gemfile and run `bundle install`.
|
|
32
32
|
gem 'pbt'
|
33
33
|
```
|
34
34
|
|
35
|
-
If you want to use multi-processes or multi-threads (other than Ractor) as workers to run tests, install the [parallel](https://github.com/grosser/parallel) gem.
|
36
|
-
|
37
|
-
```ruby
|
38
|
-
gem 'parallel'
|
39
|
-
```
|
40
|
-
|
41
35
|
Off course you can install with `gem intstall pbt`.
|
42
36
|
|
43
37
|
## Basic Usage
|
@@ -226,6 +220,9 @@ Pbt.configure do |config|
|
|
226
220
|
# Whether to report exceptions in threads.
|
227
221
|
# It's useful to suppress error logs on Ractor that reports many errors. Default is `false`.
|
228
222
|
config.thread_report_on_exception = false
|
223
|
+
|
224
|
+
# Whether to allow RSpec expectation and matchers in Ractor. It's quite experimental! Default is `false`.
|
225
|
+
config.experimental_ractor_rspec_integration = false
|
229
226
|
end
|
230
227
|
```
|
231
228
|
|
@@ -289,6 +286,22 @@ it do
|
|
289
286
|
end
|
290
287
|
```
|
291
288
|
|
289
|
+
If you're a challenger, you can enable the experimental feature to allow using RSpec expectations and matchers in Ractor. It works but it's quite experimental and could cause unexpected behaviors.
|
290
|
+
|
291
|
+
Please note that this feature depends on [prism](https://ruby.github.io/prism/) gem. If you use Ruby 3.2 or prior, you need to install the gem by yourself.
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
it do
|
295
|
+
Pbt.assert(worker: :ractor, experimental_ractor_rspec_integration: true) do
|
296
|
+
Pbt.property(Pbt.integer) do |n|
|
297
|
+
# Some RSpec expectations and matchers are available in Ractor by hack.
|
298
|
+
# Other features like `let`, `subject`, `before`, `after` that access out of block are still not available.
|
299
|
+
expect(n).to be_an(Integer)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
```
|
304
|
+
|
292
305
|
### Process
|
293
306
|
|
294
307
|
If you'd like to run test cases that are CPU-bound and `:ractor` is not available, `:process` becomes a good choice.
|
@@ -301,6 +314,8 @@ Pbt.assert(worker: :process) do
|
|
301
314
|
end
|
302
315
|
```
|
303
316
|
|
317
|
+
If you want to use `:process`, you need to install the [parallel](https://github.com/grosser/parallel) gem.
|
318
|
+
|
304
319
|
### Thread
|
305
320
|
|
306
321
|
You may not need to run test cases with multi-threads.
|
@@ -313,6 +328,8 @@ Pbt.assert(worker: :thread) do
|
|
313
328
|
end
|
314
329
|
```
|
315
330
|
|
331
|
+
If you want to use `:thread`, you need to install the [parallel](https://github.com/grosser/parallel) gem.
|
332
|
+
|
316
333
|
### None
|
317
334
|
|
318
335
|
For most cases, `:none` is the best choice. It runs tests sequentially (without parallelism) but most test cases finishes within a reasonable time.
|
@@ -343,9 +360,11 @@ Once this project finishes the following, we will release v1.0.0.
|
|
343
360
|
- [x] Configuration
|
344
361
|
- [x] Benchmark
|
345
362
|
- [x] Rich report by verbose mode
|
346
|
-
- [
|
363
|
+
- [x] (Partially) Allow to use expectations and matchers provided by test framework in Ractor if possible.
|
347
364
|
- It'd be so hard to pass assertions like `expect`, `assert` to a Ractor.
|
348
|
-
- [ ]
|
365
|
+
- [ ] Implement frequency arbitrary
|
366
|
+
- [ ] Statistics feature to aggregate generated values
|
367
|
+
- [ ] Decide DSL
|
349
368
|
|
350
369
|
## Development
|
351
370
|
|
data/Rakefile
CHANGED
@@ -19,7 +19,10 @@ namespace :benchmark do
|
|
19
19
|
puts "This runs a script that does not do any IO or CPU bound work."
|
20
20
|
puts
|
21
21
|
ENV["RUBYOPT"] = "-W:no-experimental"
|
22
|
+
ENV["RUBY_MN_THREADS"] = "1"
|
23
|
+
puts "```"
|
22
24
|
sh "ruby", "benchmark/success_simple.rb"
|
25
|
+
puts "```"
|
23
26
|
puts
|
24
27
|
end
|
25
28
|
|
@@ -29,7 +32,10 @@ namespace :benchmark do
|
|
29
32
|
puts "This runs a script that does CPU bound work."
|
30
33
|
puts
|
31
34
|
ENV["RUBYOPT"] = "-W:no-experimental"
|
35
|
+
ENV["RUBY_MN_THREADS"] = "1"
|
36
|
+
puts "```"
|
32
37
|
sh "ruby", "benchmark/success_cpu_bound.rb"
|
38
|
+
puts "```"
|
33
39
|
puts
|
34
40
|
end
|
35
41
|
|
@@ -39,7 +45,10 @@ namespace :benchmark do
|
|
39
45
|
puts "This runs a script that does IO bound work."
|
40
46
|
puts
|
41
47
|
ENV["RUBYOPT"] = "-W:no-experimental"
|
48
|
+
ENV["RUBY_MN_THREADS"] = "1"
|
49
|
+
puts "```"
|
42
50
|
sh "ruby", "benchmark/success_io_bound.rb"
|
51
|
+
puts "```"
|
43
52
|
puts
|
44
53
|
end
|
45
54
|
end
|
@@ -51,7 +60,10 @@ namespace :benchmark do
|
|
51
60
|
puts "This runs a script that fails and shrink happens."
|
52
61
|
puts
|
53
62
|
ENV["RUBYOPT"] = "-W:no-experimental"
|
63
|
+
ENV["RUBY_MN_THREADS"] = "1"
|
64
|
+
puts "```"
|
54
65
|
sh "ruby", "benchmark/failure_simple.rb"
|
66
|
+
puts "```"
|
55
67
|
puts
|
56
68
|
end
|
57
69
|
end
|
data/benchmark/README.md
CHANGED
@@ -20,9 +20,9 @@ Interestingly, both multi-process (`worker: :process`) and multi-thread (`worker
|
|
20
20
|
|
21
21
|
The following benchmarks are the results of running the benchmark suite.
|
22
22
|
|
23
|
-
- macOS
|
23
|
+
- macOS 14.4.1, Apple M1 Pro 10 cores (8 performance and 2 efficiency)
|
24
24
|
- ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
|
25
|
-
- pbt commit hash
|
25
|
+
- pbt commit hash 6f2c1cf1ab36d5d83a89fabb22da431e3600fe25
|
26
26
|
|
27
27
|
---
|
28
28
|
|
@@ -30,68 +30,74 @@ The following benchmarks are the results of running the benchmark suite.
|
|
30
30
|
|
31
31
|
This runs a script that does not do any IO or CPU bound work.
|
32
32
|
|
33
|
+
```
|
33
34
|
ruby benchmark/success_simple.rb
|
34
|
-
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
|
35
|
+
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +MN [arm64-darwin22]
|
35
36
|
Warming up --------------------------------------
|
36
|
-
ractor
|
37
|
-
process
|
38
|
-
thread
|
39
|
-
none
|
37
|
+
ractor 19.000 i/100ms
|
38
|
+
process 2.000 i/100ms
|
39
|
+
thread 114.000 i/100ms
|
40
|
+
none 234.000 i/100ms
|
40
41
|
Calculating -------------------------------------
|
41
|
-
ractor
|
42
|
-
process
|
43
|
-
thread 1.
|
44
|
-
none
|
42
|
+
ractor 75.717 (± 4.0%) i/s - 380.000 in 5.025954s
|
43
|
+
process 1.712 (± 0.0%) i/s - 10.000 in 5.840821s
|
44
|
+
thread 1.019k (±34.0%) i/s - 4.560k in 5.028774s
|
45
|
+
none 1.616k (±24.1%) i/s - 7.722k in 5.003744s
|
46
|
+
```
|
45
47
|
|
46
48
|
### Benchmark success:cpu_bound
|
47
49
|
|
48
50
|
This runs a script that does CPU bound work.
|
49
51
|
|
52
|
+
```
|
50
53
|
ruby benchmark/success_cpu_bound.rb
|
51
|
-
|
52
|
-
|
53
|
-
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
|
54
|
+
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +MN [arm64-darwin22]
|
54
55
|
Warming up --------------------------------------
|
55
|
-
ractor 3.000 i/100ms
|
56
|
-
process 2.000 i/100ms
|
57
|
-
thread 1.000 i/100ms
|
58
|
-
none 1.000 i/100ms
|
56
|
+
ractor 3.000 i/100ms
|
57
|
+
process 2.000 i/100ms
|
58
|
+
thread 1.000 i/100ms
|
59
|
+
none 1.000 i/100ms
|
59
60
|
Calculating -------------------------------------
|
60
|
-
ractor
|
61
|
-
process
|
62
|
-
thread 7.
|
63
|
-
none 7.
|
61
|
+
ractor 35.688 (± 8.4%) i/s - 180.000 in 5.079922s
|
62
|
+
process 15.329 (± 6.5%) i/s - 78.000 in 5.097040s
|
63
|
+
thread 7.654 (± 0.0%) i/s - 39.000 in 5.095252s
|
64
|
+
none 7.707 (± 0.0%) i/s - 39.000 in 5.060309s
|
65
|
+
```
|
64
66
|
|
65
67
|
### Benchmark success:io_bound
|
66
68
|
|
67
69
|
This runs a script that does IO bound work.
|
68
70
|
|
71
|
+
```
|
69
72
|
ruby benchmark/success_io_bound.rb
|
70
|
-
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
|
73
|
+
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +MN [arm64-darwin22]
|
71
74
|
Warming up --------------------------------------
|
72
|
-
ractor 11.000 i/100ms
|
73
|
-
process
|
74
|
-
thread
|
75
|
-
none
|
75
|
+
ractor 11.000 i/100ms
|
76
|
+
process 2.000 i/100ms
|
77
|
+
thread 16.000 i/100ms
|
78
|
+
none 21.000 i/100ms
|
76
79
|
Calculating -------------------------------------
|
77
|
-
ractor
|
78
|
-
process
|
79
|
-
thread
|
80
|
-
none
|
80
|
+
ractor 55.907 (± 3.6%) i/s - 286.000 in 5.122098s
|
81
|
+
process 9.916 (± 0.0%) i/s - 50.000 in 5.044945s
|
82
|
+
thread 131.280 (±17.5%) i/s - 656.000 in 5.148757s
|
83
|
+
none 144.364 (± 4.8%) i/s - 735.000 in 5.102093s
|
84
|
+
```
|
81
85
|
|
82
86
|
### Benchmark failure:simple
|
83
87
|
|
84
88
|
This runs a script that fails and shrink happens.
|
85
89
|
|
90
|
+
```
|
86
91
|
ruby benchmark/failure_simple.rb
|
87
|
-
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
|
92
|
+
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) +MN [arm64-darwin22]
|
88
93
|
Warming up --------------------------------------
|
89
|
-
ractor
|
90
|
-
process 1.000 i/100ms
|
91
|
-
thread
|
92
|
-
none
|
94
|
+
ractor 7.000 i/100ms
|
95
|
+
process 1.000 i/100ms
|
96
|
+
thread 14.000 i/100ms
|
97
|
+
none 288.000 i/100ms
|
93
98
|
Calculating -------------------------------------
|
94
|
-
ractor
|
95
|
-
process
|
96
|
-
thread
|
97
|
-
none
|
99
|
+
ractor 13.104 (± 7.6%) i/s - 70.000 in 5.358898s
|
100
|
+
process 0.060 (± 0.0%) i/s - 1.000 in 16.545525s
|
101
|
+
thread 111.493 (±20.6%) i/s - 532.000 in 5.012879s
|
102
|
+
none 2.445k (±19.0%) i/s - 12.096k in 5.086548s
|
103
|
+
```
|
@@ -9,6 +9,7 @@ module Pbt
|
|
9
9
|
:num_runs,
|
10
10
|
:seed,
|
11
11
|
:thread_report_on_exception,
|
12
|
+
:experimental_ractor_rspec_integration,
|
12
13
|
keyword_init: true
|
13
14
|
) do
|
14
15
|
# @param verbose [Boolean] Whether to print verbose output. Default is `false`.
|
@@ -16,12 +17,14 @@ module Pbt
|
|
16
17
|
# @param num_runs [Integer] The number of runs to perform. Default is `100`.
|
17
18
|
# @param seed [Integer] The seed to use for random number generation. It's useful to reproduce failed test with the seed you'd pick up from failure messages. Default is a random seed.
|
18
19
|
# @param thread_report_on_exception [Boolean] Whether to report exceptions in threads. It's useful to suppress error logs on Ractor that reports many errors. Default is `false`.
|
20
|
+
# @param experimental_ractor_rspec_integration [Boolean] Whether to allow RSpec expectation and matchers in Ractor. It's quite experimental! Default is `false`.
|
19
21
|
def initialize(
|
20
22
|
verbose: false,
|
21
23
|
worker: :none,
|
22
24
|
num_runs: 100,
|
23
25
|
seed: Random.new.seed,
|
24
|
-
thread_report_on_exception: false
|
26
|
+
thread_report_on_exception: false,
|
27
|
+
experimental_ractor_rspec_integration: false
|
25
28
|
)
|
26
29
|
super
|
27
30
|
end
|
data/lib/pbt/check/property.rb
CHANGED
@@ -34,7 +34,11 @@ module Pbt
|
|
34
34
|
# @param val [Object]
|
35
35
|
# @return [void]
|
36
36
|
def run(val)
|
37
|
-
|
37
|
+
if val.is_a?(Hash)
|
38
|
+
@predicate.call(**val)
|
39
|
+
else
|
40
|
+
@predicate.call(val)
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# Run the predicate with the generated `val` in a Ractor.
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless defined?(RSpec)
|
4
|
+
raise InvalidConfigurationError, "You configured `experimental_ractor_rspec_integration: true` but RSpec is not loaded. Please use RSpec or set the config `false`."
|
5
|
+
end
|
6
|
+
|
7
|
+
require "pbt/check/rspec_adapter/predicate_block_inspector"
|
8
|
+
require "pbt/check/rspec_adapter/property_extension"
|
9
|
+
|
10
|
+
module Pbt
|
11
|
+
module Check
|
12
|
+
# @private
|
13
|
+
module RSpecAdapter
|
14
|
+
# This custom error contains RSpec matcher and message to handle Pbt's runner.
|
15
|
+
# @private
|
16
|
+
class ExpectationNotMet < StandardError
|
17
|
+
attr_reader :matcher, :custom_message, :failure_message_method
|
18
|
+
|
19
|
+
def initialize(msg, matcher, custom_message, failure_message_method)
|
20
|
+
super(msg)
|
21
|
+
@matcher = matcher
|
22
|
+
@custom_message = custom_message
|
23
|
+
@failure_message_method = failure_message_method
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# `autoload` is not allowed in Ractor but RSpec uses autoload for matchers.
|
31
|
+
# We need to load them in advance in order to be able to use them in Ractor.
|
32
|
+
#
|
33
|
+
# e.g. Ractor raises... `be_a_kind_of': require by autoload on non-main Ractor is not supported (BeAKindOf) (Ractor::UnsafeError)
|
34
|
+
RSpec::Matchers::BuiltIn.constants.each { |c| Object.const_get("RSpec::Matchers::BuiltIn::#{c}") }
|
35
|
+
|
36
|
+
# TODO: preload more helpers like aggregate_failures.
|
37
|
+
# RSpec::Expectations.constants.each { |c| Object.const_get("RSpec::Expectations::#{c}") }
|
38
|
+
# The code above is not enough. Even if we run this code in advance, Ractor raises...
|
39
|
+
# in `failure_notifier': can not access non-shareable objects in constant RSpec::Support::DEFAULT_FAILURE_NOTIFIER by non-main ractor. (Ractor::IsolationError)
|
40
|
+
|
41
|
+
# CAUTION: This is a dirty hack! We need to override the original method to make it Ractor-safe.
|
42
|
+
RSpec::Expectations::ExpectationHelper.singleton_class.prepend(Module.new do
|
43
|
+
def with_matcher(handler, matcher, message)
|
44
|
+
check_message(message)
|
45
|
+
matcher = modern_matcher_from(matcher)
|
46
|
+
yield matcher
|
47
|
+
ensure
|
48
|
+
# The original method is not Ractor-safe unless stopping assigning these class variables.
|
49
|
+
if Ractor.current == Ractor.main
|
50
|
+
::RSpec::Matchers.last_expectation_handler = handler
|
51
|
+
::RSpec::Matchers.last_matcher = matcher
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_failure(matcher, message, failure_message_method)
|
56
|
+
# This method is not Ractor-safe. RSpec::Support::ObjectFormatter.default_instance assigns class variables.
|
57
|
+
# If this method is called in non-main-Ractor, it raises a custom error and let it handle in the main Ractor.
|
58
|
+
if Ractor.current != Ractor.main
|
59
|
+
raise Pbt::Check::RSpecAdapter::ExpectationNotMet.new(nil, matcher, message, failure_message_method)
|
60
|
+
end
|
61
|
+
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
# Use prism to get user-defined block code.
|
5
|
+
require "prism"
|
6
|
+
rescue LoadError
|
7
|
+
raise InvalidConfiguration,
|
8
|
+
"Prism gem (https://github.com/grosser/parallel) is required to use worker `:rator` and `:experimental_ractor_rspec_integration`. Please add `gem 'parallel'` to your Gemfile."
|
9
|
+
end
|
10
|
+
|
11
|
+
module Pbt
|
12
|
+
module Check
|
13
|
+
module RSpecAdapter
|
14
|
+
# This class is used to get user-defined block code.
|
15
|
+
# If a user defines code like below:
|
16
|
+
#
|
17
|
+
# Pbt.property(Pbt.integer, Pbt.integer) do |x, y|
|
18
|
+
# x > 0 && y > 0
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# inspector.method_params #=> "x, y"
|
22
|
+
# inspector.method_body #=> "x > 0 && y > 0"
|
23
|
+
#
|
24
|
+
# @private
|
25
|
+
# @!attribute [r] method_body
|
26
|
+
# @!attribute [r] method_params
|
27
|
+
class PredicateBlockInspector < Prism::Visitor
|
28
|
+
attr_reader :method_body, :method_params
|
29
|
+
|
30
|
+
def initialize(line)
|
31
|
+
@line = line
|
32
|
+
@method_body = nil
|
33
|
+
super()
|
34
|
+
end
|
35
|
+
|
36
|
+
def visit_call_node(node)
|
37
|
+
if node.name == :property && node.block.opening_loc.start_line == @line
|
38
|
+
@method_params = node.block.parameters.parameters.slice
|
39
|
+
@method_body = node.block.body.slice
|
40
|
+
end
|
41
|
+
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Check
|
5
|
+
module RSpecAdapter
|
6
|
+
# @private
|
7
|
+
module PropertyExtension
|
8
|
+
# Define an original class to be called in Ractor.
|
9
|
+
#
|
10
|
+
# @return [void]
|
11
|
+
def setup_rspec_integration
|
12
|
+
filepath, line = @predicate.source_location
|
13
|
+
basename = File.basename(filepath, ".rb")
|
14
|
+
@class_name = basename.split("_").map(&:capitalize).join + line.to_s
|
15
|
+
@method_name = "predicate_#{basename}_#{line}"
|
16
|
+
define_ractor_callable_class
|
17
|
+
end
|
18
|
+
|
19
|
+
# Clean up an original class to be called in Ractor to avoid any persisted namespace pollution.
|
20
|
+
#
|
21
|
+
# @return [void]
|
22
|
+
def teardown_rspec_integration
|
23
|
+
RSpecAdapter.__send__(:remove_const, @class_name) if RSpecAdapter.const_defined?(@class_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Run the predicate with the generated `val`.
|
27
|
+
# This overrides the original `Property#run_in_ractor`.
|
28
|
+
#
|
29
|
+
# @param val [Object]
|
30
|
+
# @return [Ractor]
|
31
|
+
def run_in_ractor(val)
|
32
|
+
Ractor.new(@class_name, @method_name, val) do |class_name, method_name, val|
|
33
|
+
klass = RSpecAdapter.const_get(class_name)
|
34
|
+
if val.is_a?(Hash)
|
35
|
+
klass.new.send(method_name, **val)
|
36
|
+
else
|
37
|
+
klass.new.send(method_name, *val)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @return [void]
|
45
|
+
def define_ractor_callable_class
|
46
|
+
# The @method_name is invisible in the Class.new block, so we need to assign it to a local variable.
|
47
|
+
method_name = @method_name
|
48
|
+
|
49
|
+
inspector = extract_predicate_source_code
|
50
|
+
|
51
|
+
RSpecAdapter.const_set(@class_name, Class.new do
|
52
|
+
include ::RSpec::Matchers
|
53
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
54
|
+
def #{method_name}(#{inspector.method_params})
|
55
|
+
#{inspector.method_body}
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [PredicateBlockInspector]
|
62
|
+
def extract_predicate_source_code
|
63
|
+
filepath, line = @predicate.source_location
|
64
|
+
PredicateBlockInspector.new(line).tap do |inspector|
|
65
|
+
res = Prism.parse_file(filepath)
|
66
|
+
res.value.statements.accept(inspector)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -43,31 +43,48 @@ module Pbt
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
|
46
|
+
setup_for_ractor(config, property) do
|
47
47
|
run_it(property, source_values, config).to_run_details(config)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
private
|
52
52
|
|
53
|
-
# If using Ractor,
|
53
|
+
# If using Ractor, some extra configurations are available and they need to be set up.
|
54
|
+
#
|
55
|
+
# - `:thread_report_on_exception`
|
56
|
+
# So many exception reports happen in Ractor and a console gets too messy. Suppress them to avoid that.
|
57
|
+
# - `:experimental_ractor_rspec_integration`
|
58
|
+
# Allow to use Ractor with RSpec. This is an experimental feature and it's not stable.
|
54
59
|
#
|
55
60
|
# @param config [Hash] Configuration parameters.
|
61
|
+
# @param property [Property]
|
56
62
|
# @param block [Proc]
|
57
|
-
def
|
63
|
+
def setup_for_ractor(config, property, &block)
|
58
64
|
if config[:worker] == :ractor
|
59
65
|
original_report_on_exception = Thread.report_on_exception
|
60
66
|
Thread.report_on_exception = config[:thread_report_on_exception]
|
67
|
+
|
68
|
+
if config[:experimental_ractor_rspec_integration]
|
69
|
+
require "pbt/check/rspec_adapter/integration"
|
70
|
+
class << property
|
71
|
+
include Pbt::Check::RSpecAdapter::PropertyExtension
|
72
|
+
end
|
73
|
+
property.setup_rspec_integration
|
74
|
+
end
|
61
75
|
end
|
62
76
|
|
63
77
|
yield
|
64
78
|
ensure
|
65
|
-
|
79
|
+
if config[:worker] == :ractor
|
80
|
+
Thread.report_on_exception = original_report_on_exception
|
81
|
+
property.teardown_rspec_integration if config[:experimental_ractor_rspec_integration]
|
82
|
+
end
|
66
83
|
end
|
67
84
|
|
68
85
|
# Run the property test for each value.
|
69
86
|
#
|
70
|
-
# @param property [
|
87
|
+
# @param property [Property] Property to test.
|
71
88
|
# @param source_values [Enumerator] Enumerator of values to test.
|
72
89
|
# @param config [Hash] Configuration parameters.
|
73
90
|
# @return [RunExecution] Result of the test.
|
@@ -88,7 +105,7 @@ module Pbt
|
|
88
105
|
runner.run_execution
|
89
106
|
end
|
90
107
|
|
91
|
-
# @param property [
|
108
|
+
# @param property [Property] Property to test.
|
92
109
|
# @param runner [RunnerIterator]
|
93
110
|
# @return [void]
|
94
111
|
def run_it_in_sequential(property, runner)
|
@@ -105,7 +122,7 @@ module Pbt
|
|
105
122
|
end
|
106
123
|
end
|
107
124
|
|
108
|
-
# @param property [
|
125
|
+
# @param property [Property] Property to test.
|
109
126
|
# @param runner [RunnerIterator]
|
110
127
|
# @return [void]
|
111
128
|
def run_it_in_ractors(property, runner)
|
@@ -115,13 +132,28 @@ module Pbt
|
|
115
132
|
c.ractor.take
|
116
133
|
runner.handle_result(c)
|
117
134
|
rescue => e
|
118
|
-
|
135
|
+
handle_ractor_error(e.cause, c)
|
119
136
|
runner.handle_result(c)
|
120
137
|
break # Ignore the rest of the cases. Just pick up the first failure.
|
121
138
|
end
|
122
139
|
end
|
123
140
|
|
124
|
-
|
141
|
+
def handle_ractor_error(cause, c)
|
142
|
+
# Ractor error is wrapped in a Ractor::RemoteError. We need to get the cause.
|
143
|
+
unless defined?(Pbt::Check::RSpecAdapter) && cause.is_a?(Pbt::Check::RSpecAdapter::ExpectationNotMet) # Unknown error.
|
144
|
+
c.exception = cause
|
145
|
+
return
|
146
|
+
end
|
147
|
+
|
148
|
+
# Convert Pbt's custom error to RSpec's error.
|
149
|
+
begin
|
150
|
+
RSpec::Expectations::ExpectationHelper.handle_failure(cause.matcher, cause.custom_message, cause.failure_message_method)
|
151
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e # The class inherits Exception, not StandardError.
|
152
|
+
c.exception = e
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param property [Property] Property to test.
|
125
157
|
# @param runner [RunnerIterator]
|
126
158
|
# @return [void]
|
127
159
|
def run_it_in_threads(property, runner)
|
@@ -142,7 +174,7 @@ module Pbt
|
|
142
174
|
end
|
143
175
|
end
|
144
176
|
|
145
|
-
# @param property [
|
177
|
+
# @param property [Property] Property to test.
|
146
178
|
# @param runner [RunnerIterator]
|
147
179
|
# @return [void]
|
148
180
|
def run_it_in_processes(property, runner)
|
data/lib/pbt/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pbt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ohbarye
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -46,6 +46,9 @@ files:
|
|
46
46
|
- lib/pbt/check/case.rb
|
47
47
|
- lib/pbt/check/configuration.rb
|
48
48
|
- lib/pbt/check/property.rb
|
49
|
+
- lib/pbt/check/rspec_adapter/integration.rb
|
50
|
+
- lib/pbt/check/rspec_adapter/predicate_block_inspector.rb
|
51
|
+
- lib/pbt/check/rspec_adapter/property_extension.rb
|
49
52
|
- lib/pbt/check/runner_iterator.rb
|
50
53
|
- lib/pbt/check/runner_methods.rb
|
51
54
|
- lib/pbt/check/tosser.rb
|