vaguerent/test/unit/vagrant/action/builder_test.rb
Chris Roberts 5003bb6e15 Updates to address all Ruby deprecations and warnings
This includes updates for resolving all warnings provided by Ruby
for deprecations and/or removed methods. It also enables support
for Ruby 2.7 in the specification constraint as all 2.7 related
warnings are resolved with this changeset.
2020-08-10 13:05:41 -07:00

762 lines
21 KiB
Ruby

require File.expand_path("../../../base", __FILE__)
describe Vagrant::Action::Builder do
let(:data) { { data: [] } }
# This returns a proc that can be used with the builder
# that simply appends data to an array in the env.
def appender_proc(data)
result = Proc.new { |env| env[:data] << data }
# Define a to_s on it for helpful output
result.define_singleton_method(:to_s) do
"<Appender: #{data}>"
end
result
end
def wrapper_proc(data)
Class.new do
def initialize(app, env)
@app = app
end
def self.name
"TestAction"
end
define_method(:call) do |env|
env[:data] << "#{data}_in"
@app.call(env)
env[:data] << "#{data}_out"
end
end
end
context "copying" do
it "should copy the stack" do
copy = subject.dup
expect(copy.stack.object_id).not_to eq(subject.stack.object_id)
end
end
context "build" do
it "should provide build as a shortcut for basic sequences" do
data = {}
proc = Proc.new { |env| env[:data] = true }
subject = described_class.build(proc)
subject.call(data)
expect(data[:data]).to eq(true)
end
end
context "basic `use`" do
it "should add items to the stack and make them callable" do
data = {}
proc = Proc.new { |env| env[:data] = true }
subject.use proc
subject.call(data)
expect(data[:data]).to eq(true)
end
it "should be able to add multiple items" do
data = {}
proc1 = Proc.new { |env| env[:one] = true }
proc2 = Proc.new { |env| env[:two] = true }
subject.use proc1
subject.use proc2
subject.call(data)
expect(data[:one]).to eq(true)
expect(data[:two]).to eq(true)
end
it "should be able to add another builder" do
data = {}
proc1 = Proc.new { |env| env[:one] = true }
# Build the first builder
one = described_class.new
one.use proc1
# Add it to this builder
two = described_class.new
two.use one
# Call the 2nd and verify results
two.call(data)
expect(data[:one]).to eq(true)
end
end
context "inserting" do
it "can insert at an index" do
subject.use appender_proc(1)
subject.insert(0, appender_proc(2))
subject.call(data)
expect(data[:data]).to eq([2, 1])
end
it "can insert by name" do
# Create the proc then make sure it has a name
bar_proc = appender_proc(2)
def bar_proc.name; :bar; end
subject.use appender_proc(1)
subject.use bar_proc
subject.insert_before :bar, appender_proc(3)
subject.call(data)
expect(data[:data]).to eq([1, 3, 2])
end
it "can insert next to a previous object" do
proc2 = appender_proc(2)
subject.use appender_proc(1)
subject.use proc2
subject.insert(proc2, appender_proc(3))
subject.call(data)
expect(data[:data]).to eq([1, 3, 2])
end
it "can insert before" do
subject.use appender_proc(1)
subject.insert_before 0, appender_proc(2)
subject.call(data)
expect(data[:data]).to eq([2, 1])
end
it "can insert after" do
subject.use appender_proc(1)
subject.use appender_proc(3)
subject.insert_after 0, appender_proc(2)
subject.call(data)
expect(data[:data]).to eq([1, 2, 3])
end
it "merges middleware stacks of other builders" do
wrapper_class = Proc.new do |letter|
Class.new do
def initialize(app, env)
@app = app
end
def self.name
"TestAction"
end
define_method(:call) do |env|
env[:data] << "#{letter}1"
@app.call(env)
env[:data] << "#{letter}2"
end
end
end
proc2 = appender_proc(2)
subject.use appender_proc(1)
subject.use proc2
builder = described_class.new
builder.use wrapper_class.call("A")
builder.use wrapper_class.call("B")
subject.insert(proc2, builder)
subject.call(data)
expect(data[:data]).to eq([1, "A1", "B1", 2, "B2", "A2"])
end
it "raises an exception if an invalid object given for insert" do
expect { subject.insert "object", appender_proc(1) }.
to raise_error(RuntimeError)
end
it "raises an exception if an invalid object given for insert_after" do
expect { subject.insert_after "object", appender_proc(1) }.
to raise_error(RuntimeError)
end
end
context "replace" do
it "can replace an object" do
proc1 = appender_proc(1)
proc2 = appender_proc(2)
subject.use proc1
subject.replace proc1, proc2
subject.call(data)
expect(data[:data]).to eq([2])
end
it "can replace by index" do
proc1 = appender_proc(1)
proc2 = appender_proc(2)
subject.use proc1
subject.replace 0, proc2
subject.call(data)
expect(data[:data]).to eq([2])
end
end
context "deleting" do
it "can delete by object" do
proc1 = appender_proc(1)
subject.use proc1
subject.use appender_proc(2)
subject.delete proc1
subject.call(data)
expect(data[:data]).to eq([2])
end
it "can delete by index" do
proc1 = appender_proc(1)
subject.use proc1
subject.use appender_proc(2)
subject.delete 0
subject.call(data)
expect(data[:data]).to eq([2])
end
end
describe "action hooks" do
let(:hook) { double("hook") }
let(:manager) { Vagrant.plugin("2").manager }
before do
allow(manager).to receive(:action_hooks).and_return([])
end
it "applies them properly" do
hook_proc = proc{ |h| h.append(appender_proc(2)) }
expect(manager).to receive(:action_hooks).with(:test_action).
and_return([hook_proc])
data[:action_name] = :test_action
subject.use appender_proc(1)
subject.call(data)
expect(data[:data]).to eq([1, 2])
expect(data[:action_hooks_already_ran]).to eq(true)
end
it "applies without prepend/append if it has already" do
hook_proc = proc{ |h| h.append(appender_proc(2)) }
expect(manager).to receive(:action_hooks).with(:test_action).
and_return([hook_proc])
data[:action_name] = :test_action
subject.use appender_proc(1)
subject.call(data.merge(action_hooks_already_ran: true))
expect(data[:data]).to eq([1])
subject.call(data)
end
end
describe "calling another app later" do
it "calls in the proper order" do
# We have to do this because inside the Class.new, it can't see these
# rspec methods...
described_klass = described_class
wrapper_proc = self.method(:wrapper_proc)
wrapper = Class.new do
def initialize(app, env)
@app = app
end
def self.name
"TestAction"
end
define_method(:call) do |env|
inner = described_klass.new
inner.use wrapper_proc[2]
inner.use @app
inner.call(env)
end
end
subject.use wrapper_proc(1)
subject.use wrapper
subject.use wrapper_proc(3)
subject.call(data)
expect(data[:data]).to eq([
"1_in", "2_in", "3_in", "3_out", "2_out", "1_out"])
end
end
describe "dynamic action hooks" do
class ActionOne
def initialize(app, env)
@app = app
end
def call(env)
env[:data] << 1 if env[:data]
@app.call(env)
end
def recover(env)
env[:recover] << 1
end
end
class ActionTwo
def initialize(app, env)
@app = app
end
def call(env)
env[:data] << 2 if env[:data]
@app.call(env)
end
def recover(env)
env[:recover] << 2
end
end
let(:data) { {data: []} }
let(:hook_action_name) { :action_two }
let(:plugin) do
h_name = hook_action_name
@plugin ||= Class.new(Vagrant.plugin("2")) do
name "Test Plugin"
action_hook(:before_test, h_name) do |hook|
hook.prepend(proc{ |env| env[:data] << :first })
end
end
end
before { plugin }
after do
Vagrant.plugin("2").manager.unregister(@plugin) if @plugin
@plugin = nil
end
it "should call hook before running action" do
instance = described_class.build(ActionTwo)
instance.call(data)
expect(data[:data].first).to eq(:first)
expect(data[:data].last).to eq(2)
end
context "when hook is appending to action" do
let(:plugin) do
@plugin ||= Class.new(Vagrant.plugin("2")) do
name "Test Plugin"
action_hook(:before_test, :action_two) do |hook|
hook.append(proc{ |env| env[:data] << :first })
end
end
end
it "should call hook after action when action is nested" do
instance = described_class.build(ActionTwo).use(described_class.build(ActionOne))
instance.call(data)
expect(data[:data][0]).to eq(2)
expect(data[:data][1]).to eq(:first)
expect(data[:data][2]).to eq(1)
end
end
context "when hook uses class name" do
let(:hook_action_name) { "ActionTwo" }
it "should execute the hook" do
instance = described_class.build(ActionTwo)
instance.call(data)
expect(data[:data]).to include(:first)
end
end
context "when action includes a namespace" do
module Vagrant
module Test
class ActionTest
def initialize(app, env)
@app = app
end
def call(env)
env[:data] << :test if env[:data]
@app.call(env)
end
end
end
end
let(:instance) { described_class.build(Vagrant::Test::ActionTest) }
context "when hook uses short snake case name" do
let(:hook_action_name) { :action_test }
it "should execute the hook" do
instance.call(data)
expect(data[:data]).to include(:first)
end
end
context "when hook uses partial snake case name" do
let(:hook_action_name) { :test_action_test }
it "should execute the hook" do
instance.call(data)
expect(data[:data]).to include(:first)
end
end
context "when hook uses full snake case name" do
let(:hook_action_name) { :vagrant_test_action_test }
it "should execute the hook" do
instance.call(data)
expect(data[:data]).to include(:first)
end
end
context "when hook uses short class name" do
let(:hook_action_name) { "ActionTest" }
it "should execute the hook" do
instance.call(data)
expect(data[:data]).to include(:first)
end
end
context "when hook uses partial namespace class name" do
let(:hook_action_name) { "Test::ActionTest" }
it "should execute the hook" do
instance.call(data)
expect(data[:data]).to include(:first)
end
end
context "when hook uses full namespace class name" do
let(:hook_action_name) { "Vagrant::Test::ActionTest" }
it "should execute the hook" do
instance.call(data)
expect(data[:data]).to include(:first)
end
end
end
end
describe "#apply_dynamic_updates" do
let(:env) { {triggers: triggers, machine: machine} }
let(:machine) { nil }
let(:triggers) { nil }
let(:subject) do
@subject ||= described_class.new.tap do |b|
b.use Vagrant::Action::Builtin::EnvSet
b.use Vagrant::Action::Builtin::Confirm
end
end
after { @subject = nil }
it "should not modify the builder stack by default" do
s1 = subject.stack.dup
subject.apply_dynamic_updates(env)
s2 = subject.stack.dup
expect(s1).to eq(s2)
end
context "when an action hooks is defined" do
let(:plugin) do
@plugin ||= Class.new(Vagrant.plugin("2")) do
name "Test Plugin"
action_hook(:before_action, Vagrant::Action::Builtin::Confirm) do |hook|
hook.prepend(Vagrant::Action::Builtin::Call)
end
end
end
before { plugin }
after do
Vagrant.plugin("2").manager.unregister(@plugin) if @plugin
@plugin = nil
end
it "should modify the builder stack" do
s1 = subject.stack.dup
subject.apply_dynamic_updates(env)
s2 = subject.stack.dup
expect(s1).not_to eq(s2)
end
it "should add new action to the middle of the call stack" do
subject.apply_dynamic_updates(env)
expect(subject.stack[1].first).to eq(Vagrant::Action::Builtin::Call)
end
end
context "when triggers are enabled" do
let(:triggers) { double("triggers") }
before do
allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).
with("typed_triggers").and_return(true)
allow(triggers).to receive(:find).and_return([])
end
it "should not modify the builder stack by default" do
s1 = subject.stack.dup
subject.apply_dynamic_updates(env)
s2 = subject.stack.dup
expect(s1).to eq(s2)
end
context "when triggers are found" do
let(:action) { Vagrant::Action::Builtin::EnvSet }
before { expect(triggers).to receive(:find).
with(action, timing, nil, type).and_return([true]) }
context "for action type" do
let(:type) { :action }
context "for before timing" do
let(:timing) { :before }
it "should add trigger action to start of stack" do
subject.apply_dynamic_updates(env)
expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should have timing and type arguments" do
subject.apply_dynamic_updates(env)
args = subject.stack[0].arguments.parameters
expect(args).to include(type)
expect(args).to include(timing)
expect(args).to include(action.to_s)
end
end
context "for after timing" do
let(:timing) { :after }
it "should add trigger action to middle of stack" do
subject.apply_dynamic_updates(env)
expect(subject.stack[1].middleware).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should have timing and type arguments" do
subject.apply_dynamic_updates(env)
args = subject.stack[1].arguments.parameters
expect(args).to include(type)
expect(args).to include(timing)
expect(args).to include(action.to_s)
end
end
end
context "for hook type" do
let(:type) { :hook }
context "for before timing" do
let(:timing) { :before }
it "should add trigger action to start of stack" do
subject.apply_dynamic_updates(env)
expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should have timing and type arguments" do
subject.apply_dynamic_updates(env)
args = subject.stack[0].arguments.parameters
expect(args).to include(type)
expect(args).to include(timing)
expect(args).to include(action.to_s)
end
end
context "for after timing" do
let(:timing) { :after }
it "should add trigger action to middle of stack" do
subject.apply_dynamic_updates(env)
expect(subject.stack[1].first).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should have timing and type arguments" do
subject.apply_dynamic_updates(env)
args = subject.stack[1].arguments.parameters
expect(args).to include(type)
expect(args).to include(timing)
expect(args).to include(action.to_s)
end
end
end
end
end
end
describe "#apply_action_name" do
let(:env) { {triggers: triggers, machine: machine, action_name: action_name, raw_action_name: raw_action_name} }
let(:raw_action_name) { :up }
let(:action_name) { "machine_#{raw_action_name}".to_sym }
let(:machine) { nil }
let(:triggers) { double("triggers") }
let(:subject) do
@subject ||= described_class.new.tap do |b|
b.use Vagrant::Action::Builtin::EnvSet
b.use Vagrant::Action::Builtin::Confirm
end
end
before { allow(triggers).to receive(:find).and_return([]) }
after { @subject = nil }
it "should mark action hooks applied within env" do
subject.apply_action_name(env)
expect(env[:action_hooks_already_ran]).to be_truthy
end
context "when a plugin has added an action hook" do
let(:plugin) do
@plugin ||= Class.new(Vagrant.plugin("2")) do
name "Test Plugin"
action_hook(:before_action, :machine_up) do |hook|
hook.prepend(Vagrant::Action::Builtin::Call)
end
end
end
before { plugin }
after do
Vagrant.plugin("2").manager.unregister(@plugin) if @plugin
@plugin = nil
end
it "should add new action to the call stack" do
subject.apply_action_name(env)
expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Call)
end
it "should only add new action to the call stack once" do
subject.apply_action_name(env)
subject.apply_action_name(env)
expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Call)
expect(subject.stack[1].first).not_to eq(Vagrant::Action::Builtin::Call)
end
end
context "when trigger has been defined for raw action" do
before do
expect(triggers).to receive(:find).with(raw_action_name, timing, nil, :action, all: true).
and_return([true])
end
context "when timing is before" do
let(:timing) { :before }
it "should add a trigger action to the start of the stack" do
subject.apply_action_name(env)
expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should include arguments to the trigger action" do
subject.apply_action_name(env)
args = subject.stack[0].arguments.parameters
expect(args).to include(raw_action_name)
expect(args).to include(timing)
expect(args).to include(:action)
end
end
context "when timing is after" do
let(:timing) { :after }
it "should add a trigger action to the end of the stack" do
subject.apply_action_name(env)
expect(subject.stack.first.first).to eq(Vagrant::Action::Builtin::Delayed)
end
it "should include arguments to the trigger action" do
subject.apply_action_name(env)
builder = subject.stack.first.arguments.parameters.first
expect(builder).not_to be_nil
args = builder.stack.first.arguments.parameters
expect(args).to include(raw_action_name)
expect(args).to include(timing)
expect(args).to include(:action)
end
end
end
context "when trigger has been defined for hook" do
before do
allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).
with("typed_triggers").and_return(true)
expect(triggers).to receive(:find).with(action_name, timing, nil, :hook).
and_return([true])
end
context "when timing is before" do
let(:timing) { :before }
it "should add a trigger action to the start of the stack" do
subject.apply_action_name(env)
expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should include arguments to the trigger action" do
subject.apply_action_name(env)
args = subject.stack[0].arguments.parameters
expect(args).to include(action_name)
expect(args).to include(timing)
expect(args).to include(:hook)
end
end
context "when timing is after" do
let(:timing) { :after }
it "should add a trigger action to the end of the stack" do
subject.apply_action_name(env)
expect(subject.stack.last.first).to eq(Vagrant::Action::Builtin::Trigger)
end
it "should include arguments to the trigger action" do
subject.apply_action_name(env)
args = subject.stack.last.arguments.parameters
expect(args).to include(action_name)
expect(args).to include(timing)
expect(args).to include(:hook)
end
end
end
end
end