require 'spec_helper'
require 'pedump'

module Omnibus
  describe HealthCheck do
    let(:project) do
      double(Project,
        name: 'chefdk',
        install_dir: '/opt/chefdk',
        library: double(Library,
          components: [],
        ),
      )
    end

    def mkdump(base, size, x64 = false)
      dump = double(PEdump)
      pe = double(PEdump::PE,
        x64?: x64,
        ioh: double(x64 ? PEdump::IMAGE_OPTIONAL_HEADER64 : PEdump::IMAGE_OPTIONAL_HEADER32,
          ImageBase: base,
          SizeOfImage: size,
        ),
      )
      expect(dump).to receive(:pe).and_return(pe)
      dump
    end

    subject { described_class.new(project) }

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

      it 'will perform dll base relocation checks' do
        stub_ohai(platform: 'windows', version: '2012')
        expect(subject.relocation_checkable?).to be true
      end

      context 'when performing dll base relocation checks' do
        let(:pmdumps) do
          {
            'a' => mkdump(0x10000000, 0x00001000),
            'b/b' => mkdump(0x20000000, 0x00002000),
            'c/c/c' => mkdump(0x30000000, 0x00004000),
          }
        end

        let(:search_dir) { "#{project.install_dir}/embedded/bin" }

        before do
          r = allow(Dir).to receive(:glob).with("#{search_dir}/*.dll")
          pmdumps.each do |file, dump|
            path = File.join(search_dir, file)
            r.and_yield(path)
            expect(File).to receive(:open).with(path, 'rb').and_yield(double(File))
            expect(PEdump).to receive(:new).with(path).and_return(dump)
          end
        end

        context 'when given non-overlapping dlls' do
          it 'should always return true' do
            expect(subject.run!).to eq(true)
          end

          it 'should not identify conflicts' do
            expect(subject.relocation_check).to eq({})
          end
        end

        context 'when presented with overlapping dlls' do
          let(:pmdumps) do
            {
              'a' => mkdump(0x10000000, 0x00001000),
              'b/b' => mkdump(0x10000500, 0x00002000),
              'c/c/c' => mkdump(0x30000000, 0x00004000),
            }
          end

          it 'should always return true' do
            expect(subject.run!).to eq(true)
          end

          it 'should identify two conflicts' do
            expect(subject.relocation_check).to eq({
              'a' => {
                base: 0x10000000,
                size: 0x00001000,
                conflicts: [ 'b' ],
              },
              'b' => {
                base: 0x10000500,
                size: 0x00002000,
                conflicts: [ 'a' ],
              },
            })
          end
        end
      end
    end

    context 'on linux' do
      before { stub_ohai(platform: 'ubuntu', version: '12.04') }

      let(:bad_healthcheck) do
        double('Mixlib::Shellout',
          stdout: <<-EOH.gsub(/^ {12}/, '')
            /bin/ls:
              linux-vdso.so.1 =>  (0x00007fff583ff000)
              libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007fad8592a000)
              librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fad85722000)
              libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x00007fad85518000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fad8518d000)
              libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fad84f89000)
              /lib64/ld-linux-x86-64.so.2 (0x00007fad85b51000)
              libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fad84d6c000)
              libattr.so.1 => /lib/x86_64-linux-gnu/libattr.so.1 (0x00007fad84b67000)
            /bin/cat:
              linux-vdso.so.1 =>  (0x00007fffa4dcf000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a858cd000)
              /lib64/ld-linux-x86-64.so.2 (0x00007f4a85c5f000)
          EOH
        )
      end

      let(:good_healthcheck) do
        double('Mixlib::Shellout',
          stdout: <<-EOH.gsub(/^ {12}/, '')
            /bin/echo:
              linux-vdso.so.1 =>  (0x00007fff8a6ee000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f70f58c0000)
              /lib64/ld-linux-x86-64.so.2 (0x00007f70f5c52000)
            /bin/cat:
              linux-vdso.so.1 =>  (0x00007fff095b3000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe868ec0000)
              /lib64/ld-linux-x86-64.so.2 (0x00007fe869252000)
          EOH
        )
      end

      it 'raises an exception when there are external dependencies' do
        allow(subject).to receive(:shellout)
          .with("find #{project.install_dir}/ -type f | xargs ldd")
          .and_return(bad_healthcheck)

        expect { subject.run! }.to raise_error(HealthCheckFailed)
      end

      it 'does not raise an exception when the healthcheck passes' do
        allow(subject).to receive(:shellout)
          .with("find #{project.install_dir}/ -type f | xargs ldd")
          .and_return(good_healthcheck)

        expect { subject.run! }.to_not raise_error
      end

      it 'will not perform dll base relocation checks' do
        expect(subject.relocation_checkable?).to be false
      end
    end
  end
end