Skip to content

Commit 68f8473

Browse files
committed
Add smoke tests for telemetry
1 parent 9e8d0dc commit 68f8473

File tree

7 files changed

+156
-1
lines changed

7 files changed

+156
-1
lines changed

dd-smoke-tests/spring-boot-2.3-webmvc-jetty/src/test/groovy/SpringBootWebmvcIntegrationTest.groovy

+5
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ class SpringBootWebmvcIntegrationTest extends AbstractServerSmokeTest {
8686
response.code() == 404
8787
waitForTraceCount(1)
8888
}
89+
90+
@Override
91+
List<String> expectedTelemetryDependencies() {
92+
['org.eclipse.jetty:jetty-client']
93+
}
8994
}

dd-smoke-tests/spring-boot-3.0-native/src/test/groovy/SpringBootNativeInstrumentationTest.groovy

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ class SpringBootNativeInstrumentationTest extends AbstractServerSmokeTest {
5555
return ["[servlet.request[spring.handler[WebController.doHello[WebController.sayHello]]]]"]
5656
}
5757

58+
@Override
59+
boolean testTelemetry() {
60+
false
61+
}
62+
5863
def "check native instrumentation"() {
5964
setup:
6065
String url = "http://localhost:${httpPort}/hello"

dd-smoke-tests/spring-boot-3.0-webmvc/src/test/groovy/SpringBootWebmvcIntegrationTest.groovy

+5
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,9 @@ class SpringBootWebmvcIntegrationTest extends AbstractServerSmokeTest {
7878
responseBodyStr.contains("banana")
7979
waitForTraceCount(1)
8080
}
81+
82+
@Override
83+
List<String> expectedTelemetryDependencies() {
84+
['spring-core']
85+
}
8186
}

dd-smoke-tests/src/main/groovy/datadog/smoketest/AbstractServerSmokeTest.groovy

+56
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import datadog.trace.agent.test.utils.OkHttpUtils
55
import datadog.trace.agent.test.utils.PortUtils
66
import okhttp3.OkHttpClient
77
import spock.lang.Shared
8+
import static org.junit.Assume.assumeTrue
89

910
import java.util.concurrent.TimeUnit
1011
import java.util.concurrent.atomic.AtomicInteger
@@ -116,4 +117,59 @@ abstract class AbstractServerSmokeTest extends AbstractSmokeTest {
116117
}
117118
return remaining
118119
}
120+
121+
/** Set to false in a test suite to skip telemetry tests. */
122+
boolean testTelemetry() {
123+
true
124+
}
125+
126+
@RunLast
127+
void 'receive telemetry app-started'() {
128+
when:
129+
assumeTrue(testTelemetry())
130+
waitForTelemetryCount(1)
131+
132+
then:
133+
telemetryMessages.size() >= 1
134+
Object msg = telemetryMessages.get(0)
135+
msg['request_type'] == 'app-started'
136+
}
137+
138+
List<String> expectedTelemetryDependencies() {
139+
[]
140+
}
141+
142+
@SuppressWarnings('UnnecessaryBooleanExpression')
143+
@RunLast
144+
void 'receive telemetry app-dependencies-loaded'() {
145+
when:
146+
assumeTrue(testTelemetry())
147+
// app-started + 3 message-batch
148+
waitForTelemetryCount(4)
149+
waitForTelemetryFlat { it.get('request_type') == 'app-dependencies-loaded' }
150+
151+
then: 'received some dependencies'
152+
def dependenciesLoaded = telemetryFlatMessages.findAll { it.get('request_type') == 'app-dependencies-loaded' }
153+
def dependencies = []
154+
dependenciesLoaded.each {
155+
def payload = it.get('payload') as Map<String, Object>
156+
dependencies.addAll(payload.get('dependencies')) }
157+
dependencies.size() > 0
158+
159+
Set<String> dependencyNames = dependencies.collect {
160+
def dependency = it as Map<String, Object>
161+
dependency.get('name') as String
162+
}.toSet()
163+
164+
and: 'received tracer dependencies'
165+
// Not exhaustive list of tracer dependencies.
166+
Set<String> missingDependencyNames = ['com.github.jnr:jnr-ffi', 'net.bytebuddy:byte-buddy-agent',].toSet()
167+
missingDependencyNames.removeAll(dependencyNames) || true
168+
missingDependencyNames.isEmpty()
169+
170+
and: 'received application dependencies'
171+
Set<String> missingExtraDependencyNames = expectedTelemetryDependencies().toSet()
172+
missingExtraDependencyNames.removeAll(dependencyNames) || true
173+
missingExtraDependencyNames.isEmpty()
174+
}
119175
}

dd-smoke-tests/src/main/groovy/datadog/smoketest/AbstractSmokeTest.groovy

+55-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import datadog.trace.test.agent.decoder.Decoder
77
import datadog.trace.test.agent.decoder.DecodedMessage
88
import datadog.trace.test.agent.decoder.DecodedTrace
99
import datadog.trace.util.Strings
10+
import groovy.json.JsonSlurper
1011

1112
import java.nio.charset.StandardCharsets
1213
import java.util.concurrent.CopyOnWriteArrayList
@@ -28,6 +29,12 @@ abstract class AbstractSmokeTest extends ProcessManager {
2829
@Shared
2930
protected CopyOnWriteArrayList<DecodedTrace> decodeTraces = new CopyOnWriteArrayList()
3031

32+
@Shared
33+
protected CopyOnWriteArrayList<Map<String, Object>> telemetryMessages = new CopyOnWriteArrayList()
34+
35+
@Shared
36+
protected CopyOnWriteArrayList<Map<String, Object>> telemetryFlatMessages = new CopyOnWriteArrayList()
37+
3138
@Shared
3239
private Closure decode = decodedTracesCallback()
3340

@@ -37,6 +44,9 @@ abstract class AbstractSmokeTest extends ProcessManager {
3744
@Shared
3845
private Throwable traceDecodingFailure = null
3946

47+
@Shared
48+
private Throwable telemetryDecodingFailure = null
49+
4050
@Shared
4151
@AutoCleanup
4252
protected TestHttpServer server = httpServer {
@@ -111,6 +121,24 @@ abstract class AbstractSmokeTest extends ProcessManager {
111121
response.status(200).send(remoteConfigResponse)
112122
}
113123
prefix("/telemetry/proxy/api/v2/apmtelemetry") {
124+
def body = request.getBody()
125+
if (body != null) {
126+
Map<String, Object> msg = null
127+
try {
128+
msg = new JsonSlurper().parseText(new String(body, StandardCharsets.UTF_8)) as Map<String, Object>
129+
} catch (Throwable t) {
130+
println("=== Failure during telemetry decoding ===")
131+
t.printStackTrace(System.out)
132+
telemetryDecodingFailure = t
133+
throw t
134+
}
135+
telemetryMessages.add(msg)
136+
if (msg.get("request_type") == "message-batch") {
137+
msg.get("payload")?.each { telemetryFlatMessages.add(it as Map<String, Object>) }
138+
} else {
139+
telemetryFlatMessages.add(msg)
140+
}
141+
}
114142
response.status(202).send()
115143
}
116144
}
@@ -147,7 +175,8 @@ abstract class AbstractSmokeTest extends ProcessManager {
147175
"-Ddd.profiling.ddprof.alloc.enabled=${isDdprofSafe()}",
148176
"-Ddatadog.slf4j.simpleLogger.defaultLogLevel=${logLevel()}",
149177
"-Dorg.slf4j.simpleLogger.defaultLogLevel=${logLevel()}",
150-
"-Ddd.site="
178+
"-Ddd.site=",
179+
"-Ddd.telemetry.heartbeat.interval=2",
151180
]
152181
if (inferServiceName()) {
153182
ret += "-Ddd.service.name=${SERVICE_NAME}"
@@ -268,6 +297,31 @@ abstract class AbstractSmokeTest extends ProcessManager {
268297
decodeTraces
269298
}
270299

300+
void waitForTelemetryCount(final int count) {
301+
def conditions = new PollingConditions(timeout: 30, initialDelay: 0, delay: 1, factor: 1)
302+
waitForTelemetryCount(conditions, count)
303+
}
304+
305+
void waitForTelemetryCount(final PollingConditions poll, final int count) {
306+
poll.eventually {
307+
telemetryMessages.size() >= count
308+
}
309+
}
310+
311+
void waitForTelemetryFlat(final Function<Map<String, Object>, Boolean> predicate) {
312+
def conditions = new PollingConditions(timeout: 30, initialDelay: 0, delay: 1, factor: 1)
313+
waitForTelemetryFlat(conditions, predicate)
314+
}
315+
316+
void waitForTelemetryFlat(final PollingConditions poll, final Function<Map<String, Object>, Boolean> predicate) {
317+
poll.eventually {
318+
if (telemetryDecodingFailure != null) {
319+
throw telemetryDecodingFailure
320+
}
321+
assert telemetryFlatMessages.find { predicate.apply(it) } != null
322+
}
323+
}
324+
271325
def logLevel() {
272326
return "info"
273327
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package datadog.smoketest
2+
3+
import org.spockframework.runtime.extension.ExtensionAnnotation
4+
5+
import java.lang.annotation.ElementType
6+
import java.lang.annotation.Retention
7+
import java.lang.annotation.RetentionPolicy
8+
import java.lang.annotation.Target
9+
10+
/**
11+
* Spock test methods annotated with this will be executed last.
12+
* This is useful for tests that need to wait for some test to settle while other tests run (e.g. telemetry), so it is
13+
* more efficient to run them at the end.
14+
*/
15+
@Retention(RetentionPolicy.RUNTIME)
16+
@Target([ElementType.METHOD])
17+
@ExtensionAnnotation(RunLastExtension)
18+
@interface RunLast {
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package datadog.smoketest
2+
3+
import org.spockframework.runtime.extension.IAnnotationDrivenExtension
4+
import org.spockframework.runtime.model.FeatureInfo
5+
6+
class RunLastExtension implements IAnnotationDrivenExtension<RunLast> {
7+
@Override
8+
void visitFeatureAnnotations(List<RunLast> annotations, FeatureInfo feature) {
9+
feature.setExecutionOrder(Integer.MAX_VALUE)
10+
}
11+
}

0 commit comments

Comments
 (0)