From 1262335bf4b26c86188a2f9d5cf7f6dc9ff7ba2d Mon Sep 17 00:00:00 2001 From: ohbarye Date: Sun, 14 Apr 2024 18:08:18 +0900 Subject: [PATCH] Add description for each concurrency method --- README.md | 10 ++++++++- lib/pbt/check/runner_methods.rb | 38 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index e8f2e91..d4ad321 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ Pbt.assert(num_runs: 100, seed: 42) do end ``` -## Concurrent methods +## Concurrency methods 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`. @@ -184,6 +184,8 @@ Be aware that the performance of each method depends on the test subject. For ex ### 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`. + ```ruby Pbt.assert(worker: :ractor) do Pbt.property(Pbt.integer) do |n| @@ -224,6 +226,8 @@ 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| @@ -234,6 +238,8 @@ end ### Thread +You may not need to run test cases with multi-threads. + ```ruby Pbt.assert(worker: :thread) do Pbt.property(Pbt.integer) do |n| @@ -244,6 +250,8 @@ end ### None +For most cases, `:none` is the best choice. It runs tests sequentially (without parallelism) but most test cases finishes within a reasonable time. + ```ruby Pbt.assert(worker: :none) do Pbt.property(Pbt.integer) do |n| diff --git a/lib/pbt/check/runner_methods.rb b/lib/pbt/check/runner_methods.rb index bfdbf02..19b9662 100644 --- a/lib/pbt/check/runner_methods.rb +++ b/lib/pbt/check/runner_methods.rb @@ -75,19 +75,36 @@ def run_it(property, source_values, config) runner = Check::RunnerIterator.new(source_values, property, config[:verbose]) while runner.has_next? case config[:worker] + in :none + 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) - in :none - run_it_in_sequential(property, runner) end end runner.run_execution end + # @param property [Proc] + # @param runner [RunnerIterator] + # @return [void] + def run_it_in_sequential(property, runner) + runner.each_with_index do |val, index| + c = Case.new(val:, index:) + begin + property.run(val) + runner.handle_result(c) + rescue => e + c.exception = e + runner.handle_result(c) + break # Ignore the rest of the cases. Just pick up the first failure. + end + end + end + # @param property [Proc] # @param runner [RunnerIterator] # @return [void] @@ -146,23 +163,6 @@ def run_it_in_processes(property, runner) end end - # @param property [Proc] - # @param runner [RunnerIterator] - # @return [void] - def run_it_in_sequential(property, runner) - runner.each_with_index do |val, index| - c = Case.new(val:, index:) - begin - property.run(val) - runner.handle_result(c) - rescue => e - c.exception = e - runner.handle_result(c) - break # Ignore the rest of the cases. Just pick up the first failure. - end - end - end - # Load Parallel gem. If it's not installed, raise an error. # @see https://github.com/grosser/parallel # @raise [InvalidConfiguration]