spec/functional/builder_spec.rb (535 lines of code) (raw):
require "spec_helper"
module Omnibus
describe Builder do
include_examples "a software"
#
# Fakes the embedded bin path to whatever exists in bundler. This is useful
# for testing methods like +ruby+ and +rake+ without the need to compile
# a real Ruby just for functional tests. This strategy does not work on
# Windows because a) windows doesn't have symlinks and b) the windows
# omnibus installation has a post installation step that fixes up
# shebang paths to point to embedded ruby and drops bat files with
# the correct path. If we need to invoke bundler/appbundler etc. in a
# manner similar to one that omnibus provides, we would need to emulate
# the fixup steps here as well, which is a pain the ass.
#
# Instead we write batch files that redirect to the batch files
# corresponding to the system installation and hope it all works out.
def fake_embedded_bin(name)
if windows?
ext = name == "ruby" ? ".exe" : ".bat"
source = Bundler.which(name + ext)
raise "Could not find #{name} in bundler environment" unless source
File.open(File.join(embedded_bin_dir, name + ".bat"), "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
@"#{source}" %*
EOH
end
else
source = Bundler.which(name)
raise "Could not find #{name} in bundler environment" unless source
target = File.join(embedded_bin_dir, name)
create_link(source, target) unless File.exist?(target)
end
end
def shellout_opts(subject)
# Pass GEM_HOME and GEM_PATH to subprocess so our fake bin works
options = {}
options[:env] = {
"GEM_HOME" => ENV["GEM_HOME"],
"GEM_PATH" => ENV["GEM_PATH"],
}
options[:env].merge!(subject.with_embedded_path)
options
end
def make_gemspec
gemspec = File.join(project_dir, "#{project_name}.gemspec")
File.open(gemspec, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
Gem::Specification.new do |gem|
gem.name = '#{project_name}'
gem.version = '1.0.0'
gem.author = 'Chef Software, Inc.'
gem.email = 'info@getchef.com'
gem.description = 'Installs a thing'
gem.summary = gem.description
end
EOH
end
gemspec
end
def make_gemfile
gemfile = File.join(project_dir, "Gemfile")
File.open(gemfile, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
gemspec
EOH
end
gemfile
end
def make_gemfile_lock
gemfile_lock = File.join(project_dir, "Gemfile.lock")
File.open(gemfile_lock, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
PATH
remote: .
specs:
#{project_name} (1.0.0)
GEM
specs:
PLATFORMS
ruby
DEPENDENCIES
#{project_name}!
EOH
end
gemfile_lock
end
subject { described_class.new(software) }
let(:project_name) { "example" }
let(:project_dir) { File.join(source_dir, project_name) }
describe "#command" do
it "executes the command" do
subject.command("echo 'Hello World!'")
output = capture_logging { subject.build }
expect(output).to include("Hello World")
end
end
describe "#make" do
it "is waiting for a good samaritan to write tests" do
skip
end
end
describe "#patch" do
it "applies the patch" do
configure = File.join(project_dir, "configure")
File.open(configure, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
THING="-e foo"
ZIP="zap"
EOH
end
patch = File.join(patches_dir, "apply.patch")
File.open(patch, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
--- a/configure
+++ b/configure
@@ -1,2 +1,3 @@
THING="-e foo"
+FOO="bar"
ZIP="zap"
EOH
end
if windows?
bash_path = Bundler.which("bash.exe")
allow(subject).to receive(:embedded_msys_bin)
.with("bash.exe")
.and_return("#{bash_path}")
end
subject.patch(source: "apply.patch")
subject.build
end
end
describe "#ruby" do
it "executes the command as the embdedded ruby" do
ruby = File.join(scripts_dir, "setup.rb")
File.open(ruby, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
File.write("#{software.install_dir}/test.txt", 'This is content!')
EOH
end
fake_embedded_bin("ruby")
subject.ruby(ruby, env: subject.with_embedded_path)
subject.build
path = "#{software.install_dir}/test.txt"
expect(path).to be_a_file
expect(File.read(path)).to eq("This is content!")
end
end
describe "#gem" do
it "executes the command as the embedded gem" do
make_gemspec
fake_embedded_bin("gem")
gem_file = "#{project_name}-1.0.0.gem"
subject.gem("build #{project_name}.gemspec", shellout_opts(subject))
subject.gem("install #{gem_file}", shellout_opts(subject))
output = capture_logging { subject.build }
expect(File.join(project_dir, gem_file)).to be_a_file
expect(output).to include("gem build")
expect(output).to include("gem install")
end
end
describe "#bundler" do
it "executes the command as the embedded bundler" do
make_gemspec
make_gemfile
fake_embedded_bin("bundle")
subject.bundle("install", shellout_opts(subject))
output = capture_logging { subject.build }
expect(File.join(project_dir, "Gemfile.lock")).to be_a_file
expect(output).to include("bundle install")
end
end
describe "#appbundle" do
let(:project) { double("Project") }
let(:project_softwares) { [ double("Software", name: project_name, project_dir: project_dir) ] }
it "executes the command as the embedded appbundler" do
make_gemspec
make_gemfile
make_gemfile_lock
fake_embedded_bin("gem")
fake_embedded_bin("appbundler")
subject.gem("build #{project_name}.gemspec", shellout_opts(subject))
subject.gem("install #{project_name}-1.0.0.gem", shellout_opts(subject))
subject.appbundle(project_name, shellout_opts(subject))
expect(subject).to receive(:project).and_return(project)
expect(project).to receive(:softwares).and_return(project_softwares)
output = capture_logging { subject.build }
appbundler_path = File.join(embedded_bin_dir, "appbundler")
appbundler_path.gsub!(%r{/}, '\\') if windows? # rubocop:disable Style/StringLiterals
expect(output).to include("#{appbundler_path} '#{project_dir}' '#{bin_dir}'")
end
end
describe "#rake" do
it "executes the command as the embedded rake" do
rakefile = File.join(project_dir, "Rakefile")
File.open(rakefile, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
task(:foo) { }
EOH
end
fake_embedded_bin("rake")
subject.rake("-T", shellout_opts(subject))
subject.rake("foo", shellout_opts(subject))
output = capture_logging { subject.build }
expect(output).to include("rake -T")
expect(output).to include("rake foo")
end
end
describe "#block" do
it "executes the command as a block" do
subject.block("A complex operation") do
FileUtils.touch("#{project_dir}/bacon")
end
output = capture_logging { subject.build }
expect(output).to include("A complex operation")
expect("#{software.project_dir}/bacon").to be_a_file
end
end
describe "#erb" do
it "renders the erb" do
erb = File.join(templates_dir, "example.erb")
File.open(erb, "w") do |f|
f.write <<-EOH.gsub(/^ {12}/, "")
<%= a %>
<%= b %>
EOH
end
destination = File.join(tmp_path, "rendered")
subject.erb(
source: "example.erb",
dest: destination,
vars: { a: "foo", b: "bar" }
)
subject.build
expect(destination).to be_a_file
expect(File.read(destination)).to eq("foo\nbar\n")
end
end
describe "#mkdir" do
it "creates the directory" do
path = File.join(tmp_path, "scratch")
remove_directory(path)
subject.mkdir(path)
subject.build
expect(path).to be_a_directory
end
end
describe "#touch" do
it "creates the file" do
path = File.join(tmp_path, "file")
remove_file(path)
subject.touch(path)
subject.build
expect(path).to be_a_file
end
it "creates the containing directory" do
path = File.join(tmp_path, "foo", "bar", "file")
FileUtils.rm_rf(path)
subject.touch(path)
subject.build
expect(path).to be_a_file
end
end
describe "#delete" do
it "deletes the directory" do
path = File.join(tmp_path, "scratch")
create_directory(path)
subject.delete(path)
subject.build
expect(path).to_not be_a_directory
end
it "deletes the file" do
path = File.join(tmp_path, "file")
create_file(path)
subject.delete(path)
subject.build
expect(path).to_not be_a_file
end
it "accepts a glob pattern" do
path_a = File.join(tmp_path, "file_a")
path_b = File.join(tmp_path, "file_b")
FileUtils.touch(path_a)
FileUtils.touch(path_b)
subject.delete("#{tmp_path}/**/file_*")
subject.build
expect(path_a).to_not be_a_file
expect(path_b).to_not be_a_file
end
end
describe "#copy" do
it "copies the file" do
path_a = File.join(tmp_path, "file1")
path_b = File.join(tmp_path, "file2")
create_file(path_a)
subject.copy(path_a, path_b)
subject.build
expect(path_b).to be_a_file
expect(File.read(path_b)).to eq(File.read(path_a))
end
it "copies the directory and entries" do
destination = File.join(tmp_path, "destination")
directory = File.join(tmp_path, "scratch")
FileUtils.mkdir_p(directory)
path_a = File.join(directory, "file_a")
path_b = File.join(directory, "file_b")
FileUtils.touch(path_a)
FileUtils.touch(path_b)
subject.copy(directory, destination)
subject.build
expect(destination).to be_a_directory
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
end
it "accepts a glob pattern" do
destination = File.join(tmp_path, "destination")
FileUtils.mkdir_p(destination)
directory = File.join(tmp_path, "scratch")
FileUtils.mkdir_p(directory)
path_a = File.join(directory, "file_a")
path_b = File.join(directory, "file_b")
FileUtils.touch(path_a)
FileUtils.touch(path_b)
subject.copy("#{directory}/*", destination)
subject.build
expect(destination).to be_a_directory
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
end
end
describe "#move" do
it "moves the file" do
path_a = File.join(tmp_path, "file1")
path_b = File.join(tmp_path, "file2")
create_file(path_a)
subject.move(path_a, path_b)
subject.build
expect(path_b).to be_a_file
expect(path_a).to_not be_a_file
end
it "moves the directory and entries" do
destination = File.join(tmp_path, "destination")
directory = File.join(tmp_path, "scratch")
FileUtils.mkdir_p(directory)
path_a = File.join(directory, "file_a")
path_b = File.join(directory, "file_b")
FileUtils.touch(path_a)
FileUtils.touch(path_b)
subject.move(directory, destination)
subject.build
expect(destination).to be_a_directory
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
expect(directory).to_not be_a_directory
end
it "accepts a glob pattern" do
destination = File.join(tmp_path, "destination")
FileUtils.mkdir_p(destination)
directory = File.join(tmp_path, "scratch")
FileUtils.mkdir_p(directory)
path_a = File.join(directory, "file_a")
path_b = File.join(directory, "file_b")
FileUtils.touch(path_a)
FileUtils.touch(path_b)
subject.move("#{directory}/*", destination)
subject.build
expect(destination).to be_a_directory
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
expect(directory).to be_a_directory
end
end
describe "#link", :not_supported_on_windows do
it "links the file" do
path_a = File.join(tmp_path, "file1")
path_b = File.join(tmp_path, "file2")
create_file(path_a)
subject.link(path_a, path_b)
subject.build
expect(path_b).to be_a_symlink
end
it "links the directory" do
destination = File.join(tmp_path, "destination")
directory = File.join(tmp_path, "scratch")
FileUtils.mkdir_p(directory)
subject.link(directory, destination)
subject.build
expect(destination).to be_a_symlink
end
it "accepts a glob pattern" do
destination = File.join(tmp_path, "destination")
FileUtils.mkdir_p(destination)
directory = File.join(tmp_path, "scratch")
FileUtils.mkdir_p(directory)
path_a = File.join(directory, "file_a")
path_b = File.join(directory, "file_b")
FileUtils.touch(path_a)
FileUtils.touch(path_b)
subject.link("#{directory}/*", destination)
subject.build
expect("#{destination}/file_a").to be_a_symlink
expect("#{destination}/file_b").to be_a_symlink
end
end
describe "#sync" do
let(:source) do
source = File.join(tmp_path, "source")
FileUtils.mkdir_p(source)
FileUtils.touch(File.join(source, "file_a"))
FileUtils.touch(File.join(source, "file_b"))
FileUtils.touch(File.join(source, "file_c"))
FileUtils.mkdir_p(File.join(source, "folder"))
FileUtils.touch(File.join(source, "folder", "file_d"))
FileUtils.touch(File.join(source, "folder", "file_e"))
FileUtils.mkdir_p(File.join(source, ".dot_folder"))
FileUtils.touch(File.join(source, ".dot_folder", "file_f"))
FileUtils.touch(File.join(source, ".file_g"))
source
end
let(:destination) { File.join(tmp_path, "destination") }
context "when the destination is empty" do
it "syncs the directories" do
subject.sync(source, destination)
subject.build
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
expect("#{destination}/file_c").to be_a_file
expect("#{destination}/folder/file_d").to be_a_file
expect("#{destination}/folder/file_e").to be_a_file
expect("#{destination}/.dot_folder/file_f").to be_a_file
expect("#{destination}/.file_g").to be_a_file
end
end
context "when the directory exists" do
before { FileUtils.mkdir_p(destination) }
it "deletes existing files and folders" do
FileUtils.mkdir_p("#{destination}/existing_folder")
FileUtils.mkdir_p("#{destination}/.existing_folder")
FileUtils.touch("#{destination}/existing_file")
FileUtils.touch("#{destination}/.existing_file")
subject.sync(source, destination)
subject.build
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
expect("#{destination}/file_c").to be_a_file
expect("#{destination}/folder/file_d").to be_a_file
expect("#{destination}/folder/file_e").to be_a_file
expect("#{destination}/.dot_folder/file_f").to be_a_file
expect("#{destination}/.file_g").to be_a_file
expect("#{destination}/existing_folder").to_not be_a_directory
expect("#{destination}/.existing_folder").to_not be_a_directory
expect("#{destination}/existing_file").to_not be_a_file
expect("#{destination}/.existing_file").to_not be_a_file
end
end
context "when :exclude is given" do
it "does not copy files and folders that match the pattern" do
subject.sync(source, destination, exclude: ".dot_folder")
subject.build
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
expect("#{destination}/file_c").to be_a_file
expect("#{destination}/folder/file_d").to be_a_file
expect("#{destination}/folder/file_e").to be_a_file
expect("#{destination}/.dot_folder").to_not be_a_directory
expect("#{destination}/.dot_folder/file_f").to_not be_a_file
expect("#{destination}/.file_g").to be_a_file
end
it "removes existing files and folders in destination" do
FileUtils.mkdir_p("#{destination}/existing_folder")
FileUtils.touch("#{destination}/existing_file")
FileUtils.mkdir_p("#{destination}/.dot_folder")
FileUtils.touch("#{destination}/.dot_folder/file_f")
subject.sync(source, destination, exclude: ".dot_folder")
subject.build
expect("#{destination}/file_a").to be_a_file
expect("#{destination}/file_b").to be_a_file
expect("#{destination}/file_c").to be_a_file
expect("#{destination}/folder/file_d").to be_a_file
expect("#{destination}/folder/file_e").to be_a_file
expect("#{destination}/.dot_folder").to_not be_a_directory
expect("#{destination}/.dot_folder/file_f").to_not be_a_file
expect("#{destination}/.file_g").to be_a_file
expect("#{destination}/existing_folder").to_not be_a_directory
expect("#{destination}/existing_file").to_not be_a_file
end
end
end
describe "#update_config_guess", :not_supported_on_windows do
let(:config_guess_dir) { "#{install_dir}/embedded/lib/config_guess" }
before do
FileUtils.mkdir_p(config_guess_dir)
end
context "with no config.guess" do
before do
File.open("#{config_guess_dir}/config.sub", "w+") do |f|
f.write("This is config.sub")
end
end
it "fails" do
subject.update_config_guess
expect { subject.build }.to raise_error(RuntimeError)
end
end
context "with no config.sub" do
before do
File.open("#{config_guess_dir}/config.guess", "w+") do |f|
f.write("This is config.guess")
end
end
it "fails" do
subject.update_config_guess
expect { subject.build }.to raise_error(RuntimeError)
end
end
context "with config_guess dependency" do
before do
File.open("#{config_guess_dir}/config.guess", "w+") do |f|
f.write("This is config.guess")
end
File.open("#{config_guess_dir}/config.sub", "w+") do |f|
f.write("This is config.sub")
end
end
it "update config_guess with defaults" do
subject.update_config_guess
subject.build
expect(File.read("#{project_dir}/config.guess")).to match /config.guess/
expect(File.read("#{project_dir}/config.sub")).to match /config.sub/
end
it "honors :target option" do
subject.update_config_guess(target: "sub_dir")
subject.build
expect(File.read("#{project_dir}/sub_dir/config.guess")).to match /config.guess/
expect(File.read("#{project_dir}/sub_dir/config.sub")).to match /config.sub/
end
it "honors :config_guess in :install option" do
subject.update_config_guess(install: [:config_guess])
subject.build
expect(File.read("#{project_dir}/config.guess")).to match /config.guess/
expect(File.exist?("#{project_dir}/config.sub")).to be false
end
it "honors :config_sub in :install option" do
subject.update_config_guess(install: [:config_sub])
subject.build
expect(File.read("#{project_dir}/config.sub")).to match /config.sub/
expect(File.exist?("#{project_dir}/config.guess")).to be false
end
end
end
end
end