Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 6e81a08

Browse files
Use recursive strategy for bisection
To speed up bisection of complex dependent failures, a recursive bisection strategy is introduced in place of permutation searching. The strategy works as follows: 1. Split the candidate set into two slices 2. Test each slice with the expected failure(s) 3. If either slice can be ignored: - recurse on the other slice 4. If neither slice can be ignored, for each slice: - recurse on slice, retaining other slice for test runs Graphically, this looks somewhat like: F = expected failure P = unexpected pass X = culprit - = innocent [ ] = current bisection scope 1 2 3 4 5 6 7 8 9 [- - X - - X - -] F # Initial run [ - X - -] F # RHS0 implicated [- - X - ] F # LHS0 implicated => (1) recurse on LHS0, fixing RHS0 [ X -]- X - - F # RHS1 implicated [- - ]- X - - P # LHS1 can be ignored => (2) recurse on RHS, retaining fixed set [ -]- X - - P # LHS2 implicated [X ]- X - - F # RHS2 can be ignored => (3) recurse on LHS2 => terminate as candidates.length == 1 => (1) recurse on RHS0, fixing LHS0 - - X -[ - -] P # LHS3 implicated - - X -[- X ] F # RHS3 can be ignored => (4) recurse on LHS3 - - X -[ X] F # LHS4 can be ignored - - X -[- ] P # RHS4 implicated => (5) recurse on RHS5 => terminate as candidates.length == 1 => return LHS2 + RHS5 = [3, 6]
1 parent 9007b0e commit 6e81a08

File tree

3 files changed

+35
-125
lines changed

3 files changed

+35
-125
lines changed

lib/rspec/core/bisect/example_minimizer.rb

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
RSpec::Support.require_rspec_core "bisect/subset_enumerator"
2-
31
module RSpec
42
module Core
53
module Bisect
@@ -18,20 +16,46 @@ def initialize(runner, reporter)
1816
def find_minimal_repro
1917
prep
2018

21-
self.remaining_ids = non_failing_example_ids
19+
self.remaining_ids, duration = track_duration do
20+
bisect_over(non_failing_example_ids, [])
21+
end
2222

23-
each_bisect_round do |subsets|
24-
ids_to_ignore = subsets.find do |ids|
25-
get_expected_failures_for?(remaining_ids - ids)
26-
end
23+
notify(:bisect_complete, :round => 10, :duration => duration,
24+
:original_non_failing_count => non_failing_example_ids.size,
25+
:remaining_count => remaining_ids.size)
2726

28-
next :done unless ids_to_ignore
27+
remaining_ids + failed_example_ids
28+
end
2929

30-
self.remaining_ids -= ids_to_ignore
31-
notify(:bisect_ignoring_ids, :ids_to_ignore => ids_to_ignore, :remaining_ids => remaining_ids)
30+
def bisect_over(candidate_ids, fixed)
31+
return candidate_ids if candidate_ids.length == 1
32+
slice_size = (candidate_ids.length / 2.0).ceil
33+
slices = candidate_ids.each_slice(slice_size).to_a
34+
35+
notify(
36+
:bisect_round_started,
37+
:round => 10,
38+
:subset_size => slice_size,
39+
:remaining_count => candidate_ids.size
40+
)
41+
42+
ids_to_ignore = slices.find do |ids|
43+
get_expected_failures_for?(candidate_ids - ids + fixed)
3244
end
3345

34-
currently_needed_ids
46+
if ids_to_ignore
47+
self.remaining_ids = candidate_ids - ids_to_ignore
48+
notify(
49+
:bisect_ignoring_ids,
50+
:ids_to_ignore => ids_to_ignore,
51+
:remaining_ids => remaining_ids
52+
)
53+
bisect_over(remaining_ids, fixed)
54+
else
55+
slices.permutation(2).map do |slice, other_slice|
56+
bisect_over(slice, fixed + other_slice)
57+
end.flatten(1)
58+
end
3559
end
3660

3761
def currently_needed_ids
@@ -79,34 +103,6 @@ def get_expected_failures_for?(ids)
79103
(failed_example_ids & results.failed_example_ids) == failed_example_ids
80104
end
81105

82-
INFINITY = (1.0 / 0) # 1.8.7 doesn't define Float::INFINITY so we define our own...
83-
84-
def each_bisect_round(&block)
85-
last_round, duration = track_duration do
86-
1.upto(INFINITY) do |round|
87-
break if :done == bisect_round(round, &block)
88-
end
89-
end
90-
91-
notify(:bisect_complete, :round => last_round, :duration => duration,
92-
:original_non_failing_count => non_failing_example_ids.size,
93-
:remaining_count => remaining_ids.size)
94-
end
95-
96-
def bisect_round(round)
97-
value, duration = track_duration do
98-
subsets = SubsetEnumerator.new(remaining_ids)
99-
notify(:bisect_round_started, :round => round,
100-
:subset_size => subsets.subset_size,
101-
:remaining_count => remaining_ids.size)
102-
103-
yield subsets
104-
end
105-
106-
notify(:bisect_round_finished, :duration => duration, :round => round)
107-
value
108-
end
109-
110106
def track_duration
111107
start = ::RSpec::Core::Time.now
112108
[yield, ::RSpec::Core::Time.now - start]

lib/rspec/core/bisect/subset_enumerator.rb

Lines changed: 0 additions & 39 deletions
This file was deleted.

spec/rspec/core/bisect/subset_enumerator_spec.rb

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)