require 'spec_helper'

module Omnibus
  describe Software do
    let(:project) do
      Project.new.evaluate do
        name 'project'
        install_dir '/opt/project'
      end
    end

    subject do
      described_class.new(project).evaluate do
        name 'software'
        default_version '1.2.3'

        source url: 'http://example.com/',
               md5: 'abcd1234'
      end
    end

    it_behaves_like 'a cleanroom getter', :project
    it_behaves_like 'a cleanroom setter', :name, %|name 'libxml2'|
    it_behaves_like 'a cleanroom setter', :description, %|description 'The XML magician'|
    it_behaves_like 'a cleanroom setter', :dependency, %|dependency 'libxslt'|
    it_behaves_like 'a cleanroom setter', :source, %|source url: 'https://source.example.com'|
    it_behaves_like 'a cleanroom setter', :default_version, %|default_version '1.2.3'|
    it_behaves_like 'a cleanroom setter', :version, %|version '1.2.3'|
    it_behaves_like 'a cleanroom setter', :whitelist_file, %|whitelist_file '/opt/whatever'|
    it_behaves_like 'a cleanroom setter', :relative_path, %|relative_path '/path/to/extracted'|
    it_behaves_like 'a cleanroom setter', :build, %|build {}|
    it_behaves_like 'a cleanroom getter', :project_dir
    it_behaves_like 'a cleanroom getter', :build_dir
    it_behaves_like 'a cleanroom getter', :install_dir
    it_behaves_like 'a cleanroom getter', :with_standard_compiler_flags
    it_behaves_like 'a cleanroom setter', :with_embedded_path, %|with_embedded_path({ 'foo' => 'bar' })|
    it_behaves_like 'a cleanroom setter', :prepend_path, %|prepend_path({ 'foo' => 'bar' })|

    context 'when a source_uri is present' do
      let(:uri) { URI.parse('http://example.com/foo.tar.gz') }
      before { allow(subject).to receive(:source_uri).and_return(uri) }

      it_behaves_like 'a cleanroom getter', :project_file
    end

    describe "with_standard_compiler_flags helper" do
      context "on ubuntu" do
        before { stub_ohai(platform: 'ubuntu', version: '12.04') }

        it "sets the defaults" do
          expect(subject.with_standard_compiler_flags).to eq(
            'LDFLAGS'         => '-Wl,-rpath,/opt/project/embedded/lib -L/opt/project/embedded/lib',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            'CXXFLAGS'        => '-I/opt/project/embedded/include',
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
        it 'ovesrride LDFLAGS' do
          expect(subject.with_standard_compiler_flags('LDFLAGS'        => 'foo')).to eq(
            'LDFLAGS'         => '-Wl,-rpath,/opt/project/embedded/lib -L/opt/project/embedded/lib',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            'CXXFLAGS'        => '-I/opt/project/embedded/include',
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
        it 'ovesrride CFLAGS' do
          expect(subject.with_standard_compiler_flags('CFLAGS'=>'foo')).to eq(
            'LDFLAGS'         => '-Wl,-rpath,/opt/project/embedded/lib -L/opt/project/embedded/lib',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            'CXXFLAGS'        => '-I/opt/project/embedded/include',
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
        it 'ovesrride CXXFLAGS' do
          expect(subject.with_standard_compiler_flags('CXXFLAGS'=>'foo')).to eq(
            'LDFLAGS'         => '-Wl,-rpath,/opt/project/embedded/lib -L/opt/project/embedded/lib',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            'CXXFLAGS'        => '-I/opt/project/embedded/include',
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
        it 'presserve anything else' do
          expect(subject.with_standard_compiler_flags('numberwang'=>4)).to eq(
            'numberwang'      => 4,
            'LDFLAGS'         => '-Wl,-rpath,/opt/project/embedded/lib -L/opt/project/embedded/lib',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            'CXXFLAGS'        => '-I/opt/project/embedded/include',
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
      end

      context 'on solaris2' do
        before do
          stub_ohai(platform: 'solaris2', version: '5.11') do |data|
            # For some reason, this isn't set in Fauxhai
            data['platform'] = 'solaris2'
          end
        end

        it 'sets the defaults' do
          expect(subject.with_standard_compiler_flags).to eq(
            'CC'              => 'gcc -static-libgcc',
            'LDFLAGS'         => '-R/opt/project/embedded/lib -L/opt/project/embedded/lib -static-libgcc',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            "CXXFLAGS"        => "-I/opt/project/embedded/include",
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'LD_OPTIONS'      => '-R/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
      end

      context 'on mac_os_x' do
        before { stub_ohai(platform: 'mac_os_x', version: '10.9.2') }

        it 'sets the defaults' do
          expect(subject.with_standard_compiler_flags).to eq(
            'LDFLAGS'         => '-L/opt/project/embedded/lib',
            'CFLAGS'          => '-I/opt/project/embedded/include',
            "CXXFLAGS"        => "-I/opt/project/embedded/include",
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
      end

      context 'on aix' do
        before do
          # There's no AIX in Fauxhai :(
          stub_ohai(platform: 'solaris2', version: '5.11') do |data|
            data['platform'] = 'aix'
          end
        end

        it 'sets the defaults' do
          expect(subject.with_standard_compiler_flags).to eq(
            'CC'              => 'xlc_r -q64',
            'CXX'             => 'xlC_r -q64',
            'CFLAGS'          => '-q64 -I/opt/project/embedded/include -D_LARGE_FILES -O',
            "CXXFLAGS"        => "-q64 -I/opt/project/embedded/include -D_LARGE_FILES -O",
            'LDFLAGS'         => '-q64 -L/opt/project/embedded/lib -Wl,-blibpath:/opt/project/embedded/lib:/usr/lib:/lib',
            'LD'              => 'ld -b64',
            'OBJECT_MODE'     => '64',
            'ARFLAGS'         => '-X64 cru',
            'LD_RUN_PATH'     => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig'
          )
        end
      end

      context 'on freebsd' do
        before do
          stub_ohai(platform: 'freebsd', version: '9.2')
        end

        it 'sets the defaults' do
          expect(subject.with_standard_compiler_flags).to eq(
            'CFLAGS'  => '-I/opt/project/embedded/include',
            'CXXFLAGS'  => '-I/opt/project/embedded/include',
            'LDFLAGS' => '-L/opt/project/embedded/lib',
            'LD_RUN_PATH' => '/opt/project/embedded/lib',
            'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig',
          )
        end

        context 'on freebsd' do
          before do
            stub_ohai(platform: 'freebsd', version: '10.0')
          end

          it 'Clang as the default compiler' do
            expect(subject.with_standard_compiler_flags).to eq(
              'CC'              => 'clang',
              'CXX'             => 'clang++',
              'CFLAGS'          => '-I/opt/project/embedded/include',
              'CXXFLAGS'        => '-I/opt/project/embedded/include',
              'LDFLAGS'         => '-L/opt/project/embedded/lib',
              'LD_RUN_PATH'     => '/opt/project/embedded/lib',
              'PKG_CONFIG_PATH' => '/opt/project/embedded/lib/pkgconfig',
            )
          end
        end
      end

    end

    describe 'path helpers' do

      before do
        stub_const('File::PATH_SEPARATOR', separator)
        stub_env('PATH', path)
        allow(project).to receive(:install_dir).and_return(install_dir)
      end

      let(:prepended_path) do
        ["#{install_dir}/bin", separator, "#{install_dir}/embedded/bin", separator, path].join
      end

      context 'on *Nix' do
        let(:separator) { ':' }
        let(:path) { '/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin' }
        let(:install_dir) { '/opt/project' }

        it 'prepends a path to PATH' do
          expect(subject.prepend_path('/foo/bar')).to eq(
            ['/foo/bar', separator, path].join
          )
        end

        it 'prepends the embedded bin to PATH' do
          expect(subject.with_embedded_path).to eq(
            'PATH' => prepended_path
          )
        end

        it 'with_embedded_path merges with a hash argument' do
          expect(subject.with_embedded_path('numberwang' => 4)).to eq(
            'numberwang' => 4,
            'PATH' => prepended_path
          )
        end

        it 'prepends multiple paths to PATH' do
          expect(subject.prepend_path('/foo/bar', '/foo/baz')).to eq(
            ['/foo/bar', separator, '/foo/baz', separator, path].join
          )
        end
      end

      context 'on Windows' do
        before do
          stub_ohai(platform: 'windows', version: '2012')
        end

        let(:separator) { ';' }
        let(:path) { 'c:/Ruby193/bin;c:/Windows/system32;c:/Windows;c:/Windows/System32/Wbem' }
        let(:install_dir) { 'c:/opt/project' }

        context '`Path` exists in the environment' do
          before do
            stub_env('Path', path)
            allow(ENV).to receive(:keys).and_return(%w( Path PATH ))
          end

          it 'returns a path key of `Path`' do
            expect(subject.with_embedded_path).to eq(
              'Path' => prepended_path
            )
          end
        end

        context '`Path` does not exist in the environment' do
          it 'returns a path key of `PATH`' do
            expect(subject.with_embedded_path).to eq(
              'PATH' => prepended_path
            )
          end
        end
      end
    end

    describe '#ohai' do
      before { stub_ohai(platform: 'ubuntu', version: '12.04') }

      it 'is a DSL method' do
        expect(subject).to have_exposed_method(:ohai)
      end

      it 'delegates to the Ohai class' do
        expect(subject.ohai).to be(Ohai)
      end
    end

    describe '#<=>' do
      let(:zlib)   { described_class.new(project).tap { |s| s.name('zlib') } }
      let(:erchef) { described_class.new(project).tap { |s| s.name('erchef') } }
      let(:bacon)  { described_class.new(project).tap { |s| s.name('bacon') } }

      it 'compares projects by name' do
        list = [zlib, erchef, bacon]
        expect(list.sort.map(&:name)).to eq(%w(bacon erchef zlib))
      end
    end

    describe '#whitelist_file' do
      it 'appends to the whitelist_files array' do
        expect(subject.whitelist_files.size).to eq(0)
        subject.whitelist_file(/foo\/bar/)
        expect(subject.whitelist_files.size).to eq(1)
      end

      it 'converts Strings to Regexp instances' do
        subject.whitelist_file 'foo/bar'
        expect(subject.whitelist_files.size).to eq(1)
        expect(subject.whitelist_files.first).to be_kind_of(Regexp)
      end
    end

    context 'testing repo-level version overrides' do
      context 'without overrides' do
        it 'returns the original values' do
          expect(subject.version).to eq('1.2.3')
          expect(subject.source).to eq(url: 'http://example.com/', md5: 'abcd1234')
        end
      end

      context 'with overrides for different software' do
        before { project.override(:chaos_monkey, version: '1.2.8') }

        it 'does not change the software' do
          expect(subject.version).to eq('1.2.3')
        end
      end

      context 'with overrides for this software' do
        context 'version' do
          let(:version) { '2.0.0.pre' }
          before { project.override(:software, version: '2.0.0.pre') }

          it 'returns the correct version' do
            expect(subject.version).to eq(version)
          end
        end

        context 'source' do
          let(:source) { { url: 'http://new.example.com', md5: 'defg5678' } }
          before { project.override(:software, source: source) }

          it 'returns the correct source' do
            expect(subject.source).to eq(source)
          end
        end
      end
    end

    describe '#shasum' do
      context 'when a filepath is given' do
        let(:path) { '/software.rb' }
        let(:file) { double(File) }

        before { subject.instance_variable_set(:@filepath, path) }

        before do
          allow(File).to receive(:exist?)
            .with(path)
            .and_return(true)
          allow(File).to receive(:open)
            .with(path)
            .and_return(file)
        end

        it 'returns the correct shasum' do
          expect(subject.shasum).to eq('69dcce6da5580abe1da581e3f09d81e13ac676c48790eb0aa44d0ca2f93a16de')
        end
      end

      context 'when a filepath is not given' do
        before { subject.send(:remove_instance_variable, :@filepath) }

        it 'returns the correct shasum' do
          expect(subject.shasum).to eq('acd88f56f17b7cbc146f351a9265b652bcf96d544821e7bc1e9663c80617276d')
        end
      end
    end
  end
end