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 01e52d1a11 Add tests to increase code coverage: `CharBufTest`, 
`SysTest`, etc.
01e52d1a11 is described below

commit 01e52d1a11b2d99021b04818a2041889153fc4e5
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Feb 1 21:34:24 2026 +0900

    Add tests to increase code coverage: `CharBufTest`, `SysTest`, etc.
---
 .../java/groovy/lang/GroovySystemJUnit5Test.java   | 125 +++++++
 src/test/java/groovy/lang/ReferenceJUnit5Test.java | 156 ++++++++
 .../groovy/runtime/FormatHelperJUnit5Test.java     | 349 ++++++++++++++++++
 .../runtime/NumberAwareComparatorJUnit5Test.java   | 201 +++++++++++
 .../codehaus/groovy/syntax/CSTNodeJUnit5Test.java  | 368 +++++++++++++++++++
 .../groovy/tools/ErrorReporterJUnit5Test.java      | 226 ++++++++++++
 .../codehaus/groovy/util/FastArrayJUnit5Test.java  | 375 +++++++++++++++++++
 .../apache/groovy/json/internal/CharBufTest.java   | 402 +++++++++++++++++++++
 .../groovy/json/internal/SimpleCacheTest.java      | 213 +++++++++++
 .../org/apache/groovy/json/internal/SysTest.java   | 103 ++++++
 10 files changed, 2518 insertions(+)

diff --git a/src/test/java/groovy/lang/GroovySystemJUnit5Test.java 
b/src/test/java/groovy/lang/GroovySystemJUnit5Test.java
new file mode 100644
index 0000000000..6af9351d71
--- /dev/null
+++ b/src/test/java/groovy/lang/GroovySystemJUnit5Test.java
@@ -0,0 +1,125 @@
+/*
+ *  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.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for GroovySystem class.
+ */
+class GroovySystemJUnit5Test {
+
+    @Test
+    void testGetMetaClassRegistry() {
+        MetaClassRegistry registry = GroovySystem.getMetaClassRegistry();
+        assertNotNull(registry);
+    }
+
+    @Test
+    void testGetMetaClassRegistryReturnsSameInstance() {
+        MetaClassRegistry registry1 = GroovySystem.getMetaClassRegistry();
+        MetaClassRegistry registry2 = GroovySystem.getMetaClassRegistry();
+        assertSame(registry1, registry2);
+    }
+
+    @Test
+    void testGetVersion() {
+        String version = GroovySystem.getVersion();
+        assertNotNull(version);
+        assertFalse(version.isEmpty());
+    }
+
+    @Test
+    void testGetShortVersion() {
+        String shortVersion = GroovySystem.getShortVersion();
+        assertNotNull(shortVersion);
+        
+        // Short version should contain exactly one dot
+        int dotCount = shortVersion.length() - shortVersion.replace(".", 
"").length();
+        assertEquals(1, dotCount);
+        
+        // Should start with a number
+        assertTrue(Character.isDigit(shortVersion.charAt(0)));
+    }
+
+    @Test
+    void testShortVersionIsPartOfFullVersion() {
+        String fullVersion = GroovySystem.getVersion();
+        String shortVersion = GroovySystem.getShortVersion();
+        
+        assertTrue(fullVersion.startsWith(shortVersion));
+    }
+
+    @Test
+    void testIsKeepJavaMetaClassesDefault() {
+        // Default should be false
+        boolean original = GroovySystem.isKeepJavaMetaClasses();
+        try {
+            // Test the default behavior
+            assertFalse(original);
+        } finally {
+            // Restore original value
+            GroovySystem.setKeepJavaMetaClasses(original);
+        }
+    }
+
+    @Test
+    void testSetKeepJavaMetaClasses() {
+        boolean original = GroovySystem.isKeepJavaMetaClasses();
+        try {
+            GroovySystem.setKeepJavaMetaClasses(true);
+            assertTrue(GroovySystem.isKeepJavaMetaClasses());
+            
+            GroovySystem.setKeepJavaMetaClasses(false);
+            assertFalse(GroovySystem.isKeepJavaMetaClasses());
+        } finally {
+            GroovySystem.setKeepJavaMetaClasses(original);
+        }
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    void testIsUseReflection() {
+        // This is deprecated but we can still test it
+        assertTrue(GroovySystem.isUseReflection());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    void testRunnerRegistry() {
+        // RUNNER_REGISTRY is deprecated but we can verify it exists
+        assertNotNull(GroovySystem.RUNNER_REGISTRY);
+    }
+
+    @Test
+    void testVersionFormat() {
+        String version = GroovySystem.getVersion();
+        // Version should match pattern like "4.0.0", "4.0.1-SNAPSHOT", 
"4.0.0-rc-1", etc.
+        assertTrue(version.matches("\\d+\\.\\d+\\.\\d+.*"), 
+            "Version should match pattern X.Y.Z[suffix], got: " + version);
+    }
+
+    @Test
+    void testStopThreadedReferenceManager() {
+        // This should not throw any exception
+        assertDoesNotThrow(() -> GroovySystem.stopThreadedReferenceManager());
+    }
+}
diff --git a/src/test/java/groovy/lang/ReferenceJUnit5Test.java 
b/src/test/java/groovy/lang/ReferenceJUnit5Test.java
new file mode 100644
index 0000000000..2af9c2f081
--- /dev/null
+++ b/src/test/java/groovy/lang/ReferenceJUnit5Test.java
@@ -0,0 +1,156 @@
+/*
+ *  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.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for Reference class.
+ */
+class ReferenceJUnit5Test {
+
+    @Test
+    void testDefaultConstructor() {
+        Reference<String> ref = new Reference<>();
+        assertNull(ref.get());
+    }
+
+    @Test
+    void testConstructorWithValue() {
+        Reference<String> ref = new Reference<>("hello");
+        assertEquals("hello", ref.get());
+    }
+
+    @Test
+    void testGetAndSet() {
+        Reference<Integer> ref = new Reference<>();
+        assertNull(ref.get());
+        
+        ref.set(42);
+        assertEquals(42, ref.get());
+        
+        ref.set(100);
+        assertEquals(100, ref.get());
+    }
+
+    @Test
+    void testSetNull() {
+        Reference<String> ref = new Reference<>("initial");
+        assertEquals("initial", ref.get());
+        
+        ref.set(null);
+        assertNull(ref.get());
+    }
+
+    @Test
+    void testWithDifferentTypes() {
+        Reference<Double> doubleRef = new Reference<>(3.14);
+        assertEquals(3.14, doubleRef.get());
+        
+        Reference<Boolean> boolRef = new Reference<>(true);
+        assertTrue(boolRef.get());
+        
+        Reference<Object> objRef = new Reference<>(new int[]{1, 2, 3});
+        assertArrayEquals(new int[]{1, 2, 3}, (int[]) objRef.get());
+    }
+
+    @Test
+    void testGetPropertyOnNullValue() {
+        Reference<String> ref = new Reference<>();
+        // When value is null, getProperty should delegate to super
+        assertThrows(MissingPropertyException.class, () -> {
+            ref.getProperty("length");
+        });
+    }
+
+    @Test
+    void testSetPropertyOnNullValue() {
+        Reference<Object> ref = new Reference<>();
+        // When value is null, setProperty should delegate to super
+        assertThrows(MissingPropertyException.class, () -> {
+            ref.setProperty("someProp", "value");
+        });
+    }
+
+    @Test
+    void testInvokeMethodOnNullValue() {
+        Reference<String> ref = new Reference<>();
+        // When value is null, invokeMethod delegates to super 
(GroovyObjectSupport)
+        // It throws MissingMethodException for undefined methods
+        assertThrows(MissingMethodException.class, () -> {
+            ref.invokeMethod("someUndefinedMethod", null);
+        });
+    }
+
+    @Test
+    void testReferenceIsSerializable() {
+        Reference<String> ref = new Reference<>("test");
+        assertTrue(ref instanceof java.io.Serializable);
+    }
+
+    @Test
+    void testReferenceExtendsGroovyObjectSupport() {
+        Reference<String> ref = new Reference<>();
+        assertTrue(ref instanceof GroovyObjectSupport);
+    }
+
+    @Test
+    void testMultipleReferences() {
+        Reference<String> ref1 = new Reference<>("one");
+        Reference<String> ref2 = new Reference<>("two");
+        
+        assertNotEquals(ref1.get(), ref2.get());
+        
+        ref1.set("same");
+        ref2.set("same");
+        assertEquals(ref1.get(), ref2.get());
+    }
+
+    @Test
+    void testReferenceWithComplexObject() {
+        class Person {
+            String name;
+            int age;
+            Person(String name, int age) {
+                this.name = name;
+                this.age = age;
+            }
+        }
+        
+        Person person = new Person("John", 30);
+        Reference<Person> ref = new Reference<>(person);
+        
+        assertSame(person, ref.get());
+        assertEquals("John", ref.get().name);
+        assertEquals(30, ref.get().age);
+    }
+
+    @Test
+    void testReferenceValueMutation() {
+        StringBuilder sb = new StringBuilder("initial");
+        Reference<StringBuilder> ref = new Reference<>(sb);
+        
+        sb.append(" modified");
+        
+        // Since Reference holds the same object, mutation is visible
+        assertEquals("initial modified", ref.get().toString());
+    }
+}
diff --git 
a/src/test/java/org/codehaus/groovy/runtime/FormatHelperJUnit5Test.java 
b/src/test/java/org/codehaus/groovy/runtime/FormatHelperJUnit5Test.java
new file mode 100644
index 0000000000..88b54fcb9b
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/runtime/FormatHelperJUnit5Test.java
@@ -0,0 +1,349 @@
+/*
+ *  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.runtime;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for FormatHelper class.
+ */
+class FormatHelperJUnit5Test {
+
+    @Test
+    void testToStringNull() {
+        String result = FormatHelper.toString(null);
+        assertEquals("null", result);
+    }
+
+    @Test
+    void testToStringString() {
+        String result = FormatHelper.toString("hello");
+        assertEquals("hello", result);
+    }
+
+    @Test
+    void testToStringInteger() {
+        String result = FormatHelper.toString(42);
+        assertEquals("42", result);
+    }
+
+    @Test
+    void testToStringCollection() {
+        List<String> list = Arrays.asList("a", "b", "c");
+        String result = FormatHelper.toString(list);
+        assertEquals("[a, b, c]", result);
+    }
+
+    @Test
+    void testToStringMap() {
+        Map<String, Integer> map = new LinkedHashMap<>();
+        map.put("one", 1);
+        map.put("two", 2);
+        String result = FormatHelper.toString(map);
+        assertTrue(result.contains("one") && result.contains("1"));
+    }
+
+    @Test
+    void testToStringObjectArray() {
+        Object[] arr = {"a", "b", "c"};
+        String result = FormatHelper.toString(arr);
+        assertEquals("[a, b, c]", result);
+    }
+
+    @Test
+    void testToStringCharArray() {
+        char[] chars = {'h', 'i'};
+        String result = FormatHelper.toString(chars);
+        assertEquals("hi", result);
+    }
+
+    @Test
+    void testToStringPrimitiveIntArray() {
+        int[] arr = {1, 2, 3};
+        String result = FormatHelper.toString(arr);
+        assertEquals("[1, 2, 3]", result);
+    }
+
+    @Test
+    void testInspectString() {
+        String result = FormatHelper.inspect("hello");
+        assertTrue(result.contains("hello"));
+    }
+
+    @Test
+    void testInspectNull() {
+        String result = FormatHelper.inspect(null);
+        assertEquals("null", result);
+    }
+
+    @Test
+    void testFormatVerbose() {
+        String result = FormatHelper.format("hello", true);
+        assertTrue(result.contains("hello"));
+    }
+
+    @Test
+    void testFormatWithMaxSize() {
+        String longString = "This is a very long string that should be 
truncated";
+        String result = FormatHelper.format(longString, false, 10);
+        // Format doesn't truncate simple strings, just collections/maps
+        assertNotNull(result);
+    }
+
+    @Test
+    void testFormatWithMaxSizeOnCollection() {
+        List<String> list = Arrays.asList("item1", "item2", "item3", "item4", 
"item5");
+        String result = FormatHelper.format(list, false, 20);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testFormatEmptyCollection() {
+        List<String> list = Collections.emptyList();
+        String result = FormatHelper.format(list, false);
+        assertEquals("[]", result);
+    }
+
+    @Test
+    void testFormatEmptyMap() {
+        Map<String, String> map = Collections.emptyMap();
+        String result = FormatHelper.format(map, false);
+        assertEquals("[:]", result);
+    }
+
+    @Test
+    void testFormatMapWithInspect() {
+        Map<String, String> map = new LinkedHashMap<>();
+        map.put("key", "value");
+        String result = FormatHelper.format(map, true);
+        assertTrue(result.contains("key") && result.contains("value"));
+    }
+
+    @Test
+    void testToStringWithOptions() {
+        Map<String, Object> options = new HashMap<>();
+        options.put("safe", true);
+        options.put("maxSize", 100);
+        
+        String result = FormatHelper.toString(options, "hello");
+        assertEquals("hello", result);
+    }
+
+    @Test
+    void testToStringWithOptionsVerbose() {
+        Map<String, Object> options = new HashMap<>();
+        options.put("verbose", true);
+        
+        String result = FormatHelper.toString(options, "hello");
+        assertNotNull(result);
+    }
+
+    @Test
+    void testToStringWithOptionsInspect() {
+        Map<String, Object> options = new HashMap<>();
+        options.put("inspect", true);
+        
+        String result = FormatHelper.toString(options, "hello");
+        assertTrue(result.contains("hello"));
+    }
+
+    @Test
+    void testToStringWithStringBuilder() {
+        StringBuilder sb = new StringBuilder("test");
+        String result = FormatHelper.toString(sb);
+        assertEquals("test", result);
+    }
+
+    @Test
+    void testToStringWithNestedCollection() {
+        List<List<Integer>> nested = Arrays.asList(
+            Arrays.asList(1, 2),
+            Arrays.asList(3, 4)
+        );
+        String result = FormatHelper.toString(nested);
+        assertEquals("[[1, 2], [3, 4]]", result);
+    }
+
+    @Test
+    void testWriteToBuffer() throws java.io.IOException {
+        java.io.StringWriter sw = new java.io.StringWriter();
+        FormatHelper.write(sw, "hello");
+        assertEquals("hello", sw.toString());
+    }
+
+    @Test
+    void testWriteNullToBuffer() throws java.io.IOException {
+        java.io.StringWriter sw = new java.io.StringWriter();
+        FormatHelper.write(sw, null);
+        assertEquals("null", sw.toString());
+    }
+
+    @Test
+    void testWriteArrayToBuffer() throws java.io.IOException {
+        java.io.StringWriter sw = new java.io.StringWriter();
+        Object[] arr = {"a", "b"};
+        FormatHelper.write(sw, arr);
+        assertEquals("[a, b]", sw.toString());
+    }
+
+    @Test
+    void testFormatSafeMode() {
+        // Object that throws exception on toString
+        Object problematic = new Object() {
+            @Override
+            public String toString() {
+                throw new RuntimeException("Cannot convert to string");
+            }
+        };
+        
+        // Safe mode should not throw
+        String result = FormatHelper.format(problematic, false, -1, true);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testFormatUnsafeMode() {
+        Object problematic = new Object() {
+            @Override
+            public String toString() {
+                throw new RuntimeException("Cannot convert to string");
+            }
+        };
+        
+        // Unsafe mode should throw
+        assertThrows(RuntimeException.class, () -> 
+            FormatHelper.format(problematic, false, -1, false)
+        );
+    }
+
+    @Test
+    void testEscapeBackslashes() {
+        String input = "hello\tworld\n";
+        String result = FormatHelper.escapeBackslashes(input);
+        assertTrue(result.contains("\\t") || result.contains("\\n"));
+    }
+
+    @Test
+    void testFormatWithEscapeBackslashes() {
+        String input = "line1\nline2";
+        String result = FormatHelper.format(input, true, true, -1, false);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testToArrayString() {
+        Object[] arr = {1, 2, 3};
+        String result = FormatHelper.toArrayString(arr);
+        assertEquals("[1, 2, 3]", result);
+    }
+
+    @Test
+    void testToArrayStringWithInspect() {
+        Object[] arr = {"a", "b"};
+        // toArrayString with params is private, test via format
+        String result = FormatHelper.format(arr, true, -1);
+        assertTrue(result.contains("a") && result.contains("b"));
+    }
+
+    @Test
+    void testToMapString() {
+        Map<String, Integer> map = new LinkedHashMap<>();
+        map.put("a", 1);
+        String result = FormatHelper.toMapString(map);
+        assertTrue(result.contains("a") && result.contains("1"));
+    }
+
+    @Test
+    void testToMapStringMaxSize() {
+        Map<String, Integer> map = new LinkedHashMap<>();
+        for (int i = 0; i < 100; i++) {
+            map.put("key" + i, i);
+        }
+        String result = FormatHelper.toMapString(map, 50);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testToListString() {
+        List<String> list = Arrays.asList("one", "two", "three");
+        String result = FormatHelper.toListString(list);
+        assertEquals("[one, two, three]", result);
+    }
+
+    @Test
+    void testToListStringWithMaxSize() {
+        List<String> list = Arrays.asList("one", "two", "three", "four", 
"five");
+        String result = FormatHelper.toListString(list, 15);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testToTypeString() {
+        Object[] args = {"hello", 42, true};
+        String result = FormatHelper.toTypeString(args);
+        assertTrue(result.contains("String"));
+        assertTrue(result.contains("Integer"));
+        assertTrue(result.contains("Boolean"));
+    }
+
+    @Test
+    void testToTypeStringWithMaxSize() {
+        Object[] args = {"hello", 42, true, 3.14, "world"};
+        String result = FormatHelper.toTypeString(args, 20);
+        assertNotNull(result);
+    }
+
+    @Test
+    void testToTypeStringNull() {
+        String result = FormatHelper.toTypeString(null);
+        assertEquals("null", result);
+    }
+
+    @Test
+    void testToTypeStringEmpty() {
+        String result = FormatHelper.toTypeString(new Object[0]);
+        assertEquals("", result);
+    }
+
+    @Test
+    void testFormatBoolean() {
+        assertEquals("true", FormatHelper.toString(true));
+        assertEquals("false", FormatHelper.toString(false));
+    }
+
+    @Test
+    void testFormatDouble() {
+        String result = FormatHelper.toString(3.14159);
+        assertTrue(result.startsWith("3.14"));
+    }
+
+    @Test
+    void testFormatLong() {
+        assertEquals("9999999999", FormatHelper.toString(9999999999L));
+    }
+
+    @Test
+    void testMetaRegistryNotNull() {
+        assertNotNull(FormatHelper.metaRegistry);
+    }
+}
diff --git 
a/src/test/java/org/codehaus/groovy/runtime/NumberAwareComparatorJUnit5Test.java
 
b/src/test/java/org/codehaus/groovy/runtime/NumberAwareComparatorJUnit5Test.java
new file mode 100644
index 0000000000..20c8f56243
--- /dev/null
+++ 
b/src/test/java/org/codehaus/groovy/runtime/NumberAwareComparatorJUnit5Test.java
@@ -0,0 +1,201 @@
+/*
+ *  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.runtime;
+
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for NumberAwareComparator class.
+ */
+class NumberAwareComparatorJUnit5Test {
+
+    @Test
+    void testCompareEqualIntegers() {
+        NumberAwareComparator<Integer> comparator = new 
NumberAwareComparator<>();
+        assertEquals(0, comparator.compare(5, 5));
+    }
+
+    @Test
+    void testCompareDifferentIntegers() {
+        NumberAwareComparator<Integer> comparator = new 
NumberAwareComparator<>();
+        assertTrue(comparator.compare(3, 5) < 0);
+        assertTrue(comparator.compare(5, 3) > 0);
+    }
+
+    @Test
+    void testCompareEqualStrings() {
+        NumberAwareComparator<String> comparator = new 
NumberAwareComparator<>();
+        assertEquals(0, comparator.compare("hello", "hello"));
+    }
+
+    @Test
+    void testCompareDifferentStrings() {
+        NumberAwareComparator<String> comparator = new 
NumberAwareComparator<>();
+        assertTrue(comparator.compare("apple", "banana") < 0);
+        assertTrue(comparator.compare("banana", "apple") > 0);
+    }
+
+    @Test
+    void testCompareNullFirst() {
+        NumberAwareComparator<String> comparator = new 
NumberAwareComparator<>();
+        assertTrue(comparator.compare(null, "hello") < 0);
+    }
+
+    @Test
+    void testCompareNullSecond() {
+        NumberAwareComparator<String> comparator = new 
NumberAwareComparator<>();
+        assertTrue(comparator.compare("hello", null) > 0);
+    }
+
+    @Test
+    void testCompareBothNull() {
+        NumberAwareComparator<String> comparator = new 
NumberAwareComparator<>();
+        assertEquals(0, comparator.compare(null, null));
+    }
+
+    @Test
+    void testCompareMixedNumberTypes() {
+        NumberAwareComparator<Number> comparator = new 
NumberAwareComparator<>();
+        // Different numeric types should be comparable
+        assertEquals(0, comparator.compare(5, 5L));
+        assertEquals(0, comparator.compare(5.0, 5));
+        assertTrue(comparator.compare(3, 5.0) < 0);
+    }
+
+    @Test
+    void testCompareDoubles() {
+        NumberAwareComparator<Double> comparator = new 
NumberAwareComparator<>();
+        assertEquals(0, comparator.compare(3.14, 3.14));
+        assertTrue(comparator.compare(2.71, 3.14) < 0);
+    }
+
+    @Test
+    void testCompareBigDecimal() {
+        NumberAwareComparator<BigDecimal> comparator = new 
NumberAwareComparator<>();
+        BigDecimal a = new BigDecimal("100.50");
+        BigDecimal b = new BigDecimal("100.50");
+        BigDecimal c = new BigDecimal("200.00");
+        
+        assertEquals(0, comparator.compare(a, b));
+        assertTrue(comparator.compare(a, c) < 0);
+    }
+
+    @Test
+    void testCompareBigInteger() {
+        NumberAwareComparator<BigInteger> comparator = new 
NumberAwareComparator<>();
+        BigInteger a = new BigInteger("12345678901234567890");
+        BigInteger b = new BigInteger("12345678901234567890");
+        BigInteger c = new BigInteger("98765432109876543210");
+        
+        assertEquals(0, comparator.compare(a, b));
+        assertTrue(comparator.compare(a, c) < 0);
+    }
+
+    @Test
+    void testIgnoreZeroSignFalse() {
+        NumberAwareComparator<Float> comparator = new 
NumberAwareComparator<>(false);
+        // +0.0f and -0.0f should be different
+        int result = comparator.compare(0.0f, -0.0f);
+        // They may or may not be equal depending on compareTo implementation
+        assertNotNull(result);
+    }
+
+    @Test
+    void testIgnoreZeroSignTrueForFloat() {
+        NumberAwareComparator<Float> comparator = new 
NumberAwareComparator<>(true);
+        // With ignoreZeroSign=true, +0.0f and -0.0f should be equal
+        assertEquals(0, comparator.compare(0.0f, -0.0f));
+        assertEquals(0, comparator.compare(-0.0f, 0.0f));
+    }
+
+    @Test
+    void testIgnoreZeroSignTrueForDouble() {
+        NumberAwareComparator<Double> comparator = new 
NumberAwareComparator<>(true);
+        // With ignoreZeroSign=true, +0.0 and -0.0 should be equal
+        assertEquals(0, comparator.compare(0.0d, -0.0d));
+        assertEquals(0, comparator.compare(-0.0d, 0.0d));
+    }
+
+    @Test
+    void testIgnoreZeroSignOnlyAffectsZero() {
+        NumberAwareComparator<Double> comparator = new 
NumberAwareComparator<>(true);
+        // Non-zero values should still compare normally
+        assertTrue(comparator.compare(1.0, 2.0) < 0);
+        assertTrue(comparator.compare(-1.0, 0.0) < 0);
+    }
+
+    @Test
+    void testSortingWithComparator() {
+        NumberAwareComparator<Integer> comparator = new 
NumberAwareComparator<>();
+        List<Integer> list = Arrays.asList(5, 2, 8, 1, 9);
+        Collections.sort(list, comparator);
+        
+        assertEquals(Arrays.asList(1, 2, 5, 8, 9), list);
+    }
+
+    @Test
+    void testSortingWithNulls() {
+        NumberAwareComparator<Integer> comparator = new 
NumberAwareComparator<>();
+        List<Integer> list = Arrays.asList(5, null, 2, null, 1);
+        Collections.sort(list, comparator);
+        
+        // Nulls should be first (less than any non-null)
+        assertNull(list.get(0));
+        assertNull(list.get(1));
+        assertEquals(1, list.get(2));
+    }
+
+    @Test
+    void testIsSerializable() {
+        NumberAwareComparator<Integer> comparator = new 
NumberAwareComparator<>();
+        assertTrue(comparator instanceof java.io.Serializable);
+    }
+
+    @Test
+    void testCompareIncompatibleTypes() {
+        NumberAwareComparator<Object> comparator = new 
NumberAwareComparator<>();
+        // Incompatible types should not throw but use hashCode comparison
+        int result = comparator.compare("string", 42);
+        // Result should be non-zero since they are different
+        assertNotEquals(0, result);
+    }
+
+    @Test
+    void testCompareSameObjectReference() {
+        NumberAwareComparator<Object> comparator = new 
NumberAwareComparator<>();
+        Object obj = new Object();
+        assertEquals(0, comparator.compare(obj, obj));
+    }
+
+    @Test
+    void testCompareEqualObjects() {
+        NumberAwareComparator<String> comparator = new 
NumberAwareComparator<>();
+        String s1 = new String("test");
+        String s2 = new String("test");
+        assertEquals(0, comparator.compare(s1, s2));
+    }
+}
diff --git a/src/test/java/org/codehaus/groovy/syntax/CSTNodeJUnit5Test.java 
b/src/test/java/org/codehaus/groovy/syntax/CSTNodeJUnit5Test.java
new file mode 100644
index 0000000000..95afc38920
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/syntax/CSTNodeJUnit5Test.java
@@ -0,0 +1,368 @@
+/*
+ *  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 java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for CSTNode class (via Token and Reduction implementations).
+ */
+class CSTNodeJUnit5Test {
+
+    @Test
+    void testTokenAsCSTNode() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        
+        assertNotNull(token);
+        assertEquals(Types.PLUS, token.getType());
+        assertEquals(Types.PLUS, token.getMeaning());
+    }
+
+    @Test
+    void testGetMeaning() {
+        Token token = Token.newSymbol(Types.STAR, 1, 1);
+        assertEquals(Types.STAR, token.getMeaning());
+    }
+
+    @Test
+    void testSetMeaning() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        CSTNode result = token.setMeaning(Types.MINUS);
+        
+        assertEquals(Types.MINUS, token.getMeaning());
+        assertSame(token, result);
+    }
+
+    @Test
+    void testGetType() {
+        Token token = Token.newIdentifier("test", 1, 1);
+        assertEquals(Types.IDENTIFIER, token.getType());
+    }
+
+    @Test
+    void testCanMean() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        
+        assertTrue(token.canMean(Types.PLUS));
+    }
+
+    @Test
+    void testIsA() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertTrue(token.isA(Types.PLUS));
+    }
+
+    @Test
+    void testIsOneOf() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        
+        int[] types = {Types.MINUS, Types.PLUS, Types.STAR};
+        assertTrue(token.isOneOf(types));
+        
+        int[] otherTypes = {Types.MINUS, Types.STAR};
+        assertFalse(token.isOneOf(otherTypes));
+    }
+
+    @Test
+    void testIsAllOf() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        
+        int[] types = {Types.PLUS};
+        assertTrue(token.isAllOf(types));
+    }
+
+    @Test
+    void testGetMeaningAs() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        
+        int[] types = {Types.MINUS, Types.PLUS};
+        assertEquals(Types.PLUS, token.getMeaningAs(types));
+        
+        int[] otherTypes = {Types.MINUS, Types.STAR};
+        assertEquals(Types.UNKNOWN, token.getMeaningAs(otherTypes));
+    }
+
+    @Test
+    void testIsEmpty() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertFalse(token.isEmpty());
+        
+        // Token.NULL is not actually "empty" - it's a valid token with type 
UNKNOWN
+        assertFalse(Token.NULL.isEmpty());
+    }
+
+    @Test
+    void testSize() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertEquals(1, token.size());
+    }
+
+    @Test
+    void testHasChildren() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertFalse(token.hasChildren());
+        
+        Reduction reduction = new Reduction(token);
+        assertFalse(reduction.hasChildren());
+        
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        assertTrue(reduction.hasChildren());
+    }
+
+    @Test
+    void testChildren() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        assertEquals(0, reduction.children());
+        
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        assertEquals(1, reduction.children());
+    }
+
+    @Test
+    void testGet() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        Token child = Token.newSymbol(Types.MINUS, 2, 2);
+        reduction.add(child);
+        
+        assertNotNull(reduction.get(0));
+        assertSame(child, reduction.get(1));
+        assertNull(reduction.get(99));
+    }
+
+    @Test
+    void testGetSafe() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        
+        CSTNode safeMissing = reduction.get(99, true);
+        assertSame(Token.NULL, safeMissing);
+        
+        CSTNode unsafeMissing = reduction.get(99, false);
+        assertNull(unsafeMissing);
+    }
+
+    @Test
+    void testGetRoot() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertSame(token, token.getRoot());
+        
+        Reduction reduction = new Reduction(token);
+        assertSame(token, reduction.getRoot());
+    }
+
+    @Test
+    void testGetRootSafe() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertSame(token, token.getRoot(true));
+        assertSame(token, token.getRoot(false));
+    }
+
+    @Test
+    void testGetRootText() {
+        Token token = Token.newIdentifier("myvar", 1, 1);
+        assertEquals("myvar", token.getRootText());
+    }
+
+    @Test
+    void testGetDescription() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        String desc = token.getDescription();
+        assertNotNull(desc);
+    }
+
+    @Test
+    void testGetStartLine() {
+        Token token = Token.newSymbol(Types.PLUS, 5, 10);
+        assertEquals(5, token.getStartLine());
+    }
+
+    @Test
+    void testGetStartColumn() {
+        Token token = Token.newSymbol(Types.PLUS, 5, 10);
+        assertEquals(10, token.getStartColumn());
+    }
+
+    @Test
+    void testMarkAsExpressionOnToken() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertThrows(GroovyBugError.class, () -> token.markAsExpression());
+    }
+
+    @Test
+    void testIsAnExpression() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertFalse(token.isAnExpression());
+    }
+
+    @Test
+    void testAddOnToken() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertThrows(GroovyBugError.class, () -> token.add(Token.NULL));
+    }
+
+    @Test
+    void testSetOnToken() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertThrows(GroovyBugError.class, () -> token.set(0, Token.NULL));
+    }
+
+    @Test
+    void testReductionAdd() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        Token child = Token.newSymbol(Types.MINUS, 2, 2);
+        
+        CSTNode result = reduction.add(child);
+        assertSame(child, result);
+        assertEquals(2, reduction.size());
+    }
+
+    @Test
+    void testReductionSet() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        Token child1 = Token.newSymbol(Types.MINUS, 2, 2);
+        Token child2 = Token.newSymbol(Types.STAR, 3, 3);
+        
+        reduction.add(child1);
+        reduction.set(1, child2);
+        
+        assertSame(child2, reduction.get(1));
+    }
+
+    @Test
+    void testAddChildrenOf() {
+        Reduction source = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        source.add(Token.newSymbol(Types.MINUS, 2, 2));
+        source.add(Token.newSymbol(Types.STAR, 3, 3));
+        
+        Reduction target = new Reduction(Token.newSymbol(Types.DIVIDE, 4, 4));
+        target.addChildrenOf(source);
+        
+        assertEquals(3, target.size()); // root + 2 children
+    }
+
+    @Test
+    void testAsReduction() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        Reduction reduction = token.asReduction();
+        
+        assertNotNull(reduction);
+        assertSame(token, reduction.getRoot());
+        
+        // Calling asReduction on Reduction returns self
+        Reduction existingReduction = new Reduction(token);
+        assertSame(existingReduction, existingReduction.asReduction());
+    }
+
+    @Test
+    void testToString() {
+        Token token = Token.newIdentifier("test", 1, 5);
+        String str = token.toString();
+        assertNotNull(str);
+        assertTrue(str.contains("IDENTIFIER") || str.contains("test"));
+    }
+
+    @Test
+    void testWriteToWriter() {
+        Token token = Token.newIdentifier("test", 1, 5);
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        
+        token.write(pw);
+        pw.flush();
+        
+        String output = sw.toString();
+        assertNotNull(output);
+        assertTrue(output.length() > 0);
+    }
+
+    @Test
+    void testWriteWithChildren() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        
+        reduction.write(pw);
+        pw.flush();
+        
+        String output = sw.toString();
+        assertNotNull(output);
+    }
+
+    @Test
+    void testTokenNullConstant() {
+        Token nullToken = Token.NULL;
+        
+        // Token.NULL is not empty - it's a valid token with type UNKNOWN
+        assertFalse(nullToken.isEmpty());
+        assertEquals(Types.UNKNOWN, nullToken.getType());
+    }
+
+    @Test
+    void testMatches() {
+        Token token = Token.newSymbol(Types.PLUS, 1, 1);
+        assertTrue(token.matches(Types.PLUS));
+        assertFalse(token.matches(Types.MINUS));
+    }
+
+    @Test
+    void testMatchesWithChildren() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        
+        assertTrue(reduction.matches(Types.PLUS, Types.MINUS));
+        assertFalse(reduction.matches(Types.PLUS, Types.STAR));
+    }
+
+    @Test
+    void testMatchesTwoChildren() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        reduction.add(Token.newSymbol(Types.STAR, 3, 3));
+        
+        assertTrue(reduction.matches(Types.PLUS, Types.MINUS, Types.STAR));
+    }
+
+    @Test
+    void testMatchesThreeChildren() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        reduction.add(Token.newSymbol(Types.STAR, 3, 3));
+        reduction.add(Token.newSymbol(Types.DIVIDE, 4, 4));
+        
+        assertTrue(reduction.matches(Types.PLUS, Types.MINUS, Types.STAR, 
Types.DIVIDE));
+    }
+
+    @Test
+    void testMatchesFourChildren() {
+        Reduction reduction = new Reduction(Token.newSymbol(Types.PLUS, 1, 1));
+        reduction.add(Token.newSymbol(Types.MINUS, 2, 2));
+        reduction.add(Token.newSymbol(Types.STAR, 3, 3));
+        reduction.add(Token.newSymbol(Types.DIVIDE, 4, 4));
+        reduction.add(Token.newSymbol(Types.MOD, 5, 5));
+        
+        assertTrue(reduction.matches(Types.PLUS, Types.MINUS, Types.STAR, 
Types.DIVIDE, Types.MOD));
+    }
+}
diff --git 
a/src/test/java/org/codehaus/groovy/tools/ErrorReporterJUnit5Test.java 
b/src/test/java/org/codehaus/groovy/tools/ErrorReporterJUnit5Test.java
new file mode 100644
index 0000000000..e0d6024094
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/tools/ErrorReporterJUnit5Test.java
@@ -0,0 +1,226 @@
+/*
+ *  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.tools;
+
+import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.junit.jupiter.api.Test;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.ByteArrayOutputStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for ErrorReporter class.
+ */
+class ErrorReporterJUnit5Test {
+
+    @Test
+    void testConstructorWithException() {
+        Exception e = new RuntimeException("test error");
+        ErrorReporter reporter = new ErrorReporter(e);
+        assertNotNull(reporter);
+    }
+
+    @Test
+    void testConstructorWithExceptionAndDebug() {
+        Exception e = new RuntimeException("test error");
+        ErrorReporter reporter = new ErrorReporter(e, true);
+        assertNotNull(reporter);
+    }
+
+    @Test
+    void testWriteToPrintStream() {
+        Exception e = new RuntimeException("test message");
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("test message"));
+    }
+
+    @Test
+    void testWriteToPrintWriter() {
+        Exception e = new RuntimeException("writer test");
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        
+        reporter.write(pw);
+        
+        String output = sw.toString();
+        assertTrue(output.contains("writer test"));
+    }
+
+    @Test
+    void testWriteWithDebugMode() {
+        Exception e = new RuntimeException("debug error");
+        ErrorReporter reporter = new ErrorReporter(e, true);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("debug error"));
+        assertTrue(output.contains("stacktrace"));
+    }
+
+    @Test
+    void testReportCompilationFailedException() {
+        Exception cause = new Exception("compile error");
+        CompilationFailedException e = new CompilationFailedException(0, null, 
cause);
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertNotNull(output);
+    }
+
+    @Test
+    void testReportGroovyRuntimeException() {
+        GroovyRuntimeException e = new GroovyRuntimeException("runtime error");
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("runtime error"));
+    }
+
+    @Test
+    void testReportGenericException() {
+        Exception e = new IllegalArgumentException("generic error");
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("generic error"));
+    }
+
+    @Test
+    void testReportThrowable() {
+        Throwable t = new Error("serious error");
+        ErrorReporter reporter = new ErrorReporter(t);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("serious error"));
+        assertTrue(output.contains("a serious error occurred"));
+    }
+
+    @Test
+    void testReportWithNullMessage() {
+        Exception e = new RuntimeException((String) null);
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        // Should not throw
+        assertDoesNotThrow(() -> reporter.write(ps));
+    }
+
+    @Test
+    void testDebugModeAlwaysShowsStackTrace() {
+        Exception e = new RuntimeException("test");
+        ErrorReporter reporter = new ErrorReporter(e, true);
+        
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        
+        reporter.write(pw);
+        
+        String output = sw.toString();
+        assertTrue(output.contains("stacktrace"));
+    }
+
+    @Test
+    void testNonDebugModeForRegularException() {
+        Exception e = new RuntimeException("simple error");
+        ErrorReporter reporter = new ErrorReporter(e, false);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("simple error"));
+        // In non-debug mode, regular exceptions don't show stacktrace by 
default
+    }
+
+    @Test
+    void testFlushIsCalled() {
+        Exception e = new RuntimeException("flush test");
+        ErrorReporter reporter = new ErrorReporter(e);
+        
+        final boolean[] flushed = {false};
+        PrintStream ps = new PrintStream(new ByteArrayOutputStream()) {
+            @Override
+            public void flush() {
+                flushed[0] = true;
+                super.flush();
+            }
+        };
+        
+        reporter.write(ps);
+        assertTrue(flushed[0]);
+    }
+
+    @Test
+    void testExceptionChain() {
+        Exception cause = new RuntimeException("root cause");
+        Exception wrapper = new RuntimeException("wrapper", cause);
+        ErrorReporter reporter = new ErrorReporter(wrapper);
+        
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        
+        reporter.write(ps);
+        
+        String output = baos.toString();
+        assertTrue(output.contains("wrapper"));
+    }
+}
diff --git a/src/test/java/org/codehaus/groovy/util/FastArrayJUnit5Test.java 
b/src/test/java/org/codehaus/groovy/util/FastArrayJUnit5Test.java
new file mode 100644
index 0000000000..39320ef50e
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/util/FastArrayJUnit5Test.java
@@ -0,0 +1,375 @@
+/*
+ *  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 java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for FastArray class.
+ */
+class FastArrayJUnit5Test {
+
+    @Test
+    void testDefaultConstructor() {
+        FastArray fa = new FastArray();
+        assertEquals(0, fa.size());
+        assertTrue(fa.isEmpty());
+    }
+
+    @Test
+    void testConstructorWithCapacity() {
+        FastArray fa = new FastArray(100);
+        assertEquals(0, fa.size());
+        assertTrue(fa.isEmpty());
+    }
+
+    @Test
+    void testConstructorWithCollection() {
+        List<String> list = Arrays.asList("a", "b", "c");
+        FastArray fa = new FastArray(list);
+        assertEquals(3, fa.size());
+        assertEquals("a", fa.get(0));
+        assertEquals("b", fa.get(1));
+        assertEquals("c", fa.get(2));
+    }
+
+    @Test
+    void testConstructorWithArray() {
+        Object[] array = {"x", "y", "z"};
+        FastArray fa = new FastArray(array);
+        assertEquals(3, fa.size());
+        assertEquals("x", fa.get(0));
+    }
+
+    @Test
+    void testAdd() {
+        FastArray fa = new FastArray();
+        fa.add("first");
+        assertEquals(1, fa.size());
+        assertEquals("first", fa.get(0));
+        
+        fa.add("second");
+        assertEquals(2, fa.size());
+        assertEquals("second", fa.get(1));
+    }
+
+    @Test
+    void testAddWithGrowth() {
+        FastArray fa = new FastArray(2);
+        fa.add("1");
+        fa.add("2");
+        fa.add("3"); // This should trigger growth
+        
+        assertEquals(3, fa.size());
+        assertEquals("3", fa.get(2));
+    }
+
+    @Test
+    void testAddFromEmptyArray() {
+        FastArray fa = new FastArray(0);
+        fa.add("item");
+        assertEquals(1, fa.size());
+        assertEquals("item", fa.get(0));
+    }
+
+    @Test
+    void testGet() {
+        FastArray fa = new FastArray();
+        fa.add("zero");
+        fa.add("one");
+        fa.add("two");
+        
+        assertEquals("zero", fa.get(0));
+        assertEquals("one", fa.get(1));
+        assertEquals("two", fa.get(2));
+    }
+
+    @Test
+    void testSet() {
+        FastArray fa = new FastArray();
+        fa.add("original");
+        
+        fa.set(0, "modified");
+        assertEquals("modified", fa.get(0));
+    }
+
+    @Test
+    void testSize() {
+        FastArray fa = new FastArray();
+        assertEquals(0, fa.size());
+        
+        fa.add("a");
+        assertEquals(1, fa.size());
+        
+        fa.add("b");
+        fa.add("c");
+        assertEquals(3, fa.size());
+    }
+
+    @Test
+    void testClear() {
+        FastArray fa = new FastArray();
+        fa.add("a");
+        fa.add("b");
+        fa.add("c");
+        assertEquals(3, fa.size());
+        
+        fa.clear();
+        assertEquals(0, fa.size());
+        assertTrue(fa.isEmpty());
+    }
+
+    @Test
+    void testIsEmpty() {
+        FastArray fa = new FastArray();
+        assertTrue(fa.isEmpty());
+        
+        fa.add("item");
+        assertFalse(fa.isEmpty());
+        
+        fa.clear();
+        assertTrue(fa.isEmpty());
+    }
+
+    @Test
+    void testAddAllFastArray() {
+        FastArray fa1 = new FastArray();
+        fa1.add("a");
+        fa1.add("b");
+        
+        FastArray fa2 = new FastArray();
+        fa2.add("c");
+        fa2.add("d");
+        
+        fa1.addAll(fa2);
+        
+        assertEquals(4, fa1.size());
+        assertEquals("a", fa1.get(0));
+        assertEquals("b", fa1.get(1));
+        assertEquals("c", fa1.get(2));
+        assertEquals("d", fa1.get(3));
+    }
+
+    @Test
+    void testAddAllEmptyFastArray() {
+        FastArray fa1 = new FastArray();
+        fa1.add("a");
+        
+        FastArray fa2 = new FastArray();
+        
+        fa1.addAll(fa2);
+        assertEquals(1, fa1.size());
+    }
+
+    @Test
+    void testAddAllList() {
+        FastArray fa = new FastArray();
+        fa.add("first");
+        
+        List<String> list = Arrays.asList("second", "third");
+        fa.addAll(list);
+        
+        assertEquals(3, fa.size());
+        assertEquals("second", fa.get(1));
+        assertEquals("third", fa.get(2));
+    }
+
+    @Test
+    void testAddAllObjectArray() {
+        FastArray fa = new FastArray();
+        fa.add("start");
+        
+        Object[] array = {"middle", "end"};
+        fa.addAll(array, array.length);
+        
+        assertEquals(3, fa.size());
+    }
+
+    @Test
+    void testCopy() {
+        FastArray original = new FastArray();
+        original.add("a");
+        original.add("b");
+        original.add("c");
+        
+        FastArray copy = original.copy();
+        
+        assertEquals(original.size(), copy.size());
+        assertEquals(original.get(0), copy.get(0));
+        assertEquals(original.get(1), copy.get(1));
+        assertEquals(original.get(2), copy.get(2));
+        
+        // Verify it's a real copy, not the same reference
+        copy.set(0, "modified");
+        assertEquals("a", original.get(0));
+        assertEquals("modified", copy.get(0));
+    }
+
+    @Test
+    void testRemove() {
+        FastArray fa = new FastArray();
+        fa.add("a");
+        fa.add("b");
+        fa.add("c");
+        
+        fa.remove(1);
+        
+        assertEquals(2, fa.size());
+        assertEquals("a", fa.get(0));
+        assertEquals("c", fa.get(1));
+    }
+
+    @Test
+    void testRemoveFirst() {
+        FastArray fa = new FastArray();
+        fa.add("a");
+        fa.add("b");
+        fa.add("c");
+        
+        fa.remove(0);
+        
+        assertEquals(2, fa.size());
+        assertEquals("b", fa.get(0));
+    }
+
+    @Test
+    void testRemoveLast() {
+        FastArray fa = new FastArray();
+        fa.add("a");
+        fa.add("b");
+        fa.add("c");
+        
+        fa.remove(2);
+        
+        assertEquals(2, fa.size());
+        assertEquals("b", fa.get(1));
+    }
+
+    @Test
+    void testToListEmpty() {
+        FastArray fa = new FastArray();
+        List<?> list = fa.toList();
+        assertTrue(list.isEmpty());
+    }
+
+    @Test
+    void testToListSingle() {
+        FastArray fa = new FastArray();
+        fa.add("only");
+        
+        List<?> list = fa.toList();
+        assertEquals(1, list.size());
+        assertEquals("only", list.get(0));
+    }
+
+    @Test
+    void testToListMultiple() {
+        FastArray fa = new FastArray();
+        fa.add("a");
+        fa.add("b");
+        fa.add("c");
+        
+        List<?> list = fa.toList();
+        assertEquals(3, list.size());
+        assertEquals("a", list.get(0));
+        assertEquals("b", list.get(1));
+        assertEquals("c", list.get(2));
+    }
+
+    @Test
+    void testGetArray() {
+        FastArray fa = new FastArray();
+        fa.add("x");
+        fa.add("y");
+        
+        Object[] array = fa.getArray();
+        assertNotNull(array);
+        assertEquals("x", array[0]);
+        assertEquals("y", array[1]);
+    }
+
+    @Test
+    void testToStringEmpty() {
+        FastArray fa = new FastArray();
+        assertEquals("[]", fa.toString());
+    }
+
+    @Test
+    void testToStringNonEmpty() {
+        FastArray fa = new FastArray();
+        fa.add("a");
+        fa.add("b");
+        
+        String str = fa.toString();
+        assertTrue(str.contains("a"));
+        assertTrue(str.contains("b"));
+    }
+
+    @Test
+    void testClone() {
+        FastArray original = new FastArray();
+        original.add("1");
+        original.add("2");
+        
+        FastArray clone = original.clone();
+        
+        assertEquals(original.size(), clone.size());
+        assertEquals(original.get(0), clone.get(0));
+        
+        // Verify it's independent
+        clone.add("3");
+        assertEquals(2, original.size());
+        assertEquals(3, clone.size());
+    }
+
+    @Test
+    void testEmptyList() {
+        assertNotNull(FastArray.EMPTY_LIST);
+        assertEquals(0, FastArray.EMPTY_LIST.size());
+        assertTrue(FastArray.EMPTY_LIST.isEmpty());
+    }
+
+    @Test
+    void testAddNull() {
+        FastArray fa = new FastArray();
+        fa.add(null);
+        assertEquals(1, fa.size());
+        assertNull(fa.get(0));
+    }
+
+    @Test
+    void testMixedTypes() {
+        FastArray fa = new FastArray();
+        fa.add("string");
+        fa.add(42);
+        fa.add(3.14);
+        fa.add(true);
+        
+        assertEquals(4, fa.size());
+        assertEquals("string", fa.get(0));
+        assertEquals(42, fa.get(1));
+        assertEquals(3.14, fa.get(2));
+        assertEquals(true, fa.get(3));
+    }
+}
diff --git 
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/CharBufTest.java
 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/CharBufTest.java
new file mode 100644
index 0000000000..b701be60da
--- /dev/null
+++ 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/CharBufTest.java
@@ -0,0 +1,402 @@
+/*
+ *  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.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for CharBuf class.
+ */
+class CharBufTest {
+
+    @Test
+    void testCreateWithCapacity() {
+        CharBuf buf = CharBuf.create(100);
+        assertNotNull(buf);
+        assertEquals(0, buf.length());
+    }
+
+    @Test
+    void testCreateWithCharArray() {
+        char[] chars = "hello".toCharArray();
+        CharBuf buf = CharBuf.create(chars);
+        assertNotNull(buf);
+        // create() with char[] sets the capacity but location is still 0
+        // Content must be added separately
+        assertEquals(0, buf.length());
+    }
+
+    @Test
+    void testCreateExact() {
+        CharBuf buf = CharBuf.createExact(100);
+        assertNotNull(buf);
+    }
+
+    @Test
+    void testAddString() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("hello");
+        assertEquals(5, buf.length());
+        assertEquals("hello", buf.toString());
+    }
+
+    @Test
+    void testAddStringChain() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("hello").add(" ").add("world");
+        assertEquals("hello world", buf.toString());
+    }
+
+    @Test
+    void testAddStringMethod() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addString("test");
+        assertEquals("test", buf.toString());
+    }
+
+    @Test
+    void testAddInt() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add(42);
+        assertEquals("42", buf.toString());
+    }
+
+    @Test
+    void testAddIntSpecialCases() {
+        CharBuf buf1 = CharBuf.create(16);
+        buf1.addInt(0);
+        assertEquals("0", buf1.toString());
+
+        CharBuf buf2 = CharBuf.create(16);
+        buf2.addInt(1);
+        assertEquals("1", buf2.toString());
+
+        CharBuf buf3 = CharBuf.create(16);
+        buf3.addInt(-1);
+        assertEquals("-1", buf3.toString());
+    }
+
+    @Test
+    void testAddIntCaching() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addInt(42);
+        buf.addInt(42); // Should use cached value
+        assertEquals("4242", buf.toString());
+    }
+
+    @Test
+    void testAddBoolean() {
+        CharBuf bufTrue = CharBuf.create(16);
+        bufTrue.add(true);
+        assertEquals("true", bufTrue.toString());
+
+        CharBuf bufFalse = CharBuf.create(16);
+        bufFalse.add(false);
+        assertEquals("false", bufFalse.toString());
+    }
+
+    @Test
+    void testAddBooleanMethod() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addBoolean(true);
+        assertEquals("true", buf.toString());
+    }
+
+    @Test
+    void testAddByte() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add((byte) 42);
+        assertEquals("42", buf.toString());
+    }
+
+    @Test
+    void testAddByteMethod() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addByte((byte) 10);
+        assertEquals("10", buf.toString());
+    }
+
+    @Test
+    void testAddShort() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add((short) 1000);
+        assertEquals("1000", buf.toString());
+    }
+
+    @Test
+    void testAddShortMethod() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addShort((short) 500);
+        assertEquals("500", buf.toString());
+    }
+
+    @Test
+    void testAddLong() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add(9999999999L);
+        assertEquals("9999999999", buf.toString());
+    }
+
+    @Test
+    void testAddDouble() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add(3.14);
+        assertTrue(buf.toString().startsWith("3.14"));
+    }
+
+    @Test
+    void testAddDoubleMethod() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addDouble(2.5);
+        buf.addDouble(2.5); // Should use caching
+        String result = buf.toString();
+        assertTrue(result.contains("2.5"));
+    }
+
+    @Test
+    void testAddFloat() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add(1.5f);
+        assertTrue(buf.toString().contains("1.5"));
+    }
+
+    @Test
+    void testAddFloatMethod() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addFloat(2.5f);
+        buf.addFloat(2.5f); // Should use caching
+        assertTrue(buf.toString().contains("2.5"));
+    }
+
+    @Test
+    void testAddChar() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addChar('A');
+        assertEquals("A", buf.toString());
+    }
+
+    @Test
+    void testAddCharFromByte() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addChar((byte) 65);
+        assertEquals("A", buf.toString());
+    }
+
+    @Test
+    void testAddCharFromInt() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addChar(65);
+        assertEquals("A", buf.toString());
+    }
+
+    @Test
+    void testAddCharFromShort() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addChar((short) 65);
+        assertEquals("A", buf.toString());
+    }
+
+    @Test
+    void testAddLine() {
+        CharBuf buf = CharBuf.create(32);
+        buf.addLine("hello");
+        assertEquals("hello\n", buf.toString());
+    }
+
+    @Test
+    void testAddLineCharSequence() {
+        CharBuf buf = CharBuf.create(32);
+        CharSequence cs = "world";
+        buf.addLine(cs);
+        assertEquals("world\n", buf.toString());
+    }
+
+    @Test
+    void testAddCharArray() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("test".toCharArray());
+        assertEquals("test", buf.toString());
+    }
+
+    @Test
+    void testAddChars() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addChars("data".toCharArray());
+        assertEquals("data", buf.toString());
+    }
+
+    @Test
+    void testAddQuoted() {
+        CharBuf buf = CharBuf.create(16);
+        buf.addQuoted("hello".toCharArray());
+        assertEquals("\"hello\"", buf.toString());
+    }
+
+    @Test
+    void testAddJsonEscapedString() {
+        CharBuf buf = CharBuf.create(32);
+        buf.addJsonEscapedString("hello");
+        assertEquals("\"hello\"", buf.toString());
+    }
+
+    @Test
+    void testAddJsonEscapedStringWithSpecialChars() {
+        CharBuf buf = CharBuf.create(64);
+        buf.addJsonEscapedString("hello\nworld\t!");
+        String result = buf.toString();
+        assertTrue(result.contains("\\n"));
+        assertTrue(result.contains("\\t"));
+    }
+
+    @Test
+    void testAddJsonEscapedStringWithUnicodeDisabled() {
+        CharBuf buf = CharBuf.create(32);
+        buf.addJsonEscapedString("hello", true);
+        assertEquals("\"hello\"", buf.toString());
+    }
+
+    @Test
+    void testCharSequenceInterface() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("hello");
+
+        assertEquals(5, buf.length());
+        assertEquals('h', buf.charAt(0));
+        assertEquals('e', buf.charAt(1));
+        assertEquals('o', buf.charAt(4));
+    }
+
+    @Test
+    void testSubSequence() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("hello world");
+        CharSequence sub = buf.subSequence(0, 5);
+        assertEquals("hello", sub.toString());
+    }
+
+    @Test
+    void testWriterWrite() throws IOException {
+        CharBuf buf = CharBuf.create(16);
+        buf.write("test".toCharArray(), 0, 4);
+        assertEquals("test", buf.toString());
+    }
+
+    @Test
+    void testWriterWritePartial() throws IOException {
+        CharBuf buf = CharBuf.create(16);
+        buf.write("hello world".toCharArray(), 6, 5);
+        assertEquals("world", buf.toString());
+    }
+
+    @Test
+    void testWriterFlush() throws IOException {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("test");
+        buf.flush(); // Should not throw
+        assertEquals("test", buf.toString());
+    }
+
+    @Test
+    void testWriterClose() throws IOException {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("test");
+        buf.close(); // Should not throw
+        assertEquals("test", buf.toString());
+    }
+
+    @Test
+    void testToCharArray() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("hello");
+        // toCharArray returns the underlying buffer which is larger than 
content
+        char[] chars = buf.toCharArray();
+        assertNotNull(chars);
+        assertTrue(chars.length >= 5);
+    }
+
+    @Test
+    void testAutoExpansion() {
+        CharBuf buf = CharBuf.create(4);
+        buf.add("this is a much longer string that exceeds initial capacity");
+        assertEquals("this is a much longer string that exceeds initial 
capacity", buf.toString());
+    }
+
+    @Test
+    void testAutoExpansionWithChars() {
+        CharBuf buf = CharBuf.create(4);
+        for (int i = 0; i < 100; i++) {
+            buf.addChar('x');
+        }
+        assertEquals(100, buf.length());
+    }
+
+    @Test
+    void testReadForRecycleWithSmallLength() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("hello");
+        char[] result = buf.readForRecycle();
+        assertNotNull(result);
+        // Should return the underlying buffer for recycling
+    }
+
+    @Test
+    void testEmptyBuffer() {
+        CharBuf buf = CharBuf.create(16);
+        assertEquals(0, buf.length());
+        assertEquals("", buf.toString());
+    }
+
+    @Test
+    void testToStringWithBuffer() {
+        char[] initial = "initial".toCharArray();
+        CharBuf buf = new CharBuf(initial);
+        // Constructor sets capacity from buffer but location is 0
+        assertEquals("", buf.toString());
+        // Add content to see it
+        buf.add("test");
+        assertEquals("test", buf.toString());
+    }
+
+    @Test
+    void testConstructorWithBytes() {
+        byte[] bytes = "hello".getBytes();
+        CharBuf buf = new CharBuf(bytes);
+        // Constructor sets the buffer but location is 0
+        assertEquals("", buf.toString());
+        // Add content after construction
+        buf.add("world");
+        assertEquals("world", buf.toString());
+    }
+
+    @Test
+    void testMultipleAdditions() {
+        CharBuf buf = CharBuf.create(16);
+        buf.add("one")
+           .add(2)
+           .add(true)
+           .addChar('-')
+           .add(3.14);
+        String result = buf.toString();
+        assertTrue(result.startsWith("one2true-3.14"));
+    }
+}
diff --git 
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/SimpleCacheTest.java
 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/SimpleCacheTest.java
new file mode 100644
index 0000000000..cb283541f2
--- /dev/null
+++ 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/SimpleCacheTest.java
@@ -0,0 +1,213 @@
+/*
+ *  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 static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for SimpleCache class.
+ */
+class SimpleCacheTest {
+
+    @Test
+    void testConstructorWithLimit() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        assertNotNull(cache);
+        assertEquals(0, cache.size());
+    }
+
+    @Test
+    void testConstructorWithLimitAndLRUType() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10, 
CacheType.LRU);
+        assertNotNull(cache);
+    }
+
+    @Test
+    void testConstructorWithLimitAndFIFOType() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10, 
CacheType.FIFO);
+        assertNotNull(cache);
+    }
+
+    @Test
+    void testPutAndGet() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        
+        cache.put("key1", "value1");
+        assertEquals("value1", cache.get("key1"));
+    }
+
+    @Test
+    void testGetNonExistent() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        assertNull(cache.get("nonexistent"));
+    }
+
+    @Test
+    void testPutMultiple() {
+        SimpleCache<String, Integer> cache = new SimpleCache<>(10);
+        
+        cache.put("one", 1);
+        cache.put("two", 2);
+        cache.put("three", 3);
+        
+        assertEquals(3, cache.size());
+        assertEquals(1, cache.get("one"));
+        assertEquals(2, cache.get("two"));
+        assertEquals(3, cache.get("three"));
+    }
+
+    @Test
+    void testPutOverwrite() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        
+        cache.put("key", "original");
+        assertEquals("original", cache.get("key"));
+        
+        cache.put("key", "updated");
+        assertEquals("updated", cache.get("key"));
+    }
+
+    @Test
+    void testRemove() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        
+        cache.put("key", "value");
+        assertEquals(1, cache.size());
+        
+        cache.remove("key");
+        assertNull(cache.get("key"));
+    }
+
+    @Test
+    void testRemoveNonExistent() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        // Should not throw
+        assertDoesNotThrow(() -> cache.remove("nonexistent"));
+    }
+
+    @Test
+    void testSize() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        assertEquals(0, cache.size());
+        
+        cache.put("a", "1");
+        assertEquals(1, cache.size());
+        
+        cache.put("b", "2");
+        cache.put("c", "3");
+        assertEquals(3, cache.size());
+    }
+
+    @Test
+    void testGetSilent() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        
+        cache.put("key", "value");
+        
+        // getSilent should return the value without affecting LRU order
+        String value = cache.getSilent("key");
+        assertEquals("value", value);
+    }
+
+    @Test
+    void testGetSilentNonExistent() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        assertNull(cache.getSilent("nonexistent"));
+    }
+
+    @Test
+    void testToString() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        cache.put("key", "value");
+        
+        String str = cache.toString();
+        assertNotNull(str);
+    }
+
+    @Test
+    void testEvictionLRU() {
+        // Small cache that will evict
+        SimpleCache<Integer, String> cache = new SimpleCache<>(3, 
CacheType.LRU);
+        
+        cache.put(1, "one");
+        cache.put(2, "two");
+        cache.put(3, "three");
+        
+        // Access 1 to make it recently used
+        cache.get(1);
+        
+        // Add 4, should evict least recently used (2)
+        cache.put(4, "four");
+        
+        assertEquals(3, cache.size());
+        assertNotNull(cache.get(1)); // 1 should still be there
+        assertNotNull(cache.get(4)); // 4 should be there
+    }
+
+    @Test
+    void testEvictionFIFO() {
+        // Small cache that will evict
+        SimpleCache<Integer, String> cache = new SimpleCache<>(3, 
CacheType.FIFO);
+        
+        cache.put(1, "one");
+        cache.put(2, "two");
+        cache.put(3, "three");
+        
+        // Add 4, should evict first in (1)
+        cache.put(4, "four");
+        
+        assertEquals(3, cache.size());
+        assertNotNull(cache.get(4)); // 4 should be there
+    }
+
+    @Test
+    void testWithNullValue() {
+        SimpleCache<String, String> cache = new SimpleCache<>(10);
+        
+        // Null values are not supported - throws NullPointerException
+        assertThrows(NullPointerException.class, () -> {
+            cache.put("key", null);
+        });
+    }
+
+    @Test
+    void testCacheTypeLRU() {
+        assertEquals(CacheType.LRU, CacheType.valueOf("LRU"));
+    }
+
+    @Test
+    void testCacheTypeFIFO() {
+        assertEquals(CacheType.FIFO, CacheType.valueOf("FIFO"));
+    }
+
+    @Test
+    void testLargeCache() {
+        SimpleCache<Integer, Integer> cache = new SimpleCache<>(1000);
+        
+        for (int i = 0; i < 500; i++) {
+            cache.put(i, i * 2);
+        }
+        
+        assertEquals(500, cache.size());
+        assertEquals(0, cache.get(0));
+        assertEquals(998, cache.get(499));
+    }
+}
diff --git 
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/SysTest.java
 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/SysTest.java
new file mode 100644
index 0000000000..bf232d0b6c
--- /dev/null
+++ 
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/SysTest.java
@@ -0,0 +1,103 @@
+/*
+ *  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 static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for Sys class (Java version detection utilities).
+ */
+class SysTest {
+
+    @Test
+    void testIs1_7OrLater() {
+        // Since we're running on modern Java, this should always be true
+        assertTrue(Sys.is1_7OrLater());
+    }
+
+    @Test
+    void testIs1_8OrLater() {
+        // Since we're running on Java 8+, this should be true
+        assertTrue(Sys.is1_8OrLater());
+    }
+
+    @Test
+    void testIs1_7() {
+        // If we're on Java 7, this is true, otherwise false
+        // On modern JDKs (8+), this should be false
+        boolean result = Sys.is1_7();
+        // Just verify it returns a boolean without error
+        assertTrue(result || !result);
+    }
+
+    @Test
+    void testIs1_8() {
+        // Returns true only if we're on exactly Java 8
+        boolean result = Sys.is1_8();
+        // Just verify it returns a boolean without error
+        assertTrue(result || !result);
+    }
+
+    @Test
+    void testVersionDetectionConsistency() {
+        // If is1_8 is true, is1_8OrLater must also be true
+        if (Sys.is1_8()) {
+            assertTrue(Sys.is1_8OrLater());
+        }
+
+        // If is1_7 is true, is1_7OrLater must be true
+        if (Sys.is1_7()) {
+            assertTrue(Sys.is1_7OrLater());
+        }
+    }
+
+    @Test
+    void testNotBothJava7And8() {
+        // Cannot be both Java 7 and Java 8
+        assertFalse(Sys.is1_7() && Sys.is1_8());
+    }
+
+    @Test
+    void testJava7ImpliesNotJava8() {
+        if (Sys.is1_7()) {
+            assertFalse(Sys.is1_8());
+        }
+    }
+
+    @Test
+    void testJava8ImpliesNotJava7() {
+        if (Sys.is1_8()) {
+            assertFalse(Sys.is1_7());
+        }
+    }
+
+    @Test
+    void testModernJavaVersion() {
+        // On Java 9+, both is1_7() and is1_8() should be false
+        String javaVersion = System.getProperty("java.version");
+        if (javaVersion.startsWith("9") || !javaVersion.startsWith("1")) {
+            // We're on Java 9 or later (version strings like "11", "17", "21")
+            assertFalse(Sys.is1_7());
+            assertFalse(Sys.is1_8());
+            assertTrue(Sys.is1_8OrLater());
+        }
+    }
+}

Reply via email to