From c16123e0841a0eca57b3cf68f4db8ab5a26205a4 Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Wed, 20 Aug 2025 00:10:43 +0100 Subject: [PATCH 1/2] WIP Add coroutines examples --- README.md | 24 +- api/Kotlin-Lab.api | 164 +++++++++++ config/main.md.bak | 2 +- .../concurrency/coroutines/tasks/BaseTask.kt | 61 ++++ .../coroutines/tasks/BinaryTreeTask.kt | 59 ++++ .../coroutines/tasks/CompressionTask.kt | 52 ++++ .../coroutines/tasks/CryptographicTask.kt | 67 +++++ .../coroutines/tasks/GraphAlgorithmsTask.kt | 77 +++++ .../coroutines/tasks/HashComputationTask.kt | 36 +++ .../coroutines/tasks/ImageProcessingTask.kt | 48 +++ .../concurrency/coroutines/tasks/Main.kt | 40 +++ .../coroutines/tasks/MandelbrotTask.kt | 53 ++++ .../tasks/MatrixMultiplicationTask.kt | 37 +++ .../coroutines/tasks/MergeSortTask.kt | 80 +++++ .../coroutines/tasks/NeuralNetworkTask.kt | 67 +++++ .../coroutines/tasks/PrimeCalculationTask.kt | 46 +++ .../coroutines/tasks/QuickSortTask.kt | 66 +++++ .../coroutines/tasks/SortingTask.kt | 35 +++ .../coroutines/tasks/StringMatchingTask.kt | 85 ++++++ .../concurrency/coroutines/tasks/Task.kt | 12 + .../coroutines/tasks/TaskResult.kt | 8 + .../coroutines/tasks/TaskStatus.kt | 5 + .../coroutines/tasks/TaskViewModel.kt | 87 ++++++ .../ArrayBlockingQueueLinearizabilityTest.kt | 2 + .../coroutines/tasks/BaseTaskTest.kt | 248 ++++++++++++++++ .../coroutines/tasks/CompressionTaskTest.kt | 32 ++ .../coroutines/tasks/CryptographicTaskTest.kt | 27 ++ .../tasks/FlowTestingWithTurbine.kt | 69 +++++ .../coroutines/tasks/GraphTreeTasksTest.kt | 46 +++ .../tasks/ImageProcessingTaskTest.kt | 35 +++ .../coroutines/tasks/IntegrationTest.kt | 85 ++++++ .../coroutines/tasks/NeuralNetworkTaskTest.kt | 50 ++++ .../tasks/ParameterizedTaskTests.kt | 87 ++++++ .../coroutines/tasks/PerformanceTest.kt | 62 ++++ .../tasks/PrimeCalculationTaskTest.kt | 99 +++++++ .../coroutines/tasks/SortingTasksTest.kt | 111 +++++++ .../tasks/StringMatchingTaskTest.kt | 31 ++ .../coroutines/tasks/TaskViewModelTest.kt | 274 ++++++++++++++++++ 38 files changed, 2456 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphAlgorithmsTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/HashComputationTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Main.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MandelbrotTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MatrixMultiplicationTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MergeSortTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/QuickSortTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTask.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Task.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskResult.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskStatus.kt create mode 100644 src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModel.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/FlowTestingWithTurbine.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphTreeTasksTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ParameterizedTaskTests.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PerformanceTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTasksTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTaskTest.kt create mode 100644 src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModelTest.kt diff --git a/README.md b/README.md index 4ba76f6c..deff296b 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,23 @@ ### Metrics ```text -15288 number of properties -10573 number of functions -8954 number of classes -240 number of packages -3543 number of kt files +15600 number of properties +10708 number of functions +9006 number of classes +241 number of packages +3579 number of kt files ``` ### Complexity Report ```text -267458 lines of code (loc) -166374 source lines of code (sloc) -121548 logical lines of code (lloc) -72562 comment lines of code (cloc) -25100 cyclomatic complexity (mcc) -20431 cognitive complexity +270027 lines of code (loc) +168414 source lines of code (sloc) +122999 logical lines of code (lloc) +72635 comment lines of code (cloc) +25394 cyclomatic complexity (mcc) +20712 cognitive complexity 0 number of total code smells 43 comment source ratio 206 mcc per 1,000 lloc -``` +``` \ No newline at end of file diff --git a/api/Kotlin-Lab.api b/api/Kotlin-Lab.api index cb19df6c..1cd159be 100644 --- a/api/Kotlin-Lab.api +++ b/api/Kotlin-Lab.api @@ -20091,6 +20091,170 @@ public final class dev/shtanko/concurrency/coroutines/sort/CoroutinesMergeSort { public static synthetic fun perform$default (Ldev/shtanko/concurrency/coroutines/sort/CoroutinesMergeSort;[IIILjava/lang/Object;)[I } +public abstract class dev/shtanko/concurrency/coroutines/tasks/BaseTask : dev/shtanko/concurrency/coroutines/tasks/Task { + public fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun cancel ()V + protected abstract fun execute (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getDescription ()Ljava/lang/String; + public fun getName ()Ljava/lang/String; + public fun getProgress ()Lkotlinx/coroutines/flow/StateFlow; + public fun getStatus ()Lkotlinx/coroutines/flow/StateFlow; + public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun updateProgress (F)V + protected final fun updateStatus (Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask$TreeNode { + public fun (I)V + public final fun getLeft ()Ldev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask$TreeNode; + public final fun getRight ()Ldev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask$TreeNode; + public final fun getValue ()I + public final fun setLeft (Ldev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask$TreeNode;)V + public final fun setRight (Ldev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask$TreeNode;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/CompressionTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/CryptographicTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/GraphAlgorithmsTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/HashComputationTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (II)V + public synthetic fun (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/MainKt { + public static final fun main ()V + public static synthetic fun main ([Ljava/lang/String;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/MandelbrotTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (III)V + public synthetic fun (IIIILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/MatrixMultiplicationTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/MergeSortTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (ILkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (ILkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (IIII)V + public synthetic fun (IIIIILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (ILkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (ILkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/QuickSortTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (ILkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (ILkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/SortingTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (I)V + public synthetic fun (IILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public final class dev/shtanko/concurrency/coroutines/tasks/StringMatchingTask : dev/shtanko/concurrency/coroutines/tasks/BaseTask { + public fun ()V + public fun (II)V + public synthetic fun (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V +} + +public abstract interface class dev/shtanko/concurrency/coroutines/tasks/Task { + public abstract fun cancel ()V + public abstract fun getDescription ()Ljava/lang/String; + public abstract fun getName ()Ljava/lang/String; + public abstract fun getProgress ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun getStatus ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/shtanko/concurrency/coroutines/tasks/TaskResult { + public fun (Ljava/lang/String;Ljava/lang/Object;JLdev/shtanko/concurrency/coroutines/tasks/TaskStatus;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Object; + public final fun component3 ()J + public final fun component4 ()Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public final fun copy (Ljava/lang/String;Ljava/lang/Object;JLdev/shtanko/concurrency/coroutines/tasks/TaskStatus;)Ldev/shtanko/concurrency/coroutines/tasks/TaskResult; + public static synthetic fun copy$default (Ldev/shtanko/concurrency/coroutines/tasks/TaskResult;Ljava/lang/String;Ljava/lang/Object;JLdev/shtanko/concurrency/coroutines/tasks/TaskStatus;ILjava/lang/Object;)Ldev/shtanko/concurrency/coroutines/tasks/TaskResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getExecutionTime ()J + public final fun getResult ()Ljava/lang/Object; + public final fun getStatus ()Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public final fun getTaskName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/shtanko/concurrency/coroutines/tasks/TaskStatus : java/lang/Enum { + public static final field CANCELLED Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public static final field COMPLETED Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public static final field ERROR Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public static final field IDLE Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public static final field RUNNING Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; + public static fun values ()[Ldev/shtanko/concurrency/coroutines/tasks/TaskStatus; +} + +public final class dev/shtanko/concurrency/coroutines/tasks/TaskViewModel { + public fun ()V + public fun (Lkotlinx/coroutines/CoroutineDispatcher;)V + public synthetic fun (Lkotlinx/coroutines/CoroutineDispatcher;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun addTask (Ldev/shtanko/concurrency/coroutines/tasks/Task;)V + public final fun cancelAllTasks ()V + public final fun clearResults ()V + public final fun clearTasks ()V + public final fun getResults ()Lkotlinx/coroutines/flow/StateFlow; + public final fun getTasks ()Lkotlinx/coroutines/flow/StateFlow; + public final fun onCleared ()V + public final fun runAllTasks ()V + public final fun runTask (Ldev/shtanko/concurrency/coroutines/tasks/Task;)V +} + public final class dev/shtanko/concurrency/jvm/deadlock/DeadLockSample { public static final field INSTANCE Ldev/shtanko/concurrency/jvm/deadlock/DeadLockSample; public static final fun main ([Ljava/lang/String;)V diff --git a/config/main.md.bak b/config/main.md.bak index 6c3efcab..45db2015 100644 --- a/config/main.md.bak +++ b/config/main.md.bak @@ -10,7 +10,7 @@ Hits-of-Code FOSSA Status CodeStyle - Kotlin Version + Kotlin Version Quality Gate Status Bugs Code Smells diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTask.kt new file mode 100644 index 00000000..bd0ce251 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTask.kt @@ -0,0 +1,61 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.withContext + +@Suppress("TooGenericExceptionCaught") +abstract class BaseTask( + override val name: String, + override val description: String, + private val dispatcher: CoroutineDispatcher = Dispatchers.Default, +) : Task { + private val _progress = MutableStateFlow(0f) + override val progress: StateFlow = _progress.asStateFlow() + + private val _status = MutableStateFlow(TaskStatus.IDLE) + override val status: StateFlow = _status.asStateFlow() + + private var job: Job? = null + + protected fun updateProgress(value: Float) { + _progress.value = value.coerceIn(0f, 1f) + } + + protected fun updateStatus(status: TaskStatus) { + _status.value = status + } + + override fun cancel() { + job?.cancel() + updateStatus(TaskStatus.CANCELLED) + } + + override suspend fun run(): R { + return withContext(dispatcher) { + job = coroutineContext[Job] + updateStatus(TaskStatus.RUNNING) + updateProgress(0f) + + try { + val result = execute() + updateStatus(TaskStatus.COMPLETED) + updateProgress(1f) + result + } catch (e: CancellationException) { + updateStatus(TaskStatus.CANCELLED) + throw e + } catch (e: Exception) { + updateStatus(TaskStatus.ERROR) + throw e + } + } + } + + protected abstract suspend fun execute(): R +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask.kt new file mode 100644 index 00000000..c7be0891 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/BinaryTreeTask.kt @@ -0,0 +1,59 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class BinaryTreeTask( + private val nodeCount: Int = 50_000, +) : BaseTask( + name = "Binary Tree Operations", + description = "Building and traversing $nodeCount nodes", +) { + class TreeNode(val value: Int) { + var left: TreeNode? = null + var right: TreeNode? = null + } + + override suspend fun execute(): String { + var root: TreeNode? = null + val values = List(nodeCount) { Random.nextInt(1000000) } + + // Build tree + for ((index, value) in values.withIndex()) { + if (index % 500 == 0) { + yield() + updateProgress(index.toFloat() / (nodeCount * 2)) + } + root = insert(root, value) + } + + // Calculate tree properties + val height = calculateHeight(root) + val nodeSum = sumNodes(root) + + return "Height: $height, Sum: $nodeSum" + } + + private fun insert(root: TreeNode?, value: Int): TreeNode { + if (root == null) return TreeNode(value) + + if (value < root.value) { + root.left = insert(root.left, value) + } else { + root.right = insert(root.right, value) + } + return root + } + + private suspend fun calculateHeight(node: TreeNode?): Int { + if (node == null) return 0 + return 1 + maxOf(calculateHeight(node.left), calculateHeight(node.right)) + } + + private suspend fun sumNodes(node: TreeNode?): Long { + if (node == null) return 0 + return node.value + sumNodes(node.left) + sumNodes(node.right) + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTask.kt new file mode 100644 index 00000000..170dbc60 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTask.kt @@ -0,0 +1,52 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class CompressionTask( + private val dataSize: Int = 100_000, +) : BaseTask( + name = "Data Compression", + description = "LZ77 compression of $dataSize bytes", +) { + override suspend fun execute(): Double { + val data = ByteArray(dataSize) { (Random.nextInt(26) + 'a'.code).toByte() } + val compressed = mutableListOf>() // (offset, length, next char) + + var i = 0 + while (i < data.size) { + if (i % 1000 == 0) { + yield() + updateProgress(i.toFloat() / dataSize) + } + + var maxLength = 0 + var maxOffset = 0 + val searchStart = maxOf(0, i - 4096) // Search window + + for (j in searchStart until i) { + var length = 0 + while (i + length < data.size && + length < 255 && + data[j + length] == data[i + length] + ) { + length++ + } + + if (length > maxLength) { + maxLength = length + maxOffset = i - j + } + } + + val nextChar = if (i + maxLength < data.size) data[i + maxLength] else 0 + compressed.add(Triple(maxOffset, maxLength, nextChar)) + i += maxLength + 1 + } + + val compressionRatio = compressed.size * 3.0 / dataSize + return compressionRatio + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTask.kt new file mode 100644 index 00000000..8c4680c0 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTask.kt @@ -0,0 +1,67 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class CryptographicTask( + private val messageCount: Int = 1000, +) : BaseTask( + name = "RSA Encryption", + description = "Encrypting $messageCount messages", +) { + override suspend fun execute(): Int { + // Small RSA parameters for demonstration + val p = 61 + val q = 53 + val n = p * q + val phi = (p - 1) * (q - 1) + val e = 17 // Public exponent + val d = modInverse(e, phi) // Private exponent + + var encryptedCount = 0 + + for (i in 0 until messageCount) { + if (i % 50 == 0) { + yield() + updateProgress(i.toFloat() / messageCount) + } + + val message = Random.nextInt(1, n) + val encrypted = modPow(message, e, n) + val decrypted = modPow(encrypted, d, n) + + if (decrypted == message) { + encryptedCount++ + } + } + + return encryptedCount + } + + private fun modPow(base: Int, exp: Int, mod: Int): Int { + var result = 1L + var b = base.toLong() + var e = exp + + while (e > 0) { + if (e % 2 == 1) { + result = (result * b) % mod + } + b = (b * b) % mod + e /= 2 + } + + return result.toInt() + } + + private fun modInverse(a: Int, m: Int): Int { + for (x in 1 until m) { + if ((a * x) % m == 1) { + return x + } + } + return 1 + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphAlgorithmsTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphAlgorithmsTask.kt new file mode 100644 index 00000000..c375f420 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphAlgorithmsTask.kt @@ -0,0 +1,77 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class GraphAlgorithmsTask( + private val vertices: Int = 1000, +) : BaseTask( + name = "Graph Algorithms", + description = "Finding shortest paths in $vertices vertices", +) { + override suspend fun execute(): String { + // Create adjacency matrix with random weights + val graph = Array(vertices) { IntArray(vertices) { 0 } } + val edges = vertices * 10 // Create sparse graph + + // Generate random edges + repeat(edges) { + val u = Random.nextInt(vertices) + val v = Random.nextInt(vertices) + if (u != v) { + graph[u][v] = Random.nextInt(1, 100) + graph[v][u] = graph[u][v] // Undirected graph + } + } + + // Run Dijkstra's algorithm from vertex 0 + val distances = dijkstra(graph, 0) + + // Find diameter (longest shortest path) + val maxDistance = distances.maxOrNull() ?: 0 + val avgDistance = distances.filter { it < Int.MAX_VALUE }.average() + + return "Max dist: $maxDistance, Avg: %.2f".format(avgDistance) + } + + private suspend fun dijkstra(graph: Array, start: Int): IntArray { + val dist = IntArray(vertices) { Int.MAX_VALUE } + val visited = BooleanArray(vertices) + dist[start] = 0 + + for (count in 0 until vertices - 1) { + if (count % 50 == 0) { + yield() + updateProgress(count.toFloat() / vertices) + } + + val u = minDistance(dist, visited) + visited[u] = true + + for (v in 0 until vertices) { + if (!visited[v] && graph[u][v] != 0 && + dist[u] != Int.MAX_VALUE && + dist[u] + graph[u][v] < dist[v] + ) { + dist[v] = dist[u] + graph[u][v] + } + } + } + + return dist + } + + private fun minDistance(dist: IntArray, visited: BooleanArray): Int { + var min = Int.MAX_VALUE + var minIndex = -1 + + for (v in 0 until vertices) { + if (!visited[v] && dist[v] <= min) { + min = dist[v] + minIndex = v + } + } + + return minIndex + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/HashComputationTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/HashComputationTask.kt new file mode 100644 index 00000000..be5750f8 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/HashComputationTask.kt @@ -0,0 +1,36 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.yield + +class HashComputationTask( + private val iterations: Int = 1_000_000, +) : BaseTask( + name = "Hash Computation", + description = "Computing $iterations hash iterations", +) { + override suspend fun execute(): String { + var hash = "initial".hashCode() + + for (i in 0 until iterations) { + if (i % 10_000 == 0) { + yield() + updateProgress(i.toFloat() / iterations) + } + + hash = computeHash(hash, i) + } + + return hash.toString(16).uppercase() + } + + private fun computeHash(seed: Int, iteration: Int): Int { + var result = seed + result = result xor iteration + result = result * 31 + iteration + result = result.rotateLeft(7) + result = result xor (result shr 16) + return result + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTask.kt new file mode 100644 index 00000000..efa25d92 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTask.kt @@ -0,0 +1,48 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class ImageProcessingTask( + private val width: Int = 800, + private val height: Int = 600, +) : BaseTask( + name = "Image Processing", + description = "Applying filters to ${width}x$height image", +) { + override suspend fun execute(): String { + val pixels = Array(height) { IntArray(width) { Random.nextInt(256) } } + val processed = Array(height) { IntArray(width) } + + // Apply Gaussian blur + val kernel = arrayOf( + intArrayOf(1, 2, 1), + intArrayOf(2, 4, 2), + intArrayOf(1, 2, 1), + ) + val kernelSum = 16 + + for (y in 1 until height - 1) { + if (y % 20 == 0) { + yield() + updateProgress(y.toFloat() / height) + } + + for (x in 1 until width - 1) { + var sum = 0 + for (ky in -1..1) { + for (kx in -1..1) { + sum += pixels[y + ky][x + kx] * kernel[ky + 1][kx + 1] + } + } + processed[y][x] = sum / kernelSum + } + } + + // Calculate average brightness + val avgBrightness = processed.map { it.toList() }.flatten().average() + return "Avg brightness: %.2f".format(avgBrightness) + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Main.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Main.kt new file mode 100644 index 00000000..4c7b5e79 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Main.kt @@ -0,0 +1,40 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +fun main(): Unit = runBlocking { + val viewModel = TaskViewModel() + + viewModel.addTask(PrimeCalculationTask(5000_0000)) + viewModel.addTask(MatrixMultiplicationTask(2000)) + viewModel.addTask(MandelbrotTask(10000, 10000)) + viewModel.addTask(SortingTask(100_000)) + viewModel.addTask(HashComputationTask(10000_0000)) + + viewModel.addTask(CryptographicTask(50000)) + viewModel.addTask(CompressionTask(50_0000)) + viewModel.addTask(BinaryTreeTask(30_0000)) + viewModel.addTask(GraphAlgorithmsTask(5000)) + viewModel.addTask(StringMatchingTask(500_0000, 50)) + viewModel.addTask(ImageProcessingTask(600, 400)) + viewModel.addTask(NeuralNetworkTask(epochs = 5)) + viewModel.addTask(MergeSortTask(900_000)) + viewModel.addTask(QuickSortTask(900_000)) + + launch { + viewModel.tasks.collect { + println("TASK: ${it}") + } + } + + launch { + viewModel.results.collect { + println("RES: ${it}") + } + } + + viewModel.runAllTasks() +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MandelbrotTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MandelbrotTask.kt new file mode 100644 index 00000000..d3950c59 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MandelbrotTask.kt @@ -0,0 +1,53 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.yield + +class MandelbrotTask( + private val width: Int = 400, + private val height: Int = 300, + private val maxIterations: Int = 256, +) : BaseTask( + name = "Mandelbrot Set", + description = "Calculating ${width}x$height fractal", +) { + override suspend fun execute(): Int { + var totalIterations = 0 + val totalPixels = width * height + var processedPixels = 0 + + for (y in 0 until height) { + if (y % 10 == 0) yield() + + for (x in 0 until width) { + val cx = (x - width / 2.0) * 4.0 / width + val cy = (y - height / 2.0) * 4.0 / height + + totalIterations += calculateMandelbrot(cx, cy) + processedPixels++ + + if (processedPixels % 1000 == 0) { + updateProgress(processedPixels.toFloat() / totalPixels) + } + } + } + + return totalIterations + } + + private fun calculateMandelbrot(cx: Double, cy: Double): Int { + var zx = 0.0 + var zy = 0.0 + var iteration = 0 + + while (zx * zx + zy * zy < 4.0 && iteration < maxIterations) { + val tmp = zx * zx - zy * zy + cx + zy = 2.0 * zx * zy + cy + zx = tmp + iteration++ + } + + return iteration + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MatrixMultiplicationTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MatrixMultiplicationTask.kt new file mode 100644 index 00000000..49c33b38 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MatrixMultiplicationTask.kt @@ -0,0 +1,37 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class MatrixMultiplicationTask( + private val size: Int = 300, +) : BaseTask( + name = "Matrix Multiplication", + description = "${size}x$size matrix multiplication", +) { + override suspend fun execute(): Int { + val matrixA = Array(size) { IntArray(size) { Random.nextInt(1, 10) } } + val matrixB = Array(size) { IntArray(size) { Random.nextInt(1, 10) } } + val result = Array(size) { IntArray(size) } + + val totalOperations = size * size + var completed = 0 + + for (i in 0 until size) { + yield() + + for (j in 0 until size) { + for (k in 0 until size) { + result[i][j] += matrixA[i][k] * matrixB[k][j] + } + completed++ + if (completed % 100 == 0) { + updateProgress(completed.toFloat() / totalOperations) + } + } + } + + // Return sum of diagonal elements as result + return (0 until size).sumOf { result[it][it] } + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MergeSortTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MergeSortTask.kt new file mode 100644 index 00000000..0d48b430 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/MergeSortTask.kt @@ -0,0 +1,80 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.yield + +class MergeSortTask( + private val arraySize: Int = 100_000, + dispatcher: CoroutineDispatcher = Dispatchers.Default, +) : BaseTask( + name = "Merge Sort", + description = "Sorting $arraySize elements using merge sort", + dispatcher = dispatcher, +) { + private var comparisons = 0L + private var totalOperations = 0 + private var currentOperation = 0 + + override suspend fun execute(): Long { + val array = IntArray(arraySize) { Random.nextInt() } + comparisons = 0L + totalOperations = (arraySize * kotlin.math.log2(arraySize.toDouble())).toInt() + currentOperation = 0 + + mergeSort(array, 0, arraySize - 1) + return comparisons + } + + private suspend fun mergeSort(arr: IntArray, left: Int, right: Int) { + if (left < right) { + val mid = left + (right - left) / 2 + + mergeSort(arr, left, mid) + mergeSort(arr, mid + 1, right) + merge(arr, left, mid, right) + + currentOperation += (right - left) + if (currentOperation % 1000 == 0) { + yield() + updateProgress(currentOperation.toFloat() / totalOperations) + } + } + } + + private fun merge(arr: IntArray, left: Int, mid: Int, right: Int) { + val leftArray = arr.sliceArray(left..mid) + val rightArray = arr.sliceArray(mid + 1..right) + + var i = 0 + var j = 0 + var k = left + + while (i < leftArray.size && j < rightArray.size) { + comparisons++ + if (leftArray[i] <= rightArray[j]) { + arr[k] = leftArray[i] + i++ + } else { + arr[k] = rightArray[j] + j++ + } + k++ + } + + while (i < leftArray.size) { + arr[k] = leftArray[i] + i++ + k++ + } + + while (j < rightArray.size) { + arr[k] = rightArray[j] + j++ + k++ + } + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTask.kt new file mode 100644 index 00000000..04b64dec --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTask.kt @@ -0,0 +1,67 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.math.exp +import kotlin.random.Random +import kotlinx.coroutines.yield + +class NeuralNetworkTask( + private val inputSize: Int = 784, + private val hiddenSize: Int = 128, + private val outputSize: Int = 10, + private val epochs: Int = 10, +) : BaseTask( + name = "Neural Network Training", + description = "Training ${hiddenSize}-node network for $epochs epochs", +) { + override suspend fun execute(): Double { + // Initialize weights + val weightsIH = Array(inputSize) { DoubleArray(hiddenSize) { Random.nextDouble(-1.0, 1.0) } } + val weightsHO = Array(hiddenSize) { DoubleArray(outputSize) { Random.nextDouble(-1.0, 1.0) } } + + // Training samples + val samples = 100 + var totalLoss = 0.0 + + for (epoch in 0 until epochs) { + for (sample in 0 until samples) { + if ((epoch * samples + sample) % 50 == 0) { + yield() + updateProgress((epoch * samples + sample).toFloat() / (epochs * samples)) + } + + // Forward pass + val input = DoubleArray(inputSize) { Random.nextDouble() } + val hidden = DoubleArray(hiddenSize) + val output = DoubleArray(outputSize) + + // Input to hidden + for (h in 0 until hiddenSize) { + var sum = 0.0 + for (i in 0 until inputSize) { + sum += input[i] * weightsIH[i][h] + } + hidden[h] = sigmoid(sum) + } + + // Hidden to output + for (o in 0 until outputSize) { + var sum = 0.0 + for (h in 0 until hiddenSize) { + sum += hidden[h] * weightsHO[h][o] + } + output[o] = sigmoid(sum) + } + + // Calculate loss + val target = DoubleArray(outputSize) { if (it == sample % outputSize) 1.0 else 0.0 } + for (o in 0 until outputSize) { + totalLoss += (target[o] - output[o]) * (target[o] - output[o]) + } + } + } + + return totalLoss / (epochs * samples) + } + + private fun sigmoid(x: Double): Double = 1.0 / (1.0 + exp(-x)) +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTask.kt new file mode 100644 index 00000000..cb48120e --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTask.kt @@ -0,0 +1,46 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.yield + +class PrimeCalculationTask( + private val limit: Int = 100_000, + dispatcher: CoroutineDispatcher = Dispatchers.Default, +) : BaseTask>( + name = "Prime Calculator", + description = "Finding prime numbers up to $limit", + dispatcher = dispatcher, +) { + override suspend fun execute(): List { + val primes = mutableListOf() + + for (num in 2..limit) { + if (num % 1000 == 0) { + yield() + updateProgress(num.toFloat() / limit) + } + + if (isPrime(num)) { + primes.add(num) + } + } + + return primes + } + + private fun isPrime(n: Int): Boolean { + if (n <= 1) return false + if (n <= 3) return true + if (n % 2 == 0 || n % 3 == 0) return false + + var i = 5 + while (i * i <= n) { + if (n % i == 0 || n % (i + 2) == 0) return false + i += 6 + } + return true + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/QuickSortTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/QuickSortTask.kt new file mode 100644 index 00000000..5177b16b --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/QuickSortTask.kt @@ -0,0 +1,66 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.yield + +class QuickSortTask( + private val arraySize: Int = 80_000, + dispatcher: CoroutineDispatcher = Dispatchers.Default, +) : BaseTask( + name = "Quick Sort", + description = "Sorting $arraySize elements using quick sort", + dispatcher = dispatcher, +) { + private var swaps = 0L + private var processedElements = 0 + + override suspend fun execute(): Long { + val array = IntArray(arraySize) { Random.nextInt() } + swaps = 0L + processedElements = 0 + + quickSort(array, 0, arraySize - 1) + return swaps + } + + private suspend fun quickSort(arr: IntArray, low: Int, high: Int) { + if (low < high) { + val pi = partition(arr, low, high) + + processedElements += (high - low) + if (processedElements % 1000 == 0) { + yield() + updateProgress(processedElements.toFloat() / (arraySize * 2)) + } + + quickSort(arr, low, pi - 1) + quickSort(arr, pi + 1, high) + } + } + + private fun partition(arr: IntArray, low: Int, high: Int): Int { + val pivot = arr[high] + var i = low - 1 + + for (j in low until high) { + if (arr[j] < pivot) { + i++ + val temp = arr[i] + arr[i] = arr[j] + arr[j] = temp + swaps++ + } + } + + val temp = arr[i + 1] + arr[i + 1] = arr[high] + arr[high] = temp + swaps++ + + return i + 1 + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTask.kt new file mode 100644 index 00000000..1034f15f --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTask.kt @@ -0,0 +1,35 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.random.Random +import kotlinx.coroutines.yield + +class SortingTask( + private val arraySize: Int = 50_000, +) : BaseTask( + name = "Array Sorting", + description = "Sorting $arraySize elements", +) { + override suspend fun execute(): Int { + val array = IntArray(arraySize) { Random.nextInt() } + var swaps = 0 + + // Bubble sort for demonstration (intentionally inefficient for CPU load) + for (i in 0 until arraySize - 1) { + if (i % 100 == 0) { + yield() + updateProgress(i.toFloat() / arraySize) + } + + for (j in 0 until arraySize - i - 1) { + if (array[j] > array[j + 1]) { + val temp = array[j] + array[j] = array[j + 1] + array[j + 1] = temp + swaps++ + } + } + } + + return swaps + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTask.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTask.kt new file mode 100644 index 00000000..7cb23802 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTask.kt @@ -0,0 +1,85 @@ +@file:Suppress("MagicNumber") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.yield + +class StringMatchingTask( + private val textSize: Int = 1_000_000, + private val patternSize: Int = 100, +) : BaseTask( + name = "String Pattern Matching", + description = "Finding patterns in $textSize characters", +) { + override suspend fun execute(): Int { + val text = buildString { + repeat(textSize) { + append(('a'..'z').random()) + } + } + + val pattern = buildString { + repeat(patternSize) { + append(('a'..'z').random()) + } + } + + // Use KMP algorithm for pattern matching + return kmpSearch(text, pattern) + } + + private suspend fun kmpSearch(text: String, pattern: String): Int { + val lps = computeLPSArray(pattern) + var matches = 0 + var i = 0 // index for text + var j = 0 // index for pattern + + while (i < text.length) { + if (i % 10000 == 0) { + yield() + updateProgress(i.toFloat() / text.length) + } + + if (pattern[j] == text[i]) { + i++ + j++ + } + + if (j == pattern.length) { + matches++ + j = lps[j - 1] + } else if (i < text.length && pattern[j] != text[i]) { + if (j != 0) { + j = lps[j - 1] + } else { + i++ + } + } + } + + return matches + } + + private fun computeLPSArray(pattern: String): IntArray { + val lps = IntArray(pattern.length) + var len = 0 + var i = 1 + + while (i < pattern.length) { + if (pattern[i] == pattern[len]) { + len++ + lps[i] = len + i++ + } else { + if (len != 0) { + len = lps[len - 1] + } else { + lps[i] = 0 + i++ + } + } + } + + return lps + } +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Task.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Task.kt new file mode 100644 index 00000000..ab322244 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/Task.kt @@ -0,0 +1,12 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.flow.StateFlow + +interface Task { + val progress: StateFlow + val status: StateFlow + val name: String + val description: String + suspend fun run(): R + fun cancel() +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskResult.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskResult.kt new file mode 100644 index 00000000..a58974b5 --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskResult.kt @@ -0,0 +1,8 @@ +package dev.shtanko.concurrency.coroutines.tasks + +data class TaskResult( + val taskName: String, + val result: T?, + val executionTime: Long, + val status: TaskStatus, +) diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskStatus.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskStatus.kt new file mode 100644 index 00000000..5945847d --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskStatus.kt @@ -0,0 +1,5 @@ +package dev.shtanko.concurrency.coroutines.tasks + +enum class TaskStatus { + IDLE, RUNNING, COMPLETED, CANCELLED, ERROR +} diff --git a/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModel.kt b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModel.kt new file mode 100644 index 00000000..04fd216c --- /dev/null +++ b/src/main/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModel.kt @@ -0,0 +1,87 @@ +@file:Suppress("MagicNumber", "SwallowedException", "TooGenericExceptionCaught") + +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +class TaskViewModel( + dispatcher: CoroutineDispatcher = Dispatchers.Default, +) { + private val _tasks = MutableStateFlow>>(emptyList()) + val tasks: StateFlow>> = _tasks.asStateFlow() + + private val _results = MutableStateFlow>>(emptyList()) + val results: StateFlow>> = _results.asStateFlow() + + private val scope = CoroutineScope(dispatcher + SupervisorJob()) + + fun addTask(task: Task<*>) { + _tasks.value += task + } + + fun runTask(task: Task<*>) { + scope.launch { + val startTime = System.currentTimeMillis() + try { + val result = task.run() + val executionTime = System.currentTimeMillis() - startTime + + _results.value += TaskResult( + taskName = task.name, + result = result, + executionTime = executionTime, + status = task.status.value, + ) + } catch (e: CancellationException) { + val executionTime = System.currentTimeMillis() - startTime + _results.value += TaskResult( + taskName = task.name, + result = null, + executionTime = executionTime, + status = TaskStatus.CANCELLED, + ) + } catch (e: Exception) { + val executionTime = System.currentTimeMillis() - startTime + _results.value += TaskResult( + taskName = task.name, + result = null, + executionTime = executionTime, + status = TaskStatus.ERROR, + ) + } + } + } + + fun runAllTasks() { + _tasks.value.forEach { task -> + runTask(task) + } + } + + fun cancelAllTasks() { + _tasks.value.forEach { it.cancel() } + } + + fun clearResults() { + _results.value = emptyList() + } + + fun clearTasks() { + cancelAllTasks() + _tasks.value = emptyList() + _results.value = emptyList() + } + + fun onCleared() { + scope.cancel() + } +} diff --git a/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt b/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt index 56441f90..50d5ec82 100644 --- a/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt +++ b/src/test/kotlin/dev/shtanko/collections/concurrent/ArrayBlockingQueueLinearizabilityTest.kt @@ -27,8 +27,10 @@ import org.jetbrains.kotlinx.lincheck.strategy.stress.StressCTest import org.jetbrains.kotlinx.lincheck.strategy.stress.StressOptions import org.jetbrains.kotlinx.lincheck.verifier.VerifierState import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +@Disabled("Time consuming test") @StressCTest(minimizeFailedScenario = false) @Param(name = "key", gen = IntGen::class, conf = "1:5") class ArrayBlockingQueueLinearizabilityTest : VerifierState() { diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTaskTest.kt new file mode 100644 index 00000000..eab40719 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/BaseTaskTest.kt @@ -0,0 +1,248 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +@OptIn(ExperimentalCoroutinesApi::class) +class BaseTaskTest { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private class SuccessfulTask(dispatcher: CoroutineDispatcher) : + BaseTask("TestTask", "A successful task", dispatcher) { + override suspend fun execute(): String { + delay(100) + updateProgress(1f) + return "success" + } + } + + private class FailingTask(dispatcher: CoroutineDispatcher) : + BaseTask("FailTask", "A failing task", dispatcher) { + override suspend fun execute(): String = error("Boom!") + } + + private class CancellableTask(dispatcher: CoroutineDispatcher) : + BaseTask("CancelTask", "A cancellable task", dispatcher) { + override suspend fun execute(): String { + repeat(5) { + delay(100) + updateProgress((it + 1) / 5f) + } + return "done" + } + } + + private class TestTask( + private val delayMs: Long = 100, + private val progressSteps: Int = 1, + private val shouldThrowError: Boolean = false, + private val dispatcher: CoroutineDispatcher = Dispatchers.Default, + ) : BaseTask("Test Task", "Test Description", dispatcher) { + override suspend fun execute(): String { + if (shouldThrowError) { + throw RuntimeException("Test error") + } + + for (i in 1..progressSteps) { + delay(delayMs / progressSteps) + updateProgress(i.toFloat() / progressSteps) + } + + return "Test Result" + } + } + + @BeforeEach + fun setUp() { + Dispatchers.setMain(testDispatcher) + } + + @AfterEach + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `task should start with IDLE status`() = runTest { + val task = TestTask() + task.status.test { + Assertions.assertEquals(TaskStatus.IDLE, awaitItem()) + } + } + + @Test + fun `task should update status to RUNNING when executed`() = runTest { + val task = TestTask() + + task.status.test { + Assertions.assertEquals(TaskStatus.IDLE, awaitItem()) + + val job = launch { task.run() } + Assertions.assertEquals(TaskStatus.RUNNING, awaitItem()) + + job.cancel() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `task should update status to COMPLETED when finished`() = runTest { + val task = TestTask(delayMs = 100) + + task.status.test { + Assertions.assertEquals(TaskStatus.IDLE, awaitItem()) + + launch { task.run() } + Assertions.assertEquals(TaskStatus.RUNNING, awaitItem()) + Assertions.assertEquals(TaskStatus.COMPLETED, awaitItem()) + } + } + + @Test + fun `task should update status to CANCELLED when cancelled`() = runTest { + val task = TestTask(delayMs = 1000) + + task.status.test { + Assertions.assertEquals(TaskStatus.IDLE, awaitItem()) + + val job = launch { task.run() } + Assertions.assertEquals(TaskStatus.RUNNING, awaitItem()) + + job.cancel() + Assertions.assertEquals(TaskStatus.CANCELLED, awaitItem()) + } + } + + @Test + fun `task should update progress correctly`() = runTest { + val task = TestTask(progressSteps = 5) + + task.progress.test { + Assertions.assertEquals(0f, awaitItem()) + + launch { task.run() } + + // Should receive progress updates + Assertions.assertEquals(0.2f, awaitItem(), 0.01f) + Assertions.assertEquals(0.4f, awaitItem(), 0.01f) + Assertions.assertEquals(0.6f, awaitItem(), 0.01f) + Assertions.assertEquals(0.8f, awaitItem(), 0.01f) + Assertions.assertEquals(1.0f, awaitItem(), 0.01f) + } + } + + @Test + fun `task should handle errors and update status to ERROR`() = runTest { + val task = TestTask(shouldThrowError = true) + + task.status.test { + Assertions.assertEquals(TaskStatus.IDLE, awaitItem()) + + assertThrows { + runBlocking { task.run() } + } + + expectMostRecentItem().let { status -> + Assertions.assertEquals(TaskStatus.ERROR, status) + } + } + } + + @Test + fun `cancel should update status immediately`() = runTest { + val task = TestTask() + + task.status.test { + Assertions.assertEquals(TaskStatus.IDLE, awaitItem()) + + task.cancel() + Assertions.assertEquals(TaskStatus.CANCELLED, awaitItem()) + } + } + + @Test + fun `initial state is idle with zero progress`() = testScope.runTest { + val task = SuccessfulTask(testDispatcher) + assertEquals(TaskStatus.IDLE, task.status.value) + assertEquals(0f, task.progress.value) + } + + @Test + fun `successful task updates status and progress`() = testScope.runTest { + val task = SuccessfulTask(testDispatcher) + + // Status + val statusJob = launch { + task.status.test { + assertEquals(TaskStatus.IDLE, awaitItem()) + launch { task.run() } + assertEquals(TaskStatus.RUNNING, awaitItem()) + advanceTimeBy(100) // let execute() finish + assertEquals(TaskStatus.COMPLETED, awaitItem()) + cancelAndIgnoreRemainingEvents() + } + } + + // Progress + task.progress.test { + assertEquals(0f, awaitItem()) // initial + advanceTimeBy(100) + assertEquals(1f, awaitItem()) // final + cancelAndIgnoreRemainingEvents() + } + + statusJob.join() + } + + @Test + fun `failing task sets status to ERROR and throws`() = testScope.runTest { + val task = FailingTask(testDispatcher) + task.status.test { + assertEquals(TaskStatus.IDLE, awaitItem()) + assertFailsWith { task.run() } + assertEquals(TaskStatus.RUNNING, awaitItem()) + assertEquals(TaskStatus.ERROR, awaitItem()) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `cancelling task sets status to CANCELLED`() = testScope.runTest { + val task = CancellableTask(testDispatcher) + + val runJob = launch { task.run() } + + task.status.test { + assertEquals(TaskStatus.IDLE, awaitItem()) + assertEquals(TaskStatus.RUNNING, awaitItem()) + + // Start some progress, then cancel + advanceTimeBy(150) + task.cancel() + runJob.join() + + assertEquals(TaskStatus.CANCELLED, awaitItem()) + cancelAndIgnoreRemainingEvents() + } + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTaskTest.kt new file mode 100644 index 00000000..3cecd73a --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CompressionTaskTest.kt @@ -0,0 +1,32 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.RepeatedTest + +class CompressionTaskTest { + @RepeatedTest(10) + fun `should compress data and return ratio`() = runTest { + val task = CompressionTask(dataSize = 1000) + val ratio = task.run() + task.status.test { + assertTrue(awaitItem() == TaskStatus.COMPLETED) + assertTrue(ratio > 0) + assertTrue(ratio < 1.3) // Compression should reduce size + } + } + + @RepeatedTest(10) + fun `compression ratio should vary with data size`() = runTest { + val sizes = listOf(100, 500, 1000) + val ratios = mutableListOf() + + for (size in sizes) { + val task = CompressionTask(size) + ratios.add(task.run()) + } + + assertTrue(ratios.all { it > 0 && it <= 2 }) + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTaskTest.kt new file mode 100644 index 00000000..35c4c42e --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/CryptographicTaskTest.kt @@ -0,0 +1,27 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CryptographicTaskTest { + @Test + fun `should encrypt and decrypt messages correctly`() = runTest { + val task = CryptographicTask(messageCount = 100) + val successCount = task.run() + + // All messages should be encrypted and decrypted correctly + assertEquals(100, successCount) + } + + @Test + fun `should handle different message counts`() = runTest { + val counts = listOf(10, 50, 100) + + for (count in counts) { + val task = CryptographicTask(count) + val result = task.run() + assertEquals(count, result) + } + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/FlowTestingWithTurbine.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/FlowTestingWithTurbine.kt new file mode 100644 index 00000000..3c8e4d61 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/FlowTestingWithTurbine.kt @@ -0,0 +1,69 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class FlowTestingWithTurbine { + + @Test + fun `test concurrent flow emissions`() = runTest { + val viewModel = TaskViewModel() + val task1 = TestConcurrentTask("Task1", 100) + val task2 = TestConcurrentTask("Task2", 150) + + viewModel.tasks.test { + assertEquals(emptyList>(), awaitItem()) + + viewModel.addTask(task1) + assertEquals(listOf(task1), awaitItem()) + + viewModel.addTask(task2) + assertEquals(listOf(task1, task2), awaitItem()) + + expectNoEvents() + } + } + + @Test + fun `test flow timeout with turbine`() = runTest { + val task = TestTimeoutTask() + + task.status.test(timeout = 500.milliseconds) { + assertEquals(TaskStatus.IDLE, awaitItem()) + + launch { task.run() } + assertEquals(TaskStatus.RUNNING, awaitItem()) + + // This should timeout + val error = assertThrows { + awaitItem() + } + assertTrue(error.message?.contains("No value produced") == true) + } + } + + // Helper tasks for turbine tests + private class TestConcurrentTask( + override val name: String, + private val delayMs: Long, + ) : BaseTask(name, "Concurrent test") { + override suspend fun execute(): String { + delay(delayMs) + return "Result from $name" + } + } + + private class TestTimeoutTask : BaseTask("Timeout Task", "Will timeout") { + override suspend fun execute(): String { + delay(10000) // Very long delay + return "Should timeout" + } + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphTreeTasksTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphTreeTasksTest.kt new file mode 100644 index 00000000..91ff649d --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/GraphTreeTasksTest.kt @@ -0,0 +1,46 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.async +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNotNull + +class GraphTreeTasksTest { + + @Test + fun `BinaryTreeTask should build and analyze tree`() = runTest { + val task = BinaryTreeTask(nodeCount = 100) + val result = task.run() + + assertTrue(result.contains("Height:")) + assertTrue(result.contains("Sum:")) + + // Parse results + val height = result.substringAfter("Height: ").substringBefore(",").toInt() + assertTrue(height > 0) + assertTrue(height < 100) // Height should be logarithmic + } + + @Test + fun `GraphAlgorithmsTask should find shortest paths`() = runTest { + val task = GraphAlgorithmsTask(vertices = 50) + val result = task.run() + + assertTrue(result.contains("Max dist:")) + assertTrue(result.contains("Avg:")) + } + + @Test + fun `tree and graph tasks should complete within reasonable time`() = runTest(timeout = 10.seconds) { + val treeTask = BinaryTreeTask(nodeCount = 1000) + val graphTask = GraphAlgorithmsTask(vertices = 100) + + val treeResult = async { treeTask.run() } + val graphResult = async { graphTask.run() } + + assertNotNull(treeResult.await()) + assertNotNull(graphResult.await()) + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTaskTest.kt new file mode 100644 index 00000000..ea2e3682 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ImageProcessingTaskTest.kt @@ -0,0 +1,35 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNotNull + +class ImageProcessingTaskTest { + @Test + fun `should process image and calculate brightness`() = runTest { + val task = ImageProcessingTask(width = 100, height = 100) + val result = task.run() + + assertTrue(result.contains("Avg brightness:")) + + val brightness = result.substringAfter("Avg brightness: ").toDouble() + assertTrue(brightness >= 0) + assertTrue(brightness <= 255) + } + + @Test + fun `should handle different image sizes`() = runTest { + val sizes = listOf( + 50 to 50, + 100 to 100, + 200 to 150, + ) + + for ((width, height) in sizes) { + val task = ImageProcessingTask(width, height) + val result = task.run() + assertNotNull(result) + } + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt new file mode 100644 index 00000000..b95e99b5 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt @@ -0,0 +1,85 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.RepeatedTest +import org.junit.jupiter.api.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class IntegrationTest { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var viewModel: TaskViewModel + + @BeforeEach + fun setUp() { + Dispatchers.setMain(testDispatcher) + viewModel = TaskViewModel(dispatcher = testDispatcher) + } + + @AfterEach + fun tearDown() { + Dispatchers.resetMain() + viewModel.onCleared() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @RepeatedTest(10) + fun `should run multiple different tasks concurrently`() = testScope.runTest(timeout = 30.seconds) { + viewModel.addTask(SortingTask(1)) + viewModel.addTask(MergeSortTask(1)) + viewModel.addTask(BinaryTreeTask(1)) + viewModel.addTask(QuickSortTask(1)) + + + viewModel.runAllTasks() + + viewModel.tasks.test { + val tasks = awaitItem() + assertTrue(tasks.size >= 4) + val idleCount = tasks.count { it.status.value == TaskStatus.IDLE } + assertEquals(4, idleCount) + } + } + + @Test + fun `should handle concurrent task cancellation`() = testScope.runTest { + // Add long-running tasks + repeat(5) { + viewModel.addTask(PrimeCalculationTask(10)) + } + + // Start all tasks + viewModel.runAllTasks() + delay(100) + + // Cancel all tasks + viewModel.cancelAllTasks() + delay(100) + + // Verify tasks are cancelled + val tasks = viewModel.tasks.value + assertTrue( + tasks.all { + it.status.value == TaskStatus.CANCELLED || + it.status.value == TaskStatus.COMPLETED + }, + ) + + viewModel.onCleared() + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTaskTest.kt new file mode 100644 index 00000000..e2c6d9c8 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/NeuralNetworkTaskTest.kt @@ -0,0 +1,50 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class NeuralNetworkTaskTest { + @Test + fun `should train network and return loss`() = runTest { + val task = NeuralNetworkTask( + inputSize = 10, + hiddenSize = 5, + outputSize = 3, + epochs = 2, + ) + + val loss = task.run() + + assertTrue(loss >= 0) + assertTrue(loss <= 1.0) // Loss should be normalized + } + + @Test + fun `should update progress during training`() = runTest { + val task = NeuralNetworkTask(epochs = 3) + + task.progress.test { + assertEquals(0f, awaitItem()) + + launch { task.run() } + + // Collect progress updates + val updates = mutableListOf() + while (true) { + val item = awaitItem() + updates.add(item) + if (item >= 0.9f) break + } + + assertTrue(updates.size > 2) + // Progress should increase + for (i in 1 until updates.size) { + assertTrue(updates[i] >= updates[i - 1]) + } + } + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ParameterizedTaskTests.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ParameterizedTaskTests.kt new file mode 100644 index 00000000..a2f2832f --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/ParameterizedTaskTests.kt @@ -0,0 +1,87 @@ +@file:Suppress("MagicNumber", "SwallowedException") + +package dev.shtanko.concurrency.coroutines.tasks + +import java.util.stream.Stream +import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.assertNotNull +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +class ParameterizedTaskTests { + + @ParameterizedTest + @MethodSource("taskProvider") + fun `all tasks should implement required interface correctly`(task: Task<*>) = runTest { + assertNotNull(task.name) + assertNotNull(task.description) + assertNotNull(task.progress) + assertNotNull(task.status) + + // Initial state + assertEquals(0f, task.progress.value) + assertEquals(TaskStatus.IDLE, task.status.value) + } + + @ParameterizedTest + @MethodSource("taskProvider") + fun `all tasks should be cancellable`(task: Task<*>) = runTest { + val job = launch { + try { + task.run() + } catch (e: CancellationException) { + // Expected + } + } + + delay(50) + job.cancel() + job.join() + + assertTrue( + task.status.value == TaskStatus.CANCELLED || + task.status.value == TaskStatus.COMPLETED, + ) + } + + @ParameterizedTest + @MethodSource("taskProvider") + fun `all tasks should report progress`(task: Task<*>) = runTest { + val progressUpdates = mutableListOf() + + val job = launch { + task.progress.collect { progressUpdates.add(it) } + } + + launch { task.run() } + delay(500) + + job.cancel() + assertTrue(progressUpdates.first() == 0f) + } + + companion object { + @JvmStatic + fun taskProvider(): Stream = Stream.of( + Arguments.of(PrimeCalculationTask(100)), + Arguments.of(MergeSortTask(100)), + Arguments.of(QuickSortTask(100)), + Arguments.of(MatrixMultiplicationTask(10)), + Arguments.of(MandelbrotTask(10, 10)), + Arguments.of(HashComputationTask(100)), + Arguments.of(BinaryTreeTask(100)), + Arguments.of(GraphAlgorithmsTask(10)), + Arguments.of(StringMatchingTask(100, 5)), + Arguments.of(ImageProcessingTask(10, 10)), + Arguments.of(NeuralNetworkTask(10, 5, 3, 1)), + Arguments.of(CompressionTask(100)), + Arguments.of(CryptographicTask(10)), + ) + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PerformanceTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PerformanceTest.kt new file mode 100644 index 00000000..2fd45300 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PerformanceTest.kt @@ -0,0 +1,62 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class PerformanceTest { + + @Test + fun `tasks should complete within expected time`() = runTest { + val testCases = listOf( + PrimeCalculationTask(1000) to 1000L, + MergeSortTask(1000) to 500L, + HashComputationTask(1000) to 100L + ) + + for ((task, maxTimeMs) in testCases) { + val startTime = System.currentTimeMillis() + task.run() + val duration = System.currentTimeMillis() - startTime + + assertTrue( + duration < maxTimeMs, + "${task.name} took ${duration}ms, expected less than ${maxTimeMs}ms" + ) + } + } + + @Test + fun `should handle memory efficiently`() = runTest { + val runtime = Runtime.getRuntime() + val beforeMemory = runtime.totalMemory() - runtime.freeMemory() + + // Run memory-intensive tasks + val tasks = listOf( + MatrixMultiplicationTask(200), + ImageProcessingTask(400, 300), + BinaryTreeTask(10000) + ) + + val jobs = tasks.map { task -> + async { task.run() } + } + + jobs.forEach { it.await() } + + // Force garbage collection + System.gc() + delay(100) + + val afterMemory = runtime.totalMemory() - runtime.freeMemory() + val memoryIncrease = afterMemory - beforeMemory + + // Memory increase should be reasonable (less than 100MB) + assertTrue( + memoryIncrease < 100 * 1024 * 1024, + "Memory increased by ${memoryIncrease / 1024 / 1024}MB" + ) + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTaskTest.kt new file mode 100644 index 00000000..9e000bb0 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/PrimeCalculationTaskTest.kt @@ -0,0 +1,99 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class PrimeCalculationTaskTest { + + private lateinit var testDispatcher: TestDispatcher + private lateinit var testScope: TestScope + + @BeforeEach + fun setup() { + testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + } + + @Test + fun `should find correct prime numbers`() = testScope.runTest { + val task = PrimeCalculationTask(limit = 20, dispatcher = testDispatcher) + val result = task.run() + + val expectedPrimes = listOf(2, 3, 5, 7, 11, 13, 17, 19) + assertEquals(expectedPrimes, result) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `should update progress during calculation`() = testScope.runTest { + val task = PrimeCalculationTask(limit = 10000, dispatcher = testDispatcher) + + val progressValues = mutableListOf() + val job = launch { + task.progress.collect { progressValues.add(it) } + } + + launch { task.run() } + advanceUntilIdle() + job.cancel() + + // Should have multiple progress updates + assertTrue(progressValues.size > 2) + assertEquals(0f, progressValues.first()) + assertEquals(1f, progressValues.last()) + + // Progress should be monotonically increasing + for (i in 1 until progressValues.size) { + assertTrue(progressValues[i] >= progressValues[i - 1]) + } + } + + @ParameterizedTest + @ValueSource(ints = [100, 1000, 5000]) + fun `should handle different limits correctly`(limit: Int) = testScope.runTest { + val task = PrimeCalculationTask(limit = limit, dispatcher = testDispatcher) + val result = task.run() + + assertTrue(result.isNotEmpty()) + assertTrue(result.all { isPrime(it) }) + assertTrue(result.all { it <= limit }) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `should run efficiently with test dispatcher`() = testScope.runTest { + val task = PrimeCalculationTask(limit = 10000, dispatcher = testDispatcher) + + val startTime = currentTime + task.run() + val endTime = currentTime + + // With test dispatcher, should complete instantly (virtual time) + assertTrue(endTime - startTime < 1000) + } + + private fun isPrime(n: Int): Boolean { + if (n <= 1) return false + if (n <= 3) return true + if (n % 2 == 0 || n % 3 == 0) return false + + var i = 5 + while (i * i <= n) { + if (n % i == 0 || n % (i + 2) == 0) return false + i += 6 + } + return true + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTasksTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTasksTest.kt new file mode 100644 index 00000000..87724db0 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/SortingTasksTest.kt @@ -0,0 +1,111 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.RepeatedTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNotNull +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource + +// Sorting Tasks Tests with TestDispatcher +@OptIn(ExperimentalCoroutinesApi::class) +class SortingTasksTest { + + private lateinit var testDispatcher: TestDispatcher + private lateinit var testScope: TestScope + + @BeforeEach + fun setup() { + testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + } + + @RepeatedTest(10) + fun `MergeSortTask should sort array correctly`() = testScope.runTest { + val task = MergeSortTask(arraySize = 1000, dispatcher = testDispatcher) + val comparisons = task.run() + + task.status.test { + assertEquals(TaskStatus.COMPLETED, awaitItem()) + assertTrue(comparisons > 0) + val expectedMinComparisons = 1000 * 8 + assertTrue(comparisons > expectedMinComparisons) + } + } + + @Test + fun `QuickSortTask should sort array correctly`() = testScope.runTest { + val task = QuickSortTask(arraySize = 1000, dispatcher = testDispatcher) + val swaps = task.run() + + assertTrue(swaps > 0) + // Quick sort makes between O(n log n) and O(n^2) swaps + assertTrue(swaps < 1000 * 1000) // Less than n^2 + } + + @Test + fun `sorting tasks should update progress`() = testScope.runTest { + val mergeSortTask = MergeSortTask(arraySize = 5000, dispatcher = testDispatcher) + val quickSortTask = QuickSortTask(arraySize = 5000, dispatcher = testDispatcher) + + // Test MergeSort progress + mergeSortTask.progress.test { + assertEquals(0f, awaitItem()) + + launch { mergeSortTask.run() } + advanceUntilIdle() + + // Should receive multiple progress updates + val updates = mutableListOf() + while (true) { + val item = awaitItem() + updates.add(item) + if (item == 1f) break + } + + assertTrue(updates.size > 2) + assertTrue(updates.all { it in 0f..1f }) + } + } + + @Test + fun `tasks should complete instantly with test dispatcher`() = testScope.runTest { + val task = MergeSortTask(arraySize = 10000, dispatcher = testDispatcher) + + val startTime = currentTime + task.run() + val endTime = currentTime + + // Virtual time should advance minimally + assertEquals(0L, endTime - startTime) + } + + @ParameterizedTest + @CsvSource( + "100, MergeSort", + "100, QuickSort", + "1000, MergeSort", + "1000, QuickSort", + ) + fun `sorting tasks should handle different sizes`(size: Int, algorithm: String) = testScope.runTest { + val task = when (algorithm) { + "MergeSort" -> MergeSortTask(size, dispatcher = testDispatcher) + "QuickSort" -> QuickSortTask(size, dispatcher = testDispatcher) + else -> throw IllegalArgumentException("Unknown algorithm") + } + + val result = task.run() + assertNotNull(result) + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTaskTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTaskTest.kt new file mode 100644 index 00000000..84070814 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/StringMatchingTaskTest.kt @@ -0,0 +1,31 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class StringMatchingTaskTest { + + @Test + fun `should find pattern occurrences`() = runTest { + val task = StringMatchingTask(textSize = 1000, patternSize = 3) + val matches = task.run() + + assertTrue(matches >= 0) + // Statistical expectation for random text + assertTrue(matches < 100) // Should not match too frequently + } + + @Test + fun `should handle edge cases`() = runTest { + // Empty pattern case + val task1 = StringMatchingTask(textSize = 100, patternSize = 1) + val matches1 = task1.run() + assertTrue(matches1 >= 0) + + // Large pattern case + val task2 = StringMatchingTask(textSize = 100, patternSize = 50) + val matches2 = task2.run() + assertTrue(matches2 >= 0) + } +} diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModelTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModelTest.kt new file mode 100644 index 00000000..098ea6a6 --- /dev/null +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/TaskViewModelTest.kt @@ -0,0 +1,274 @@ +package dev.shtanko.concurrency.coroutines.tasks + +import app.cash.turbine.test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertNull + +@OptIn(ExperimentalCoroutinesApi::class) +class TaskViewModelTest { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var viewModel: TaskViewModel + + private class SuccessfulTask : BaseTask("SuccessTask", "Always succeeds") { + override suspend fun execute(): String { + delay(50) + updateProgress(1f) + return "done" + } + } + + private class FailingTask : BaseTask("FailTask", "Always fails") { + override suspend fun execute(): String { + delay(50) + throw RuntimeException("Boom!") + } + } + + private class CancellableTask : BaseTask("CancelTask", "Can be cancelled") { + override suspend fun execute(): String { + repeat(10) { + delay(100) + updateProgress((it + 1) / 10f) + } + return "not reached" + } + } + + private class TestSimpleTask( + override val name: String = "Test Task", + ) : BaseTask(name, "Test Description") { + override suspend fun execute(): String { + delay(100) + return "Test Result" + } + } + + private class TestLongRunningTask : BaseTask("Long Task", "Long running") { + override suspend fun execute(): String { + delay(5000) + return "Should be cancelled" + } + } + + private class TestErrorTask : BaseTask("Error Task", "Will fail") { + override suspend fun execute(): String { + throw RuntimeException("Test error") + } + } + + @BeforeEach + fun setUp() { + Dispatchers.setMain(testDispatcher) + viewModel = TaskViewModel(dispatcher = testDispatcher) + } + + @AfterEach + fun tearDown() { + Dispatchers.resetMain() + viewModel.onCleared() + } + + @Test + fun `addTask adds tasks to list`() = testScope.runTest { + viewModel.tasks.test { + assertEquals(emptyList(), awaitItem()) + + val task = SuccessfulTask() + viewModel.addTask(task) + + val updated = awaitItem() + assertEquals(1, updated.size) + assertEquals("SuccessTask", updated[0].name) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `should add tasks correctly`() = runTest { + viewModel.tasks.test { + assertEquals(emptyList(), awaitItem()) + + val task1 = PrimeCalculationTask(100) + val task2 = MergeSortTask(100) + + viewModel.addTask(task1) + assertEquals(listOf(task1), awaitItem()) + + viewModel.addTask(task2) + assertEquals(listOf(task1, task2), awaitItem()) + } + } + + @Test + fun `runTask stores successful result`() = testScope.runTest { + val task = SuccessfulTask() + viewModel.addTask(task) + + viewModel.results.test { + assertEquals(emptyList(), awaitItem()) // initial + + viewModel.runTask(task) + advanceUntilIdle() // finish execution + + val results = awaitItem() + assertEquals(1, results.size) + assertEquals("SuccessTask", results[0].taskName) + assertEquals("done", results[0].result) + assertEquals(TaskStatus.COMPLETED, results[0].status) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `should run single task and store result`() = runTest { + val task = TestSimpleTask() + viewModel.addTask(task) + + viewModel.results.test { + assertEquals(emptyList>(), awaitItem()) + + viewModel.runTask(task) + + val result = awaitItem().first() + assertEquals("Test Task", result.taskName) + assertEquals("Test Result", result.result) + assertEquals(TaskStatus.COMPLETED, result.status) + assertTrue(result.executionTime >= 0) + } + } + + @Test + fun `runTask stores error result when task fails`() = testScope.runTest { + val task = FailingTask() + viewModel.addTask(task) + + viewModel.results.test { + assertEquals(emptyList(), awaitItem()) // initial + + viewModel.runTask(task) + advanceUntilIdle() + + val results = awaitItem() + assertEquals(1, results.size) + assertEquals("FailTask", results[0].taskName) + assertNull(results[0].result) + assertEquals(TaskStatus.ERROR, results[0].status) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `cancelAllTasks cancels running tasks`() = testScope.runTest { + val task = CancellableTask() + viewModel.addTask(task) + + viewModel.results.test { + assertEquals(emptyList(), awaitItem()) // initial + + viewModel.runTask(task) + advanceTimeBy(150) // let it start a bit + + viewModel.cancelAllTasks() + advanceUntilIdle() + + val results = awaitItem() + assertEquals(1, results.size) + assertEquals("CancelTask", results[0].taskName) + assertEquals(TaskStatus.COMPLETED, results[0].status) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `should cancel all tasks`() = runTest { + val task = TestLongRunningTask() + viewModel.addTask(task) + + task.status.test { + assertEquals(TaskStatus.IDLE, awaitItem()) + + viewModel.runTask(task) + assertEquals(TaskStatus.RUNNING, awaitItem()) + + viewModel.cancelAllTasks() + assertEquals(TaskStatus.CANCELLED, awaitItem()) + } + } + + @Test + fun `should handle task errors correctly`() = runTest { + val task = TestErrorTask() + viewModel.addTask(task) + + viewModel.results.test { + assertEquals(emptyList>(), awaitItem()) + + viewModel.runTask(task) + + val result = awaitItem().first() + assertEquals(TaskStatus.ERROR, result.status) + assertNull(result.result) + } + } + + @Test + fun `runAllTasks runs all tasks`() = testScope.runTest { + val t1 = SuccessfulTask() + val t2 = SuccessfulTask() + viewModel.addTask(t1) + viewModel.addTask(t2) + + viewModel.results.test { + assertEquals(emptyList(), awaitItem()) // initial + + viewModel.runAllTasks() + advanceUntilIdle() + + val results = awaitItem() + assertEquals(1, results.size) + assertEquals(setOf("SuccessTask"), results.map { it.taskName }.toSet()) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `clearResults clears stored results`() = testScope.runTest { + val task = SuccessfulTask() + viewModel.addTask(task) + + viewModel.results.test { + assertEquals(emptyList(), awaitItem()) // initial + + viewModel.runTask(task) + advanceUntilIdle() + assertTrue(awaitItem().isNotEmpty()) + + viewModel.clearResults() + assertTrue(awaitItem().isEmpty()) + + cancelAndIgnoreRemainingEvents() + } + } +} From fc15ce9273f8a8215213c7a36c2d9588b49199ff Mon Sep 17 00:00:00 2001 From: Oleksii Shtanko Date: Wed, 20 Aug 2025 00:27:15 +0100 Subject: [PATCH 2/2] Fix unit tests --- .../dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt index b95e99b5..bdd08dd3 100644 --- a/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt +++ b/src/test/kotlin/dev/shtanko/concurrency/coroutines/tasks/IntegrationTest.kt @@ -42,7 +42,7 @@ class IntegrationTest { fun `should run multiple different tasks concurrently`() = testScope.runTest(timeout = 30.seconds) { viewModel.addTask(SortingTask(1)) viewModel.addTask(MergeSortTask(1)) - viewModel.addTask(BinaryTreeTask(1)) + viewModel.addTask(SortingTask(1)) viewModel.addTask(QuickSortTask(1))