Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create commmon backend-mock module to use in smoke tests #7080

Merged
merged 1 commit into from
May 29, 2024
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,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
Loading