# Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 require "pathname" require "tmpdir" require File.expand_path("../../../../base", __FILE__) require Vagrant.source_root.join("plugins/kernel_v2/config/vm") describe Vagrant::Action::Builtin::SyncedFolders do include_context "unit" include_context "synced folder actions" let(:app) { lambda { |env| } } let(:env) { { machine: machine, ui: ui } } let(:machine) do double("machine").tap do |machine| allow(machine).to receive(:config).and_return(machine_config) end end let(:machine_config) do double("machine_config").tap do |top_config| allow(top_config).to receive(:vm).and_return(vm_config) end end let(:vm_config) { double("machine_vm_config") } let(:ui) { Vagrant::UI::Silent.new } subject { described_class.new(app, env) } describe "call" do let(:synced_folders) { {} } let(:plugins) { {} } before do plugins[:default] = [impl(true, "default"), 10] plugins[:nfs] = [impl(true, "nfs"), 5] env[:root_path] = Pathname.new(Dir.mktmpdir("vagrant-test-synced-folders-call")) allow(subject).to receive(:plugins).and_return(plugins) allow(subject).to receive(:synced_folders).and_return(synced_folders) allow(subject).to receive(:save_synced_folders) allow(machine).to receive_message_chain(:guest, :capability?).with(:persist_mount_shared_folder).and_return(false) end after do FileUtils.rm_rf(env[:root_path]) end it "should create on the host if specified" do synced_folders["default"] = { "root" => { hostpath: "foo", }, "other" => { hostpath: "bar", create: true, } } subject.call(env) expect(env[:root_path].join("foo")).not_to be_directory expect(env[:root_path].join("bar")).to be_directory end it "doesn't expand the host path if told not to" do called_folders = nil tracker = Class.new(impl(true, "good")) do define_method(:prepare) do |machine, folders, opts| called_folders = folders end end plugins[:tracker] = [tracker, 15] synced_folders["tracker"] = { "root" => { hostpath: "foo", hostpath_exact: true, }, "other" => { hostpath: "/bar", } } subject.call(env) expect(called_folders).to_not be_nil expect(called_folders["root"][:hostpath]).to eq("foo") end it "expands the host path relative to the root path" do called_folders = nil tracker = Class.new(impl(true, "good")) do define_method(:prepare) do |machine, folders, opts| called_folders = folders end end plugins[:tracker] = [tracker, 15] synced_folders["tracker"] = { "root" => { hostpath: "foo", }, "other" => { hostpath: "/bar", } } subject.call(env) expect(called_folders).to_not be_nil expect(called_folders["root"][:hostpath]).to eq( Pathname.new(File.expand_path( called_folders["root"][:hostpath], env[:root_path])).to_s) end it "should invoke prepare then enable" do ids = [] order = [] tracker = Class.new(impl(true, "good")) do define_method(:prepare) do |machine, folders, opts| ids << self.object_id order << :prepare end define_method(:enable) do |machine, folders, opts| ids << self.object_id order << :enable end end plugins[:tracker] = [tracker, 15] synced_folders["tracker"] = { "root" => { hostpath: "foo", }, "other" => { hostpath: "bar", create: true, } } subject.call(env) expect(order).to eq([:prepare, :enable]) expect(ids.length).to eq(2) expect(ids[0]).to eq(ids[1]) end it "syncs custom folders" do ids = [] order = [] tracker = Class.new(impl(true, "good")) do define_method(:prepare) do |machine, folders, opts| ids << self.object_id order << :prepare end define_method(:enable) do |machine, folders, opts| ids << self.object_id order << :enable end end plugins[:tracker] = [tracker, 15] synced_folders["tracker"] = { "root" => { hostpath: "foo", }, "other" => { hostpath: "bar", create: true, } } new_config = double("config") env[:synced_folders_config] = new_config expect(subject).to receive(:synced_folders). with(machine, config: new_config, cached: false). and_return(synced_folders) subject.call(env) expect(order).to eq([:prepare, :enable]) expect(ids.length).to eq(2) expect(ids[0]).to eq(ids[1]) end context "with folders from the machine" do it "removes outdated folders not present in config" do expect(subject).to receive(:save_synced_folders).with( machine, anything, merge: true, vagrantfile: true) subject.call(env) end end context "with custom folders" do before do new_config = double("config") env[:synced_folders_config] = new_config allow(subject).to receive(:synced_folders). with(machine, config: new_config, cached: false). and_return({}) end it "doesn't remove outdated folders not present in config" do expect(subject).to receive(:save_synced_folders).with( machine, anything, merge: true) subject.call(env) end end context "with guest capability to persist synced folders" do it "persists folders" do synced_folders["default"] = { "root" => { hostpath: "foo", }, "other" => { hostpath: "bar", create: true, } } allow(machine).to receive_message_chain(:guest, :capability?).with(:persist_mount_shared_folder).and_return(true) expect(vm_config).to receive(:allow_fstab_modification).and_return(true) expect(machine).to receive_message_chain(:guest, :capability).with(:persist_mount_shared_folder, synced_folders) subject.call(env) end it "does not persists folders if configured to not do so" do allow(machine).to receive_message_chain(:guest, :capability?).with(:persist_mount_shared_folder).and_return(true) expect(vm_config).to receive(:allow_fstab_modification).and_return(false) expect(machine).to receive_message_chain(:guest, :capability).with(:persist_mount_shared_folder, nil) subject.call(env) end end context "when guest is not available" do it "does not persist folders if guest is not available" do allow(machine).to receive_message_chain(:guest, :capability?).and_raise(Vagrant::Errors::MachineGuestNotReady) expect(vm_config).to_not receive(:allow_fstab_modification) subject.call(env) end end end end