diff --git a/features/task.feature b/features/task.feature index 4a261a4..55dd29d 100644 --- a/features/task.feature +++ b/features/task.feature @@ -10,3 +10,17 @@ Feature: tasks When I execute the recipe Then the exit status must be 0 And the output must contain "hello from recipe" + + Scenario: reports errors for invalid action calls in a task + Given a recipe with: + """ + task 'some_task' do + invalid_action + end + """ + When I execute the recipe + Then the exit status must be 70 + And the output must match: + """ + \Arecipe.rb:2:.+invalid task action `invalid_action' + """ diff --git a/lib/producer/core.rb b/lib/producer/core.rb index 0a138d2..c268636 100644 --- a/lib/producer/core.rb +++ b/lib/producer/core.rb @@ -1,5 +1,6 @@ require 'producer/core/cli' require 'producer/core/env' +require 'producer/core/errors' require 'producer/core/recipe' require 'producer/core/recipe/dsl' require 'producer/core/task' diff --git a/lib/producer/core/cli.rb b/lib/producer/core/cli.rb index a3e9f6c..9cbb012 100644 --- a/lib/producer/core/cli.rb +++ b/lib/producer/core/cli.rb @@ -24,9 +24,10 @@ module Producer env = Env.new(recipe) begin recipe.evaluate env - rescue Recipe::RecipeEvaluationError => e - @stdout.puts [e.backtrace.shift, e.message].join ': ' - @stdout.puts e.backtrace + rescue RecipeEvaluationError => e + backtrace = e.backtrace.reject { |l| l =~ /\/producer-core\// } + @stdout.puts [backtrace.shift, e.message].join ': ' + @stdout.puts backtrace exit 70 end diff --git a/lib/producer/core/errors.rb b/lib/producer/core/errors.rb new file mode 100644 index 0000000..f87d2d7 --- /dev/null +++ b/lib/producer/core/errors.rb @@ -0,0 +1,8 @@ +module Producer + module Core + Error = Class.new(StandardError) + ConditionNotMetError = Class.new(Error) + RecipeEvaluationError = Class.new(StandardError) + TaskEvaluationError = Class.new(RecipeEvaluationError) + end +end diff --git a/lib/producer/core/recipe.rb b/lib/producer/core/recipe.rb index 153e087..d3f27c2 100644 --- a/lib/producer/core/recipe.rb +++ b/lib/producer/core/recipe.rb @@ -1,8 +1,6 @@ module Producer module Core class Recipe - RecipeEvaluationError = Class.new(StandardError) - attr_reader :code, :filepath def self.from_file(filepath) diff --git a/lib/producer/core/recipe/dsl.rb b/lib/producer/core/recipe/dsl.rb index f814de1..03bb34d 100644 --- a/lib/producer/core/recipe/dsl.rb +++ b/lib/producer/core/recipe/dsl.rb @@ -18,9 +18,9 @@ module Producer end self rescue NameError => e - err = RecipeEvaluationError.new("invalid recipe keyword `#{e.name}'") - err.set_backtrace e.backtrace.reject { |l| l =~ /\/producer-core\// } - raise err + raise RecipeEvaluationError, + "invalid recipe keyword `#{e.name}'", + e.backtrace end private diff --git a/lib/producer/core/task/dsl.rb b/lib/producer/core/task/dsl.rb index f1a52b8..227182d 100644 --- a/lib/producer/core/task/dsl.rb +++ b/lib/producer/core/task/dsl.rb @@ -2,8 +2,6 @@ module Producer module Core class Task class DSL - ConditionNotMetError = Class.new(StandardError) - def initialize(&block) @block = block end @@ -11,10 +9,14 @@ module Producer def evaluate(env) instance_eval &@block rescue ConditionNotMetError + rescue NameError => e + raise TaskEvaluationError, + "invalid task action `#{e.name}'", + e.backtrace end def condition(&block) - raise ConditionNotMetError unless block.call + fail ConditionNotMetError unless block.call end end end diff --git a/spec/producer/core/cli_spec.rb b/spec/producer/core/cli_spec.rb index 3048e2c..e335027 100644 --- a/spec/producer/core/cli_spec.rb +++ b/spec/producer/core/cli_spec.rb @@ -89,11 +89,13 @@ module Producer::Core } end - it 'prints the error' do - begin - cli.evaluate_recipe_file - rescue SystemExit - end + def trap_exit + yield + rescue SystemExit + end + + it 'prints the specific error' do + trap_exit { cli.evaluate_recipe_file } expect(stdout.string).to match(/ \A #{recipe_file}:4: @@ -101,6 +103,11 @@ module Producer::Core invalid\srecipe\skeyword\s`invalid_keyword' /x) end + + it 'excludes producer own source code from the error backtrace' do + trap_exit { cli.evaluate_recipe_file } + expect(stdout.string).to_not match /\/producer-core\// + end end end end diff --git a/spec/producer/core/recipe/dsl_spec.rb b/spec/producer/core/recipe/dsl_spec.rb index 7aff141..aacc551 100644 --- a/spec/producer/core/recipe/dsl_spec.rb +++ b/spec/producer/core/recipe/dsl_spec.rb @@ -50,7 +50,7 @@ module Producer::Core it 'raises a RecipeEvaluationError on NameError' do dsl = Recipe::DSL.new { invalid_keyword } - expect { dsl.evaluate(env) }.to raise_error(Recipe::RecipeEvaluationError) + expect { dsl.evaluate(env) }.to raise_error(RecipeEvaluationError) end end end diff --git a/spec/producer/core/task/dsl_spec.rb b/spec/producer/core/task/dsl_spec.rb index f1b2b8d..cfc2383 100644 --- a/spec/producer/core/task/dsl_spec.rb +++ b/spec/producer/core/task/dsl_spec.rb @@ -12,6 +12,14 @@ module Producer::Core expect { dsl.evaluate(env) } .to raise_error(RuntimeError, 'error from task') end + + context 'when given block is invalid' do + it 'raises a TaskEvaluationError on NameError' do + dsl = Task::DSL.new { invalid_action } + expect { dsl.evaluate(env) } + .to raise_error(TaskEvaluationError) + end + end end describe '#condition' do