Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move string-based argument conversion tests to platform-test #4305

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public final Object convert(Object source, Class<?> targetType, ParameterContext
Class<?> declaringClass = context.getDeclaringExecutable().getDeclaringClass();
ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass);
try {
return ConversionSupport.convert((String) source, targetType, classLoader);
return convert((String) source, targetType, classLoader);
}
catch (ConversionException ex) {
throw new ArgumentConversionException(ex.getMessage(), ex);
Expand All @@ -90,4 +90,8 @@ public final Object convert(Object source, Class<?> targetType, ParameterContext
source.getClass().getTypeName(), targetType.getTypeName()));
}

Object convert(String source, Class<?> targetType, ClassLoader classLoader) {
return ConversionSupport.convert(source, targetType, classLoader);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,24 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.platform.commons.util.ClassLoaderUtils.getClassLoader;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.io.File;
import java.lang.Thread.State;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Currency;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.support.conversion.ConversionException;
import org.junit.platform.commons.test.TestClassLoader;

/**
Expand All @@ -59,6 +39,8 @@
*/
class DefaultArgumentConverterTests {

private final DefaultArgumentConverter underTest = spy(DefaultArgumentConverter.INSTANCE);

@Test
void isAwareOfNull() {
assertConverts(null, Object.class, null);
Expand Down Expand Up @@ -92,187 +74,51 @@ void isAwareOfWideningConversions() {
assertConverts(1.0f, double.class, 1.0f);
}

@Test
void convertsStringsToPrimitiveTypes() {
assertConverts("true", boolean.class, true);
assertConverts("false", boolean.class, false);
assertConverts("o", char.class, 'o');
assertConverts("1", byte.class, (byte) 1);
assertConverts("1_0", byte.class, (byte) 10);
assertConverts("1", short.class, (short) 1);
assertConverts("1_2", short.class, (short) 12);
assertConverts("42", int.class, 42);
assertConverts("700_050_000", int.class, 700_050_000);
assertConverts("42", long.class, 42L);
assertConverts("4_2", long.class, 42L);
assertConverts("42.23", float.class, 42.23f);
assertConverts("42.2_3", float.class, 42.23f);
assertConverts("42.23", double.class, 42.23);
assertConverts("42.2_3", double.class, 42.23);
}

@Test
void convertsStringsToPrimitiveWrapperTypes() {
assertConverts("true", Boolean.class, true);
assertConverts("false", Boolean.class, false);
assertConverts("o", Character.class, 'o');
assertConverts("1", Byte.class, (byte) 1);
assertConverts("1_0", Byte.class, (byte) 10);
assertConverts("1", Short.class, (short) 1);
assertConverts("1_2", Short.class, (short) 12);
assertConverts("42", Integer.class, 42);
assertConverts("700_050_000", Integer.class, 700_050_000);
assertConverts("42", Long.class, 42L);
assertConverts("4_2", Long.class, 42L);
assertConverts("42.23", Float.class, 42.23f);
assertConverts("42.2_3", Float.class, 42.23f);
assertConverts("42.23", Double.class, 42.23);
assertConverts("42.2_3", Double.class, 42.23);
}

@ParameterizedTest(name = "[{index}] {0}")
@ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class,
double.class, void.class })
void throwsExceptionForNullToPrimitiveTypeConversion(Class<?> type) {
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert(null, type)) //
.withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName());
}

@ParameterizedTest(name = "[{index}] {0}")
@ValueSource(classes = { Boolean.class, Character.class, Short.class, Byte.class, Integer.class, Long.class,
Float.class, Double.class })
void throwsExceptionWhenConvertingTheWordNullToPrimitiveWrapperType(Class<?> type) {
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("null", type)) //
.withMessage("Failed to convert String \"null\" to type " + type.getCanonicalName());
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("NULL", type)) //
.withMessage("Failed to convert String \"NULL\" to type " + type.getCanonicalName());
verify(underTest, never()).convert(any(), any(), any(ClassLoader.class));
}

@Test
void throwsExceptionOnInvalidStringForPrimitiveTypes() {
void throwsExceptionForNonStringsConversion() {
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("ab", char.class)) //
.withMessage("Failed to convert String \"ab\" to type char") //
.havingCause() //
.havingCause() //
.withMessage("String must have length of 1: ab");

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("tru", boolean.class)) //
.withMessage("Failed to convert String \"tru\" to type boolean") //
.havingCause() //
.havingCause() //
.withMessage("String must be 'true' or 'false' (ignoring case): tru");

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("null", boolean.class)) //
.withMessage("Failed to convert String \"null\" to type boolean") //
.havingCause() //
.havingCause() //
.withMessage("String must be 'true' or 'false' (ignoring case): null");

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("NULL", boolean.class)) //
.withMessage("Failed to convert String \"NULL\" to type boolean") //
.havingCause() //
.havingCause() //
.withMessage("String must be 'true' or 'false' (ignoring case): NULL");
}

@Test
void throwsExceptionWhenImplicitConverstionIsUnsupported() {
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("foo", Enigma.class)) //
.withMessage("No built-in converter for source type java.lang.String and target type %s",
.isThrownBy(() -> convert(new Enigma(), String.class)) //
.withMessage("No built-in converter for source type %s and target type java.lang.String",
Enigma.class.getName());

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert(new Enigma(), int[].class)) //
.withMessage("No built-in converter for source type %s and target type int[]", Enigma.class.getName());

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert(new long[] {}, int[].class)) //
.withMessage("No built-in converter for source type long[] and target type int[]");

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert(new String[] {}, boolean.class)) //
.withMessage("No built-in converter for source type java.lang.String[] and target type boolean");

assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert(Class.class, int[].class)) //
.withMessage("No built-in converter for source type java.lang.Class and target type int[]");
}

/**
* @since 5.4
*/
@Test
@SuppressWarnings("OctalInteger") // We test parsing octal integers here as well as hex.
void convertsEncodedStringsToIntegralTypes() {
assertConverts("0x1f", byte.class, (byte) 0x1F);
assertConverts("-0x1F", byte.class, (byte) -0x1F);
assertConverts("010", byte.class, (byte) 010);

assertConverts("0x1f00", short.class, (short) 0x1F00);
assertConverts("-0x1F00", short.class, (short) -0x1F00);
assertConverts("01000", short.class, (short) 01000);

assertConverts("0x1f000000", int.class, 0x1F000000);
assertConverts("-0x1F000000", int.class, -0x1F000000);
assertConverts("010000000", int.class, 010000000);

assertConverts("0x1f000000000", long.class, 0x1F000000000L);
assertConverts("-0x1F000000000", long.class, -0x1F000000000L);
assertConverts("0100000000000", long.class, 0100000000000L);
verify(underTest, never()).convert(any(), any(), any(ClassLoader.class));
}

@Test
void convertsStringsToEnumConstants() {
assertConverts("DAYS", TimeUnit.class, TimeUnit.DAYS);
}

// --- java.io and java.nio ------------------------------------------------
void delegatesStringsConversion() {
doReturn(null).when(underTest).convert(any(), any(), any(ClassLoader.class));

@Test
void convertsStringToCharset() {
assertConverts("ISO-8859-1", Charset.class, StandardCharsets.ISO_8859_1);
assertConverts("UTF-8", Charset.class, StandardCharsets.UTF_8);
}
convert("value", int.class);

@Test
void convertsStringToFile() {
assertConverts("file", File.class, new File("file"));
assertConverts("/file", File.class, new File("/file"));
assertConverts("/some/file", File.class, new File("/some/file"));
verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class));
}

@Test
void convertsStringToPath() {
assertConverts("path", Path.class, Paths.get("path"));
assertConverts("/path", Path.class, Paths.get("/path"));
assertConverts("/some/path", Path.class, Paths.get("/some/path"));
}
void throwsExceptionForDelegatedConversionFailure() {
ConversionException exception = new ConversionException("fail");
doThrow(exception).when(underTest).convert(any(), any(), any(ClassLoader.class));

// --- java.lang -----------------------------------------------------------
assertThatExceptionOfType(ArgumentConversionException.class) //
.isThrownBy(() -> convert("value", int.class)) //
.withCause(exception) //
.withMessage(exception.getMessage());

@Test
void convertsStringToClass() {
assertConverts("java.lang.Integer", Class.class, Integer.class);
assertConverts("java.lang.Void", Class.class, Void.class);
assertConverts("java.lang.Thread$State", Class.class, State.class);
assertConverts("byte", Class.class, byte.class);
assertConverts("void", Class.class, void.class);
assertConverts("char[]", Class.class, char[].class);
assertConverts("java.lang.Long[][]", Class.class, Long[][].class);
assertConverts("[[[I", Class.class, int[][][].class);
assertConverts("[[Ljava.lang.String;", Class.class, String[][].class);
verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class));
}

@Test
void convertsStringToClassWithCustomTypeFromDifferentClassLoader() throws Exception {
void delegatesStringToClassWithCustomTypeFromDifferentClassLoaderConversion() throws Exception {
String customTypeName = Enigma.class.getName();
try (var testClassLoader = TestClassLoader.forClasses(Enigma.class)) {
var customType = testClassLoader.loadClass(customTypeName);
Expand All @@ -281,79 +127,15 @@ void convertsStringToClassWithCustomTypeFromDifferentClassLoader() throws Except
var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").get();
assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader);

doReturn(customType).when(underTest).convert(any(), any(), any(ClassLoader.class));

var clazz = (Class<?>) convert(customTypeName, Class.class, parameterContext(declaringExecutable));
assertThat(clazz).isNotEqualTo(Enigma.class);
assertThat(clazz).isEqualTo(customType);
assertThat(clazz.getClassLoader()).isSameAs(testClassLoader);
}
}

// --- java.math -----------------------------------------------------------

@Test
void convertsStringToBigDecimal() {
assertConverts("123.456e789", BigDecimal.class, new BigDecimal("123.456e789"));
}

@Test
void convertsStringToBigInteger() {
assertConverts("1234567890123456789", BigInteger.class, new BigInteger("1234567890123456789"));
}

// --- java.net ------------------------------------------------------------

@Test
void convertsStringToURI() {
assertConverts("https://docs.oracle.com/en/java/javase/12/", URI.class,
URI.create("https://docs.oracle.com/en/java/javase/12/"));
}

@Test
void convertsStringToURL() throws Exception {
assertConverts("https://junit.org/junit5", URL.class, URI.create("https://junit.org/junit5").toURL());
}

// --- java.time -----------------------------------------------------------

@Test
void convertsStringsToJavaTimeInstances() {
assertConverts("PT1234.5678S", Duration.class, Duration.ofSeconds(1234, 567800000));
assertConverts("1970-01-01T00:00:00Z", Instant.class, Instant.ofEpochMilli(0));
assertConverts("2017-03-14", LocalDate.class, LocalDate.of(2017, 3, 14));
assertConverts("2017-03-14T12:34:56.789", LocalDateTime.class,
LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000));
assertConverts("12:34:56.789", LocalTime.class, LocalTime.of(12, 34, 56, 789_000_000));
assertConverts("--03-14", MonthDay.class, MonthDay.of(3, 14));
assertConverts("2017-03-14T12:34:56.789Z", OffsetDateTime.class,
OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC));
assertConverts("12:34:56.789Z", OffsetTime.class, OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC));
assertConverts("P2M6D", Period.class, Period.of(0, 2, 6));
assertConverts("2017", Year.class, Year.of(2017));
assertConverts("2017-03", YearMonth.class, YearMonth.of(2017, 3));
assertConverts("2017-03-14T12:34:56.789Z", ZonedDateTime.class,
ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC));
assertConverts("Europe/Berlin", ZoneId.class, ZoneId.of("Europe/Berlin"));
assertConverts("+02:30", ZoneOffset.class, ZoneOffset.ofHoursMinutes(2, 30));
}

// --- java.util -----------------------------------------------------------

@Test
void convertsStringToCurrency() {
assertConverts("JPY", Currency.class, Currency.getInstance("JPY"));
}

@Test
@SuppressWarnings("deprecation")
void convertsStringToLocale() {
assertConverts("en", Locale.class, Locale.ENGLISH);
assertConverts("en_us", Locale.class, new Locale(Locale.US.toString()));
}

@Test
void convertsStringToUUID() {
var uuid = "d043e930-7b3b-48e3-bdbe-5a3ccfb833db";
assertConverts(uuid, UUID.class, UUID.fromString(uuid));
verify(underTest).convert(customTypeName, Class.class, testClassLoader);
}
}

// -------------------------------------------------------------------------
Expand All @@ -364,14 +146,16 @@ private void assertConverts(Object input, Class<?> targetClass, Object expectedO
assertThat(result) //
.describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) //
.isEqualTo(expectedOutput);

verify(underTest, never()).convert(any(), any(), any(ClassLoader.class));
}

private Object convert(Object input, Class<?> targetClass) {
return convert(input, targetClass, parameterContext());
}

private Object convert(Object input, Class<?> targetClass, ParameterContext parameterContext) {
return DefaultArgumentConverter.INSTANCE.convert(input, targetClass, parameterContext);
return underTest.convert(input, targetClass, parameterContext);
}

private static ParameterContext parameterContext() {
Expand Down
Loading
Loading