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

mattcasters 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 19e86590aa Issue #8752 : Clear the Hop namespace when no project is 
active (#8753)
19e86590aa is described below

commit 19e86590aa9c9083ff1cff674a87a639b40665f7
Author: Matt Casters <[email protected]>
AuthorDate: Sun Oct 4 18:15:40 2026 +0200

    Issue #8752 : Clear the Hop namespace when no project is active (#8753)
    
    A null or blank namespace means this display has no project. The display 
map cannot store null, which crashed the daily build after a UI test left a 
display on the Surefire thread.
---
 .../org/apache/hop/ui/core/gui/HopNamespace.java   |   9 +-
 .../hop/ui/core/gui/HopNamespaceDisplayTest.java   | 113 +++++++++++++++++++++
 2 files changed, 121 insertions(+), 1 deletion(-)

diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/HopNamespace.java 
b/ui/src/main/java/org/apache/hop/ui/core/gui/HopNamespace.java
index 501b616e16..8e4c75065d 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/HopNamespace.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/HopNamespace.java
@@ -105,12 +105,19 @@ public class HopNamespace {
     return NAMESPACE_BY_DISPLAY.get(display);
   }
 
-  /** Remember the namespace for the session on this thread. Only call with a 
user interface. */
+  /**
+   * Remember the namespace for the session on this thread. Only call with a 
user interface. A null
+   * or blank value clears it: that is "no project", and the map cannot store 
null.
+   */
   private static void rememberForCurrentDisplay(String namespace) {
     Display display = SessionDisplay.current();
     if (display == null || display.isDisposed()) {
       return;
     }
+    if (Utils.isEmpty(namespace)) {
+      NAMESPACE_BY_DISPLAY.remove(display);
+      return;
+    }
     if (!NAMESPACE_BY_DISPLAY.containsKey(display)) {
       display.addListener(SWT.Dispose, e -> 
NAMESPACE_BY_DISPLAY.remove(display));
     }
diff --git 
a/ui/src/test/java/org/apache/hop/ui/core/gui/HopNamespaceDisplayTest.java 
b/ui/src/test/java/org/apache/hop/ui/core/gui/HopNamespaceDisplayTest.java
new file mode 100644
index 0000000000..2460053426
--- /dev/null
+++ b/ui/src/test/java/org/apache/hop/ui/core/gui/HopNamespaceDisplayTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.core.gui;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assumptions.abort;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+
+import java.awt.GraphicsEnvironment;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.exception.HopRuntimeException;
+import org.apache.hop.ui.hopgui.SessionDisplay;
+import org.eclipse.swt.widgets.Display;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * A null namespace has to clear the display as well as the process. The daily 
build runs UI tests
+ * in the same Surefire fork, and HopGui leaves that display and {@code 
HOP_PLATFORM_RUNTIME=GUI}
+ * behind. Storing null in the display map crashes the build (issue #8752).
+ */
+@Tag("uitest")
+class HopNamespaceDisplayTest {
+
+  private String originalRuntime;
+  private String previousNamespace;
+  private Display createdDisplay;
+  private boolean runtimeChanged;
+
+  @BeforeEach
+  void rememberRuntime() {
+    originalRuntime = System.getProperty(Const.HOP_PLATFORM_RUNTIME);
+    assumeFalse(
+        GraphicsEnvironment.isHeadless(),
+        "No display available (headless); skipping the namespace display 
test.");
+    System.setProperty(Const.HOP_PLATFORM_RUNTIME, "GUI");
+    runtimeChanged = true;
+    try {
+      previousNamespace = HopNamespace.getNamespace();
+    } catch (RuntimeException e) {
+      previousNamespace = null;
+    }
+  }
+
+  @AfterEach
+  void restoreRuntime() {
+    if (!runtimeChanged) {
+      return;
+    }
+    if (createdDisplay != null && !createdDisplay.isDisposed()) {
+      createdDisplay.dispose();
+      createdDisplay = null;
+    }
+    try {
+      HopNamespace.setNamespace(previousNamespace);
+    } catch (RuntimeException e) {
+      // The display is gone, or there was nothing to restore.
+    }
+    if (originalRuntime == null) {
+      System.clearProperty(Const.HOP_PLATFORM_RUNTIME);
+    } else {
+      System.setProperty(Const.HOP_PLATFORM_RUNTIME, originalRuntime);
+    }
+  }
+
+  @Test
+  void clearingTheNamespaceForgetsTheDisplayAndTheProcess() {
+    ensureDisplay();
+
+    HopNamespace.setNamespace("display-project");
+    assertEquals("display-project", HopNamespace.getNamespace());
+
+    HopNamespace.setNamespace(null);
+    assertThrows(HopRuntimeException.class, HopNamespace::getNamespace);
+
+    HopNamespace.setNamespace("");
+    assertThrows(HopRuntimeException.class, HopNamespace::getNamespace);
+
+    HopNamespace.setNamespace("display-project-again");
+    assertEquals("display-project-again", HopNamespace.getNamespace());
+  }
+
+  /** The display of this thread, created when the UI tests have not already 
left one behind. */
+  private void ensureDisplay() {
+    Display display = SessionDisplay.current();
+    if (display != null && !display.isDisposed()) {
+      return;
+    }
+    try {
+      createdDisplay = new Display();
+    } catch (Throwable t) {
+      abort("SWT display is not available: " + t.getMessage());
+    }
+  }
+}

Reply via email to