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 +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +67 -0
- data/Rakefile +1 -0
- data/bin/rspec-sad +27 -0
- data/examples/spec/bad_ordering_spec.rb +16 -0
- data/examples/spec/spec_helper.rb +15 -0
- data/lib/rspec-sad.rb +157 -0
- data/lib/rspec-search-and-destroy/binary_chop_example_selector.rb +12 -0
- data/lib/rspec-search-and-destroy/bisector.rb +35 -0
- data/lib/rspec-search-and-destroy/version.rb +3 -0
- data/rspec-search-and-destroy.gemspec +21 -0
- data/spec/binary_chop_example_selector_spec.rb +50 -0
- data/spec/bisector_spec.rb +106 -0
- data/spec/rspec_example_ordering_spec.rb +52 -0
- data/spec/spec_helper.rb +17 -0
- metadata +84 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
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,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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|