From 3a61baeeb4fe4ac6b6ba2950d9371e729442a50a Mon Sep 17 00:00:00 2001
From: Frank Winklmeier <frank.winklmeier@cern.ch>
Date: Wed, 5 Sep 2018 11:52:40 +0200
Subject: [PATCH] AppMgr.py: Fix bug in backwards-compatibility code

Fix a (probably) harmless bug in some of the backwards-compatibility
code in AppMgr. There was a double negation (`not not`) and a comparison
to the wrong configurable type (Service instead of AlgTool).

In addition made the code handle lists of tools correct and added a unit
test for ToolSvc.__iadd__
---
 Control/AthenaCommon/python/AppMgr.py         | 21 +++---
 .../AthenaCommon/share/AthAppMgrUnitTests.ref | 14 ++--
 .../AthenaCommon/test/AthAppMgrUnitTests.py   | 69 +++++++++++++------
 3 files changed, 68 insertions(+), 36 deletions(-)

diff --git a/Control/AthenaCommon/python/AppMgr.py b/Control/AthenaCommon/python/AppMgr.py
index 35389b0641f..73977078a52 100755
--- a/Control/AthenaCommon/python/AppMgr.py
+++ b/Control/AthenaCommon/python/AppMgr.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+# Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
 
 # File: AthenaCommon/share/AppMgr.py
 # Author: Wim Lavrijsen (WLavrijsen@lbl.gov)
@@ -68,14 +68,19 @@ def release_metadata():
 
 ### associator for public tools ----------------------------------------------
 def iadd( self, tool ):
- # only add once (allow silently)
-   if tool in self.getChildren():
-      return self
 
- # this is only allowed for new-style AlgTools
-   if not not isinstance( tool, Configurable.ConfigurableService ):
-      raise TypeError( '"%s" is not an AlgTool' %\
-         (hasattr(tool,'name') and tool.name() or str(value) ) )
+   if not type(tool) in (list,tuple):
+      tool = (tool,)
+
+ # only add once (allow silently)
+   tool = [t for t in tool if t not in self.getChildren()]
+   if len(tool)==0: return self
+
+ # this is only allowed for new-style AlgTool
+   for t in tool:
+      if not isinstance( t, Configurable.ConfigurableAlgTool ):
+         raise TypeError( '"%s" is not an AlgTool' %
+                          (hasattr(t,'name') and t.name() or "This configurable" ) )
 
    super( GaudiSvcConf.ToolSvc, self ).__iadd__( tool )
 
diff --git a/Control/AthenaCommon/share/AthAppMgrUnitTests.ref b/Control/AthenaCommon/share/AthAppMgrUnitTests.ref
index 2a669070d6a..decc61df1b3 100644
--- a/Control/AthenaCommon/share/AthAppMgrUnitTests.ref
+++ b/Control/AthenaCommon/share/AthAppMgrUnitTests.ref
@@ -1,19 +1,21 @@
-Mon Mar  5 12:38:02 EST 2018
-WARNING: TCMALLOCDIR not defined, will use libc malloc
+Wed Sep 12 13:56:37 CEST 2018
+Preloading tcmalloc_minimal.so
 Py:Athena            INFO including file "AthenaCommon/Preparation.py"
-Py:Athena            INFO using release [?-21.0.0] [i686-slc5-gcc43-dbg] [?/?] -- built on [?]
+Py:Athena            INFO using release [AthenaExternals-22.0.1] [x86_64-slc6-gcc62-opt] [2.0.10/22f26cd] -- built on [2018-08-27T2029]
 Py:Athena            INFO including file "AthenaCommon/Atlas.UnixStandardJob.py"
 Py:Athena            INFO executing ROOT6Setup
 Py:Athena            INFO including file "AthenaCommon/Execution.py"
 Py:Athena            INFO including file "AthenaCommon/AthAppMgrUnitTests.py"
 test1NamedSingleton (__main__.BasicAthAppMgrTestCase)
-Test that instances w/ same name are the same ... Py:Athena            INFO using release [?-21.0.0] [i686-slc5-gcc43-dbg] [?/?] -- built on [?]
+Test that instances w/ same name are the same ... Py:Athena            INFO using release [AthenaExternals-22.0.1] [x86_64-slc6-gcc62-opt] [2.0.10/22f26cd] -- built on [2018-08-27T2029]
 ok
 test2CppAppMgr (__main__.BasicAthAppMgrTestCase)
-Test communication b/w Py-proxy and C++ app mgr ... Py:Athena            INFO using release [?-21.0.0] [i686-slc5-gcc43-dbg] [?/?] -- built on [?]
+Test communication b/w Py-proxy and C++ app mgr ... Py:Athena            INFO using release [AthenaExternals-22.0.1] [x86_64-slc6-gcc62-opt] [2.0.10/22f26cd] -- built on [2018-08-27T2029]
 ok
+test3ToolSvc (__main__.BasicAthAppMgrTestCase)
+Test ToolSvc __iadd__ ... ok
 
 ----------------------------------------------------------------------
-Ran 2 tests in 5.315s
+Ran 3 tests in 7.230s
 
 OK
diff --git a/Control/AthenaCommon/test/AthAppMgrUnitTests.py b/Control/AthenaCommon/test/AthAppMgrUnitTests.py
index c5ead827e3c..89724590c60 100755
--- a/Control/AthenaCommon/test/AthAppMgrUnitTests.py
+++ b/Control/AthenaCommon/test/AthAppMgrUnitTests.py
@@ -1,14 +1,13 @@
 #!/usr/bin/env python
 
-# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration
+# Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration
 # Author: Sebastien Binet (binet@cern.ch)
 
 """Unit tests for verifying basic features of AthAppMgr."""
 
-import unittest, sys
+import unittest
 
 from AthenaCommon.AppMgr import AthAppMgr
-from AthenaCommon.Logging import log as msg
 
 ### data ---------------------------------------------------------------------
 __version__ = '$Revision: 1.1 $'
@@ -22,7 +21,6 @@ import sys
 import os
 
 ### helper class to sanitize output-------------------------------------------
-import re
 from tempfile import NamedTemporaryFile
 class ShutUp(object):
     """
@@ -54,8 +52,8 @@ class BasicAthAppMgrTestCase( unittest.TestCase ):
 
         app1 = AthAppMgr( "MyApp" )
         app2 = AthAppMgr( "MyApp" )
-        self.failUnless( app1 == app2, "instances are not equal !" )
-        self.failUnless( app1 is app2, "instances are not identical !" )
+        self.assertTrue( app1 == app2, "instances are not equal !" )
+        self.assertTrue( app1 is app2, "instances are not identical !" )
 
     def test2CppAppMgr( self ):
         """Test communication b/w Py-proxy and C++ app mgr"""
@@ -63,49 +61,49 @@ class BasicAthAppMgrTestCase( unittest.TestCase ):
         createSvc = app.CreateSvc[:]
         dlls = app.Dlls[:]
         app.CreateSvc += [ "StoreGateSvc/abcde" ]
-        self.failUnless( app.CreateSvc == createSvc + [ "StoreGateSvc/abcde" ] )
+        self.assertTrue( app.CreateSvc == createSvc + [ "StoreGateSvc/abcde" ] )
 
         app.Dlls += [ "NothingThereLikeSuch" ]
-        self.failUnless( app.Dlls    == []   )
-        self.failUnless( app._cppApp == None )
+        self.assertTrue( app.Dlls    == []   )
+        self.assertTrue( app._cppApp is None )
         
         Cout.instance.mute()
         ## instantiates the C++ application mgr (un-configured)
         cppApp = app.getHandle()
         Cout.instance.unMute()
 
-        self.failUnless( app.Dlls    == ["AthenaServices"]   )
-        self.failUnless( app._cppApp != None )
+        self.assertTrue( app.Dlls    == ["AthenaServices"]   )
+        self.assertTrue( app._cppApp is not None )
 
         ## now Dlls calls should be re-directed to C++ app
         Cout.instance.mute()
         app.Dlls += [ "AthenaKernel" ]
         Cout.instance.unMute()
-        self.failUnless( set(app.Dlls[:]) ==
+        self.assertTrue( set(app.Dlls[:]) ==
                          set(["AthenaServices","AthenaKernel"]) )
         
-        self.failUnless( cppApp.CreateSvc == [] )
+        self.assertTrue( cppApp.CreateSvc == [] )
 
         app.CreateSvc += [ "StoreGateSvc/svcA" ]
-        self.failUnless( cppApp.CreateSvc == [ "StoreGateSvc/svcA" ] )
+        self.assertTrue( cppApp.CreateSvc == [ "StoreGateSvc/svcA" ] )
 
         ## configure the application (not just the app mgr)
         app.setup()
-        self.failUnless( cppApp.CreateSvc == app.CreateSvc )
+        self.assertTrue( cppApp.CreateSvc == app.CreateSvc )
 
         # really want to test that ?
-        self.failUnless( cppApp.CreateSvc[0] == "ToolSvc/ToolSvc" )
+        self.assertTrue( cppApp.CreateSvc[0] == "ToolSvc/ToolSvc" )
 
         app.CreateSvc += [ "StoreGateSvc/svcB" ]
-        self.failUnless( cppApp.CreateSvc == app.CreateSvc )
+        self.assertTrue( cppApp.CreateSvc == app.CreateSvc )
 
         app.CreateSvc = [ "StoreGateSvc/svcC" ]
-        self.failUnless( app.CreateSvc    == [ "StoreGateSvc/svcC" ] )
-        self.failUnless( cppApp.CreateSvc == [ "StoreGateSvc/svcC" ] )
+        self.assertTrue( app.CreateSvc    == [ "StoreGateSvc/svcC" ] )
+        self.assertTrue( cppApp.CreateSvc == [ "StoreGateSvc/svcC" ] )
         
         ## test all properties are synchronized b/w py-proxy & C++
         for p in AthAppMgr.__slots__:
-            self.failUnless( getattr(cppApp,p) == getattr(app,p),
+            self.assertTrue( getattr(cppApp,p) == getattr(app,p),
                              "Property [%s] DIFFERS !!" % str(p) )
 
         ## test that multiple calls to setup() are stable
@@ -117,10 +115,37 @@ class BasicAthAppMgrTestCase( unittest.TestCase ):
             Cout.instance.mute()
             app.setup()
             Cout.instance.unMute()
-            self.failUnless( createSvc == app.CreateSvc[:] )
+            self.assertTrue( createSvc == app.CreateSvc[:] )
             pass
         return
-        
+
+    def test3ToolSvc( self ):
+        """Test ToolSvc __iadd__"""
+        from AthenaCommon.AppMgr import ToolSvc
+        from AthenaCommon.Configurable import ConfigurableAlgTool, ConfigurableService
+
+        t1 = ConfigurableAlgTool('tool1')
+        t2 = ConfigurableAlgTool('tool2')
+        s1 = ConfigurableService('svc1')
+
+        ## Check silent adding of duplicate
+        ToolSvc += t1
+        self.assertEqual( ToolSvc.getChildren(), [t1] )
+        ToolSvc += t1
+        self.assertEqual( ToolSvc.getChildren(), [t1] )
+        ToolSvc += [t1,t2]
+        self.assertEqual( ToolSvc.getChildren(), [t1,t2] )
+
+        ## Check type checking
+        with self.assertRaises(TypeError):
+            ToolSvc += s1
+
+        with self.assertRaises(TypeError):
+            ToolSvc += [s1]
+
+        return
+
+
 ## actual test run
 if __name__ == '__main__':
    loader = unittest.TestLoader()
-- 
GitLab