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 aa72d4fc16 Add tests to increase code coverage: `ChrTest`,
`ExceptionsTest`, etc.
aa72d4fc16 is described below
commit aa72d4fc162cea141a57c8c2a3d60b6da235cc61
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Feb 1 20:58:16 2026 +0900
Add tests to increase code coverage: `ChrTest`, `ExceptionsTest`, etc.
---
.../concurrentlinkedhashmap/LinkedDequeTest.java | 624 +++++++++++++++++++++
.../groovy/runtime/StackTraceUtilsJUnit5Test.java | 225 ++++++++
.../groovy/util/HashCodeHelperJUnit5Test.java | 393 +++++++++++++
.../org/apache/groovy/json/internal/ChrTest.java | 347 ++++++++++++
.../groovy/json/internal/ExceptionsTest.java | 318 +++++++++++
.../groovy/json/internal/ValueMapImplTest.java | 278 +++++++++
6 files changed, 2185 insertions(+)
diff --git
a/src/test/java/org/apache/groovy/util/concurrent/concurrentlinkedhashmap/LinkedDequeTest.java
b/src/test/java/org/apache/groovy/util/concurrent/concurrentlinkedhashmap/LinkedDequeTest.java
new file mode 100644
index 0000000000..cabdcddc65
--- /dev/null
+++
b/src/test/java/org/apache/groovy/util/concurrent/concurrentlinkedhashmap/LinkedDequeTest.java
@@ -0,0 +1,624 @@
+/*
+ * 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.util.concurrent.concurrentlinkedhashmap;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for LinkedDeque class.
+ */
+class LinkedDequeTest {
+
+ private LinkedDeque<TestNode> deque;
+
+ @BeforeEach
+ void setUp() {
+ deque = new LinkedDeque<>();
+ }
+
+ // Test node implementation
+ static class TestNode implements Linked<TestNode> {
+ private TestNode prev;
+ private TestNode next;
+ private final String value;
+
+ TestNode(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public TestNode getPrevious() {
+ return prev;
+ }
+
+ @Override
+ public void setPrevious(TestNode prev) {
+ this.prev = prev;
+ }
+
+ @Override
+ public TestNode getNext() {
+ return next;
+ }
+
+ @Override
+ public void setNext(TestNode next) {
+ this.next = next;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+ }
+
+ @Test
+ void testIsEmptyOnNew() {
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testSizeOnEmpty() {
+ assertEquals(0, deque.size());
+ }
+
+ @Test
+ void testAddFirst() {
+ TestNode node = new TestNode("first");
+ deque.addFirst(node);
+
+ assertFalse(deque.isEmpty());
+ assertEquals(1, deque.size());
+ assertSame(node, deque.peekFirst());
+ }
+
+ @Test
+ void testAddLast() {
+ TestNode node = new TestNode("last");
+ deque.addLast(node);
+
+ assertFalse(deque.isEmpty());
+ assertEquals(1, deque.size());
+ assertSame(node, deque.peekLast());
+ }
+
+ @Test
+ void testOfferFirst() {
+ TestNode node = new TestNode("offer-first");
+ assertTrue(deque.offerFirst(node));
+ assertSame(node, deque.peekFirst());
+ }
+
+ @Test
+ void testOfferLast() {
+ TestNode node = new TestNode("offer-last");
+ assertTrue(deque.offerLast(node));
+ assertSame(node, deque.peekLast());
+ }
+
+ @Test
+ void testOfferFirstDuplicateReturnsfalse() {
+ TestNode node = new TestNode("dup");
+ assertTrue(deque.offerFirst(node));
+ assertFalse(deque.offerFirst(node)); // Already in deque
+ }
+
+ @Test
+ void testOfferLastDuplicateReturnsFalse() {
+ TestNode node = new TestNode("dup");
+ assertTrue(deque.offerLast(node));
+ assertFalse(deque.offerLast(node)); // Already in deque
+ }
+
+ @Test
+ void testOffer() {
+ TestNode node = new TestNode("offer");
+ assertTrue(deque.offer(node));
+ assertSame(node, deque.peekLast());
+ }
+
+ @Test
+ void testAdd() {
+ TestNode node = new TestNode("add");
+ assertTrue(deque.add(node));
+ assertEquals(1, deque.size());
+ }
+
+ @Test
+ void testPeekOnEmpty() {
+ assertNull(deque.peek());
+ }
+
+ @Test
+ void testPeekFirstOnEmpty() {
+ assertNull(deque.peekFirst());
+ }
+
+ @Test
+ void testPeekLastOnEmpty() {
+ assertNull(deque.peekLast());
+ }
+
+ @Test
+ void testPeek() {
+ TestNode node = new TestNode("peek-test");
+ deque.addFirst(node);
+ assertSame(node, deque.peek());
+ }
+
+ @Test
+ void testGetFirstOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.getFirst());
+ }
+
+ @Test
+ void testGetLastOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.getLast());
+ }
+
+ @Test
+ void testElementOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.element());
+ }
+
+ @Test
+ void testGetFirst() {
+ TestNode node = new TestNode("get-first");
+ deque.addFirst(node);
+ assertSame(node, deque.getFirst());
+ }
+
+ @Test
+ void testGetLast() {
+ TestNode node = new TestNode("get-last");
+ deque.addLast(node);
+ assertSame(node, deque.getLast());
+ }
+
+ @Test
+ void testElement() {
+ TestNode node = new TestNode("element");
+ deque.addFirst(node);
+ assertSame(node, deque.element());
+ }
+
+ @Test
+ void testPollOnEmpty() {
+ assertNull(deque.poll());
+ }
+
+ @Test
+ void testPollFirstOnEmpty() {
+ assertNull(deque.pollFirst());
+ }
+
+ @Test
+ void testPollLastOnEmpty() {
+ assertNull(deque.pollLast());
+ }
+
+ @Test
+ void testPoll() {
+ TestNode node = new TestNode("poll");
+ deque.addFirst(node);
+ assertSame(node, deque.poll());
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testPollFirst() {
+ TestNode first = new TestNode("first");
+ TestNode second = new TestNode("second");
+ deque.addLast(first);
+ deque.addLast(second);
+
+ assertSame(first, deque.pollFirst());
+ assertEquals(1, deque.size());
+ assertSame(second, deque.peekFirst());
+ }
+
+ @Test
+ void testPollLast() {
+ TestNode first = new TestNode("first");
+ TestNode second = new TestNode("second");
+ deque.addLast(first);
+ deque.addLast(second);
+
+ assertSame(second, deque.pollLast());
+ assertEquals(1, deque.size());
+ assertSame(first, deque.peekLast());
+ }
+
+ @Test
+ void testRemoveOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.remove());
+ }
+
+ @Test
+ void testRemoveFirstOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.removeFirst());
+ }
+
+ @Test
+ void testRemoveLastOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.removeLast());
+ }
+
+ @Test
+ void testRemove() {
+ TestNode node = new TestNode("remove");
+ deque.addFirst(node);
+ assertSame(node, deque.remove());
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testRemoveFirst() {
+ TestNode first = new TestNode("first");
+ TestNode second = new TestNode("second");
+ deque.addLast(first);
+ deque.addLast(second);
+
+ assertSame(first, deque.removeFirst());
+ assertEquals(1, deque.size());
+ }
+
+ @Test
+ void testRemoveLast() {
+ TestNode first = new TestNode("first");
+ TestNode second = new TestNode("second");
+ deque.addLast(first);
+ deque.addLast(second);
+
+ assertSame(second, deque.removeLast());
+ assertEquals(1, deque.size());
+ }
+
+ @Test
+ void testRemoveObject() {
+ TestNode node = new TestNode("to-remove");
+ deque.addFirst(node);
+
+ assertTrue(deque.remove(node));
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testRemoveObjectNotFound() {
+ TestNode node1 = new TestNode("in-deque");
+ TestNode node2 = new TestNode("not-in-deque");
+ deque.addFirst(node1);
+
+ assertFalse(deque.remove(node2));
+ assertEquals(1, deque.size());
+ }
+
+ @Test
+ void testRemoveNonLinked() {
+ assertFalse(deque.remove("not a linked object"));
+ }
+
+ @Test
+ void testContains() {
+ TestNode node = new TestNode("contained");
+ deque.addFirst(node);
+
+ assertTrue(deque.contains(node));
+ }
+
+ @Test
+ void testContainsNotFound() {
+ TestNode node1 = new TestNode("in-deque");
+ TestNode node2 = new TestNode("not-in-deque");
+ deque.addFirst(node1);
+
+ assertFalse(deque.contains(node2));
+ }
+
+ @Test
+ void testContainsNonLinked() {
+ assertFalse(deque.contains("not a linked object"));
+ }
+
+ @Test
+ void testClear() {
+ deque.addLast(new TestNode("a"));
+ deque.addLast(new TestNode("b"));
+ deque.addLast(new TestNode("c"));
+
+ assertEquals(3, deque.size());
+
+ deque.clear();
+
+ assertTrue(deque.isEmpty());
+ assertEquals(0, deque.size());
+ }
+
+ @Test
+ void testPush() {
+ TestNode node = new TestNode("pushed");
+ deque.push(node);
+
+ assertSame(node, deque.peekFirst());
+ }
+
+ @Test
+ void testPop() {
+ TestNode node = new TestNode("popped");
+ deque.push(node);
+
+ assertSame(node, deque.pop());
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testPopOnEmpty() {
+ assertThrows(NoSuchElementException.class, () -> deque.pop());
+ }
+
+ @Test
+ void testIterator() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ List<String> values = new ArrayList<>();
+ for (TestNode node : deque) {
+ values.add(node.value);
+ }
+
+ assertEquals(Arrays.asList("a", "b", "c"), values);
+ }
+
+ @Test
+ void testIteratorHasNextOnEmpty() {
+ Iterator<TestNode> it = deque.iterator();
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ void testIteratorNextOnEmpty() {
+ Iterator<TestNode> it = deque.iterator();
+ assertThrows(NoSuchElementException.class, it::next);
+ }
+
+ @Test
+ void testIteratorRemoveUnsupported() {
+ TestNode node = new TestNode("test");
+ deque.addFirst(node);
+
+ Iterator<TestNode> it = deque.iterator();
+ it.next();
+
+ assertThrows(UnsupportedOperationException.class, it::remove);
+ }
+
+ @Test
+ void testDescendingIterator() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ List<String> values = new ArrayList<>();
+ Iterator<TestNode> it = deque.descendingIterator();
+ while (it.hasNext()) {
+ values.add(it.next().value);
+ }
+
+ assertEquals(Arrays.asList("c", "b", "a"), values);
+ }
+
+ @Test
+ void testDescendingIteratorOnEmpty() {
+ Iterator<TestNode> it = deque.descendingIterator();
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ void testRemoveFirstOccurrence() {
+ TestNode node = new TestNode("target");
+ deque.addFirst(node);
+
+ assertTrue(deque.removeFirstOccurrence(node));
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testRemoveFirstOccurrenceNotFound() {
+ TestNode node1 = new TestNode("in");
+ TestNode node2 = new TestNode("out");
+ deque.addFirst(node1);
+
+ assertFalse(deque.removeFirstOccurrence(node2));
+ }
+
+ @Test
+ void testRemoveLastOccurrence() {
+ TestNode node = new TestNode("target");
+ deque.addLast(node);
+
+ assertTrue(deque.removeLastOccurrence(node));
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testRemoveAll() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ assertTrue(deque.removeAll(Arrays.asList(a, c)));
+ assertEquals(1, deque.size());
+ assertSame(b, deque.peekFirst());
+ }
+
+ @Test
+ void testRemoveAllEmpty() {
+ assertFalse(deque.removeAll(Arrays.asList()));
+ }
+
+ @Test
+ void testMoveToFront() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ deque.moveToFront(c);
+
+ assertSame(c, deque.peekFirst());
+ assertSame(b, deque.peekLast());
+ }
+
+ @Test
+ void testMoveToFrontAlreadyFirst() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+
+ deque.addLast(a);
+ deque.addLast(b);
+
+ deque.moveToFront(a);
+
+ assertSame(a, deque.peekFirst());
+ assertSame(b, deque.peekLast());
+ }
+
+ @Test
+ void testMoveToBack() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ deque.moveToBack(a);
+
+ assertSame(b, deque.peekFirst());
+ assertSame(a, deque.peekLast());
+ }
+
+ @Test
+ void testMoveToBackAlreadyLast() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+
+ deque.addLast(a);
+ deque.addLast(b);
+
+ deque.moveToBack(b);
+
+ assertSame(a, deque.peekFirst());
+ assertSame(b, deque.peekLast());
+ }
+
+ @Test
+ void testMultipleAddRemove() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ assertEquals(3, deque.size());
+
+ deque.removeFirst();
+ assertEquals(2, deque.size());
+ assertSame(b, deque.peekFirst());
+
+ deque.removeLast();
+ assertEquals(1, deque.size());
+ assertSame(b, deque.peekFirst());
+ assertSame(b, deque.peekLast());
+
+ deque.remove(b);
+ assertTrue(deque.isEmpty());
+ }
+
+ @Test
+ void testAddFirstDuplicateThrows() {
+ TestNode node = new TestNode("dup");
+ deque.addFirst(node);
+
+ assertThrows(IllegalArgumentException.class, () ->
deque.addFirst(node));
+ }
+
+ @Test
+ void testAddLastDuplicateThrows() {
+ TestNode node = new TestNode("dup");
+ deque.addLast(node);
+
+ assertThrows(IllegalArgumentException.class, () ->
deque.addLast(node));
+ }
+
+ @Test
+ void testSingleElement() {
+ TestNode single = new TestNode("single");
+ deque.addFirst(single);
+
+ assertSame(single, deque.peekFirst());
+ assertSame(single, deque.peekLast());
+ assertSame(deque.removeFirst(), deque.peekFirst() == null ? single :
null);
+ }
+
+ @Test
+ void testRemoveMiddleElement() {
+ TestNode a = new TestNode("a");
+ TestNode b = new TestNode("b");
+ TestNode c = new TestNode("c");
+
+ deque.addLast(a);
+ deque.addLast(b);
+ deque.addLast(c);
+
+ assertTrue(deque.remove(b));
+
+ assertEquals(2, deque.size());
+ assertSame(a, deque.peekFirst());
+ assertSame(c, deque.peekLast());
+ }
+}
diff --git
a/src/test/java/org/codehaus/groovy/runtime/StackTraceUtilsJUnit5Test.java
b/src/test/java/org/codehaus/groovy/runtime/StackTraceUtilsJUnit5Test.java
new file mode 100644
index 0000000000..d144339591
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/runtime/StackTraceUtilsJUnit5Test.java
@@ -0,0 +1,225 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.junit.jupiter.api.AfterEach;
+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 StackTraceUtils class.
+ */
+class StackTraceUtilsJUnit5Test {
+
+ @AfterEach
+ void tearDown() {
+ // Clear any custom class tests that may have been added
+ // Note: There's no public method to clear tests, so we rely on test
isolation
+ }
+
+ @Test
+ void testIsApplicationClassWithApplicationClass() {
+ // A class that doesn't start with groovy internal packages
+ assertTrue(StackTraceUtils.isApplicationClass("com.example.MyClass"));
+
assertTrue(StackTraceUtils.isApplicationClass("org.example.SomeClass"));
+ assertTrue(StackTraceUtils.isApplicationClass("MyClass"));
+ }
+
+ @Test
+ void testIsApplicationClassWithGroovyInternalClasses() {
+ // Classes that start with groovy internal packages should not be
application classes
+
assertFalse(StackTraceUtils.isApplicationClass("groovy.lang.GroovyObject"));
+
assertFalse(StackTraceUtils.isApplicationClass("org.codehaus.groovy.runtime.DefaultGroovyMethods"));
+ assertFalse(StackTraceUtils.isApplicationClass("java.lang.String"));
+ assertFalse(StackTraceUtils.isApplicationClass("javax.swing.JFrame"));
+ assertFalse(StackTraceUtils.isApplicationClass("sun.misc.Unsafe"));
+ assertFalse(StackTraceUtils.isApplicationClass("com.sun.proxy.Proxy"));
+
assertFalse(StackTraceUtils.isApplicationClass("org.apache.groovy.util.Something"));
+
assertFalse(StackTraceUtils.isApplicationClass("jdk.internal.misc.Unsafe"));
+ }
+
+ @Test
+ void testAddClassTest() {
+ // Add a custom test that returns true for a specific class
+ StackTraceUtils.addClassTest(new Closure<Boolean>(null) {
+ @Override
+ public Boolean call(Object... args) {
+ String className = (String) args[0];
+ if (className.equals("my.custom.TestClass")) {
+ return true;
+ }
+ return null; // continue with other tests
+ }
+ });
+
+ // The custom class should now be considered an application class
+ assertTrue(StackTraceUtils.isApplicationClass("my.custom.TestClass"));
+ }
+
+ @Test
+ void testAddClassTestReturningFalse() {
+ // Add a custom test that returns false for a specific class
+ StackTraceUtils.addClassTest(new Closure<Boolean>(null) {
+ @Override
+ public Boolean call(Object... args) {
+ String className = (String) args[0];
+ if (className.startsWith("force.exclude.")) {
+ return false;
+ }
+ return null;
+ }
+ });
+
+ // The class should be excluded
+
assertFalse(StackTraceUtils.isApplicationClass("force.exclude.SomeClass"));
+ }
+
+ @Test
+ void testExtractRootCause() {
+ Exception root = new RuntimeException("root cause");
+ Exception middle = new Exception("middle", root);
+ Exception top = new Exception("top", middle);
+
+ Throwable extracted = StackTraceUtils.extractRootCause(top);
+ assertSame(root, extracted);
+ assertEquals("root cause", extracted.getMessage());
+ }
+
+ @Test
+ void testExtractRootCauseWithNoCause() {
+ Exception single = new RuntimeException("single exception");
+ Throwable extracted = StackTraceUtils.extractRootCause(single);
+ assertSame(single, extracted);
+ }
+
+ @Test
+ void testExtractRootCauseWithDeepNesting() {
+ Exception e1 = new RuntimeException("level 1");
+ Exception e2 = new Exception("level 2", e1);
+ Exception e3 = new Exception("level 3", e2);
+ Exception e4 = new Exception("level 4", e3);
+ Exception e5 = new Exception("level 5", e4);
+
+ Throwable extracted = StackTraceUtils.extractRootCause(e5);
+ assertSame(e1, extracted);
+ }
+
+ @Test
+ void testSanitize() {
+ // Create an exception with a stack trace
+ Exception ex = new RuntimeException("test exception");
+
+ // Sanitize it
+ Throwable sanitized = StackTraceUtils.sanitize(ex);
+
+ // Should return the same exception instance
+ assertSame(ex, sanitized);
+
+ // The stack trace should be modified (groovy internal classes removed)
+ // Note: The actual filtering depends on system property
groovy.full.stacktrace
+ assertNotNull(sanitized.getStackTrace());
+ }
+
+ @Test
+ void testSanitizeRootCause() {
+ Exception root = new RuntimeException("root");
+ Exception wrapper = new Exception("wrapper", root);
+
+ Throwable sanitizedRoot = StackTraceUtils.sanitizeRootCause(wrapper);
+
+ // Should return the sanitized root cause
+ assertEquals("root", sanitizedRoot.getMessage());
+ }
+
+ @Test
+ void testDeepSanitize() {
+ Exception root = new RuntimeException("root");
+ Exception middle = new Exception("middle", root);
+ Exception top = new Exception("top", middle);
+
+ Throwable result = StackTraceUtils.deepSanitize(top);
+
+ // Should return the top exception (sanitized)
+ assertSame(top, result);
+ assertNotNull(result.getStackTrace());
+ }
+
+ @Test
+ void testDeepSanitizeWithSingleException() {
+ Exception single = new RuntimeException("single");
+
+ Throwable result = StackTraceUtils.deepSanitize(single);
+
+ assertSame(single, result);
+ }
+
+ @Test
+ void testPrintSanitizedStackTraceWithPrintWriter() {
+ Exception ex = new RuntimeException("test");
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ StackTraceUtils.printSanitizedStackTrace(ex, pw);
+ pw.flush();
+
+ String output = sw.toString();
+ // Should contain "at" entries for the stack trace
+ assertTrue(output.contains("at ") || output.isEmpty());
+ }
+
+ @Test
+ void testStackLogName() {
+ assertEquals("StackTrace", StackTraceUtils.STACK_LOG_NAME);
+ }
+
+ @Test
+ void testIsApplicationClassWithGjdkClasses() {
+
assertFalse(StackTraceUtils.isApplicationClass("gjdk.groovy.lang.SomeThing"));
+ }
+
+ @Test
+ void testIsApplicationClassWithGroovyJarJarClasses() {
+
assertFalse(StackTraceUtils.isApplicationClass("groovyjarjar.asm.ClassVisitor"));
+ }
+
+ @Test
+ void testSanitizePreservesMessage() {
+ String message = "Original error message";
+ Exception ex = new RuntimeException(message);
+
+ Throwable sanitized = StackTraceUtils.sanitize(ex);
+
+ assertEquals(message, sanitized.getMessage());
+ }
+
+ @Test
+ void testSanitizePreservesCause() {
+ Exception cause = new IllegalArgumentException("cause");
+ Exception ex = new RuntimeException("wrapper", cause);
+
+ Throwable sanitized = StackTraceUtils.sanitize(ex);
+
+ assertSame(cause, sanitized.getCause());
+ }
+}
diff --git
a/src/test/java/org/codehaus/groovy/util/HashCodeHelperJUnit5Test.java
b/src/test/java/org/codehaus/groovy/util/HashCodeHelperJUnit5Test.java
new file mode 100644
index 0000000000..5e66e91f7a
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/util/HashCodeHelperJUnit5Test.java
@@ -0,0 +1,393 @@
+/*
+ * 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.*;
+
+/**
+ * JUnit 5 tests for HashCodeHelper class.
+ */
+class HashCodeHelperJUnit5Test {
+
+ @Test
+ void testInitHash() {
+ int hash = HashCodeHelper.initHash();
+ // The initial hash should be the SEED value (127)
+ assertEquals(127, hash);
+ }
+
+ @Test
+ void testUpdateHashWithBoolean() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashTrue = HashCodeHelper.updateHash(hash, true);
+ int hashFalse = HashCodeHelper.updateHash(hash, false);
+
+ // true and false should produce different hashes
+ assertNotEquals(hashTrue, hashFalse);
+
+ // Same value should produce same hash
+ assertEquals(hashTrue, HashCodeHelper.updateHash(hash, true));
+ assertEquals(hashFalse, HashCodeHelper.updateHash(hash, false));
+ }
+
+ @Test
+ void testUpdateHashWithChar() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashA = HashCodeHelper.updateHash(hash, 'a');
+ int hashB = HashCodeHelper.updateHash(hash, 'b');
+
+ assertNotEquals(hashA, hashB);
+ assertEquals(hashA, HashCodeHelper.updateHash(hash, 'a'));
+ }
+
+ @Test
+ void testUpdateHashWithCharacter() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashChar = HashCodeHelper.updateHash(hash, Character.valueOf('x'));
+ int hashNull = HashCodeHelper.updateHash(hash, (Character) null);
+
+ assertNotEquals(hashChar, hashNull);
+ // Null should be treated as 0
+ assertEquals(HashCodeHelper.updateHash(hash, (char) 0), hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithInt() {
+ int hash = HashCodeHelper.initHash();
+
+ int hash1 = HashCodeHelper.updateHash(hash, 42);
+ int hash2 = HashCodeHelper.updateHash(hash, 43);
+
+ assertNotEquals(hash1, hash2);
+ assertEquals(hash1, HashCodeHelper.updateHash(hash, 42));
+ }
+
+ @Test
+ void testUpdateHashWithInteger() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashInt = HashCodeHelper.updateHash(hash, Integer.valueOf(100));
+ int hashNull = HashCodeHelper.updateHash(hash, (Integer) null);
+
+ assertNotEquals(hashInt, hashNull);
+ assertEquals(HashCodeHelper.updateHash(hash, 0), hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithLong() {
+ int hash = HashCodeHelper.initHash();
+
+ int hash1 = HashCodeHelper.updateHash(hash, 123456789L);
+ int hash2 = HashCodeHelper.updateHash(hash, 987654321L);
+
+ assertNotEquals(hash1, hash2);
+ assertEquals(hash1, HashCodeHelper.updateHash(hash, 123456789L));
+ }
+
+ @Test
+ void testUpdateHashWithLongObject() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashLong = HashCodeHelper.updateHash(hash, Long.valueOf(999L));
+ int hashNull = HashCodeHelper.updateHash(hash, (Long) null);
+
+ assertNotEquals(hashLong, hashNull);
+ assertEquals(HashCodeHelper.updateHash(hash, 0L), hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithFloat() {
+ int hash = HashCodeHelper.initHash();
+
+ int hash1 = HashCodeHelper.updateHash(hash, 3.14f);
+ int hash2 = HashCodeHelper.updateHash(hash, 2.71f);
+
+ assertNotEquals(hash1, hash2);
+ assertEquals(hash1, HashCodeHelper.updateHash(hash, 3.14f));
+ }
+
+ @Test
+ void testUpdateHashWithFloatObject() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashFloat = HashCodeHelper.updateHash(hash, Float.valueOf(1.5f));
+ int hashNull = HashCodeHelper.updateHash(hash, (Float) null);
+
+ assertNotEquals(hashFloat, hashNull);
+ assertEquals(HashCodeHelper.updateHash(hash, 0f), hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithDouble() {
+ int hash = HashCodeHelper.initHash();
+
+ int hash1 = HashCodeHelper.updateHash(hash, 3.14159265);
+ int hash2 = HashCodeHelper.updateHash(hash, 2.71828182);
+
+ assertNotEquals(hash1, hash2);
+ assertEquals(hash1, HashCodeHelper.updateHash(hash, 3.14159265));
+ }
+
+ @Test
+ void testUpdateHashWithDoubleObject() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashDouble = HashCodeHelper.updateHash(hash,
Double.valueOf(99.99));
+ int hashNull = HashCodeHelper.updateHash(hash, (Double) null);
+
+ assertNotEquals(hashDouble, hashNull);
+ assertEquals(HashCodeHelper.updateHash(hash, 0d), hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithObject() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashStr1 = HashCodeHelper.updateHash(hash, "hello");
+ int hashStr2 = HashCodeHelper.updateHash(hash, "world");
+ int hashNull = HashCodeHelper.updateHash(hash, (Object) null);
+
+ assertNotEquals(hashStr1, hashStr2);
+ assertNotEquals(hashStr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithObjectArray() {
+ int hash = HashCodeHelper.initHash();
+
+ Object[] arr1 = {"a", "b", "c"};
+ Object[] arr2 = {"x", "y", "z"};
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, (Object) arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, (Object) arr2);
+
+ assertNotEquals(hashArr1, hashArr2);
+ }
+
+ @Test
+ void testUpdateHashWithBooleanArray() {
+ int hash = HashCodeHelper.initHash();
+
+ boolean[] arr1 = {true, false, true};
+ boolean[] arr2 = {false, true, false};
+ boolean[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithCharArray() {
+ int hash = HashCodeHelper.initHash();
+
+ char[] arr1 = {'a', 'b', 'c'};
+ char[] arr2 = {'x', 'y', 'z'};
+ char[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithByteArray() {
+ int hash = HashCodeHelper.initHash();
+
+ byte[] arr1 = {1, 2, 3};
+ byte[] arr2 = {4, 5, 6};
+ byte[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithShortArray() {
+ int hash = HashCodeHelper.initHash();
+
+ short[] arr1 = {100, 200, 300};
+ short[] arr2 = {400, 500, 600};
+ short[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithIntArray() {
+ int hash = HashCodeHelper.initHash();
+
+ int[] arr1 = {1, 2, 3};
+ int[] arr2 = {4, 5, 6};
+ int[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithLongArray() {
+ int hash = HashCodeHelper.initHash();
+
+ long[] arr1 = {1L, 2L, 3L};
+ long[] arr2 = {4L, 5L, 6L};
+ long[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithFloatArray() {
+ int hash = HashCodeHelper.initHash();
+
+ float[] arr1 = {1.0f, 2.0f, 3.0f};
+ float[] arr2 = {4.0f, 5.0f, 6.0f};
+ float[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testUpdateHashWithDoubleArray() {
+ int hash = HashCodeHelper.initHash();
+
+ double[] arr1 = {1.0, 2.0, 3.0};
+ double[] arr2 = {4.0, 5.0, 6.0};
+ double[] nullArr = null;
+
+ int hashArr1 = HashCodeHelper.updateHash(hash, arr1);
+ int hashArr2 = HashCodeHelper.updateHash(hash, arr2);
+ int hashNull = HashCodeHelper.updateHash(hash, nullArr);
+
+ assertNotEquals(hashArr1, hashArr2);
+ assertNotEquals(hashArr1, hashNull);
+ }
+
+ @Test
+ void testChainingHashUpdates() {
+ int hash = HashCodeHelper.initHash();
+ hash = HashCodeHelper.updateHash(hash, 42);
+ hash = HashCodeHelper.updateHash(hash, "test");
+ hash = HashCodeHelper.updateHash(hash, true);
+ hash = HashCodeHelper.updateHash(hash, 3.14);
+
+ // Verify we get a valid hash value
+ assertNotEquals(0, hash);
+
+ // Same sequence should produce same result
+ int hash2 = HashCodeHelper.initHash();
+ hash2 = HashCodeHelper.updateHash(hash2, 42);
+ hash2 = HashCodeHelper.updateHash(hash2, "test");
+ hash2 = HashCodeHelper.updateHash(hash2, true);
+ hash2 = HashCodeHelper.updateHash(hash2, 3.14);
+
+ assertEquals(hash, hash2);
+ }
+
+ @Test
+ void testOrderMatters() {
+ int hash1 = HashCodeHelper.initHash();
+ hash1 = HashCodeHelper.updateHash(hash1, 1);
+ hash1 = HashCodeHelper.updateHash(hash1, 2);
+
+ int hash2 = HashCodeHelper.initHash();
+ hash2 = HashCodeHelper.updateHash(hash2, 2);
+ hash2 = HashCodeHelper.updateHash(hash2, 1);
+
+ // Order should matter
+ assertNotEquals(hash1, hash2);
+ }
+
+ @Test
+ void testEmptyArrays() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashEmptyInt = HashCodeHelper.updateHash(hash, new int[0]);
+ int hashEmptyLong = HashCodeHelper.updateHash(hash, new long[0]);
+
+ // Empty arrays of same type should produce same hash
+ assertEquals(hashEmptyInt, HashCodeHelper.updateHash(hash, new
int[0]));
+
+ // Note: Empty arrays may produce the same hash code due to
Arrays.hashCode() behavior
+ // Both int[0] and long[0] have hashCode of 1
+ assertEquals(hashEmptyInt, hashEmptyLong);
+ }
+
+ @Test
+ void testSpecialDoubleValues() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashNaN = HashCodeHelper.updateHash(hash, Double.NaN);
+ int hashPosInf = HashCodeHelper.updateHash(hash,
Double.POSITIVE_INFINITY);
+ int hashNegInf = HashCodeHelper.updateHash(hash,
Double.NEGATIVE_INFINITY);
+
+ // All should be different
+ assertNotEquals(hashNaN, hashPosInf);
+ assertNotEquals(hashNaN, hashNegInf);
+ assertNotEquals(hashPosInf, hashNegInf);
+ }
+
+ @Test
+ void testSpecialFloatValues() {
+ int hash = HashCodeHelper.initHash();
+
+ int hashNaN = HashCodeHelper.updateHash(hash, Float.NaN);
+ int hashPosInf = HashCodeHelper.updateHash(hash,
Float.POSITIVE_INFINITY);
+ int hashNegInf = HashCodeHelper.updateHash(hash,
Float.NEGATIVE_INFINITY);
+
+ // All should be different
+ assertNotEquals(hashNaN, hashPosInf);
+ assertNotEquals(hashNaN, hashNegInf);
+ assertNotEquals(hashPosInf, hashNegInf);
+ }
+}
diff --git
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ChrTest.java
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ChrTest.java
new file mode 100644
index 0000000000..997be23c76
--- /dev/null
+++
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ChrTest.java
@@ -0,0 +1,347 @@
+/*
+ * 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 Chr class (char array utilities).
+ */
+class ChrTest {
+
+ @Test
+ void testArray() {
+ char[] result = Chr.array('a', 'b', 'c');
+ assertArrayEquals(new char[]{'a', 'b', 'c'}, result);
+ }
+
+ @Test
+ void testArrayEmpty() {
+ char[] result = Chr.array();
+ assertEquals(0, result.length);
+ }
+
+ @Test
+ void testArraySingleChar() {
+ char[] result = Chr.array('x');
+ assertArrayEquals(new char[]{'x'}, result);
+ }
+
+ @Test
+ void testChars() {
+ char[] result = Chr.chars("hello");
+ assertArrayEquals(new char[]{'h', 'e', 'l', 'l', 'o'}, result);
+ }
+
+ @Test
+ void testCharsEmpty() {
+ char[] result = Chr.chars("");
+ assertEquals(0, result.length);
+ }
+
+ @Test
+ void testInCharFound() {
+ char[] array = {'a', 'b', 'c', 'd'};
+ assertTrue(Chr.in('b', array));
+ assertTrue(Chr.in('a', array));
+ assertTrue(Chr.in('d', array));
+ }
+
+ @Test
+ void testInCharNotFound() {
+ char[] array = {'a', 'b', 'c'};
+ assertFalse(Chr.in('x', array));
+ assertFalse(Chr.in('z', array));
+ }
+
+ @Test
+ void testInIntFound() {
+ char[] array = {'a', 'b', 'c'};
+ assertTrue(Chr.in((int) 'a', array));
+ assertTrue(Chr.in((int) 'b', array));
+ }
+
+ @Test
+ void testInIntNotFound() {
+ char[] array = {'a', 'b', 'c'};
+ assertFalse(Chr.in((int) 'x', array));
+ }
+
+ @Test
+ void testInWithOffset() {
+ char[] array = {'a', 'b', 'c', 'd', 'e'};
+ // Search from offset 2 (starting at 'c')
+ assertTrue(Chr.in('c', 2, array));
+ assertTrue(Chr.in('d', 2, array));
+ assertTrue(Chr.in('e', 2, array));
+ assertFalse(Chr.in('a', 2, array));
+ assertFalse(Chr.in('b', 2, array));
+ }
+
+ @Test
+ void testInWithOffsetAndEnd() {
+ char[] array = {'a', 'b', 'c', 'd', 'e'};
+ // Search from offset 1 to 3 (searching 'b', 'c')
+ assertTrue(Chr.in('b', 1, 3, array));
+ assertTrue(Chr.in('c', 1, 3, array));
+ assertFalse(Chr.in('a', 1, 3, array));
+ assertFalse(Chr.in('d', 1, 3, array));
+ }
+
+ @Test
+ void testGrowWithSize() {
+ char[] array = {'a', 'b', 'c'};
+ char[] grown = Chr.grow(array, 5);
+
+ assertEquals(8, grown.length);
+ assertEquals('a', grown[0]);
+ assertEquals('b', grown[1]);
+ assertEquals('c', grown[2]);
+ }
+
+ @Test
+ void testGrowDouble() {
+ char[] array = {'a', 'b', 'c'};
+ char[] grown = Chr.grow(array);
+
+ assertEquals(6, grown.length);
+ assertEquals('a', grown[0]);
+ assertEquals('b', grown[1]);
+ assertEquals('c', grown[2]);
+ }
+
+ @Test
+ void testCopy() {
+ char[] array = {'h', 'e', 'l', 'l', 'o'};
+ char[] copy = Chr.copy(array);
+
+ assertArrayEquals(array, copy);
+ assertNotSame(array, copy);
+ }
+
+ @Test
+ void testCopyWithOffsetAndLength() {
+ char[] array = {'h', 'e', 'l', 'l', 'o'};
+ char[] copy = Chr.copy(array, 1, 3);
+
+ assertArrayEquals(new char[]{'e', 'l', 'l'}, copy);
+ }
+
+ @Test
+ void testAddChar() {
+ char[] array = {'a', 'b'};
+ char[] result = Chr.add(array, 'c');
+
+ assertArrayEquals(new char[]{'a', 'b', 'c'}, result);
+ }
+
+ @Test
+ void testAddString() {
+ char[] array = {'h', 'i'};
+ char[] result = Chr.add(array, " there");
+
+ assertArrayEquals(new char[]{'h', 'i', ' ', 't', 'h', 'e', 'r', 'e'},
result);
+ }
+
+ @Test
+ void testAddStringBuilder() {
+ char[] array = {'a', 'b'};
+ StringBuilder sb = new StringBuilder("cd");
+ char[] result = Chr.add(array, sb);
+
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd'}, result);
+ }
+
+ @Test
+ void testAddTwoArrays() {
+ char[] array1 = {'a', 'b'};
+ char[] array2 = {'c', 'd', 'e'};
+ char[] result = Chr.add(array1, array2);
+
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd', 'e'}, result);
+ }
+
+ @Test
+ void testAddMultipleArrays() {
+ char[] arr1 = {'a', 'b'};
+ char[] arr2 = {'c'};
+ char[] arr3 = {'d', 'e', 'f'};
+
+ char[] result = Chr.add(arr1, arr2, arr3);
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd', 'e', 'f'}, result);
+ }
+
+ @Test
+ void testAddMultipleArraysWithNull() {
+ char[] arr1 = {'a', 'b'};
+ char[] arr2 = null;
+ char[] arr3 = {'c', 'd'};
+
+ char[] result = Chr.add(arr1, arr2, arr3);
+ assertArrayEquals(new char[]{'a', 'b', 'c', 'd'}, result);
+ }
+
+ @Test
+ void testLpad() {
+ char[] input = {'1', '2', '3'};
+ char[] result = Chr.lpad(input, 6, '0');
+
+ assertArrayEquals(new char[]{'0', '0', '0', '1', '2', '3'}, result);
+ }
+
+ @Test
+ void testLpadNoChange() {
+ char[] input = {'1', '2', '3'};
+ char[] result = Chr.lpad(input, 3, '0');
+
+ assertSame(input, result);
+ }
+
+ @Test
+ void testLpadSmallerSize() {
+ char[] input = {'1', '2', '3'};
+ char[] result = Chr.lpad(input, 2, '0');
+
+ assertSame(input, result);
+ }
+
+ @Test
+ void testContains() {
+ char[] chars = {'a', 'b', 'c', 'd', 'e'};
+
+ assertTrue(Chr.contains(chars, 'b', 0, 5));
+ assertTrue(Chr.contains(chars, 'a', 0, 5));
+ assertTrue(Chr.contains(chars, 'e', 0, 5));
+ assertFalse(Chr.contains(chars, 'x', 0, 5));
+ }
+
+ @Test
+ void testContainsWithStartAndLength() {
+ char[] chars = {'a', 'b', 'c', 'd', 'e'};
+
+ // Only search within 'b', 'c', 'd' (start=1, length=3)
+ assertTrue(Chr.contains(chars, 'b', 1, 3));
+ assertTrue(Chr.contains(chars, 'c', 1, 3));
+ assertTrue(Chr.contains(chars, 'd', 1, 3));
+ assertFalse(Chr.contains(chars, 'a', 1, 3));
+ assertFalse(Chr.contains(chars, 'e', 1, 3));
+ }
+
+ @Test
+ void testIdxWithByteArray() {
+ char[] buffer = new char[10];
+ byte[] bytes = {65, 66, 67}; // A, B, C
+
+ Chr._idx(buffer, 2, bytes);
+
+ assertEquals('A', buffer[2]);
+ assertEquals('B', buffer[3]);
+ assertEquals('C', buffer[4]);
+ }
+
+ @Test
+ void testIdxWithCharArray() {
+ char[] buffer = new char[10];
+ char[] input = {'x', 'y', 'z'};
+
+ Chr._idx(buffer, 3, input);
+
+ assertEquals('x', buffer[3]);
+ assertEquals('y', buffer[4]);
+ assertEquals('z', buffer[5]);
+ }
+
+ @Test
+ void testIdxWithCharArrayAndLength() {
+ char[] buffer = new char[10];
+ char[] input = {'x', 'y', 'z', 'w'};
+
+ Chr._idx(buffer, 1, input, 2);
+
+ assertEquals('x', buffer[1]);
+ assertEquals('y', buffer[2]);
+ assertEquals(0, buffer[3]); // Not written
+ }
+
+ @Test
+ void testIdxWithByteArrayRange() {
+ char[] buffer = new char[10];
+ byte[] bytes = {65, 66, 67, 68, 69}; // A, B, C, D, E
+
+ // Copy bytes[1] to bytes[3] (B, C, D) starting at buffer[0]
+ Chr._idx(buffer, 0, bytes, 1, 4);
+
+ assertEquals('B', buffer[0]);
+ assertEquals('C', buffer[1]);
+ assertEquals('D', buffer[2]);
+ }
+
+ @Test
+ void testInEmptyArray() {
+ char[] array = {};
+ assertFalse(Chr.in('a', array));
+ }
+
+ @Test
+ void testGrowEmptyArray() {
+ char[] array = {};
+ char[] grown = Chr.grow(array, 5);
+ assertEquals(5, grown.length);
+ }
+
+ @Test
+ void testCopyEmptyArray() {
+ char[] array = {};
+ char[] copy = Chr.copy(array);
+ assertEquals(0, copy.length);
+ }
+
+ @Test
+ void testAddToEmptyArray() {
+ char[] array = {};
+ char[] result = Chr.add(array, 'a');
+ assertArrayEquals(new char[]{'a'}, result);
+ }
+
+ @Test
+ void testAddEmptyString() {
+ char[] array = {'a', 'b'};
+ char[] result = Chr.add(array, "");
+ assertArrayEquals(new char[]{'a', 'b'}, result);
+ }
+
+ @Test
+ void testCharsUnicode() {
+ char[] result = Chr.chars("日本語");
+ assertEquals(3, result.length);
+ assertEquals('日', result[0]);
+ assertEquals('本', result[1]);
+ assertEquals('語', result[2]);
+ }
+
+ @Test
+ void testAddEmptyArrays() {
+ char[] arr1 = {};
+ char[] arr2 = {};
+ char[] result = Chr.add(arr1, arr2);
+ assertEquals(0, result.length);
+ }
+}
diff --git
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ExceptionsTest.java
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ExceptionsTest.java
new file mode 100644
index 0000000000..b21fbe482d
--- /dev/null
+++
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ExceptionsTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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 groovy.json.JsonException;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for Exceptions class (JSON internal exception utilities).
+ */
+class ExceptionsTest {
+
+ @Test
+ void testDieNoMessage() {
+ assertThrows(Exceptions.JsonInternalException.class, () -> {
+ Exceptions.die();
+ });
+ }
+
+ @Test
+ void testDieWithMessage() {
+ Exceptions.JsonInternalException ex = assertThrows(
+ Exceptions.JsonInternalException.class,
+ () -> Exceptions.die("custom message")
+ );
+ assertTrue(ex.getMessage().contains("custom message"));
+ }
+
+ @Test
+ void testDieWithClassAndMessage() {
+ assertThrows(Exceptions.JsonInternalException.class, () -> {
+ Exceptions.die(String.class, "test error");
+ });
+ }
+
+ @Test
+ void testHandle() {
+ Exception original = new RuntimeException("original");
+
+ Exceptions.JsonInternalException ex = assertThrows(
+ Exceptions.JsonInternalException.class,
+ () -> Exceptions.handle(original)
+ );
+
+ assertSame(original, ex.getCause());
+ }
+
+ @Test
+ void testHandleWithClass() {
+ Exception original = new IllegalArgumentException("arg error");
+
+ Exceptions.JsonInternalException ex = assertThrows(
+ Exceptions.JsonInternalException.class,
+ () -> Exceptions.handle(String.class, original)
+ );
+
+ assertSame(original, ex.getCause());
+ }
+
+ @Test
+ void testHandleWithClassAlreadyJsonInternalException() {
+ Exceptions.JsonInternalException original = new
Exceptions.JsonInternalException("already wrapped");
+
+ Exceptions.JsonInternalException ex = assertThrows(
+ Exceptions.JsonInternalException.class,
+ () -> Exceptions.handle(String.class, original)
+ );
+
+ assertSame(original, ex);
+ }
+
+ @Test
+ void testHandleWithClassMessageAndThrowable() {
+ Throwable original = new RuntimeException("cause");
+
+ Exceptions.JsonInternalException ex = assertThrows(
+ Exceptions.JsonInternalException.class,
+ () -> Exceptions.handle(String.class, "wrapper message", original)
+ );
+
+ assertTrue(ex.getMessage().contains("wrapper message"));
+ assertSame(original, ex.getCause());
+ }
+
+ @Test
+ void testHandleWithMessageAndThrowable() {
+ Throwable original = new RuntimeException("cause");
+
+ Exceptions.JsonInternalException ex = assertThrows(
+ Exceptions.JsonInternalException.class,
+ () -> Exceptions.handle("error occurred", original)
+ );
+
+ assertTrue(ex.getMessage().contains("error occurred"));
+ assertSame(original, ex.getCause());
+ }
+
+ @Test
+ void testJsonInternalExceptionMessage() {
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("test message");
+ assertEquals("test message", ex.getMessage());
+ }
+
+ @Test
+ void testJsonInternalExceptionWithCause() {
+ RuntimeException cause = new RuntimeException("cause message");
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("wrapper", cause);
+
+ assertTrue(ex.getMessage().contains("wrapper"));
+ assertTrue(ex.getMessage().contains("cause message"));
+ assertSame(cause, ex.getCause());
+ }
+
+ @Test
+ void testJsonInternalExceptionWrappingCause() {
+ RuntimeException cause = new RuntimeException("original error");
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException(cause);
+
+ assertEquals("Wrapped Exception",
ex.getMessage().split("\n")[0].trim());
+ assertSame(cause, ex.getCause());
+ }
+
+ @Test
+ void testJsonInternalExceptionGetLocalizedMessage() {
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("localized test");
+ assertEquals(ex.getMessage(), ex.getLocalizedMessage());
+ }
+
+ @Test
+ void testJsonInternalExceptionGetStackTraceWithCause() {
+ RuntimeException cause = new RuntimeException("cause");
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException(cause);
+
+ // Should return the cause's stack trace elements (equivalent content)
+ StackTraceElement[] causeTrace = cause.getStackTrace();
+ StackTraceElement[] exTrace = ex.getStackTrace();
+
+ assertEquals(causeTrace.length, exTrace.length);
+ for (int i = 0; i < causeTrace.length; i++) {
+ assertEquals(causeTrace[i], exTrace[i]);
+ }
+ }
+
+ @Test
+ void testJsonInternalExceptionGetStackTraceWithoutCause() {
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("no cause");
+
+ // Should return its own stack trace
+ assertNotNull(ex.getStackTrace());
+ assertTrue(ex.getStackTrace().length > 0);
+ }
+
+ @Test
+ void testJsonInternalExceptionGetCause() {
+ RuntimeException cause = new RuntimeException("the cause");
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException(cause);
+
+ assertSame(cause, ex.getCause());
+ }
+
+ @Test
+ void testJsonInternalExceptionPrintStackTracePrintStream() {
+ RuntimeException cause = new RuntimeException("cause error");
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException(cause);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos);
+
+ ex.printStackTrace(ps);
+ ps.flush();
+
+ String output = baos.toString();
+ assertTrue(output.contains("Wrapped Exception"));
+ assertTrue(output.contains("original exception"));
+ }
+
+ @Test
+ void testJsonInternalExceptionPrintStackTracePrintStreamNoCause() {
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("direct error");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos);
+
+ ex.printStackTrace(ps);
+ ps.flush();
+
+ String output = baos.toString();
+ assertTrue(output.contains("direct error"));
+ }
+
+ @Test
+ void testJsonInternalExceptionPrintStackTracePrintWriter() {
+ RuntimeException cause = new RuntimeException("writer cause");
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException(cause);
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ ex.printStackTrace(pw);
+ pw.flush();
+
+ String output = sw.toString();
+ assertTrue(output.contains("Wrapped Exception"));
+ }
+
+ @Test
+ void testJsonInternalExceptionPrintStackTracePrintWriterNoCause() {
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("writer direct");
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ ex.printStackTrace(pw);
+ pw.flush();
+
+ String output = sw.toString();
+ assertTrue(output.contains("writer direct"));
+ }
+
+ @Test
+ void testJsonInternalExceptionIsJsonException() {
+ Exceptions.JsonInternalException ex = new
Exceptions.JsonInternalException("test");
+ assertTrue(ex instanceof JsonException);
+ }
+
+ @Test
+ void testToString() {
+ Exception ex = new RuntimeException("test error for toString");
+ String result = Exceptions.toString(ex);
+
+ assertNotNull(result);
+ assertTrue(result.contains("test error for toString"));
+ }
+
+ @Test
+ void testSputsWithBuffer() {
+ CharBuf buf = CharBuf.create(100);
+ String result = Exceptions.sputs(buf, "hello", "world", 42);
+
+ assertTrue(result.contains("hello"));
+ assertTrue(result.contains("world"));
+ assertTrue(result.contains("42"));
+ }
+
+ @Test
+ void testSputsWithoutBuffer() {
+ String result = Exceptions.sputs("one", "two", "three");
+
+ assertTrue(result.contains("one"));
+ assertTrue(result.contains("two"));
+ assertTrue(result.contains("three"));
+ }
+
+ @Test
+ void testSputsWithNullValue() {
+ String result = Exceptions.sputs("value", null, "other");
+
+ assertTrue(result.contains("value"));
+ assertTrue(result.contains("<NULL>"));
+ assertTrue(result.contains("other"));
+ }
+
+ @Test
+ void testSputsWithArray() {
+ Object[] arr = {"a", "b"};
+ String result = Exceptions.sputs("prefix", arr);
+
+ assertTrue(result.contains("prefix"));
+ // Arrays are printed using Collections.singletonList().toString()
+ assertNotNull(result);
+ }
+
+ @Test
+ void testSputsEmpty() {
+ String result = Exceptions.sputs();
+ assertNotNull(result);
+ assertTrue(result.endsWith("\n"));
+ }
+
+ @Test
+ void testSputsSingleValue() {
+ String result = Exceptions.sputs("single");
+ assertTrue(result.contains("single"));
+ }
+
+ @Test
+ void testToStringWithNestedCause() {
+ RuntimeException cause = new RuntimeException("root cause");
+ Exception wrapper = new Exception("wrapper", cause);
+
+ String result = Exceptions.toString(wrapper);
+ assertNotNull(result);
+ assertTrue(result.contains("wrapper"));
+ }
+}
diff --git
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ValueMapImplTest.java
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ValueMapImplTest.java
new file mode 100644
index 0000000000..b6e8e92be8
--- /dev/null
+++
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/ValueMapImplTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for ValueMapImpl class.
+ */
+class ValueMapImplTest {
+
+ private ValueMapImpl valueMap;
+
+ @BeforeEach
+ void setUp() {
+ valueMap = new ValueMapImpl();
+ }
+
+ // Simple Value implementation for testing
+ private static Value createStringValue(final String str) {
+ return new Value() {
+ @Override
+ public byte byteValue() { return 0; }
+ @Override
+ public short shortValue() { return 0; }
+ @Override
+ public int intValue() { return 0; }
+ @Override
+ public long longValue() { return 0; }
+ @Override
+ public BigDecimal bigDecimalValue() { return null; }
+ @Override
+ public BigInteger bigIntegerValue() { return null; }
+ @Override
+ public float floatValue() { return 0; }
+ @Override
+ public double doubleValue() { return 0; }
+ @Override
+ public boolean booleanValue() { return false; }
+ @Override
+ public Date dateValue() { return null; }
+ @Override
+ public String stringValue() { return str; }
+ @Override
+ public String stringValueEncoded() { return str; }
+ @Override
+ public Object toValue() { return str; }
+ @Override
+ public <T extends Enum> T toEnum(Class<T> cls) { return null; }
+ @Override
+ public boolean isContainer() { return false; }
+ @Override
+ public void chop() { }
+ @Override
+ public char charValue() { return str != null && str.length() > 0 ?
str.charAt(0) : 0; }
+ @Override
+ public String toString() { return str; }
+ };
+ }
+
+ private MapItemValue createEntry(String key, String value) {
+ return new MapItemValue(createStringValue(key),
createStringValue(value));
+ }
+
+ @Test
+ void testInitialState() {
+ assertEquals(0, valueMap.len());
+ assertFalse(valueMap.hydrated());
+ }
+
+ @Test
+ void testAdd() {
+ MapItemValue entry = createEntry("key1", "value1");
+ valueMap.add(entry);
+
+ assertEquals(1, valueMap.len());
+ }
+
+ @Test
+ void testAddMultiple() {
+ valueMap.add(createEntry("key1", "value1"));
+ valueMap.add(createEntry("key2", "value2"));
+ valueMap.add(createEntry("key3", "value3"));
+
+ assertEquals(3, valueMap.len());
+ }
+
+ @Test
+ void testItems() {
+ MapItemValue entry1 = createEntry("a", "1");
+ MapItemValue entry2 = createEntry("b", "2");
+
+ valueMap.add(entry1);
+ valueMap.add(entry2);
+
+ Map.Entry<String, Value>[] items = valueMap.items();
+ assertNotNull(items);
+ assertEquals(entry1, items[0]);
+ assertEquals(entry2, items[1]);
+ }
+
+ @Test
+ void testGetBeforeHydration() {
+ MapItemValue entry = createEntry("mykey", "myvalue");
+ valueMap.add(entry);
+
+ Value result = valueMap.get("mykey");
+ assertNotNull(result);
+ assertEquals("myvalue", result.toString());
+ }
+
+ @Test
+ void testGetNotFound() {
+ valueMap.add(createEntry("key1", "value1"));
+
+ Value result = valueMap.get("nonexistent");
+ assertNull(result);
+ }
+
+ @Test
+ void testHydratedAfterEntrySet() {
+ valueMap.add(createEntry("key1", "value1"));
+ assertFalse(valueMap.hydrated());
+
+ valueMap.entrySet();
+ assertTrue(valueMap.hydrated());
+ }
+
+ @Test
+ void testEntrySet() {
+ valueMap.add(createEntry("a", "1"));
+ valueMap.add(createEntry("b", "2"));
+
+ Set<Map.Entry<String, Value>> entrySet = valueMap.entrySet();
+ assertEquals(2, entrySet.size());
+ }
+
+ @Test
+ void testValues() {
+ valueMap.add(createEntry("key1", "val1"));
+ valueMap.add(createEntry("key2", "val2"));
+
+ Collection<Value> values = valueMap.values();
+ assertEquals(2, values.size());
+ }
+
+ @Test
+ void testSize() {
+ valueMap.add(createEntry("a", "1"));
+ valueMap.add(createEntry("b", "2"));
+ valueMap.add(createEntry("c", "3"));
+
+ assertEquals(3, valueMap.size());
+ }
+
+ @Test
+ void testSizeEmpty() {
+ assertEquals(0, valueMap.size());
+ }
+
+ @Test
+ void testPutThrowsException() {
+ assertThrows(Exceptions.JsonInternalException.class, () -> {
+ valueMap.put("key", createStringValue("value"));
+ });
+ }
+
+ @Test
+ void testGetAfterHydration() {
+ valueMap.add(createEntry("key1", "value1"));
+ valueMap.add(createEntry("key2", "value2"));
+
+ // Force hydration by calling entrySet
+ valueMap.entrySet();
+
+ // Now get should use the internal map
+ Value result = valueMap.get("key1");
+ assertNotNull(result);
+ assertEquals("value1", result.toString());
+ }
+
+ @Test
+ void testAddMoreThanInitialCapacity() {
+ // The initial capacity is 20, test adding more
+ for (int i = 0; i < 25; i++) {
+ valueMap.add(createEntry("key" + i, "value" + i));
+ }
+
+ assertEquals(25, valueMap.len());
+ assertEquals(25, valueMap.size());
+ }
+
+ @Test
+ void testGetMultipleKeys() {
+ valueMap.add(createEntry("first", "1st"));
+ valueMap.add(createEntry("second", "2nd"));
+ valueMap.add(createEntry("third", "3rd"));
+
+ assertEquals("1st", valueMap.get("first").toString());
+ assertEquals("2nd", valueMap.get("second").toString());
+ assertEquals("3rd", valueMap.get("third").toString());
+ }
+
+ @Test
+ void testEntrySetMultipleCalls() {
+ valueMap.add(createEntry("x", "y"));
+
+ Set<Map.Entry<String, Value>> set1 = valueMap.entrySet();
+ Set<Map.Entry<String, Value>> set2 = valueMap.entrySet();
+
+ // Both should return the same map's entry set
+ assertEquals(set1.size(), set2.size());
+ }
+
+ @Test
+ void testValuesContents() {
+ valueMap.add(createEntry("a", "alpha"));
+ valueMap.add(createEntry("b", "beta"));
+
+ Collection<Value> values = valueMap.values();
+
+ boolean foundAlpha = false;
+ boolean foundBeta = false;
+ for (Value v : values) {
+ if ("alpha".equals(v.toString())) foundAlpha = true;
+ if ("beta".equals(v.toString())) foundBeta = true;
+ }
+
+ assertTrue(foundAlpha);
+ assertTrue(foundBeta);
+ }
+
+ @Test
+ void testEmptyEntrySet() {
+ Set<Map.Entry<String, Value>> entrySet = valueMap.entrySet();
+ assertTrue(entrySet.isEmpty());
+ }
+
+ @Test
+ void testEmptyValues() {
+ Collection<Value> values = valueMap.values();
+ assertTrue(values.isEmpty());
+ }
+
+ @Test
+ void testItemsArray() {
+ Map.Entry<String, Value>[] items = valueMap.items();
+ assertNotNull(items);
+ // Initial array has capacity 20
+ assertEquals(20, items.length);
+ }
+}