diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..59cf4c0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# configuration options available at https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" + + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6f9f88..635f7c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,9 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'temurin' @@ -25,6 +25,6 @@ jobs: settings-path: ${{ github.workspace }} # location for the settings.xml file - name: Build with Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 with: arguments: build diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index 1a8aa4d..647a20b 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -20,9 +20,9 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'temurin' @@ -30,14 +30,14 @@ jobs: settings-path: ${{ github.workspace }} # location for the settings.xml file - name: Build with Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 with: arguments: -Pversion=${{ github.event.release.tag_name }} build # The USERNAME and TOKEN need to correspond to the credentials environment variables used in # the publishing section of your build.gradle - name: Publish to GitHub Packages - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 with: arguments: -Pversion=${{ github.event.release.tag_name }} publish env: diff --git a/.gitignore b/.gitignore index 09bdf3f..35ff797 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,19 @@ -build/ +.gradle + +# Compiled class file +*.class + +# Log file +*.log + +build +.kotlintest +out +*.ipr +*.iml +*.iws + +.DS_Store + +*.bkp .idea/ -.gradle/ diff --git a/README.md b/README.md index 4c42cfe..752ebd1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # native_memory_allocator -O(1) malloc for off-heap storage in Kotlin, and a NativeMemoryMap based on [Caffeine](https://github.com/ben-manes/caffeine) so you don't have to call it directly. +O(1) malloc for off-heap storage in Kotlin, and a NativeMemoryMap based on [Caffeine](https://github.com/ben-manes/caffeine). + +## Java Version + +Project is built with Java 11. Works at runtime with any Java >= 11. + +Used in production and regularly tested with Java 17 and 21 LTS versions. ## Motivation diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index b1bacf7..980723e 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -1,6 +1,7 @@ plugins { - kotlin("jvm") - id("me.champeau.jmh") version "0.6.8" + kotlin("jvm") version libs.versions.kotlin.core + alias(libs.plugins.jmh) + } repositories { @@ -9,10 +10,10 @@ repositories { dependencies { implementation(rootProject) - implementation("io.github.microutils:kotlin-logging:2.1.21") - implementation("ch.qos.logback:logback-classic:1.2.11") - implementation("org.rocksdb:rocksdbjni:7.9.2") - implementation("org.openjdk.jmh:jmh-core:1.35") + implementation(libs.kotlin.logging) + implementation(libs.logback.classic) + implementation(libs.rocksdb.jni) + implementation(libs.jmh.core) } jmh { diff --git a/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/OffHeapGetPutBenchmark.kt b/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/OffHeapGetPutBenchmark.kt index 92b1633..e2cab59 100644 --- a/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/OffHeapGetPutBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/OffHeapGetPutBenchmark.kt @@ -3,8 +3,15 @@ package com.target.nativememoryallocator.benchmarks import com.target.nativememoryallocator.benchmarks.impl.NMAOffHeapCache import com.target.nativememoryallocator.benchmarks.impl.RocksDBOffHeapCache import com.target.nativememoryallocator.benchmarks.impl.UnimplementedOffHeapCache -import mu.KotlinLogging -import org.openjdk.jmh.annotations.* +import io.github.oshai.kotlinlogging.KotlinLogging +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Group +import org.openjdk.jmh.annotations.GroupThreads +import org.openjdk.jmh.annotations.Param +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.annotations.TearDown import java.nio.ByteBuffer private val logger = KotlinLogging.logger {} diff --git a/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/NMAOffHeapCache.kt b/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/NMAOffHeapCache.kt index 1221dca..a87bb04 100644 --- a/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/NMAOffHeapCache.kt +++ b/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/NMAOffHeapCache.kt @@ -1,12 +1,12 @@ package com.target.nativememoryallocator.benchmarks.impl import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorBuilder -import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer import com.target.nativememoryallocator.benchmarks.OffHeapCache +import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder import com.target.nativememoryallocator.map.NativeMemoryMapSerializer -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.nio.ByteBuffer private val logger = KotlinLogging.logger {} diff --git a/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/RocksDBOffHeapCache.kt b/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/RocksDBOffHeapCache.kt index 661971d..5b393e6 100644 --- a/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/RocksDBOffHeapCache.kt +++ b/benchmarks/src/jmh/kotlin/com/target/nativememoryallocator/benchmarks/impl/RocksDBOffHeapCache.kt @@ -1,7 +1,7 @@ package com.target.nativememoryallocator.benchmarks.impl import com.target.nativememoryallocator.benchmarks.OffHeapCache -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import org.rocksdb.RocksDB import java.io.File import java.nio.ByteBuffer diff --git a/build.gradle.kts b/build.gradle.kts index 98be384..fead992 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,28 +1,16 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - buildscript { repositories { mavenLocal() mavenCentral() - maven { url = uri("https://plugins.gradle.org/m2/") } } } plugins { - kotlin("jvm") version "1.6.10" - `maven-publish` - jacoco - id("org.jetbrains.dokka") version "1.6.10" apply false + kotlin("jvm") version libs.versions.kotlin.core +// `maven-publish` } -// Conditionally enable dokka only when dokkaEnabled=true property is set. -// Latest version 1.6.10 depends on vulnerable versions of jackson/jsoup/etc. -val dokkaEnabled = (project.properties["dokkaEnabled"]?.toString()?.toBoolean()) ?: false -project.logger.lifecycle("dokkaEnabled = $dokkaEnabled") - -if (dokkaEnabled) { - apply(plugin = "org.jetbrains.dokka") -} +val jvmTargetVersion: String by project group = "com.target" java.sourceCompatibility = JavaVersion.VERSION_11 @@ -33,62 +21,39 @@ repositories { } dependencies { - implementation(kotlin("stdlib-jdk8")) - implementation("io.github.microutils:kotlin-logging:2.1.21") - implementation("org.objenesis:objenesis:3.2") - implementation("io.micrometer:micrometer-core:1.8.1") - api("com.github.ben-manes.caffeine:caffeine:3.0.5") + implementation(libs.kotlin.logging) + implementation(libs.objnesis) + implementation(libs.micrometer.core) + api(libs.caffeine) - testImplementation("org.spekframework.spek2:spek-dsl-jvm:2.0.17") - testImplementation("org.spekframework.spek2:spek-runner-junit5:2.0.17") - testImplementation("io.mockk:mockk:1.12.2") - testImplementation(platform("org.junit:junit-bom:5.8.2")) - testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation("ch.qos.logback:logback-classic:1.2.10") - testImplementation("com.google.guava:guava-testlib:31.0.1-jre") + testImplementation(rootProject.libs.bundles.testing) } -tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = "11" +tasks { + java { + withSourcesJar() + toolchain { languageVersion.set(JavaLanguageVersion.of(jvmTargetVersion)) } } -} -tasks.withType { - useJUnitPlatform() -} - -java { - withSourcesJar() -} - -jacoco { - toolVersion = "0.8.7" -} - -tasks.jacocoTestReport { - reports { - xml.isEnabled = true - csv.isEnabled = false - html.destination = file("${buildDir}/jacocoHtml") + withType { + useJUnitPlatform() } } -publishing { - repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/target/native_memory_allocator") - credentials { - username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME") - password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN") - } - } - } - publications { - register("gpr") { - from(components["java"]) - } - } -} \ No newline at end of file +//publishing { +// repositories { +// maven { +// name = "GitHubPackages" +// url = uri("https://maven.pkg.github.com/target/native_memory_allocator") +// credentials { +// username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME") +// password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN") +// } +// } +// } +// publications { +// register("gpr") { +// from(components["java"]) +// } +// } +//} \ No newline at end of file diff --git a/examples/map/offheap-eviction-operationcounters/build.gradle.kts b/examples/map/offheap-eviction-operationcounters/build.gradle.kts index ae634cc..6d08f67 100644 --- a/examples/map/offheap-eviction-operationcounters/build.gradle.kts +++ b/examples/map/offheap-eviction-operationcounters/build.gradle.kts @@ -1,11 +1,10 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { - kotlin("jvm") - id("com.github.johnrengelman.shadow") version "7.1.2" + kotlin("jvm") version libs.versions.kotlin.core + alias(libs.plugins.shadow) } repositories { + mavenLocal() mavenCentral() } @@ -14,15 +13,13 @@ dependencies { } tasks { - named("shadowJar") { + shadowJar { archiveBaseName.set("offheap-eviction-operationcounters-shadowjar") manifest { attributes(mapOf("Main-Class" to "com.target.nativememoryallocator.examples.map.offheap.eviction.operationcounters.OffHeapEvictionOperationCountersKt")) } } -} -tasks { build { dependsOn(shadowJar) } diff --git a/examples/map/offheap-eviction-operationcounters/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction.operationcounters/OffHeapEvictionOperationCounters.kt b/examples/map/offheap-eviction-operationcounters/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction.operationcounters/OffHeapEvictionOperationCounters.kt index fae5ef6..beec7b5 100644 --- a/examples/map/offheap-eviction-operationcounters/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction.operationcounters/OffHeapEvictionOperationCounters.kt +++ b/examples/map/offheap-eviction-operationcounters/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction.operationcounters/OffHeapEvictionOperationCounters.kt @@ -6,8 +6,12 @@ import com.target.nativememoryallocator.examples.map.utils.CacheObjectSerializer import com.target.nativememoryallocator.examples.map.utils.buildRandomString import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder -import kotlinx.coroutines.* -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.random.Random private val logger = KotlinLogging.logger {} diff --git a/examples/map/offheap-eviction/build.gradle.kts b/examples/map/offheap-eviction/build.gradle.kts index c4b2e92..f25e48e 100644 --- a/examples/map/offheap-eviction/build.gradle.kts +++ b/examples/map/offheap-eviction/build.gradle.kts @@ -1,11 +1,10 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { - kotlin("jvm") - id("com.github.johnrengelman.shadow") version "7.1.2" + kotlin("jvm") version libs.versions.kotlin.core + alias(libs.plugins.shadow) } repositories { + mavenLocal() mavenCentral() } @@ -14,15 +13,13 @@ dependencies { } tasks { - named("shadowJar") { + shadowJar { archiveBaseName.set("offheap-eviction-shadowjar") manifest { attributes(mapOf("Main-Class" to "com.target.nativememoryallocator.examples.map.offheap.eviction.OffHeapEvictionKt")) } } -} -tasks { build { dependsOn(shadowJar) } diff --git a/examples/map/offheap-eviction/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction/OffHeapEviction.kt b/examples/map/offheap-eviction/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction/OffHeapEviction.kt index 900e892..42e6a51 100644 --- a/examples/map/offheap-eviction/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction/OffHeapEviction.kt +++ b/examples/map/offheap-eviction/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap.eviction/OffHeapEviction.kt @@ -6,8 +6,12 @@ import com.target.nativememoryallocator.examples.map.utils.CacheObjectSerializer import com.target.nativememoryallocator.examples.map.utils.buildRandomString import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder -import kotlinx.coroutines.* -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.random.Random private val logger = KotlinLogging.logger {} diff --git a/examples/map/offheap-flatbuffers/build.gradle.kts b/examples/map/offheap-flatbuffers/build.gradle.kts index 18d70f1..e385081 100644 --- a/examples/map/offheap-flatbuffers/build.gradle.kts +++ b/examples/map/offheap-flatbuffers/build.gradle.kts @@ -1,29 +1,26 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { - kotlin("jvm") - id("com.github.johnrengelman.shadow") version "7.1.2" + kotlin("jvm") version libs.versions.kotlin.core + alias(libs.plugins.shadow) } repositories { + mavenLocal() mavenCentral() } dependencies { implementation(project(":examples:map:utils")) - implementation("com.google.flatbuffers:flatbuffers-java:2.0.3") + implementation(libs.flatbuffers.java) } tasks { - named("shadowJar") { + shadowJar { archiveBaseName.set("offheap-flatbuffers-shadowjar") manifest { attributes(mapOf("Main-Class" to "com.target.nativememoryallocator.examples.map.offheap.flatbuffers.OffHeapFlatBuffersKt")) } } -} -tasks { build { dependsOn(shadowJar) } diff --git a/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/OffHeapFlatBuffers.kt b/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/OffHeapFlatBuffers.kt index 5962bd1..028b0a8 100644 --- a/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/OffHeapFlatBuffers.kt +++ b/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/OffHeapFlatBuffers.kt @@ -7,8 +7,8 @@ import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.seriali import com.target.nativememoryallocator.examples.map.utils.buildRandomString import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.* -import mu.KotlinLogging import java.util.concurrent.ThreadLocalRandom import kotlin.random.Random diff --git a/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObject.kt b/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObject.kt index cf3904a..ab08673 100644 --- a/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObject.kt +++ b/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObject.kt @@ -2,9 +2,21 @@ package com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated -import java.nio.* +import com.google.flatbuffers.BaseVector +import com.google.flatbuffers.BooleanVector +import com.google.flatbuffers.ByteVector +import com.google.flatbuffers.Constants +import com.google.flatbuffers.DoubleVector +import com.google.flatbuffers.FlatBufferBuilder +import com.google.flatbuffers.FloatVector +import com.google.flatbuffers.LongVector +import com.google.flatbuffers.StringVector +import com.google.flatbuffers.Struct +import com.google.flatbuffers.Table +import com.google.flatbuffers.UnionVector +import java.nio.ByteBuffer +import java.nio.ByteOrder import kotlin.math.sign -import com.google.flatbuffers.* @Suppress("unused") class FlatBufferDemoCacheObject : Table() { @@ -35,7 +47,7 @@ class FlatBufferDemoCacheObject : Table() { val o = __offset(6); return if (o != 0) __vector_len(o) else 0 } companion object { - fun validateVersion() = Constants.FLATBUFFERS_2_0_0() + fun validateVersion() = Constants.FLATBUFFERS_25_2_10() fun getRootAsFlatBufferDemoCacheObject(_bb: ByteBuffer): FlatBufferDemoCacheObject = getRootAsFlatBufferDemoCacheObject(_bb, FlatBufferDemoCacheObject()) fun getRootAsFlatBufferDemoCacheObject(_bb: ByteBuffer, obj: FlatBufferDemoCacheObject): FlatBufferDemoCacheObject { _bb.order(ByteOrder.LITTLE_ENDIAN) diff --git a/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObjectListEntry.kt b/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObjectListEntry.kt index 143e019..55b18c3 100644 --- a/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObjectListEntry.kt +++ b/examples/map/offheap-flatbuffers/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated/FlatBufferDemoCacheObjectListEntry.kt @@ -2,9 +2,21 @@ package com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated -import java.nio.* +import com.google.flatbuffers.BaseVector +import com.google.flatbuffers.BooleanVector +import com.google.flatbuffers.ByteVector +import com.google.flatbuffers.Constants +import com.google.flatbuffers.DoubleVector +import com.google.flatbuffers.FlatBufferBuilder +import com.google.flatbuffers.FloatVector +import com.google.flatbuffers.LongVector +import com.google.flatbuffers.StringVector +import com.google.flatbuffers.Struct +import com.google.flatbuffers.Table +import com.google.flatbuffers.UnionVector +import java.nio.ByteBuffer +import java.nio.ByteOrder import kotlin.math.sign -import com.google.flatbuffers.* @Suppress("unused") class FlatBufferDemoCacheObjectListEntry : Table() { @@ -29,12 +41,16 @@ class FlatBufferDemoCacheObjectListEntry : Table() { val stringField : String? get() { val o = __offset(8) - return if (o != 0) __string(o + bb_pos) else null + return if (o != 0) { + __string(o + bb_pos) + } else { + null + } } val stringFieldAsByteBuffer : ByteBuffer get() = __vector_as_bytebuffer(8, 1) fun stringFieldInByteBuffer(_bb: ByteBuffer) : ByteBuffer = __vector_in_bytebuffer(_bb, 8, 1) companion object { - fun validateVersion() = Constants.FLATBUFFERS_2_0_0() + fun validateVersion() = Constants.FLATBUFFERS_25_2_10() fun getRootAsFlatBufferDemoCacheObjectListEntry(_bb: ByteBuffer): FlatBufferDemoCacheObjectListEntry = getRootAsFlatBufferDemoCacheObjectListEntry(_bb, FlatBufferDemoCacheObjectListEntry()) fun getRootAsFlatBufferDemoCacheObjectListEntry(_bb: ByteBuffer, obj: FlatBufferDemoCacheObjectListEntry): FlatBufferDemoCacheObjectListEntry { _bb.order(ByteOrder.LITTLE_ENDIAN) diff --git a/examples/map/offheap/build.gradle.kts b/examples/map/offheap/build.gradle.kts index 8ae4a8a..6e0fd8f 100644 --- a/examples/map/offheap/build.gradle.kts +++ b/examples/map/offheap/build.gradle.kts @@ -1,11 +1,10 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { - kotlin("jvm") - id("com.github.johnrengelman.shadow") version "7.1.2" + kotlin("jvm") version libs.versions.kotlin.core + alias(libs.plugins.shadow) } repositories { + mavenLocal() mavenCentral() } @@ -14,15 +13,13 @@ dependencies { } tasks { - named("shadowJar") { + shadowJar { archiveBaseName.set("offheap-shadowjar") manifest { attributes(mapOf("Main-Class" to "com.target.nativememoryallocator.examples.map.offheap.OffHeapKt")) } } -} -tasks { build { dependsOn(shadowJar) } diff --git a/examples/map/offheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/OffHeap.kt b/examples/map/offheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/OffHeap.kt index 62242e4..829b799 100644 --- a/examples/map/offheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/OffHeap.kt +++ b/examples/map/offheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/OffHeap.kt @@ -6,8 +6,8 @@ import com.target.nativememoryallocator.examples.map.utils.CacheObjectSerializer import com.target.nativememoryallocator.examples.map.utils.buildRandomString import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.* -import mu.KotlinLogging import kotlin.random.Random private val logger = KotlinLogging.logger {} diff --git a/examples/map/onheap/build.gradle.kts b/examples/map/onheap/build.gradle.kts index fbb1ab0..68295b2 100644 --- a/examples/map/onheap/build.gradle.kts +++ b/examples/map/onheap/build.gradle.kts @@ -1,11 +1,10 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - plugins { - kotlin("jvm") - id("com.github.johnrengelman.shadow") version "7.1.2" + kotlin("jvm") version libs.versions.kotlin.core + alias(libs.plugins.shadow) } repositories { + mavenLocal() mavenCentral() } @@ -14,15 +13,13 @@ dependencies { } tasks { - named("shadowJar") { + shadowJar { archiveBaseName.set("onheap-shadowjar") manifest { attributes(mapOf("Main-Class" to "com.target.nativememoryallocator.examples.map.onheap.OnHeapKt")) } } -} -tasks { build { dependsOn(shadowJar) } diff --git a/examples/map/onheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/onheap/OnHeap.kt b/examples/map/onheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/onheap/OnHeap.kt index da0581a..2ed7937 100644 --- a/examples/map/onheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/onheap/OnHeap.kt +++ b/examples/map/onheap/src/main/kotlin/com/target/nativememoryallocator/examples/map/onheap/OnHeap.kt @@ -2,8 +2,12 @@ package com.target.nativememoryallocator.examples.map.onheap import com.target.nativememoryallocator.examples.map.utils.CacheObject import com.target.nativememoryallocator.examples.map.utils.buildRandomString -import kotlinx.coroutines.* -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap import kotlin.random.Random diff --git a/examples/map/utils/build.gradle.kts b/examples/map/utils/build.gradle.kts index c94f87c..9507a3d 100644 --- a/examples/map/utils/build.gradle.kts +++ b/examples/map/utils/build.gradle.kts @@ -1,14 +1,15 @@ plugins { - kotlin("jvm") + kotlin("jvm") version libs.versions.kotlin.core } repositories { + mavenLocal() mavenCentral() } dependencies { api(rootProject) - api("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.6.1") - api("io.github.microutils:kotlin-logging:2.1.21") - api("ch.qos.logback:logback-classic:1.2.11") + api(libs.kotlin.coroutines) + api(libs.kotlin.logging) + api(libs.logback.classic) } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..713c1c7 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +jvmTargetVersion = 11 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..eef7a24 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,53 @@ +[versions] + +# main library dependencies +caffeine = "3.2.1" +kotlin-core = "2.1.20" +kotlin-logging = "7.0.7" +micrometer-core = "1.15.1" +objenesis = "3.4" + +# examples dependencies +flatbuffers = "25.2.10" +kotlin-coroutines = "1.10.2" +shadowjar = "8.3.6" + +# test dependencies +guava = "33.4.8-jre" +jupiter = "5.13.1" +kotest = "5.9.1" +logback = "1.5.18" +mockk = "1.14.2" + +# benchmark dependencies +jmh = "1.37" +jmh-gradle = "0.7.3" +rocksdb = "10.2.1" + +[libraries] +caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +flatbuffers-java = { module = "com.google.flatbuffers:flatbuffers-java", version.ref = "flatbuffers" } +guava-testlib = { module = "com.google.guava:guava-testlib", version.ref = "guava" } +jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } +jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "jupiter" } +jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "jupiter" } +jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "jupiter" } +kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } +kotest-assertions-json = { module = "io.kotest:kotest-assertions-json", version.ref = "kotest" } +kotest-extensions = { module = "io.kotest:kotest-extensions", version.ref = "kotest" } +kotest-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } +kotest-property = { module = "io.kotest:kotest-property-jvm", version.ref = "kotest" } +kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } +kotlin-logging = { module = "io.github.oshai:kotlin-logging", version.ref = "kotlin-logging" } +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } +micrometer-core = { module = "io.micrometer:micrometer-core", version.ref = "micrometer-core" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +objnesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" } +rocksdb-jni = { module = "org.rocksdb:rocksdbjni", version.ref = "rocksdb" } + +[bundles] +testing = ["guava-testlib", "jupiter-api", "jupiter-engine", "jupiter-params", "kotest-assertions-core", "kotest-junit5", "kotest-property", "kotest-assertions-json", "kotest-extensions", "logback-classic", "mockk"] + +[plugins] +shadow = { id = "com.gradleup.shadow", version.ref = "shadowjar" } +jmh = { id = "me.champeau.jmh", version.ref = "jmh-gradle" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e589..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -133,22 +133,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,18 +200,28 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..5eed7ee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImpl.kt b/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImpl.kt index 991a231..d761b51 100644 --- a/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImpl.kt +++ b/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImpl.kt @@ -1,7 +1,7 @@ package com.target.nativememoryallocator.allocator.impl import com.target.nativememoryallocator.unsafe.NativeMemoryPage -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging private val logger = KotlinLogging.logger {} diff --git a/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImpl.kt b/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImpl.kt index 341a5f6..cf8e464 100644 --- a/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImpl.kt +++ b/src/main/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImpl.kt @@ -5,7 +5,7 @@ import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorMetadata import com.target.nativememoryallocator.buffer.NativeMemoryBuffer import com.target.nativememoryallocator.buffer.impl.NativeMemoryBufferImpl import com.target.nativememoryallocator.unsafe.UnsafeContainer -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging private val logger = KotlinLogging.logger {} @@ -199,6 +199,7 @@ internal class NativeMemoryAllocatorImpl( bufferImpl.freed -> { throw IllegalStateException("attempt to resize already freed buffer $buffer") } + bufferImpl.capacityBytes != newCapacityBytes -> { val newPagesNeeded = computePagesNeeded(newCapacityBytes) @@ -206,6 +207,7 @@ internal class NativeMemoryAllocatorImpl( newPagesNeeded == bufferImpl.pages.size -> { bufferImpl.capacityBytes = newCapacityBytes } + newPagesNeeded < bufferImpl.pages.size -> { shrinkNativeMemoryBuffer( bufferImpl = bufferImpl, @@ -213,6 +215,7 @@ internal class NativeMemoryAllocatorImpl( newPagesNeeded = newPagesNeeded, ) } + else -> { expandNativeMemoryBuffer( bufferImpl = bufferImpl, diff --git a/src/main/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImpl.kt b/src/main/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImpl.kt index 32a9bfc..5e3158b 100644 --- a/src/main/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImpl.kt +++ b/src/main/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImpl.kt @@ -1,12 +1,17 @@ package com.target.nativememoryallocator.map.impl -import com.github.benmanes.caffeine.cache.* +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.RemovalCause +import com.github.benmanes.caffeine.cache.RemovalListener +import com.github.benmanes.caffeine.cache.Scheduler +import com.github.benmanes.caffeine.cache.Ticker import com.target.nativememoryallocator.allocator.NativeMemoryAllocator import com.target.nativememoryallocator.buffer.NativeMemoryBuffer import com.target.nativememoryallocator.map.CaffeineConfigBuilder import com.target.nativememoryallocator.map.NativeMemoryMap import com.target.nativememoryallocator.map.NativeMemoryMapStats -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging import java.util.concurrent.TimeUnit private val logger = KotlinLogging.logger {} @@ -46,7 +51,7 @@ internal class CaffeineConfigBuilderImpl( * * @param nativeMemoryAllocator used to freeing value storage buffers. */ -internal class CaffeineEvictionListener( +internal class CaffeineEvictionListener( private val nativeMemoryAllocator: NativeMemoryAllocator, ) : RemovalListener { @@ -57,9 +62,9 @@ internal class CaffeineEvictionListener( * @param value [NativeMemoryBuffer] object * @param cause [RemovalCause] object */ - override fun onRemoval(key: KEY_TYPE?, value: NativeMemoryBuffer?, cause: RemovalCause?) { + override fun onRemoval(key: KEY_TYPE?, value: NativeMemoryBuffer?, cause: RemovalCause) { try { - if ((cause?.wasEvicted() == true) && (value?.freed == false)) { + if (cause.wasEvicted() && (value?.freed == false)) { nativeMemoryAllocator.freeNativeMemoryBuffer(value) } } catch (e: Exception) { @@ -76,7 +81,7 @@ internal class CaffeineEvictionListener( * @param caffeineConfigFunction caffeine configuration function used for builder pattern. * @return [Cache] caffeine cache */ -internal fun buildCaffeineCache( +internal fun buildCaffeineCache( nativeMemoryAllocator: NativeMemoryAllocator, caffeineConfigFunction: (CaffeineConfigBuilder) -> Unit, ): Cache { diff --git a/src/test/kotlin/com/target/allocator/impl/FreeListImplTest.kt b/src/test/kotlin/com/target/allocator/impl/FreeListImplTest.kt new file mode 100644 index 0000000..550c111 --- /dev/null +++ b/src/test/kotlin/com/target/allocator/impl/FreeListImplTest.kt @@ -0,0 +1,284 @@ +package com.target.allocator.impl + +import com.target.nativememoryallocator.allocator.impl.FreeListImpl +import com.target.nativememoryallocator.unsafe.NativeMemoryPage +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class FreeListImplTest { + + @Test + fun `test initialization`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + } + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + + freeList.freePageArray().size shouldBe totalNumPages + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 0 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe totalNumPages + freeList.numUsedPages() shouldBe 0 + freeList.numAllocationExceptions() shouldBe 0 + freeList.numFreeExceptions() shouldBe 0 + } + + @Test + fun `test allocation of 1 page`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + } + expectedFreePageArray[0] = 0 + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + + val retVal = freeList.allocatePages(1) + + retVal shouldBe arrayListOf( + NativeMemoryPage( + startAddress = baseNativeMemoryPointer, + ) + ) + freeList.freePageArray().size shouldBe totalNumPages + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 1 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe totalNumPages - 1 + freeList.numUsedPages() shouldBe 1 + freeList.numAllocationExceptions() shouldBe 0 + freeList.numFreeExceptions() shouldBe 0 + } + + @Test + fun `test allocation of 101 pages sequentially`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val expectedFreePageArray = LongArray(totalNumPages) { 0 } + + val pagesAllocated = arrayListOf() + var exceptionsCaught = 0 + + val expectedAllocations = (0 until totalNumPages).map { + NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) + } + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + (0 until 101).forEach { _ -> + try { + pagesAllocated.addAll(freeList.allocatePages(1)) + } catch (e: IllegalStateException) { + exceptionsCaught += 1 + } + } + + pagesAllocated shouldBe expectedAllocations + exceptionsCaught shouldBe 1 + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 100 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe 0 + freeList.numUsedPages() shouldBe 100 + freeList.numAllocationExceptions() shouldBe 1 + freeList.numFreeExceptions() shouldBe 0 + } + + @Test + fun `test allocation of 101 pages non-sequentially`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + + val pagesAllocated = arrayListOf() + var exceptionsCaught = 0 + + val expectedAllocations = emptyList() + + try { + pagesAllocated.addAll(freeList.allocatePages(101)) + } catch (e: IllegalStateException) { + exceptionsCaught += 1 + } + + val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + }.reversedArray() + + pagesAllocated shouldBe expectedAllocations + exceptionsCaught shouldBe 1 + + // all pages allocated are freed + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 0 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe 100 + freeList.numUsedPages() shouldBe 0 + freeList.numAllocationExceptions() shouldBe 1 + freeList.numFreeExceptions() shouldBe 0 + } + + @Test + fun `test allocation of 100 pages then free 100 pages`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + + val pagesAllocated = arrayListOf() + + val expectedAllocations = (0 until totalNumPages).map { + NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) + } + + pagesAllocated.addAll(freeList.allocatePages(100)) + + pagesAllocated shouldBe expectedAllocations + var expectedFreePageArray = LongArray(totalNumPages) { 0 } + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 100 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe 0 + freeList.numUsedPages() shouldBe 100 + freeList.numAllocationExceptions() shouldBe 0 + freeList.numFreeExceptions() shouldBe 0 + + freeList.freePages(pagesAllocated) + + expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + }.reversedArray() + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 0 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe totalNumPages + freeList.numUsedPages() shouldBe 0 + freeList.numAllocationExceptions() shouldBe 0 + freeList.numFreeExceptions() shouldBe 0 + } + + @Test + fun `test allocation of 2 pages then free 1 page`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val pagesAllocated = arrayListOf() + + val expectedAllocations = (0 until 2).map { + NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) + } + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + pagesAllocated.addAll(freeList.allocatePages(2)) + + pagesAllocated shouldBe expectedAllocations + + val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + } + expectedFreePageArray[0] = 0 + expectedFreePageArray[1] = 0 + + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 2 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe totalNumPages - 2 + freeList.numUsedPages() shouldBe 2 + } + + @Test + fun `test allocation of 2 pages then free 3 pages`() { + val baseNativeMemoryPointer = 0x80000000 + val pageSizeBytes = 4_096 + val totalNumPages = 100 + + val pagesAllocated = arrayListOf() + var numFreeExceptions = 0 + + val expectedAllocations = (0 until 2).map { + NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) + } + + val freeList = FreeListImpl( + baseNativeMemoryPointer = baseNativeMemoryPointer, + pageSizeBytes = pageSizeBytes, + totalNumPages = totalNumPages, + ) + + pagesAllocated.addAll(freeList.allocatePages(2)) + + pagesAllocated shouldBe expectedAllocations + var expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + } + expectedFreePageArray[0] = 0 + expectedFreePageArray[1] = 0 + + freeList.freePageArray() shouldBe expectedFreePageArray + freeList.nextFreePageIndex() shouldBe 2 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe totalNumPages - 2 + freeList.numUsedPages() shouldBe 2 + + freeList.freePages(listOf(pagesAllocated[1], pagesAllocated[0])) + + try { + freeList.freePages(listOf(pagesAllocated[0])) + } catch (e: IllegalStateException) { + numFreeExceptions++ + } + + pagesAllocated shouldBe expectedAllocations + + expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> + baseNativeMemoryPointer + (pageNumber * pageSizeBytes) + } + + freeList.freePageArray() shouldBe expectedFreePageArray + + freeList.nextFreePageIndex() shouldBe 0 + freeList.totalNumPages shouldBe totalNumPages + freeList.numFreePages() shouldBe 100 + freeList.numUsedPages() shouldBe 0 + freeList.numAllocationExceptions() shouldBe 0 + freeList.numFreeExceptions() shouldBe 1 + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/allocator/impl/NativeMemoryAllocatorImplTest.kt b/src/test/kotlin/com/target/allocator/impl/NativeMemoryAllocatorImplTest.kt new file mode 100644 index 0000000..f089582 --- /dev/null +++ b/src/test/kotlin/com/target/allocator/impl/NativeMemoryAllocatorImplTest.kt @@ -0,0 +1,701 @@ +package com.target.allocator.impl + +import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorMetadata +import com.target.nativememoryallocator.allocator.impl.NativeMemoryAllocatorImpl +import com.target.nativememoryallocator.allocator.impl.validateNativeMemoryAllocatorInitialParameters +import com.target.nativememoryallocator.unsafe.UnsafeContainer +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import sun.misc.Unsafe + +class NativeMemoryAllocatorImplTest { + + private val mockUnsafe = mockk() + + @BeforeEach + fun beforeEach() { + mockkObject(UnsafeContainer) + + every { UnsafeContainer.unsafe } returns mockUnsafe + } + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun `test validateNativeMemoryAllocatorInitialParameters`() { + var pageSizeBytes = 0 + var nativeMemorySizeBytes = 0L + + // negative pageSizeBytes + pageSizeBytes = -1 + nativeMemorySizeBytes = 1L * 1024L * 1024L * 1024L + + shouldThrow { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // zero pageSizeBytes + pageSizeBytes = 0 + nativeMemorySizeBytes = 1L * 1024L * 1024L * 1024L + + shouldThrow { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // positive pageSizeBytes + pageSizeBytes = 1 + nativeMemorySizeBytes = 1L * 1024L * 1024L * 1024L + + shouldNotThrowAny { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // max pageSizeBytes + pageSizeBytes = Int.MAX_VALUE + nativeMemorySizeBytes = Int.MAX_VALUE.toLong() + + shouldNotThrowAny { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // negative nativeMemorySizeBytes + pageSizeBytes = 1 + nativeMemorySizeBytes = -1L + + shouldThrow { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // zero nativeMemorySizeBytes + pageSizeBytes = 1 + nativeMemorySizeBytes = 0L + + shouldThrow { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // positive nativeMemorySizeBytes + pageSizeBytes = 1 + nativeMemorySizeBytes = 1L + + shouldNotThrowAny { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // positive nativeMemorySizeBytes + pageSizeBytes = 4 * 1024 + nativeMemorySizeBytes = (1L * 1024L * 1024L * 1024L) + + shouldNotThrowAny { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // nativeMemorySizeBytes not evenly divisible by pageSizeBytes + pageSizeBytes = 4 * 1024 + nativeMemorySizeBytes = (1L * 1024L * 1024L * 1024L) + 1L + + shouldThrow { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + + // invalid totalNumPages (larger than Int.MAX_VALUE) + pageSizeBytes = 1 + nativeMemorySizeBytes = (20L * 1024L * 1024L * 1024L) + shouldThrow { + validateNativeMemoryAllocatorInitialParameters( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + } + } + + @Test + fun `test initialization`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val expectedNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe expectedNumPages + nativeMemoryAllocator.totalNumPages shouldBe expectedNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( + pageSizeBytes = pageSizeBytes, + nextFreePageIndex = 0, + numFreePages = expectedNumPages, + totalNumPages = expectedNumPages, + numUsedPages = 0, + numAllocationExceptions = 0, + numFreeExceptions = 0, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + + nativeMemoryAllocator.nativeMemoryAllocatorMetadata shouldBe expectedNativeMemoryAllocatorMetadata + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + verify(exactly = 0) { + mockUnsafe.setMemory(any(), any(), any()) + } + } + + @Test + fun `test initialization with zeroNativeMemoryOnStartup = true`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val expectedNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + every { + mockUnsafe.setMemory(mockNativeMemoryPointer, nativeMemorySizeBytes, 0) + } returns Unit + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = true, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe expectedNumPages + nativeMemoryAllocator.totalNumPages shouldBe expectedNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( + pageSizeBytes = pageSizeBytes, + nextFreePageIndex = 0, + numFreePages = expectedNumPages, + totalNumPages = expectedNumPages, + numUsedPages = 0, + numAllocationExceptions = 0, + numFreeExceptions = 0, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + + nativeMemoryAllocator.nativeMemoryAllocatorMetadata shouldBe expectedNativeMemoryAllocatorMetadata + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + verify(exactly = 1) { + mockUnsafe.setMemory(mockNativeMemoryPointer, nativeMemorySizeBytes, 0) + } + } + + @Test + fun `test initialization invalid nativeMemorySizeBytes`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = (1L * 1024 * 1024 * 1024) + 1 // 1gb + 1 byte + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(any()) + } returns mockNativeMemoryPointer + + shouldThrow { + NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + } + + verify(exactly = 0) { + mockUnsafe.allocateMemory(any()) + } + } + + @Test + fun `test allocation of 100 bytes, then free, then double free, then resize freed buffer`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + + val buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( + capacityBytes = 100, + ) + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 1 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 1 + + val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( + pageSizeBytes = pageSizeBytes, + nextFreePageIndex = 1, + numFreePages = totalNumPages - 1, + totalNumPages = totalNumPages, + numUsedPages = 1, + numAllocationExceptions = 0, + numFreeExceptions = 0, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + + nativeMemoryAllocator.nativeMemoryAllocatorMetadata shouldBe expectedNativeMemoryAllocatorMetadata + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 100 + buffer.freed shouldBe false + buffer.numPages shouldBe 1 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + // Free the buffer + shouldNotThrowAny { + nativeMemoryAllocator.freeNativeMemoryBuffer( + buffer = buffer, + ) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 0 + buffer.freed shouldBe true + buffer.numPages shouldBe 0 + + // Free the buffer again + shouldThrow { + nativeMemoryAllocator.freeNativeMemoryBuffer( + buffer = buffer, + ) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 0 + buffer.freed shouldBe true + buffer.numPages shouldBe 0 + } + + @Test + fun `test allocation of 10_000 bytes, then resize scenarios, then free`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + + val buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( + capacityBytes = 10_000, + ) + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 3 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 3 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 10_000 + buffer.freed shouldBe false + buffer.numPages shouldBe 3 + + // resize to same capacity + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = 10_000, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 3 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 3 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 10_000 + buffer.freed shouldBe false + buffer.numPages shouldBe 3 + + // resize to new capacity, same number of pages + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = 10_500, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 3 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 3 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 10_500 + buffer.freed shouldBe false + buffer.numPages shouldBe 3 + + // resize to new capacity, more pages + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = 17_000, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 5 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 5 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 17_000 + buffer.freed shouldBe false + buffer.numPages shouldBe 5 + + // resize to new capacity, fewer pages + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = 4_097, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 2 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 2 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 4_097 + buffer.freed shouldBe false + buffer.numPages shouldBe 2 + + // free buffer + nativeMemoryAllocator.freeNativeMemoryBuffer(buffer) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 0 + buffer.freed shouldBe true + buffer.numPages shouldBe 0 + } + + @Test + fun `test allocation of 5_000 bytes, then resize out of memory, then free`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + + val buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( + capacityBytes = 5_000, + ) + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 2 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 2 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 5_000 + buffer.freed shouldBe false + buffer.numPages shouldBe 2 + + // resize buffer to max capacity + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = nativeMemorySizeBytes.toInt(), //1gb + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe 0 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe totalNumPages + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe nativeMemorySizeBytes.toInt() + buffer.freed shouldBe false + buffer.numPages shouldBe totalNumPages + + // resize buffer to 1 page + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = 4_096, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 1 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 1 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 4_096 + buffer.freed shouldBe false + buffer.numPages shouldBe 1 + + // resize buffer 1 byte more than max capacity + shouldThrow { + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = nativeMemorySizeBytes.toInt() + 1, + ) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 1 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 1 + nativeMemoryAllocator.numAllocationExceptions shouldBe 1 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( + pageSizeBytes = pageSizeBytes, + nextFreePageIndex = 1, + numFreePages = totalNumPages - 1, + totalNumPages = totalNumPages, + numUsedPages = 1, + numAllocationExceptions = 1, + numFreeExceptions = 0, + nativeMemorySizeBytes = nativeMemorySizeBytes, + ) + + nativeMemoryAllocator.nativeMemoryAllocatorMetadata shouldBe expectedNativeMemoryAllocatorMetadata + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 4_096 + buffer.freed shouldBe false + buffer.numPages shouldBe 1 + + // free buffer + nativeMemoryAllocator.freeNativeMemoryBuffer( + buffer = buffer, + ) + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 1 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 0 + buffer.freed shouldBe true + buffer.numPages shouldBe 0 + } + + @Test + fun `test allocation of negative capacity`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + + shouldThrow { + nativeMemoryAllocator.allocateNativeMemoryBuffer( + capacityBytes = -1, + ) + } + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 0 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + } + + @Test + fun `test allocation of 10_000 bytes, then resize to negative value`() { + val pageSizeBytes = 4_096 // 4kb + val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb + val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() + val mockNativeMemoryPointer = 0x80000000 + + every { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } returns mockNativeMemoryPointer + + val nativeMemoryAllocator = NativeMemoryAllocatorImpl( + pageSizeBytes = pageSizeBytes, + nativeMemorySizeBytes = nativeMemorySizeBytes, + zeroNativeMemoryOnStartup = false, + ) + + val buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( + capacityBytes = 10_000, + ) + + verify(exactly = 1) { + mockUnsafe.allocateMemory(nativeMemorySizeBytes) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 3 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 3 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 10_000 + buffer.freed shouldBe false + buffer.numPages shouldBe 3 + + // resize to -1 + shouldThrow { + nativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = buffer, + newCapacityBytes = -1, + ) + } + + nativeMemoryAllocator.baseNativeMemoryPointer() shouldBe mockNativeMemoryPointer + nativeMemoryAllocator.numFreePages shouldBe totalNumPages - 3 + nativeMemoryAllocator.totalNumPages shouldBe totalNumPages + nativeMemoryAllocator.numUsedPages shouldBe 3 + nativeMemoryAllocator.numAllocationExceptions shouldBe 0 + nativeMemoryAllocator.numFreeExceptions shouldBe 0 + + buffer.pageSizeBytes shouldBe pageSizeBytes + buffer.capacityBytes shouldBe 10_000 + buffer.freed shouldBe false + buffer.numPages shouldBe 3 + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/buffer/impl/NativeMemoryBufferImplTest.kt b/src/test/kotlin/com/target/buffer/impl/NativeMemoryBufferImplTest.kt new file mode 100644 index 0000000..03e3b7d --- /dev/null +++ b/src/test/kotlin/com/target/buffer/impl/NativeMemoryBufferImplTest.kt @@ -0,0 +1,430 @@ +package com.target.buffer.impl + +import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer +import com.target.nativememoryallocator.buffer.impl.NativeMemoryBufferImpl +import com.target.nativememoryallocator.unsafe.NativeMemoryPage +import com.target.nativememoryallocator.unsafe.UnsafeContainer +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import sun.misc.Unsafe + +class NativeMemoryBufferImplTest { + + private val mockUnsafe = mockk() + + @BeforeEach + fun beforeEach() { + mockkObject(UnsafeContainer) + + every { UnsafeContainer.unsafe } returns mockUnsafe + } + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun `readByte(0) reads the correct data at the correct offset`() { + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.getByte(2L * 4_096) + } returns 123 + + val readRetVal = nativeMemoryBufferImpl.readByte(offset = 0) + + readRetVal shouldBe 123.toByte() + verify(exactly = 1) { mockUnsafe.getByte(2L * 4_096) } + } + + @Test + fun `readByte(4095) reads the correct data at the correct offset`() { + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.getByte((2L * 4_096) + 4_095) + } returns 42 + + val readRetVal = nativeMemoryBufferImpl.readByte(offset = 4_095) + + readRetVal shouldBe 42.toByte() + verify(exactly = 1) { mockUnsafe.getByte((2L * 4_096) + 4_095) } + } + + @Test + fun `readByte(8500) reads the correct data at the correct offset`() { + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.getByte((0 * 4_096L) + 308) + } returns 55 + + val readRetVal = nativeMemoryBufferImpl.readByte(offset = 8500) + + readRetVal shouldBe 55.toByte() + verify(exactly = 1) { mockUnsafe.getByte((0L * 4_096) + 308) } + } + + @Test + fun `readByte(12287) reads the correct data at the correct offset`() { + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.getByte((0 * 4_096L) + 4_095) + } returns 22 + + val readRetVal = nativeMemoryBufferImpl.readByte(offset = 12_287) + + readRetVal shouldBe 22.toByte() + verify(exactly = 1) { mockUnsafe.getByte((0L * 4_096) + 4_095) } + } + + @Test + fun `writeByte(0) writes the correct data at the correct offset`() { + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.putByte(2L * 4_096, 42) + } returns Unit + + nativeMemoryBufferImpl.writeByte(offset = 0, byte = 42) + + verify(exactly = 1) { mockUnsafe.putByte(2L * 4_096, 42) } + } + + @Test + fun `writeByte(12287) writes the correct data at the correct offset`() { + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.putByte((0L * 4_096) + 4095, 33) + } returns Unit + + nativeMemoryBufferImpl.writeByte(offset = 12287, byte = 33) + + verify(exactly = 1) { mockUnsafe.putByte((0L * 4_096) + 4095, 33) } + } + + @Test + fun `test readAllToByteArray`() { + val byteArrayBaseOffset = 16L + every { + UnsafeContainer.BYTE_ARRAY_BASE_OFFSET + } returns byteArrayBaseOffset + + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 3 * (4_096), + freed = false, + pages = pages, + ) + + every { + mockUnsafe.copyMemory( + null, pages[0].startAddress, + any(), byteArrayBaseOffset + 0, + 4_096.toLong() + ) + } returns Unit + + every { + mockUnsafe.copyMemory( + null, pages[1].startAddress, + any(), byteArrayBaseOffset + 4_096, + 4_096.toLong() + ) + } returns Unit + + every { + mockUnsafe.copyMemory( + null, pages[2].startAddress, + any(), byteArrayBaseOffset + 8_192, + 4_096.toLong() + ) + } returns Unit + + nativeMemoryBufferImpl.readAllToByteArray() + + verify(exactly = 1) { + mockUnsafe.copyMemory( + null, pages[0].startAddress, + any(), byteArrayBaseOffset + 0, + 4_096.toLong() + ) + } + verify(exactly = 1) { + mockUnsafe.copyMemory( + null, pages[1].startAddress, + any(), byteArrayBaseOffset + 4_096, + 4_096.toLong() + ) + } + verify(exactly = 1) { + mockUnsafe.copyMemory( + null, pages[2].startAddress, + any(), byteArrayBaseOffset + 8_192, + 4_096.toLong() + ) + } + } + + @Test + fun `test copyToOnHeapMemoryBuffer`() { + val byteArrayBaseOffset = 16L + every { + UnsafeContainer.BYTE_ARRAY_BASE_OFFSET + } returns byteArrayBaseOffset + + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 10_000, + freed = false, + pages = pages, + ) + + val array = ByteArray(10_000) + + val onHeapMemoryBuffer = mockk() + every { + onHeapMemoryBuffer.array + } returns array + every { + onHeapMemoryBuffer.setReadableBytes(10_000) + } returns Unit + + every { + mockUnsafe.copyMemory( + null, pages[0].startAddress, + any(), byteArrayBaseOffset + 0, + 4_096.toLong() + ) + } returns Unit + + every { + mockUnsafe.copyMemory( + null, pages[1].startAddress, + any(), byteArrayBaseOffset + 4_096, + 4_096.toLong() + ) + } returns Unit + + every { + mockUnsafe.copyMemory( + null, pages[2].startAddress, + any(), byteArrayBaseOffset + 8_192, + 1_808.toLong() + ) + } returns Unit + + nativeMemoryBufferImpl.copyToOnHeapMemoryBuffer(onHeapMemoryBuffer = onHeapMemoryBuffer) + + verify(exactly = 1) { + mockUnsafe.copyMemory( + null, pages[0].startAddress, + any(), byteArrayBaseOffset + 0, + 4_096.toLong() + ) + } + verify(exactly = 1) { + mockUnsafe.copyMemory( + null, pages[1].startAddress, + any(), byteArrayBaseOffset + 4_096, + 4_096.toLong() + ) + } + verify(exactly = 1) { + mockUnsafe.copyMemory( + null, pages[2].startAddress, + any(), byteArrayBaseOffset + 8_192, + 1_808.toLong() + ) + } + verify(exactly = 1) { + onHeapMemoryBuffer.setReadableBytes(10_000) + } + } + + @Test + fun `test copyFromArray`() { + val byteArrayBaseOffset = 16L + every { + UnsafeContainer.BYTE_ARRAY_BASE_OFFSET + } returns byteArrayBaseOffset + + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 8_193, + freed = false, + pages = pages, + ) + + val array = ByteArray(8_193) + + every { + mockUnsafe.copyMemory( + array, byteArrayBaseOffset + 0, + null, pages[0].startAddress, + 4_096.toLong() + ) + } returns Unit + + every { + mockUnsafe.copyMemory( + array, byteArrayBaseOffset + 4_096, + null, pages[1].startAddress, + 4_096.toLong() + ) + } returns Unit + + every { + mockUnsafe.copyMemory( + array, byteArrayBaseOffset + 8_192, + null, pages[2].startAddress, + 1.toLong() + ) + } returns Unit + + nativeMemoryBufferImpl.copyFromArray(array) + + verify(exactly = 1) { + mockUnsafe.copyMemory( + array, byteArrayBaseOffset + 0, + null, pages[0].startAddress, + 4_096.toLong() + ) + } + verify(exactly = 1) { + mockUnsafe.copyMemory( + array, byteArrayBaseOffset + 4_096, + null, pages[1].startAddress, + 4_096.toLong() + ) + } + verify(exactly = 1) { + mockUnsafe.copyMemory( + array, byteArrayBaseOffset + 8_192, + null, pages[2].startAddress, + 1.toLong() + ) + } + } + + @Test + fun `test copyFromArray array too large`() { + val byteArrayBaseOffset = 16L + every { + UnsafeContainer.BYTE_ARRAY_BASE_OFFSET + } returns byteArrayBaseOffset + + val pages = ArrayList() + pages.add(NativeMemoryPage(2L * 4_096)) + pages.add(NativeMemoryPage(1L * 4_096)) + pages.add(NativeMemoryPage(0L * 4_096)) + + val nativeMemoryBufferImpl = NativeMemoryBufferImpl( + pageSizeBytes = 4_096, + capacityBytes = 8_193, + freed = false, + pages = pages, + ) + + val array = ByteArray(8_194) + + shouldThrow { + nativeMemoryBufferImpl.copyFromArray(array) + } + + verify(exactly = 0) { + mockUnsafe.copyMemory( + any(), any(), + any(), any(), + any(), + ) + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/buffer/impl/OnHeapMemoryBufferImplTest.kt b/src/test/kotlin/com/target/buffer/impl/OnHeapMemoryBufferImplTest.kt new file mode 100644 index 0000000..5f0c50a --- /dev/null +++ b/src/test/kotlin/com/target/buffer/impl/OnHeapMemoryBufferImplTest.kt @@ -0,0 +1,53 @@ +package com.target.buffer.impl + +import com.target.nativememoryallocator.buffer.impl.OnHeapMemoryBufferImpl +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import kotlin.random.Random + +class OnHeapMemoryBufferImplTest { + + @Test + fun `test initial capacity set to 2 in constructor`() { + val onHeapMemoryBufferImpl = OnHeapMemoryBufferImpl( + initialCapacityBytes = 0, + ) + + onHeapMemoryBufferImpl.array.size shouldBe 2 + onHeapMemoryBufferImpl.getReadableBytes() shouldBe 0 + } + + @Test + fun `test setReadableBytes`() { + val onHeapMemoryBufferImpl = OnHeapMemoryBufferImpl( + initialCapacityBytes = 0, + ) + + onHeapMemoryBufferImpl.setReadableBytes(1_023 * 1_024) + + onHeapMemoryBufferImpl.array.size shouldBe 1_024 * 1_024 + onHeapMemoryBufferImpl.getReadableBytes() shouldBe 1_023 * 1_024 + } + + @Test + fun `test asByteBuffer`() { + val onHeapMemoryBufferImpl = OnHeapMemoryBufferImpl( + initialCapacityBytes = 0, + ) + + onHeapMemoryBufferImpl.setReadableBytes((1_024 * 1_024) - 1) + + Random.nextBytes(onHeapMemoryBufferImpl.array) + + onHeapMemoryBufferImpl.array.size shouldBe 1_024 * 1_024 + onHeapMemoryBufferImpl.getReadableBytes() shouldBe (1_024 * 1_024) - 1 + + val byteBuffer = onHeapMemoryBufferImpl.asByteBuffer() + + byteBuffer.capacity() shouldBe 1_024 * 1_024 + byteBuffer.limit() shouldBe (1_024 * 1_024) - 1 + byteBuffer.arrayOffset() shouldBe 0 + onHeapMemoryBufferImpl.array.sliceArray(0 until ((1024 * 1024) - 1)) shouldBe + onHeapMemoryBufferImpl.asByteBuffer().array().sliceArray(0 until byteBuffer.limit()) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/integrationtests/CaffeineScenario.kt b/src/test/kotlin/com/target/integrationtests/CaffeineScenario.kt similarity index 65% rename from src/test/kotlin/com/target/nativememoryallocator/integrationtests/CaffeineScenario.kt rename to src/test/kotlin/com/target/integrationtests/CaffeineScenario.kt index cad1a86..091ad8e 100644 --- a/src/test/kotlin/com/target/nativememoryallocator/integrationtests/CaffeineScenario.kt +++ b/src/test/kotlin/com/target/integrationtests/CaffeineScenario.kt @@ -1,14 +1,16 @@ -package com.target.nativememoryallocator.integrationtests +package com.target.integrationtests import com.google.common.testing.FakeTicker import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorBuilder import com.target.nativememoryallocator.map.NativeMemoryMap import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder +import io.github.oshai.kotlinlogging.KotlinLogging +import io.kotest.matchers.longs.shouldBeGreaterThanOrEqual +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import io.mockk.clearAllMocks -import mu.KotlinLogging -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import java.util.concurrent.TimeUnit @@ -32,12 +34,11 @@ private fun awaitCondition( class CaffeineScenario { - @BeforeEach - fun before() { + @AfterEach + fun afterEach() { clearAllMocks() } - @Test fun caffeineNoOperationCountersScenarioTest() { logger.info { "begin caffeineNoOperationCountersScenarioTest" } @@ -63,20 +64,20 @@ class CaffeineScenario { ).build() val putResult1 = nativeMemoryMap.put(1, TestCacheValue("1234")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult1) + putResult1 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER val putResult2 = nativeMemoryMap.put(2, TestCacheValue("2345")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult2) + putResult2 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER // await asynchronous eviction due to max size awaitCondition { nativeMemoryMap.get(1) == null } awaitCondition { nativeMemoryMap.size == 1 } awaitCondition { nativeMemoryMap.stats.caffeineStats?.evictionCount() == 1L } - assertEquals(1, nativeMemoryMap.size) - assertEquals(null, nativeMemoryMap.get(1)) - assertEquals(TestCacheValue("2345"), nativeMemoryMap.get(2)) - assertEquals(1L, nativeMemoryMap.stats.caffeineStats?.evictionCount()) + nativeMemoryMap.size shouldBe 1 + nativeMemoryMap.get(1) shouldBe null + nativeMemoryMap.get(2) shouldBe TestCacheValue("2345") + nativeMemoryMap.stats.caffeineStats?.evictionCount() shouldBe 1L // advance fakeTicker by 6 seconds to trigger expireAfterAccess TTL fakeTicker.advance(6, TimeUnit.SECONDS) @@ -86,13 +87,12 @@ class CaffeineScenario { awaitCondition { nativeMemoryMap.size == 0 } awaitCondition { nativeMemoryMap.stats.caffeineStats?.evictionCount() == 2L } - assertEquals(0, nativeMemoryMap.size) - assertEquals(null, nativeMemoryMap.get(1)) - assertEquals(null, nativeMemoryMap.get(2)) - assertEquals(2L, nativeMemoryMap.stats.caffeineStats?.evictionCount()) + nativeMemoryMap.size shouldBe 0 + nativeMemoryMap.get(1) shouldBe null + nativeMemoryMap.get(2) shouldBe null + nativeMemoryMap.stats.caffeineStats?.evictionCount() shouldBe 2L - val operationCounters = nativeMemoryMap.operationCounters - assertNull(operationCounters) + nativeMemoryMap.operationCounters shouldBe null } @Test @@ -121,20 +121,20 @@ class CaffeineScenario { ).build() val putResult1 = nativeMemoryMap.put(1, TestCacheValue("1234")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult1) + putResult1 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER val putResult2 = nativeMemoryMap.put(2, TestCacheValue("2345")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult2) + putResult2 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER // await asynchronous eviction due to max size awaitCondition { nativeMemoryMap.get(1) == null } awaitCondition { nativeMemoryMap.size == 1 } awaitCondition { nativeMemoryMap.stats.caffeineStats?.evictionCount() == 1L } - assertEquals(1, nativeMemoryMap.size) - assertEquals(null, nativeMemoryMap.get(1)) - assertEquals(TestCacheValue("2345"), nativeMemoryMap.get(2)) - assertEquals(1L, nativeMemoryMap.stats.caffeineStats?.evictionCount()) + nativeMemoryMap.size shouldBe 1 + nativeMemoryMap.get(1) shouldBe null + nativeMemoryMap.get(2) shouldBe TestCacheValue("2345") + nativeMemoryMap.stats.caffeineStats?.evictionCount() shouldBe 1L // advance fakeTicker by 6 seconds to trigger expireAfterAccess TTL fakeTicker.advance(6, TimeUnit.SECONDS) @@ -144,23 +144,24 @@ class CaffeineScenario { awaitCondition { nativeMemoryMap.size == 0 } awaitCondition { nativeMemoryMap.stats.caffeineStats?.evictionCount() == 2L } - assertEquals(0, nativeMemoryMap.size) - assertEquals(null, nativeMemoryMap.get(1)) - assertEquals(null, nativeMemoryMap.get(2)) - assertFalse(nativeMemoryMap.delete(1)) - assertFalse(nativeMemoryMap.delete(2)) - assertEquals(2L, nativeMemoryMap.stats.caffeineStats?.evictionCount()) + nativeMemoryMap.size shouldBe 0 + nativeMemoryMap.get(1) shouldBe null + nativeMemoryMap.get(2) shouldBe null + nativeMemoryMap.delete(1) shouldBe false + nativeMemoryMap.delete(2) shouldBe false + nativeMemoryMap.stats.caffeineStats?.evictionCount() shouldBe 2L val operationCounters = nativeMemoryMap.operationCounters - assertNotNull(operationCounters) - assertEquals(0, operationCounters?.numPutsNoChange?.toLong()) - assertEquals(0, operationCounters?.numPutsFreedBuffer?.toLong()) - assertEquals(0, operationCounters?.numPutsReusedBuffer?.toLong()) - assertEquals(2, operationCounters?.numPutsNewBuffer?.toLong()) - assertEquals(0, operationCounters?.numDeletesFreedBuffer?.toLong()) - assertEquals(2, operationCounters?.numDeletesNoChange?.toLong()) + operationCounters shouldNotBe null + + operationCounters?.numPutsNoChange?.toLong() shouldBe 0L + operationCounters?.numPutsFreedBuffer?.toLong() shouldBe 0L + operationCounters?.numPutsReusedBuffer?.toLong() shouldBe 0L + operationCounters?.numPutsNewBuffer?.toLong() shouldBe 2L + operationCounters?.numDeletesFreedBuffer?.toLong() shouldBe 0L + operationCounters?.numDeletesNoChange?.toLong() shouldBe 2L // exact values are not known due to awaitCondition - assertTrue((operationCounters?.numGetsNullValue?.toLong() ?: 0) >= 5) - assertTrue((operationCounters?.numGetsNonNullValue?.toLong() ?: 0) >= 1) + (operationCounters?.numGetsNullValue?.toLong() ?: 0) shouldBeGreaterThanOrEqual 5 + (operationCounters?.numGetsNonNullValue?.toLong() ?: 0) shouldBeGreaterThanOrEqual 1 } } \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/integrationtests/ConcurrentHashMapScenario.kt b/src/test/kotlin/com/target/integrationtests/ConcurrentHashMapScenario.kt similarity index 53% rename from src/test/kotlin/com/target/nativememoryallocator/integrationtests/ConcurrentHashMapScenario.kt rename to src/test/kotlin/com/target/integrationtests/ConcurrentHashMapScenario.kt index 41f466b..24ccffc 100644 --- a/src/test/kotlin/com/target/nativememoryallocator/integrationtests/ConcurrentHashMapScenario.kt +++ b/src/test/kotlin/com/target/integrationtests/ConcurrentHashMapScenario.kt @@ -1,22 +1,23 @@ -package com.target.nativememoryallocator.integrationtests +package com.target.integrationtests import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorBuilder import com.target.nativememoryallocator.map.NativeMemoryMap import com.target.nativememoryallocator.map.NativeMemoryMapBackend import com.target.nativememoryallocator.map.NativeMemoryMapBuilder import com.target.nativememoryallocator.map.NativeMemoryMapStats +import io.github.oshai.kotlinlogging.KotlinLogging +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import io.mockk.clearAllMocks -import mu.KotlinLogging -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test private val logger = KotlinLogging.logger {} class ConcurrentHashMapScenario { - @BeforeEach - fun before() { + @AfterEach + fun afterEach() { clearAllMocks() } @@ -36,28 +37,27 @@ class ConcurrentHashMapScenario { ).build() val putResult1 = nativeMemoryMap.put(1, TestCacheValue("1234")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult1) + putResult1 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER val putResult2 = nativeMemoryMap.put(2, TestCacheValue("2345")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult2) + putResult2 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER - assertEquals(2, nativeMemoryMap.size) - assertEquals(NativeMemoryMapStats(), nativeMemoryMap.stats) + nativeMemoryMap.size shouldBe 2 + nativeMemoryMap.stats shouldBe NativeMemoryMapStats() logger.info { "nativeMemoryMap.get(1) = ${nativeMemoryMap.get(1)}" } logger.info { "nativeMemoryMap.get(2) = ${nativeMemoryMap.get(2)}" } - assertEquals(TestCacheValue("1234"), nativeMemoryMap.get(1)) - assertEquals(TestCacheValue("2345"), nativeMemoryMap.get(2)) + nativeMemoryMap.get(1) shouldBe TestCacheValue("1234") + nativeMemoryMap.get(2) shouldBe TestCacheValue("2345") nativeMemoryMap.delete(1) - assertEquals(1, nativeMemoryMap.size) - assertEquals(null, nativeMemoryMap.get(1)) - assertEquals(TestCacheValue("2345"), nativeMemoryMap.get(2)) + nativeMemoryMap.size shouldBe 1 + nativeMemoryMap.get(1) shouldBe null + nativeMemoryMap.get(2) shouldBe TestCacheValue("2345") - val operationCounters = nativeMemoryMap.operationCounters - assertNull(operationCounters) + nativeMemoryMap.operationCounters shouldBe null } @Test @@ -77,35 +77,36 @@ class ConcurrentHashMapScenario { ).build() val putResult1 = nativeMemoryMap.put(1, TestCacheValue("1234")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult1) + putResult1 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER val putResult2 = nativeMemoryMap.put(2, TestCacheValue("2345")) - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult2) + putResult2 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER - assertEquals(2, nativeMemoryMap.size) - assertEquals(NativeMemoryMapStats(), nativeMemoryMap.stats) + nativeMemoryMap.size shouldBe 2 + nativeMemoryMap.stats shouldBe NativeMemoryMapStats() logger.info { "nativeMemoryMap.get(1) = ${nativeMemoryMap.get(1)}" } logger.info { "nativeMemoryMap.get(2) = ${nativeMemoryMap.get(2)}" } - assertEquals(TestCacheValue("1234"), nativeMemoryMap.get(1)) - assertEquals(TestCacheValue("2345"), nativeMemoryMap.get(2)) + nativeMemoryMap.get(1) shouldBe TestCacheValue("1234") + nativeMemoryMap.get(2) shouldBe TestCacheValue("2345") - assertTrue(nativeMemoryMap.delete(1)) + nativeMemoryMap.delete(1) shouldBe true - assertEquals(1, nativeMemoryMap.size) - assertEquals(null, nativeMemoryMap.get(1)) - assertEquals(TestCacheValue("2345"), nativeMemoryMap.get(2)) + nativeMemoryMap.size shouldBe 1 + nativeMemoryMap.get(1) shouldBe null + nativeMemoryMap.get(2) shouldBe TestCacheValue("2345") val operationCounters = nativeMemoryMap.operationCounters - assertNotNull(operationCounters) - assertEquals(0, operationCounters?.numPutsNoChange?.toLong()) - assertEquals(0, operationCounters?.numPutsFreedBuffer?.toLong()) - assertEquals(0, operationCounters?.numPutsReusedBuffer?.toLong()) - assertEquals(2, operationCounters?.numPutsNewBuffer?.toLong()) - assertEquals(1, operationCounters?.numDeletesFreedBuffer?.toLong()) - assertEquals(0, operationCounters?.numDeletesNoChange?.toLong()) - assertEquals(5, operationCounters?.numGetsNonNullValue?.toLong()) - assertEquals(1, operationCounters?.numGetsNullValue?.toLong()) + operationCounters shouldNotBe null + + operationCounters?.numPutsNoChange?.toLong() shouldBe 0 + operationCounters?.numPutsFreedBuffer?.toLong() shouldBe 0 + operationCounters?.numPutsReusedBuffer?.toLong() shouldBe 0 + operationCounters?.numPutsNewBuffer?.toLong() shouldBe 2 + operationCounters?.numDeletesFreedBuffer?.toLong() shouldBe 1 + operationCounters?.numDeletesNoChange?.toLong() shouldBe 0 + operationCounters?.numGetsNonNullValue?.toLong() shouldBe 5 + operationCounters?.numGetsNullValue?.toLong() shouldBe 1 } } \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/integrationtests/TestCacheValue.kt b/src/test/kotlin/com/target/integrationtests/TestCacheValue.kt similarity index 90% rename from src/test/kotlin/com/target/nativememoryallocator/integrationtests/TestCacheValue.kt rename to src/test/kotlin/com/target/integrationtests/TestCacheValue.kt index efe5d60..c7668c0 100644 --- a/src/test/kotlin/com/target/nativememoryallocator/integrationtests/TestCacheValue.kt +++ b/src/test/kotlin/com/target/integrationtests/TestCacheValue.kt @@ -1,8 +1,8 @@ -package com.target.nativememoryallocator.integrationtests +package com.target.integrationtests import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer import com.target.nativememoryallocator.map.NativeMemoryMapSerializer -import mu.KotlinLogging +import io.github.oshai.kotlinlogging.KotlinLogging private val logger = KotlinLogging.logger {} diff --git a/src/test/kotlin/com/target/map/impl/CaffeineNativeMemoryMapImplTest.kt b/src/test/kotlin/com/target/map/impl/CaffeineNativeMemoryMapImplTest.kt new file mode 100644 index 0000000..0d9b79b --- /dev/null +++ b/src/test/kotlin/com/target/map/impl/CaffeineNativeMemoryMapImplTest.kt @@ -0,0 +1,120 @@ +package com.target.map.impl + +import com.github.benmanes.caffeine.cache.RemovalCause +import com.target.nativememoryallocator.allocator.NativeMemoryAllocator +import com.target.nativememoryallocator.buffer.NativeMemoryBuffer +import com.target.nativememoryallocator.map.impl.CaffeineEvictionListener +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test + +class CaffeineNativeMemoryMapImplTest { + + private val mockNativeMemoryAllocator = mockk() + private val mockNativeMemoryBuffer = mockk() + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun `test CaffeineEvictionListener with RemovalCause EXPLICIT`() { + val key = "test" + + val caffeineEvictionListener = CaffeineEvictionListener( + nativeMemoryAllocator = mockNativeMemoryAllocator, + ) + + caffeineEvictionListener.onRemoval( + key = key, + value = mockNativeMemoryBuffer, + cause = RemovalCause.EXPLICIT, + ) + + verify(exactly = 0) { + mockNativeMemoryBuffer.freed + } + verify(exactly = 0) { + mockNativeMemoryAllocator.freeNativeMemoryBuffer(any()) + } + } + + @Test + fun `test CaffeineEvictionListener with RemovalCause EXPIRED`() { + val key = "test" + + val caffeineEvictionListener = CaffeineEvictionListener( + nativeMemoryAllocator = mockNativeMemoryAllocator, + ) + + every { + mockNativeMemoryBuffer.freed + } returns false + + caffeineEvictionListener.onRemoval( + key = key, + value = mockNativeMemoryBuffer, + cause = RemovalCause.EXPIRED, + ) + + verify(exactly = 1) { + mockNativeMemoryBuffer.freed + } + verify(exactly = 1) { + mockNativeMemoryAllocator.freeNativeMemoryBuffer( + buffer = mockNativeMemoryBuffer, + ) + } + } + + @Test + fun `test CaffeineEvictionListener with RemovalCause EXPIRED, buffer is already freed`() { + val key = "test" + + val caffeineEvictionListener = CaffeineEvictionListener( + nativeMemoryAllocator = mockNativeMemoryAllocator, + ) + + every { + mockNativeMemoryBuffer.freed + } returns true + + caffeineEvictionListener.onRemoval( + key = key, + value = mockNativeMemoryBuffer, + cause = RemovalCause.EXPIRED, + ) + + verify(exactly = 1) { + mockNativeMemoryBuffer.freed + } + verify(exactly = 0) { + mockNativeMemoryAllocator.freeNativeMemoryBuffer( + buffer = any(), + ) + } + } + + @Test + fun `test CaffeineEvictionListener with null parameters`() { + val caffeineEvictionListener = CaffeineEvictionListener( + nativeMemoryAllocator = mockNativeMemoryAllocator, + ) + + caffeineEvictionListener.onRemoval( + key = null, + value = null, + cause = RemovalCause.EXPLICIT, + ) + + verify(exactly = 0) { + mockNativeMemoryAllocator.freeNativeMemoryBuffer( + buffer = any(), + ) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/map/impl/NativeMemoryMapImplTest.kt b/src/test/kotlin/com/target/map/impl/NativeMemoryMapImplTest.kt new file mode 100644 index 0000000..3e02833 --- /dev/null +++ b/src/test/kotlin/com/target/map/impl/NativeMemoryMapImplTest.kt @@ -0,0 +1,444 @@ +package com.target.map.impl + +import com.target.nativememoryallocator.allocator.NativeMemoryAllocator +import com.target.nativememoryallocator.buffer.NativeMemoryBuffer +import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer +import com.target.nativememoryallocator.buffer.OnHeapMemoryBufferFactory +import com.target.nativememoryallocator.map.NativeMemoryMap +import com.target.nativememoryallocator.map.NativeMemoryMapSerializer +import com.target.nativememoryallocator.map.impl.NativeMemoryMapImpl +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.AbstractMap +import java.util.concurrent.ConcurrentHashMap +import kotlin.random.Random + +class NativeMemoryMapImplTest { + + private class TestValueObject + + private val mockTestValueObjectNativeMemoryMapSerializer = mockk>() + + private val mockNativeMemoryAllocator = mockk() + + private val mockNativeMemoryBuffer = mockk() + + @BeforeEach + fun beforeEach() { + mockkObject(OnHeapMemoryBufferFactory) + } + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun `construct NativeMemoryMapImpl`() { + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = true, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + nativeMemoryMap.entries.isEmpty() shouldBe true + nativeMemoryMap.keys.isEmpty() shouldBe true + nativeMemoryMap.size shouldBe 0 + } + + @Test + fun `test put of null value`() { + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = true, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + val putResult = nativeMemoryMap.put(key = 1, value = null) + + putResult shouldBe NativeMemoryMap.PutResult.NO_CHANGE + nativeMemoryMap.entries.isEmpty() shouldBe true + nativeMemoryMap.keys.isEmpty() shouldBe true + nativeMemoryMap.size shouldBe 0 + } + + @Test + fun `test put`() { + val serializedValue = ByteArray(10) + Random.nextBytes(serializedValue) + + val putValue = mockk() + + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = true, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + every { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } returns serializedValue + + every { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } returns mockNativeMemoryBuffer + + every { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } returns Unit + + val putResult = nativeMemoryMap.put(key = 1, value = putValue) + + putResult shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + nativeMemoryMap.entries shouldBe setOf( + AbstractMap.SimpleEntry( + 1, + mockNativeMemoryBuffer, + ), + ) + nativeMemoryMap.keys shouldBe setOf(1) + nativeMemoryMap.size shouldBe 1 + + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } + } + + @Test + fun `test put then get useThreadLocalOnHeapReadBuffer = true`() { + val serializedValue = ByteArray(10) + Random.nextBytes(serializedValue) + + val threadLocalReadBuffer = mockk() + + val putValue = mockk() + + val getDeserializedValue = mockk() + + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = true, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + every { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } returns serializedValue + + every { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } returns mockNativeMemoryBuffer + + every { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } returns Unit + + every { + mockNativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) + } returns Unit + + every { + mockTestValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) + } returns getDeserializedValue + + val putResult = nativeMemoryMap.put(key = 1, value = putValue) + putResult shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + + nativeMemoryMap.threadLocalOnHeapReadBuffer!!.set(threadLocalReadBuffer) + + val getResult = nativeMemoryMap.get(key = 1) + getResult shouldBe getDeserializedValue + + nativeMemoryMap.entries shouldBe setOf( + AbstractMap.SimpleEntry( + 1, + mockNativeMemoryBuffer, + ) + ) + nativeMemoryMap.keys shouldBe setOf(1) + nativeMemoryMap.size shouldBe 1 + + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) + } + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) + } + } + + + @Test + fun `test put then get useThreadLocalOnHeapReadBuffer = false`() { + val serializedValue = ByteArray(10) + Random.nextBytes(serializedValue) + + val putValue = mockk() + + val getDeserializedValue = mockk() + + val onHeapMemoryBuffer = mockk() + + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = false, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + every { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } returns serializedValue + + every { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } returns mockNativeMemoryBuffer + + every { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } returns Unit + + every { + OnHeapMemoryBufferFactory.newOnHeapMemoryBuffer(initialCapacityBytes = 10) + } returns onHeapMemoryBuffer + + every { + mockTestValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = onHeapMemoryBuffer) + } returns getDeserializedValue + + val putResult = nativeMemoryMap.put(key = 1, value = putValue) + putResult shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + + every { + mockNativeMemoryBuffer.capacityBytes + } returns 10 + + every { + mockNativeMemoryBuffer.copyToOnHeapMemoryBuffer(onHeapMemoryBuffer) + } returns Unit + + val getResult = nativeMemoryMap.get(key = 1) + getResult shouldBe getDeserializedValue + + nativeMemoryMap.entries shouldBe setOf( + AbstractMap.SimpleEntry( + 1, + mockNativeMemoryBuffer, + ) + ) + nativeMemoryMap.keys shouldBe setOf(1) + nativeMemoryMap.size shouldBe 1 + + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } + verify(exactly = 1) { + OnHeapMemoryBufferFactory.newOnHeapMemoryBuffer(initialCapacityBytes = 10) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyToOnHeapMemoryBuffer(onHeapMemoryBuffer) + } + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = onHeapMemoryBuffer) + } + } + + @Test + fun `test put reuse buffer`() { + val serializedValue1 = ByteArray(10) + Random.nextBytes(serializedValue1) + + val serializedValue2 = ByteArray(20) + Random.nextBytes(serializedValue2) + + val putValue1 = mockk() + + val putValue2 = mockk() + + val threadLocalReadBuffer = mockk() + + val getDeserializedValue = mockk() + + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = true, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + every { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue1) + } returns serializedValue1 + + every { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue2) + } returns serializedValue2 + + every { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } returns mockNativeMemoryBuffer + + every { + mockNativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = mockNativeMemoryBuffer, + newCapacityBytes = 20 + ) + } returns Unit + + every { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue1) + } returns Unit + + every { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue2) + } returns Unit + + nativeMemoryMap.threadLocalOnHeapReadBuffer!!.set(threadLocalReadBuffer) + + every { + mockNativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) + } returns Unit + + every { + mockTestValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) + } returns getDeserializedValue + + val putResult1 = nativeMemoryMap.put(key = 1, value = putValue1) + putResult1 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + + val putResult2 = nativeMemoryMap.put(key = 1, value = putValue2) + putResult2 shouldBe NativeMemoryMap.PutResult.REUSED_EXISTING_BUFFER + + val getResult = nativeMemoryMap.get(key = 1) + getResult shouldBe getDeserializedValue + + nativeMemoryMap.entries shouldBe setOf( + AbstractMap.SimpleEntry( + 1, + mockNativeMemoryBuffer, + ), + ) + nativeMemoryMap.keys shouldBe setOf(1) + nativeMemoryMap.size shouldBe 1 + + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue1) + } + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue2) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.resizeNativeMemoryBuffer( + buffer = mockNativeMemoryBuffer, + newCapacityBytes = 20 + ) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue1) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue2) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) + } + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) + } + } + + @Test + fun `test put then delete`() { + val serializedValue = ByteArray(10) + Random.nextBytes(serializedValue) + + val putValue = mockk() + + val nativeMemoryMap = NativeMemoryMapImpl( + valueSerializer = mockTestValueObjectNativeMemoryMapSerializer, + nativeMemoryAllocator = mockNativeMemoryAllocator, + useThreadLocalOnHeapReadBuffer = true, + threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), + cacheMap = ConcurrentHashMap(), + ) + + every { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } returns serializedValue + + every { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } returns mockNativeMemoryBuffer + + every { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } returns Unit + + every { + mockNativeMemoryAllocator.freeNativeMemoryBuffer(buffer = mockNativeMemoryBuffer) + } returns Unit + + val putResult1 = nativeMemoryMap.put(key = 1, value = putValue) + putResult1 shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + + val putResult2 = nativeMemoryMap.put(key = 1, value = null) + putResult2 shouldBe NativeMemoryMap.PutResult.FREED_CURRENT_BUFFER + + nativeMemoryMap.entries.isEmpty() shouldBe true + nativeMemoryMap.keys.isEmpty() shouldBe true + nativeMemoryMap.size shouldBe 0 + + verify(exactly = 1) { + mockTestValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) + } + verify(exactly = 1) { + mockNativeMemoryBuffer.copyFromArray(byteArray = serializedValue) + } + verify(exactly = 1) { + mockNativeMemoryAllocator.freeNativeMemoryBuffer(buffer = mockNativeMemoryBuffer) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/map/impl/OperationCountedNativeMemoryMapImplTest.kt b/src/test/kotlin/com/target/map/impl/OperationCountedNativeMemoryMapImplTest.kt new file mode 100644 index 0000000..6ac01fe --- /dev/null +++ b/src/test/kotlin/com/target/map/impl/OperationCountedNativeMemoryMapImplTest.kt @@ -0,0 +1,237 @@ +package com.target.map.impl + +import com.target.nativememoryallocator.map.NativeMemoryMap +import com.target.nativememoryallocator.map.impl.OperationCountedNativeMemoryMapImpl +import com.target.nativememoryallocator.map.impl.OperationCountersImpl +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicLong + +class OperationCountedNativeMemoryMapImplTest { + + private class TestValueObject + + private val mockNativeMemoryMap = mockk>() + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun `test OperationCountersImpl counterValuesEqual`() { + var operationCountersImpl1 = OperationCountersImpl() + + var operationCountersImpl2 = OperationCountersImpl() + + operationCountersImpl1.counterValuesEqual(operationCountersImpl2) shouldBe true + + operationCountersImpl1 = OperationCountersImpl() + operationCountersImpl1.numPutsNoChange.set(1) + + operationCountersImpl2 = OperationCountersImpl() + + operationCountersImpl1.counterValuesEqual(operationCountersImpl2) shouldBe false + + operationCountersImpl1 = OperationCountersImpl() + + operationCountersImpl2 = OperationCountersImpl() + operationCountersImpl2.numGetsNonNullValue.set(1) + + operationCountersImpl1.counterValuesEqual(operationCountersImpl2) shouldBe false + + } + + @Test + fun `test put no change`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + every { + mockNativeMemoryMap.put(key = 1, value = null) + } returns NativeMemoryMap.PutResult.NO_CHANGE + + val putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = null) + + putResult shouldBe NativeMemoryMap.PutResult.NO_CHANGE + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numPutsNoChange = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.put(key = 1, value = null) } + } + + @Test + fun `test test put freed buffer`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + every { + mockNativeMemoryMap.put(key = 1, value = null) + } returns NativeMemoryMap.PutResult.FREED_CURRENT_BUFFER + + val putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = null) + + putResult shouldBe NativeMemoryMap.PutResult.FREED_CURRENT_BUFFER + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numPutsFreedBuffer = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.put(key = 1, value = null) } + } + + @Test + fun `test put allocated new buffer`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + val testValueObject = mockk() + + every { + mockNativeMemoryMap.put(key = 1, value = testValueObject) + } returns NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + + val putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = testValueObject) + putResult shouldBe NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numPutsNewBuffer = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.put(key = 1, value = testValueObject) } + } + + @Test + fun `test put reused buffer`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + val testValueObject = mockk() + + every { + mockNativeMemoryMap.put(key = 1, value = testValueObject) + } returns NativeMemoryMap.PutResult.REUSED_EXISTING_BUFFER + + val putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = testValueObject) + + putResult shouldBe NativeMemoryMap.PutResult.REUSED_EXISTING_BUFFER + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numPutsReusedBuffer = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.put(key = 1, value = testValueObject) } + } + + @Test + fun `test get returning null`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + every { + mockNativeMemoryMap.get(key = 1) + } returns null + + val getResult = operationCountedNativeMemoryMapImpl.get(key = 1) + + getResult shouldBe null + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numGetsNullValue = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.get(key = 1) } + } + + @Test + fun `test get returning non-null`() { + val mockResult = mockk() + + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + every { + mockNativeMemoryMap.get(key = 1) + } returns mockResult + + val getResult = operationCountedNativeMemoryMapImpl.get(key = 1) + + getResult shouldBe mockResult + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numGetsNonNullValue = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.get(key = 1) } + } + + @Test + fun `test delete freed buffer`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + + every { + mockNativeMemoryMap.delete(key = 1) + } returns true + + val deleteResult = operationCountedNativeMemoryMapImpl.delete(key = 1) + + deleteResult shouldBe true + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numDeletesFreedBuffer = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.delete(key = 1) } + } + + @Test + fun `test delete no change`() { + val operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( + nativeMemoryMap = mockNativeMemoryMap, + ) + every { + mockNativeMemoryMap.delete(key = 1) + } returns false + + val deleteResult = operationCountedNativeMemoryMapImpl.delete(key = 1) + + deleteResult shouldBe false + + operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( + OperationCountersImpl( + numDeletesNoChange = AtomicLong(1), + ) + ) shouldBe true + + verify(exactly = 1) { mockNativeMemoryMap.delete(key = 1) } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/metrics/micrometer/MicrometerMetricsTest.kt b/src/test/kotlin/com/target/metrics/micrometer/MicrometerMetricsTest.kt new file mode 100644 index 0000000..4d8eac0 --- /dev/null +++ b/src/test/kotlin/com/target/metrics/micrometer/MicrometerMetricsTest.kt @@ -0,0 +1,222 @@ +package com.target.metrics.micrometer + +import com.github.benmanes.caffeine.cache.stats.CacheStats +import com.target.nativememoryallocator.allocator.NativeMemoryAllocator +import com.target.nativememoryallocator.map.BaseNativeMemoryMap +import com.target.nativememoryallocator.map.NativeMemoryMapStats +import com.target.nativememoryallocator.map.impl.OperationCountersImpl +import com.target.nativememoryallocator.metrics.micrometer.MicrometerNativeMemoryAllocatorMetrics +import com.target.nativememoryallocator.metrics.micrometer.MicrometerNativeMemoryMapMetrics +import io.github.oshai.kotlinlogging.KotlinLogging +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.micrometer.core.instrument.Tag +import io.micrometer.core.instrument.Tags +import io.micrometer.core.instrument.simple.SimpleMeterRegistry +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test + +private val logger = KotlinLogging.logger {} + +class MicrometerMetricsTest { + + private val mockNativeMemoryAllocator = mockk() + + private val mockNativeMemoryMap = mockk() + + @AfterEach + fun afterEach() { + clearAllMocks() + } + + @Test + fun `test MicrometerNativeMemoryAllocatorMetrics`() { + val meterRegistry = SimpleMeterRegistry() + + val tags = listOf(Tag.of("key1", "value1")) + + val numFreePagesValue = 1 + val numUsedPagesValue = 2 + val totalNumPagesValue = 3 + val numAllocationExceptionsValue = 4 + val numFreeExceptionsValue = 5 + + val meterNameToValue = mapOf( + "nativeMemoryAllocator.numFreePages" to numFreePagesValue, + "nativeMemoryAllocator.numUsedPages" to numUsedPagesValue, + "nativeMemoryAllocator.totalNumPages" to totalNumPagesValue, + "nativeMemoryAllocator.numAllocationExceptions" to numAllocationExceptionsValue, + "nativeMemoryAllocator.numFreeExceptions" to numFreeExceptionsValue, + ) + + every { + mockNativeMemoryAllocator.numFreePages + } returns numFreePagesValue + every { + mockNativeMemoryAllocator.numUsedPages + } returns numUsedPagesValue + every { + mockNativeMemoryAllocator.totalNumPages + } returns totalNumPagesValue + every { + mockNativeMemoryAllocator.numAllocationExceptions + } returns numAllocationExceptionsValue + every { + mockNativeMemoryAllocator.numFreeExceptions + } returns numFreeExceptionsValue + + MicrometerNativeMemoryAllocatorMetrics( + nativeMemoryAllocator = mockNativeMemoryAllocator, + meterRegistry = meterRegistry, + tags = Tags.of(tags), + ) + + meterRegistry.meters.size shouldBe 5 + logger.info { "meterRegistry.meters = ${meterRegistry.meters}" } + + val idToMeterMap = meterRegistry.meters.filterNotNull().associateBy { it.id } + idToMeterMap.size shouldBe 5 + + val idAndMeterList = idToMeterMap.toList() + + meterNameToValue.forEach { (meterName, expectedValue) -> + val meterObject = + idAndMeterList.find { (it.first.name == meterName) && (it.first.tags == tags) }?.second + meterObject shouldNotBe null + + val measurement = meterObject?.measure() + measurement shouldNotBe null + + measurement?.take(1)?.get(0)?.value shouldBe expectedValue.toDouble() + } + + } + + @Test + fun `test MicrometerNativeMemoryMapMetrics no caffeine, no operation counters`() { + val meterRegistry = SimpleMeterRegistry() + val tags = listOf(Tag.of("key1", "value1")) + val mapSizeValue = 42 + + val meterNameToValue = mapOf( + "nativeMemoryMap.size" to mapSizeValue, + ) + + every { + mockNativeMemoryMap.stats + } returns NativeMemoryMapStats( + caffeineStats = null, + ) + + every { + mockNativeMemoryMap.operationCounters + } returns null + + every { + mockNativeMemoryMap.size + } returns mapSizeValue + + MicrometerNativeMemoryMapMetrics( + nativeMemoryMap = mockNativeMemoryMap, + meterRegistry = meterRegistry, + tags = Tags.of(tags), + ) + + meterRegistry.meters.size shouldBe 1 + + logger.info { "meterRegistry.meters = ${meterRegistry.meters}" } + val idToMeterMap = meterRegistry.meters.filterNotNull().associateBy { it.id } + + idToMeterMap.size shouldBe 1 + val idAndMeterList = idToMeterMap.toList() + meterNameToValue.forEach { (meterName, expectedValue) -> + val meterObject = + idAndMeterList.find { (it.first.name == meterName) && (it.first.tags == tags) }?.second + meterObject shouldNotBe null + + val measurement = meterObject?.measure() + measurement shouldNotBe null + + measurement?.take(1)?.get(0)?.value shouldBe expectedValue.toDouble() + } + } + + @Test + fun `test MicrometerNativeMemoryMapMetrics with caffeine, with operation counters`() { + val meterRegistry = SimpleMeterRegistry() + val tags = listOf(Tag.of("key1", "value1")) + val caffeineStats = mockk() + + val mapSizeValue = 42 + val caffeineEvictionCountValue = 43 + + val operationCounters = OperationCountersImpl() + operationCounters.numPutsNoChange.set(44) + operationCounters.numPutsFreedBuffer.set(45) + operationCounters.numPutsReusedBuffer.set(46) + operationCounters.numPutsNewBuffer.set(47) + operationCounters.numDeletesFreedBuffer.set(48) + operationCounters.numDeletesNoChange.set(49) + operationCounters.numGetsNullValue.set(50) + operationCounters.numGetsNonNullValue.set(51) + + val meterNameToValue = mapOf( + "nativeMemoryMap.size" to mapSizeValue, + "nativeMemoryMap.caffeineEvictionCount" to caffeineEvictionCountValue, + "nativeMemoryMap.numPutsNoChange" to operationCounters.numPutsNoChange.get(), + "nativeMemoryMap.numPutsFreedBuffer" to operationCounters.numPutsFreedBuffer.get(), + "nativeMemoryMap.numPutsReusedBuffer" to operationCounters.numPutsReusedBuffer.get(), + "nativeMemoryMap.numPutsNewBuffer" to operationCounters.numPutsNewBuffer.get(), + "nativeMemoryMap.numDeletesFreedBuffer" to operationCounters.numDeletesFreedBuffer.get(), + "nativeMemoryMap.numDeletesNoChange" to operationCounters.numDeletesNoChange.get(), + "nativeMemoryMap.numGetsNullValue" to operationCounters.numGetsNullValue.get(), + "nativeMemoryMap.numGetsNonNullValue" to operationCounters.numGetsNonNullValue.get(), + ) + + every { + caffeineStats.evictionCount() + } returns caffeineEvictionCountValue.toLong() + + every { + mockNativeMemoryMap.stats + } returns NativeMemoryMapStats( + caffeineStats = caffeineStats, + ) + + every { + mockNativeMemoryMap.operationCounters + } returns operationCounters + + every { + mockNativeMemoryMap.size + } returns mapSizeValue + + MicrometerNativeMemoryMapMetrics( + nativeMemoryMap = mockNativeMemoryMap, + meterRegistry = meterRegistry, + tags = Tags.of(tags), + ) + + meterRegistry.meters.size shouldBe 10 + + logger.info { "meterRegistry.meters = ${meterRegistry.meters}" } + val idToMeterMap = meterRegistry.meters.filterNotNull().associateBy { it.id } + idToMeterMap.size shouldBe 10 + + val idAndMeterList = idToMeterMap.toList() + + meterNameToValue.forEach { (meterName, expectedValue) -> + val meterObject = + idAndMeterList.find { (it.first.name == meterName) && (it.first.tags == tags) }?.second + meterObject shouldNotBe null + + val measurement = meterObject?.measure() + measurement shouldNotBe null + + measurement?.take(1)?.get(0)?.value shouldBe expectedValue.toDouble() + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImplSpec.kt deleted file mode 100644 index 63595a2..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/allocator/impl/FreeListImplSpec.kt +++ /dev/null @@ -1,362 +0,0 @@ -package com.target.nativememoryallocator.impl - -import com.target.nativememoryallocator.allocator.impl.FreeListImpl -import com.target.nativememoryallocator.unsafe.NativeMemoryPage -import io.mockk.clearAllMocks -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature - -class FreeListSpec : Spek({ - Feature("FreeList") { - Scenario("test initialization") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - } - - lateinit var freeList: FreeListImpl - - When("construct freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - Then("initial freeList state is correct") { - assertEquals(totalNumPages, freeList.freePageArray().size) - - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(0, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(totalNumPages, freeList.numFreePages()) - assertEquals(0, freeList.numUsedPages()) - assertEquals(0, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - clearAllMocks() - } - Scenario("test allocation of 1 page") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - } - expectedFreePageArray[0] = 0 - - lateinit var freeList: FreeListImpl - lateinit var retVal: ArrayList - - Given("setup freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - When("allocate 1 page") { - retVal = freeList.allocatePages(1) - } - Then("freeList state is correct") { - assertEquals( - arrayListOf( - NativeMemoryPage( - startAddress = baseNativeMemoryPointer, - ) - ), retVal) - - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(1, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(totalNumPages - 1, freeList.numFreePages()) - assertEquals(1, freeList.numUsedPages()) - assertEquals(0, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - clearAllMocks() - } - Scenario("test allocation of 101 pages sequentially") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - lateinit var freeList: FreeListImpl - - val expectedFreePageArray = LongArray(totalNumPages) { 0 } - - val pagesAllocated = arrayListOf() - var exceptionsCaught = 0 - - val expectedAllocations = (0 until totalNumPages).map { - NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) - } - - Given("setup freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - When("try to allocate 101 pages") { - (0 until 101).forEach { _ -> - try { - pagesAllocated.addAll(freeList.allocatePages(1)) - } catch (e: IllegalStateException) { - exceptionsCaught += 1 - } - } - } - Then("freeList state is correct") { - assertEquals(expectedAllocations, pagesAllocated) - assertEquals(1, exceptionsCaught) - - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(100, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(0, freeList.numFreePages()) - assertEquals(100, freeList.numUsedPages()) - assertEquals(1, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - clearAllMocks() - } - Scenario("test allocation of 101 pages non-sequentially") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - lateinit var freeList: FreeListImpl - - val pagesAllocated = arrayListOf() - var exceptionsCaught = 0 - - val expectedAllocations = emptyList() - - - - Given("setup freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - When("try to allocate 101 pages") { - try { - pagesAllocated.addAll(freeList.allocatePages(101)) - } catch (e: IllegalStateException) { - exceptionsCaught += 1 - } - } - Then("freeList state is correct") { - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - }.reversedArray() - - assertEquals(expectedAllocations, pagesAllocated) - assertEquals(1, exceptionsCaught) - - // all pages allocated are freed - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - assertEquals(0, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(100, freeList.numFreePages()) - assertEquals(0, freeList.numUsedPages()) - assertEquals(1, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - clearAllMocks() - } - Scenario("test allocation of 100 pages then free 100 pages") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - lateinit var freeList: FreeListImpl - - val pagesAllocated = arrayListOf() - - val expectedAllocations = (0 until totalNumPages).map { - NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) - } - - Given("setup freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - When("allocate 100 pages") { - pagesAllocated.addAll(freeList.allocatePages(100)) - } - Then("freeList state is correct") { - assertEquals(expectedAllocations, pagesAllocated) - - val expectedFreePageArray = LongArray(totalNumPages) { 0 } - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - assertEquals(100, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(0, freeList.numFreePages()) - assertEquals(100, freeList.numUsedPages()) - assertEquals(0, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - When("free 100 pages") { - freeList.freePages(pagesAllocated) - } - Then("freeList state is correct") { - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - }.reversedArray() - - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - assertEquals(0, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(totalNumPages, freeList.numFreePages()) - assertEquals(0, freeList.numUsedPages()) - assertEquals(0, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - clearAllMocks() - } - Scenario("test allocation of 2 pages then free 1 page") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - lateinit var freeList: FreeListImpl - - val pagesAllocated = arrayListOf() - - val expectedAllocations = (0 until 2).map { - NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) - } - - Given("setup freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - When("allocate 2 pages") { - pagesAllocated.addAll(freeList.allocatePages(2)) - } - Then("freeList state is correct") { - assertEquals(expectedAllocations, pagesAllocated) - - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - } - expectedFreePageArray[0] = 0 - expectedFreePageArray[1] = 0 - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(2, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(98, freeList.numFreePages()) - assertEquals(2, freeList.numUsedPages()) - } - When("free 1 page") { - freeList.freePages(listOf(pagesAllocated[0])) - } - Then("freeList state is correct") { - assertEquals(expectedAllocations, pagesAllocated) - - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - } - expectedFreePageArray[1] = expectedFreePageArray[0] - expectedFreePageArray[0] = 0 - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(1, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(99, freeList.numFreePages()) - assertEquals(1, freeList.numUsedPages()) - assertEquals(0, freeList.numAllocationExceptions()) - assertEquals(0, freeList.numFreeExceptions()) - } - clearAllMocks() - } - Scenario("test allocation of 2 pages then free 3 pages") { - val baseNativeMemoryPointer = 0x80000000 - val pageSizeBytes = 4_096 - val totalNumPages = 100 - - lateinit var freeList: FreeListImpl - - val pagesAllocated = arrayListOf() - var numFreeExceptions = 0 - - val expectedAllocations = (0 until 2).map { - NativeMemoryPage(baseNativeMemoryPointer + (pageSizeBytes * it)) - } - - Given("setup freeList") { - freeList = FreeListImpl( - baseNativeMemoryPointer = baseNativeMemoryPointer, - pageSizeBytes = pageSizeBytes, - totalNumPages = totalNumPages, - ) - } - When("allocate 2 pages") { - pagesAllocated.addAll(freeList.allocatePages(2)) - } - Then("freeList state is correct") { - assertEquals(expectedAllocations, pagesAllocated) - - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - } - expectedFreePageArray[0] = 0 - expectedFreePageArray[1] = 0 - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(2, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(98, freeList.numFreePages()) - assertEquals(2, freeList.numUsedPages()) - } - When("free 3 pages") { - freeList.freePages(listOf(pagesAllocated[1], pagesAllocated[0])) - - try { - freeList.freePages(listOf(pagesAllocated[0])) - } catch (e: IllegalStateException) { - numFreeExceptions++ - } - } - Then("freeList state is correct") { - assertEquals(expectedAllocations, pagesAllocated) - - val expectedFreePageArray = LongArray(totalNumPages) { pageNumber -> - baseNativeMemoryPointer + (pageNumber * pageSizeBytes) - } - - assertTrue(expectedFreePageArray.contentEquals(freeList.freePageArray())) - - assertEquals(0, freeList.nextFreePageIndex()) - assertEquals(totalNumPages, freeList.totalNumPages) - assertEquals(100, freeList.numFreePages()) - assertEquals(0, freeList.numUsedPages()) - assertEquals(0, freeList.numAllocationExceptions()) - assertEquals(1, freeList.numFreeExceptions()) - } - clearAllMocks() - } - } - -}) diff --git a/src/test/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImplSpec.kt deleted file mode 100644 index bdfb387..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/allocator/impl/NativeMemoryAllocatorImplSpec.kt +++ /dev/null @@ -1,827 +0,0 @@ -package com.target.nativememoryallocator.allocator.impl - -import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorMetadata -import com.target.nativememoryallocator.buffer.NativeMemoryBuffer -import com.target.nativememoryallocator.unsafe.UnsafeContainer -import io.mockk.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertThrows -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature -import sun.misc.Unsafe - -class NativeMemoryAllocatorImplSpec : Spek({ - Feature("NativeMemoryAllocatorImpl") { - Scenario("test validateNativeMemoryAllocatorInitialParameters") { - var pageSizeBytes = 0 - var nativeMemorySizeBytes = 0L - - When("negative pageSizeBytes") { - pageSizeBytes = -1 - nativeMemorySizeBytes = 1L * 1024L * 1024L * 1024L - } - Then("validateNativeMemoryAllocatorInitialParameters throws IllegalArgumentException") { - assertThrows(IllegalArgumentException::class.java) { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - } - When("zero pageSizeBytes") { - pageSizeBytes = 0 - nativeMemorySizeBytes = 1L * 1024L * 1024L * 1024L - } - Then("validateNativeMemoryAllocatorInitialParameters throws IllegalArgumentException") { - assertThrows(IllegalArgumentException::class.java) { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - } - When("positive pageSizeBytes") { - pageSizeBytes = 1 - nativeMemorySizeBytes = 1L * 1024L * 1024L * 1024L - } - Then("validateNativeMemoryAllocatorInitialParameters does not throw") { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - When("max pageSizeBytes") { - pageSizeBytes = Int.MAX_VALUE - nativeMemorySizeBytes = Int.MAX_VALUE.toLong() - } - Then("validateNativeMemoryAllocatorInitialParameters does not throw") { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - When("negative nativeMemorySizeBytes") { - pageSizeBytes = 1 - nativeMemorySizeBytes = -1L - } - Then("validateNativeMemoryAllocatorInitialParameters throws IllegalArgumentException") { - assertThrows(IllegalArgumentException::class.java) { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - } - When("zero nativeMemorySizeBytes") { - pageSizeBytes = 1 - nativeMemorySizeBytes = 0L - } - Then("validateNativeMemoryAllocatorInitialParameters throws IllegalArgumentException") { - assertThrows(IllegalArgumentException::class.java) { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - } - When("positive nativeMemorySizeBytes") { - pageSizeBytes = 1 - nativeMemorySizeBytes = 1L - } - Then("validateNativeMemoryAllocatorInitialParameters does not throw") { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - When("positive nativeMemorySizeBytes") { - pageSizeBytes = 4 * 1024 - nativeMemorySizeBytes = (1L * 1024L * 1024L * 1024L) - } - Then("validateNativeMemoryAllocatorInitialParameters does not throw") { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - When("nativeMemorySizeBytes not evenly divisible by pageSizeBytes") { - pageSizeBytes = 4 * 1024 - nativeMemorySizeBytes = (1L * 1024L * 1024L * 1024L) + 1L - } - Then("validateNativeMemoryAllocatorInitialParameters throws IllegalArgumentException") { - assertThrows(IllegalArgumentException::class.java) { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - } - When("invalid totalNumPages") { - pageSizeBytes = 1 - nativeMemorySizeBytes = (20L * 1024L * 1024L * 1024L) - } - Then("validateNativeMemoryAllocatorInitialParameters throws IllegalArgumentException") { - assertThrows(IllegalArgumentException::class.java) { - validateNativeMemoryAllocatorInitialParameters( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - } - } - } - Scenario("test initialization") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val expectedNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - - Given("setup unsafe") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - } - Then("initial nativeMemoryAllocator is correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(expectedNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(expectedNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( - pageSizeBytes = pageSizeBytes, - nextFreePageIndex = 0, - numFreePages = expectedNumPages, - totalNumPages = expectedNumPages, - numUsedPages = 0, - numAllocationExceptions = 0, - numFreeExceptions = 0, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - assertEquals(expectedNativeMemoryAllocatorMetadata, nativeMemoryAllocator.nativeMemoryAllocatorMetadata) - - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - verify(exactly = 0) { - mockUnsafe.setMemory(any(), any(), any()) - } - } - clearAllMocks() - } - Scenario("test initialization with zeroNativeMemoryOnStartup = true") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val expectedNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - - Given("setup unsafe") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - - every { - mockUnsafe.setMemory(mockNativeMemoryPointer, nativeMemorySizeBytes, 0) - } returns Unit - } - When("construct nativeMemoryAllocator") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = true, - ) - } - Then("initial nativeMemoryAllocator is correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(expectedNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(expectedNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( - pageSizeBytes = pageSizeBytes, - nextFreePageIndex = 0, - numFreePages = expectedNumPages, - totalNumPages = expectedNumPages, - numUsedPages = 0, - numAllocationExceptions = 0, - numFreeExceptions = 0, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - assertEquals(expectedNativeMemoryAllocatorMetadata, nativeMemoryAllocator.nativeMemoryAllocatorMetadata) - - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - verify(exactly = 1) { - mockUnsafe.setMemory(mockNativeMemoryPointer, nativeMemorySizeBytes, 0) - } - } - clearAllMocks() - } - Scenario("test initialization invalid nativeMemorySizeBytes") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = (1L * 1024 * 1024 * 1024) + 1 // 1gb + 1 byte - val mockNativeMemoryPointer = 0x80000000 - var exceptionsCaught = 0 - - Given("setup unsafe") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(any()) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator") { - try { - NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - } catch (e: IllegalArgumentException) { - exceptionsCaught += 1 - } - } - Then("initial nativeMemoryAllocator is correct") { - assertEquals(1, exceptionsCaught) - - verify(exactly = 0) { - mockUnsafe.allocateMemory(any()) - } - } - clearAllMocks() - } - Scenario("test allocation of 100 bytes, then free, then double free, then resize freed buffer") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - lateinit var buffer: NativeMemoryBuffer - var doubleFreeExceptions = 0 - var resizeExceptions = 0 - - Given("setup unsafe") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator and allocate a buffer") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - - buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( - capacityBytes = 100, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 1, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(1, nativeMemoryAllocator.numUsedPages) - - val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( - pageSizeBytes = pageSizeBytes, - nextFreePageIndex = 1, - numFreePages = totalNumPages - 1, - totalNumPages = totalNumPages, - numUsedPages = 1, - numAllocationExceptions = 0, - numFreeExceptions = 0, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - assertEquals(expectedNativeMemoryAllocatorMetadata, nativeMemoryAllocator.nativeMemoryAllocatorMetadata) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(100, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(1, buffer.numPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - } - When("free buffer") { - nativeMemoryAllocator.freeNativeMemoryBuffer( - buffer = buffer, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(0, buffer.capacityBytes) - assertEquals(true, buffer.freed) - assertEquals(0, buffer.numPages) - } - When("free buffer again") { - try { - nativeMemoryAllocator.freeNativeMemoryBuffer( - buffer = buffer, - ) - } catch (e: IllegalStateException) { - doubleFreeExceptions += 1 - } - } - Then("double free exception thrown, buffer and nativeMemoryAllocator state are correct") { - assertEquals(1, doubleFreeExceptions) - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(0, buffer.capacityBytes) - assertEquals(true, buffer.freed) - assertEquals(0, buffer.numPages) - } - When("resize buffer") { - try { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = 100, - ) - } catch (e: IllegalStateException) { - resizeExceptions += 1 - } - } - Then("resize freed exception thrown, buffer and nativeMemoryAllocator state are correct") { - assertEquals(1, resizeExceptions) - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(0, buffer.capacityBytes) - assertEquals(true, buffer.freed) - assertEquals(0, buffer.numPages) - } - clearAllMocks() - } - Scenario("test allocation of 10_000 bytes, then resize scenarios, then free") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - lateinit var buffer: NativeMemoryBuffer - - Given("setup unsafe") { - mockUnsafe = mockk() - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator and allocate a buffer") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - - buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( - capacityBytes = 10_000, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 3, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(3, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(10_000, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(3, buffer.numPages) - } - When("resize to same capacity") { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = 10_000, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 3, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(3, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(10_000, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(3, buffer.numPages) - } - When("resize to new capacity, same number of pages") { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = 10_500, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 3, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(3, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(10_500, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(3, buffer.numPages) - } - When("resize to new capacity, more pages") { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = 17_000, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 5, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(5, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(17_000, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(5, buffer.numPages) - } - When("resize to new capacity, fewer pages") { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = 4_097, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 2, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(2, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(4_097, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(2, buffer.numPages) - } - When("free buffer") { - nativeMemoryAllocator.freeNativeMemoryBuffer(buffer) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(0, buffer.capacityBytes) - assertEquals(true, buffer.freed) - assertEquals(0, buffer.numPages) - } - clearAllMocks() - } - Scenario("test allocation of 5_000 bytes, then resize out of memory, then free") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - lateinit var buffer: NativeMemoryBuffer - var resizeExceptions = 0 - - Given("setup unsafe") { - mockUnsafe = mockk() - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator and allocate a buffer") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - - buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( - capacityBytes = 5_000, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 2, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(2, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(5_000, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(2, buffer.numPages) - } - When("resize buffer to max capacity") { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = nativeMemorySizeBytes.toInt(), //1gb - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(0, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(totalNumPages, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(nativeMemorySizeBytes.toInt(), buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(totalNumPages, buffer.numPages) - } - When("resize buffer to 1 page") { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = 4_096, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 1, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(1, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(4_096, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(1, buffer.numPages) - } - When("resize buffer 1 byte more than max capacity") { - try { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = nativeMemorySizeBytes.toInt() + 1, - ) - } catch (e: IllegalStateException) { - resizeExceptions += 1 - } - } - Then("resize throws exception, buffer and nativeMemoryAllocator state are correct") { - assertEquals(1, resizeExceptions) - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 1, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(1, nativeMemoryAllocator.numUsedPages) - assertEquals(1, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - val expectedNativeMemoryAllocatorMetadata = NativeMemoryAllocatorMetadata( - pageSizeBytes = pageSizeBytes, - nextFreePageIndex = 1, - numFreePages = totalNumPages - 1, - totalNumPages = totalNumPages, - numUsedPages = 1, - numAllocationExceptions = 1, - numFreeExceptions = 0, - nativeMemorySizeBytes = nativeMemorySizeBytes, - ) - assertEquals(expectedNativeMemoryAllocatorMetadata, nativeMemoryAllocator.nativeMemoryAllocatorMetadata) - - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(4_096, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(1, buffer.numPages) - } - When("free buffer") { - nativeMemoryAllocator.freeNativeMemoryBuffer( - buffer = buffer, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(1, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(0, buffer.capacityBytes) - assertEquals(true, buffer.freed) - assertEquals(0, buffer.numPages) - } - clearAllMocks() - } - Scenario("test allocation of negative capacity") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - var buffer: NativeMemoryBuffer? = null - var allocateNativeMemoryBufferExceptions = 0 - - Given("setup unsafe") { - mockUnsafe = mockk() - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator and allocate a buffer") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - - try { - buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( - capacityBytes = -1, - ) - } catch (e: IllegalArgumentException) { - ++allocateNativeMemoryBufferExceptions - } - } - Then("buffer and nativeMemoryAllocator state are correct") { - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - assertEquals(1, allocateNativeMemoryBufferExceptions) - assertEquals(null, buffer) - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(0, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - } - clearAllMocks() - } - Scenario("test allocation of 10_000 bytes, then resize to negative value") { - lateinit var mockUnsafe: Unsafe - val pageSizeBytes = 4_096 // 4kb - val nativeMemorySizeBytes = 1L * 1024 * 1024 * 1024 // 1gb - val totalNumPages = (nativeMemorySizeBytes / pageSizeBytes).toInt() - val mockNativeMemoryPointer = 0x80000000 - lateinit var nativeMemoryAllocator: NativeMemoryAllocatorImpl - lateinit var buffer: NativeMemoryBuffer - var resizeNativeMemoryBufferExceptions = 0 - - Given("setup unsafe") { - mockUnsafe = mockk() - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - every { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } returns mockNativeMemoryPointer - } - When("construct nativeMemoryAllocator and allocate a buffer") { - nativeMemoryAllocator = NativeMemoryAllocatorImpl( - pageSizeBytes = pageSizeBytes, - nativeMemorySizeBytes = nativeMemorySizeBytes, - zeroNativeMemoryOnStartup = false, - ) - - buffer = nativeMemoryAllocator.allocateNativeMemoryBuffer( - capacityBytes = 10_000, - ) - } - Then("buffer and nativeMemoryAllocator state are correct") { - verify(exactly = 1) { - mockUnsafe.allocateMemory(nativeMemorySizeBytes) - } - - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 3, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(3, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(10_000, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(3, buffer.numPages) - } - When("resize to -1") { - try { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = buffer, - newCapacityBytes = -1, - ) - } catch (e: IllegalArgumentException) { - ++resizeNativeMemoryBufferExceptions - } - } - Then("buffer and nativeMemoryAllocator state are correct") { - assertEquals(1, resizeNativeMemoryBufferExceptions) - assertEquals(mockNativeMemoryPointer, nativeMemoryAllocator.baseNativeMemoryPointer()) - assertEquals(totalNumPages - 3, nativeMemoryAllocator.numFreePages) - assertEquals(totalNumPages, nativeMemoryAllocator.totalNumPages) - assertEquals(3, nativeMemoryAllocator.numUsedPages) - assertEquals(0, nativeMemoryAllocator.numAllocationExceptions) - assertEquals(0, nativeMemoryAllocator.numFreeExceptions) - - assertEquals(pageSizeBytes, buffer.pageSizeBytes) - assertEquals(10_000, buffer.capacityBytes) - assertEquals(false, buffer.freed) - assertEquals(3, buffer.numPages) - } - clearAllMocks() - } - } -}) \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/buffer/impl/NativeMemoryBufferImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/buffer/impl/NativeMemoryBufferImplSpec.kt deleted file mode 100644 index b57411f..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/buffer/impl/NativeMemoryBufferImplSpec.kt +++ /dev/null @@ -1,548 +0,0 @@ -package com.target.nativememoryallocator.buffer.impl - -import com.target.nativememoryallocator.unsafe.NativeMemoryPage -import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer -import com.target.nativememoryallocator.unsafe.UnsafeContainer -import io.mockk.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature -import sun.misc.Unsafe - -class NativeMemoryBufferImplSpec : Spek({ - Feature("NativeMemoryBufferImpl") { - Scenario("readByte(0) reads the correct data at the correct offset") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - var readRetVal: Byte = 0 - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.getByte(2L * 4_096) - } returns 123 - } - When("readByte(0)") { - readRetVal = nativeMemoryBufferImpl.readByte(offset = 0) - } - Then("value is as expected") { - assertEquals(123.toByte(), readRetVal) - verify(exactly = 1) { mockUnsafe.getByte(2L * 4_096) } - } - clearAllMocks() - } - Scenario("readByte(4095) reads the correct data at the correct offset") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - var readRetVal: Byte = 0 - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.getByte((2L * 4_096) + 4_095) - } returns 42 - } - When("readByte(4095)") { - readRetVal = nativeMemoryBufferImpl.readByte(offset = 4_095) - } - Then("value is as expected") { - assertEquals(42.toByte(), readRetVal) - verify(exactly = 1) { mockUnsafe.getByte((2L * 4_096) + 4_095) } - } - clearAllMocks() - } - Scenario("readByte(8500) reads the correct data at the correct offset") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - var readRetVal: Byte = 0 - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.getByte((0 * 4_096L) + 308) - } returns 55 - } - When("readByte(8500)") { - readRetVal = nativeMemoryBufferImpl.readByte(offset = 8500) - } - Then("value is as expected") { - assertEquals(55.toByte(), readRetVal) - verify(exactly = 1) { mockUnsafe.getByte((0L * 4_096) + 308) } - } - clearAllMocks() - } - Scenario("readByte(12287) reads the correct data at the correct offset") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - var readRetVal: Byte = 0 - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.getByte((0L * 4_096) + 4_095) - } returns 22 - } - When("readByte(12287)") { - readRetVal = nativeMemoryBufferImpl.readByte(offset = 12287) - } - Then("value is as expected") { - assertEquals(22.toByte(), readRetVal) - verify(exactly = 1) { mockUnsafe.getByte((0L * 4_096) + 4_095) } - } - clearAllMocks() - } - Scenario("writeByte(0) writes the correct data at the correct offset") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.putByte(2L * 4_096, 42) - } returns Unit - } - When("writeByte(0)") { - nativeMemoryBufferImpl.writeByte(offset = 0, byte = 42) - } - Then("value is as expected") { - verify(exactly = 1) { mockUnsafe.putByte(2L * 4_096, 42) } - } - clearAllMocks() - } - Scenario("writeByte(12287) writes the correct data at the correct offset") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.putByte((0L * 4_096) + 4095, 33) - } returns Unit - } - When("writeByte(12287)") { - nativeMemoryBufferImpl.writeByte(offset = 12287, byte = 33) - } - Then("value is as expected") { - verify(exactly = 1) { mockUnsafe.putByte((0L * 4_096) + 4095, 33) } - } - clearAllMocks() - } - Scenario("test readAllToByteArray") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - val byteArrayBaseOffset = 16L - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - every { - UnsafeContainer.BYTE_ARRAY_BASE_OFFSET - } returns byteArrayBaseOffset - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 3 * (4_096), - freed = false, - pages = pages, - ) - - every { - mockUnsafe.copyMemory( - null, pages[0].startAddress, - any(), byteArrayBaseOffset + 0, - 4_096.toLong() - ) - } returns Unit - - every { - mockUnsafe.copyMemory( - null, pages[1].startAddress, - any(), byteArrayBaseOffset + 4_096, - 4_096.toLong() - ) - } returns Unit - - every { - mockUnsafe.copyMemory( - null, pages[2].startAddress, - any(), byteArrayBaseOffset + 8_192, - 4_096.toLong() - ) - } returns Unit - } - When("readAllToByteArray") { - nativeMemoryBufferImpl.readAllToByteArray() - } - Then("calls are as expected") { - verify(exactly = 1) { - mockUnsafe.copyMemory( - null, pages[0].startAddress, - any(), byteArrayBaseOffset + 0, - 4_096.toLong() - ) - } - verify(exactly = 1) { - mockUnsafe.copyMemory( - null, pages[1].startAddress, - any(), byteArrayBaseOffset + 4_096, - 4_096.toLong() - ) - } - verify(exactly = 1) { - mockUnsafe.copyMemory( - null, pages[2].startAddress, - any(), byteArrayBaseOffset + 8_192, - 4_096.toLong() - ) - } - } - clearAllMocks() - } - Scenario("test copyToOnHeapMemoryBuffer") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - lateinit var onHeapMemoryBuffer: OnHeapMemoryBuffer - val byteArrayBaseOffset = 16L - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - every { - UnsafeContainer.BYTE_ARRAY_BASE_OFFSET - } returns byteArrayBaseOffset - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 10_000, - freed = false, - pages = pages, - ) - - val array = ByteArray(10_000) - - onHeapMemoryBuffer = mockk() - every { - onHeapMemoryBuffer.array - } returns array - every { - onHeapMemoryBuffer.setReadableBytes(10_000) - } returns Unit - - every { - mockUnsafe.copyMemory( - null, pages[0].startAddress, - any(), byteArrayBaseOffset + 0, - 4_096.toLong() - ) - } returns Unit - - every { - mockUnsafe.copyMemory( - null, pages[1].startAddress, - any(), byteArrayBaseOffset + 4_096, - 4_096.toLong() - ) - } returns Unit - - every { - mockUnsafe.copyMemory( - null, pages[2].startAddress, - any(), byteArrayBaseOffset + 8_192, - 1_808.toLong() - ) - } returns Unit - } - When("copyToOnHeapMemoryBuffer") { - nativeMemoryBufferImpl.copyToOnHeapMemoryBuffer(onHeapMemoryBuffer = onHeapMemoryBuffer) - } - Then("calls are as expected") { - verify(exactly = 1) { - mockUnsafe.copyMemory( - null, pages[0].startAddress, - any(), byteArrayBaseOffset + 0, - 4_096.toLong() - ) - } - verify(exactly = 1) { - mockUnsafe.copyMemory( - null, pages[1].startAddress, - any(), byteArrayBaseOffset + 4_096, - 4_096.toLong() - ) - } - verify(exactly = 1) { - mockUnsafe.copyMemory( - null, pages[2].startAddress, - any(), byteArrayBaseOffset + 8_192, - 1_808.toLong() - ) - } - verify(exactly = 1) { - onHeapMemoryBuffer.setReadableBytes(10_000) - } - } - clearAllMocks() - } - Scenario("test copyFromArray") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - lateinit var array: ByteArray - val byteArrayBaseOffset = 16L - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - every { - UnsafeContainer.BYTE_ARRAY_BASE_OFFSET - } returns byteArrayBaseOffset - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 8_193, - freed = false, - pages = pages, - ) - - array = ByteArray(8_193) - - every { - mockUnsafe.copyMemory( - array, byteArrayBaseOffset + 0, - null, pages[0].startAddress, - 4_096.toLong() - ) - } returns Unit - - every { - mockUnsafe.copyMemory( - array, byteArrayBaseOffset + 4_096, - null, pages[1].startAddress, - 4_096.toLong() - ) - } returns Unit - - every { - mockUnsafe.copyMemory( - array, byteArrayBaseOffset + 8_192, - null, pages[2].startAddress, - 1.toLong() - ) - } returns Unit - } - When("copyFromArray") { - nativeMemoryBufferImpl.copyFromArray(array) - } - Then("calls are as expected") { - verify(exactly = 1) { - mockUnsafe.copyMemory( - array, byteArrayBaseOffset + 0, - null, pages[0].startAddress, - 4_096.toLong() - ) - } - verify(exactly = 1) { - mockUnsafe.copyMemory( - array, byteArrayBaseOffset + 4_096, - null, pages[1].startAddress, - 4_096.toLong() - ) - } - verify(exactly = 1) { - mockUnsafe.copyMemory( - array, byteArrayBaseOffset + 8_192, - null, pages[2].startAddress, - 1.toLong() - ) - } - } - clearAllMocks() - } - Scenario("test copyFromArray array too large") { - lateinit var mockUnsafe: Unsafe - lateinit var nativeMemoryBufferImpl: NativeMemoryBufferImpl - lateinit var pages: ArrayList - lateinit var array: ByteArray - val byteArrayBaseOffset = 16L - var exceptionsThrown = 0 - - Given("setup nativeMemoryBufferImpl") { - mockUnsafe = mockk() - mockkObject(UnsafeContainer) - every { - UnsafeContainer.unsafe - } returns mockUnsafe - every { - UnsafeContainer.BYTE_ARRAY_BASE_OFFSET - } returns byteArrayBaseOffset - - pages = ArrayList() - pages.add(NativeMemoryPage(2L * 4_096)) - pages.add(NativeMemoryPage(1L * 4_096)) - pages.add(NativeMemoryPage(0L * 4_096)) - - nativeMemoryBufferImpl = NativeMemoryBufferImpl( - pageSizeBytes = 4_096, - capacityBytes = 8_193, - freed = false, - pages = pages, - ) - - array = ByteArray(8_194) - } - When("copyFromArray") { - try { - nativeMemoryBufferImpl.copyFromArray(array) - } catch (e: IllegalStateException) { - exceptionsThrown += 1 - } - } - Then("calls are as expected") { - assertEquals(1, exceptionsThrown) - - verify(exactly = 0) { - mockUnsafe.copyMemory( - any(), any(), - any(), any(), - any(), - ) - } - } - clearAllMocks() - } - } -}) \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/buffer/impl/OnHeapMemoryBufferImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/buffer/impl/OnHeapMemoryBufferImplSpec.kt deleted file mode 100644 index 480337c..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/buffer/impl/OnHeapMemoryBufferImplSpec.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.target.nativememoryallocator.buffer.impl - -import io.mockk.clearAllMocks -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature -import java.util.concurrent.ThreadLocalRandom - -class OnHeapMemoryBufferImplSpec : Spek({ - Feature("OnHeapMemoryBufferImpl") { - Scenario("test initial capacity set to 2 in constructor") { - lateinit var onHeapMemoryBufferImpl: OnHeapMemoryBufferImpl - - When("OnHeapMemoryBufferImpl constructor with initialCapacityBytes = 0") { - onHeapMemoryBufferImpl = OnHeapMemoryBufferImpl( - initialCapacityBytes = 0, - ) - } - Then("array size is as expected") { - assertEquals(2, onHeapMemoryBufferImpl.array.size) - assertEquals(0, onHeapMemoryBufferImpl.getReadableBytes()) - } - clearAllMocks() - } - Scenario("test setReadableBytes") { - lateinit var onHeapMemoryBufferImpl: OnHeapMemoryBufferImpl - - Given("") { - onHeapMemoryBufferImpl = OnHeapMemoryBufferImpl( - initialCapacityBytes = 0, - ) - } - When("setReadableBytes") { - onHeapMemoryBufferImpl.setReadableBytes(1023 * 1024) - } - Then("value is as expected") { - assertEquals(1024 * 1024, onHeapMemoryBufferImpl.array.size) - assertEquals(1023 * 1024, onHeapMemoryBufferImpl.getReadableBytes()) - } - clearAllMocks() - } - Scenario("test asByteBuffer") { - lateinit var onHeapMemoryBufferImpl: OnHeapMemoryBufferImpl - - Given("") { - onHeapMemoryBufferImpl = OnHeapMemoryBufferImpl( - initialCapacityBytes = 0, - ) - } - When("setReadableBytes") { - onHeapMemoryBufferImpl.setReadableBytes((1024 * 1024) - 1) - - ThreadLocalRandom.current().nextBytes(onHeapMemoryBufferImpl.array) - } - Then("value is as expected") { - assertEquals(1024 * 1024, onHeapMemoryBufferImpl.array.size) - assertEquals((1024 * 1024) - 1, onHeapMemoryBufferImpl.getReadableBytes()) - - val byteBuffer = onHeapMemoryBufferImpl.asByteBuffer() - - assertEquals(1024 * 1024, byteBuffer.capacity()) - assertEquals((1024 * 1024) - 1, byteBuffer.limit()) - assertEquals(0, byteBuffer.arrayOffset()) - assertTrue(onHeapMemoryBufferImpl.array.sliceArray(0 until ((1024 * 1024) - 1)) - .contentEquals(onHeapMemoryBufferImpl.asByteBuffer().array() - .sliceArray(0 until byteBuffer.limit()))) - } - clearAllMocks() - } - } -}) \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImplSpec.kt deleted file mode 100644 index a50c8d0..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/map/impl/CaffeineNativeMemoryMapImplSpec.kt +++ /dev/null @@ -1,151 +0,0 @@ -package com.target.nativememoryallocator.map.impl - -import com.github.benmanes.caffeine.cache.RemovalCause -import com.target.nativememoryallocator.allocator.NativeMemoryAllocator -import com.target.nativememoryallocator.buffer.NativeMemoryBuffer -import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature - -class CaffeineNativeMemoryMapImplSpec : Spek({ - Feature("CaffeineNativeMemoryMapImpl") { - Scenario("test CaffeineEvictionListener with RemovalCause.EXPLICIT") { - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var key: String - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var caffeineEvictionListener: CaffeineEvictionListener - - Given("setup test variables") { - key = "test" - nativeMemoryAllocator = mockk() - nativeMemoryBuffer = mockk() - - caffeineEvictionListener = CaffeineEvictionListener( - nativeMemoryAllocator = nativeMemoryAllocator, - ) - } - When("call CaffeineEvictionListener.onRemoval with RemovalCause.EXPLICIT") { - caffeineEvictionListener.onRemoval( - key = key, - value = nativeMemoryBuffer, - cause = RemovalCause.EXPLICIT, - ) - } - Then("calls are correct") { - verify(exactly = 0) { - nativeMemoryBuffer.freed - } - verify(exactly = 0) { - nativeMemoryAllocator.freeNativeMemoryBuffer(any()) - } - } - clearAllMocks() - } - Scenario("test CaffeineEvictionListener with RemovalCause.EXPIRED") { - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var key: String - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var caffeineEvictionListener: CaffeineEvictionListener - - Given("setup test variables") { - key = "test" - nativeMemoryAllocator = mockk() - nativeMemoryBuffer = mockk() - - caffeineEvictionListener = CaffeineEvictionListener( - nativeMemoryAllocator = nativeMemoryAllocator, - ) - - every { - nativeMemoryBuffer.freed - } returns false - } - When("call CaffeineEvictionListener.onRemoval with RemovalCause.EXPIRED") { - caffeineEvictionListener.onRemoval( - key = key, - value = nativeMemoryBuffer, - cause = RemovalCause.EXPIRED, - ) - } - Then("calls are correct") { - verify(exactly = 1) { - nativeMemoryBuffer.freed - } - verify(exactly = 1) { - nativeMemoryAllocator.freeNativeMemoryBuffer( - buffer = nativeMemoryBuffer, - ) - } - } - clearAllMocks() - } - Scenario("test CaffeineEvictionListener with RemovalCause.EXPIRED, buffer is already freed") { - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var key: String - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var caffeineEvictionListener: CaffeineEvictionListener - - Given("setup test variables") { - key = "test" - nativeMemoryAllocator = mockk() - nativeMemoryBuffer = mockk() - - caffeineEvictionListener = CaffeineEvictionListener( - nativeMemoryAllocator = nativeMemoryAllocator, - ) - - every { - nativeMemoryBuffer.freed - } returns true - } - When("call CaffeineEvictionListener.onRemoval with RemovalCause.EXPIRED") { - caffeineEvictionListener.onRemoval( - key = key, - value = nativeMemoryBuffer, - cause = RemovalCause.EXPIRED, - ) - } - Then("calls are correct") { - verify(exactly = 1) { - nativeMemoryBuffer.freed - } - verify(exactly = 0) { - nativeMemoryAllocator.freeNativeMemoryBuffer( - buffer = any(), - ) - } - } - clearAllMocks() - } - Scenario("test CaffeineEvictionListener with null parameters") { - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var caffeineEvictionListener: CaffeineEvictionListener - - Given("setup test variables") { - nativeMemoryAllocator = mockk() - - caffeineEvictionListener = CaffeineEvictionListener( - nativeMemoryAllocator = nativeMemoryAllocator, - ) - } - When("call CaffeineEvictionListener.onRemoval with null parameters") { - caffeineEvictionListener.onRemoval( - key = null, - value = null, - cause = null, - ) - } - Then("calls are correct") { - verify(exactly = 0) { - nativeMemoryAllocator.freeNativeMemoryBuffer( - buffer = any(), - ) - } - } - clearAllMocks() - } - } -}) \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/map/impl/NativeMemoryMapImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/map/impl/NativeMemoryMapImplSpec.kt deleted file mode 100644 index 4f27424..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/map/impl/NativeMemoryMapImplSpec.kt +++ /dev/null @@ -1,520 +0,0 @@ -package com.target.nativememoryallocator.map.impl - -import com.target.nativememoryallocator.allocator.NativeMemoryAllocator -import com.target.nativememoryallocator.buffer.NativeMemoryBuffer -import com.target.nativememoryallocator.buffer.OnHeapMemoryBuffer -import com.target.nativememoryallocator.buffer.OnHeapMemoryBufferFactory -import com.target.nativememoryallocator.map.NativeMemoryMap -import com.target.nativememoryallocator.map.NativeMemoryMapSerializer -import io.mockk.* -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ThreadLocalRandom - -class NativeMemoryMapImplSpec : Spek({ - class TestValueObject - - Feature("NativeMemoryMapImpl") { - Scenario("test initialization") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var nativeMemoryMap: NativeMemoryMapImpl - - When("construct NativeMemoryMapImpl") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = true, - threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), - cacheMap = ConcurrentHashMap(), - ) - } - Then("initial state is correct") { - assertTrue(nativeMemoryMap.entries.isEmpty()) - assertTrue(nativeMemoryMap.keys.isEmpty()) - assertEquals(0, nativeMemoryMap.size) - } - clearAllMocks() - } - Scenario("test put of null value") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var nativeMemoryMap: NativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - - When("test single put") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = true, - threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), - cacheMap = ConcurrentHashMap(), - ) - - putResult = nativeMemoryMap.put(key = 1, value = null) - } - Then("state is correct") { - assertEquals(NativeMemoryMap.PutResult.NO_CHANGE, putResult) - assertTrue(nativeMemoryMap.entries.isEmpty()) - assertTrue(nativeMemoryMap.keys.isEmpty()) - assertEquals(0, nativeMemoryMap.size) - } - clearAllMocks() - } - Scenario("test put") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var putValue: TestValueObject - val serializedValue = ByteArray(10) - ThreadLocalRandom.current().nextBytes(serializedValue) - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var nativeMemoryMap: NativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - - When("test single put") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - putValue = mockk() - nativeMemoryBuffer = mockk() - - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = true, - threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), - cacheMap = ConcurrentHashMap(), - ) - - every { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } returns serializedValue - - every { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } returns nativeMemoryBuffer - - every { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } returns Unit - - putResult = nativeMemoryMap.put(key = 1, value = putValue) - } - Then("state is correct") { - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult) - assertEquals( - setOf( - AbstractMap.SimpleEntry( - 1, - nativeMemoryBuffer - ) - ), - nativeMemoryMap.entries - ) - assertEquals(setOf(1), nativeMemoryMap.keys) - assertEquals(1, nativeMemoryMap.size) - - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } - verify(exactly = 1) { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } - } - clearAllMocks() - } - Scenario("test put then get useThreadLocalOnHeapReadBuffer = true") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var putValue: TestValueObject - val serializedValue = ByteArray(10) - ThreadLocalRandom.current().nextBytes(serializedValue) - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var threadLocalReadBuffer: OnHeapMemoryBuffer - lateinit var getDeserializedValue: TestValueObject - lateinit var nativeMemoryMap: NativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - var getResult: TestValueObject? = null - - When("test single put then get") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - putValue = mockk() - nativeMemoryBuffer = mockk() - threadLocalReadBuffer = mockk() - getDeserializedValue = mockk() - - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = true, - threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), - cacheMap = ConcurrentHashMap(), - ) - - every { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } returns serializedValue - - every { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } returns nativeMemoryBuffer - - every { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } returns Unit - - every { - nativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) - } returns Unit - - every { - testValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) - } returns getDeserializedValue - - putResult = nativeMemoryMap.put(key = 1, value = putValue) - - nativeMemoryMap.threadLocalOnHeapReadBuffer!!.set(threadLocalReadBuffer) - - getResult = nativeMemoryMap.get(key = 1) - } - Then("state is correct") { - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult) - assertEquals( - setOf( - AbstractMap.SimpleEntry( - 1, - nativeMemoryBuffer - ) - ), - nativeMemoryMap.entries - ) - assertEquals(setOf(1), nativeMemoryMap.keys) - assertEquals(1, nativeMemoryMap.size) - assertEquals(getDeserializedValue, getResult) - - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } - verify(exactly = 1) { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) - } - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) - } - } - clearAllMocks() - } - Scenario("test put then get useThreadLocalOnHeapReadBuffer = false") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var putValue: TestValueObject - val serializedValue = ByteArray(10) - ThreadLocalRandom.current().nextBytes(serializedValue) - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var getDeserializedValue: TestValueObject - lateinit var onHeapMemoryBuffer: OnHeapMemoryBuffer - lateinit var nativeMemoryMap: NativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - var getResult: TestValueObject? = null - - When("test single put then get") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - putValue = mockk() - nativeMemoryBuffer = mockk() - getDeserializedValue = mockk() - onHeapMemoryBuffer = mockk() - - mockkObject(OnHeapMemoryBufferFactory) - - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = false, - threadLocalOnHeapReadBufferInitialCapacityBytes = 0, - cacheMap = ConcurrentHashMap(), - ) - - every { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } returns serializedValue - - every { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } returns nativeMemoryBuffer - - every { - nativeMemoryBuffer.capacityBytes - } returns 10 - - every { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } returns Unit - - every { - OnHeapMemoryBufferFactory.newOnHeapMemoryBuffer(initialCapacityBytes = 10) - } returns onHeapMemoryBuffer - - every { - nativeMemoryBuffer.copyToOnHeapMemoryBuffer(onHeapMemoryBuffer) - } returns Unit - - every { - testValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = onHeapMemoryBuffer) - } returns getDeserializedValue - - putResult = nativeMemoryMap.put(key = 1, value = putValue) - - getResult = nativeMemoryMap.get(key = 1) - } - Then("state is correct") { - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult) - assertEquals( - setOf( - AbstractMap.SimpleEntry( - 1, - nativeMemoryBuffer - ) - ), - nativeMemoryMap.entries - ) - assertEquals(setOf(1), nativeMemoryMap.keys) - assertEquals(1, nativeMemoryMap.size) - assertEquals(getDeserializedValue, getResult) - - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } - verify(exactly = 1) { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } - verify(exactly = 1) { - nativeMemoryBuffer.capacityBytes - } - verify(exactly = 1) { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } - verify(exactly = 1) { - OnHeapMemoryBufferFactory.newOnHeapMemoryBuffer(initialCapacityBytes = 10) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyToOnHeapMemoryBuffer(onHeapMemoryBuffer) - } - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = onHeapMemoryBuffer) - } - } - clearAllMocks() - } - Scenario("test put reuse buffer") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var putValue1: TestValueObject - lateinit var putValue2: TestValueObject - val serializedValue1 = ByteArray(10) - ThreadLocalRandom.current().nextBytes(serializedValue1) - val serializedValue2 = ByteArray(20) - ThreadLocalRandom.current().nextBytes(serializedValue2) - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var getDeserializedValue: TestValueObject - lateinit var nativeMemoryMap: NativeMemoryMapImpl - lateinit var putResult1: NativeMemoryMap.PutResult - lateinit var putResult2: NativeMemoryMap.PutResult - lateinit var threadLocalReadBuffer: OnHeapMemoryBuffer - var getResult: TestValueObject? = null - - When("test 2 puts for same key") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - putValue1 = mockk() - nativeMemoryBuffer = mockk() - getDeserializedValue = mockk() - putValue2 = mockk() - threadLocalReadBuffer = mockk() - - mockkObject(OnHeapMemoryBufferFactory) - - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = true, - threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), - cacheMap = ConcurrentHashMap(), - ) - - every { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue1) - } returns serializedValue1 - - every { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue2) - } returns serializedValue2 - - every { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } returns nativeMemoryBuffer - - every { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = nativeMemoryBuffer, - newCapacityBytes = 20 - ) - } returns Unit - - every { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue1) - } returns Unit - - every { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue2) - } returns Unit - - nativeMemoryMap.threadLocalOnHeapReadBuffer!!.set(threadLocalReadBuffer) - - every { - nativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) - } returns Unit - - every { - testValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) - } returns getDeserializedValue - - putResult1 = nativeMemoryMap.put(key = 1, value = putValue1) - - putResult2 = nativeMemoryMap.put(key = 1, value = putValue2) - - getResult = nativeMemoryMap.get(key = 1) - } - Then("state is correct") { - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult1) - assertEquals(NativeMemoryMap.PutResult.REUSED_EXISTING_BUFFER, putResult2) - assertEquals( - setOf( - AbstractMap.SimpleEntry( - 1, - nativeMemoryBuffer - ) - ), - nativeMemoryMap.entries - ) - assertEquals(setOf(1), nativeMemoryMap.keys) - assertEquals(1, nativeMemoryMap.size) - assertEquals(getDeserializedValue, getResult) - - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue1) - } - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue2) - } - verify(exactly = 1) { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } - verify(exactly = 1) { - nativeMemoryAllocator.resizeNativeMemoryBuffer( - buffer = nativeMemoryBuffer, - newCapacityBytes = 20 - ) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue1) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue2) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyToOnHeapMemoryBuffer(threadLocalReadBuffer) - } - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.deserializeFromOnHeapMemoryBuffer(onHeapMemoryBuffer = threadLocalReadBuffer) - } - } - clearAllMocks() - } - Scenario("test put then delete") { - lateinit var testValueObjectNativeMemoryMapSerializer: NativeMemoryMapSerializer - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - lateinit var putValue: TestValueObject - val serializedValue = ByteArray(10) - ThreadLocalRandom.current().nextBytes(serializedValue) - lateinit var nativeMemoryBuffer: NativeMemoryBuffer - lateinit var nativeMemoryMap: NativeMemoryMapImpl - lateinit var putResult1: NativeMemoryMap.PutResult - lateinit var putResult2: NativeMemoryMap.PutResult - - When("test put value, then put null") { - testValueObjectNativeMemoryMapSerializer = mockk() - nativeMemoryAllocator = mockk() - putValue = mockk() - nativeMemoryBuffer = mockk() - - nativeMemoryMap = NativeMemoryMapImpl( - valueSerializer = testValueObjectNativeMemoryMapSerializer, - nativeMemoryAllocator = nativeMemoryAllocator, - useThreadLocalOnHeapReadBuffer = true, - threadLocalOnHeapReadBufferInitialCapacityBytes = (256 * 1024), - cacheMap = ConcurrentHashMap(), - ) - - every { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } returns serializedValue - - every { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } returns nativeMemoryBuffer - - every { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } returns Unit - - every { - nativeMemoryAllocator.freeNativeMemoryBuffer(buffer = nativeMemoryBuffer) - } returns Unit - - putResult1 = nativeMemoryMap.put(key = 1, value = putValue) - - putResult2 = nativeMemoryMap.put(key = 1, value = null) - } - Then("state is correct") { - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult1) - assertEquals(NativeMemoryMap.PutResult.FREED_CURRENT_BUFFER, putResult2) - assertTrue(nativeMemoryMap.entries.isEmpty()) - assertTrue(nativeMemoryMap.keys.isEmpty()) - assertEquals(0, nativeMemoryMap.size) - - verify(exactly = 1) { - testValueObjectNativeMemoryMapSerializer.serializeToByteArray(value = putValue) - } - verify(exactly = 1) { - nativeMemoryAllocator.allocateNativeMemoryBuffer(capacityBytes = 10) - } - verify(exactly = 1) { - nativeMemoryBuffer.copyFromArray(byteArray = serializedValue) - } - verify(exactly = 1) { - nativeMemoryAllocator.freeNativeMemoryBuffer(buffer = nativeMemoryBuffer) - } - } - clearAllMocks() - } - } -}) \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/map/impl/OperationCountedNativeMemoryMapImplSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/map/impl/OperationCountedNativeMemoryMapImplSpec.kt deleted file mode 100644 index cfa0257..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/map/impl/OperationCountedNativeMemoryMapImplSpec.kt +++ /dev/null @@ -1,343 +0,0 @@ -package com.target.nativememoryallocator.map.impl - -import com.target.nativememoryallocator.map.NativeMemoryMap -import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import org.junit.jupiter.api.Assertions.* -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature -import java.util.concurrent.atomic.AtomicLong - -class OperationCountedNativeMemoryMapImplSpec : Spek({ - class TestValueObject - - Feature("NativeMemoryMapWithOperationCountersImpl") { - Scenario("test OperationCountersImpl.counterValuesEqual") { - var retVal: Boolean = false - - Given("reset retVal") { - retVal = false - } - When("compare all zero OperationCountersImpl instances") { - val operationCountersImpl1 = OperationCountersImpl() - - val operationCountersImpl2 = OperationCountersImpl() - - retVal = operationCountersImpl1.counterValuesEqual(operationCountersImpl2) - } - Then("retVal is true") { - assertTrue(retVal) - } - Given("reset retVal") { - retVal = false - } - When("compare non equal values") { - val operationCountersImpl1 = OperationCountersImpl() - operationCountersImpl1.numPutsNoChange.set(1) - - val operationCountersImpl2 = OperationCountersImpl() - - retVal = operationCountersImpl1.counterValuesEqual(operationCountersImpl2) - } - Then("retVal is false") { - assertFalse(retVal) - } - Given("reset retVal") { - retVal = false - } - When("compare non equal values") { - val operationCountersImpl1 = OperationCountersImpl() - - val operationCountersImpl2 = OperationCountersImpl() - operationCountersImpl2.numGetsNonNullValue.set(1) - - retVal = operationCountersImpl1.counterValuesEqual(operationCountersImpl2) - } - Then("retVal is false") { - assertFalse(retVal) - } - } - Scenario("test put no change") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - } - When("put operation returns NO_CHANGE") { - every { - nativeMemoryMap.put(key = 1, value = null) - } returns NativeMemoryMap.PutResult.NO_CHANGE - - putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = null) - } - Then("operationCounters state is correct") { - assertEquals(NativeMemoryMap.PutResult.NO_CHANGE, putResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numPutsNoChange = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.put(key = 1, value = null) } - } - clearAllMocks() - } - Scenario("test put freed buffer") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - } - When("put operation returns freed buffer") { - every { - nativeMemoryMap.put(key = 1, value = null) - } returns NativeMemoryMap.PutResult.FREED_CURRENT_BUFFER - - putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = null) - } - Then("operationCounters state is correct") { - assertEquals(NativeMemoryMap.PutResult.FREED_CURRENT_BUFFER, putResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numPutsFreedBuffer = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.put(key = 1, value = null) } - } - clearAllMocks() - } - Scenario("test put allocated new buffer") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - lateinit var testValueObject: TestValueObject - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - - testValueObject = mockk() - } - When("put operation returns allocated new buffer") { - every { - nativeMemoryMap.put(key = 1, value = testValueObject) - } returns NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER - - putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = testValueObject) - } - Then("operationCounters state is correct") { - assertEquals(NativeMemoryMap.PutResult.ALLOCATED_NEW_BUFFER, putResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numPutsNewBuffer = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.put(key = 1, value = testValueObject) } - } - clearAllMocks() - } - Scenario("test put reused buffer") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - lateinit var putResult: NativeMemoryMap.PutResult - lateinit var testValueObject: TestValueObject - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - - testValueObject = mockk() - } - When("put operation returns reused buffer") { - every { - nativeMemoryMap.put(key = 1, value = testValueObject) - } returns NativeMemoryMap.PutResult.REUSED_EXISTING_BUFFER - - putResult = operationCountedNativeMemoryMapImpl.put(key = 1, value = testValueObject) - } - Then("operationCounters state is correct") { - assertEquals(NativeMemoryMap.PutResult.REUSED_EXISTING_BUFFER, putResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numPutsReusedBuffer = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.put(key = 1, value = testValueObject) } - } - clearAllMocks() - } - Scenario("test get returning null") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - var getResult: TestValueObject? = null - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - } - When("get operation returns null") { - every { - nativeMemoryMap.get(key = 1) - } returns null - - getResult = operationCountedNativeMemoryMapImpl.get(key = 1) - } - Then("operationCounters state is correct") { - assertNull(getResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numGetsNullValue = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.get(key = 1) } - } - clearAllMocks() - } - Scenario("test get returning non-null") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - lateinit var mockResult: TestValueObject - var getResult: TestValueObject? = null - - Given("setup variables") { - nativeMemoryMap = mockk() - mockResult = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - } - When("get operation returns null") { - every { - nativeMemoryMap.get(key = 1) - } returns mockResult - - getResult = operationCountedNativeMemoryMapImpl.get(key = 1) - } - Then("operationCounters state is correct") { - assertEquals(mockResult, getResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numGetsNonNullValue = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.get(key = 1) } - } - clearAllMocks() - } - Scenario("test delete freed buffer") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - var deleteResult: Boolean? = null - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - } - When("delete operation returns true") { - every { - nativeMemoryMap.delete(key = 1) - } returns true - - deleteResult = operationCountedNativeMemoryMapImpl.delete(key = 1) - } - Then("operationCounters state is correct") { - assertEquals(true, deleteResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numDeletesFreedBuffer = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.delete(key = 1) } - } - clearAllMocks() - } - Scenario("test delete no change") { - lateinit var nativeMemoryMap: NativeMemoryMap - lateinit var operationCountedNativeMemoryMapImpl: OperationCountedNativeMemoryMapImpl - var deleteResult: Boolean? = null - - Given("setup variables") { - nativeMemoryMap = mockk() - - operationCountedNativeMemoryMapImpl = OperationCountedNativeMemoryMapImpl( - nativeMemoryMap = nativeMemoryMap, - ) - } - When("delete operation returns true") { - every { - nativeMemoryMap.delete(key = 1) - } returns false - - deleteResult = operationCountedNativeMemoryMapImpl.delete(key = 1) - } - Then("operationCounters state is correct") { - assertEquals(false, deleteResult) - - assertTrue( - operationCountedNativeMemoryMapImpl.operationCounters.counterValuesEqual( - OperationCountersImpl( - numDeletesNoChange = AtomicLong(1), - ) - ) - ) - - verify(exactly = 1) { nativeMemoryMap.delete(key = 1) } - } - clearAllMocks() - } - } -}) \ No newline at end of file diff --git a/src/test/kotlin/com/target/nativememoryallocator/metrics/micrometer/MicrometerMetricsSpec.kt b/src/test/kotlin/com/target/nativememoryallocator/metrics/micrometer/MicrometerMetricsSpec.kt deleted file mode 100644 index 264d3d2..0000000 --- a/src/test/kotlin/com/target/nativememoryallocator/metrics/micrometer/MicrometerMetricsSpec.kt +++ /dev/null @@ -1,229 +0,0 @@ -package com.target.nativememoryallocator.metrics.micrometer - -import com.github.benmanes.caffeine.cache.stats.CacheStats -import com.target.nativememoryallocator.allocator.NativeMemoryAllocator -import com.target.nativememoryallocator.map.BaseNativeMemoryMap -import com.target.nativememoryallocator.map.NativeMemoryMapStats -import com.target.nativememoryallocator.map.impl.OperationCountersImpl -import io.micrometer.core.instrument.Tag -import io.micrometer.core.instrument.Tags -import io.micrometer.core.instrument.simple.SimpleMeterRegistry -import io.mockk.clearAllMocks -import io.mockk.every -import io.mockk.mockk -import mu.KotlinLogging -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.spekframework.spek2.Spek -import org.spekframework.spek2.style.gherkin.Feature - -private val logger = KotlinLogging.logger {} - -class MicrometerMetricsSpec : Spek({ - Feature("MicrometerMetrics") { - Scenario("test MicrometerNativeMemoryAllocatorMetrics") { - val meterRegistry = SimpleMeterRegistry() - lateinit var nativeMemoryAllocator: NativeMemoryAllocator - val tags = listOf(Tag.of("key1", "value1")) - val numFreePagesValue = 1 - val numUsedPagesValue = 2 - val totalNumPagesValue = 3 - val numAllocationExceptionsValue = 4 - val numFreeExceptionsValue = 5 - - val meterNameToValue = mapOf( - "nativeMemoryAllocator.numFreePages" to numFreePagesValue, - "nativeMemoryAllocator.numUsedPages" to numUsedPagesValue, - "nativeMemoryAllocator.totalNumPages" to totalNumPagesValue, - "nativeMemoryAllocator.numAllocationExceptions" to numAllocationExceptionsValue, - "nativeMemoryAllocator.numFreeExceptions" to numFreeExceptionsValue, - ) - - Given("setup variables") { - nativeMemoryAllocator = mockk() - } - When("construct MicrometerNativeMemoryAllocatorMetrics") { - every { - nativeMemoryAllocator.numFreePages - } returns numFreePagesValue - every { - nativeMemoryAllocator.numUsedPages - } returns numUsedPagesValue - every { - nativeMemoryAllocator.totalNumPages - } returns totalNumPagesValue - every { - nativeMemoryAllocator.numAllocationExceptions - } returns numAllocationExceptionsValue - every { - nativeMemoryAllocator.numFreeExceptions - } returns numFreeExceptionsValue - - MicrometerNativeMemoryAllocatorMetrics( - nativeMemoryAllocator = nativeMemoryAllocator, - meterRegistry = meterRegistry, - tags = Tags.of(tags), - ) - } - Then("MicrometerNativeMemoryAllocatorMetrics registers metrics with MeterRegistry") { - assertEquals(5, meterRegistry.meters.size) - - logger.info { "meterRegistry.meters = ${meterRegistry.meters}" } - val idToMeterMap = meterRegistry.meters.filterNotNull().associateBy { it.id } - - assertEquals(5, idToMeterMap.size) - val idAndMeterList = idToMeterMap.toList() - - meterNameToValue.forEach { (meterName, expectedValue) -> - val meterObject = - idAndMeterList.find { (it.first.name == meterName) && (it.first.tags == tags) }?.second - assertNotNull(meterObject) - - val measurement = meterObject?.measure() - assertNotNull(measurement) - - assertEquals(expectedValue.toDouble(), measurement?.take(1)?.get(0)?.value) - } - } - clearAllMocks() - } - Scenario("test MicrometerNativeMemoryMapMetrics no caffiene, no operation counters") { - val meterRegistry = SimpleMeterRegistry() - lateinit var nativeMemoryMap: BaseNativeMemoryMap - val tags = listOf(Tag.of("key1", "value1")) - val mapSizeValue = 42 - - val meterNameToValue = mapOf( - "nativeMemoryMap.size" to mapSizeValue, - ) - - Given("setup variables") { - nativeMemoryMap = mockk() - } - When("construct MicrometerNativeMemoryMapMetrics") { - every { - nativeMemoryMap.stats - } returns NativeMemoryMapStats( - caffeineStats = null, - ) - - every { - nativeMemoryMap.operationCounters - } returns null - - every { - nativeMemoryMap.size - } returns mapSizeValue - - MicrometerNativeMemoryMapMetrics( - nativeMemoryMap = nativeMemoryMap, - meterRegistry = meterRegistry, - tags = Tags.of(tags), - ) - } - Then("MicrometerNativeMemoryAllocatorMetrics registers metrics with MeterRegistry") { - assertEquals(1, meterRegistry.meters.size) - - logger.info { "meterRegistry.meters = ${meterRegistry.meters}" } - val idToMeterMap = meterRegistry.meters.filterNotNull().associateBy { it.id } - - assertEquals(1, idToMeterMap.size) - val idAndMeterList = idToMeterMap.toList() - - meterNameToValue.forEach { (meterName, expectedValue) -> - val meterObject = - idAndMeterList.find { (it.first.name == meterName) && (it.first.tags == tags) }?.second - assertNotNull(meterObject) - - val measurement = meterObject?.measure() - assertNotNull(measurement) - - assertEquals(expectedValue.toDouble(), measurement?.take(1)?.get(0)?.value) - } - } - clearAllMocks() - } - Scenario("test MicrometerNativeMemoryMapMetrics with caffiene, with operation counters") { - val meterRegistry = SimpleMeterRegistry() - lateinit var nativeMemoryMap: BaseNativeMemoryMap - lateinit var caffeineStats: CacheStats - val tags = listOf(Tag.of("key1", "value1")) - val mapSizeValue = 42 - val caffeineEvictionCountValue = 43 - - val operationCounters = OperationCountersImpl() - operationCounters.numPutsNoChange.set(44) - operationCounters.numPutsFreedBuffer.set(45) - operationCounters.numPutsReusedBuffer.set(46) - operationCounters.numPutsNewBuffer.set(47) - operationCounters.numDeletesFreedBuffer.set(48) - operationCounters.numDeletesNoChange.set(49) - operationCounters.numGetsNullValue.set(50) - operationCounters.numGetsNonNullValue.set(51) - - val meterNameToValue = mapOf( - "nativeMemoryMap.size" to mapSizeValue, - "nativeMemoryMap.caffeineEvictionCount" to caffeineEvictionCountValue, - "nativeMemoryMap.numPutsNoChange" to operationCounters.numPutsNoChange.get(), - "nativeMemoryMap.numPutsFreedBuffer" to operationCounters.numPutsFreedBuffer.get(), - "nativeMemoryMap.numPutsReusedBuffer" to operationCounters.numPutsReusedBuffer.get(), - "nativeMemoryMap.numPutsNewBuffer" to operationCounters.numPutsNewBuffer.get(), - "nativeMemoryMap.numDeletesFreedBuffer" to operationCounters.numDeletesFreedBuffer.get(), - "nativeMemoryMap.numDeletesNoChange" to operationCounters.numDeletesNoChange.get(), - "nativeMemoryMap.numGetsNullValue" to operationCounters.numGetsNullValue.get(), - "nativeMemoryMap.numGetsNonNullValue" to operationCounters.numGetsNonNullValue.get(), - ) - - Given("setup variables") { - nativeMemoryMap = mockk() - caffeineStats = mockk() - } - When("construct MicrometerNativeMemoryMapMetrics") { - every { - caffeineStats.evictionCount() - } returns caffeineEvictionCountValue.toLong() - - every { - nativeMemoryMap.stats - } returns NativeMemoryMapStats( - caffeineStats = caffeineStats, - ) - - every { - nativeMemoryMap.operationCounters - } returns operationCounters - - every { - nativeMemoryMap.size - } returns mapSizeValue - - MicrometerNativeMemoryMapMetrics( - nativeMemoryMap = nativeMemoryMap, - meterRegistry = meterRegistry, - tags = Tags.of(tags), - ) - } - Then("MicrometerNativeMemoryAllocatorMetrics registers metrics with MeterRegistry") { - assertEquals(10, meterRegistry.meters.size) - - logger.info { "meterRegistry.meters = ${meterRegistry.meters}" } - val idToMeterMap = meterRegistry.meters.filterNotNull().associateBy { it.id } - - assertEquals(10, idToMeterMap.size) - val idAndMeterList = idToMeterMap.toList() - - meterNameToValue.forEach { (meterName, expectedValue) -> - val meterObject = - idAndMeterList.find { (it.first.name == meterName) && (it.first.tags == tags) }?.second - assertNotNull(meterObject) - - val measurement = meterObject?.measure() - assertNotNull(measurement) - - assertEquals(expectedValue.toDouble(), measurement?.take(1)?.get(0)?.value) - } - } - clearAllMocks() - } - } -})