From 6746229c0710f7f8f1648e62f3daf20c52d88d15 Mon Sep 17 00:00:00 2001
From: Arnaud Fontaine <arnaud.fontaine@nexedi.com>
Date: Wed, 10 Jun 2020 18:44:26 +0900
Subject: [PATCH] ZODB Components: Components not actually loaded were never
 cleared of astroid AST module cache.

This fixes testDynamicClassGeneration failure since the migration of
IMovement (e8e5f273) which uncovered this issue.
---
 product/ERP5Type/Tool/ComponentTool.py        | 14 +++++++
 product/ERP5Type/dynamic/component_package.py |  8 ----
 .../tests/testDynamicClassGeneration.py       | 40 ++++++++++++++-----
 3 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/product/ERP5Type/Tool/ComponentTool.py b/product/ERP5Type/Tool/ComponentTool.py
index ad25395e6f..2a4c6070a2 100644
--- a/product/ERP5Type/Tool/ComponentTool.py
+++ b/product/ERP5Type/Tool/ComponentTool.py
@@ -147,11 +147,25 @@ class ComponentTool(BaseTool):
     import erp5.component
     from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage
     with aq_method_lock:
+      component_package_list = []
       for package in erp5.component.__dict__.itervalues():
         if isinstance(package, ComponentDynamicPackage):
           package.reset()
+          component_package_list.append(package.__name__)
 
       erp5.component.ref_manager.gc()
+
+      # Clear pylint cache
+      try:
+        from astroid.builder import MANAGER
+      except ImportError:
+        pass
+      else:
+        astroid_cache = MANAGER.astroid_cache
+        for k in astroid_cache.keys():
+          if k.startswith('erp5.component.') and k not in component_package_list:
+            del astroid_cache[k]
+
     if reset_portal_type_at_transaction_boundary:
       portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary()
     else:
diff --git a/product/ERP5Type/dynamic/component_package.py b/product/ERP5Type/dynamic/component_package.py
index d9b6e54e8d..9352c71181 100644
--- a/product/ERP5Type/dynamic/component_package.py
+++ b/product/ERP5Type/dynamic/component_package.py
@@ -429,14 +429,6 @@ class ComponentDynamicPackage(ModuleType):
 
       delattr(package, name)
 
-      # Clear pylint cache
-      try:
-        from astroid.builder import MANAGER
-      except ImportError:
-        pass
-      else:
-        MANAGER.astroid_cache.pop(module_name, None)
-
 class ToolComponentDynamicPackage(ComponentDynamicPackage):
   def reset(self, *args, **kw):
     """
diff --git a/product/ERP5Type/tests/testDynamicClassGeneration.py b/product/ERP5Type/tests/testDynamicClassGeneration.py
index 224c21beb1..51fd3ef6c8 100644
--- a/product/ERP5Type/tests/testDynamicClassGeneration.py
+++ b/product/ERP5Type/tests/testDynamicClassGeneration.py
@@ -2242,7 +2242,15 @@ from Shared.DC.ZRDB.Results import Results # pylint: disable=unused-import
             module2=imported_module2,
             module2_with_version=imported_module2_with_version)) +
       component.getTextContent())
-    self.tic()
+
+    self._assertAstroidCacheContent(
+      must_be_in_cache_set={'%s' % namespace},
+      must_not_be_in_cache_set={'%s.erp5_version' % namespace,
+                                imported_module1,
+                                imported_module1_with_version,
+                                imported_module2,
+                                imported_module2_with_version})
+    component.checkSourceCode()
     self._assertAstroidCacheContent(
       must_be_in_cache_set={'%s' % namespace,
                             '%s.erp5_version' % namespace},
@@ -2250,6 +2258,8 @@ from Shared.DC.ZRDB.Results import Results # pylint: disable=unused-import
                                 imported_module1_with_version,
                                 imported_module2,
                                 imported_module2_with_version})
+
+    self.tic()
     self.assertEqual(component.getValidationState(), 'modified')
     self.assertEqual(
       component.getTextContentErrorMessageList(),
@@ -2288,9 +2298,8 @@ from Shared.DC.ZRDB.Results import Results # pylint: disable=unused-import
     self.assertEqual(imported_component1.getValidationState(), 'validated')
     self.assertEqual(imported_component2.getValidationState(), 'validated')
 
-    # 2) Then modify the main one so that it automatically 'validate'
-    component.setTextContent(component.getTextContent() + '\n')
-    self.tic()
+    message_list = component.checkSourceCode()
+    self.assertEqual(message_list, [])
     self._assertAstroidCacheContent(
       must_be_in_cache_set={'%s' % namespace,
                             '%s.erp5_version' % namespace,
@@ -2299,6 +2308,17 @@ from Shared.DC.ZRDB.Results import Results # pylint: disable=unused-import
                             imported_module2,
                             imported_module2_with_version},
       must_not_be_in_cache_set=set())
+
+    # 2) Then modify the main one so that it automatically 'validate'
+    component.setTextContent(component.getTextContent() + '\n')
+    self.tic()
+    self._assertAstroidCacheContent(
+      must_be_in_cache_set={'%s' % namespace},
+      must_not_be_in_cache_set={'%s.erp5_version' % namespace,
+                                imported_module1,
+                                imported_module1_with_version,
+                                imported_module2,
+                                imported_module2_with_version})
     self.assertEqual(component.getValidationState(), 'validated')
     self.assertEqual(component.getTextContentErrorMessageList(), [])
     self.assertEqual(component.getTextContentWarningMessageList(), [])
@@ -2363,12 +2383,8 @@ from %(namespace)s.erp5_version import %(reference)s
 %(reference)s.hoge()
 """ % dict(namespace=self._document_class._getDynamicModuleNamespace(),
            reference=imported_reference))
-      self.portal.portal_workflow.doActionFor(component, 'validate_action')
-      self.tic()
-      self.assertEqual(component.getValidationState(), 'validated')
-      self.assertEqual(component.getTextContentErrorMessageList(), [])
-      self.assertEqual(component.getTextContentWarningMessageList(), [])
 
+      component.checkSourceCode()
       from astroid.builder import MANAGER
       imported_module = self._getComponentFullModuleName(imported_reference)
       self.assertEqual(
@@ -2378,6 +2394,12 @@ from %(namespace)s.erp5_version import %(reference)s
         MANAGER.astroid_cache[self._getComponentFullModuleName(imported_reference, version='erp5')],
         MANAGER.astroid_cache[imported_module])
 
+      self.portal.portal_workflow.doActionFor(component, 'validate_action')
+      self.tic()
+      self.assertEqual(component.getValidationState(), 'validated')
+      self.assertEqual(component.getTextContentErrorMessageList(), [])
+      self.assertEqual(component.getTextContentWarningMessageList(), [])
+
     finally:
       self.portal.setVersionPriorityList(priority_tuple)
       self.tic()
-- 
2.30.9