Skip to content

Commit

Permalink
Create commmon backend-mock module to use in smoke tests (#7080)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikita-tkachenko-datadog committed May 29, 2024
1 parent d62398b commit 485d869
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 243 deletions.
Original file line number Diff line number Diff line change
@@ -1,181 +1,11 @@
package datadog.trace.civisibility

import com.fasterxml.jackson.databind.ObjectMapper
import datadog.trace.agent.test.server.http.TestHttpServer
import datadog.trace.test.util.MultipartRequestParser
import org.apache.commons.io.IOUtils
import org.msgpack.jackson.dataformat.MessagePackFactory
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification
import spock.util.concurrent.PollingConditions

import java.util.concurrent.ConcurrentLinkedQueue
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import spock.lang.Specification

abstract class CiVisibilitySmokeTest extends Specification {

@Shared
ObjectMapper msgPackMapper = new ObjectMapper(new MessagePackFactory())

@Shared
Queue<Map<String, Object>> receivedTraces = new ConcurrentLinkedQueue<>()

@Shared
Queue<Map<String, Object>> receivedCoverages = new ConcurrentLinkedQueue<>()

@Shared
Queue<Map<String, Object>> receivedTelemetryMetrics = new ConcurrentLinkedQueue<>()

@Shared
Queue<Map<String, Object>> receivedTelemetryDistributions = new ConcurrentLinkedQueue<>()

@Shared
boolean codeCoverageEnabled = true

@Shared
boolean testsSkippingEnabled = true

@Shared
boolean flakyRetriesEnabled = false

def setup() {
receivedTraces.clear()
receivedCoverages.clear()
receivedTelemetryMetrics.clear()
receivedTelemetryDistributions.clear()
}

@Shared
@AutoCleanup
protected TestHttpServer intakeServer = httpServer {
handlers {
prefix("/api/v2/citestcycle") {
def contentEncodingHeader = request.getHeader("Content-Encoding")
def gzipEnabled = contentEncodingHeader != null && contentEncodingHeader.contains("gzip")
def requestBody = gzipEnabled ? CiVisibilitySmokeTest.decompress(request.body) : request.body
def decodedEvent = msgPackMapper.readValue(requestBody, Map)
receivedTraces.add(decodedEvent)

response.status(200).send()
}

prefix("/api/v2/citestcov") {
def contentEncodingHeader = request.getHeader("Content-Encoding")
def gzipEnabled = contentEncodingHeader != null && contentEncodingHeader.contains("gzip")
def requestBody = gzipEnabled ? CiVisibilitySmokeTest.decompress(request.body) : request.body
def parsed = MultipartRequestParser.parseRequest(requestBody, request.headers.get("Content-Type"))
def coverages = parsed.get("coverage1")
for (def coverage : coverages) {
def decodedCoverage = msgPackMapper.readValue(coverage.get(), Map)
receivedCoverages.add(decodedCoverage)
}

response.status(202).send()
}

prefix("/api/v2/libraries/tests/services/setting") {
// not compressing settings response on purpose, to better mimic real backend behavior:
// it may choose to compress the response or not based on its size,
// so smaller responses (like those of /setting endpoint) are uncompressed,
// while the larger ones (skippable and flaky test lists) are compressed
response.status(200).send(('{ "data": { "type": "ci_app_tracers_test_service_settings", "id": "uuid", "attributes": { '
+ '"code_coverage": ' + codeCoverageEnabled
+ ', "tests_skipping": ' + testsSkippingEnabled
+ ', "flaky_test_retries_enabled": ' + flakyRetriesEnabled + '} } }').bytes)
}

prefix("/api/v2/ci/tests/skippable") {
response.status(200).addHeader("Content-Encoding", "gzip").send(CiVisibilitySmokeTest.compress(('{ "data": [{' +
' "id": "d230520a0561ee2f",' +
' "type": "test",' +
' "attributes": {' +
' "configurations": {' +
' "test.bundle": "Maven Smoke Tests Project maven-surefire-plugin default-test"' +
' },' +
' "name": "test_to_skip_with_itr",' +
' "suite": "datadog.smoke.TestSucceed"' +
' }' +
'}, {' +
' "id": "d230520a0561ee2g",' +
' "type": "test",' +
' "attributes": {' +
' "configurations": {' +
' "test.bundle": ":test"' +
' },' +
' "name": "test_to_skip_with_itr",' +
' "suite": "datadog.smoke.TestSucceed"' +
' }' +
'}] ' +
'}').bytes))
}

prefix("/api/v2/ci/libraries/tests/flaky") {
response.status(200).addHeader("Content-Encoding", "gzip").send(CiVisibilitySmokeTest.compress(('{ "data": [{' +
' "id": "d230520a0561ee2f",' +
' "type": "test",' +
' "attributes": {' +
' "configurations": {' +
' "test.bundle": "Maven Smoke Tests Project maven-surefire-plugin default-test"' +
' },' +
' "name": "test_failed",' +
' "suite": "datadog.smoke.TestFailed"' +
' }' +
'}, {' +
' "id": "d230520a0561ee2g",' +
' "type": "test",' +
' "attributes": {' +
' "configurations": {' +
' "test.bundle": ":test"' +
' },' +
' "name": "test_failed",' +
' "suite": "datadog.smoke.TestFailed"' +
' }' +
'}] ' +
'}').bytes))
}

prefix("/api/v2/apmtelemetry") {
def telemetryRequest = CiVisibilityTestUtils.JSON_MAPPER.readerFor(Map.class).readValue(request.body)
def requestType = telemetryRequest["request_type"]
if (requestType == "message-batch") {
for (def message : telemetryRequest["payload"]) {
def payload = message["payload"]
if (message["request_type"] == 'generate-metrics') {
receivedTelemetryMetrics.addAll((List) payload["series"])
} else if (message["request_type"] == 'distributions') {
receivedTelemetryDistributions.addAll((List) payload["series"])
}
}
}
response.status(202).send()
}
}
}

private static byte[] compress(byte[] bytes) {
def baos = new ByteArrayOutputStream()
try (GZIPOutputStream zip = new GZIPOutputStream(baos)) {
IOUtils.copy(new ByteArrayInputStream(bytes), zip)
}
return baos.toByteArray()
}

private static byte[] decompress(byte[] bytes) {
def baos = new ByteArrayOutputStream()
try (GZIPInputStream zip = new GZIPInputStream(new ByteArrayInputStream(bytes))) {
IOUtils.copy(zip, baos)
}
return baos.toByteArray()
}

protected verifyEventsAndCoverages(String projectName, String toolchain, String toolchainVersion, int expectedEventsCount, int expectedCoveragesCount) {
def events = waitForEvents(expectedEventsCount)
def coverages = waitForCoverages(expectedCoveragesCount)

protected verifyEventsAndCoverages(String projectName, String toolchain, String toolchainVersion, List<Map<String, Object>> events, List<Map<String, Object>> coverages) {
def additionalReplacements = ["content.meta.['test.toolchain']": "$toolchain:$toolchainVersion"]

// uncomment to generate expected data templates
Expand All @@ -192,7 +22,7 @@ abstract class CiVisibilitySmokeTest extends Specification {
* Currently the check is not performed for Gradle builds:
* Gradle daemon started with Gradle TestKit outlives the test, so the final telemetry flush happens after the assertions.
*/
protected verifyTelemetryMetrics( int expectedEventsCount) {
protected verifyTelemetryMetrics(List<Map<String, Object>> receivedTelemetryMetrics, List<Map<String, Object>> receivedTelemetryDistributions, int expectedEventsCount) {
int eventsCreated = 0, eventsFinished = 0
for (Map<String, Object> metric : receivedTelemetryMetrics) {
if (metric["metric"] == "event_created") {
Expand All @@ -212,36 +42,4 @@ abstract class CiVisibilitySmokeTest extends Specification {
// an even more basic smoke check for distributions: assert that we received some
assert !receivedTelemetryDistributions.isEmpty()
}

protected List<Map<String, Object>> waitForEvents(int expectedEventsSize) {
def traceReceiveConditions = new PollingConditions(timeout: 15, initialDelay: 1, delay: 0.5, factor: 1)
traceReceiveConditions.eventually {
int eventsSize = 0
for (Map<String, Object> trace : receivedTraces) {
eventsSize += trace["events"].size()
}
assert eventsSize >= expectedEventsSize
}

List<Map<String, Object>> events = new ArrayList<>()
while (!receivedTraces.isEmpty()) {
def trace = receivedTraces.poll()
events.addAll((List<Map<String, Object>>) trace["events"])
}
return events
}

protected List<Map<String, Object>> waitForCoverages(int traceSize) {
def traceReceiveConditions = new PollingConditions(timeout: 15, initialDelay: 1, delay: 0.5, factor: 1)
traceReceiveConditions.eventually {
assert receivedCoverages.size() == traceSize
}

List<Map<String, Object>> coverages = new ArrayList<>()
while (!receivedCoverages.isEmpty()) {
def trace = receivedCoverages.poll()
coverages.addAll((List<Map<String, Object>>) trace["coverages"])
}
return coverages
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ public static String getUniqueModuleName(MavenProject project, MojoExecution moj
private static final MethodHandle SESSION_FIELD =
METHOD_HANDLES.privateFieldGetter(MavenSession.class, "session");
private static final MethodHandle LOOKUP_FIELD =
METHOD_HANDLES.privateFieldGetter("org.apache.maven.internal.impl.AbstractSession", "lookup");
private static final MethodHandle ALTERNATIVE_LOOKUP_FIELD =
METHOD_HANDLES.privateFieldGetter("org.apache.maven.internal.impl.DefaultSession", "lookup");
private static final MethodHandle LOOKUP_METHOD =
METHOD_HANDLES.method("org.apache.maven.api.services.Lookup", "lookup", Class.class);
Expand All @@ -301,8 +303,12 @@ public static PlexusContainer getContainer(MavenSession mavenSession) {
}
Object /* org.apache.maven.internal.impl.DefaultSession */ session =
METHOD_HANDLES.invoke(SESSION_FIELD, mavenSession);
Object /* org.apache.maven.api.services.Lookup */ lookup =
METHOD_HANDLES.invoke(LOOKUP_FIELD, session);
Object /* org.apache.maven.api.services.Lookup */ lookup;
if (LOOKUP_FIELD != null) {
lookup = METHOD_HANDLES.invoke(LOOKUP_FIELD, session);
} else {
lookup = METHOD_HANDLES.invoke(ALTERNATIVE_LOOKUP_FIELD, session);
}
return METHOD_HANDLES.invoke(LOOKUP_METHOD, lookup, PlexusContainer.class);
}
}
7 changes: 7 additions & 0 deletions dd-smoke-tests/backend-mock/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apply from: "$rootDir/gradle/java.gradle"
description = 'Mock Datadog backend used by smoke tests.'

dependencies {
api project(':dd-smoke-tests')
api testFixtures(project(':dd-java-agent:agent-ci-visibility'))
}
Loading

0 comments on commit 485d869

Please sign in to comment.