Last active
September 28, 2022 13:57
-
-
Save SardarkhanStella/def37394d57baa6734749d00903e5fe0 to your computer and use it in GitHub Desktop.
Coroutine Testing with different Builder, Scope and Cancellation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.util.Log | |
import kotlinx.coroutines.* | |
import org.junit.Test | |
import java.io.IOException | |
import kotlin.math.pow | |
class AsyncCoroutineBuilderDemoTest { | |
@Test | |
fun correctUseOfCoroutinesVariant1() = runBlocking { | |
withContext(Dispatchers.Default) { | |
val deferreds = mutableListOf<Deferred<Int>>() | |
for (duration in 1..5) { | |
deferreds.add( | |
async { | |
val startTimeNano = System.nanoTime() | |
var iterations = 0 | |
while (System.nanoTime() < startTimeNano + (duration * 10f.pow(9))) { | |
iterations++ | |
} | |
iterations | |
} | |
) | |
} | |
var totalIterations = 0 | |
for (deferred in deferreds) { | |
totalIterations += deferred.await() | |
} | |
println("total iterations: $totalIterations") | |
} | |
} | |
@Test | |
fun correctUseOfCoroutinesVariant2() = runBlocking { | |
withContext(Dispatchers.Default) { | |
val totalIterations = (1..5).toList().map { duration -> | |
async { | |
val startTimeNano = System.nanoTime() | |
var iterations = 0 | |
while (System.nanoTime() < startTimeNano + (duration * 10f.pow(9))) { | |
iterations++ | |
} | |
iterations | |
} | |
}.awaitAll().sum() | |
println("total iterations: $totalIterations") | |
} | |
} | |
@Test | |
fun main() = runBlocking { | |
val asyncJob = GlobalScope.launch { | |
println("1. Exception created via launch coroutine") | |
// Will be printed to the console by | |
// Thread.defaultUncaughtExceptionHandler | |
throw IndexOutOfBoundsException() | |
} | |
try { | |
asyncJob.invokeOnCompletion { exception-> | |
println("exception. $exception") | |
} | |
asyncJob.join() | |
println("2. Joined failed job") | |
}catch (e:Exception){ | |
println("1.1 Exception caught ${e.message}") | |
} | |
val deferred = GlobalScope.async { | |
println("3. Exception created via async coroutine") | |
// Nothing is printed, relying on user to call await | |
throw ArithmeticException() | |
} | |
try { | |
deferred.await() | |
println("4. Unreachable, this statement is never executed") | |
} catch (e: Exception) { | |
println("5. Caught ${e.javaClass.simpleName}") | |
} | |
} | |
@Test | |
fun mainFunc() { | |
runBlocking { | |
// 1 | |
val exceptionHandler = CoroutineExceptionHandler { _, | |
exception -> | |
println("Caught $exception") | |
} | |
// 2 | |
val job = GlobalScope.launch(exceptionHandler) { | |
throw AssertionError("My Custom Assertion Error!") | |
} | |
// 3 | |
val deferred = GlobalScope.async(exceptionHandler) { | |
// Nothing will be printed, | |
// relying on user to call deferred.await() | |
throw ArithmeticException() | |
} | |
// 4 | |
// This suspends current coroutine until all given jobs are complete. | |
joinAll(job, deferred) | |
} | |
} | |
@Test | |
fun throwExceptionWhenDefferedCalled() { | |
runBlocking { | |
// Set this to 'true' to call await on the deferred variable | |
val callAwaitOnDeferred = true | |
val deferred = GlobalScope.async { | |
// This statement will be printed with or without | |
// a call to await() | |
println("Throwing exception from async") | |
throw ArithmeticException("Something Crashed") | |
// Nothing is printed, relying on a call to await() | |
} | |
if (callAwaitOnDeferred) { | |
try { | |
deferred.await() | |
} catch (e: ArithmeticException) { | |
println("Caught ArithmeticException") | |
} | |
} | |
} | |
} | |
@Test | |
fun childExceptionHandling() = runBlocking { | |
// Global Exception Handler | |
val handler = CoroutineExceptionHandler { _, exception -> | |
println("Caught $exception with suppressed${exception.suppressed?.contentToString()}") | |
} | |
// Parent Job | |
val parentJob = GlobalScope.launch(handler) { | |
// Child Job 1 | |
launch { | |
try { | |
delay(Long.MAX_VALUE) | |
} catch (e: Exception) { | |
println("${e.javaClass.simpleName} in Child Job 1") | |
} finally { | |
println("Finally") | |
throw ArithmeticException() | |
} | |
} | |
// Child Job 3 | |
launch { | |
try { | |
delay(Long.MAX_VALUE) | |
} catch (e: Exception) { | |
println("${e.javaClass.simpleName} in Child Job 3") | |
} finally { | |
println("Finally") | |
throw ClassNotFoundException() | |
} | |
} | |
// Child Job 2 | |
launch { | |
println("Job2 Breaks") | |
delay(100) | |
throw IllegalStateException() | |
} | |
// Delaying the parentJob | |
delay(Long.MAX_VALUE) | |
} | |
// Wait until parentJob completes | |
parentJob.join() | |
} | |
@Test | |
fun childWithSupervisorScope() = runBlocking { | |
// 1 | |
val supervisor = SupervisorJob() | |
with(CoroutineScope(coroutineContext + supervisor)) { | |
// 3 | |
val topChild = launch { | |
try { | |
delay(5000) | |
} catch (e: CancellationException) { | |
println("Third child cancelled because supervisor got cancelled.") | |
} | |
} | |
// 2 | |
val firstChild = launch { | |
println("First child throwing an exception") | |
try { | |
throw ArithmeticException() | |
}catch (e:Exception){ | |
println("exception first child ${e.message}") | |
} | |
} | |
// 3 | |
val secondChild = launch { | |
println("First child is cancelled: ${firstChild.isCancelled}") | |
try { | |
delay(5000) | |
} catch (e: CancellationException) { | |
println("Second child cancelled because supervisor got cancelled.") | |
} | |
} | |
// 4 | |
println("Second child is active: ${secondChild.isActive}") | |
firstChild.join() | |
supervisor.cancel() | |
println("Join all.") | |
secondChild.join() | |
topChild.join() | |
} | |
} | |
@Test | |
fun cancelableException() = runBlocking { | |
// 1 | |
val handler = CoroutineExceptionHandler { _, exception -> | |
// 6 | |
println("Caught original $exception") | |
} | |
// 2 | |
val parentJob = GlobalScope.launch(handler) { | |
val childJob = launch { | |
// 4 | |
throw IOException() | |
} | |
try { | |
childJob.join() | |
} catch (e: CancellationException) { | |
// 5 | |
println("Rethrowing CancellationException with original cause: ${e.cause}") | |
throw e | |
} | |
} | |
// 3 | |
parentJob.join() | |
} | |
@Test | |
fun joinCoroutineExample() = runBlocking { | |
val job = launch { | |
println("Crunching numbers [Beep.Boop.Beep]...") | |
delay(1000L) | |
} | |
// waits for job's completion | |
job.join() | |
println("main: Now I can quit.") | |
} | |
@Test | |
fun joinAllCalls() = runBlocking { | |
val jobOne = launch(Dispatchers.Default) { | |
println("Job 1: Crunching numbers [Beep.Boop.Beep]...") | |
delay(5000L) | |
} | |
val jobTwo = async(Dispatchers.Default) { | |
println("Job 2: Crunching numbers [Beep.Boop.Beep]...") | |
delay(2000L) | |
} | |
// waits for both the jobs to complete | |
joinAll(jobOne, jobTwo) | |
println("main: Now I can quit.") | |
} | |
@Test | |
fun cancelAndJoin() = runBlocking { | |
coroutineScope { | |
val job = launch { | |
repeat(1000) { i -> | |
println("$i. Crunching numbers [Beep.Boop.Beep]…") | |
delay(500L) | |
} | |
} | |
delay(1300L) // delay a bit | |
println("main: I am tired of waiting!") | |
// cancels the job and waits for job’s completion | |
job.cancelAndJoin() | |
println("main: Now I can quit.") | |
} | |
} | |
@Test | |
fun cancelChildren() = runBlocking { | |
val parentJob = launch { | |
val childOne = launch { | |
repeat(1000) { i -> | |
println("Child Coroutine 1: " + | |
"$i. Crunching numbers [Beep.Boop.Beep]…") | |
delay(500L) | |
} | |
} | |
// Handle the exception thrown from `launch` | |
// coroutine builder | |
childOne.invokeOnCompletion { exception -> | |
println("Child One: ${exception?.message}") | |
} | |
val childTwo = launch { | |
repeat(1000) { i -> | |
println("Child Coroutine 2: " + | |
"$i. Crunching numbers [Beep.Boop.Beep]…") | |
delay(500L) | |
} | |
} | |
// Handle the exception thrown from `launch` | |
// coroutine builder | |
childTwo.invokeOnCompletion { exception -> | |
println("Child Two: ${exception?.message}") | |
} | |
} | |
delay(1200L) | |
println("Calling cancelChildren() on the parentJob") | |
parentJob.cancelChildren() | |
println("parentJob isActive: ${parentJob.isActive}") | |
} | |
@Test | |
fun timeoutCancellationTryCatch() = runBlocking { | |
try { | |
withTimeout(1500L) { | |
repeat(1000) { i -> | |
println("$i. Crunching numbers [Beep.Boop.Beep]...") | |
delay(500L) | |
} | |
} | |
} catch (e: TimeoutCancellationException) { | |
println("Caught ${e.javaClass.simpleName}") | |
} | |
} | |
@Test | |
fun timeoutCancellation() = runBlocking { | |
withTimeout(1500L) { | |
repeat(1000) { i -> | |
println("$i. Crunching numbers [Beep.Boop.Beep]...") | |
delay(500L) | |
} | |
} | |
} | |
@Test | |
fun withTimeoutOrNull() = runBlocking { | |
val result = withTimeoutOrNull(1300L) { | |
repeat(1000) { i -> | |
println("$i. Crunching numbers [Beep.Boop.Beep]...") | |
delay(500L) | |
} | |
"Done" // will get canceled before it produces this result | |
} | |
// Result will be `null` | |
println("Result is $result") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment