This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 47d38419fc [CI] fix floating test numbers (#8547)
47d38419fc is described below

commit 47d38419fccdcfc23625248608c95db23f1e816a
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Sep 23 11:34:22 2026 +0200

    [CI] fix floating test numbers (#8547)
    
    GuiRegistry can have the same items multiple times because of 
environment.init() this causes our ci numbers to float around
---
 .../apache/hop/core/gui/plugin/GuiRegistry.java    | 16 ++++-
 .../hop/core/gui/plugin/GuiRegistryTest.java       | 78 ++++++++++++++++++++++
 .../java/org/apache/hop/beam/gui/WelcomeBeam.java  |  2 +-
 .../hop/ui/testing/DisabledGuiWidgetsTestBase.java | 36 ++++++++++
 4 files changed, 129 insertions(+), 3 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java 
b/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
index 22b3f5504c..0c982ae6db 100644
--- a/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
+++ b/core/src/main/java/org/apache/hop/core/gui/plugin/GuiRegistry.java
@@ -372,7 +372,7 @@ public class GuiRegistry {
     // See if we need to disable something of if something is disabled 
already...
     // In those scenarios we ignore the GuiWidgetElement
     //
-    GuiElements existing = guiElements.findChild(guiElement.id());
+    GuiElements existing = guiElements.findChild(child.getId());
     if (existing != null && existing.isIgnored()) {
       return;
     }
@@ -380,6 +380,12 @@ public class GuiRegistry {
       existing.setIgnored(true);
       return;
     }
+    // Already registered: HopGuiEnvironment.init() can run more than once in 
the same JVM, and a
+    // second copy of the element would be built as a second widget with the 
same id.
+    //
+    if (existing != null) {
+      return;
+    }
 
     guiElements.getChildren().add(child);
   }
@@ -435,7 +441,7 @@ public class GuiRegistry {
     // See if we need to disable something of if something is disabled 
already...
     // In those scenarios we ignore the GuiWidgetElement
     //
-    GuiElements existing = guiElements.findChild(guiElement.id());
+    GuiElements existing = guiElements.findChild(child.getId());
     if (existing != null && existing.isIgnored()) {
       return;
     }
@@ -443,6 +449,12 @@ public class GuiRegistry {
       existing.setIgnored(true);
       return;
     }
+    // Already registered: HopGuiEnvironment.init() can run more than once in 
the same JVM, and a
+    // second copy of the element would be built as a second widget with the 
same id.
+    //
+    if (existing != null) {
+      return;
+    }
 
     guiElements.getChildren().add(child);
   }
diff --git 
a/core/src/test/java/org/apache/hop/core/gui/plugin/GuiRegistryTest.java 
b/core/src/test/java/org/apache/hop/core/gui/plugin/GuiRegistryTest.java
index 2275716b08..eb732a7809 100644
--- a/core/src/test/java/org/apache/hop/core/gui/plugin/GuiRegistryTest.java
+++ b/core/src/test/java/org/apache/hop/core/gui/plugin/GuiRegistryTest.java
@@ -19,7 +19,10 @@ package org.apache.hop.core.gui.plugin;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -53,4 +56,79 @@ class GuiRegistryTest {
     Object verifyObject1111 = registry.findGuiPluginObject("hop-gui-id1", 
"class1", "instance1");
     assertEquals(object1, verifyObject1111);
   }
+
+  @Test
+  void registeringAFieldWidgetTwiceKeepsOneElement() throws Exception {
+    String dataClassName = getClass().getName() + "#fieldTwice";
+    Field field = WidgetSample.class.getDeclaredField("name");
+    GuiWidgetElement element = field.getAnnotation(GuiWidgetElement.class);
+
+    registry.addGuiWidgetElement(dataClassName, element, field);
+    registry.addGuiWidgetElement(dataClassName, element, field);
+
+    GuiElements elements = registry.findGuiElements(dataClassName, 
WidgetSample.PARENT_ID);
+    assertEquals(1, elements.getChildren().size());
+  }
+
+  @Test
+  void registeringAWidgetWithoutAnExplicitIdTwiceKeepsOneElement() throws 
Exception {
+    // Without an id in the annotation the element takes the field name as its 
id.
+    String dataClassName = getClass().getName() + "#noIdTwice";
+    Field field = WidgetSample.class.getDeclaredField("sampleSize");
+    GuiWidgetElement element = field.getAnnotation(GuiWidgetElement.class);
+
+    registry.addGuiWidgetElement(dataClassName, element, field);
+    registry.addGuiWidgetElement(dataClassName, element, field);
+
+    GuiElements elements = registry.findGuiElements(dataClassName, 
WidgetSample.PARENT_ID);
+    assertEquals(1, elements.getChildren().size());
+    assertEquals("sampleSize", elements.getChildren().get(0).getId());
+  }
+
+  @Test
+  void registeringAMethodWidgetTwiceKeepsOneElement() throws Exception {
+    String dataClassName = getClass().getName() + "#methodTwice";
+    Method method = WidgetSample.class.getDeclaredMethod("browse");
+    GuiWidgetElement element = method.getAnnotation(GuiWidgetElement.class);
+    ClassLoader classLoader = getClass().getClassLoader();
+
+    registry.addGuiWidgetElement(element, method, dataClassName, classLoader);
+    registry.addGuiWidgetElement(element, method, dataClassName, classLoader);
+
+    GuiElements elements = registry.findGuiElements(dataClassName, 
WidgetSample.PARENT_ID);
+    assertEquals(1, elements.getChildren().size());
+  }
+
+  @Test
+  void anIgnoredDeclarationStillHidesARegisteredWidget() throws Exception {
+    String dataClassName = getClass().getName() + "#ignored";
+    Field field = WidgetSample.class.getDeclaredField("name");
+    Field ignoredField = WidgetSample.class.getDeclaredField("hiddenName");
+
+    registry.addGuiWidgetElement(dataClassName, 
field.getAnnotation(GuiWidgetElement.class), field);
+    registry.addGuiWidgetElement(
+        dataClassName, ignoredField.getAnnotation(GuiWidgetElement.class), 
ignoredField);
+
+    GuiElements elements = registry.findGuiElements(dataClassName, 
WidgetSample.PARENT_ID);
+    assertEquals(1, elements.getChildren().size());
+    assertTrue(elements.getChildren().get(0).isIgnored());
+  }
+
+  private static class WidgetSample {
+    static final String PARENT_ID = "GuiRegistryTest-parent";
+
+    @GuiWidgetElement(id = "name", type = GuiElementType.TEXT, parentId = 
PARENT_ID)
+    private String name;
+
+    @GuiWidgetElement(id = "name", type = GuiElementType.TEXT, parentId = 
PARENT_ID, ignored = true)
+    private String hiddenName;
+
+    @GuiWidgetElement(type = GuiElementType.TEXT, parentId = PARENT_ID)
+    private String sampleSize;
+
+    @GuiWidgetElement(id = "browse", type = GuiElementType.BUTTON, parentId = 
PARENT_ID)
+    void browse() {
+      // Only the annotation matters here.
+    }
+  }
 }
diff --git 
a/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/WelcomeBeam.java 
b/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/WelcomeBeam.java
index 502a0be4e8..1113baf292 100644
--- 
a/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/WelcomeBeam.java
+++ 
b/plugins/engines/beam/src/main/java/org/apache/hop/beam/gui/WelcomeBeam.java
@@ -109,7 +109,7 @@ public class WelcomeBeam {
   private static final String EXAMPLE2_FILE = 
"${PROJECT_HOME}/beam/pipelines/complex.hpl";
 
   @GuiWidgetElement(
-      id = "WelcomeBeam.11000.example1",
+      id = "WelcomeBeam.11010.example2",
       parentId = WELCOME_BEAM_PARENT_ID,
       type = GuiElementType.LINK,
       label =
diff --git 
a/ui/src/test/java/org/apache/hop/ui/testing/DisabledGuiWidgetsTestBase.java 
b/ui/src/test/java/org/apache/hop/ui/testing/DisabledGuiWidgetsTestBase.java
index 3ec7406d55..77d19b48d0 100644
--- a/ui/src/test/java/org/apache/hop/ui/testing/DisabledGuiWidgetsTestBase.java
+++ b/ui/src/test/java/org/apache/hop/ui/testing/DisabledGuiWidgetsTestBase.java
@@ -18,6 +18,7 @@
 package org.apache.hop.ui.testing;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.DynamicTest.dynamicTest;
 
 import java.lang.reflect.Method;
@@ -26,6 +27,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.hop.core.gui.plugin.GuiElements;
 import org.apache.hop.core.gui.plugin.GuiRegistry;
@@ -123,6 +125,40 @@ public abstract class DisabledGuiWidgetsTestBase extends 
SwtBotTestBase {
             + ", so the tests below would pass without testing anything");
   }
 
+  /**
+   * Every UI test class in a module shares one JVM and one registry, and 
several of them call
+   * {@link HopGuiEnvironment#init()}. A registry that appends a second copy 
of each widget on every
+   * init makes the composite cases below grow with the number of classes that 
happened to run
+   * first, and a composite built from it holds two widgets with the same id. 
Registering once more
+   * here makes that visible no matter in which order the classes run.
+   */
+  @Test
+  void registeringTheGuiPluginsAgainDoesNotDuplicateWidgets() throws Exception 
{
+    HopGuiEnvironment.init();
+
+    Map<Element, Long> occurrences =
+        compositeElements().stream()
+            .collect(Collectors.groupingBy(element -> element, 
Collectors.counting()));
+    List<String> duplicates =
+        occurrences.entrySet().stream()
+            .filter(entry -> entry.getValue() > 1)
+            .map(
+                entry ->
+                    entry.getKey().owner()
+                        + " "
+                        + entry.getKey().container()
+                        + " '"
+                        + entry.getKey().id()
+                        + "' x"
+                        + entry.getValue())
+            .sorted()
+            .toList();
+
+    assertTrue(
+        duplicates.isEmpty(),
+        "Widgets registered more than once in the GuiRegistry: " + duplicates);
+  }
+
   @TestFactory
   Stream<DynamicTest> disablingAWidgetMustNotBreakItsComposite() {
     return casesFor(compositeElements(), "composite", this::buildComposite);

Reply via email to