Skip to content

Commit

Permalink
Initial driver-kotlin-coroutine implementation
Browse files Browse the repository at this point in the history
Wraps the driver-reactivestreams to provide a kotlin coroutine API

In general a faithful wrapping of the reactivestreams driver but with coroutine
support instead of using Publishers. Uses reified overloads to provide
synaptic sugar instead of passing resultClass.

JAVA-4870
  • Loading branch information
rozza authored Mar 30, 2023
1 parent 9609890 commit 21065b5
Show file tree
Hide file tree
Showing 49 changed files with 8,224 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .evergreen/run-kotlin-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ fi
echo "Running Kotlin tests"

./gradlew -version
./gradlew :driver-kotlin-sync:kCheck -Dorg.mongodb.test.uri=${MONGODB_URI} ${MULTI_MONGOS_URI_SYSTEM_PROPERTY}
./gradlew kCheck -Dorg.mongodb.test.uri=${MONGODB_URI} ${MULTI_MONGOS_URI_SYSTEM_PROPERTY}
3 changes: 3 additions & 0 deletions config/detekt/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
<CurrentIssues>
<ID>IteratorNotThrowingNoSuchElementException:MongoCursor.kt$MongoCursor&lt;T : Any> : IteratorCloseable</ID>
<ID>LargeClass:MongoCollectionTest.kt$MongoCollectionTest</ID>
<ID>LongMethod:FindFlowTest.kt$FindFlowTest$@Suppress("DEPRECATION") @Test fun shouldCallTheUnderlyingMethods()</ID>
<ID>LongMethod:FindIterableTest.kt$FindIterableTest$@Suppress("DEPRECATION") @Test fun shouldCallTheUnderlyingMethods()</ID>
<ID>MaxLineLength:MapReduceFlow.kt$MapReduceFlow$*</ID>
<ID>MaxLineLength:MapReduceIterable.kt$MapReduceIterable$*</ID>
<ID>SwallowedException:MockitoHelper.kt$MockitoHelper.DeepReflectionEqMatcher$e: Throwable</ID>
<ID>TooManyFunctions:ClientSession.kt$ClientSession : jClientSession</ID>
<ID>TooManyFunctions:FindFlow.kt$FindFlow&lt;T : Any> : Flow</ID>
<ID>TooManyFunctions:FindIterable.kt$FindIterable&lt;T : Any> : MongoIterable</ID>
<ID>TooManyFunctions:MongoCollection.kt$MongoCollection&lt;T : Any></ID>
<ID>TooManyFunctions:MongoDatabase.kt$MongoDatabase</ID>
Expand Down
21 changes: 21 additions & 0 deletions config/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@
<Bug pattern="NM_SAME_SIMPLE_NAME_AS_INTERFACE"/>
</Match>

<Match>
<Class name="~com.mongodb.kotlin.client.coroutine.ClientSession.*"/>
<Bug pattern="NM_SAME_SIMPLE_NAME_AS_INTERFACE"/>
</Match>

<Match>
<Class name="~com.mongodb.internal.async.client.ClientSession.*"/>
<Bug pattern="NM_SAME_SIMPLE_NAME_AS_INTERFACE"/>
Expand Down Expand Up @@ -252,4 +257,20 @@
<Method name="~.*validateAnnotations.*"/>
<Bug pattern="UC_USELESS_OBJECT"/>
</Match>


<!-- Spotbugs reports false positives for suspendable operations with default params
see: https://github.com/Kotlin/kotlinx.coroutines/issues/3099
-->
<Match>
<Class name="com.mongodb.kotlin.client.coroutine.MongoClient"/>
<Method name="startSession"/>
<Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"/>
</Match>
<Match>
<Class name="~com.mongodb.kotlin.client.coroutine.*"/>
<Bug pattern="NP_NONNULL_PARAM_VIOLATION"/>
</Match>


</FindBugsFilter>
189 changes: 189 additions & 0 deletions driver-kotlin-coroutine/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import io.gitlab.arturbosch.detekt.Detekt
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id("org.jetbrains.kotlin.jvm") version "1.8.10"
`java-library`

// Test based plugins
id("com.diffplug.spotless")
id("org.jetbrains.dokka") version "1.7.20"
id("io.gitlab.arturbosch.detekt") version "1.21.0"
}

repositories {
mavenCentral()
google()
}

base.archivesName.set("mongodb-driver-kotlin-coroutine")

description = "The MongoDB Kotlin Coroutine Driver"

ext.set("pomName", "MongoDB Kotlin Coroutine Driver")

sourceSets {
create("integrationTest") {
kotlin.srcDir("$projectDir/src/integration/kotlin")
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
compileClasspath += project(":driver-sync").sourceSets.test.get().output
runtimeClasspath += project(":driver-sync").sourceSets.test.get().output
compileClasspath += project(":driver-core").sourceSets.test.get().output
runtimeClasspath += project(":driver-core").sourceSets.test.get().output
compileClasspath += project(":bson").sourceSets.test.get().output
runtimeClasspath += project(":bson").sourceSets.test.get().output
}
}

val integrationTestImplementation: Configuration by
configurations.getting { extendsFrom(configurations.testImplementation.get()) }

dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

implementation(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactive")

api(project(path = ":bson", configuration = "default"))
api(project(path = ":driver-reactive-streams", configuration = "default"))

testImplementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
testImplementation("org.mockito:mockito-junit-jupiter:4.11.0")
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("io.github.classgraph:classgraph:4.8.154")

integrationTestImplementation("org.jetbrains.kotlin:kotlin-test-junit")
integrationTestImplementation(project(path = ":driver-sync"))
integrationTestImplementation(project(path = ":driver-core"))
integrationTestImplementation(project(path = ":bson"))
}

kotlin { explicitApi() }

tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" }

// ===========================
// Code Quality checks
// ===========================
spotless {
kotlinGradle {
ktfmt("0.39").dropboxStyle().configure { it.setMaxWidth(120) }
trimTrailingWhitespace()
indentWithSpaces()
endWithNewline()
licenseHeaderFile(rootProject.file("config/mongodb.license"), "(group|plugins|import|buildscript|rootProject)")
}

kotlin {
target("**/*.kt")
ktfmt().dropboxStyle().configure { it.setMaxWidth(120) }
trimTrailingWhitespace()
indentWithSpaces()
endWithNewline()
licenseHeaderFile(rootProject.file("config/mongodb.license"))
}

format("extraneous") {
target("*.xml", "*.yml", "*.md")
trimTrailingWhitespace()
indentWithSpaces()
endWithNewline()
}
}

tasks.named("check") { dependsOn("spotlessApply") }

detekt {
allRules = true // fail build on any finding
buildUponDefaultConfig = true // preconfigure defaults
config = rootProject.files("config/detekt/detekt.yml") // point to your custom config defining rules to run,
// overwriting default behavior
baseline = rootProject.file("config/detekt/baseline.xml") // a way of suppressing issues before introducing detekt
source =
files(
file("src/main/kotlin"),
file("src/test/kotlin"),
file("src/integrationTest/kotlin"),
)
}

tasks.withType<Detekt>().configureEach {
reports {
html.required.set(true) // observe findings in your browser with structure and code snippets
xml.required.set(true) // checkstyle like format mainly for integrations like Jenkins
txt.required.set(false) // similar to the console output, contains issue signature to manually edit
}
}

spotbugs {
showProgress.set(true)

tasks.getByName("spotbugsIntegrationTest") { enabled = false }
}

// ===========================
// Test Configuration
// ===========================
val integrationTest =
tasks.create("integrationTest", Test::class) {
description = "Runs the integration tests."
group = "verification"

testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
}

tasks.create("kCheck") {
description = "Runs all the kotlin checks"
group = "verification"

dependsOn("clean", "check", integrationTest)
tasks.findByName("check")?.mustRunAfter("clean")
tasks.findByName("integrationTest")?.mustRunAfter("check")
}

tasks.test { useJUnitPlatform() }

// ===========================
// Dokka Configuration
// ===========================
val dokkaOutputDir = "${rootProject.buildDir}/docs/${base.archivesName.get()}"

tasks.dokkaHtml.configure {
outputDirectory.set(file(dokkaOutputDir))
moduleName.set(base.archivesName.get())
}

val cleanDokka by tasks.register<Delete>("cleanDokka") { delete(dokkaOutputDir) }

project.parent?.tasks?.named("docs") {
dependsOn(tasks.dokkaHtml)
mustRunAfter(cleanDokka)
}

tasks.javadocJar.configure {
dependsOn(cleanDokka, tasks.dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaOutputDir)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.kotlin.client.coroutine

import com.mongodb.client.AbstractCrudTest
import com.mongodb.client.Fixture
import com.mongodb.client.MongoDatabase
import com.mongodb.event.CommandListener
import com.mongodb.kotlin.client.coroutine.syncadapter.SyncMongoClient
import org.bson.BsonArray
import org.bson.BsonDocument

data class CrudTest(
val filename: String,
val description: String,
val databaseName: String,
val collectionName: String,
val data: BsonArray,
val definition: BsonDocument,
val skipTest: Boolean
) : AbstractCrudTest(filename, description, databaseName, collectionName, data, definition, skipTest) {

private var mongoClient: SyncMongoClient? = null

override fun createMongoClient(commandListener: CommandListener) {
mongoClient =
SyncMongoClient(
MongoClient.create(Fixture.getMongoClientSettingsBuilder().addCommandListener(commandListener).build()))
}

override fun getDatabase(databaseName: String): MongoDatabase = mongoClient!!.getDatabase(databaseName)

override fun cleanUp() {
mongoClient?.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.kotlin.client.coroutine

import java.io.IOException
import java.net.URISyntaxException
import org.bson.BsonArray
import org.bson.BsonDocument
import org.junit.Assume.assumeFalse
import org.junit.runners.Parameterized

internal class UnifiedCrudTest(
fileDescription: String?,
testDescription: String,
schemaVersion: String,
runOnRequirements: BsonArray?,
entitiesArray: BsonArray,
initialData: BsonArray,
definition: BsonDocument
) : UnifiedTest(fileDescription, schemaVersion, runOnRequirements, entitiesArray, initialData, definition) {

init {
assumeFalse(testDescription == "Unacknowledged findOneAndReplace with hint string on 4.4+ server")
assumeFalse(testDescription == "Unacknowledged findOneAndReplace with hint document on 4.4+ server")
assumeFalse(testDescription == "Unacknowledged findOneAndUpdate with hint string on 4.4+ server")
assumeFalse(testDescription == "Unacknowledged findOneAndUpdate with hint document on 4.4+ server")
assumeFalse(testDescription == "Unacknowledged findOneAndDelete with hint string on 4.4+ server")
assumeFalse(testDescription == "Unacknowledged findOneAndDelete with hint document on 4.4+ server")
}

companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}: {1}")
@Throws(URISyntaxException::class, IOException::class)
fun data(): Collection<Array<Any?>?>? {
return getTestData("unified-test-format/crud")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2008-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mongodb.kotlin.client.coroutine

import com.mongodb.ClientEncryptionSettings
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient as JMongoClient
import com.mongodb.client.MongoDatabase as JMongoDatabase
import com.mongodb.client.gridfs.GridFSBucket
import com.mongodb.client.unified.UnifiedTest as JUnifiedTest
import com.mongodb.client.vault.ClientEncryption
import com.mongodb.kotlin.client.coroutine.syncadapter.SyncMongoClient
import org.bson.BsonArray
import org.bson.BsonDocument

internal abstract class UnifiedTest(
fileDescription: String?,
schemaVersion: String,
runOnRequirements: BsonArray?,
entitiesArray: BsonArray,
initialData: BsonArray,
definition: BsonDocument
) : JUnifiedTest(fileDescription, schemaVersion, runOnRequirements, entitiesArray, initialData, definition) {

override fun createMongoClient(settings: MongoClientSettings): JMongoClient =
SyncMongoClient(MongoClient.create(settings))

override fun createGridFSBucket(database: JMongoDatabase?): GridFSBucket {
TODO("Not yet implemented - JAVA-4893")
}

override fun createClientEncryption(
keyVaultClient: JMongoClient?,
clientEncryptionSettings: ClientEncryptionSettings?
): ClientEncryption {
TODO("Not yet implemented - JAVA-4896")
}
}
Loading

0 comments on commit 21065b5

Please sign in to comment.