diff --git a/.claude/skills/migrate-groovy-to-java/SKILL.md b/.claude/skills/migrate-groovy-to-java/SKILL.md index d75b8134607..c603bf227b6 100644 --- a/.claude/skills/migrate-groovy-to-java/SKILL.md +++ b/.claude/skills/migrate-groovy-to-java/SKILL.md @@ -32,10 +32,6 @@ When converting Groovy code to Java code, make sure that: - When translating Spock `Mock(...)` usage, use `libs.bundles.mockito` instead of writing manual recording/stub implementations TableTest usage - Dependency, if missing add: - - Groovy: testImplementation libs.tabletest - - Kotlin: testImplementation(libs.tabletest) - Import: `import org.tabletest.junit.TableTest;` JDK 8 rules: diff --git a/.claude/skills/migrate-junit-source-to-tabletest/SKILL.md b/.claude/skills/migrate-junit-source-to-tabletest/SKILL.md index ae4008039c5..797abfe6fbf 100644 --- a/.claude/skills/migrate-junit-source-to-tabletest/SKILL.md +++ b/.claude/skills/migrate-junit-source-to-tabletest/SKILL.md @@ -11,11 +11,6 @@ Process (do in this order): 4) Write each modified file once in full using Write (no incremental per-test edits). 5) Run module tests once and verify "BUILD SUCCESSFUL". If failed, inspect JUnit XML report. -Dependency: -- If missing, add: - - Groovy: testImplementation libs.tabletest - - Kotlin: testImplementation(libs.tabletest) - Import: `import org.tabletest.junit.TableTest;` JDK 8 rules: @@ -42,7 +37,8 @@ A) @CsvSource - If delimiter is ',' (default): replace ',' with '|' in rows. B) @ValueSource -- Convert to @TableTest with header from parameter name. +- Keep single-parameter tests on `@ValueSource` (and `@NullSource` when null cases are needed). +- Otherwise convert to @TableTest with header from parameter name. - Each value becomes one row. - Add "scenario" column using common sense for name. @@ -60,6 +56,11 @@ C) @MethodSource (convert only if values are representable as strings) - '' = empty string. - For String params that start with '[' or '{', quote to avoid collection parsing (prefer '[]'/'{}'). +D) @TypeConverter +- Use `@TypeConverter` for symbolic constants used by migrated table rows (e.g. `Long.MAX_VALUE`, `DDSpanId.MAX`). +- Prefer explicit one-case-one-return mappings. +- Prefer shared converter utilities (e.g. in `utils/test-utils`) when reuse across modules is likely. + Scenario handling: - If MethodSource includes a leading description string OR @ParameterizedTest(name=...) uses {0}, convert that to a scenario column and remove that parameter from method signature. diff --git a/components/json/build.gradle.kts b/components/json/build.gradle.kts index fda68a66ba8..ce67f74ff29 100644 --- a/components/json/build.gradle.kts +++ b/components/json/build.gradle.kts @@ -7,7 +7,3 @@ apply(from = "$rootDir/gradle/java.gradle") jmh { jmhVersion = libs.versions.jmh.get() } - -dependencies { - testImplementation(libs.tabletest) -} diff --git a/components/json/src/test/java/datadog/json/JsonMapperTest.java b/components/json/src/test/java/datadog/json/JsonMapperTest.java index d6cacd83b25..ffe973e52ca 100644 --- a/components/json/src/test/java/datadog/json/JsonMapperTest.java +++ b/components/json/src/test/java/datadog/json/JsonMapperTest.java @@ -17,6 +17,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; import org.tabletest.junit.Scenario; import org.tabletest.junit.TableTest; @@ -77,28 +79,16 @@ static Stream testMappingToJsonObjectArguments() { "{\"key1\":null,\"key2\":\"bar\",\"key3\":3,\"key4\":3456789123,\"key5\":3.142,\"key6\":3.141592653589793,\"key7\":true,\"key8\":\"toString\"}")); } - @TableTest({ - "Scenario | Json ", - "null | ", - "null string | 'null'", - "empty string | '' ", - "empty object | '{}' " - }) @ParameterizedTest(name = "test mapping to Map from empty JSON object: {0}") + @NullSource + @ValueSource(strings = {"null", "", "{}"}) void testMappingToMapFromEmptyJsonObject(String json) throws IOException { Map parsed = JsonMapper.fromJsonToMap(json); assertEquals(emptyMap(), parsed); } - // temporary disable spotless, will open issue to fix this. - // spotless:off - @TableTest({ - "Scenario | Json ", - "integer | 1 ", - "array | [1, 2]" - }) - // spotless:on @ParameterizedTest(name = "test mapping to Map from non-object JSON: {0}") + @ValueSource(strings = {"1", "[1, 2]"}) void testMappingToMapFromNonObjectJson(String json) { assertThrows(IOException.class, () -> JsonMapper.fromJsonToMap(json)); } @@ -138,14 +128,9 @@ void testMappingArrayToJsonArray(String ignoredScenario, String[] input, String assertArrayEquals(input != null ? input : new String[] {}, parsed); } - @TableTest({ - "Scenario | Json ", - "null | ", - "null string | 'null'", - "empty string | '' ", - "empty array | '[]' " - }) @ParameterizedTest(name = "test mapping to List from empty JSON object: {0}") + @NullSource + @ValueSource(strings = {"null", "", "[]"}) void testMappingToListFromEmptyJsonObject(String json) throws IOException { List parsed = JsonMapper.fromJsonToList(json); assertEquals(emptyList(), parsed); diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java b/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java index e6cc4719b88..38d30987c91 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java @@ -7,58 +7,61 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import java.math.BigInteger; import java.util.HashSet; import java.util.Set; -import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; +import org.tabletest.junit.TableTest; +import org.tabletest.junit.TypeConverterSources; +@TypeConverterSources(DDTraceApiTableTestConverters.class) class DDSpanIdTest { - @ParameterizedTest(name = "convert ids from/to String {0}") - @MethodSource("convertIdsFromToStringArguments") - void convertIdsFromToString(String displayName, String stringId, long expectedId) { + @TableTest({ + "scenario | stringId | expectedId ", + "zero | '0' | 0 ", + "one | '1' | 1 ", + "max | '18446744073709551615' | DDSpanId.MAX ", + "long max | '9223372036854775807' | Long.MAX_VALUE ", + "long max plus one | '9223372036854775808' | Long.MIN_VALUE " + }) + @ParameterizedTest(name = "convert ids from/to String [{index}]") + void convertIdsFromToString(String stringId, long expectedId) { long ddid = DDSpanId.from(stringId); assertEquals(expectedId, ddid); assertEquals(stringId, DDSpanId.toString(ddid)); } - static Stream convertIdsFromToStringArguments() { - return Stream.of( - Arguments.of("zero", "0", 0L), - Arguments.of("one", "1", 1L), - Arguments.of("max", "18446744073709551615", DDSpanId.MAX), - Arguments.of("long max", String.valueOf(Long.MAX_VALUE), Long.MAX_VALUE), - Arguments.of( - "long max plus one", - BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).toString(), - Long.MIN_VALUE)); - } - - @ParameterizedTest(name = "fail on illegal String {0}") - @MethodSource("failOnIllegalStringArguments") + @ParameterizedTest(name = "fail on illegal String [{index}]") + @NullSource + @ValueSource( + strings = { + "", + "-1", + "18446744073709551616", + "18446744073709551625", + "184467440737095516150", + "18446744073709551a1", + "184467440737095511a" + }) void failOnIllegalString(String stringId) { assertThrows(NumberFormatException.class, () -> DDSpanId.from(stringId)); } - static Stream failOnIllegalStringArguments() { - return Stream.of( - Arguments.of((Object) null), - Arguments.of(""), - Arguments.of("-1"), - Arguments.of("18446744073709551616"), - Arguments.of("18446744073709551625"), - Arguments.of("184467440737095516150"), - Arguments.of("18446744073709551a1"), - Arguments.of("184467440737095511a")); - } - - @ParameterizedTest(name = "convert ids from/to hex String {0}") - @MethodSource("convertIdsFromToHexStringArguments") + @TableTest({ + "scenario | hexId | expectedId ", + "zero | '0' | 0 ", + "one | '1' | 1 ", + "max | 'ffffffffffffffff' | DDSpanId.MAX ", + "long max | '7fffffffffffffff' | Long.MAX_VALUE ", + "long min | '8000000000000000' | Long.MIN_VALUE ", + "long min with leading zeros | '00008000000000000000' | Long.MIN_VALUE ", + "hex sample | 'cafebabe' | 3405691582 ", + "fifteen hex digits | '123456789abcdef' | 81985529216486895 " + }) + @ParameterizedTest(name = "convert ids from/to hex String [{index}]") void convertIdsFromToHexString(String hexId, long expectedId) { long ddid = DDSpanId.fromHex(hexId); String padded16 = @@ -73,27 +76,22 @@ void convertIdsFromToHexString(String hexId, long expectedId) { assertEquals(padded16, DDSpanId.toHexStringPadded(ddid)); } - static Stream convertIdsFromToHexStringArguments() { - return Stream.of( - Arguments.of("0", 0L), - Arguments.of("1", 1L), - Arguments.of(repeat("f", 16), DDSpanId.MAX), - Arguments.of("7" + repeat("f", 15), Long.MAX_VALUE), - Arguments.of("8" + repeat("0", 15), Long.MIN_VALUE), - Arguments.of(repeat("0", 4) + "8" + repeat("0", 15), Long.MIN_VALUE), - Arguments.of("cafebabe", 3405691582L), - Arguments.of("123456789abcdef", 81985529216486895L)); - } - - @ParameterizedTest(name = "convert ids from part of hex String {0}") - @MethodSource("convertIdsFromPartOfHexStringArguments") + @TableTest({ + "scenario | hexId | start | length | lowerCaseOnly | expectedId ", + "null input | | 1 | 1 | false | ", + "empty input | '' | 1 | 1 | false | ", + "negative start | '00' | -1 | 1 | false | ", + "zero length | '00' | 0 | 0 | false | ", + "single zero at index 0 | '00' | 0 | 1 | false | DDSpanId.ZERO", + "single zero at index 1 | '00' | 1 | 1 | false | DDSpanId.ZERO", + "single zero at index 1 duplicate | '00' | 1 | 1 | false | DDSpanId.ZERO", + "max lower-case | 'ffffffffffffffff' | 0 | 16 | true | DDSpanId.MAX ", + "upper-case rejected when lower-case only| 'ffffffffffffFfff' | 0 | 16 | true | ", + "upper-case accepted when lower disabled | 'ffffffffffffFfff' | 0 | 16 | false | DDSpanId.MAX " + }) + @ParameterizedTest(name = "convert ids from part of hex String [{index}]") void convertIdsFromPartOfHexString( - String displayName, - String hexId, - int start, - int length, - boolean lowerCaseOnly, - Long expectedId) { + String hexId, int start, int length, boolean lowerCaseOnly, Long expectedId) { Long parsedId = null; try { parsedId = DDSpanId.fromHex(hexId, start, length, lowerCaseOnly); @@ -108,48 +106,13 @@ void convertIdsFromPartOfHexString( } } - static Stream convertIdsFromPartOfHexStringArguments() { - return Stream.of( - Arguments.of("null input", null, 1, 1, false, null), - Arguments.of("empty input", "", 1, 1, false, null), - Arguments.of("negative start", "00", -1, 1, false, null), - Arguments.of("zero length", "00", 0, 0, false, null), - Arguments.of("single zero at index 0", "00", 0, 1, false, DDSpanId.ZERO), - Arguments.of("single zero at index 1", "00", 1, 1, false, DDSpanId.ZERO), - Arguments.of("single zero at index 1 duplicate", "00", 1, 1, false, DDSpanId.ZERO), - Arguments.of("max lower-case", repeat("f", 16), 0, 16, true, DDSpanId.MAX), - Arguments.of( - "upper-case rejected when lower-case only", - repeat("f", 12) + "Ffff", - 0, - 16, - true, - null), - Arguments.of( - "upper-case accepted when lower-case disabled", - repeat("f", 12) + "Ffff", - 0, - 16, - false, - DDSpanId.MAX)); - } - - @ParameterizedTest(name = "fail on illegal hex String {0}") - @MethodSource("failOnIllegalHexStringArguments") + @ParameterizedTest(name = "fail on illegal hex String [{index}]") + @NullSource + @ValueSource(strings = {"", "-1", "10000000000000000", "ffffffffffffffzf", "fffffffffffffffz"}) void failOnIllegalHexString(String hexId) { assertThrows(NumberFormatException.class, () -> DDSpanId.fromHex(hexId)); } - static Stream failOnIllegalHexStringArguments() { - return Stream.of( - Arguments.of((Object) null), - Arguments.of(""), - Arguments.of("-1"), - Arguments.of("1" + repeat("0", 16)), - Arguments.of(repeat("f", 14) + "zf"), - Arguments.of(repeat("f", 15) + "z")); - } - @ParameterizedTest(name = "generate id with {0}") @ValueSource(strings = {"RANDOM", "SEQUENTIAL", "SECURE_RANDOM"}) void generateIdWithStrategy(String strategyName) { diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceApiTableTestConverters.java b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceApiTableTestConverters.java new file mode 100644 index 00000000000..e277fe8e2e7 --- /dev/null +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceApiTableTestConverters.java @@ -0,0 +1,51 @@ +package datadog.trace.api; + +import datadog.trace.test.util.TableTestTypeConverters; +import org.tabletest.junit.TypeConverter; + +/** TableTest converters shared by dd-trace-api test classes for unparsable constants. */ +public final class DDTraceApiTableTestConverters { + + private DDTraceApiTableTestConverters() {} + + @TypeConverter + public static long toLong(String value) { + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); + } + String token = value.trim(); + switch (token) { + case "DDSpanId.MAX": + return DDSpanId.MAX; + case "DDSpanId.ZERO": + return DDSpanId.ZERO; + default: + return TableTestTypeConverters.toLong(token); + } + } + + @TypeConverter + public static DD64bTraceId toDD64bTraceId(String value) { + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); + } + switch (value.trim()) { + case "DD64bTraceId.ZERO": + return DD64bTraceId.from(0L); + case "DD64bTraceId.ONE": + return DD64bTraceId.from(1L); + case "DD64bTraceId.MAX": + return DD64bTraceId.MAX; + case "DD64bTraceId.LONG_MAX": + return DD64bTraceId.from(Long.MAX_VALUE); + case "DD64bTraceId.LONG_MIN": + return DD64bTraceId.from(Long.MIN_VALUE); + case "DD64bTraceId.CAFEBABE": + return DD64bTraceId.from(3405691582L); + case "DD64bTraceId.HEX": + return DD64bTraceId.from(81985529216486895L); + default: + throw new IllegalArgumentException("Unsupported DD64bTraceId token: " + value); + } + } +} diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java index 176b0aeed3f..3dcec4d583b 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java @@ -5,24 +5,29 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; -import java.math.BigInteger; -import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.tabletest.junit.TableTest; +import org.tabletest.junit.TypeConverterSources; +@TypeConverterSources(DDTraceApiTableTestConverters.class) class DDTraceIdTest { - @ParameterizedTest(name = "convert 64-bit ids from/to long {1} and check strings") - @MethodSource("convert64BitIdsFromToLongAndCheckStringsArguments") + @TableTest({ + "scenario | longId | expectedString | expectedHex ", + "zero | 0 | '0' | '00000000000000000000000000000000'", + "one | 1 | '1' | '00000000000000000000000000000001'", + "minus one | -1 | '18446744073709551615'| '0000000000000000ffffffffffffffff'", + "long max | Long.MAX_VALUE | '9223372036854775807' | '00000000000000007fffffffffffffff'", + "long min | Long.MIN_VALUE | '9223372036854775808' | '00000000000000008000000000000000'" + }) + @ParameterizedTest(name = "convert 64-bit ids from/to long and check strings [{index}]") void convert64BitIdsFromToLongAndCheckStrings( - String displayName, - long longId, - DD64bTraceId expectedId, - String expectedString, - String expectedHex) { + long longId, String expectedString, String expectedHex) { DD64bTraceId ddid = DD64bTraceId.from(longId); + DD64bTraceId expectedId = DD64bTraceId.from(expectedString); DDTraceId defaultDdid = DDTraceId.from(longId); assertEquals(expectedId, ddid); @@ -33,72 +38,50 @@ void convert64BitIdsFromToLongAndCheckStrings( assertEquals(expectedHex, ddid.toHexString()); } - static Stream convert64BitIdsFromToLongAndCheckStringsArguments() { - return Stream.of( - Arguments.of("zero", 0L, DD64bTraceId.ZERO, "0", repeat("0", 32)), - Arguments.of("one", 1L, DD64bTraceId.ONE, "1", repeat("0", 31) + "1"), - Arguments.of( - "minus one", - -1L, - DD64bTraceId.MAX, - "18446744073709551615", - repeat("0", 16) + repeat("f", 16)), - Arguments.of( - "long max", - Long.MAX_VALUE, - DD64bTraceId.from(Long.MAX_VALUE), - "9223372036854775807", - repeat("0", 16) + "7" + repeat("f", 15)), - Arguments.of( - "long min", - Long.MIN_VALUE, - DD64bTraceId.from(Long.MIN_VALUE), - "9223372036854775808", - repeat("0", 16) + "8" + repeat("0", 15))); - } - - @ParameterizedTest(name = "convert 64-bit ids from/to String representation: {1}") - @MethodSource("convert64BitIdsFromToStringRepresentationArguments") - void convert64BitIdsFromToStringRepresentation( - String displayName, String stringId, DD64bTraceId expectedId) { + @TableTest({ + "scenario | stringId | expectedId ", + "zero | '0' | DD64bTraceId.ZERO ", + "one | '1' | DD64bTraceId.ONE ", + "max | '18446744073709551615' | DD64bTraceId.MAX ", + "long max | '9223372036854775807' | DD64bTraceId.LONG_MAX", + "long max plus one | '9223372036854775808' | DD64bTraceId.LONG_MIN" + }) + @ParameterizedTest(name = "convert 64-bit ids from/to String representation [{index}]") + void convert64BitIdsFromToStringRepresentation(String stringId, DD64bTraceId expectedId) { DD64bTraceId ddid = DD64bTraceId.from(stringId); assertEquals(expectedId, ddid); assertEquals(stringId, ddid.toString()); } - static Stream convert64BitIdsFromToStringRepresentationArguments() { - return Stream.of( - Arguments.of("zero", "0", DD64bTraceId.ZERO), - Arguments.of("one", "1", DD64bTraceId.ONE), - Arguments.of("max", "18446744073709551615", DD64bTraceId.MAX), - Arguments.of("long max", String.valueOf(Long.MAX_VALUE), DD64bTraceId.from(Long.MAX_VALUE)), - Arguments.of( - "long max plus one", - BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).toString(), - DD64bTraceId.from(Long.MIN_VALUE))); - } - - @ParameterizedTest(name = "fail parsing illegal 64-bit id String representation: {0}") - @MethodSource("failParsingIllegal64BitIdStringRepresentationArguments") + @ParameterizedTest(name = "fail parsing illegal 64-bit id String representation [{index}]") + @NullSource + @ValueSource( + strings = { + "", + "-1", + "18446744073709551616", + "18446744073709551625", + "184467440737095516150", + "18446744073709551a1", + "184467440737095511a" + }) void failParsingIllegal64BitIdStringRepresentation(String stringId) { assertThrows(NumberFormatException.class, () -> DD64bTraceId.from(stringId)); } - static Stream failParsingIllegal64BitIdStringRepresentationArguments() { - return Stream.of( - Arguments.of((Object) null), - Arguments.of(""), - Arguments.of("-1"), - Arguments.of("18446744073709551616"), - Arguments.of("18446744073709551625"), - Arguments.of("184467440737095516150"), - Arguments.of("18446744073709551a1"), - Arguments.of("184467440737095511a")); - } - - @ParameterizedTest(name = "convert 64-bit ids from/to hex String representation: {0}") - @MethodSource("convert64BitIdsFromToHexStringRepresentationArguments") + @TableTest({ + "scenario | hexId | expectedId ", + "zero | '0' | DD64bTraceId.ZERO ", + "one | '1' | DD64bTraceId.ONE ", + "max | 'ffffffffffffffff' | DD64bTraceId.MAX ", + "long max | '7fffffffffffffff' | DD64bTraceId.LONG_MAX", + "long min | '8000000000000000' | DD64bTraceId.LONG_MIN", + "long min with leading zeros | '00008000000000000000' | DD64bTraceId.LONG_MIN", + "hex sample | 'cafebabe' | DD64bTraceId.CAFEBABE", + "fifteen hex digits | '123456789abcdef' | DD64bTraceId.HEX " + }) + @ParameterizedTest(name = "convert 64-bit ids from/to hex String representation [{index}]") void convert64BitIdsFromToHexStringRepresentation(String hexId, DD64bTraceId expectedId) { DD64bTraceId ddid = DD64bTraceId.fromHex(hexId); String padded16 = @@ -111,38 +94,39 @@ void convert64BitIdsFromToHexStringRepresentation(String hexId, DD64bTraceId exp assertEquals(padded32, ddid.toHexStringPadded(32)); } - static Stream convert64BitIdsFromToHexStringRepresentationArguments() { - return Stream.of( - Arguments.of("0", DD64bTraceId.ZERO), - Arguments.of("1", DD64bTraceId.ONE), - Arguments.of(repeat("f", 16), DD64bTraceId.MAX), - Arguments.of("7" + repeat("f", 15), DD64bTraceId.from(Long.MAX_VALUE)), - Arguments.of("8" + repeat("0", 15), DD64bTraceId.from(Long.MIN_VALUE)), - Arguments.of(repeat("0", 4) + "8" + repeat("0", 15), DD64bTraceId.from(Long.MIN_VALUE)), - Arguments.of("cafebabe", DD64bTraceId.from(3405691582L)), - Arguments.of("123456789abcdef", DD64bTraceId.from(81985529216486895L))); - } - - @ParameterizedTest(name = "fail parsing illegal 64-bit hexadecimal String representation: {0}") - @MethodSource("failParsingIllegal64BitHexadecimalStringRepresentationArguments") + @ParameterizedTest( + name = "fail parsing illegal 64-bit hexadecimal String representation [{index}]") + @NullSource + @ValueSource(strings = {"", "-1", "10000000000000000", "ffffffffffffffzf", "fffffffffffffffz"}) void failParsingIllegal64BitHexadecimalStringRepresentation(String hexId) { assertThrows(NumberFormatException.class, () -> DD64bTraceId.fromHex(hexId)); } - static Stream failParsingIllegal64BitHexadecimalStringRepresentationArguments() { - return Stream.of( - Arguments.of((Object) null), - Arguments.of(""), - Arguments.of("-1"), - Arguments.of("1" + repeat("0", 16)), - Arguments.of(repeat("f", 14) + "zf"), - Arguments.of(repeat("f", 15) + "z")); - } - - @ParameterizedTest(name = "convert 128-bit ids from/to hexadecimal String representation {3}") - @MethodSource("convert128BitIdsFromToHexadecimalStringRepresentationArguments") + @TableTest({ + "scenario | highOrderBits | lowOrderBits | hexId ", + "both long min | Long.MIN_VALUE | Long.MIN_VALUE | '80000000000000008000000000000000'", + "high long min low one | Long.MIN_VALUE | 1 | '80000000000000000000000000000001'", + "high long min low long max | Long.MIN_VALUE | Long.MAX_VALUE | '80000000000000007fffffffffffffff'", + "high one low long min | 1 | Long.MIN_VALUE | '00000000000000018000000000000000'", + "high one low one | 1 | 1 | '00000000000000010000000000000001'", + "high one low long max | 1 | Long.MAX_VALUE | '00000000000000017fffffffffffffff'", + "high long max low long min | Long.MAX_VALUE | Long.MIN_VALUE | '7fffffffffffffff8000000000000000'", + "high long max low one | Long.MAX_VALUE | 1 | '7fffffffffffffff0000000000000001'", + "high long max low long max | Long.MAX_VALUE | Long.MAX_VALUE | '7fffffffffffffff7fffffffffffffff'", + "all zeros length one | 0 | 0 | '0' ", + "all zeros length sixteen | 0 | 0 | '0000000000000000' ", + "all zeros length seventeen | 0 | 0 | '00000000000000000' ", + "all zeros length thirty-two | 0 | 0 | '00000000000000000000000000000000'", + "low fifteen | 0 | 15 | 'f' ", + "low minus one | 0 | -1 | 'ffffffffffffffff' ", + "high fifteen low minus one | 15 | -1 | 'fffffffffffffffff' ", + "all f | -1 | -1 | 'ffffffffffffffffffffffffffffffff'", + "hex literal | 1311768467463790320 | 1311768467463790320 | '123456789abcdef0123456789abcdef0'" + }) + @ParameterizedTest( + name = "convert 128-bit ids from/to hexadecimal String representation [{index}]") void convert128BitIdsFromToHexadecimalStringRepresentation( - String displayName, long highOrderBits, long lowOrderBits, String hexId) { + long highOrderBits, long lowOrderBits, String hexId) { DDTraceId parsedId = DD128bTraceId.fromHex(hexId); DDTraceId id = DD128bTraceId.from(highOrderBits, lowOrderBits); String paddedHexId = leftPadWithZeros(hexId, 32); @@ -156,110 +140,38 @@ void convert128BitIdsFromToHexadecimalStringRepresentation( assertEquals(Long.toUnsignedString(lowOrderBits), parsedId.toString()); } - static Stream convert128BitIdsFromToHexadecimalStringRepresentationArguments() { - return Stream.of( - Arguments.of( - "both long min", - Long.MIN_VALUE, - Long.MIN_VALUE, - "8" + repeat("0", 15) + "8" + repeat("0", 15)), - Arguments.of( - "high long min low one", - Long.MIN_VALUE, - 1L, - "8" + repeat("0", 15) + repeat("0", 15) + "1"), - Arguments.of( - "high long min low long max", - Long.MIN_VALUE, - Long.MAX_VALUE, - "8" + repeat("0", 15) + "7" + repeat("f", 15)), - Arguments.of( - "high one low long min", - 1L, - Long.MIN_VALUE, - repeat("0", 15) + "1" + "8" + repeat("0", 15)), - Arguments.of("high one low one", 1L, 1L, repeat("0", 15) + "1" + repeat("0", 15) + "1"), - Arguments.of( - "high one low long max", - 1L, - Long.MAX_VALUE, - repeat("0", 15) + "1" + "7" + repeat("f", 15)), - Arguments.of( - "high long max low long min", - Long.MAX_VALUE, - Long.MIN_VALUE, - "7" + repeat("f", 15) + "8" + repeat("0", 15)), - Arguments.of( - "high long max low one", - Long.MAX_VALUE, - 1L, - "7" + repeat("f", 15) + repeat("0", 15) + "1"), - Arguments.of( - "high long max low long max", - Long.MAX_VALUE, - Long.MAX_VALUE, - "7" + repeat("f", 15) + "7" + repeat("f", 15)), - Arguments.of("all zeros length one", 0L, 0L, repeat("0", 1)), - Arguments.of("all zeros length sixteen", 0L, 0L, repeat("0", 16)), - Arguments.of("all zeros length seventeen", 0L, 0L, repeat("0", 17)), - Arguments.of("all zeros length thirty-two", 0L, 0L, repeat("0", 32)), - Arguments.of("low fifteen", 0L, 15L, repeat("f", 1)), - Arguments.of("low minus one", 0L, -1L, repeat("f", 16)), - Arguments.of("high fifteen low minus one", 15L, -1L, repeat("f", 17)), - Arguments.of("all f", -1L, -1L, repeat("f", 32)), - Arguments.of( - "hex literal", - 1311768467463790320L, - 1311768467463790320L, - "123456789abcdef0123456789abcdef0")); - } - @ParameterizedTest( - name = "fail parsing illegal 128-bit id hexadecimal String representation: {0}") - @MethodSource("failParsingIllegal128BitIdHexadecimalStringRepresentationArguments") + name = "fail parsing illegal 128-bit id hexadecimal String representation [{index}]") + @NullSource + @ValueSource(strings = {"", "-1", "-A", "111111111111111111111111111111111", "123ABC", "123abcg"}) void failParsingIllegal128BitIdHexadecimalStringRepresentation(String hexId) { assertThrows(NumberFormatException.class, () -> DD128bTraceId.fromHex(hexId)); } - static Stream failParsingIllegal128BitIdHexadecimalStringRepresentationArguments() { - return Stream.of( - Arguments.of((Object) null), - Arguments.of(""), - Arguments.of("-1"), - Arguments.of("-A"), - Arguments.of(repeat("1", 33)), - Arguments.of("123ABC"), - Arguments.of("123abcg")); - } - + @TableTest({ + "scenario | hexId | start | length | lowerCaseOnly", + "null string | | 0 | 0 | true ", + "empty string | '' | 0 | 0 | true ", + "out of bound length | '123456789abcdef0' | 0 | 17 | true ", + "out of bound end | '123456789abcdef0' | 7 | 10 | true ", + "out of bound start | '123456789abcdef0' | 17 | 0 | true ", + "invalid minus one | '-1' | 0 | 1 | true ", + "invalid minus a | '-a' | 0 | 1 | true ", + "invalid character | '123abcg' | 0 | 7 | true ", + "invalid upper case A | 'A' | 0 | 1 | true ", + "invalid upper case | '123ABC' | 0 | 6 | true ", + "too long | '111111111111111111111111111111111' | 0 | 33 | true " + }) @ParameterizedTest( name = - "fail parsing illegal 128-bit id hexadecimal String representation from partial String: {1}") - @MethodSource( - "failParsingIllegal128BitIdHexadecimalStringRepresentationFromPartialStringArguments") + "fail parsing illegal 128-bit id hexadecimal String representation from partial String [{index}]") void failParsingIllegal128BitIdHexadecimalStringRepresentationFromPartialString( - String displayName, String hexId, int start, int length, boolean lowerCaseOnly) { + String hexId, int start, int length, boolean lowerCaseOnly) { assertThrows( NumberFormatException.class, () -> DD128bTraceId.fromHex(hexId, start, length, lowerCaseOnly)); } - static Stream - failParsingIllegal128BitIdHexadecimalStringRepresentationFromPartialStringArguments() { - return Stream.of( - Arguments.of("null string", null, 0, 0, true), - Arguments.of("empty string", "", 0, 0, true), - Arguments.of("out of bound length", "123456789abcdef0", 0, 17, true), - Arguments.of("out of bound end", "123456789abcdef0", 7, 10, true), - Arguments.of("out of bound start", "123456789abcdef0", 17, 0, true), - Arguments.of("invalid minus one", "-1", 0, 1, true), - Arguments.of("invalid minus a", "-a", 0, 1, true), - Arguments.of("invalid character", "123abcg", 0, 7, true), - Arguments.of("invalid upper case A", "A", 0, 1, true), - Arguments.of("invalid upper case ABC", "123ABC", 0, 6, true), - Arguments.of("too long", repeat("1", 33), 0, 33, true)); - } - @Test void checkZeroConstantInitialization() { DDTraceId zero = DDTraceId.ZERO; diff --git a/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java b/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java index 2177dcef1d5..b9b855d0849 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java @@ -12,20 +12,21 @@ import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import org.tabletest.junit.TableTest; class IdGenerationStrategyTest { - @ParameterizedTest(name = "generate id with {1} and {0} bits") - @CsvSource({ - "false,RANDOM", - "false,SEQUENTIAL", - "false,SECURE_RANDOM", - "true,RANDOM", - "true,SEQUENTIAL", - "true,SECURE_RANDOM" + @TableTest({ + "scenario | traceId128BitGenerationEnabled | strategyName ", + "random-64-bit | false | RANDOM ", + "sequential-64-bit | false | SEQUENTIAL ", + "secure-random-64-bit | false | SECURE_RANDOM", + "random-128-bit | true | RANDOM ", + "sequential-128-bit | true | SEQUENTIAL ", + "secure-random-128-bit | true | SECURE_RANDOM" }) + @ParameterizedTest(name = "generate id with {1} and {0} bits") void generateIdWithStrategyAndBitSize( boolean traceId128BitGenerationEnabled, String strategyName) { IdGenerationStrategy strategy = diff --git a/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java b/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java index 53fd9ac485e..f6ae06c716e 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java @@ -3,28 +3,29 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +import org.tabletest.junit.TableTest; class HexStringUtilsTest { - @ParameterizedTest(name = "test hexadecimal String representations high={0} low={1} size={2}") - @CsvSource({ - "0,0,10", - "0,0,16", - "0,0,20", - "0,0,32", - "0,0,40", - "1,2,10", - "1,2,16", - "1,2,20", - "1,2,32", - "1,2,40", - "6536977903480360123,3270264562721133536,10", - "6536977903480360123,3270264562721133536,16", - "6536977903480360123,3270264562721133536,20", - "6536977903480360123,3270264562721133536,32", - "6536977903480360123,3270264562721133536,40" + @TableTest({ + "scenario | highOrderBits | lowOrderBits | size", + "zero-size-10 | 0 | 0 | 10 ", + "zero-size-16 | 0 | 0 | 16 ", + "zero-size-20 | 0 | 0 | 20 ", + "zero-size-32 | 0 | 0 | 32 ", + "zero-size-40 | 0 | 0 | 40 ", + "one-two-10 | 1 | 2 | 10 ", + "one-two-16 | 1 | 2 | 16 ", + "one-two-20 | 1 | 2 | 20 ", + "one-two-32 | 1 | 2 | 32 ", + "one-two-40 | 1 | 2 | 40 ", + "large-size-10 | 6536977903480360123 | 3270264562721133536 | 10 ", + "large-size-16 | 6536977903480360123 | 3270264562721133536 | 16 ", + "large-size-20 | 6536977903480360123 | 3270264562721133536 | 20 ", + "large-size-32 | 6536977903480360123 | 3270264562721133536 | 32 ", + "large-size-40 | 6536977903480360123 | 3270264562721133536 | 40 " }) + @ParameterizedTest(name = "test hexadecimal String representations high={0} low={1} size={2}") void testHexadecimalStringRepresentations(long highOrderBits, long lowOrderBits, int size) { int highOrderSize = Math.min(16, Math.max(0, size - 16)); int lowOrderSize = Math.min(16, size); diff --git a/gradle/java_deps.gradle b/gradle/java_deps.gradle index cb0762bf10c..80c4eda9aa3 100644 --- a/gradle/java_deps.gradle +++ b/gradle/java_deps.gradle @@ -3,6 +3,7 @@ apply plugin: 'groovy' dependencies { testImplementation libs.bundles.spock testImplementation libs.bundles.groovy + testImplementation libs.tabletest testImplementation libs.bundles.test.logging diff --git a/utils/test-utils/build.gradle.kts b/utils/test-utils/build.gradle.kts index 2e9f00058c3..1f975c4ad45 100644 --- a/utils/test-utils/build.gradle.kts +++ b/utils/test-utils/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { api(group = "commons-fileupload", name = "commons-fileupload", version = "1.5") compileOnly(libs.junit.jupiter) + compileOnly(libs.tabletest) compileOnly(libs.logback.core) compileOnly(libs.logback.classic) diff --git a/utils/test-utils/src/main/java/datadog/trace/test/util/TableTestTypeConverters.java b/utils/test-utils/src/main/java/datadog/trace/test/util/TableTestTypeConverters.java new file mode 100644 index 00000000000..973408cd3fa --- /dev/null +++ b/utils/test-utils/src/main/java/datadog/trace/test/util/TableTestTypeConverters.java @@ -0,0 +1,25 @@ +package datadog.trace.test.util; + +import org.tabletest.junit.TypeConverter; + +/** Shared converters for JUnit 5 TableTest tests that use unparsable constants. */ +public final class TableTestTypeConverters { + + private TableTestTypeConverters() {} + + @TypeConverter + public static long toLong(String value) { + if (value == null) { + throw new IllegalArgumentException("Value cannot be null"); + } + String token = value.trim(); + switch (token) { + case "Long.MAX_VALUE": + return Long.MAX_VALUE; + case "Long.MIN_VALUE": + return Long.MIN_VALUE; + default: + return Long.decode(token); + } + } +}