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

hansva 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 151481e4dc Improve Abort transform: refactor processRow logic and add 
comprehensive unit tests (#5885)
151481e4dc is described below

commit 151481e4dc1f56f6fff30707ad6eb814f5ae851e
Author: lance <[email protected]>
AuthorDate: Fri Oct 24 18:31:23 2025 +0800

    Improve Abort transform: refactor processRow logic and add comprehensive 
unit tests (#5885)
    
    Signed-off-by: lance <[email protected]>
---
 .../hop/pipeline/transforms/abort/Abort.java       | 110 +++++++++--------
 .../hop/pipeline/transforms/abort/AbortDialog.java |  21 ++--
 .../hop/pipeline/transforms/abort/AbortMeta.java   |  90 ++------------
 .../pipeline/transforms/abort/AbortDataTests.java  |  42 +++++++
 .../pipeline/transforms/abort/AbortMetaTest.java   | 123 ++++++++++++++++++-
 .../hop/pipeline/transforms/abort/AbortTest.java   | 134 +++++++++++++++++----
 .../org/apache/hop/ui/core/FormDataBuilder.java    |  30 ++++-
 7 files changed, 374 insertions(+), 176 deletions(-)

diff --git 
a/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/Abort.java
 
b/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/Abort.java
index 810198299e..ccba6c61a8 100644
--- 
a/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/Abort.java
+++ 
b/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/Abort.java
@@ -19,6 +19,7 @@ package org.apache.hop.pipeline.transforms.abort;
 
 import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopValueException;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.Pipeline;
@@ -62,68 +63,71 @@ public class Abort extends BaseTransform<AbortMeta, 
AbortData> {
 
   @Override
   public boolean processRow() throws HopException {
-
-    Object[] r = getRow(); // Get row from input rowset & set row busy!
+    // Get row from input rowset & set row busy!
+    Object[] r = getRow();
     // no more input to be expected...
     if (r == null) {
       setOutputDone();
       return false;
-    } else {
-      putRow(getInputRowMeta(), r);
-      nrInputRows++;
-      if (nrInputRows > nrThresholdRows) {
-        //
-        // Here we abort!!
-        //
-        String abortOptionMessage = BaseMessages.getString(PKG, 
"AbortDialog.Options.Abort.Label");
-        if (meta.isAbortWithError()) {
-          abortOptionMessage =
-              BaseMessages.getString(PKG, 
"AbortDialog.Options.AbortWithError.Label");
-        } else if (meta.isSafeStop()) {
-          abortOptionMessage = BaseMessages.getString(PKG, 
"AbortDialog.Options.SafeStop.Label");
-        }
-        logError(
-            BaseMessages.getString(
-                PKG,
-                "Abort.Log.Wrote.AbortRow",
-                Long.toString(nrInputRows),
-                abortOptionMessage,
-                getInputRowMeta().getString(r)));
-
-        String message = resolve(meta.getMessage());
-        if (Utils.isEmpty(message)) {
-          logError(BaseMessages.getString(PKG, 
"Abort.Log.DefaultAbortMessage", "" + nrInputRows));
-        } else {
-          logError(message);
-        }
-
-        if (meta.isAbortWithError()) {
-          setErrors(1);
-        }
-        stopAll();
+    }
+    putRow(getInputRowMeta(), r);
+    nrInputRows++;
+    if (nrInputRows > nrThresholdRows) {
+      //
+      // Here we abort!!
+      //
+      String abortOptionMessage = BaseMessages.getString(PKG, 
"AbortDialog.Options.Abort.Label");
+      if (meta.isAbortWithError()) {
+        abortOptionMessage =
+            BaseMessages.getString(PKG, 
"AbortDialog.Options.AbortWithError.Label");
+      } else if (meta.isSafeStop()) {
+        abortOptionMessage = BaseMessages.getString(PKG, 
"AbortDialog.Options.SafeStop.Label");
+      }
+      logError(
+          BaseMessages.getString(
+              PKG,
+              "Abort.Log.Wrote.AbortRow",
+              Long.toString(nrInputRows),
+              abortOptionMessage,
+              getInputRowMeta().getString(r)));
 
+      String message = resolve(meta.getMessage());
+      if (Utils.isEmpty(message)) {
+        logError(BaseMessages.getString(PKG, "Abort.Log.DefaultAbortMessage", 
"" + nrInputRows));
       } else {
-        // seen a row but not yet reached the threshold
-        if (meta.isAlwaysLogRows()) {
-          logMinimal(
-              BaseMessages.getString(
-                  PKG,
-                  "Abort.Log.Wrote.Row",
-                  Long.toString(nrInputRows),
-                  getInputRowMeta().getString(r)));
-        } else {
-          if (isRowLevel()) {
-            logRowlevel(
-                BaseMessages.getString(
-                    PKG,
-                    "Abort.Log.Wrote.Row",
-                    Long.toString(nrInputRows),
-                    getInputRowMeta().getString(r)));
-          }
-        }
+        logError(message);
+      }
+
+      if (meta.isAbortWithError()) {
+        setErrors(1);
       }
+      stopAll();
+    } else {
+      logRowIfNecessary(r);
     }
 
     return true;
   }
+
+  /** Logs a single row depending on the logging configuration. */
+  private void logRowIfNecessary(Object[] row) throws HopValueException {
+    // seen a row but not yet reached the threshold
+    if (meta.isAlwaysLogRows()) {
+      logMinimal(
+          BaseMessages.getString(
+              PKG,
+              "Abort.Log.Wrote.Row",
+              Long.toString(nrInputRows),
+              getInputRowMeta().getString(row)));
+    } else {
+      if (isRowLevel()) {
+        logRowlevel(
+            BaseMessages.getString(
+                PKG,
+                "Abort.Log.Wrote.Row",
+                Long.toString(nrInputRows),
+                getInputRowMeta().getString(row)));
+      }
+    }
+  }
 }
diff --git 
a/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortDialog.java
 
b/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortDialog.java
index 94e8745ad5..85e849913d 100644
--- 
a/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortDialog.java
+++ 
b/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortDialog.java
@@ -22,6 +22,7 @@ import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.ui.core.ConstUi;
+import org.apache.hop.ui.core.FormDataBuilder;
 import org.apache.hop.ui.core.PropsUi;
 import org.apache.hop.ui.core.dialog.BaseDialog;
 import org.apache.hop.ui.core.widget.TextVar;
@@ -232,12 +233,13 @@ public class AbortDialog extends BaseTransformDialog {
     flLoggingGroup.marginWidth = 15;
     wLoggingGroup.setLayout(flLoggingGroup);
 
-    FormData fdLoggingGroup = new FormData();
-    fdLoggingGroup.left = new FormAttachment(0, 0);
-    fdLoggingGroup.top = new FormAttachment(widgetAbove, 15);
-    fdLoggingGroup.right = new FormAttachment(100, 0);
-    fdLoggingGroup.bottom = new FormAttachment(hSpacer, -15);
-    wLoggingGroup.setLayoutData(fdLoggingGroup);
+    wLoggingGroup.setLayoutData(
+        FormDataBuilder.builder()
+            .left()
+            .top(widgetAbove, 15)
+            .right(100, 0)
+            .bottom(hSpacer, -15)
+            .build());
 
     Label wlMessage = new Label(wLoggingGroup, SWT.RIGHT);
     wlMessage.setText(BaseMessages.getString(PKG, 
"AbortDialog.Logging.AbortMessage.Label"));
@@ -299,7 +301,7 @@ public class AbortDialog extends BaseTransformDialog {
     wTransformName.setFocus();
   }
 
-  private void getInfo(AbortMeta in) {
+  private void getInfo() {
     input.setRowThreshold(wRowThreshold.getText());
     input.setMessage(wMessage.getText());
     input.setAlwaysLogRows(wAlwaysLogRows.getSelection());
@@ -325,8 +327,9 @@ public class AbortDialog extends BaseTransformDialog {
       return;
     }
 
-    getInfo(input);
-    transformName = wTransformName.getText(); // return value
+    getInfo();
+    // return value
+    transformName = wTransformName.getText();
     dispose();
   }
 }
diff --git 
a/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortMeta.java
 
b/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortMeta.java
index 269de5afe2..0b5f6c8fb6 100644
--- 
a/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortMeta.java
+++ 
b/plugins/transforms/abort/src/main/java/org/apache/hop/pipeline/transforms/abort/AbortMeta.java
@@ -18,10 +18,11 @@
 package org.apache.hop.pipeline.transforms.abort;
 
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.CheckResult;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.annotations.Transform;
-import org.apache.hop.core.exception.HopTransformException;
 import org.apache.hop.core.exception.HopXmlException;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.variables.IVariables;
@@ -35,6 +36,8 @@ import org.apache.hop.pipeline.transform.TransformMeta;
 import org.w3c.dom.Node;
 
 /** Meta data for the abort transform. */
+@Setter
+@Getter
 @Transform(
     id = "Abort",
     name = "i18n::Abort.Name",
@@ -69,6 +72,7 @@ public class AbortMeta extends BaseTransformMeta<Abort, 
AbortData> {
       injectionKeyDescription = "AbortDialog.Logging.AlwaysLogRows.Label")
   private boolean alwaysLogRows;
 
+  /** value of abortOption */
   @HopMetadataProperty(
       key = "abort_option",
       injectionKeyDescription = "AbortMeta.Injection.AbortOption")
@@ -78,23 +82,11 @@ public class AbortMeta extends BaseTransformMeta<Abort, 
AbortData> {
     abortOption = AbortOption.ABORT;
   }
 
-  @Override
-  public void getFields(
-      IRowMeta inputRowMeta,
-      String name,
-      IRowMeta[] info,
-      TransformMeta nextTransform,
-      IVariables variables,
-      IHopMetadataProvider metadataProvider)
-      throws HopTransformException {
-    // Default: no values are added to the row in the transform
-  }
-
   @Override
   public void check(
       List<ICheckResult> remarks,
       PipelineMeta pipelineMeta,
-      TransformMeta transforminfo,
+      TransformMeta transform,
       IRowMeta prev,
       String[] input,
       String[] output,
@@ -107,7 +99,7 @@ public class AbortMeta extends BaseTransformMeta<Abort, 
AbortData> {
           new CheckResult(
               ICheckResult.TYPE_RESULT_WARNING,
               BaseMessages.getString(PKG, 
"AbortMeta.CheckResult.NoInputReceivedError"),
-              transforminfo);
+              transform);
       remarks.add(cr);
     }
   }
@@ -126,11 +118,11 @@ public class AbortMeta extends BaseTransformMeta<Abort, 
AbortData> {
     super.loadXml(transformNode, metadataProvider);
 
     // Backward compatible code
-    //
     if (abortOption == null) {
       String awe = XmlHandler.getTagValue(transformNode, "abort_with_error");
       if (awe == null) {
-        awe = "Y"; // existing pipelines will have to maintain backward 
compatibility with yes
+        // existing pipelines will have to maintain backward compatibility 
with yes
+        awe = "Y";
       }
       abortOption = "Y".equalsIgnoreCase(awe) ? AbortOption.ABORT_WITH_ERROR : 
AbortOption.ABORT;
     }
@@ -148,70 +140,6 @@ public class AbortMeta extends BaseTransformMeta<Abort, 
AbortData> {
     return abortOption == AbortOption.SAFE_STOP;
   }
 
-  /**
-   * Gets rowThreshold
-   *
-   * @return value of rowThreshold
-   */
-  public String getRowThreshold() {
-    return rowThreshold;
-  }
-
-  /**
-   * @param rowThreshold The rowThreshold to set
-   */
-  public void setRowThreshold(String rowThreshold) {
-    this.rowThreshold = rowThreshold;
-  }
-
-  /**
-   * Gets message
-   *
-   * @return value of message
-   */
-  public String getMessage() {
-    return message;
-  }
-
-  /**
-   * @param message The message to set
-   */
-  public void setMessage(String message) {
-    this.message = message;
-  }
-
-  /**
-   * Gets alwaysLogRows
-   *
-   * @return value of alwaysLogRows
-   */
-  public boolean isAlwaysLogRows() {
-    return alwaysLogRows;
-  }
-
-  /**
-   * @param alwaysLogRows The alwaysLogRows to set
-   */
-  public void setAlwaysLogRows(boolean alwaysLogRows) {
-    this.alwaysLogRows = alwaysLogRows;
-  }
-
-  /**
-   * Gets abortOption
-   *
-   * @return value of abortOption
-   */
-  public AbortOption getAbortOption() {
-    return abortOption;
-  }
-
-  /**
-   * @param abortOption The abortOption to set
-   */
-  public void setAbortOption(AbortOption abortOption) {
-    this.abortOption = abortOption;
-  }
-
   @Override
   public boolean supportsMultiCopyExecution() {
     return false;
diff --git 
a/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortDataTests.java
 
b/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortDataTests.java
new file mode 100644
index 0000000000..04172e97cf
--- /dev/null
+++ 
b/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortDataTests.java
@@ -0,0 +1,42 @@
+/*
+ * 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.pipeline.transforms.abort;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.hop.pipeline.transform.BaseTransformData;
+import org.apache.hop.pipeline.transform.ITransformData;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class AbortDataTests {
+
+  @Test
+  void testAbortDataInitialization() {
+    // Create instance
+    AbortData data = new AbortData();
+
+    // Verify object is not null
+    assertNotNull(data, "AbortData instance should be created successfully.");
+
+    Assertions.assertInstanceOf(
+        BaseTransformData.class, data, "AbortData should extend 
BaseTransformData.");
+    Assertions.assertInstanceOf(
+        ITransformData.class, data, "AbortData should implement 
ITransformData.");
+  }
+}
diff --git 
a/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortMetaTest.java
 
b/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortMetaTest.java
index 5ee65b8c9a..5909477d6d 100644
--- 
a/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortMetaTest.java
+++ 
b/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortMetaTest.java
@@ -17,25 +17,43 @@
 
 package org.apache.hop.pipeline.transforms.abort;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import javax.xml.parsers.DocumentBuilderFactory;
+import org.apache.hop.core.CheckResult;
+import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopXmlException;
 import org.apache.hop.core.xml.XmlHandler;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
 import 
org.apache.hop.pipeline.transforms.loadsave.validator.EnumLoadSaveValidator;
 import 
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
+/** AbortMeta test */
 class AbortMetaTest {
+  private AbortMeta meta;
+
+  @BeforeEach
+  void setUp() {
+    meta = new AbortMeta();
+  }
 
   @Test
   void testRoundTrip() throws HopException {
@@ -70,18 +88,111 @@ class AbortMetaTest {
   @Test
   void testBackwardsCompatibilityAbortWithError() throws HopXmlException {
     IHopMetadataProvider metadataProvider = mock(IHopMetadataProvider.class);
-    AbortMeta meta = new AbortMeta();
 
     // No abort option specified: leave the default: Abort
     String inputXml =
         """
-                      <transform>
-                        <name>Abort</name>
-                        <type>Abort</type>
-                      </transform>\
-                    """;
+        <transform>
+            <name>Abort</name>
+            <type>Abort</type>
+          </transform>\
+        """;
     Node node = XmlHandler.loadXmlString(inputXml).getFirstChild();
     meta.loadXml(node, metadataProvider);
     assertTrue(meta.isAbort());
   }
+
+  @Test
+  void testDefaultValuesAfterConstruction() {
+    assertEquals(AbortMeta.AbortOption.ABORT, meta.getAbortOption());
+  }
+
+  @Test
+  void testSetDefaultValues() {
+    meta.setDefault();
+
+    assertEquals("0", meta.getRowThreshold());
+    assertEquals("", meta.getMessage());
+    assertTrue(meta.isAlwaysLogRows());
+    assertEquals(AbortMeta.AbortOption.ABORT_WITH_ERROR, 
meta.getAbortOption());
+  }
+
+  @Test
+  void testCheckWithNoInputAddsWarning() {
+    List<ICheckResult> remarks = new ArrayList<>();
+    PipelineMeta pipelineMeta = mock(PipelineMeta.class);
+    TransformMeta transformMeta = mock(TransformMeta.class);
+
+    meta.check(remarks, pipelineMeta, transformMeta, null, new String[0], 
null, null, null, null);
+
+    assertFalse(remarks.isEmpty());
+    CheckResult result = (CheckResult) remarks.get(0);
+    assertEquals(ICheckResult.TYPE_RESULT_WARNING, result.getType());
+  }
+
+  @Test
+  void testCheckWithInputDoesNotAddWarning() {
+    List<ICheckResult> remarks = new ArrayList<>();
+    PipelineMeta pipelineMeta = mock(PipelineMeta.class);
+    TransformMeta transformMeta = mock(TransformMeta.class);
+
+    meta.check(
+        remarks, pipelineMeta, transformMeta, null, new String[] {"input"}, 
null, null, null, null);
+
+    assertTrue(remarks.isEmpty());
+  }
+
+  @Test
+  void testAbortOptionHelperMethods() {
+    meta.setAbortOption(AbortMeta.AbortOption.ABORT);
+    assertTrue(meta.isAbort());
+    assertFalse(meta.isAbortWithError());
+    assertFalse(meta.isSafeStop());
+
+    meta.setAbortOption(AbortMeta.AbortOption.ABORT_WITH_ERROR);
+    assertTrue(meta.isAbortWithError());
+    assertFalse(meta.isAbort());
+    assertFalse(meta.isSafeStop());
+
+    meta.setAbortOption(AbortMeta.AbortOption.SAFE_STOP);
+    assertTrue(meta.isSafeStop());
+  }
+
+  @Test
+  void testLoadXmlBackwardCompatibility() throws Exception {
+    meta.setAbortOption(null);
+    Document doc = XmlTestUtil.createDocument();
+    Element transformNode = doc.createElement("transform");
+    // Simulate legacy XML tag
+    transformNode.appendChild(XmlTestUtil.createElement(doc, 
"abort_with_error", "Y"));
+
+    meta.loadXml(transformNode, mock(IHopMetadataProvider.class));
+    assertEquals(AbortMeta.AbortOption.ABORT_WITH_ERROR, 
meta.getAbortOption());
+
+    // Test with value N
+    meta.setAbortOption(null);
+    transformNode = doc.createElement("transform");
+    transformNode.appendChild(XmlTestUtil.createElement(doc, 
"abort_with_error", "N"));
+    transformNode.appendChild(XmlTestUtil.createElement(doc, 
"abort_with_success", "Y"));
+    meta.loadXml(transformNode, mock(IHopMetadataProvider.class));
+    assertEquals(AbortMeta.AbortOption.ABORT, meta.getAbortOption());
+  }
+
+  @Test
+  void testSupportsMultiCopyExecution() {
+    assertFalse(meta.supportsMultiCopyExecution());
+  }
+
+  static class XmlTestUtil {
+
+    static Document createDocument() throws Exception {
+      return 
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+    }
+
+    static Element createElement(Document doc, String name, String value) {
+      Element element = doc.createElement(name);
+      element.setTextContent(value);
+      return element;
+    }
+  }
 }
diff --git 
a/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortTest.java
 
b/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortTest.java
index 880bfb9fd7..2287996677 100644
--- 
a/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortTest.java
+++ 
b/plugins/transforms/abort/src/test/java/org/apache/hop/pipeline/transforms/abort/AbortTest.java
@@ -21,27 +21,45 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
 import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 
 class AbortTest {
   private TransformMockHelper<AbortMeta, AbortData> transformMockHelper;
+  private Abort abort;
 
   @BeforeEach
   void setup() {
-    transformMockHelper = new TransformMockHelper("ABORT TEST", 
AbortMeta.class, AbortData.class);
+    transformMockHelper = new TransformMockHelper<>("ABORT TEST", 
AbortMeta.class, AbortData.class);
     when(transformMockHelper.logChannelFactory.create(any(), 
any(ILoggingObject.class)))
         .thenReturn(transformMockHelper.iLogChannel);
     when(transformMockHelper.pipeline.isRunning()).thenReturn(true);
+
+    abort =
+        new Abort(
+            transformMockHelper.transformMeta,
+            transformMockHelper.iTransformMeta,
+            transformMockHelper.iTransformData,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
   }
 
   @AfterEach
@@ -51,14 +69,6 @@ class AbortTest {
 
   @Test
   void testAbortDoesntAbortWithoutInputRow() throws HopException {
-    Abort abort =
-        new Abort(
-            transformMockHelper.transformMeta,
-            transformMockHelper.iTransformMeta,
-            transformMockHelper.iTransformData,
-            0,
-            transformMockHelper.pipelineMeta,
-            transformMockHelper.pipeline);
     abort.processRow();
     abort.addRowSetToInputRowSets(transformMockHelper.getMockInputRowSet());
     assertFalse(abort.isStopped());
@@ -69,14 +79,6 @@ class AbortTest {
 
   @Test
   void testAbortAbortsWithInputRow() throws HopException {
-    Abort abort =
-        new Abort(
-            transformMockHelper.transformMeta,
-            transformMockHelper.iTransformMeta,
-            transformMockHelper.iTransformData,
-            0,
-            transformMockHelper.pipelineMeta,
-            transformMockHelper.pipeline);
     abort.processRow();
     abort.addRowSetToInputRowSets(transformMockHelper.getMockInputRowSet(new 
Object[] {}));
     assertFalse(abort.isStopped());
@@ -87,14 +89,6 @@ class AbortTest {
 
   @Test
   void testAbortWithError() throws HopException {
-    Abort abort =
-        new Abort(
-            transformMockHelper.transformMeta,
-            transformMockHelper.iTransformMeta,
-            transformMockHelper.iTransformData,
-            0,
-            transformMockHelper.pipelineMeta,
-            transformMockHelper.pipeline);
     when(transformMockHelper.iTransformMeta.isSafeStop()).thenReturn(false);
     
when(transformMockHelper.iTransformMeta.isAbortWithError()).thenReturn(true);
     abort.processRow();
@@ -103,4 +97,94 @@ class AbortTest {
     assertEquals(1L, abort.getErrors());
     verify(transformMockHelper.pipeline).stopAll();
   }
+
+  @Test
+  void testInitWithValidThreshold() {
+    
when(transformMockHelper.iTransformMeta.getRowThreshold()).thenReturn("10");
+    boolean result = abort.init();
+
+    assertTrue(result);
+  }
+
+  @Test
+  void testInitWithInvalidThreshold() {
+    try (MockedStatic<Const> constMock = mockStatic(Const.class)) {
+      
when(transformMockHelper.iTransformMeta.getRowThreshold()).thenReturn("abc");
+      constMock.when(() -> Const.toInt("abc", -1)).thenReturn(-1);
+
+      boolean result = abort.init();
+      // init() returns true even if threshold invalid
+      assertTrue(result);
+    }
+  }
+
+  @Test
+  void testProcessRowStopsAfterThreshold() throws Exception {
+    when(transformMockHelper.iTransformMeta.getRowThreshold()).thenReturn("1");
+    
when(transformMockHelper.iTransformMeta.isAbortWithError()).thenReturn(true);
+    
when(transformMockHelper.iTransformMeta.isAlwaysLogRows()).thenReturn(false);
+
+    // Prepare transform
+    boolean result = abort.init();
+    assertTrue(result);
+
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaInteger("No"));
+    abort.setInputRowMeta(inputRowMeta);
+
+    // First row — should continue
+    abort = spy(abort);
+    doReturn(new Object[] {1000L}).doReturn(null).when(abort).getRow();
+
+    boolean firstResult = abort.processRow();
+    assertTrue(firstResult);
+
+    // Second call — should detect no more rows
+    boolean secondResult = abort.processRow();
+    assertFalse(secondResult);
+  }
+
+  @Test
+  void testProcessRowTriggersAbortCondition() throws Exception {
+    when(transformMockHelper.iTransformMeta.getRowThreshold()).thenReturn("0");
+    
when(transformMockHelper.iTransformMeta.isAbortWithError()).thenReturn(true);
+    when(transformMockHelper.iTransformMeta.getMessage()).thenReturn("Abort 
now!");
+    
when(transformMockHelper.iTransformMeta.isAlwaysLogRows()).thenReturn(false);
+
+    abort.init();
+
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaInteger("No"));
+    abort.setInputRowMeta(inputRowMeta);
+
+    // First row — should continue
+    abort = spy(abort);
+    doReturn(new Object[] {1000L}).doReturn(null).when(abort).getRow();
+
+    abort.processRow();
+
+    // Verify abort behavior
+    verify(transformMockHelper.iTransformMeta, 
atLeastOnce()).isAbortWithError();
+    verify(abort, atLeastOnce()).stopAll();
+  }
+
+  @Test
+  void testProcessRowLogsRowWhenAlwaysLogRowsEnabled() throws Exception {
+    
when(transformMockHelper.iTransformMeta.getRowThreshold()).thenReturn("10");
+    
when(transformMockHelper.iTransformMeta.isAlwaysLogRows()).thenReturn(true);
+
+    abort.init();
+
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaInteger("No"));
+    abort.setInputRowMeta(inputRowMeta);
+
+    // First row — should continue
+    abort = spy(abort);
+    doReturn(new Object[] {1000L}).doReturn(null).when(abort).getRow();
+
+    // Should log at minimal level
+    abort.processRow();
+    verify(transformMockHelper.iTransformMeta).isAlwaysLogRows();
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/core/FormDataBuilder.java 
b/ui/src/main/java/org/apache/hop/ui/core/FormDataBuilder.java
index 7efb9af40e..4f685bef5a 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/FormDataBuilder.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/FormDataBuilder.java
@@ -21,13 +21,39 @@ import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.widgets.Control;
 
+/**
+ * Builder class for creating and configuring {@link FormData} instances using 
a fluent API.
+ *
+ * <p>Example usage:
+ *
+ * <pre>{@code
+ * FormData fd = FormDataBuilder.builder()
+ *     .left()
+ *     .top()
+ *     .fullWidth()
+ *     .build();
+ * }</pre>
+ */
 public class FormDataBuilder implements Cloneable {
 
-  private final FormAttachment MIN = new FormAttachment(0, 0);
-  private final FormAttachment MAX = new FormAttachment(100, 0);
+  private static final FormAttachment MIN = new FormAttachment(0, 0);
+  private static final FormAttachment MAX = new FormAttachment(100, 0);
 
   protected FormData fd = new FormData();
 
+  /**
+   * Static factory method for creating a new builder instance.
+   *
+   * @return create FormDataBuilder
+   */
+  public static FormDataBuilder builder() {
+    return new FormDataBuilder();
+  }
+
+  public FormData build() {
+    return fd;
+  }
+
   public FormDataBuilder width(int width) {
     fd.width = width;
     return this;

Reply via email to