From 639bdc1c733b6272c78b36c3cb85bcaae0c359f9 Mon Sep 17 00:00:00 2001 From: Thibault Jouan Date: Thu, 8 Aug 2013 01:52:48 +0000 Subject: [PATCH] Implement basic condition DSL framework for tests: Instead of interrupting task evaluation when condition is not met, allow the whole task to be evaluated (including condition and evaluation) so that the interpreter will get all tasks actions (whether condition is met or not) and be able to query the condition. * Modify Interpreter#process_task: test if task condition is met before applying the actions; * Implement condition handling in Task and Task::DSL; * Implement Condition and Condition::DSL (useless as they are, but needed to implement later test keywords as part of the condition DSL. --- features/tasks/condition.feature | 5 +-- lib/producer/core.rb | 2 + lib/producer/core/condition.rb | 19 +++++++++ lib/producer/core/condition/dsl.rb | 22 ++++++++++ lib/producer/core/interpreter.rb | 2 +- lib/producer/core/task.rb | 13 ++++-- lib/producer/core/task/dsl.rb | 13 +++--- spec/producer/core/condition/dsl_spec.rb | 47 +++++++++++++++++++++ spec/producer/core/condition_spec.rb | 39 +++++++++++++++++ spec/producer/core/interpreter_spec.rb | 27 +++++++++--- spec/producer/core/task/dsl_spec.rb | 54 +++++++++++++++--------- spec/producer/core/task_spec.rb | 35 ++++++++++++++- 12 files changed, 237 insertions(+), 41 deletions(-) create mode 100644 lib/producer/core/condition.rb create mode 100644 lib/producer/core/condition/dsl.rb create mode 100644 spec/producer/core/condition/dsl_spec.rb create mode 100644 spec/producer/core/condition_spec.rb diff --git a/features/tasks/condition.feature b/features/tasks/condition.feature index 85f4c24..78aeb59 100644 --- a/features/tasks/condition.feature +++ b/features/tasks/condition.feature @@ -1,13 +1,12 @@ Feature: `condition' task keyword - Scenario: prevents task evaluation when condition is not met + Scenario: prevents task actions application when condition is not met Given a recipe with: """ task :hello do condition { false } - puts 'evaluated' - exit 70 + echo 'evaluated' end """ When I execute the recipe diff --git a/lib/producer/core.rb b/lib/producer/core.rb index ee5df3b..8215060 100644 --- a/lib/producer/core.rb +++ b/lib/producer/core.rb @@ -2,6 +2,8 @@ require 'producer/core/action' require 'producer/core/actions/echo' require 'producer/core/actions/shell_command' require 'producer/core/cli' +require 'producer/core/condition' +require 'producer/core/condition/dsl' require 'producer/core/env' require 'producer/core/errors' require 'producer/core/interpreter' diff --git a/lib/producer/core/condition.rb b/lib/producer/core/condition.rb new file mode 100644 index 0000000..73f0779 --- /dev/null +++ b/lib/producer/core/condition.rb @@ -0,0 +1,19 @@ +module Producer + module Core + class Condition + class << self + def evaluate(env, &block) + DSL.evaluate(env, &block) + end + end + + def initialize(expression) + @expression = expression + end + + def ! + !@expression + end + end + end +end diff --git a/lib/producer/core/condition/dsl.rb b/lib/producer/core/condition/dsl.rb new file mode 100644 index 0000000..9347352 --- /dev/null +++ b/lib/producer/core/condition/dsl.rb @@ -0,0 +1,22 @@ +module Producer + module Core + class Condition + class DSL + class << self + def evaluate(env, &block) + dsl = new(&block) + Condition.new(dsl.evaluate) + end + end + + def initialize(&block) + @block = block + end + + def evaluate + @block.call + end + end + end + end +end diff --git a/lib/producer/core/interpreter.rb b/lib/producer/core/interpreter.rb index c24dc81..42be8e9 100644 --- a/lib/producer/core/interpreter.rb +++ b/lib/producer/core/interpreter.rb @@ -6,7 +6,7 @@ module Producer end def process_task(task) - task.actions.each(&:apply) + task.actions.each(&:apply) if task.condition_met? end end end diff --git a/lib/producer/core/task.rb b/lib/producer/core/task.rb index e63ff99..5559cb2 100644 --- a/lib/producer/core/task.rb +++ b/lib/producer/core/task.rb @@ -1,15 +1,20 @@ module Producer module Core class Task - attr_reader :name, :actions + attr_reader :name, :actions, :condition def self.evaluate(name, env, &block) DSL.evaluate(name, env, &block) end - def initialize(name, actions = []) - @name = name - @actions = actions + def initialize(name, actions = [], condition = true) + @name = name + @actions = actions + @condition = condition + end + + def condition_met? + !!@condition end end end diff --git a/lib/producer/core/task/dsl.rb b/lib/producer/core/task/dsl.rb index 1103604..4d4b545 100644 --- a/lib/producer/core/task/dsl.rb +++ b/lib/producer/core/task/dsl.rb @@ -6,7 +6,7 @@ module Producer def evaluate(name, env, &block) dsl = new(&block) dsl.evaluate(env) - Task.new(name, dsl.actions) + Task.new(name, dsl.actions, dsl.condition) end def define_action(keyword, klass) @@ -22,20 +22,19 @@ module Producer attr_accessor :actions def initialize(&block) - @block = block - @actions = [] + @block = block + @actions = [] + @condition = true end def evaluate(env) @env = env instance_eval &@block - rescue ConditionNotMetError end - private - def condition(&block) - fail ConditionNotMetError unless block.call + @condition = Condition.evaluate(@env, &block) if block + @condition end end end diff --git a/spec/producer/core/condition/dsl_spec.rb b/spec/producer/core/condition/dsl_spec.rb new file mode 100644 index 0000000..62ac89e --- /dev/null +++ b/spec/producer/core/condition/dsl_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +module Producer::Core + describe Condition::DSL do + let(:block) { proc { :some_condition_code } } + let(:env) { double('env') } + subject(:dsl) { Condition::DSL.new(&block) } + + describe '.evaluate' do + it 'builds a new DSL sandbox with given code' do + expect(Condition::DSL).to receive(:new).with(&block).and_call_original + Condition::DSL.evaluate(env, &block) + end + + it 'evaluates the DSL sandbox code' do + dsl = double('dsl') + allow(Condition::DSL).to receive(:new) { dsl } + expect(dsl).to receive(:evaluate) + Condition::DSL.evaluate(env, &block) + end + + it 'builds a condition with value returned from DSL evaluation' do + expect(Condition) + .to receive(:new).with(dsl.evaluate) + Condition::DSL.evaluate(env, &block) + end + + it 'returns the condition' do + condition = double('task') + allow(Condition).to receive(:new) { condition } + expect(Condition::DSL.evaluate(env, &block)).to be condition + end + end + + describe '#initialize' do + it 'assigns the code' do + expect(dsl.instance_eval { @block }).to be block + end + end + + describe '#evaluate' do + it 'returns the value returned by the assigned block' do + expect(dsl.evaluate).to eq block.call + end + end + end +end diff --git a/spec/producer/core/condition_spec.rb b/spec/producer/core/condition_spec.rb new file mode 100644 index 0000000..057fb64 --- /dev/null +++ b/spec/producer/core/condition_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +module Producer::Core + describe Condition do + let(:expression) { double('expression') } + let(:condition) { Condition.new(expression) } + + describe '.evaluate' do + let(:env) { double('env') } + let(:block) { proc { :some_condition_code } } + + it 'delegates to DSL.evaluate' do + expect(Condition::DSL) + .to receive(:evaluate).with(env) do |&b| + expect(b.call).to eq :some_condition_code + end + Condition.evaluate(env, &block) + end + + it 'returns the evaluated condition' do + condition = double('condition') + allow(Condition::DSL).to receive(:evaluate) { condition } + expect(Condition.evaluate(env, &block)).to be condition + end + end + + describe '#initialize' do + it 'assigns the expression' do + expect(condition.instance_eval { @expression }).to be expression + end + end + + describe '#!' do + it 'returns the negated expression' do + expect(condition.!).to be !expression + end + end + end +end diff --git a/spec/producer/core/interpreter_spec.rb b/spec/producer/core/interpreter_spec.rb index 24d2ad4..d668568 100644 --- a/spec/producer/core/interpreter_spec.rb +++ b/spec/producer/core/interpreter_spec.rb @@ -12,12 +12,29 @@ module Producer::Core end describe '#process_task' do - it 'applies the task actions' do - action = double('action') - task = double('task') + let(:action) { double('action') } + let(:task) { double('task').as_null_object } + + before do allow(task).to receive(:actions) { [action] } - expect(action).to receive(:apply) - interpreter.process_task(task) + end + + context 'when task condition is met' do + it 'applies the actions' do + expect(action).to receive(:apply) + interpreter.process_task(task) + end + end + + context 'when task condition is not met' do + before do + allow(task).to receive(:condition_met?) { false } + end + + it 'does not apply the actions' do + expect(action).not_to receive(:apply) + interpreter.process_task(task) + end end end end diff --git a/spec/producer/core/task/dsl_spec.rb b/spec/producer/core/task/dsl_spec.rb index 38b508d..c1dcebb 100644 --- a/spec/producer/core/task/dsl_spec.rb +++ b/spec/producer/core/task/dsl_spec.rb @@ -27,11 +27,13 @@ module Producer::Core Task::DSL.evaluate(name, env, &block) end - it 'builds a task with its name and registered actions' do + it 'builds a task with its name, actions and condition' do dsl = double('dsl').as_null_object allow(Task::DSL).to receive(:new) { dsl } - allow(dsl).to receive(:actions) { [:some_action]} - expect(Task).to receive(:new).with(:some_task, [:some_action]) + allow(dsl).to receive(:actions) { [:some_action] } + allow(dsl).to receive(:condition) { :some_condition } + expect(Task) + .to receive(:new).with(:some_task, [:some_action], :some_condition) Task::DSL.evaluate(name, env, &block) end @@ -53,6 +55,10 @@ module Producer::Core it 'assigns no action' do expect(dsl.actions).to be_empty end + + it 'assigns true as the condition' do + expect(dsl.instance_eval { @condition }).to be_true + end end describe '#actions' do @@ -89,27 +95,35 @@ module Producer::Core end end - describe '#condition' do - context 'when met (block evals to true)' do - let(:block) { proc { - condition { true } - throw :after_condition - } } + context 'DSL specific methods' do + subject(:dsl) { Task::DSL.new(&block).evaluate(env) } - it 'evaluates all the block' do - expect { dsl.evaluate(env) } - .to throw_symbol :after_condition + describe '#condition' do + context 'when a block is given' do + let(:block) { proc { condition { :some_value } } } + + it 'builds a new evaluated condition' do + expect(Condition) + .to receive(:evaluate).with(env) do |&b| + expect(b.call).to eq :some_value + end + dsl + end + + it 'assigns the new condition' do + condition = double('condition').as_null_object + allow(Condition).to receive(:evaluate) { condition } + expect(dsl.condition).to be condition + end end end + end - context 'when not met (block evals to false)' do - let(:block) { proc { - condition { false } - throw :after_condition - } } - - it 'stops block evaluation' do - expect { dsl.evaluate(env) }.not_to throw_symbol :after_condition + describe '#condition' do + context 'without block' do + it 'returns the assigned condition' do + dsl.instance_eval { @condition = :some_condition } + expect(dsl.condition).to be :some_condition end end end diff --git a/spec/producer/core/task_spec.rb b/spec/producer/core/task_spec.rb index 3838ec7..41cd7e0 100644 --- a/spec/producer/core/task_spec.rb +++ b/spec/producer/core/task_spec.rb @@ -4,7 +4,8 @@ module Producer::Core describe Task do let(:name) { :some_task } let(:action) { double('action') } - subject(:task) { Task.new(name, [action]) } + let(:condition) { double('condition') } + subject(:task) { Task.new(name, [action], condition) } describe '.evaluate' do let(:env) { double('env') } @@ -34,12 +35,20 @@ module Producer::Core expect(task.instance_eval { @actions }).to eq [action] end + it 'assigns the condition' do + expect(task.instance_eval { @condition }).to eq condition + end + context 'when only the name is given as argument' do subject(:task) { Task.new(name) } it 'assigns no action' do expect(task.actions).to be_empty end + + it 'assigns a truthy condition' do + expect(task.condition).to be_true + end end end @@ -54,5 +63,29 @@ module Producer::Core expect(task.actions).to eq [action] end end + + describe '#condition' do + it 'returns the assigned condition' do + expect(task.condition).to be condition + end + end + + describe '#condition_met?' do + context 'when condition is truthy' do + let(:condition) { Condition.new(true) } + + it 'returns true' do + expect(task.condition_met?).to be true + end + end + + context 'when condition is falsy' do + let(:condition) { Condition.new(false) } + + it 'returns false' do + expect(task.condition_met?).to be false + end + end + end end end