extract projection snapshot logic

implement GameStateRepositoryTest
add lambda to the GameEventHandler.handle{} to set the version

add VersionBuilder
add version to the events
add creation date to the events
rename gameId to aggregateId
add EventHandler interface
This commit is contained in:
2025-03-13 00:27:44 +01:00
parent d5b033e731
commit 286dedac76
36 changed files with 684 additions and 266 deletions

View File

@@ -11,12 +11,14 @@ import eventDemo.app.notification.WelcomeToTheGameNotification
import eventDemo.configuration.appKoinModule
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldContain
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import org.koin.dsl.koinApplication
import kotlin.test.assertIs
@OptIn(DelicateCoroutinesApi::class)
class GameCommandHandlerTest :
FunSpec({
test("handle a command should execute the command") {

View File

@@ -8,6 +8,7 @@ import eventDemo.app.event.event.GameStartedEvent
import eventDemo.app.event.event.NewPlayerEvent
import eventDemo.app.event.event.PlayerReadyEvent
import eventDemo.app.event.event.disableShuffleDeck
import eventDemo.libs.event.VersionBuilderLocal
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
import kotlin.test.assertIs
@@ -17,34 +18,55 @@ class GameStateBuilderTest :
FunSpec({
test("apply") {
disableShuffleDeck()
val versionBuilder = VersionBuilderLocal()
val gameId = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
GameState(gameId)
.run {
val event = NewPlayerEvent(gameId, player1)
val event =
NewPlayerEvent(
aggregateId = gameId,
player = player1,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
state.isReady shouldBeEqual false
state.isStarted shouldBeEqual false
}
}.run {
val event = NewPlayerEvent(gameId, player2)
val event =
NewPlayerEvent(
aggregateId = gameId,
player = player2,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
state.players shouldBeEqual setOf(player1, player2)
}
}.run {
val event = PlayerReadyEvent(gameId, player1)
val event =
PlayerReadyEvent(
aggregateId = gameId,
player = player1,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
state.readyPlayers shouldBeEqual setOf(player1)
}
}.run {
val event = PlayerReadyEvent(gameId, player2)
val event =
PlayerReadyEvent(
aggregateId = gameId,
player = player2,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
state.readyPlayers shouldBeEqual setOf(player1, player2)
state.isReady shouldBeEqual true
state.isStarted shouldBeEqual false
@@ -52,12 +74,13 @@ class GameStateBuilderTest :
}.run {
val event =
GameStartedEvent.new(
gameId,
setOf(player1, player2),
id = gameId,
players = setOf(player1, player2),
shuffleIsDisabled = true,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
state.isStarted shouldBeEqual true
assertIs<Card.NumericCard>(state.deck.stack.first()).let {
it.number shouldBeEqual 6
@@ -66,9 +89,15 @@ class GameStateBuilderTest :
}
}.run {
val playedCard = playableCards(player1)[0]
val event = CardIsPlayedEvent(gameId, playedCard, player1)
val event =
CardIsPlayedEvent(
aggregateId = gameId,
card = playedCard,
player = player1,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
assertNotNull(state.cardOnCurrentStack).card shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 0
@@ -77,9 +106,15 @@ class GameStateBuilderTest :
}
}.run {
val playedCard = playableCards(player2)[0]
val event = CardIsPlayedEvent(gameId, playedCard, player2)
val event =
CardIsPlayedEvent(
aggregateId = gameId,
card = playedCard,
player = player2,
version = versionBuilder.buildNextVersion(),
)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.aggregateId shouldBeEqual gameId
assertNotNull(state.cardOnCurrentStack).card shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 7

View File

@@ -1,16 +1,128 @@
package eventDemo.app.event.projection
import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventHandler
import eventDemo.app.event.event.NewPlayerEvent
import eventDemo.configuration.appKoinModule
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.koin.core.context.stopKoin
import org.koin.dsl.koinApplication
import kotlin.test.assertNotNull
@OptIn(DelicateCoroutinesApi::class)
class GameStateRepositoryTest :
FunSpec({
xtest("GameStateRepository should build the projection when a new event occurs") { }
val player1 = Player("Tesla")
val player2 = Player(name = "Einstein")
xtest("get should build the last version of the state") { }
xtest("get should be concurrently secure") { }
xtest("get should be concurrently secure") { }
test("GameStateRepository should build the projection when a new event occurs") {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
eventHandler
.handle { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
.also { event ->
assertNotNull(repo.getUntil(event)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
assertNotNull(repo.getLast(aggregateId)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
}
stopKoin()
}
xtest("getUntil should build the state until the event") { }
xtest("call getUntil twice should get the state from the cache") { }
xtest("getUntil should be concurrently secure") { }
test("get should build the last version of the state") {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
eventHandler
.handle { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
.also {
assertNotNull(repo.getLast(aggregateId)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
eventHandler
.handle { NewPlayerEvent(aggregateId = aggregateId, player = player2, version = it) }
.also {
assertNotNull(repo.getLast(aggregateId)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1, player2)
}
}
}
}
test("getUntil should build the state until the event") {
repeat(10) {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
val event1 =
eventHandler
.handle { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
.also { event1 ->
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
eventHandler
.handle { NewPlayerEvent(aggregateId = aggregateId, player = player2, version = it) }
.also { event2 ->
assertNotNull(repo.getUntil(event2)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1, player2)
}
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
}
}
}
test("getUntil should be concurrently secure") {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
(1..10)
.map { r ->
GlobalScope
.launch {
repeat(100) { r2 ->
val playerX = Player("player$r$r2")
eventHandler
.handle {
NewPlayerEvent(
aggregateId = aggregateId,
player = playerX,
version = it,
)
}
}
}
}.joinAll()
repo.getLast(aggregateId).players shouldHaveSize 1000
repo.getLast(aggregateId).lastEventVersion shouldBeEqual 1000
}
}
xtest("get should be concurrently secure") { }
})

View File

@@ -0,0 +1,125 @@
package eventDemo.app.event.projection
import eventDemo.libs.event.AggregateId
import eventDemo.libs.event.Event
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import java.util.UUID
import kotlin.test.assertNotNull
@OptIn(DelicateCoroutinesApi::class)
class ProjectionSnapshotRepositoryInMemoryTest :
FunSpec({
test("when call applyAndPutToCache, the getUntil method must be use the built projection cache") {
repeat(10) {
val repo = getRepoTest()
val aggregateId = IdTest()
val eventOther = Event2Test(value2 = "valOther", version = 1, aggregateId = IdTest())
repo.applyAndPutToCache(eventOther)
assertNotNull(repo.getUntil(eventOther)).also {
assertNotNull(it.value) shouldBeEqual "valOther"
}
val event1 = Event1Test(value1 = "val1", version = 1, aggregateId = aggregateId)
repo.applyAndPutToCache(event1)
assertNotNull(repo.getLast(event1.aggregateId)).also {
assertNotNull(it.value) shouldBeEqual "val1"
}
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.value) shouldBeEqual "val1"
}
val event2 = Event2Test(value2 = "val2", version = 2, aggregateId = aggregateId)
repo.applyAndPutToCache(event2)
assertNotNull(repo.getLast(event2.aggregateId)).also {
assertNotNull(it.value) shouldBeEqual "val1val2"
}
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.value) shouldBeEqual "val1"
}
assertNotNull(repo.getUntil(event2)).also {
assertNotNull(it.value) shouldBeEqual "val1val2"
}
}
}
test("ProjectionSnapshotRepositoryInMemory should be thread safe") {
val repo = getRepoTest(2000)
val aggregateId = IdTest()
(1..10)
.map { r ->
GlobalScope.launch {
repeat(10) {
val eventX = EventXTest(num = 1, version = r, aggregateId = aggregateId)
repo.applyAndPutToCache(eventX)
}
}
}.joinAll()
assertNotNull(repo.getLast(aggregateId)).num shouldBeEqual 100
}
})
@JvmInline
private value class IdTest(
override val id: UUID = UUID.randomUUID(),
) : AggregateId
private data class ProjectionTest(
override val aggregateId: IdTest,
override val lastEventVersion: Int = 0,
var value: String? = null,
var num: Int = 0,
) : Projection<IdTest>
private sealed interface TestEvents : Event<IdTest>
private data class Event1Test(
override val eventId: UUID = UUID.randomUUID(),
override val aggregateId: IdTest,
override val createdAt: Instant = Clock.System.now(),
override val version: Int,
val value1: String,
) : TestEvents
private data class Event2Test(
override val eventId: UUID = UUID.randomUUID(),
override val aggregateId: IdTest,
override val createdAt: Instant = Clock.System.now(),
override val version: Int,
val value2: String,
) : TestEvents
private data class EventXTest(
override val eventId: UUID = UUID.randomUUID(),
override val aggregateId: IdTest,
override val createdAt: Instant = Clock.System.now(),
override val version: Int,
val num: Int,
) : TestEvents
private fun getRepoTest(maxSnapshotCacheSize: Int = 2000): ProjectionSnapshotRepositoryInMemory<TestEvents, ProjectionTest, IdTest> =
ProjectionSnapshotRepositoryInMemory(maxSnapshotCacheSize) { event ->
(this ?: ProjectionTest(event.aggregateId)).let { projection ->
when (event) {
is Event1Test -> {
projection.copy(value = (projection.value ?: "") + event.value1)
}
is Event2Test -> {
projection.copy(value = (projection.value ?: "") + event.value2)
}
is EventXTest -> {
projection.copy(num = projection.num + event.num)
}
}
}
}

View File

@@ -50,7 +50,7 @@ class GameStateRouteTest :
}.apply {
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
val state = call.body<GameState>()
id shouldBeEqual state.gameId
id shouldBeEqual state.aggregateId
state.players shouldHaveSize 0
state.isStarted shouldBeEqual false
}
@@ -71,19 +71,20 @@ class GameStateRouteTest :
val eventHandler by inject<GameEventHandler>()
val stateRepo by inject<GameStateRepository>()
runBlocking {
eventHandler.handle(
NewPlayerEvent(gameId, player1),
NewPlayerEvent(gameId, player2),
PlayerReadyEvent(gameId, player1),
PlayerReadyEvent(gameId, player2),
eventHandler.handle { NewPlayerEvent(gameId, player1, it) }
eventHandler.handle { NewPlayerEvent(gameId, player2, it) }
eventHandler.handle { PlayerReadyEvent(gameId, player1, it) }
eventHandler.handle { PlayerReadyEvent(gameId, player2, it) }
eventHandler.handle {
GameStartedEvent.new(
gameId,
setOf(player1, player2),
shuffleIsDisabled = true,
),
)
it,
)
}
delay(100)
lastPlayedCard = stateRepo.get(gameId).playableCards(player1).first()
lastPlayedCard = stateRepo.getLast(gameId).playableCards(player1).first()
assertNotNull(lastPlayedCard)
.let { assertIs<Card.NumericCard>(lastPlayedCard) }
.let {
@@ -91,13 +92,14 @@ class GameStateRouteTest :
it.color shouldBeEqual Card.Color.Red
}
delay(100)
eventHandler.handle(
eventHandler.handle {
CardIsPlayedEvent(
gameId,
assertNotNull(lastPlayedCard),
player1,
),
)
it,
)
}
delay(100)
}
}

View File

@@ -32,127 +32,131 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import org.koin.dsl.koinApplication
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
@DelicateCoroutinesApi
class GameStateTest :
FunSpec({
test("Simulation of a game") {
disableShuffleDeck()
val id = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
val channelCommand1 = Channel<GameCommand>(Channel.BUFFERED)
val channelCommand2 = Channel<GameCommand>(Channel.BUFFERED)
val channelNotification1 = Channel<Notification>(Channel.BUFFERED)
val channelNotification2 = Channel<Notification>(Channel.BUFFERED)
withTimeout(2.seconds) {
disableShuffleDeck()
val id = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
val channelCommand1 = Channel<GameCommand>(Channel.BUFFERED)
val channelCommand2 = Channel<GameCommand>(Channel.BUFFERED)
val channelNotification1 = Channel<Notification>(Channel.BUFFERED)
val channelNotification2 = Channel<Notification>(Channel.BUFFERED)
var playedCard1: Card? = null
var playedCard2: Card? = null
var playedCard1: Card? = null
var playedCard2: Card? = null
val player1Job =
launch {
channelCommand1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)))
channelNotification1.receive().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
}
channelNotification1.receive().let {
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
}
channelCommand1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)))
channelNotification1.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
}
val player1Hand =
val player1Job =
launch {
channelCommand1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)))
channelNotification1.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
}
playedCard1 = player1Hand.first()
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
channelNotification1.receive().let {
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
}
}
channelCommand1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())))
channelCommand1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)))
channelNotification1.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
}
val player1Hand =
channelNotification1.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
playedCard1 = player1Hand.first()
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelCommand1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())))
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
channelNotification1.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player2
card shouldBeEqual assertNotNull(playedCard2)
}
}
}
channelNotification1.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player2
card shouldBeEqual assertNotNull(playedCard2)
}
}
}
val player2Job =
launch {
delay(100)
channelCommand2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)))
channelNotification2.receive().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
}
channelNotification2.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
}
channelCommand2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)))
val player2Hand =
val player2Job =
launch {
delay(100)
channelCommand2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)))
channelNotification2.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
}
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
channelNotification2.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
}
channelCommand2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)))
val player2Hand =
channelNotification2.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelNotification2.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player1
card shouldBeEqual assertNotNull(playedCard1)
}
}
playedCard2 = player2Hand.first()
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
channelCommand2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())))
}
channelNotification2.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player1
card shouldBeEqual assertNotNull(playedCard1)
}
koinApplication { modules(appKoinModule) }.koin.apply {
val commandHandler by inject<GameCommandHandler>()
val eventStream by inject<GameEventStream>()
val playerNotificationListener by inject<PlayerNotificationEventListener>()
ReactionEventListener(get(), get(), get()).init()
playerNotificationListener.startListening(channelNotification1, player1)
playerNotificationListener.startListening(channelNotification2, player2)
GlobalScope.launch(Dispatchers.IO) {
commandHandler.handle(player1, channelCommand1, channelNotification1)
}
playedCard2 = player2Hand.first()
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
GlobalScope.launch(Dispatchers.IO) {
commandHandler.handle(player2, channelCommand2, channelNotification2)
}
channelCommand2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())))
joinAll(player1Job, player2Job)
val state = id.buildStateFromEventStream(eventStream)
state.aggregateId shouldBeEqual id
assertTrue(state.isStarted)
state.players shouldBeEqual setOf(player1, player2)
state.readyPlayers shouldBeEqual setOf(player1, player2)
state.direction shouldBeEqual GameState.Direction.CLOCKWISE
assertNotNull(state.cardOnCurrentStack) shouldBeEqual GameState.LastCard(assertNotNull(playedCard2), player2)
}
koinApplication { modules(appKoinModule) }.koin.apply {
val commandHandler by inject<GameCommandHandler>()
val eventStream by inject<GameEventStream>()
val playerNotificationListener by inject<PlayerNotificationEventListener>()
ReactionEventListener(get(), get(), get()).init()
playerNotificationListener.startListening(channelNotification1, player1)
playerNotificationListener.startListening(channelNotification2, player2)
GlobalScope.launch(Dispatchers.IO) {
commandHandler.handle(player1, channelCommand1, channelNotification1)
}
GlobalScope.launch(Dispatchers.IO) {
commandHandler.handle(player2, channelCommand2, channelNotification2)
}
joinAll(player1Job, player2Job)
val state = id.buildStateFromEventStream(eventStream)
state.gameId shouldBeEqual id
assertTrue(state.isStarted)
state.players shouldBeEqual setOf(player1, player2)
state.readyPlayers shouldBeEqual setOf(player1, player2)
state.direction shouldBeEqual GameState.Direction.CLOCKWISE
assertNotNull(state.cardOnCurrentStack) shouldBeEqual GameState.LastCard(assertNotNull(playedCard2), player2)
}
}
})

View File

@@ -0,0 +1,42 @@
package eventDemo.libs.event
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
@OptIn(DelicateCoroutinesApi::class)
class VersionBuilderLocalTest :
FunSpec({
test("buildNextVersion") {
VersionBuilderLocal().run {
buildNextVersion() shouldBeEqual 1
buildNextVersion() shouldBeEqual 2
buildNextVersion() shouldBeEqual 3
}
}
test("buildNextVersion concurrently") {
val versionBuilder = VersionBuilderLocal()
(1..20)
.map {
GlobalScope.launch {
(1..1000).map {
versionBuilder.buildNextVersion()
}
}
}.joinAll()
versionBuilder.getLastVersion() shouldBeEqual 20 * 1000
}
test("getLastVersion") {
VersionBuilderLocal().run {
getLastVersion() shouldBeEqual 0
getLastVersion() shouldBeEqual 0
getLastVersion() shouldBeEqual 0
}
}
})