rspec-search-and-destroy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format doc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rspec-search-and-destroy.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Jake Goulding
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ RSpec Search-and-Destroy (SAD) runs your RSpec suites to automatically
2
+ find the root causes of test ordering bugs.
3
+
4
+ # Background
5
+
6
+ Ideally, tests are independent of each other, but sometimes global
7
+ state can leak out of one test and cause another test to fail. When
8
+ you have many tests, these types of failures can be insidious to
9
+ debug.
10
+
11
+ # Usage
12
+
13
+ First, add the appropriate hooks in your `spec_helper.rb`:
14
+
15
+ ```ruby
16
+ require 'rspec-sad'
17
+
18
+ RSpec.configure do |config|
19
+ RSpecSearchAndDestroy.configure(config)
20
+ end
21
+ ```
22
+
23
+ Then run the driver program:
24
+
25
+ ```
26
+ # Search and destroy mode
27
+ rspec-sad
28
+
29
+ # If you have a particular ordering that creates issues
30
+ SPEC_OPTIONS="--seed 12345" rspec-sad
31
+ ```
32
+
33
+ # Search and destroy mode
34
+
35
+ In the search phase, SAD will run your test suite until a test failure
36
+ occurs, then switch to the destroy phase. In the destroy phase, the
37
+ contributing tests are narrowed down until a single test is found that
38
+ causes the failure.
39
+
40
+ # Caveats
41
+
42
+ RSpec SAD requires that your test suite not have any flaky tests. Any
43
+ intermittently failing tests will cause false positives or false
44
+ negatives. In this case, the results will not provide any useful
45
+ information.
46
+
47
+ # How it works
48
+
49
+ During the search phase, your test suite will be run once to get the
50
+ set of tests that could contribute to the test failure. The order of
51
+ tests will be saved and passed off to the destroy phase.
52
+
53
+ During each step of the destroy phase, half of the remaining tests
54
+ will be disabled. If the test continues to fail, these disabled tests
55
+ can be ignored. If the test stops failing, then the currently enabled
56
+ tests can be enabled.
57
+
58
+ ## Implementation notes
59
+
60
+ The driver process runs RSpec as a subprocess. The two processes
61
+ communicate using environment variables and files. The driver program
62
+ specifies a set of tests to run, and RSpec reports the results back to
63
+ the driver.
64
+
65
+ The test ordering and filtering is done via a custom
66
+ `config.order_examples` block, and the results are saved using a
67
+ custom formatter.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/rspec-sad ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << "$PWD/../lib/"
4
+ require 'rspec-sad'
5
+
6
+ include RSpecSearchAndDestroy
7
+
8
+ class TTYOutput
9
+ def found(causal_example, failed_example)
10
+ puts "Culprit found"
11
+ puts "Run #{causal_example[:location]}"
12
+ puts "Before #{failed_example[:location]}"
13
+ end
14
+ end
15
+
16
+ output = TTYOutput.new
17
+ selector = BinaryChopExampleSelector.new
18
+ driver = RSpecDriver.new
19
+
20
+ driver.initial_run
21
+ results = driver.load_run_results
22
+ raise "no failures found" unless results.failed?
23
+
24
+ bisector = Bisector.new(output, selector, driver)
25
+ bisector.bisect(results.causal_examples, results.failed_example)
26
+
27
+ driver.cleanup
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Tests that fail when run in a specific order" do
4
+ it "leaves bad global state" do
5
+ $fail_next = true
6
+ expect($fail_next).to be_true
7
+ end
8
+
9
+ it "just takes up space" do
10
+ expect(true).to be_true
11
+ end
12
+
13
+ it "fails when run last" do
14
+ expect($fail_next).to be_false
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ require 'rspec-sad'
2
+
3
+ RSpec.configure do |config|
4
+ config.treat_symbols_as_metadata_keys_with_true_values = true
5
+ config.run_all_when_everything_filtered = true
6
+ config.filter_run :focus
7
+
8
+ config.color = true
9
+ config.formatter = 'documentation'
10
+
11
+ # For this example, run in an order that is guaranteed to be bad
12
+ config.order = 'default'
13
+
14
+ RSpecSearchAndDestroy.configure(config)
15
+ end
data/lib/rspec-sad.rb ADDED
@@ -0,0 +1,157 @@
1
+ require "rspec-search-and-destroy/version"
2
+ require "rspec-search-and-destroy/bisector"
3
+ require "rspec-search-and-destroy/binary_chop_example_selector"
4
+
5
+ require 'rspec'
6
+ require 'rspec/core/formatters/base_formatter'
7
+
8
+ module RSpecSearchAndDestroy
9
+ def self.configure(config)
10
+ config.add_formatter(OrderFormatter)
11
+
12
+ source = LocationSource.new
13
+ if source.enabled?
14
+ ordering = ReorderAndFilter.new(source)
15
+ config.order_examples(&ordering.block)
16
+ end
17
+ end
18
+
19
+ class OrderFormatter < RSpec::Core::Formatters::BaseFormatter
20
+ def stop
21
+ File.open(filename, 'wb') do |f|
22
+ Marshal.dump(results, f)
23
+ end
24
+
25
+ super
26
+ end
27
+
28
+ private
29
+
30
+ def filename
31
+ ENV['RSPEC_SAD_RESULTS'] or raise "No result filename provided"
32
+ end
33
+
34
+ def results
35
+ examples.map do |e|
36
+ {
37
+ location: e.location,
38
+ failed: !!e.exception
39
+ }
40
+ end
41
+ end
42
+ end
43
+
44
+ class LocationSource
45
+ def enabled?
46
+ filename && File.exist?(filename)
47
+ end
48
+
49
+ def example_locations_to_run
50
+ raise "LocationSource is not currently enabled" unless enabled?
51
+
52
+ File.open(filename, 'rb') do |f|
53
+ Marshal.load(f)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def filename
60
+ ENV['RSPEC_SAD_EXAMPLES']
61
+ end
62
+ end
63
+
64
+ class ReorderAndFilter
65
+ attr_reader :source
66
+
67
+ def initialize(source)
68
+ @source = source
69
+ end
70
+
71
+ def block
72
+ lambda do |examples|
73
+ locations = source.example_locations_to_run
74
+
75
+ enabled_examples = examples.select do |e|
76
+ locations.include? e.location
77
+ end
78
+
79
+ enabled_examples.sort_by do |e|
80
+ locations.index(e.location)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ class RSpecResults
87
+ attr_reader :results
88
+
89
+ def initialize(results)
90
+ @results = results
91
+ end
92
+
93
+ def causal_examples
94
+ results.slice(0, failure_index)
95
+ end
96
+
97
+ def failed_example
98
+ results[failure_index]
99
+ end
100
+
101
+ def failed?
102
+ results.find {|result| result[:failed] }
103
+ end
104
+
105
+ private
106
+
107
+ def failure_index
108
+ @failure_index ||= results.find_index { |r| r[:failed] }
109
+ end
110
+ end
111
+
112
+ class RSpecDriver
113
+ RESULT_FILE = '/tmp/example-results'
114
+ EXAMPLE_FILE = '/tmp/examples-to-run'
115
+
116
+ def load_run_results
117
+ File.open(RESULT_FILE, 'rb') do |f|
118
+ RSpecResults.new(Marshal.load(f))
119
+ end
120
+ end
121
+
122
+ def initial_run
123
+ cleanup
124
+ run_rspec
125
+ end
126
+
127
+ def run_examples(examples)
128
+ locations = examples.map {|x| x[:location]}
129
+
130
+ File.open(EXAMPLE_FILE, 'wb') do |f|
131
+ Marshal.dump(locations, f)
132
+ end
133
+
134
+ run_rspec
135
+ end
136
+
137
+ def cleanup
138
+ [EXAMPLE_FILE, RESULT_FILE].each do |fname|
139
+ File.delete(fname) if File.exist? fname
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def run_rspec
146
+ env = {
147
+ "RSPEC_SAD_EXAMPLES" => EXAMPLE_FILE,
148
+ "RSPEC_SAD_RESULTS" => RESULT_FILE
149
+ }
150
+ cmd = "rspec"
151
+
152
+ success = system(env, cmd)
153
+
154
+ raise "unable to run command: #{cmd}" if success.nil?
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,12 @@
1
+ module RSpecSearchAndDestroy
2
+ class BinaryChopExampleSelector
3
+ def enable_set(examples)
4
+ half_size = examples.size / 2
5
+
6
+ enabled = examples.slice(0, half_size)
7
+ disabled = examples.slice(half_size..-1)
8
+
9
+ [enabled, disabled]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module RSpecSearchAndDestroy
2
+ class Bisector
3
+ attr_reader :output, :selector, :executor
4
+
5
+ def initialize(output, selector, executor)
6
+ @output = output
7
+ @selector = selector
8
+ @executor = executor
9
+ end
10
+
11
+ def bisect(causal_examples, failed_example)
12
+ case causal_examples.size
13
+ when 1
14
+ output.found(causal_examples.first, failed_example)
15
+ return
16
+ when 0
17
+ output.unable_to_bisect
18
+ return
19
+ end
20
+
21
+ enabled, disabled = selector.enable_set(causal_examples)
22
+
23
+ to_run = enabled + [failed_example]
24
+ executor.run_examples(to_run)
25
+
26
+ results = executor.load_run_results
27
+
28
+ if results.failed?
29
+ bisect(enabled, failed_example)
30
+ else
31
+ bisect(disabled, failed_example)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module RSpecSearchAndDestroy
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rspec-search-and-destroy/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rspec-search-and-destroy"
8
+ gem.version = RSpecSearchAndDestroy::VERSION
9
+ gem.authors = ["Jake Goulding"]
10
+ gem.email = ["jake.goulding@gmail.com"]
11
+ gem.description = %q{Finds RSpec test ordering bugs }
12
+ gem.summary = %q{Finds RSpec test ordering bugs }
13
+ gem.homepage = "https://github.com/shepmaster/rspec-search-and-destroy"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency('rspec', '>= 2.14.0')
21
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'rspec-search-and-destroy/binary_chop_example_selector'
3
+
4
+ describe RSpecSearchAndDestroy::BinaryChopExampleSelector do
5
+ subject(:selector) { RSpecSearchAndDestroy::BinaryChopExampleSelector.new }
6
+
7
+ context "when there is an even number of examples" do
8
+ let(:examples) { 4.times.map {|i| double("example #{i}") } }
9
+
10
+ it "includes all the examples" do
11
+ enabled, disabled = selector.enable_set(examples)
12
+
13
+ expect(enabled + disabled).to eql(examples)
14
+ end
15
+
16
+ it "enables exactly half of the examples" do
17
+ enabled, disabled = selector.enable_set(examples)
18
+
19
+ expect(enabled.size).to eql(examples.size / 2)
20
+ end
21
+
22
+ it "disables exactly half of the examples" do
23
+ enabled, disabled = selector.enable_set(examples)
24
+
25
+ expect(disabled.size).to eql(examples.size / 2)
26
+ end
27
+ end
28
+
29
+ context "when there is an odd number of examples" do
30
+ let(:examples) { 3.times.map {|i| double("example #{i}") } }
31
+
32
+ it "includes all the examples" do
33
+ enabled, disabled = selector.enable_set(examples)
34
+
35
+ expect(enabled + disabled).to eql(examples)
36
+ end
37
+
38
+ it "enables about half of the examples" do
39
+ enabled, disabled = selector.enable_set(examples)
40
+
41
+ expect(enabled.size).to be_within(1).of(examples.size / 2)
42
+ end
43
+
44
+ it "disables about half of the examples" do
45
+ enabled, disabled = selector.enable_set(examples)
46
+
47
+ expect(disabled.size).to be_within(1).of(examples.size / 2)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+ require 'rspec-search-and-destroy/bisector.rb'
3
+
4
+ describe RSpecSearchAndDestroy::Bisector do
5
+ subject(:bisector) do
6
+ RSpecSearchAndDestroy::Bisector.new(output, selector, executor)
7
+ end
8
+
9
+ let(:output) { double("output") }
10
+ let(:selector) { double("selector") }
11
+ let(:executor) { double("executor") }
12
+
13
+ let(:failing_example) { double("failing example") }
14
+
15
+ context "when there is only one potential test left" do
16
+ let(:potential_causes) { [double("bad example")] }
17
+
18
+ it "reports that it has finished" do
19
+ expect(output).to receive(:found)
20
+ .with(potential_causes.first, failing_example)
21
+
22
+ bisector.bisect(potential_causes, failing_example)
23
+ end
24
+ end
25
+
26
+ context "when there are no potential tests left" do
27
+ let(:potential_causes) { [] }
28
+
29
+ it "reports the error condition" do
30
+ expect(output).to receive(:unable_to_bisect)
31
+
32
+ bisector.bisect(potential_causes, failing_example)
33
+ end
34
+ end
35
+
36
+ context "when executing examples" do
37
+ let(:potential_causes) { 2.times.map {|i| double("potential example #{i}")} }
38
+ let(:enabled_examples) { [potential_causes.first] }
39
+ let(:disabled_examples) { [potential_causes.last] }
40
+
41
+ before do
42
+ selector.stub(:enable_set)
43
+ .and_return([enabled_examples, disabled_examples])
44
+
45
+ # These stubs do not provide useful feedback for these tests,
46
+ # they are just needed to prevent failure
47
+ results = double("results", :failed? => true)
48
+ executor.stub(:load_run_results).and_return(results)
49
+ output.stub(:found)
50
+ end
51
+
52
+ it "executes enabled examples" do
53
+ expect(executor).to receive(:run_examples) do |enabled_examples|
54
+ expect(enabled_examples).to include(*enabled_examples)
55
+ end
56
+
57
+ bisector.bisect(potential_causes, failing_example)
58
+ end
59
+
60
+ it "does not execute disabled examples" do
61
+ expect(executor).to receive(:run_examples) do |enabled_examples|
62
+ expect(enabled_examples).to_not include(*disabled_examples)
63
+ end
64
+
65
+ bisector.bisect(potential_causes, failing_example)
66
+ end
67
+
68
+ it "executes the failing test last" do
69
+ expect(executor).to receive(:run_examples) do |enabled_examples|
70
+ expect(enabled_examples.last).to eql(failing_example)
71
+ end
72
+
73
+ bisector.bisect(potential_causes, failing_example)
74
+ end
75
+
76
+ context "when the executed tests fail" do
77
+ before do
78
+ executor.stub(:run_examples)
79
+ results = double("results", :failed? => true)
80
+ executor.stub(:load_run_results).and_return(results)
81
+ end
82
+
83
+ it "continues exploring the enabled tests" do
84
+ expect(output).to receive(:found)
85
+ .with(enabled_examples.first, failing_example)
86
+
87
+ bisector.bisect(potential_causes, failing_example)
88
+ end
89
+ end
90
+
91
+ context "when the executed tests do not fail" do
92
+ before do
93
+ executor.stub(:run_examples)
94
+ results = double("results", :failed? => false)
95
+ executor.stub(:load_run_results).and_return(results)
96
+ end
97
+
98
+ it "continues exploring the disabled tests" do
99
+ expect(output).to receive(:found)
100
+ .with(disabled_examples.first, failing_example)
101
+
102
+ bisector.bisect(potential_causes, failing_example)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'rspec-sad'
3
+
4
+ describe RSpecSearchAndDestroy::ReorderAndFilter do
5
+ subject(:ordering_block) do
6
+ RSpecSearchAndDestroy::ReorderAndFilter.new(location_source).block
7
+ end
8
+
9
+ let(:location_source) { double("location source") }
10
+
11
+ context "when filtering examples" do
12
+ let(:examples) do
13
+ [double('example 1', location: 'location 1'),
14
+ double('example 2', location: 'location 2')]
15
+ end
16
+
17
+ before do
18
+ location_source.stub(:example_locations_to_run)
19
+ .and_return ["location 1"]
20
+ end
21
+
22
+ it "leaves examples that are enabled" do
23
+ sorted_examples = ordering_block.call(examples)
24
+
25
+ expect(sorted_examples).to include examples.first
26
+ end
27
+
28
+ it "removes examples that are not enabled" do
29
+ sorted_examples = ordering_block.call(examples)
30
+
31
+ expect(sorted_examples).to_not include examples.last
32
+ end
33
+ end
34
+
35
+ context "when reordering examples" do
36
+ let(:examples) do
37
+ [double('example 1', location: 'location 1'),
38
+ double('example 2', location: 'location 2')]
39
+ end
40
+
41
+ before do
42
+ location_source.stub(:example_locations_to_run)
43
+ .and_return ["location 2", "location 1"]
44
+ end
45
+
46
+ it "reorders examples to match" do
47
+ sorted_examples = ordering_block.call(examples)
48
+
49
+ expect(sorted_examples).to eql [examples.last, examples.first]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-search-and-destroy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jake Goulding
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.14.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.14.0
30
+ description: ! 'Finds RSpec test ordering bugs '
31
+ email:
32
+ - jake.goulding@gmail.com
33
+ executables:
34
+ - rspec-sad
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - .rspec
40
+ - Gemfile
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - bin/rspec-sad
45
+ - examples/spec/bad_ordering_spec.rb
46
+ - examples/spec/spec_helper.rb
47
+ - lib/rspec-sad.rb
48
+ - lib/rspec-search-and-destroy/binary_chop_example_selector.rb
49
+ - lib/rspec-search-and-destroy/bisector.rb
50
+ - lib/rspec-search-and-destroy/version.rb
51
+ - rspec-search-and-destroy.gemspec
52
+ - spec/binary_chop_example_selector_spec.rb
53
+ - spec/bisector_spec.rb
54
+ - spec/rspec_example_ordering_spec.rb
55
+ - spec/spec_helper.rb
56
+ homepage: https://github.com/shepmaster/rspec-search-and-destroy
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.25
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Finds RSpec test ordering bugs
80
+ test_files:
81
+ - spec/binary_chop_example_selector_spec.rb
82
+ - spec/bisector_spec.rb
83
+ - spec/rspec_example_ordering_spec.rb
84
+ - spec/spec_helper.rb