diff --git a/Gaudi/doc/release.notes b/Gaudi/doc/release.notes index 233fc0e05afe0c0996f3974de70e4966b6c420bc..85084e2f087970931988e0060288c2fd456a9b57 100644 --- a/Gaudi/doc/release.notes +++ b/Gaudi/doc/release.notes @@ -1,6 +1,15 @@ Package : Gaudi Package manager : Marco Clemencic +! 2014-02-06 - Marco Clemencic + - Improved test gaudi.conf_user.autoapply to be more robust in case of a change + in the order of application of configurables. + +! 2014-02-06 - Marco Clemencic + - Added test exposing bug #103803: + ConfigurableUser instances created in the __apply_configuration__ of another + one are not "applied". + ! 2013-12-11 - Sebastien Binet - Added hwaf configuration files. diff --git a/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/autoapply.qmt b/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/autoapply.qmt new file mode 100644 index 0000000000000000000000000000000000000000..4fabe0f9cff634283fccd542766150887189ef4d --- /dev/null +++ b/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/autoapply.qmt @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'> +<extension class="GaudiTest.GaudiExeTest" kind="test"> +<argument name="program"><text>../scripts/test_configurable_users.py</text></argument> +</extension> diff --git a/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/delayed_creation.qmt b/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/delayed_creation.qmt new file mode 100644 index 0000000000000000000000000000000000000000..560fddc2f6e966a5fe2ea74e40c5824bd44e928a --- /dev/null +++ b/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/delayed_creation.qmt @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'> +<extension class="GaudiTest.GaudiExeTest" kind="test"> +<argument name="program"><text>../scripts/test_cu_delayed_creation.py</text></argument> +</extension> diff --git a/Gaudi/tests/qmtest/gaudi.qms/conf_user_passive_dep.qmt b/Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/passive_dep.qmt similarity index 100% rename from Gaudi/tests/qmtest/gaudi.qms/conf_user_passive_dep.qmt rename to Gaudi/tests/qmtest/gaudi.qms/conf_user.qms/passive_dep.qmt diff --git a/Gaudi/tests/qmtest/gaudi.qms/conf_user_autoapply.qmt b/Gaudi/tests/qmtest/gaudi.qms/conf_user_autoapply.qmt deleted file mode 100644 index 0654948433569e7f515dfbf420718c8e7f082555..0000000000000000000000000000000000000000 --- a/Gaudi/tests/qmtest/gaudi.qms/conf_user_autoapply.qmt +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" ?> -<!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'> -<extension class="GaudiTest.GaudiExeTest" kind="test"> -<argument name="program"><text>../scripts/test_configurable_users.py</text></argument> -<argument name="validator"><text> -findReferenceBlock(""" -Applying Application -/***** User Application/Application **************************************************************** -|-Property1 = 10 -\----- (End of User Application/Application) ------------------------------------------------------- -Applying TestInstance -/***** User MultiInstance/TestInstance ************************************************************* -|-Property = 1 (default: 0) -\----- (End of User MultiInstance/TestInstance) ---------------------------------------------------- -Applying Application_MultiInstance -/***** User MultiInstance/Application_MultiInstance ************************************************ -|-Property = 0 -\----- (End of User MultiInstance/Application_MultiInstance) --------------------------------------- -Applying SubModule1 -/***** User SubModule1/SubModule1 ****************************************************************** -|-Property1 = 10 (default: 0) -\----- (End of User SubModule1/SubModule1) --------------------------------------------------------- -Applying SubModule2 -/***** User SubModule2/SubModule2 ****************************************************************** -|-Property1 = 10 (default: 2) -\----- (End of User SubModule2/SubModule2) --------------------------------------------------------- -Applying TestInstance_SubModule1 -/***** User SubModule1/TestInstance_SubModule1 ***************************************************** -|-Property1 = 0 -\----- (End of User SubModule1/TestInstance_SubModule1) -------------------------------------------- -Applying Application_MultiInstance_SubModule1 -/***** User SubModule1/Application_MultiInstance_SubModule1 **************************************** -|-Property1 = 0 -\----- (End of User SubModule1/Application_MultiInstance_SubModule1) ------------------------------- -Action Object One -Action Function -Action Object Two -Done. -""") -</text></argument> -</extension> diff --git a/Gaudi/tests/scripts/test_configurable_users.py b/Gaudi/tests/scripts/test_configurable_users.py index 83b5ba2dfa6a35b0af7fcba285087f7c338ce7a4..4e92201b2e94ed120e4b89ad1483535a5a15b181 100755 --- a/Gaudi/tests/scripts/test_configurable_users.py +++ b/Gaudi/tests/scripts/test_configurable_users.py @@ -1,39 +1,37 @@ from Gaudi.Configuration import * -class SubModule1(ConfigurableUser): - __slots__ = { "Property1": 0 } +appliedConf = [] + +class ConfigurableUserTest(ConfigurableUser): + __slots__ = {} def __apply_configuration__(self): print "Applying", self.getName() print self + appliedConf.append(self.getName()) + +class SubModule1(ConfigurableUserTest): + __slots__ = { "Property1": 0 } -class SubModule2(ConfigurableUser): +class SubModule2(ConfigurableUserTest): __slots__ = { "Property1": 2 } - def __apply_configuration__(self): - print "Applying", self.getName() - print self -class SubModule3(ConfigurableUser): +class SubModule3(ConfigurableUserTest): __slots__ = { "Property1": 3 } - def __apply_configuration__(self): - print "Applying", self.getName() - print self -class MultiInstance(ConfigurableUser): +class MultiInstance(ConfigurableUserTest): __slots__ = { "Property": 0 } __used_configurables__ = [ (SubModule1, None) ] def __apply_configuration__(self): - print "Applying", self.getName() - print self + super(MultiInstance, self).__apply_configuration__() SubModule1(self._instanceName(SubModule1)) -class Application(ConfigurableUser): +class Application(ConfigurableUserTest): __slots__ = { "Property1": 10 } __used_configurables__ = [ SubModule1, SubModule2, SubModule3, (MultiInstance, None), (MultiInstance, "TestInstance") ] def __apply_configuration__(self): - print "Applying", self.getName() - print self - if hasattr(self,"Property1"): + super(Application, self).__apply_configuration__() + if hasattr(self, "Property1"): val = self.Property1 else: val = self.getDefaultProperty("Property1") @@ -44,13 +42,17 @@ class Application(ConfigurableUser): ti = self.getUsedInstance("TestInstance") ti.Property = 1 +calledActions = [] + class Action(object): def __init__(self, msg): self.msg = msg def __call__(self): + calledActions.append(self.msg) print self.msg def ActionFunction(): + calledActions.append("Action Function") print "Action Function" appendPostConfigAction(Action("Action Object One")) @@ -63,4 +65,34 @@ Application() # apply all ConfigurableUser instances from GaudiKernel.Configurable import applyConfigurableUsers applyConfigurableUsers() -print "Done." + +# check that everything has been applied +expected = {'Application': ('Property1', 10), + 'TestInstance': ('Property', 1), + 'Application_MultiInstance': ('Property', 0), + 'SubModule1': ('Property1', 10), + 'SubModule2': ('Property1', 10), + 'TestInstance_SubModule1': ('Property1', 0), + 'Application_MultiInstance_SubModule1': ('Property1', 0)} +assert set(appliedConf) == set(expected) + +# check the order of application +assert appliedConf.index('Application') < appliedConf.index('SubModule1') +assert appliedConf.index('Application') < appliedConf.index('SubModule2') +assert appliedConf.index('Application') < appliedConf.index('Application_MultiInstance') +assert appliedConf.index('Application') < appliedConf.index('TestInstance') + +assert appliedConf.index('TestInstance') < appliedConf.index('TestInstance_SubModule1') + +assert appliedConf.index('Application_MultiInstance') < appliedConf.index('Application_MultiInstance_SubModule1') + +# check that the actions has been called in the right order +expectedActions = ["Action Object One", "Action Function", "Action Object Two"] +assert calledActions == expectedActions + +# check property values +allConfs = Configurable.allConfigurables +for name, (prop, value) in expected.items(): + assert allConfs[name].getProp(prop) == value, "%s.%s != %s" % (name, prop, value) + +print "Success." diff --git a/Gaudi/tests/scripts/test_cu_delayed_creation.py b/Gaudi/tests/scripts/test_cu_delayed_creation.py new file mode 100755 index 0000000000000000000000000000000000000000..b41bdf3b7adf67085ce895268324d81522fbb158 --- /dev/null +++ b/Gaudi/tests/scripts/test_cu_delayed_creation.py @@ -0,0 +1,32 @@ +from Gaudi.Configuration import * + +class DelayedInstance(ConfigurableUser): + __slots__ = { "Property": 0, + "Applied": False } + def __apply_configuration__(self): + print "Applying", self.getName() + print self + self.Applied = True + +class Application(ConfigurableUser): + __slots__ = { "Property": 10, + "Applied": False } + __used_configurables__ = [] + def __apply_configuration__(self): + print "Applying", self.getName() + print self + self.Applied = True + # This is instantiated late + DelayedInstance() + +# Instantiate the application configurable +Application() + +# apply all ConfigurableUser instances +from GaudiKernel.Configurable import applyConfigurableUsers +applyConfigurableUsers() + +assert Application().Applied +assert DelayedInstance().Applied + +print "Done." diff --git a/GaudiKernel/doc/release.notes b/GaudiKernel/doc/release.notes index 2c6047ab112b7b0d63816a5ee1bc514c076e4451..037d9a5863838fce66f0240b3a656e35e95a9c84 100644 --- a/GaudiKernel/doc/release.notes +++ b/GaudiKernel/doc/release.notes @@ -1,9 +1,12 @@ Package : GaudiKernel Package manager : Marco Clemencic +! 2014-02-06 - Marco Clemencic + - Fixed bug #103803: handle correctly ConfUsers created during the apply step. + ! 2014-02-03 - Marco Clemencic - Do not generate configurables for the factory aliases used for backward - compatibility with the Reflex Plugin Service. + compatibility with the Reflex Plugin Service. ! 2014-01-31 - Marco Clemencic - Replaced non-ASCII characters in string literals with the corresponding diff --git a/GaudiKernel/python/GaudiKernel/Configurable.py b/GaudiKernel/python/GaudiKernel/Configurable.py index 33a14d77067cc4e81591b2b3d555c0c170fb9a7a..ea5d90acea53f02ec4cde38f9ff761048c6430ab 100644 --- a/GaudiKernel/python/GaudiKernel/Configurable.py +++ b/GaudiKernel/python/GaudiKernel/Configurable.py @@ -909,6 +909,14 @@ class Configurable( object ): rep += Configurable._printFooter( indentStr, title ) return rep + def isApplicable(self): + ''' + Return True is the instance can be "applied". + Always False for plain Configurable instances + (i.e. not ConfigurableUser). + ''' + return False + ### classes for generic Gaudi component =========== class DummyDescriptor( object ): def __init__( self, name ): @@ -1112,7 +1120,8 @@ class ConfigurableAuditor( Configurable ): class ConfigurableUser( Configurable ): __slots__ = { "__users__": [], "__used_instances__": [], - "_enabled": True } + "_enabled": True, + "_applied": False } ## list of ConfigurableUser classes this one is going to modify in the # __apply_configuration__ method. # The list may contain class objects, strings representing class objects or @@ -1131,6 +1140,7 @@ class ConfigurableUser( Configurable ): setattr(self, n, v) self._enabled = _enabled self.__users__ = [] + self._applied = False # Needed to retrieve the actual class if the declaration in __used_configurables__ # and __queried_configurables__ is done with strings. @@ -1304,6 +1314,13 @@ class ConfigurableUser( Configurable ): return i raise KeyError(name) + def isApplicable(self): + ''' + Return True is the instance can be "applied". + ''' + return (not self.__users__) and self._enabled and not self._applied + + # list of callables to be called after all the __apply_configuration__ are called. postConfigActions = [] def appendPostConfigAction(function): @@ -1319,7 +1336,7 @@ def appendPostConfigAction(function): postConfigActions.append(function) def removePostConfigAction(function): """ - Remove a collable from the list of post-config actions. + Remove a callable from the list of post-config actions. The list is directly accessible as 'GaudiKernel.Configurable.postConfigActions'. """ postConfigActions.remove(function) @@ -1337,36 +1354,42 @@ def applyConfigurableUsers(): return _appliedConfigurableUsers_ = True - confUsers = [ c - for c in Configurable.allConfigurables.values() - if hasattr(c,"__apply_configuration__") ] - applied = True # needed to detect dependency loops - while applied and confUsers: - newConfUsers = [] # list of conf users that cannot be applied yet - applied = False - for c in confUsers: - if hasattr(c,"__users__") and c.__users__: - newConfUsers.append(c) # cannot use this one yet - else: # it does not have users or the list is empty - applied = True - # the ConfigurableUser is enabled if it doesn't have an _enabled - # property or its value is True - enabled = (not hasattr(c, "_enabled")) or c._enabled - if enabled: - log.info("applying configuration of %s", c.name()) - c.__apply_configuration__() - log.info(c) - else: - log.info("skipping configuration of %s", c.name()) - if hasattr(c, "__detach_used__"): - # tells the used configurables that they are not needed anymore - c.__detach_used__() - confUsers = newConfUsers # list of C.U.s still to go - if confUsers: - # this means that some C.U.s could not be applied because of a dependency loop - raise Error("Detected loop in the ConfigurableUser " + def applicableConfUsers(): + ''' + Generator returning all the configurables that can be applied in the + order in which they can be applied. + ''' + # This is tricky... + # We keep on looking for the first configurable that can be applied. + # When we cannot find any, 'next()' raises a StopIteration that is + # propagated outside of the infinite loop and the function, then handled + # correctly from outside (it is the exception that is raised when you + # exit from a generator). + # Checking every time the full list is inefficient, but it is the + # easiest way to fix bug #103803. + # <https://savannah.cern.ch/bugs/?103803> + while True: + yield (c for c in Configurable.allConfigurables.values() + if c.isApplicable()).next() + + for c in applicableConfUsers(): + log.info("applying configuration of %s", c.name()) + c.__apply_configuration__() + c._applied = True # flag the instance as already applied + log.info(c) + if hasattr(c, "__detach_used__"): + # tells the used configurables that they are not needed anymore + c.__detach_used__() + + # check for dependency loops + leftConfUsers = [c for c in Configurable.allConfigurables.values() + if hasattr(c, '__apply_configuration__') and + c._enabled and not c._applied] + # if an enabled configurable has not been applied, there must be a dependency loop + if leftConfUsers: + raise Error("Detected loop in the ConfigurableUser" " dependencies: %r" % [ c.name() - for c in confUsers ]) + for c in leftConfUsers ]) # ensure that all the Handles have been triggered known = set() unknown = set(Configurable.allConfigurables)