From 590b1e304e9906a9e64ccea6a1fe10a733471c40 Mon Sep 17 00:00:00 2001 From: krystynaShatkovska Date: Tue, 13 Oct 2026 13:12:13 +0200 Subject: [PATCH 1/2] #2589: Inject an application-level IdeContext into NlsService from the GUI The GUI previously constructed NlsService with a null context, which made NlsService fall back to lazily creating its own IdeContextConsole at persistence time -- a service reaching into the environment to build its own dependency. Inject an application-level IdeContextConsole bound to the GUI's start context from GuiStateManager, and remove the now-unused NlsService(Locale) constructor and the fallback creation. NlsService now always receives its context via dependency injection. This also fixes a latent issue: the old fallback built its own start context and discarded the GUI logging context. Locale handling and IDE_OPTIONS isolation behavior are unchanged. --- .../ide/gui/core/context/GuiStateManager.java | 12 ++++---- .../ide/gui/core/service/NlsService.java | 28 ++++--------------- .../ide/gui/core/service/NlsServiceTest.java | 2 +- 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java b/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java index 0ba75876d7..c8c18a494e 100644 --- a/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java +++ b/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java @@ -18,6 +18,7 @@ import com.devonfw.ide.gui.core.mainwindow.console.ConsoleController; import com.devonfw.ide.gui.core.service.CommandletService; import com.devonfw.ide.gui.core.service.NlsService; +import com.devonfw.tools.ide.context.IdeContextConsole; import com.devonfw.tools.ide.context.IdeStartContextImpl; import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.log.IdeLogListenerBuffer; @@ -69,17 +70,18 @@ public GuiStateManager(String ideRoot) { this.ideRootDir = Path.of(ideRoot != null ? ideRoot : System.getenv(IdeVariables.IDE_ROOT.getName())); //This specific ordering needs to be kept + final IdeLogListenerBuffer buffer = new IdeLogListenerBuffer(); + IdeLogLevel logLevel = IdeLogLevel.DEBUG; + startContext = new IdeStartContextImpl(logLevel, buffer); + this.projectManager = new ProjectManager(ideRootDir); this.taskManager = new TaskManager(); this.eventBus = new GuiEventBus(); - this.nlsService = new NlsService(null); + // inject an application-level context so NlsService never has to create one itself; binding it to startContext keeps the GUI logging context + this.nlsService = new NlsService(new IdeContextConsole(startContext), null); this.consoleController = new ConsoleController(this); this.commandletService = new CommandletService(this); this.tabFactory = new TabFactory(this); - - final IdeLogListenerBuffer buffer = new IdeLogListenerBuffer(); - IdeLogLevel logLevel = IdeLogLevel.DEBUG; - startContext = new IdeStartContextImpl(logLevel, buffer); } /** diff --git a/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java b/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java index 227e285242..a11b7d22b1 100644 --- a/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java +++ b/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java @@ -23,7 +23,6 @@ import org.slf4j.LoggerFactory; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.context.IdeContextConsole; import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.environment.EnvironmentVariablesType; @@ -59,32 +58,17 @@ public class NlsService { private volatile EnvironmentVariables userEnvironmentVariables; - /** - * The {@link IdeContext} used to resolve the user environment variables. If {@code null}, an {@link IdeContextConsole} is created lazily on first - * persistence, so that merely constructing the service has no side effects. - */ + /** The {@link IdeContext} used to resolve the user environment variables. */ private final IdeContext context; private static final Pattern USER_LANG_PATTERN = Pattern.compile("-Duser\\.language=(\\S*)"); - /** - * Creates the service and initializes it with the given locale. The user environment variables are resolved from an {@link IdeContextConsole} that is - * created lazily on first persistence, so that merely constructing the service has no side effects. - * - * @param locale the preferred locale, or {@code null} to use persisted or default locale. - */ - public NlsService(Locale locale) { - - this.context = null; - initialize(locale); - } - /** * Creates the service using the given {@link IdeContext} to resolve the user configuration and initializes it with the given locale. *

- * This constructor enables "design for testability" (see {@code documentation/contributing/junit-testing.adoc}): by injecting an {@link IdeContext} (for - * example an {@link com.devonfw.tools.ide.context.IdeTestContext}) the service can be isolated from the real user home and the environment of the - * machine running the test. + * The {@link IdeContext} is always injected by the caller (see the {@code documentation/contributing/junit-testing.adoc} guidance): production code + * passes an application-level context that resolves the real user configuration, while tests pass an isolated {@code IdeTestContext} to stay independent + * from the machine's user home and the {@code IDE_OPTIONS} environment variable. * * @param context the {@link IdeContext} to use for resolving the user environment variables. * @param locale the preferred locale, or {@code null} to use persisted or default locale. @@ -388,9 +372,7 @@ private EnvironmentVariables getUserEnvironmentVariables() { private EnvironmentVariables createUserEnvironmentVariables() { - // if no context was injected (see NlsService(Locale)) we create a console context lazily here, so constructing the service has no side effects - IdeContext context = (this.context != null) ? this.context : new IdeContextConsole(); - EnvironmentVariables userVariables = context.getVariables().getByType(EnvironmentVariablesType.USER); + EnvironmentVariables userVariables = this.context.getVariables().getByType(EnvironmentVariablesType.USER); if (userVariables == null) { throw new IllegalStateException("Failed to resolve USER environment variables from IDE context"); } diff --git a/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java b/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java index 3385dcdf78..83fa1ec6dc 100644 --- a/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java +++ b/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java @@ -87,7 +87,7 @@ private NlsService newService(Locale locale) { @MethodSource("testGetInstanceWithLocaleProvider") public void testGetInstanceWithLocale(Locale systemLocale, Locale providedLocale, Locale expectedServiceLocale) { Locale.setDefault(systemLocale); - NlsService service = new NlsService(providedLocale); + NlsService service = newService(providedLocale); assertThat(service.getLocale()).isEqualTo(expectedServiceLocale); assertThat(service.getResourceBundle()).isNotNull(); From 2ab3ff9331189941234e8c72be8b87da44b4bee4 Mon Sep 17 00:00:00 2001 From: krystynaShatkovska Date: Thu, 15 Oct 2026 11:01:32 +0200 Subject: [PATCH 2/2] #2589: isolate AppBaseTest's NlsService from the real user home Address review feedback: GuiStateManager now offers an (ideRoot, IdeContext) overload; the single-arg constructor delegates and derives the context from the GUI start context. AppBaseTest injects an isolated IdeTestContext (stub System, throw-away user home) so persisting the locale no longer rewrites the developer's real ~/.ide/ide.properties. --- .../ide/gui/core/context/GuiStateManager.java | 22 +++++++++++++++++-- .../java/com/devonfw/ide/gui/AppBaseTest.java | 9 +++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java b/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java index c8c18a494e..59bbb7fd7e 100644 --- a/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java +++ b/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java @@ -18,6 +18,7 @@ import com.devonfw.ide.gui.core.mainwindow.console.ConsoleController; import com.devonfw.ide.gui.core.service.CommandletService; import com.devonfw.ide.gui.core.service.NlsService; +import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.context.IdeContextConsole; import com.devonfw.tools.ide.context.IdeStartContextImpl; import com.devonfw.tools.ide.log.IdeLogLevel; @@ -63,10 +64,26 @@ public class GuiStateManager { /** + * Creates the state manager for the given root directory, deriving the {@link NlsService} context from the GUI's own start context. + * * @param ideRoot the root directory of IDEasy. If null, the IDE_ROOT environment variable is used. */ public GuiStateManager(String ideRoot) { + // production derives the NlsService context from the GUI start context (which resolves the real user configuration); tests inject an isolated context + this(ideRoot, null); + } + + /** + * Creates the state manager for the given root directory using the supplied {@link NlsService} context. + * + * @param ideRoot the root directory of IDEasy. If null, the IDE_ROOT environment variable is used. + * @param nlsContext the {@link IdeContext} handed to the {@link NlsService}, or {@code null} to derive one from the GUI start context (production default). + * Injecting an isolated context (for example an {@link com.devonfw.tools.ide.context.IdeTestContext}) keeps the GUI test independent from the real + * user home and the {@code IDE_OPTIONS} environment variable. + */ + public GuiStateManager(String ideRoot, IdeContext nlsContext) { + this.ideRootDir = Path.of(ideRoot != null ? ideRoot : System.getenv(IdeVariables.IDE_ROOT.getName())); //This specific ordering needs to be kept @@ -77,8 +94,9 @@ public GuiStateManager(String ideRoot) { this.projectManager = new ProjectManager(ideRootDir); this.taskManager = new TaskManager(); this.eventBus = new GuiEventBus(); - // inject an application-level context so NlsService never has to create one itself; binding it to startContext keeps the GUI logging context - this.nlsService = new NlsService(new IdeContextConsole(startContext), null); + // inject an application-level context so NlsService never has to create one itself; binding the default to startContext keeps the GUI logging context + IdeContext resolvedNlsContext = (nlsContext != null) ? nlsContext : new IdeContextConsole(startContext); + this.nlsService = new NlsService(resolvedNlsContext, null); this.consoleController = new ConsoleController(this); this.commandletService = new CommandletService(this); this.tabFactory = new TabFactory(this); diff --git a/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java b/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java index 69e613c9b0..cfbcdddf2c 100644 --- a/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java +++ b/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java @@ -34,6 +34,8 @@ import com.devonfw.ide.gui.core.mainwindow.MainWindowViewModel; import com.devonfw.ide.gui.core.progress.ProgressBarTask; import com.devonfw.ide.gui.core.progress.taskwindow.TaskOverviewWindow; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.environment.IdeSystemTestImpl; /** * Basic UI Test for the main screen @@ -70,7 +72,12 @@ public class AppBaseTest extends HeadlessApplicationTest { @Override public void start(Stage stage) { - this.guiStateManager = new GuiStateManager(mockIdeRoot.toString()); + // isolate the NlsService from the developer's real user home and the IDE_OPTIONS environment variable: persisting the locale must not rewrite the + // real ~/.ide/ide.properties, so point an isolated IdeTestContext at a throw-away home under the mock root + final IdeTestContext nlsContext = new IdeTestContext(); + nlsContext.setSystem(new IdeSystemTestImpl()); + nlsContext.setUserHome(mockIdeRoot.resolve("home")); + this.guiStateManager = new GuiStateManager(mockIdeRoot.toString(), nlsContext); this.guiStateManager.getNlsService().setLocale(Locale.ENGLISH); this.taskManager = guiStateManager.getTaskManager(); this.viewModel = new MainWindowViewModel(guiStateManager);