Skip to content
Closed
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
Expand Up @@ -5,7 +5,20 @@

package io.opentelemetry.smoketest;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.smoketest.windows.WindowsTestContainerManager;
import io.opentelemetry.testing.internal.armeria.client.WebClient;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import javax.annotation.Nullable;
import org.junit.jupiter.api.extension.ExtensionContext;

/**
Expand All @@ -15,12 +28,14 @@
* <p>Example usage:
*
* <pre>
* class MySmokeTest implements TelemetryRetrieverProvider {
* class MySmokeTest {
* {@literal @}RegisterExtension
* static final InstrumentationExtension testing = SmokeTestInstrumentationExtension.create();
* static final SmokeTestInstrumentationExtension testing = SmokeTestInstrumentationExtension.springBoot("version").build();
*
* {@literal @}Test
* void test() {
* {@literal @}ParameterizedTest
* {@literal @}ValueSource(ints = {8, 11, 17})
* void test(int jdk) throws Exception {
* SmokeTestOutput output = testing.start(jdk);
* // test code ...
*
* var spans = testing.spans();
Expand All @@ -29,29 +44,225 @@
* }
* </pre>
*/
public class SmokeTestInstrumentationExtension extends InstrumentationExtension {
private SmokeTestInstrumentationExtension() {
super(SmokeTestRunner.instance());
public class SmokeTestInstrumentationExtension extends InstrumentationExtension
implements TelemetryRetrieverProvider {

private final TestContainerManager containerManager = createContainerManager();

private TelemetryRetriever telemetryRetriever;

private final String agentPath =
System.getProperty("io.opentelemetry.smoketest.agent.shadowJar.path");

private final AutoCleanupExtension autoCleanup = AutoCleanupExtension.create();

@FunctionalInterface
public interface GetTargetImage {
String getTargetImage(String jdk, String serverVersion, boolean windows);
}

private final GetTargetImage getTargetImage;

private final String[] command;
private final String jvmArgsEnvVarName;
private final boolean setServiceName;
private final Map<String, String> extraEnv;
private final List<ResourceMapping> extraResources;
private final TargetWaitStrategy waitStrategy;
private final List<Integer> extraPorts;
private final Duration telemetryTimeout;

private SmokeTestInstrumentationExtension(
GetTargetImage getTargetImage,
String[] command,
String jvmArgsEnvVarName,
boolean setServiceName,
Map<String, String> extraEnv,
List<ResourceMapping> extraResources,
TargetWaitStrategy waitStrategy,
List<Integer> extraPorts,
Duration telemetryTimeout) {
super(new SmokeTestRunner());
this.getTargetImage = getTargetImage;
this.command = command;
this.jvmArgsEnvVarName = jvmArgsEnvVarName;
this.setServiceName = setServiceName;
this.extraEnv = extraEnv;
this.extraResources = extraResources;
this.waitStrategy = waitStrategy;
this.extraPorts = extraPorts;
this.telemetryTimeout = telemetryTimeout;
}

public static SmokeTestInstrumentationExtension create() {
return new SmokeTestInstrumentationExtension();
public WebClient client() {
return WebClient.of("h1c://localhost:" + containerManager.getTargetMappedPort(8080));
}

@Override
public void beforeAll(ExtensionContext context) throws Exception {
containerManager.startEnvironmentOnce();
telemetryRetriever =
new TelemetryRetriever(containerManager.getBackendMappedPort(), telemetryTimeout);
super.beforeAll(context);
}

@Override
public void beforeEach(ExtensionContext extensionContext) {
Object testInstance = extensionContext.getRequiredTestInstance();
SmokeTestRunner smokeTestRunner = (SmokeTestRunner) getTestRunner();
smokeTestRunner.setTelemetryRetriever(getTelemetryRetriever());
super.beforeEach(extensionContext);
}

@Override
public void afterEach(ExtensionContext context) throws Exception {
autoCleanup.afterEach(context);
super.afterEach(context);
}

if (!(testInstance instanceof TelemetryRetrieverProvider)) {
throw new AssertionError(
"SmokeTestInstrumentationExtension can only be applied to a subclass of "
+ "TelemetryRetrieverProvider");
public String getAgentImplementationVersion() {
try (JarFile agentJar = new JarFile(agentPath)) {
return agentJar
.getManifest()
.getMainAttributes()
.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

SmokeTestRunner smokeTestRunner = (SmokeTestRunner) getTestRunner();
smokeTestRunner.setTelemetryRetriever(
((TelemetryRetrieverProvider) testInstance).getTelemetryRetriever());
public SmokeTestOutput start(int jdk) {
return start(String.valueOf(jdk), null, false);
}

super.beforeEach(extensionContext);
public SmokeTestOutput start(String jdk, String serverVersion, boolean windows) {
autoCleanup.deferCleanup(() -> containerManager.stopTarget());

return new SmokeTestOutput(
this,
containerManager.startTarget(
getTargetImage.getTargetImage(jdk, serverVersion, windows),
agentPath,
jvmArgsEnvVarName,
extraEnv,
setServiceName,
extraResources,
extraPorts,
waitStrategy,
command));
}

@Override
public TelemetryRetriever getTelemetryRetriever() {
return telemetryRetriever;
}

public static Builder builder(Function<String, String> getTargetImage) {
return builder((jdk, serverVersion, windows) -> getTargetImage.apply(jdk));
}

public static Builder builder(GetTargetImage getTargetImage) {
return new Builder(getTargetImage);
}

public static Builder springBoot(String imageTag) {
return builder(
jdk ->
String.format(
"ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk%s-%s",
jdk, imageTag))
.waitStrategy(
new TargetWaitStrategy.Log(
Duration.ofMinutes(1), ".*Started SpringbootApplication in.*"));
}

private static TestContainerManager createContainerManager() {
return TestContainerManager.useWindowsContainers()
? new WindowsTestContainerManager()
: new LinuxTestContainerManager();
}

public static class Builder {
private final GetTargetImage getTargetImage;
private String[] command;
private String jvmArgsEnvVarName = "JAVA_TOOL_OPTIONS";
private boolean setServiceName = true;
private final Map<String, String> extraEnv = new HashMap<>();
private List<ResourceMapping> extraResources = List.of();
private TargetWaitStrategy waitStrategy;
private List<Integer> extraPorts = List.of();
private Duration telemetryTimeout = Duration.ofSeconds(30);

private Builder(GetTargetImage getTargetImage) {
this.getTargetImage = getTargetImage;
}

/** Sets the command to run in the target container. */
@CanIgnoreReturnValue
public Builder command(String... command) {
this.command = command;
return this;
}

/** Sets the environment variable name used to pass JVM arguments to the target application. */
@CanIgnoreReturnValue
public Builder jvmArgsEnvVarName(String jvmArgsEnvVarName) {
this.jvmArgsEnvVarName = jvmArgsEnvVarName;
return this;
}

/** Enables or disables setting the default service name for the target application. */
@CanIgnoreReturnValue
public Builder setServiceName(boolean setServiceName) {
this.setServiceName = setServiceName;
return this;
}

/** Adds an environment variable to the target application's environment. */
@CanIgnoreReturnValue
public Builder env(String key, String value) {
this.extraEnv.put(key, value);
return this;
}

/** Specifies additional files to copy to the target container. */
@CanIgnoreReturnValue
public Builder extraResources(ResourceMapping... resources) {
this.extraResources = List.of(resources);
return this;
}

/** Sets the wait strategy for the target container startup. */
@CanIgnoreReturnValue
public Builder waitStrategy(@Nullable TargetWaitStrategy waitStrategy) {
this.waitStrategy = waitStrategy;
return this;
}

/** Specifies additional ports to expose from the target container. */
@CanIgnoreReturnValue
public Builder extraPorts(Integer... ports) {
this.extraPorts = List.of(ports);
return this;
}

/** Sets the timeout duration for retrieving telemetry data. */
@CanIgnoreReturnValue
public Builder telemetryTimeout(Duration telemetryTimeout) {
this.telemetryTimeout = telemetryTimeout;
return this;
}

public SmokeTestInstrumentationExtension build() {
return new SmokeTestInstrumentationExtension(
getTargetImage,
command,
jvmArgsEnvVarName,
setServiceName,
extraEnv,
extraResources,
waitStrategy,
extraPorts,
telemetryTimeout);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.smoketest;

import static java.util.stream.Collectors.toSet;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.testcontainers.containers.output.OutputFrame;
import org.testcontainers.containers.output.ToStringConsumer;

public class SmokeTestOutput {

private static final Pattern TRACE_ID_PATTERN =
Pattern.compile(".*trace_id=(?<traceId>[a-zA-Z0-9]+).*");
private final SmokeTestInstrumentationExtension extension;
private final Consumer<OutputFrame> output;

public SmokeTestOutput(
SmokeTestInstrumentationExtension extension, Consumer<OutputFrame> output) {
this.extension = extension;
this.output = output;
}

public static Stream<String> findTraceId(String log) {
var m = TRACE_ID_PATTERN.matcher(log);
return m.matches() ? Stream.of(m.group("traceId")) : Stream.empty();
}

public void assertAgentVersionLogged() {
String version = extension.getAgentImplementationVersion();
assertThat(
logLines().anyMatch(l -> l.contains("opentelemetry-javaagent - version: " + version)))
.isTrue();
}

public Set<String> getLoggedTraceIds() {
return logLines().flatMap(SmokeTestOutput::findTraceId).collect(toSet());
}

public Stream<String> logLines() {
return ((ToStringConsumer) output).toUtf8String().lines();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,9 @@
*/
public class SmokeTestRunner extends InstrumentationTestRunner {

private static final SmokeTestRunner INSTANCE = new SmokeTestRunner();

private TelemetryRetriever telemetryRetriever;

public static SmokeTestRunner instance() {
return INSTANCE;
}

private SmokeTestRunner() {
SmokeTestRunner() {
super(OpenTelemetry.noop());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.opentelemetry.testing.internal.protobuf.GeneratedMessage;
import io.opentelemetry.testing.internal.protobuf.InvalidProtocolBufferException;
import io.opentelemetry.testing.internal.protobuf.util.JsonFormat;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
Expand All @@ -28,9 +29,11 @@
public class TelemetryRetriever {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final WebClient client;
private final Duration telemetryTimeout;

public TelemetryRetriever(int backendPort) {
public TelemetryRetriever(int backendPort, Duration telemetryTimeout) {
client = WebClient.of("http://localhost:" + backendPort);
this.telemetryTimeout = telemetryTimeout;
}

public void clearTelemetry() {
Expand Down Expand Up @@ -91,7 +94,7 @@ Collection<T> waitForTelemetry(String path, Supplier<B> builderConstructor) {
@SuppressWarnings("SystemOut")
private String waitForContent(String path) throws InterruptedException {
long previousSize = 0;
long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30);
long deadline = System.currentTimeMillis() + telemetryTimeout.toMillis();
String content = "[]";
while (System.currentTimeMillis() < deadline) {
content = client.get(path).aggregate().join().contentUtf8();
Expand Down
Loading