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

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 2f0a4f5108 Add tests to increase code coverage: 
`CharSequenceReaderTest`, `NumberValueTest`, etc.
2f0a4f5108 is described below

commit 2f0a4f5108d815ce8b5f0f95555df86492545d11
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Feb 1 20:08:22 2026 +0900

    Add tests to increase code coverage: `CharSequenceReaderTest`, 
`NumberValueTest`, etc.
---
 src/test/java/groovy/lang/SpreadMapJUnit5Test.java | 240 ++++++++++++++
 src/test/java/groovy/time/DurationJUnit5Test.java  | 233 ++++++++++++++
 .../time/TimeDatumDependentDurationJUnit5Test.java | 177 +++++++++++
 .../java/groovy/time/TimeDurationJUnit5Test.java   | 218 +++++++++++++
 .../groovy/syntax/ReductionJUnit5Test.java         | 263 ++++++++++++++++
 .../codehaus/groovy/syntax/TokenJUnit5Test.java    | 262 ++++++++++++++++
 .../groovy/util/CharSequenceReaderTest.java        | 189 ++++++++++++
 .../java/groovy/json/StringEscapeUtilsTest.java    | 343 +++++++++++++++++++++
 .../json/internal/CharSequenceValueTest.java       | 337 ++++++++++++++++++++
 .../groovy/json/internal/NumberValueTest.java      | 320 +++++++++++++++++++
 10 files changed, 2582 insertions(+)

diff --git a/src/test/java/groovy/lang/SpreadMapJUnit5Test.java 
b/src/test/java/groovy/lang/SpreadMapJUnit5Test.java
new file mode 100644
index 0000000000..39392f4340
--- /dev/null
+++ b/src/test/java/groovy/lang/SpreadMapJUnit5Test.java
@@ -0,0 +1,240 @@
+/*
+ *  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 groovy.lang;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link SpreadMap}.
+ */
+class SpreadMapJUnit5Test {
+
+    private SpreadMap spreadMap;
+
+    @BeforeEach
+    void setUp() {
+        String[] entries = new String[]{"key1", "value1", "key2", "value2"};
+        spreadMap = new SpreadMap(entries);
+    }
+
+    @Test
+    void testConstructFromArray() {
+        Object[] entries = {"a", 1, "b", 2, "c", 3};
+        SpreadMap map = new SpreadMap(entries);
+        assertEquals(3, map.size());
+        assertEquals(1, map.get("a"));
+        assertEquals(2, map.get("b"));
+        assertEquals(3, map.get("c"));
+    }
+
+    @Test
+    void testConstructFromMap() {
+        Map<String, Integer> source = new HashMap<>();
+        source.put("x", 10);
+        source.put("y", 20);
+        SpreadMap map = new SpreadMap(source);
+        assertEquals(2, map.size());
+        assertEquals(10, map.get("x"));
+        assertEquals(20, map.get("y"));
+    }
+
+    @Test
+    void testConstructFromList() {
+        List<Object> list = Arrays.asList("first", 100, "second", 200);
+        SpreadMap map = new SpreadMap(list);
+        assertEquals(2, map.size());
+        assertEquals(100, map.get("first"));
+        assertEquals(200, map.get("second"));
+    }
+
+    @Test
+    void testPutThrowsException() {
+        assertThrows(RuntimeException.class, () -> spreadMap.put("newKey", 
"newValue"));
+    }
+
+    @Test
+    void testRemoveThrowsException() {
+        assertThrows(RuntimeException.class, () -> spreadMap.remove("key1"));
+    }
+
+    @Test
+    void testPutAllThrowsException() {
+        Map<String, String> newMap = new HashMap<>();
+        newMap.put("a", "b");
+        assertThrows(RuntimeException.class, () -> spreadMap.putAll(newMap));
+    }
+
+    @Test
+    void testEqualsWithSameSpreadMap() {
+        SpreadMap map1 = new SpreadMap(new Object[]{"a", 1, "b", 2});
+        SpreadMap map2 = new SpreadMap(new Object[]{"a", 1, "b", 2});
+        assertEquals(map1, map2);
+    }
+
+    @Test
+    void testEqualsWithSelf() {
+        assertTrue(spreadMap.equals(spreadMap));
+    }
+
+    @Test
+    void testEqualsWithDifferentSize() {
+        SpreadMap map1 = new SpreadMap(new Object[]{"a", 1});
+        SpreadMap map2 = new SpreadMap(new Object[]{"a", 1, "b", 2});
+        assertNotEquals(map1, map2);
+    }
+
+    @Test
+    void testEqualsWithDifferentValues() {
+        SpreadMap map1 = new SpreadMap(new Object[]{"a", 1});
+        SpreadMap map2 = new SpreadMap(new Object[]{"a", 2});
+        assertNotEquals(map1, map2);
+    }
+
+    @Test
+    void testEqualsWithNonSpreadMap() {
+        Map<String, Integer> regularMap = new HashMap<>();
+        regularMap.put("key1", 1);
+        assertFalse(spreadMap.equals(regularMap));
+    }
+
+    @Test
+    void testEqualsWithNull() {
+        assertFalse(spreadMap.equals((SpreadMap) null));
+    }
+
+    @Test
+    void testHashCode() {
+        SpreadMap map1 = new SpreadMap(new Object[]{"a", 1, "b", 2});
+        SpreadMap map2 = new SpreadMap(new Object[]{"a", 1, "b", 2});
+        assertEquals(map1.hashCode(), map2.hashCode());
+    }
+
+    @Test
+    void testHashCodeWithNullKey() {
+        SpreadMap map = new SpreadMap(new Object[]{null, "value"});
+        // Should not throw, hashCode should handle null keys
+        int hash = map.hashCode();
+        // Just verify it doesn't throw and returns something
+        assertTrue(hash != 0 || hash == 0); // Always true, just checking no 
exception
+    }
+
+    @Test
+    void testToStringEmpty() {
+        SpreadMap emptyMap = new SpreadMap(new Object[]{});
+        assertEquals("*:[:]", emptyMap.toString());
+    }
+
+    @Test
+    void testToStringWithEntries() {
+        SpreadMap map = new SpreadMap(new Object[]{"a", 1});
+        String result = map.toString();
+        assertTrue(result.startsWith("*:["));
+        assertTrue(result.endsWith("]"));
+        assertTrue(result.contains("a:1"));
+    }
+
+    @Test
+    void testToStringMultipleEntries() {
+        SpreadMap map = new SpreadMap(new Object[]{"a", 1, "b", 2});
+        String result = map.toString();
+        assertTrue(result.startsWith("*:["));
+        assertTrue(result.endsWith("]"));
+        assertTrue(result.contains(":"));
+        assertTrue(result.contains(", "));
+    }
+
+    @Test
+    void testContainsKey() {
+        assertTrue(spreadMap.containsKey("key1"));
+        assertTrue(spreadMap.containsKey("key2"));
+        assertFalse(spreadMap.containsKey("nonexistent"));
+    }
+
+    @Test
+    void testContainsValue() {
+        assertTrue(spreadMap.containsValue("value1"));
+        assertTrue(spreadMap.containsValue("value2"));
+        assertFalse(spreadMap.containsValue("nonexistent"));
+    }
+
+    @Test
+    void testKeySet() {
+        assertEquals(2, spreadMap.keySet().size());
+        assertTrue(spreadMap.keySet().contains("key1"));
+        assertTrue(spreadMap.keySet().contains("key2"));
+    }
+
+    @Test
+    void testValues() {
+        assertEquals(2, spreadMap.values().size());
+        assertTrue(spreadMap.values().contains("value1"));
+        assertTrue(spreadMap.values().contains("value2"));
+    }
+
+    @Test
+    void testEntrySet() {
+        assertEquals(2, spreadMap.entrySet().size());
+    }
+
+    @Test
+    void testIsEmpty() {
+        assertFalse(spreadMap.isEmpty());
+        SpreadMap emptyMap = new SpreadMap(new Object[]{});
+        assertTrue(emptyMap.isEmpty());
+    }
+
+    @Test
+    void testConstructFromEmptyList() {
+        List<Object> emptyList = new ArrayList<>();
+        SpreadMap map = new SpreadMap(emptyList);
+        assertTrue(map.isEmpty());
+        assertEquals("*:[:]", map.toString());
+    }
+
+    @Test
+    void testWithIntegerKeysAndValues() {
+        SpreadMap map = new SpreadMap(new Object[]{1, "one", 2, "two"});
+        assertEquals("one", map.get(1));
+        assertEquals("two", map.get(2));
+    }
+
+    @Test
+    void testWithMixedTypes() {
+        SpreadMap map = new SpreadMap(new Object[]{"string", 123, 456, 
"number", null, "nullKey"});
+        assertEquals(123, map.get("string"));
+        assertEquals("number", map.get(456));
+        assertEquals("nullKey", map.get(null));
+    }
+
+    @Test
+    void testHashCodeConsistency() {
+        int hash1 = spreadMap.hashCode();
+        int hash2 = spreadMap.hashCode();
+        assertEquals(hash1, hash2);
+    }
+}
diff --git a/src/test/java/groovy/time/DurationJUnit5Test.java 
b/src/test/java/groovy/time/DurationJUnit5Test.java
new file mode 100644
index 0000000000..85ea5801a2
--- /dev/null
+++ b/src/test/java/groovy/time/DurationJUnit5Test.java
@@ -0,0 +1,233 @@
+/*
+ *  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 groovy.time;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link Duration} class.
+ */
+class DurationJUnit5Test {
+
+    @Test
+    void testConstructor() {
+        Duration duration = new Duration(1, 2, 3, 4, 5);
+        assertEquals(1, duration.getDays());
+        assertEquals(2, duration.getHours());
+        assertEquals(3, duration.getMinutes());
+        assertEquals(4, duration.getSeconds());
+        assertEquals(5, duration.getMillis());
+    }
+
+    @Test
+    void testToMilliseconds() {
+        Duration duration = new Duration(1, 0, 0, 0, 0);
+        assertEquals(24 * 60 * 60 * 1000L, duration.toMilliseconds());
+    }
+
+    @Test
+    void testToMillisecondsComplex() {
+        Duration duration = new Duration(1, 2, 3, 4, 5);
+        long expected = ((((1L * 24 + 2) * 60 + 3) * 60 + 4) * 1000) + 5;
+        assertEquals(expected, duration.toMilliseconds());
+    }
+
+    @Test
+    void testPlusDuration() {
+        Duration d1 = new Duration(1, 2, 3, 4, 5);
+        Duration d2 = new Duration(1, 1, 1, 1, 1);
+        Duration result = d1.plus(d2);
+        assertEquals(2, result.getDays());
+        assertEquals(3, result.getHours());
+        assertEquals(4, result.getMinutes());
+        assertEquals(5, result.getSeconds());
+        assertEquals(6, result.getMillis());
+    }
+
+    @Test
+    void testPlusTimeDuration() {
+        Duration d = new Duration(1, 0, 0, 0, 0);
+        TimeDuration td = new TimeDuration(0, 2, 30, 0, 0);
+        Duration result = d.plus(td);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testPlusDatumDependentDuration() {
+        Duration d = new Duration(1, 0, 0, 0, 0);
+        DatumDependentDuration ddd = new DatumDependentDuration(1, 2, 3, 4, 5, 
6, 7);
+        DatumDependentDuration result = d.plus(ddd);
+        assertNotNull(result);
+        assertEquals(1, result.getYears());
+        assertEquals(2, result.getMonths());
+    }
+
+    @Test
+    void testMinusDuration() {
+        Duration d1 = new Duration(5, 10, 30, 45, 500);
+        Duration d2 = new Duration(2, 5, 15, 20, 200);
+        Duration result = d1.minus(d2);
+        assertEquals(3, result.getDays());
+        assertEquals(5, result.getHours());
+        assertEquals(15, result.getMinutes());
+        assertEquals(25, result.getSeconds());
+        assertEquals(300, result.getMillis());
+    }
+
+    @Test
+    void testMinusTimeDuration() {
+        Duration d = new Duration(2, 5, 30, 0, 0);
+        TimeDuration td = new TimeDuration(1, 2, 15, 0, 0);
+        TimeDuration result = d.minus(td);
+        assertEquals(1, result.getDays());
+        assertEquals(3, result.getHours());
+        assertEquals(15, result.getMinutes());
+    }
+
+    @Test
+    void testMinusDatumDependentDuration() {
+        Duration d = new Duration(10, 5, 30, 20, 100);
+        DatumDependentDuration ddd = new DatumDependentDuration(1, 2, 3, 1, 
10, 5, 50);
+        DatumDependentDuration result = d.minus(ddd);
+        assertEquals(-1, result.getYears());
+        assertEquals(-2, result.getMonths());
+        assertEquals(7, result.getDays());
+    }
+
+    @Test
+    void testMinusTimeDatumDependentDuration() {
+        Duration d = new Duration(10, 5, 30, 20, 100);
+        TimeDatumDependentDuration tddd = new TimeDatumDependentDuration(1, 2, 
3, 1, 10, 5, 50);
+        TimeDatumDependentDuration result = d.minus(tddd);
+        assertEquals(-1, result.getYears());
+        assertEquals(-2, result.getMonths());
+    }
+
+    @Test
+    void testGetAgo() {
+        Duration duration = new Duration(1, 0, 0, 0, 0);
+        Date ago = duration.getAgo();
+        assertNotNull(ago);
+        
+        Calendar expected = Calendar.getInstance();
+        expected.add(Calendar.DAY_OF_YEAR, -1);
+        expected.set(Calendar.HOUR_OF_DAY, 0);
+        expected.set(Calendar.MINUTE, 0);
+        expected.set(Calendar.SECOND, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        
+        assertEquals(expected.getTimeInMillis(), ago.getTime());
+    }
+
+    @Test
+    void testGetFrom() {
+        Duration duration = new Duration(3, 0, 0, 0, 0);
+        BaseDuration.From from = duration.getFrom();
+        assertNotNull(from);
+        Date now = from.getNow();
+        assertNotNull(now);
+        
+        Calendar expected = Calendar.getInstance();
+        expected.add(Calendar.DAY_OF_YEAR, 3);
+        expected.set(Calendar.HOUR_OF_DAY, 0);
+        expected.set(Calendar.MINUTE, 0);
+        expected.set(Calendar.SECOND, 0);
+        expected.set(Calendar.MILLISECOND, 0);
+        
+        assertEquals(expected.getTimeInMillis(), now.getTime());
+    }
+
+    @Test
+    void testZeroDuration() {
+        Duration zero = new Duration(0, 0, 0, 0, 0);
+        assertEquals(0, zero.toMilliseconds());
+    }
+
+    @Test
+    void testNegativeDuration() {
+        Duration negative = new Duration(-1, -2, -3, -4, -5);
+        assertTrue(negative.toMilliseconds() < 0);
+    }
+
+    @Test
+    void testMillisecondsOnly() {
+        Duration millis = new Duration(0, 0, 0, 0, 500);
+        assertEquals(500, millis.toMilliseconds());
+    }
+
+    @Test
+    void testSecondsOnly() {
+        Duration seconds = new Duration(0, 0, 0, 45, 0);
+        assertEquals(45 * 1000L, seconds.toMilliseconds());
+    }
+
+    @Test
+    void testMinutesOnly() {
+        Duration minutes = new Duration(0, 0, 30, 0, 0);
+        assertEquals(30 * 60 * 1000L, minutes.toMilliseconds());
+    }
+
+    @Test
+    void testHoursOnly() {
+        Duration hours = new Duration(0, 12, 0, 0, 0);
+        assertEquals(12 * 60 * 60 * 1000L, hours.toMilliseconds());
+    }
+
+    @Test
+    void testDaysOnly() {
+        Duration days = new Duration(7, 0, 0, 0, 0);
+        assertEquals(7 * 24 * 60 * 60 * 1000L, days.toMilliseconds());
+    }
+
+    @Test
+    void testPlusWithZero() {
+        Duration d = new Duration(1, 2, 3, 4, 5);
+        Duration zero = new Duration(0, 0, 0, 0, 0);
+        Duration result = d.plus(zero);
+        assertEquals(1, result.getDays());
+        assertEquals(2, result.getHours());
+        assertEquals(3, result.getMinutes());
+        assertEquals(4, result.getSeconds());
+        assertEquals(5, result.getMillis());
+    }
+
+    @Test
+    void testMinusWithZero() {
+        Duration d = new Duration(1, 2, 3, 4, 5);
+        Duration zero = new Duration(0, 0, 0, 0, 0);
+        Duration result = d.minus(zero);
+        assertEquals(1, result.getDays());
+        assertEquals(2, result.getHours());
+        assertEquals(3, result.getMinutes());
+        assertEquals(4, result.getSeconds());
+        assertEquals(5, result.getMillis());
+    }
+
+    @Test
+    void testLargeDuration() {
+        Duration large = new Duration(365, 23, 59, 59, 999);
+        long expected = ((((365L * 24 + 23) * 60 + 59) * 60 + 59) * 1000) + 
999;
+        assertEquals(expected, large.toMilliseconds());
+    }
+}
diff --git 
a/src/test/java/groovy/time/TimeDatumDependentDurationJUnit5Test.java 
b/src/test/java/groovy/time/TimeDatumDependentDurationJUnit5Test.java
new file mode 100644
index 0000000000..b965f4ca13
--- /dev/null
+++ b/src/test/java/groovy/time/TimeDatumDependentDurationJUnit5Test.java
@@ -0,0 +1,177 @@
+/*
+ *  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 groovy.time;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link TimeDatumDependentDuration} class.
+ */
+class TimeDatumDependentDurationJUnit5Test {
+
+    @Test
+    void testConstructor() {
+        TimeDatumDependentDuration duration = new 
TimeDatumDependentDuration(1, 2, 3, 4, 5, 6, 7);
+        assertEquals(1, duration.getYears());
+        assertEquals(2, duration.getMonths());
+        assertEquals(3, duration.getDays());
+        assertEquals(4, duration.getHours());
+        assertEquals(5, duration.getMinutes());
+        assertEquals(6, duration.getSeconds());
+        assertEquals(7, duration.getMillis());
+    }
+
+    @Test
+    void testPlusDuration() {
+        TimeDatumDependentDuration tddd = new TimeDatumDependentDuration(1, 2, 
3, 4, 5, 6, 7);
+        Duration d = new Duration(1, 2, 3, 4, 5);
+        DatumDependentDuration result = tddd.plus(d);
+        assertTrue(result instanceof TimeDatumDependentDuration);
+        assertEquals(1, result.getYears());
+        assertEquals(2, result.getMonths());
+        assertEquals(4, result.getDays());
+        assertEquals(6, result.getHours());
+        assertEquals(8, result.getMinutes());
+        assertEquals(10, result.getSeconds());
+        assertEquals(12, result.getMillis());
+    }
+
+    @Test
+    void testPlusDatumDependentDuration() {
+        TimeDatumDependentDuration tddd1 = new TimeDatumDependentDuration(1, 
2, 3, 4, 5, 6, 7);
+        DatumDependentDuration ddd2 = new DatumDependentDuration(2, 3, 4, 5, 
6, 7, 8);
+        DatumDependentDuration result = tddd1.plus(ddd2);
+        assertTrue(result instanceof TimeDatumDependentDuration);
+        assertEquals(3, result.getYears());
+        assertEquals(5, result.getMonths());
+        assertEquals(7, result.getDays());
+        assertEquals(9, result.getHours());
+        assertEquals(11, result.getMinutes());
+        assertEquals(13, result.getSeconds());
+        assertEquals(15, result.getMillis());
+    }
+
+    @Test
+    void testMinusDuration() {
+        TimeDatumDependentDuration tddd = new TimeDatumDependentDuration(1, 2, 
10, 10, 30, 45, 500);
+        Duration d = new Duration(3, 2, 10, 15, 100);
+        DatumDependentDuration result = tddd.minus(d);
+        assertTrue(result instanceof TimeDatumDependentDuration);
+        assertEquals(1, result.getYears());
+        assertEquals(2, result.getMonths());
+        assertEquals(7, result.getDays());
+        assertEquals(8, result.getHours());
+        assertEquals(20, result.getMinutes());
+        assertEquals(30, result.getSeconds());
+        assertEquals(400, result.getMillis());
+    }
+
+    @Test
+    void testMinusDatumDependentDuration() {
+        TimeDatumDependentDuration tddd1 = new TimeDatumDependentDuration(5, 
6, 10, 10, 30, 45, 500);
+        DatumDependentDuration ddd2 = new DatumDependentDuration(2, 3, 4, 5, 
15, 20, 100);
+        DatumDependentDuration result = tddd1.minus(ddd2);
+        assertTrue(result instanceof TimeDatumDependentDuration);
+        assertEquals(3, result.getYears());
+        assertEquals(3, result.getMonths());
+        assertEquals(6, result.getDays());
+        assertEquals(5, result.getHours());
+        assertEquals(15, result.getMinutes());
+        assertEquals(25, result.getSeconds());
+        assertEquals(400, result.getMillis());
+    }
+
+    @Test
+    void testGetFrom() {
+        TimeDatumDependentDuration duration = new 
TimeDatumDependentDuration(1, 2, 3, 4, 5, 6, 7);
+        BaseDuration.From from = duration.getFrom();
+        assertNotNull(from);
+        Date now = from.getNow();
+        assertNotNull(now);
+        
+        Calendar expected = Calendar.getInstance();
+        expected.add(Calendar.YEAR, 1);
+        expected.add(Calendar.MONTH, 2);
+        expected.add(Calendar.DAY_OF_YEAR, 3);
+        expected.add(Calendar.HOUR_OF_DAY, 4);
+        expected.add(Calendar.MINUTE, 5);
+        expected.add(Calendar.SECOND, 6);
+        expected.add(Calendar.MILLISECOND, 7);
+        
+        long tolerance = 5000;
+        assertTrue(Math.abs(expected.getTimeInMillis() - now.getTime()) < 
tolerance);
+    }
+
+    @Test
+    void testZeroDuration() {
+        TimeDatumDependentDuration zero = new TimeDatumDependentDuration(0, 0, 
0, 0, 0, 0, 0);
+        assertEquals(0, zero.getYears());
+        assertEquals(0, zero.getMonths());
+        assertEquals(0, zero.getDays());
+        assertEquals(0, zero.getHours());
+        assertEquals(0, zero.getMinutes());
+        assertEquals(0, zero.getSeconds());
+        assertEquals(0, zero.getMillis());
+    }
+
+    @Test
+    void testNegativeDuration() {
+        TimeDatumDependentDuration negative = new 
TimeDatumDependentDuration(-1, -2, -3, -4, -5, -6, -7);
+        assertEquals(-1, negative.getYears());
+        assertEquals(-2, negative.getMonths());
+        assertEquals(-3, negative.getDays());
+        assertEquals(-4, negative.getHours());
+        assertEquals(-5, negative.getMinutes());
+        assertEquals(-6, negative.getSeconds());
+        assertEquals(-7, negative.getMillis());
+    }
+
+    @Test
+    void testPlusTimeDatumDependentDuration() {
+        TimeDatumDependentDuration tddd1 = new TimeDatumDependentDuration(1, 
2, 3, 4, 5, 6, 7);
+        TimeDatumDependentDuration tddd2 = new TimeDatumDependentDuration(1, 
1, 1, 1, 1, 1, 1);
+        DatumDependentDuration result = tddd1.plus(tddd2);
+        assertEquals(2, result.getYears());
+        assertEquals(3, result.getMonths());
+        assertEquals(4, result.getDays());
+        assertEquals(5, result.getHours());
+        assertEquals(6, result.getMinutes());
+        assertEquals(7, result.getSeconds());
+        assertEquals(8, result.getMillis());
+    }
+
+    @Test
+    void testMinusTimeDatumDependentDuration() {
+        TimeDatumDependentDuration tddd1 = new TimeDatumDependentDuration(5, 
6, 7, 8, 9, 10, 11);
+        TimeDatumDependentDuration tddd2 = new TimeDatumDependentDuration(1, 
2, 3, 4, 5, 6, 7);
+        DatumDependentDuration result = tddd1.minus(tddd2);
+        assertEquals(4, result.getYears());
+        assertEquals(4, result.getMonths());
+        assertEquals(4, result.getDays());
+        assertEquals(4, result.getHours());
+        assertEquals(4, result.getMinutes());
+        assertEquals(4, result.getSeconds());
+        assertEquals(4, result.getMillis());
+    }
+}
diff --git a/src/test/java/groovy/time/TimeDurationJUnit5Test.java 
b/src/test/java/groovy/time/TimeDurationJUnit5Test.java
new file mode 100644
index 0000000000..503b6563cc
--- /dev/null
+++ b/src/test/java/groovy/time/TimeDurationJUnit5Test.java
@@ -0,0 +1,218 @@
+/*
+ *  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 groovy.time;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link TimeDuration} class.
+ */
+class TimeDurationJUnit5Test {
+
+    @Test
+    void testConstructorWithoutDays() {
+        TimeDuration duration = new TimeDuration(2, 30, 45, 100);
+        assertEquals(0, duration.getDays());
+        assertEquals(2, duration.getHours());
+        assertEquals(30, duration.getMinutes());
+        assertEquals(45, duration.getSeconds());
+        assertEquals(100, duration.getMillis());
+    }
+
+    @Test
+    void testConstructorWithDays() {
+        TimeDuration duration = new TimeDuration(1, 2, 30, 45, 100);
+        assertEquals(1, duration.getDays());
+        assertEquals(2, duration.getHours());
+        assertEquals(30, duration.getMinutes());
+        assertEquals(45, duration.getSeconds());
+        assertEquals(100, duration.getMillis());
+    }
+
+    @Test
+    void testToMilliseconds() {
+        TimeDuration duration = new TimeDuration(1, 2, 3, 4);
+        long expected = (((1L * 60) + 2) * 60 + 3) * 1000 + 4;
+        assertEquals(expected, duration.toMilliseconds());
+    }
+
+    @Test
+    void testPlusDuration() {
+        // TimeDuration constructor: hours, minutes, seconds, millis
+        TimeDuration td1 = new TimeDuration(1, 2, 30, 100);
+        // Duration constructor: days, hours, minutes, seconds, millis
+        Duration d2 = new Duration(1, 1, 15, 20, 50);
+        Duration result = td1.plus(d2);
+        assertTrue(result instanceof TimeDuration);
+        // td1: days=0, hours=1, minutes=2, seconds=30, millis=100
+        // d2: days=1, hours=1, minutes=15, seconds=20, millis=50
+        // result: days=1, hours=2, minutes=17, seconds=50, millis=150 (no 
normalization)
+        assertEquals(1, result.getDays());
+        assertEquals(2, result.getHours());
+        assertEquals(17, result.getMinutes());
+        assertEquals(50, result.getSeconds());
+        assertEquals(150, result.getMillis());
+    }
+
+    @Test
+    void testPlusDatumDependentDuration() {
+        TimeDuration td = new TimeDuration(1, 2, 30, 45, 100);
+        DatumDependentDuration ddd = new DatumDependentDuration(2, 3, 4, 5, 
15, 10, 50);
+        DatumDependentDuration result = td.plus(ddd);
+        assertTrue(result instanceof TimeDatumDependentDuration);
+        assertEquals(2, result.getYears());
+        assertEquals(3, result.getMonths());
+        assertEquals(5, result.getDays());
+        assertEquals(7, result.getHours());
+        assertEquals(45, result.getMinutes());
+        assertEquals(55, result.getSeconds());
+        assertEquals(150, result.getMillis());
+    }
+
+    @Test
+    void testMinusDuration() {
+        TimeDuration td1 = new TimeDuration(2, 5, 30, 45, 100);
+        Duration d2 = new Duration(1, 2, 15, 20, 50);
+        Duration result = td1.minus(d2);
+        assertTrue(result instanceof TimeDuration);
+        assertEquals(1, result.getDays());
+        assertEquals(3, result.getHours());
+        assertEquals(15, result.getMinutes());
+        assertEquals(25, result.getSeconds());
+        assertEquals(50, result.getMillis());
+    }
+
+    @Test
+    void testMinusDatumDependentDuration() {
+        TimeDuration td = new TimeDuration(2, 5, 30, 45, 100);
+        DatumDependentDuration ddd = new DatumDependentDuration(1, 2, 1, 2, 
15, 20, 50);
+        DatumDependentDuration result = td.minus(ddd);
+        assertTrue(result instanceof TimeDatumDependentDuration);
+        assertEquals(-1, result.getYears());
+        assertEquals(-2, result.getMonths());
+        assertEquals(1, result.getDays());
+        assertEquals(3, result.getHours());
+        assertEquals(15, result.getMinutes());
+        assertEquals(25, result.getSeconds());
+        assertEquals(50, result.getMillis());
+    }
+
+    @Test
+    void testGetAgo() {
+        TimeDuration duration = new TimeDuration(0, 1, 0, 0, 0);
+        Date ago = duration.getAgo();
+        assertNotNull(ago);
+        
+        // The time should be approximately 1 hour ago (with some tolerance 
for test execution)
+        long expectedTime = System.currentTimeMillis() - (60 * 60 * 1000L);
+        long actualTime = ago.getTime();
+        assertTrue(Math.abs(expectedTime - actualTime) < 5000, "Time should be 
approximately 1 hour ago");
+    }
+
+    @Test
+    void testGetAgoWithAllComponents() {
+        TimeDuration duration = new TimeDuration(1, 2, 30, 45, 500);
+        Date ago = duration.getAgo();
+        assertNotNull(ago);
+        
+        Calendar expected = Calendar.getInstance();
+        expected.add(Calendar.DAY_OF_YEAR, -1);
+        expected.add(Calendar.HOUR_OF_DAY, -2);
+        expected.add(Calendar.MINUTE, -30);
+        expected.add(Calendar.SECOND, -45);
+        expected.add(Calendar.MILLISECOND, -500);
+        
+        // Allow some tolerance for test execution time
+        long tolerance = 5000;
+        assertTrue(Math.abs(expected.getTimeInMillis() - ago.getTime()) < 
tolerance);
+    }
+
+    @Test
+    void testGetFrom() {
+        TimeDuration duration = new TimeDuration(0, 1, 0, 0, 0);
+        BaseDuration.From from = duration.getFrom();
+        assertNotNull(from);
+        Date now = from.getNow();
+        assertNotNull(now);
+        
+        // The time should be approximately 1 hour from now
+        long expectedTime = System.currentTimeMillis() + (60 * 60 * 1000L);
+        long actualTime = now.getTime();
+        assertTrue(Math.abs(expectedTime - actualTime) < 5000, "Time should be 
approximately 1 hour from now");
+    }
+
+    @Test
+    void testGetFromWithAllComponents() {
+        TimeDuration duration = new TimeDuration(1, 2, 30, 45, 500);
+        BaseDuration.From from = duration.getFrom();
+        Date now = from.getNow();
+        assertNotNull(now);
+        
+        Calendar expected = Calendar.getInstance();
+        expected.add(Calendar.DAY_OF_YEAR, 1);
+        expected.add(Calendar.HOUR_OF_DAY, 2);
+        expected.add(Calendar.MINUTE, 30);
+        expected.add(Calendar.SECOND, 45);
+        expected.add(Calendar.MILLISECOND, 500);
+        
+        long tolerance = 5000;
+        assertTrue(Math.abs(expected.getTimeInMillis() - now.getTime()) < 
tolerance);
+    }
+
+    @Test
+    void testZeroTimeDuration() {
+        TimeDuration zero = new TimeDuration(0, 0, 0, 0);
+        assertEquals(0, zero.toMilliseconds());
+    }
+
+    @Test
+    void testNegativeTimeDuration() {
+        TimeDuration negative = new TimeDuration(-1, -30, -15, -500);
+        assertTrue(negative.toMilliseconds() < 0);
+    }
+
+    @Test
+    void testOnlyMillis() {
+        TimeDuration millis = new TimeDuration(0, 0, 0, 500);
+        assertEquals(500, millis.toMilliseconds());
+    }
+
+    @Test
+    void testOnlySeconds() {
+        TimeDuration seconds = new TimeDuration(0, 0, 30, 0);
+        assertEquals(30 * 1000L, seconds.toMilliseconds());
+    }
+
+    @Test
+    void testOnlyMinutes() {
+        TimeDuration minutes = new TimeDuration(0, 15, 0, 0);
+        assertEquals(15 * 60 * 1000L, minutes.toMilliseconds());
+    }
+
+    @Test
+    void testOnlyHours() {
+        TimeDuration hours = new TimeDuration(3, 0, 0, 0);
+        assertEquals(3 * 60 * 60 * 1000L, hours.toMilliseconds());
+    }
+}
diff --git a/src/test/java/org/codehaus/groovy/syntax/ReductionJUnit5Test.java 
b/src/test/java/org/codehaus/groovy/syntax/ReductionJUnit5Test.java
new file mode 100644
index 0000000000..8389b80632
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/syntax/ReductionJUnit5Test.java
@@ -0,0 +1,263 @@
+/*
+ *  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.codehaus.groovy.syntax;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link Reduction}.
+ */
+class ReductionJUnit5Test {
+
+    @Test
+    void testConstructorWithToken() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        assertEquals(1, reduction.size());
+        assertSame(root, reduction.getRoot());
+    }
+
+    @Test
+    void testEmptyReduction() {
+        assertTrue(Reduction.EMPTY.isEmpty());
+        assertEquals(0, Reduction.EMPTY.size());
+        assertNull(Reduction.EMPTY.getRoot());
+    }
+
+    @Test
+    void testNewContainer() {
+        Reduction container = Reduction.newContainer();
+        assertFalse(container.isEmpty());
+        assertEquals(1, container.size());
+        assertSame(Token.NULL, container.getRoot());
+    }
+
+    @Test
+    void testAdd() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        Token child = new Token(Types.INTEGER_NUMBER, "5", 1, 3);
+        reduction.add(child);
+        
+        assertEquals(2, reduction.size());
+        assertSame(child, reduction.get(1));
+    }
+
+    @Test
+    void testAddMultiple() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        Token child1 = new Token(Types.INTEGER_NUMBER, "5", 1, 3);
+        Token child2 = new Token(Types.INTEGER_NUMBER, "3", 1, 5);
+        reduction.add(child1);
+        reduction.add(child2);
+        
+        assertEquals(3, reduction.size());
+        assertSame(child1, reduction.get(1));
+        assertSame(child2, reduction.get(2));
+    }
+
+    @Test
+    void testSetAtIndex() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        Token child = new Token(Types.INTEGER_NUMBER, "5", 1, 3);
+        reduction.set(1, child);
+        
+        assertEquals(2, reduction.size());
+        assertSame(child, reduction.get(1));
+    }
+
+    @Test
+    void testSetAtGapIndex() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        Token child = new Token(Types.INTEGER_NUMBER, "5", 1, 3);
+        reduction.set(5, child);
+        
+        assertEquals(6, reduction.size());
+        assertNull(reduction.get(1));
+        assertNull(reduction.get(2));
+        assertNull(reduction.get(3));
+        assertNull(reduction.get(4));
+        assertSame(child, reduction.get(5));
+    }
+
+    @Test
+    void testSetNonTokenAsRootThrows() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        Reduction nestedReduction = Reduction.newContainer();
+        
+        assertThrows(GroovyBugError.class, () -> reduction.set(0, 
nestedReduction));
+    }
+
+    @Test
+    void testRemove() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        Token child1 = new Token(Types.INTEGER_NUMBER, "5", 1, 3);
+        Token child2 = new Token(Types.INTEGER_NUMBER, "3", 1, 5);
+        reduction.add(child1);
+        reduction.add(child2);
+        
+        CSTNode removed = reduction.remove(1);
+        
+        assertSame(child1, removed);
+        assertEquals(2, reduction.size());
+        assertSame(child2, reduction.get(1));
+    }
+
+    @Test
+    void testRemoveRootThrows() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        assertThrows(GroovyBugError.class, () -> reduction.remove(0));
+    }
+
+    @Test
+    void testGetBeyondSize() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        assertNull(reduction.get(10));
+    }
+
+    @Test
+    void testMarkAsExpression() {
+        // Use PLUS operator which is not a complex expression type by default
+        Token root = Token.newSymbol(Types.PLUS, 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        assertFalse(reduction.isAnExpression());
+        reduction.markAsExpression();
+        assertTrue(reduction.isAnExpression());
+    }
+
+    @Test
+    void testIsAnExpressionWithComplexExpression() {
+        Token root = Token.newSymbol(Types.LEFT_PARENTHESIS, 1, 1);
+        Reduction reduction = new Reduction(root);
+        // Types.LEFT_PARENTHESIS is categorized as a complex expression type
+        // Test the behavior
+        assertFalse(reduction.isAnExpression());
+    }
+
+    @Test
+    void testAsReduction() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        assertSame(reduction, reduction.asReduction());
+    }
+
+    @Test
+    void testSize() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        assertEquals(1, reduction.size());
+        
+        reduction.add(new Token(Types.INTEGER_NUMBER, "1", 1, 3));
+        assertEquals(2, reduction.size());
+        
+        reduction.add(new Token(Types.INTEGER_NUMBER, "2", 1, 5));
+        assertEquals(3, reduction.size());
+    }
+
+    @Test
+    void testHasChildren() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        assertFalse(reduction.hasChildren());
+        
+        reduction.add(new Token(Types.INTEGER_NUMBER, "1", 1, 3));
+        assertTrue(reduction.hasChildren());
+    }
+
+    @Test
+    void testChildren() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        assertEquals(0, reduction.children());
+        
+        reduction.add(new Token(Types.INTEGER_NUMBER, "1", 1, 3));
+        assertEquals(1, reduction.children());
+        
+        reduction.add(new Token(Types.INTEGER_NUMBER, "2", 1, 5));
+        assertEquals(2, reduction.children());
+    }
+
+    @Test
+    void testGetWithSafe() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        // Safe mode returns Token.NULL for out of bounds
+        CSTNode result = reduction.get(10, true);
+        assertSame(Token.NULL, result);
+        
+        // Regular mode returns null
+        assertNull(reduction.get(10, false));
+    }
+
+    @Test
+    void testGetRoot() {
+        Token root = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction = new Reduction(root);
+        
+        assertSame(root, reduction.getRoot());
+        assertSame(root, reduction.getRoot(false));
+        assertSame(root, reduction.getRoot(true));
+    }
+
+    @Test
+    void testEmptyReductionGetRootSafe() {
+        // Safe mode on empty reduction
+        Token result = Reduction.EMPTY.getRoot(true);
+        assertSame(Token.NULL, result);
+    }
+
+    @Test
+    void testAddChildrenOf() {
+        Token root1 = new Token(Types.PLUS, "+", 1, 1);
+        Reduction reduction1 = new Reduction(root1);
+        
+        Token root2 = new Token(Types.MINUS, "-", 1, 1);
+        Reduction reduction2 = new Reduction(root2);
+        Token child1 = new Token(Types.INTEGER_NUMBER, "1", 1, 3);
+        Token child2 = new Token(Types.INTEGER_NUMBER, "2", 1, 5);
+        reduction2.add(child1);
+        reduction2.add(child2);
+        
+        reduction1.addChildrenOf(reduction2);
+        
+        assertEquals(3, reduction1.size());
+        assertSame(child1, reduction1.get(1));
+        assertSame(child2, reduction1.get(2));
+    }
+}
diff --git a/src/test/java/org/codehaus/groovy/syntax/TokenJUnit5Test.java 
b/src/test/java/org/codehaus/groovy/syntax/TokenJUnit5Test.java
new file mode 100644
index 0000000000..d8301b7cf5
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/syntax/TokenJUnit5Test.java
@@ -0,0 +1,262 @@
+/*
+ *  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.codehaus.groovy.syntax;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link Token}.
+ */
+class TokenJUnit5Test {
+
+    private static final int LINE = 11;
+    private static final int COLUMN = 33;
+
+    @Test
+    void testConstruct() {
+        Token token = new Token(42, "forty-two", LINE, COLUMN);
+
+        assertEquals(42, token.getType());
+        assertEquals("forty-two", token.getText());
+        assertEquals(LINE, token.getStartLine());
+        assertEquals(COLUMN, token.getStartColumn());
+    }
+
+    @Test
+    void testGetMeaning() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        assertEquals(Types.PLUS, token.getMeaning());
+    }
+
+    @Test
+    void testSetMeaning() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        token.setMeaning(Types.MINUS);
+        assertEquals(Types.MINUS, token.getMeaning());
+        assertEquals(Types.PLUS, token.getType()); // Type unchanged
+    }
+
+    @Test
+    void testEofTokenImmutable() {
+        Token eof = Token.EOF;
+        eof.setMeaning(Types.PLUS);
+        assertEquals(Types.EOF, eof.getMeaning()); // Should not change
+        eof.setText("changed");
+        assertEquals("", eof.getText()); // Should not change
+    }
+
+    @Test
+    void testNullTokenImmutable() {
+        Token nullToken = Token.NULL;
+        nullToken.setMeaning(Types.PLUS);
+        assertEquals(Types.UNKNOWN, nullToken.getMeaning()); // Should not 
change
+        nullToken.setText("changed");
+        assertEquals("", nullToken.getText()); // Should not change
+    }
+
+    @Test
+    void testDup() {
+        Token original = new Token(Types.IDENTIFIER, "myVar", LINE, COLUMN);
+        original.setMeaning(Types.LEFT_SQUARE_BRACKET);
+        
+        Token copy = original.dup();
+        
+        assertEquals(original.getType(), copy.getType());
+        assertEquals(original.getText(), copy.getText());
+        assertEquals(original.getMeaning(), copy.getMeaning());
+        assertEquals(original.getStartLine(), copy.getStartLine());
+        assertEquals(original.getStartColumn(), copy.getStartColumn());
+        
+        // Verify they are different objects
+        assertNotSame(original, copy);
+    }
+
+    @Test
+    void testSize() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        assertEquals(1, token.size());
+    }
+
+    @Test
+    void testGetReturnsThis() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        assertSame(token, token.get(0));
+    }
+
+    @Test
+    void testGetWithInvalidIndexThrows() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        assertThrows(org.codehaus.groovy.GroovyBugError.class, () -> 
token.get(1));
+    }
+
+    @Test
+    void testGetRoot() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        assertSame(token, token.getRoot());
+    }
+
+    @Test
+    void testGetRootText() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        assertEquals("+", token.getRootText());
+    }
+
+    @Test
+    void testAsReduction() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        Reduction reduction = token.asReduction();
+        assertNotNull(reduction);
+        assertEquals(1, reduction.size());
+        assertSame(token, reduction.getRoot());
+    }
+
+    @Test
+    void testAsReductionWithSecond() {
+        Token token = new Token(Types.PLUS, "+", LINE, COLUMN);
+        Token second = new Token(Types.INTEGER_NUMBER, "5", LINE, COLUMN + 1);
+        Reduction reduction = token.asReduction(second);
+        assertEquals(2, reduction.size());
+        assertSame(token, reduction.getRoot());
+        assertSame(second, reduction.get(1));
+    }
+
+    @Test
+    void testAsReductionWithThird() {
+        Token first = new Token(Types.PLUS, "+", LINE, COLUMN);
+        Token second = new Token(Types.INTEGER_NUMBER, "5", LINE, COLUMN + 1);
+        Token third = new Token(Types.INTEGER_NUMBER, "3", LINE, COLUMN + 3);
+        Reduction reduction = first.asReduction(second, third);
+        assertEquals(3, reduction.size());
+        assertSame(first, reduction.getRoot());
+        assertSame(second, reduction.get(1));
+        assertSame(third, reduction.get(2));
+    }
+
+    @Test
+    void testAsReductionWithFourth() {
+        Token first = new Token(Types.PLUS, "+", LINE, COLUMN);
+        Token second = new Token(Types.INTEGER_NUMBER, "5", LINE, COLUMN + 1);
+        Token third = new Token(Types.INTEGER_NUMBER, "3", LINE, COLUMN + 3);
+        Token fourth = new Token(Types.INTEGER_NUMBER, "7", LINE, COLUMN + 5);
+        Reduction reduction = first.asReduction(second, third, fourth);
+        assertEquals(4, reduction.size());
+    }
+
+    @Test
+    void testNewKeyword() {
+        Token ifToken = Token.newKeyword("if", LINE, COLUMN);
+        assertNotNull(ifToken);
+        assertEquals(Types.KEYWORD_IF, ifToken.getType());
+        assertEquals("if", ifToken.getText());
+    }
+
+    @Test
+    void testNewKeywordNotKeyword() {
+        Token notKeyword = Token.newKeyword("notAKeyword", LINE, COLUMN);
+        assertNull(notKeyword);
+    }
+
+    @Test
+    void testNewString() {
+        Token stringToken = Token.newString("hello", LINE, COLUMN);
+        assertEquals(Types.STRING, stringToken.getType());
+        assertEquals("hello", stringToken.getText());
+    }
+
+    @Test
+    void testNewIdentifier() {
+        Token idToken = Token.newIdentifier("myVariable", LINE, COLUMN);
+        assertEquals(Types.IDENTIFIER, idToken.getType());
+        assertEquals("myVariable", idToken.getText());
+    }
+
+    @Test
+    void testNewInteger() {
+        Token intToken = Token.newInteger("42", LINE, COLUMN);
+        assertEquals(Types.INTEGER_NUMBER, intToken.getType());
+        assertEquals("42", intToken.getText());
+    }
+
+    @Test
+    void testNewDecimal() {
+        Token decimalToken = Token.newDecimal("3.14", LINE, COLUMN);
+        assertEquals(Types.DECIMAL_NUMBER, decimalToken.getType());
+        assertEquals("3.14", decimalToken.getText());
+    }
+
+    @Test
+    void testNewSymbolByType() {
+        Token plusToken = Token.newSymbol(Types.PLUS, LINE, COLUMN);
+        assertEquals(Types.PLUS, plusToken.getType());
+        assertEquals("+", plusToken.getText());
+    }
+
+    @Test
+    void testNewSymbolByText() {
+        Token plusToken = Token.newSymbol("+", LINE, COLUMN);
+        assertEquals(Types.PLUS, plusToken.getType());
+        assertEquals("+", plusToken.getText());
+    }
+
+    @Test
+    void testNewPlaceholder() {
+        Token placeholder = Token.newPlaceholder(Types.SYNTH_METHOD);
+        assertEquals(Types.UNKNOWN, placeholder.getType());
+        assertEquals(Types.SYNTH_METHOD, placeholder.getMeaning());
+        assertEquals("", placeholder.getText());
+        assertEquals(-1, placeholder.getStartLine());
+        assertEquals(-1, placeholder.getStartColumn());
+    }
+
+    @Test
+    void testSetText() {
+        Token token = new Token(Types.STRING, "original", LINE, COLUMN);
+        token.setText("changed");
+        assertEquals("changed", token.getText());
+    }
+
+    @Test
+    void testKeywords() {
+        // Test a selection of keywords
+        assertKeyword("class", Types.KEYWORD_CLASS);
+        assertKeyword("def", Types.KEYWORD_DEF);
+        assertKeyword("if", Types.KEYWORD_IF);
+        assertKeyword("else", Types.KEYWORD_ELSE);
+        assertKeyword("while", Types.KEYWORD_WHILE);
+        assertKeyword("for", Types.KEYWORD_FOR);
+        assertKeyword("return", Types.KEYWORD_RETURN);
+        assertKeyword("try", Types.KEYWORD_TRY);
+        assertKeyword("catch", Types.KEYWORD_CATCH);
+        assertKeyword("finally", Types.KEYWORD_FINALLY);
+        assertKeyword("throw", Types.KEYWORD_THROW);
+        assertKeyword("new", Types.KEYWORD_NEW);
+        assertKeyword("true", Types.KEYWORD_TRUE);
+        assertKeyword("false", Types.KEYWORD_FALSE);
+        assertKeyword("null", Types.KEYWORD_NULL);
+    }
+
+    private void assertKeyword(String text, int expectedType) {
+        Token token = Token.newKeyword(text, LINE, COLUMN);
+        assertNotNull(token, "Expected keyword for: " + text);
+        assertEquals(expectedType, token.getType());
+        assertEquals(text, token.getText());
+    }
+}
diff --git a/src/test/java/org/codehaus/groovy/util/CharSequenceReaderTest.java 
b/src/test/java/org/codehaus/groovy/util/CharSequenceReaderTest.java
new file mode 100644
index 0000000000..01f5904c0d
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/util/CharSequenceReaderTest.java
@@ -0,0 +1,189 @@
+/*
+ *  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.codehaus.groovy.util;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link CharSequenceReader}.
+ */
+class CharSequenceReaderTest {
+
+    @Test
+    void testReadSingleCharacter() {
+        CharSequenceReader reader = new CharSequenceReader("abc");
+        assertEquals('a', reader.read());
+        assertEquals('b', reader.read());
+        assertEquals('c', reader.read());
+        assertEquals(-1, reader.read());
+    }
+
+    @Test
+    void testReadIntoArray() {
+        CharSequenceReader reader = new CharSequenceReader("hello world");
+        char[] buffer = new char[5];
+        int count = reader.read(buffer, 0, 5);
+        assertEquals(5, count);
+        assertArrayEquals("hello".toCharArray(), buffer);
+    }
+
+    @Test
+    void testReadIntoArrayWithOffset() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        char[] buffer = new char[10];
+        buffer[0] = 'x';
+        buffer[1] = 'y';
+        int count = reader.read(buffer, 2, 4);
+        assertEquals(4, count);
+        assertEquals('x', buffer[0]);
+        assertEquals('y', buffer[1]);
+        assertEquals('t', buffer[2]);
+        assertEquals('e', buffer[3]);
+        assertEquals('s', buffer[4]);
+        assertEquals('t', buffer[5]);
+    }
+
+    @Test
+    void testReadBeyondEnd() {
+        CharSequenceReader reader = new CharSequenceReader("ab");
+        char[] buffer = new char[5];
+        int count = reader.read(buffer, 0, 5);
+        assertEquals(2, count);
+        assertEquals('a', buffer[0]);
+        assertEquals('b', buffer[1]);
+
+        count = reader.read(buffer, 0, 5);
+        assertEquals(-1, count);
+    }
+
+    @Test
+    void testMarkAndReset() {
+        CharSequenceReader reader = new CharSequenceReader("abcdef");
+        assertEquals('a', reader.read());
+        assertEquals('b', reader.read());
+        reader.mark(100);
+        assertEquals('c', reader.read());
+        assertEquals('d', reader.read());
+        reader.reset();
+        assertEquals('c', reader.read());
+        assertEquals('d', reader.read());
+    }
+
+    @Test
+    void testMarkSupported() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        assertTrue(reader.markSupported());
+    }
+
+    @Test
+    void testSkip() {
+        CharSequenceReader reader = new CharSequenceReader("abcdefgh");
+        assertEquals('a', reader.read());
+        long skipped = reader.skip(3);
+        assertEquals(3, skipped);
+        assertEquals('e', reader.read());
+    }
+
+    @Test
+    void testSkipBeyondEnd() {
+        CharSequenceReader reader = new CharSequenceReader("abc");
+        long skipped = reader.skip(10);
+        assertEquals(3, skipped);
+        assertEquals(-1, reader.read());
+    }
+
+    @Test
+    void testSkipNegativeThrows() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        assertThrows(IllegalArgumentException.class, () -> reader.skip(-1));
+    }
+
+    @Test
+    void testSkipAtEnd() {
+        CharSequenceReader reader = new CharSequenceReader("a");
+        reader.read();
+        long skipped = reader.skip(5);
+        assertEquals(-1, skipped);
+    }
+
+    @Test
+    void testClose() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        reader.read();
+        reader.read();
+        reader.mark(10);
+        reader.close();
+        // After close, reader resets to start and mark is cleared
+        assertEquals('t', reader.read());
+    }
+
+    @Test
+    void testToString() {
+        String input = "hello world";
+        CharSequenceReader reader = new CharSequenceReader(input);
+        assertEquals(input, reader.toString());
+    }
+
+    @Test
+    void testNullInput() {
+        CharSequenceReader reader = new CharSequenceReader(null);
+        assertEquals(-1, reader.read());
+        assertEquals("", reader.toString());
+    }
+
+    @Test
+    void testEmptyInput() {
+        CharSequenceReader reader = new CharSequenceReader("");
+        assertEquals(-1, reader.read());
+    }
+
+    @Test
+    void testReadArrayWithNullThrows() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        assertThrows(NullPointerException.class, () -> reader.read(null, 0, 
1));
+    }
+
+    @Test
+    void testReadArrayWithInvalidBoundsThrows() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        char[] buffer = new char[5];
+        assertThrows(IndexOutOfBoundsException.class, () -> 
reader.read(buffer, -1, 1));
+        assertThrows(IndexOutOfBoundsException.class, () -> 
reader.read(buffer, 0, -1));
+        assertThrows(IndexOutOfBoundsException.class, () -> 
reader.read(buffer, 3, 5));
+    }
+
+    @Test
+    void testStringBuilder() {
+        StringBuilder sb = new StringBuilder("StringBuilder content");
+        CharSequenceReader reader = new CharSequenceReader(sb);
+        char[] buffer = new char[13];
+        reader.read(buffer, 0, 13);
+        assertEquals("StringBuilder", new String(buffer));
+    }
+
+    @Test
+    void testZeroLengthRead() {
+        CharSequenceReader reader = new CharSequenceReader("test");
+        char[] buffer = new char[5];
+        int count = reader.read(buffer, 0, 0);
+        assertEquals(0, count);
+    }
+}
diff --git 
a/subprojects/groovy-json/src/test/java/groovy/json/StringEscapeUtilsTest.java 
b/subprojects/groovy-json/src/test/java/groovy/json/StringEscapeUtilsTest.java
new file mode 100644
index 0000000000..0019edd428
--- /dev/null
+++ 
b/subprojects/groovy-json/src/test/java/groovy/json/StringEscapeUtilsTest.java
@@ -0,0 +1,343 @@
+/*
+ *  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 groovy.json;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link StringEscapeUtils}.
+ */
+class StringEscapeUtilsTest {
+
+    @Test
+    void testEscapeJavaNull() {
+        assertNull(StringEscapeUtils.escapeJava(null));
+    }
+
+    @Test
+    void testEscapeJavaEmpty() {
+        assertEquals("", StringEscapeUtils.escapeJava(""));
+    }
+
+    @Test
+    void testEscapeJavaBasicString() {
+        assertEquals("hello", StringEscapeUtils.escapeJava("hello"));
+    }
+
+    @Test
+    void testEscapeJavaDoubleQuote() {
+        assertEquals("\\\"hello\\\"", 
StringEscapeUtils.escapeJava("\"hello\""));
+    }
+
+    @Test
+    void testEscapeJavaBackslash() {
+        assertEquals("path\\\\to\\\\file", 
StringEscapeUtils.escapeJava("path\\to\\file"));
+    }
+
+    @Test
+    void testEscapeJavaNewline() {
+        assertEquals("line1\\nline2", 
StringEscapeUtils.escapeJava("line1\nline2"));
+    }
+
+    @Test
+    void testEscapeJavaTab() {
+        assertEquals("col1\\tcol2", 
StringEscapeUtils.escapeJava("col1\tcol2"));
+    }
+
+    @Test
+    void testEscapeJavaCarriageReturn() {
+        assertEquals("line1\\rline2", 
StringEscapeUtils.escapeJava("line1\rline2"));
+    }
+
+    @Test
+    void testEscapeJavaFormFeed() {
+        assertEquals("page1\\fpage2", 
StringEscapeUtils.escapeJava("page1\fpage2"));
+    }
+
+    @Test
+    void testEscapeJavaBackspace() {
+        assertEquals("back\\bspace", 
StringEscapeUtils.escapeJava("back\bspace"));
+    }
+
+    @Test
+    void testEscapeJavaSingleQuoteNotEscaped() {
+        assertEquals("it's", StringEscapeUtils.escapeJava("it's"));
+    }
+
+    @Test
+    void testEscapeJavaForwardSlashNotEscaped() {
+        assertEquals("path/to/file", 
StringEscapeUtils.escapeJava("path/to/file"));
+    }
+
+    @Test
+    void testEscapeJavaUnicodeHigh() {
+        // Characters > 0xfff
+        String input = "\u4e2d\u6587"; // Chinese characters
+        String result = StringEscapeUtils.escapeJava(input);
+        assertTrue(result.contains("\\u"));
+    }
+
+    @Test
+    void testEscapeJavaUnicodeMid() {
+        // Characters > 0xff and <= 0xfff
+        String input = "\u0100"; // Latin Extended-A
+        String result = StringEscapeUtils.escapeJava(input);
+        assertTrue(result.contains("\\u0100"));
+    }
+
+    @Test
+    void testEscapeJavaUnicodeLow() {
+        // Characters >= 0x7f and <= 0xff
+        String input = "\u007f"; // DEL character
+        String result = StringEscapeUtils.escapeJava(input);
+        assertTrue(result.contains("\\u007F") || result.contains("\\u007f"));
+    }
+
+    @Test
+    void testEscapeJavaControlCharacter() {
+        // Control characters < 32 (not the standard escape sequences)
+        String input = "\u0001"; // SOH
+        String result = StringEscapeUtils.escapeJava(input);
+        assertTrue(result.contains("\\u0001"));
+    }
+
+    @Test
+    void testEscapeJavaControlCharacterBetween15And31() {
+        String input = "\u0010"; // DLE
+        String result = StringEscapeUtils.escapeJava(input);
+        assertTrue(result.contains("\\u0010"));
+    }
+
+    @Test
+    void testEscapeJavaToWriter() throws IOException {
+        StringWriter writer = new StringWriter();
+        StringEscapeUtils.escapeJava(writer, "hello\"world");
+        assertEquals("hello\\\"world", writer.toString());
+    }
+
+    @Test
+    void testEscapeJavaToWriterNull() throws IOException {
+        StringWriter writer = new StringWriter();
+        StringEscapeUtils.escapeJava(writer, null);
+        assertEquals("", writer.toString());
+    }
+
+    @Test
+    void testEscapeJavaToWriterWithNullWriter() {
+        assertThrows(IllegalArgumentException.class, () -> 
+            StringEscapeUtils.escapeJava(null, "test"));
+    }
+
+    @Test
+    void testEscapeJavaScript() {
+        assertEquals("it\\'s", StringEscapeUtils.escapeJavaScript("it's"));
+    }
+
+    @Test
+    void testEscapeJavaScriptForwardSlash() {
+        assertEquals("path\\/to\\/file", 
StringEscapeUtils.escapeJavaScript("path/to/file"));
+    }
+
+    @Test
+    void testEscapeJavaScriptNull() {
+        assertNull(StringEscapeUtils.escapeJavaScript(null));
+    }
+
+    @Test
+    void testEscapeJavaScriptToWriter() throws IOException {
+        StringWriter writer = new StringWriter();
+        StringEscapeUtils.escapeJavaScript(writer, "it's \"quoted\"");
+        assertEquals("it\\'s \\\"quoted\\\"", writer.toString());
+    }
+
+    @Test
+    void testEscapeJavaScriptToWriterWithNullWriter() {
+        assertThrows(IllegalArgumentException.class, () -> 
+            StringEscapeUtils.escapeJavaScript(null, "test"));
+    }
+
+    @Test
+    void testUnescapeJavaNull() {
+        assertNull(StringEscapeUtils.unescapeJava(null));
+    }
+
+    @Test
+    void testUnescapeJavaEmpty() {
+        assertEquals("", StringEscapeUtils.unescapeJava(""));
+    }
+
+    @Test
+    void testUnescapeJavaBasicString() {
+        assertEquals("hello", StringEscapeUtils.unescapeJava("hello"));
+    }
+
+    @Test
+    void testUnescapeJavaDoubleQuote() {
+        assertEquals("\"hello\"", 
StringEscapeUtils.unescapeJava("\\\"hello\\\""));
+    }
+
+    @Test
+    void testUnescapeJavaSingleQuote() {
+        assertEquals("'hello'", StringEscapeUtils.unescapeJava("\\'hello\\'"));
+    }
+
+    @Test
+    void testUnescapeJavaBackslash() {
+        assertEquals("path\\to\\file", 
StringEscapeUtils.unescapeJava("path\\\\to\\\\file"));
+    }
+
+    @Test
+    void testUnescapeJavaNewline() {
+        assertEquals("line1\nline2", 
StringEscapeUtils.unescapeJava("line1\\nline2"));
+    }
+
+    @Test
+    void testUnescapeJavaTab() {
+        assertEquals("col1\tcol2", 
StringEscapeUtils.unescapeJava("col1\\tcol2"));
+    }
+
+    @Test
+    void testUnescapeJavaCarriageReturn() {
+        assertEquals("line1\rline2", 
StringEscapeUtils.unescapeJava("line1\\rline2"));
+    }
+
+    @Test
+    void testUnescapeJavaFormFeed() {
+        assertEquals("page1\fpage2", 
StringEscapeUtils.unescapeJava("page1\\fpage2"));
+    }
+
+    @Test
+    void testUnescapeJavaBackspace() {
+        assertEquals("back\bspace", 
StringEscapeUtils.unescapeJava("back\\bspace"));
+    }
+
+    @Test
+    void testUnescapeJavaUnicode() {
+        assertEquals("\u0041", StringEscapeUtils.unescapeJava("\\u0041"));
+        assertEquals("A", StringEscapeUtils.unescapeJava("\\u0041"));
+    }
+
+    @Test
+    void testUnescapeJavaUnicodeMultiple() {
+        assertEquals("AB", StringEscapeUtils.unescapeJava("\\u0041\\u0042"));
+    }
+
+    @Test
+    void testUnescapeJavaMixedContent() {
+        assertEquals("hello\nworld\ttab", 
StringEscapeUtils.unescapeJava("hello\\nworld\\ttab"));
+    }
+
+    @Test
+    void testUnescapeJavaInvalidUnicode() {
+        assertThrows(RuntimeException.class, () -> 
+            StringEscapeUtils.unescapeJava("\\uXYZQ"));
+    }
+
+    @Test
+    void testUnescapeJavaTrailingBackslash() {
+        assertEquals("trailing\\", 
StringEscapeUtils.unescapeJava("trailing\\"));
+    }
+
+    @Test
+    void testUnescapeJavaUnknownEscape() {
+        // Unknown escape sequences should just return the character after 
backslash
+        assertEquals("x", StringEscapeUtils.unescapeJava("\\x"));
+    }
+
+    @Test
+    void testUnescapeJavaToWriter() throws IOException {
+        StringWriter writer = new StringWriter();
+        StringEscapeUtils.unescapeJava(writer, "hello\\\"world");
+        assertEquals("hello\"world", writer.toString());
+    }
+
+    @Test
+    void testUnescapeJavaToWriterNull() throws IOException {
+        StringWriter writer = new StringWriter();
+        StringEscapeUtils.unescapeJava(writer, null);
+        assertEquals("", writer.toString());
+    }
+
+    @Test
+    void testUnescapeJavaToWriterWithNullWriter() {
+        assertThrows(IllegalArgumentException.class, () -> 
+            StringEscapeUtils.unescapeJava(null, "test"));
+    }
+
+    @Test
+    void testUnescapeJavaScriptSameAsJava() {
+        String escaped = "hello\\\"world\\'test";
+        assertEquals(StringEscapeUtils.unescapeJava(escaped), 
+                     StringEscapeUtils.unescapeJavaScript(escaped));
+    }
+
+    @Test
+    void testUnescapeJavaScriptNull() {
+        assertNull(StringEscapeUtils.unescapeJavaScript(null));
+    }
+
+    @Test
+    void testUnescapeJavaScriptToWriter() throws IOException {
+        StringWriter writer = new StringWriter();
+        StringEscapeUtils.unescapeJavaScript(writer, "hello\\nworld");
+        assertEquals("hello\nworld", writer.toString());
+    }
+
+    @Test
+    void testRoundTripJava() {
+        String original = "Hello\n\"World\"\t\\Test/";
+        String escaped = StringEscapeUtils.escapeJava(original);
+        String unescaped = StringEscapeUtils.unescapeJava(escaped);
+        assertEquals(original, unescaped);
+    }
+
+    @Test
+    void testRoundTripJavaScript() {
+        String original = "Hello'World";
+        String escaped = StringEscapeUtils.escapeJavaScript(original);
+        String unescaped = StringEscapeUtils.unescapeJavaScript(escaped);
+        assertEquals(original, unescaped);
+    }
+
+    @Test
+    void testConstructor() {
+        // Test that constructor can be called (even though it's typically 
used statically)
+        StringEscapeUtils utils = new StringEscapeUtils();
+        assertNotNull(utils);
+    }
+
+    @Test
+    void testAllControlCharacters() {
+        // Test all control characters (0x00 to 0x1f except standard escapes)
+        for (int i = 0; i < 32; i++) {
+            if (i != '\b' && i != '\t' && i != '\n' && i != '\f' && i != '\r') 
{
+                String input = String.valueOf((char) i);
+                String escaped = StringEscapeUtils.escapeJava(input);
+                assertTrue(escaped.startsWith("\\u000") || 
escaped.startsWith("\\u00"), 
+                    "Control char " + i + " should be unicode escaped");
+            }
+        }
+    }
+}
diff --git 
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/CharSequenceValueTest.java
 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/CharSequenceValueTest.java
new file mode 100644
index 0000000000..3404710583
--- /dev/null
+++ 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/CharSequenceValueTest.java
@@ -0,0 +1,337 @@
+/*
+ *  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.groovy.json.internal;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link CharSequenceValue}.
+ */
+class CharSequenceValueTest {
+
+    @Test
+    void testStringValue() {
+        char[] buffer = "hello".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals("hello", value.stringValue());
+    }
+
+    @Test
+    void testStringValueWithOffset() {
+        char[] buffer = "___hello___".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 3, 
8, buffer, false, false);
+        assertEquals("hello", value.stringValue());
+    }
+
+    @Test
+    void testIntegerValue() {
+        char[] buffer = "42".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertEquals(42, value.intValue());
+    }
+
+    @Test
+    void testNegativeIntegerValue() {
+        char[] buffer = "-42".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertEquals(-42, value.intValue());
+    }
+
+    @Test
+    void testLongValue() {
+        char[] buffer = "9876543210".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertEquals(9876543210L, value.longValue());
+    }
+
+    @Test
+    void testShortLongValue() {
+        char[] buffer = "42".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertEquals(42L, value.longValue());
+    }
+
+    @Test
+    void testDoubleValue() {
+        char[] buffer = "3.14159".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.DOUBLE, 0, 
buffer.length, buffer, false, false);
+        assertEquals(3.14159, value.doubleValue(), 0.00001);
+    }
+
+    @Test
+    void testFloatValue() {
+        char[] buffer = "2.5".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.DOUBLE, 0, 
buffer.length, buffer, false, false);
+        assertEquals(2.5f, value.floatValue(), 0.0001f);
+    }
+
+    @Test
+    void testByteValue() {
+        char[] buffer = "127".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertEquals((byte) 127, value.byteValue());
+    }
+
+    @Test
+    void testShortValue() {
+        char[] buffer = "1000".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertEquals((short) 1000, value.shortValue());
+    }
+
+    @Test
+    void testBigDecimalValue() {
+        char[] buffer = "123.456789".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.DOUBLE, 0, 
buffer.length, buffer, false, false);
+        BigDecimal expected = new BigDecimal("123.456789");
+        assertEquals(expected, value.bigDecimalValue());
+    }
+
+    @Test
+    void testBigIntegerValue() {
+        char[] buffer = "123456789012345678901234567890".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        BigInteger expected = new BigInteger("123456789012345678901234567890");
+        assertEquals(expected, value.bigIntegerValue());
+    }
+
+    @Test
+    void testToString() {
+        char[] buffer = "test string".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals("test string", value.toString());
+    }
+
+    @Test
+    void testToStringWithOffset() {
+        char[] buffer = "___test___".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 3, 
7, buffer, false, false);
+        assertEquals("test", value.toString());
+    }
+
+    @Test
+    void testToValue() {
+        char[] buffer = "42".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        Object result = value.toValue();
+        assertEquals(42, result);
+    }
+
+    @Test
+    void testToValueDouble() {
+        char[] buffer = "3.14".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.DOUBLE, 0, 
buffer.length, buffer, false, false);
+        Object result = value.toValue();
+        assertEquals(3.14, (Double) result, 0.0001);
+    }
+
+    @Test
+    void testToValueString() {
+        char[] buffer = "hello".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        Object result = value.toValue();
+        assertEquals("hello", result);
+    }
+
+    @Test
+    void testToValueCached() {
+        char[] buffer = "hello".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        Object result1 = value.toValue();
+        Object result2 = value.toValue();
+        assertSame(result1, result2);
+    }
+
+    @Test
+    void testIsContainer() {
+        char[] buffer = "test".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertFalse(value.isContainer());
+    }
+
+    @Test
+    void testLength() {
+        char[] buffer = "hello".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals(5, value.length());
+    }
+
+    @Test
+    void testCharAt() {
+        char[] buffer = "hello".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals('h', value.charAt(0));
+        assertEquals('e', value.charAt(1));
+        assertEquals('o', value.charAt(4));
+    }
+
+    @Test
+    void testSubSequence() {
+        char[] buffer = "hello world".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        CharSequence sub = value.subSequence(0, 5);
+        assertEquals("hello", sub.toString());
+    }
+
+    @Test
+    void testChop() {
+        char[] buffer = "___hello___".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 3, 
8, buffer, false, false);
+        value.chop();
+        assertEquals("hello", value.toString());
+        // Call chop again to ensure idempotency
+        value.chop();
+        assertEquals("hello", value.toString());
+    }
+
+    @Test
+    void testChopOnConstruction() {
+        char[] buffer = "___hello___".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(true, Type.STRING, 3, 
8, buffer, false, false);
+        assertEquals("hello", value.toString());
+    }
+
+    @Test
+    void testCharValue() {
+        char[] buffer = "A".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals('A', value.charValue());
+    }
+
+    @Test
+    void testBooleanValue() {
+        char[] buffer = "true".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertTrue(value.booleanValue());
+    }
+
+    @Test
+    void testBooleanValueFalse() {
+        char[] buffer = "false".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertFalse(value.booleanValue());
+    }
+
+    @Test
+    void testEquals() {
+        char[] buffer = "test".toCharArray();
+        CharSequenceValue value1 = new CharSequenceValue(false, Type.STRING, 
0, buffer.length, buffer, false, false);
+        CharSequenceValue value2 = new CharSequenceValue(false, Type.STRING, 
0, buffer.length, buffer, false, false);
+        assertEquals(value1, value2);
+    }
+
+    @Test
+    void testEqualsSameObject() {
+        char[] buffer = "test".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals(value, value);
+    }
+
+    @Test
+    void testNotEqualsDifferentIndex() {
+        char[] buffer = "test1234".toCharArray();
+        CharSequenceValue value1 = new CharSequenceValue(false, Type.STRING, 
0, 4, buffer, false, false);
+        CharSequenceValue value2 = new CharSequenceValue(false, Type.STRING, 
4, 8, buffer, false, false);
+        assertNotEquals(value1, value2);
+    }
+
+    @Test
+    void testNotEqualsDifferentType() {
+        char[] buffer = "42".toCharArray();
+        CharSequenceValue value1 = new CharSequenceValue(false, Type.STRING, 
0, buffer.length, buffer, false, false);
+        CharSequenceValue value2 = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        assertNotEquals(value1, value2);
+    }
+
+    @Test
+    void testHashCode() {
+        char[] buffer = "test".toCharArray();
+        CharSequenceValue value1 = new CharSequenceValue(false, Type.STRING, 
0, buffer.length, buffer, false, false);
+        CharSequenceValue value2 = new CharSequenceValue(false, Type.STRING, 
0, buffer.length, buffer, false, false);
+        assertEquals(value1.hashCode(), value2.hashCode());
+    }
+
+    @Test
+    void testStringValueEncoded() {
+        char[] buffer = "hello".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        assertEquals("hello", value.stringValueEncoded());
+    }
+
+    @Test
+    void testToEnumWithString() {
+        char[] buffer = "TWO".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.STRING, 0, 
buffer.length, buffer, false, false);
+        TestEnum result = value.toEnum(TestEnum.class);
+        assertEquals(TestEnum.TWO, result);
+    }
+
+    @Test
+    void testToEnumWithInteger() {
+        char[] buffer = "1".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        TestEnum result = value.toEnum(TestEnum.class);
+        assertEquals(TestEnum.TWO, result);
+    }
+
+    @Test
+    void testToEnumNull() {
+        char[] buffer = "null".toCharArray();
+        CharSequenceValue value = new CharSequenceValue(false, Type.NULL, 0, 
buffer.length, buffer, false, false);
+        TestEnum result = value.toEnum(TestEnum.class);
+        assertNull(result);
+    }
+
+    @Test
+    void testStaticToEnumByValue() {
+        TestEnum result = CharSequenceValue.toEnum(TestEnum.class, "ONE");
+        assertEquals(TestEnum.ONE, result);
+    }
+
+    @Test
+    void testStaticToEnumByValueWithDash() {
+        // The implementation converts dashes to underscores and uses uppercase
+        TestEnum result = CharSequenceValue.toEnum(TestEnum.class, "one");
+        assertEquals(TestEnum.ONE, result);
+    }
+
+    @Test
+    void testStaticToEnumByOrdinal() {
+        TestEnum result = CharSequenceValue.toEnum(TestEnum.class, 2);
+        assertEquals(TestEnum.THREE, result);
+    }
+
+    @Test
+    void testIntegerWithLongRange() {
+        char[] buffer = "2147483648".toCharArray(); // Integer.MAX_VALUE + 1
+        CharSequenceValue value = new CharSequenceValue(false, Type.INTEGER, 
0, buffer.length, buffer, false, false);
+        Object result = value.toValue();
+        assertEquals(2147483648L, result);
+    }
+
+    // Helper enum for testing
+    enum TestEnum {
+        ONE, TWO, THREE
+    }
+}
diff --git 
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/NumberValueTest.java
 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/NumberValueTest.java
new file mode 100644
index 0000000000..a1e3eeb3b9
--- /dev/null
+++ 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/NumberValueTest.java
@@ -0,0 +1,320 @@
+/*
+ *  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.groovy.json.internal;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit tests for {@link NumberValue}.
+ */
+class NumberValueTest {
+
+    @Test
+    void testIntegerValue() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(42, value.intValue());
+        assertEquals(42L, value.longValue());
+        assertEquals(42.0, value.doubleValue(), 0.0001);
+        assertEquals(42.0f, value.floatValue(), 0.0001f);
+    }
+
+    @Test
+    void testLongValue() {
+        char[] buffer = "9876543210".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(9876543210L, value.longValue());
+    }
+
+    @Test
+    void testDoubleValue() {
+        char[] buffer = "3.14159".toCharArray();
+        NumberValue value = new NumberValue(false, Type.DOUBLE, 0, 
buffer.length, buffer);
+        assertEquals(3.14159, value.doubleValue(), 0.00001);
+    }
+
+    @Test
+    void testFloatValue() {
+        char[] buffer = "2.5".toCharArray();
+        NumberValue value = new NumberValue(false, Type.DOUBLE, 0, 
buffer.length, buffer);
+        assertEquals(2.5f, value.floatValue(), 0.0001f);
+    }
+
+    @Test
+    void testByteValue() {
+        char[] buffer = "127".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals((byte) 127, value.byteValue());
+    }
+
+    @Test
+    void testShortValue() {
+        char[] buffer = "1000".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals((short) 1000, value.shortValue());
+    }
+
+    @Test
+    void testBigDecimalValue() {
+        char[] buffer = "123.456789".toCharArray();
+        NumberValue value = new NumberValue(false, Type.DOUBLE, 0, 
buffer.length, buffer);
+        BigDecimal expected = new BigDecimal("123.456789");
+        assertEquals(expected, value.bigDecimalValue());
+    }
+
+    @Test
+    void testBigIntegerValue() {
+        char[] buffer = "123456789012345678901234567890".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        BigInteger expected = new BigInteger("123456789012345678901234567890");
+        assertEquals(expected, value.bigIntegerValue());
+    }
+
+    @Test
+    void testStringValue() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals("42", value.stringValue());
+    }
+
+    @Test
+    void testStringValueEncoded() {
+        char[] buffer = "123".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals("123", value.stringValueEncoded());
+    }
+
+    @Test
+    void testToString() {
+        char[] buffer = "999".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals("999", value.toString());
+    }
+
+    @Test
+    void testToStringWithOffset() {
+        char[] buffer = "___42___".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 3, 5, buffer);
+        assertEquals("42", value.toString());
+    }
+
+    @Test
+    void testToValue() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        Object result = value.toValue();
+        assertEquals(42, result);
+    }
+
+    @Test
+    void testToValueDouble() {
+        char[] buffer = "3.14".toCharArray();
+        NumberValue value = new NumberValue(false, Type.DOUBLE, 0, 
buffer.length, buffer);
+        Object result = value.toValue();
+        assertTrue(result instanceof BigDecimal);
+    }
+
+    @Test
+    void testToValueLong() {
+        char[] buffer = "9876543210".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        Object result = value.toValue();
+        assertEquals(9876543210L, result);
+    }
+
+    @Test
+    void testToValueCached() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        Object result1 = value.toValue();
+        Object result2 = value.toValue();
+        assertSame(result1, result2);
+    }
+
+    @Test
+    void testIsContainer() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertFalse(value.isContainer());
+    }
+
+    @Test
+    void testChop() {
+        char[] buffer = "___42___".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 3, 5, buffer);
+        value.chop();
+        assertEquals("42", value.toString());
+        // Call chop again to test idempotency
+        value.chop();
+        assertEquals("42", value.toString());
+    }
+
+    @Test
+    void testChopOnConstruction() {
+        char[] buffer = "___42___".toCharArray();
+        NumberValue value = new NumberValue(true, Type.INTEGER, 3, 5, buffer);
+        assertEquals("42", value.toString());
+        assertEquals(42, value.intValue());
+    }
+
+    @Test
+    void testCharValue() {
+        char[] buffer = "5".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals('5', value.charValue());
+    }
+
+    @Test
+    void testDateValue() {
+        char[] buffer = "1609459200000".toCharArray(); // 2021-01-01 00:00:00 
UTC
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertNotNull(value.dateValue());
+    }
+
+    @Test
+    void testBooleanValue() {
+        char[] buffer = "true".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        // Boolean.parseBoolean("true") returns true
+        assertTrue(value.booleanValue());
+    }
+
+    @Test
+    void testBooleanValueFalse() {
+        char[] buffer = "0".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        // Boolean.parseBoolean("0") returns false
+        assertFalse(value.booleanValue());
+    }
+
+    @Test
+    void testEquals() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value1 = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        NumberValue value2 = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(value1, value2);
+    }
+
+    @Test
+    void testEqualsSameObject() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(value, value);
+    }
+
+    @Test
+    void testEqualsNull() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertNotEquals(null, value);
+    }
+
+    @Test
+    void testNotEqualsDifferentIndex() {
+        char[] buffer = "1234".toCharArray();
+        NumberValue value1 = new NumberValue(false, Type.INTEGER, 0, 2, 
buffer);
+        NumberValue value2 = new NumberValue(false, Type.INTEGER, 2, 4, 
buffer);
+        assertNotEquals(value1, value2);
+    }
+
+    @Test
+    void testNotEqualsDifferentType() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value1 = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        NumberValue value2 = new NumberValue(false, Type.DOUBLE, 0, 
buffer.length, buffer);
+        assertNotEquals(value1, value2);
+    }
+
+    @Test
+    void testHashCode() {
+        char[] buffer = "42".toCharArray();
+        NumberValue value1 = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        NumberValue value2 = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(value1.hashCode(), value2.hashCode());
+    }
+
+    @Test
+    void testHashCodeDifferent() {
+        char[] buffer1 = "42".toCharArray();
+        char[] buffer2 = "43".toCharArray();
+        NumberValue value1 = new NumberValue(false, Type.INTEGER, 0, 
buffer1.length, buffer1);
+        NumberValue value2 = new NumberValue(false, Type.INTEGER, 0, 
buffer2.length, buffer2);
+        assertNotEquals(value1.hashCode(), value2.hashCode());
+    }
+
+    @Test
+    void testDefaultConstructor() {
+        NumberValue value = new NumberValue();
+        assertNotNull(value);
+    }
+
+    @Test
+    void testTypeConstructor() {
+        NumberValue value = new NumberValue(Type.INTEGER);
+        assertNotNull(value);
+    }
+
+    @Test
+    void testNegativeInteger() {
+        char[] buffer = "-42".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(-42, value.intValue());
+    }
+
+    @Test
+    void testNegativeDouble() {
+        char[] buffer = "-3.14".toCharArray();
+        NumberValue value = new NumberValue(false, Type.DOUBLE, 0, 
buffer.length, buffer);
+        assertEquals(-3.14, value.doubleValue(), 0.0001);
+    }
+
+    @Test
+    void testZero() {
+        char[] buffer = "0".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        assertEquals(0, value.intValue());
+        assertEquals(0L, value.longValue());
+        assertEquals(0.0, value.doubleValue(), 0.0001);
+    }
+
+    @Test
+    void testSingleMinusThrows() {
+        char[] buffer = "-".toCharArray();
+        assertThrows(RuntimeException.class, () -> 
+            new NumberValue(false, Type.INTEGER, 0, buffer.length, buffer));
+    }
+
+    @Test
+    void testToEnumByOrdinal() {
+        char[] buffer = "1".toCharArray();
+        NumberValue value = new NumberValue(false, Type.INTEGER, 0, 
buffer.length, buffer);
+        TestEnum result = value.toEnum(TestEnum.class);
+        assertEquals(TestEnum.TWO, result);
+    }
+
+    // Helper enum for testing
+    enum TestEnum {
+        ONE, TWO, THREE
+    }
+}


Reply via email to