|
| 1 | +package org.junit.platform.commons.support.conversion; |
| 2 | + |
| 3 | +import static org.assertj.core.api.Assertions.assertThat; |
| 4 | +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
| 5 | + |
| 6 | +import java.io.File; |
| 7 | +import java.lang.reflect.Method; |
| 8 | +import java.math.BigDecimal; |
| 9 | +import java.math.BigInteger; |
| 10 | +import java.net.URI; |
| 11 | +import java.net.URL; |
| 12 | +import java.nio.charset.Charset; |
| 13 | +import java.nio.charset.StandardCharsets; |
| 14 | +import java.nio.file.Path; |
| 15 | +import java.nio.file.Paths; |
| 16 | +import java.time.Duration; |
| 17 | +import java.time.Instant; |
| 18 | +import java.time.LocalDate; |
| 19 | +import java.time.LocalDateTime; |
| 20 | +import java.time.LocalTime; |
| 21 | +import java.time.MonthDay; |
| 22 | +import java.time.OffsetDateTime; |
| 23 | +import java.time.OffsetTime; |
| 24 | +import java.time.Period; |
| 25 | +import java.time.Year; |
| 26 | +import java.time.YearMonth; |
| 27 | +import java.time.ZoneId; |
| 28 | +import java.time.ZoneOffset; |
| 29 | +import java.time.ZonedDateTime; |
| 30 | +import java.util.Currency; |
| 31 | +import java.util.Locale; |
| 32 | +import java.util.UUID; |
| 33 | +import java.util.concurrent.TimeUnit; |
| 34 | + |
| 35 | +import org.junit.jupiter.api.Test; |
| 36 | +import org.junit.jupiter.params.ParameterizedTest; |
| 37 | +import org.junit.jupiter.params.provider.ValueSource; |
| 38 | +import org.junit.platform.commons.support.ReflectionSupport; |
| 39 | +import org.junit.platform.commons.test.TestClassLoader; |
| 40 | +import org.junit.platform.commons.util.ClassLoaderUtils; |
| 41 | + |
| 42 | +/** |
| 43 | + * Unit tests for {@link ConversionSupport}. |
| 44 | + * |
| 45 | + * @since 5.12 |
| 46 | + */ |
| 47 | +class ConversionSupportTests { |
| 48 | + |
| 49 | + @Test |
| 50 | + void isAwareOfNull() { |
| 51 | + assertConverts(null, Object.class, null); |
| 52 | + assertConverts(null, String.class, null); |
| 53 | + assertConverts(null, Boolean.class, null); |
| 54 | + } |
| 55 | + |
| 56 | + @Test |
| 57 | + void convertsStringsToPrimitiveTypes() { |
| 58 | + assertConverts("true", boolean.class, true); |
| 59 | + assertConverts("false", boolean.class, false); |
| 60 | + assertConverts("o", char.class, 'o'); |
| 61 | + assertConverts("1", byte.class, (byte) 1); |
| 62 | + assertConverts("1_0", byte.class, (byte) 10); |
| 63 | + assertConverts("1", short.class, (short) 1); |
| 64 | + assertConverts("1_2", short.class, (short) 12); |
| 65 | + assertConverts("42", int.class, 42); |
| 66 | + assertConverts("700_050_000", int.class, 700_050_000); |
| 67 | + assertConverts("42", long.class, 42L); |
| 68 | + assertConverts("4_2", long.class, 42L); |
| 69 | + assertConverts("42.23", float.class, 42.23f); |
| 70 | + assertConverts("42.2_3", float.class, 42.23f); |
| 71 | + assertConverts("42.23", double.class, 42.23); |
| 72 | + assertConverts("42.2_3", double.class, 42.23); |
| 73 | + } |
| 74 | + |
| 75 | + @Test |
| 76 | + void convertsStringsToPrimitiveWrapperTypes() { |
| 77 | + assertConverts("true", Boolean.class, true); |
| 78 | + assertConverts("false", Boolean.class, false); |
| 79 | + assertConverts("o", Character.class, 'o'); |
| 80 | + assertConverts("1", Byte.class, (byte) 1); |
| 81 | + assertConverts("1_0", Byte.class, (byte) 10); |
| 82 | + assertConverts("1", Short.class, (short) 1); |
| 83 | + assertConverts("1_2", Short.class, (short) 12); |
| 84 | + assertConverts("42", Integer.class, 42); |
| 85 | + assertConverts("700_050_000", Integer.class, 700_050_000); |
| 86 | + assertConverts("42", Long.class, 42L); |
| 87 | + assertConverts("4_2", Long.class, 42L); |
| 88 | + assertConverts("42.23", Float.class, 42.23f); |
| 89 | + assertConverts("42.2_3", Float.class, 42.23f); |
| 90 | + assertConverts("42.23", Double.class, 42.23); |
| 91 | + assertConverts("42.2_3", Double.class, 42.23); |
| 92 | + } |
| 93 | + |
| 94 | + @ParameterizedTest(name = "[{index}] {0}") |
| 95 | + @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class, |
| 96 | + double.class, void.class }) |
| 97 | + void throwsExceptionForNullToPrimitiveTypeConversion(Class<?> type) { |
| 98 | + assertThatExceptionOfType(ConversionException.class) // |
| 99 | + .isThrownBy(() -> convert(null, type)) // |
| 100 | + .withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName()); |
| 101 | + } |
| 102 | + |
| 103 | + @ParameterizedTest(name = "[{index}] {0}") |
| 104 | + @ValueSource(classes = { Boolean.class, Character.class, Short.class, Byte.class, Integer.class, Long.class, |
| 105 | + Float.class, Double.class }) |
| 106 | + void throwsExceptionWhenConvertingTheWordNullToPrimitiveWrapperType(Class<?> type) { |
| 107 | + assertThatExceptionOfType(ConversionException.class) // |
| 108 | + .isThrownBy(() -> convert("null", type)) // |
| 109 | + .withMessage("Failed to convert String \"null\" to type " + type.getCanonicalName()); |
| 110 | + assertThatExceptionOfType(ConversionException.class) // |
| 111 | + .isThrownBy(() -> convert("NULL", type)) // |
| 112 | + .withMessage("Failed to convert String \"NULL\" to type " + type.getCanonicalName()); |
| 113 | + } |
| 114 | + |
| 115 | + @Test |
| 116 | + void throwsExceptionOnInvalidStringForPrimitiveTypes() { |
| 117 | + assertThatExceptionOfType(ConversionException.class) // |
| 118 | + .isThrownBy(() -> convert("ab", char.class)) // |
| 119 | + .withMessage("Failed to convert String \"ab\" to type char") // |
| 120 | + .havingCause() // |
| 121 | + .withMessage("String must have length of 1: ab"); |
| 122 | + |
| 123 | + assertThatExceptionOfType(ConversionException.class) // |
| 124 | + .isThrownBy(() -> convert("tru", boolean.class)) // |
| 125 | + .withMessage("Failed to convert String \"tru\" to type boolean") // |
| 126 | + .havingCause() // |
| 127 | + .withMessage("String must be 'true' or 'false' (ignoring case): tru"); |
| 128 | + |
| 129 | + assertThatExceptionOfType(ConversionException.class) // |
| 130 | + .isThrownBy(() -> convert("null", boolean.class)) // |
| 131 | + .withMessage("Failed to convert String \"null\" to type boolean") // |
| 132 | + .havingCause() // |
| 133 | + .withMessage("String must be 'true' or 'false' (ignoring case): null"); |
| 134 | + |
| 135 | + assertThatExceptionOfType(ConversionException.class) // |
| 136 | + .isThrownBy(() -> convert("NULL", boolean.class)) // |
| 137 | + .withMessage("Failed to convert String \"NULL\" to type boolean") // |
| 138 | + .havingCause() // |
| 139 | + .withMessage("String must be 'true' or 'false' (ignoring case): NULL"); |
| 140 | + } |
| 141 | + |
| 142 | + @Test |
| 143 | + void throwsExceptionWhenImplicitConversionIsUnsupported() { |
| 144 | + assertThatExceptionOfType(ConversionException.class) // |
| 145 | + .isThrownBy(() -> convert("foo", Enigma.class)) // |
| 146 | + .withMessage("No built-in converter for source type java.lang.String and target type %s", |
| 147 | + Enigma.class.getName()); |
| 148 | + } |
| 149 | + |
| 150 | + /** |
| 151 | + * @since 5.4 |
| 152 | + */ |
| 153 | + @Test |
| 154 | + @SuppressWarnings("OctalInteger") // We test parsing octal integers here as well as hex. |
| 155 | + void convertsEncodedStringsToIntegralTypes() { |
| 156 | + assertConverts("0x1f", byte.class, (byte) 0x1F); |
| 157 | + assertConverts("-0x1F", byte.class, (byte) -0x1F); |
| 158 | + assertConverts("010", byte.class, (byte) 010); |
| 159 | + |
| 160 | + assertConverts("0x1f00", short.class, (short) 0x1F00); |
| 161 | + assertConverts("-0x1F00", short.class, (short) -0x1F00); |
| 162 | + assertConverts("01000", short.class, (short) 01000); |
| 163 | + |
| 164 | + assertConverts("0x1f000000", int.class, 0x1F000000); |
| 165 | + assertConverts("-0x1F000000", int.class, -0x1F000000); |
| 166 | + assertConverts("010000000", int.class, 010000000); |
| 167 | + |
| 168 | + assertConverts("0x1f000000000", long.class, 0x1F000000000L); |
| 169 | + assertConverts("-0x1F000000000", long.class, -0x1F000000000L); |
| 170 | + assertConverts("0100000000000", long.class, 0100000000000L); |
| 171 | + } |
| 172 | + |
| 173 | + @Test |
| 174 | + void convertsStringsToEnumConstants() { |
| 175 | + assertConverts("DAYS", TimeUnit.class, TimeUnit.DAYS); |
| 176 | + } |
| 177 | + |
| 178 | + // --- java.io and java.nio ------------------------------------------------ |
| 179 | + |
| 180 | + @Test |
| 181 | + void convertsStringToCharset() { |
| 182 | + assertConverts("ISO-8859-1", Charset.class, StandardCharsets.ISO_8859_1); |
| 183 | + assertConverts("UTF-8", Charset.class, StandardCharsets.UTF_8); |
| 184 | + } |
| 185 | + |
| 186 | + @Test |
| 187 | + void convertsStringToFile() { |
| 188 | + assertConverts("file", File.class, new File("file")); |
| 189 | + assertConverts("/file", File.class, new File("/file")); |
| 190 | + assertConverts("/some/file", File.class, new File("/some/file")); |
| 191 | + } |
| 192 | + |
| 193 | + @Test |
| 194 | + void convertsStringToPath() { |
| 195 | + assertConverts("path", Path.class, Paths.get("path")); |
| 196 | + assertConverts("/path", Path.class, Paths.get("/path")); |
| 197 | + assertConverts("/some/path", Path.class, Paths.get("/some/path")); |
| 198 | + } |
| 199 | + |
| 200 | + // --- java.lang ----------------------------------------------------------- |
| 201 | + |
| 202 | + @Test |
| 203 | + void convertsStringToClass() { |
| 204 | + assertConverts("java.lang.Integer", Class.class, Integer.class); |
| 205 | + assertConverts("java.lang.Void", Class.class, Void.class); |
| 206 | + assertConverts("java.lang.Thread$State", Class.class, Thread.State.class); |
| 207 | + assertConverts("byte", Class.class, byte.class); |
| 208 | + assertConverts("void", Class.class, void.class); |
| 209 | + assertConverts("char[]", Class.class, char[].class); |
| 210 | + assertConverts("java.lang.Long[][]", Class.class, Long[][].class); |
| 211 | + assertConverts("[[[I", Class.class, int[][][].class); |
| 212 | + assertConverts("[[Ljava.lang.String;", Class.class, String[][].class); |
| 213 | + } |
| 214 | + |
| 215 | + @Test |
| 216 | + void convertsStringToClassWithCustomTypeFromDifferentClassLoader() throws Exception { |
| 217 | + String customTypeName = Enigma.class.getName(); |
| 218 | + try (var testClassLoader = TestClassLoader.forClasses(Enigma.class)) { |
| 219 | + var customType = testClassLoader.loadClass(customTypeName); |
| 220 | + assertThat(customType.getClassLoader()).isSameAs(testClassLoader); |
| 221 | + |
| 222 | + var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").get(); |
| 223 | + assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader); |
| 224 | + |
| 225 | + var clazz = (Class<?>) convert(customTypeName, Class.class, classLoader(declaringExecutable)); |
| 226 | + assertThat(clazz).isNotEqualTo(Enigma.class); |
| 227 | + assertThat(clazz).isEqualTo(customType); |
| 228 | + assertThat(clazz.getClassLoader()).isSameAs(testClassLoader); |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + // --- java.math ----------------------------------------------------------- |
| 233 | + |
| 234 | + @Test |
| 235 | + void convertsStringToBigDecimal() { |
| 236 | + assertConverts("123.456e789", BigDecimal.class, new BigDecimal("123.456e789")); |
| 237 | + } |
| 238 | + |
| 239 | + @Test |
| 240 | + void convertsStringToBigInteger() { |
| 241 | + assertConverts("1234567890123456789", BigInteger.class, new BigInteger("1234567890123456789")); |
| 242 | + } |
| 243 | + |
| 244 | + // --- java.net ------------------------------------------------------------ |
| 245 | + |
| 246 | + @Test |
| 247 | + void convertsStringToURI() { |
| 248 | + assertConverts("https://docs.oracle.com/en/java/javase/12/", URI.class, |
| 249 | + URI.create("https://docs.oracle.com/en/java/javase/12/")); |
| 250 | + } |
| 251 | + |
| 252 | + @Test |
| 253 | + void convertsStringToURL() throws Exception { |
| 254 | + assertConverts("https://junit.org/junit5", URL.class, URI.create("https://junit.org/junit5").toURL()); |
| 255 | + } |
| 256 | + |
| 257 | + // --- java.time ----------------------------------------------------------- |
| 258 | + |
| 259 | + @Test |
| 260 | + void convertsStringsToJavaTimeInstances() { |
| 261 | + assertConverts("PT1234.5678S", Duration.class, Duration.ofSeconds(1234, 567800000)); |
| 262 | + assertConverts("1970-01-01T00:00:00Z", Instant.class, Instant.ofEpochMilli(0)); |
| 263 | + assertConverts("2017-03-14", LocalDate.class, LocalDate.of(2017, 3, 14)); |
| 264 | + assertConverts("2017-03-14T12:34:56.789", LocalDateTime.class, |
| 265 | + LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)); |
| 266 | + assertConverts("12:34:56.789", LocalTime.class, LocalTime.of(12, 34, 56, 789_000_000)); |
| 267 | + assertConverts("--03-14", MonthDay.class, MonthDay.of(3, 14)); |
| 268 | + assertConverts("2017-03-14T12:34:56.789Z", OffsetDateTime.class, |
| 269 | + OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); |
| 270 | + assertConverts("12:34:56.789Z", OffsetTime.class, OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)); |
| 271 | + assertConverts("P2M6D", Period.class, Period.of(0, 2, 6)); |
| 272 | + assertConverts("2017", Year.class, Year.of(2017)); |
| 273 | + assertConverts("2017-03", YearMonth.class, YearMonth.of(2017, 3)); |
| 274 | + assertConverts("2017-03-14T12:34:56.789Z", ZonedDateTime.class, |
| 275 | + ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)); |
| 276 | + assertConverts("Europe/Berlin", ZoneId.class, ZoneId.of("Europe/Berlin")); |
| 277 | + assertConverts("+02:30", ZoneOffset.class, ZoneOffset.ofHoursMinutes(2, 30)); |
| 278 | + } |
| 279 | + |
| 280 | + // --- java.util ----------------------------------------------------------- |
| 281 | + |
| 282 | + @Test |
| 283 | + void convertsStringToCurrency() { |
| 284 | + assertConverts("JPY", Currency.class, Currency.getInstance("JPY")); |
| 285 | + } |
| 286 | + |
| 287 | + @Test |
| 288 | + @SuppressWarnings("deprecation") |
| 289 | + void convertsStringToLocale() { |
| 290 | + assertConverts("en", Locale.class, Locale.ENGLISH); |
| 291 | + assertConverts("en_us", Locale.class, new Locale(Locale.US.toString())); |
| 292 | + } |
| 293 | + |
| 294 | + @Test |
| 295 | + void convertsStringToUUID() { |
| 296 | + var uuid = "d043e930-7b3b-48e3-bdbe-5a3ccfb833db"; |
| 297 | + assertConverts(uuid, UUID.class, UUID.fromString(uuid)); |
| 298 | + } |
| 299 | + |
| 300 | + // ------------------------------------------------------------------------- |
| 301 | + |
| 302 | + private void assertConverts(String input, Class<?> targetClass, Object expectedOutput) { |
| 303 | + var result = convert(input, targetClass); |
| 304 | + |
| 305 | + assertThat(result) // |
| 306 | + .describedAs(input + " --(" + targetClass.getName() + ")--> " + expectedOutput) // |
| 307 | + .isEqualTo(expectedOutput); |
| 308 | + } |
| 309 | + |
| 310 | + private Object convert(String input, Class<?> targetClass) { |
| 311 | + return convert(input, targetClass, classLoader()); |
| 312 | + } |
| 313 | + |
| 314 | + private Object convert(String input, Class<?> targetClass, ClassLoader classLoader) { |
| 315 | + return ConversionSupport.convert(input, targetClass, classLoader); |
| 316 | + } |
| 317 | + |
| 318 | + private static ClassLoader classLoader() { |
| 319 | + Method declaringExecutable = ReflectionSupport.findMethod(ConversionSupportTests.class, "foo").get(); |
| 320 | + return classLoader(declaringExecutable); |
| 321 | + } |
| 322 | + |
| 323 | + private static ClassLoader classLoader(Method declaringExecutable) { |
| 324 | + return ClassLoaderUtils.getClassLoader(declaringExecutable.getDeclaringClass()); |
| 325 | + } |
| 326 | + |
| 327 | + @SuppressWarnings("unused") |
| 328 | + private static void foo() { |
| 329 | + } |
| 330 | + |
| 331 | + private static class Enigma { |
| 332 | + |
| 333 | + @SuppressWarnings("unused") |
| 334 | + void foo() { |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | +} |
0 commit comments