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

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


The following commit(s) were added to refs/heads/main by this push:
     new 47fd462a0 OPENNLP-1859: Add tests for BilouCodec encode/decode and 
outcome compatibility (#1135)
47fd462a0 is described below

commit 47fd462a0d3f41d8a2b228f87691587a7b49ed87
Author: Vasiliy Mikhailov <[email protected]>
AuthorDate: Thu Jul 2 16:42:58 2026 +0300

    OPENNLP-1859: Add tests for BilouCodec encode/decode and outcome 
compatibility (#1135)
    
    * Add edge-case tests for BilouCodec encode/decode and outcome compatibility
    
    * Rewrite test comments to describe behaviour instead of production line 
numbers
    
    * Make testCompatibilityContinueWithoutStartOrLast a distinct case 
({B_CONTINUE, A_UNIT}) per review: it duplicated WithoutStart and 
short-circuited in the last-validation loop instead of exercising the 
cont-with-no-matching-start-or-last branch; 81 tests green
---
 .../opennlp/tools/namefind/BilouCodecTest.java     | 381 +++++++++++++++++++++
 1 file changed, 381 insertions(+)

diff --git 
a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/BilouCodecTest.java
 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/BilouCodecTest.java
index e13abbfc0..5711df331 100644
--- 
a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/BilouCodecTest.java
+++ 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/BilouCodecTest.java
@@ -46,6 +46,9 @@ public class BilouCodecTest {
   private static final String B_UNIT = B_TYPE + "-" + BilouCodec.UNIT;
 
   private static final String C_TYPE = "ctype";
+  private static final String C_START = C_TYPE + "-" + BilouCodec.START;
+  private static final String C_CONTINUE = C_TYPE + "-" + BilouCodec.CONTINUE;
+  private static final String C_LAST = C_TYPE + "-" + BilouCodec.LAST;
   private static final String C_UNIT = C_TYPE + "-" + BilouCodec.UNIT;
 
   private static final String OTHER = BilouCodec.OTHER;
@@ -580,5 +583,383 @@ public class BilouCodecTest {
     Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_START, 
B_LAST, OTHER}));
   }
 
+  // ---- Additional tests to improve mutation coverage ----
+
+  /**
+   * Decode with LAST tag appearing without prior START (dangling LAST).
+   * The guard (start != -1) should prevent creating a span.
+   */
+  @Test
+  void testDecodeDanglingLast() {
+    List<String> encoded = Arrays.asList(A_LAST, OTHER);
+    Span[] actual = codec.decode(encoded);
+    Assertions.assertEquals(0, actual.length, "Dangling LAST without START 
should produce no spans");
+  }
+
+  /**
+   * Decode with LAST at position 0 without prior START.
+   */
+  @Test
+  void testDecodeLastAtStart() {
+    List<String> encoded = Arrays.asList(A_LAST);
+    Span[] actual = codec.decode(encoded);
+    Assertions.assertEquals(0, actual.length, "LAST at position 0 without 
START should produce no spans");
+  }
+
+  /**
+   * Decode with multiple dangling LAST tags interspersed.
+   */
+  @Test
+  void testDecodeMultipleDanglingLast() {
+    List<String> encoded = Arrays.asList(A_LAST, OTHER, B_LAST, A_UNIT);
+    Span[] actual = codec.decode(encoded);
+    Span[] expected = new Span[] {new Span(3, 4, A_TYPE)};
+    Assertions.assertArrayEquals(expected, actual);
+  }
+
+  /**
+   * Decode with START, LAST pair to verify exact span boundaries (end + 1).
+   */
+  @Test
+  void testDecodeStartLastBoundaries() {
+    List<String> encoded = Arrays.asList(OTHER, A_START, A_LAST, OTHER);
+    Span[] actual = codec.decode(encoded);
+    Span[] expected = new Span[] {new Span(1, 3, A_TYPE)};
+    Assertions.assertArrayEquals(expected, actual, "Span should cover 
positions 1 to 3");
+  }
+
+  /**
+   * Encode span with null type - single token (UNIT path).
+   */
+  @Test
+  void testEncodeNullTypeUnit() {
+    Span[] spans = new Span[] {new Span(1, 2, null)};
+    String[] actual = codec.encode(spans, 3);
+    String[] expected = new String[] {OTHER, "default-unit", OTHER};
+    Assertions.assertArrayEquals(expected, actual, "Null type single-token 
span should use default-unit");
+  }
+
+  /**
+   * Encode span with null type - two tokens (START + LAST, no CONTINUE).
+   */
+  @Test
+  void testEncodeNullTypeTwoTokens() {
+    Span[] spans = new Span[] {new Span(0, 2, null)};
+    String[] actual = codec.encode(spans, 4);
+    String[] expected = new String[] {"default-start", "default-last", OTHER, 
OTHER};
+    Assertions.assertArrayEquals(expected, actual,
+        "Null type two-token span should use default-start and default-last");
+  }
+
+  /**
+   * Encode span with null type - four tokens (START + CONTINUE*2 + LAST).
+   * Exercises the CONTINUE inner loop.
+   */
+  @Test
+  void testEncodeNullTypeLong() {
+    Span[] spans = new Span[] {new Span(0, 4, null)};
+    String[] actual = codec.encode(spans, 6);
+    String[] expected = new String[] {"default-start", "default-cont", 
"default-cont",
+        "default-last", OTHER, OTHER};
+    Assertions.assertArrayEquals(expected, actual,
+        "Null type long span should use default-start, default-cont, 
default-last");
+  }
+
+  /**
+   * areOutcomesCompatible: unit-only outcomes should be compatible.
+   * Exercises the UNIT parsing path.
+   */
+  @Test
+  void testCompatibilityUnitOnly() {
+    Assertions.assertTrue(codec.areOutcomesCompatible(new String[] {A_UNIT}),
+        "Unit-only outcomes should be compatible");
+  }
+
+  /**
+   * areOutcomesCompatible: multiple unit-only outcomes should be compatible.
+   */
+  @Test
+  void testCompatibilityMultipleUnitsOnly() {
+    Assertions.assertTrue(codec.areOutcomesCompatible(new String[] {A_UNIT, 
B_UNIT, C_UNIT}),
+        "Multiple unit-only outcomes should be compatible");
+  }
+
+  /**
+   * areOutcomesCompatible: CONTINUE without matching START should fail.
+   * Exercises the cont validation (start.contains check).
+   */
+  @Test
+  void testCompatibilityContinueWithoutStart() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] 
{B_CONTINUE, B_LAST, B_UNIT}),
+        "CONTINUE without matching START should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: CONTINUE without matching LAST should fail.
+   * Exercises the cont validation (last.contains check).
+   */
+  @Test
+  void testCompatibilityContinueWithoutLast() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {B_START, 
B_CONTINUE, B_UNIT}),
+        "CONTINUE without matching LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: CONTINUE without matching START or LAST should 
fail.
+   * Both contains checks should return false.
+   */
+  @Test
+  void testCompatibilityContinueWithoutStartOrLast() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] 
{B_CONTINUE, A_UNIT}),
+        "CONTINUE without matching START or LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: UNIT + START without matching LAST should fail.
+   * Exercises the start validation (last.contains check).
+   */
+  @Test
+  void testCompatibilityUnitStartNoLast() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_UNIT, 
A_START}),
+        "UNIT with START but no matching LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: UNIT + START + CONTINUE without LAST should fail.
+   * Exercises both start and cont validation paths.
+   */
+  @Test
+  void testCompatibilityUnitStartContinueNoLast() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_UNIT, 
A_START, A_CONTINUE}),
+        "UNIT with START and CONTINUE but no LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: only START without LAST should fail.
+   */
+  @Test
+  void testCompatibilityStartOnly() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_START}),
+        "START without matching LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: only CONTINUE should fail.
+   */
+  @Test
+  void testCompatibilityContinueOnly() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] 
{A_CONTINUE}),
+        "CONTINUE without matching START or LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: only LAST without START should fail.
+   */
+  @Test
+  void testCompatibilityLastOnly() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_LAST}),
+        "LAST without matching START should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: START + LAST + CONTINUE for different type should 
fail.
+   * The CONTINUE type has no matching START or LAST.
+   */
+  @Test
+  void testCompatibilityStartLastOtherContinue() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_START, 
A_LAST, B_CONTINUE}),
+        "CONTINUE for different type without matching START/LAST should be 
incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: UNIT + START + LAST + CONTINUE for different type 
should fail.
+   */
+  @Test
+  void testCompatibilityUnitStartLastOtherContinue() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {A_UNIT, 
A_START, A_LAST, B_CONTINUE}),
+        "CONTINUE for different type should be incompatible even with other 
valid types");
+  }
+
+  /**
+   * areOutcomesCompatible: only OTHER should fail (no start or unit).
+   */
+  @Test
+  void testCompatibilityOtherOnly() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {OTHER}),
+        "Only OTHER outcomes should be incompatible (no start or unit)");
+  }
+
+  /**
+   * areOutcomesCompatible: multiple OTHER should fail.
+   */
+  @Test
+  void testCompatibilityMultipleOtherOnly() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {OTHER, 
OTHER, OTHER}),
+        "Multiple OTHER outcomes should be incompatible (no start or unit)");
+  }
+
+  /**
+   * areOutcomesCompatible: empty array should fail.
+   */
+  @Test
+  void testCompatibilityEmptyArray() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(new String[] {}),
+        "Empty outcomes array should be incompatible");
+  }
+
+  /**
+   * Decode with only OTHER tags should produce no spans.
+   */
+  @Test
+  void testDecodeOnlyOther() {
+    List<String> encoded = Arrays.asList(OTHER, OTHER, OTHER);
+    Span[] actual = codec.decode(encoded);
+    Assertions.assertEquals(0, actual.length, "Only OTHER tags should produce 
no spans");
+  }
+
+  /**
+   * Decode with only CONTINUE tags should produce no spans.
+   */
+  @Test
+  void testDecodeOnlyContinue() {
+    List<String> encoded = Arrays.asList(A_CONTINUE, A_CONTINUE);
+    Span[] actual = codec.decode(encoded);
+    Assertions.assertEquals(0, actual.length, "Only CONTINUE tags should 
produce no spans");
+  }
+
+  /**
+   * Decode with START but no LAST should produce no spans.
+   */
+  @Test
+  void testDecodeStartWithoutLast() {
+    List<String> encoded = Arrays.asList(A_START, A_CONTINUE, OTHER);
+    Span[] actual = codec.decode(encoded);
+    Assertions.assertEquals(0, actual.length, "START without LAST should 
produce no spans");
+  }
+
+  /**
+   * Decode with START, CONTINUE, LAST at the end of the list.
+   */
+  @Test
+  void testDecodeSpanAtEnd() {
+    List<String> encoded = Arrays.asList(OTHER, A_START, A_CONTINUE, A_LAST);
+    Span[] actual = codec.decode(encoded);
+    Span[] expected = new Span[] {new Span(1, 4, A_TYPE)};
+    Assertions.assertArrayEquals(expected, actual, "Span at end should be 
decoded correctly");
+  }
+
+  /**
+   * Decode with multiple consecutive spans.
+   */
+  @Test
+  void testDecodeMultipleConsecutiveSpans() {
+    List<String> encoded = Arrays.asList(A_START, A_LAST, B_START, B_LAST);
+    Span[] actual = codec.decode(encoded);
+    Span[] expected = new Span[] {new Span(0, 2, A_TYPE), new Span(2, 4, 
B_TYPE)};
+    Assertions.assertArrayEquals(expected, actual, "Multiple consecutive spans 
should be decoded correctly");
+  }
+
+  /**
+   * Decode with four-token span (START, CONTINUE, CONTINUE, LAST).
+   */
+  @Test
+  void testDecodeFourTokenSpan() {
+    List<String> encoded = Arrays.asList(A_START, A_CONTINUE, A_CONTINUE, 
A_LAST);
+    Span[] actual = codec.decode(encoded);
+    Span[] expected = new Span[] {new Span(0, 4, A_TYPE)};
+    Assertions.assertArrayEquals(expected, actual, "Four-token span should be 
decoded correctly");
+  }
+
+  /**
+   * Encode with null type and typed spans mixed.
+   */
+  @Test
+  void testEncodeMixedNullAndTypedTypes() {
+    Span[] spans = new Span[] {new Span(0, 2, null), new Span(3, 4, A_TYPE)};
+    String[] actual = codec.encode(spans, 5);
+    String[] expected = new String[] {"default-start", "default-last", OTHER, 
A_UNIT, OTHER};
+    Assertions.assertArrayEquals(expected, actual, "Mixed null and typed spans 
should encode correctly");
+  }
+
+  /**
+   * Encode with five-token span (START + CONTINUE*3 + LAST).
+   */
+  @Test
+  void testEncodeFiveTokenSpan() {
+    Span[] spans = new Span[] {new Span(0, 5, A_TYPE)};
+    String[] actual = codec.encode(spans, 7);
+    String[] expected = new String[] {A_START, A_CONTINUE, A_CONTINUE, 
A_CONTINUE, A_LAST, OTHER, OTHER};
+    Assertions.assertArrayEquals(expected, actual, "Five-token span should 
have three CONTINUE tags");
+  }
+
+  /**
+   * areOutcomesCompatible: default-prefixed outcomes should work.
+   */
+  @Test
+  void testCompatibilityDefaultPrefix() {
+    Assertions.assertTrue(codec.areOutcomesCompatible(
+        new String[] {"default-start", "default-last", OTHER}),
+        "Default-prefixed START and LAST should be compatible");
+  }
+
+  /**
+   * areOutcomesCompatible: default-prefixed with CONTINUE should work.
+   */
+  @Test
+  void testCompatibilityDefaultPrefixWithContinue() {
+    Assertions.assertTrue(codec.areOutcomesCompatible(
+        new String[] {"default-start", "default-cont", "default-last", OTHER}),
+        "Default-prefixed START, CONTINUE, LAST should be compatible");
+  }
+
+  /**
+   * areOutcomesCompatible: default-prefixed START without LAST should fail.
+   */
+  @Test
+  void testCompatibilityDefaultPrefixStartNoLast() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(
+        new String[] {"default-start", A_UNIT}),
+        "Default-prefixed START without matching LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: mixed default and typed outcomes with mismatch.
+   */
+  @Test
+  void testCompatibilityMixedDefaultTypedMismatch() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(
+        new String[] {"default-start", "default-last", A_CONTINUE}),
+        "Typed CONTINUE without matching typed START/LAST should be 
incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: three types all valid.
+   */
+  @Test
+  void testCompatibilityThreeTypesValid() {
+    Assertions.assertTrue(codec.areOutcomesCompatible(
+        new String[] {A_START, A_LAST, B_START, B_LAST, C_START, C_LAST, 
OTHER}),
+        "Three valid types should be compatible");
+  }
+
+  /**
+   * areOutcomesCompatible: three types with one missing LAST.
+   */
+  @Test
+  void testCompatibilityThreeTypesOneMissingLast() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(
+        new String[] {A_START, A_LAST, B_START, B_LAST, C_START, OTHER}),
+        "Three types with one missing LAST should be incompatible");
+  }
+
+  /**
+   * areOutcomesCompatible: three types with one having CONTINUE but no START.
+   */
+  @Test
+  void testCompatibilityThreeTypesOneContinueNoStart() {
+    Assertions.assertFalse(codec.areOutcomesCompatible(
+        new String[] {A_START, A_LAST, B_START, B_LAST, C_CONTINUE, C_LAST, 
OTHER}),
+        "Three types with one CONTINUE missing START should be incompatible");
+  }
 
 }

Reply via email to