diff --git a/nanoc/lib/nanoc/cli/error_handler.rb b/nanoc/lib/nanoc/cli/error_handler.rb index 4e808fa416..d8f8ed727b 100644 --- a/nanoc/lib/nanoc/cli/error_handler.rb +++ b/nanoc/lib/nanoc/cli/error_handler.rb @@ -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 @@ -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] A hash containing the gem names as keys and gem versions as value @@ -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. # @@ -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`.' @@ -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 diff --git a/nanoc/spec/nanoc/cli/error_handler_spec.rb b/nanoc/spec/nanoc/cli/error_handler_spec.rb index 797abb7cda..afb7046cb6 100644 --- a/nanoc/spec/nanoc/cli/error_handler_spec.rb +++ b/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 @@ -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