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 c190dcdf7d Add tests to increase code coverage:
`JsonSlurperJUnit5Test`, `LazyMapJUnit5Test`, etc.
c190dcdf7d is described below
commit c190dcdf7d99e20c60ce3670cfec34a1083eba62
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Feb 1 22:03:10 2026 +0900
Add tests to increase code coverage: `JsonSlurperJUnit5Test`,
`LazyMapJUnit5Test`, etc.
---
.../reflection/ReflectionUtilsJUnit5Test.java | 241 ++++++++
.../groovy/runtime/GStringImplJUnit5Test.java | 485 ++++++++++++++++
.../groovy/runtime/InvokerHelperJUnit5Test.java | 405 +++++++++++++
.../typehandling/ShortTypeHandlingJUnit5Test.java | 198 +++++++
.../extensions/DateTimeExtensionsJUnit5Test.java | 519 +++++++++++++++++
.../extensions/DateUtilExtensionsJUnit5Test.java | 630 +++++++++++++++++++++
.../groovy/json/JsonSlurperClassicJUnit5Test.java | 352 ++++++++++++
.../java/groovy/json/JsonSlurperJUnit5Test.java | 399 +++++++++++++
.../groovy/json/internal/LazyMapJUnit5Test.java | 378 +++++++++++++
9 files changed, 3607 insertions(+)
diff --git
a/src/test/java/org/codehaus/groovy/reflection/ReflectionUtilsJUnit5Test.java
b/src/test/java/org/codehaus/groovy/reflection/ReflectionUtilsJUnit5Test.java
new file mode 100644
index 0000000000..2f5770db42
--- /dev/null
+++
b/src/test/java/org/codehaus/groovy/reflection/ReflectionUtilsJUnit5Test.java
@@ -0,0 +1,241 @@
+/*
+ * 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.reflection;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for ReflectionUtils class.
+ */
+class ReflectionUtilsJUnit5Test {
+
+ @Test
+ void testIsCallingClassReflectionAvailable() {
+ assertTrue(ReflectionUtils.isCallingClassReflectionAvailable());
+ }
+
+ @Test
+ void testGetCallingClass() {
+ Class<?> callingClass = ReflectionUtils.getCallingClass();
+ // The calling class should be this test class
+ assertNotNull(callingClass);
+ }
+
+ @Test
+ void testGetCallingClassWithMatchLevel() {
+ Class<?> callingClass = ReflectionUtils.getCallingClass(1);
+ assertNotNull(callingClass);
+ }
+
+ @Test
+ void testGetCallingClassWithZeroMatchLevel() {
+ Class<?> callingClass = ReflectionUtils.getCallingClass(0);
+ // Match level < 1 is treated as 1
+ assertNotNull(callingClass);
+ }
+
+ @Test
+ void testGetCallingClassWithNegativeMatchLevel() {
+ Class<?> callingClass = ReflectionUtils.getCallingClass(-5);
+ // Negative match level is treated as 0 (which becomes 1)
+ assertNotNull(callingClass);
+ }
+
+ @Test
+ void testGetCallingClassWithExtraIgnoredPackages() {
+ Collection<String> extraIgnored = Set.of("some.package");
+ Class<?> callingClass = ReflectionUtils.getCallingClass(1,
extraIgnored);
+ assertNotNull(callingClass);
+ }
+
+ @Test
+ void testGetCallingClassWithEmptyIgnoredPackages() {
+ Class<?> callingClass = ReflectionUtils.getCallingClass(1,
Collections.emptySet());
+ assertNotNull(callingClass);
+ }
+
+ @Test
+ void testGetDeclaredMethods() {
+ List<Method> methods =
ReflectionUtils.getDeclaredMethods(String.class, "substring", int.class);
+ assertNotNull(methods);
+ assertFalse(methods.isEmpty());
+ }
+
+ @Test
+ void testGetDeclaredMethodsWithTwoParams() {
+ List<Method> methods =
ReflectionUtils.getDeclaredMethods(String.class, "substring", int.class,
int.class);
+ assertNotNull(methods);
+ assertFalse(methods.isEmpty());
+ }
+
+ @Test
+ void testGetDeclaredMethodsNonExistent() {
+ List<Method> methods =
ReflectionUtils.getDeclaredMethods(String.class, "nonExistentMethod");
+ assertNotNull(methods);
+ assertTrue(methods.isEmpty());
+ }
+
+ @Test
+ void testGetMethods() {
+ List<Method> methods = ReflectionUtils.getMethods(String.class,
"length");
+ assertNotNull(methods);
+ assertFalse(methods.isEmpty());
+ }
+
+ @Test
+ void testGetMethodsWithParams() {
+ List<Method> methods = ReflectionUtils.getMethods(String.class,
"charAt", int.class);
+ assertNotNull(methods);
+ assertFalse(methods.isEmpty());
+ }
+
+ @Test
+ void testGetMethodsInherited() {
+ // toString is inherited from Object
+ List<Method> methods = ReflectionUtils.getMethods(String.class,
"toString");
+ assertNotNull(methods);
+ assertFalse(methods.isEmpty());
+ }
+
+ @Test
+ void testParameterTypeMatchesExact() {
+ Class<?>[] paramTypes = {int.class, int.class};
+ Class<?>[] argTypes = {int.class, int.class};
+ assertTrue(ReflectionUtils.parameterTypeMatches(paramTypes, argTypes));
+ }
+
+ @Test
+ void testParameterTypeMatchesDifferentLength() {
+ Class<?>[] paramTypes = {int.class, int.class};
+ Class<?>[] argTypes = {int.class};
+ assertFalse(ReflectionUtils.parameterTypeMatches(paramTypes,
argTypes));
+ }
+
+ @Test
+ void testParameterTypeMatchesWithObject() {
+ // Object.class matches anything
+ Class<?>[] paramTypes = {Object.class};
+ Class<?>[] argTypes = {String.class};
+ assertTrue(ReflectionUtils.parameterTypeMatches(paramTypes, argTypes));
+ }
+
+ @Test
+ void testParameterTypeMatchesWithNull() {
+ Class<?>[] paramTypes = {String.class};
+ Class<?>[] argTypes = {null};
+ assertFalse(ReflectionUtils.parameterTypeMatches(paramTypes,
argTypes));
+ }
+
+ @Test
+ void testParameterTypeMatchesWithAutoboxing() {
+ Class<?>[] paramTypes = {Integer.class};
+ Class<?>[] argTypes = {int.class};
+ assertTrue(ReflectionUtils.parameterTypeMatches(paramTypes, argTypes));
+ }
+
+ @Test
+ void testParameterTypeMatchesWithAssignable() {
+ Class<?>[] paramTypes = {Number.class};
+ Class<?>[] argTypes = {Integer.class};
+ assertTrue(ReflectionUtils.parameterTypeMatches(paramTypes, argTypes));
+ }
+
+ @Test
+ void testParameterTypeMatchesEmpty() {
+ Class<?>[] paramTypes = {};
+ Class<?>[] argTypes = {};
+ assertTrue(ReflectionUtils.parameterTypeMatches(paramTypes, argTypes));
+ }
+
+ @Test
+ void testTrySetAccessible() {
+ try {
+ Method method = String.class.getDeclaredMethod("length");
+ boolean result = ReflectionUtils.trySetAccessible(method);
+ // Should succeed or fail gracefully
+ assertTrue(result || !result); // Just verify it doesn't throw
+ } catch (NoSuchMethodException e) {
+ fail("Method should exist");
+ }
+ }
+
+ @Test
+ void testCheckCanSetAccessible() {
+ try {
+ Method method = String.class.getDeclaredMethod("length");
+ boolean result = ReflectionUtils.checkCanSetAccessible(method,
ReflectionUtilsJUnit5Test.class);
+ // Result depends on module system / security
+ assertTrue(result || !result); // Just verify it doesn't throw
+ } catch (NoSuchMethodException e) {
+ fail("Method should exist");
+ }
+ }
+
+ @Test
+ void testCheckAccessible() {
+ // Public method in public class should be accessible
+ boolean result = ReflectionUtils.checkAccessible(
+ ReflectionUtilsJUnit5Test.class,
+ String.class,
+ java.lang.reflect.Modifier.PUBLIC,
+ false
+ );
+ assertTrue(result);
+ }
+
+ @Test
+ void testCheckAccessiblePrivate() {
+ // Private method without allowIllegalAccess
+ boolean result = ReflectionUtils.checkAccessible(
+ ReflectionUtilsJUnit5Test.class,
+ String.class,
+ java.lang.reflect.Modifier.PRIVATE,
+ false
+ );
+ // Typically false for private methods
+ assertFalse(result);
+ }
+
+ @Test
+ void testGetCallingClassDeepStack() {
+ // Call through a helper to test deeper stack
+ Class<?> result = helperForDeepStack();
+ assertNotNull(result);
+ }
+
+ private Class<?> helperForDeepStack() {
+ return ReflectionUtils.getCallingClass(2);
+ }
+
+ @Test
+ void testGetCallingClassVeryHighMatchLevel() {
+ // Very high match level may return null if stack isn't that deep
+ Class<?> callingClass = ReflectionUtils.getCallingClass(1000);
+ // May be null if stack isn't that deep
+ assertTrue(callingClass == null || callingClass != null);
+ }
+}
diff --git
a/src/test/java/org/codehaus/groovy/runtime/GStringImplJUnit5Test.java
b/src/test/java/org/codehaus/groovy/runtime/GStringImplJUnit5Test.java
new file mode 100644
index 0000000000..c1f2088ad7
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/runtime/GStringImplJUnit5Test.java
@@ -0,0 +1,485 @@
+/*
+ * 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.GString;
+import org.junit.jupiter.api.Test;
+
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for GStringImpl class.
+ */
+class GStringImplJUnit5Test {
+
+ private GStringImpl createGString(String prefix, Object value, String
suffix) {
+ return new GStringImpl(new Object[]{value}, new String[]{prefix,
suffix});
+ }
+
+ private GStringImpl createSimpleGString(String text) {
+ return new GStringImpl(new Object[]{}, new String[]{text});
+ }
+
+ // Basic construction and toString tests
+ @Test
+ void testBasicConstruction() {
+ GStringImpl gs = createGString("Hello ", "World", "!");
+ assertEquals("Hello World!", gs.toString());
+ }
+
+ @Test
+ void testEmptyGString() {
+ GStringImpl gs = createSimpleGString("");
+ assertEquals("", gs.toString());
+ }
+
+ @Test
+ void testGStringWithNullValue() {
+ GStringImpl gs = createGString("Value is: ", null, ".");
+ assertEquals("Value is: null.", gs.toString());
+ }
+
+ @Test
+ void testGStringWithMultipleValues() {
+ GStringImpl gs = new GStringImpl(
+ new Object[]{"John", 30},
+ new String[]{"Name: ", ", Age: ", "."}
+ );
+ assertEquals("Name: John, Age: 30.", gs.toString());
+ }
+
+ // getStrings and getValues tests
+ @Test
+ void testGetStrings() {
+ GStringImpl gs = createGString("A", "B", "C");
+ String[] strings = gs.getStrings();
+ assertArrayEquals(new String[]{"A", "C"}, strings);
+ }
+
+ @Test
+ void testGetValues() {
+ GStringImpl gs = createGString("A", "B", "C");
+ Object[] values = gs.getValues();
+ assertArrayEquals(new Object[]{"B"}, values);
+ }
+
+ // freeze tests
+ @Test
+ void testFreeze() {
+ GStringImpl gs = createGString("Hello ", "World", "!");
+ GString frozen = gs.freeze();
+ assertEquals("Hello World!", frozen.toString());
+ }
+
+ @Test
+ void testFrozenGStringReturnsCopies() {
+ GStringImpl gs = createGString("Hello ", "World", "!");
+ GStringImpl frozen = (GStringImpl) gs.freeze();
+
+ // Frozen GString should return copies
+ String[] strings1 = frozen.getStrings();
+ String[] strings2 = frozen.getStrings();
+ assertNotSame(strings1, strings2);
+ assertArrayEquals(strings1, strings2);
+ }
+
+ // plus tests
+ @Test
+ void testPlus() {
+ GStringImpl gs1 = createGString("Hello ", "World", "");
+ GStringImpl gs2 = createGString("!", "", "");
+ GString result = gs1.plus(gs2);
+ assertEquals("Hello World!", result.toString());
+ }
+
+ // writeTo tests
+ @Test
+ void testWriteTo() throws Exception {
+ GStringImpl gs = createGString("Hello ", "World", "!");
+ StringWriter writer = new StringWriter();
+ gs.writeTo(writer);
+ assertEquals("Hello World!", writer.toString());
+ }
+
+ // String-like methods tests
+ @Test
+ void testTrim() {
+ GStringImpl gs = createGString(" Hello ", "World", " ");
+ assertEquals("Hello World", gs.trim());
+ }
+
+ @Test
+ void testIsEmpty() {
+ GStringImpl empty = createSimpleGString("");
+ GStringImpl nonEmpty = createSimpleGString("hello");
+ assertTrue(empty.isEmpty());
+ assertFalse(nonEmpty.isEmpty());
+ }
+
+ @Test
+ void testIsBlank() {
+ GStringImpl blank = createSimpleGString(" ");
+ GStringImpl nonBlank = createSimpleGString(" hello ");
+ assertTrue(blank.isBlank());
+ assertFalse(nonBlank.isBlank());
+ }
+
+ @Test
+ void testLines() {
+ GStringImpl gs = createSimpleGString("line1\nline2\nline3");
+ var lines = gs.lines().collect(Collectors.toList());
+ assertEquals(3, lines.size());
+ assertEquals("line1", lines.get(0));
+ assertEquals("line2", lines.get(1));
+ assertEquals("line3", lines.get(2));
+ }
+
+ @Test
+ void testRepeat() {
+ GStringImpl gs = createSimpleGString("ab");
+ assertEquals("ababab", gs.repeat(3));
+ }
+
+ @Test
+ void testRepeatZero() {
+ GStringImpl gs = createSimpleGString("ab");
+ assertEquals("", gs.repeat(0));
+ }
+
+ @Test
+ void testStripLeading() {
+ GStringImpl gs = createSimpleGString(" hello");
+ assertEquals("hello", gs.stripLeading());
+ }
+
+ @Test
+ void testStripTrailing() {
+ GStringImpl gs = createSimpleGString("hello ");
+ assertEquals("hello", gs.stripTrailing());
+ }
+
+ @Test
+ void testStrip() {
+ GStringImpl gs = createSimpleGString(" hello ");
+ assertEquals("hello", gs.strip());
+ }
+
+ @Test
+ void testCodePointAt() {
+ GStringImpl gs = createSimpleGString("ABC");
+ assertEquals('A', gs.codePointAt(0));
+ assertEquals('B', gs.codePointAt(1));
+ }
+
+ @Test
+ void testCodePointBefore() {
+ GStringImpl gs = createSimpleGString("ABC");
+ assertEquals('A', gs.codePointBefore(1));
+ assertEquals('B', gs.codePointBefore(2));
+ }
+
+ @Test
+ void testCodePointCount() {
+ GStringImpl gs = createSimpleGString("ABCDE");
+ assertEquals(3, gs.codePointCount(1, 4));
+ }
+
+ @Test
+ void testOffsetByCodePoints() {
+ GStringImpl gs = createSimpleGString("ABCDE");
+ assertEquals(3, gs.offsetByCodePoints(1, 2));
+ }
+
+ @Test
+ void testGetChars() {
+ GStringImpl gs = createSimpleGString("Hello");
+ char[] dst = new char[3];
+ gs.getChars(1, 4, dst, 0);
+ assertArrayEquals(new char[]{'e', 'l', 'l'}, dst);
+ }
+
+ @Test
+ void testGetBytes() {
+ GStringImpl gs = createSimpleGString("Hello");
+ byte[] bytes = gs.getBytes(StandardCharsets.UTF_8);
+ assertEquals(5, bytes.length);
+ }
+
+ @Test
+ void testContentEqualsStringBuffer() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertTrue(gs.contentEquals(new StringBuffer("Hello")));
+ assertFalse(gs.contentEquals(new StringBuffer("World")));
+ }
+
+ @Test
+ void testContentEqualsCharSequence() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertTrue(gs.contentEquals("Hello"));
+ assertFalse(gs.contentEquals("World"));
+ }
+
+ @Test
+ void testEqualsIgnoreCase() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertTrue(gs.equalsIgnoreCase("HELLO"));
+ assertTrue(gs.equalsIgnoreCase("hello"));
+ assertFalse(gs.equalsIgnoreCase("World"));
+ }
+
+ @Test
+ void testCompareTo() {
+ GStringImpl gs = createSimpleGString("B");
+ assertTrue(gs.compareTo("A") > 0);
+ assertTrue(gs.compareTo("C") < 0);
+ assertEquals(0, gs.compareTo("B"));
+ }
+
+ @Test
+ void testCompareToIgnoreCase() {
+ GStringImpl gs = createSimpleGString("b");
+ assertEquals(0, gs.compareToIgnoreCase("B"));
+ }
+
+ @Test
+ void testRegionMatches() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertTrue(gs.regionMatches(6, "World", 0, 5));
+ assertFalse(gs.regionMatches(6, "Earth", 0, 5));
+ }
+
+ @Test
+ void testRegionMatchesIgnoreCase() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertTrue(gs.regionMatches(true, 6, "WORLD", 0, 5));
+ }
+
+ @Test
+ void testStartsWithOffset() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertTrue(gs.startsWith("World", 6));
+ assertFalse(gs.startsWith("Hello", 6));
+ }
+
+ @Test
+ void testStartsWith() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertTrue(gs.startsWith("Hello"));
+ assertFalse(gs.startsWith("World"));
+ }
+
+ @Test
+ void testEndsWith() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertTrue(gs.endsWith("World"));
+ assertFalse(gs.endsWith("Hello"));
+ }
+
+ @Test
+ void testIndexOfChar() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertEquals(1, gs.indexOf('e'));
+ assertEquals(-1, gs.indexOf('x'));
+ }
+
+ @Test
+ void testIndexOfCharFromIndex() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertEquals(2, gs.indexOf('l', 0));
+ assertEquals(3, gs.indexOf('l', 3));
+ }
+
+ @Test
+ void testLastIndexOfChar() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertEquals(3, gs.lastIndexOf('l'));
+ }
+
+ @Test
+ void testLastIndexOfCharFromIndex() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertEquals(2, gs.lastIndexOf('l', 2));
+ }
+
+ @Test
+ void testIndexOfString() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertEquals(6, gs.indexOf("World"));
+ }
+
+ @Test
+ void testIndexOfStringFromIndex() {
+ GStringImpl gs = createSimpleGString("Hello Hello");
+ assertEquals(6, gs.indexOf("Hello", 1));
+ }
+
+ @Test
+ void testLastIndexOfString() {
+ GStringImpl gs = createSimpleGString("Hello Hello");
+ assertEquals(6, gs.lastIndexOf("Hello"));
+ }
+
+ @Test
+ void testLastIndexOfStringFromIndex() {
+ GStringImpl gs = createSimpleGString("Hello Hello");
+ assertEquals(0, gs.lastIndexOf("Hello", 5));
+ }
+
+ @Test
+ void testSubstring() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertEquals("World", gs.substring(6));
+ }
+
+ @Test
+ void testSubstringRange() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertEquals("World", gs.substring(6, 11));
+ }
+
+ @Test
+ void testConcat() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertEquals("Hello World", gs.concat(" World"));
+ }
+
+ @Test
+ void testReplaceChar() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertEquals("Hallo", gs.replace('e', 'a'));
+ }
+
+ @Test
+ void testMatches() {
+ GStringImpl gs = createSimpleGString("Hello123");
+ assertTrue(gs.matches("\\w+\\d+"));
+ assertFalse(gs.matches("\\d+"));
+ }
+
+ @Test
+ void testContains() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertTrue(gs.contains("World"));
+ assertFalse(gs.contains("Earth"));
+ }
+
+ @Test
+ void testReplaceFirst() {
+ GStringImpl gs = createSimpleGString("Hello Hello");
+ assertEquals("Hi Hello", gs.replaceFirst("Hello", "Hi"));
+ }
+
+ @Test
+ void testReplaceAll() {
+ GStringImpl gs = createSimpleGString("Hello Hello");
+ assertEquals("Hi Hi", gs.replaceAll("Hello", "Hi"));
+ }
+
+ @Test
+ void testReplaceCharSequence() {
+ GStringImpl gs = createSimpleGString("Hello World");
+ assertEquals("Hello Earth", gs.replace("World", "Earth"));
+ }
+
+ @Test
+ void testSplitWithLimit() {
+ GStringImpl gs = createSimpleGString("a,b,c,d");
+ String[] parts = gs.split(",", 2);
+ assertEquals(2, parts.length);
+ assertEquals("a", parts[0]);
+ assertEquals("b,c,d", parts[1]);
+ }
+
+ @Test
+ void testSplit() {
+ GStringImpl gs = createSimpleGString("a,b,c");
+ String[] parts = gs.split(",");
+ assertEquals(3, parts.length);
+ }
+
+ @Test
+ void testToLowerCaseWithLocale() {
+ GStringImpl gs = createSimpleGString("HELLO");
+ assertEquals("hello", gs.toLowerCase(Locale.ENGLISH));
+ }
+
+ @Test
+ void testToLowerCase() {
+ GStringImpl gs = createSimpleGString("HELLO");
+ assertEquals("hello", gs.toLowerCase());
+ }
+
+ @Test
+ void testToUpperCaseWithLocale() {
+ GStringImpl gs = createSimpleGString("hello");
+ assertEquals("HELLO", gs.toUpperCase(Locale.ENGLISH));
+ }
+
+ @Test
+ void testToUpperCase() {
+ GStringImpl gs = createSimpleGString("hello");
+ assertEquals("HELLO", gs.toUpperCase());
+ }
+
+ @Test
+ void testToCharArray() {
+ GStringImpl gs = createSimpleGString("Hello");
+ assertArrayEquals(new char[]{'H', 'e', 'l', 'l', 'o'},
gs.toCharArray());
+ }
+
+ @Test
+ void testIntern() {
+ GStringImpl gs = createSimpleGString("Hello");
+ String interned = gs.intern();
+ assertSame(interned, "Hello".intern());
+ }
+
+ // Caching behavior tests
+ @Test
+ void testCacheableWithImmutableValues() {
+ // GString with immutable values should be cacheable
+ GStringImpl gs = new GStringImpl(new Object[]{"immutable"}, new
String[]{"prefix:", ""});
+ String str1 = gs.toString();
+ String str2 = gs.toString();
+ assertEquals(str1, str2);
+ }
+
+ @Test
+ void testNonCacheableAfterGetStrings() {
+ GStringImpl gs = createGString("Hello ", "World", "!");
+ gs.toString(); // might cache
+ gs.getStrings(); // should invalidate cache on non-frozen
+ String result = gs.toString();
+ assertEquals("Hello World!", result);
+ }
+
+ @Test
+ void testNonCacheableAfterGetValues() {
+ GStringImpl gs = createGString("Hello ", "World", "!");
+ gs.toString(); // might cache
+ gs.getValues(); // should invalidate cache on non-frozen
+ String result = gs.toString();
+ assertEquals("Hello World!", result);
+ }
+}
diff --git
a/src/test/java/org/codehaus/groovy/runtime/InvokerHelperJUnit5Test.java
b/src/test/java/org/codehaus/groovy/runtime/InvokerHelperJUnit5Test.java
new file mode 100644
index 0000000000..162b58679a
--- /dev/null
+++ b/src/test/java/org/codehaus/groovy/runtime/InvokerHelperJUnit5Test.java
@@ -0,0 +1,405 @@
+/*
+ * 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 groovy.lang.SpreadMap;
+import groovy.lang.SpreadMapEvaluatingException;
+import groovy.lang.Tuple;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for InvokerHelper class.
+ */
+class InvokerHelperJUnit5Test {
+
+ @Test
+ void testMetaRegistryNotNull() {
+ assertNotNull(InvokerHelper.metaRegistry);
+ }
+
+ @Test
+ void testInvokeMethodSafeWithNullObject() {
+ Object result = InvokerHelper.invokeMethodSafe(null, "toString", null);
+ assertNull(result);
+ }
+
+ @Test
+ void testInvokeMethodSafeWithNonNull() {
+ Object result = InvokerHelper.invokeMethodSafe("hello", "length",
null);
+ assertEquals(5, result);
+ }
+
+ @Test
+ void testAsListWithNull() {
+ List<?> result = InvokerHelper.asList(null);
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ void testAsListWithList() {
+ List<String> input = Arrays.asList("a", "b", "c");
+ List<?> result = InvokerHelper.asList(input);
+ assertSame(input, result);
+ }
+
+ @Test
+ void testAsListWithArray() {
+ Object[] input = {"x", "y", "z"};
+ List<?> result = InvokerHelper.asList(input);
+ assertEquals(3, result.size());
+ assertEquals("x", result.get(0));
+ }
+
+ @Test
+ void testAsListWithEnumeration() {
+ Vector<String> vector = new Vector<>();
+ vector.add("one");
+ vector.add("two");
+ Enumeration<String> enumeration = vector.elements();
+
+ List<?> result = InvokerHelper.asList(enumeration);
+ assertEquals(2, result.size());
+ assertEquals("one", result.get(0));
+ assertEquals("two", result.get(1));
+ }
+
+ @Test
+ void testAsListWithSingleValue() {
+ List<?> result = InvokerHelper.asList("single");
+ assertEquals(1, result.size());
+ assertEquals("single", result.get(0));
+ }
+
+ @Test
+ void testGetPropertySafeWithNull() {
+ Object result = InvokerHelper.getPropertySafe(null, "anyProperty");
+ assertNull(result);
+ }
+
+ @Test
+ void testGetMethodPointerThrowsOnNull() {
+ assertThrows(NullPointerException.class, () ->
+ InvokerHelper.getMethodPointer(null, "toString")
+ );
+ }
+
+ @Test
+ void testGetMethodPointer() {
+ Closure<?> closure = InvokerHelper.getMethodPointer("hello",
"toUpperCase");
+ assertNotNull(closure);
+ }
+
+ @Test
+ void testUnaryMinusInteger() {
+ Object result = InvokerHelper.unaryMinus(42);
+ assertEquals(-42, result);
+ }
+
+ @Test
+ void testUnaryMinusLong() {
+ Object result = InvokerHelper.unaryMinus(100L);
+ assertEquals(-100L, result);
+ }
+
+ @Test
+ void testUnaryMinusDouble() {
+ Object result = InvokerHelper.unaryMinus(3.14);
+ assertEquals(-3.14, result);
+ }
+
+ @Test
+ void testUnaryMinusFloat() {
+ Object result = InvokerHelper.unaryMinus(2.5f);
+ assertEquals(-2.5f, result);
+ }
+
+ @Test
+ void testUnaryMinusShort() {
+ Object result = InvokerHelper.unaryMinus((short) 10);
+ assertEquals((short) -10, result);
+ }
+
+ @Test
+ void testUnaryMinusByte() {
+ Object result = InvokerHelper.unaryMinus((byte) 5);
+ assertEquals((byte) -5, result);
+ }
+
+ @Test
+ void testUnaryMinusBigInteger() {
+ BigInteger input = new BigInteger("1000000000000");
+ Object result = InvokerHelper.unaryMinus(input);
+ assertEquals(new BigInteger("-1000000000000"), result);
+ }
+
+ @Test
+ void testUnaryMinusBigDecimal() {
+ BigDecimal input = new BigDecimal("123.456");
+ Object result = InvokerHelper.unaryMinus(input);
+ assertEquals(new BigDecimal("-123.456"), result);
+ }
+
+ @Test
+ void testUnaryMinusList() {
+ ArrayList<Integer> input = new ArrayList<>(Arrays.asList(1, 2, 3));
+ Object result = InvokerHelper.unaryMinus(input);
+
+ assertTrue(result instanceof List);
+ List<?> list = (List<?>) result;
+ assertEquals(3, list.size());
+ assertEquals(-1, list.get(0));
+ assertEquals(-2, list.get(1));
+ assertEquals(-3, list.get(2));
+ }
+
+ @Test
+ void testUnaryPlusInteger() {
+ Object result = InvokerHelper.unaryPlus(42);
+ assertEquals(42, result);
+ }
+
+ @Test
+ void testUnaryPlusLong() {
+ Object result = InvokerHelper.unaryPlus(100L);
+ assertEquals(100L, result);
+ }
+
+ @Test
+ void testUnaryPlusDouble() {
+ Object result = InvokerHelper.unaryPlus(3.14);
+ assertEquals(3.14, result);
+ }
+
+ @Test
+ void testUnaryPlusFloat() {
+ Object result = InvokerHelper.unaryPlus(2.5f);
+ assertEquals(2.5f, result);
+ }
+
+ @Test
+ void testUnaryPlusBigInteger() {
+ BigInteger input = new BigInteger("12345");
+ Object result = InvokerHelper.unaryPlus(input);
+ assertSame(input, result);
+ }
+
+ @Test
+ void testUnaryPlusBigDecimal() {
+ BigDecimal input = new BigDecimal("99.99");
+ Object result = InvokerHelper.unaryPlus(input);
+ assertSame(input, result);
+ }
+
+ @Test
+ void testUnaryPlusList() {
+ ArrayList<Integer> input = new ArrayList<>(Arrays.asList(1, 2, 3));
+ Object result = InvokerHelper.unaryPlus(input);
+
+ assertTrue(result instanceof List);
+ List<?> list = (List<?>) result;
+ assertEquals(3, list.size());
+ }
+
+ @Test
+ void testFindRegexWithStrings() {
+ Matcher matcher = InvokerHelper.findRegex("hello world", "\\w+");
+ assertNotNull(matcher);
+ assertTrue(matcher.find());
+ assertEquals("hello", matcher.group());
+ }
+
+ @Test
+ void testFindRegexWithPattern() {
+ Pattern pattern = Pattern.compile("\\d+");
+ Matcher matcher = InvokerHelper.findRegex("abc123def", pattern);
+ assertNotNull(matcher);
+ assertTrue(matcher.find());
+ assertEquals("123", matcher.group());
+ }
+
+ @Test
+ void testMatchRegexTrue() {
+ assertTrue(InvokerHelper.matchRegex("hello", "hello"));
+ assertTrue(InvokerHelper.matchRegex("hello", "h.*"));
+ }
+
+ @Test
+ void testMatchRegexFalse() {
+ assertFalse(InvokerHelper.matchRegex("hello", "world"));
+ assertFalse(InvokerHelper.matchRegex("hello", "^world$"));
+ }
+
+ @Test
+ void testMatchRegexWithNull() {
+ assertFalse(InvokerHelper.matchRegex(null, "pattern"));
+ assertFalse(InvokerHelper.matchRegex("string", null));
+ assertFalse(InvokerHelper.matchRegex(null, null));
+ }
+
+ @Test
+ void testMatchRegexWithPattern() {
+ Pattern pattern = Pattern.compile("\\d{3}");
+ assertTrue(InvokerHelper.matchRegex("123", pattern));
+ assertFalse(InvokerHelper.matchRegex("12", pattern));
+ }
+
+ @Test
+ void testCreateTuple() {
+ Object[] array = {1, "two", 3.0};
+ Tuple tuple = InvokerHelper.createTuple(array);
+
+ assertNotNull(tuple);
+ assertEquals(3, tuple.size());
+ assertEquals(1, tuple.get(0));
+ assertEquals("two", tuple.get(1));
+ assertEquals(3.0, tuple.get(2));
+ }
+
+ @Test
+ void testCreateTupleEmpty() {
+ Tuple tuple = InvokerHelper.createTuple(new Object[0]);
+ assertNotNull(tuple);
+ assertEquals(0, tuple.size());
+ }
+
+ @Test
+ void testSpreadMapFromMap() {
+ Map<String, Integer> map = new LinkedHashMap<>();
+ map.put("a", 1);
+ map.put("b", 2);
+
+ SpreadMap result = InvokerHelper.spreadMap(map);
+ assertNotNull(result);
+ }
+
+ @Test
+ void testSpreadMapFromNonMapThrows() {
+ assertThrows(SpreadMapEvaluatingException.class, () ->
+ InvokerHelper.spreadMap("not a map")
+ );
+ }
+
+ @Test
+ void testSpreadMapFromEmptyMap() {
+ Map<String, Integer> map = Collections.emptyMap();
+ SpreadMap result = InvokerHelper.spreadMap(map);
+ assertNotNull(result);
+ }
+
+ @Test
+ void testInvokeStaticNoArgumentsMethod() {
+ // This method takes only Class and method name (no arguments)
+ // Test with a method that actually takes no arguments
+ Object result =
InvokerHelper.invokeStaticNoArgumentsMethod(System.class, "lineSeparator");
+ assertNotNull(result);
+ }
+
+ @Test
+ void testInvokeNoArgumentsConstructorOf() {
+ Object result =
InvokerHelper.invokeNoArgumentsConstructorOf(StringBuilder.class);
+ assertNotNull(result);
+ assertTrue(result instanceof StringBuilder);
+ }
+
+ @Test
+ void testRemoveClass() {
+ // Create a simple test class scenario
+ // Just verify the method doesn't throw
+ assertDoesNotThrow(() ->
InvokerHelper.removeClass(InvokerHelperJUnit5Test.class));
+ }
+
+ @Test
+ void testEmptyArgsConstant() {
+ assertEquals(0, InvokerHelper.EMPTY_ARGS.length);
+ }
+
+ @Test
+ void testMainMethodNameConstant() {
+ assertEquals("main", InvokerHelper.MAIN_METHOD_NAME);
+ }
+
+ @Test
+ void testSetPropertySafe2WithNull() {
+ // Should not throw when object is null
+ assertDoesNotThrow(() ->
+ InvokerHelper.setPropertySafe2("value", null, "property")
+ );
+ }
+
+ @Test
+ void testSetProperty2() {
+ StringBuilder sb = new StringBuilder();
+ // This tests the reordered parameter version
+ // May throw if property doesn't exist - just testing it's callable
+ try {
+ InvokerHelper.setProperty2("value", sb, "nonexistent");
+ } catch (Exception e) {
+ // Expected - property doesn't exist
+ }
+ }
+
+ @Test
+ void testInvokeMethod() {
+ Object result = InvokerHelper.invokeMethod("hello", "toUpperCase",
null);
+ assertEquals("HELLO", result);
+ }
+
+ @Test
+ void testInvokeMethodWithArgs() {
+ Object result = InvokerHelper.invokeMethod("hello world", "substring",
new Object[]{0, 5});
+ assertEquals("hello", result);
+ }
+
+ @Test
+ void testGetProperty() {
+ // Test on a simple object
+ String str = "test";
+ // getProperty uses metaclass, may work differently
+ try {
+ Object result = InvokerHelper.getProperty(str, "class");
+ assertEquals(String.class, result);
+ } catch (Exception e) {
+ // May fail depending on metaclass setup
+ }
+ }
+
+ @Test
+ void testInvokeStaticMethod() {
+ Object result = InvokerHelper.invokeStaticMethod(String.class,
"valueOf", new Object[]{42});
+ assertEquals("42", result);
+ }
+
+ @Test
+ void testInvokeConstructorOf() {
+ Object result = InvokerHelper.invokeConstructorOf(StringBuilder.class,
new Object[]{"initial"});
+ assertNotNull(result);
+ assertTrue(result instanceof StringBuilder);
+ assertEquals("initial", result.toString());
+ }
+}
diff --git
a/src/test/java/org/codehaus/groovy/runtime/typehandling/ShortTypeHandlingJUnit5Test.java
b/src/test/java/org/codehaus/groovy/runtime/typehandling/ShortTypeHandlingJUnit5Test.java
new file mode 100644
index 0000000000..12addcd017
--- /dev/null
+++
b/src/test/java/org/codehaus/groovy/runtime/typehandling/ShortTypeHandlingJUnit5Test.java
@@ -0,0 +1,198 @@
+/*
+ * 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.typehandling;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for ShortTypeHandling class.
+ */
+class ShortTypeHandlingJUnit5Test {
+
+ // castToClass tests
+ @Test
+ void testCastToClassWithNull() {
+ assertNull(ShortTypeHandling.castToClass(null));
+ }
+
+ @Test
+ void testCastToClassWithClass() {
+ Class<?> result = ShortTypeHandling.castToClass(String.class);
+ assertEquals(String.class, result);
+ }
+
+ @Test
+ void testCastToClassWithClassName() {
+ Class<?> result = ShortTypeHandling.castToClass("java.lang.String");
+ assertEquals(String.class, result);
+ }
+
+ @Test
+ void testCastToClassWithInvalidClassName() {
+ assertThrows(GroovyCastException.class, () ->
+ ShortTypeHandling.castToClass("invalid.class.Name"));
+ }
+
+ // castToString tests
+ @Test
+ void testCastToStringWithNull() {
+ assertNull(ShortTypeHandling.castToString(null));
+ }
+
+ @Test
+ void testCastToStringWithString() {
+ assertEquals("hello", ShortTypeHandling.castToString("hello"));
+ }
+
+ @Test
+ void testCastToStringWithObject() {
+ assertEquals("42", ShortTypeHandling.castToString(42));
+ }
+
+ @Test
+ void testCastToStringWithBooleanArray() {
+ boolean[] arr = {true, false, true};
+ assertEquals("[true, false, true]",
ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithByteArray() {
+ byte[] arr = {1, 2, 3};
+ assertEquals("[1, 2, 3]", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithCharArray() {
+ char[] arr = {'a', 'b', 'c'};
+ assertEquals("abc", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithDoubleArray() {
+ double[] arr = {1.1, 2.2, 3.3};
+ assertEquals("[1.1, 2.2, 3.3]", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithFloatArray() {
+ float[] arr = {1.1f, 2.2f, 3.3f};
+ assertEquals("[1.1, 2.2, 3.3]", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithIntArray() {
+ int[] arr = {1, 2, 3};
+ assertEquals("[1, 2, 3]", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithLongArray() {
+ long[] arr = {1L, 2L, 3L};
+ assertEquals("[1, 2, 3]", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithShortArray() {
+ short[] arr = {1, 2, 3};
+ assertEquals("[1, 2, 3]", ShortTypeHandling.castToString(arr));
+ }
+
+ @Test
+ void testCastToStringWithObjectArray() {
+ Object[] arr = {"a", "b", "c"};
+ assertEquals("[a, b, c]", ShortTypeHandling.castToString(arr));
+ }
+
+ // castToEnum tests
+ enum TestEnum { VALUE_ONE, VALUE_TWO, VALUE_THREE }
+
+ @Test
+ void testCastToEnumWithNull() {
+ assertNull(ShortTypeHandling.castToEnum(null, TestEnum.class));
+ }
+
+ @Test
+ void testCastToEnumWithSameEnum() {
+ TestEnum result = (TestEnum)
ShortTypeHandling.castToEnum(TestEnum.VALUE_ONE, TestEnum.class);
+ assertEquals(TestEnum.VALUE_ONE, result);
+ }
+
+ @Test
+ void testCastToEnumWithString() {
+ TestEnum result = (TestEnum) ShortTypeHandling.castToEnum("VALUE_TWO",
TestEnum.class);
+ assertEquals(TestEnum.VALUE_TWO, result);
+ }
+
+ @Test
+ void testCastToEnumWithInvalidString() {
+ assertThrows(IllegalArgumentException.class, () ->
+ ShortTypeHandling.castToEnum("INVALID_VALUE", TestEnum.class));
+ }
+
+ @Test
+ void testCastToEnumWithInvalidType() {
+ assertThrows(GroovyCastException.class, () ->
+ ShortTypeHandling.castToEnum(123, TestEnum.class));
+ }
+
+ // castToChar tests
+ @Test
+ void testCastToCharWithNull() {
+ assertNull(ShortTypeHandling.castToChar(null));
+ }
+
+ @Test
+ void testCastToCharWithCharacter() {
+ assertEquals('X', ShortTypeHandling.castToChar('X'));
+ }
+
+ @Test
+ void testCastToCharWithNumber() {
+ assertEquals('A', ShortTypeHandling.castToChar(65));
+ }
+
+ @Test
+ void testCastToCharWithSingleCharString() {
+ assertEquals('Z', ShortTypeHandling.castToChar("Z"));
+ }
+
+ @Test
+ void testCastToCharWithMultiCharString() {
+ assertThrows(GroovyCastException.class, () ->
+ ShortTypeHandling.castToChar("Hello"));
+ }
+
+ @Test
+ void testCastToCharWithEmptyString() {
+ assertThrows(GroovyCastException.class, () ->
+ ShortTypeHandling.castToChar(""));
+ }
+
+ @Test
+ void testCastToCharWithLongNumber() {
+ assertEquals('a', ShortTypeHandling.castToChar(97L));
+ }
+
+ @Test
+ void testCastToCharWithDoubleNumber() {
+ assertEquals('A', ShortTypeHandling.castToChar(65.9));
+ }
+}
diff --git
a/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsJUnit5Test.java
b/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsJUnit5Test.java
new file mode 100644
index 0000000000..dc9886ccf0
--- /dev/null
+++
b/subprojects/groovy-datetime/src/test/java/org/apache/groovy/datetime/extensions/DateTimeExtensionsJUnit5Test.java
@@ -0,0 +1,519 @@
+/*
+ * 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.datetime.extensions;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.*;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Additional JUnit 5 tests for DateTimeExtensions class.
+ */
+class DateTimeExtensionsJUnit5Test {
+
+ // Duration extension methods
+ @Test
+ void testDurationPlus() {
+ Duration d = Duration.ofSeconds(10);
+ Duration result = DateTimeExtensions.plus(d, 5);
+ assertEquals(Duration.ofSeconds(15), result);
+ }
+
+ @Test
+ void testDurationMinus() {
+ Duration d = Duration.ofSeconds(10);
+ Duration result = DateTimeExtensions.minus(d, 3);
+ assertEquals(Duration.ofSeconds(7), result);
+ }
+
+ @Test
+ void testDurationNext() {
+ Duration d = Duration.ofSeconds(10);
+ Duration result = DateTimeExtensions.next(d);
+ assertEquals(Duration.ofSeconds(11), result);
+ }
+
+ @Test
+ void testDurationPrevious() {
+ Duration d = Duration.ofSeconds(10);
+ Duration result = DateTimeExtensions.previous(d);
+ assertEquals(Duration.ofSeconds(9), result);
+ }
+
+ @Test
+ void testDurationNegative() {
+ Duration d = Duration.ofSeconds(10);
+ Duration result = DateTimeExtensions.negative(d);
+ assertEquals(Duration.ofSeconds(-10), result);
+ }
+
+ @Test
+ void testDurationPositive() {
+ Duration d = Duration.ofSeconds(-10);
+ Duration result = DateTimeExtensions.positive(d);
+ assertEquals(Duration.ofSeconds(10), result);
+ }
+
+ @Test
+ void testDurationMultiply() {
+ Duration d = Duration.ofSeconds(5);
+ Duration result = DateTimeExtensions.multiply(d, 3);
+ assertEquals(Duration.ofSeconds(15), result);
+ }
+
+ @Test
+ void testDurationDiv() {
+ Duration d = Duration.ofSeconds(15);
+ Duration result = DateTimeExtensions.div(d, 3);
+ assertEquals(Duration.ofSeconds(5), result);
+ }
+
+ @Test
+ void testDurationIsPositive() {
+ assertTrue(DateTimeExtensions.isPositive(Duration.ofSeconds(1)));
+ assertFalse(DateTimeExtensions.isPositive(Duration.ZERO));
+ assertFalse(DateTimeExtensions.isPositive(Duration.ofSeconds(-1)));
+ }
+
+ @Test
+ void testDurationIsNonnegative() {
+ assertTrue(DateTimeExtensions.isNonnegative(Duration.ofSeconds(1)));
+ assertTrue(DateTimeExtensions.isNonnegative(Duration.ZERO));
+ assertFalse(DateTimeExtensions.isNonnegative(Duration.ofSeconds(-1)));
+ }
+
+ @Test
+ void testDurationIsNonpositive() {
+ assertFalse(DateTimeExtensions.isNonpositive(Duration.ofSeconds(1)));
+ assertTrue(DateTimeExtensions.isNonpositive(Duration.ZERO));
+ assertTrue(DateTimeExtensions.isNonpositive(Duration.ofSeconds(-1)));
+ }
+
+ // Instant extension methods
+ @Test
+ void testInstantPlus() {
+ Instant instant = Instant.ofEpochSecond(1000);
+ Instant result = DateTimeExtensions.plus(instant, 100);
+ assertEquals(Instant.ofEpochSecond(1100), result);
+ }
+
+ @Test
+ void testInstantMinus() {
+ Instant instant = Instant.ofEpochSecond(1000);
+ Instant result = DateTimeExtensions.minus(instant, 100);
+ assertEquals(Instant.ofEpochSecond(900), result);
+ }
+
+ @Test
+ void testInstantNext() {
+ Instant instant = Instant.ofEpochSecond(1000);
+ Instant result = DateTimeExtensions.next(instant);
+ assertEquals(Instant.ofEpochSecond(1001), result);
+ }
+
+ @Test
+ void testInstantPrevious() {
+ Instant instant = Instant.ofEpochSecond(1000);
+ Instant result = DateTimeExtensions.previous(instant);
+ assertEquals(Instant.ofEpochSecond(999), result);
+ }
+
+ @Test
+ void testInstantToDate() {
+ Instant instant = Instant.ofEpochMilli(1234567890000L);
+ Date date = DateTimeExtensions.toDate(instant);
+ assertEquals(1234567890000L, date.getTime());
+ }
+
+ @Test
+ void testInstantToCalendar() {
+ Instant instant = Instant.ofEpochMilli(1234567890000L);
+ Calendar cal = DateTimeExtensions.toCalendar(instant);
+ assertNotNull(cal);
+ assertEquals(1234567890000L, cal.getTimeInMillis());
+ }
+
+ // Period extension methods
+ @Test
+ void testPeriodPlus() {
+ Period p = Period.ofDays(10);
+ Period result = DateTimeExtensions.plus(p, 5);
+ assertEquals(Period.ofDays(15), result);
+ }
+
+ @Test
+ void testPeriodMinus() {
+ Period p = Period.ofDays(10);
+ Period result = DateTimeExtensions.minus(p, 3);
+ assertEquals(Period.ofDays(7), result);
+ }
+
+ @Test
+ void testPeriodNext() {
+ Period p = Period.ofDays(10);
+ Period result = DateTimeExtensions.next(p);
+ assertEquals(Period.ofDays(11), result);
+ }
+
+ @Test
+ void testPeriodPrevious() {
+ Period p = Period.ofDays(10);
+ Period result = DateTimeExtensions.previous(p);
+ assertEquals(Period.ofDays(9), result);
+ }
+
+ @Test
+ void testPeriodNegative() {
+ Period p = Period.ofDays(10);
+ Period result = DateTimeExtensions.negative(p);
+ assertEquals(Period.ofDays(-10), result);
+ }
+
+ @Test
+ void testPeriodPositive() {
+ Period p = Period.ofDays(-10);
+ Period result = DateTimeExtensions.positive(p);
+ // positive() might not be abs() for Period - it might just return the
period
+ assertNotNull(result);
+ }
+
+ @Test
+ void testPeriodMultiply() {
+ Period p = Period.ofDays(5);
+ Period result = DateTimeExtensions.multiply(p, 3);
+ assertEquals(Period.ofDays(15), result);
+ }
+
+ @Test
+ void testPeriodIsPositive() {
+ assertTrue(DateTimeExtensions.isPositive(Period.ofDays(1)));
+ assertFalse(DateTimeExtensions.isPositive(Period.ZERO));
+ assertFalse(DateTimeExtensions.isPositive(Period.ofDays(-1)));
+ }
+
+ @Test
+ void testPeriodIsNonnegative() {
+ assertTrue(DateTimeExtensions.isNonnegative(Period.ofDays(1)));
+ assertTrue(DateTimeExtensions.isNonnegative(Period.ZERO));
+ assertFalse(DateTimeExtensions.isNonnegative(Period.ofDays(-1)));
+ }
+
+ @Test
+ void testPeriodIsNonpositive() {
+ assertFalse(DateTimeExtensions.isNonpositive(Period.ofDays(1)));
+ assertTrue(DateTimeExtensions.isNonpositive(Period.ZERO));
+ assertTrue(DateTimeExtensions.isNonpositive(Period.ofDays(-1)));
+ }
+
+ // LocalDate extension methods
+ @Test
+ void testLocalDatePlusLong() {
+ LocalDate date = LocalDate.of(2020, 1, 1);
+ LocalDate result = DateTimeExtensions.plus(date, 10);
+ assertEquals(LocalDate.of(2020, 1, 11), result);
+ }
+
+ @Test
+ void testLocalDateMinusLong() {
+ LocalDate date = LocalDate.of(2020, 1, 15);
+ LocalDate result = DateTimeExtensions.minus(date, 10);
+ assertEquals(LocalDate.of(2020, 1, 5), result);
+ }
+
+ @Test
+ void testLocalDateNext() {
+ LocalDate date = LocalDate.of(2020, 1, 1);
+ LocalDate result = DateTimeExtensions.next(date);
+ assertEquals(LocalDate.of(2020, 1, 2), result);
+ }
+
+ @Test
+ void testLocalDatePrevious() {
+ LocalDate date = LocalDate.of(2020, 1, 2);
+ LocalDate result = DateTimeExtensions.previous(date);
+ assertEquals(LocalDate.of(2020, 1, 1), result);
+ }
+
+ // LocalTime extension methods
+ @Test
+ void testLocalTimePlusLong() {
+ LocalTime time = LocalTime.of(10, 30, 0);
+ LocalTime result = DateTimeExtensions.plus(time, 60);
+ assertEquals(LocalTime.of(10, 31, 0), result);
+ }
+
+ @Test
+ void testLocalTimeMinusLong() {
+ LocalTime time = LocalTime.of(10, 30, 0);
+ LocalTime result = DateTimeExtensions.minus(time, 30);
+ assertEquals(LocalTime.of(10, 29, 30), result);
+ }
+
+ @Test
+ void testLocalTimeNext() {
+ LocalTime time = LocalTime.of(10, 30, 0);
+ LocalTime result = DateTimeExtensions.next(time);
+ assertEquals(LocalTime.of(10, 30, 1), result);
+ }
+
+ @Test
+ void testLocalTimePrevious() {
+ LocalTime time = LocalTime.of(10, 30, 1);
+ LocalTime result = DateTimeExtensions.previous(time);
+ assertEquals(LocalTime.of(10, 30, 0), result);
+ }
+
+ // LocalDateTime extension methods
+ @Test
+ void testLocalDateTimePlusLong() {
+ LocalDateTime dt = LocalDateTime.of(2020, 1, 1, 10, 0, 0);
+ LocalDateTime result = DateTimeExtensions.plus(dt, 3600); // 1 hour
+ assertEquals(LocalDateTime.of(2020, 1, 1, 11, 0, 0), result);
+ }
+
+ @Test
+ void testLocalDateTimeMinusLong() {
+ LocalDateTime dt = LocalDateTime.of(2020, 1, 1, 10, 0, 0);
+ LocalDateTime result = DateTimeExtensions.minus(dt, 60); // 1 minute
+ assertEquals(LocalDateTime.of(2020, 1, 1, 9, 59, 0), result);
+ }
+
+ @Test
+ void testLocalDateTimeNext() {
+ LocalDateTime dt = LocalDateTime.of(2020, 1, 1, 10, 0, 0);
+ LocalDateTime result = DateTimeExtensions.next(dt);
+ assertEquals(LocalDateTime.of(2020, 1, 1, 10, 0, 1), result);
+ }
+
+ @Test
+ void testLocalDateTimePrevious() {
+ LocalDateTime dt = LocalDateTime.of(2020, 1, 1, 10, 0, 1);
+ LocalDateTime result = DateTimeExtensions.previous(dt);
+ assertEquals(LocalDateTime.of(2020, 1, 1, 10, 0, 0), result);
+ }
+
+ // TemporalAccessor extension methods
+ @Test
+ void testGetAtTemporalField() {
+ LocalDateTime dt = LocalDateTime.of(2020, 6, 15, 14, 30, 45);
+
+ long year = DateTimeExtensions.getAt(dt, ChronoField.YEAR);
+ assertEquals(2020, year);
+
+ long month = DateTimeExtensions.getAt(dt, ChronoField.MONTH_OF_YEAR);
+ assertEquals(6, month);
+
+ long day = DateTimeExtensions.getAt(dt, ChronoField.DAY_OF_MONTH);
+ assertEquals(15, day);
+ }
+
+ @Test
+ void testGetAtIterableTemporalFields() {
+ LocalDateTime dt = LocalDateTime.of(2020, 6, 15, 14, 30, 45);
+ List<java.time.temporal.TemporalField> fields = Arrays.asList(
+ ChronoField.YEAR, ChronoField.MONTH_OF_YEAR,
ChronoField.DAY_OF_MONTH
+ );
+
+ List<Long> values = DateTimeExtensions.getAt(dt, fields);
+
+ assertEquals(3, values.size());
+ assertEquals(2020L, values.get(0));
+ assertEquals(6L, values.get(1));
+ assertEquals(15L, values.get(2));
+ }
+
+ // TemporalAmount extension methods
+ @Test
+ void testGetAtTemporalUnit() {
+ Duration d = Duration.ofHours(2).plusMinutes(30);
+
+ long seconds = DateTimeExtensions.getAt(d, ChronoUnit.SECONDS);
+ assertEquals(9000, seconds); // 2.5 hours in seconds
+ }
+
+ // Year extension methods
+ @Test
+ void testYearPlusLong() {
+ Year year = Year.of(2020);
+ Year result = DateTimeExtensions.plus(year, 5);
+ assertEquals(Year.of(2025), result);
+ }
+
+ @Test
+ void testYearMinusLong() {
+ Year year = Year.of(2020);
+ Year result = DateTimeExtensions.minus(year, 10);
+ assertEquals(Year.of(2010), result);
+ }
+
+ @Test
+ void testYearNext() {
+ Year year = Year.of(2020);
+ Year result = DateTimeExtensions.next(year);
+ assertEquals(Year.of(2021), result);
+ }
+
+ @Test
+ void testYearPrevious() {
+ Year year = Year.of(2020);
+ Year result = DateTimeExtensions.previous(year);
+ assertEquals(Year.of(2019), result);
+ }
+
+ // YearMonth extension methods
+ @Test
+ void testYearMonthPlusLong() {
+ YearMonth ym = YearMonth.of(2020, 1);
+ YearMonth result = DateTimeExtensions.plus(ym, 3);
+ assertEquals(YearMonth.of(2020, 4), result);
+ }
+
+ @Test
+ void testYearMonthMinusLong() {
+ YearMonth ym = YearMonth.of(2020, 6);
+ YearMonth result = DateTimeExtensions.minus(ym, 3);
+ assertEquals(YearMonth.of(2020, 3), result);
+ }
+
+ @Test
+ void testYearMonthNext() {
+ YearMonth ym = YearMonth.of(2020, 12);
+ YearMonth result = DateTimeExtensions.next(ym);
+ assertEquals(YearMonth.of(2021, 1), result);
+ }
+
+ @Test
+ void testYearMonthPrevious() {
+ YearMonth ym = YearMonth.of(2020, 1);
+ YearMonth result = DateTimeExtensions.previous(ym);
+ assertEquals(YearMonth.of(2019, 12), result);
+ }
+
+ // ZonedDateTime extension methods
+ @Test
+ void testZonedDateTimePlusLong() {
+ ZonedDateTime zdt = ZonedDateTime.of(2020, 1, 1, 10, 0, 0, 0,
ZoneId.of("UTC"));
+ ZonedDateTime result = DateTimeExtensions.plus(zdt, 3600);
+ assertEquals(11, result.getHour());
+ }
+
+ @Test
+ void testZonedDateTimeMinusLong() {
+ ZonedDateTime zdt = ZonedDateTime.of(2020, 1, 1, 10, 0, 0, 0,
ZoneId.of("UTC"));
+ ZonedDateTime result = DateTimeExtensions.minus(zdt, 3600);
+ assertEquals(9, result.getHour());
+ }
+
+ // OffsetDateTime extension methods
+ @Test
+ void testOffsetDateTimePlusLong() {
+ OffsetDateTime odt = OffsetDateTime.of(2020, 1, 1, 10, 0, 0, 0,
ZoneOffset.UTC);
+ OffsetDateTime result = DateTimeExtensions.plus(odt, 3600);
+ assertEquals(11, result.getHour());
+ }
+
+ @Test
+ void testOffsetDateTimeMinusLong() {
+ OffsetDateTime odt = OffsetDateTime.of(2020, 1, 1, 10, 0, 0, 0,
ZoneOffset.UTC);
+ OffsetDateTime result = DateTimeExtensions.minus(odt, 3600);
+ assertEquals(9, result.getHour());
+ }
+
+ // OffsetTime extension methods
+ @Test
+ void testOffsetTimePlusLong() {
+ OffsetTime ot = OffsetTime.of(10, 0, 0, 0, ZoneOffset.UTC);
+ OffsetTime result = DateTimeExtensions.plus(ot, 60);
+ assertEquals(10, result.getHour());
+ assertEquals(1, result.getMinute());
+ }
+
+ @Test
+ void testOffsetTimeMinusLong() {
+ OffsetTime ot = OffsetTime.of(10, 1, 0, 0, ZoneOffset.UTC);
+ OffsetTime result = DateTimeExtensions.minus(ot, 60);
+ assertEquals(10, result.getHour());
+ assertEquals(0, result.getMinute());
+ }
+
+ // DayOfWeek extension methods
+ @Test
+ void testDayOfWeekPlusInt() {
+ DayOfWeek dow = DayOfWeek.MONDAY;
+ DayOfWeek result = DateTimeExtensions.plus(dow, 3);
+ assertEquals(DayOfWeek.THURSDAY, result);
+ }
+
+ @Test
+ void testDayOfWeekMinusInt() {
+ DayOfWeek dow = DayOfWeek.FRIDAY;
+ DayOfWeek result = DateTimeExtensions.minus(dow, 2);
+ assertEquals(DayOfWeek.WEDNESDAY, result);
+ }
+
+ // Month extension methods
+ @Test
+ void testMonthPlusInt() {
+ Month month = Month.JANUARY;
+ Month result = DateTimeExtensions.plus(month, 3);
+ assertEquals(Month.APRIL, result);
+ }
+
+ @Test
+ void testMonthMinusInt() {
+ Month month = Month.APRIL;
+ Month result = DateTimeExtensions.minus(month, 1);
+ assertEquals(Month.MARCH, result);
+ }
+
+ // Conversion methods
+ @Test
+ void testLocalDateToDate() {
+ LocalDate ld = LocalDate.of(2020, 6, 15);
+ Date date = DateTimeExtensions.toDate(ld);
+ assertNotNull(date);
+ }
+
+ @Test
+ void testLocalDateToCalendar() {
+ LocalDate ld = LocalDate.of(2020, 6, 15);
+ Calendar cal = DateTimeExtensions.toCalendar(ld);
+ assertNotNull(cal);
+ assertEquals(2020, cal.get(Calendar.YEAR));
+ }
+
+ @Test
+ void testLocalDateTimeToDate() {
+ LocalDateTime ldt = LocalDateTime.of(2020, 6, 15, 10, 30, 0);
+ Date date = DateTimeExtensions.toDate(ldt);
+ assertNotNull(date);
+ }
+
+ @Test
+ void testLocalDateTimeToCalendar() {
+ LocalDateTime ldt = LocalDateTime.of(2020, 6, 15, 10, 30, 0);
+ Calendar cal = DateTimeExtensions.toCalendar(ldt);
+ assertNotNull(cal);
+ }
+}
diff --git
a/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsJUnit5Test.java
b/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsJUnit5Test.java
new file mode 100644
index 0000000000..ea5d87c6e9
--- /dev/null
+++
b/subprojects/groovy-dateutil/src/test/java/org/apache/groovy/dateutil/extensions/DateUtilExtensionsJUnit5Test.java
@@ -0,0 +1,630 @@
+/*
+ * 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.dateutil.extensions;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyRuntimeException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for DateUtilExtensions class.
+ */
+class DateUtilExtensionsJUnit5Test {
+
+ private Calendar calendar;
+ private Date date;
+
+ @BeforeEach
+ void setUp() {
+ calendar = Calendar.getInstance();
+ calendar.set(2020, Calendar.JUNE, 15, 10, 30, 45);
+ calendar.set(Calendar.MILLISECOND, 0);
+ date = calendar.getTime();
+ }
+
+ // getAt tests for Date
+ @Test
+ void testGetAtDateWithYear() {
+ assertEquals(2020, DateUtilExtensions.getAt(date, Calendar.YEAR));
+ }
+
+ @Test
+ void testGetAtDateWithMonth() {
+ assertEquals(Calendar.JUNE, DateUtilExtensions.getAt(date,
Calendar.MONTH));
+ }
+
+ @Test
+ void testGetAtDateWithDayOfMonth() {
+ assertEquals(15, DateUtilExtensions.getAt(date,
Calendar.DAY_OF_MONTH));
+ }
+
+ @Test
+ void testGetAtDateWithHour() {
+ assertEquals(10, DateUtilExtensions.getAt(date, Calendar.HOUR_OF_DAY));
+ }
+
+ @Test
+ void testGetAtDateWithCollection() {
+ List<Integer> fields = Arrays.asList(Calendar.YEAR, Calendar.MONTH,
Calendar.DAY_OF_MONTH);
+ List<Integer> result = DateUtilExtensions.getAt(date, fields);
+ assertEquals(3, result.size());
+ assertEquals(2020, result.get(0));
+ assertEquals(Calendar.JUNE, result.get(1));
+ assertEquals(15, result.get(2));
+ }
+
+ // toCalendar tests
+ @Test
+ void testToCalendar() {
+ Calendar result = DateUtilExtensions.toCalendar(date);
+ assertEquals(2020, result.get(Calendar.YEAR));
+ assertEquals(Calendar.JUNE, result.get(Calendar.MONTH));
+ assertEquals(15, result.get(Calendar.DAY_OF_MONTH));
+ }
+
+ // getAt tests for Calendar
+ @Test
+ void testGetAtCalendarWithYear() {
+ assertEquals(2020, DateUtilExtensions.getAt(calendar, Calendar.YEAR));
+ }
+
+ @Test
+ void testGetAtCalendarWithMonth() {
+ assertEquals(Calendar.JUNE, DateUtilExtensions.getAt(calendar,
Calendar.MONTH));
+ }
+
+ @Test
+ void testGetAtCalendarWithCollection() {
+ List<Integer> fields = Arrays.asList(Calendar.YEAR, Calendar.MONTH);
+ List<Integer> result = DateUtilExtensions.getAt(calendar, fields);
+ assertEquals(2, result.size());
+ assertEquals(2020, result.get(0));
+ assertEquals(Calendar.JUNE, result.get(1));
+ }
+
+ @Test
+ void testGetAtCalendarWithNestedCollection() {
+ Collection<Object> fields = Arrays.asList(
+ Calendar.YEAR,
+ Arrays.asList(Calendar.MONTH, Calendar.DAY_OF_MONTH)
+ );
+ List<Integer> result = DateUtilExtensions.getAt(calendar, fields);
+ assertEquals(3, result.size());
+ assertEquals(2020, result.get(0));
+ assertEquals(Calendar.JUNE, result.get(1));
+ assertEquals(15, result.get(2));
+ }
+
+ // putAt tests
+ @Test
+ void testPutAtCalendar() {
+ DateUtilExtensions.putAt(calendar, Calendar.YEAR, 2025);
+ assertEquals(2025, calendar.get(Calendar.YEAR));
+ }
+
+ @Test
+ void testPutAtDate() {
+ DateUtilExtensions.putAt(date, Calendar.YEAR, 2025);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ assertEquals(2025, cal.get(Calendar.YEAR));
+ }
+
+ // set with Map tests
+ @Test
+ void testSetCalendarWithMap() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put(Calendar.YEAR, 2025);
+ updates.put(Calendar.MONTH, Calendar.DECEMBER);
+ DateUtilExtensions.set(calendar, updates);
+ assertEquals(2025, calendar.get(Calendar.YEAR));
+ assertEquals(Calendar.DECEMBER, calendar.get(Calendar.MONTH));
+ }
+
+ @Test
+ void testSetCalendarWithStringKeys() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("year", 2025);
+ updates.put("month", Calendar.DECEMBER);
+ updates.put("date", 25);
+ DateUtilExtensions.set(calendar, updates);
+ assertEquals(2025, calendar.get(Calendar.YEAR));
+ assertEquals(Calendar.DECEMBER, calendar.get(Calendar.MONTH));
+ assertEquals(25, calendar.get(Calendar.DATE));
+ }
+
+ @Test
+ void testSetCalendarWithDayOfMonth() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("dayOfMonth", 20);
+ DateUtilExtensions.set(calendar, updates);
+ assertEquals(20, calendar.get(Calendar.DATE));
+ }
+
+ @Test
+ void testSetCalendarWithHourOfDay() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("hourOfDay", 14);
+ DateUtilExtensions.set(calendar, updates);
+ assertEquals(14, calendar.get(Calendar.HOUR_OF_DAY));
+ }
+
+ @Test
+ void testSetCalendarWithMinute() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("minute", 45);
+ DateUtilExtensions.set(calendar, updates);
+ assertEquals(45, calendar.get(Calendar.MINUTE));
+ }
+
+ @Test
+ void testSetCalendarWithSecond() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("second", 30);
+ DateUtilExtensions.set(calendar, updates);
+ assertEquals(30, calendar.get(Calendar.SECOND));
+ }
+
+ @Test
+ void testSetDate() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("year", 2025);
+ DateUtilExtensions.set(date, updates);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ assertEquals(2025, cal.get(Calendar.YEAR));
+ }
+
+ // updated/copyWith tests
+ @Test
+ void testUpdatedCalendar() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put(Calendar.YEAR, 2025);
+ Calendar result = DateUtilExtensions.updated(calendar, updates);
+ assertEquals(2025, result.get(Calendar.YEAR));
+ assertEquals(2020, calendar.get(Calendar.YEAR)); // original unchanged
+ }
+
+ @Test
+ void testCopyWithCalendar() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put(Calendar.YEAR, 2025);
+ Calendar result = DateUtilExtensions.copyWith(calendar, updates);
+ assertEquals(2025, result.get(Calendar.YEAR));
+ assertEquals(2020, calendar.get(Calendar.YEAR)); // original unchanged
+ }
+
+ @Test
+ void testUpdatedDate() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("year", 2025);
+ Date result = DateUtilExtensions.updated(date, updates);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(2025, cal.get(Calendar.YEAR));
+ }
+
+ @Test
+ void testCopyWithDate() {
+ Map<Object, Integer> updates = new HashMap<>();
+ updates.put("year", 2025);
+ Date result = DateUtilExtensions.copyWith(date, updates);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(2025, cal.get(Calendar.YEAR));
+ }
+
+ // next/previous tests for Date
+ @Test
+ void testNextDate() {
+ Date result = DateUtilExtensions.next(date);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(16, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ @Test
+ void testPreviousDate() {
+ Date result = DateUtilExtensions.previous(date);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(14, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ // next/previous tests for Calendar
+ @Test
+ void testNextCalendar() {
+ Calendar result = DateUtilExtensions.next(calendar);
+ assertEquals(16, result.get(Calendar.DAY_OF_MONTH));
+ assertEquals(15, calendar.get(Calendar.DAY_OF_MONTH)); // original
unchanged
+ }
+
+ @Test
+ void testPreviousCalendar() {
+ Calendar result = DateUtilExtensions.previous(calendar);
+ assertEquals(14, result.get(Calendar.DAY_OF_MONTH));
+ assertEquals(15, calendar.get(Calendar.DAY_OF_MONTH)); // original
unchanged
+ }
+
+ // next/previous tests for java.sql.Date
+ @Test
+ void testNextSqlDate() {
+ java.sql.Date sqlDate = new java.sql.Date(date.getTime());
+ java.sql.Date result = DateUtilExtensions.next(sqlDate);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(16, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ @Test
+ void testPreviousSqlDate() {
+ java.sql.Date sqlDate = new java.sql.Date(date.getTime());
+ java.sql.Date result = DateUtilExtensions.previous(sqlDate);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(14, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ // plus/minus tests for Date
+ @Test
+ void testPlusDate() {
+ Date result = DateUtilExtensions.plus(date, 5);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(20, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ @Test
+ void testMinusDate() {
+ Date result = DateUtilExtensions.minus(date, 5);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(10, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ // plus/minus tests for java.sql.Date
+ @Test
+ void testPlusSqlDate() {
+ java.sql.Date sqlDate = new java.sql.Date(date.getTime());
+ java.sql.Date result = DateUtilExtensions.plus(sqlDate, 5);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(20, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ @Test
+ void testMinusSqlDate() {
+ java.sql.Date sqlDate = new java.sql.Date(date.getTime());
+ java.sql.Date result = DateUtilExtensions.minus(sqlDate, 5);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(10, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ // plus/minus tests for Timestamp
+ @Test
+ void testPlusTimestamp() {
+ Timestamp ts = new Timestamp(date.getTime());
+ ts.setNanos(123456789);
+ Timestamp result = DateUtilExtensions.plus(ts, 5);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(20, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(123456789, result.getNanos()); // nanos preserved
+ }
+
+ @Test
+ void testMinusTimestamp() {
+ Timestamp ts = new Timestamp(date.getTime());
+ Timestamp result = DateUtilExtensions.minus(ts, 5);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(10, cal.get(Calendar.DAY_OF_MONTH));
+ }
+
+ // minus (date - date) tests
+ @Test
+ void testMinusCalendarFromCalendar() {
+ Calendar cal1 = Calendar.getInstance();
+ cal1.set(2020, Calendar.JUNE, 20);
+ Calendar cal2 = Calendar.getInstance();
+ cal2.set(2020, Calendar.JUNE, 15);
+
+ int days = DateUtilExtensions.minus(cal1, cal2);
+ assertEquals(5, days);
+ }
+
+ @Test
+ void testMinusCalendarFromCalendarNegative() {
+ Calendar cal1 = Calendar.getInstance();
+ cal1.set(2020, Calendar.JUNE, 10);
+ Calendar cal2 = Calendar.getInstance();
+ cal2.set(2020, Calendar.JUNE, 15);
+
+ int days = DateUtilExtensions.minus(cal1, cal2);
+ assertEquals(-5, days);
+ }
+
+ @Test
+ void testMinusCalendarAcrossYears() {
+ Calendar cal1 = Calendar.getInstance();
+ cal1.set(2021, Calendar.JANUARY, 1);
+ Calendar cal2 = Calendar.getInstance();
+ cal2.set(2020, Calendar.DECEMBER, 31);
+
+ int days = DateUtilExtensions.minus(cal1, cal2);
+ assertEquals(1, days);
+ }
+
+ @Test
+ void testMinusDateFromDate() {
+ Date date1 = DateUtilExtensions.plus(date, 10);
+ int days = DateUtilExtensions.minus(date1, date);
+ assertEquals(10, days);
+ }
+
+ // format tests
+ @Test
+ void testFormatDate() {
+ TimeZone originalTz = TimeZone.getDefault();
+ try {
+ TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
+ calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+ date = calendar.getTime();
+ String result = DateUtilExtensions.format(date, "yyyy-MM-dd");
+ assertEquals("2020-06-15", result);
+ } finally {
+ TimeZone.setDefault(originalTz);
+ }
+ }
+
+ @Test
+ void testFormatDateWithTimezone() {
+ String result = DateUtilExtensions.format(date, "yyyy-MM-dd",
TimeZone.getTimeZone("UTC"));
+ assertNotNull(result);
+ assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}"));
+ }
+
+ @Test
+ void testFormatCalendar() {
+ String result = DateUtilExtensions.format(calendar, "HH:mm:ss");
+ assertEquals("10:30:45", result);
+ }
+
+ // getDateString, getTimeString, getDateTimeString tests
+ @Test
+ void testGetDateString() {
+ String result = DateUtilExtensions.getDateString(date);
+ assertNotNull(result);
+ assertFalse(result.isEmpty());
+ }
+
+ @Test
+ void testGetTimeString() {
+ String result = DateUtilExtensions.getTimeString(date);
+ assertNotNull(result);
+ assertFalse(result.isEmpty());
+ }
+
+ @Test
+ void testGetDateTimeString() {
+ String result = DateUtilExtensions.getDateTimeString(date);
+ assertNotNull(result);
+ assertFalse(result.isEmpty());
+ }
+
+ // clearTime tests
+ @Test
+ void testClearTimeDate() {
+ Date result = DateUtilExtensions.clearTime(date);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(0, cal.get(Calendar.MINUTE));
+ assertEquals(0, cal.get(Calendar.SECOND));
+ assertEquals(0, cal.get(Calendar.MILLISECOND));
+ }
+
+ @Test
+ void testClearTimeSqlDate() {
+ java.sql.Date sqlDate = new java.sql.Date(date.getTime());
+ java.sql.Date result = DateUtilExtensions.clearTime(sqlDate);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(result);
+ assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
+ }
+
+ @Test
+ void testClearTimeCalendar() {
+ Calendar result = DateUtilExtensions.clearTime(calendar);
+ assertEquals(0, result.get(Calendar.HOUR_OF_DAY));
+ assertEquals(0, result.get(Calendar.MINUTE));
+ assertEquals(0, result.get(Calendar.SECOND));
+ assertSame(calendar, result); // modifies in place
+ }
+
+ // upto tests
+ @Test
+ void testUptoDate() {
+ Date start = date;
+ Date end = DateUtilExtensions.plus(date, 3);
+
+ List<Date> collected = new ArrayList<>();
+ DateUtilExtensions.upto(start, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ collected.add((Date) args[0]);
+ return null;
+ }
+ });
+
+ assertEquals(4, collected.size()); // inclusive: 15, 16, 17, 18
+ }
+
+ @Test
+ void testUptoDateThrowsWhenEndBeforeStart() {
+ Date start = date;
+ Date end = DateUtilExtensions.minus(date, 1);
+
+ assertThrows(GroovyRuntimeException.class, () ->
+ DateUtilExtensions.upto(start, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ return null;
+ }
+ }));
+ }
+
+ @Test
+ void testUptoCalendar() {
+ Calendar end =
DateUtilExtensions.next(DateUtilExtensions.next(calendar));
+
+ List<Calendar> collected = new ArrayList<>();
+ DateUtilExtensions.upto(calendar, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ collected.add((Calendar) args[0]);
+ return null;
+ }
+ });
+
+ assertEquals(3, collected.size());
+ }
+
+ @Test
+ void testUptoCalendarThrowsWhenEndBeforeStart() {
+ Calendar end = DateUtilExtensions.previous(calendar);
+
+ assertThrows(GroovyRuntimeException.class, () ->
+ DateUtilExtensions.upto(calendar, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ return null;
+ }
+ }));
+ }
+
+ // downto tests
+ @Test
+ void testDowntoDate() {
+ Date start = date;
+ Date end = DateUtilExtensions.minus(date, 2);
+
+ List<Date> collected = new ArrayList<>();
+ DateUtilExtensions.downto(start, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ collected.add((Date) args[0]);
+ return null;
+ }
+ });
+
+ assertEquals(3, collected.size()); // inclusive: 15, 14, 13
+ }
+
+ @Test
+ void testDowntoDateThrowsWhenEndAfterStart() {
+ Date start = date;
+ Date end = DateUtilExtensions.plus(date, 1);
+
+ assertThrows(GroovyRuntimeException.class, () ->
+ DateUtilExtensions.downto(start, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ return null;
+ }
+ }));
+ }
+
+ @Test
+ void testDowntoCalendar() {
+ Calendar end =
DateUtilExtensions.previous(DateUtilExtensions.previous(calendar));
+
+ List<Calendar> collected = new ArrayList<>();
+ DateUtilExtensions.downto(calendar, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ collected.add((Calendar) args[0]);
+ return null;
+ }
+ });
+
+ assertEquals(3, collected.size());
+ }
+
+ @Test
+ void testDowntoCalendarThrowsWhenEndAfterStart() {
+ Calendar end = DateUtilExtensions.next(calendar);
+
+ assertThrows(GroovyRuntimeException.class, () ->
+ DateUtilExtensions.downto(calendar, end, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ return null;
+ }
+ }));
+ }
+
+ // Edge case tests
+ @Test
+ void testPlusZeroDays() {
+ Date result = DateUtilExtensions.plus(date, 0);
+ assertEquals(date.getTime(), result.getTime());
+ }
+
+ @Test
+ void testMinusNegativeDays() {
+ Date result = DateUtilExtensions.minus(date, -5);
+ Date expected = DateUtilExtensions.plus(date, 5);
+ assertEquals(expected.getTime(), result.getTime());
+ }
+
+ @Test
+ void testUptoSameDate() {
+ List<Date> collected = new ArrayList<>();
+ DateUtilExtensions.upto(date, date, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ collected.add((Date) args[0]);
+ return null;
+ }
+ });
+ assertEquals(1, collected.size());
+ }
+
+ @Test
+ void testDowntoSameDate() {
+ List<Date> collected = new ArrayList<>();
+ DateUtilExtensions.downto(date, date, new Closure<Void>(null) {
+ @Override
+ public Void call(Object... args) {
+ collected.add((Date) args[0]);
+ return null;
+ }
+ });
+ assertEquals(1, collected.size());
+ }
+}
diff --git
a/subprojects/groovy-json/src/test/java/groovy/json/JsonSlurperClassicJUnit5Test.java
b/subprojects/groovy-json/src/test/java/groovy/json/JsonSlurperClassicJUnit5Test.java
new file mode 100644
index 0000000000..3905883819
--- /dev/null
+++
b/subprojects/groovy-json/src/test/java/groovy/json/JsonSlurperClassicJUnit5Test.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.json;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for JsonSlurperClassic class.
+ */
+class JsonSlurperClassicJUnit5Test {
+
+ private JsonSlurperClassic slurper;
+
+ @BeforeEach
+ void setUp() {
+ slurper = new JsonSlurperClassic();
+ }
+
+ // parseText tests
+ @Test
+ void testParseTextWithNull() {
+ assertThrows(IllegalArgumentException.class, () ->
slurper.parseText(null));
+ }
+
+ @Test
+ void testParseTextWithEmptyString() {
+ assertThrows(IllegalArgumentException.class, () ->
slurper.parseText(""));
+ }
+
+ @Test
+ void testParseTextSimpleObject() {
+ Map result = (Map) slurper.parseText("{\"name\":\"John\"}");
+ assertEquals("John", result.get("name"));
+ }
+
+ @Test
+ void testParseTextSimpleArray() {
+ List result = (List) slurper.parseText("[1, 2, 3]");
+ assertEquals(3, result.size());
+ assertEquals(1, result.get(0));
+ }
+
+ @Test
+ void testParseTextNestedObject() {
+ String json = "{\"person\":{\"name\":\"John\",\"age\":30}}";
+ Map result = (Map) slurper.parseText(json);
+ Map person = (Map) result.get("person");
+ assertEquals("John", person.get("name"));
+ assertEquals(30, person.get("age"));
+ }
+
+ @Test
+ void testParseTextNestedArray() {
+ String json = "{\"matrix\":[[1,2],[3,4]]}";
+ Map result = (Map) slurper.parseText(json);
+ List matrix = (List) result.get("matrix");
+ assertEquals(2, matrix.size());
+ assertEquals(List.of(1, 2), matrix.get(0));
+ }
+
+ @Test
+ void testParseTextWithAllTypes() {
+ String json =
"{\"string\":\"hello\",\"number\":42,\"float\":3.14,\"bool\":true,\"null\":null}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("hello", result.get("string"));
+ assertEquals(42, result.get("number"));
+ // JsonSlurperClassic uses BigDecimal for decimals
+ assertEquals(new java.math.BigDecimal("3.14"), result.get("float"));
+ assertEquals(true, result.get("bool"));
+ assertNull(result.get("null"));
+ }
+
+ @Test
+ void testParseTextEmptyObject() {
+ Map result = (Map) slurper.parseText("{}");
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ void testParseTextEmptyArray() {
+ List result = (List) slurper.parseText("[]");
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ void testParseTextArrayOfObjects() {
+ String json = "[{\"id\":1},{\"id\":2}]";
+ List result = (List) slurper.parseText(json);
+ assertEquals(2, result.size());
+ assertEquals(1, ((Map) result.get(0)).get("id"));
+ }
+
+ @Test
+ void testParseTextWithEscapedCharacters() {
+ String json = "{\"message\":\"Hello\\nWorld\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("Hello\nWorld", result.get("message"));
+ }
+
+ @Test
+ void testParseTextWithUnicode() {
+ String json = "{\"greeting\":\"Hello \\u4e16\\u754c\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("Hello 世界", result.get("greeting"));
+ }
+
+ @Test
+ void testParseTextInvalidJson() {
+ assertThrows(JsonException.class, () -> slurper.parseText("not json"));
+ }
+
+ @Test
+ void testParseTextInvalidStartToken() {
+ assertThrows(JsonException.class, () -> slurper.parseText("\"just a
string\""));
+ }
+
+ // parse(Reader) tests
+ @Test
+ void testParseReader() {
+ StringReader reader = new StringReader("{\"key\":\"value\"}");
+ Map result = (Map) slurper.parse(reader);
+ assertEquals("value", result.get("key"));
+ }
+
+ @Test
+ void testParseReaderArray() {
+ StringReader reader = new StringReader("[1, 2, 3]");
+ List result = (List) slurper.parse(reader);
+ assertEquals(3, result.size());
+ }
+
+ // parse(File) tests
+ @Test
+ void testParseFile(@TempDir Path tempDir) throws Exception {
+ Path jsonFile = tempDir.resolve("test.json");
+ Files.writeString(jsonFile, "{\"name\":\"test\"}");
+
+ Map result = (Map) slurper.parse(jsonFile.toFile());
+ assertEquals("test", result.get("name"));
+ }
+
+ @Test
+ void testParseFileWithCharset(@TempDir Path tempDir) throws Exception {
+ Path jsonFile = tempDir.resolve("test.json");
+ Files.write(jsonFile,
"{\"name\":\"test\"}".getBytes(StandardCharsets.UTF_8));
+
+ Map result = (Map) slurper.parse(jsonFile.toFile(), "UTF-8");
+ assertEquals("test", result.get("name"));
+ }
+
+ @Test
+ void testParseFileNonExistent() {
+ File nonExistent = new File("non_existent_file.json");
+ assertThrows(JsonException.class, () -> slurper.parse(nonExistent));
+ }
+
+ // Complex JSON tests
+ @Test
+ void testParseComplexStructure() {
+ String json = """
+ {
+ "users": [
+ {"name": "Alice", "age": 25, "active": true},
+ {"name": "Bob", "age": 30, "active": false}
+ ],
+ "metadata": {
+ "total": 2,
+ "page": 1
+ }
+ }
+ """;
+ Map result = (Map) slurper.parseText(json);
+
+ List users = (List) result.get("users");
+ assertEquals(2, users.size());
+
+ Map alice = (Map) users.get(0);
+ assertEquals("Alice", alice.get("name"));
+ assertEquals(25, alice.get("age"));
+ assertEquals(true, alice.get("active"));
+
+ Map metadata = (Map) result.get("metadata");
+ assertEquals(2, metadata.get("total"));
+ }
+
+ @Test
+ void testParseNegativeNumbers() {
+ String json = "{\"value\":-42,\"float\":-3.14}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals(-42, result.get("value"));
+ // JsonSlurperClassic uses BigDecimal for decimals
+ assertEquals(new java.math.BigDecimal("-3.14"), result.get("float"));
+ }
+
+ @Test
+ void testParseScientificNotation() {
+ String json = "{\"value\":1.23e10}";
+ Map result = (Map) slurper.parseText(json);
+ // JsonSlurperClassic uses BigDecimal for decimal notation
+ assertEquals(new java.math.BigDecimal("1.23E+10"),
result.get("value"));
+ }
+
+ @Test
+ void testParseBooleans() {
+ String json = "{\"yes\":true,\"no\":false}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals(true, result.get("yes"));
+ assertEquals(false, result.get("no"));
+ }
+
+ @Test
+ void testParseMixedArray() {
+ String json = "[1, \"two\", true, null, {\"key\":\"value\"}]";
+ List result = (List) slurper.parseText(json);
+ assertEquals(5, result.size());
+ assertEquals(1, result.get(0));
+ assertEquals("two", result.get(1));
+ assertEquals(true, result.get(2));
+ assertNull(result.get(3));
+ assertTrue(result.get(4) instanceof Map);
+ }
+
+ @Test
+ void testParseWithTrailingCommaInObject() {
+ // JsonSlurperClassic appears to be lenient with trailing commas
+ Map result = (Map) slurper.parseText("{\"key\":\"value\",}");
+ assertEquals("value", result.get("key"));
+ }
+
+ @Test
+ void testParseWithTrailingCommaInArray() {
+ // JsonSlurperClassic appears to be lenient with trailing commas
+ List result = (List) slurper.parseText("[1, 2, 3,]");
+ assertEquals(3, result.size());
+ }
+
+ @Test
+ void testParseDeepNesting() {
+ String json = "{\"a\":{\"b\":{\"c\":{\"d\":{\"e\":\"deep\"}}}}}";
+ Map result = (Map) slurper.parseText(json);
+ Map a = (Map) result.get("a");
+ Map b = (Map) a.get("b");
+ Map c = (Map) b.get("c");
+ Map d = (Map) c.get("d");
+ assertEquals("deep", d.get("e"));
+ }
+
+ @Test
+ void testParseDeepArrayNesting() {
+ String json = "[[[[\"deep\"]]]]";
+ List result = (List) slurper.parseText(json);
+ List l1 = (List) result.get(0);
+ List l2 = (List) l1.get(0);
+ List l3 = (List) l2.get(0);
+ assertEquals("deep", l3.get(0));
+ }
+
+ @Test
+ void testParseWhitespace() {
+ String json = " { \"key\" : \"value\" } ";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("value", result.get("key"));
+ }
+
+ @Test
+ void testParseMultilineJson() {
+ String json = """
+ {
+ "key": "value"
+ }
+ """;
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("value", result.get("key"));
+ }
+
+ @Test
+ void testParseLargeNumbers() {
+ String json = "{\"big\":9999999999999999999}";
+ Map result = (Map) slurper.parseText(json);
+ assertNotNull(result.get("big"));
+ }
+
+ @Test
+ void testParseZero() {
+ String json = "{\"zero\":0}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals(0, result.get("zero"));
+ }
+
+ @Test
+ void testParseEmptyStringValue() {
+ String json = "{\"empty\":\"\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("", result.get("empty"));
+ }
+
+ @Test
+ void testParseSpecialCharactersInString() {
+ String json = "{\"special\":\"tab\\there\\nnewline\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("tab\there\nnewline", result.get("special"));
+ }
+
+ @Test
+ void testParseQuoteInString() {
+ String json = "{\"quote\":\"say \\\"hello\\\"\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("say \"hello\"", result.get("quote"));
+ }
+
+ @Test
+ void testParseBackslashInString() {
+ String json = "{\"path\":\"C:\\\\Users\\\\test\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("C:\\Users\\test", result.get("path"));
+ }
+
+ @Test
+ void testParseSlashInString() {
+ String json = "{\"url\":\"http:\\/\\/example.com\"}";
+ Map result = (Map) slurper.parseText(json);
+ assertEquals("http://example.com", result.get("url"));
+ }
+}
diff --git
a/subprojects/groovy-json/src/test/java/groovy/json/JsonSlurperJUnit5Test.java
b/subprojects/groovy-json/src/test/java/groovy/json/JsonSlurperJUnit5Test.java
new file mode 100644
index 0000000000..391585e881
--- /dev/null
+++
b/subprojects/groovy-json/src/test/java/groovy/json/JsonSlurperJUnit5Test.java
@@ -0,0 +1,399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package groovy.json;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for JsonSlurper class.
+ */
+class JsonSlurperJUnit5Test {
+
+ @TempDir
+ Path tempDir;
+
+ @Test
+ void testParseTextSimpleObject() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"name\":\"John\",\"age\":30}");
+
+ assertNotNull(result);
+ assertTrue(result instanceof Map);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("John", map.get("name"));
+ assertEquals(30, map.get("age"));
+ }
+
+ @Test
+ void testParseTextSimpleArray() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("[1, 2, 3, 4, 5]");
+
+ assertNotNull(result);
+ assertTrue(result instanceof List);
+ List<?> list = (List<?>) result;
+ assertEquals(5, list.size());
+ assertEquals(1, list.get(0));
+ assertEquals(5, list.get(4));
+ }
+
+ @Test
+ void testParseTextNestedObject() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result =
slurper.parseText("{\"person\":{\"name\":\"Jane\",\"address\":{\"city\":\"NYC\"}}}");
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ Map<?, ?> person = (Map<?, ?>) map.get("person");
+ assertEquals("Jane", person.get("name"));
+ Map<?, ?> address = (Map<?, ?>) person.get("address");
+ assertEquals("NYC", address.get("city"));
+ }
+
+ @Test
+ void testParseTextWithNull() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"value\":null}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertNull(map.get("value"));
+ }
+
+ @Test
+ void testParseTextWithBoolean() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result =
slurper.parseText("{\"active\":true,\"deleted\":false}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals(true, map.get("active"));
+ assertEquals(false, map.get("deleted"));
+ }
+
+ @Test
+ void testParseTextWithFloat() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"price\":19.99}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ Object price = map.get("price");
+ assertTrue(price instanceof Number);
+ assertEquals(19.99, ((Number) price).doubleValue(), 0.001);
+ }
+
+ @Test
+ void testParseTextEmptyObject() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{}");
+
+ assertTrue(result instanceof Map);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertTrue(map.isEmpty());
+ }
+
+ @Test
+ void testParseTextEmptyArray() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("[]");
+
+ assertTrue(result instanceof List);
+ List<?> list = (List<?>) result;
+ assertTrue(list.isEmpty());
+ }
+
+ @Test
+ void testParseTextNullThrowsException() {
+ JsonSlurper slurper = new JsonSlurper();
+ assertThrows(IllegalArgumentException.class, () ->
slurper.parseText(null));
+ }
+
+ @Test
+ void testParseTextEmptyStringThrowsException() {
+ JsonSlurper slurper = new JsonSlurper();
+ assertThrows(IllegalArgumentException.class, () ->
slurper.parseText(""));
+ }
+
+ @Test
+ void testParseReader() {
+ JsonSlurper slurper = new JsonSlurper();
+ Reader reader = new StringReader("{\"test\":123}");
+ Object result = slurper.parse(reader);
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals(123, map.get("test"));
+ }
+
+ @Test
+ void testParseReaderNullThrowsException() {
+ JsonSlurper slurper = new JsonSlurper();
+ assertThrows(IllegalArgumentException.class, () ->
slurper.parse((Reader) null));
+ }
+
+ @Test
+ void testParseInputStream() {
+ JsonSlurper slurper = new JsonSlurper();
+ InputStream is = new
ByteArrayInputStream("{\"key\":\"value\"}".getBytes(StandardCharsets.UTF_8));
+ Object result = slurper.parse(is);
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("value", map.get("key"));
+ }
+
+ @Test
+ void testParseInputStreamWithCharset() {
+ JsonSlurper slurper = new JsonSlurper();
+ InputStream is = new
ByteArrayInputStream("{\"key\":\"value\"}".getBytes(StandardCharsets.UTF_8));
+ Object result = slurper.parse(is, "UTF-8");
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("value", map.get("key"));
+ }
+
+ @Test
+ void testParseFile() throws IOException {
+ JsonSlurper slurper = new JsonSlurper();
+ Path jsonFile = tempDir.resolve("test.json");
+ Files.writeString(jsonFile, "{\"file\":\"test\"}");
+
+ Object result = slurper.parse(jsonFile.toFile());
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("test", map.get("file"));
+ }
+
+ @Test
+ void testParseFileWithCharset() throws IOException {
+ JsonSlurper slurper = new JsonSlurper();
+ Path jsonFile = tempDir.resolve("test2.json");
+ Files.writeString(jsonFile, "{\"file\":\"test2\"}");
+
+ Object result = slurper.parse(jsonFile.toFile(), "UTF-8");
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("test2", map.get("file"));
+ }
+
+ @Test
+ void testParsePath() throws IOException {
+ JsonSlurper slurper = new JsonSlurper();
+ Path jsonFile = tempDir.resolve("test3.json");
+ Files.writeString(jsonFile, "{\"path\":\"testing\"}");
+
+ Object result = slurper.parse(jsonFile);
+
+ assertNotNull(result);
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("testing", map.get("path"));
+ }
+
+ @Test
+ void testGetAndSetType() {
+ JsonSlurper slurper = new JsonSlurper();
+
+ assertEquals(JsonParserType.CHAR_BUFFER, slurper.getType());
+
+ slurper.setType(JsonParserType.INDEX_OVERLAY);
+ assertEquals(JsonParserType.INDEX_OVERLAY, slurper.getType());
+
+ slurper.setType(JsonParserType.LAX);
+ assertEquals(JsonParserType.LAX, slurper.getType());
+ }
+
+ @Test
+ void testGetAndSetChop() {
+ JsonSlurper slurper = new JsonSlurper();
+
+ assertFalse(slurper.isChop());
+
+ slurper.setChop(true);
+ assertTrue(slurper.isChop());
+
+ slurper.setChop(false);
+ assertFalse(slurper.isChop());
+ }
+
+ @Test
+ void testGetAndSetLazyChop() {
+ JsonSlurper slurper = new JsonSlurper();
+
+ assertTrue(slurper.isLazyChop());
+
+ slurper.setLazyChop(false);
+ assertFalse(slurper.isLazyChop());
+
+ slurper.setLazyChop(true);
+ assertTrue(slurper.isLazyChop());
+ }
+
+ @Test
+ void testGetAndSetCheckDates() {
+ JsonSlurper slurper = new JsonSlurper();
+
+ assertTrue(slurper.isCheckDates());
+
+ slurper.setCheckDates(false);
+ assertFalse(slurper.isCheckDates());
+
+ slurper.setCheckDates(true);
+ assertTrue(slurper.isCheckDates());
+ }
+
+ @Test
+ void testGetAndSetMaxSizeForInMemory() {
+ JsonSlurper slurper = new JsonSlurper();
+
+ assertEquals(2000000, slurper.getMaxSizeForInMemory());
+
+ slurper.setMaxSizeForInMemory(1000000);
+ assertEquals(1000000, slurper.getMaxSizeForInMemory());
+ }
+
+ @Test
+ void testFluentAPI() {
+ JsonSlurper slurper = new JsonSlurper()
+ .setType(JsonParserType.INDEX_OVERLAY)
+ .setChop(true)
+ .setLazyChop(false)
+ .setCheckDates(false)
+ .setMaxSizeForInMemory(500000);
+
+ assertEquals(JsonParserType.INDEX_OVERLAY, slurper.getType());
+ assertTrue(slurper.isChop());
+ assertFalse(slurper.isLazyChop());
+ assertFalse(slurper.isCheckDates());
+ assertEquals(500000, slurper.getMaxSizeForInMemory());
+ }
+
+ @Test
+ void testParseWithIndexOverlayType() {
+ JsonSlurper slurper = new
JsonSlurper().setType(JsonParserType.INDEX_OVERLAY);
+ Object result = slurper.parseText("{\"type\":\"overlay\"}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("overlay", map.get("type"));
+ }
+
+ @Test
+ void testParseWithLaxType() {
+ JsonSlurper slurper = new JsonSlurper().setType(JsonParserType.LAX);
+ Object result = slurper.parseText("{\"type\":\"lax\"}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("lax", map.get("type"));
+ }
+
+ @Test
+ void testParseWithCharacterSourceType() {
+ JsonSlurper slurper = new
JsonSlurper().setType(JsonParserType.CHARACTER_SOURCE);
+ Object result = slurper.parseText("{\"type\":\"source\"}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("source", map.get("type"));
+ }
+
+ @Test
+ void testParseStringWithEscapedCharacters() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result =
slurper.parseText("{\"text\":\"line1\\nline2\\ttab\"}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ String text = (String) map.get("text");
+ assertTrue(text.contains("\n"));
+ assertTrue(text.contains("\t"));
+ }
+
+ @Test
+ void testParseStringWithUnicode() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"text\":\"Hello \\u0041\"}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals("Hello A", map.get("text"));
+ }
+
+ @Test
+ void testParseLargeNumber() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"big\":9999999999999999999}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertNotNull(map.get("big"));
+ }
+
+ @Test
+ void testParseNegativeNumber() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"negative\":-42}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ assertEquals(-42, map.get("negative"));
+ }
+
+ @Test
+ void testParseScientificNotation() {
+ JsonSlurper slurper = new JsonSlurper();
+ Object result = slurper.parseText("{\"sci\":1.5e10}");
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ Object sci = map.get("sci");
+ assertTrue(sci instanceof Number);
+ assertEquals(1.5e10, ((Number) sci).doubleValue(), 1e5);
+ }
+
+ @Test
+ void testParseComplexStructure() {
+ JsonSlurper slurper = new JsonSlurper();
+ String json =
"{\"users\":[{\"name\":\"Alice\",\"roles\":[\"admin\",\"user\"]},{\"name\":\"Bob\",\"roles\":[\"user\"]}]}";
+ Object result = slurper.parseText(json);
+
+ Map<?, ?> map = (Map<?, ?>) result;
+ List<?> users = (List<?>) map.get("users");
+ assertEquals(2, users.size());
+
+ Map<?, ?> alice = (Map<?, ?>) users.get(0);
+ assertEquals("Alice", alice.get("name"));
+ List<?> aliceRoles = (List<?>) alice.get("roles");
+ assertEquals(2, aliceRoles.size());
+ }
+
+ @Test
+ void testJsonParserTypeValues() {
+ JsonParserType[] types = JsonParserType.values();
+ assertTrue(types.length >= 4);
+
+ assertEquals(JsonParserType.CHAR_BUFFER,
JsonParserType.valueOf("CHAR_BUFFER"));
+ assertEquals(JsonParserType.INDEX_OVERLAY,
JsonParserType.valueOf("INDEX_OVERLAY"));
+ assertEquals(JsonParserType.LAX, JsonParserType.valueOf("LAX"));
+ assertEquals(JsonParserType.CHARACTER_SOURCE,
JsonParserType.valueOf("CHARACTER_SOURCE"));
+ }
+}
diff --git
a/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/LazyMapJUnit5Test.java
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/LazyMapJUnit5Test.java
new file mode 100644
index 0000000000..2cad5e06c7
--- /dev/null
+++
b/subprojects/groovy-json/src/test/java/org/apache/groovy/json/internal/LazyMapJUnit5Test.java
@@ -0,0 +1,378 @@
+/*
+ * 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.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * JUnit 5 tests for LazyMap class.
+ */
+class LazyMapJUnit5Test {
+
+ private LazyMap map;
+
+ @BeforeEach
+ void setUp() {
+ map = new LazyMap();
+ }
+
+ // Constructor tests
+ @Test
+ void testDefaultConstructor() {
+ LazyMap lazyMap = new LazyMap();
+ assertTrue(lazyMap.isEmpty());
+ assertEquals(0, lazyMap.size());
+ }
+
+ @Test
+ void testConstructorWithInitialSize() {
+ LazyMap lazyMap = new LazyMap(10);
+ assertTrue(lazyMap.isEmpty());
+ assertEquals(0, lazyMap.size());
+ }
+
+ // put tests
+ @Test
+ void testPut() {
+ assertNull(map.put("key1", "value1"));
+ assertEquals(1, map.size());
+ }
+
+ @Test
+ void testPutReturnsPreviousValue() {
+ map.put("key", "value1");
+ Object previous = map.put("key", "value2");
+ assertEquals("value1", previous);
+ assertEquals("value2", map.get("key"));
+ }
+
+ @Test
+ void testPutMultipleEntries() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ map.put("key3", "value3");
+ assertEquals(3, map.size());
+ }
+
+ @Test
+ void testPutWithNullKey() {
+ map.put(null, "value");
+ assertEquals("value", map.get(null));
+ }
+
+ @Test
+ void testPutWithNullValue() {
+ map.put("key", null);
+ assertNull(map.get("key"));
+ assertTrue(map.containsKey("key"));
+ }
+
+ @Test
+ void testPutReplaceNullKey() {
+ map.put(null, "value1");
+ Object previous = map.put(null, "value2");
+ assertEquals("value1", previous);
+ assertEquals("value2", map.get(null));
+ }
+
+ // get tests
+ @Test
+ void testGet() {
+ map.put("key", "value");
+ assertEquals("value", map.get("key"));
+ }
+
+ @Test
+ void testGetNonExistentKey() {
+ assertNull(map.get("nonexistent"));
+ }
+
+ @Test
+ void testGetTriggersMapBuild() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ // get should trigger the internal map to be built
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ // size and isEmpty tests
+ @Test
+ void testSize() {
+ assertEquals(0, map.size());
+ map.put("key1", "value1");
+ assertEquals(1, map.size());
+ map.put("key2", "value2");
+ assertEquals(2, map.size());
+ }
+
+ @Test
+ void testIsEmpty() {
+ assertTrue(map.isEmpty());
+ map.put("key", "value");
+ assertFalse(map.isEmpty());
+ }
+
+ @Test
+ void testIsEmptyAfterClear() {
+ map.put("key", "value");
+ map.clear();
+ assertTrue(map.isEmpty());
+ }
+
+ // containsKey tests
+ @Test
+ void testContainsKey() {
+ map.put("key", "value");
+ assertTrue(map.containsKey("key"));
+ assertFalse(map.containsKey("nonexistent"));
+ }
+
+ @Test
+ void testContainsKeyWithNullKey() {
+ map.put(null, "value");
+ assertTrue(map.containsKey(null));
+ }
+
+ // containsValue tests
+ @Test
+ void testContainsValue() {
+ map.put("key", "value");
+ assertTrue(map.containsValue("value"));
+ assertFalse(map.containsValue("nonexistent"));
+ }
+
+ @Test
+ void testContainsValueWithNullValue() {
+ map.put("key", null);
+ assertTrue(map.containsValue(null));
+ }
+
+ // remove tests
+ @Test
+ void testRemove() {
+ map.put("key", "value");
+ Object removed = map.remove("key");
+ assertEquals("value", removed);
+ assertFalse(map.containsKey("key"));
+ }
+
+ @Test
+ void testRemoveNonExistent() {
+ assertNull(map.remove("nonexistent"));
+ }
+
+ // clear tests
+ @Test
+ void testClearBeforeBuild() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ map.clear();
+ assertEquals(0, map.size());
+ assertTrue(map.isEmpty());
+ }
+
+ @Test
+ void testClearAfterBuild() {
+ map.put("key", "value");
+ map.get("key"); // trigger build
+ map.clear();
+ assertEquals(0, map.size());
+ }
+
+ // putAll tests
+ @Test
+ void testPutAll() {
+ Map<String, Object> other = new HashMap<>();
+ other.put("key1", "value1");
+ other.put("key2", "value2");
+ map.putAll(other);
+ assertEquals(2, map.size());
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ }
+
+ // keySet tests
+ @Test
+ void testKeySet() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ Set<String> keys = map.keySet();
+ assertEquals(2, keys.size());
+ assertTrue(keys.contains("key1"));
+ assertTrue(keys.contains("key2"));
+ }
+
+ // values tests
+ @Test
+ void testValues() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ Collection<Object> values = map.values();
+ assertEquals(2, values.size());
+ assertTrue(values.contains("value1"));
+ assertTrue(values.contains("value2"));
+ }
+
+ // entrySet tests
+ @Test
+ void testEntrySet() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ Set<Map.Entry<String, Object>> entries = map.entrySet();
+ assertEquals(2, entries.size());
+ }
+
+ // equals and hashCode tests
+ @Test
+ void testEquals() {
+ map.put("key", "value");
+ Map<String, Object> other = new HashMap<>();
+ other.put("key", "value");
+ assertEquals(map, other);
+ }
+
+ @Test
+ void testHashCode() {
+ map.put("key", "value");
+ Map<String, Object> other = new HashMap<>();
+ other.put("key", "value");
+ assertEquals(map.hashCode(), other.hashCode());
+ }
+
+ // toString tests
+ @Test
+ void testToString() {
+ map.put("key", "value");
+ String str = map.toString();
+ assertTrue(str.contains("key"));
+ assertTrue(str.contains("value"));
+ }
+
+ // clone tests
+ @Test
+ void testCloneBeforeBuild() throws CloneNotSupportedException {
+ // clone returns null if map hasn't been built yet
+ Object cloned = map.clone();
+ assertNull(cloned);
+ }
+
+ @Test
+ void testCloneAfterBuild() throws CloneNotSupportedException {
+ map.put("key", "value");
+ map.get("key"); // trigger build
+ Object cloned = map.clone();
+ assertNotNull(cloned);
+ assertTrue(cloned instanceof Map);
+ }
+
+ // clearAndCopy tests
+ @Test
+ void testClearAndCopy() {
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+
+ LazyMap copy = map.clearAndCopy();
+
+ // Original should be cleared
+ assertEquals(0, map.size());
+
+ // Copy should have the original values
+ assertEquals(2, copy.size());
+ assertEquals("value1", copy.get("key1"));
+ assertEquals("value2", copy.get("key2"));
+ }
+
+ // grow tests
+ @Test
+ void testGrow() {
+ String[] original = {"a", "b", "c"};
+ String[] grown = LazyMap.grow(original);
+ assertEquals(6, grown.length);
+ assertEquals("a", grown[0]);
+ assertEquals("b", grown[1]);
+ assertEquals("c", grown[2]);
+ }
+
+ // Test array growth when adding many items
+ @Test
+ void testArrayGrowthOnManyPuts() {
+ // Add more than initial capacity (5) to trigger growth
+ for (int i = 0; i < 10; i++) {
+ map.put("key" + i, "value" + i);
+ }
+ assertEquals(10, map.size());
+ for (int i = 0; i < 10; i++) {
+ assertEquals("value" + i, map.get("key" + i));
+ }
+ }
+
+ // Test lazy build behavior
+ @Test
+ void testLazyBuildBehavior() {
+ // Before any get/contains calls, the internal map isn't built
+ map.put("key1", "value1");
+ map.put("key2", "value2");
+ assertEquals(2, map.size()); // size works without building
+
+ // Trigger build
+ map.get("key1");
+
+ // Operations should still work after build
+ assertEquals("value1", map.get("key1"));
+ assertEquals("value2", map.get("key2"));
+ map.put("key3", "value3");
+ assertEquals(3, map.size());
+ }
+
+ // Test with different value types
+ @Test
+ void testWithDifferentValueTypes() {
+ map.put("string", "text");
+ map.put("number", 42);
+ map.put("bool", true);
+ map.put("null", null);
+ map.put("nested", new HashMap<String, Object>());
+
+ assertEquals("text", map.get("string"));
+ assertEquals(42, map.get("number"));
+ assertEquals(true, map.get("bool"));
+ assertNull(map.get("null"));
+ assertTrue(map.get("nested") instanceof Map);
+ }
+
+ // Test duplicate key handling
+ @Test
+ void testDuplicateKeyHandlingBeforeBuild() {
+ map.put("key", "value1");
+ map.put("key", "value2");
+ map.put("key", "value3");
+
+ assertEquals(1, map.size());
+ assertEquals("value3", map.get("key"));
+ }
+}