From 14b8cfc68fc23e0bae6912670c734cad6f0eb753 Mon Sep 17 00:00:00 2001 From: Jhonas Date: Thu, 16 Mar 2023 15:58:45 -0300 Subject: [PATCH 1/2] fix: test whether explicitly selected services are forwarded --- test/actions/test_profile.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/actions/test_profile.rb b/test/actions/test_profile.rb index 5c99da2c7..8c7cca3d3 100644 --- a/test/actions/test_profile.rb +++ b/test/actions/test_profile.rb @@ -226,6 +226,22 @@ module Actions .requirements.instanciate(plan) assert_kind_of srv_task_m, task.test_child end + + it "forwards explicitly selected services" do + srv_m = DataService.new_submodel + task_m = TaskContext.new_submodel + task_m.provides srv_m, as: "srv1" + task_m.provides srv_m, as: "srv2" + cmp_m = Composition.new_submodel + cmp_m.add srv_m, as: "srv" + base_profile = Profile.new + base_profile.tag "srv", srv_m + base_profile.define "d", cmp_m.use("srv" => base_profile.srv_tag) + profile = Profile.new + profile.use_profile base_profile, "srv" => task_m.srv1_srv + + profile.d_def.instanciate(plan) + end end describe "#define" do From b214536ca5fc1c3f12d5392ce90e400c9fd8ad73 Mon Sep 17 00:00:00 2001 From: Sylvain Joyeux Date: Tue, 13 May 2025 13:38:06 -0300 Subject: [PATCH 2/2] fix: handling of placeholder models in dependency injection The placeholder models (of which the most obvious instance is the profile tag) were handled as normal component models by DependencyInjection. This led to their selection to be reduced to component models as well, which fails if there are ambiguities and one want to select a particular service, that is some_tag => component_m.my_srv Rewrite the selection logic to use the placeholder-friendly API (each_required_model and fullfills?) --- lib/syskit/dependency_injection.rb | 76 ++- test/test_dependency_injection.rb | 723 +++++++++++++++-------------- test/test_instance_requirements.rb | 26 +- 3 files changed, 449 insertions(+), 376 deletions(-) diff --git a/lib/syskit/dependency_injection.rb b/lib/syskit/dependency_injection.rb index 7240ff629..c908259be 100644 --- a/lib/syskit/dependency_injection.rb +++ b/lib/syskit/dependency_injection.rb @@ -202,31 +202,65 @@ def self.normalize_selection(selection) next end - if value.respond_to?(:fullfills?) && !value.fullfills?(key) - raise ArgumentError, - "found #{value.name}(of class #{value.class}) " \ - "as a selection for #{key.name}, but " \ - "#{value.name} does not fullfill #{key.name}" + normalized[key] = normalize_component_model_selection(key, value) + end + normalized + end + + # @api private + # + # Helper for normalize_selection when the key is a component model (e.g. + # "real" component model, data service model or placeholder model) + # + # @param [Class] key + # @param [String,SpecialDIValue,#fullfills?] value + # @return [String,SpecialDIValue,#fullfills?] normalized selected value + def self.normalize_component_model_selection(key, value) + if value.respond_to?(:to_str) + return value + elsif value.kind_of?(SpecialDIValue) + return value + end + + unless value.fullfills?(key) + raise ArgumentError, + "found #{value.name}(of class #{value.class}) " \ + "as a selection for #{key.name}, but " \ + "#{value.name} does not fullfill #{key.name}" + end + + required_component_m = nil + required_services_m = [] + required_component_m, required_services_m = + key.each_required_model.partition do |m| + m <= Component end + if required_services_m.size > 1 + raise NotImplementedError, + "there is no support (yet) to select for multiple services" + elsif !required_component_m && required_services_m.empty? + raise NotImplementedError, "nothing to select in #{key}" + end - if key <= Component - if value.kind_of?(Models::BoundDataService) - value = value.component_model - elsif value.kind_of?(Syskit::BoundDataService) - value = value.component - end - normalized[key] = value - elsif key <= DataService - if value.respond_to?(:find_data_service_from_type) - value = value.find_data_service_from_type(key) - end - normalized[key] = value - else - raise NotImplementedError, - "should not have get there, but did" + required_component_m = required_component_m.first + required_service_m = required_services_m.first + + if required_service_m && !required_component_m + if value.respond_to?(:find_data_service_from_type) + value = + value.find_data_service_from_type(required_services_m.first) + end + + value + else + if value.kind_of?(Models::BoundDataService) + value = value.component_model + elsif value.kind_of?(Syskit::BoundDataService) + value = value.component end + + value end - normalized end # Add a list of objects to the default list. diff --git a/test/test_dependency_injection.rb b/test/test_dependency_injection.rb index d40d1e47b..f1a3671a2 100644 --- a/test/test_dependency_injection.rb +++ b/test/test_dependency_injection.rb @@ -2,228 +2,405 @@ require "syskit/test/self" -describe Syskit::DependencyInjection do - describe "#selection_for" do - it "returns an existing instance if one is selected" do - task = Syskit::Component.new_submodel.new - di = Syskit::DependencyInjection.new("child" => task) - result = di.selection_for("child", Syskit::InstanceRequirements.new) - assert_equal [task, Syskit::InstanceRequirements.new([task.model]), {}, ["child"].to_set], result - end - it "validates the instance with the provided requirements and pass if it fullfills" do - task = Syskit::Component.new_submodel.new - di = Syskit::DependencyInjection.new("child" => task) - - requirements = task.model.to_instance_requirements - flexmock(task).should_receive(:fullfills?).with(requirements, {}).and_return(true) - result = di.selection_for("child", requirements) - assert_equal [task, Syskit::InstanceRequirements.new([task.model]), {}, ["child"].to_set], result - end - it "validates the instance with the provided requirements and raises if it does not match" do - task = Syskit::Component.new_submodel.new - di = Syskit::DependencyInjection.new("child" => task) - - requirements = task.model.to_instance_requirements - flexmock(task).should_receive(:fullfills?).with(requirements, {}).and_return(false) - assert_raises(ArgumentError) do - di.selection_for("child", requirements) - end - end - it "returns an existing instance service if one is selected and required" do - srv = Syskit::DataService.new_submodel - task = Syskit::Component.new_submodel { provides srv, as: "srv" }.new - di = Syskit::DependencyInjection.new("child" => task.srv_srv) - - instance, requirements, services = di.selection_for("child", Syskit::InstanceRequirements.new([srv])) - assert_equal task, instance - assert_equal Syskit::InstanceRequirements.new([task.model]), requirements - assert_equal Hash[srv => task.model.srv_srv], services - end - it "maps name-to-service selections to the requirements" do - base_srv_m = Syskit::DataService.new_submodel - srv_m = Syskit::DataService.new_submodel - srv_m.provides base_srv_m - task_m = Syskit::Component.new_submodel - task_m.provides srv_m, as: "test" - di = Syskit::DependencyInjection.new("child" => task_m.test_srv) - _, _, service_selections = di.selection_for("child", Syskit::InstanceRequirements.new([base_srv_m])) - assert_equal task_m.test_srv, service_selections[base_srv_m] - end - it "uses service-to-bound_service selections as service mappings when matching the task model" do - base_srv_m = Syskit::DataService.new_submodel - srv_m = Syskit::DataService.new_submodel - srv_m.provides base_srv_m - task_m = Syskit::Component.new_submodel - task_m.provides srv_m, as: "test" - task_m.provides srv_m, as: "ambiguous" - di = Syskit::DependencyInjection.new("child" => task_m, srv_m => task_m.test_srv) - _, _, service_selections = di.selection_for("child", Syskit::InstanceRequirements.new([base_srv_m])) - assert_equal task_m.test_srv, service_selections[base_srv_m] - end - it "will accept DependencyInjection.nothing as a selection, thus overriding more general selections" do - srv_m = Syskit::DataService.new_submodel - task_m = Syskit::TaskContext.new_submodel - task_m.provides srv_m, as: "test" - di = Syskit::DependencyInjection.new( - "child" => Syskit::DependencyInjection.nothing, srv_m => task_m - ) - _, requirements, = di.selection_for("child", srv_m) - assert_equal srv_m.placeholder_model, requirements.model - end - it "will accept DependencyInjection.do_not_inherit as a selection, thus falling back to more general selections" do - srv_m = Syskit::DataService.new_submodel - task_m = Syskit::TaskContext.new_submodel - task_m.provides srv_m, as: "test" - di = Syskit::DependencyInjection.new( - "child" => Syskit::DependencyInjection.do_not_inherit, srv_m => task_m - ) - _, requirements, = di.selection_for("child", srv_m) - assert_equal task_m, requirements.model +module Syskit + describe DependencyInjection do + describe "#selection_for" do + it "returns an existing instance if one is selected" do + task = Syskit::Component.new_submodel.new + di = Syskit::DependencyInjection.new("child" => task) + result = di.selection_for("child", Syskit::InstanceRequirements.new) + assert_equal [task, Syskit::InstanceRequirements.new([task.model]), {}, ["child"].to_set], result + end + it "validates the instance with the provided requirements and pass if it fullfills" do + task = Syskit::Component.new_submodel.new + di = Syskit::DependencyInjection.new("child" => task) + + requirements = task.model.to_instance_requirements + flexmock(task).should_receive(:fullfills?).with(requirements, {}).and_return(true) + result = di.selection_for("child", requirements) + assert_equal [task, Syskit::InstanceRequirements.new([task.model]), {}, ["child"].to_set], result + end + it "validates the instance with the provided requirements and raises if it does not match" do + task = Syskit::Component.new_submodel.new + di = Syskit::DependencyInjection.new("child" => task) + + requirements = task.model.to_instance_requirements + flexmock(task).should_receive(:fullfills?).with(requirements, {}).and_return(false) + assert_raises(ArgumentError) do + di.selection_for("child", requirements) + end + end + it "returns an existing instance service if one is selected and required" do + srv = Syskit::DataService.new_submodel + task = Syskit::Component.new_submodel { provides srv, as: "srv" }.new + di = Syskit::DependencyInjection.new("child" => task.srv_srv) + + instance, requirements, services = di.selection_for("child", Syskit::InstanceRequirements.new([srv])) + assert_equal task, instance + assert_equal Syskit::InstanceRequirements.new([task.model]), requirements + assert_equal Hash[srv => task.model.srv_srv], services + end + it "maps name-to-service selections to the requirements" do + base_srv_m = Syskit::DataService.new_submodel + srv_m = Syskit::DataService.new_submodel + srv_m.provides base_srv_m + task_m = Syskit::Component.new_submodel + task_m.provides srv_m, as: "test" + di = Syskit::DependencyInjection.new("child" => task_m.test_srv) + _, _, service_selections = di.selection_for("child", Syskit::InstanceRequirements.new([base_srv_m])) + assert_equal task_m.test_srv, service_selections[base_srv_m] + end + it "uses service-to-bound_service selections as service mappings when matching the task model" do + base_srv_m = Syskit::DataService.new_submodel + srv_m = Syskit::DataService.new_submodel + srv_m.provides base_srv_m + task_m = Syskit::Component.new_submodel + task_m.provides srv_m, as: "test" + task_m.provides srv_m, as: "ambiguous" + di = Syskit::DependencyInjection.new("child" => task_m, srv_m => task_m.test_srv) + _, _, service_selections = di.selection_for("child", Syskit::InstanceRequirements.new([base_srv_m])) + assert_equal task_m.test_srv, service_selections[base_srv_m] + end + it "will accept DependencyInjection.nothing as a selection, thus overriding more general selections" do + srv_m = Syskit::DataService.new_submodel + task_m = Syskit::TaskContext.new_submodel + task_m.provides srv_m, as: "test" + di = Syskit::DependencyInjection.new( + "child" => Syskit::DependencyInjection.nothing, srv_m => task_m + ) + _, requirements, = di.selection_for("child", srv_m) + assert_equal srv_m.placeholder_model, requirements.model + end + it "will accept DependencyInjection.do_not_inherit as a selection, thus falling back to more general selections" do + srv_m = Syskit::DataService.new_submodel + task_m = Syskit::TaskContext.new_submodel + task_m.provides srv_m, as: "test" + di = Syskit::DependencyInjection.new( + "child" => Syskit::DependencyInjection.do_not_inherit, srv_m => task_m + ) + _, requirements, = di.selection_for("child", srv_m) + assert_equal task_m, requirements.model + end end - end - describe "#instance_selection_for" do - it "propagates the abstract flag" do - srv_m = Syskit::DataService.new_submodel - task_m = Syskit::TaskContext.new_submodel - task_m.provides srv_m, as: "test" - di = Syskit::DependencyInjection.new("child" => task_m.to_instance_requirements.abstract) - assert di.instance_selection_for("child", task_m.to_instance_requirements)[0].selected.abstract? - di = Syskit::DependencyInjection.new(task_m => task_m.to_instance_requirements.abstract) - assert di.instance_selection_for(nil, task_m.to_instance_requirements)[0].selected.abstract? - di = Syskit::DependencyInjection.new("child" => srv_m.to_instance_requirements.abstract, srv_m => task_m) - assert di.instance_selection_for("child", task_m.to_instance_requirements)[0].selected.abstract? + describe "#instance_selection_for" do + it "propagates the abstract flag" do + srv_m = Syskit::DataService.new_submodel + task_m = Syskit::TaskContext.new_submodel + task_m.provides srv_m, as: "test" + di = Syskit::DependencyInjection.new("child" => task_m.to_instance_requirements.abstract) + assert di.instance_selection_for("child", task_m.to_instance_requirements)[0].selected.abstract? + di = Syskit::DependencyInjection.new(task_m => task_m.to_instance_requirements.abstract) + assert di.instance_selection_for(nil, task_m.to_instance_requirements)[0].selected.abstract? + di = Syskit::DependencyInjection.new("child" => srv_m.to_instance_requirements.abstract, srv_m => task_m) + assert di.instance_selection_for("child", task_m.to_instance_requirements)[0].selected.abstract? + end end - end - describe "#add" do - attr_reader :di, :explicit_m, :default_m - - before do - @di = flexmock(Syskit::DependencyInjection.new) - @explicit_m = Syskit::TaskContext.new_submodel - @default_m = Syskit::TaskContext.new_submodel - end - - it "normalizes the explicit arguments" do - flexmock(Syskit::DependencyInjection).should_receive(:normalize_selection).with("test" => explicit_m).once.pass_thru - di.add("test" => explicit_m) - end - it "normalizes the default arguments" do - flexmock(Syskit::DependencyInjection).should_receive(:normalize_selected_object).with(m = flexmock).once.and_return(default_m) - di.should_receive(:add_defaults).with([default_m].to_set).once - di.add(m) - end - it "merges new default arguments with the existing ones" do - a_m = Syskit::TaskContext.new_submodel - b_m = Syskit::TaskContext.new_submodel - di = Syskit::DependencyInjection.new - flexmock(di).should_receive(:add_defaults).with([a_m, b_m].to_set).once - flexmock(di).should_receive(:add_explicit).with({}).once - di.add(a_m, b_m) - end - it "adds both explicit selections and defaults from given DI objects" do - a_m = Syskit::TaskContext.new_submodel - b_m = Syskit::TaskContext.new_submodel - added = Syskit::DependencyInjection.new - added.add(a_m, "test" => b_m) - di = Syskit::DependencyInjection.new - flexmock(di).should_receive(:add_defaults).with([a_m].to_set).once - flexmock(di).should_receive(:add_explicit).with("test" => b_m).once - di.add(added) - end - end + describe "#add" do + attr_reader :di, :explicit_m, :default_m - describe "#add_explicit" do - it "does not modify the selections if given an identity mapping" do - a = Syskit::DataService.new_submodel(name: "A") - b = Syskit::DataService.new_submodel(name: "B") { provides a } - di = Syskit::DependencyInjection.new(a => b) - di.add(a => a) - assert_equal Hash[a => b], di.explicit - end - end + before do + @di = flexmock(Syskit::DependencyInjection.new) + @explicit_m = Syskit::TaskContext.new_submodel + @default_m = Syskit::TaskContext.new_submodel + end - describe "#resolve_recursive_selection_mapping" do - it "resolves component models recursively" do - srv0 = Syskit::DataService.new_submodel - srv1 = Syskit::DataService.new_submodel - srv1.provides srv0 - mapping = { "value" => srv0, srv0 => srv1 } - assert_equal({ "value" => srv1, srv0 => srv1 }, - Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)) + it "normalizes the explicit arguments" do + flexmock(Syskit::DependencyInjection).should_receive(:normalize_selection).with("test" => explicit_m).once.pass_thru + di.add("test" => explicit_m) + end + it "normalizes the default arguments" do + flexmock(Syskit::DependencyInjection).should_receive(:normalize_selected_object).with(m = flexmock).once.and_return(default_m) + di.should_receive(:add_defaults).with([default_m].to_set).once + di.add(m) + end + it "merges new default arguments with the existing ones" do + a_m = Syskit::TaskContext.new_submodel + b_m = Syskit::TaskContext.new_submodel + di = Syskit::DependencyInjection.new + flexmock(di).should_receive(:add_defaults).with([a_m, b_m].to_set).once + flexmock(di).should_receive(:add_explicit).with({}).once + di.add(a_m, b_m) + end + it "adds both explicit selections and defaults from given DI objects" do + a_m = Syskit::TaskContext.new_submodel + b_m = Syskit::TaskContext.new_submodel + added = Syskit::DependencyInjection.new + added.add(a_m, "test" => b_m) + di = Syskit::DependencyInjection.new + flexmock(di).should_receive(:add_defaults).with([a_m].to_set).once + flexmock(di).should_receive(:add_explicit).with("test" => b_m).once + di.add(added) + end end - it "does not resolve names" do - mapping = { "name" => "value", "value" => "bla" } - assert_equal(mapping, - Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)) + describe "#add_explicit" do + it "does not modify the selections if given an identity mapping" do + a = Syskit::DataService.new_submodel(name: "A") + b = Syskit::DataService.new_submodel(name: "B") { provides a } + di = Syskit::DependencyInjection.new(a => b) + di.add(a => a) + assert_equal Hash[a => b], di.explicit + end end - it "resolves the component model of bound data services" do - srv_m = Syskit::DataService.new_submodel - proxy_m = srv_m.placeholder_model - proxy2_m = srv_m.placeholder_model - task_m = Syskit::TaskContext.new_submodel - task_m.provides srv_m, as: "test" + describe "#resolve_recursive_selection_mapping" do + it "resolves component models recursively" do + srv0 = Syskit::DataService.new_submodel + srv1 = Syskit::DataService.new_submodel + srv1.provides srv0 + mapping = { "value" => srv0, srv0 => srv1 } + assert_equal({ "value" => srv1, srv0 => srv1 }, + Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)) + end - mapping = { srv_m => proxy_m.m0_srv, proxy_m => proxy2_m, proxy2_m => task_m } - assert_equal(task_m.test_srv, - Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)[srv_m]) - end + it "does not resolve names" do + mapping = { "name" => "value", "value" => "bla" } + assert_equal(mapping, + Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)) + end - it "properly maintains already resolved bound data services if an indentity selection is present" do - srv_m = Syskit::DataService.new_submodel - task_m = Syskit::TaskContext.new_submodel - task_m.provides srv_m, as: "test" - task_m.provides srv_m, as: "ambiguous" + it "resolves the component model of bound data services" do + srv_m = Syskit::DataService.new_submodel + proxy_m = srv_m.placeholder_model + proxy2_m = srv_m.placeholder_model + task_m = Syskit::TaskContext.new_submodel + task_m.provides srv_m, as: "test" - mapping = { srv_m => task_m.test_srv, task_m => task_m } - assert_equal(task_m.test_srv, - Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)[srv_m]) - end - end + mapping = { srv_m => proxy_m.m0_srv, proxy_m => proxy2_m, proxy2_m => task_m } + assert_equal(task_m.test_srv, + Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)[srv_m]) + end - describe "#merge" do - attr_reader :model0, :model1, :di0, :di1 + it "properly maintains already resolved bound data services if an indentity selection is present" do + srv_m = Syskit::DataService.new_submodel + task_m = Syskit::TaskContext.new_submodel + task_m.provides srv_m, as: "test" + task_m.provides srv_m, as: "ambiguous" - before do - @model0 = flexmock(Syskit::InstanceRequirements.new) - @model1 = flexmock(Syskit::InstanceRequirements.new) - model0.should_receive(:==).with(model1).and_return(false) - model1.should_receive(:==).with(model0).and_return(false) - @di0 = Syskit::DependencyInjection.new("test" => model0) - @di1 = Syskit::DependencyInjection.new("test" => model1) + mapping = { srv_m => task_m.test_srv, task_m => task_m } + assert_equal(task_m.test_srv, + Syskit::DependencyInjection.resolve_recursive_selection_mapping(mapping)[srv_m]) + end end - it "should simply pass on identical models" do - model0.should_receive(:==).with(model1).and_return(true) - di0.merge(di1) - assert_same model0, di0.explicit["test"] - end + describe "#merge" do + attr_reader :model0, :model1, :di0, :di1 - it "should use the most-specific model if both DI objects have conflicting selections" do - model0.should_receive(:fullfills?).with(model1).and_return(true) - model1.should_receive(:fullfills?).with(model0).and_return(false) + before do + @model0 = flexmock(Syskit::InstanceRequirements.new) + @model1 = flexmock(Syskit::InstanceRequirements.new) + model0.should_receive(:==).with(model1).and_return(false) + model1.should_receive(:==).with(model0).and_return(false) + @di0 = Syskit::DependencyInjection.new("test" => model0) + @di1 = Syskit::DependencyInjection.new("test" => model1) + end - di0 = self.di0.dup - di1 = self.di1.dup - di0.merge(di1) - assert_same model0, di0.explicit["test"] + it "should simply pass on identical models" do + model0.should_receive(:==).with(model1).and_return(true) + di0.merge(di1) + assert_same model0, di0.explicit["test"] + end + + it "should use the most-specific model if both DI objects have conflicting selections" do + model0.should_receive(:fullfills?).with(model1).and_return(true) + model1.should_receive(:fullfills?).with(model0).and_return(false) + + di0 = self.di0.dup + di1 = self.di1.dup + di0.merge(di1) + assert_same model0, di0.explicit["test"] + + # Test the other way around + di0 = self.di0.dup + di1 = self.di1.dup + di1.merge(di0) + assert_same model0, di1.explicit["test"] + end - # Test the other way around - di0 = self.di0.dup - di1 = self.di1.dup - di1.merge(di0) - assert_same model0, di1.explicit["test"] + it "should raise if conflicting selections cannot be resolved" do + model0.should_receive(:fullfills?).with(model1).and_return(false) + model1.should_receive(:fullfills?).with(model0).and_return(false) + + assert_raises(ArgumentError) do + di0.merge(di1) + end + end end - it "should raise if conflicting selections cannot be resolved" do - model0.should_receive(:fullfills?).with(model1).and_return(false) - model1.should_receive(:fullfills?).with(model0).and_return(false) + describe "#normalize_selection" do + it "raises on invalid keys" do + assert_raises(ArgumentError) { DependencyInjection.normalize_selection(nil => "value") } + assert_raises(ArgumentError) { DependencyInjection.normalize_selection(Object.new => "value") } + assert_raises(ArgumentError) { DependencyInjection.normalize_selection(Class.new => "value") } + end - assert_raises(ArgumentError) do - di0.merge(di1) + it "accepts strings to allowed values" do + srv = DataService.new_submodel + component = Component.new_submodel + component.provides srv, as: "srv" + key = "key" + assert_equal(Hash[key => DependencyInjection.nothing], DependencyInjection.normalize_selection(key => DependencyInjection.nothing)) + assert_equal(Hash[key => DependencyInjection.do_not_inherit], DependencyInjection.normalize_selection(key => DependencyInjection.do_not_inherit)) + assert_equal(Hash[key => "value"], DependencyInjection.normalize_selection(key => "value")) + assert_equal(Hash[key => srv], DependencyInjection.normalize_selection(key => srv)) + assert_equal(Hash[key => component], DependencyInjection.normalize_selection(key => component)) + assert_equal(Hash[key => component.srv_srv], DependencyInjection.normalize_selection(key => component.srv_srv)) + component = component.new + assert_equal(Hash[key => component], DependencyInjection.normalize_selection(key => component)) + assert_equal(Hash[key => component.srv_srv], DependencyInjection.normalize_selection(key => component.srv_srv)) + req = InstanceRequirements.new + assert_equal(Hash[key => req], DependencyInjection.normalize_selection(key => req)) + end + + it "rejects strings to arbitrary" do + key = "key" + assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => Object.new) } + end + + it "accepts mappings from a component model to nil, string and identity" do + srv = DataService.new_submodel + component = Component.new_submodel + component.provides srv, as: "srv" + key = component + + assert_equal(Hash[key => DependencyInjection.nothing], DependencyInjection.normalize_selection(key => DependencyInjection.nothing)) + assert_equal(Hash[key => DependencyInjection.do_not_inherit], DependencyInjection.normalize_selection(key => DependencyInjection.do_not_inherit)) + assert_equal(Hash[key => "value"], DependencyInjection.normalize_selection(key => "value")) + assert_equal(Hash[key => key], DependencyInjection.normalize_selection(key => key)) + end + + it "refuses mappings from component to data service" do + key = Component.new_submodel + assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => DataService.new_submodel) } + end + + it "accepts mappings from a component model to a component that fullfills the key" do + key = Component.new_submodel + subcomponent_m = key.new_submodel + + assert_equal( + { key => subcomponent_m }, + DependencyInjection.normalize_selection(key => subcomponent_m) + ) + subcomponent = subcomponent_m.new + assert_equal( + { key => subcomponent }, + DependencyInjection.normalize_selection(key => subcomponent) + ) + end + + it "accepts mappings from a component model to an instance requirement that fullfills it" do + key = Component.new_submodel + req = InstanceRequirements.new([key]) + assert_equal(Hash[key => req], DependencyInjection.normalize_selection(key => req)) + end + + it "rejects mappings from a component to a component that does not fullfill it" do + key = Component.new_submodel + srv = DataService.new_submodel + component_m = Component.new_submodel { provides(srv, as: "srv") } + + assert_raises(ArgumentError) do + DependencyInjection.normalize_selection(key => component_m) + end + assert_raises(ArgumentError) do + DependencyInjection.normalize_selection(key => component_m.srv_srv) + end + component = component_m.new + e = assert_raises(ArgumentError) do + DependencyInjection.normalize_selection(key => component) + end + assert_raises(ArgumentError) do + DependencyInjection.normalize_selection(key => component.srv_srv) + end + end + + it "rejects mappings from a component model to an instance requirement " \ + "that does not fullfill it" do + key = Component.new_submodel + req = InstanceRequirements.new([Component.new_submodel]) + assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => req) } + end + + describe "from a data service model" do + it "accepts mappings to nil, string and identity" do + key = DataService.new_submodel + assert_equal(Hash[key => DependencyInjection.nothing], DependencyInjection.normalize_selection(key => DependencyInjection.nothing)) + assert_equal(Hash[key => DependencyInjection.do_not_inherit], DependencyInjection.normalize_selection(key => DependencyInjection.do_not_inherit)) + assert_equal(Hash[key => "value"], DependencyInjection.normalize_selection(key => "value")) + assert_equal(Hash[key => key], DependencyInjection.normalize_selection(key => key)) + end + + it "accepts mappings to a compatible data service model" do + srv0 = DataService.new_submodel + srv1 = DataService.new_submodel { provides srv0 } + assert_equal(Hash[srv0 => srv1], DependencyInjection.normalize_selection(srv0 => srv1)) + end + + it "accepts mappings to a compatible instance requirement" do + key = DataService.new_submodel + req = InstanceRequirements.new([key]) + assert_equal(Hash[key => req.find_data_service_from_type(key)], DependencyInjection.normalize_selection(key => req)) + end + + it "accepts mappings to an instance requirement representing a " \ + "component which provides the service" do + key = DataService.new_submodel + c = Component.new_submodel { provides key, as: "srv" } + req = InstanceRequirements.new([c]) + normalized = DependencyInjection.normalize_selection(key => req) + req_srv = req.dup + req_srv.select_service(c.srv_srv) + assert_equal(Hash[key => req_srv], normalized) + end + + it "accepts mappings to a component which provides the service" do + srv0 = DataService.new_submodel + c = Component.new_submodel { provides srv0, as: "srv" } + assert_equal(Hash[srv0 => c.srv_srv], DependencyInjection.normalize_selection(srv0 => c)) + c = c.new + assert_equal(Hash[srv0 => c.srv_srv], DependencyInjection.normalize_selection(srv0 => c)) + end + + it "rejects mappings to an instance requirement that provide an " \ + "incompatible data service model" do + key = DataService.new_submodel + req = InstanceRequirements.new([DataService.new_submodel]) + assert_raises(ArgumentError) do + DependencyInjection.normalize_selection(key => req) + end + end + + it "rejects mappings to a component that provides it more than once" do + srv0 = DataService.new_submodel + c = Component.new_submodel do + provides srv0, as: "srv0" + provides srv0, as: "srv1" + end + assert_raises(Syskit::AmbiguousServiceSelection) do + DependencyInjection.normalize_selection(srv0 => c) + end + end + + it "rejects mappings to a data service that is not compatible" do + assert_raises(ArgumentError) do + DependencyInjection.normalize_selection( + DataService.new_submodel => DataService.new_submodel + ) + end + end + end + + it "converts arbitrary objects to instance requirements" do + req = InstanceRequirements.new + value = flexmock + value.should_receive(:to_instance_requirements).once + .and_return(req) + di = DependencyInjection.new + di.add("name" => value) + assert_same req, di.explicit["name"] end end end @@ -274,154 +451,6 @@ def test_pretty_print PP.pp(dep, "".dup) end - def test_normalize_selection_raises_on_invalid_keys - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(nil => "value") } - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(Object.new => "value") } - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(Class.new => "value") } - end - - def test_normalize_selection_accepts_string_to_allowed_values - srv = DataService.new_submodel - component = Component.new_submodel - component.provides srv, as: "srv" - key = "key" - assert_equal(Hash[key => DependencyInjection.nothing], DependencyInjection.normalize_selection(key => DependencyInjection.nothing)) - assert_equal(Hash[key => DependencyInjection.do_not_inherit], DependencyInjection.normalize_selection(key => DependencyInjection.do_not_inherit)) - assert_equal(Hash[key => "value"], DependencyInjection.normalize_selection(key => "value")) - assert_equal(Hash[key => srv], DependencyInjection.normalize_selection(key => srv)) - assert_equal(Hash[key => component], DependencyInjection.normalize_selection(key => component)) - assert_equal(Hash[key => component.srv_srv], DependencyInjection.normalize_selection(key => component.srv_srv)) - component = component.new - assert_equal(Hash[key => component], DependencyInjection.normalize_selection(key => component)) - assert_equal(Hash[key => component.srv_srv], DependencyInjection.normalize_selection(key => component.srv_srv)) - req = InstanceRequirements.new - assert_equal(Hash[key => req], DependencyInjection.normalize_selection(key => req)) - end - - def test_normalize_selection_rejects_string_to_arbitrary - key = "key" - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => Object.new) } - end - - def test_normalize_selection_accepts_component_to_nil_string_and_identity - srv = DataService.new_submodel - component = Component.new_submodel - component.provides srv, as: "srv" - key = component - - assert_equal(Hash[key => DependencyInjection.nothing], DependencyInjection.normalize_selection(key => DependencyInjection.nothing)) - assert_equal(Hash[key => DependencyInjection.do_not_inherit], DependencyInjection.normalize_selection(key => DependencyInjection.do_not_inherit)) - assert_equal(Hash[key => "value"], DependencyInjection.normalize_selection(key => "value")) - assert_equal(Hash[key => key], DependencyInjection.normalize_selection(key => key)) - end - - def test_normalize_selection_refuses_component_to_data_service - key = Component.new_submodel - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => DataService.new_submodel) } - end - - def test_normalize_selection_accepts_component_to_component_that_fullfill_the_key - srv = DataService.new_submodel - key = Component.new_submodel - subcomponent = key.new_submodel - subcomponent.provides srv, as: "srv" - - assert_equal(Hash[key => subcomponent], DependencyInjection.normalize_selection(key => subcomponent)) - subcomponent = subcomponent.new - assert_equal(Hash[key => subcomponent], DependencyInjection.normalize_selection(key => subcomponent)) - end - - def test_normalize_selection_accepts_component_to_instance_requirements_that_fullfill_the_key - key = Component.new_submodel - req = InstanceRequirements.new([key]) - assert_equal(Hash[key => req], DependencyInjection.normalize_selection(key => req)) - end - - def test_normalize_selection_rejects_component_to_component_that_does_not_fullfill_the_key - key = Component.new_submodel - srv = DataService.new_submodel - component = Component.new_submodel { provides(srv, as: "srv") } - - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => component) } - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => component.srv_srv) } - component = component.new - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => component) } - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => component.srv_srv) } - end - - def test_normalize_selection_rejects_component_to_instance_requirements_that_fullfill_the_key - key = Component.new_submodel - req = InstanceRequirements.new([Component.new_submodel]) - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => req) } - end - - def test_normalize_selection_accepts_data_service_to_string_nil_and_identity - key = DataService.new_submodel - assert_equal(Hash[key => DependencyInjection.nothing], DependencyInjection.normalize_selection(key => DependencyInjection.nothing)) - assert_equal(Hash[key => DependencyInjection.do_not_inherit], DependencyInjection.normalize_selection(key => DependencyInjection.do_not_inherit)) - assert_equal(Hash[key => "value"], DependencyInjection.normalize_selection(key => "value")) - assert_equal(Hash[key => key], DependencyInjection.normalize_selection(key => key)) - end - - def test_normalize_selection_accepts_data_service_to_data_service_that_fullfill_the_key - srv0 = DataService.new_submodel - srv1 = DataService.new_submodel { provides srv0 } - assert_equal(Hash[srv0 => srv1], DependencyInjection.normalize_selection(srv0 => srv1)) - end - - def test_normalize_selection_accepts_data_service_to_instance_requirements_that_fullfill_the_key - key = DataService.new_submodel - req = InstanceRequirements.new([key]) - assert_equal(Hash[key => req.find_data_service_from_type(key)], DependencyInjection.normalize_selection(key => req)) - end - - def test_normalize_selection_accepts_data_service_to_instance_requirements_that_fullfill_the_key_and_selects_the_corresponding_service - key = DataService.new_submodel - c = Component.new_submodel { provides key, as: "srv" } - req = InstanceRequirements.new([c]) - normalized = DependencyInjection.normalize_selection(key => req) - req_srv = req.dup - req_srv.select_service(c.srv_srv) - assert_equal(Hash[key => req_srv], normalized) - end - - def test_normalize_selection_accepts_data_service_to_component_that_fullfill_the_key_and_maps_the_service - srv0 = DataService.new_submodel - c = Component.new_submodel { provides srv0, as: "srv" } - assert_equal(Hash[srv0 => c.srv_srv], DependencyInjection.normalize_selection(srv0 => c)) - c = c.new - assert_equal(Hash[srv0 => c.srv_srv], DependencyInjection.normalize_selection(srv0 => c)) - end - - def test_normalize_selection_rejects_data_service_to_instance_requirements_that_does_not_fullfill_the_key - key = DataService.new_submodel - req = InstanceRequirements.new([DataService.new_submodel]) - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(key => req) } - end - - def test_normalize_selection_rejects_data_service_to_component_that_has_multiple_matching_candidates - srv0 = DataService.new_submodel - c = Component.new_submodel do - provides srv0, as: "srv0" - provides srv0, as: "srv1" - end - assert_raises(Syskit::AmbiguousServiceSelection) { DependencyInjection.normalize_selection(srv0 => c) } - end - - def test_normalize_selection_rejects_data_service_to_data_service_that_does_not_fullfill_the_key - assert_raises(ArgumentError) { DependencyInjection.normalize_selection(DataService.new_submodel => DataService.new_submodel) } - end - - def test_normalize_selection_converts_arbitrary_values_to_instance_requirements - req = InstanceRequirements.new - value = flexmock - value.should_receive(:to_instance_requirements).once - .and_return(req) - di = DependencyInjection.new - di.add("name" => value) - assert_same req, di.explicit["name"] - end - def test_resolve_default_selections_selects_all_models_fullfilled_by_a_component_model srv0 = DataService.new_submodel srv1 = DataService.new_submodel diff --git a/test/test_instance_requirements.rb b/test/test_instance_requirements.rb index 72bd27279..43f86763b 100644 --- a/test/test_instance_requirements.rb +++ b/test/test_instance_requirements.rb @@ -290,24 +290,30 @@ @task_m = Syskit::TaskContext.new_submodel task_m.provides srv_m, as: "test" end - it "should not try to verify a name to value mapping for a known child if the value is a string" do + it "verifies a name to value mapping for a known child " \ + "if the value is a string" do simple_composition_model.overload("srv", simple_component_model) simple_composition_model.use("srv" => "device") end - it "should raise if a name to value mapping is invalid for a known child" do + it "raises if a name to value mapping is invalid for a known child" do simple_composition_model.overload("srv", simple_component_model) assert_raises(Syskit::InvalidSelection) do simple_composition_model.use("srv" => Syskit::TaskContext.new_submodel) end end - it "should raise if a name to value mapping is invalid for a known child, even though the model does not respond to #fullfills?" do + it "raises if a name to value mapping is invalid for a known child, " \ + "even though the model does not respond to #fullfills?" do simple_composition_model.overload("srv", simple_component_model) - req = flexmock(to_instance_requirements: Syskit::TaskContext.new_submodel.to_instance_requirements) + req = flexmock( + to_instance_requirements: + Syskit::TaskContext + .new_submodel.to_instance_requirements + ) assert_raises(Syskit::InvalidSelection) do simple_composition_model.use("srv" => req) end end - it "should allow providing a service submodel as a selection for a composition child" do + it "accepts a service submodel as a selection for a composition child" do srv_m = Syskit::DataService.new_submodel subsrv_m = srv_m.new_submodel cmp_m = Syskit::Composition.new_submodel do @@ -317,14 +323,18 @@ ir.use("test" => subsrv_m) end - it "should raise if a child selection is ambiguous" do + it "raises if a child selection is ambiguous" do task_m.provides srv_m, as: "ambiguous" cmp_m.use("test" => task_m) end - it "should allow selecting a service explicitly" do + + it "allows selecting a service explicitly for a child name" do task_m.provides srv_m, as: "ambiguous" req = cmp_m.use("test" => task_m.test_srv) - assert_equal task_m.test_srv, req.resolved_dependency_injection.explicit["test"] + assert_equal( + task_m.test_srv, + req.resolved_dependency_injection.explicit["test"] + ) end end