From ed8cf0c0c285abab24479b7af3bc73f2c7340822 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Mon, 17 Feb 2025 00:12:20 +0200 Subject: can detect dependency loop --- lib/dslkeywords/resource.rb | 19 +++++++++++++++---- test/lib/dslkeywords/dependency_test.rb | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/dslkeywords/resource.rb b/lib/dslkeywords/resource.rb index 1634061..2320077 100644 --- a/lib/dslkeywords/resource.rb +++ b/lib/dslkeywords/resource.rb @@ -40,14 +40,25 @@ module RCM module DependencyEvaluator attr_reader :evaluated + class DependencyLoop < StandardError; end + class UnresolvedDependency < StandardError; end + def evaluate! return false if @evaluated + raise DependencyLoop, "Dependency loop detected for #{id}" if @loop_detection + + @loop_detection = true @depends_on = {} if @depends_on.nil? - @depends_on.each_key do |id| - dependency = Resource.find(id) - end + # Try to evaluate all dependencies recursively. + @depends_on.each_key.map { Resource.find(_1) }.each(&:evaluate!) + + # Raise an exception when there are still unresolved dependencies. + unresolved = @depends_on.each_key.map { Resource.find(_1) }.reject(&:evaluated) + raise UnresolvedDependency, "Unresolved dependencies: #{unresolved.map(&:id)}" if unresolved.count.positive? + + @loop_detection = false @evaluated = true end end @@ -61,7 +72,7 @@ module RCM def self.find(id) klass = Object.const_get("RCM::#{id.split('(').first.capitalize}") - resource = ObjectSpace.each_object(klass).find { |obj| obj.id == id } + resource = ObjectSpace.each_object(klass).find { _1.id == id } raise NoSuchResourceObject, "Unable to find resource #{id}" if resource.nil? resource diff --git a/test/lib/dslkeywords/dependency_test.rb b/test/lib/dslkeywords/dependency_test.rb index e9bc205..f1280eb 100644 --- a/test/lib/dslkeywords/dependency_test.rb +++ b/test/lib/dslkeywords/dependency_test.rb @@ -48,4 +48,21 @@ class RCMDependencyTest < Minitest::Test end end end + + def test_dependency_loop + assert_raises(RCM::DependencyEvaluator::DependencyLoop) do + configure_from_scratch do + notify('loop') { depends_on notify('loop') } + end + end + end + + def test_dependency_loop_indirect + assert_raises(RCM::DependencyEvaluator::DependencyLoop) do + configure_from_scratch do + notify('loop') { depends_on notify('pool') } + notify('pool') { depends_on notify('loop') } + end + end + end end -- cgit v1.2.3