spec/unit/health_check_spec.rb (197 lines of code) (raw):
require "spec_helper"
require "pedump"
module Omnibus
describe HealthCheck do
let(:project) do
double(Project,
name: "chefdk",
install_dir: "/opt/chefdk",
allowed_libs: [],
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: "2012R2")
end
it "will perform dll base relocation checks" do
stub_ohai(platform: "windows", version: "2012R2")
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: "16.04") }
# file_list just needs to have one file which is inside of the install_dir
let(:file_list) do
double("Mixlib::Shellout",
error!: false,
stdout: <<~EOH
/opt/chefdk/shouldnt/matter
EOH
)
end
let(:empty_list) do
double("Mixlib::Shellout",
error!: false,
stdout: <<~EOH
EOH
)
end
let(:failed_list) do
failed_list = double("Mixlib::Shellout",
stdout: <<~EOH
/opt/chefdk/shouldnt/matter
EOH
)
allow(failed_list).to receive(:error!).and_raise("Mixlib::Shellout::ShellCommandFailed")
failed_list
end
let(:bad_list) do
double("Mixlib::Shellout",
error!: false,
stdout: <<~EOH
/somewhere/other/than/install/dir
EOH
)
end
let(:bad_healthcheck) do
double("Mixlib::Shellout",
error!: false,
stdout: <<~EOH
/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",
error!: false,
stdout: <<~EOH
/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 /opt/chefdk/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'")
.and_return(file_list)
allow(subject).to receive(:shellout)
.with("xargs ldd", { input: "/opt/chefdk/shouldnt/matter\n" })
.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 /opt/chefdk/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'")
.and_return(file_list)
allow(subject).to receive(:shellout)
.with("xargs ldd", { input: "/opt/chefdk/shouldnt/matter\n" })
.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
it "raises an exception if there's nothing in the file list" do
allow(subject).to receive(:shellout)
.with("find /opt/chefdk/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'")
.and_return(empty_list)
expect { subject.run! }.to raise_error(RuntimeError, "Internal Error: Health Check found no lines")
end
it "raises an exception if the file list command raises" do
allow(subject).to receive(:shellout)
.with("find /opt/chefdk/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'")
.and_return(failed_list)
expect { subject.run! }.to raise_error(RuntimeError, "Mixlib::Shellout::ShellCommandFailed")
end
it "raises an exception if the file list command has no entries in the install_dir" do
allow(subject).to receive(:shellout)
.with("find /opt/chefdk/ -type f | xargs file | grep \"ELF\" | awk -F: '{print $1}' | sed -e 's/:$//'")
.and_return(bad_list)
expect { subject.run! }.to raise_error(RuntimeError, "Internal Error: Health Check lines not matching the install_dir")
end
end
end
end