mirror of
https://github.com/inspec/inspec
synced 2024-11-27 07:00:39 +00:00
Improve duplicate and cycle detection in resolver
Signed-off-by: Steven Danna <steve@chef.io>
This commit is contained in:
parent
bff5bb2320
commit
b48b9edae9
3 changed files with 51 additions and 11 deletions
|
@ -31,7 +31,23 @@ module Inspec
|
||||||
new.resolve(reqs)
|
new.resolve(reqs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve(deps, top_level = true, seen_items = {}, path_string = '')
|
def detect_duplicates(deps, top_level, path_string)
|
||||||
|
seen_items_local = []
|
||||||
|
deps.each do |dep|
|
||||||
|
if seen_items_local.include?(dep.name)
|
||||||
|
problem_cookbook = if top_level
|
||||||
|
'the inspec.yml for this profile.'
|
||||||
|
else
|
||||||
|
"the dependency information for #{path_string.split(' ').last}"
|
||||||
|
end
|
||||||
|
fail Inspec::DuplicateDep, "The dependency #{dep.name} is listed twice in #{problem_cookbook}"
|
||||||
|
else
|
||||||
|
seen_items_local << dep.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve(deps, top_level = true, seen_items = {}, path_string = '') # rubocop:disable Metrics/AbcSize
|
||||||
graph = {}
|
graph = {}
|
||||||
if top_level
|
if top_level
|
||||||
Inspec::Log.debug("Starting traversal of dependencies #{deps.map(&:name)}")
|
Inspec::Log.debug("Starting traversal of dependencies #{deps.map(&:name)}")
|
||||||
|
@ -39,17 +55,19 @@ module Inspec
|
||||||
Inspec::Log.debug("Traversing dependency tree of transitive dependency #{deps.map(&:name)}")
|
Inspec::Log.debug("Traversing dependency tree of transitive dependency #{deps.map(&:name)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
detect_duplicates(deps, top_level, path_string)
|
||||||
deps.each do |dep|
|
deps.each do |dep|
|
||||||
path_string = if path_string.empty?
|
new_seen_items = seen_items.dup
|
||||||
|
new_path_string = if path_string.empty?
|
||||||
dep.name
|
dep.name
|
||||||
else
|
else
|
||||||
path_string + " -> #{dep.name}"
|
path_string + " -> #{dep.name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
if seen_items.key?(dep.resolved_source)
|
if new_seen_items.key?(dep.resolved_source)
|
||||||
fail Inspec::CyclicDependencyError, "Dependency #{dep} would cause a dependency cycle (#{path_string})"
|
fail Inspec::CyclicDependencyError, "Dependency #{dep} would cause a dependency cycle (#{new_path_string})"
|
||||||
else
|
else
|
||||||
seen_items[dep.resolved_source] = true
|
new_seen_items[dep.resolved_source] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if !dep.source_satisfies_spec?
|
if !dep.source_satisfies_spec?
|
||||||
|
@ -59,8 +77,7 @@ module Inspec
|
||||||
Inspec::Log.debug("Adding dependency #{dep.name} (#{dep.resolved_source})")
|
Inspec::Log.debug("Adding dependency #{dep.name} (#{dep.resolved_source})")
|
||||||
graph[dep.name] = dep
|
graph[dep.name] = dep
|
||||||
if !dep.dependencies.empty?
|
if !dep.dependencies.empty?
|
||||||
# Recursively resolve any transitive dependencies.
|
resolve(dep.dependencies, false, new_seen_items.dup, new_path_string)
|
||||||
resolve(dep.dependencies, false, seen_items.dup, path_string)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,5 @@ module Inspec
|
||||||
# dependency resolution
|
# dependency resolution
|
||||||
class CyclicDependencyError < Error; end
|
class CyclicDependencyError < Error; end
|
||||||
class UnsatisfiedVersionSpecification < Error; end
|
class UnsatisfiedVersionSpecification < Error; end
|
||||||
|
class DuplicateDep < Error; end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,10 @@ class FakeDep
|
||||||
def resolved_source
|
def resolved_source
|
||||||
{ path: name }
|
{ path: name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def source_satisfies_spec?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe Inspec::Resolver do
|
describe Inspec::Resolver do
|
||||||
|
@ -21,6 +25,24 @@ describe Inspec::Resolver do
|
||||||
subject.resolve([]).must_equal({})
|
subject.resolve([]).must_equal({})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "errors if a dependency is listed twice at the same level" do
|
||||||
|
dep = FakeDep.new("fake_dep_0")
|
||||||
|
lambda { subject.resolve([dep, dep]) }.must_raise Inspec::DuplicateDep
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails if there is a simple cycle " do
|
||||||
|
dep0 = FakeDep.new("fake_dep_0")
|
||||||
|
dep1 = FakeDep.new("fake_dep_1")
|
||||||
|
dep2 = FakeDep.new("fake_dep_2")
|
||||||
|
|
||||||
|
dep0.stubs(:dependencies).returns([dep1])
|
||||||
|
|
||||||
|
dep1.stubs(:dependencies).returns([dep2])
|
||||||
|
dep2.stubs(:dependencies).returns([dep1])
|
||||||
|
lambda { subject.resolve([dep0]) }.must_raise Inspec::CyclicDependencyError
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
it "errors if the source version doesn't match the requirement" do
|
it "errors if the source version doesn't match the requirement" do
|
||||||
dep = FakeDep.new("fake_dep_0")
|
dep = FakeDep.new("fake_dep_0")
|
||||||
dep.expects(:source_satisfies_spec?).returns(false)
|
dep.expects(:source_satisfies_spec?).returns(false)
|
||||||
|
|
Loading…
Reference in a new issue