This is an automated email from the ASF dual-hosted git repository. exceptionfactory pushed a commit to branch support/nifi-1.x in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/support/nifi-1.x by this push: new 990d0807c9 NIFI-12041 Converted Groovy tests to Java in nifi-scripting-processors 990d0807c9 is described below commit 990d0807c9cbc19cda4b41998c2ab55da52fd898 Author: dan-s1 <dsti...@gmail.com> AuthorDate: Mon Sep 18 20:37:13 2023 +0000 NIFI-12041 Converted Groovy tests to Java in nifi-scripting-processors This closes #7752 Signed-off-by: David Handermann <exceptionfact...@apache.org> (cherry picked from commit 9b591a2fe374fa21dc4a5778edad904af6a7bbe5) --- .../org/apache/nifi/util/MockProcessContext.java | 2 +- .../nifi-scripting-processors/pom.xml | 1 + .../lookup/script/TestScriptedLookupService.java | 133 +++++++++++++++++ .../script/TestSimpleScriptedLookupService.java | 86 +++++++++++ .../processors/script/ExecuteScriptGroovyTest.java | 126 ++++++++++++++++ .../nifi/record/script/ScriptedReaderTest.java | 150 +++++++++++++++++++ .../record/script/ScriptedRecordSetWriterTest.java | 130 ++++++++++++++++ .../script/ScriptedReportingTaskTest.java | 165 +++++++++++++++++++++ .../src/test/resources/xmlRecord.xml | 17 +++ 9 files changed, 809 insertions(+), 1 deletion(-) diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java b/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java index daa8ceee52..79a73eef50 100644 --- a/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java +++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockProcessContext.java @@ -532,7 +532,7 @@ public class MockProcessContext extends MockControllerServiceLookup implements P return componentName; } - protected void setMaxConcurrentTasks(int maxConcurrentTasks) { + public void setMaxConcurrentTasks(int maxConcurrentTasks) { this.maxConcurrentTasks = maxConcurrentTasks; } diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/pom.xml b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/pom.xml index ec594f3b78..3e81d79002 100644 --- a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/pom.xml +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/pom.xml @@ -165,6 +165,7 @@ <artifactId>apache-rat-plugin</artifactId> <configuration> <excludes combine.children="append"> + <exclude>src/test/resources/xmlRecord.xml</exclude> <exclude>src/test/resources/jython/test_compress.py</exclude> </excludes> </configuration> diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/lookup/script/TestScriptedLookupService.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/lookup/script/TestScriptedLookupService.java new file mode 100644 index 0000000000..43db8be24e --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/lookup/script/TestScriptedLookupService.java @@ -0,0 +1,133 @@ +/* + * 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.lookup.script; + +import org.apache.nifi.processors.script.AccessibleScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentUtils; +import org.apache.nifi.util.NoOpProcessor; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit tests for the ScriptedLookupService controller service + */ +public class TestScriptedLookupService { + @TempDir + private static Path targetPath; + @TempDir + private static Path alternateTargetPath; + private ScriptedLookupService scriptedLookupService; + private TestRunner runner; + + @BeforeAll + public static void setUpOnce() throws Exception { + Files.copy(Paths.get("src/test/resources/groovy/test_lookup_inline.groovy"), targetPath, StandardCopyOption.REPLACE_EXISTING); + Files.copy(Paths.get("src/test/resources/groovy/test_simple_lookup_inline.groovy"), alternateTargetPath, StandardCopyOption.REPLACE_EXISTING); + } + + @BeforeEach + public void setUp() throws Exception { + scriptedLookupService = new MockScriptedLookupService(); + runner = TestRunners.newTestRunner(NoOpProcessor.class); + runner.addControllerService("lookupService", scriptedLookupService); + runner.setProperty(scriptedLookupService, "Script Engine", "Groovy"); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.SCRIPT_FILE, targetPath.toString()); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.SCRIPT_BODY, (String) null); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.MODULES, (String) null); + runner.enableControllerService(scriptedLookupService); + } + + @Test + void testLookupServiceGroovyScript() throws Exception { + Map<String, Object> map = new LinkedHashMap<>(1); + map.put("key", "Hello"); + Optional<Object> opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("Hi", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "World"); + opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("there", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "Not There"); + opt = scriptedLookupService.lookup(map); + assertFalse(opt.isPresent()); + } + + @Test + void testLookupServiceScriptReload() throws Exception { + Map<String, Object> map = new LinkedHashMap<>(1); + map.put("key", "Hello"); + Optional<Object> opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("Hi", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "World"); + opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("there", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "Not There"); + opt = scriptedLookupService.lookup(map); + assertFalse(opt.isPresent()); + + // Disable and load different script + runner.disableControllerService(scriptedLookupService); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.SCRIPT_FILE, alternateTargetPath.toString()); + runner.enableControllerService(scriptedLookupService); + + map = new LinkedHashMap<>(1); + map.put("key", "Hello"); + opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("Goodbye", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "World"); + opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("Stranger", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "Not There"); + opt = scriptedLookupService.lookup(map); + assertFalse(opt.isPresent()); + } + + public static class MockScriptedLookupService extends ScriptedLookupService implements AccessibleScriptingComponentHelper { + @Override + public ScriptingComponentHelper getScriptingComponentHelper() { + return this.scriptingComponentHelper; + } + } +} diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/lookup/script/TestSimpleScriptedLookupService.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/lookup/script/TestSimpleScriptedLookupService.java new file mode 100644 index 0000000000..5324f241f6 --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/lookup/script/TestSimpleScriptedLookupService.java @@ -0,0 +1,86 @@ +/* + * 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.lookup.script; + +import org.apache.nifi.processors.script.AccessibleScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentUtils; +import org.apache.nifi.util.NoOpProcessor; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +/** + * Unit tests for the SimpleScriptedLookupService controller service + */ +public class TestSimpleScriptedLookupService { + @TempDir + private static Path targetPath; + + @BeforeAll + public static void setUpOnce() throws Exception { + Files.copy(Paths.get("src/test/resources/groovy/test_lookup_inline.groovy"), targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + @Test + void testSimpleLookupServiceGroovyScript() throws Exception { + final TestRunner runner = TestRunners.newTestRunner(NoOpProcessor.class); + SimpleScriptedLookupService scriptedLookupService = new MockScriptedLookupService(); + runner.addControllerService("lookupService", scriptedLookupService); + runner.setProperty(scriptedLookupService, "Script Engine", "Groovy"); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.SCRIPT_FILE, targetPath.toString()); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.SCRIPT_BODY, (String) null); + runner.setProperty(scriptedLookupService, ScriptingComponentUtils.MODULES, (String) null); + runner.enableControllerService(scriptedLookupService); + + Map<String, Object> map = new LinkedHashMap<>(1); + map.put("key", "Hello"); + Optional<String> opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("Hi", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "World"); + opt = scriptedLookupService.lookup(map); + assertTrue(opt.isPresent()); + assertEquals("there", opt.get()); + map = new LinkedHashMap<>(1); + map.put("key", "Not There"); + opt = scriptedLookupService.lookup(map); + assertFalse(opt.isPresent()); + } + + public static class MockScriptedLookupService extends SimpleScriptedLookupService implements AccessibleScriptingComponentHelper { + @Override + public ScriptingComponentHelper getScriptingComponentHelper() { + return this.scriptingComponentHelper; + } + + } +} diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/processors/script/ExecuteScriptGroovyTest.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/processors/script/ExecuteScriptGroovyTest.java new file mode 100644 index 0000000000..7f0fa2509e --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/processors/script/ExecuteScriptGroovyTest.java @@ -0,0 +1,126 @@ +/* + * 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.processors.script; + +import org.apache.nifi.script.ScriptingComponentUtils; +import org.apache.nifi.util.MockFlowFile; +import org.apache.nifi.util.MockProcessContext; +import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExecuteScriptGroovyTest extends BaseScriptTest { + private static final Pattern SINGLE_POOL_THREAD_PATTERN = Pattern.compile("pool-\\d+-thread-1"); + + @BeforeEach + public void setUp() throws Exception { + super.setupExecuteScript(); + runner.setValidateExpressionUsage(false); + runner.setProperty(scriptingComponent.getScriptingComponentHelper().SCRIPT_ENGINE, "Groovy"); + runner.setProperty(ScriptingComponentUtils.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "groovy/testAddTimeAndThreadAttribute.groovy"); + runner.setProperty(ScriptingComponentUtils.MODULES, TEST_RESOURCE_LOCATION + "groovy"); + } + + @Test + void testShouldExecuteScript() { + runner.assertValid(); + + runner.run(); + + runner.assertAllFlowFilesTransferred(ExecuteScript.REL_SUCCESS, 1); + MockFlowFile flowFile = runner.getFlowFilesForRelationship(ExecuteScript.REL_SUCCESS).get(0); + flowFile.assertAttributeExists("time-updated"); + flowFile.assertAttributeExists("thread"); + assertTrue(SINGLE_POOL_THREAD_PATTERN.matcher(flowFile.getAttribute("thread")).find()); + } + + @Test + void testShouldExecuteScriptSerially() { + final int iterations = 10; + runner.assertValid(); + + runner.run(iterations); + + runner.assertAllFlowFilesTransferred(ExecuteScript.REL_SUCCESS, iterations); + runner.getFlowFilesForRelationship(ExecuteScript.REL_SUCCESS).forEach( flowFile -> { + flowFile.assertAttributeExists("time-updated"); + flowFile.assertAttributeExists("thread"); + assertTrue(SINGLE_POOL_THREAD_PATTERN.matcher(flowFile.getAttribute("thread")).find()); + }); + } + + @Test + void testShouldExecuteScriptWithPool() { + final int iterations = 10; + final int poolSize = 2; + + setupPooledExecuteScript(poolSize); + runner.setThreadCount(poolSize); + runner.assertValid(); + + runner.run(iterations); + + runner.assertAllFlowFilesTransferred(ExecuteScript.REL_SUCCESS, iterations); + runner.getFlowFilesForRelationship(ExecuteScript.REL_SUCCESS).forEach(flowFile -> { + flowFile.assertAttributeExists("time-updated"); + flowFile.assertAttributeExists("thread"); + assertTrue((Pattern.compile("pool-\\d+-thread-[1-" + poolSize + "]").matcher(flowFile.getAttribute("thread"))).find()); + }); + } + + @Test + void testExecuteScriptRecompileOnChange() { + + runner.setProperty(ScriptingComponentUtils.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "groovy/setAttributeHello_executescript.groovy"); + runner.enqueue(""); + runner.run(); + + runner.assertAllFlowFilesTransferred(ExecuteScript.REL_SUCCESS, 1); + MockFlowFile flowFile = runner.getFlowFilesForRelationship(ExecuteScript.REL_SUCCESS).get(0); + flowFile.assertAttributeExists("greeting"); + flowFile.assertAttributeEquals("greeting", "hello"); + runner.clearTransferState(); + + runner.setProperty(ScriptingComponentUtils.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "groovy/setAttributeGoodbye_executescript.groovy"); + runner.enqueue(""); + runner.run(); + + runner.assertAllFlowFilesTransferred(ExecuteScript.REL_SUCCESS, 1); + flowFile = runner.getFlowFilesForRelationship(ExecuteScript.REL_SUCCESS).get(0); + flowFile.assertAttributeExists("greeting"); + flowFile.assertAttributeEquals("greeting", "good-bye"); + } + + private void setupPooledExecuteScript(int poolSize) { + final ExecuteScript executeScript = new ExecuteScript(); + // Need to do something to initialize the properties, like retrieve the list of properties + assertNotNull(executeScript.getSupportedPropertyDescriptors()); + runner = TestRunners.newTestRunner(executeScript); + runner.setValidateExpressionUsage(false); + runner.setProperty(scriptingComponent.getScriptingComponentHelper().SCRIPT_ENGINE, "Groovy"); + runner.setProperty(ScriptingComponentUtils.SCRIPT_FILE, TEST_RESOURCE_LOCATION + "groovy/testAddTimeAndThreadAttribute.groovy"); + runner.setProperty(ScriptingComponentUtils.MODULES, TEST_RESOURCE_LOCATION + "groovy"); + + // Override userContext value + ((MockProcessContext)runner.getProcessContext()).setMaxConcurrentTasks(poolSize); + } +} diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/record/script/ScriptedReaderTest.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/record/script/ScriptedReaderTest.java new file mode 100644 index 0000000000..682e1e3640 --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/record/script/ScriptedReaderTest.java @@ -0,0 +1,150 @@ +/* + * 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.record.script; + +import org.apache.nifi.processors.script.AccessibleScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentUtils; +import org.apache.nifi.serialization.RecordReader; +import org.apache.nifi.serialization.record.Record; +import org.apache.nifi.util.MockComponentLog; +import org.apache.nifi.util.NoOpProcessor; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +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.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Unit tests for the ScriptedReader class + */ +class ScriptedReaderTest { + private static final String SOURCE_DIR = "src/test/resources"; + private static final String GROOVY_DIR = "groovy"; + private static Path tempJar; + @TempDir + private Path targetScriptFile; + private ScriptedReader recordReaderFactory; + private TestRunner runner; + + @BeforeAll + public static void before() throws IOException { + tempJar = File.createTempFile("test-jar", null).toPath(); + } + + @AfterAll + public static void after() { + tempJar.toFile().delete(); + } + + @BeforeEach + public void setUp() throws Exception { + recordReaderFactory = new MockScriptedReader(); + runner = TestRunners.newTestRunner(NoOpProcessor.class); + runner.addControllerService("reader", recordReaderFactory); + runner.setProperty(recordReaderFactory, "Script Engine", "Groovy"); + runner.setProperty(recordReaderFactory, ScriptingComponentUtils.SCRIPT_BODY, (String) null); + runner.setProperty(recordReaderFactory, ScriptingComponentUtils.MODULES, (String) null); + } + + @Test + void testRecordReaderGroovyScript() throws Exception { + Files.copy(Paths.get(SOURCE_DIR, GROOVY_DIR, "test_record_reader_inline.groovy"), targetScriptFile, StandardCopyOption.REPLACE_EXISTING); + runner.setProperty(recordReaderFactory, ScriptingComponentUtils.SCRIPT_FILE, targetScriptFile.toString()); + runner.enableControllerService(recordReaderFactory); + byte[] contentBytes = "Flow file content not used".getBytes(); + InputStream inStream = new ByteArrayInputStream(contentBytes); + + final RecordReader recordReader = + recordReaderFactory.createRecordReader(Collections.emptyMap(), inStream, contentBytes.length, new MockComponentLog("id", recordReaderFactory)); + assertNotNull(recordReader); + + for(int index = 0; index < 3; index++) { + Record record = recordReader.nextRecord(); + assertNotNull(record); + assertEquals(record.getAsInt("code"), record.getAsInt("id") * 100); + } + assertNull(recordReader.nextRecord()); + } + + @Test + void testXmlRecordReaderGroovyScript() throws Exception { + Files.copy(Paths.get(SOURCE_DIR, GROOVY_DIR, "test_record_reader_xml.groovy"), targetScriptFile, StandardCopyOption.REPLACE_EXISTING); + runner.setProperty(recordReaderFactory, ScriptingComponentUtils.SCRIPT_FILE, targetScriptFile.toString()); + String schemaText = "\n[\n{\"id\": \"int\"},\n{\"name\": \"string\"},\n{\"code\": \"int\"}\n]\n"; + runner.setProperty(recordReaderFactory, "schema.text", schemaText); + runner.enableControllerService(recordReaderFactory); + + Map<String, String> map = new LinkedHashMap<>(1); + map.put("record.tag", "myRecord"); + byte[] contentBytes = Files.readAllBytes(Paths.get("src/test/resources/xmlRecord.xml")); + InputStream inStream = new ByteArrayInputStream(contentBytes); + final RecordReader recordReader = recordReaderFactory.createRecordReader(map, inStream, contentBytes.length, new MockComponentLog("ScriptedReader", "")); + assertNotNull(recordReader); + + for(int index = 0; index < 3; index++) { + Record record = recordReader.nextRecord(); + assertNotNull(record); + assertEquals(record.getAsInt("code"), record.getAsInt("id") * 100); + } + assertNull(recordReader.nextRecord()); + } + + @Test + void testRecordReaderGroovyScriptChangeModuleDirectory() throws Exception { + Files.copy(Paths.get(SOURCE_DIR, GROOVY_DIR, "test_record_reader_load_module.groovy"), targetScriptFile, StandardCopyOption.REPLACE_EXISTING); + runner.setProperty(recordReaderFactory, ScriptingComponentUtils.SCRIPT_FILE, targetScriptFile.toString()); + + assertThrows(Throwable.class, () -> runner.enableControllerService(recordReaderFactory)); + + Files.copy(Paths.get(SOURCE_DIR, "jar", "test.jar"), tempJar, StandardCopyOption.REPLACE_EXISTING); + runner.setProperty(recordReaderFactory, ScriptingComponentUtils.MODULES, tempJar.toString()); + runner.enableControllerService(recordReaderFactory); + byte[] contentBytes = "Flow file content not used".getBytes(); + InputStream inStream = new ByteArrayInputStream(contentBytes); + + final RecordReader recordReader = + recordReaderFactory.createRecordReader(Collections.emptyMap(), inStream, contentBytes.length, new MockComponentLog("id", recordReaderFactory)); + assertNotNull(recordReader); + } + + public static class MockScriptedReader extends ScriptedReader implements AccessibleScriptingComponentHelper { + @Override + public ScriptingComponentHelper getScriptingComponentHelper() { + return this.scriptingComponentHelper; + } + } +} diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/record/script/ScriptedRecordSetWriterTest.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/record/script/ScriptedRecordSetWriterTest.java new file mode 100644 index 0000000000..b03fc5d956 --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/record/script/ScriptedRecordSetWriterTest.java @@ -0,0 +1,130 @@ +/* + * 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.record.script; + +import org.apache.nifi.processor.AbstractProcessor; +import org.apache.nifi.processor.ProcessContext; +import org.apache.nifi.processor.ProcessSession; +import org.apache.nifi.processor.exception.ProcessException; +import org.apache.nifi.processors.script.AccessibleScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentUtils; +import org.apache.nifi.serialization.RecordSetWriter; +import org.apache.nifi.serialization.SimpleRecordSchema; +import org.apache.nifi.serialization.record.MapRecord; +import org.apache.nifi.serialization.record.RecordField; +import org.apache.nifi.serialization.record.RecordFieldType; +import org.apache.nifi.serialization.record.RecordSchema; +import org.apache.nifi.serialization.record.RecordSet; +import org.apache.nifi.util.MockComponentLog; +import org.apache.nifi.util.TestRunner; +import org.apache.nifi.util.TestRunners; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Unit tests for the ScriptedReader class + */ +public class ScriptedRecordSetWriterTest { + @TempDir + private static Path targetPath; + + @BeforeAll + public static void setUpOnce() throws Exception { + Files.copy(Paths.get("src/test/resources/groovy/test_record_writer_inline.groovy"), targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + @Test + void testRecordWriterGroovyScript() throws Exception { + final TestRunner runner = TestRunners.newTestRunner(new AbstractProcessor() { + @Override + public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException { + } + }); + + MockScriptedWriter recordSetWriterFactory = new MockScriptedWriter(); + runner.addControllerService("writer", recordSetWriterFactory); + runner.setProperty(recordSetWriterFactory, "Script Engine", "Groovy"); + runner.setProperty(recordSetWriterFactory, ScriptingComponentUtils.SCRIPT_FILE, targetPath.toString()); + runner.setProperty(recordSetWriterFactory, ScriptingComponentUtils.SCRIPT_BODY, (String) null); + runner.setProperty(recordSetWriterFactory, ScriptingComponentUtils.MODULES, (String) null); + runner.enableControllerService(recordSetWriterFactory); + + RecordSchema schema = recordSetWriterFactory.getSchema(Collections.emptyMap(), null); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + RecordSetWriter recordSetWriter = recordSetWriterFactory.createWriter(new MockComponentLog("id", recordSetWriterFactory), schema, outputStream, Collections.emptyMap()); + assertNotNull(recordSetWriter); + + SimpleRecordSchema recordSchema = new SimpleRecordSchema(Arrays.asList(new RecordField("id", RecordFieldType.INT.getDataType()), + new RecordField("name", RecordFieldType.STRING.getDataType()), + new RecordField("code", RecordFieldType.INT.getDataType()))); + MapRecord [] records = createMapRecords(recordSchema); + + recordSetWriter.write(RecordSet.of(recordSchema, records)); + + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = documentBuilder.parse(new ByteArrayInputStream(outputStream.toByteArray())); + XPathFactory xpathfactory = XPathFactory.newInstance(); + XPath xpath = xpathfactory.newXPath(); + assertEquals("1", xpath.evaluate("//record[1]/id/text()", document)); + assertEquals("200", xpath.evaluate("//record[2]/code/text()", document)); + assertEquals("Ramon", xpath.evaluate("//record[3]/name/text()", document)); + } + + private static MapRecord[] createMapRecords(SimpleRecordSchema recordSchema) { + Map<String, Object> map = new LinkedHashMap<>(3); + map.put("id", 1); + map.put("name", "John"); + map.put("code", 100); + Map<String, Object> map1 = new LinkedHashMap<>(3); + map1.put("id", 2); + map1.put("name", "Mary"); + map1.put("code", 200); + Map<String, Object> map2 = new LinkedHashMap<>(3); + map2.put("id", 3); + map2.put("name", "Ramon"); + map2.put("code", 300); + + return new MapRecord[]{new MapRecord(recordSchema, map), new MapRecord(recordSchema, map1), new MapRecord(recordSchema, map2)}; + } + public static class MockScriptedWriter extends ScriptedRecordSetWriter implements AccessibleScriptingComponentHelper { + @Override + public ScriptingComponentHelper getScriptingComponentHelper() { + return this.scriptingComponentHelper; + } + } +} diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/reporting/script/ScriptedReportingTaskTest.java b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/reporting/script/ScriptedReportingTaskTest.java new file mode 100644 index 0000000000..5de5aaf842 --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/java/org/apache/nifi/reporting/script/ScriptedReportingTaskTest.java @@ -0,0 +1,165 @@ +/* + * 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.reporting.script; + +import org.apache.nifi.components.PropertyDescriptor; +import org.apache.nifi.controller.ConfigurationContext; +import org.apache.nifi.logging.ComponentLog; +import org.apache.nifi.processors.script.AccessibleScriptingComponentHelper; +import org.apache.nifi.processors.script.ScriptRunner; +import org.apache.nifi.provenance.ProvenanceEventRecord; +import org.apache.nifi.registry.VariableRegistry; +import org.apache.nifi.reporting.ReportingInitializationContext; +import org.apache.nifi.script.ScriptingComponentHelper; +import org.apache.nifi.script.ScriptingComponentUtils; +import org.apache.nifi.util.MockConfigurationContext; +import org.apache.nifi.util.MockEventAccess; +import org.apache.nifi.util.MockReportingContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.script.ScriptEngine; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit tests for ScriptedReportingTask. + */ +@ExtendWith(MockitoExtension.class) +class ScriptedReportingTaskTest { + private static final String SCRIPT_ENGINE = "Script Engine"; + private static final PropertyDescriptor SCRIPT_ENGINE_PROPERTY_DESCRIPTOR = new PropertyDescriptor.Builder().name(SCRIPT_ENGINE).build(); + private static final String GROOVY = "Groovy"; + @TempDir + private Path targetPath; + @Mock + private ReportingInitializationContext initContext; + private MockScriptedReportingTask task; + private Map<PropertyDescriptor, String> properties; + private ConfigurationContext configurationContext; + private MockReportingContext reportingContext; + + @BeforeEach + public void setUp(@Mock ComponentLog logger) { + task = new MockScriptedReportingTask(); + properties = new HashMap<>(); + configurationContext = new MockConfigurationContext(properties, null); + reportingContext = new MockReportingContext(new LinkedHashMap<>(), null, VariableRegistry.EMPTY_REGISTRY); + when(initContext.getIdentifier()).thenReturn(UUID.randomUUID().toString()); + when(initContext.getLogger()).thenReturn(logger); + } + + @Test + void testProvenanceGroovyScript() throws Exception { + properties.put(SCRIPT_ENGINE_PROPERTY_DESCRIPTOR, GROOVY); + Files.copy(Paths.get("src/test/resources/groovy/test_log_provenance_events.groovy"), targetPath, StandardCopyOption.REPLACE_EXISTING); + properties.put(ScriptingComponentUtils.SCRIPT_FILE, targetPath.toString()); + reportingContext.setProperty(SCRIPT_ENGINE, GROOVY); + reportingContext.setProperty(ScriptingComponentUtils.SCRIPT_FILE.getName(), targetPath.toString()); + + final MockEventAccess eventAccess = reportingContext.getEventAccess(); + for (long index = 1; index < 4; index++) { + final ProvenanceEventRecord event = mock(ProvenanceEventRecord.class); + doReturn(index).when(event).getEventId(); + if(index == 1) { + doReturn("1234").when(event).getComponentId(); + Map<String, String> map = new LinkedHashMap<>(1); + map.put("abc", "xyz"); + doReturn(map).when(event).getAttributes(); + } + eventAccess.addProvenanceEvent(event); + } + + run(); + + // This script should return a variable x with the number of events and a variable e with the first event + ScriptEngine se = task.getScriptRunner().getScriptEngine(); + assertEquals(3, se.get("x")); + ProvenanceEventRecord per = (ProvenanceEventRecord)se.get("e"); + assertEquals("1234", per.getComponentId()); + assertEquals("xyz",per.getAttributes().get("abc")); + } + + @Test + void testVMEventsGroovyScript() throws Exception { + properties.put(SCRIPT_ENGINE_PROPERTY_DESCRIPTOR, GROOVY); + Files.copy(Paths.get("src/test/resources/groovy/test_log_vm_stats.groovy"), targetPath, StandardCopyOption.REPLACE_EXISTING); + properties.put(ScriptingComponentUtils.SCRIPT_FILE, targetPath.toString()); + reportingContext.setProperty(SCRIPT_ENGINE, GROOVY); + reportingContext.setProperty(ScriptingComponentUtils.SCRIPT_FILE.getName(), targetPath.toString()); + + run(); + + // This script should store a variable called x with a map of stats to values + ScriptEngine se = task.getScriptRunner().getScriptEngine(); + @SuppressWarnings("unchecked") + final Map<String, Long> x = (Map<String, Long>)se.get("x"); + assertTrue(x.get("uptime") >= 0); + } + + @Test + void testVMEventsJythonScript() throws Exception { + properties.put(SCRIPT_ENGINE_PROPERTY_DESCRIPTOR, "python"); + Files.copy(Paths.get("src/test/resources/jython/test_log_vm_stats.py"), targetPath, StandardCopyOption.REPLACE_EXISTING); + properties.put(ScriptingComponentUtils.SCRIPT_FILE, targetPath.toString()); + reportingContext.setProperty(SCRIPT_ENGINE, "python"); + reportingContext.setProperty(ScriptingComponentUtils.SCRIPT_FILE.getName(), targetPath.toString()); + + run(); + + // This script should store a variable called x with a map of stats to values + ScriptEngine se = task.getScriptRunner().getScriptEngine(); + @SuppressWarnings("unchecked") + final Map<String, BigInteger> x = (Map<String, BigInteger>)se.get("x"); + assertTrue(x.get("uptime").longValue() >= 0); + } + + private void run() throws Exception { + task.initialize(initContext); + task.getSupportedPropertyDescriptors(); + task.setup(configurationContext); + task.onTrigger(reportingContext); + } + + public static class MockScriptedReportingTask extends ScriptedReportingTask implements AccessibleScriptingComponentHelper { + public ScriptRunner getScriptRunner() { + return getScriptingComponentHelper().scriptRunnerQ.poll(); + } + + @Override + public ScriptingComponentHelper getScriptingComponentHelper() { + return this.scriptingComponentHelper; + } + } +} diff --git a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/xmlRecord.xml b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/xmlRecord.xml new file mode 100644 index 0000000000..f7ae65afcb --- /dev/null +++ b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/xmlRecord.xml @@ -0,0 +1,17 @@ +<root> + <myRecord> + <id>1</id> + <name>John</name> + <code>100</code> + </myRecord> + <myRecord> + <id>2</id> + <name>Mary</name> + <code>200</code> + </myRecord> + <myRecord> + <id>3</id> + <name>Ramon</name> + <code>300</code> + </myRecord> +</root> \ No newline at end of file