Skip to content

Commit

Permalink
Tests for utils (#20)
Browse files Browse the repository at this point in the history
* tests for failsafe

* tests for withRetry

* tests for failsafe

* apply format

* tests for failsafe

* tests for multy instance execution
  • Loading branch information
himadieievsv authored Jan 4, 2024
1 parent 5243658 commit caa35af
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ suspend inline fun <T> waitAnyJobs(
jobs.forEach { job -> job.cancel() }
// enough one success result to consider latch opened
if (results.isNotEmpty()) {
repeat(jobs.size - 1) { results.add(results.first()) }
repeat(jobs.size - results.size) { results.add(results.first()) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package io.redpulsar.core.locks.excecutors

import TestTags
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.redpulsar.core.locks.abstracts.Backend
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Tag
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import kotlin.random.Random
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@Tag(TestTags.UNIT)
class MultyInstanceExecutorTest {
private var backends: List<TestBackend> = emptyList()
private lateinit var scope: CoroutineScope

@BeforeEach
fun setUp() {
backends = createBackends(1)
scope = CoroutineScope(Dispatchers.Default)
}

@ParameterizedTest(name = "all {0} instances are ok")
@ValueSource(ints = [1, 2, 3, 4, 5, 7, 10])
fun `all instances are ok`(number: Int) {
backends = createBackends(number)
backends.forEach { backend -> every { backend.test() } returns "OK" }

val result =
multyInstanceExecute(
backends = backends,
scope = scope,
releaseTime = 1.seconds,
waiter = ::waitAllJobs,
) { backend ->
backend.test()
}

assertEquals(createResponses(number), result)
verify(exactly = 1) { backends.forEach { backend -> backend.test() } }
}

@ParameterizedTest(name = "quorum instance count is down {0} instances")
@ValueSource(ints = [2, 3, 4, 5, 7, 10])
fun `quorum instance count is down`(number: Int) {
val quorum = number / 2 + 1
backends = createBackends(number)
backends.forEach { backend -> every { backend.test() } returns "OK" }
repeat(quorum) { i ->
every { backends[i].test() } returns null
}

val result =
multyInstanceExecute(
backends = backends,
scope = scope,
releaseTime = 1.seconds,
waiter = ::waitAllJobs,
) { backend ->
backend.test()
}

assertEquals(emptyList<String>(), result)
verify(exactly = 1) { backends.forEach { backend -> backend.test() } }
}

@ParameterizedTest(name = "non quorum instance count is down {0} instances")
@ValueSource(ints = [2, 3, 4, 5, 7, 10])
fun `non quorum instance count is down`(number: Int) {
val quorum = number / 2 + 1
backends = createBackends(number)
backends.forEach { backend -> every { backend.test() } returns "OK" }
repeat(number - quorum) { i ->
every { backends[i].test() } returns null
}

val result =
multyInstanceExecute(
backends = backends,
scope = scope,
releaseTime = 1.seconds,
waiter = ::waitAllJobs,
) { backend ->
backend.test()
}

assertEquals(createResponses(quorum), result)
verify(exactly = 1) { backends.forEach { backend -> backend.test() } }
}

@ParameterizedTest(name = "all instances are ok, wait any with {0} instances")
@ValueSource(ints = [1, 2, 3, 4, 5, 7, 10])
fun `all instances are ok, wait any`(number: Int) {
backends = createBackends(number)
backends.forEach { backend -> every { backend.test() } returns "OK" }

val result =
multyInstanceExecute(
backends = backends,
scope = scope,
releaseTime = 1.seconds,
waiter = ::waitAnyJobs,
) { backend -> backend.test() }

assertEquals(createResponses(number), result)
verify(exactly = 1) { backends.forEach { backend -> backend.test() } }
}

@ParameterizedTest(name = "all instances are ok, wait any with {0} instances")
@ValueSource(ints = [1, 2, 3, 4, 5, 7, 10])
fun `one instance is up, wait any`(number: Int) {
backends = createBackends(number)
backends.forEach { backend -> every { backend.test() } returns null }
every { backends[Random.nextInt(0, number)].test() } returns "OK"

val result =
multyInstanceExecute(
backends = backends,
scope = scope,
releaseTime = 1.seconds,
waiter = ::waitAnyJobs,
) { backend -> backend.test() }

assertEquals(createResponses(number), result)
verify(exactly = 1) { backends.forEach { backend -> backend.test() } }
}

@ParameterizedTest(name = "retry on non quorum instance count is down {0} instances")
@ValueSource(ints = [2, 3, 4, 5, 7, 10])
fun `retry on non quorum instance count is down`(number: Int) {
val quorum = number / 2 + 1
backends = createBackends(number)
backends.forEach { backend -> every { backend.test() } returns "OK" }
repeat(quorum) { i ->
every { backends[i].test() } returns null
}

val result =
multyInstanceExecuteWithRetry(
backends = backends,
scope = scope,
releaseTime = 1.seconds,
retryCount = 3,
retryDelay = 1.milliseconds,
waiter = ::waitAllJobs,
) { backend ->
backend.test()
}

assertEquals(emptyList<String>(), result)
verify(exactly = 3) { backends.forEach { backend -> backend.test() } }
}

private abstract inner class TestBackend : Backend() {
abstract fun test(): String?
}

private fun createBackends(number: Int): List<TestBackend> {
val backends = mutableListOf<TestBackend>()
repeat(number) {
backends.add(mockk<TestBackend>())
}
return backends
}

private fun createResponses(number: Int): List<String> {
val responses = mutableListOf<String>()
repeat(number) {
responses.add("OK")
}
return responses
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.redpulsar.core.utils

import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows

@Tag(TestTags.UNIT)
class FailsafeTest {
@Test
fun `returned value correctly`() {
val returnValue =
failsafe(0) {
"OK"
}
assertEquals("OK", returnValue)
}

@Test
fun `supress top level exception`() {
assertDoesNotThrow {
failsafe(0) {
throw Exception("test")
}
}
}

@Test
fun `throwable is not captured`() {
assertThrows<Throwable> {
failsafe(0) {
throw mockk<Throwable>()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.redpulsar.core.utils

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import kotlin.system.measureTimeMillis
import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.milliseconds

@Tag(TestTags.UNIT)
class WithRetryTest {
@Test
fun `doesn't retry`() {
var counter = 0
val returnVal =
withRetry(3, 1.milliseconds) {
counter++
listOf("OK")
}

assertEquals(1, counter)
assertEquals(listOf("OK"), returnVal)
}

@ParameterizedTest(name = "retry count with ttl - {0}")
@ValueSource(ints = [-123, -1, 0, 1, 2, 5, 10, 11, 12, 20, 40])
fun `check retry count`(withCount: Int) {
var counter = 0
val returnVal =
withRetry(withCount, 1.microseconds) {
counter++
emptyList<Int>()
}
if (withCount > 0) {
assertEquals(withCount, counter)
} else {
assertEquals(1, counter)
}
assertEquals(emptyList<Int>(), returnVal)
}

@Test
fun `retry with negative delay is ignored`() {
var counter = 0
val returnVal =
withRetry(3, (-1).milliseconds) {
counter++
emptyList<Int>()
}
assertEquals(3, counter)
assertEquals(emptyList<Int>(), returnVal)
}

@Test
fun `check exponential delay`() {
var counter = 0
val time =
measureTimeMillis {
val returnVal =
withRetry(4, 50.milliseconds) {
counter++
emptyList<Int>()
}
assertEquals(emptyList<Int>(), returnVal)
}

assertEquals(4, counter)
// 50 + 100 + 200 + 400 = 750, 45 - is allowed clock error
assertTrue(time in 750 - 45..750 + 45)
}
}

0 comments on commit caa35af

Please sign in to comment.