pbt 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22925816f6d46363aed5699db3a724c85ac0655992f595639d0b20a22a61f36e
4
- data.tar.gz: 6ca22340f23595028c9c34148ac382859ad584159a2305d2f3ca5a663185834d
3
+ metadata.gz: d02ba2667633586ac6e6582c3f0f181dd089e63d56b160877a0b3e51ebb353e0
4
+ data.tar.gz: 1e4dc60aa8ddbac429091bd86d3f8d601bb1a34034aa8e7c6c8c914d43e5e9b8
5
5
  SHA512:
6
- metadata.gz: c148bf93b07038796726016344892f259d7b459081d27a0978e47ff8173e99efab69be0b1c4f81c8a90991dcb5181ead98fb309bf72613129a86bcc8d9580dc4
7
- data.tar.gz: 3659e6906c96a4d0f2c311ccecbb627ad7483472fbfe129cfed4d6308cabfc792f6e426ab5f3a659cc4bc1954ce83218d42c46d0ac8783c0bd446e34fe2e7a2a
6
+ metadata.gz: 9ba49e79546492deec531f0ebef12e86e8458482740d20b3d0b3398a02aae220ea5fed476bee6489909bc2f62990fadcda2fff556fab2df69d30984a9d48435c
7
+ data.tar.gz: df6c5128d3f2b839068bd736946114a81dabf590b49d1d93d7053cf7368fa65d299515323396864c9a942f2e004a3cab73f36dc215ba5660c3d5213e41ee7cfd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2024-04-17
4
+
5
+ - Add verbose mode. It's useful to debug the test case.
6
+
3
7
  ## [0.1.1] - 2024-04-14
4
8
 
5
9
  - Change default worker from `:ractor` to `:none`
data/README.md CHANGED
@@ -107,7 +107,7 @@ There are many built-in arbitraries in `Pbt`. You can use them to generate rando
107
107
  #### Primitives
108
108
 
109
109
  ```ruby
110
- rng = Random.new(
110
+ rng = Random.new
111
111
 
112
112
  Pbt.integer.generate(rng) # => 42
113
113
  Pbt.integer(min: -1, max: 8).generate(rng) # => Integer between -1 and 8
@@ -139,6 +139,71 @@ Pbt.one_of(:a, 1, 0.1).generate(rng) # => :a or 1 or 0.1
139
139
 
140
140
  See [ArbitraryMethods](https://github.com/ohbarye/pbt/blob/main/lib/pbt/arbitrary/arbitrary_methods.rb) module for more details.
141
141
 
142
+ ## What if property-based tests fail?
143
+
144
+ Once a test fails it's time to debug. `Pbt` provides some features to help you debug.
145
+
146
+ ### How to reproduce
147
+
148
+ When a test fails, you'll see a message like below.
149
+
150
+ ```text
151
+ Pbt::PropertyFailure:
152
+ Property failed after 23 test(s)
153
+ { seed: 11001296583699917659214176011685741769 }
154
+ Counterexample: 0
155
+ Shrunk 3 time(s)
156
+ Got ZeroDivisionError: divided by 0
157
+ # and backtraces
158
+ ```
159
+
160
+ You can reproduce the failure by passing the seed to `Pbt.assert`.
161
+
162
+ ```ruby
163
+ Pbt.assert(seed: 11001296583699917659214176011685741769) do
164
+ Pbt.property(Pbt.integer) do |number|
165
+ # your test
166
+ end
167
+ end
168
+ ```
169
+
170
+ ### Verbose mode
171
+
172
+ You may want to know which values pass and which values fail. You can enable verbose mode by passing `verbose: true` to `Pbt.assert`.
173
+
174
+ ```ruby
175
+ Pbt.assert(verbose: true) do
176
+ Pbt.property(Pbt.array(Pbt.integer)) do |numbers|
177
+ # your failed test
178
+ end
179
+ end
180
+ ```
181
+
182
+ The verbose mode prints the results of each tested values.
183
+
184
+ ```text
185
+ Encountered failures were:
186
+ - [152477, 666997, -531468, -92182, 623948, 425913, 656138, 856463, -64529]
187
+ - [76239, 666997, -531468, -92182, 623948]
188
+ - [76239, 666997, -531468]
189
+ (snipped for README)
190
+ - [2, 163]
191
+ - [2, 11]
192
+
193
+ Execution summary:
194
+ . × [152477, 666997, -531468, -92182, 623948, 425913, 656138, 856463, -64529]
195
+ . . √ [152477, 666997, -531468, -92182, 623948]
196
+ . . √ [-64529]
197
+ . . × [76239, 666997, -531468, -92182, 623948, 425913, 656138, 856463, -64529]
198
+ . . . × [76239, 666997, -531468, -92182, 623948]
199
+ (snipped for README)
200
+ . . . . . . . . . . . . . . . . . √ [2, 21]
201
+ . . . . . . . . . . . . . . . . . × [2, 11]
202
+ . . . . . . . . . . . . . . . . . . √ []
203
+ . . . . . . . . . . . . . . . . . . √ [2, 1]
204
+ . . . . . . . . . . . . . . . . . . √ [2, 0]
205
+ ```
206
+
142
207
  ## Configuration
143
208
 
144
209
  You can configure `Pbt` by calling `Pbt.configure` before running tests.
@@ -277,7 +342,7 @@ Once this project finishes the following, we will release v1.0.0.
277
342
  - [x] Arbitrary usage
278
343
  - [x] Configuration
279
344
  - [x] Benchmark
280
- - [ ] Rich report like verbose mode
345
+ - [x] Rich report by verbose mode
281
346
  - [ ] Allow to use expectations and matchers provided by test framework in Ractor if possible.
282
347
  - It'd be so hard to pass assertions like `expect`, `assert` to a Ractor.
283
348
  - [ ] More parallelism or faster execution if possible
@@ -51,7 +51,7 @@ module Pbt
51
51
  @next_values = @property.shrink(c.val)
52
52
  else
53
53
  # successful run
54
- @run_execution.record_success
54
+ @run_execution.record_success(c)
55
55
  end
56
56
  end
57
57
  end
@@ -14,6 +14,7 @@ module Pbt
14
14
  :error_instance,
15
15
  :failures,
16
16
  :verbose,
17
+ :execution_summary,
17
18
  :run_configuration,
18
19
  keyword_init: true
19
20
  )
@@ -16,24 +16,85 @@ module Pbt
16
16
  def report_run_details
17
17
  if @run_details.failed
18
18
  message = []
19
-
20
- message << <<~EOS
21
- Property failed after #{@run_details.num_runs} test(s)
22
- { seed: #{@run_details.seed} }
23
- Counterexample: #{@run_details.counterexample}
24
- Shrunk #{@run_details.num_shrinks} time(s)
25
- Got #{@run_details.error_instance.class}: #{@run_details.error_message}
26
- EOS
27
-
28
- if @run_details.verbose
29
- message << " \n#{@run_details.error_instance.backtrace_locations.join("\n ")}"
30
- message << "\nEncountered failures were:"
31
- message << @run_details.failures.map { |f| "- #{f.val}" }
19
+ message << format_error_message
20
+ message << error_backtrace
21
+ message << if @run_details.verbose
22
+ verbose_details
23
+ else
24
+ hint
32
25
  end
33
26
 
34
27
  raise PropertyFailure, message.join("\n") if message.size > 0
35
28
  end
36
29
  end
30
+
31
+ private
32
+
33
+ # @return [String]
34
+ def format_error_message
35
+ <<~MSG.chomp
36
+ Property failed after #{@run_details.num_runs} test(s)
37
+ { seed: #{@run_details.seed} }
38
+ Counterexample: #{@run_details.counterexample}
39
+ Shrunk #{@run_details.num_shrinks} time(s)
40
+ Got #{@run_details.error_instance.class}: #{@run_details.error_message}
41
+ MSG
42
+ end
43
+
44
+ def error_backtrace
45
+ i = @run_details.verbose ? -1 : 10
46
+ " #{@run_details.error_instance.backtrace_locations[..i].join("\n ")}"
47
+ end
48
+
49
+ # @return [String]
50
+ def verbose_details
51
+ [
52
+ "\nEncountered failures were:",
53
+ @run_details.failures.map { |f| "- #{f.val}" },
54
+ format_execution_summary
55
+ ].join("\n")
56
+ end
57
+
58
+ # @return [String]
59
+ def format_execution_summary
60
+ summary_lines = []
61
+ remaining_trees_and_depth = []
62
+
63
+ @run_details.execution_summary.reverse_each do |tree|
64
+ remaining_trees_and_depth << {depth: 1, tree:}
65
+ end
66
+
67
+ until remaining_trees_and_depth.empty?
68
+ current_tree_and_depth = remaining_trees_and_depth.pop
69
+
70
+ # format current tree according to its depth and result
71
+ current_tree = current_tree_and_depth[:tree]
72
+ current_depth = current_tree_and_depth[:depth]
73
+ result_icon = case current_tree.result
74
+ in :success
75
+ "\x1b[32m\u221A\x1b[0m" # green "√"
76
+ in :failure
77
+ "\x1b[31m\u00D7\x1b[0m" # red "×"
78
+ end
79
+ left_padding = ". " * current_depth
80
+ summary_lines << "#{left_padding}#{result_icon} #{current_tree.value}"
81
+
82
+ # push its children to the queue
83
+ current_tree.children.reverse_each do |tree|
84
+ remaining_trees_and_depth << {depth: current_depth + 1, tree:}
85
+ end
86
+ end
87
+
88
+ "\nExecution summary:\n#{summary_lines.join("\n")}\n"
89
+ end
90
+
91
+ # @return [String]
92
+ def hint
93
+ [
94
+ "\nHint: Set `verbose: true` in order to check the list of all failing values encountered during the run.",
95
+ "Hint: Set `seed: #{@run_details.seed}` in order to reproduce the failed test case with the same values."
96
+ ].join("\n")
97
+ end
37
98
  end
38
99
  end
39
100
  end
@@ -6,6 +6,14 @@ module Pbt
6
6
  module Reporter
7
7
  # Represents the result of a single run of a property test.
8
8
  class RunExecution
9
+ # A tree node of the execution for verbose output.
10
+ ExecutionTreeNode = Struct.new(
11
+ :result,
12
+ :value,
13
+ :children,
14
+ keyword_init: true
15
+ )
16
+
9
17
  # @param verbose [Boolean] Whether to print verbose output.
10
18
  def initialize(verbose)
11
19
  @verbose = verbose
@@ -13,12 +21,18 @@ module Pbt
13
21
  @failure = nil
14
22
  @failures = []
15
23
  @num_successes = 0
24
+ @root_execution_trees = []
25
+ @current_level_execution_trees = @root_execution_trees
16
26
  end
17
27
 
18
28
  # Record a failure in the run.
19
29
  #
20
30
  # @param c [Pbt::Check::Case]
21
31
  def record_failure(c)
32
+ if @verbose
33
+ current_tree = append_execution_tree_node(:failure, c.val)
34
+ @current_level_execution_trees = current_tree.children
35
+ end
22
36
  @path_to_failure << c.index
23
37
  @failures << c
24
38
 
@@ -29,8 +43,12 @@ module Pbt
29
43
 
30
44
  # Record a successful run.
31
45
  #
46
+ # @param c [Pbt::Check::Case]
32
47
  # @return [void]
33
- def record_success
48
+ def record_success(c)
49
+ if @verbose
50
+ append_execution_tree_node(:success, c.val)
51
+ end
34
52
  @num_successes += 1
35
53
  end
36
54
 
@@ -58,6 +76,7 @@ module Pbt
58
76
  error_instance: nil,
59
77
  failures: @failures,
60
78
  verbose: @verbose,
79
+ execution_summary: @root_execution_trees,
61
80
  run_configuration: config
62
81
  )
63
82
  else
@@ -72,10 +91,22 @@ module Pbt
72
91
  error_instance: @failure,
73
92
  failures: @failures,
74
93
  verbose: @verbose,
94
+ execution_summary: @root_execution_trees,
75
95
  run_configuration: config
76
96
  )
77
97
  end
78
98
  end
99
+
100
+ private
101
+
102
+ # @param result [Symbol] The result of the current node.
103
+ # @param value [Object] The value to test.
104
+ # @return [Hash] The current execution tree.
105
+ def append_execution_tree_node(result, value)
106
+ current_tree = ExecutionTreeNode.new(result:, value:, children: [])
107
+ @current_level_execution_trees << current_tree
108
+ current_tree
109
+ end
79
110
  end
80
111
  end
81
112
  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.1.1"
4
+ VERSION = "0.2.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.1.1
4
+ version: 0.2.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-14 00:00:00.000000000 Z
11
+ date: 2024-04-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: