Skip to content
Open
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
4 changes: 0 additions & 4 deletions .claude/skills/migrate-groovy-to-java/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 7 additions & 6 deletions .claude/skills/migrate-junit-source-to-tabletest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.

Expand All @@ -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.

Expand Down
4 changes: 0 additions & 4 deletions components/json/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,3 @@ apply(from = "$rootDir/gradle/java.gradle")
jmh {
jmhVersion = libs.versions.jmh.get()
}

dependencies {
testImplementation(libs.tabletest)
}
29 changes: 7 additions & 22 deletions components/json/src/test/java/datadog/json/JsonMapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -77,28 +79,16 @@ static Stream<Arguments> 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<String, Object> 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));
}
Expand Down Expand Up @@ -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<String> parsed = JsonMapper.fromJsonToList(json);
assertEquals(emptyList(), parsed);
Expand Down
149 changes: 56 additions & 93 deletions dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Arguments> 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<Arguments> 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 =
Expand All @@ -73,27 +76,22 @@ void convertIdsFromToHexString(String hexId, long expectedId) {
assertEquals(padded16, DDSpanId.toHexStringPadded(ddid));
}

static Stream<Arguments> 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);
Expand All @@ -108,48 +106,13 @@ void convertIdsFromPartOfHexString(
}
}

static Stream<Arguments> 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<Arguments> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading
Loading