diff --git a/CHANGELOG.md b/CHANGELOG.md index 0754798..aff3fe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ ## [Unreleased] +- Drop `:process` and `:thread` workers since there are no concrete use cases. + ## [0.4.2] - 2024-05-23 - Fix Prism `LoadError` message [#27](https://github.com/ohbarye/pbt/pull/27) by @sambostock - + ## [0.4.1] - 2024-05-10 - 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. diff --git a/Gemfile b/Gemfile index 4196231..caae970 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,5 @@ gemspec gem "rake" gem "rspec" gem "standard" -gem "parallel" gem "prism" gem "benchmark-ips" diff --git a/README.md b/README.md index 9e2fe6c..fff5882 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ Pbt.configure do |config| # Whether to print verbose output. Default is `false`. config.verbose = false - # The concurrency method to use. `:ractor`, `:thread`, `:process` and `:none` are supported. Default is `:none`. + # The concurrency method to use. `:ractor` and `:none` are supported. Default is `:none`. config.worker = :none # The number of runs to perform. Default is `100`. @@ -249,15 +249,13 @@ end 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`. -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. - -`Pbt` supports 3 concurrency methods and 1 sequential one. You can choose one of them by setting the `worker` option. +For concurrent processing, you can specify `:ractor` using the `worker` option. Alternatively, choose `:none` for serial execution. 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). ### Ractor -`: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`. +`: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`. ```ruby Pbt.assert(worker: :ractor) do @@ -313,37 +311,9 @@ it do end ``` -### Process - -If you'd like to run test cases that are CPU-bound and `:ractor` is not available, `:process` becomes a good choice. - -```ruby -Pbt.assert(worker: :process) do - Pbt.property(Pbt.integer) do |n| - # ... - end -end -``` - -If you want to use `:process`, you need to install the [parallel](https://github.com/grosser/parallel) gem. - -### Thread - -You may not need to run test cases with multi-threads. - -```ruby -Pbt.assert(worker: :thread) do - Pbt.property(Pbt.integer) do |n| - # ... - end -end -``` - -If you want to use `:thread`, you need to install the [parallel](https://github.com/grosser/parallel) gem. - ### None -For most cases, `:none` is the best choice. It runs tests sequentially (without parallelism) but most test cases finishes within a reasonable time. +For most cases, `:none` is the best choice. It runs tests sequentially but most test cases finishes within a reasonable time. ```ruby Pbt.assert(worker: :none) do @@ -362,8 +332,8 @@ Once this project finishes the following, we will release v1.0.0. - [x] Support shrinking - [x] Support multiple concurrency methods - [x] Ractor - - [x] Process - - [x] Thread + - [x] Process (dropped) + - [x] Thread (dropped) - [x] None (Run tests sequentially) - [x] Documentation - [x] Add better examples diff --git a/lib/pbt/check/configuration.rb b/lib/pbt/check/configuration.rb index a02a644..3105f94 100644 --- a/lib/pbt/check/configuration.rb +++ b/lib/pbt/check/configuration.rb @@ -13,7 +13,7 @@ module Check keyword_init: true ) do # @param verbose [Boolean] Whether to print verbose output. Default is `false`. - # @param worker [Symbol] The concurrency method to use. :ractor`, `:thread`, `:process` and `:none` are supported. Default is `:none`. + # @param worker [Symbol] The concurrency method to use. :ractor` and `:none` are supported. Default is `:none`. # @param num_runs [Integer] The number of runs to perform. Default is `100`. # @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. # @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`. diff --git a/lib/pbt/check/runner_methods.rb b/lib/pbt/check/runner_methods.rb index 6e8b389..769720e 100644 --- a/lib/pbt/check/runner_methods.rb +++ b/lib/pbt/check/runner_methods.rb @@ -96,10 +96,6 @@ def run_it(property, source_values, config) run_it_in_sequential(property, runner) in :ractor run_it_in_ractors(property, runner) - in :process - run_it_in_processes(property, runner) - in :thread - run_it_in_threads(property, runner) end end runner.run_execution @@ -153,60 +149,6 @@ def handle_ractor_error(cause, c) c.exception = e end end - - # @param property [Property] Property to test. - # @param runner [RunnerIterator] - # @return [void] - def run_it_in_threads(property, runner) - require_parallel - - Parallel.map_with_index(runner, in_threads: Parallel.processor_count) do |val, index| - Case.new(val:, index:).tap do |c| - property.run(val) - # Catch all exceptions including RSpec's ExpectationNotMet (It inherits Exception). - rescue Exception => e # standard:disable Lint/RescueException: - c.exception = e - # It's possible to break this loop here by raising `Parallel::Break`. - # But if it raises, we cannot fetch all cases' result. So this loop continues until the end. - end - end.each do |c| - runner.handle_result(c) - break if c.exception - # Ignore the rest of the cases. Just pick up the first failure. - end - end - - # @param property [Property] Property to test. - # @param runner [RunnerIterator] - # @return [void] - def run_it_in_processes(property, runner) - require_parallel - - Parallel.map_with_index(runner, in_processes: Parallel.processor_count) do |val, index| - Case.new(val:, index:).tap do |c| - property.run(val) - # Catch all exceptions including RSpec's ExpectationNotMet (It inherits Exception). - rescue Exception => e # standard:disable Lint/RescueException: - c.exception = e - # It's possible to break this loop here by raising `Parallel::Break`. - # But if it raises, we cannot fetch all cases' result. So this loop continues until the end. - end - end.each do |c| - runner.handle_result(c) - break if c.exception - # Ignore the rest of the cases. Just pick up the first failure. - end - end - - # Load Parallel gem. If it's not installed, raise an error. - # @see https://github.com/grosser/parallel - # @raise [InvalidConfiguration] - def require_parallel - require "parallel" - rescue LoadError - raise InvalidConfiguration, - "Parallel gem (https://github.com/grosser/parallel) is required to use worker `:process` or `:thread`. Please add `gem 'parallel'` to your Gemfile." - end end end end diff --git a/spec/pbt/check/configuration_spec.rb b/spec/pbt/check/configuration_spec.rb index 84d4e03..f2942f7 100644 --- a/spec/pbt/check/configuration_spec.rb +++ b/spec/pbt/check/configuration_spec.rb @@ -107,138 +107,6 @@ end end - describe ":thread" do - context "when all cases pass" do - it "reports success" do - run_details = Pbt.check num_runs: 5, worker: :thread do - Pbt.property(Pbt.integer) {} - end - - expect(run_details.to_h).to include( - failed: false, - num_runs: 5, - num_shrinks: 0, - seed: anything, - counterexample: nil, - counterexample_path: nil, - error_message: nil, - error_instance: nil, - failures: [], - verbose: false, - run_configuration: { - verbose: false, - worker: :thread, - num_runs: 5, - seed: anything, - thread_report_on_exception: false, - experimental_ractor_rspec_integration: false - } - ) - end - end - - context "when any cases fail" do - it "reports failure" do - seed = 0 - - # This seed generates [5, 1, 4] and the 4 fails. - # Then it shrinks from 4 towards with [3, 2, 1] and finds 2 as the smallest counterexample. - run_details = Pbt.check num_runs: 10, worker: :thread, seed: do - Pbt.property(Pbt.one_of(1, 2, 3, 4, 5)) do |n| - raise "dummy error" if n % 2 == 0 - end - end - - expect(run_details.to_h).to include( - failed: true, - num_runs: 3, - num_shrinks: 1, - seed:, - counterexample: 2, - counterexample_path: "2:1", - error_message: "dummy error", - error_instance: be_a(RuntimeError), - failures: [anything, anything], - verbose: false, - run_configuration: { - verbose: false, - worker: :thread, - num_runs: 10, - seed:, - thread_report_on_exception: false, - experimental_ractor_rspec_integration: false - } - ) - end - end - end - - describe ":process" do - context "when all cases pass" do - it "reports success" do - run_details = Pbt.check num_runs: 5, worker: :process do - Pbt.property(Pbt.integer) {} - end - - expect(run_details.to_h).to include( - failed: false, - num_runs: 5, - num_shrinks: 0, - seed: anything, - counterexample: nil, - counterexample_path: nil, - error_message: nil, - error_instance: nil, - failures: [], - verbose: false, - run_configuration: { - verbose: false, - worker: :process, - num_runs: 5, - seed: anything, - thread_report_on_exception: false, - experimental_ractor_rspec_integration: false - } - ) - end - end - - context "when any cases fail" do - it "reports failure" do - seed = 0 - - # This seed generates [5, 1, 4] and the 4 fails. - # Then it shrinks from 4 towards with [3, 2, 1] and finds 2 as the smallest counterexample. - run_details = Pbt.check num_runs: 10, worker: :process, seed: do - Pbt.property(Pbt.one_of(1, 2, 3, 4, 5)) do |n| - raise "dummy error" if n % 2 == 0 - end - end - - expect(run_details.to_h).to include( - failed: true, - num_runs: 3, - num_shrinks: 1, - seed:, - counterexample: 2, - counterexample_path: "2:1", - error_message: "dummy error", - error_instance: be_a(RuntimeError), - failures: [anything, anything], - verbose: false, - run_configuration: { - verbose: false, - worker: :process, - num_runs: 10, - seed:, - thread_report_on_exception: false, - experimental_ractor_rspec_integration: false - } - ) - end - end - end - describe ":none" do context "when all cases pass" do it "reports success" do @@ -349,84 +217,6 @@ end end - describe "process" do - it "allows to use RSpec expectation and matchers" do - Pbt.assert(worker: :process) do - Pbt.property(Pbt.integer) do |x| - raise unless x.is_a?(Integer) - end - end - - Pbt.assert(worker: :process) do - Pbt.property(Pbt.integer, Pbt.integer) do |x, y| - raise unless x.is_a?(Integer) - raise unless y.is_a?(Integer) - end - end - - Pbt.assert(worker: :process) do - Pbt.property(Pbt.array(Pbt.integer, empty: false)) do |arr| - raise unless arr.is_a?(Array) - raise unless arr[0].is_a?(Integer) - end - end - - Pbt.assert(worker: :process) do - Pbt.property(x: Pbt.integer, y: Pbt.char) do |h| - raise unless h.is_a?(Hash) - raise unless h[:x].is_a?(Integer) - raise unless h[:y].is_a?(String) - end - end - - Pbt.assert(worker: :process) do - Pbt.property(x: Pbt.integer, y: Pbt.char) do |x:, y:| - raise unless x.is_a?(Integer) - raise unless y.is_a?(String) - end - end - end - end - - describe "thread" do - it "allows to use RSpec expectation and matchers" do - Pbt.assert(worker: :thread) do - Pbt.property(Pbt.integer) do |x| - raise unless x.is_a?(Integer) - end - end - - Pbt.assert(worker: :thread) do - Pbt.property(Pbt.integer, Pbt.integer) do |x, y| - raise unless x.is_a?(Integer) - raise unless y.is_a?(Integer) - end - end - - Pbt.assert(worker: :thread) do - Pbt.property(Pbt.array(Pbt.integer, empty: false)) do |arr| - raise unless arr.is_a?(Array) - raise unless arr[0].is_a?(Integer) - end - end - - Pbt.assert(worker: :thread) do - Pbt.property(x: Pbt.integer, y: Pbt.char) do |h| - raise unless h.is_a?(Hash) - raise unless h[:x].is_a?(Integer) - raise unless h[:y].is_a?(String) - end - end - - Pbt.assert(worker: :thread) do - Pbt.property(x: Pbt.integer, y: Pbt.char) do |x:, y:| - raise unless x.is_a?(Integer) - raise unless y.is_a?(String) - end - end - end - end - describe "none" do it "allows to use RSpec expectation and matchers" do Pbt.assert(worker: :none) do