test-unit 3.2.5 → 3.4.7

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.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/BSDL +24 -0
  3. data/COPYING +41 -44
  4. data/README.md +8 -11
  5. data/Rakefile +0 -23
  6. data/doc/text/getting-started.md +1 -1
  7. data/doc/text/news.md +366 -0
  8. data/lib/test/unit/assertion-failed-error.rb +35 -0
  9. data/lib/test/unit/assertions.rb +312 -112
  10. data/lib/test/unit/attribute.rb +7 -2
  11. data/lib/test/unit/autorunner.rb +79 -31
  12. data/lib/test/unit/code-snippet-fetcher.rb +7 -7
  13. data/lib/test/unit/collector/descendant.rb +1 -0
  14. data/lib/test/unit/collector/dir.rb +4 -2
  15. data/lib/test/unit/collector/load.rb +10 -13
  16. data/lib/test/unit/collector/objectspace.rb +1 -0
  17. data/lib/test/unit/collector.rb +31 -0
  18. data/lib/test/unit/color-scheme.rb +20 -2
  19. data/lib/test/unit/data-sets.rb +116 -0
  20. data/lib/test/unit/data.rb +121 -12
  21. data/lib/test/unit/diff.rb +2 -3
  22. data/lib/test/unit/fixture.rb +6 -0
  23. data/lib/test/unit/notification.rb +9 -7
  24. data/lib/test/unit/omission.rb +34 -31
  25. data/lib/test/unit/pending.rb +12 -11
  26. data/lib/test/unit/priority.rb +7 -3
  27. data/lib/test/unit/runner/console.rb +8 -0
  28. data/lib/test/unit/test-suite-creator.rb +22 -8
  29. data/lib/test/unit/testcase.rb +216 -146
  30. data/lib/test/unit/testsuite.rb +1 -1
  31. data/lib/test/unit/ui/console/testrunner.rb +92 -32
  32. data/lib/test/unit/util/memory-usage.rb +47 -0
  33. data/lib/test/unit/util/observable.rb +2 -2
  34. data/lib/test/unit/util/output.rb +5 -4
  35. data/lib/test/unit/util/procwrapper.rb +4 -4
  36. data/lib/test/unit/version.rb +1 -1
  37. data/lib/test/unit/warning.rb +3 -0
  38. data/lib/test/unit.rb +177 -161
  39. data/lib/test-unit.rb +2 -17
  40. metadata +13 -88
  41. data/GPL +0 -339
  42. data/LGPL +0 -502
  43. data/test/collector/test-descendant.rb +0 -182
  44. data/test/collector/test-load.rb +0 -442
  45. data/test/collector/test_dir.rb +0 -407
  46. data/test/collector/test_objectspace.rb +0 -102
  47. data/test/fixtures/header-label.csv +0 -3
  48. data/test/fixtures/header-label.tsv +0 -3
  49. data/test/fixtures/header.csv +0 -3
  50. data/test/fixtures/header.tsv +0 -3
  51. data/test/fixtures/no-header.csv +0 -2
  52. data/test/fixtures/no-header.tsv +0 -2
  53. data/test/fixtures/plus.csv +0 -3
  54. data/test/run-test.rb +0 -22
  55. data/test/test-assertions.rb +0 -2180
  56. data/test/test-attribute-matcher.rb +0 -38
  57. data/test/test-attribute.rb +0 -123
  58. data/test/test-code-snippet.rb +0 -37
  59. data/test/test-color-scheme.rb +0 -82
  60. data/test/test-color.rb +0 -47
  61. data/test/test-data.rb +0 -303
  62. data/test/test-diff.rb +0 -518
  63. data/test/test-emacs-runner.rb +0 -60
  64. data/test/test-error.rb +0 -26
  65. data/test/test-failure.rb +0 -33
  66. data/test/test-fault-location-detector.rb +0 -163
  67. data/test/test-fixture.rb +0 -713
  68. data/test/test-notification.rb +0 -33
  69. data/test/test-omission.rb +0 -81
  70. data/test/test-pending.rb +0 -70
  71. data/test/test-priority.rb +0 -173
  72. data/test/test-test-case.rb +0 -1278
  73. data/test/test-test-result.rb +0 -113
  74. data/test/test-test-suite-creator.rb +0 -97
  75. data/test/test-test-suite.rb +0 -151
  76. data/test/testunit-test-util.rb +0 -31
  77. data/test/ui/test_testrunmediator.rb +0 -20
  78. data/test/util/test-method-owner-finder.rb +0 -38
  79. data/test/util/test-output.rb +0 -11
  80. data/test/util/test_backtracefilter.rb +0 -52
  81. data/test/util/test_observable.rb +0 -102
  82. data/test/util/test_procwrapper.rb +0 -36
@@ -43,7 +43,11 @@ module Test
43
43
  kept_attributes = StringifyKeyHash.new
44
44
  @current_attributes.each do |attribute_name, attribute|
45
45
  attributes[attribute_name] = attribute[:value]
46
- kept_attributes[attribute_name] = attribute if attribute[:keep]
46
+ if attribute[:keep]
47
+ keep_hook = attribute[:keep_hook]
48
+ attribute = keep_hook.call(attribute) if keep_hook
49
+ kept_attributes[attribute_name] = attribute
50
+ end
47
51
  end
48
52
  set_attributes(name, attributes)
49
53
  @current_attributes = kept_attributes
@@ -200,7 +204,8 @@ module Test
200
204
  end
201
205
 
202
206
  @@attribute_observers = StringifyKeyHash.new
203
- def register_attribute_observer(attribute_name, observer=Proc.new)
207
+ def register_attribute_observer(attribute_name, observer=nil, &block)
208
+ observer ||= Proc.new(&block)
204
209
  @@attribute_observers[attribute_name] ||= []
205
210
  @@attribute_observers[attribute_name] << observer
206
211
  end
@@ -1,10 +1,10 @@
1
1
  require "English"
2
+ require "optparse"
2
3
 
3
4
  require "test/unit/color-scheme"
4
5
  require "test/unit/priority"
5
6
  require "test/unit/attribute-matcher"
6
7
  require "test/unit/testcase"
7
- require "optparse"
8
8
 
9
9
  module Test
10
10
  module Unit
@@ -15,7 +15,8 @@ module Test
15
15
  PREPARE_HOOKS = []
16
16
 
17
17
  class << self
18
- def register_runner(id, runner_builder=Proc.new)
18
+ def register_runner(id, runner_builder=nil, &block)
19
+ runner_builder ||= Proc.new(&block)
19
20
  RUNNERS[id] = runner_builder
20
21
  RUNNERS[id.to_s] = runner_builder
21
22
  end
@@ -33,7 +34,8 @@ module Test
33
34
  @@default_runner = id
34
35
  end
35
36
 
36
- def register_collector(id, collector_builder=Proc.new)
37
+ def register_collector(id, collector_builder=nil, &block)
38
+ collector_builder ||= Proc.new(&block)
37
39
  COLLECTORS[id] = collector_builder
38
40
  COLLECTORS[id.to_s] = collector_builder
39
41
  end
@@ -46,11 +48,13 @@ module Test
46
48
  ColorScheme[id] = scheme
47
49
  end
48
50
 
49
- def setup_option(option_builder=Proc.new)
51
+ def setup_option(option_builder=nil, &block)
52
+ option_builder ||= Proc.new(&block)
50
53
  ADDITIONAL_OPTIONS << option_builder
51
54
  end
52
55
 
53
- def prepare(hook=Proc.new)
56
+ def prepare(hook=nil, &block)
57
+ hook ||= Proc.new(&block)
54
58
  PREPARE_HOOKS << hook
55
59
  end
56
60
 
@@ -139,7 +143,8 @@ module Test
139
143
  attr_accessor :default_test_paths
140
144
  attr_accessor :pattern, :exclude, :base, :workdir
141
145
  attr_accessor :color_scheme, :listeners
142
- attr_writer :stop_on_failuere
146
+ attr_writer :stop_on_failure
147
+ attr_writer :debug_on_failure
143
148
  attr_writer :runner, :collector
144
149
 
145
150
  def initialize(standalone)
@@ -155,6 +160,7 @@ module Test
155
160
  @workdir = nil
156
161
  @listeners = []
157
162
  @stop_on_failure = false
163
+ @debug_on_failure = false
158
164
  config_file = "test-unit.yml"
159
165
  if File.exist?(config_file)
160
166
  load_config(config_file)
@@ -168,6 +174,10 @@ module Test
168
174
  @stop_on_failure
169
175
  end
170
176
 
177
+ def debug_on_failure?
178
+ @debug_on_failure
179
+ end
180
+
171
181
  def prepare
172
182
  PREPARE_HOOKS.each do |handler|
173
183
  handler.call(self)
@@ -242,54 +252,61 @@ module Test
242
252
 
243
253
  o.on("-n", "--name=NAME", String,
244
254
  "Runs tests matching NAME.",
245
- "Use '/PATTERN/' for NAME to use regular expression.") do |name|
246
- name = (%r{\A/(.*)/\Z} =~ name ? Regexp.new($1) : name)
255
+ "Use '/PATTERN/' for NAME to use regular expression.",
256
+ "Regular expression accepts options.",
257
+ "Example: '/taRget/i' matches 'target' and 'TARGET'") do |name|
258
+ name = prepare_name(name)
247
259
  @filters << lambda do |test|
248
- return true if name === test.method_name
249
- return true if name === test.local_name
250
- false
260
+ match_test_name(test, name)
251
261
  end
252
262
  end
253
263
 
254
264
  o.on("--ignore-name=NAME", String,
255
265
  "Ignores tests matching NAME.",
256
- "Use '/PATTERN/' for NAME to use regular expression.") do |n|
257
- n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
258
- case n
259
- when Regexp
260
- @filters << proc {|t| n =~ t.method_name ? false : true}
261
- else
262
- @filters << proc {|t| n != t.method_name}
266
+ "Use '/PATTERN/' for NAME to use regular expression.",
267
+ "Regular expression accepts options.",
268
+ "Example: '/taRget/i' matches 'target' and 'TARGET'") do |name|
269
+ name = prepare_name(name)
270
+ @filters << lambda do |test|
271
+ not match_test_name(test, name)
263
272
  end
264
273
  end
265
274
 
266
275
  o.on("-t", "--testcase=TESTCASE", String,
267
276
  "Runs tests in TestCases matching TESTCASE.",
268
- "Use '/PATTERN/' for TESTCASE to use regular expression.") do |n|
269
- n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
277
+ "Use '/PATTERN/' for TESTCASE to use regular expression.",
278
+ "Regular expression accepts options.",
279
+ "Example: '/taRget/i' matches 'target' and 'TARGET'") do |name|
280
+ name = prepare_name(name)
270
281
  @filters << lambda do |test|
271
- match_test_case_name(test, n)
282
+ match_test_case_name(test, name)
272
283
  end
273
284
  end
274
285
 
275
286
  o.on("--ignore-testcase=TESTCASE", String,
276
287
  "Ignores tests in TestCases matching TESTCASE.",
277
- "Use '/PATTERN/' for TESTCASE to use regular expression.") do |n|
278
- n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
288
+ "Use '/PATTERN/' for TESTCASE to use regular expression.",
289
+ "Regular expression accepts options.",
290
+ "Example: '/taRget/i' matches 'target' and 'TARGET'") do |name|
291
+ name = prepare_name(name)
279
292
  @filters << lambda do |test|
280
- not match_test_case_name(test, n)
293
+ not match_test_case_name(test, name)
281
294
  end
282
295
  end
283
296
 
284
297
  o.on("--location=LOCATION", String,
285
298
  "Runs tests that defined in LOCATION.",
286
- "LOCATION is one of PATH:LINE, PATH or LINE") do |location|
287
- if /\A\d+\z/ =~ location
299
+ "LOCATION is one of PATH:LINE, PATH or LINE.") do |location|
300
+ case location
301
+ when /\A(\d+)\z/
288
302
  path = nil
289
- line = location.to_i
303
+ line = $1.to_i
304
+ when /:(\d+)\z/
305
+ path = $PREMATCH
306
+ line = $1.to_i
290
307
  else
291
- path, line, = location.split(/:(\d+)/, 2)
292
- line = line.to_i unless line.nil?
308
+ path = location
309
+ line = nil
293
310
  end
294
311
  add_location_filter(path, line)
295
312
  end
@@ -345,7 +362,7 @@ module Test
345
362
  end
346
363
 
347
364
  o.on("--config=FILE",
348
- "Use YAML fomat FILE content as configuration file.") do |file|
365
+ "Use YAML format FILE content as configuration file.") do |file|
349
366
  load_config(file)
350
367
  end
351
368
 
@@ -370,6 +387,12 @@ module Test
370
387
  @stop_on_failure = boolean
371
388
  end
372
389
 
390
+ o.on("--[no-]debug-on-failure",
391
+ "Run debugger if available on failure",
392
+ "(#{AssertionFailedError.debug_on_failure?})") do |boolean|
393
+ AssertionFailedError.debug_on_failure = boolean
394
+ end
395
+
373
396
  ADDITIONAL_OPTIONS.each do |option_builder|
374
397
  option_builder.call(self, o)
375
398
  end
@@ -452,7 +475,7 @@ module Test
452
475
  if key == :arguments
453
476
  @default_arguments.concat(value.split)
454
477
  else
455
- runner_options[key.to_sym] = value
478
+ runner_options[key] = value
456
479
  end
457
480
  end
458
481
  @runner_options = @runner_options.merge(runner_options)
@@ -492,6 +515,31 @@ module Test
492
515
  end
493
516
  end
494
517
 
518
+ def prepare_name(name)
519
+ case name
520
+ when /\A\/(.*)\/([imx]*)\z/
521
+ pattern = $1
522
+ options_raw = $2
523
+ options = 0
524
+ options |= Regexp::IGNORECASE if options_raw.include?("i")
525
+ options |= Regexp::MULTILINE if options_raw.include?("m")
526
+ options |= Regexp::EXTENDED if options_raw.include?("x")
527
+ Regexp.new(pattern, options)
528
+ else
529
+ name
530
+ end
531
+ end
532
+
533
+ def match_test_name(test, pattern)
534
+ return true if pattern === test.method_name
535
+ return true if pattern === test.local_name
536
+ if pattern.is_a?(String)
537
+ return true if pattern === "#{test.class}##{test.method_name}"
538
+ return true if pattern === "#{test.class}##{test.local_name}"
539
+ end
540
+ false
541
+ end
542
+
495
543
  def match_test_case_name(test, pattern)
496
544
  test.class.ancestors.each do |test_class|
497
545
  break if test_class == TestCase
@@ -26,16 +26,16 @@ module Test
26
26
  def read_source(path)
27
27
  return nil unless File.exist?(path)
28
28
  lines = []
29
- File.open(path) do |file|
29
+ File.open(path, "rb") do |file|
30
30
  first_line = file.gets
31
31
  break if first_line.nil?
32
- encoding = detect_encoding(first_line)
33
- if encoding
34
- first_line.force_encoding(encoding)
35
- file.set_encoding(encoding, encoding)
36
- end
32
+ encoding = detect_encoding(first_line) || Encoding::UTF_8
33
+ first_line.force_encoding(encoding)
37
34
  lines << first_line
38
- lines.concat(file.readlines)
35
+ file.each_line do |line|
36
+ line.force_encoding(encoding)
37
+ lines << line
38
+ end
39
39
  end
40
40
  lines
41
41
  end
@@ -11,6 +11,7 @@ module Test
11
11
  def collect(name=NAME)
12
12
  suite = TestSuite.new(name)
13
13
  add_test_cases(suite, TestCase::DESCENDANTS)
14
+ adjust_ractor_tests(suite)
14
15
  suite
15
16
  end
16
17
  end
@@ -25,9 +25,9 @@ module Test
25
25
  basedir = @base
26
26
  $:.push(basedir) if basedir
27
27
  if(from.empty?)
28
- recursive_collect('.', find_test_cases)
28
+ suite = recursive_collect('.', find_test_cases)
29
29
  elsif(from.size == 1)
30
- recursive_collect(from.first, find_test_cases)
30
+ suite = recursive_collect(from.first, find_test_cases)
31
31
  else
32
32
  suites = []
33
33
  from.each do |f|
@@ -38,6 +38,8 @@ module Test
38
38
  sort(suites).each{|s| suite << s}
39
39
  suite
40
40
  end
41
+ adjust_ractor_tests(suite)
42
+ suite
41
43
  ensure
42
44
  $:.delete_at($:.rindex(basedir)) if basedir
43
45
  end
@@ -60,6 +60,8 @@ module Test
60
60
  test_suite = test_suites.first
61
61
  end
62
62
 
63
+ adjust_ractor_tests(test_suite)
64
+
63
65
  test_suite
64
66
  end
65
67
  end
@@ -111,7 +113,7 @@ module Test
111
113
  return if @program_file == expanded_path.to_s
112
114
  add_load_path(expanded_path.dirname) do
113
115
  begin
114
- require(path.basename.to_s)
116
+ require(expanded_path.to_s)
115
117
  rescue LoadError
116
118
  @require_failed_infos << {:path => expanded_path, :exception => $!}
117
119
  end
@@ -131,8 +133,6 @@ module Test
131
133
  return yield if path.nil?
132
134
 
133
135
  path = path.to_s
134
- return yield if $LOAD_PATH.index(path)
135
-
136
136
  begin
137
137
  $LOAD_PATH.unshift(path)
138
138
  yield
@@ -166,11 +166,11 @@ module Test
166
166
  return if @require_failed_infos.empty?
167
167
 
168
168
  require_failed_infos = @require_failed_infos
169
- require_failed_omissions = Class.new(Test::Unit::TestCase)
170
- require_failed_omissions.class_eval do
169
+ require_failed_errors = Class.new(Test::Unit::TestCase)
170
+ require_failed_errors.class_eval do
171
171
  class << self
172
172
  def name
173
- "RequireFailedOmissions"
173
+ "RequireFailedErrors"
174
174
  end
175
175
  end
176
176
 
@@ -180,21 +180,18 @@ module Test
180
180
  normalized_path = normalized_path.gsub(/\A_+/, '')
181
181
  exception = info[:exception]
182
182
  define_method("test_require_#{normalized_path}") do
183
- @require_failed_exception = exception
184
- omit("failed to load: <#{path}>: <#{exception.message}>")
183
+ raise(exception.class,
184
+ "failed to load <#{path}>: #{exception.message}",
185
+ exception.backtrace)
185
186
  end
186
187
  end
187
188
 
188
189
  def priority
189
190
  100
190
191
  end
191
-
192
- def filter_backtrace(location)
193
- super(@require_failed_exception.backtrace)
194
- end
195
192
  end
196
193
 
197
- add_suite(test_suites, require_failed_omissions.suite)
194
+ add_suite(test_suites, require_failed_errors.suite)
198
195
  end
199
196
  end
200
197
  end
@@ -26,6 +26,7 @@ module Test
26
26
  end
27
27
  end
28
28
  sort(sub_suites).each{|s| suite << s}
29
+ adjust_ractor_tests(suite)
29
30
  suite
30
31
  end
31
32
  end
@@ -68,6 +68,37 @@ module Test
68
68
  suite << sub_suite
69
69
  end
70
70
  end
71
+
72
+ def adjust_ractor_tests(suite)
73
+ return if suite.nil?
74
+ ractor_suites = extract_ractor_tests(suite)
75
+ ractor_suites.each do |ractor_suite|
76
+ suite << ractor_suite
77
+ end
78
+ end
79
+
80
+ def extract_ractor_tests(suite)
81
+ ractor_suites = []
82
+ ractor_tests = []
83
+ suite.tests.each do |test|
84
+ case test
85
+ when TestSuite
86
+ ractor_suites.concat(extract_ractor_tests(test))
87
+ else
88
+ next unless test[:ractor]
89
+ ractor_tests << test
90
+ end
91
+ end
92
+ unless ractor_tests.empty?
93
+ suite.delete_tests(ractor_tests)
94
+ ractor_suite = TestSuite.new(suite.name, suite.test_case)
95
+ ractor_tests.each do |ractor_test|
96
+ ractor_suite << ractor_test
97
+ end
98
+ ractor_suites << ractor_suite
99
+ end
100
+ ractor_suites
101
+ end
71
102
  end
72
103
  end
73
104
  end
@@ -5,9 +5,25 @@ module Test
5
5
  class ColorScheme
6
6
  include Enumerable
7
7
 
8
+ TERM_256 = /
9
+ [+-]256color|
10
+ \A(?:
11
+ alacritty|
12
+ iTerm\s?\d*\.app|
13
+ kitty|
14
+ mintty|
15
+ ms-terminal|
16
+ nsterm-build\d+|
17
+ nsterm|
18
+ terminator|
19
+ terminology(?:-[0-9.]+)?|
20
+ termite|
21
+ vscode
22
+ )\z/x
23
+
8
24
  class << self
9
25
  def default
10
- if available_colors == 256
26
+ if available_colors >= 256
11
27
  default_for_256_colors
12
28
  else
13
29
  default_for_8_colors
@@ -140,7 +156,9 @@ module Test
140
156
 
141
157
  def guess_available_colors_from_term_env
142
158
  case ENV["TERM"]
143
- when /-256color\z/
159
+ when /[+-]direct/
160
+ 2**24
161
+ when TERM_256
144
162
  256
145
163
  else
146
164
  nil
@@ -0,0 +1,116 @@
1
+ module Test
2
+ module Unit
3
+ class DataSets
4
+ def initialize
5
+ @variables = []
6
+ @procs = []
7
+ @value_sets = []
8
+ end
9
+
10
+ def add(data_set, options=nil)
11
+ options ||= {}
12
+ if data_set.respond_to?(:call)
13
+ @procs << [data_set, options]
14
+ elsif data_set.is_a?(Array)
15
+ @variables << [data_set, options]
16
+ else
17
+ @value_sets << [data_set, options]
18
+ end
19
+ end
20
+
21
+ def <<(data_set)
22
+ add(data_set)
23
+ end
24
+
25
+ def keep
26
+ new_data_sets = self.class.new
27
+ all_data_sets = Enumerator.new do |yielder|
28
+ block = lambda do |(data_set, options)|
29
+ yielder << [data_set, options]
30
+ end
31
+ @procs.each(&block)
32
+ @variables.each(&block)
33
+ @value_sets.each(&block)
34
+ end
35
+ all_data_sets.each do |data_set, options|
36
+ next if options.nil?
37
+ next unless options[:keep]
38
+ new_data_sets.add(data_set, options)
39
+ end
40
+ new_data_sets
41
+ end
42
+
43
+ def each
44
+ variables = @variables
45
+ value_sets = @value_sets
46
+ @procs.each do |proc, options|
47
+ data_set = proc.call
48
+ case data_set
49
+ when Array
50
+ variables += [[data_set, options]]
51
+ else
52
+ value_sets += [[data_set, options]]
53
+ end
54
+ end
55
+
56
+ value_sets.each do |values, _options|
57
+ values.each do |label, data|
58
+ yield(label, data)
59
+ end
60
+ end
61
+
62
+ each_pattern(variables) do |label, data|
63
+ yield(label, data)
64
+ end
65
+ end
66
+
67
+ def ==(other)
68
+ @variables == other.instance_variable_get(:@variables) and
69
+ @procs == other.instance_variable_get(:@procs) and
70
+ @value_sets == other.instance_variable_get(:@value_sets)
71
+ end
72
+
73
+ def eql?(other)
74
+ self == other
75
+ end
76
+
77
+ def hash
78
+ [@variables, @procs, @value_sets].hash
79
+ end
80
+
81
+ private
82
+ def each_pattern(variables)
83
+ grouped_variables = variables.group_by do |_, options|
84
+ options[:group]
85
+ end
86
+ grouped_variables.each do |group, group_variables|
87
+ each_raw_pattern(group_variables) do |cell|
88
+ label = String.new
89
+ label << "group: #{group.inspect}" unless group.nil?
90
+ data = {}
91
+ cell.each do |variable, pattern|
92
+ label << ", " unless label.empty?
93
+ label << "#{variable}: #{pattern.inspect}"
94
+ data[variable] = pattern
95
+ end
96
+ yield(label, data)
97
+ end
98
+ end
99
+ end
100
+
101
+ def each_raw_pattern(variables, &block)
102
+ return if variables.empty?
103
+
104
+ sorted_variables = variables.sort_by do |(variable, _), _|
105
+ variable
106
+ end
107
+ all_patterns = sorted_variables.collect do |(variable, patterns), _|
108
+ patterns.collect do |pattern|
109
+ [variable, pattern]
110
+ end
111
+ end
112
+ all_patterns[0].product(*all_patterns[1..-1], &block)
113
+ end
114
+ end
115
+ end
116
+ end