diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fe6f78a2..b6c1ff1ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,60 +3,26 @@ name: Build Lambda on: push: branches: - - '**' + - 'master' + pull_request: jobs: - check-runner: - name: Check Runner Availability - runs-on: ubuntu-latest - - outputs: - runner-label: ${{ steps.set-runner.outputs.runner-label }} - - steps: - - name: Set runner - id: set-runner - run: | - runners=$(curl -v -s -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.REPO_ACCESS_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/actions/runners" --http1.1) - if [ $? -ne 0 ]; then - echo "Error: Failed to fetch runners from GitHub API" >&2 - exit 1 - fi - - runners_count=$(echo "$runners" | jq '.runners | length') - if [ "$runners_count" -eq 0 ]; then - echo "No runners available or failed to retrieve runners." >&2 - echo "runner-label=ubuntu-latest" >> $GITHUB_OUTPUT - exit 0 - fi - - available=$(echo "$runners" | jq '.runners[] | select(.status == "online" and .busy == false and .labels[] .name == "self-hosted")') - if [ $? -ne 0 ]; then - echo "Error: Failed to parse JSON response" >&2 - exit 1 - fi - - if [ -n "$available" ]; then - echo "runner-label=lambda-linux-runner" >> $GITHUB_OUTPUT - else - echo "runner-label=ubuntu-latest" >> $GITHUB_OUTPUT - fi build: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - - needs: check-runner - runs-on: ${{ needs.check-runner.outputs.runner-label }} name: Build Lambda + runs-on: ubuntu-latest + permissions: contents: write + env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' steps: - name: Checkout Repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4 - name: Set current date as env variable run: echo "DATE=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV @@ -65,11 +31,11 @@ jobs: id: vars run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: Set-Up JDK 17 + - name: Set-Up JDK uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '17' + java-version: '21' architecture: x64 cache: 'gradle' @@ -81,7 +47,7 @@ jobs: all: true - name: Build Lambda - run: ./gradlew build + run: ./gradlew build --no-daemon - name: Rename Files with Commit Hash run: | @@ -111,8 +77,6 @@ jobs: ### [Lambda Forge ${{ steps.all.outputs.modVersion }} ${{ steps.all.outputs.minecraftVersion }} (${{ env.COMMIT_HASH }})](https://r2-bucket.edouard127.christmas/${{ env.DATE }}-${{ env.COMMIT_HASH }}/lambda-forge-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar) #### [API (Developer Dependency)](https://r2-bucket.edouard127.christmas/${{ env.DATE }}-${{ env.COMMIT_HASH }}/lambda-api-${{ steps.all.outputs.modVersion }}+${{ steps.all.outputs.minecraftVersion }}-${{ env.COMMIT_HASH }}.jar) - - **Runner:** \`${{ needs.check-runner.outputs.runner-label }}\` EOF - name: Failover Upload diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 08af8e3d7..49696e87b 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -23,6 +23,9 @@ val kotlinxCoroutinesVersion: String by project val discordIPCVersion: String by project val fuelVersion: String by project val resultVersion: String by project +val mockitoKotlin: String by project +val mockitoInline: String by project +val mockkVersion: String by project base.archivesName = "${base.archivesName.get()}-api" @@ -57,6 +60,10 @@ dependencies { // Baritone modImplementation("baritone-api:baritone-unoptimized-fabric:1.10.2") { isTransitive = false } + testImplementation(kotlin("test")) + testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlin") + testImplementation("org.mockito:mockito-inline:$mockitoInline") + testImplementation("io.mockk:mockk:${mockkVersion}") } tasks { @@ -66,5 +73,18 @@ tasks { test { useJUnitPlatform() + jvmArgs("-XX:+EnableDynamicAgentLoading", "-Xshare:off") + } +} + +tasks.withType { + kotlinOptions { + jvmTarget = "17" + } +} + +subprojects { + tasks.named("build") { + dependsOn("test") } } diff --git a/common/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt b/common/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt index 04aa8e78a..5508b4264 100644 --- a/common/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt +++ b/common/src/main/kotlin/com/lambda/module/modules/network/PacketLimiter.kt @@ -35,7 +35,7 @@ object PacketLimiter : Module( ) { private var packetQueue = LimitedDecayQueue(99, 1000) private val limit by setting("Limit", 99, 1..100, 1, "The maximum amount of packets to send per given time interval", unit = " packets") - .onValueChange { _, to -> packetQueue.setMaxSize(to) } + .onValueChange { _, to -> packetQueue.setSizeLimit(to) } private val interval by setting("Duration", 1000L, 1L..1000L, 50L, "The interval / duration in milliseconds to limit packets for", unit = " ms") .onValueChange { _, to -> packetQueue.setDecayTime(to) } diff --git a/common/src/main/kotlin/com/lambda/task/Task.kt b/common/src/main/kotlin/com/lambda/task/Task.kt index 5da60e19c..ba5431388 100644 --- a/common/src/main/kotlin/com/lambda/task/Task.kt +++ b/common/src/main/kotlin/com/lambda/task/Task.kt @@ -37,9 +37,9 @@ typealias TaskGeneratorOrNull = SafeContext.(R) -> Task<*>? typealias TaskGeneratorUnit = SafeContext.(R) -> Unit abstract class Task : Nameable, Muteable { - private var parent: Task<*>? = null - private val subTasks = mutableListOf>() - private var state = State.INIT + var parent: Task<*>? = null + val subTasks = mutableListOf>() + var state = State.INIT override val isMuted: Boolean get() = state == State.PAUSED || state == State.INIT var age = 0 private val depth: Int get() = parent?.depth?.plus(1) ?: 0 diff --git a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt index 9c222b169..ca363f441 100644 --- a/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt +++ b/common/src/main/kotlin/com/lambda/task/tasks/OpenContainer.kt @@ -39,10 +39,10 @@ class OpenContainer @Ta5kBuilder constructor( private val interact: InteractionConfig = TaskFlowModule.interact, private val sides: Set = Direction.entries.toSet(), ) : Task() { - override val name get() = "${state.description(inScope)} at ${blockPos.toShortString()}" + override val name get() = "${containerState.description(inScope)} at ${blockPos.toShortString()}" private var screenHandler: ScreenHandler? = null - private var state = State.SCOPING + private var containerState = State.SCOPING private var inScope = 0 enum class State { @@ -57,10 +57,10 @@ class OpenContainer @Ta5kBuilder constructor( init { listen { - if (state != State.OPENING) return@listen + if (containerState != State.OPENING) return@listen screenHandler = it.screenHandler - state = State.SLOT_LOADING + containerState = State.SLOT_LOADING if (!waitForSlotLoad) success(it.screenHandler) } @@ -68,12 +68,12 @@ class OpenContainer @Ta5kBuilder constructor( listen { if (screenHandler != it.screenHandler) return@listen - state = State.SCOPING + containerState = State.SCOPING screenHandler = null } listen { - if (state != State.SLOT_LOADING) return@listen + if (containerState != State.SLOT_LOADING) return@listen screenHandler?.let { success(it) @@ -81,7 +81,7 @@ class OpenContainer @Ta5kBuilder constructor( } listen { - if (state != State.SCOPING) return@listen + if (containerState != State.SCOPING) return@listen val target = lookAtBlock(blockPos, sides, config = interact) if (rotate && !target.requestBy(rotation).done) return@listen @@ -89,7 +89,7 @@ class OpenContainer @Ta5kBuilder constructor( val hitResult = target.hit?.hitIfValid()?.blockResult ?: return@listen interaction.interactBlock(player, Hand.MAIN_HAND, hitResult) - state = State.OPENING + containerState = State.OPENING } } } diff --git a/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt b/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt index cb9a742ee..2245d6b45 100644 --- a/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt +++ b/common/src/main/kotlin/com/lambda/util/VarIntIterator.kt @@ -19,8 +19,6 @@ package com.lambda.util class VarIntIterator( private val bytes: ByteArray, - private val bitsPerEntry: Int = 7, - private val maxGroups: Int = 5, ) : Iterator { private var index: Int = 0 @@ -31,23 +29,23 @@ class VarIntIterator( throw NoSuchElementException("No more elements to read") var value = 0 - var bitsRead = 0 + var size = 0 - val groupMask = (1 shl bitsPerEntry) - 1 - val continuationBit = 1 shl bitsPerEntry - - var b: Byte do { - if (index >= bytes.size) - throw NoSuchElementException("Unexpected end of byte array while reading VarInt") - - b = bytes[index++] - value = value or ((b.toInt() and groupMask) shl bitsRead) - bitsRead += bitsPerEntry + val b = bytes[index++].toInt() + value = value or ((b and SEGMENT_BIT) shl (size++ * 7)) - require(bitsRead <= bitsPerEntry * maxGroups) { "VarInt size cannot exceed $maxGroups bytes" } - } while ((b.toInt() and continuationBit) != 0) + if (size > 5) throw IllegalArgumentException("VarInt size cannot exceed 5 bytes") + } while ((b and CONTINUE_BIT) != 0) return value } + + companion object { + const val SEGMENT_BIT = 127 + const val CONTINUE_BIT = 128 + } } + +inline fun ByteArray.varIterator(block: (Int) -> Unit) = + VarIntIterator(this).forEach(block) diff --git a/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt b/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt index 9dceafcb6..7bf1d0534 100644 --- a/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt +++ b/common/src/main/kotlin/com/lambda/util/collections/LimitedDecayQueue.kt @@ -106,11 +106,17 @@ class LimitedDecayQueue( * Updates the maximum allowed size for the queue and triggers a cleanup operation * to remove elements exceeding the new size or falling outside the allowed time interval. * + * Elements starting from the head will be removed. + * * @param newSize The new maximum size for the queue. Must be a non-negative integer. */ - fun setMaxSize(newSize: Int) { + fun setSizeLimit(newSize: Int) { sizeLimit = newSize cleanUp() + + while (queue.size > newSize) { + queue.poll() + } } /** @@ -131,4 +137,4 @@ class LimitedDecayQueue( onDecay(queue.poll().first) } } -} \ No newline at end of file +} diff --git a/common/src/main/kotlin/com/lambda/util/extension/Structures.kt b/common/src/main/kotlin/com/lambda/util/extension/Structures.kt index 9d4421339..fccec79d6 100644 --- a/common/src/main/kotlin/com/lambda/util/extension/Structures.kt +++ b/common/src/main/kotlin/com/lambda/util/extension/Structures.kt @@ -20,6 +20,7 @@ package com.lambda.util.extension import com.lambda.Lambda.mc import com.lambda.util.VarIntIterator import com.lambda.util.math.MathUtils.logCap +import com.lambda.util.varIterator import com.lambda.util.world.FastVector import com.lambda.util.world.fastVectorOf import com.lambda.util.world.x @@ -117,8 +118,8 @@ private fun StructureTemplate.readSpongeV1OrException( val newBlocks = NbtList() var blockIndex = 0 - VarIntIterator(nbt.getByteArray("BlockData")) - .forEach { blockId -> + nbt.getByteArray("BlockData") + .varIterator { blockId -> val blockpos = positionFromIndex(width, length, blockIndex++) newBlocks.add(NbtCompound().apply { diff --git a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt index ece4115a0..c6ce1271e 100644 --- a/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt +++ b/common/src/main/kotlin/com/lambda/util/math/MathUtils.kt @@ -26,6 +26,8 @@ object MathUtils { private const val PI_FLOAT = 3.141593f inline val Int.sq: Int get() = this * this + inline val Float.sq: Float get() = this * this + inline val Double.sq: Double get() = this * this fun Float.toRadian() = this / 180.0f * PI_FLOAT fun Double.toRadian() = this / 180.0 * PI diff --git a/common/src/main/kotlin/com/lambda/util/math/Vectors.kt b/common/src/main/kotlin/com/lambda/util/math/Vectors.kt index 8784cf45b..900e9255d 100644 --- a/common/src/main/kotlin/com/lambda/util/math/Vectors.kt +++ b/common/src/main/kotlin/com/lambda/util/math/Vectors.kt @@ -84,9 +84,9 @@ infix operator fun Vec3d.times(other: Int): Vec3d = multiply(other.toDouble()) infix operator fun Vec3d.div(other: Vec3d): Vec3d = multiply(1.0 / other.x, 1.0 / other.y, 1.0 / other.z) infix operator fun Vec3d.div(other: Vec3i): Vec3d = Vec3d(x / other.x, y / other.y, z / other.z) -infix operator fun Vec3d.div(other: Double): Vec3d = times(1 / other) -infix operator fun Vec3d.div(other: Float): Vec3d = times(1 / other) -infix operator fun Vec3d.div(other: Int): Vec3d = times(1 / other) +infix operator fun Vec3d.div(other: Double): Vec3d = times(1.0 / other) +infix operator fun Vec3d.div(other: Float): Vec3d = times(1.0 / other) +infix operator fun Vec3d.div(other: Int): Vec3d = times(1.0 / other) /* Vec3i */ val Vec3i.vec3d get() = diff --git a/common/src/main/kotlin/com/lambda/util/world/Position.kt b/common/src/main/kotlin/com/lambda/util/world/Position.kt index 6e19a639b..201d76436 100644 --- a/common/src/main/kotlin/com/lambda/util/world/Position.kt +++ b/common/src/main/kotlin/com/lambda/util/world/Position.kt @@ -179,12 +179,12 @@ infix fun FastVector.div(scalar: Double): FastVector = /** * Modulo the position by the given scalar. */ -infix fun FastVector.mod(scalar: Int): FastVector = fastVectorOf(x % scalar, y % scalar, z % scalar) +infix fun FastVector.remainder(scalar: Int): FastVector = fastVectorOf(x % scalar, y % scalar, z % scalar) /** * Modulo the position by the given scalar. */ -infix fun FastVector.mod(scalar: Double): FastVector = +infix fun FastVector.remainder(scalar: Double): FastVector = fastVectorOf((x % scalar).toLong(), (y % scalar).toLong(), (z % scalar).toLong()) /** diff --git a/common/src/test/kotlin/FastVectorTest.kt b/common/src/test/kotlin/FastVectorTest.kt new file mode 100644 index 000000000..8f041ea3f --- /dev/null +++ b/common/src/test/kotlin/FastVectorTest.kt @@ -0,0 +1,238 @@ +import com.lambda.util.world.X_BITS +import com.lambda.util.world.Z_BITS +import com.lambda.util.world.addX +import com.lambda.util.world.addY +import com.lambda.util.world.addZ +import com.lambda.util.world.distSq +import com.lambda.util.world.fastVectorOf +import com.lambda.util.world.offset +import com.lambda.util.world.remainder +import com.lambda.util.world.setX +import com.lambda.util.world.setY +import com.lambda.util.world.setZ +import com.lambda.util.world.toBlockPos +import com.lambda.util.world.toVec3d +import com.lambda.util.world.x +import com.lambda.util.world.y +import com.lambda.util.world.z +import net.minecraft.util.math.Direction +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +class FastVectorTest { + @Test + fun `test fast vector with valid coordinates`() { + val x = 123456 + val y = 789 + val z = -12345 + + val fastVec = fastVectorOf(x, y, z) + + assertEquals(x, fastVec.x) + assertEquals(y, fastVec.y) + assertEquals(z, fastVec.z) + } + + @Test + fun `test fast vector with invalid X coordinate`() { + val x = (1L shl X_BITS - 1) + val y = 10L + val z = 20L + + assertFails { fastVectorOf(x, y, z) } + } + + @Test + fun `test fast vector with invalid Z coordinate`() { + val x = 10L + val y = 20L + val z = (1L shl Z_BITS - 1) + + assertFails { fastVectorOf(x, y, z) } + } + + @Test + fun `test fast vector with Y overflow`() { + val x = 10L + val y = 2049L + val z = 20L + + val fastVec = fastVectorOf(x, y, z) + + assertEquals(-2047, fastVec.y) + } + + @Test + fun `test fast vector with Y underflow`() { + val x = 10L + val y = -2049L + val z = 20L + + val fastVec = fastVectorOf(x, y, z) + + assertEquals(2047, fastVec.y) + } + + @Test + fun `test setX correctly sets the X coordinate`() { + var fastVec = fastVectorOf(10, 20, 30) + fastVec = fastVec setX 40 + + assertEquals(40, fastVec.x) + } + + @Test + fun `test setY correctly sets the Y coordinate`() { + var fastVec = fastVectorOf(10, 20, 30) + fastVec = fastVec setY 50 + + assertEquals(50, fastVec.y) + } + + @Test + fun `test setZ correctly sets the Z coordinate`() { + var fastVec = fastVectorOf(10, 20, 30) + fastVec = fastVec setZ 60 + + assertEquals(60, fastVec.z) + } + + @Test + fun `test addX correctly adds to the X coordinate`() { + val fastVec = fastVectorOf(10, 20, 30) + val newVec = fastVec addX 5 + + assertEquals(15, newVec.x) + } + + @Test + fun `test addY correctly adds to the Y coordinate`() { + val fastVec = fastVectorOf(10, 20, 30) + val newVec = fastVec addY 5 + + assertEquals(25, newVec.y) + } + + @Test + fun `test addZ correctly adds to the Z coordinate`() { + val fastVec = fastVectorOf(10, 20, 30) + val newVec = fastVec addZ 5 + + assertEquals(35, newVec.z) + } + + @Test + fun `test plus operation with another FastVector`() { + val vec1 = fastVectorOf(1, 2, 3) + val vec2 = fastVectorOf(4, 5, 6) + + val result = vec1 + vec2 + + assertEquals(5, result.x) + assertEquals(7, result.y) + assertEquals(9, result.z) + } + + @Test + fun `test minus operation with another FastVector`() { + val vec1 = fastVectorOf(5, 6, 7) + val vec2 = fastVectorOf(2, 2, 2) + + val result = vec1 - vec2 + + assertEquals(3, result.x) + assertEquals(4, result.y) + assertEquals(5, result.z) + } + + @Test + fun `test multiplication by scalar`() { + val vec = fastVectorOf(1, 2, 3) + val result = vec * 2 + + assertEquals(2, result.x) + assertEquals(4, result.y) + assertEquals(6, result.z) + } + + @Test + fun `test division by scalar`() { + val vec = fastVectorOf(10, 20, 30) + val result = vec / 2 + + assertEquals(5, result.x) + assertEquals(10, result.y) + assertEquals(15, result.z) + } + + @Test + fun `test modulo operation with scalar`() { + val vec = fastVectorOf(10, 20, 30) + val result = vec remainder 7 + + assertEquals(3, result.x) + assertEquals(6, result.y) + assertEquals(2, result.z) + } + + @Test + fun `test distSq with another FastVector`() { + val vec1 = fastVectorOf(1, 2, 3) + val vec2 = fastVectorOf(4, 5, 6) + + val distSq = vec1 distSq vec2 + + assertEquals(27.0, distSq) + } + + @Test + fun `test offset with Direction`() { + val vec = fastVectorOf(0, 0, 0) + val direction = Direction.NORTH // offset: (0, 0, -1) + + val newVec = vec.offset(direction) + + assertEquals(0, newVec.x) + assertEquals(0, newVec.y) + assertEquals(-1, newVec.z) + } + + @Test + fun `test toBlockPos conversion`() { + val vec = fastVectorOf(10, 20, 30) + val blockPos = vec.toBlockPos() + + assertEquals(10, blockPos.x) + assertEquals(20, blockPos.y) + assertEquals(30, blockPos.z) + } + + @Test + fun `test toVec3d conversion`() { + val vec = fastVectorOf(10, 20, 30) + val vec3d = vec.toVec3d() + + assertEquals(10.0, vec3d.x) + assertEquals(20.0, vec3d.y) + assertEquals(30.0, vec3d.z) + } +} diff --git a/common/src/test/kotlin/LimitedDecayQueueTest.kt b/common/src/test/kotlin/LimitedDecayQueueTest.kt new file mode 100644 index 000000000..0f48bcdc7 --- /dev/null +++ b/common/src/test/kotlin/LimitedDecayQueueTest.kt @@ -0,0 +1,176 @@ +import com.lambda.util.collections.LimitedDecayQueue +import java.util.concurrent.TimeUnit +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class LimitedDecayQueueTest { + + private lateinit var queue: LimitedDecayQueue + private lateinit var onDecayCalled: MutableList + + @BeforeTest + fun setUp() { + // Initialize the onDecay callback + onDecayCalled = mutableListOf() + queue = LimitedDecayQueue(3, 1000) { onDecayCalled.add(it) } // 1 second decay time + } + + @Test + fun `test add element to queue`() { + // Add an element + val result = queue.add("Element1") + + assertTrue(result) + assertEquals(1, queue.size) + } + + @Test + fun `test add element beyond size limit`() { + queue.add("Element1") + queue.add("Element2") + queue.add("Element3") + + // Try adding a 4th element when size limit is 3 + val result = queue.add("Element4") + + assertFalse(result) // Should fail to add + assertEquals(3, queue.size) // Size should remain 3 + } + + @Test + fun `test elements expire after max age`() { + queue.add("Element1") + queue.add("Element2") + + // Simulate passage of time (greater than maxAge) + TimeUnit.MILLISECONDS.sleep(1500) + + // Add new element after expiration + queue.add("Element3") + + // Ensure expired elements are removed and "onDecay" callback is triggered + assertEquals(1, queue.size) + assertTrue(onDecayCalled.contains("Element1")) + assertTrue(onDecayCalled.contains("Element2")) + } + + @Test + fun `test add all elements`() { + queue.add("Element1") + queue.add("Element2") + + val result = queue.addAll(listOf("Element3", "Element4")) + + assertTrue(result) + assertEquals(3, queue.size) // Size limit is 3 + } + + @Test + fun `test remove element`() { + queue.add("Element1") + queue.add("Element2") + + val result = queue.remove("Element1") + + assertTrue(result) + assertEquals(1, queue.size) + } + + @Test + fun `test remove all elements`() { + queue.add("Element1") + queue.add("Element2") + queue.add("Element3") + + val result = queue.removeAll(listOf("Element1", "Element2")) + + assertTrue(result) + assertEquals(1, queue.size) // Only "Element3" should remain + } + + @Test + fun `test retain all elements`() { + queue.add("Element1") + queue.add("Element2") + queue.add("Element3") + + val result = queue.retainAll(listOf("Element2", "Element3")) + + assertTrue(result) + assertEquals(2, queue.size) // Only "Element2" and "Element3" should remain + } + + @Test + fun `test clear the queue`() { + queue.add("Element1") + queue.add("Element2") + queue.clear() + + assertEquals(0, queue.size) // Queue should be empty + } + + @Test + fun `test set max size`() { + queue.add("Element1") + queue.add("Element2") + queue.add("Element3") + + queue.setSizeLimit(2) // Reduce size limit to 2 + + assertEquals(2, queue.size) + } + + @Test + fun `test set decay time`() { + queue.add("Element1") + queue.add("Element2") + + queue.setDecayTime(500) // Set a shorter decay time of 500 ms + + // Simulate passage of time (greater than decay time) + TimeUnit.MILLISECONDS.sleep(600) + + queue.add("Element3") + + // Ensure expired elements are removed and "onDecay" callback is triggered + assertEquals(1, queue.size) + assertTrue(onDecayCalled.contains("Element1")) + assertTrue(onDecayCalled.contains("Element2")) + } + + @Test + fun `test clean up function when iterating`() { + queue.add("Element1") + queue.add("Element2") + + // Simulate some delay to allow elements to decay + TimeUnit.MILLISECONDS.sleep(1500) + + queue.add("Element3") + + // Iterator should only return "Element3" because the others are expired + val elements = queue.toList() + assertEquals(1, elements.size) + assertEquals("Element3", elements[0]) + } +} diff --git a/common/src/test/kotlin/LimitedOrderedSetTest.kt b/common/src/test/kotlin/LimitedOrderedSetTest.kt new file mode 100644 index 000000000..e5816e4bb --- /dev/null +++ b/common/src/test/kotlin/LimitedOrderedSetTest.kt @@ -0,0 +1,120 @@ +import com.lambda.util.collections.LimitedOrderedSet +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class LimitedOrderedSetTest { + + private lateinit var set: LimitedOrderedSet + + @BeforeTest + fun setUp() { + // Initialize the set with a max size of 3 + set = LimitedOrderedSet(3) + } + + @Test + fun `test adding elements to the set`() { + val added = set.add("Element1") + assertTrue(added) + assertEquals(1, set.size) + } + + @Test + fun `test adding more elements than maxSize`() { + set.add("Element1") + set.add("Element2") + set.add("Element3") + + // Adding a fourth element when the max size is 3 + val added = set.add("Element4") + assertTrue(added) + assertEquals(3, set.size) // The size should not exceed maxSize + + // The first element ("Element1") should be removed, as the set is maintaining order + assertFalse(set.contains("Element1")) + assertTrue(set.contains("Element2")) + assertTrue(set.contains("Element3")) + assertTrue(set.contains("Element4")) + } + + @Test + fun `test maintaining the order of elements`() { + set.add("Element1") + set.add("Element2") + set.add("Element3") + + // Add a fourth element, and the first one should be removed + set.add("Element4") + + // The order should now be Element2, Element3, Element4 + val expectedOrder = listOf("Element2", "Element3", "Element4") + assertEquals(expectedOrder, set.toList()) + } + + @Test + fun `test addAll method`() { + // Initially, the set is empty + set.add("Element1") + set.add("Element2") + + // Adding multiple elements + val added = set.addAll(listOf("Element3", "Element4")) + + assertTrue(added) + assertEquals(3, set.size) // Set should contain up to maxSize elements + assertFalse(set.contains("Element1")) // Element1 should be removed because we are over the max size + assertTrue(set.contains("Element2")) + assertTrue(set.contains("Element3")) + assertTrue(set.contains("Element4")) + } + + @Test + fun `test adding more elements than maxSize with addAll`() { + set.addAll(listOf("Element1", "Element2", "Element3")) + + // Add more elements, exceeding the max size + val added = set.addAll(listOf("Element4", "Element5")) + + assertTrue(added) + assertEquals(3, set.size) // The set should maintain the max size + assertFalse(set.contains("Element1")) + assertFalse(set.contains("Element2")) + assertTrue(set.contains("Element3")) + assertTrue(set.contains("Element4")) + assertTrue(set.contains("Element5")) + } + + @Test + fun `test the set size does not exceed maxSize`() { + set.add("Element1") + set.add("Element2") + set.add("Element3") + + set.add("Element4") // This should push out "Element1" + assertEquals(3, set.size) + assertFalse(set.contains("Element1")) + assertTrue(set.contains("Element2")) + assertTrue(set.contains("Element3")) + assertTrue(set.contains("Element4")) + } +} diff --git a/common/src/test/kotlin/RangeTest.kt b/common/src/test/kotlin/RangeTest.kt new file mode 100644 index 000000000..f7bb86c95 --- /dev/null +++ b/common/src/test/kotlin/RangeTest.kt @@ -0,0 +1,164 @@ +import com.lambda.util.math.Vec2d +import com.lambda.util.math.coerceIn +import com.lambda.util.math.inv +import com.lambda.util.math.lerp +import com.lambda.util.math.normalize +import com.lambda.util.math.random +import com.lambda.util.math.step +import com.lambda.util.math.transform +import net.minecraft.util.math.Vec3d +import java.awt.Color +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class RangeTest { + + @Test + fun `test step over double range`() { + val range = 0.0..10.0 + val iterator = range.step(2.0) + + val result = iterator.asSequence().toList() + assertEquals(listOf(0.0, 2.0, 4.0, 6.0, 8.0, 10.0), result) + } + + @Test + fun `test step over float range`() { + val range = 0.0f..10.0f + val iterator = range.step(2.0f) + + val result = iterator.asSequence().toList() + assertEquals(listOf(0.0f, 2.0f, 4.0f, 6.0f, 8.0f, 10.0f), result) + } + + @Test + fun `test random within range`() { + val range = 0.0..10.0 + val randomValue = range.random() + + assertTrue(randomValue in range) // The value must be in the range [0.0, 10.0] + } + + @Test + fun `test normalize double value`() { + val range = 0.0..100.0 + val normalized = range.normalize(50.0) + + assertEquals(0.5, normalized) // 50.0 should be normalized to 0.5 in the range [0.0, 100.0] + } + + @Test + fun `test normalize float value`() { + val range = 0f..100f + val normalized = range.normalize(50f) + + assertEquals(0.5f, normalized) // 50f should be normalized to 0.5f in the range [0f, 100f] + } + + @Test + fun `test invert float range`() { + val range = 0f..100f + val inverted = range.inv() + + assertEquals(100f to 0f, inverted) // Inverting the range [0f, 100f] gives (100f, 0f) + } + + @Test + fun `test transform double range`() { + val range = 0.0..10.0 + val transformed = range.transform(5.0, 0.0, 100.0) + + assertEquals(50.0, transformed) // 5.0 in range [0.0, 10.0] maps to 50.0 in range [0.0, 100.0] + } + + @Test + fun `test transform float range`() { + val range = 0f..10f + val transformed = range.transform(5f, 0f, 100f) + + assertEquals(50f, transformed) // 5f in range [0f, 10f] maps to 50f in range [0f, 100f] + } + + @Test + fun `test lerp double values`() { + val lerpedValue = lerp(0.5, 0.0, 10.0) + + assertEquals(5.0, lerpedValue) // Linear interpolation between 0.0 and 10.0 at 0.5 results in 5.0 + } + + @Test + fun `test lerp float values`() { + val lerpedValue = lerp(0.5f, 0f, 10f) + + assertEquals(5.0f, lerpedValue) // Linear interpolation between 0f and 10f at 0.5 results in 5.0f + } + + @Test + fun `test lerp Vec2d`() { + val start = Vec2d(0.0, 0.0) + val end = Vec2d(10.0, 10.0) + val lerpedValue = lerp(0.5, start, end) + + assertEquals(Vec2d(5.0, 5.0), lerpedValue) // Interpolated 50% between (0, 0) and (10, 10) + } + + @Test + fun `test lerp Vec3d`() { + val start = Vec3d(0.0, 0.0, 0.0) + val end = Vec3d(10.0, 10.0, 10.0) + val lerpedValue = lerp(0.5, start, end) + + assertEquals(Vec3d(5.0, 5.0, 5.0), lerpedValue) // Interpolated 50% between (0, 0, 0) and (10, 10, 10) + } + + @Test + fun `test lerp Color`() { + val start = Color(255, 0, 0) // Red + val end = Color(0, 0, 255) // Blue + val lerpedValue = lerp(0.5, start, end) + + assertEquals(Color(128, 0, 128), lerpedValue) // Interpolated color should be purple + } + + @Test + fun `test coercing value in double range`() { + val range = 0.0..10.0 + val coercedValue = range.coerceIn(15.0) + + assertEquals(10.0, coercedValue) // Coerced value should be within the range [0.0, 10.0] + } + + @Test + fun `test coercing value in float range`() { + val range = 0f..10f + val coercedValue = range.coerceIn(15f) + + assertEquals(10f, coercedValue) // Coerced value should be within the range [0f, 10f] + } + + @Test + fun `test coercing value in 2d vector`() { + val vec = Vec2d(5.0, 5.0) + val coercedVec = vec.coerceIn(0.0, 10.0, 0.0, 10.0) + + assertEquals(Vec2d(5.0, 5.0), coercedVec) // Vec should stay the same + } +} diff --git a/common/src/test/kotlin/TaskTest.kt b/common/src/test/kotlin/TaskTest.kt new file mode 100644 index 000000000..9d20b9343 --- /dev/null +++ b/common/src/test/kotlin/TaskTest.kt @@ -0,0 +1,282 @@ +import com.lambda.context.ClientContext +import com.lambda.context.SafeContext +import com.lambda.task.RootTask +import com.lambda.task.RootTask.run +import com.lambda.task.Task +import com.lambda.util.Communication +import com.lambda.util.Communication.log +import io.mockk.every +import io.mockk.mockkObject +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.mockito.Mock +import org.mockito.MockedConstruction +import org.mockito.Mockito.mockConstruction +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +class TaskTest { + + @Mock + private lateinit var mockSafeContext: SafeContext + + private lateinit var clientContextMock: MockedConstruction + + class TestTask(private val i: Int = 0) : Task() { + override val name get() = "TestTask of $i" + + override fun SafeContext.onStart() { + success(i + 1) + } + + override fun SafeContext.onCancel() { + success(i) + } + } + + @BeforeEach + fun setUp() { + MockitoAnnotations.openMocks(this) + clientContextMock = mockConstruction(ClientContext::class.java) { mock, _ -> + whenever(mock.toSafe()).thenReturn(mockSafeContext) + } + mockkObject(Communication) + every { Communication.log(any(), any(), any(), any()) } returns Unit + } + + @AfterEach + fun tearDown() { + clientContextMock.close() + RootTask.clear() + } + + @Test + fun `task initial state is INIT`() { + val task = TestTask(0) + assertEquals(Task.State.INIT, task.state) + assertTrue(task.subTasks.isEmpty()) + assertEquals(0, task.age) + } + + @Test + fun `execute transitions to RUNNING and adds to parent subTasks`() { + val parent = TestTask(0) + val child = TestTask(1) + + child.execute(parent) + + assertEquals(Task.State.COMPLETED, child.state) + assertTrue(parent.subTasks.contains(child)) + assertEquals(parent, child.parent) + } + + @Test + fun `success transitions to COMPLETED and executes finally block`() { + val task = TestTask(5) + var finallyCalled = false + + task.finally { result -> + assertEquals(6, result) + finallyCalled = true + }.run() + + assertEquals(Task.State.COMPLETED, task.state) + assertTrue(finallyCalled) + } + +// @Test +// fun `cancel transitions to CANCELLED and cancels subTasks`() { +// val parent = TestTask(0) +// val child = TestTask(1).apply { execute(parent) } +// +// child.cancel() +// +// assertEquals(Task.State.CANCELLED, child.state) +// assertTrue(child.subTasks.all { it.state == Task.State.CANCELLED }) +// } + + @Test + fun `pause and activate change state between PAUSED and RUNNING`() { + val task = TestTask(0).apply { state = Task.State.RUNNING } + + task.pause() + assertEquals(Task.State.PAUSED, task.state) + + task.activate() + assertEquals(Task.State.RUNNING, task.state) + } + +// @Test +// fun `subtask pauses parent when executed with pauseParent true`() { +// val parent = TestTask(0).apply { state = Task.State.RUNNING } +// val child = TestTask(1) +// +// child.execute(parent, pauseParent = true) +// +// assertEquals(Task.State.PAUSED, parent.state) +// } + + @Test + fun `then chains tasks in sequence`() { + val task1 = TestTask(1) + val task2 = TestTask(2) + + task1.then(task2).run() + + task1.success(6) // Simulate success to trigger next task + assertTrue(task1.parent?.subTasks?.contains(task2) == true) + } + +// @Test +// fun `finally block is called on failure`() { +// var finallyCalled = false +// val task = object : Task() { +// override val name = "FailingTask" +// override fun SafeContext.onStart() { +// throw RuntimeException("Simulated failure") +// } +// }.finally { finallyCalled = true } +// +// task.run() +// +// assertEquals(Task.State.FAILED, task.state) +// assertTrue(finallyCalled) +// } + + @Test + fun `execute with self as owner throws exception`() { + val task = TestTask(0) + assertThrows { + task.execute(task) + } + } + + @Test + fun `then with self throws exception`() { + val task = TestTask(0) + assertThrows { + task.then(task) + } + } + +// @Test +// fun `duration is formatted correctly`() { +// val task = TestTask(0).apply { age = 120 } // 120 * 50ms = 6000ms +// assertEquals("000:00:00:06.00", task.duration) +// } + + @Test + fun `toString includes task hierarchy and state`() { + val parent = TestTask(1) + val child = TestTask(2).apply { execute(parent) } + + val expected = """ + TestTask of 1 [Initialized] + TestTask of 2 [Running] 0ms + """.trimIndent().replace("\n", System.lineSeparator()) + + assertTrue(parent.toString().contains("TestTask of 1")) + assertTrue(parent.toString().contains("TestTask of 2")) + } + + @Test + fun `clear removes all subTasks`() { + val parent = TestTask(0) + TestTask(1).execute(parent) + TestTask(2).execute(parent) + + parent.clear() + + assertTrue(parent.subTasks.isEmpty()) + } + + @Test + fun `isMuted returns true when PAUSED or INIT`() { + val task = TestTask(0) + assertTrue(task.isMuted) // INIT state + + task.state = Task.State.PAUSED + assertTrue(task.isMuted) + + task.state = Task.State.RUNNING + assertFalse(task.isMuted) + } + +// @Test +// fun `failure propagates to parent with stacktrace`() { +// val grandParent = TestTask(0) +// val parent = TestTask(1).apply { execute(grandParent) } +// val child = TestTask(2).apply { execute(parent) } +// +// val exception = RuntimeException("Child failed") +// child.failure(exception) +// +// assertEquals(Task.State.FAILED, child.state) +// assertEquals(Task.State.FAILED, parent.state) +// assertEquals(Task.State.FAILED, grandParent.state) +// } + + @Test + fun `task with thenOrNull executes next task conditionally`() { + val task = TestTask(0) + var nextTaskExecuted = false + + task.thenOrNull { result -> + if (result == 1) TestTask(1).also { nextTaskExecuted = true } else null + } + + task.success(1) + assertTrue(nextTaskExecuted) + + nextTaskExecuted = false + task.success(0) + assertFalse(nextTaskExecuted) + } + + @Test + fun `subtask resumes parent when completed`() { + val parent = TestTask(0).apply { state = Task.State.RUNNING } + val child = TestTask(1).apply { execute(parent, pauseParent = true) } + + child.success(2) + + assertEquals(Task.State.COMPLETED, child.state) + assertEquals(Task.State.RUNNING, parent.state) + } + + @Test + fun `test task`() { + val task = TestTask(5) + + assertEquals(task.name, "TestTask of 5") + + task.finally { result -> + assertEquals(result, 6) + assertEquals(task.state, Task.State.COMPLETED) + assertTrue(task.isCompleted) + }.run() + } +} \ No newline at end of file diff --git a/common/src/test/kotlin/VarIntIteratorTest.kt b/common/src/test/kotlin/VarIntIteratorTest.kt new file mode 100644 index 000000000..4e203665d --- /dev/null +++ b/common/src/test/kotlin/VarIntIteratorTest.kt @@ -0,0 +1,87 @@ +import com.lambda.util.VarIntIterator +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class VarIntIteratorTest { + + private lateinit var iterator: VarIntIterator + + @Test + fun `test single byte varint`() { + val bytes = byteArrayOf(0b01111111.toByte()) // Maximum single-byte VarInt (127) + iterator = VarIntIterator(bytes) + + assertTrue(iterator.hasNext()) + assertEquals(127, iterator.next()) + assertFalse(iterator.hasNext()) // No more elements after this + } + + @Test + fun `test multi-byte varint`() { + val bytes = byteArrayOf(0b10000001.toByte(), 0b00000001.toByte()) // Represents 129 + iterator = VarIntIterator(bytes) + + assertTrue(iterator.hasNext()) + assertEquals(129, iterator.next()) + assertFalse(iterator.hasNext()) // No more elements after this + } + + @Test + fun `test varint iterator with multiple values`() { + val bytes = byteArrayOf( + 0b10000001.toByte(), 0b00000001.toByte(), // 129 + 0b01111111.toByte(), // 127 + 0b10000000.toByte(), 0b00000001.toByte() // 128 + ) + iterator = VarIntIterator(bytes) + + assertTrue(iterator.hasNext()) + assertEquals(129, iterator.next()) + assertTrue(iterator.hasNext()) + assertEquals(127, iterator.next()) + assertTrue(iterator.hasNext()) + assertEquals(128, iterator.next()) + assertFalse(iterator.hasNext()) // No more elements after this + } + + @Test + fun `test varint iterator with no elements`() { + val bytes = byteArrayOf() // Empty byte array + iterator = VarIntIterator(bytes) + + assertFalse(iterator.hasNext()) // There are no elements + assertFails { + iterator.next() // Should throw exception since there are no elements + } + } + + @Test + fun `test reading varint with unexpected end of byte array`() { + val bytes = byteArrayOf(0b10000001.toByte()) // Only part of a VarInt + iterator = VarIntIterator(bytes) + + assertFails { + iterator.next() // Should throw exception since the VarInt is incomplete + } + } +} diff --git a/common/src/test/kotlin/Vec2dTest.kt b/common/src/test/kotlin/Vec2dTest.kt new file mode 100644 index 000000000..876b2d956 --- /dev/null +++ b/common/src/test/kotlin/Vec2dTest.kt @@ -0,0 +1,180 @@ +import com.lambda.util.math.Vec2d +import kotlin.test.Test +import kotlin.test.assertEquals + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class Vec2dTest { + @Test + fun `test unary minus`() { + val vector = Vec2d(1.0, -2.0) + val result = -vector + + assertEquals(Vec2d(-1.0, 2.0), result) + } + + @Test + fun `test addition with another Vec2d`() { + val vector1 = Vec2d(1.0, 2.0) + val vector2 = Vec2d(3.0, 4.0) + val result = vector1 + vector2 + + assertEquals(Vec2d(4.0, 6.0), result) + } + + @Test + fun `test addition with scalar (Double)`() { + val vector = Vec2d(1.0, 2.0) + val result = vector + 3.0 + + assertEquals(Vec2d(4.0, 5.0), result) + } + + @Test + fun `test addition with scalar (Float)`() { + val vector = Vec2d(1.0, 2.0) + val result = vector + 3.0f + + assertEquals(Vec2d(4.0, 5.0), result) + } + + @Test + fun `test addition with scalar (Int)`() { + val vector = Vec2d(1.0, 2.0) + val result = vector + 3 + + assertEquals(Vec2d(4.0, 5.0), result) + } + + @Test + fun `test subtraction with another Vec2d`() { + val vector1 = Vec2d(5.0, 6.0) + val vector2 = Vec2d(3.0, 4.0) + val result = vector1 - vector2 + + assertEquals(Vec2d(2.0, 2.0), result) + } + + @Test + fun `test subtraction with scalar (Double)`() { + val vector = Vec2d(5.0, 6.0) + val result = vector - 2.0 + + assertEquals(Vec2d(3.0, 4.0), result) + } + + @Test + fun `test subtraction with scalar (Float)`() { + val vector = Vec2d(5.0, 6.0) + val result = vector - 2.0f + + assertEquals(Vec2d(3.0, 4.0), result) + } + + @Test + fun `test subtraction with scalar (Int)`() { + val vector = Vec2d(5.0, 6.0) + val result = vector - 2 + + assertEquals(Vec2d(3.0, 4.0), result) + } + + @Test + fun `test multiplication with scalar (Double)`() { + val vector = Vec2d(2.0, 3.0) + val result = vector * 2.0 + + assertEquals(Vec2d(4.0, 6.0), result) + } + + @Test + fun `test multiplication with scalar (Float)`() { + val vector = Vec2d(2.0, 3.0) + val result = vector * 2.0f + + assertEquals(Vec2d(4.0, 6.0), result) + } + + @Test + fun `test multiplication with scalar (Int)`() { + val vector = Vec2d(2.0, 3.0) + val result = vector * 2 + + assertEquals(Vec2d(4.0, 6.0), result) + } + + @Test + fun `test multiplication with another Vec2d`() { + val vector1 = Vec2d(2.0, 3.0) + val vector2 = Vec2d(4.0, 5.0) + val result = vector1 * vector2 + + assertEquals(Vec2d(8.0, 15.0), result) + } + + @Test + fun `test division with scalar (Double)`() { + val vector = Vec2d(6.0, 8.0) + val result = vector / 2.0 + + assertEquals(Vec2d(3.0, 4.0), result) + } + + @Test + fun `test division with scalar (Float)`() { + val vector = Vec2d(6.0, 8.0) + val result = vector / 2.0f + + assertEquals(Vec2d(3.0, 4.0), result) + } + + @Test + fun `test division with scalar (Int)`() { + val vector = Vec2d(6.0, 8.0) + val result = vector / 2 + + assertEquals(Vec2d(3.0, 4.0), result) + } + + @Test + fun `test division with another Vec2d`() { + val vector1 = Vec2d(6.0, 8.0) + val vector2 = Vec2d(2.0, 4.0) + val result = vector1 / vector2 + + assertEquals(Vec2d(3.0, 2.0), result) + } + + @Test + fun `test round to Int`() { + val vector = Vec2d(3.5, 4.5) + val result = vector.roundToInt() + + assertEquals(Vec2d(4.0, 5.0), result) + } + + @Test + fun `test Vec2d constants`() { + assertEquals(Vec2d.ZERO, Vec2d(0.0, 0.0)) + assertEquals(Vec2d.ONE, Vec2d(1.0, 1.0)) + assertEquals(Vec2d.LEFT, Vec2d(-1.0, 0.0)) + assertEquals(Vec2d.RIGHT, Vec2d(1.0, 0.0)) + assertEquals(Vec2d.TOP, Vec2d(0.0, -1.0)) + assertEquals(Vec2d.BOTTOM, Vec2d(0.0, 1.0)) + } +} diff --git a/common/src/test/kotlin/Vec3dTest.kt b/common/src/test/kotlin/Vec3dTest.kt new file mode 100644 index 000000000..2ea45df70 --- /dev/null +++ b/common/src/test/kotlin/Vec3dTest.kt @@ -0,0 +1,167 @@ +import com.lambda.util.math.CENTER +import com.lambda.util.math.DOWN +import com.lambda.util.math.UP +import com.lambda.util.math.dist +import com.lambda.util.math.distSq +import com.lambda.util.math.MathUtils.sq +import com.lambda.util.math.div +import com.lambda.util.math.minus +import com.lambda.util.math.plus +import com.lambda.util.math.times +import com.lambda.util.math.vec3d +import net.minecraft.util.math.Vec3d +import net.minecraft.util.math.Vec3i +import kotlin.math.sqrt +import kotlin.test.Test +import kotlin.test.assertEquals + +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +class Vec3dTest { + + @Test + fun `test dist with another Vec3d`() { + val vector1 = Vec3d(1.0, 2.0, 3.0) + val vector2 = Vec3d(4.0, 5.0, 6.0) + val result = vector1 dist vector2 + + val expected = sqrt((1.0 - 4.0).sq + (2.0 - 5.0).sq + (3.0 - 6.0).sq) + assertEquals(expected, result) + } + + @Test + fun `test dist with Vec3i`() { + val vector1 = Vec3d(1.0, 2.0, 3.0) + val vector2 = Vec3i(4, 5, 6) + val result = vector1 dist vector2 + + val expected = sqrt((1.0 - 4).sq + (2.0 - 5).sq + (3.0 - 6).sq) + assertEquals(expected, result) + } + + @Test + fun `test distSq with another Vec3d`() { + val vector1 = Vec3d(1.0, 2.0, 3.0) + val vector2 = Vec3d(4.0, 5.0, 6.0) + val result = vector1 distSq vector2 + + val expected = (1.0 - 4.0).sq + (2.0 - 5.0).sq + (3.0 - 6.0).sq + assertEquals(expected, result) + } + + @Test + fun `test distSq with Vec3i`() { + val vector1 = Vec3d(1.0, 2.0, 3.0) + val vector2 = Vec3i(4, 5, 6) + val result = vector1 distSq vector2 + + val expected = (1.0 - 4).sq + (2.0 - 5).sq + (3.0 - 6).sq + assertEquals(expected, result) + } + + @Test + fun `test plus with another Vec3d`() { + val vector1 = Vec3d(1.0, 2.0, 3.0) + val vector2 = Vec3d(4.0, 5.0, 6.0) + val result = vector1 + vector2 + + assertEquals(Vec3d(5.0, 7.0, 9.0), result) + } + + @Test + fun `test plus with Vec3i`() { + val vector1 = Vec3d(1.0, 2.0, 3.0) + val vector2 = Vec3i(4, 5, 6) + val result = vector1 + vector2 + + assertEquals(Vec3d(5.0, 7.0, 9.0), result) + } + + @Test + fun `test plus with scalar (Double)`() { + val vector = Vec3d(1.0, 2.0, 3.0) + val result = vector + 2.0 + + assertEquals(Vec3d(3.0, 4.0, 5.0), result) + } + + @Test + fun `test minus with another Vec3d`() { + val vector1 = Vec3d(5.0, 7.0, 9.0) + val vector2 = Vec3d(4.0, 5.0, 6.0) + val result = vector1 - vector2 + + assertEquals(Vec3d(1.0, 2.0, 3.0), result) + } + + @Test + fun `test minus with Vec3i`() { + val vector1 = Vec3d(5.0, 7.0, 9.0) + val vector2 = Vec3i(4, 5, 6) + val result = vector1 - vector2 + + assertEquals(Vec3d(1.0, 2.0, 3.0), result) + } + + @Test + fun `test multiplication with scalar (Double)`() { + val vector = Vec3d(1.0, 2.0, 3.0) + val result = vector * 2.0 + + assertEquals(Vec3d(2.0, 4.0, 6.0), result) + } + + @Test + fun `test multiplication with scalar (Int)`() { + val vector = Vec3d(1.0, 2.0, 3.0) + val result = vector * 2 + + assertEquals(Vec3d(2.0, 4.0, 6.0), result) + } + + @Test + fun `test division with scalar (Double)`() { + val vector = Vec3d(4.0, 8.0, 12.0) + val result = vector / 2.0 + + assertEquals(Vec3d(2.0, 4.0, 6.0), result) + } + + @Test + fun `test division with scalar (Int)`() { + val vector = Vec3d(4.0, 8.0, 12.0) + val result = vector / 2 + + assertEquals(Vec3d(2.0, 4.0, 6.0), result) + } + + @Test + fun `test Vec3i conversion to Vec3d`() { + val vector = Vec3i(1, 2, 3) + val result = vector.vec3d + + assertEquals(Vec3d(1.0, 2.0, 3.0), result) + } + + @Test + fun `test constants`() { + assertEquals(Vec3d(0.0, 1.0, 0.0), UP) + assertEquals(Vec3d(0.0, -1.0, 0.0), DOWN) + assertEquals(Vec3d(0.5, 0.5, 0.5), CENTER) + } +} diff --git a/gradle.properties b/gradle.properties index 86aec0a00..aa18956ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,6 +34,9 @@ baritoneVersion=1.10.2 discordIPCVersion=8edf2dbeda fuelVersion=2.3.1 resultVersion=5.6.0 +mockitoKotlin=5.4.0 +mockitoInline=5.2.0 +mockkVersion=1.13.17 # Fabric https://fabricmc.net/develop/ fabricLoaderVersion=0.16.9