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

Handle errors during spec file load time in a nicer manner. #2323

Merged
merged 2 commits into from
Sep 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Enhancements:

* Warn when duplicate shared exmaples definitions are loaded due to being
* Warn when duplicate shared examples definitions are loaded due to being
defined in files matching the spec pattern (e.g. `_spec.rb`) (#2278, Devon Estes)
* Improve metadata filtering so that it can match against any object
that implements `===` instead of treating regular expressions as
Expand All @@ -12,6 +12,8 @@ Enhancements:
RSpec to prevent confusion. (Myron Marston, #2304)
* Add `config.fail_if_no_examples` option which causes RSpec to fail if
no examples are found. (Ewa Czechowska, #2302)
* Nicely format errors encountered while loading spec files.
(Myron Marston, #2323)

### 3.5.3 / 2016-09-02
[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.2...v3.5.3)
Expand Down
7 changes: 6 additions & 1 deletion lib/rspec/core/bisect/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ def capture_run_results(files_or_directories_to_run=[], expected_failures=[])
self.files_or_directories_to_run = files_or_directories_to_run
self.latest_run_results = nil
run_output = yield
latest_run_results || raise_bisect_failed(run_output)

if latest_run_results.nil? || latest_run_results.all_example_ids.empty?
raise_bisect_failed(run_output)
end

latest_run_results
end

def start
Expand Down
10 changes: 9 additions & 1 deletion lib/rspec/core/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ def load_spec_files

files_to_run.uniq.each do |f|
file = File.expand_path(f)
load file
load_spec_file_handling_errors(file)
loaded_spec_files << file
end

Expand Down Expand Up @@ -1864,6 +1864,14 @@ def on_example_group_definition_callbacks

private

def load_spec_file_handling_errors(file)
load file
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => ex
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't believe we didn't think of this before :)

relative_file = Metadata.relative_path(file)
reporter.notify_non_example_exception(ex, "An error occurred while loading #{relative_file}.")
RSpec.world.wants_to_quit = true
end

def handle_suite_hook(scope, meta)
return nil unless scope == :suite

Expand Down
1 change: 0 additions & 1 deletion lib/rspec/core/formatters/base_text_formatter.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
RSpec::Support.require_rspec_core "formatters/base_formatter"
RSpec::Support.require_rspec_core "formatters/console_codes"

module RSpec
module Core
Expand Down
11 changes: 7 additions & 4 deletions lib/rspec/core/formatters/console_codes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ module ConsoleCodes
module_function

# @private
CONFIG_COLORS_TO_METHODS = Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method|
hash[method.to_s.sub(/_color\z/, '').to_sym] = method
hash
def config_colors_to_methods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation in the commit message!

@config_colors_to_methods ||=
Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method|
hash[method.to_s.sub(/_color\z/, '').to_sym] = method
hash
end
end

# Fetches the correct code for the supplied symbol, or checks
Expand All @@ -34,7 +37,7 @@ module ConsoleCodes
# @param code_or_symbol [Symbol, Fixnum] Symbol or code to check
# @return [Fixnum] a console code
def console_code_for(code_or_symbol)
if (config_method = CONFIG_COLORS_TO_METHODS[code_or_symbol])
if (config_method = config_colors_to_methods[code_or_symbol])
console_code_for RSpec.configuration.__send__(config_method)
elsif VT100_CODE_VALUES.key?(code_or_symbol)
code_or_symbol
Expand Down
1 change: 1 addition & 0 deletions lib/rspec/core/formatters/documentation_formatter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
RSpec::Support.require_rspec_core "formatters/console_codes"

module RSpec
module Core
Expand Down
1 change: 1 addition & 0 deletions lib/rspec/core/formatters/exception_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
RSpec::Support.require_rspec_core "formatters/console_codes"
RSpec::Support.require_rspec_core "formatters/snippet_extractor"
RSpec::Support.require_rspec_support "encoded_string"

Expand Down
1 change: 1 addition & 0 deletions lib/rspec/core/formatters/progress_formatter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
RSpec::Support.require_rspec_core "formatters/console_codes"

module RSpec
module Core
Expand Down
1 change: 1 addition & 0 deletions lib/rspec/core/notifications.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
RSpec::Support.require_rspec_core "formatters/console_codes"
RSpec::Support.require_rspec_core "formatters/exception_presenter"
RSpec::Support.require_rspec_core "formatters/helpers"
RSpec::Support.require_rspec_core "shell_escape"
Expand Down
87 changes: 87 additions & 0 deletions spec/integration/spec_file_load_errors_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
require 'support/aruba_support'
require 'support/formatter_support'

RSpec.describe 'Spec file load errors' do
include_context "aruba support"
include FormatterSupport

let(:failure_exit_code) { rand(97) + 2 } # 2..99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a random code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I want the test to force the implementation to return the configured failure_exit_code and this seemed like the easiest way to do that. Consider: if I just set config.failure_exit_code to a static value, an implementation that also returns that static value (but that does not reference config.failure_exit_code) could pass the test. By using a random exit code between 2 and 99 it means that the only way the test can consistently pass is if the implementation really does use the configured value.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough :)


if RSpec::Support::Ruby.jruby_9000?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh rubies...

let(:spec_line_suffix) { ":in `<top>'" }
elsif RSpec::Support::Ruby.jruby?
let(:spec_line_suffix) { ":in `(root)'" }
elsif RUBY_VERSION == "1.8.7"
let(:spec_line_suffix) { "" }
else
let(:spec_line_suffix) { ":in `<top (required)>'" }
end

before do
# get out of `aruba` sub-dir so that `filter_gems_from_backtrace 'aruba'`
# below does not filter out our spec file.
expect(dirs.pop).to eq "aruba"

clean_current_dir

RSpec.configure do |c|
c.filter_gems_from_backtrace "aruba"
c.backtrace_exclusion_patterns << %r{/rspec-core/spec/} << %r{rspec_with_simplecov}
c.failure_exit_code = failure_exit_code
end
end

it 'nicely handles load-time errors in user spec files' do
write_file_formatted "1_spec.rb", "
boom

RSpec.describe 'Calling boom' do
it 'will not run this example' do
expect(1).to eq 1
end
end
"

write_file_formatted "2_spec.rb", "
RSpec.describe 'No Error' do
it 'will not run this example, either' do
expect(1).to eq 1
end
end
"

write_file_formatted "3_spec.rb", "
boom

RSpec.describe 'Calling boom again' do
it 'will not run this example, either' do
expect(1).to eq 1
end
end
"

run_command "1_spec.rb 2_spec.rb 3_spec.rb"
expect(last_cmd_exit_status).to eq(failure_exit_code)
output = normalize_durations(last_cmd_stdout)
expect(output).to eq unindent(<<-EOS)

An error occurred while loading ./1_spec.rb.
Failure/Error: boom

NameError:
undefined local variable or method `boom' for main:Object
# ./1_spec.rb:1#{spec_line_suffix}

An error occurred while loading ./3_spec.rb.
Failure/Error: boom

NameError:
undefined local variable or method `boom' for main:Object
# ./3_spec.rb:1#{spec_line_suffix}


Finished in n.nnnn seconds (files took n.nnnn seconds to load)
0 examples, 0 failures
EOS
end
end