pbt 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 +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
|