Skip to content

Commit

Permalink
Non-zero exit status for bad scripts.
Browse files Browse the repository at this point in the history
When gitsh is invoked in a non-interactive manner, a parse error should
result in a non-zero exit status.

To make sure this happens, parse error handling has been delegated to the
input strategies by the interpreter. In an interactive session, we output an
error message and continue the session. In a non-interactive session, we
raise a Gitsh::ParseError, which will bubble up to the gitsh binary and
terminate the session with an exit status of 1.

Since the input strategy for files is used in a few internal places (in the
interactive input strategy, for loading .gitshrc, and in the :source
command), those call sites have been modified to handle the possibility of a
Gitsh::ParseError.
  • Loading branch information
georgebrock committed Apr 30, 2017
1 parent 34f6a0d commit 24a2287
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 12 deletions.
13 changes: 11 additions & 2 deletions lib/gitsh/commands/internal_command.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'gitsh/error'

module Gitsh::Commands
module InternalCommand
def self.new(command, args)
Expand Down Expand Up @@ -166,8 +168,7 @@ def self.help_message

def execute(env)
if valid_arguments?
Gitsh::FileRunner.run(env: env, path: path(env))
true
run_script(env)
else
env.puts_error USAGE_MESSAGE
false
Expand All @@ -176,6 +177,14 @@ def execute(env)

private

def run_script(env)
Gitsh::FileRunner.run(env: env, path: path(env))
true
rescue Gitsh::ParseError => e
env.puts_error("gitsh: #{e.message}")
false
end

def valid_arguments?
args.length == 1
end
Expand Down
3 changes: 3 additions & 0 deletions lib/gitsh/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ class UnsetVariableError < Error

class NoInputError < Error
end

class ParseError < Error
end
end
4 changes: 4 additions & 0 deletions lib/gitsh/input_strategies/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def read_command
nil
end

def handle_parse_error(message)
raise ParseError, message
end

private

attr_reader :env, :file, :path
Expand Down
7 changes: 7 additions & 0 deletions lib/gitsh/input_strategies/interactive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ def read_command
retry
end


def handle_parse_error(message)
env.puts_error("gitsh: #{message}")
end

private

attr_reader :history, :line_editor, :env, :terminal
Expand Down Expand Up @@ -83,6 +88,8 @@ def greeting_enabled?

def load_gitshrc
FileRunner.run(env: env, path: gitshrc_path)
rescue ParseError => e
env.puts_error "gitsh: #{e.message}"
rescue NoInputError
end

Expand Down
2 changes: 1 addition & 1 deletion lib/gitsh/interpreter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def run
def execute(input)
build_command(input).execute(env)
rescue RLTK::LexingError, RLTK::NotInLanguage, RLTK::BadToken
env.puts_error('gitsh: parse error')
input_strategy.handle_parse_error('parse error')
end

def build_command(input)
Expand Down
10 changes: 10 additions & 0 deletions spec/integration/error_handling_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,14 @@
expect(gitsh).to output_error /gitsh: parse error/
end
end

it 'does not explode when given a badly formatted script' do
in_a_temporary_directory do
write_file('bad.gitsh', ":echo 'foo")

expect("#{gitsh_path} bad.gitsh").
to execute.with_exit_status(1).
with_error_output_matching(/parse error/)
end
end
end
10 changes: 3 additions & 7 deletions spec/integration/running_scripts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
in_a_temporary_directory do
write_file('myscript.gitsh', "init\n\ncommit")

expect("#{gitsh} --git #{fake_git_path} myscript.gitsh").to execute.
expect("#{gitsh_path} --git #{fake_git_path} myscript.gitsh").to execute.
successfully.
with_output_matching(/^Fake git: init\nFake git: commit\n$/)
end
end

context 'when the script file does not exist' do
it 'exits with a useful error message' do
expect("#{gitsh} --git #{fake_git_path} noscript.gitsh").to execute.
expect("#{gitsh_path} --git #{fake_git_path} noscript.gitsh").to execute.
with_exit_status(66).
with_error_output_matching(/^gitsh: noscript\.gitsh: No such file or directory$/)
end
Expand All @@ -26,14 +26,10 @@
in_a_temporary_directory do
write_file('myscript.gitsh', "init\n\ncommit")

expect("cat myscript.gitsh | #{gitsh} --git #{fake_git_path}").
expect("cat myscript.gitsh | #{gitsh_path} --git #{fake_git_path}").
to execute.successfully.
with_output_matching(/^Fake git: init\nFake git: commit\n$/)
end
end
end

def gitsh
File.expand_path('../../../bin/gitsh', __FILE__)
end
end
9 changes: 9 additions & 0 deletions spec/support/scripts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Scripts
def gitsh_path
File.expand_path('../../../bin/gitsh', __FILE__)
end
end

RSpec.configure do |config|
config.include Scripts
end
14 changes: 14 additions & 0 deletions spec/units/commands/internal/source_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,19 @@
expect(result).to eq false
end
end

context 'with a file that fails to parse' do
it 'prints an error message and returns false' do
env = spy('env', puts_error: nil)
command = described_class.new('source', arguments('/bad_script'))
allow(Gitsh::FileRunner).
to receive(:run).and_raise(Gitsh::ParseError, 'Oh no!')

result = command.execute(env)

expect(env).to have_received(:puts_error).with('gitsh: Oh no!')
expect(result).to eq false
end
end
end
end
9 changes: 9 additions & 0 deletions spec/units/input_strategies/file_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,13 @@
end
end
end

describe '#handle_parse_error' do
it 'raises' do
input_strategy = described_class.new(path: double)

expect { input_strategy.handle_parse_error('my message') }.
to raise_exception(Gitsh::ParseError, 'my message')
end
end
end
22 changes: 22 additions & 0 deletions spec/units/input_strategies/interactive_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@
expect(Gitsh::FileRunner).to have_received(:run).
with(hash_including(path: "#{ENV['HOME']}/.gitshrc"))
end

it 'handles parse errors in the ~/.gitshrc file' do
input_strategy = build_input_strategy
allow(Gitsh::FileRunner).
to receive(:run).and_raise(Gitsh::ParseError, 'my message')

input_strategy.setup

expect(env).to have_received(:puts_error).with('gitsh: my message')
end
end

describe '#teardown' do
Expand Down Expand Up @@ -97,6 +107,17 @@
end
end

describe '#handle_parse_error' do
it 'outputs the error' do
input_strategy = build_input_strategy
allow(env).to receive(:puts_error)

input_strategy.handle_parse_error('my message')

expect(env).to have_received(:puts_error).with('gitsh: my message')
end
end

def build_input_strategy(options={})
described_class.new(
line_editor: options.fetch(:line_editor, line_editor),
Expand Down Expand Up @@ -126,6 +147,7 @@ def env
@env ||= double('Environment', {
print: nil,
puts: nil,
puts_error: nil,
repo_config_color: '',
fetch: '',
:[] => nil
Expand Down
10 changes: 8 additions & 2 deletions spec/units/interpreter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@
allow(parser).to receive(:parse).
and_raise(RLTK::NotInLanguage.new([], double(:token), []))
lexer = double('Lexer', lex: double(:tokens))
input_strategy = double(:input_strategy, setup: nil, teardown: nil)
input_strategy = double(
:input_strategy,
setup: nil,
teardown: nil,
handle_parse_error: nil,
)
allow(input_strategy).to receive(:read_command).and_return(
'bad command',
nil,
Expand All @@ -51,7 +56,8 @@

interpreter.run

expect(env).to have_received(:puts_error).with('gitsh: parse error')
expect(input_strategy).
to have_received(:handle_parse_error).with('parse error')
end
end
end

0 comments on commit 24a2287

Please sign in to comment.