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);
}
}