ibessonov commented on code in PR #6241:
URL: https://github.com/apache/ignite-3/pull/6241#discussion_r2204341140


##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigNode.java:
##########
@@ -250,16 +281,18 @@ String path() {
     public void accept(ConfigShuttle visitor) {
         visitor.visit(this);
 
-        for (ConfigNode child : childNodes()) {
-            child.accept(visitor);
+        for (NodeReference ref : childNodes()) {
+            for (var node : ref.nodes()) {

Review Comment:
   Please don't use `var` in this context, it violates coding guidelines



##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigNode.java:
##########
@@ -168,18 +176,26 @@ public Collection<ConfigNode> childNodes() {
     /**
      * Add the child nodes to this node.
      */
-    void addChildNodes(Collection<ConfigNode> childNodes) {
+    void addChildReferences(Collection<NodeReference> childNodes) {
         assert !flags.contains(Flags.IS_VALUE) : "Value node can't have 
children.";
 
-        childNodes.forEach(e -> childNodeMap.put(e.name(), e));
+        childReferences.addAll(childNodes);
+    }
+
+    /**
+     * Add the child nodes to this node.
+     */
+    @TestOnly

Review Comment:
   This whole class is test-only, what do we mean by this annotation? Should 
this class be in test fixtures instead in such a case? Just asking



##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigNode.java:
##########
@@ -329,5 +362,31 @@ static class Attributes {
         static String NAME = "name";
         static String KIND = "kind";
         static String CLASS = "class";
+        static String INSTANCE_TYPE = "instanceType";
+    }
+
+    /**
+     * Child node reference.
+     */

Review Comment:
   This comment doesn't really explain anything, can you please expand it?



##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigurationTreeComparatorSelfTest.java:
##########
@@ -406,10 +407,8 @@ void testInCompatibleRename() {
     }
 
     /**
-     * Test scenario. <br>
-     * config ver1 has property : prop1 <br>
-     * config ver2 has renamed property : prop1 -> prop2 <br>
-     * config ver3 has deleted property : prop1, prop2 <br>
+     * Test scenario. <br> config ver1 has property : prop1 <br> config ver2 
has renamed property : prop1 -> prop2 <br> config ver3 has
+     * deleted property : prop1, prop2 <br>

Review Comment:
   Why did you reformat it? It looks bad now



##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigurationTreeScanner.java:
##########
@@ -94,21 +94,47 @@ public static void scan(ConfigNode currentNode, Class<?> 
schemaClass, ScanContex
             return;
         }
 
-        List<ConfigNode> children = new ArrayList<>();
+        List<NodeReference> children = new ArrayList<>();
+
         configurationClasses(schemaClass).stream()
                 .flatMap(c -> Arrays.stream(c.getDeclaredFields()))
                 .filter(field -> !Modifier.isStatic(field.getModifiers()))
                 .sorted(Comparator.comparing(Field::getName)) // Sort for test 
stability.
                 .forEach(field -> {
-                    ConfigNode node = createNodeForField(currentNode, field);
+                    Set<Class<?>> instanceClasses = 
context.getPolymorphicInstances(field.getType());
+                    List<ConfigNode> fieldNodes = new 
ArrayList<>(instanceClasses.size() + 1);
+
+                    // Field itself
+                    ConfigNode node = createNodeForField(currentNode, field, 
field.getType());
+                    fieldNodes.add(node);
 
-                    children.add(node);
                     if (!node.isValue()) {
                         scan(node, field.getType(), context);
                     }
+
+                    // Collect nodes that correspond to polymorphic instances
+                    if (!instanceClasses.isEmpty()) {
+                        List<Class<?>> instances = new 
ArrayList<>(instanceClasses);
+                        // Sort classes to make processing stable
+                        instances.sort(Comparator.comparing(Class::getName));
+
+                        for (Class<?> instanceClass : instances) {
+                            ConfigNode instanceTypeNode = 
createNodeForField(currentNode, field, instanceClass);
+                            fieldNodes.add(instanceTypeNode);
+
+                            if (!instanceTypeNode.isValue()) {
+                                scan(instanceTypeNode, instanceClass, context);
+                            }
+                        }
+
+                        // Sort subclasses to make data stable.
+                        
fieldNodes.sort(Comparator.comparing(ConfigNode::className));

Review Comment:
   Classes can be renamed, and the rename should not affect configuration 
compatibility. What exactly do we mean by stability?



##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigurationTreeComparator.java:
##########
@@ -136,12 +207,13 @@ private static List<ConfigNode> getPath(ConfigNode node) {
     /**
      * Returns {@code true} if given node is compatible with candidate node, 
{@code false} otherwise.
      */
-    private static boolean match(ConfigNode node, ConfigNode candidate) {
+    private static boolean match(ConfigNode node, ConfigNode candidate, 
@Nullable String instanceType) {
         return Objects.equals(candidate.kind(), node.kind())
                 && matchNames(candidate, node)
                 && validateFlags(candidate, node)
                 && 
candidate.deletedPrefixes().containsAll(node.deletedPrefixes())
                 && (!node.isValue() || Objects.equals(candidate.type(), 
node.type())) // Value node types can be changed.
+                && (instanceType == null || 
Objects.equals(candidate.instanceType(), node.instanceType()))

Review Comment:
   Could you please explain the meaning behind `instanceType == null` check? 
Can we simply remove it?
   
   I believe there's one situation that will be difficult to cover with this 
framework. Let's think about it in the future:
   - we have a non-polymorphic schema in 3.a
   - we make it polymorphic in 3.b, converting on of existing string properties 
into a polymorphic type ID
   - we add another polymorphic implementation
   
   I believe such thing already happened during development (I don't remember 
if it was before or after the release) - it's node finder configuration. Let's 
discuss it later



##########
modules/runner/src/test/java/org/apache/ignite/internal/configuration/compatibility/framework/ConfigurationTreeComparatorSelfTest.java:
##########
@@ -465,10 +464,137 @@ public Collection<String> deletedPrefixes() {
         assertCompatible(metadataVer2, metadataVer3, new 
ComparisonContext(allModules));
     }
 
+    @Test
+    public void testPolymorphicConfigNoChanges() {
+        ConfigNode root = createRoot("root");
+
+        ConfigNode base = createNode(root, "subconfig", "ClassBase");
+        base.addChildNodes(createChild(base, "f1"));
+
+        ConfigNode subclassA = createInstanceNode(root, "subconfig", "ClassA", 
"A");
+        subclassA.addChildNodes(createChild(subclassA, "f2"));
+
+        ConfigNode subclassB = createInstanceNode(root, "subconfig", "ClassB", 
"B");
+        subclassB.addChildNodes(createChild(subclassB, "f3"));
+
+        root.addChildReferences(List.of(new NodeReference(List.of(base, 
subclassA, subclassB))));
+
+        assertCompatible(List.of(root), List.of(root));
+    }
+
+    @Test
+    public void testPolymorphicConfigAddInstance() {
+        ConfigNode root1 = createRoot("root");
+        {
+            ConfigNode base = createNode(root1, "subconfig", "ClassBase");
+            base.addChildNodes(createChild(base, "f1"));
+
+            ConfigNode subclassA = createInstanceNode(root1, "subconfig", 
"ClassA", "A");
+            subclassA.addChildNodes(createChild(subclassA, "f2"));
+
+            root1.addChildReferences(List.of(new NodeReference(List.of(base, 
subclassA))));
+        }
+
+        ConfigNode root2 = createRoot("root");
+        {
+            ConfigNode base = createNode(root2, "subconfig", "ClassBase");
+            base.addChildNodes(createChild(base, "f1"));
+
+            ConfigNode subclassA = createInstanceNode(root2, "subconfig", 
"ClassA", "A");
+            subclassA.addChildNodes(createChild(subclassA, "f2"));
+
+            ConfigNode subclassB = createInstanceNode(root2, "subconfig", 
"ClassB", "B");
+            subclassB.addChildNodes(createChild(subclassB, "f3"));
+
+            root2.addChildReferences(List.of(new NodeReference(List.of(base, 
subclassA, subclassB))));
+        }
+
+        assertCompatible(List.of(root1), List.of(root2));
+        // Removing should be an incompatible change.
+        assertIncompatible(List.of(root2), List.of(root1));
+    }
+
+    @Test
+    public void testPolymorphicConfigChangeType() {
+        ConfigNode root1 = createRoot("root");
+        {
+            ConfigNode base = createNode(root1, "subconfig", "ClassBase");
+            base.addChildNodes(createChild(base, "f1"));
+
+            ConfigNode subclassA = createInstanceNode(root1, "subconfig", 
"ClassA", "A");
+            subclassA.addChildNodes(createChild(subclassA, "f2"));
+
+            root1.addChildReferences(List.of(new NodeReference(List.of(base, 
subclassA))));
+        }
+
+        ConfigNode root2 = createRoot("root");
+        {
+            ConfigNode base = createNode(root2, "subconfig", "ClassBase");
+            base.addChildNodes(createChild(base, "f1"));
+
+            ConfigNode subclassA = createInstanceNode(root2, "subconfig", 
"ClassA", "B");
+            subclassA.addChildNodes(createChild(subclassA, "f2"));
+
+            root2.addChildReferences(List.of(new NodeReference(List.of(base, 
subclassA))));
+        }
+
+        // Changing instance type is always an incompatible change 
+        assertIncompatible(List.of(root1), List.of(root2));
+        assertIncompatible(List.of(root2), List.of(root1));
+    }
+
+    @Test
+    public void testPolymorphicConfigAddInstanceField() {

Review Comment:
   Let's add one more test:
   - you have polymorphic instances with the same field declared in all of 
them, for example `int size`
   - you decided to more this field into their base class
   - this does not violate anything
   
   The opposite must also be true



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@ignite.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to