This is an automated email from the ASF dual-hosted git repository.
bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new e32491e99b Issue #2420 : Allow custom pipeline unit test types (#8676)
e32491e99b is described below
commit e32491e99b64159015c46ae0e7fa17cd511dc227
Author: Matt Casters <[email protected]>
AuthorDate: Thu Oct 1 09:08:00 2026 +0200
Issue #2420 : Allow custom pipeline unit test types (#8676)
---
.../pages/metadata-types/pipeline-unit-test.adoc | 2 +-
.../ROOT/pages/pipeline/pipeline-unit-testing.adoc | 2 +-
.../ROOT/pages/pipeline/transforms/exectests.adoc | 2 +-
.../org/apache/hop/testing/PipelineUnitTest.java | 10 +-
.../PipelineUnitTestSearchableAnalyser.java | 8 +-
.../main/java/org/apache/hop/testing/TestType.java | 23 --
.../testing/transforms/exectests/ExecuteTests.java | 4 +-
.../transforms/exectests/ExecuteTestsDialog.java | 4 +-
.../transforms/exectests/ExecuteTestsMeta.java | 7 +-
.../org/apache/hop/testing/util/DataSetConst.java | 118 ++++++++--
.../xp/HopGuiUnitTestCreateBeforeDialog.java | 4 +-
.../hop/ui/testing/PipelineUnitTestEditor.java | 4 +-
.../exectests/messages/messages_en_US.properties | 1 +
.../ui/testing/messages/messages_en_US.properties | 1 +
.../hop/testing/PipelineUnitTestTypeTest.java | 255 +++++++++++++++++++++
15 files changed, 373 insertions(+), 72 deletions(-)
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/pipeline-unit-test.adoc
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/pipeline-unit-test.adoc
index 4956235643..76055c7efc 100644
---
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/pipeline-unit-test.adoc
+++
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/pipeline-unit-test.adoc
@@ -48,7 +48,7 @@ None/All
|Option|Default|Description
|Name||the name to use for this unit test
|Description||the description to use for this unit test
-|Type of test|Development|'Development' or 'unit test'
+|Type of test|Development|Development, Unit test, or a custom type. The list
includes every type already used by unit tests in the project. Development and
Unit test are stored as DEVELOPMENT and UNIT_TEST; any other type is stored as
entered.
|The pipeline to test||the pipeline that will be tested by this unit test
|Test pipeline filename (Optional)||
|Base test path (or use HOP_UNIT_TESTS_FOLDER)||
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
index 5094c5ec5b..90ab0d9ebe 100644
---
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
+++
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/pipeline-unit-testing.adoc
@@ -139,7 +139,7 @@ The options in this dialog are:
|===
|Name|name to use for this unit test
|Description|a description for this unit test
-|Type of test|'Unit test' or 'Development'
+|Type of test|Development, Unit test, or a custom type. The list includes
every type already used in the project. Type a new value to add one.
Development and Unit test are stored as DEVELOPMENT and UNIT_TEST.
|The pipeline to test|the pipeline this test applies to.
By default, you should see the active pipeline filename here.
|Test pipeline filename (Optional)|the filename to use for this unit test.
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/exectests.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/exectests.adoc
index 1ea0db689d..5beb2feaeb 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/exectests.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/exectests.adoc
@@ -33,7 +33,7 @@ The Execute unit tests transform fetches and executes the
available xref:pipelin
|Transform name|name for this transform
|Test name input field|name of a field to get the unit test name from to
determine which transforms to execute.
This option is only available when the transform receives input.
-|Type of tests to run|Development or Unit Test
+|Type of tests to run|Development, Unit test, or a custom type from the
project. Only unit tests of that type are executed. The list includes every
type already used in the project.
|Pipeline output field name|the pipeline that is tested
|Unit test output field name|the unit test that is executed
|Data set output field name|the (golden) data set used for this test
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTest.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTest.java
index 91ed6c8526..b9dd2c041a 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTest.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTest.java
@@ -68,7 +68,7 @@ public class PipelineUnitTest extends HopMetadataBase
implements Cloneable, IHop
protected List<PipelineUnitTestTweak> tweaks;
@HopMetadataProperty(key = "test_type")
- protected TestType type;
+ protected String type;
@HopMetadataProperty(key = "persist_filename")
protected String filename;
@@ -86,7 +86,7 @@ public class PipelineUnitTest extends HopMetadataBase
implements Cloneable, IHop
inputDataSets = new ArrayList<>();
goldenDataSets = new ArrayList<>();
tweaks = new ArrayList<>();
- type = TestType.DEVELOPMENT;
+ type = DataSetConst.TEST_TYPE_DEVELOPMENT;
databaseReplacements = new ArrayList<>();
variableValues = new ArrayList<>();
basePath = "${" + DataSetConst.VARIABLE_HOP_UNIT_TESTS_FOLDER + "}";
@@ -100,7 +100,7 @@ public class PipelineUnitTest extends HopMetadataBase
implements Cloneable, IHop
List<PipelineUnitTestSetLocation> inputDataSets,
List<PipelineUnitTestSetLocation> goldenDataSets,
List<PipelineUnitTestTweak> tweaks,
- TestType type,
+ String type,
String filename,
List<PipelineUnitTestDatabaseReplacement> databaseReplacements,
boolean autoOpening) {
@@ -390,14 +390,14 @@ public class PipelineUnitTest extends HopMetadataBase
implements Cloneable, IHop
*
* @return value of type
*/
- public TestType getType() {
+ public String getType() {
return type;
}
/**
* @param type The type to set
*/
- public void setType(TestType type) {
+ public void setType(String type) {
this.type = type;
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTestSearchableAnalyser.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTestSearchableAnalyser.java
index 895a4fd6b9..a74e9144d8 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTestSearchableAnalyser.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/PipelineUnitTestSearchableAnalyser.java
@@ -47,13 +47,7 @@ public class PipelineUnitTestSearchableAnalyser
matchProperty(searchable, results, searchQuery, "name",
unitTest.getName(), null);
matchProperty(searchable, results, searchQuery, "description",
unitTest.getDescription(), null);
- matchProperty(
- searchable,
- results,
- searchQuery,
- "type",
- unitTest.getType() != null ? unitTest.getType().name() : null,
- null);
+ matchProperty(searchable, results, searchQuery, "type",
unitTest.getType(), null);
matchProperty(
searchable,
results,
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/TestType.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/TestType.java
deleted file mode 100644
index f7d0e1a54e..0000000000
--- a/plugins/misc/testing/src/main/java/org/apache/hop/testing/TestType.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.hop.testing;
-
-public enum TestType {
- DEVELOPMENT,
- UNIT_TEST;
-}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTests.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTests.java
index 4e2ac2d878..43fc1bf826 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTests.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTests.java
@@ -32,6 +32,7 @@ import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.testing.PipelineUnitTest;
import org.apache.hop.testing.UnitTestResult;
import org.apache.hop.testing.gui.TestingGuiPlugin;
+import org.apache.hop.testing.util.DataSetConst;
import org.apache.hop.testing.util.UnitTestUtil;
public class ExecuteTests extends BaseTransform<ExecuteTestsMeta,
ExecuteTestsData> {
@@ -119,8 +120,7 @@ public class ExecuteTests extends
BaseTransform<ExecuteTestsMeta, ExecuteTestsDa
data.tests = new ArrayList<>();
for (String testName : testSerializer.listObjectNames()) {
PipelineUnitTest pipelineUnitTest = testSerializer.load(testName);
- if (meta.getTypeToExecute() == null
- || meta.getTypeToExecute() == pipelineUnitTest.getType()) {
+ if (DataSetConst.matchesTestType(meta.getTypeToExecute(),
pipelineUnitTest.getType())) {
data.tests.add(pipelineUnitTest);
}
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsDialog.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsDialog.java
index 3ed816b4bd..b7245a5a82 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsDialog.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsDialog.java
@@ -145,7 +145,9 @@ public class ExecuteTestsDialog extends BaseTransformDialog
{
fdlTypeToExecute.top = new FormAttachment(lastControl, margin);
wlTypeToExecute.setLayoutData(fdlTypeToExecute);
wTypeToExecute = new Combo(wContent, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
- wTypeToExecute.setItems(DataSetConst.getTestTypeDescriptions());
+
wTypeToExecute.setItems(DataSetConst.getTestTypeDescriptions(metadataProvider));
+ wTypeToExecute.setToolTipText(
+ BaseMessages.getString(PKG,
"ExecuteTestsDialog.TypeToExecute.Tooltip"));
PropsUi.setLook(wTypeToExecute);
FormData fdTypeToExecute = new FormData();
fdTypeToExecute.left = new FormAttachment(middle, 0);
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsMeta.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsMeta.java
index 7555845e2f..d5655b4996 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsMeta.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/transforms/exectests/ExecuteTestsMeta.java
@@ -24,7 +24,6 @@ import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.pipeline.transform.BaseTransformMeta;
import org.apache.hop.pipeline.transform.TransformMeta;
-import org.apache.hop.testing.TestType;
import org.apache.hop.testing.UnitTestResult;
@Transform(
@@ -50,7 +49,7 @@ public class ExecuteTestsMeta extends
BaseTransformMeta<ExecuteTests, ExecuteTes
private String testNameInputField;
@HopMetadataProperty(key = TAG_TYPE_TO_EXECUTE)
- private TestType typeToExecute;
+ private String typeToExecute;
@HopMetadataProperty(key = TAG_PIPELINE_NAME_FIELD)
private String pipelineNameField;
@@ -127,14 +126,14 @@ public class ExecuteTestsMeta extends
BaseTransformMeta<ExecuteTests, ExecuteTes
*
* @return value of typeToExecute
*/
- public TestType getTypeToExecute() {
+ public String getTypeToExecute() {
return typeToExecute;
}
/**
* @param typeToExecute The typeToExecute to set
*/
- public void setTypeToExecute(TestType typeToExecute) {
+ public void setTypeToExecute(String typeToExecute) {
this.typeToExecute = typeToExecute;
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/util/DataSetConst.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/util/DataSetConst.java
index 085104d1f6..a12677c160 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/util/DataSetConst.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/util/DataSetConst.java
@@ -23,6 +23,7 @@ import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -32,12 +33,14 @@ import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopRuntimeException;
import org.apache.hop.core.exception.HopValueException;
import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.logging.LogChannel;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.api.IHopMetadataSerializer;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.engine.IPipelineEngine;
import org.apache.hop.testing.DataSet;
@@ -45,7 +48,6 @@ import org.apache.hop.testing.PipelineTweak;
import org.apache.hop.testing.PipelineUnitTest;
import org.apache.hop.testing.PipelineUnitTestFieldMapping;
import org.apache.hop.testing.PipelineUnitTestSetLocation;
-import org.apache.hop.testing.TestType;
import org.apache.hop.testing.UnitTestResult;
import org.apache.hop.testing.xp.RowCollection;
@@ -111,6 +113,14 @@ public class DataSetConst {
BaseMessages.getString(PKG,
"DataSetConst.Tweak.REMOVE_TRANSFORM.Desc"),
};
+ /**
+ * Legacy stored codes. Dialogs show the translated label and write this
code back. Any other type
+ * is stored exactly as entered.
+ */
+ public static final String TEST_TYPE_DEVELOPMENT = "DEVELOPMENT";
+
+ public static final String TEST_TYPE_UNIT_TEST = "UNIT_TEST";
+
private static final String[] testTypeDesc =
new String[] {
BaseMessages.getString(PKG, "DataSetConst.TestType.DEVELOPMENT.Desc"),
@@ -602,40 +612,100 @@ public class DataSetConst {
return outputRowMeta;
}
- public static String getTestTypeDescription(TestType testType) {
- int index = 0; // DEVELOPMENT
- if (testType != null) {
- TestType[] testTypes = TestType.values();
- for (int i = 0; i < testTypes.length; i++) {
- if (testTypes[i] == testType) {
- index = i;
- break;
- }
- }
+ /**
+ * Dialog label for a stored test type. {@code DEVELOPMENT}, {@code
UNIT_TEST}, and a missing type
+ * use the translated labels. Every other value is shown unchanged.
+ *
+ * @param testType stored type, or null
+ * @return label to put in the combo
+ */
+ public static String getTestTypeDescription(String testType) {
+ if (TEST_TYPE_UNIT_TEST.equals(testType)) {
+ return testTypeDesc[1];
}
-
- return testTypeDesc[index];
+ if (StringUtils.isEmpty(testType) ||
TEST_TYPE_DEVELOPMENT.equals(testType)) {
+ return testTypeDesc[0];
+ }
+ return testType;
}
/**
- * Get the TestType for a tweak description (from the dialog)
+ * Stored type for a dialog label. The Development and Unit test labels map
back to {@code
+ * DEVELOPMENT} and {@code UNIT_TEST}. An empty label maps to {@code
DEVELOPMENT}. Anything else
+ * is stored as entered.
*
- * @param testTypeDescription The description to look for
- * @return the test type or NONE if nothing matched
+ * @param testTypeDescription label from the combo
+ * @return value written to metadata
*/
- public static TestType getTestTypeForDescription(String testTypeDescription)
{
- if (StringUtils.isEmpty(testTypeDescription)) {
- return TestType.DEVELOPMENT;
+ public static String getTestTypeForDescription(String testTypeDescription) {
+ if (StringUtils.isEmpty(testTypeDescription)
+ || testTypeDescription.equalsIgnoreCase(testTypeDesc[0])) {
+ return TEST_TYPE_DEVELOPMENT;
}
- int index = Const.indexOfString(testTypeDescription, testTypeDesc);
- if (index < 0) {
- return TestType.DEVELOPMENT;
+ if (testTypeDescription.equalsIgnoreCase(testTypeDesc[1])) {
+ return TEST_TYPE_UNIT_TEST;
}
- return TestType.values()[index];
+ return testTypeDescription;
}
+ /**
+ * @return translated Development and Unit test labels
+ */
public static String[] getTestTypeDescriptions() {
- return testTypeDesc;
+ return getTestTypeDescriptions(null);
+ }
+
+ /**
+ * Development, Unit test, then every other type already stored on a
pipeline unit test in the
+ * project. A metadata error leaves the two built-in labels in place.
+ *
+ * @param metadataProvider project metadata, or null for the built-in labels
only
+ * @return combo items
+ */
+ public static String[] getTestTypeDescriptions(IHopMetadataProvider
metadataProvider) {
+ LinkedHashSet<String> descriptions = new LinkedHashSet<>();
+ descriptions.add(testTypeDesc[0]);
+ descriptions.add(testTypeDesc[1]);
+ if (metadataProvider == null) {
+ return descriptions.toArray(new String[0]);
+ }
+ try {
+ IHopMetadataSerializer<PipelineUnitTest> serializer =
+ metadataProvider.getSerializer(PipelineUnitTest.class);
+ LinkedHashSet<String> custom = new LinkedHashSet<>();
+ for (String name : serializer.listObjectNames()) {
+ try {
+ PipelineUnitTest unitTest = serializer.load(name);
+ if (unitTest == null) {
+ continue;
+ }
+ String description = getTestTypeDescription(unitTest.getType());
+ if (StringUtils.isNotEmpty(description) &&
!descriptions.contains(description)) {
+ custom.add(description);
+ }
+ } catch (Exception e) {
+ LogChannel.GENERAL.logError(
+ "Unable to load pipeline unit test '" + name + "' while listing
test types", e);
+ }
+ }
+ List<String> sorted = new ArrayList<>(custom);
+ sorted.sort(String.CASE_INSENSITIVE_ORDER);
+ descriptions.addAll(sorted);
+ } catch (Exception e) {
+ LogChannel.GENERAL.logError("Unable to list pipeline unit test types",
e);
+ }
+ return descriptions.toArray(new String[0]);
+ }
+
+ /**
+ * An unset type runs every unit test. A set type matches the stored type
exactly.
+ *
+ * @param typeToExecute type selected on Execute unit tests, or null when it
was never set
+ * @param unitTestType type stored on the pipeline unit test
+ * @return true when this unit test should run
+ */
+ public static boolean matchesTestType(String typeToExecute, String
unitTestType) {
+ return typeToExecute == null || typeToExecute.equals(unitTestType);
}
/**
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiUnitTestCreateBeforeDialog.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiUnitTestCreateBeforeDialog.java
index ca0d98a630..a1c0e30951 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiUnitTestCreateBeforeDialog.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiUnitTestCreateBeforeDialog.java
@@ -27,8 +27,8 @@ import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.metadata.api.IHopMetadataSerializer;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.testing.PipelineUnitTest;
-import org.apache.hop.testing.TestType;
import org.apache.hop.testing.gui.TestingGuiPlugin;
+import org.apache.hop.testing.util.DataSetConst;
import org.apache.hop.ui.hopgui.HopGui;
@ExtensionPoint(
@@ -54,7 +54,7 @@ public class HopGuiUnitTestCreateBeforeDialog extends
HopGuiUnitTestChanged
HopGui hopGui = HopGui.getInstance();
test.setName(uniqueUnitTestName(pipelineMeta.getName(),
hopGui.getMetadataProvider()));
- test.setType(TestType.UNIT_TEST);
+ test.setType(DataSetConst.TEST_TYPE_UNIT_TEST);
test.setRelativeFilename(hopGui.getVariables(),
pipelineMeta.getFilename());
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestEditor.java
b/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestEditor.java
index 487ae88c4c..3aca6161c2 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestEditor.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestEditor.java
@@ -198,7 +198,9 @@ public class PipelineUnitTestEditor extends
MetadataEditor<PipelineUnitTest> {
fdTestType.left = new FormAttachment(middle, 0);
fdTestType.right = new FormAttachment(100, 0);
wTestType.setLayoutData(fdTestType);
- wTestType.setItems(DataSetConst.getTestTypeDescriptions());
+ wTestType.setItems(DataSetConst.getTestTypeDescriptions(metadataProvider));
+ wTestType.setToolTipText(
+ BaseMessages.getString(PKG,
"PipelineUnitTestDialog.TestType.Tooltip"));
lastControl = wTestType;
// The filename of the pipeline to test
diff --git
a/plugins/misc/testing/src/main/resources/org/apache/hop/testing/transforms/exectests/messages/messages_en_US.properties
b/plugins/misc/testing/src/main/resources/org/apache/hop/testing/transforms/exectests/messages/messages_en_US.properties
index 3ddf817d3c..0b84810abe 100644
---
a/plugins/misc/testing/src/main/resources/org/apache/hop/testing/transforms/exectests/messages/messages_en_US.properties
+++
b/plugins/misc/testing/src/main/resources/org/apache/hop/testing/transforms/exectests/messages/messages_en_US.properties
@@ -26,5 +26,6 @@ ExecuteTestsDialog.TestNameInputField.Label=Test name input
field
ExecuteTestsDialog.TransformName.Label=Transform name
ExecuteTestsDialog.TransformNameField.Label=transform output field name
ExecuteTestsDialog.TypeToExecute.Label=Type of tests to run
+ExecuteTestsDialog.TypeToExecute.Tooltip=Run unit tests of this type. The list
includes Development, Unit test, and every other type already used in this
project. Type a value that is not listed yet to use it.
ExecuteTestsDialog.UnitTestNameField.Label=Unit test output field name
ExecuteTestsMeta.keyword=test,unit,run,validate,assert
diff --git
a/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
b/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
index 7304fbe298..16b09c895f 100644
---
a/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
+++
b/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
@@ -55,6 +55,7 @@ PipelineUnitTestDialog.Tab.General=General
PipelineUnitTestDialog.Tab.Tweaks=Tweaks
PipelineUnitTestDialog.Tab.Variables=Parameters & Variables
PipelineUnitTestDialog.TestType.Label=Type of test
+PipelineUnitTestDialog.TestType.Tooltip=Development, Unit test, or a custom
type. The list includes types already used in this project. Type a new value to
add one.
PipelineUnitTestDialog.Tweaks.ColumnInfo.TransformName=Transform name
PipelineUnitTestDialog.Tweaks.ColumnInfo.Tweak=Tweak
PipelineUnitTestDialog.Tweaks.Label=Transform tweaks applied when this unit
test runs:
diff --git
a/plugins/misc/testing/src/test/java/org/apache/hop/testing/PipelineUnitTestTypeTest.java
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/PipelineUnitTestTypeTest.java
new file mode 100644
index 0000000000..949da9e2b6
--- /dev/null
+++
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/PipelineUnitTestTypeTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.hop.testing;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.encryption.HopTwoWayPasswordEncoder;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.api.IHopMetadataSerializer;
+import org.apache.hop.metadata.serializer.json.JsonMetadataProvider;
+import org.apache.hop.metadata.serializer.json.JsonMetadataSerializer;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.metadata.serializer.xml.XmlMetadataUtil;
+import org.apache.hop.testing.transforms.exectests.ExecuteTestsMeta;
+import org.apache.hop.testing.util.DataSetConst;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.w3c.dom.Node;
+
+class PipelineUnitTestTypeTest {
+
+ @BeforeAll
+ static void initHop() throws HopException {
+ HopEnvironment.init();
+ }
+
+ @Test
+ void legacyCodesRoundTripThroughDialogLabels() {
+ String development =
DataSetConst.getTestTypeDescription(DataSetConst.TEST_TYPE_DEVELOPMENT);
+ String unitTest =
DataSetConst.getTestTypeDescription(DataSetConst.TEST_TYPE_UNIT_TEST);
+
+ assertEquals(development, DataSetConst.getTestTypeDescription(null));
+ assertEquals(development, DataSetConst.getTestTypeDescription(""));
+ assertEquals(DataSetConst.TEST_TYPE_DEVELOPMENT,
DataSetConst.getTestTypeForDescription(null));
+ assertEquals(DataSetConst.TEST_TYPE_DEVELOPMENT,
DataSetConst.getTestTypeForDescription(""));
+ assertEquals(
+ DataSetConst.TEST_TYPE_DEVELOPMENT,
DataSetConst.getTestTypeForDescription(development));
+ assertEquals(
+ DataSetConst.TEST_TYPE_DEVELOPMENT,
+
DataSetConst.getTestTypeForDescription(development.toLowerCase(Locale.ROOT)));
+ assertEquals(
+ DataSetConst.TEST_TYPE_UNIT_TEST,
DataSetConst.getTestTypeForDescription(unitTest));
+ assertEquals(
+ DataSetConst.TEST_TYPE_UNIT_TEST,
+
DataSetConst.getTestTypeForDescription(unitTest.toUpperCase(Locale.ROOT)));
+
+ assertEquals("Smoke", DataSetConst.getTestTypeDescription("Smoke"));
+ assertEquals("Smoke", DataSetConst.getTestTypeForDescription("Smoke"));
+ assertEquals(" ", DataSetConst.getTestTypeForDescription(" "));
+ }
+
+ @Test
+ void unsetTypeRunsEveryTestAndASetTypeMatchesExactly() {
+ assertTrue(DataSetConst.matchesTestType(null, "Smoke"));
+ assertTrue(DataSetConst.matchesTestType(null, null));
+ assertTrue(DataSetConst.matchesTestType("Smoke", "Smoke"));
+ assertTrue(
+ DataSetConst.matchesTestType(
+ DataSetConst.TEST_TYPE_UNIT_TEST,
DataSetConst.TEST_TYPE_UNIT_TEST));
+ assertFalse(DataSetConst.matchesTestType("Smoke",
DataSetConst.TEST_TYPE_UNIT_TEST));
+ assertFalse(DataSetConst.matchesTestType("Smoke", null));
+ assertFalse(DataSetConst.matchesTestType("", "Smoke"));
+ assertFalse(DataSetConst.matchesTestType("Unit test",
DataSetConst.TEST_TYPE_UNIT_TEST));
+ }
+
+ @Test
+ void newUnitTestDefaultsToDevelopment() {
+ assertEquals(DataSetConst.TEST_TYPE_DEVELOPMENT, new
PipelineUnitTest().getType());
+ }
+
+ @Test
+ void descriptionsListBuiltInTypesAndProjectTypes() throws Exception {
+ MemoryMetadataProvider provider = new MemoryMetadataProvider();
+ IHopMetadataSerializer<PipelineUnitTest> serializer =
+ provider.getSerializer(PipelineUnitTest.class);
+ save(serializer, "dev", DataSetConst.TEST_TYPE_DEVELOPMENT);
+ save(serializer, "unit", DataSetConst.TEST_TYPE_UNIT_TEST);
+ save(serializer, "smoke", "Smoke");
+ save(serializer, "smoke-again", "Smoke");
+ save(serializer, "regression", "Regression");
+ save(serializer, "blank", "");
+
+ String[] items = DataSetConst.getTestTypeDescriptions(provider);
+
+
assertEquals(DataSetConst.getTestTypeDescription(DataSetConst.TEST_TYPE_DEVELOPMENT),
items[0]);
+
assertEquals(DataSetConst.getTestTypeDescription(DataSetConst.TEST_TYPE_UNIT_TEST),
items[1]);
+ assertEquals(List.of(items[0], items[1], "Regression", "Smoke"),
Arrays.asList(items));
+ assertEquals(2, DataSetConst.getTestTypeDescriptions().length);
+ assertEquals(2, DataSetConst.getTestTypeDescriptions(null).length);
+ }
+
+ @Test
+ void descriptionsStayUsableWhenMetadataCannotBeListed() throws Exception {
+ IHopMetadataProvider broken = mock(IHopMetadataProvider.class);
+ when(broken.getSerializer(PipelineUnitTest.class)).thenThrow(new
HopException("nope"));
+
+ assertEquals(2, DataSetConst.getTestTypeDescriptions(broken).length);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void descriptionsSkipAUnitTestThatFailsToLoad() throws Exception {
+ IHopMetadataSerializer<PipelineUnitTest> serializer =
mock(IHopMetadataSerializer.class);
+ when(serializer.listObjectNames()).thenReturn(List.of("ok", "bad"));
+ PipelineUnitTest ok = new PipelineUnitTest();
+ ok.setName("ok");
+ ok.setType("Smoke");
+ when(serializer.load("ok")).thenReturn(ok);
+ when(serializer.load("bad")).thenThrow(new HopException("bad"));
+ IHopMetadataProvider provider = mock(IHopMetadataProvider.class);
+
when(provider.getSerializer(PipelineUnitTest.class)).thenReturn(serializer);
+
+ String[] items = DataSetConst.getTestTypeDescriptions(provider);
+
+ assertEquals(3, items.length);
+ assertEquals("Smoke", items[2]);
+ }
+
+ @Test
+ void legacyJsonKeepsStoredCodesAndCustomTypesAreStoredAsEntered(@TempDir
Path folder)
+ throws Exception {
+ writeUnitTest(folder, "legacy-unit", DataSetConst.TEST_TYPE_UNIT_TEST);
+ writeUnitTest(folder, "legacy-dev", DataSetConst.TEST_TYPE_DEVELOPMENT);
+
+ JsonMetadataSerializer<PipelineUnitTest> serializer = serializer(folder);
+
+ PipelineUnitTest legacyUnit = serializer.load("legacy-unit");
+ PipelineUnitTest legacyDev = serializer.load("legacy-dev");
+ assertEquals(DataSetConst.TEST_TYPE_UNIT_TEST, legacyUnit.getType());
+ assertEquals(DataSetConst.TEST_TYPE_DEVELOPMENT, legacyDev.getType());
+
+ serializer.save(legacyUnit);
+ String savedLegacy = read(serializer.calculateFilename("legacy-unit"));
+
assertTrue(withoutSpaces(savedLegacy).contains("\"test_type\":\"UNIT_TEST\""));
+ assertFalse(
+ savedLegacy.contains(
+
DataSetConst.getTestTypeDescription(DataSetConst.TEST_TYPE_UNIT_TEST)));
+
+ PipelineUnitTest custom = new PipelineUnitTest();
+ custom.setName("custom");
+ custom.setType("Smoke");
+ serializer.save(custom);
+
+ assertEquals("Smoke", serializer.load("custom").getType());
+ String savedCustom = read(serializer.calculateFilename("custom"));
+ assertTrue(withoutSpaces(savedCustom).contains("\"test_type\":\"Smoke\""));
+
assertFalse(withoutSpaces(savedCustom).contains("\"test_type\":\"DEVELOPMENT\""));
+ }
+
+ @Test
+ void executeTestsKeepsLegacyAndCustomTypesInXml() throws Exception {
+ assertNull(loadExecuteTests(null).getTypeToExecute());
+ assertEquals(
+ DataSetConst.TEST_TYPE_DEVELOPMENT,
loadExecuteTests("DEVELOPMENT").getTypeToExecute());
+ assertEquals(
+ DataSetConst.TEST_TYPE_UNIT_TEST,
loadExecuteTests("UNIT_TEST").getTypeToExecute());
+
+ ExecuteTestsMeta smoke = loadExecuteTests("Smoke");
+ assertEquals("Smoke", smoke.getTypeToExecute());
+ String xml = smoke.getXml();
+ assertTrue(xml.contains("<type_to_execute>Smoke</type_to_execute>"));
+
+ Node copyNode = XmlHandler.loadXmlString("<transform>" + xml +
"</transform>", "transform");
+ ExecuteTestsMeta copy =
+ XmlMetadataUtil.deSerializeFromXml(
+ copyNode, ExecuteTestsMeta.class, new MemoryMetadataProvider());
+ assertEquals("Smoke", copy.getTypeToExecute());
+ }
+
+ private static void save(
+ IHopMetadataSerializer<PipelineUnitTest> serializer, String name, String
type)
+ throws HopException {
+ PipelineUnitTest unitTest = new PipelineUnitTest();
+ unitTest.setName(name);
+ unitTest.setType(type);
+ serializer.save(unitTest);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static JsonMetadataSerializer<PipelineUnitTest> serializer(Path
folder)
+ throws HopException {
+ JsonMetadataProvider provider =
+ new JsonMetadataProvider(
+ new HopTwoWayPasswordEncoder(),
+ folder.toString(),
+ Variables.getADefaultVariableSpace());
+ return (JsonMetadataSerializer<PipelineUnitTest>)
+ provider.getSerializer(PipelineUnitTest.class);
+ }
+
+ private static void writeUnitTest(Path folder, String name, String testType)
throws Exception {
+ String directory = folder.resolve("unit-test").toString();
+ HopVfs.getFileObject(directory).createFolder();
+ String filename = directory + "/" + name + ".json";
+ String json = "{\"name\":\"" + name + "\",\"test_type\":\"" + testType +
"\"}";
+ try (OutputStream out = HopVfs.getOutputStream(filename, false)) {
+ out.write(json.getBytes(UTF_8));
+ }
+ }
+
+ private static String read(String filename) throws Exception {
+ try (InputStream in = HopVfs.getInputStream(filename)) {
+ return new String(in.readAllBytes(), UTF_8);
+ }
+ }
+
+ private static String withoutSpaces(String value) {
+ return value.replace(" ", "");
+ }
+
+ private static ExecuteTestsMeta loadExecuteTests(String typeToExecute)
throws Exception {
+ String xml =
+ "<transform>"
+ + (typeToExecute == null
+ ? ""
+ : "<type_to_execute>" + typeToExecute + "</type_to_execute>")
+ + "</transform>";
+ Node node = XmlHandler.loadXmlString(xml, "transform");
+ return XmlMetadataUtil.deSerializeFromXml(
+ node, ExecuteTestsMeta.class, new MemoryMetadataProvider());
+ }
+}