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

davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new 73b4105440a0 [backport camel-4.22.x] camel-launcher: skip sources and 
javadoc jars (#27827)
73b4105440a0 is described below

commit 73b4105440a0de137478be4d0865227b706fe0c5
Author: Federico Mariani <[email protected]>
AuthorDate: Fri Oct 16 22:22:49 2026 +0200

    [backport camel-4.22.x] camel-launcher: skip sources and javadoc jars 
(#27827)
    
    Backport of #27826 to camel-4.22.x: the launch scripts skip sources and
    javadoc JARs, and the distribution assemblies leave them out.
    
    It also carries the Windows validator fix of 7ab6c2946729 (CAMEL-24485):
    bounded process execution, a shared PowerShell cache, timing diagnostics,
    archived surefire reports and a 60-minute job limit. The Windows launcher
    script normalizes the selected JAR path, and the POSIX packaging steps use
    the pinned Maven wrapper with the snapshot repositories.
    
    AI-generated by Codex on behalf of @Croway.
---
 .github/workflows/package-native-validation.yml    | 48 ++++++++++++++----
 .../src/main/assembly/launcher-content.xml         |  2 +
 .../src/main/resources/bin/camel.bat               |  7 ++-
 .../camel-launcher/src/main/resources/bin/camel.sh |  3 +-
 .../dsl/jbang/launcher/CamelBatDiscoveryTest.java  | 19 +++++++
 .../dsl/jbang/launcher/CamelShDiscoveryTest.java   | 20 ++++++++
 .../dsl/jbang/launcher/WebsiteInstallTest.java     | 58 +++++++---------------
 .../jbang/launcher/WebsiteInstallerFixture.java    | 33 +++++++++++-
 8 files changed, 137 insertions(+), 53 deletions(-)

diff --git a/.github/workflows/package-native-validation.yml 
b/.github/workflows/package-native-validation.yml
index 4027b4b6c79a..27de46e58c2d 100644
--- a/.github/workflows/package-native-validation.yml
+++ b/.github/workflows/package-native-validation.yml
@@ -41,6 +41,8 @@ concurrency:
 permissions:
   contents: read
 
+# Use the project Maven wrapper throughout: runner-provided Maven versions can 
differ
+# from the version supported by this branch's build extensions.
 jobs:
   # ── macOS / Linux (POSIX validators) ──────────────────────────────
   posix-validator:
@@ -81,6 +83,16 @@ jobs:
           java-version: '21'
           cache: 'maven'
 
+      # Packaging scripts invoke mvn themselves, so pin their PATH entry as 
well as
+      # the direct workflow commands. Keep the snapshot profile active in 
subprocesses.
+      - name: Pin Maven for packaging subprocesses
+        shell: bash
+        run: |
+          CAMEL_CI_MAVEN_HOME=$(./mvnw -q -B -P apache-snapshots -ntp -N 
org.apache.maven.plugins:maven-help-plugin:3.5.1:evaluate \
+            -Dexpression=maven.home -DforceStdout)
+          echo "$CAMEL_CI_MAVEN_HOME/bin" >> "$GITHUB_PATH"
+          echo "MAVEN_ARGS=${MAVEN_ARGS:-} -P apache-snapshots" >> 
"$GITHUB_ENV"
+
       # camel-repackager-maven-plugin builds the launcher's self-executing 
JAR. It is not in the
       # -pl list below, so without this step Maven resolves it from the Apache 
snapshot repository.
       # This workflow only triggers on dsl/camel-jbang/camel-launcher and 
tooling/camel-exe changes
@@ -88,7 +100,7 @@ jobs:
       - name: Install camel-repackager-maven-plugin (build-time plugin, 
unresolvable from this reactor pass)
         shell: bash
         run: |
-          mvn -B -P apache-snapshots -ntp \
+          ./mvnw -B -P apache-snapshots -ntp \
             -pl tooling/maven/camel-repackager-maven-plugin install -DskipTests
 
       - name: Build camel-launcher distribution
@@ -98,14 +110,14 @@ jobs:
         # published snapshot instead of being rebuilt from source, which is 
what previously turned
         # this into a ~626-module reactor build. -P apache-snapshots is 
required for that resolution.
         run: |
-          mvn -B -P apache-snapshots -ntp \
+          ./mvnw -B -P apache-snapshots -ntp \
             -pl buildingtools,dsl/camel-jbang/camel-launcher install 
-DskipTests
 
       - name: Stage a synthetic release version for offline validation
         shell: bash
         run: |
           cd dsl/camel-jbang/camel-launcher
-          REAL_VERSION=$(mvn -q -B -ntp 
org.apache.maven.plugins:maven-help-plugin:3.5.1:evaluate \
+          REAL_VERSION=$(../../../mvnw -q -B -P apache-snapshots -ntp 
org.apache.maven.plugins:maven-help-plugin:3.5.1:evaluate \
             -Dexpression=project.version -DforceStdout)
           TEST_VERSION="${REAL_VERSION%-SNAPSHOT}"
           echo "TEST_VERSION=$TEST_VERSION" >> "$GITHUB_ENV"
@@ -142,14 +154,16 @@ jobs:
         # WebsiteInstallTest's PosixShell suite only gets targeted CI coverage 
today as a side
         # effect of the generic incremental-build check noticing a direct 
change under
         # dsl/camel-jbang/camel-launcher; a PR that only touches an upstream 
dependency wouldn't
-        # trigger it. Run it here explicitly so this dedicated validation 
workflow covers it too.
+        # trigger it. Run it here explicitly, together with the POSIX launcher 
script tests.
         run: |
           cd dsl/camel-jbang/camel-launcher
-          mvn -B -ntp test -pl . 
-Dtest=PackageNativeValidationTest,WebsiteInstallTest
+          ../../../mvnw -B -P apache-snapshots -ntp test -pl . 
-Dtest=PackageNativeValidationTest,WebsiteInstallTest,CamelShDiscoveryTest,FakeJavaTest
 
-  # ── Windows (install.ps1 unit tests) ──────────────────────────────
+  # ── Windows (installer and launcher script unit tests) 
──────────────────────────────
   windows-validator:
     runs-on: windows-latest
+    # Caps a genuine hang well below GitHub's 6h default; a passing run has 
taken up to ~25 minutes.
+    timeout-minutes: 60
     steps:
       - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 
v7.0.1
         with:
@@ -169,7 +183,7 @@ jobs:
       - name: Install camel-repackager-maven-plugin (build-time plugin, 
unresolvable from this reactor pass)
         shell: bash
         run: |
-          mvn -B -P apache-snapshots -ntp \
+          ./mvnw -B -P apache-snapshots -ntp \
             -pl tooling/maven/camel-repackager-maven-plugin install -DskipTests
 
       - name: Build camel-launcher module
@@ -179,11 +193,25 @@ jobs:
         # published snapshot instead of being rebuilt from source, which is 
what previously turned
         # this into a ~626-module reactor build. -P apache-snapshots is 
required for that resolution.
         run: |
-          mvn -B -P apache-snapshots -ntp \
+          ./mvnw -B -P apache-snapshots -ntp \
             -pl buildingtools,dsl/camel-jbang/camel-launcher install 
-DskipTests
 
-      - name: Run Windows installer unit tests
+      - name: Run Windows installer and launcher script unit tests
         shell: bash
+        # The PowerShell suite has taken 10-20 minutes on windows-latest, 
straddling camel-launcher's 1200s
+        # surefire fork timeout, so slow-but-passing runs were killed. Give 
this job's fork more room.
         run: |
           cd dsl/camel-jbang/camel-launcher
-          mvn -B -ntp test -pl . -Dtest=WebsiteInstallTest
+          ../../../mvnw -B -P apache-snapshots -ntp test -pl . 
-Dtest=WebsiteInstallTest,CamelBatDiscoveryTest,FakeJavaTest 
-Dcamel.surefire.forkTimeout=2700
+
+      # Per-test timings for the PowerShell suite, which runs close to 
camel-launcher's surefire fork timeout.
+      # A fork killed on that timeout writes no XML report; the per-process 
timings the fixture prints to the
+      # build log cover that case.
+      - name: Archive surefire reports
+        if: always()
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
+        with:
+          name: windows-validator-surefire-reports
+          path: dsl/camel-jbang/camel-launcher/target/surefire-reports
+          retention-days: 7
+          if-no-files-found: ignore
diff --git 
a/dsl/camel-jbang/camel-launcher/src/main/assembly/launcher-content.xml 
b/dsl/camel-jbang/camel-launcher/src/main/assembly/launcher-content.xml
index f648f2f9d0ea..296373b1c045 100644
--- a/dsl/camel-jbang/camel-launcher/src/main/assembly/launcher-content.xml
+++ b/dsl/camel-jbang/camel-launcher/src/main/assembly/launcher-content.xml
@@ -56,6 +56,8 @@
             </includes>
             <excludes>
                 <exclude>original-*.jar</exclude>
+                <exclude>*-sources.jar</exclude>
+                <exclude>*-javadoc.jar</exclude>
             </excludes>
         </fileSet>
         <fileSet>
diff --git a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat 
b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
index 692dbea4de26..9d485f9a39f9 100644
--- a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
+++ b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.bat
@@ -49,9 +49,12 @@ set "JAVACMD=%CAMEL_JAVACMD%"
 @REM Set JVM options if specified
 if "%JAVA_OPTS%" == "" set JAVA_OPTS=-Xmx512m
 
-@REM Find the launcher JAR
+@REM Find the launcher JAR, excluding sources and javadoc
 set "LAUNCHER_JAR="
-for %%i in ("%BASEDIR%\camel-launcher-*.jar") do set "LAUNCHER_JAR=%%i"
+for %%i in ("%BASEDIR%\camel-launcher-*.jar") do (
+  echo %%~nxi| findstr /i /e /l /c:"-sources.jar" /c:"-javadoc.jar" > nul
+  if errorlevel 1 if not defined LAUNCHER_JAR set "LAUNCHER_JAR=%%~fi"
+)
 
 @REM Execute Camel CLI, preserving arguments and the child exit code
 for %%e in ("%JAVACMD%") do set "_JAVACMD_EXT=%%~xe"
diff --git a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh 
b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
index c5f1fb6fd0c7..36a66c77efab 100644
--- a/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
+++ b/dsl/camel-jbang/camel-launcher/src/main/resources/bin/camel.sh
@@ -100,9 +100,10 @@ then
   REPO="$BASEDIR"
 fi
 
-# Find the launcher JAR
+# Find the launcher JAR, excluding sources and javadoc
 LAUNCHER_JAR=
 for f in "$BASEDIR"/camel-launcher-*.jar; do
+  case "$f" in *-sources.jar|*-javadoc.jar) continue ;; esac
   if [ -f "$f" ]; then
     LAUNCHER_JAR="$f"
     break
diff --git 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
index 424311576275..03883353c0eb 100644
--- 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
+++ 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelBatDiscoveryTest.java
@@ -76,6 +76,25 @@ class CamelBatDiscoveryTest {
         return javaHome;
     }
 
+    @Test
+    void skipsSourcesAndJavadocJars(@TempDir Path base) throws Exception {
+        Path h = home(Files.createDirectories(base.resolve("launcher with 
spaces")));
+        Files.writeString(h.resolve("camel-launcher-9.9.9-sources.jar"), 
"sources");
+        Files.writeString(h.resolve("camel-launcher-zzz-sources.jar"), 
"sources");
+        Files.writeString(h.resolve("camel-launcher-9.9.9-javadoc.jar"), 
"javadoc");
+        Files.writeString(h.resolve("camel-launcher-zzz-javadoc.jar"), 
"javadoc");
+        Path java = FakeJava.writeFakeJava(base, "java", JAVA17, 0, "JAVACMD");
+        Map<String, String> env = isolatedEnvironment();
+        env.put("JAVACMD", java.toString());
+
+        FakeJava.Result r = FakeJava.run(h.resolve("camel.bat"), env, 
"hawtio");
+
+        assertEquals(0, r.exitCode(), r.stderr());
+        
assertTrue(r.stdout().contains(h.resolve("camel-launcher-9.9.9.jar").toString()),
 r.stdout());
+        assertFalse(r.stdout().contains("-sources.jar"), r.stdout());
+        assertFalse(r.stdout().contains("-javadoc.jar"), r.stdout());
+    }
+
     @Test
     void acceptsJava17ViaJavacmd(@TempDir Path base) throws Exception {
         Path h = home(base);
diff --git 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
index ad2735ed362c..08fac4adf315 100644
--- 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
+++ 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/CamelShDiscoveryTest.java
@@ -56,6 +56,26 @@ class CamelShDiscoveryTest {
         return pathDir + ":/usr/bin:/bin";
     }
 
+    @Test
+    void skipsSourcesAndJavadocJars(@TempDir Path base) throws Exception {
+        Path h = home(Files.createDirectories(base.resolve("launcher with 
spaces")));
+        Files.writeString(h.resolve("camel-launcher-9.9.9-sources.jar"), 
"sources");
+        Files.writeString(h.resolve("camel-launcher-zzz-sources.jar"), 
"sources");
+        Files.writeString(h.resolve("camel-launcher-9.9.9-javadoc.jar"), 
"javadoc");
+        Files.writeString(h.resolve("camel-launcher-zzz-javadoc.jar"), 
"javadoc");
+        Path java = FakeJava.writeFakeJava(base, "java", JAVA17, 0, "JAVACMD");
+        Map<String, String> env = new HashMap<>();
+        env.put("LC_ALL", "C");
+        env.put("JAVACMD", java.toString());
+
+        FakeJava.Result r = FakeJava.run(h.resolve("camel.sh"), env, "hawtio");
+
+        assertEquals(0, r.exitCode(), r.stderr());
+        
assertTrue(r.stdout().contains(h.resolve("camel-launcher-9.9.9.jar").toString()),
 r.stdout());
+        assertFalse(r.stdout().contains("-sources.jar"), r.stdout());
+        assertFalse(r.stdout().contains("-javadoc.jar"), r.stdout());
+    }
+
     @Test
     void acceptsJava17ViaJavacmd(@TempDir Path base) throws Exception {
         Path h = home(base);
diff --git 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallTest.java
 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallTest.java
index 0fbda6496e9d..a514a7c10828 100644
--- 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallTest.java
+++ 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallTest.java
@@ -34,7 +34,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
 
 import org.junit.jupiter.api.AfterEach;
@@ -152,11 +151,8 @@ class WebsiteInstallTest {
             Path versionDir = home.resolve(".local/share/camel-cli/versions/" 
+ version);
             assertTrue(Files.isDirectory(versionDir), "expected version 
directory " + versionDir);
 
-            Process check = new ProcessBuilder("/bin/sh", shim.toString(), 
"version")
-                    .redirectErrorStream(true)
-                    .start();
-            String output = new String(check.getInputStream().readAllBytes(), 
StandardCharsets.UTF_8);
-            check.waitFor();
+            String output = WebsiteInstallerFixture.execute(
+                    new ProcessBuilder("/bin/sh", shim.toString(), 
"version").redirectErrorStream(true)).stdout();
             assertTrue(output.contains("Camel " + version), output);
         }
 
@@ -672,9 +668,8 @@ class WebsiteInstallTest {
                           + "if ($path) { $kind = $k.GetValueKind('Path'); "
                           + "$kept = ($path -split ';') | Where-Object { $_ 
-and (-not ($dirs -icontains $_)) }; "
                           + "$k.SetValue('Path', ($kept -join ';'), $kind) } 
$k.Close() }");
-            Process cleanup = new ProcessBuilder("powershell", "-NoProfile", 
"-Command", script.toString())
-                    .redirectErrorStream(true).start();
-            cleanup.waitFor(30, TimeUnit.SECONDS);
+            WebsiteInstallerFixture.execute(
+                    new ProcessBuilder("powershell", "-NoProfile", "-Command", 
script.toString()).redirectErrorStream(true));
         }
 
         private WebsiteInstallerFixture.Result install(
@@ -716,11 +711,8 @@ class WebsiteInstallTest {
             assertTrue(Files.isDirectory(versionDir(home, version)),
                     "expected version directory " + versionDir(home, version));
 
-            Process check = new ProcessBuilder("cmd.exe", "/c", 
shim.toString(), "version")
-                    .redirectErrorStream(true)
-                    .start();
-            String output = new String(check.getInputStream().readAllBytes(), 
StandardCharsets.UTF_8);
-            check.waitFor(30, TimeUnit.SECONDS);
+            String output = WebsiteInstallerFixture.execute(
+                    new ProcessBuilder("cmd.exe", "/c", shim.toString(), 
"version").redirectErrorStream(true)).stdout();
             assertTrue(output.contains("Camel " + version), output);
         }
 
@@ -740,13 +732,10 @@ class WebsiteInstallTest {
         }
 
         private static String queryEnvironmentPath(String scope) throws 
Exception {
-            Process p = new ProcessBuilder(
+            return WebsiteInstallerFixture.execute(new ProcessBuilder(
                     "powershell", "-NoProfile", "-Command",
                     "[Environment]::GetEnvironmentVariable('Path','" + scope + 
"')")
-                    .redirectErrorStream(true).start();
-            String out = new String(p.getInputStream().readAllBytes(), 
StandardCharsets.UTF_8).trim();
-            p.waitFor(30, TimeUnit.SECONDS);
-            return out;
+                    .redirectErrorStream(true)).stdout().trim();
         }
 
         private static long countOccurrencesCaseInsensitive(String path, 
String dir) {
@@ -760,15 +749,12 @@ class WebsiteInstallTest {
 
         // Reads HKCU\Environment\Path without expanding %VAR% references, 
returning "" when unset.
         private static String readUserPathRaw() throws Exception {
-            Process p = new ProcessBuilder(
+            return WebsiteInstallerFixture.execute(new ProcessBuilder(
                     "powershell", "-NoProfile", "-Command",
                     "$k = 
[Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment'); "
                                                             + "if ($k) { 
[Console]::Out.Write($k.GetValue('Path', '', "
-                                                            + 
"[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)); 
$k.Close() }")
-                    .start();
-            String out = new String(p.getInputStream().readAllBytes(), 
StandardCharsets.UTF_8);
-            p.waitFor(30, TimeUnit.SECONDS);
-            return out;
+                                                            + 
"[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)); 
$k.Close() }"))
+                    .stdout();
         }
 
         // Writes HKCU\Environment\Path as a REG_EXPAND_SZ value; the value is 
passed via the environment
@@ -781,21 +767,18 @@ class WebsiteInstallTest {
                                                             + 
"[Microsoft.Win32.RegistryValueKind]::ExpandString); $k.Close()")
                     .redirectErrorStream(true);
             pb.environment().put("CAMEL_TEST_USERPATH", value);
-            Process p = pb.start();
-            String out = new String(p.getInputStream().readAllBytes(), 
StandardCharsets.UTF_8);
-            if (!p.waitFor(30, TimeUnit.SECONDS) || p.exitValue() != 0) {
-                throw new IllegalStateException("failed to seed user PATH: " + 
out);
+            WebsiteInstallerFixture.Result r = 
WebsiteInstallerFixture.execute(pb);
+            if (r.exit() != 0) {
+                throw new IllegalStateException("failed to seed user PATH: " + 
r.stdout());
             }
         }
 
         private static void deleteUserPath() throws Exception {
-            Process p = new ProcessBuilder(
+            WebsiteInstallerFixture.execute(new ProcessBuilder(
                     "powershell", "-NoProfile", "-Command",
                     "$k = 
[Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true); "
                                                             + "if ($k) { 
$k.DeleteValue('Path', $false); $k.Close() }")
-                    .redirectErrorStream(true).start();
-            p.getInputStream().readAllBytes();
-            p.waitFor(30, TimeUnit.SECONDS);
+                    .redirectErrorStream(true));
         }
 
         @Test
@@ -1039,14 +1022,11 @@ class WebsiteInstallTest {
                 // ("bar baz") trips cmd.exe's quote-stripping rule (it only 
preserves quotes when there are
                 // exactly two), which would unquote the space in "Apache 
Camel" and break the call. A bare
                 // command name resolved through PATH+PATHEXT avoids that 
quirk and mirrors real usage.
-                Process argsProc = shimProcess(binDir, "echo-args", "foo", 
"bar baz").start();
-                String argsOut = new 
String(argsProc.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
-                argsProc.waitFor(30, TimeUnit.SECONDS);
+                String argsOut = 
WebsiteInstallerFixture.execute(shimProcess(binDir, "echo-args", "foo", "bar 
baz"))
+                        .stdout();
                 assertTrue(argsOut.contains("foo") && argsOut.contains("bar 
baz"), argsOut);
 
-                Process exitProc = shimProcess(binDir, "exit-code", 
"7").start();
-                exitProc.waitFor(30, TimeUnit.SECONDS);
-                assertEquals(7, exitProc.exitValue());
+                assertEquals(7, 
WebsiteInstallerFixture.execute(shimProcess(binDir, "exit-code", "7")).exit());
             }
         }
 
diff --git 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallerFixture.java
 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallerFixture.java
index a94857d9b708..996e2eddf74f 100644
--- 
a/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallerFixture.java
+++ 
b/dsl/camel-jbang/camel-launcher/src/test/java/org/apache/camel/dsl/jbang/launcher/WebsiteInstallerFixture.java
@@ -64,6 +64,8 @@ final class WebsiteInstallerFixture implements AutoCloseable {
     private static final String KEY_ALIAS = "camel-installer-test";
     private static final String DEFAULT_BASE_VERSION = "9.9.9";
     private static final Duration PROCESS_TIMEOUT = Duration.ofSeconds(60);
+    private static final Path POWERSHELL_MODULE_ANALYSIS_CACHE
+            = Path.of("target", "powershell", 
"ModuleAnalysisCache").toAbsolutePath();
 
     record Result(int exit, String stdout, String stderr) {
     }
@@ -446,6 +448,20 @@ final class WebsiteInstallerFixture implements 
AutoCloseable {
             if (pathExt != null) {
                 pb.environment().put("PATHEXT", pathExt);
             }
+            // Without TEMP/TMP, .NET falls back to USERPROFILE (the fresh 
per-test home) for the scratch
+            // files Add-Type's C# compile writes on every install.ps1 run. 
install.ps1 itself never stages
+            // anything under the temp directory, so passing the runner's 
through does not affect isolation.
+            for (String name : List.of("TEMP", "TMP")) {
+                String value = System.getenv(name);
+                if (value != null) {
+                    pb.environment().put(name, value);
+                }
+            }
+            // PowerShell keeps its module analysis cache under LOCALAPPDATA, 
which every test points at a
+            // fresh home, so each powershell.exe launch would rebuild it from 
scratch when auto-loading
+            // Expand-Archive / Get-FileHash. Share one cache file across the 
whole test run instead.
+            
Files.createDirectories(POWERSHELL_MODULE_ANALYSIS_CACHE.getParent());
+            pb.environment().put("PSModuleAnalysisCachePath", 
POWERSHELL_MODULE_ANALYSIS_CACHE.toString());
         }
         pb.environment().putAll(env);
         String home = env.get("HOME");
@@ -454,7 +470,16 @@ final class WebsiteInstallerFixture implements 
AutoCloseable {
             // wherever the test JVM happens to be running from.
             pb.directory(Path.of(home).toFile());
         }
+        return execute(pb);
+    }
 
+    /**
+     * Runs {@code pb} with stdout and stderr drained on background threads, 
so a chatty or stuck child can never block
+     * the test past {@link #PROCESS_TIMEOUT}: reading a stream to EOF before 
{@code waitFor} would wait for as long as
+     * the child (or any grandchild inheriting its handles) keeps the pipe 
open.
+     */
+    static Result execute(ProcessBuilder pb) throws Exception {
+        long start = System.nanoTime();
         Process process = pb.start();
         ExecutorService collectors = Executors.newFixedThreadPool(2);
         try {
@@ -463,7 +488,7 @@ final class WebsiteInstallerFixture implements 
AutoCloseable {
             process.getOutputStream().close();
             if (!process.waitFor(PROCESS_TIMEOUT.toSeconds(), 
TimeUnit.SECONDS)) {
                 process.destroyForcibly();
-                throw new IllegalStateException("installer did not exit in 
time");
+                throw new IllegalStateException("process did not exit in time: 
" + pb.command());
             }
             String out = new String(await(stdout), StandardCharsets.UTF_8);
             String err = new String(await(stderr), StandardCharsets.UTF_8);
@@ -474,6 +499,12 @@ final class WebsiteInstallerFixture implements 
AutoCloseable {
             }
             collectors.shutdownNow();
             collectors.awaitTermination(10, TimeUnit.SECONDS);
+            // Surefire streams test stdout to the build log as it happens, so 
these timings survive even when
+            // the fork is killed by forkedProcessTimeoutInSeconds and no XML 
report is ever written.
+            String command = String.join(" ", pb.command());
+            System.out.printf("[WebsiteInstallerFixture] %d ms: %s%n",
+                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start),
+                    command.length() > 160 ? command.substring(0, 160) + "..." 
: command);
         }
     }
 

Reply via email to