Skip to content

Commit

Permalink
Merge pull request #78 from Martomate/reliable-tests
Browse files Browse the repository at this point in the history
Fixed flaky integration tests
  • Loading branch information
Martomate authored Oct 13, 2024
2 parents bf8c874 + 180afe5 commit c6ce828
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 64 deletions.
4 changes: 4 additions & 0 deletions client/src/main/scala/hexacraft/client/GameClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ class GameClient(
private val leftMouseButtonTimer: TickableTimer = TickableTimer(10, initEnabled = false)
private val walkSoundTimer: TickableTimer = TickableTimer(20, initEnabled = false)

def isReadyToPlay: Boolean = {
this.world.getChunk(this.camera.blockCoords.getChunkRelWorld).isDefined
}

private def setUniforms(windowAspectRatio: Float): Unit = {
setProjMatrixForAll()
worldRenderer.onTotalSizeChanged(world.size.totalSize)
Expand Down
36 changes: 14 additions & 22 deletions main/src/test/scala/hexacraft/main/GameSceneTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ class GameSceneTest extends FunSuite {

private val windowSize = WindowSize(Vector2i(1920, 1080), Vector2i(1920, 1080))

def waitFor(maxIterations: Int, waitTimeMs: Int)(success: => Boolean)(iterate: => Unit): Boolean = {
(0 until maxIterations).exists { _ =>
iterate
Thread.sleep(waitTimeMs)
success
}
}

test("GameScene.unload frees all shader programs owned by the GameScene") {
OpenGL._enterTestMode()

Expand Down Expand Up @@ -149,17 +157,9 @@ class GameSceneTest extends FunSuite {

// ensure the spawn chunk gets loaded
val tickContext = TickContext(windowSize, MousePosition(Vector2f(0, 0)), MousePosition(Vector2f(0, 0)))
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
assert(waitFor(20, 10)(gameScene.client.isReadyToPlay) {
gameScene.tick(tickContext)
})

// start listening for audio events
val audioTracker = Tracker.withStorage[AudioSystem.Event]
Expand Down Expand Up @@ -212,17 +212,9 @@ class GameSceneTest extends FunSuite {

// ensure the spawn chunk gets loaded
val tickContext = TickContext(windowSize, MousePosition(Vector2f(0, 0)), MousePosition(Vector2f(0, 0)))
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
Thread.sleep(20)
gameScene.tick(tickContext)
assert(waitFor(20, 10)(gameScene.client.isReadyToPlay) {
gameScene.tick(tickContext)
})

// start listening for audio events
val audioTracker = Tracker.withStorage[AudioSystem.Event]
Expand Down
66 changes: 24 additions & 42 deletions server/src/test/scala/hexacraft/world/ServerWorldTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ package hexacraft.world

import hexacraft.server.ServerWorld
import hexacraft.world.block.{Block, BlockState}
import hexacraft.world.chunk.Chunk
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, ChunkRelWorld, CylCoords}
import hexacraft.world.chunk.{Chunk, ChunkColumnData, ChunkColumnHeightMap, ChunkData}
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, ChunkRelWorld, ColumnRelWorld, CylCoords}
import hexacraft.world.entity.{BoundsComponent, Entity, MotionComponent, TransformComponent}

import com.martomate.nbt.Nbt
import munit.FunSuite

import java.util.UUID

class ServerWorldTest extends FunSuite {
given CylinderSize = CylinderSize(8)

def waitFor(maxIterations: Int, waitTimeMs: Int)(success: => Boolean)(iterate: => Unit): Boolean = {
(0 until maxIterations).exists { _ =>
iterate
Thread.sleep(waitTimeMs)
success
}
}

test("the world should not crash") {
val provider = new FakeWorldProvider(1234)
val world = ServerWorld(provider, provider.getWorldInfo)
Expand Down Expand Up @@ -72,17 +81,7 @@ class ServerWorldTest extends FunSuite {
assert(world.getChunk(cCoords).isEmpty)

// Run the game a bit
world.tick(Seq(camera), Seq(cCoords), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(cCoords), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
assert(waitFor(20, 10)(world.getChunk(cCoords).isDefined)(world.tick(Seq(camera), Seq(cCoords), Seq())))

// The chunk should be loaded
assert(world.getChunk(cCoords).isDefined)
Expand All @@ -100,17 +99,9 @@ class ServerWorldTest extends FunSuite {
camera.setPosition(BlockCoords(BlockRelWorld(8, 8, 8, cCoords)).toCylCoords.toVector3d)

// Run the game a bit
world.tick(Seq(camera), Seq(cCoords), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(cCoords), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
assert(waitFor(20, 10)(world.getChunk(cCoords).isDefined) {
world.tick(Seq(camera), Seq(cCoords), Seq())
})

// The chunk should be loaded
assert(world.getChunk(cCoords).isDefined)
Expand All @@ -119,39 +110,30 @@ class ServerWorldTest extends FunSuite {
camera.setPosition(BlockCoords(BlockRelWorld(8, 8, 8, cCoords.offset(100, 0, 0))).toCylCoords.toVector3d)

// Run the game a bit
world.tick(Seq(camera), Seq(), Seq(cCoords))
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())

// The chunk should be unloaded
assert(world.getChunk(cCoords).isEmpty)
assert(waitFor(20, 10)(world.getChunk(cCoords).isEmpty) {
world.tick(Seq(camera), Seq(), Seq(cCoords))
})

// Clean up
world.unload()
}

test("the world should allow entities to be added to and removed from a loaded chunk") {
val provider = new FakeWorldProvider(1234)
provider.saveColumnData(ChunkColumnData(Some(ChunkColumnHeightMap.from((_, _) => 0))).toNBT, ColumnRelWorld(0, 0))
provider.saveChunkData(ChunkData.fromNBT(Nbt.makeMap()).toNBT, ChunkRelWorld(0, 0, 0))

val world = ServerWorld(provider, provider.getWorldInfo)
val camera = new Camera(new CameraProjection(70, 1.6f, 0.01f, 1000f))

val entityPosition = CylCoords(1, 2, 3)

// Make sure the chunk is loaded
camera.setPosition(entityPosition.toVector3d)
world.tick(Seq(camera), Seq(ChunkRelWorld(0, 0, 0)), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(ChunkRelWorld(0, 0, 0)), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())
Thread.sleep(20)
world.tick(Seq(camera), Seq(), Seq())

assert(world.getChunk(ChunkRelWorld(0, 0, 0)).isDefined)
assert(waitFor(20, 10)(world.getChunk(ChunkRelWorld(0, 0, 0)).isDefined) {
world.tick(Seq(camera), Seq(ChunkRelWorld(0, 0, 0)), Seq())
})

val entity = Entity(
UUID.randomUUID(),
Expand Down

0 comments on commit c6ce828

Please sign in to comment.