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.
This commit is contained in:
Thibault Jouan 2013-08-08 01:52:48 +00:00
parent 0008f0255a
commit 639bdc1c73
12 changed files with 237 additions and 41 deletions

View File

@ -1,13 +1,12 @@
Feature: `condition' task keyword 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: Given a recipe with:
""" """
task :hello do task :hello do
condition { false } condition { false }
puts 'evaluated' echo 'evaluated'
exit 70
end end
""" """
When I execute the recipe When I execute the recipe

View File

@ -2,6 +2,8 @@ require 'producer/core/action'
require 'producer/core/actions/echo' require 'producer/core/actions/echo'
require 'producer/core/actions/shell_command' require 'producer/core/actions/shell_command'
require 'producer/core/cli' require 'producer/core/cli'
require 'producer/core/condition'
require 'producer/core/condition/dsl'
require 'producer/core/env' require 'producer/core/env'
require 'producer/core/errors' require 'producer/core/errors'
require 'producer/core/interpreter' require 'producer/core/interpreter'

View File

@ -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

View File

@ -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

View File

@ -6,7 +6,7 @@ module Producer
end end
def process_task(task) def process_task(task)
task.actions.each(&:apply) task.actions.each(&:apply) if task.condition_met?
end end
end end
end end

View File

@ -1,15 +1,20 @@
module Producer module Producer
module Core module Core
class Task class Task
attr_reader :name, :actions attr_reader :name, :actions, :condition
def self.evaluate(name, env, &block) def self.evaluate(name, env, &block)
DSL.evaluate(name, env, &block) DSL.evaluate(name, env, &block)
end end
def initialize(name, actions = []) def initialize(name, actions = [], condition = true)
@name = name @name = name
@actions = actions @actions = actions
@condition = condition
end
def condition_met?
!!@condition
end end
end end
end end

View File

@ -6,7 +6,7 @@ module Producer
def evaluate(name, env, &block) def evaluate(name, env, &block)
dsl = new(&block) dsl = new(&block)
dsl.evaluate(env) dsl.evaluate(env)
Task.new(name, dsl.actions) Task.new(name, dsl.actions, dsl.condition)
end end
def define_action(keyword, klass) def define_action(keyword, klass)
@ -24,18 +24,17 @@ module Producer
def initialize(&block) def initialize(&block)
@block = block @block = block
@actions = [] @actions = []
@condition = true
end end
def evaluate(env) def evaluate(env)
@env = env @env = env
instance_eval &@block instance_eval &@block
rescue ConditionNotMetError
end end
private
def condition(&block) def condition(&block)
fail ConditionNotMetError unless block.call @condition = Condition.evaluate(@env, &block) if block
@condition
end end
end end
end end

View File

@ -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

View File

@ -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

View File

@ -12,13 +12,30 @@ module Producer::Core
end end
describe '#process_task' do describe '#process_task' do
it 'applies the task actions' do let(:action) { double('action') }
action = double('action') let(:task) { double('task').as_null_object }
task = double('task')
before do
allow(task).to receive(:actions) { [action] } allow(task).to receive(:actions) { [action] }
end
context 'when task condition is met' do
it 'applies the actions' do
expect(action).to receive(:apply) expect(action).to receive(:apply)
interpreter.process_task(task) interpreter.process_task(task)
end end
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 end
end end

View File

@ -27,11 +27,13 @@ module Producer::Core
Task::DSL.evaluate(name, env, &block) Task::DSL.evaluate(name, env, &block)
end 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 dsl = double('dsl').as_null_object
allow(Task::DSL).to receive(:new) { dsl } allow(Task::DSL).to receive(:new) { dsl }
allow(dsl).to receive(:actions) { [:some_action]} allow(dsl).to receive(:actions) { [:some_action] }
expect(Task).to receive(:new).with(:some_task, [: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) Task::DSL.evaluate(name, env, &block)
end end
@ -53,6 +55,10 @@ module Producer::Core
it 'assigns no action' do it 'assigns no action' do
expect(dsl.actions).to be_empty expect(dsl.actions).to be_empty
end end
it 'assigns true as the condition' do
expect(dsl.instance_eval { @condition }).to be_true
end
end end
describe '#actions' do describe '#actions' do
@ -89,27 +95,35 @@ module Producer::Core
end end
end end
context 'DSL specific methods' do
subject(:dsl) { Task::DSL.new(&block).evaluate(env) }
describe '#condition' do describe '#condition' do
context 'when met (block evals to true)' do context 'when a block is given' do
let(:block) { proc { let(:block) { proc { condition { :some_value } } }
condition { true }
throw :after_condition
} }
it 'evaluates all the block' do it 'builds a new evaluated condition' do
expect { dsl.evaluate(env) } expect(Condition)
.to throw_symbol :after_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
end end
context 'when not met (block evals to false)' do describe '#condition' do
let(:block) { proc { context 'without block' do
condition { false } it 'returns the assigned condition' do
throw :after_condition dsl.instance_eval { @condition = :some_condition }
} } expect(dsl.condition).to be :some_condition
it 'stops block evaluation' do
expect { dsl.evaluate(env) }.not_to throw_symbol :after_condition
end end
end end
end end

View File

@ -4,7 +4,8 @@ module Producer::Core
describe Task do describe Task do
let(:name) { :some_task } let(:name) { :some_task }
let(:action) { double('action') } let(:action) { double('action') }
subject(:task) { Task.new(name, [action]) } let(:condition) { double('condition') }
subject(:task) { Task.new(name, [action], condition) }
describe '.evaluate' do describe '.evaluate' do
let(:env) { double('env') } let(:env) { double('env') }
@ -34,12 +35,20 @@ module Producer::Core
expect(task.instance_eval { @actions }).to eq [action] expect(task.instance_eval { @actions }).to eq [action]
end 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 context 'when only the name is given as argument' do
subject(:task) { Task.new(name) } subject(:task) { Task.new(name) }
it 'assigns no action' do it 'assigns no action' do
expect(task.actions).to be_empty expect(task.actions).to be_empty
end end
it 'assigns a truthy condition' do
expect(task.condition).to be_true
end
end end
end end
@ -54,5 +63,29 @@ module Producer::Core
expect(task.actions).to eq [action] expect(task.actions).to eq [action]
end end
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
end end