Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat LoadErrors for known gems as trivial #1274

Merged
merged 2 commits into from Dec 15, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 27 additions & 18 deletions nanoc/lib/nanoc/cli/error_handler.rb
Expand Up @@ -58,12 +58,16 @@ def handle_while(exit_on_error:)
rescue Interrupt
exit(1)
rescue StandardError, ScriptError => e
if trivial?(e)
$stderr.puts "Error: #{e.message}"
resolution = resolution_for(e)
handle_error(e, exit_on_error: exit_on_error)
end

def handle_error(error, exit_on_error:)
if trivial?(error)
$stderr.puts "Error: #{error.message}"
resolution = resolution_for(error)
$stderr.puts resolution if resolution
else
print_error(e)
print_error(error)
end
exit(1) if exit_on_error
end
Expand Down Expand Up @@ -148,6 +152,18 @@ def forwards_stack_trace?
feature_enabled || ruby_2_5_used
end

# @api private
def trivial?(error)
case error
when Nanoc::Int::Errors::GenericTrivial, Errno::EADDRINUSE
true
when LoadError
GEM_NAMES.keys.include?(gem_name_from_load_error(error))
else
false
end
end

protected

# @return [Hash<String, Array>] A hash containing the gem names as keys and gem versions as value
Expand Down Expand Up @@ -196,15 +212,6 @@ def gems_and_versions
'w3c_validators' => 'w3c_validators',
}.freeze

def trivial?(error)
case error
when Nanoc::Int::Errors::GenericTrivial, Errno::EADDRINUSE
true
else
false
end
end

# Attempts to find a resolution for the given error, or nil if no
# resolution can be automatically obtained.
#
Expand All @@ -216,12 +223,8 @@ def resolution_for(error)

case error
when LoadError
# Get gem name
matches = error.message.match(/(no such file to load|cannot load such file) -- ([^\s]+)/)
return nil if matches.nil?
gem_name = GEM_NAMES[matches[2]]
gem_name = gem_name_from_load_error(error)

# Build message
if gem_name
if using_bundler?
'Make sure the gem is added to Gemfile and run `bundle install`.'
Expand All @@ -241,6 +244,12 @@ def resolution_for(error)
end
end

def gem_name_from_load_error(error)
matches = error.message.match(/(no such file to load|cannot load such file) -- ([^\s]+)/)
return nil if matches.nil?
GEM_NAMES[matches[2]]
end

def using_bundler?
defined?(Bundler) && Bundler::SharedHelpers.in_bundle?
end
Expand Down
136 changes: 135 additions & 1 deletion nanoc/spec/nanoc/cli/error_handler_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true

describe Nanoc::CLI::ErrorHandler do
describe Nanoc::CLI::ErrorHandler, stdio: true do
subject(:error_handler) { described_class.new }

describe '#forwards_stack_trace?' do
Expand Down Expand Up @@ -40,4 +40,138 @@
end
end
end

describe '#trivial?' do
subject { error_handler.trivial?(error) }

context 'LoadError of known gem' do
let(:error) do
begin
raise LoadError, 'cannot load such file -- nokogiri'
rescue LoadError => e
return e
end
end

it { is_expected.to be(true) }
end

context 'LoadError of unknown gem' do
let(:error) do
begin
raise LoadError, 'cannot load such file -- whatever'
rescue LoadError => e
return e
end
end

it { is_expected.to be(false) }
end

context 'random error' do
let(:error) do
begin
raise 'stuff'
rescue => e
return e
end
end

it { is_expected.to be(false) }
end

context 'Errno::EADDRINUSE' do
let(:error) do
begin
raise Errno::EADDRINUSE
rescue => e
return e
end
end

it { is_expected.to be(true) }
end

context 'GenericTrivial' do
let(:error) do
begin
raise Nanoc::Int::Errors::GenericTrivial, 'oh just a tiny thing'
rescue => e
return e
end
end

it { is_expected.to be(true) }
end
end

describe '#handle_error' do
subject { error_handler.handle_error(error, exit_on_error: exit_on_error) }

let(:error) do
begin
raise 'Bewm'
rescue => e
return e
end
end

let(:exit_on_error) { false }

describe 'exit behavior' do
context 'exit on error' do
let(:exit_on_error) { true }

it 'exits on error' do
expect { subject }.to raise_error(SystemExit)
end
end

context 'no exit on error' do
let(:exit_on_error) { false }

it 'does not exit on error' do
expect { subject }.not_to raise_error
end
end
end

describe 'printing behavior' do
context 'trivial error with no resolution' do
let(:error) do
begin
raise Nanoc::Int::Errors::GenericTrivial, 'asdf'
rescue => e
return e
end
end

it 'prints summary' do
expect { subject }.to output("Error: asdf\n").to_stderr
end
end

context 'trivial error with resolution' do
let(:error) do
begin
raise LoadError, 'cannot load such file -- nokogiri'
rescue LoadError => e
return e
end
end

it 'prints summary' do
expected_output = <<~OUT
Error: cannot load such file -- nokogiri
Make sure the gem is added to Gemfile and run `bundle install`.
OUT
expect { subject }.to output(expected_output).to_stderr
end
end

context 'non-trivial error' do
# …
end
end
end
end