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
5 changes: 2 additions & 3 deletions .claude/skills/migrate-junit-source-to-tabletest/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ Process (do in this order):
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)
- @TableTest should be in `gradle/java_deps.gradle` as `testImplementation libs.tabletest` where Java modules inherit it via `gradle/java.gradle`.
- As a fallback, add module-specific `testImplementation(libs.tabletest)` or `testImplementation libs.tabletest`.
Comment on lines 14 to +16
Copy link
Contributor

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can delete whole Dependency block (here and in migrate-spock skill too), as it will be available by default, no need to specify at all.


Import: `import org.tabletest.junit.TableTest;`

Expand Down
163 changes: 67 additions & 96 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,57 @@
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.ValueSource;
import org.tabletest.junit.TableTest;

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' | -1 ",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DDSpanId.MAX is not parse-able by TableTest, but it's defined as -1 in DDSpanId

"long max | '9223372036854775807' | 9223372036854775807 ",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long.MAX_VALUE and similar are not parse-able by TableTest, so the integer is used instead. This hurts readability... but worth?

"long max plus one | '9223372036854775808' | -9223372036854775808"
})
@ParameterizedTest
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")
@TableTest({
"scenario | stringId ",
"null | ",
"empty | '' ",
"negative one | '-1' ",
"too large | '18446744073709551616'",
"too large variant | '18446744073709551625'",
"too large long | '184467440737095516150'",
"contains alpha first | '18446744073709551a1' ",
"contains alpha last | '184467440737095511a' "
})
@ParameterizedTest
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' | -1 ",
"long max | '7fffffffffffffff' | 9223372036854775807 ",
"long min | '8000000000000000' | -9223372036854775808",
"long min with leading zeros | '00008000000000000000' | -9223372036854775808",
"cafebabe | 'cafebabe' | 3405691582 ",
"fifteen hex digits | '123456789abcdef' | 81985529216486895 "
})
@ParameterizedTest
void convertIdsFromToHexString(String hexId, long expectedId) {
long ddid = DDSpanId.fromHex(hexId);
String padded16 =
Expand All @@ -73,27 +72,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 | 0 ",
"single zero at index 1 | '00' | 1 | 1 | false | 0 ",
"single zero at index 1 duplicate | '00' | 1 | 1 | false | 0 ",
"max lower-case | 'ffffffffffffffff' | 0 | 16 | true | -1 ",
"upper-case rejected when lower-case only| 'ffffffffffffFfff' | 0 | 16 | true | ",
"upper-case accepted when lower disabled | 'ffffffffffffFfff' | 0 | 16 | false | -1 "
})
@ParameterizedTest
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,50 +102,27 @@ 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")
@TableTest({
"scenario | hexId ",
"null | ",
"empty | '' ",
"negative one | '-1' ",
"too long | '10000000000000000'",
"invalid middle | 'ffffffffffffffzf' ",
"invalid tail | 'fffffffffffffffz' "
})
@ParameterizedTest
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"})
@TableTest({
"scenario | strategyName ",
"random | RANDOM ",
"sequential | SEQUENTIAL ",
"secure random | SECURE_RANDOM"
})
@ParameterizedTest
void generateIdWithStrategy(String strategyName) {
IdGenerationStrategy strategy = IdGenerationStrategy.fromName(strategyName);
Set<Long> checked = new HashSet<Long>();
Expand Down
Loading
Loading