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

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


The following commit(s) were added to refs/heads/main by this push:
     new 856f2f4698e NIFI-16353 - Isolate StandardConnectorTestRunner 
filesystem paths (#11686)
856f2f4698e is described below

commit 856f2f4698e00ba25fa58e0e8343d628b9ae7a35
Author: skeossei <[email protected]>
AuthorDate: Wed Sep 30 08:43:25 2026 -0400

    NIFI-16353 - Isolate StandardConnectorTestRunner filesystem paths (#11686)
    
    * NIFI-16353 Isolate StandardConnectorTestRunner filesystem paths
    
    * NIFI-16353 Address review feedback for ConnectorTestRunner isolation
    
    Use File for instance directories, drop mirrored property-mapping tests, 
and assert isolation sequentially.
    
    Co-authored-by: Cursor <[email protected]>
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
---
 .../server/MockExtensionDiscoveringManager.java    |  13 +-
 .../server/StandardConnectorMockServer.java        |  16 ++-
 .../StandardConnectorMockServerJettyTest.java      |  51 ++++++++
 .../mock/connectors/tests/AllowableValuesIT.java   |   6 +
 .../mock/connectors/tests/CreateConnectorIT.java   |   9 ++
 .../nifi/mock/connectors/tests/CronScheduleIT.java |   6 +
 .../nifi/mock/connectors/tests/FlowSnapshotIT.java |   6 +
 .../connectors/tests/MockControllerServiceIT.java  |   6 +
 .../mock/connectors/tests/MockProcessorIT.java     |   6 +
 .../StandardConnectorTestRunnerIsolationIT.java    | 145 +++++++++++++++++++++
 .../connector/StandardConnectorTestRunner.java     |  89 ++++++++++---
 11 files changed, 334 insertions(+), 19 deletions(-)

diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
index 0dc4c1558e7..ba51d530287 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/MockExtensionDiscoveringManager.java
@@ -35,11 +35,20 @@ public class MockExtensionDiscoveringManager extends 
StandardExtensionDiscoverin
     private static final String MOCK_BUNDLE_GROUP = "org.apache.nifi.mock";
     private static final String MOCK_BUNDLE_VERSION = "1.0.0";
 
+    private final File mockBundleWorkingDirectory;
     private final ConcurrentMap<String, ClassLoader> mockComponentClassLoaders 
= new ConcurrentHashMap<>();
 
+    public MockExtensionDiscoveringManager() {
+        this(new File("target/work/extensions/mock-bundle"));
+    }
+
+    public MockExtensionDiscoveringManager(final File 
mockBundleWorkingDirectory) {
+        this.mockBundleWorkingDirectory = mockBundleWorkingDirectory;
+    }
+
     public synchronized void addProcessor(final Class<? extends Processor> 
mockProcessorClass) {
         final BundleDetails bundleDetails = new BundleDetails.Builder()
-            .workingDir(new File("target/work/extensions/mock-bundle"))
+            .workingDir(mockBundleWorkingDirectory)
             .coordinate(new BundleCoordinate(MOCK_BUNDLE_GROUP, 
mockProcessorClass.getName(), MOCK_BUNDLE_VERSION))
             .build();
 
@@ -52,7 +61,7 @@ public class MockExtensionDiscoveringManager extends 
StandardExtensionDiscoverin
 
     public synchronized void addControllerService(final Class<? extends 
ControllerService> mockControllerServiceClass) {
         final BundleDetails bundleDetails = new BundleDetails.Builder()
-            .workingDir(new File("target/work/extensions/mock-bundle"))
+            .workingDir(mockBundleWorkingDirectory)
             .coordinate(new BundleCoordinate(MOCK_BUNDLE_GROUP, 
mockControllerServiceClass.getName(), MOCK_BUNDLE_VERSION))
             .build();
 
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
index 1c40b648611..ff4a18e6ae9 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/main/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServer.java
@@ -129,7 +129,10 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
 
     @Override
     public void start() {
-        extensionManager = new MockExtensionDiscoveringManager();
+        final String narWorkingDirectory = 
nifiProperties.getProperty(NiFiProperties.NAR_WORKING_DIRECTORY);
+        extensionManager = narWorkingDirectory == null
+                ? new MockExtensionDiscoveringManager()
+                : new MockExtensionDiscoveringManager(new 
File(nifiProperties.getExtensionsWorkingDirectory(), "mock-bundle"));
         extensionManager.discoverExtensions(systemBundle, bundles);
         extensionManager.logClassLoaderMapping();
 
@@ -565,7 +568,7 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
         stop();
     }
 
-    private void startJettyServer() {
+    void startJettyServer() {
         final String httpPortValue = 
nifiProperties.getProperty(NiFiProperties.WEB_HTTP_PORT);
         if (httpPortValue == null || httpPortValue.isBlank()) {
             logger.debug("No HTTP port configured; skipping Jetty server 
startup");
@@ -596,6 +599,15 @@ public class StandardConnectorMockServer implements 
ConnectorMockServer {
 
             final WebAppContext webAppContext = new 
WebAppContext(warFile.getPath(), contextPath);
             webAppContext.setClassLoader(new 
WebAppClassLoader(bundle.getClassLoader(), webAppContext));
+            if 
(nifiProperties.getProperty(NiFiProperties.NAR_WORKING_DIRECTORY) != null) {
+                try {
+                    final Path webWorkingDirectory = 
nifiProperties.getWebWorkingDirectory().toPath();
+                    Files.createDirectories(webWorkingDirectory);
+                    
webAppContext.setTempDirectory(Files.createTempDirectory(webWorkingDirectory, 
"webapp-").toFile());
+                } catch (final IOException e) {
+                    throw new RuntimeException("Failed to create Jetty working 
directory", e);
+                }
+            }
 
             handlers.addHandler(webAppContext);
             webAppContexts.add(webAppContext);
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
index 980d8341c97..3aa5c0eae26 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-server/src/test/java/org/apache/nifi/mock/connector/server/StandardConnectorMockServerJettyTest.java
@@ -20,6 +20,7 @@ package org.apache.nifi.mock.connector.server;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.util.NiFiProperties;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
@@ -29,11 +30,14 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
+import java.util.stream.Stream;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class StandardConnectorMockServerJettyTest {
@@ -156,6 +160,53 @@ class StandardConnectorMockServerJettyTest {
         assertTrue(wars.isEmpty());
     }
 
+    @Test
+    void testJettyServersUseIndependentWorkingDirectoriesAndPorts() throws 
Exception {
+        final Path bundleWorkingDirectory = tempDir.resolve("test-bundle");
+        final Path dependenciesDirectory = 
bundleWorkingDirectory.resolve(NAR_DEPENDENCIES_PATH);
+        Files.createDirectories(dependenciesDirectory);
+        createConnectorWar(dependenciesDirectory.resolve("connector.war"));
+        final Bundle bundle = createBundle(bundleWorkingDirectory);
+
+        final Path firstInstanceDirectory = tempDir.resolve("first");
+        final Path secondInstanceDirectory = tempDir.resolve("second");
+        final StandardConnectorMockServer firstServer = createServer(bundle, 
firstInstanceDirectory);
+        final StandardConnectorMockServer secondServer = createServer(bundle, 
secondInstanceDirectory);
+
+        try {
+            firstServer.startJettyServer();
+            secondServer.startJettyServer();
+
+            assertTrue(firstServer.getHttpPort() > 0);
+            assertTrue(secondServer.getHttpPort() > 0);
+            assertNotEquals(firstServer.getHttpPort(), 
secondServer.getHttpPort());
+            assertWebAppWorkingDirectoryCreated(firstInstanceDirectory);
+            assertWebAppWorkingDirectoryCreated(secondInstanceDirectory);
+        } finally {
+            firstServer.stop();
+            secondServer.stop();
+        }
+    }
+
+    private static StandardConnectorMockServer createServer(final Bundle 
bundle, final Path instanceDirectory) {
+        final Properties additionalProperties = new Properties();
+        additionalProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, "0");
+        additionalProperties.setProperty(NiFiProperties.NAR_WORKING_DIRECTORY, 
instanceDirectory.resolve("work").toString());
+        additionalProperties.setProperty(NiFiProperties.WEB_WORKING_DIR, 
instanceDirectory.resolve("work/jetty").toString());
+        final NiFiProperties properties = 
NiFiProperties.createBasicNiFiProperties((String) null, additionalProperties);
+
+        final StandardConnectorMockServer server = new 
StandardConnectorMockServer();
+        server.initialize(properties, null, Set.of(bundle), null);
+        return server;
+    }
+
+    private static void assertWebAppWorkingDirectoryCreated(final Path 
instanceDirectory) throws IOException {
+        final Path jettyWorkingDirectory = 
instanceDirectory.resolve("work/jetty");
+        try (final Stream<Path> paths = Files.list(jettyWorkingDirectory)) {
+            assertTrue(paths.anyMatch(path -> Files.isDirectory(path) && 
path.getFileName().toString().startsWith("webapp-")));
+        }
+    }
+
     private static void createConnectorWar(final Path warPath) throws 
IOException {
         try (final JarOutputStream jarOut = new JarOutputStream(new 
FileOutputStream(warPath.toFile()))) {
             jarOut.putNextEntry(new JarEntry("META-INF/nifi-connector"));
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/AllowableValuesIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/AllowableValuesIT.java
index a350730b722..b4c10605ece 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/AllowableValuesIT.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/AllowableValuesIT.java
@@ -22,9 +22,11 @@ import 
org.apache.nifi.mock.connector.StandardConnectorTestRunner;
 import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
 import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
 
@@ -46,6 +48,9 @@ public class AllowableValuesIT {
 
     private static final String CONNECTOR_CLASS = 
"org.apache.nifi.mock.connectors.AllowableValuesConnector";
 
+    @TempDir
+    private Path temporaryDirectory;
+
     @Test
     public void testVerifyConfigurationRejectsInvalidAllowableValue() throws 
IOException {
         try (final ConnectorTestRunner runner = createRunner()) {
@@ -82,6 +87,7 @@ public class AllowableValuesIT {
         return new StandardConnectorTestRunner.Builder()
             .connectorClassName(CONNECTOR_CLASS)
             .narLibraryDirectory(new File("target/libDir"))
+            .instanceDirectory(temporaryDirectory.toFile())
             .build();
     }
 }
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
index f3d529c6a5e..c046c95ec46 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CreateConnectorIT.java
@@ -25,9 +25,11 @@ import org.apache.nifi.flow.VersionedProcessor;
 import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
 import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.time.Duration;
 import java.util.List;
 import java.util.Optional;
@@ -41,11 +43,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class CreateConnectorIT {
 
+    @TempDir
+    private Path temporaryDirectory;
+
     @Test
     public void testCreateStartAndStopGenerateAndUpdateConnector() throws 
IOException {
         try (final ConnectorTestRunner testRunner = new 
StandardConnectorTestRunner.Builder()
             
.connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
             .narLibraryDirectory(new File("target/libDir"))
+            .instanceDirectory(temporaryDirectory.toFile())
             .build()) {
 
             // Verify the active flow snapshot reflects the initial flow 
loaded from Generate_and_Update.json
@@ -74,6 +80,7 @@ public class CreateConnectorIT {
         try (final ConnectorTestRunner testRunner = new 
StandardConnectorTestRunner.Builder()
                 
.connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
                 .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(temporaryDirectory.toFile())
                 .build()) {
 
             testRunner.startConnector();
@@ -88,6 +95,7 @@ public class CreateConnectorIT {
         try (final ConnectorTestRunner testRunner = new 
StandardConnectorTestRunner.Builder()
                 
.connectorClassName("org.apache.nifi.mock.connectors.MissingBundleConnector")
                 .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(temporaryDirectory.toFile())
                 .build()) {
 
             final List<ValidationResult> results = testRunner.validate();
@@ -103,6 +111,7 @@ public class CreateConnectorIT {
         try (final ConnectorTestRunner testRunner = new 
StandardConnectorTestRunner.Builder()
                 
.connectorClassName("org.apache.nifi.mock.connectors.MissingBundleConnector")
                 .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(temporaryDirectory.toFile())
                 .build()) {
 
             final IllegalStateException exception = 
assertThrows(IllegalStateException.class, testRunner::startConnector);
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CronScheduleIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CronScheduleIT.java
index 10dea97a432..2e3ffad0ff0 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CronScheduleIT.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/CronScheduleIT.java
@@ -22,9 +22,11 @@ import 
org.apache.nifi.mock.connector.StandardConnectorTestRunner;
 import org.apache.nifi.mock.connector.server.ConnectorConfigVerificationResult;
 import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
 
@@ -40,6 +42,9 @@ public class CronScheduleIT {
 
     private static final String CONNECTOR_CLASS = 
"org.apache.nifi.mock.connectors.CronScheduleConnector";
 
+    @TempDir
+    private Path temporaryDirectory;
+
     @Test
     public void testValidCronExpression() throws IOException {
         try (final ConnectorTestRunner runner = createRunner()) {
@@ -63,6 +68,7 @@ public class CronScheduleIT {
         return new StandardConnectorTestRunner.Builder()
                 .connectorClassName(CONNECTOR_CLASS)
                 .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(temporaryDirectory.toFile())
                 .build();
     }
 }
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/FlowSnapshotIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/FlowSnapshotIT.java
index ce0c4465879..63878892db6 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/FlowSnapshotIT.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/FlowSnapshotIT.java
@@ -26,11 +26,13 @@ import org.apache.nifi.flow.VersionedParameterContext;
 import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
 import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -47,11 +49,15 @@ public class FlowSnapshotIT {
     private static final String SENSITIVE_PARAMETER_NAME = 
"sensitive_parameter";
     private static final String ASSET_PARAMETER_NAME = "asset_parameter";
 
+    @TempDir
+    private Path temporaryDirectory;
+
     @Test
     public void testWorkingAndActiveFlowSnapshotsPreserveParameterMetadata() 
throws IOException, FlowUpdateException {
         try (final ConnectorTestRunner runner = new 
StandardConnectorTestRunner.Builder()
                 .connectorClassName(CONNECTOR_CLASS)
                 .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(temporaryDirectory.toFile())
                 .build()) {
 
             final ByteArrayInputStream assetContents = new 
ByteArrayInputStream("certificate contents".getBytes(StandardCharsets.UTF_8));
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
index 1f044ccf21c..66e7a6e6e89 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockControllerServiceIT.java
@@ -26,9 +26,11 @@ import 
org.apache.nifi.mock.connector.StandardConnectorTestRunner;
 import org.apache.nifi.mock.connector.server.ConnectorTestRunner;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -38,6 +40,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class MockControllerServiceIT {
 
+    @TempDir
+    private Path temporaryDirectory;
+
     @Test
     @Timeout(10)
     public void testMockControllerService() throws IOException {
@@ -45,6 +50,7 @@ public class MockControllerServiceIT {
                 .narLibraryDirectory(new File("target/libDir"))
                 
.connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
                 
.mockControllerService("org.apache.nifi.lookup.SimpleKeyValueLookupService", 
MockStringLookupService.class)
+                .instanceDirectory(temporaryDirectory.toFile())
                 .build()) {
 
             runner.startConnector();
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java
index 465d0340002..34680ed1cbc 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/MockProcessorIT.java
@@ -29,14 +29,19 @@ import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.exception.ProcessException;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 
 public class MockProcessorIT {
 
+    @TempDir
+    private Path temporaryDirectory;
+
     @Test
     @Timeout(10)
     public void testMockProcessor() throws IOException {
@@ -44,6 +49,7 @@ public class MockProcessorIT {
             .narLibraryDirectory(new File("target/libDir"))
             
.connectorClassName("org.apache.nifi.mock.connectors.GenerateAndLog")
             
.mockProcessor("org.apache.nifi.processors.attributes.UpdateAttribute", 
MockProcessor.class)
+            .instanceDirectory(temporaryDirectory.toFile())
             .build()) {
 
             runner.startConnector();
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/StandardConnectorTestRunnerIsolationIT.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/StandardConnectorTestRunnerIsolationIT.java
new file mode 100644
index 00000000000..f3c7b87b783
--- /dev/null
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock-test-bundle/nifi-connector-mock-integration-tests/src/test/java/org/apache/nifi/mock/connectors/tests/StandardConnectorTestRunnerIsolationIT.java
@@ -0,0 +1,145 @@
+/*
+ * 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.nifi.mock.connectors.tests;
+
+import org.apache.nifi.mock.connector.StandardConnectorTestRunner;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class StandardConnectorTestRunnerIsolationIT {
+
+    private static final String CONNECTOR_CLASS = 
"org.apache.nifi.mock.connectors.GenerateAndLog";
+
+    @TempDir
+    private Path temporaryDirectory;
+
+    @Test
+    @Timeout(120)
+    void testIndependentInstanceDirectories() throws Exception {
+        final Path firstInstanceDirectory = 
temporaryDirectory.resolve("first");
+        final Path secondInstanceDirectory = 
temporaryDirectory.resolve("second");
+        StandardConnectorTestRunner firstRunner = 
createRunner(firstInstanceDirectory);
+        StandardConnectorTestRunner secondRunner = null;
+
+        try {
+            secondRunner = createRunner(secondInstanceDirectory);
+
+            startAndStop(firstRunner);
+            startAndStop(secondRunner);
+
+            addAsset(firstRunner, "first.txt", "first-runner");
+            addAsset(secondRunner, "second.txt", "second-runner");
+
+            assertEquals(Set.of("first-runner"), new 
HashSet<>(readAssetContents(firstInstanceDirectory)));
+            assertEquals(Set.of("second-runner"), new 
HashSet<>(readAssetContents(secondInstanceDirectory)));
+
+            firstRunner.close();
+            firstRunner = null;
+
+            secondRunner.validate();
+            addAsset(secondRunner, "after-close.txt", 
"second-runner-after-close");
+            assertEquals(Set.of("second-runner", "second-runner-after-close"), 
new HashSet<>(readAssetContents(secondInstanceDirectory)));
+        } finally {
+            if (firstRunner != null) {
+                firstRunner.close();
+            }
+
+            if (secondRunner != null) {
+                secondRunner.close();
+            }
+        }
+
+        assertTrue(Files.isDirectory(firstInstanceDirectory));
+        assertTrue(Files.isDirectory(secondInstanceDirectory));
+        
assertTrue(Files.isDirectory(firstInstanceDirectory.resolve("work/framework")));
+        
assertTrue(Files.isDirectory(firstInstanceDirectory.resolve("work/extensions")));
+        
assertTrue(Files.isDirectory(secondInstanceDirectory.resolve("work/framework")));
+        
assertTrue(Files.isDirectory(secondInstanceDirectory.resolve("work/extensions")));
+    }
+
+    @Test
+    void 
testCallerOwnedInstanceDirectoryRetainedAndReusableAfterBootstrapFailure() 
throws Exception {
+        final Path instanceDirectory = temporaryDirectory.resolve("failed");
+        Files.createDirectories(instanceDirectory);
+        final Path marker = 
Files.writeString(instanceDirectory.resolve("marker"), "caller-owned");
+
+        assertThrows(RuntimeException.class, () -> new 
StandardConnectorTestRunner.Builder()
+                
.connectorClassName("org.apache.nifi.mock.connectors.DoesNotExist")
+                .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(instanceDirectory.toFile())
+                .build());
+
+        assertEquals("caller-owned", Files.readString(marker));
+        try (final StandardConnectorTestRunner runner = 
createRunner(instanceDirectory)) {
+            runner.validate();
+        }
+    }
+
+    private StandardConnectorTestRunner createRunner(final Path 
instanceDirectory) {
+        return new StandardConnectorTestRunner.Builder()
+                .connectorClassName(CONNECTOR_CLASS)
+                .narLibraryDirectory(new File("target/libDir"))
+                .instanceDirectory(instanceDirectory.toFile())
+                .build();
+    }
+
+    private static void startAndStop(final StandardConnectorTestRunner runner) 
throws TimeoutException {
+        runner.startConnector();
+        runner.waitForDataIngested(Duration.ofSeconds(20));
+        runner.stopConnector(Duration.ofSeconds(20));
+    }
+
+    private static void addAsset(final StandardConnectorTestRunner runner, 
final String name, final String contents) {
+        runner.addAsset(name, new 
ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)));
+    }
+
+    private static List<String> readAssetContents(final Path 
instanceDirectory) throws Exception {
+        try (final Stream<Path> paths = 
Files.walk(instanceDirectory.resolve("connector-assets"))) {
+            return paths.filter(Files::isRegularFile)
+                    .sorted()
+                    .map(StandardConnectorTestRunnerIsolationIT::readString)
+                    .toList();
+        }
+    }
+
+    private static String readString(final Path path) {
+        try {
+            return Files.readString(path);
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git 
a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
 
b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
index 186867d7e80..313eb4c8f06 100644
--- 
a/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
+++ 
b/nifi-connector-mock-bundle/nifi-connector-mock/src/main/java/org/apache/nifi/mock/connector/StandardConnectorTestRunner.java
@@ -48,6 +48,7 @@ import java.time.Duration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.TimeoutException;
@@ -55,35 +56,59 @@ import java.util.concurrent.TimeoutException;
 public class StandardConnectorTestRunner implements ConnectorTestRunner, 
Closeable {
     private final File narLibraryDirectory;
     private final int httpPort;
+    private final File instanceDirectory;
 
     private ConnectorMockServer mockServer;
 
     private StandardConnectorTestRunner(final Builder builder) {
         this.narLibraryDirectory = builder.narLibraryDirectory;
         this.httpPort = builder.httpPort;
+        this.instanceDirectory = builder.instanceDirectory;
 
         try {
             bootstrapInstance();
         } catch (final Exception e) {
+            closeQuietly(e);
             throw new RuntimeException("Failed to bootstrap 
ConnectorTestRunner", e);
         }
 
-        // It is important that we register the processor and controller 
service mocks before instantiating the connector.
-        // Otherwise, the call to instantiateConnector will initialize the 
Connector, which may update the flow.
-        // If the flow is updated before the mocks are registered, the 
components will be created without
-        // using the mocks. Subsequent updates to the flow will not replace 
the components already created because
-        // these are not recognized as updates to the flow, since the 
framework assumes that the type of a component
-        // with a given ID does not change.
-        builder.processorMocks.forEach(mockServer::mockProcessor);
-        
builder.controllerServiceMocks.forEach(mockServer::mockControllerService);
+        try {
+            // It is important that we register the processor and controller 
service mocks before instantiating the connector.
+            // Otherwise, the call to instantiateConnector will initialize the 
Connector, which may update the flow.
+            // If the flow is updated before the mocks are registered, the 
components will be created without
+            // using the mocks. Subsequent updates to the flow will not 
replace the components already created because
+            // these are not recognized as updates to the flow, since the 
framework assumes that the type of a component
+            // with a given ID does not change.
+            builder.processorMocks.forEach(mockServer::mockProcessor);
+            
builder.controllerServiceMocks.forEach(mockServer::mockControllerService);
+
+            mockServer.instantiateConnector(builder.connectorClassName);
+        } catch (final RuntimeException e) {
+            closeQuietly(e);
+            throw e;
+        }
+    }
 
-        mockServer.instantiateConnector(builder.connectorClassName);
+    private void closeQuietly(final Exception failure) {
+        try {
+            close();
+        } catch (final RuntimeException e) {
+            failure.addSuppressed(e);
+        }
     }
 
     private void bootstrapInstance() throws IOException, 
ClassNotFoundException {
         final List<Path> libDirectoryPaths = 
List.of(narLibraryDirectory.toPath());
-        final File extensionsWorkingDir = new File("target/work/extensions");
-        final File frameworkWorkingDir = new File("target/work/framework");
+        final File extensionsWorkingDir;
+        final File frameworkWorkingDir;
+        if (instanceDirectory == null) {
+            extensionsWorkingDir = new File("target/work/extensions");
+            frameworkWorkingDir = new File("target/work/framework");
+        } else {
+            final File narWorkingDirectory = new File(instanceDirectory, 
"work");
+            extensionsWorkingDir = new File(narWorkingDirectory, "extensions");
+            frameworkWorkingDir = new File(narWorkingDirectory, "framework");
+        }
 
         final Bundle systemBundle = 
SystemBundle.create(narLibraryDirectory.getAbsolutePath(), 
ClassLoader.getSystemClassLoader());
 
@@ -108,7 +133,7 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
 
         final Set<Bundle> narBundles = narClassLoaders.getBundles();
 
-        final Properties additionalProperties = new Properties();
+        final Properties additionalProperties = 
getInstanceProperties(instanceDirectory);
         if (httpPort >= 0) {
             additionalProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, 
String.valueOf(httpPort));
         }
@@ -118,10 +143,9 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
             properties = 
NiFiProperties.createBasicNiFiProperties(propertiesIn, additionalProperties);
         }
 
-        nifiServer.initialize(properties, systemBundle, narBundles, 
extensionMapping);
-        nifiServer.start();
-
         mockServer = (ConnectorMockServer) nifiServer;
+        mockServer.initialize(properties, systemBundle, narBundles, 
extensionMapping);
+        mockServer.start();
         mockServer.registerMockBundle(getClass().getClassLoader(), new 
File(extensionsWorkingDir, "mock-implementations-bundle"));
     }
 
@@ -244,11 +268,36 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
         return mockServer.getWorkingFlowSnapshot();
     }
 
+    private static Properties getInstanceProperties(final File 
instanceDirectory) {
+        final Properties properties = new Properties();
+        if (instanceDirectory == null) {
+            return properties;
+        }
+
+        properties.setProperty(NiFiProperties.FLOW_CONFIGURATION_FILE, 
resolvePath(instanceDirectory, "conf/flow.json.gz"));
+        properties.setProperty(NiFiProperties.FLOW_CONFIGURATION_ARCHIVE_DIR, 
resolvePath(instanceDirectory, "conf/archive"));
+        properties.setProperty(NiFiProperties.STATE_MANAGEMENT_CONFIG_FILE, 
resolvePath(instanceDirectory, "conf/state-management.xml"));
+        properties.setProperty(NiFiProperties.REPOSITORY_DATABASE_DIRECTORY, 
resolvePath(instanceDirectory, "database_repository"));
+        properties.setProperty(NiFiProperties.FLOWFILE_REPOSITORY_DIRECTORY, 
resolvePath(instanceDirectory, "flowfile_repository"));
+        properties.setProperty(NiFiProperties.REPOSITORY_CONTENT_PREFIX + 
"default", resolvePath(instanceDirectory, "content_repository"));
+        
properties.setProperty(NiFiProperties.NAR_PERSISTENCE_PROVIDER_PROPERTIES_PREFIX
 + "directory", resolvePath(instanceDirectory, "nar_repository"));
+        properties.setProperty(NiFiProperties.ASSET_MANAGER_PREFIX + 
"directory", resolvePath(instanceDirectory, "assets"));
+        properties.setProperty(NiFiProperties.CONNECTOR_ASSET_MANAGER_PREFIX + 
"directory", resolvePath(instanceDirectory, "connector-assets"));
+        properties.setProperty(NiFiProperties.NAR_WORKING_DIRECTORY, 
resolvePath(instanceDirectory, "work"));
+        properties.setProperty(NiFiProperties.NAR_LIBRARY_AUTOLOAD_DIRECTORY, 
resolvePath(instanceDirectory, "autoload"));
+        properties.setProperty(NiFiProperties.WEB_WORKING_DIR, 
resolvePath(instanceDirectory, "work/jetty"));
+        return properties;
+    }
+
+    private static String resolvePath(final File instanceDirectory, final 
String relativePath) {
+        return new File(instanceDirectory, relativePath).getAbsolutePath();
+    }
 
     public static class Builder {
         private String connectorClassName;
         private File narLibraryDirectory;
         private int httpPort = -1;
+        private File instanceDirectory;
         private final Map<String, Class<? extends Processor>> processorMocks = 
new HashMap<>();
         private final Map<String, Class<? extends ControllerService>> 
controllerServiceMocks = new HashMap<>();
 
@@ -267,6 +316,12 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
             return this;
         }
 
+        public Builder instanceDirectory(final File instanceDirectory) {
+            this.instanceDirectory = Objects.requireNonNull(instanceDirectory, 
"Instance Directory required")
+                    .getAbsoluteFile();
+            return this;
+        }
+
         public Builder mockProcessor(final String processorType, final Class<? 
extends Processor> mockProcessorClass) {
             processorMocks.put(processorType, mockProcessorClass);
             return this;
@@ -282,6 +337,10 @@ public class StandardConnectorTestRunner implements 
ConnectorTestRunner, Closeab
                 throw new IllegalArgumentException("NAR file does not exist or 
is not a directory: " + narLibraryDirectory.getAbsolutePath());
             }
 
+            if (instanceDirectory != null && !instanceDirectory.mkdirs() && 
!instanceDirectory.isDirectory()) {
+                throw new IllegalStateException("Failed to create instance 
directory: " + instanceDirectory.getAbsolutePath());
+            }
+
             return new StandardConnectorTestRunner(this);
         }
     }

Reply via email to