rspec-search-and-destroy 0.0.1
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.
- 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
|