Skip to content
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
@@ -1,9 +1,12 @@
package datadog.trace.agent.test;

import static java.util.function.Function.identity;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import datadog.instrument.classinject.ClassInjector;
import datadog.trace.agent.test.assertions.TraceAssertions;
import datadog.trace.agent.test.assertions.TraceMatcher;
import datadog.trace.agent.tooling.AgentInstaller;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.agent.tooling.TracerInstaller;
Expand All @@ -22,11 +25,19 @@
import java.util.ServiceLoader;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;

import org.opentest4j.AssertionFailedError;

/**
* This class is an experimental base to run instrumentation tests using JUnit Jupiter. It is still
* early development, and the overall API is expected to change to leverage its extension model. The
* current implementation is inspired and kept close to it Groovy / Spock counterpart, the {@code
* InstrumentationSpecification}.
*/
@ExtendWith(TestClassShadowingExtension.class)
public abstract class AbstractInstrumentationTest {
static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.getInstrumentation();
Expand Down Expand Up @@ -100,6 +111,33 @@ public void tearDown() {
this.transformerLister = null;
}

/**
* Checks the structure of the traces captured from the test tracer.
*
* @param matchers The matchers to verify the trace collection, one matcher by expected trace.
*/
protected void assertTraces(TraceMatcher... matchers) {
assertTraces(identity(), matchers);
}

/**
* Checks the structure of the traces captured from the test tracer.
*
* @param options The {@link TraceAssertions.Options} to configure the checks.
* @param matchers The matchers to verify the trace collection, one matcher by expected trace.
*/
protected void assertTraces(
Function<TraceAssertions.Options, TraceAssertions.Options> options,
TraceMatcher... matchers) {
int expectedTraceCount = matchers.length;
try {
this.writer.waitForTraces(expectedTraceCount);
} catch (InterruptedException | TimeoutException e) {
throw new AssertionFailedError("Timeout while waiting for traces", e);
}
TraceAssertions.assertTraces(this.writer, options, matchers);
}

protected void blockUntilChildSpansFinished(final int numberOfSpans) {
blockUntilChildSpansFinished(this.tracer.activeSpan(), numberOfSpans);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;

/**
* A generic {@link Matcher} implementation that always evaluates to {@code true} for any input.
*
* <p>This class can be used when any value is acceptable for a match. It is typically used for test
* assertions where no specific value validation is required.
*
* @param <T> the type of the value being matched
*/
public class Any<T> implements Matcher<T> {
Any() {}

@Override
public Optional<T> expected() {
return Optional.empty();
}

@Override
public String failureReason() {
return "";
}

@Override
public boolean test(T t) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;

/**
* A generic {@link Matcher} implementation that verifies if a given value matches the expected
* value. This matcher compares the provided input with a predefined expected value for equality.
*
* @param <T> The type of the value being matched.
*/
public class Is<T> implements Matcher<T> {
private final T expected;

Is(T expected) {
this.expected = expected;
}

@Override
public Optional<T> expected() {
return Optional.of(this.expected);
}

@Override
public String failureReason() {
return "Unexpected value";
}

@Override
public boolean test(T t) {
return this.expected.equals(t);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;

/** A {@link Matcher} implementation that checks if a given boolean value is {@code false}. */
public class IsFalse implements Matcher<Boolean> {
IsFalse() {}

@Override
public Optional<Boolean> expected() {
return Optional.of(false);
}

@Override
public String failureReason() {
return "False expected";
}

@Override
public boolean test(Boolean t) {
return !t;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;

/**
* A {@link Matcher} implementation that checks if a given value is not {@code null}.
*
* @param <T> The type of the value being matched.
*/
public class IsNonNull<T> implements Matcher<T> {
IsNonNull() {}

@Override
public Optional<T> expected() {
return Optional.empty();
}

@Override
public String failureReason() {
return "Non-null value expected";
}

@Override
public boolean test(T t) {
return t != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;

/**
* A {@link Matcher} implementation that checks if a given value is {@code null}.
*
* @param <T> The type of the value being matched.
*/
public class IsNull<T> implements Matcher<T> {
IsNull() {}

@Override
public Optional<T> expected() {
return Optional.empty();
}

@Override
public String failureReason() {
return "Null value expected";
}

@Override
public boolean test(T t) {
return t == null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;

/** A {@link Matcher} implementation that checks if a given boolean value is {@code true}. */
public class IsTrue implements Matcher<Boolean> {
IsTrue() {}

@Override
public Optional<Boolean> expected() {
return Optional.of(true);
}

@Override
public String failureReason() {
return "True expected";
}

@Override
public boolean test(Boolean t) {
return t;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;
import java.util.function.Predicate;

/**
* This interface represents a generic matcher to evaluate whether a given value matches certain
* criteria defined by the implementation.
*
* @param <T> the type of the value being matched.
*/
public interface Matcher<T> extends Predicate<T> {
/**
* Gets the expected value for this matcher, if any.
*
* @return The expected value wrapped into an {@link Optional}, or {@link Optional#empty()} if no
* specific value is expected.
*/
Optional<T> expected();

/**
* Explains the reason why the value does not match the expectation.
*
* @return The failure reason.
*/
String failureReason();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package datadog.trace.agent.test.assertions;

import java.util.Optional;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.opentest4j.AssertionFailedError;

/** This class is a utility class to create generic matchers. */
public final class Matchers {
private Matchers() {}

/**
* Creates a matcher that checks if the provided value is equal to the expected value.
*
* @param <T> The type of the value being matched.
* @param expected The value to compare against
* @return A {@link Matcher} that verifies equality with the expected value.
*/
public static <T> Matcher<T> is(T expected) {
return new Is<>(expected);
}

/**
* Creates a matcher that checks if a given value is {@code null}.
*
* @param <T> The type of the value being matched.
* @return A {@link Matcher} that verifies the value is {@code null}.
*/
public static <T> Matcher<T> isNull() {
return new IsNull<>();
}

/**
* Creates a matcher that checks if a given value is not {@code null}.
*
* @param <T> The type of the value being matched.
* @return A {@link Matcher} that verifies the value is not {@code null}.
*/
public static <T> Matcher<T> isNonNull() {
return new IsNonNull<>();
}

/**
* Creates a matcher that checks if a given boolean value is {@code true}.
*
* @return A {@link Matcher} that verifies the value is {@code true}.
*/
public static Matcher<Boolean> isTrue() {
return new IsTrue();
}

/**
* Creates a matcher that checks if a given boolean value is {@code false}.
*
* @return A {@link Matcher} that verifies the value is {@code false}.
*/
public static Matcher<Boolean> isFalse() {
return new IsFalse();
}

/**
* Creates a {@link Matcher} that checks if a given string matches a specified regular expression.
*
* @param regex The regular expression pattern used for matching.
* @return A {@link Matcher} that validates if a string matches the provided regular expression.
*/
public static Matcher<CharSequence> matches(String regex) {
return new Matches(Pattern.compile(regex));
}

/**
* Creates a {@link Matcher} that checks if a given string matches a specified {@link Pattern}.
*
* @param pattern The regular expression pattern used for matching.
* @return A {@link Matcher} that validates if a string matches the provided {@link Pattern}.
*/
public static Matcher<CharSequence> matches(Pattern pattern) {
return new Matches(pattern);
}

/**
* Creates a {@link Matcher} that validates a given value based on the provided {@link Predicate}.
* This method allows specifying custom validation logic for matching input values.
*
* @param <T> The type of the value being validated.
* @param validator A {@link Predicate} representing the custom validation logic to be applied.
* @return A {@link Matcher} that uses the provided {@link Predicate} to validate input values.
*/
public static <T> Matcher<T> validates(Predicate<T> validator) {
return new Validates<>(validator);
}

/**
* Creates a matcher that always accepts any input value.
*
* @param <T> The type of the value being matched.
* @return A {@link Matcher} that accepts any value and always matches.
*/
public static <T> Matcher<T> any() {
return new Any<>();
}

static <T> void assertValue(Matcher<T> matcher, T value, String message) {
if (matcher != null && !matcher.test(value)) {
Optional<T> expected = matcher.expected();
if (expected.isPresent()) {
throw new AssertionFailedError(
message + ". " + matcher.failureReason(), expected.get(), value);
} else {
throw new AssertionFailedError(message + ": " + value + ". " + matcher.failureReason());
}
}
}
}
Loading