Ensure gradle runs base mongo test first
Otherwise there is a race condition between the projects initializing the mongo instance.
RENAME
dd-java-agent/instrumentation/mongo-3.1/mongo-3.1.gradle
dd-java-agent/instrumentation/mongo/driver-3.1/driver-3.1.gradle
@@ -28,7 +28,7 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
-
+ testCompile project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output
testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '2.2.0'
testCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0'

RENAME
dd-java-agent/instrumentation/mongo-3.1/src/main/java/datadog/trace/instrumentation/mongo/MongoClientDecorator.java
dd-java-agent/instrumentation/mongo/driver-3.1/src/main/java/datadog/trace/instrumentation/mongo/MongoClientDecorator.java
RENAME
dd-java-agent/instrumentation/mongo-3.1/src/main/java/datadog/trace/instrumentation/mongo/MongoClientInstrumentation.java
dd-java-agent/instrumentation/mongo/driver-3.1/src/main/java/datadog/trace/instrumentation/mongo/MongoClientInstrumentation.java
RENAME
dd-java-agent/instrumentation/mongo-3.1/src/main/java/datadog/trace/instrumentation/mongo/TracingCommandListener.java
dd-java-agent/instrumentation/mongo/driver-3.1/src/main/java/datadog/trace/instrumentation/mongo/TracingCommandListener.java
RENAME
dd-java-agent/instrumentation/mongo-3.1/src/test/groovy/MongoClientTest.groovy
dd-java-agent/instrumentation/mongo/driver-3.1/src/test/groovy/MongoClientTest.groovy
@@ -5,18 +5,8 @@ import com.mongodb.ServerAddress
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import datadog.opentracing.DDSpan
-import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.asserts.TraceAssert
-import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes
-import de.flapdoodle.embed.mongo.MongodExecutable
-import de.flapdoodle.embed.mongo.MongodProcess
-import de.flapdoodle.embed.mongo.MongodStarter
-import de.flapdoodle.embed.mongo.config.IMongodConfig
-import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
-import de.flapdoodle.embed.mongo.config.Net
-import de.flapdoodle.embed.mongo.distribution.Version
-import de.flapdoodle.embed.process.runtime.Network
import io.opentracing.tag.Tags
import org.bson.BsonDocument
import org.bson.BsonString
@@ -26,38 +16,18 @@ import spock.lang.Shared
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
-class MongoClientTest extends AgentTestRunner {
+abstract class MongoClientTest extends MongoBaseTest {
@Shared
MongoClient client
- @Shared
- int port = PortUtils.randomOpenPort()
- @Shared
- MongodExecutable mongodExe
- @Shared
- MongodProcess mongod
def setup() throws Exception {
- final MongodStarter starter = MongodStarter.getDefaultInstance()
- final IMongodConfig mongodConfig =
- new MongodConfigBuilder()
- .version(Version.Main.PRODUCTION)
- .net(new Net("localhost", port, Network.localhostIsIPv6()))
- .build()
-
- mongodExe = starter.prepare(mongodConfig)
- mongod = mongodExe.start()
-
client = new MongoClient("localhost", port)
}
def cleanup() throws Exception {
client?.close()
client = null
- mongod?.stop()
- mongod = null
- mongodExe?.stop()
- mongodExe = null
}
def "test create collection"() {

RENAME
dd-java-agent/instrumentation/mongo-async-3.3/mongo-async-3.3.gradle
dd-java-agent/instrumentation/mongo/driver-async-3.3/driver-async-3.3.gradle
@@ -20,10 +20,9 @@ testSets {
dependencies {
// use mongo listener
- compile(project(':dd-java-agent:instrumentation:mongo-3.1')) {
+ compile(project(':dd-java-agent:instrumentation:mongo:driver-3.1')) {
transitive = false
}
- compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.3.0'
compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0'
compile project(':dd-java-agent:agent-tooling')
@@ -34,7 +33,7 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
-
+ testCompile project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output
testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '2.2.0'
testCompile group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0'

RENAME
dd-java-agent/instrumentation/mongo-async-3.3/src/main/java/datadog/trace/instrumentation/mongo/MongoAsyncClientInstrumentation.java
dd-java-agent/instrumentation/mongo/driver-async-3.3/src/main/java/datadog/trace/instrumentation/mongo/MongoAsyncClientInstrumentation.java
RENAME
dd-java-agent/instrumentation/mongo-async-3.3/src/test/groovy/MongoAsyncClientTest.groovy
dd-java-agent/instrumentation/mongo/driver-async-3.3/src/test/groovy/MongoAsyncClientTest.groovy
@@ -6,18 +6,8 @@ import com.mongodb.async.client.MongoDatabase
import com.mongodb.client.result.DeleteResult
import com.mongodb.client.result.UpdateResult
import datadog.opentracing.DDSpan
-import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.asserts.TraceAssert
-import datadog.trace.agent.test.utils.PortUtils
import datadog.trace.api.DDSpanTypes
-import de.flapdoodle.embed.mongo.MongodExecutable
-import de.flapdoodle.embed.mongo.MongodProcess
-import de.flapdoodle.embed.mongo.MongodStarter
-import de.flapdoodle.embed.mongo.config.IMongodConfig
-import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
-import de.flapdoodle.embed.mongo.config.Net
-import de.flapdoodle.embed.mongo.distribution.Version
-import de.flapdoodle.embed.process.runtime.Network
import io.opentracing.tag.Tags
import org.bson.BsonDocument
import org.bson.BsonString
@@ -31,38 +21,18 @@ import java.util.concurrent.CountDownLatch
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
@Timeout(10)
-class MongoAsyncClientTest extends AgentTestRunner {
+class MongoAsyncClientTest extends MongoBaseTest {
@Shared
MongoClient client
- @Shared
- int port = PortUtils.randomOpenPort()
- @Shared
- MongodExecutable mongodExe
- @Shared
- MongodProcess mongod
def setup() throws Exception {
- final MongodStarter starter = MongodStarter.getDefaultInstance()
- final IMongodConfig mongodConfig =
- new MongodConfigBuilder()
- .version(Version.Main.PRODUCTION)
- .net(new Net("localhost", port, Network.localhostIsIPv6()))
- .build()
-
- mongodExe = starter.prepare(mongodConfig)
- mongod = mongodExe.start()
-
client = MongoClients.create("mongodb://localhost:$port")
}
def cleanup() throws Exception {
client?.close()
client = null
- mongod?.stop()
- mongod = null
- mongodExe?.stop()
- mongodExe = null
}
def "test create collection"() {
@@ -75,7 +45,11 @@ class MongoAsyncClientTest extends AgentTestRunner {
then:
assertTraces(1) {
trace(0, 1) {
- mongoSpan(it, 0, "{ \"create\" : \"$collectionName\", \"capped\" : \"?\" }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"create\" : \"$collectionName\", \"capped\" : \"?\" }" ||
+ it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
+ true
+ }
}
}
@@ -96,7 +70,11 @@ class MongoAsyncClientTest extends AgentTestRunner {
count.get() == 0
assertTraces(1) {
trace(0, 1) {
- mongoSpan(it, 0, "{ \"count\" : \"$collectionName\", \"query\" : { } }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"count\" : \"$collectionName\", \"query\" : { } }" ||
+ it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
+ true
+ }
}
}
@@ -127,10 +105,18 @@ class MongoAsyncClientTest extends AgentTestRunner {
count.get() == 1
assertTraces(2) {
trace(0, 1) {
- mongoSpan(it, 0, "{ \"insert\" : \"$collectionName\", \"ordered\" : \"?\", \"documents\" : [{ \"_id\" : \"?\", \"password\" : \"?\" }] }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"insert\" : \"$collectionName\", \"ordered\" : \"?\", \"documents\" : [{ \"_id\" : \"?\", \"password\" : \"?\" }] }" ||
+ it == "{\"insert\": \"$collectionName\", \"ordered\": \"?\", \"\$db\": \"?\", \"documents\": [{\"_id\": \"?\", \"password\": \"?\"}]}"
+ true
+ }
}
trace(1, 1) {
- mongoSpan(it, 0, "{ \"count\" : \"$collectionName\", \"query\" : { } }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"count\" : \"$collectionName\", \"query\" : { } }" ||
+ it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
+ true
+ }
}
}
@@ -170,10 +156,18 @@ class MongoAsyncClientTest extends AgentTestRunner {
count.get() == 1
assertTraces(2) {
trace(0, 1) {
- mongoSpan(it, 0, "{ \"update\" : \"?\", \"ordered\" : \"?\", \"updates\" : [{ \"q\" : { \"password\" : \"?\" }, \"u\" : { \"\$set\" : { \"password\" : \"?\" } } }] }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"update\" : \"?\", \"ordered\" : \"?\", \"updates\" : [{ \"q\" : { \"password\" : \"?\" }, \"u\" : { \"\$set\" : { \"password\" : \"?\" } } }] }" ||
+ it == "{\"update\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"updates\": [{\"q\": {\"password\": \"?\"}, \"u\": {\"\$set\": {\"password\": \"?\"}}}]}"
+ true
+ }
}
trace(1, 1) {
- mongoSpan(it, 0, "{ \"count\" : \"$collectionName\", \"query\" : { } }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"count\" : \"$collectionName\", \"query\" : { } }" ||
+ it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
+ true
+ }
}
}
@@ -211,10 +205,18 @@ class MongoAsyncClientTest extends AgentTestRunner {
count.get() == 0
assertTraces(2) {
trace(0, 1) {
- mongoSpan(it, 0, "{ \"delete\" : \"?\", \"ordered\" : \"?\", \"deletes\" : [{ \"q\" : { \"password\" : \"?\" }, \"limit\" : \"?\" }] }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"delete\" : \"?\", \"ordered\" : \"?\", \"deletes\" : [{ \"q\" : { \"password\" : \"?\" }, \"limit\" : \"?\" }] }" ||
+ it == "{\"delete\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"deletes\": [{\"q\": {\"password\": \"?\"}, \"limit\": \"?\"}]}"
+ true
+ }
}
trace(1, 1) {
- mongoSpan(it, 0, "{ \"count\" : \"$collectionName\", \"query\" : { } }")
+ mongoSpan(it, 0) {
+ assert it == "{ \"count\" : \"$collectionName\", \"query\" : { } }" ||
+ it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
+ true
+ }
}
}
@@ -236,11 +238,11 @@ class MongoAsyncClientTest extends AgentTestRunner {
}
}
- def mongoSpan(TraceAssert trace, int index, String statement, Object parentSpan = null, Throwable exception = null) {
+ def mongoSpan(TraceAssert trace, int index, Closure<Boolean> statementEval, Object parentSpan = null, Throwable exception = null) {
trace.span(index) {
serviceName "mongo"
operationName "mongo.query"
- resourceName statement
+ resourceName statementEval
spanType DDSpanTypes.MONGO
if (parentSpan == null) {
parent()
@@ -251,7 +253,7 @@ class MongoAsyncClientTest extends AgentTestRunner {
"$Tags.COMPONENT.key" "java-mongo"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.DB_INSTANCE.key" "test_db"
- "$Tags.DB_STATEMENT.key" statement
+ "$Tags.DB_STATEMENT.key" statementEval
"$Tags.DB_TYPE.key" "mongo"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"

ADD
dd-java-agent/instrumentation/mongo/mongo.gradle
+apply from: "${rootDir}/gradle/java.gradle"
+
+dependencies {
+ testAnnotationProcessor deps.autoservice
+ testImplementation deps.autoservice
+
+ testCompile project(':dd-java-agent:testing')
+ testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '2.2.0'
+}
+
+// Forcing strict test execution order (no parallel execution) to ensure proper mongo executable initialization.
+List<Test> testTasks = []
+tasks.withType(Test) { Test testTask ->
+ testTasks.each {
+ testTask.shouldRunAfter(it)
+ }
+ testTasks.add(testTask)
+}
+subprojects {
+ afterEvaluate {
+ tasks.withType(Test) { Test testTask ->
+ testTasks.each {
+ testTask.shouldRunAfter(it)
+ }
+ testTasks.add(testTask)
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/mongo/src/test/groovy/MongoBaseTest.groovy
+import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.agent.test.utils.PortUtils
+import de.flapdoodle.embed.mongo.MongodExecutable
+import de.flapdoodle.embed.mongo.MongodProcess
+import de.flapdoodle.embed.mongo.MongodStarter
+import de.flapdoodle.embed.mongo.config.IMongodConfig
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
+import de.flapdoodle.embed.mongo.config.Net
+import de.flapdoodle.embed.mongo.distribution.Version
+import de.flapdoodle.embed.process.runtime.Network
+import spock.lang.Shared
+
+/**
+ * Testing needs to be in a centralized project.
+ * If tests in multiple different projects are using embedded mongo,
+ * they downloader is at risk of a race condition.
+ */
+class MongoBaseTest extends AgentTestRunner {
+ // https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo#executable-collision
+ private static final MongodStarter starter = MongodStarter.getDefaultInstance()
+
+ @Shared
+ int port = PortUtils.randomOpenPort()
+ @Shared
+ MongodExecutable mongodExe
+ @Shared
+ MongodProcess mongod
+
+ def setup() throws Exception {
+ final IMongodConfig mongodConfig =
+ new MongodConfigBuilder()
+ .version(Version.Main.PRODUCTION)
+ .net(new Net("localhost", port, Network.localhostIsIPv6()))
+ .build()
+
+ mongodExe = starter.prepare(mongodConfig)
+ mongod = mongodExe.start()
+ }
+
+ def cleanup() throws Exception {
+ mongod?.stop()
+ mongod = null
+ mongodExe?.stop()
+ mongodExe = null
+ }
+
+ def "test port open"() {
+ when:
+ new Socket("localhost", port)
+
+ then:
+ noExceptionThrown()
+ }
+}

ADD
dd-java-agent/instrumentation/mongo/src/test/java/NoOpInstrumentation.java
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import net.bytebuddy.agent.builder.AgentBuilder;
+
+@AutoService(Instrumenter.class)
+public class NoOpInstrumentation implements Instrumenter {
+
+ @Override
+ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
+ return agentBuilder;
+ }
+}

MODIFY
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/SpanAssert.groovy
dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/asserts/SpanAssert.groovy
@@ -64,6 +64,11 @@ class SpanAssert {
checked.resourceName = true
}
+ def resourceName(Closure<Boolean> eval) {
+ assert eval(span.resourceName)
+ checked.resourceName = true
+ }
+
def resourceNameContains(String... resourceNameParts) {
assertSpanNameContains(span.resourceName, resourceNameParts)
checked.resourceName = true

MODIFY
settings.gradle
settings.gradle
@@ -66,8 +66,9 @@ include ':dd-java-agent:instrumentation:jsp-2.3'
include ':dd-java-agent:instrumentation:kafka-clients-0.11'
include ':dd-java-agent:instrumentation:kafka-streams-0.11'
include ':dd-java-agent:instrumentation:lettuce-5'
-include ':dd-java-agent:instrumentation:mongo-3.1'
-include ':dd-java-agent:instrumentation:mongo-async-3.3'
+include ':dd-java-agent:instrumentation:mongo'
+include ':dd-java-agent:instrumentation:mongo:driver-3.1'
+include ':dd-java-agent:instrumentation:mongo:driver-async-3.3'
include ':dd-java-agent:instrumentation:netty-4.0'
include ':dd-java-agent:instrumentation:netty-4.1'
include ':dd-java-agent:instrumentation:okhttp-3'

Ensure gradle runs base mongo test first

Otherwise there is a race condition between the projects initializing the mongo instance.