"Fossies" - the Fresh Open Source Software Archive

Member "vagrant-2.2.14/test/unit/vagrant/action/builder_test.rb" (20 Nov 2020, 22301 Bytes) of package /linux/misc/vagrant-2.2.14.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "builder_test.rb": 2.2.13_vs_2.2.14.

    1 require File.expand_path("../../../base", __FILE__)
    2 
    3 describe Vagrant::Action::Builder do
    4   let(:data) { { data: [] } }
    5 
    6   # This returns a proc that can be used with the builder
    7   # that simply appends data to an array in the env.
    8   def appender_proc(data)
    9     result = Proc.new { |env| env[:data] << data }
   10 
   11     # Define a to_s on it for helpful output
   12     result.define_singleton_method(:to_s) do
   13       "<Appender: #{data}>"
   14     end
   15 
   16     result
   17   end
   18 
   19   def wrapper_proc(data)
   20     Class.new do
   21       def initialize(app, env)
   22         @app = app
   23       end
   24 
   25       def self.name
   26         "TestAction"
   27       end
   28 
   29       define_method(:call) do |env|
   30         env[:data] << "#{data}_in"
   31         @app.call(env)
   32         env[:data] << "#{data}_out"
   33       end
   34     end
   35   end
   36 
   37   context "copying" do
   38     it "should copy the stack" do
   39       copy = subject.dup
   40       expect(copy.stack.object_id).not_to eq(subject.stack.object_id)
   41     end
   42   end
   43 
   44   context "build" do
   45     it "should provide build as a shortcut for basic sequences" do
   46       data = {}
   47       proc = Proc.new { |env| env[:data] = true }
   48 
   49       subject = described_class.build(proc)
   50       subject.call(data)
   51 
   52       expect(data[:data]).to eq(true)
   53     end
   54   end
   55 
   56   context "basic `use`" do
   57     it "should add items to the stack and make them callable" do
   58       data = {}
   59       proc = Proc.new { |env| env[:data] = true }
   60 
   61       subject.use proc
   62       subject.call(data)
   63 
   64       expect(data[:data]).to eq(true)
   65     end
   66 
   67     it "should be able to add multiple items" do
   68       data = {}
   69       proc1 = Proc.new { |env| env[:one] = true }
   70       proc2 = Proc.new { |env| env[:two] = true }
   71 
   72       subject.use proc1
   73       subject.use proc2
   74       subject.call(data)
   75 
   76       expect(data[:one]).to eq(true)
   77       expect(data[:two]).to eq(true)
   78     end
   79 
   80     it "should be able to add another builder" do
   81       data  = {}
   82       proc1 = Proc.new { |env| env[:one] = true }
   83 
   84       # Build the first builder
   85       one   = described_class.new
   86       one.use proc1
   87 
   88       # Add it to this builder
   89       two   = described_class.new
   90       two.use one
   91 
   92       # Call the 2nd and verify results
   93       two.call(data)
   94       expect(data[:one]).to eq(true)
   95     end
   96   end
   97 
   98   context "inserting" do
   99     it "can insert at an index" do
  100       subject.use appender_proc(1)
  101       subject.insert(0, appender_proc(2))
  102       subject.call(data)
  103 
  104       expect(data[:data]).to eq([2, 1])
  105     end
  106 
  107     it "can insert by name" do
  108       # Create the proc then make sure it has a name
  109       bar_proc = appender_proc(2)
  110       def bar_proc.name; :bar; end
  111 
  112       subject.use appender_proc(1)
  113       subject.use bar_proc
  114       subject.insert_before :bar, appender_proc(3)
  115       subject.call(data)
  116 
  117       expect(data[:data]).to eq([1, 3, 2])
  118     end
  119 
  120     it "can insert next to a previous object" do
  121       proc2 = appender_proc(2)
  122       subject.use appender_proc(1)
  123       subject.use proc2
  124       subject.insert(proc2, appender_proc(3))
  125       subject.call(data)
  126 
  127       expect(data[:data]).to eq([1, 3, 2])
  128     end
  129 
  130     it "can insert before" do
  131       subject.use appender_proc(1)
  132       subject.insert_before 0, appender_proc(2)
  133       subject.call(data)
  134 
  135       expect(data[:data]).to eq([2, 1])
  136     end
  137 
  138     it "can insert after" do
  139       subject.use appender_proc(1)
  140       subject.use appender_proc(3)
  141       subject.insert_after 0, appender_proc(2)
  142       subject.call(data)
  143 
  144       expect(data[:data]).to eq([1, 2, 3])
  145     end
  146 
  147     it "merges middleware stacks of other builders" do
  148       wrapper_class = Proc.new do |letter|
  149         Class.new do
  150           def initialize(app, env)
  151             @app = app
  152           end
  153 
  154           def self.name
  155             "TestAction"
  156           end
  157 
  158           define_method(:call) do |env|
  159             env[:data] << "#{letter}1"
  160             @app.call(env)
  161             env[:data] << "#{letter}2"
  162           end
  163         end
  164       end
  165 
  166       proc2 = appender_proc(2)
  167       subject.use appender_proc(1)
  168       subject.use proc2
  169 
  170       builder = described_class.new
  171       builder.use wrapper_class.call("A")
  172       builder.use wrapper_class.call("B")
  173 
  174       subject.insert(proc2, builder)
  175       subject.call(data)
  176 
  177       expect(data[:data]).to eq([1, "A1", "B1", 2, "B2", "A2"])
  178     end
  179 
  180     it "raises an exception if an invalid object given for insert" do
  181       expect { subject.insert "object", appender_proc(1) }.
  182         to raise_error(RuntimeError)
  183     end
  184 
  185     it "raises an exception if an invalid object given for insert_after" do
  186       expect { subject.insert_after "object", appender_proc(1) }.
  187         to raise_error(RuntimeError)
  188     end
  189   end
  190 
  191   context "replace" do
  192     it "can replace an object" do
  193       proc1 = appender_proc(1)
  194       proc2 = appender_proc(2)
  195 
  196       subject.use proc1
  197       subject.replace proc1, proc2
  198       subject.call(data)
  199 
  200       expect(data[:data]).to eq([2])
  201     end
  202 
  203     it "can replace by index" do
  204       proc1 = appender_proc(1)
  205       proc2 = appender_proc(2)
  206 
  207       subject.use proc1
  208       subject.replace 0, proc2
  209       subject.call(data)
  210 
  211       expect(data[:data]).to eq([2])
  212     end
  213   end
  214 
  215   context "deleting" do
  216     it "can delete by object" do
  217       proc1 = appender_proc(1)
  218 
  219       subject.use proc1
  220       subject.use appender_proc(2)
  221       subject.delete proc1
  222       subject.call(data)
  223 
  224       expect(data[:data]).to eq([2])
  225     end
  226 
  227     it "can delete by index" do
  228       proc1 = appender_proc(1)
  229 
  230       subject.use proc1
  231       subject.use appender_proc(2)
  232       subject.delete 0
  233       subject.call(data)
  234 
  235       expect(data[:data]).to eq([2])
  236     end
  237   end
  238 
  239   describe "action hooks" do
  240     let(:hook) { double("hook") }
  241     let(:manager) { Vagrant.plugin("2").manager }
  242 
  243     before do
  244       allow(manager).to receive(:action_hooks).and_return([])
  245     end
  246 
  247     it "applies them properly" do
  248       hook_proc = proc{ |h| h.append(appender_proc(2)) }
  249       expect(manager).to receive(:action_hooks).with(:test_action).
  250         and_return([hook_proc])
  251 
  252       data[:action_name] = :test_action
  253 
  254       subject.use appender_proc(1)
  255       subject.call(data)
  256 
  257       expect(data[:data]).to eq([1, 2])
  258     end
  259   end
  260 
  261   describe "calling another app later" do
  262     it "calls in the proper order" do
  263       # We have to do this because inside the Class.new, it can't see these
  264       # rspec methods...
  265       described_klass = described_class
  266       wrapper_proc    = self.method(:wrapper_proc)
  267 
  268       wrapper = Class.new do
  269         def initialize(app, env)
  270           @app = app
  271         end
  272 
  273         def self.name
  274           "TestAction"
  275         end
  276 
  277         define_method(:call) do |env|
  278           inner = described_klass.new
  279           inner.use wrapper_proc[2]
  280           inner.use @app
  281           inner.call(env)
  282         end
  283       end
  284 
  285       subject.use wrapper_proc(1)
  286       subject.use wrapper
  287       subject.use wrapper_proc(3)
  288       subject.call(data)
  289 
  290       expect(data[:data]).to eq([
  291         "1_in", "2_in", "3_in", "3_out", "2_out", "1_out"])
  292     end
  293   end
  294 
  295   describe "dynamic action hooks" do
  296     class ActionOne
  297       def initialize(app, env)
  298         @app = app
  299       end
  300 
  301       def call(env)
  302         env[:data] << 1 if env[:data]
  303         @app.call(env)
  304       end
  305 
  306       def recover(env)
  307         env[:recover] << 1
  308       end
  309     end
  310 
  311     class ActionTwo
  312       def initialize(app, env)
  313         @app = app
  314       end
  315 
  316       def call(env)
  317         env[:data] << 2 if env[:data]
  318         @app.call(env)
  319       end
  320 
  321       def recover(env)
  322         env[:recover] << 2
  323       end
  324     end
  325 
  326     let(:data) { {data: []} }
  327     let(:hook_action_name) { :action_two }
  328 
  329     let(:plugin) do
  330       h_name = hook_action_name
  331       @plugin ||= Class.new(Vagrant.plugin("2")) do
  332         name "Test Plugin"
  333         action_hook(:before_test, h_name) do |hook|
  334           hook.prepend(proc{ |env| env[:data] << :first })
  335         end
  336       end
  337     end
  338 
  339     before { plugin }
  340 
  341     after do
  342       Vagrant.plugin("2").manager.unregister(@plugin) if @plugin
  343       @plugin = nil
  344     end
  345 
  346     it "should call hook before running action" do
  347       instance = described_class.build(ActionTwo)
  348       instance.call(data)
  349       expect(data[:data].first).to eq(:first)
  350       expect(data[:data].last).to eq(2)
  351     end
  352 
  353     context "when hook matches action in subsequent builder" do
  354       let(:hook_action_name) { ActionOne }
  355 
  356       before do
  357         data[:action_name] = :test_action_name
  358         data[:raw_action_name] = :machine_test_action_name
  359       end
  360 
  361       it "should execute the hook" do
  362         described_class.build(ActionTwo).call(data)
  363         described_class.build(ActionOne).call(data)
  364         expect(data[:data]).to include(:first)
  365       end
  366     end
  367 
  368     context "when hook matches action name in subsequent builder" do
  369       let(:hook_action_name) { :test_action_name }
  370 
  371       before do
  372         data[:action_name] = :test_action_name
  373         data[:raw_action_name] = :machine_test_action_name
  374       end
  375 
  376       it "should execute the hook" do
  377         described_class.build(ActionTwo).call(data)
  378         described_class.build(ActionOne).call(data)
  379         expect(data[:data]).to include(:first)
  380       end
  381 
  382       it "should execute the hook multiple times" do
  383         described_class.build(ActionTwo).call(data)
  384         described_class.build(ActionOne).call(data)
  385         expect(data[:data].count{|d| d == :first}).to eq(2)
  386       end
  387     end
  388 
  389     context "when applying triggers" do
  390       let(:triggers) { double("triggers") }
  391 
  392       before do
  393         data[:action_name] = :test_action_name
  394         data[:raw_action_name] = :machine_test_action_name
  395         data[:triggers] = triggers
  396         allow(triggers).to receive(:find).and_return([])
  397       end
  398 
  399       it "should attempt to find triggers based on raw action" do
  400         expect(triggers).to receive(:find).with(data[:raw_action_name], any_args).and_return([])
  401         described_class.build(ActionOne).call(data)
  402       end
  403 
  404       it "should only attempt to find triggers based on raw action once" do
  405         expect(triggers).to receive(:find).with(data[:raw_action_name], :before, any_args).once.and_return([])
  406         expect(triggers).to receive(:find).with(data[:raw_action_name], :after, any_args).once.and_return([])
  407         described_class.build(ActionOne).call(data)
  408         described_class.build(ActionOne).call(data)
  409       end
  410     end
  411 
  412     context "when hook is appending to action" do
  413       let(:plugin) do
  414         @plugin ||= Class.new(Vagrant.plugin("2")) do
  415           name "Test Plugin"
  416           action_hook(:before_test, :action_two) do |hook|
  417             hook.append(proc{ |env| env[:data] << :first })
  418           end
  419         end
  420       end
  421 
  422       it "should call hook after action when action is nested" do
  423         instance = described_class.build(ActionTwo).use(described_class.build(ActionOne))
  424         instance.call(data)
  425         expect(data[:data][0]).to eq(2)
  426         expect(data[:data][1]).to eq(:first)
  427         expect(data[:data][2]).to eq(1)
  428       end
  429     end
  430 
  431     context "when hook uses class name" do
  432       let(:hook_action_name) { "ActionTwo" }
  433 
  434       it "should execute the hook" do
  435         instance = described_class.build(ActionTwo)
  436         instance.call(data)
  437         expect(data[:data]).to include(:first)
  438       end
  439     end
  440 
  441     context "when action includes a namespace" do
  442       module Vagrant
  443         module Test
  444           class ActionTest
  445             def initialize(app, env)
  446               @app = app
  447             end
  448 
  449             def call(env)
  450               env[:data] << :test if env[:data]
  451               @app.call(env)
  452             end
  453           end
  454         end
  455       end
  456 
  457       let(:instance) { described_class.build(Vagrant::Test::ActionTest) }
  458 
  459       context "when hook uses short snake case name" do
  460         let(:hook_action_name) { :action_test }
  461 
  462         it "should execute the hook" do
  463           instance.call(data)
  464           expect(data[:data]).to include(:first)
  465         end
  466       end
  467 
  468       context "when hook uses partial snake case name" do
  469         let(:hook_action_name) { :test_action_test }
  470 
  471         it "should execute the hook" do
  472           instance.call(data)
  473           expect(data[:data]).to include(:first)
  474         end
  475       end
  476 
  477       context "when hook uses full snake case name" do
  478         let(:hook_action_name) { :vagrant_test_action_test }
  479 
  480         it "should execute the hook" do
  481           instance.call(data)
  482           expect(data[:data]).to include(:first)
  483         end
  484       end
  485 
  486       context "when hook uses short class name" do
  487         let(:hook_action_name) { "ActionTest" }
  488 
  489         it "should execute the hook" do
  490           instance.call(data)
  491           expect(data[:data]).to include(:first)
  492         end
  493       end
  494 
  495       context "when hook uses partial namespace class name" do
  496         let(:hook_action_name) { "Test::ActionTest" }
  497 
  498         it "should execute the hook" do
  499           instance.call(data)
  500           expect(data[:data]).to include(:first)
  501         end
  502       end
  503 
  504       context "when hook uses full namespace class name" do
  505         let(:hook_action_name) { "Vagrant::Test::ActionTest" }
  506 
  507         it "should execute the hook" do
  508           instance.call(data)
  509           expect(data[:data]).to include(:first)
  510         end
  511       end
  512     end
  513   end
  514 
  515   describe "#apply_dynamic_updates" do
  516     let(:env) { {triggers: triggers, machine: machine} }
  517     let(:machine) { nil }
  518     let(:triggers) { nil }
  519 
  520     let(:subject) do
  521       @subject ||= described_class.new.tap do |b|
  522         b.use Vagrant::Action::Builtin::EnvSet
  523         b.use Vagrant::Action::Builtin::Confirm
  524       end
  525     end
  526 
  527     after { @subject = nil }
  528 
  529     it "should not modify the builder stack by default" do
  530       s1 = subject.stack.dup
  531       subject.apply_dynamic_updates(env)
  532       s2 = subject.stack.dup
  533       expect(s1).to eq(s2)
  534     end
  535 
  536     context "when an action hooks is defined" do
  537       let(:plugin) do
  538         @plugin ||= Class.new(Vagrant.plugin("2")) do
  539           name "Test Plugin"
  540           action_hook(:before_action, Vagrant::Action::Builtin::Confirm) do |hook|
  541             hook.prepend(Vagrant::Action::Builtin::Call)
  542           end
  543         end
  544       end
  545 
  546       before { plugin }
  547 
  548       after do
  549         Vagrant.plugin("2").manager.unregister(@plugin) if @plugin
  550         @plugin = nil
  551       end
  552 
  553       it "should modify the builder stack" do
  554         s1 = subject.stack.dup
  555         subject.apply_dynamic_updates(env)
  556         s2 = subject.stack.dup
  557         expect(s1).not_to eq(s2)
  558       end
  559 
  560       it "should add new action to the middle of the call stack" do
  561         subject.apply_dynamic_updates(env)
  562         expect(subject.stack[1].first).to eq(Vagrant::Action::Builtin::Call)
  563       end
  564     end
  565 
  566     context "when triggers are enabled" do
  567       let(:triggers) { double("triggers") }
  568 
  569       before do
  570         allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).
  571           with("typed_triggers").and_return(true)
  572         allow(triggers).to receive(:find).and_return([])
  573       end
  574 
  575       it "should not modify the builder stack by default" do
  576         s1 = subject.stack.dup
  577         subject.apply_dynamic_updates(env)
  578         s2 = subject.stack.dup
  579         expect(s1).to eq(s2)
  580       end
  581 
  582       context "when triggers are found" do
  583         let(:action) { Vagrant::Action::Builtin::EnvSet }
  584 
  585         before { expect(triggers).to receive(:find).
  586             with(action, timing, nil, type).and_return([true]) }
  587 
  588         context "for action type" do
  589           let(:type) { :action }
  590 
  591           context "for before timing" do
  592             let(:timing) { :before }
  593 
  594             it "should add trigger action to start of stack" do
  595               subject.apply_dynamic_updates(env)
  596               expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)
  597             end
  598 
  599             it "should have timing and type arguments" do
  600               subject.apply_dynamic_updates(env)
  601               args = subject.stack[0].arguments.parameters
  602               expect(args).to include(type)
  603               expect(args).to include(timing)
  604               expect(args).to include(action.to_s)
  605             end
  606           end
  607 
  608           context "for after timing" do
  609             let(:timing) { :after }
  610 
  611             it "should add trigger action to middle of stack" do
  612               subject.apply_dynamic_updates(env)
  613               expect(subject.stack[1].middleware).to eq(Vagrant::Action::Builtin::Trigger)
  614             end
  615 
  616             it "should have timing and type arguments" do
  617               subject.apply_dynamic_updates(env)
  618               args = subject.stack[1].arguments.parameters
  619               expect(args).to include(type)
  620               expect(args).to include(timing)
  621               expect(args).to include(action.to_s)
  622             end
  623           end
  624         end
  625 
  626         context "for hook type" do
  627           let(:type) { :hook }
  628 
  629           context "for before timing" do
  630             let(:timing) { :before }
  631 
  632             it "should add trigger action to start of stack" do
  633               subject.apply_dynamic_updates(env)
  634               expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)
  635             end
  636 
  637             it "should have timing and type arguments" do
  638               subject.apply_dynamic_updates(env)
  639               args = subject.stack[0].arguments.parameters
  640               expect(args).to include(type)
  641               expect(args).to include(timing)
  642               expect(args).to include(action.to_s)
  643             end
  644           end
  645 
  646           context "for after timing" do
  647             let(:timing) { :after }
  648 
  649             it "should add trigger action to middle of stack" do
  650               subject.apply_dynamic_updates(env)
  651               expect(subject.stack[1].first).to eq(Vagrant::Action::Builtin::Trigger)
  652             end
  653 
  654             it "should have timing and type arguments" do
  655               subject.apply_dynamic_updates(env)
  656               args = subject.stack[1].arguments.parameters
  657               expect(args).to include(type)
  658               expect(args).to include(timing)
  659               expect(args).to include(action.to_s)
  660             end
  661           end
  662         end
  663       end
  664     end
  665   end
  666 
  667   describe "#apply_action_name" do
  668     let(:env) { {triggers: triggers, machine: machine, action_name: action_name, raw_action_name: raw_action_name} }
  669     let(:raw_action_name) { :up }
  670     let(:action_name) { "machine_#{raw_action_name}".to_sym }
  671     let(:machine) { nil }
  672     let(:triggers) { double("triggers") }
  673 
  674     let(:subject) do
  675       @subject ||= described_class.new.tap do |b|
  676         b.use Vagrant::Action::Builtin::EnvSet
  677         b.use Vagrant::Action::Builtin::Confirm
  678       end
  679     end
  680 
  681     before { allow(triggers).to receive(:find).and_return([]) }
  682     after { @subject = nil }
  683 
  684     context "when a plugin has added an action hook" do
  685       let(:plugin) do
  686         @plugin ||= Class.new(Vagrant.plugin("2")) do
  687           name "Test Plugin"
  688           action_hook(:before_action, :machine_up) do |hook|
  689             hook.prepend(Vagrant::Action::Builtin::Call)
  690           end
  691         end
  692       end
  693 
  694       before { plugin }
  695 
  696       after do
  697         Vagrant.plugin("2").manager.unregister(@plugin) if @plugin
  698         @plugin = nil
  699       end
  700 
  701       it "should add new action to the call stack" do
  702         subject.apply_action_name(env)
  703         expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Call)
  704       end
  705     end
  706 
  707     context "when trigger has been defined for raw action" do
  708       before do
  709         expect(triggers).to receive(:find).with(raw_action_name, timing, nil, :action, all: true).
  710           and_return([true])
  711       end
  712 
  713       context "when timing is before" do
  714         let(:timing) { :before }
  715 
  716         it "should add a trigger action to the start of the stack" do
  717           subject.apply_action_name(env)
  718           expect(subject.stack[0].first).to eq(Vagrant::Action::Builtin::Trigger)
  719         end
  720 
  721         it "should include arguments to the trigger action" do
  722           subject.apply_action_name(env)
  723           args = subject.stack[0].arguments.parameters
  724           expect(args).to include(raw_action_name)
  725           expect(args).to include(timing)
  726           expect(args).to include(:action)
  727         end
  728       end
  729 
  730       context "when timing is after" do
  731         let(:timing) { :after }
  732 
  733         it "should add a trigger action to the end of the stack" do
  734           subject.apply_action_name(env)
  735           expect(subject.stack.first.first).to eq(Vagrant::Action::Builtin::Delayed)
  736         end
  737 
  738         it "should include arguments to the trigger action" do
  739           subject.apply_action_name(env)
  740           builder = subject.stack.first.arguments.parameters.first
  741           expect(builder).not_to be_nil
  742           args = builder.stack.first.arguments.parameters
  743           expect(args).to include(raw_action_name)
  744           expect(args).to include(timing)
  745           expect(args).to include(:action)
  746         end
  747       end
  748     end
  749 
  750     context "when trigger has been defined for hook" do
  751       before do
  752         allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).
  753           with("typed_triggers").and_return(true)
  754         expect(triggers).to receive(:find).with(action_name, timing, nil, :hook).
  755           and_return([true])
  756       end
  757 
  758       context "when timing is before" do
  759         let(:timing) { :before }
  760 
  761         it "should add a trigger action to the start of the stack" do
  762           subject.apply_action_name(env)
  763           expect(subject.stack[0].middleware).to eq(Vagrant::Action::Builtin::Trigger)
  764         end
  765 
  766         it "should include arguments to the trigger action" do
  767           subject.apply_action_name(env)
  768           args = subject.stack[0].arguments.parameters
  769           expect(args).to include(action_name)
  770           expect(args).to include(timing)
  771           expect(args).to include(:hook)
  772         end
  773       end
  774 
  775       context "when timing is after" do
  776         let(:timing) { :after }
  777 
  778         it "should add a trigger action to the end of the stack" do
  779           subject.apply_action_name(env)
  780           expect(subject.stack.last.first).to eq(Vagrant::Action::Builtin::Trigger)
  781         end
  782 
  783         it "should include arguments to the trigger action" do
  784           subject.apply_action_name(env)
  785           args = subject.stack.last.arguments.parameters
  786           expect(args).to include(action_name)
  787           expect(args).to include(timing)
  788           expect(args).to include(:hook)
  789         end
  790       end
  791     end
  792   end
  793 end