rspec-core 3.0.4 → 3.12.2

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 (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +2 -1
  5. data/Changelog.md +888 -2
  6. data/{License.txt → LICENSE.md} +6 -5
  7. data/README.md +165 -24
  8. data/lib/rspec/autorun.rb +1 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +19 -20
  10. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  11. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  12. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  13. data/lib/rspec/core/bisect/server.rb +61 -0
  14. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  15. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  16. data/lib/rspec/core/bisect/utilities.rb +69 -0
  17. data/lib/rspec/core/configuration.rb +1287 -246
  18. data/lib/rspec/core/configuration_options.rb +95 -35
  19. data/lib/rspec/core/did_you_mean.rb +46 -0
  20. data/lib/rspec/core/drb.rb +21 -12
  21. data/lib/rspec/core/dsl.rb +10 -6
  22. data/lib/rspec/core/example.rb +305 -113
  23. data/lib/rspec/core/example_group.rb +431 -223
  24. data/lib/rspec/core/example_status_persister.rb +235 -0
  25. data/lib/rspec/core/filter_manager.rb +86 -115
  26. data/lib/rspec/core/flat_map.rb +6 -4
  27. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  28. data/lib/rspec/core/formatters/base_formatter.rb +14 -116
  29. data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
  30. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  31. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  32. data/lib/rspec/core/formatters/console_codes.rb +29 -18
  33. data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
  34. data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
  35. data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
  36. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  37. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  38. data/lib/rspec/core/formatters/helpers.rb +45 -15
  39. data/lib/rspec/core/formatters/html_formatter.rb +33 -28
  40. data/lib/rspec/core/formatters/html_printer.rb +30 -20
  41. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  42. data/lib/rspec/core/formatters/json_formatter.rb +18 -9
  43. data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
  44. data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
  45. data/lib/rspec/core/formatters/protocol.rb +182 -0
  46. data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
  47. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  48. data/lib/rspec/core/formatters.rb +81 -41
  49. data/lib/rspec/core/hooks.rb +314 -244
  50. data/lib/rspec/core/invocations.rb +87 -0
  51. data/lib/rspec/core/memoized_helpers.rb +161 -51
  52. data/lib/rspec/core/metadata.rb +132 -61
  53. data/lib/rspec/core/metadata_filter.rb +224 -64
  54. data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
  55. data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
  56. data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
  57. data/lib/rspec/core/mocking_adapters/null.rb +2 -0
  58. data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
  59. data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
  60. data/lib/rspec/core/notifications.rb +192 -206
  61. data/lib/rspec/core/option_parser.rb +174 -69
  62. data/lib/rspec/core/ordering.rb +48 -35
  63. data/lib/rspec/core/output_wrapper.rb +29 -0
  64. data/lib/rspec/core/pending.rb +25 -33
  65. data/lib/rspec/core/profiler.rb +34 -0
  66. data/lib/rspec/core/project_initializer/.rspec +0 -2
  67. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
  68. data/lib/rspec/core/project_initializer.rb +5 -3
  69. data/lib/rspec/core/rake_task.rb +99 -55
  70. data/lib/rspec/core/reporter.rb +128 -15
  71. data/lib/rspec/core/ruby_project.rb +14 -6
  72. data/lib/rspec/core/runner.rb +96 -45
  73. data/lib/rspec/core/sandbox.rb +37 -0
  74. data/lib/rspec/core/set.rb +54 -0
  75. data/lib/rspec/core/shared_example_group.rb +133 -43
  76. data/lib/rspec/core/shell_escape.rb +49 -0
  77. data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
  78. data/lib/rspec/core/version.rb +1 -1
  79. data/lib/rspec/core/warnings.rb +6 -6
  80. data/lib/rspec/core/world.rb +172 -68
  81. data/lib/rspec/core.rb +66 -21
  82. data.tar.gz.sig +0 -0
  83. metadata +93 -69
  84. metadata.gz.sig +0 -0
  85. data/lib/rspec/core/backport_random.rb +0 -336
@@ -1,9 +1,10 @@
1
- (The MIT License)
1
+ The MIT License (MIT)
2
+ =====================
2
3
 
3
- Copyright (c) 2012 Chad Humphries, David Chelimsky, Myron Marston
4
- Copyright (c) 2009 Chad Humphries, David Chelimsky
5
- Copyright (c) 2006 David Chelimsky, The RSpec Development Team
6
- Copyright (c) 2005 Steven Baker
4
+ * Copyright © 2012 Chad Humphries, David Chelimsky, Myron Marston
5
+ * Copyright © 2009 Chad Humphries, David Chelimsky
6
+ * Copyright © 2006 David Chelimsky, The RSpec Development Team
7
+ * Copyright © 2005 Steven Baker
7
8
 
8
9
  Permission is hereby granted, free of charge, to any person obtaining
9
10
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,16 +1,25 @@
1
- # rspec-core [![Build Status](https://secure.travis-ci.org/rspec/rspec-core.png?branch=master)](http://travis-ci.org/rspec/rspec-core) [![Code Climate](https://codeclimate.com/github/rspec/rspec-core.png)](https://codeclimate.com/github/rspec/rspec-core)
1
+ # rspec-core [![Build Status](https://github.com/rspec/rspec-core/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-core/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-core.svg)](https://codeclimate.com/github/rspec/rspec-core)
2
2
 
3
3
  rspec-core provides the structure for writing executable examples of how your
4
4
  code should behave, and an `rspec` command with tools to constrain which
5
5
  examples get run and tailor the output.
6
6
 
7
- ## install
7
+ ## Install
8
8
 
9
9
  gem install rspec # for rspec-core, rspec-expectations, rspec-mocks
10
10
  gem install rspec-core # for rspec-core only
11
11
  rspec --help
12
12
 
13
- ## basic structure
13
+ Want to run against the `main` branch? You'll need to include the dependent
14
+ RSpec repos as well. Add the following to your `Gemfile`:
15
+
16
+ ```ruby
17
+ %w[rspec rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
18
+ gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main'
19
+ end
20
+ ```
21
+
22
+ ## Basic Structure
14
23
 
15
24
  RSpec uses the words "describe" and "it" so we can express concepts like a conversation:
16
25
 
@@ -21,6 +30,7 @@ RSpec uses the words "describe" and "it" so we can express concepts like a conve
21
30
  RSpec.describe Order do
22
31
  it "sums the prices of its line items" do
23
32
  order = Order.new
33
+
24
34
  order.add_entry(LineItem.new(:item => Item.new(
25
35
  :price => Money.new(1.11, :USD)
26
36
  )))
@@ -28,6 +38,7 @@ RSpec.describe Order do
28
38
  :price => Money.new(2.22, :USD),
29
39
  :quantity => 2
30
40
  )))
41
+
31
42
  expect(order.total).to eq(Money.new(5.55, :USD))
32
43
  end
33
44
  end
@@ -40,9 +51,9 @@ Under the hood, an example group is a class in which the block passed to
40
51
  `describe` is evaluated. The blocks passed to `it` are evaluated in the
41
52
  context of an _instance_ of that class.
42
53
 
43
- ## nested groups
54
+ ## Nested Groups
44
55
 
45
- You can also declare nested nested groups using the `describe` or `context`
56
+ You can also declare nested groups using the `describe` or `context`
46
57
  methods:
47
58
 
48
59
  ```ruby
@@ -61,7 +72,10 @@ RSpec.describe Order do
61
72
  end
62
73
  ```
63
74
 
64
- ## aliases
75
+ Nested groups are subclasses of the outer example group class, providing
76
+ the inheritance semantics you'd want for free.
77
+
78
+ ## Aliases
65
79
 
66
80
  You can declare example groups using either `describe` or `context`.
67
81
  For a top level example group, `describe` and `context` are available
@@ -72,7 +86,7 @@ patching.
72
86
  You can declare examples within a group using any of `it`, `specify`, or
73
87
  `example`.
74
88
 
75
- ## shared examples and contexts
89
+ ## Shared Examples and Contexts
76
90
 
77
91
  Declare a shared example group using `shared_examples`, and then include it
78
92
  in any group using `include_examples`.
@@ -102,7 +116,7 @@ pretty much the same as `shared_examples` and `include_examples`, providing
102
116
  more accurate naming when you share hooks, `let` declarations, helper methods,
103
117
  etc, but no examples.
104
118
 
105
- ## metadata
119
+ ## Metadata
106
120
 
107
121
  rspec-core stores a metadata hash with every example and group, which
108
122
  contains their descriptions, the locations at which they were
@@ -114,7 +128,7 @@ Although you probably won't ever need this unless you are writing an
114
128
  extension, you can access it from an example like this:
115
129
 
116
130
  ```ruby
117
- it "does something" do
131
+ it "does something" do |example|
118
132
  expect(example.metadata[:description]).to eq("does something")
119
133
  end
120
134
  ```
@@ -153,26 +167,106 @@ RSpec.describe Hash do
153
167
  end
154
168
  ```
155
169
 
156
- ## the `rspec` command
170
+ ## A Word on Scope
171
+
172
+ RSpec has two scopes:
173
+
174
+ * **Example Group**: Example groups are defined by a `describe` or
175
+ `context` block, which is eagerly evaluated when the spec file is
176
+ loaded. The block is evaluated in the context of a subclass of
177
+ `RSpec::Core::ExampleGroup`, or a subclass of the parent example group
178
+ when you're nesting them.
179
+ * **Example**: Examples -- typically defined by an `it` block -- and any other
180
+ blocks with per-example semantics -- such as a `before(:example)` hook -- are
181
+ evaluated in the context of
182
+ an _instance_ of the example group class to which the example belongs.
183
+ Examples are _not_ executed when the spec file is loaded; instead,
184
+ RSpec waits to run any examples until all spec files have been loaded,
185
+ at which point it can apply filtering, randomization, etc.
186
+
187
+ To make this more concrete, consider this code snippet:
188
+
189
+ ``` ruby
190
+ RSpec.describe "Using an array as a stack" do
191
+ def build_stack
192
+ []
193
+ end
194
+
195
+ before(:example) do
196
+ @stack = build_stack
197
+ end
198
+
199
+ it 'is initially empty' do
200
+ expect(@stack).to be_empty
201
+ end
202
+
203
+ context "after an item has been pushed" do
204
+ before(:example) do
205
+ @stack.push :item
206
+ end
207
+
208
+ it 'allows the pushed item to be popped' do
209
+ expect(@stack.pop).to eq(:item)
210
+ end
211
+ end
212
+ end
213
+ ```
214
+
215
+ Under the covers, this is (roughly) equivalent to:
216
+
217
+ ``` ruby
218
+ class UsingAnArrayAsAStack < RSpec::Core::ExampleGroup
219
+ def build_stack
220
+ []
221
+ end
222
+
223
+ def before_example_1
224
+ @stack = build_stack
225
+ end
226
+
227
+ def it_is_initially_empty
228
+ expect(@stack).to be_empty
229
+ end
230
+
231
+ class AfterAnItemHasBeenPushed < self
232
+ def before_example_2
233
+ @stack.push :item
234
+ end
235
+
236
+ def it_allows_the_pushed_item_to_be_popped
237
+ expect(@stack.pop).to eq(:item)
238
+ end
239
+ end
240
+ end
241
+ ```
242
+
243
+ To run these examples, RSpec would (roughly) do the following:
244
+
245
+ ``` ruby
246
+ example_1 = UsingAnArrayAsAStack.new
247
+ example_1.before_example_1
248
+ example_1.it_is_initially_empty
249
+
250
+ example_2 = UsingAnArrayAsAStack::AfterAnItemHasBeenPushed.new
251
+ example_2.before_example_1
252
+ example_2.before_example_2
253
+ example_2.it_allows_the_pushed_item_to_be_popped
254
+ ```
255
+
256
+ ## The `rspec` Command
157
257
 
158
258
  When you install the rspec-core gem, it installs the `rspec` executable,
159
259
  which you'll use to run rspec. The `rspec` command comes with many useful
160
260
  options.
161
261
  Run `rspec --help` to see the complete list.
162
262
 
163
- ## store command line options `.rspec`
263
+ ## Store Command Line Options `.rspec`
164
264
 
165
265
  You can store command line options in a `.rspec` file in the project's root
166
266
  directory, and the `rspec` command will read them as though you typed them on
167
267
  the command line.
168
268
 
169
- ## autotest integration
170
-
171
- rspec-core no longer ships with an Autotest extension, if you require Autotest
172
- integration, please use the `rspec-autotest` gem and see [rspec/rspec-autotest](https://github.com/rspec/rspec-autotest)
173
- for details
174
-
175
- ## get started
269
+ ## Get Started
176
270
 
177
271
  Start with a simple example of behavior you expect from your system. Do
178
272
  this before you write any implementation code:
@@ -195,13 +289,12 @@ $ rspec spec/calculator_spec.rb
195
289
  ./spec/calculator_spec.rb:1: uninitialized constant Calculator
196
290
  ```
197
291
 
198
- Implement the simplest solution:
292
+ Address the failure by defining a skeleton of the `Calculator` class:
199
293
 
200
294
  ```ruby
201
295
  # in lib/calculator.rb
202
296
  class Calculator
203
- def add(a,b)
204
- a + b
297
+ def add(a, b)
205
298
  end
206
299
  end
207
300
  ```
@@ -214,6 +307,39 @@ Be sure to require the implementation file in the spec:
214
307
  require "calculator"
215
308
  ```
216
309
 
310
+ Now run the spec again, and watch the expectation fail:
311
+
312
+ ```
313
+ $ rspec spec/calculator_spec.rb
314
+ F
315
+
316
+ Failures:
317
+
318
+ 1) Calculator#add returns the sum of its arguments
319
+ Failure/Error: expect(Calculator.new.add(1, 2)).to eq(3)
320
+
321
+ expected: 3
322
+ got: nil
323
+
324
+ (compared using ==)
325
+ # ./spec/calculator_spec.rb:6:in `block (3 levels) in <top (required)>'
326
+
327
+ Finished in 0.00131 seconds (files took 0.10968 seconds to load)
328
+ 1 example, 1 failure
329
+
330
+ Failed examples:
331
+
332
+ rspec ./spec/calculator_spec.rb:5 # Calculator#add returns the sum of its arguments
333
+ ```
334
+
335
+ Implement the simplest solution, by changing the definition of `Calculator#add` to:
336
+
337
+ ```ruby
338
+ def add(a, b)
339
+ a + b
340
+ end
341
+ ```
342
+
217
343
  Now run the spec again, and watch it pass:
218
344
 
219
345
  ```
@@ -236,8 +362,23 @@ Finished in 0.000379 seconds
236
362
  1 example, 0 failures
237
363
  ```
238
364
 
365
+ ## Contributing
366
+
367
+ Once you've set up the environment, you'll need to cd into the working
368
+ directory of whichever repo you want to work in. From there you can run the
369
+ specs and cucumber features, and make patches.
370
+
371
+ NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You
372
+ can treat each RSpec repo as an independent project.
373
+
374
+ * [Build details](BUILD_DETAIL.md)
375
+ * [Code of Conduct](CODE_OF_CONDUCT.md)
376
+ * [Detailed contributing guide](CONTRIBUTING.md)
377
+ * [Development setup guide](DEVELOPMENT.md)
378
+
239
379
  ## Also see
240
380
 
241
- * [http://github.com/rspec/rspec](http://github.com/rspec/rspec)
242
- * [http://github.com/rspec/rspec-expectations](http://github.com/rspec/rspec-expectations)
243
- * [http://github.com/rspec/rspec-mocks](http://github.com/rspec/rspec-mocks)
381
+ * [https://github.com/rspec/rspec](https://github.com/rspec/rspec)
382
+ * [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations)
383
+ * [https://github.com/rspec/rspec-mocks](https://github.com/rspec/rspec-mocks)
384
+ * [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails)
data/lib/rspec/autorun.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  require 'rspec/core'
2
+ # Ensure the default config is loaded
2
3
  RSpec::Core::Runner.autorun
@@ -8,28 +8,31 @@ module RSpec
8
8
  def initialize
9
9
  @full_backtrace = false
10
10
 
11
- patterns = [
12
- "/lib\d*/ruby/",
13
- "org/jruby/",
14
- "bin/",
15
- "/gems/",
16
- ].map { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) }
11
+ patterns = %w[ /lib\d*/ruby/ bin/ exe/rspec /lib/bundler/ /exe/bundle: ]
12
+ patterns << "org/jruby/" if RUBY_PLATFORM == 'java'
13
+ patterns.map! { |s| Regexp.new(s.gsub("/", File::SEPARATOR)) }
17
14
 
18
- @system_exclusion_patterns = [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *patterns)]
19
- @exclusion_patterns = [] + @system_exclusion_patterns
20
- @inclusion_patterns = [Regexp.new(Dir.getwd)]
21
- end
15
+ @exclusion_patterns = [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *patterns)]
16
+ @inclusion_patterns = []
22
17
 
23
- def full_backtrace=(full_backtrace)
24
- @full_backtrace = full_backtrace
18
+ return unless matches?(@exclusion_patterns, File.join(Dir.getwd, "lib", "foo.rb:13"))
19
+ inclusion_patterns << Regexp.new(Dir.getwd)
25
20
  end
26
21
 
22
+ attr_writer :full_backtrace
23
+
27
24
  def full_backtrace?
28
- @full_backtrace || @exclusion_patterns.empty?
25
+ @full_backtrace || exclusion_patterns.empty?
26
+ end
27
+
28
+ def filter_gem(gem_name)
29
+ sep = File::SEPARATOR
30
+ exclusion_patterns << /#{sep}#{gem_name}(-[^#{sep}]+)?#{sep}/
29
31
  end
30
32
 
31
- def format_backtrace(backtrace, options = {})
32
- return backtrace if options[:full_backtrace]
33
+ def format_backtrace(backtrace, options={})
34
+ return [] unless backtrace
35
+ return backtrace if options[:full_backtrace] || backtrace.empty?
33
36
 
34
37
  backtrace.map { |l| backtrace_line(l) }.compact.
35
38
  tap do |filtered|
@@ -45,15 +48,11 @@ module RSpec
45
48
 
46
49
  def backtrace_line(line)
47
50
  Metadata.relative_path(line) unless exclude?(line)
48
- rescue SecurityError
49
- nil
50
51
  end
51
52
 
52
53
  def exclude?(line)
53
54
  return false if @full_backtrace
54
- relative_line = Metadata.relative_path(line)
55
- return false unless matches?(@exclusion_patterns, relative_line)
56
- matches?(@system_exclusion_patterns, relative_line) || !matches?(@inclusion_patterns, line)
55
+ matches?(exclusion_patterns, line) && !matches?(inclusion_patterns, line)
57
56
  end
58
57
 
59
58
  private
@@ -0,0 +1,62 @@
1
+ RSpec::Support.require_rspec_core "bisect/shell_command"
2
+ RSpec::Support.require_rspec_core "bisect/example_minimizer"
3
+ RSpec::Support.require_rspec_core "bisect/utilities"
4
+ RSpec::Support.require_rspec_core "formatters/bisect_progress_formatter"
5
+
6
+ module RSpec
7
+ module Core
8
+ module Bisect
9
+ # The main entry point into the bisect logic. Coordinates among:
10
+ # - Bisect::ShellCommand: Generates shell commands to run spec subsets
11
+ # - Bisect::ExampleMinimizer: Contains the core bisect logic.
12
+ # - A bisect runner: runs a set of examples and returns the results.
13
+ # - A bisect formatter: provides progress updates to the user.
14
+ # @private
15
+ class Coordinator
16
+ def self.bisect_with(spec_runner, original_cli_args, formatter)
17
+ new(spec_runner, original_cli_args, formatter).bisect
18
+ end
19
+
20
+ def initialize(spec_runner, original_cli_args, formatter)
21
+ @spec_runner = spec_runner
22
+ @shell_command = ShellCommand.new(original_cli_args)
23
+ @notifier = Bisect::Notifier.new(formatter)
24
+ end
25
+
26
+ def bisect
27
+ repro = start_bisect_runner do |runner|
28
+ minimizer = ExampleMinimizer.new(@shell_command, runner, @notifier)
29
+
30
+ gracefully_abort_on_sigint(minimizer)
31
+ minimizer.find_minimal_repro
32
+ minimizer.repro_command_for_currently_needed_ids
33
+ end
34
+
35
+ @notifier.publish(:bisect_repro_command, :repro => repro)
36
+
37
+ true
38
+ rescue BisectFailedError => e
39
+ @notifier.publish(:bisect_failed, :failure_explanation => e.message)
40
+ false
41
+ ensure
42
+ @notifier.publish(:close)
43
+ end
44
+
45
+ private
46
+
47
+ def start_bisect_runner(&block)
48
+ klass = @spec_runner.configuration.bisect_runner_class
49
+ klass.start(@shell_command, @spec_runner, &block)
50
+ end
51
+
52
+ def gracefully_abort_on_sigint(minimizer)
53
+ trap('INT') do
54
+ repro = minimizer.repro_command_for_currently_needed_ids
55
+ @notifier.publish(:bisect_aborted, :repro => repro)
56
+ exit(1)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,173 @@
1
+ RSpec::Support.require_rspec_core "bisect/utilities"
2
+
3
+ module RSpec
4
+ module Core
5
+ module Bisect
6
+ # @private
7
+ # Contains the core bisect logic. Searches for examples we can ignore by
8
+ # repeatedly running different subsets of the suite.
9
+ class ExampleMinimizer
10
+ attr_reader :shell_command, :runner, :all_example_ids, :failed_example_ids
11
+ attr_accessor :remaining_ids
12
+
13
+ def initialize(shell_command, runner, notifier)
14
+ @shell_command = shell_command
15
+ @runner = runner
16
+ @notifier = notifier
17
+ end
18
+
19
+ def find_minimal_repro
20
+ prep
21
+
22
+ _, duration = track_duration do
23
+ bisect(non_failing_example_ids)
24
+ end
25
+
26
+ notify(:bisect_complete, :duration => duration,
27
+ :original_non_failing_count => non_failing_example_ids.size,
28
+ :remaining_count => remaining_ids.size)
29
+
30
+ remaining_ids + failed_example_ids
31
+ end
32
+
33
+ def bisect(candidate_ids)
34
+ notify(:bisect_dependency_check_started)
35
+ if get_expected_failures_for?([])
36
+ notify(:bisect_dependency_check_failed)
37
+ self.remaining_ids = []
38
+ return
39
+ end
40
+ notify(:bisect_dependency_check_passed)
41
+
42
+ bisect_over(candidate_ids)
43
+ end
44
+
45
+ def bisect_over(candidate_ids)
46
+ return if candidate_ids.one?
47
+
48
+ notify(
49
+ :bisect_round_started,
50
+ :candidate_range => example_range(candidate_ids),
51
+ :candidates_count => candidate_ids.size
52
+ )
53
+
54
+ slice_size = (candidate_ids.length / 2.0).ceil
55
+ lhs, rhs = candidate_ids.each_slice(slice_size).to_a
56
+
57
+ ids_to_ignore, duration = track_duration do
58
+ [lhs, rhs].find do |ids|
59
+ get_expected_failures_for?(remaining_ids - ids)
60
+ end
61
+ end
62
+
63
+ if ids_to_ignore
64
+ self.remaining_ids -= ids_to_ignore
65
+ notify(
66
+ :bisect_round_ignoring_ids,
67
+ :ids_to_ignore => ids_to_ignore,
68
+ :ignore_range => example_range(ids_to_ignore),
69
+ :remaining_ids => remaining_ids,
70
+ :duration => duration
71
+ )
72
+ bisect_over(candidate_ids - ids_to_ignore)
73
+ else
74
+ notify(
75
+ :bisect_round_detected_multiple_culprits,
76
+ :duration => duration
77
+ )
78
+ bisect_over(lhs)
79
+ bisect_over(rhs)
80
+ end
81
+ end
82
+
83
+ def currently_needed_ids
84
+ remaining_ids + failed_example_ids
85
+ end
86
+
87
+ def repro_command_for_currently_needed_ids
88
+ return shell_command.repro_command_from(currently_needed_ids) if remaining_ids
89
+ "(Not yet enough information to provide any repro command)"
90
+ end
91
+
92
+ # @private
93
+ # Convenience class for describing a subset of the candidate examples
94
+ ExampleRange = Struct.new(:start, :finish) do
95
+ def description
96
+ if start == finish
97
+ "example #{start}"
98
+ else
99
+ "examples #{start}-#{finish}"
100
+ end
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def example_range(ids)
107
+ ExampleRange.new(
108
+ non_failing_example_ids.find_index(ids.first) + 1,
109
+ non_failing_example_ids.find_index(ids.last) + 1
110
+ )
111
+ end
112
+
113
+ def prep
114
+ notify(:bisect_starting, :original_cli_args => shell_command.original_cli_args,
115
+ :bisect_runner => runner.class.name)
116
+
117
+ _, duration = track_duration do
118
+ original_results = runner.original_results
119
+ @all_example_ids = original_results.all_example_ids
120
+ @failed_example_ids = original_results.failed_example_ids
121
+ @remaining_ids = non_failing_example_ids
122
+ end
123
+
124
+ if @failed_example_ids.empty?
125
+ raise BisectFailedError, "\n\nNo failures found. Bisect only works " \
126
+ "in the presence of one or more failing examples."
127
+ else
128
+ notify(:bisect_original_run_complete, :failed_example_ids => failed_example_ids,
129
+ :non_failing_example_ids => non_failing_example_ids,
130
+ :duration => duration)
131
+ end
132
+ end
133
+
134
+ def non_failing_example_ids
135
+ @non_failing_example_ids ||= all_example_ids - failed_example_ids
136
+ end
137
+
138
+ def get_expected_failures_for?(ids)
139
+ ids_to_run = ids + failed_example_ids
140
+ notify(
141
+ :bisect_individual_run_start,
142
+ :command => shell_command.repro_command_from(ids_to_run),
143
+ :ids_to_run => ids_to_run
144
+ )
145
+
146
+ results, duration = track_duration { runner.run(ids_to_run) }
147
+ notify(:bisect_individual_run_complete, :duration => duration, :results => results)
148
+
149
+ abort_if_ordering_inconsistent(results)
150
+ (failed_example_ids & results.failed_example_ids) == failed_example_ids
151
+ end
152
+
153
+ def track_duration
154
+ start = ::RSpec::Core::Time.now
155
+ [yield, ::RSpec::Core::Time.now - start]
156
+ end
157
+
158
+ def abort_if_ordering_inconsistent(results)
159
+ expected_order = all_example_ids & results.all_example_ids
160
+ return if expected_order == results.all_example_ids
161
+
162
+ raise BisectFailedError, "\n\nThe example ordering is inconsistent. " \
163
+ "`--bisect` relies upon consistent ordering (e.g. by passing " \
164
+ "`--seed` if you're using random ordering) to work properly."
165
+ end
166
+
167
+ def notify(*args)
168
+ @notifier.publish(*args)
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end