vagrant/test/unit/plugins/synced_folders/rsync/helper_test.rb

554 lines
18 KiB
Ruby

require_relative "../../../base"
require "vagrant/util/platform"
require Vagrant.source_root.join("plugins/synced_folders/rsync/helper")
describe VagrantPlugins::SyncedFolderRSync::RsyncHelper do
include_context "unit"
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
env = isolated_environment
env.vagrantfile("")
env.create_vagrant_env
end
let(:guest) { double("guest") }
let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
subject { described_class }
before do
allow(machine).to receive(:guest).and_return(guest)
# Don't do all the crazy Cygwin stuff
allow(Vagrant::Util::Platform).to receive(:cygwin_path) do |path, **opts|
path
end
end
describe "#exclude_to_regexp" do
let(:path) { "/foo/bar" }
it "converts a directory match" do
expect(described_class.exclude_to_regexp("foo/")).
to eq(/^foo\/.[^\/]*/)
end
it "converts the start anchor" do
expect(described_class.exclude_to_regexp("/foo")).
to eq(/^foo\//)
end
it "converts the **" do
expect(described_class.exclude_to_regexp("fo**o")).
to eq(/^fo.*o\/.[^\/]*/)
end
it "converts the *" do
expect(described_class.exclude_to_regexp("fo*o")).
to eq(/^fo[^\/]*o\/.[^\/]*/)
end
end
describe "#rsync_single" do
let(:result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
let(:ssh_info) {{
private_key_path: [],
}}
let(:opts) {{
hostpath: "/foo",
}}
let(:ui) { machine.ui }
before do
allow(Vagrant::Util::Subprocess).to receive(:execute){ result }
allow(guest).to receive(:capability?){ false }
end
it "doesn't raise an error if it succeeds" do
subject.rsync_single(machine, ssh_info, opts)
end
it "doesn't call cygwin_path on non-Windows" do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)
expect(Vagrant::Util::Platform).not_to receive(:cygwin_path)
subject.rsync_single(machine, ssh_info, opts)
end
it "calls cygwin_path on Windows" do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
expect(Vagrant::Util::Platform).to receive(:cygwin_path).and_return("foo")
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[args.length - 3]).to eql("foo/")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "raises an error if the exit code is non-zero" do
allow(Vagrant::Util::Subprocess).to receive(:execute)
.and_return(Vagrant::Util::Subprocess::Result.new(1, "", ""))
expect {subject.rsync_single(machine, ssh_info, opts) }.
to raise_error(Vagrant::Errors::RSyncError)
end
context "host and guest paths" do
it "syncs the hostpath to the guest path" do
opts[:hostpath] = "/foo"
opts[:guestpath] = "/bar"
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expected = Vagrant::Util::Platform.fs_real_path("/foo").to_s
expect(args[args.length - 3]).to eql("#{expected}/")
expect(args[args.length - 2]).to include("/bar")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "expands the hostpath relative to the root path" do
opts[:hostpath] = "foo"
opts[:guestpath] = "/bar"
hostpath_expanded = File.expand_path(opts[:hostpath], machine.env.root_path)
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[args.length - 3]).to eql("#{hostpath_expanded}/")
expect(args[args.length - 2]).to include("/bar")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
end
it "executes within the root path" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args.last).to be_kind_of(Hash)
opts = args.last
expect(opts[:workdir]).to eql(machine.env.root_path.to_s)
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "executes the rsync_pre capability first if it exists" do
expect(guest).to receive(:capability?).with(:rsync_pre).and_return(true)
expect(guest).to receive(:capability).with(:rsync_pre, opts).ordered
expect(Vagrant::Util::Subprocess).to receive(:execute).ordered.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "executes the rsync_post capability after if it exists" do
expect(guest).to receive(:capability?).with(:rsync_post).and_return(true)
expect(Vagrant::Util::Subprocess).to receive(:execute).ordered.and_return(result)
expect(guest).to receive(:capability).with(:rsync_post, opts).ordered
subject.rsync_single(machine, ssh_info, opts)
end
context "with rsync_post capability" do
before do
allow(guest).to receive(:capability?).with(:rsync_post).and_return(true)
allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result)
end
it "should raise custom error when capability errors" do
expect(guest).to receive(:capability).with(:rsync_post, opts).
and_raise(Vagrant::Errors::VagrantError)
expect { subject.rsync_single(machine, ssh_info, opts) }.
to raise_error(Vagrant::Errors::RSyncPostCommandError)
end
end
context "with rsync_ownership option" do
let(:rsync_local_version) { "3.1.1" }
let(:rsync_remote_version) { "3.1.1" }
let(:rsync_result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
before do
expect(Vagrant::Util::Subprocess).to receive(:execute).
with("rsync", "--version").and_return(Vagrant::Util::Subprocess::Result.new(0, " version #{rsync_local_version} ", ""))
allow(machine.communicate).to receive(:execute).with(/--version/).and_yield(:stdout, " version #{rsync_remote_version} ")
allow(Vagrant::Util::Subprocess).to receive(:execute).with("rsync", any_args).and_return(rsync_result)
opts[:rsync_ownership] = true
end
after { subject.reset! }
it "should use the rsync --chown flag" do
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
expect(args.detect{|a| a.include?("--chown")}).to be_truthy
rsync_result
}
subject.rsync_single(machine, ssh_info, opts)
end
it "should set the chown option to false" do
expect(opts.has_key?(:chown)).to eq(false)
subject.rsync_single(machine, ssh_info, opts)
expect(opts[:chown]).to eq(false)
end
context "when local rsync version does not support --chown" do
let(:rsync_local_version) { "2.0" }
it "should not use the --chown flag" do
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
expect(args.detect{|a| a.include?("--chown")}).to be_falsey
rsync_result
}
subject.rsync_single(machine, ssh_info, opts)
end
end
context "when remote rsync version does not support --chown" do
let(:rsync_remote_version) { "2.0" }
it "should not use the --chown flag" do
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
expect(args.detect{|a| a.include?("--chown")}).to be_falsey
rsync_result
}
subject.rsync_single(machine, ssh_info, opts)
end
end
end
context "excluding files" do
it "excludes files if given as a string" do
opts[:exclude] = "foo"
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
index = args.find_index("foo")
expect(index).to be > 0
expect(args[index-1]).to eql("--exclude")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "excludes multiple files" do
opts[:exclude] = ["foo", "bar"]
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
index = args.find_index("foo")
expect(index).to be > 0
expect(args[index-1]).to eql("--exclude")
index = args.find_index("bar")
expect(index).to be > 0
expect(args[index-1]).to eql("--exclude")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
end
context "custom arguments" do
it "uses the default arguments if not given" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[1]).to eq("--verbose")
expect(args[2]).to eq("--archive")
expect(args[3]).to eq("--delete")
expected = Vagrant::Util::Platform.fs_real_path("/foo").to_s
expect(args[args.length - 3]).to eql("#{expected}/")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "uses the custom arguments if given" do
opts[:args] = ["--verbose", "-z"]
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[1]).to eq("--verbose")
expect(args[2]).to eq("-z")
expected = Vagrant::Util::Platform.fs_real_path("/foo").to_s
expect(args[args.length - 3]).to eql("#{expected}/")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
end
context "control sockets" do
it "creates a tmp dir" do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(false)
allow(Dir).to receive(:mktmpdir).with("vagrant-rsync-").
and_return("/tmp/vagrant-rsync-12345")
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[9]).to include("ControlPath=/tmp/vagrant-rsync-12345")
}.and_return(result)
expect(FileUtils).to receive(:remove_entry_secure).with("/tmp/vagrant-rsync-12345", true).and_return(true)
subject.rsync_single(machine, ssh_info, opts)
end
it "does not create tmp dir on windows platforms" do
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
allow(Dir).to receive(:mktmpdir).with("vagrant-rsync-").
and_return("/tmp/vagrant-rsync-12345")
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args).not_to include("ControlPath=/tmp/vagrant-rsync-12345")
}.and_return(result)
expect(FileUtils).not_to receive(:remove_entry_secure).with("/tmp/vagrant-rsync-12345", true)
subject.rsync_single(machine, ssh_info, opts)
end
end
end
describe "#rsync_single with custom ssh_info" do
let(:result) { Vagrant::Util::Subprocess::Result.new(0, "", "") }
let(:ssh_info) {{
:private_key_path => ['/path/to/key'],
:keys_only => true,
:verify_host_key => false,
}}
let(:opts) {{
hostpath: "/foo",
}}
let(:ui) { machine.ui }
before do
allow(Vagrant::Util::Subprocess).to receive(:execute){ result }
allow(guest).to receive(:capability?){ false }
end
context "with an IPv6 address" do
before { ssh_info[:host] = "fe00::0" }
it "formats the address correctly" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, "@[#{ssh_info[:host]}]:''", instance_of(Hash))
subject.rsync_single(machine, ssh_info, opts)
end
end
context "with an IPv4 address" do
before { ssh_info[:host] = "127.0.0.1" }
it "formats the address correctly" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args, "@#{ssh_info[:host]}:''", instance_of(Hash))
subject.rsync_single(machine, ssh_info, opts)
end
end
it "includes IdentitiesOnly, StrictHostKeyChecking, and UserKnownHostsFile with defaults" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[9]).to include('IdentitiesOnly')
expect(args[9]).to include('StrictHostKeyChecking')
expect(args[9]).to include('UserKnownHostsFile')
expect(args[9]).to include("-i '/path/to/key'")
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "includes StrictHostKeyChecking, and UserKnownHostsFile when verify_host_key is false" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[9]).to include('StrictHostKeyChecking')
expect(args[9]).to include('UserKnownHostsFile')
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "includes StrictHostKeyChecking, and UserKnownHostsFile when verify_host_key is :never" do
ssh_info[:verify_host_key] = :never
expect(Vagrant::Util::Subprocess).to receive(:execute).with(any_args) { |*args|
expect(args[9]).to include('StrictHostKeyChecking')
expect(args[9]).to include('UserKnownHostsFile')
}.and_return(result)
subject.rsync_single(machine, ssh_info, opts)
end
it "omits IdentitiesOnly with keys_only = false" do
ssh_info[:keys_only] = false
expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
expect(args[9]).not_to include('IdentitiesOnly')
result
end
subject.rsync_single(machine, ssh_info, opts)
end
it "omits StrictHostKeyChecking and UserKnownHostsFile with paranoid = true" do
ssh_info[:keys_only] = false
expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
expect(args[9]).not_to include('StrictHostKeyChecking ')
expect(args[9]).not_to include('UserKnownHostsFile ')
result
end
subject.rsync_single(machine, ssh_info, opts)
end
it "includes custom ssh config when set" do
ssh_info[:config] = "/path/to/ssh/config"
expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args|
ssh_config_args = "-F /path/to/ssh/config"
expect(args.any?{|a| a.include?(ssh_config_args)}).to be_truthy
result
end
subject.rsync_single(machine, ssh_info, opts)
end
end
describe ".rsync_chown_support?" do
let(:local_version) { "3.1.1" }
let(:remote_version) { "3.1.1" }
before do
allow(subject).to receive(:local_rsync_version).and_return(local_version)
allow(subject).to receive(:machine_rsync_version).and_return(remote_version)
end
it "should return when local and remote versions support chown" do
expect(subject.rsync_chown_support?(machine)).to be_truthy
end
context "when local version does not support chown" do
let(:local_version) { "2.0" }
it "should return false" do
expect(subject.rsync_chown_support?(machine)).to be_falsey
end
end
context "when remote version does not support chown" do
let(:remote_version) { "2.0" }
it "should return false" do
expect(subject.rsync_chown_support?(machine)).to be_falsey
end
end
context "when both local and remote versions do not support chown" do
let(:local_version) { "2.0" }
let(:remote_version) { "2.0" }
it "should return false" do
expect(subject.rsync_chown_support?(machine)).to be_falsey
end
end
end
describe ".machine_rsync_version" do
let(:version_output) {
<<-EOV
rsync version 3.1.3 protocol version 31
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
append, ACLs, xattrs, iconv, symtimes, prealloc
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
are welcome to redistribute it under certain conditions. See the GNU
General Public Licence for details.
EOV
}
before do
allow(machine.communicate).to receive(:execute).with(/--version/).
and_yield(:stdout, version_output)
allow(guest).to receive(:capability?).and_return(false)
end
it "should extract the version string" do
expect(subject.machine_rsync_version(machine)).to eq("3.1.3")
end
context "when version output is an unknown format" do
let(:version_output) { "unknown" }
it "should return nil value" do
expect(subject.machine_rsync_version(machine)).to be_nil
end
end
context "with guest rsync_command capability" do
let(:rsync_path) { "custom_rsync" }
before do
allow(guest).to receive(:capability?).with(:rsync_command).
and_return(true)
allow(guest).to receive(:capability).with(:rsync_command).
and_return(rsync_path)
end
it "should use custom rsync_path" do
expect(machine.communicate).to receive(:execute).
with("#{rsync_path} --version").and_yield(:stdout, version_output)
subject.machine_rsync_version(machine)
end
end
end
describe ".local_rsync_version" do
let(:version_output) {
<<-EOV
rsync version 3.1.3 protocol version 31
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints,
socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
append, ACLs, xattrs, iconv, symtimes, prealloc
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
are welcome to redistribute it under certain conditions. See the GNU
General Public Licence for details.
EOV
}
let(:result) { Vagrant::Util::Subprocess::Result.new(0, version_output, "") }
before do
allow(Vagrant::Util::Subprocess).to receive(:execute).with("rsync", "--version").
and_return(result)
end
after { subject.reset! }
it "should extract the version string" do
expect(subject.local_rsync_version).to eq("3.1.3")
end
it "should cache the version lookup" do
expect(Vagrant::Util::Subprocess).to receive(:execute).with("rsync", "--version").
and_return(result).once
expect(subject.local_rsync_version).to eq("3.1.3")
expect(subject.local_rsync_version).to eq("3.1.3")
end
context "when version output is an unknown format" do
let(:version_output) { "unknown" }
it "should return nil value" do
expect(subject.local_rsync_version).to be_nil
end
end
end
end