diff --git a/features/recipe_test_macro.feature b/features/recipe_test_macro.feature new file mode 100644 index 0000000..03e5f55 --- /dev/null +++ b/features/recipe_test_macro.feature @@ -0,0 +1,52 @@ +Feature: `test_macro' recipe keyword + + Scenario: declares a new test keyword + Given a recipe with: + """ + test_macro :even? do |n| + n % 2 == 0 + end + + [1, 2].each do |n| + task "test_macro-even-#{n}" do + condition { even? n } + echo n + end + end + """ + When I successfully execute the recipe + Then the output must contain "2" + And the output must not contain "1" + + @sshd + Scenario: has access to core tests + Given a recipe with: + """ + target 'some_host.test' + + test_macro(:other_env?) { |k| env? k } + + [:shell, :non_existent_var].each do |k| + task "test_macro-condition-#{k}" do + condition { other_env? k } + echo "#{k}_ok" + end + end + """ + When I successfully execute the recipe + Then the output must contain "shell_ok" + Then the output must not contain "non_existent_var_ok" + + Scenario: has access to other tests declared with `test_macro' + Given a recipe with: + """ + test_macro(:one?) { |e| e == 1 } + test_macro(:one_alias?) { |e| one? e } + + task :test_macro_macro do + condition { one_alias? 1 } + echo 'one_alias_ok' + end + """ + When I successfully execute the recipe + Then the output must contain "one_alias_ok" diff --git a/lib/producer/core.rb b/lib/producer/core.rb index c32070c..6e0d665 100644 --- a/lib/producer/core.rb +++ b/lib/producer/core.rb @@ -16,6 +16,7 @@ require 'producer/core/actions/file_writer' # condition tests (need to be defined before the condition DSL) require 'producer/core/test' +require 'producer/core/tests/condition_test' require 'producer/core/tests/file_contains' require 'producer/core/tests/has_dir' require 'producer/core/tests/has_env' diff --git a/lib/producer/core/condition.rb b/lib/producer/core/condition.rb index 0f1f9a3..c77b522 100644 --- a/lib/producer/core/condition.rb +++ b/lib/producer/core/condition.rb @@ -2,9 +2,9 @@ module Producer module Core class Condition class << self - def evaluate(env, &block) + def evaluate(env, *args, &block) dsl = DSL.new(env, &block) - return_value = dsl.evaluate + return_value = dsl.evaluate *args Condition.new(dsl.tests, return_value) end end diff --git a/lib/producer/core/condition/dsl.rb b/lib/producer/core/condition/dsl.rb index 4bc0721..d6e17ae 100644 --- a/lib/producer/core/condition/dsl.rb +++ b/lib/producer/core/condition/dsl.rb @@ -3,12 +3,21 @@ module Producer class Condition class DSL class << self - def define_test(keyword, klass) - define_method(keyword) do |*args| - @tests << klass.new(@env, *args) - end - define_method("no_#{keyword}") do |*args| - @tests << klass.new(@env, *args, negated: true) + def define_test(keyword, test) + { + keyword => false, + "no_#{keyword}" => true + }.each do |kw, negated| + define_method(kw) do |*args| + if test.respond_to? :call + args = [test, *args] + klass = Tests::ConditionTest + else + klass = test + end + t = klass.new(@env, *args, negated: negated) + @tests << t + end end end end @@ -29,8 +38,8 @@ module Producer @tests = [] end - def evaluate - instance_eval &@block + def evaluate(*args) + instance_exec *args, &@block end end end diff --git a/lib/producer/core/recipe/dsl.rb b/lib/producer/core/recipe/dsl.rb index 311de7e..6d61b00 100644 --- a/lib/producer/core/recipe/dsl.rb +++ b/lib/producer/core/recipe/dsl.rb @@ -38,6 +38,10 @@ module Producer end end + def test_macro(name, dsl: Condition::DSL, &block) + dsl.define_test(name, block) + end + def set(key, value) env[key] = value end diff --git a/lib/producer/core/tests/condition_test.rb b/lib/producer/core/tests/condition_test.rb new file mode 100644 index 0000000..d12d79d --- /dev/null +++ b/lib/producer/core/tests/condition_test.rb @@ -0,0 +1,23 @@ +module Producer + module Core + module Tests + class ConditionTest < Test + def verify + condition.met? + end + + def condition + Condition.evaluate(env, *condition_args, &condition_block) + end + + def condition_args + arguments.drop 1 + end + + def condition_block + arguments.first + end + end + end + end +end diff --git a/spec/producer/core/condition/dsl_spec.rb b/spec/producer/core/condition/dsl_spec.rb index 14ef1d3..5baca4a 100644 --- a/spec/producer/core/condition/dsl_spec.rb +++ b/spec/producer/core/condition/dsl_spec.rb @@ -22,9 +22,9 @@ module Producer::Core end describe '.define_test' do - let(:some_test_class) { Test } + let(:some_test) { Test } - before { described_class.define_test(:some_test, some_test_class) } + before { described_class.define_test(:some_test, some_test) } it 'defines a new test keyword' do expect(dsl).to respond_to :some_test @@ -41,19 +41,38 @@ module Producer::Core it 'registers the test with current env' do dsl.some_test - expect(dsl.tests.first.env).to be env + expect(dsl.tests.last.env).to be env end it 'registers the test with given arguments' do dsl.some_test :some, :args - expect(dsl.tests.first.arguments).to eq [:some, :args] + expect(dsl.tests.last.arguments).to eq [:some, :args] + end + + context 'when given test is callable' do + let(:some_test) { proc {} } + + before { dsl.some_test } + + it 'registers a condition test' do + expect(dsl.tests.last).to be_a Tests::ConditionTest + end + + it 'registers the test with given block' do + expect(dsl.tests.last.condition_block).to be some_test + end + + it 'registers the test with given arguments' do + dsl.some_test :some, :args + expect(dsl.tests.last.condition_args).to eq [:some, :args] + end end end context 'when a negated test keyword is called' do it 'registers a negated test' do dsl.no_some_test - expect(dsl.tests.first).to be_negated + expect(dsl.tests.last).to be_negated end end end @@ -81,6 +100,15 @@ module Producer::Core it 'returns the value returned by the assigned block' do expect(dsl.evaluate).to eq block.call end + + context 'when arguments are given' do + let(:block) { proc { |e| throw e } } + + it 'passes arguments as block parameters' do + expect { dsl.evaluate :some_argument } + .to throw_symbol :some_argument + end + end end end end diff --git a/spec/producer/core/recipe/dsl_spec.rb b/spec/producer/core/recipe/dsl_spec.rb index fa138f5..884fb99 100644 --- a/spec/producer/core/recipe/dsl_spec.rb +++ b/spec/producer/core/recipe/dsl_spec.rb @@ -102,6 +102,16 @@ module Producer::Core end end + describe '#test_macro' do + it 'defines the new test' do + condition_dsl = double 'condition dsl' + test_block = proc {} + expect(condition_dsl) + .to receive(:define_test).with(:some_test, test_block) + dsl.test_macro(:some_test, dsl: condition_dsl, &test_block) + end + end + describe '#set' do it 'registers a key/value pair in env registry' do dsl.set :some_key, :some_value diff --git a/spec/producer/core/tests/condition_test_spec.rb b/spec/producer/core/tests/condition_test_spec.rb new file mode 100644 index 0000000..7fc498e --- /dev/null +++ b/spec/producer/core/tests/condition_test_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +module Producer::Core + module Tests + describe ConditionTest do + let(:env) { double 'env' } + let(:block) { proc { true } } + let(:arguments) { [:some, :args] } + subject(:test) { described_class.new(env, block, *arguments) } + + it_behaves_like 'test' + + describe '#verify' do + context 'when condition is met' do + it 'returns true' do + expect(test.verify).to be true + end + end + + context 'when condition is not met' do + let(:block) { proc { false } } + + it 'returns false' do + expect(test.verify).to be false + end + end + end + + describe '#condition' do + it 'evaluates a conditon' do + expect(Condition).to receive(:evaluate).with(env, *arguments, &block) + test.condition + end + + it 'returns the evaluated condition' do + condition = double 'condition' + allow(Condition).to receive(:evaluate) { condition } + expect(test.condition).to eq condition + end + end + + describe '#condition_args' do + it 'returns arguments for condition' do + expect(test.condition_args).to eq arguments + end + end + + describe '#condition_block' do + it 'returns condition block' do + expect(test.condition_block).to eq block + end + end + end + end +end