pbt 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 129484c73e5be7741c4bbf70c2ab374a0e48f28926c3cd4b6b3ca004c1037b98
4
- data.tar.gz: 83ec64585e4b33bfad802b262784b0507f341621764099279af3f6faeda273fa
3
+ metadata.gz: 2b93d8a06cbff5946bab644527868f42601ccd7521d4d11e3746fa00c279cd62
4
+ data.tar.gz: ab19c9a123d4408cb49e7a24225bca930d5bd4134000e98a3efc9c233ce97f9c
5
5
  SHA512:
6
- metadata.gz: 3c61fa019e0aff56404f4c3346b039615c368e175e98407f118be9ef55060e61f4ebacbd6cec79b5e0834cfec2550fdbdf6952be47eeacb1437455f5fd54dd5b
7
- data.tar.gz: 209b104767846e73d8db2b94d0b4c9e2e9528c695e9d4cb766d6845de7e8c1556e523b172461c693c77e6deea3f0a764dd51c8e2a17611a2914760146e399bcc
6
+ metadata.gz: cd5fd29fb5e5d3afb5817fd24b0c8967037e2158dd3cc57e4d4ab83eb12c7a31c1dfff376a88b96173ca14853cd3d56b89de212c3630bb6cffe4bb4f04b106d6
7
+ data.tar.gz: b0d37a7198cd0d1c504170f00041b5decab223e73c8896ebd1d357f4482503665ec0ca200cf5557bbed6c04f775cb3c7264eb61b1a42e6b6982f30e90941155e
data/CHANGELOG.md CHANGED
@@ -1,9 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2024-12-30
4
+
5
+ - [Breaking change] Drop `:process` and `:thread` workers since there are no concrete use cases.
6
+ - [Breaking change] Drop `:experimental_ractor_rspec_integration` option since there are no concrete use cases.
7
+
3
8
  ## [0.4.2] - 2024-05-23
4
9
 
5
10
  - Fix Prism `LoadError` message [#27](https://github.com/ohbarye/pbt/pull/27) by @sambostock
6
-
11
+
7
12
  ## [0.4.1] - 2024-05-10
8
13
 
9
14
  - Fix a bug for experimental_ractor_rspec_integration mode. When a test file name starts with a number, it can't be a constant name.
data/README.md CHANGED
@@ -51,7 +51,7 @@ end
51
51
 
52
52
  Pbt.assert do
53
53
  # The given block is executed 100 times with different arrays with random numbers.
54
- # Besides, the block runs in parallel by Ractor.
54
+ # Besides, if you set `worker: :ractor` option to `assert` method, it runs in parallel using Ractor.
55
55
  Pbt.property(Pbt.array(Pbt.integer)) do |numbers|
56
56
  result = sort(numbers)
57
57
  result.each_cons(2) do |x, y|
@@ -218,7 +218,7 @@ Pbt.configure do |config|
218
218
  # Whether to print verbose output. Default is `false`.
219
219
  config.verbose = false
220
220
 
221
- # The concurrency method to use. `:ractor`, `:thread`, `:process` and `:none` are supported. Default is `:none`.
221
+ # The concurrency method to use. `:ractor` and `:none` are supported. Default is `:none`.
222
222
  config.worker = :none
223
223
 
224
224
  # The number of runs to perform. Default is `100`.
@@ -231,9 +231,6 @@ Pbt.configure do |config|
231
231
  # Whether to report exceptions in threads.
232
232
  # It's useful to suppress error logs on Ractor that reports many errors. Default is `false`.
233
233
  config.thread_report_on_exception = false
234
-
235
- # Whether to allow RSpec expectation and matchers in Ractor. It's quite experimental! Default is `false`.
236
- config.experimental_ractor_rspec_integration = false
237
234
  end
238
235
  ```
239
236
 
@@ -249,15 +246,13 @@ end
249
246
 
250
247
  One of the key features of `Pbt` is its ability to rapidly execute test cases in parallel or concurrently, using a large number of values (by default, `100`) generated by `Arbitrary`.
251
248
 
252
- For concurrent processing, you can specify any of the three workers—`:ractor`, `:process`, or `:thread`—using the `worker` option. Alternatively, choose `:none` for serial execution.
253
-
254
- `Pbt` supports 3 concurrency methods and 1 sequential one. You can choose one of them by setting the `worker` option.
249
+ For concurrent processing, you can specify `:ractor` using the `worker` option. Alternatively, choose `:none` for serial execution.
255
250
 
256
251
  Be aware that the performance of each method depends on the test subject. For example, if the test subject is CPU-bound, `:ractor` may be the best choice. Otherwise, `:none` shall be the best choice for most cases. See [benchmarks](benchmark/README.md).
257
252
 
258
253
  ### Ractor
259
254
 
260
- `:ractor` worker is useful for test cases that are CPU-bound. But it's experimental and has some limitations as described below. If you encounter any issues due to those limitations, consider using `:process` as workers whose benchmark is the most similar to `:ractor`.
255
+ `:ractor` worker is useful for test cases that are CPU-bound. But it's experimental and has some limitations as described below. If you encounter any issues due to those limitations, consider falling back to `:none`.
261
256
 
262
257
  ```ruby
263
258
  Pbt.assert(worker: :ractor) do
@@ -297,53 +292,9 @@ it do
297
292
  end
298
293
  ```
299
294
 
300
- 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.
301
-
302
- 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.
303
-
304
- ```ruby
305
- it do
306
- Pbt.assert(worker: :ractor, experimental_ractor_rspec_integration: true) do
307
- Pbt.property(Pbt.integer) do |n|
308
- # Some RSpec expectations and matchers are available in Ractor by hack.
309
- # Other features like `let`, `subject`, `before`, `after` that access out of block are still not available.
310
- expect(n).to be_an(Integer)
311
- end
312
- end
313
- end
314
- ```
315
-
316
- ### Process
317
-
318
- If you'd like to run test cases that are CPU-bound and `:ractor` is not available, `:process` becomes a good choice.
319
-
320
- ```ruby
321
- Pbt.assert(worker: :process) do
322
- Pbt.property(Pbt.integer) do |n|
323
- # ...
324
- end
325
- end
326
- ```
327
-
328
- If you want to use `:process`, you need to install the [parallel](https://github.com/grosser/parallel) gem.
329
-
330
- ### Thread
331
-
332
- You may not need to run test cases with multi-threads.
333
-
334
- ```ruby
335
- Pbt.assert(worker: :thread) do
336
- Pbt.property(Pbt.integer) do |n|
337
- # ...
338
- end
339
- end
340
- ```
341
-
342
- If you want to use `:thread`, you need to install the [parallel](https://github.com/grosser/parallel) gem.
343
-
344
295
  ### None
345
296
 
346
- For most cases, `:none` is the best choice. It runs tests sequentially (without parallelism) but most test cases finishes within a reasonable time.
297
+ For most cases, `:none` is the best choice. It runs tests sequentially but most test cases finishes within a reasonable time.
347
298
 
348
299
  ```ruby
349
300
  Pbt.assert(worker: :none) do
@@ -362,8 +313,8 @@ Once this project finishes the following, we will release v1.0.0.
362
313
  - [x] Support shrinking
363
314
  - [x] Support multiple concurrency methods
364
315
  - [x] Ractor
365
- - [x] Process
366
- - [x] Thread
316
+ - [x] Process (dropped)
317
+ - [x] Thread (dropped)
367
318
  - [x] None (Run tests sequentially)
368
319
  - [x] Documentation
369
320
  - [x] Add better examples
@@ -371,7 +322,7 @@ Once this project finishes the following, we will release v1.0.0.
371
322
  - [x] Configuration
372
323
  - [x] Benchmark
373
324
  - [x] Rich report by verbose mode
374
- - [x] (Partially) Allow to use expectations and matchers provided by test framework in Ractor if possible.
325
+ - [x] (Partially) Allow to use expectations and matchers provided by test framework in Ractor. (dropped)
375
326
  - It'd be so hard to pass assertions like `expect`, `assert` to a Ractor.
376
327
  - [ ] Implement frequency arbitrary
377
328
  - [ ] Statistics feature to aggregate generated values
@@ -17,26 +17,6 @@ Benchmark.ips do |x|
17
17
  # noop
18
18
  end
19
19
 
20
- x.report("process") do
21
- Pbt.assert(worker: :process, seed:, num_runs: 100) do
22
- Pbt.property(Pbt.integer) do |x|
23
- task(x)
24
- end
25
- end
26
- rescue Pbt::PropertyFailure
27
- # noop
28
- end
29
-
30
- x.report("thread") do
31
- Pbt.assert(worker: :thread, seed:, num_runs: 100) do
32
- Pbt.property(Pbt.integer) do |x|
33
- task(x)
34
- end
35
- end
36
- rescue Pbt::PropertyFailure
37
- # noop
38
- end
39
-
40
20
  x.report("none") do
41
21
  Pbt.assert(worker: :none, seed:, num_runs: 100) do
42
22
  Pbt.property(Pbt.integer) do |x|
@@ -25,22 +25,6 @@ Benchmark.ips do |x|
25
25
  end
26
26
  end
27
27
 
28
- x.report("process") do
29
- Pbt.assert(worker: :process, num_runs: 100) do
30
- Pbt.property(Pbt.constant([a, b, c])) do |x, y, z|
31
- task(x, y, z)
32
- end
33
- end
34
- end
35
-
36
- x.report("thread") do
37
- Pbt.assert(worker: :thread, num_runs: 100) do
38
- Pbt.property(Pbt.constant([a, b, c])) do |x, y, z|
39
- task(x, y, z)
40
- end
41
- end
42
- end
43
-
44
28
  x.report("none") do
45
29
  Pbt.assert(worker: :none, num_runs: 100) do
46
30
  Pbt.property(Pbt.constant([a, b, c])) do |x, y, z|
@@ -18,22 +18,6 @@ Benchmark.ips do |x|
18
18
  end
19
19
  end
20
20
 
21
- x.report("process") do
22
- Pbt.assert(worker: :process, seed:, num_runs: 100) do
23
- Pbt.property(Pbt.ascii_string) do |str|
24
- task(str)
25
- end
26
- end
27
- end
28
-
29
- x.report("thread") do
30
- Pbt.assert(worker: :thread, seed:, num_runs: 100) do
31
- Pbt.property(Pbt.ascii_string) do |str|
32
- task(str)
33
- end
34
- end
35
- end
36
-
37
21
  x.report("none") do
38
22
  Pbt.assert(worker: :none, seed:, num_runs: 100) do
39
23
  Pbt.property(Pbt.ascii_string) do |str|
@@ -14,22 +14,6 @@ Benchmark.ips do |x|
14
14
  end
15
15
  end
16
16
 
17
- x.report("process") do
18
- Pbt.assert(worker: :process, seed:, num_runs: 100) do
19
- Pbt.property(Pbt.integer) do |x|
20
- task(x)
21
- end
22
- end
23
- end
24
-
25
- x.report("thread") do
26
- Pbt.assert(worker: :thread, seed:, num_runs: 100) do
27
- Pbt.property(Pbt.integer) do |x|
28
- task(x)
29
- end
30
- end
31
- end
32
-
33
17
  x.report("none") do
34
18
  Pbt.assert(worker: :none, seed:, num_runs: 100) do
35
19
  Pbt.property(Pbt.integer) do |x|
@@ -92,7 +92,7 @@ module Pbt
92
92
  one_of(*HEXA_CHARS)
93
93
  end
94
94
 
95
- # For lowercase hexadecimal stings.
95
+ # For lowercase hexadecimal strings.
96
96
  #
97
97
  # @see Pbt.array
98
98
  # @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
@@ -9,22 +9,19 @@ module Pbt
9
9
  :num_runs,
10
10
  :seed,
11
11
  :thread_report_on_exception,
12
- :experimental_ractor_rspec_integration,
13
12
  keyword_init: true
14
13
  ) do
15
14
  # @param verbose [Boolean] Whether to print verbose output. Default is `false`.
16
- # @param worker [Symbol] The concurrency method to use. :ractor`, `:thread`, `:process` and `:none` are supported. Default is `:none`.
15
+ # @param worker [Symbol] The concurrency method to use. :ractor` and `:none` are supported. Default is `:none`.
17
16
  # @param num_runs [Integer] The number of runs to perform. Default is `100`.
18
17
  # @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.
19
18
  # @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`.
21
19
  def initialize(
22
20
  verbose: false,
23
21
  worker: :none,
24
22
  num_runs: 100,
25
- seed: Random.new.seed,
26
- thread_report_on_exception: false,
27
- experimental_ractor_rspec_integration: false
23
+ seed: Random.new_seed,
24
+ thread_report_on_exception: false
28
25
  )
29
26
  super
30
27
  end
@@ -54,8 +54,6 @@ module Pbt
54
54
  #
55
55
  # - `:thread_report_on_exception`
56
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.
59
57
  #
60
58
  # @param config [Hash] Configuration parameters.
61
59
  # @param property [Property]
@@ -64,21 +62,12 @@ module Pbt
64
62
  if config[:worker] == :ractor
65
63
  original_report_on_exception = Thread.report_on_exception
66
64
  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
75
65
  end
76
66
 
77
67
  yield
78
68
  ensure
79
69
  if config[:worker] == :ractor
80
70
  Thread.report_on_exception = original_report_on_exception
81
- property.teardown_rspec_integration if config[:experimental_ractor_rspec_integration]
82
71
  end
83
72
  end
84
73
 
@@ -96,10 +85,6 @@ module Pbt
96
85
  run_it_in_sequential(property, runner)
97
86
  in :ractor
98
87
  run_it_in_ractors(property, runner)
99
- in :process
100
- run_it_in_processes(property, runner)
101
- in :thread
102
- run_it_in_threads(property, runner)
103
88
  end
104
89
  end
105
90
  runner.run_execution
@@ -133,80 +118,11 @@ module Pbt
133
118
  c.ractor.take
134
119
  runner.handle_result(c)
135
120
  rescue => e
136
- handle_ractor_error(e.cause, c)
121
+ c.exception = e.cause # Ractor error is wrapped in a Ractor::RemoteError. We need to get the cause.
137
122
  runner.handle_result(c)
138
123
  break # Ignore the rest of the cases. Just pick up the first failure.
139
124
  end
140
125
  end
141
-
142
- def handle_ractor_error(cause, c)
143
- # Ractor error is wrapped in a Ractor::RemoteError. We need to get the cause.
144
- unless defined?(Pbt::Check::RSpecAdapter) && cause.is_a?(Pbt::Check::RSpecAdapter::ExpectationNotMet) # Unknown error.
145
- c.exception = cause
146
- return
147
- end
148
-
149
- # Convert Pbt's custom error to RSpec's error.
150
- begin
151
- RSpec::Expectations::ExpectationHelper.handle_failure(cause.matcher, cause.custom_message, cause.failure_message_method)
152
- rescue RSpec::Expectations::ExpectationNotMetError => e # The class inherits Exception, not StandardError.
153
- c.exception = e
154
- end
155
- end
156
-
157
- # @param property [Property] Property to test.
158
- # @param runner [RunnerIterator]
159
- # @return [void]
160
- def run_it_in_threads(property, runner)
161
- require_parallel
162
-
163
- Parallel.map_with_index(runner, in_threads: Parallel.processor_count) do |val, index|
164
- Case.new(val:, index:).tap do |c|
165
- property.run(val)
166
- # Catch all exceptions including RSpec's ExpectationNotMet (It inherits Exception).
167
- rescue Exception => e # standard:disable Lint/RescueException:
168
- c.exception = e
169
- # It's possible to break this loop here by raising `Parallel::Break`.
170
- # But if it raises, we cannot fetch all cases' result. So this loop continues until the end.
171
- end
172
- end.each do |c|
173
- runner.handle_result(c)
174
- break if c.exception
175
- # Ignore the rest of the cases. Just pick up the first failure.
176
- end
177
- end
178
-
179
- # @param property [Property] Property to test.
180
- # @param runner [RunnerIterator]
181
- # @return [void]
182
- def run_it_in_processes(property, runner)
183
- require_parallel
184
-
185
- Parallel.map_with_index(runner, in_processes: Parallel.processor_count) do |val, index|
186
- Case.new(val:, index:).tap do |c|
187
- property.run(val)
188
- # Catch all exceptions including RSpec's ExpectationNotMet (It inherits Exception).
189
- rescue Exception => e # standard:disable Lint/RescueException:
190
- c.exception = e
191
- # It's possible to break this loop here by raising `Parallel::Break`.
192
- # But if it raises, we cannot fetch all cases' result. So this loop continues until the end.
193
- end
194
- end.each do |c|
195
- runner.handle_result(c)
196
- break if c.exception
197
- # Ignore the rest of the cases. Just pick up the first failure.
198
- end
199
- end
200
-
201
- # Load Parallel gem. If it's not installed, raise an error.
202
- # @see https://github.com/grosser/parallel
203
- # @raise [InvalidConfiguration]
204
- def require_parallel
205
- require "parallel"
206
- rescue LoadError
207
- raise InvalidConfiguration,
208
- "Parallel gem (https://github.com/grosser/parallel) is required to use worker `:process` or `:thread`. Please add `gem 'parallel'` to your Gemfile."
209
- end
210
126
  end
211
127
  end
212
128
  end
data/lib/pbt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pbt
4
- VERSION = "0.4.2"
4
+ VERSION = "0.5.0"
5
5
  end
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.2
4
+ version: 0.5.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-05-23 00:00:00.000000000 Z
11
+ date: 2024-12-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -46,9 +46,6 @@ 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
52
49
  - lib/pbt/check/runner_iterator.rb
53
50
  - lib/pbt/check/runner_methods.rb
54
51
  - lib/pbt/check/tosser.rb
@@ -79,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
76
  - !ruby/object:Gem::Version
80
77
  version: '0'
81
78
  requirements: []
82
- rubygems_version: 3.5.9
79
+ rubygems_version: 3.5.21
83
80
  signing_key:
84
81
  specification_version: 4
85
82
  summary: Property-Based Testing tool for Ruby, utilizing Ractor for parallelizing
@@ -1,64 +0,0 @@
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)
@@ -1,47 +0,0 @@
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/ruby/prism) is required to use worker `:ractor` and `:experimental_ractor_rspec_integration`. Please add `gem 'prism'` 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
@@ -1,74 +0,0 @@
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 = "Test" + 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, @predicate.parameters.size, val) do |class_name, method_name, param_size, val|
33
- klass = RSpecAdapter.const_get(class_name)
34
- if val.is_a?(Hash)
35
- klass.new.send(method_name, **val)
36
- elsif param_size >= 2
37
- klass.new.send(method_name, *val)
38
- else
39
- klass.new.send(method_name, val)
40
- end
41
- end
42
- end
43
-
44
- private
45
-
46
- # @return [void]
47
- def define_ractor_callable_class
48
- # The @method_name is invisible in the Class.new block, so we need to assign it to a local variable.
49
- method_name = @method_name
50
-
51
- inspector = extract_predicate_source_code
52
-
53
- RSpecAdapter.const_set(@class_name, Class.new do
54
- include ::RSpec::Matchers
55
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
56
- def #{method_name}(#{inspector.method_params})
57
- #{inspector.method_body}
58
- end
59
- RUBY
60
- end)
61
- end
62
-
63
- # @return [PredicateBlockInspector]
64
- def extract_predicate_source_code
65
- filepath, line = @predicate.source_location
66
- PredicateBlockInspector.new(line).tap do |inspector|
67
- res = Prism.parse_file(filepath)
68
- res.value.statements.accept(inspector)
69
- end
70
- end
71
- end
72
- end
73
- end
74
- end