From 826d8ac7815b11506d65ececfc8077c338d628c5 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 7 Apr 2021 14:02:56 +0900 Subject: [PATCH] Cleanup mongodb tests (#2732) * Cleanup mongodb tests * Drift * Cleanup * Timeout * Cleanup --- .../src/test/groovy/MongoClientTest.groovy | 195 ++--------- .../src/test/groovy/MongoClientTest.groovy | 195 ++--------- .../groovy/Mongo4ReactiveClientTest.groovy | 225 +++---------- .../src/test/groovy/MongoClientTest.groovy | 187 +++-------- .../test/groovy/MongoAsyncClientTest.groovy | 250 +++----------- .../mongo/mongo-testing/mongo-testing.gradle | 2 +- .../groovy/AbstractMongoClientTest.groovy | 307 ++++++++++++++++++ .../src/main/groovy/MongoBaseTest.groovy | 102 ------ 8 files changed, 515 insertions(+), 948 deletions(-) create mode 100644 instrumentation/mongo/mongo-testing/src/main/groovy/AbstractMongoClientTest.groovy delete mode 100644 instrumentation/mongo/mongo-testing/src/main/groovy/MongoBaseTest.groovy diff --git a/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy index 76bfc6d17a..956f0b7616 100644 --- a/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy +++ b/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy @@ -17,135 +17,63 @@ import org.bson.BsonString import org.bson.Document import spock.lang.Shared -class MongoClientTest extends MongoBaseTest { +class MongoClientTest extends AbstractMongoClientTest { @Shared MongoClient client - def setup() throws Exception { + def setupSpec() throws Exception { client = new MongoClient(new ServerAddress("localhost", port), MongoClientOptions.builder() .description("some-description") .build()) } - def cleanup() throws Exception { + def cleanupSpec() throws Exception { client?.close() client = null } - def "test create collection"() { - setup: + @Override + void createCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - // Tests the fix for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/457 - // TracingCommandListener might get added multiple times if clientOptions are built using existing clientOptions or when calling a build method twice. - // This test asserts that duplicate traces are not created in those cases. - def "test create collection with already built ClientOptions"() { - setup: + @Override + void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = new MongoClient("localhost", port).getDatabase(dbName) + db.createCollection(collectionName) + } + + @Override + void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { def clientOptions = client.mongoClientOptions def newClientOptions = MongoClientOptions.builder(clientOptions).build() MongoDatabase db = new MongoClient(new ServerAddress("localhost", port), newClientOptions).getDatabase(dbName) - - when: db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test create collection no description"() { - setup: - MongoDatabase db = new MongoClient("localhost", port).getDatabase(dbName) - - when: - db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" - } - - def "test get collection"() { - setup: + @Override + int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: - int count = db.getCollection(collectionName).count() - - then: - count == 0 - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return db.getCollection(collectionName).count() } - def "test insert"() { - setup: + @Override + int insert(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: collection.insertOne(new Document("password", "SECRET")) - - then: - collection.count() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "insert", collectionName, dbName, "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return collection.count() } - def "test update"() { - setup: + @Override + int update(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) @@ -154,31 +82,15 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = collection.updateOne( new BsonDocument("password", new BsonString("OLDPW")), new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - - then: - result.modifiedCount == 1 - collection.count() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "update", collectionName, dbName, "{\"update\":\"$collectionName\",\"ordered\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"\$set\":{\"password\":\"?\"}}}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + collection.count() + return result.modifiedCount } - def "test delete"() { - setup: + @Override + int delete(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) @@ -187,29 +99,13 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - - then: - result.deletedCount == 1 - collection.count() == 0 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "delete", collectionName, dbName, "{\"delete\":\"$collectionName\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + collection.count() + return result.deletedCount } - def "test collection name for getMore command"() { - setup: + @Override + void getMore(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def coll = db.getCollection(collectionName) @@ -217,46 +113,19 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: collection.find().filter(new Document("_id", new Document('$gte', 0))) .batchSize(2).into(new ArrayList()) - - then: - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "find", collectionName, dbName, '{"find":"testCollection","filter":{"_id":{"$gte":"?"}},"batchSize":"?"}') - } - trace(1, 1) { - mongoSpan(it, 0, "getMore", collectionName, dbName, '{"getMore":"?","collection":"?","batchSize":"?"}') - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test error"() { - setup: + @Override + void error(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: collection.updateOne(new BsonDocument(), new BsonDocument()) - - then: - thrown(IllegalArgumentException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = "testCollection" } def "test client failure"() { @@ -275,6 +144,6 @@ class MongoClientTest extends MongoBaseTest { where: dbName = "test_db" - collectionName = "testCollection" + collectionName = createCollectionName() } } diff --git a/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy index 9df7d1bc74..d8d0c61378 100644 --- a/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy +++ b/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy @@ -18,12 +18,12 @@ import org.bson.BsonString import org.bson.Document import spock.lang.Shared -class MongoClientTest extends MongoBaseTest { +class MongoClientTest extends AbstractMongoClientTest { @Shared MongoClient client - def setup() throws Exception { + def setupSpec() throws Exception { client = MongoClients.create(MongoClientSettings.builder() .applyToClusterSettings({ builder -> builder.hosts(Arrays.asList( @@ -33,35 +33,25 @@ class MongoClientTest extends MongoBaseTest { .build()) } - def cleanup() throws Exception { + def cleanupSpec() throws Exception { client?.close() client = null } - def "test create collection"() { - setup: + @Override + void createCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - // Tests the fix for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/457 - // TracingCommandListener might get added multiple times if ClientSettings are built using existing ClientSettings or when calling a build method twice. - // This test asserts that duplicate traces are not created in those cases. - def "test create collection with already built ClientSettings"() { - setup: + @Override + void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://localhost:${port}").getDatabase(dbName) + db.createCollection(collectionName) + } + + @Override + void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { def clientSettings = MongoClientSettings.builder() .applyToClusterSettings({ builder -> builder.hosts(Arrays.asList( @@ -71,91 +61,29 @@ class MongoClientTest extends MongoBaseTest { .build() def newClientSettings = MongoClientSettings.builder(clientSettings).build() MongoDatabase db = MongoClients.create(newClientSettings).getDatabase(dbName) - - when: db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test create collection no description"() { - setup: - MongoDatabase db = MongoClients.create("mongodb://localhost:" + port).getDatabase(dbName) - - when: - db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" - } - - def "test get collection"() { - setup: + @Override + int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: - int count = db.getCollection(collectionName).count() - - then: - count == 0 - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return db.getCollection(collectionName).count() } - def "test insert"() { - setup: + @Override + int insert(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: collection.insertOne(new Document("password", "SECRET")) - - then: - collection.count() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "insert", collectionName, dbName, "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return collection.count() } - def "test update"() { - setup: + @Override + int update(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) @@ -164,31 +92,15 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = collection.updateOne( new BsonDocument("password", new BsonString("OLDPW")), new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - - then: - result.modifiedCount == 1 - collection.count() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "update", collectionName, dbName, "{\"update\":\"$collectionName\",\"ordered\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"\$set\":{\"password\":\"?\"}}}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + collection.count() + return result.modifiedCount } - def "test delete"() { - setup: + @Override + int delete(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) @@ -197,29 +109,13 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - - then: - result.deletedCount == 1 - collection.count() == 0 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "delete", collectionName, dbName, "{\"delete\":\"$collectionName\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + collection.count() + return result.deletedCount } - def "test collection name for getMore command"() { - setup: + @Override + void getMore(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def coll = db.getCollection(collectionName) @@ -227,46 +123,19 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: collection.find().filter(new Document("_id", new Document('$gte', 0))) .batchSize(2).into(new ArrayList()) - - then: - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "find", collectionName, dbName, '{"find":"testCollection","filter":{"_id":{"$gte":"?"}},"batchSize":"?"}') - } - trace(1, 1) { - mongoSpan(it, 0, "getMore", collectionName, dbName, '{"getMore":"?","collection":"?","batchSize":"?"}') - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test error"() { - setup: + @Override + void error(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: collection.updateOne(new BsonDocument(), new BsonDocument()) - - then: - thrown(IllegalArgumentException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = "testCollection" } def "test client failure"() { @@ -284,6 +153,6 @@ class MongoClientTest extends MongoBaseTest { where: dbName = "test_db" - collectionName = "testCollection" + collectionName = createCollectionName() } } diff --git a/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/Mongo4ReactiveClientTest.groovy b/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/Mongo4ReactiveClientTest.groovy index a6a958b262..de53e2d945 100644 --- a/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/Mongo4ReactiveClientTest.groovy +++ b/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/Mongo4ReactiveClientTest.groovy @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace import com.mongodb.client.result.DeleteResult @@ -12,105 +11,57 @@ import com.mongodb.reactivestreams.client.MongoClient import com.mongodb.reactivestreams.client.MongoClients import com.mongodb.reactivestreams.client.MongoCollection import com.mongodb.reactivestreams.client.MongoDatabase -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import java.util.concurrent.CompletableFuture import java.util.concurrent.CountDownLatch import org.bson.BsonDocument import org.bson.BsonString import org.bson.Document +import org.junit.AssumptionViolatedException import org.reactivestreams.Subscriber import org.reactivestreams.Subscription import spock.lang.Shared -class Mongo4ReactiveClientTest extends MongoBaseTest { +class Mongo4ReactiveClientTest extends AbstractMongoClientTest { @Shared MongoClient client - def setup() throws Exception { + def setupSpec() throws Exception { client = MongoClients.create("mongodb://localhost:$port") } - def cleanup() throws Exception { + def cleanupSpec() throws Exception { client?.close() client = null } - def "test create collection"() { - setup: + @Override + void createCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: db.createCollection(collectionName).subscribe(toSubscriber {}) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || - it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test create collection no description"() { - setup: - MongoDatabase db = MongoClients.create("mongodb://localhost:$port").getDatabase(dbName) - - when: + @Override + void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://localhost:${port}").getDatabase(dbName) db.createCollection(collectionName).subscribe(toSubscriber {}) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, { - assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || - it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - }) - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test get collection"() { - setup: - MongoDatabase db = client.getDatabase(dbName) + @Override + void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { + throw new AssumptionViolatedException("not tested on 4.0") + } - when: - def count = new CompletableFuture() + @Override + int getCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName) + def count = new CompletableFuture() db.getCollection(collectionName).estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) - - then: - count.get() == 0 - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "count", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return count.join() } - def "test insert"() { - setup: + @Override + int insert(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def latch1 = new CountDownLatch(1) @@ -120,39 +71,15 @@ class Mongo4ReactiveClientTest extends MongoBaseTest { return db.getCollection(collectionName) } ignoreTracesAndClear(2) - - when: - def count = new CompletableFuture() + def count = new CompletableFuture() collection.insertOne(new Document("password", "SECRET")).subscribe(toSubscriber { collection.estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) }) - - then: - count.get() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "insert", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}" || - it == "{\"insert\": \"$collectionName\", \"ordered\": \"?\", \"\$db\": \"?\", \"documents\": [{\"_id\": \"?\", \"password\": \"?\"}]}" - true - } - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return count.join() } - def "test update"() { - setup: + @Override + int update(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def latch1 = new CountDownLatch(1) @@ -165,8 +92,6 @@ class Mongo4ReactiveClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = new CompletableFuture() def count = new CompletableFuture() collection.updateOne( @@ -175,34 +100,11 @@ class Mongo4ReactiveClientTest extends MongoBaseTest { result.complete(it) collection.estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) }) - - then: - result.get().modifiedCount == 1 - count.get() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "update", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"update\":\"$collectionName\",\"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, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return result.join().modifiedCount } - def "test delete"() { - setup: + @Override + int delete(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def latch1 = new CountDownLatch(1) @@ -215,38 +117,37 @@ class Mongo4ReactiveClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = new CompletableFuture() def count = new CompletableFuture() collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))).subscribe(toSubscriber { result.complete(it) collection.estimatedDocumentCount().subscribe(toSubscriber { count.complete(it) }) }) + return result.join().deletedCount + } - then: - result.get().deletedCount == 1 - count.get() == 0 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "delete", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"delete\":\"$collectionName\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}" || - it == "{\"delete\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"deletes\": [{\"q\": {\"password\": \"?\"}, \"limit\": \"?\"}]}" - true - } - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } + @Override + void getMore(String dbName, String collectionName) { + throw new AssumptionViolatedException("not tested on reactive") + } + + @Override + void error(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + def latch = new CountDownLatch(1) + db.createCollection(collectionName).subscribe(toSubscriber { + latch.countDown() + }) + latch.await() + return db.getCollection(collectionName) } - - where: - dbName = "test_db" - collectionName = "testCollection" + ignoreTracesAndClear(1) + def result = new CompletableFuture() + collection.updateOne(new BsonDocument(), new BsonDocument()).subscribe(toSubscriber { + result.complete(it) + }) + throw result.join() } def Subscriber toSubscriber(Closure closure) { @@ -273,30 +174,4 @@ class Mongo4ReactiveClientTest extends MongoBaseTest { } } } - - def mongoSpan(TraceAssert trace, int index, - String operation, String collection, - String dbName, Closure statementEval, - Object parentSpan = null, Throwable exception = null) { - trace.span(index) { - name { operation + " " + dbName + "." + collection } - kind CLIENT - if (parentSpan == null) { - hasNoParent() - } else { - childOf((SpanData) parentSpan) - } - attributes { - "$SemanticAttributes.NET_PEER_NAME.key" "localhost" - "$SemanticAttributes.NET_PEER_IP.key" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT.key" port - "$SemanticAttributes.DB_CONNECTION_STRING.key" "mongodb://localhost:" + port - "$SemanticAttributes.DB_STATEMENT.key" statementEval - "$SemanticAttributes.DB_SYSTEM.key" "mongodb" - "$SemanticAttributes.DB_NAME.key" dbName - "$SemanticAttributes.DB_OPERATION.key" operation - "$SemanticAttributes.DB_MONGODB_COLLECTION.key" collection - } - } - } } diff --git a/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/MongoClientTest.groovy index b51389df0d..1142791c43 100644 --- a/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/MongoClientTest.groovy +++ b/instrumentation/mongo/mongo-4.0-testing/src/test/groovy/MongoClientTest.groovy @@ -3,10 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace -import com.mongodb.MongoTimeoutException import com.mongodb.client.MongoClient import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection @@ -14,110 +12,60 @@ import com.mongodb.client.MongoDatabase import org.bson.BsonDocument import org.bson.BsonString import org.bson.Document +import org.junit.AssumptionViolatedException import spock.lang.Shared -class MongoClientTest extends MongoBaseTest { +class MongoClientTest extends AbstractMongoClientTest { @Shared MongoClient client - def setup() throws Exception { + def setupSpec() throws Exception { client = MongoClients.create("mongodb://localhost:$port") } - def cleanup() throws Exception { + def cleanupSpec() throws Exception { client?.close() client = null } - def "test create collection"() { - setup: + @Override + void createCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test create collection no description"() { - setup: - MongoDatabase db = MongoClients.create("mongodb://localhost:$port").getDatabase(dbName) - - when: + @Override + void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://localhost:${port}").getDatabase(dbName) db.createCollection(collectionName) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName, "{\"create\":\"$collectionName\",\"capped\":\"?\"}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test get collection"() { - setup: + @Override + void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { + throw new AssumptionViolatedException("not tested on 4.0") + } + + @Override + int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: - int count = db.getCollection(collectionName).estimatedDocumentCount() - - then: - count == 0 - assertTraces(1) { - trace(0,1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return db.getCollection(collectionName).estimatedDocumentCount() } - def "test insert"() { - setup: + @Override + int insert(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: collection.insertOne(new Document("password", "SECRET")) - - then: - collection.estimatedDocumentCount() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "insert", collectionName, dbName, "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return collection.estimatedDocumentCount() } - def "test update"() { - setup: + @Override + int update(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) @@ -126,31 +74,15 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = collection.updateOne( new BsonDocument("password", new BsonString("OLDPW")), new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - - then: - result.modifiedCount == 1 - collection.estimatedDocumentCount() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "update", collectionName, dbName, "{\"update\":\"$collectionName\",\"ordered\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"\$set\":{\"password\":\"?\"}}}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + collection.estimatedDocumentCount() + return result.modifiedCount } - def "test delete"() { - setup: + @Override + int delete(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) @@ -159,65 +91,32 @@ class MongoClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - - then: - result.deletedCount == 1 - collection.estimatedDocumentCount() == 0 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "delete", collectionName, dbName, "{\"delete\":\"$collectionName\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}") - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName, "{\"count\":\"$collectionName\",\"query\":{}}") - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + collection.estimatedDocumentCount() + return result.deletedCount } - def "test error"() { - setup: + @Override + void getMore(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + def coll = db.getCollection(collectionName) + coll.insertMany([new Document("_id", 0), new Document("_id", 1), new Document("_id", 2)]) + return coll + } + ignoreTracesAndClear(1) + collection.find().filter(new Document("_id", new Document('$gte', 0))) + .batchSize(2).into(new ArrayList()) + } + + @Override + void error(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) db.createCollection(collectionName) return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: collection.updateOne(new BsonDocument(), new BsonDocument()) - - then: - thrown(IllegalArgumentException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = "testCollection" } - - def "test client failure"() { - setup: - def client = MongoClients.create("mongodb://localhost:$UNUSABLE_PORT/?serverselectiontimeoutms=10") - - when: - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - - then: - thrown(MongoTimeoutException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = "testCollection" - } - } diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy index 999efa859f..1ea58f2994 100644 --- a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy +++ b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace import com.mongodb.ConnectionString @@ -16,22 +15,20 @@ import com.mongodb.async.client.MongoDatabase import com.mongodb.client.result.DeleteResult import com.mongodb.client.result.UpdateResult import com.mongodb.connection.ClusterSettings -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import java.util.concurrent.CompletableFuture import java.util.concurrent.CountDownLatch import org.bson.BsonDocument import org.bson.BsonString import org.bson.Document +import org.junit.AssumptionViolatedException import spock.lang.Shared -class MongoAsyncClientTest extends MongoBaseTest { +class MongoAsyncClientTest extends AbstractMongoClientTest { @Shared MongoClient client - def setup() throws Exception { + def setupSpec() throws Exception { client = MongoClients.create( MongoClientSettings.builder() .clusterSettings( @@ -42,112 +39,41 @@ class MongoAsyncClientTest extends MongoBaseTest { .build()) } - def cleanup() throws Exception { + def cleanupSpec() throws Exception { client?.close() client = null } - def "test create collection"() { - setup: + @Override + void createCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: db.createCollection(collectionName, toCallback {}) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || - it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - // Tests the fix for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/457 - // TracingCommandListener might get added multiple times if ClientSettings are built using existing ClientSettings or when calling a build method twice. - // This test asserts that duplicate traces are not created in those cases. - def "test create collection with already built ClientSettings"() { - setup: + @Override + void createCollectionNoDescription(String dbName, String collectionName) { + MongoDatabase db = MongoClients.create("mongodb://localhost:$port").getDatabase(dbName) + db.createCollection(collectionName, toCallback {}) + } + + @Override + void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { def clientSettings = client.settings def newClientSettings = MongoClientSettings.builder(clientSettings).build() MongoDatabase db = MongoClients.create(newClientSettings).getDatabase(dbName) - - when: db.createCollection(collectionName, toCallback {}) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || - it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" } - def "test create collection no description"() { - setup: - MongoDatabase db = MongoClients.create("mongodb://localhost:$port").getDatabase(dbName) - - when: - db.createCollection(collectionName, toCallback {}) - - then: - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "create", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || - it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" - } - - def "test get collection"() { - setup: + @Override + int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) - - when: - def count = new CompletableFuture() + def count = new CompletableFuture() db.getCollection(collectionName).count toCallback { count.complete(it) } - - then: - count.get() == 0 - assertTraces(1) { - trace(0, 1) { - mongoSpan(it, 0, "count", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return count.join() } - def "test insert"() { - setup: + @Override + int insert(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def latch1 = new CountDownLatch(1) @@ -156,39 +82,15 @@ class MongoAsyncClientTest extends MongoBaseTest { return db.getCollection(collectionName) } ignoreTracesAndClear(1) - - when: - def count = new CompletableFuture() + def count = new CompletableFuture() collection.insertOne(new Document("password", "SECRET"), toCallback { collection.count toCallback { count.complete(it) } }) - - then: - count.get() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "insert", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}" || - it == "{\"insert\": \"$collectionName\", \"ordered\": \"?\", \"\$db\": \"?\", \"documents\": [{\"_id\": \"?\", \"password\": \"?\"}]}" - true - } - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return count.get() } - def "test update"() { - setup: + @Override + int update(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def latch1 = new CountDownLatch(1) @@ -201,8 +103,6 @@ class MongoAsyncClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = new CompletableFuture() def count = new CompletableFuture() collection.updateOne( @@ -211,34 +111,11 @@ class MongoAsyncClientTest extends MongoBaseTest { result.complete(it) collection.count toCallback { count.complete(it) } }) - - then: - result.get().modifiedCount == 1 - count.get() == 1 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "update", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"update\":\"$collectionName\",\"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, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } - } - - where: - dbName = "test_db" - collectionName = "testCollection" + return result.get().modifiedCount } - def "test delete"() { - setup: + @Override + int delete(String dbName, String collectionName) { MongoCollection collection = runUnderTrace("setup") { MongoDatabase db = client.getDatabase(dbName) def latch1 = new CountDownLatch(1) @@ -251,38 +128,37 @@ class MongoAsyncClientTest extends MongoBaseTest { return coll } ignoreTracesAndClear(1) - - when: def result = new CompletableFuture() def count = new CompletableFuture() collection.deleteOne(new BsonDocument("password", new BsonString("SECRET")), toCallback { result.complete(it) collection.count toCallback { count.complete(it) } }) + return result.get().deletedCount + } - then: - result.get().deletedCount == 1 - count.get() == 0 - assertTraces(2) { - trace(0, 1) { - mongoSpan(it, 0, "delete", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"delete\":\"$collectionName\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}" || - it == "{\"delete\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"deletes\": [{\"q\": {\"password\": \"?\"}, \"limit\": \"?\"}]}" - true - } - } - trace(1, 1) { - mongoSpan(it, 0, "count", collectionName, dbName) { - assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" || - it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" - true - } - } + @Override + void getMore(String dbName, String collectionName) { + throw new AssumptionViolatedException("not tested on async") + } + + @Override + void error(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + def latch = new CountDownLatch(1) + db.createCollection(collectionName, toCallback { + latch.countDown() + }) + latch.await() + return db.getCollection(collectionName) } - - where: - dbName = "test_db" - collectionName = "testCollection" + ignoreTracesAndClear(1) + def result = new CompletableFuture() + collection.updateOne(new BsonDocument(), new BsonDocument(), toCallback { + result.complete(it) + }) + throw result.join() } SingleResultCallback toCallback(Closure closure) { @@ -297,30 +173,4 @@ class MongoAsyncClientTest extends MongoBaseTest { } } } - - def mongoSpan(TraceAssert trace, int index, - String operation, String collection, - String dbName, Closure statementEval, - Object parentSpan = null, Throwable exception = null) { - trace.span(index) { - name { operation + " " + dbName + "." + collection } - kind CLIENT - if (parentSpan == null) { - hasNoParent() - } else { - childOf((SpanData) parentSpan) - } - attributes { - "$SemanticAttributes.NET_PEER_NAME.key" "localhost" - "$SemanticAttributes.NET_PEER_IP.key" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT.key" port - "$SemanticAttributes.DB_CONNECTION_STRING.key" "mongodb://localhost:" + port - "$SemanticAttributes.DB_STATEMENT.key" statementEval - "$SemanticAttributes.DB_SYSTEM.key" "mongodb" - "$SemanticAttributes.DB_NAME.key" dbName - "$SemanticAttributes.DB_OPERATION.key" operation - "$SemanticAttributes.DB_MONGODB_COLLECTION.key" collection - } - } - } } diff --git a/instrumentation/mongo/mongo-testing/mongo-testing.gradle b/instrumentation/mongo/mongo-testing/mongo-testing.gradle index 84c3fd7b28..f7f3ad70ce 100644 --- a/instrumentation/mongo/mongo-testing/mongo-testing.gradle +++ b/instrumentation/mongo/mongo-testing/mongo-testing.gradle @@ -2,7 +2,7 @@ apply from: "$rootDir/gradle/java.gradle" dependencies { api project(':testing-common') - api group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5' + api group: 'org.testcontainers', name: 'mongodb', version: versions.testcontainers implementation deps.groovy implementation deps.opentelemetryApi diff --git a/instrumentation/mongo/mongo-testing/src/main/groovy/AbstractMongoClientTest.groovy b/instrumentation/mongo/mongo-testing/src/main/groovy/AbstractMongoClientTest.groovy new file mode 100644 index 0000000000..bd3f0a6348 --- /dev/null +++ b/instrumentation/mongo/mongo-testing/src/main/groovy/AbstractMongoClientTest.groovy @@ -0,0 +1,307 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.api.trace.SpanKind.CLIENT + +import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification +import io.opentelemetry.instrumentation.test.asserts.TraceAssert +import io.opentelemetry.sdk.trace.data.SpanData +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import java.util.concurrent.atomic.AtomicInteger +import org.slf4j.LoggerFactory +import org.testcontainers.containers.GenericContainer +import org.testcontainers.containers.output.Slf4jLogConsumer +import spock.lang.Shared + +abstract class AbstractMongoClientTest extends AgentInstrumentationSpecification { + + @Shared + GenericContainer mongodb + + @Shared + int port + + def setupSpec() { + mongodb = new GenericContainer("mongo:3.2") + .withExposedPorts(27017) + .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("mongodb"))) + mongodb.start() + + port = mongodb.getMappedPort(27017) + } + + def cleanupSpec() throws Exception { + mongodb.stop() + } + + // Different client versions have different APIs to do these operations. If adding a test for a new + // version, refer to existing ones on how to implement these operations. + + abstract void createCollection(String dbName, String collectionName) + + abstract void createCollectionNoDescription(String dbName, String collectionName) + + // Tests the fix for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/457 + // TracingCommandListener might get added multiple times if clientOptions are built using existing clientOptions or when calling a build method twice. + // This test asserts that duplicate traces are not created in those cases. + abstract void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) + + abstract int getCollection(String dbName, String collectionName) + + abstract int insert(String dbName, String collectionName) + + abstract int update(String dbName, String collectionName) + + abstract int delete(String dbName, String collectionName) + + abstract void getMore(String dbName, String collectionName) + + abstract void error(String dbName, String collectionName) + + def "test port open"() { + when: + new Socket("localhost", port) + + then: + noExceptionThrown() + } + + def "test create collection"() { + when: + createCollection(dbName, collectionName) + + then: + assertTraces(1) { + trace(0, 1) { + mongoSpan(it, 0, "create", collectionName, dbName) { + assert it == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || + it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test create collection no description"() { + when: + createCollectionNoDescription(dbName, collectionName) + + then: + assertTraces(1) { + trace(0, 1) { + mongoSpan(it, 0, "create", collectionName, dbName, { + assert it == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || + it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + }) + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test get collection"() { + when: + def count = getCollection(dbName, collectionName) + + then: + count == 0 + assertTraces(1) { + trace(0, 1) { + mongoSpan(it, 0, "count", collectionName, dbName) { + assert it == "{\"count\":\"$collectionName\",\"query\":{}}" || + it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test insert"() { + when: + def count = insert(dbName, collectionName) + + then: + count == 1 + assertTraces(2) { + trace(0, 1) { + mongoSpan(it, 0, "insert", collectionName, dbName) { + 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, dbName) { + assert it == "{\"count\":\"$collectionName\",\"query\":{}}" || + it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test update"() { + when: + int modifiedCount = update(dbName, collectionName) + + then: + modifiedCount == 1 + assertTraces(2) { + trace(0, 1) { + mongoSpan(it, 0, "update", collectionName, dbName) { + assert it == "{\"update\":\"$collectionName\",\"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, dbName) { + assert it == "{\"count\":\"$collectionName\",\"query\":{}}" || + it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test delete"() { + when: + int deletedCount = delete(dbName, collectionName) + + then: + deletedCount == 1 + assertTraces(2) { + trace(0, 1) { + mongoSpan(it, 0, "delete", collectionName, dbName) { + assert it == "{\"delete\":\"$collectionName\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}" || + it == "{\"delete\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"deletes\": [{\"q\": {\"password\": \"?\"}, \"limit\": \"?\"}]}" + true + } + } + trace(1, 1) { + mongoSpan(it, 0, "count", collectionName, dbName) { + assert it == "{\"count\":\"$collectionName\",\"query\":{}}" || + it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test collection name for getMore command"() { + when: + getMore(dbName, collectionName) + + then: + assertTraces(2) { + trace(0, 1) { + mongoSpan(it, 0, "find", collectionName, dbName) { + assert it == '{"find":"' + collectionName + '","filter":{"_id":{"$gte":"?"}},"batchSize":"?"}' + true + } + } + trace(1, 1) { + mongoSpan(it, 0, "getMore", collectionName, dbName) { + assert it == '{"getMore":"?","collection":"?","batchSize":"?"}' + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test error"() { + when: + error(dbName, collectionName) + + then: + thrown(IllegalArgumentException) + // Unfortunately not caught by our instrumentation. + assertTraces(0) {} + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + def "test create collection with already built ClientOptions"() { + when: + createCollectionWithAlreadyBuiltClientOptions(dbName, collectionName) + + then: + assertTraces(1) { + trace(0, 1) { + mongoSpan(it, 0, "create", collectionName, dbName) { + assert it == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + + private static final AtomicInteger collectionIndex = new AtomicInteger() + + def createCollectionName() { + return "testCollection-${collectionIndex.getAndIncrement()}" + } + + def mongoSpan(TraceAssert trace, int index, + String operation, String collection, + String dbName, Closure statementEval, + Object parentSpan = null, Throwable exception = null) { + trace.span(index) { + name { operation + " " + dbName + "." + collection } + kind CLIENT + if (parentSpan == null) { + hasNoParent() + } else { + childOf((SpanData) parentSpan) + } + attributes { + "$SemanticAttributes.NET_PEER_NAME.key" "localhost" + "$SemanticAttributes.NET_PEER_IP.key" "127.0.0.1" + "$SemanticAttributes.NET_PEER_PORT.key" port + "$SemanticAttributes.DB_STATEMENT.key" { + statementEval.call(it.replaceAll(" ", "")) + } + "$SemanticAttributes.DB_SYSTEM.key" "mongodb" + "$SemanticAttributes.DB_CONNECTION_STRING.key" "mongodb://localhost:" + port + "$SemanticAttributes.DB_NAME.key" dbName + "$SemanticAttributes.DB_OPERATION.key" operation + "$SemanticAttributes.DB_MONGODB_COLLECTION.key" collection + } + } + } +} diff --git a/instrumentation/mongo/mongo-testing/src/main/groovy/MongoBaseTest.groovy b/instrumentation/mongo/mongo-testing/src/main/groovy/MongoBaseTest.groovy deleted file mode 100644 index 1ec50c6a7e..0000000000 --- a/instrumentation/mongo/mongo-testing/src/main/groovy/MongoBaseTest.groovy +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import static io.opentelemetry.api.trace.SpanKind.CLIENT - -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.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.sdk.trace.data.SpanData -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -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 AgentInstrumentationSpecification { - // https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo#executable-collision - private static final MongodStarter STARTER = MongodStarter.getDefaultInstance() - - @Shared - int port = PortUtils.findOpenPort() - @Shared - MongodExecutable mongodExe - @Shared - MongodProcess mongod - - def setup() throws Exception { - IMongodConfig mongodConfig = - new MongodConfigBuilder() - .version(Version.Main.PRODUCTION) - .net(new Net("localhost", port, Network.localhostIsIPv6())) - .build() - - // using a system-wide file lock to prevent other modules that may be running in parallel - // from clobbering each other while downloading and extracting mongodb - def lockFile = new File(System.getProperty("java.io.tmpdir"), "prepare-embedded-mongo.lock") - def channel = new RandomAccessFile(lockFile, "rw").getChannel() - def lock = channel.lock() - try { - mongodExe = STARTER.prepare(mongodConfig) - } finally { - lock.release() - channel.close() - } - 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() - } - - def mongoSpan(TraceAssert trace, int index, - String operation, String collection, - String dbName, String statement, - Object parentSpan = null, Throwable exception = null) { - trace.span(index) { - name { operation + " " + dbName + "." + collection } - kind CLIENT - if (parentSpan == null) { - hasNoParent() - } else { - childOf((SpanData) parentSpan) - } - attributes { - "$SemanticAttributes.NET_PEER_NAME.key" "localhost" - "$SemanticAttributes.NET_PEER_IP.key" "127.0.0.1" - "$SemanticAttributes.NET_PEER_PORT.key" port - "$SemanticAttributes.DB_STATEMENT.key" { - it.replace(" ", "") == statement - } - "$SemanticAttributes.DB_SYSTEM.key" "mongodb" - "$SemanticAttributes.DB_CONNECTION_STRING.key" "mongodb://localhost:" + port - "$SemanticAttributes.DB_NAME.key" dbName - "$SemanticAttributes.DB_OPERATION.key" operation - "$SemanticAttributes.DB_MONGODB_COLLECTION.key" collection - } - } - } -}