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.exists?(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!(/\//,'\\') if windows? 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