├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── CounterContext.kt ├── CustomCoroutineName.kt ├── Downloader.kt ├── Fibonacci.kt ├── Main.kt ├── Playground.kt ├── ShowUserData.kt ├── ShowUserDataTest.kt ├── Sieve.kt ├── Tweets.kt ├── UserRepository.kt ├── UsersSequence.kt ├── Utils.kt ├── actors ├── Actors.kt └── ActorsTests.kt ├── backend ├── Api.kt ├── ApiDsl.kt ├── ApiTests.kt ├── Database.kt └── EmailService.kt ├── callback └── FetchTasksUseCase.kt ├── channel └── OfferReclassify.kt ├── coffee ├── Coffee.kt ├── CoffeeChannel.kt └── CoffeeDispatchers.kt ├── comment ├── CommentFactory.kt ├── CommentService.kt ├── CommentServiceTests.kt ├── Data.kt ├── FakeCommentsRepository.kt ├── FakeTimeProvider.kt ├── FakeUserService.kt ├── FakeUuidProvider.kt ├── TimeProvider.kt └── UuidProvider.kt ├── continuation ├── ContinuationSteal.kt └── ContinuationStealTests.kt ├── examples ├── 1.kt ├── 10.kt ├── 2.kt ├── 3.kt ├── 4.kt ├── 5.kt ├── 6.kt ├── 7.kt ├── 8.kt ├── 9.kt ├── CombiningFlows.kt ├── Continuation.kt ├── Delay.kt ├── Exceptions.kt ├── Massive.kt ├── Numbers.kt ├── b1.kt ├── b2.kt ├── b3.kt ├── c1.kt ├── c10.kt ├── c11.kt ├── c12.kt ├── c13.kt ├── c2.kt ├── c3.kt ├── c4.kt ├── c5.kt ├── c6.kt ├── c7.kt ├── c8.kt ├── c9.kt ├── d1.kt ├── d2.kt ├── d3.kt ├── d4.kt ├── d5.kt ├── d6.kt ├── e1.kt ├── e2.kt ├── f1.kt ├── f10.kt ├── f11.kt ├── f12.kt ├── f13.kt ├── f14.kt ├── f15.kt ├── f16.kt ├── f17.kt ├── f18.kt ├── f19.kt ├── f2.kt ├── f3.kt ├── f4.kt ├── f5.kt ├── f6.kt ├── f7.kt ├── f8.kt ├── f9.kt ├── hc1.kt ├── hc2.kt ├── hc3.kt ├── j1.kt ├── j10.kt ├── j11.kt ├── j12.kt ├── j13.kt ├── j14.kt ├── j15.kt ├── j16.kt ├── j17.kt ├── j18.kt ├── j19.kt ├── j2.kt ├── j3.kt ├── j4.kt ├── j5.kt ├── j6.kt ├── j7.kt ├── j8.kt ├── j9.kt ├── s1.kt ├── s2.kt ├── s3.kt ├── s4.kt ├── select1.kt ├── select2.kt ├── select3.kt ├── sf1.kt ├── sf2.kt ├── sf3.kt ├── sf4.kt ├── sf5.kt ├── sus.kt ├── sus1.kt ├── sus2.kt ├── sus3.kt ├── sus4.kt ├── sus5.kt ├── sus6.kt ├── t1.kt ├── t2.kt ├── t3.kt ├── t4.kt ├── t5.kt └── t6.kt ├── flow ├── Factory.kt ├── FactoryTests.kt ├── Kata.kt └── ScanElements.kt ├── github ├── Aggregated.kt ├── AggregatedTest.kt ├── Channels.kt ├── ChannelsTests.kt └── GithubRepo.kt ├── notification └── NotificationSender.kt ├── request └── Request.kt ├── structured ├── Structured.kt └── StructuredTests.kt └── ui ├── BasePresenter.kt └── MainPresenter.kt /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Coroutines Workshop 2 | 3 | This project containes exercises and examples for [Kt. Academy Kotlin Coroutines workshop](https://kt.academy/Coroutines.html). 4 | 5 | Workshop coveres: 6 | * Styles of concurrence 7 | * Sequence builders 8 | * Continuation 9 | * Understanding how suspension works 10 | * Coroutine Context 11 | * Interceptors and dispatchers 12 | * Coroutine Scope 13 | * Coroutine builders 14 | * Structured concurrency 15 | * Understanding Job 16 | * Composing suspending functions 17 | * Exceptions handling 18 | * Shared mutable state and concurrency 19 | * Channels 20 | * Actors 21 | * Select expression 22 | * UI programming with coroutines 23 | * Reactive streams with coroutines 24 | * Unit testing Kotlin coroutines 25 | 26 | If you are interested in having this workshop in your company, you can find details [here](https://kt.academy/Coroutines.html) or just fill [the form](https://marcinmoskala.typeform.com/to/F1twEo). 27 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.jetbrains.kotlin.jvm" version "1.7.20" 3 | } 4 | repositories { 5 | mavenCentral() 6 | } 7 | 8 | apply plugin: "kotlin" 9 | 10 | dependencies { 11 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20' 12 | implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20' 13 | 14 | implementation 'org.jetbrains.kotlin:kotlin-test:1.7.20' 15 | implementation 'org.jetbrains.kotlin:kotlin-test-junit:1.7.20' 16 | 17 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3-native-mt' 18 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.3-native-mt' 19 | 20 | // Callback exercise 21 | implementation "com.squareup.retrofit2:retrofit:2.9.0" 22 | implementation "com.squareup.retrofit2:converter-jackson:2.9.0" 23 | implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0' 24 | } 25 | 26 | kotlin { 27 | sourceSets { 28 | main.kotlin.srcDirs += 'src' 29 | test.kotlin.srcDirs += 'src' 30 | } 31 | } 32 | 33 | compileKotlin { 34 | kotlinOptions.freeCompilerArgs += [ 35 | "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcinMoskala/kotlin-coroutines-workshop/ca91e0eb28d276189bbaeddbfdff06a906769a13/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 02 16:43:55 CEST 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local request for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local request for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/CounterContext.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.delay 2 | import kotlinx.coroutines.launch 3 | import kotlinx.coroutines.runBlocking 4 | import org.junit.Test 5 | import ui.BasePresenter 6 | import kotlin.coroutines.CoroutineContext 7 | import kotlin.coroutines.coroutineContext 8 | import kotlin.test.assertEquals 9 | import kotlin.test.assertTrue 10 | 11 | // TODO 12 | 13 | @Suppress("FunctionName") 14 | class CounterContextTests { 15 | 16 | // TODO: Uncomment 17 | // 18 | // @Test 19 | // fun `should return next numbers in the same coroutine`() = runBlocking(CounterContext()) { 20 | // assertEquals(0, coroutineContext[CounterContext]?.next()) 21 | // assertEquals(1, coroutineContext[CounterContext]?.next()) 22 | // assertEquals(2, coroutineContext[CounterContext]?.next()) 23 | // assertEquals(3, coroutineContext[CounterContext]?.next()) 24 | // assertEquals(4, coroutineContext[CounterContext]?.next()) 25 | // } 26 | // 27 | // @Test 28 | // fun `should have independent counter for each instance`() = runBlocking { 29 | // launch(CounterContext()) { 30 | // assertEquals(0, coroutineContext[CounterContext]?.next()) 31 | // assertEquals(1, coroutineContext[CounterContext]?.next()) 32 | // assertEquals(2, coroutineContext[CounterContext]?.next()) 33 | // } 34 | // launch(CounterContext()) { 35 | // assertEquals(0, coroutineContext[CounterContext]?.next()) 36 | // assertEquals(1, coroutineContext[CounterContext]?.next()) 37 | // assertEquals(2, coroutineContext[CounterContext]?.next()) 38 | // } 39 | // } 40 | // 41 | // @Test 42 | // fun `should propagate to the child`() = runBlocking(CounterContext()) { 43 | // assertEquals(0, coroutineContext[CounterContext]?.next()) 44 | // launch { 45 | // assertEquals(1, coroutineContext[CounterContext]?.next()) 46 | // launch { 47 | // assertEquals(2, coroutineContext[CounterContext]?.next()) 48 | // } 49 | // launch(CounterContext()) { 50 | // assertEquals(0, coroutineContext[CounterContext]?.next()) 51 | // assertEquals(1, coroutineContext[CounterContext]?.next()) 52 | // launch { 53 | // assertEquals(2, coroutineContext[CounterContext]?.next()) 54 | // } 55 | // } 56 | // } 57 | // } 58 | } 59 | -------------------------------------------------------------------------------- /src/CustomCoroutineName.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.CoroutineName 2 | import kotlinx.coroutines.launch 3 | import kotlinx.coroutines.runBlocking 4 | import kotlin.coroutines.AbstractCoroutineContextElement 5 | import kotlin.coroutines.CoroutineContext 6 | 7 | //data class CoroutineName( 8 | // val name: String 9 | //) : AbstractCoroutineContextElement(CoroutineName) { 10 | // 11 | // companion object Key : CoroutineContext.Key 12 | //} 13 | // 14 | // 15 | //fun main() { 16 | // CoroutineName 17 | //} 18 | -------------------------------------------------------------------------------- /src/Downloader.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.* 2 | 3 | class User(val name: String) 4 | 5 | interface NetworkService { 6 | suspend fun getUser(id: Int): User 7 | } 8 | 9 | class FakeNetworkService : NetworkService { 10 | override suspend fun getUser(id: Int): User { 11 | delay(2) 12 | return User("User$id") 13 | } 14 | } 15 | 16 | class UserDownloader(private val api: NetworkService) { 17 | private val users = mutableListOf() 18 | 19 | fun downloaded(): List = users.toList() 20 | 21 | suspend fun getUser(id: Int) { 22 | val newUser = api.getUser(id) 23 | users += newUser 24 | } 25 | } 26 | 27 | suspend fun main() = coroutineScope { 28 | val downloader = UserDownloader(FakeNetworkService()) 29 | coroutineScope { 30 | repeat(1_000_000) { 31 | launch { 32 | downloader.getUser(it) 33 | } 34 | } 35 | } 36 | print(downloader.downloaded().size) // ~714725 37 | } 38 | -------------------------------------------------------------------------------- /src/Fibonacci.kt: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | import kotlin.test.assertEquals 3 | 4 | val fibonacci = sequence { 5 | TODO() 6 | } 7 | 8 | @Suppress("FunctionName") 9 | internal class FibonacciTests { 10 | 11 | @Test 12 | fun `First two numbers should be 1 and 1`() { 13 | assertEquals(listOf(1, 1), fibonacci.take(2).toList()) 14 | } 15 | 16 | @Test 17 | fun `Check first 11 numbers`() { 18 | assertEquals(listOf(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89), fibonacci.take(11).toList()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Main.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.coroutineScope 2 | 3 | suspend fun main(): Unit = coroutineScope { 4 | 5 | } -------------------------------------------------------------------------------- /src/Playground.kt: -------------------------------------------------------------------------------- 1 | import flow.makeTimer 2 | import kotlinx.coroutines.coroutineScope 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.* 5 | import kotlinx.coroutines.test.currentTime 6 | import kotlinx.coroutines.test.runTest 7 | 8 | 9 | fun main(): Unit = runTest { 10 | makeTimer(1000, 5, 8).collect { println("$currentTime ms -> $it") } 11 | } 12 | -------------------------------------------------------------------------------- /src/ShowUserData.kt: -------------------------------------------------------------------------------- 1 | package userdata 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.runBlocking 5 | 6 | suspend fun showUserData(repo: UserDataRepository, view: UserDataView) { 7 | // TODO 8 | } 9 | 10 | fun main() = runBlocking { 11 | showUserData(TestUserDataRepository(), TestUserDataView()) 12 | } 13 | 14 | interface UserDataRepository { 15 | suspend fun notifyProfileShown() 16 | suspend fun getName(): String 17 | suspend fun getFriends(): List 18 | suspend fun getProfile(): Profile 19 | } 20 | 21 | interface UserDataView { 22 | fun show(user: User) 23 | } 24 | 25 | data class User(val name: String, val friends: List, val profile: Profile) 26 | data class Friend(val id: String) 27 | data class Profile(val description: String) 28 | 29 | class TestUserDataRepository : UserDataRepository { 30 | override suspend fun notifyProfileShown() { 31 | delay(10000) 32 | } 33 | 34 | override suspend fun getName(): String { 35 | delay(1000) 36 | return "Ben" 37 | } 38 | 39 | override suspend fun getFriends(): List { 40 | delay(1000) 41 | return listOf(Friend("some-friend-id-1")) 42 | } 43 | 44 | override suspend fun getProfile(): Profile { 45 | delay(1000) 46 | return Profile("Example description") 47 | } 48 | } 49 | 50 | class TestUserDataView : UserDataView { 51 | override fun show(user: User) { 52 | print(user) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ShowUserDataTest.kt: -------------------------------------------------------------------------------- 1 | package userdata 2 | 3 | import kotlinx.coroutines.ObsoleteCoroutinesApi 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.runBlocking 6 | import org.junit.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertTrue 9 | 10 | @ObsoleteCoroutinesApi 11 | @Suppress("FunctionName") 12 | class ShowUserDataTest { 13 | 14 | @Test 15 | fun `should show data on view`() = runBlocking { 16 | // given 17 | val repo = FakeUserDataRepository() 18 | val view = FakeUserDataView() 19 | 20 | // when 21 | showUserData(repo, view) 22 | 23 | // then 24 | assertEquals( 25 | listOf(User("Ben", listOf(Friend("some-friend-id-1")), Profile("Example description"))), 26 | view.printed 27 | ) 28 | } 29 | 30 | @Test(timeout = 800) 31 | fun `should load user data asynchronously and not wait for notify`() = runBlocking { 32 | // given 33 | val repo = FakeUserDataRepository() 34 | val view = FakeUserDataView() 35 | 36 | // when 37 | showUserData(repo, view) 38 | 39 | // then 40 | assertEquals(1, view.printed.size) 41 | } 42 | 43 | @Test 44 | fun `should start notify profile shown`() = runBlocking { 45 | // given 46 | val repo = FakeUserDataRepository() 47 | val view = FakeUserDataView() 48 | 49 | // when 50 | showUserData(repo, view) 51 | delay(100) 52 | 53 | // then 54 | assertTrue(repo.notifyCalled) 55 | } 56 | 57 | class FakeUserDataRepository : UserDataRepository { 58 | var notifyCalled = false 59 | 60 | override suspend fun notifyProfileShown() { 61 | notifyCalled = true 62 | delay(2000) 63 | } 64 | 65 | override suspend fun getName(): String { 66 | delay(300) 67 | return "Ben" 68 | } 69 | 70 | override suspend fun getFriends(): List { 71 | delay(300) 72 | return listOf(Friend("some-friend-id-1")) 73 | } 74 | 75 | override suspend fun getProfile(): Profile { 76 | delay(300) 77 | return Profile("Example description") 78 | } 79 | } 80 | 81 | class FakeUserDataView : UserDataView { 82 | var printed = listOf() 83 | 84 | override fun show(user: User) { 85 | printed = printed + user 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Sieve.kt: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | import kotlin.test.assertEquals 3 | 4 | val primes = sequence { 5 | TODO() 6 | } 7 | 8 | // TODO: Delete it and replace it with sequence builder (above) 9 | fun getPrimeNumbers(num: Int): List { 10 | var numbers = generateSequence(2) { it + 1 } 11 | val primes = mutableListOf() 12 | repeat(num) { 13 | val prime = numbers.first() 14 | primes += prime 15 | numbers = numbers.drop(1).filter { it % prime != 0 } 16 | } 17 | return primes 18 | } 19 | 20 | fun main() { 21 | print(getPrimeNumbers(20)) 22 | // print(primes.take(20).toList()) 23 | } 24 | 25 | @Suppress("FunctionName") 26 | class SieveTests { 27 | 28 | @Test 29 | fun `First prime number is 2`() { 30 | assert(2 in primes) 31 | assertEquals(0, primes.indexOf(2)) 32 | assertEquals(2, primes.first()) 33 | } 34 | 35 | @Test 36 | fun `Second prime number is 3`() { 37 | assert(3 in primes) 38 | assertEquals(1, primes.indexOf(3)) 39 | assertEquals(3, primes.take(2).last()) 40 | } 41 | 42 | @Test 43 | fun `Third prime number is 5`() { 44 | assert(5 in primes) 45 | assertEquals(2, primes.indexOf(5)) 46 | assertEquals(5, primes.take(3).last()) 47 | } 48 | 49 | @Test 50 | fun `Check first 10 prime numbers`() { 51 | assertEquals(listOf(2, 3, 5, 7, 11, 13, 17, 19, 23, 29), primes.take(10).toList()) 52 | } 53 | 54 | @Test 55 | fun `Check first 100 prime numbers`() { 56 | val first100primes = listOf( 57 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 58 | 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 59 | 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 60 | 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 61 | 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541 62 | ) 63 | assertEquals(first100primes, primes.take(100).toList()) 64 | } 65 | 66 | @Test 67 | fun `Prime at 200th position is 1223`() { 68 | assertEquals(1223, primes.take(200).last()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Tweets.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.CoroutineScope 2 | import kotlinx.coroutines.async 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.runBlocking 5 | 6 | fun main() = runBlocking { 7 | val tweets = async { getTweets() } 8 | val details = try { 9 | getUserDetails() 10 | } catch (e: Error) { 11 | null 12 | } 13 | println("User: $details") 14 | println("Tweets: ${tweets.await()}") 15 | } 16 | 17 | suspend fun getUserDetails(): Details { 18 | val userName = getUserName() 19 | val followersNumber = getFollowersNumber() 20 | return Details(userName, followersNumber) 21 | } 22 | 23 | data class Details(val name: String, val followers: Int) 24 | data class Tweet(val text: String) 25 | 26 | suspend fun getFollowersNumber(): Int { 27 | delay(1000) 28 | return 42 29 | } 30 | 31 | suspend fun getUserName(): String { 32 | delay(1500) 33 | return "marcinmoskala" 34 | } 35 | 36 | suspend fun getTweets(): List { 37 | delay(2000) 38 | return listOf(Tweet("Hello, world")) 39 | } 40 | -------------------------------------------------------------------------------- /src/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import kotlinx.coroutines.* 4 | import org.junit.Test 5 | import kotlin.system.measureTimeMillis 6 | import kotlin.test.assertEquals 7 | 8 | class DiscUserRepository(private val discReader: DiscReader) : UserRepository { 9 | override suspend fun getUser(userId: String): UserData = UserData(discReader.read("user/$userId")) 10 | } 11 | 12 | interface DiscReader { 13 | fun read(key: String): String 14 | } 15 | 16 | interface UserRepository { 17 | suspend fun getUser(userId: String): UserData 18 | } 19 | 20 | data class UserData(val name: String) 21 | 22 | @Suppress("FunctionName") 23 | class DiscUserRepositoryTests { 24 | 25 | @Test 26 | fun `should read data from disc using DiscReader`() = runBlocking { 27 | val name = "Marcin" 28 | val repo = DiscUserRepository(OneSecDiscReader("Marcin")) 29 | val res = repo.getUser("SomeUserId") 30 | assertEquals(name, res.name) 31 | } 32 | 33 | class ImmediateDiscReader(val map: Map) : DiscReader { 34 | override fun read(key: String): String = map[key] ?: error("Element not found") 35 | } 36 | 37 | @Test 38 | fun `should be prepared for many reads at the same time`() = runBlocking { 39 | val repo = DiscUserRepository(OneSecDiscReader("Marcin")) 40 | val time = measureTimeMillis { 41 | coroutineScope { 42 | repeat(10) { id -> 43 | launch { 44 | repo.getUser("SomeUserId$id") 45 | } 46 | } 47 | } 48 | } 49 | assert(time < 2000) { "Should take less than 2000, took $time" } 50 | } 51 | 52 | @Test 53 | fun `should be prepared for 200 reads at the same time`() = runBlocking { 54 | val repo = DiscUserRepository(OneSecDiscReader("Marcin")) 55 | val time = measureTimeMillis { 56 | coroutineScope { 57 | repeat(200) { id -> 58 | launch { 59 | repo.getUser("SomeUserId$id") 60 | } 61 | } 62 | } 63 | } 64 | assert(time < 2000) { "Should take less than 2000, took $time" } 65 | } 66 | 67 | class OneSecDiscReader(private val response: String) : DiscReader { 68 | override fun read(key: String): String { 69 | Thread.sleep(1000) 70 | return response 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/UsersSequence.kt: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.count 5 | import kotlinx.coroutines.test.runTest 6 | import org.junit.Test 7 | import kotlin.test.assertEquals 8 | 9 | data class User(val name: String) 10 | 11 | interface UserRepository { 12 | fun takePage(pageNumber: Int): List 13 | } 14 | 15 | fun makeUsersSequence(repository: UserRepository): Flow = TODO() 16 | 17 | @Suppress("FunctionName") 18 | internal class UsersSequenceTests { 19 | 20 | @Test 21 | fun test() = runTest { 22 | val size = 10_000 23 | val pageSize = 10 24 | val repo = FakeUserRepository(size, pageSize) 25 | val s = makeUsersSequence(repo) 26 | assertEquals(size, s.count()) 27 | assertEquals(size / pageSize + 1, repo.timesUsed) 28 | } 29 | 30 | class FakeUserRepository(val size: Int, val pageSize: Int) : UserRepository { 31 | val users = List(size) { User("User$it") } 32 | var timesUsed = 0 33 | 34 | override fun takePage(pageNumber: Int): List = 35 | users.dropLast(pageSize * pageNumber).take(pageSize).also { timesUsed++ } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Utils.kt: -------------------------------------------------------------------------------- 1 | import kotlin.test.assertEquals 2 | 3 | inline fun assertThrows(body: () -> Unit) { 4 | val error = try { 5 | body() 6 | Any() 7 | } catch (t: Throwable) { 8 | t 9 | } 10 | assertEquals(T::class, error::class) 11 | } -------------------------------------------------------------------------------- /src/actors/Actors.kt: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import kotlinx.coroutines.* 4 | import kotlinx.coroutines.channels.SendChannel 5 | import java.util.* 6 | 7 | // Finish the below implementation by sending messages and implementing the following actors: 8 | // Worker is informed every 800. If there are less than 5 machines it produces a new one. 9 | // Every machine produces a code using `structured.produce` function every second or breaks (random). 10 | // Manager collects all the codes, and stores them using `storeCode`, and if there are more than 20 stored is ends everything. 11 | 12 | fun main() = runBlocking { 13 | setupFactory(StandardFactoryControl()) 14 | } 15 | 16 | fun CoroutineScope.setupFactory(control: FactoryControl) = launch { 17 | val managerChannel = managerActor(control) 18 | val workerChannel = workerActor(control, managerChannel) 19 | launch { 20 | repeat(1000) { 21 | delay(800) 22 | // TODO 23 | } 24 | } 25 | } 26 | 27 | fun CoroutineScope.managerActor(control: FactoryControl): SendChannel = TODO() 28 | 29 | fun CoroutineScope.workerActor( 30 | control: FactoryControl, 31 | managerChannel: SendChannel 32 | ): SendChannel = TODO() 33 | 34 | fun CoroutineScope.startMachine( 35 | control: FactoryControl, 36 | workerChannel: SendChannel, 37 | managerChannel: SendChannel 38 | ) { 39 | val machine = control.makeMachine() 40 | launch { 41 | try { 42 | repeat(1000) { 43 | delay(1000) 44 | val code = machine.produce() 45 | // TODO 46 | } 47 | } catch (error: ProductionError) { 48 | // TODO 49 | } 50 | } 51 | } 52 | 53 | interface ManagerMessages 54 | interface WorkerMessages 55 | 56 | interface FactoryControl { 57 | fun makeMachine(): Machine 58 | fun storeCode(code: String) 59 | } 60 | 61 | class StandardFactoryControl : FactoryControl { 62 | private var broken = false 63 | private var waiting = false 64 | private var codes = listOf() 65 | 66 | override fun makeMachine(): Machine = StandardMachine() 67 | .also { println("Newly created machine") } 68 | 69 | override fun storeCode(code: String) { 70 | if (waiting || broken) { 71 | println("Factory control is broken due to 2 attempts to store code at the same time") 72 | broken = true 73 | throw BrokenMachineError() 74 | } 75 | waiting = true 76 | Thread.sleep(500) 77 | waiting = false 78 | codes = codes + code 79 | println("Newly stored code is $code") 80 | } 81 | } 82 | 83 | interface Machine { 84 | @Throws(ProductionError::class) 85 | fun produce(): String 86 | } 87 | 88 | class StandardMachine : Machine { 89 | private var broken = false 90 | 91 | override fun produce(): String = when { 92 | broken -> throw BrokenMachineError() 93 | random.nextInt(8) == 0 -> { 94 | broken = true 95 | println("Machine broken") 96 | throw ProductionError() 97 | } 98 | else -> (1..5).map { letters[random.nextInt(letters.size)] }.joinToString(separator = "") 99 | .also { println("Newly produced code $it") } 100 | } 101 | 102 | companion object { 103 | private val letters = ('a'..'z') + ('0'..'9') 104 | private val random = Random() 105 | } 106 | } 107 | 108 | class ProductionError() : Throwable() 109 | class BrokenMachineError() : Throwable() 110 | -------------------------------------------------------------------------------- /src/actors/ActorsTests.kt: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import assertThrows 4 | import kotlinx.coroutines.ExperimentalCoroutinesApi 5 | import kotlinx.coroutines.ObsoleteCoroutinesApi 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | import kotlinx.coroutines.test.currentTime 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Test 11 | import kotlin.test.assertEquals 12 | 13 | @ObsoleteCoroutinesApi 14 | @Suppress("FunctionName") 15 | class ActorsTests { 16 | 17 | class FakeFactoryControl( 18 | val machineProducer: () -> Machine 19 | ) : FactoryControl { 20 | var createdMachines = listOf() 21 | var codesStored = listOf() 22 | private var finished = false 23 | 24 | override fun makeMachine(): Machine { 25 | require(!finished) 26 | return machineProducer() 27 | .also { createdMachines = createdMachines + it } 28 | } 29 | 30 | override fun storeCode(code: String) { 31 | require(!finished) 32 | codesStored = codesStored + code 33 | } 34 | 35 | fun finish() { 36 | finished = true 37 | } 38 | } 39 | 40 | class PerfectMachine : Machine { 41 | var timesUsed = 0 42 | private var finished = false 43 | 44 | override fun produce(): String { 45 | require(!finished) 46 | return (timesUsed++).toString() 47 | } 48 | 49 | fun finish() { 50 | finished = true 51 | } 52 | } 53 | 54 | class FailingMachine : Machine { 55 | override fun produce(): String = throw ProductionError() 56 | } 57 | 58 | @Test(timeout = 500) 59 | fun `PerfectMachine produces next numbers`() { 60 | val machine = PerfectMachine() 61 | assertEquals("0", machine.produce()) 62 | assertEquals("1", machine.produce()) 63 | assertEquals("2", machine.produce()) 64 | assertEquals("3", machine.produce()) 65 | assertEquals("4", machine.produce()) 66 | } 67 | 68 | @Test(timeout = 500) 69 | fun `FakeFactoryControl produces machines using producer`() { 70 | val perfectFactoryControl = FakeFactoryControl(machineProducer = ::PerfectMachine) 71 | val machine1 = perfectFactoryControl.makeMachine() 72 | assertEquals("0", machine1.produce()) 73 | assertEquals("1", machine1.produce()) 74 | assertEquals("2", machine1.produce()) 75 | val machine2 = perfectFactoryControl.makeMachine() 76 | assertEquals("0", machine2.produce()) 77 | assertEquals("1", machine2.produce()) 78 | assertEquals("2", machine2.produce()) 79 | 80 | val failingFactoryControl = FakeFactoryControl(machineProducer = ::FailingMachine) 81 | val machine3 = failingFactoryControl.makeMachine() 82 | assertThrows { machine3.produce() } 83 | } 84 | 85 | @Test 86 | fun `Function creates a new machine every 800ms up to 5 and no more if they are all perfect`() = runTest { 87 | val control = FakeFactoryControl(machineProducer = ::PerfectMachine) 88 | 89 | setupFactory(control) 90 | delay(5 * 800 + 10) 91 | for (i in 0..10) { 92 | assertEquals(5, control.createdMachines.size) 93 | delay(800) 94 | } 95 | } 96 | 97 | 98 | @Test 99 | fun `Function creates a new machine every 800ms every time if all machines are failing`() = runTest { 100 | val control = FakeFactoryControl(machineProducer = ::FailingMachine) 101 | 102 | val job = launch { setupFactory(control) } 103 | for (i in 0..20) { 104 | delay(800) 105 | assertEquals(i, control.createdMachines.size) 106 | } 107 | job.cancel() 108 | } 109 | 110 | @Test 111 | fun `Function creates a new machine after 800ms if less then 5`() = runTest { 112 | var correctMachines = 0 113 | var nextIsCorrect = false 114 | val control = FakeFactoryControl(machineProducer = { 115 | val next = if (nextIsCorrect) { 116 | correctMachines++ 117 | PerfectMachine() 118 | } else { 119 | FailingMachine() 120 | } 121 | nextIsCorrect = !nextIsCorrect 122 | next 123 | }) 124 | 125 | 126 | setupFactory(control) 127 | delay(20_000) 128 | 129 | assertEquals(5, control.createdMachines.filterIsInstance().size) 130 | 131 | // Is not producing any new 132 | val producedPre = control.createdMachines.size 133 | delay(2_000) 134 | 135 | val producedPost = control.createdMachines.size 136 | assertEquals( 137 | producedPre, 138 | producedPost, 139 | "It should not produce any new machines when there are already 5 perfect" 140 | ) 141 | } 142 | 143 | 144 | @Test 145 | fun `The first code should be created after time to create machine and time to produce code`() = runTest { 146 | val perfectMachine = PerfectMachine() 147 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 148 | 149 | setupFactory(control) 150 | delay(800 + 1000 + 10) 151 | 152 | assertEquals(1, perfectMachine.timesUsed) 153 | } 154 | 155 | 156 | /* 157 | 800 1600 1800 2400 2600 2800 3200 3400 3600 3800 158 | m1 ----------> CODE --------------> CODE --------------> CODE 159 | m1 -----------------> CODE ---------------> CODE ----- 160 | m3 ------------------> CODE ---------- 161 | m4 ---------------- 162 | Codes 1 2 3 4 5 6 163 | */ 164 | @Test 165 | fun `Every machine produces code every second`() = runTest { 166 | val perfectMachine = PerfectMachine() 167 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 168 | 169 | suspend fun checkAt(timeMillis: Long, codes: Int) { 170 | delay(timeMillis - currentTime) 171 | 172 | assertEquals(codes, perfectMachine.timesUsed) 173 | } 174 | 175 | setupFactory(control) 176 | checkAt(800, 0) 177 | checkAt(1600, 0) 178 | checkAt(1800, 1) 179 | checkAt(2400, 1) 180 | checkAt(2600, 2) 181 | checkAt(2800, 3) 182 | checkAt(3200, 3) 183 | checkAt(3400, 4) 184 | checkAt(3600, 5) 185 | checkAt(3800, 6) 186 | } 187 | 188 | @Test 189 | fun `Created codes are stored no later then 100ms after created`() = runTest { 190 | val perfectMachine = PerfectMachine() 191 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 192 | 193 | suspend fun checkAt(timeMillis: Long, codes: Int) { 194 | delay(timeMillis - currentTime) 195 | assertEquals(codes, control.codesStored.size) 196 | } 197 | 198 | setupFactory(control) 199 | checkAt(900, 0) 200 | checkAt(1700, 0) 201 | checkAt(1900, 1) 202 | checkAt(2500, 1) 203 | checkAt(2700, 2) 204 | checkAt(2900, 3) 205 | checkAt(3300, 3) 206 | checkAt(3500, 4) 207 | checkAt(3700, 5) 208 | checkAt(3900, 6) 209 | } 210 | 211 | @Test 212 | fun `When there are 20 codes stored, process ends`() = runTest { 213 | val perfectMachine = PerfectMachine() 214 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 215 | 216 | setupFactory(control) 217 | delay(6810) // Time when 20'th code is produced + 10ms 218 | 219 | assertEquals(20, control.codesStored.size) 220 | delay(1_000) 221 | 222 | assertEquals(20, control.codesStored.size) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/backend/Api.kt: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.runBlocking 5 | 6 | data class User(val name: String) 7 | 8 | fun setupApi(database: Database, emailService: EmailService) { 9 | api { 10 | get("hello") { 11 | "Hello, world" 12 | } 13 | get("slow_hello") { 14 | delay(1000) 15 | "Hello, world" 16 | } 17 | get("users") { 18 | // TODO: Return users from DB 19 | } 20 | post("user") { user -> 21 | // TODO: Add user to DB and send email to "contact@kt.academy" with body "New user $name" 22 | } 23 | get("users/count") { 24 | // TODO: Get users count 25 | } 26 | }.start() 27 | } 28 | 29 | fun main() { 30 | val database = DatabaseImpl() 31 | val emailService = EmailServiceImpl() 32 | setupApi(database, emailService) 33 | runBlocking { 34 | println(get("hello")) 35 | println("User count is ${get("user/count")}") 36 | println("Users are ${get("users")}") 37 | post("user", User("Marcin")) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/backend/ApiDsl.kt: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | 5 | fun api(config: ApiConfig.() -> Unit) = ApiConfig().also(config) 6 | 7 | fun cleanupApi() { 8 | publicHandles = mapOf() 9 | } 10 | 11 | private var publicHandles = mapOf Any>() 12 | 13 | data class Endpoint(val method: String, val path: String) 14 | 15 | class ApiConfig { 16 | var handles = mapOf Any>() 17 | 18 | fun get(path: String, handle: suspend CoroutineScope.(body: Any?) -> Any) { 19 | addHandle("get", path, handle) 20 | } 21 | 22 | inline fun post(path: String, noinline handle: suspend CoroutineScope.(body: T) -> Any) { 23 | addHandle("post", path, { handle(it as T) }) 24 | } 25 | 26 | fun start() { 27 | publicHandles = publicHandles + handles 28 | } 29 | 30 | fun addHandle(method: String, path: String, handle: suspend CoroutineScope.(body: Any?) -> Any) { 31 | handles = handles + (Endpoint(method, path) to handle) 32 | } 33 | } 34 | 35 | @Suppress("SuspendFunctionOnCoroutineScope") 36 | suspend fun CoroutineScope.get(path: String, body: Any? = null) = this.respond("get", path, body) 37 | 38 | @Suppress("SuspendFunctionOnCoroutineScope") 39 | suspend fun CoroutineScope.post(path: String, body: Any? = null) = this.respond("post", path, body) 40 | 41 | suspend fun CoroutineScope.respond(method: String, path: String, body: Any?): Any? { 42 | val handle = publicHandles[Endpoint(method, path)] ?: throw NoSuchMethodError() 43 | return try { 44 | handle(this, body) 45 | } catch (e: Throwable) { 46 | e.printStackTrace() 47 | null 48 | } 49 | } -------------------------------------------------------------------------------- /src/backend/ApiTests.kt: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import org.junit.After 5 | import org.junit.Test 6 | import kotlin.test.assertEquals 7 | 8 | @Suppress("FunctionName") 9 | class ApiTests { 10 | 11 | @After 12 | fun clean() { 13 | cleanupApi() 14 | } 15 | 16 | class FakeEmailService : EmailService { 17 | var emailsSend = listOf>() 18 | 19 | override suspend fun sendEmail(to: String, body: String) { 20 | emailsSend = emailsSend + (to to body) 21 | } 22 | } 23 | 24 | class FakeDatabase : Database { 25 | var users = listOf() 26 | 27 | override suspend fun getUsers(): List { 28 | return users 29 | } 30 | 31 | override suspend fun addUser(user: User) { 32 | users = users + user 33 | } 34 | } 35 | 36 | @Test 37 | fun `When user is added, email is sent`() = runBlocking { 38 | val emailService = FakeEmailService() 39 | setupApi(FakeDatabase(), emailService) 40 | post("user", User("Piotr")) 41 | assertEquals(listOf("contact@kt.academy" to "New user Piotr"), emailService.emailsSend) 42 | } 43 | 44 | @Test 45 | fun `When we add email, it is added to the database`() = runBlocking { 46 | val database = FakeDatabase() 47 | setupApi(database, FakeEmailService()) 48 | 49 | assertEquals(listOf(), get("users")) 50 | assertEquals(0, get("users/count")) 51 | 52 | post("user", User("Piotr")) 53 | assertEquals(listOf(User("Piotr")), database.users) 54 | val users = get("users") 55 | assertEquals(listOf(User("Piotr")), users) 56 | assertEquals(1, get("users/count")) 57 | } 58 | } -------------------------------------------------------------------------------- /src/backend/Database.kt: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import kotlinx.coroutines.delay 4 | 5 | interface Database { 6 | suspend fun getUsers(): List 7 | suspend fun addUser(user: User) 8 | } 9 | 10 | class DatabaseImpl : Database { 11 | 12 | var users = listOf() 13 | 14 | override suspend fun getUsers(): List { 15 | delay(500) 16 | return users 17 | } 18 | 19 | override suspend fun addUser(user: User) { 20 | delay(500) 21 | users = users + user 22 | } 23 | } -------------------------------------------------------------------------------- /src/backend/EmailService.kt: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import kotlinx.coroutines.delay 4 | 5 | interface EmailService { 6 | suspend fun sendEmail(to: String, body: String) 7 | } 8 | 9 | class EmailServiceImpl : EmailService { 10 | 11 | override suspend fun sendEmail(to: String, body: String) { 12 | delay(2000) 13 | print("Sent email to $to with body $body") 14 | } 15 | } -------------------------------------------------------------------------------- /src/callback/FetchTasksUseCase.kt: -------------------------------------------------------------------------------- 1 | package callback 2 | 3 | import kotlinx.coroutines.ExperimentalCoroutinesApi 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.test.runCurrent 6 | import kotlinx.coroutines.test.runTest 7 | import kotlin.test.Test 8 | import kotlin.test.assertEquals 9 | import kotlin.test.assertNotNull 10 | import kotlin.test.assertTrue 11 | 12 | class FetchTasksUseCase( 13 | private val callbackUseCase: FetchTasksCallbackUseCase 14 | ) { 15 | @Throws(ApiException::class) 16 | suspend fun fetchTasks(): List = TODO() 17 | suspend fun fetchTasksResult(): Result> = TODO() 18 | suspend fun fetchTasksOrNull(): List? = TODO() 19 | } 20 | 21 | interface FetchTasksCallbackUseCase { 22 | fun fetchTasks(onSuccess: (List) -> Unit, onError: (Throwable) -> Unit): Cancellable 23 | } 24 | 25 | fun interface Cancellable { 26 | fun cancel() 27 | } 28 | data class Task(val name: String, val priority: Int) 29 | class ApiException(val code: Int, message: String): Throwable(message) 30 | 31 | @OptIn(ExperimentalCoroutinesApi::class) 32 | class FetchTasksTests { 33 | val someTasks = listOf(Task("1", 123), Task("2", 456)) 34 | val someException = ApiException(500, "Some exception") 35 | 36 | @Test 37 | fun `fetchTasks should resume with result`() = runTest { 38 | // given 39 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 40 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 41 | var result: List? = null 42 | 43 | // when 44 | launch { 45 | result = useCase.fetchTasks() 46 | } 47 | 48 | // then 49 | runCurrent() 50 | assertEquals(null, result) 51 | fakeFetchTaskCallback.onSuccess?.invoke(someTasks) 52 | runCurrent() 53 | assertEquals(someTasks, result) 54 | } 55 | 56 | @Test 57 | fun `fetchTasks should resume with exception`() = runTest { 58 | // given 59 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 60 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 61 | var exception: Throwable? = null 62 | 63 | // when 64 | launch { 65 | try { 66 | useCase.fetchTasks() 67 | } catch (e: Throwable) { 68 | exception = e 69 | } 70 | } 71 | 72 | // then 73 | runCurrent() 74 | assertEquals(null, exception) 75 | fakeFetchTaskCallback.onError?.invoke(someException) 76 | runCurrent() 77 | assertEquals(someException, exception) 78 | } 79 | 80 | @Test 81 | fun `fetchTasks should support cancellation`() = runTest { 82 | // given 83 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 84 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 85 | var cancelled = false 86 | fakeFetchTaskCallback.onCancelled = { cancelled = true } 87 | 88 | // when 89 | val job = launch { 90 | useCase.fetchTasks() 91 | } 92 | 93 | // then 94 | runCurrent() 95 | assertEquals(false, cancelled) 96 | job.cancel() 97 | assertEquals(true, cancelled) 98 | } 99 | 100 | @Test 101 | fun `fetchTasksResult should resume with result`() = runTest { 102 | // given 103 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 104 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 105 | var result: Result>? = null 106 | 107 | // when 108 | launch { 109 | result = useCase.fetchTasksResult() 110 | } 111 | 112 | // then 113 | runCurrent() 114 | assertEquals(null, result) 115 | fakeFetchTaskCallback.onSuccess?.invoke(someTasks) 116 | runCurrent() 117 | assertNotNull(result) 118 | assertTrue(result!!.isSuccess) 119 | assertEquals(someTasks, result!!.getOrNull()) 120 | } 121 | 122 | @Test 123 | fun `fetchTasksResult should resume with failure`() = runTest { 124 | // given 125 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 126 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 127 | var result: Result>? = null 128 | 129 | // when 130 | launch { 131 | result = useCase.fetchTasksResult() 132 | } 133 | 134 | // then 135 | runCurrent() 136 | assertEquals(null, result) 137 | fakeFetchTaskCallback.onError?.invoke(someException) 138 | runCurrent() 139 | assertNotNull(result) 140 | assertTrue(result!!.isFailure) 141 | assertEquals(someException, result!!.exceptionOrNull()) 142 | } 143 | 144 | @Test 145 | fun `fetchTasksResult should support cancellation`() = runTest { 146 | // given 147 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 148 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 149 | var cancelled = false 150 | fakeFetchTaskCallback.onCancelled = { cancelled = true } 151 | 152 | // when 153 | val job = launch { 154 | useCase.fetchTasksResult() 155 | } 156 | 157 | // then 158 | runCurrent() 159 | assertEquals(false, cancelled) 160 | job.cancel() 161 | assertEquals(true, cancelled) 162 | } 163 | 164 | @Test 165 | fun `fetchTasksOrNull should resume with result`() = runTest { 166 | // given 167 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 168 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 169 | val NO_VALUE = Any() 170 | var result: Any? = NO_VALUE 171 | 172 | // when 173 | launch { 174 | result = useCase.fetchTasksOrNull() 175 | } 176 | 177 | // then 178 | runCurrent() 179 | assertEquals(NO_VALUE, result) 180 | fakeFetchTaskCallback.onSuccess?.invoke(someTasks) 181 | runCurrent() 182 | assertEquals(someTasks, result) 183 | } 184 | 185 | @Test 186 | fun `fetchTasksOrNull should resume with failure`() = runTest { 187 | // given 188 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 189 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 190 | val NO_VALUE = Any() 191 | var result: Any? = NO_VALUE 192 | 193 | // when 194 | launch { 195 | result = useCase.fetchTasksOrNull() 196 | } 197 | 198 | // then 199 | runCurrent() 200 | assertEquals(NO_VALUE, result) 201 | fakeFetchTaskCallback.onError?.invoke(someException) 202 | runCurrent() 203 | assertEquals(null, result) 204 | } 205 | 206 | @Test 207 | fun `fetchTasksOrNull should support cancellation`() = runTest { 208 | // given 209 | val fakeFetchTaskCallback = FakeFetchTasksCallbackUseCase() 210 | val useCase = FetchTasksUseCase(fakeFetchTaskCallback) 211 | var cancelled = false 212 | fakeFetchTaskCallback.onCancelled = { cancelled = true } 213 | 214 | // when 215 | val job = launch { 216 | useCase.fetchTasksOrNull() 217 | } 218 | 219 | // then 220 | runCurrent() 221 | assertEquals(false, cancelled) 222 | job.cancel() 223 | assertEquals(true, cancelled) 224 | } 225 | 226 | class FakeFetchTasksCallbackUseCase: FetchTasksCallbackUseCase { 227 | var onSuccess: ((List) -> Unit)? = null 228 | var onError: ((Throwable) -> Unit)? = null 229 | var onCancelled: (()->Unit)? = null 230 | 231 | override fun fetchTasks(onSuccess: (List) -> Unit, onError: (Throwable) -> Unit): Cancellable { 232 | this.onSuccess = onSuccess 233 | this.onError = onError 234 | return Cancellable { onCancelled?.invoke() } 235 | } 236 | } 237 | } -------------------------------------------------------------------------------- /src/channel/OfferReclassify.kt: -------------------------------------------------------------------------------- 1 | package channel 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.flow.catch 6 | import kotlinx.coroutines.flow.collect 7 | import kotlinx.coroutines.flow.flow 8 | import kotlinx.coroutines.flow.flowOf 9 | import kotlinx.coroutines.flow.map 10 | import kotlinx.coroutines.runBlocking 11 | import kotlinx.coroutines.sync.Mutex 12 | import kotlinx.coroutines.sync.withLock 13 | import kotlinx.coroutines.test.currentTime 14 | import kotlinx.coroutines.test.runTest 15 | import javax.naming.ServiceUnavailableException 16 | import kotlin.random.Random 17 | import kotlin.test.Test 18 | import kotlin.test.assertEquals 19 | 20 | suspend fun reclassifyAllOffers(offerStoreApi: OfferStoreApi, sendEventApi: SendEventApi) = coroutineScope { 21 | TODO() 22 | } 23 | 24 | interface OfferStoreApi { 25 | suspend fun getNextOfferIds(last: OfferId?): List 26 | } 27 | 28 | interface SendEventApi { 29 | @Throws(ServiceUnavailableException::class) 30 | suspend fun reclassifyOffer(id: OfferId) 31 | } 32 | 33 | data class OfferId(val raw: String) 34 | 35 | class TestOfferStoreApi(private val callDelay: Long = 0) : OfferStoreApi { 36 | val allIds = List(1000) { OfferId("Offer$it") } 37 | private val mutex = Mutex() 38 | 39 | override suspend fun getNextOfferIds(last: OfferId?): List = mutex.withLock { 40 | delay(callDelay) 41 | val remaining = if (last == null) allIds else allIds.dropWhile { it != last }.drop(1) 42 | return remaining.take(10) 43 | } 44 | } 45 | 46 | class TestSendEventApi(sometimesFailing: Boolean = false, private val callDelay: Long = 0) : SendEventApi { 47 | val allIds = List(1000) { OfferId("Offer$it") } 48 | var failingIds = if (sometimesFailing) allIds.shuffled().take(10) else emptyList() 49 | var reclassified = listOf() 50 | private val mutex = Mutex() 51 | 52 | override suspend fun reclassifyOffer(id: OfferId) = mutex.withLock { 53 | delay(callDelay) 54 | if (id in failingIds) { 55 | if (Random.nextBoolean()) failingIds = failingIds - id 56 | throw ServiceUnavailableException() 57 | } 58 | reclassified = reclassified + id 59 | } 60 | } 61 | 62 | class OfferReclassifyTest { 63 | 64 | @Test 65 | fun `Should reclassify all elements`() = runTest { 66 | val offerStoreApi = TestOfferStoreApi() 67 | val sendEventApi = TestSendEventApi() 68 | reclassifyAllOffers(offerStoreApi, sendEventApi) 69 | assertEquals(sendEventApi.allIds.sortedBy { it.raw }, sendEventApi.reclassified.sortedBy { it.raw }) 70 | } 71 | 72 | @Test 73 | fun `Should get and reclassify asynchroniously`() = runTest { 74 | val callDelay = 10L 75 | val offerStoreApi = TestOfferStoreApi(callDelay = callDelay) 76 | val sendEventApi = TestSendEventApi(callDelay = callDelay) 77 | val before = currentTime 78 | reclassifyAllOffers(offerStoreApi, sendEventApi) 79 | val after = currentTime 80 | assertEquals(1001 * callDelay, after - before) 81 | } 82 | 83 | // Extra 84 | // @Test 85 | // fun `Should try to classify again if failed`() = runTest { 86 | // val offerStoreApi = TestOfferStoreApi() 87 | // val sendEventApi = TestSendEventApi(sometimesFailing = true) 88 | // reclassifyAllOffers(offerStoreApi, sendEventApi) 89 | // assertEquals(sendEventApi.allIds.sortedBy { it.raw }, sendEventApi.reclassified.sortedBy { it.raw }) 90 | // } 91 | } -------------------------------------------------------------------------------- /src/coffee/Coffee.kt: -------------------------------------------------------------------------------- 1 | package coffee 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.launch 5 | 6 | data class Order(val customer: String, val type: CoffeeType) 7 | enum class CoffeeType { ESPRESSO, LATE } 8 | class Milk 9 | class GroundCoffee 10 | 11 | sealed class Coffee 12 | class Espresso(ground: GroundCoffee) : Coffee() { 13 | override fun toString(): String = "Espresso" 14 | } 15 | 16 | class Latte(milk: Milk, espresso: Espresso) : Coffee() { 17 | override fun toString(): String = "Latte" 18 | } 19 | 20 | suspend fun main() = coroutineScope { 21 | val orders = List(100) { Order("Customer$it", CoffeeType.values().random()) } 22 | val startTime = System.currentTimeMillis() 23 | 24 | serveOrders(orders) { coffee, customer, barista -> 25 | println("Coffee $coffee for $customer made by $barista") 26 | } 27 | 28 | val endTime = System.currentTimeMillis() 29 | println("Done, took ${endTime - startTime}") 30 | } 31 | 32 | suspend fun serveOrders(orders: List, serveCoffee: (coffee: Coffee, customer: String, barista: String) -> Unit) = 33 | coroutineScope { 34 | for (order in orders) { 35 | launch { 36 | val groundCoffee = groundCoffee() 37 | val espresso = makeEspresso(groundCoffee) 38 | val coffee = when (order.type) { 39 | CoffeeType.ESPRESSO -> espresso 40 | CoffeeType.LATE -> { 41 | val milk = brewMilk() 42 | Latte(milk, espresso) 43 | } 44 | } 45 | serveCoffee(coffee, order.customer, "Bob") 46 | } 47 | } 48 | } 49 | 50 | fun groundCoffee(): GroundCoffee { 51 | longOperation() 52 | return GroundCoffee() 53 | } 54 | 55 | fun brewMilk(): Milk { 56 | longOperation() 57 | return Milk() 58 | } 59 | 60 | 61 | fun makeEspresso(ground: GroundCoffee): Espresso { 62 | longOperation() 63 | return Espresso(ground) 64 | } 65 | 66 | class EspressoMachine { 67 | fun makeEspresso(ground: GroundCoffee): Espresso = synchronized(this) { 68 | Thread.sleep(1000) 69 | return Espresso(ground) 70 | } 71 | } 72 | 73 | fun longOperation() { 74 | // val size = 820 // ~1 second on my MacBook 75 | val size = 350 // ~0.1 second on my MacBook 76 | val list = List(size) { it } 77 | val listOfLists = List(size) { list } 78 | val listOfListsOfLists = List(size) { listOfLists } 79 | listOfListsOfLists.hashCode() 80 | // Thread.sleep(1000) 81 | } -------------------------------------------------------------------------------- /src/coffee/CoffeeChannel.kt: -------------------------------------------------------------------------------- 1 | package coffee.channel 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.channels.ReceiveChannel 5 | import kotlinx.coroutines.coroutineScope 6 | 7 | data class Order(val customer: String, val type: CoffeeType) 8 | enum class CoffeeType { ESPRESSO, LATE } 9 | class Milk 10 | class GroundCoffee 11 | 12 | sealed class Coffee 13 | class Espresso(ground: GroundCoffee) : Coffee() { 14 | override fun toString(): String = "Espresso" 15 | } 16 | 17 | class Latte(milk: Milk, espresso: Espresso) : Coffee() { 18 | override fun toString(): String = "Latte" 19 | } 20 | 21 | suspend fun main() = coroutineScope { 22 | val ordersList = List(100) { Order("Customer$it", CoffeeType.values().random()) } 23 | val orders = TODO() 24 | val startTime = System.currentTimeMillis() 25 | 26 | serveOrders(orders, "Alice") 27 | serveOrders(orders, "Bob") 28 | serveOrders(orders, "Celine") 29 | serveOrders(orders, "Dave") 30 | 31 | // for ((coffee, customer, barista) in servedOrders) { 32 | // println("Coffee $coffee for $customer made by $barista") 33 | // } 34 | 35 | val endTime = System.currentTimeMillis() 36 | println("Done, took ${endTime - startTime}") 37 | } 38 | 39 | data class CoffeeResult(val coffee: Coffee, val customer: String, val barista: String) 40 | 41 | fun CoroutineScope.serveOrders(orders: ReceiveChannel, baristaName: String): ReceiveChannel = TODO() 42 | // val coffee = makeCoffee(order) 43 | // send(CoffeeResult(coffee, order.customer, baristaName)) 44 | 45 | private suspend fun makeCoffee(order: Order): Coffee { 46 | val groundCoffee = groundCoffee() 47 | val espresso = makeEspresso(groundCoffee) 48 | return when (order.type) { 49 | CoffeeType.ESPRESSO -> espresso 50 | CoffeeType.LATE -> { 51 | val milk = brewMilk() 52 | Latte(milk, espresso) 53 | } 54 | } 55 | } 56 | 57 | suspend fun groundCoffee(): GroundCoffee { 58 | longOperation() 59 | return GroundCoffee() 60 | } 61 | 62 | suspend fun brewMilk(): Milk { 63 | longOperation() 64 | return Milk() 65 | } 66 | 67 | 68 | suspend fun makeEspresso(ground: GroundCoffee): Espresso { 69 | longOperation() 70 | return Espresso(ground) 71 | } 72 | 73 | class EspressoMachine { 74 | fun makeEspresso(ground: GroundCoffee): Espresso = synchronized(this) { 75 | Thread.sleep(1000) 76 | return Espresso(ground) 77 | } 78 | } 79 | 80 | suspend fun longOperation() { 81 | val size = 350 // ~0.1 second on my MacBook 82 | val list = List(size) { it } 83 | val listOfLists = List(size) { list } 84 | val listOfListsOfLists = List(size) { listOfLists } 85 | listOfListsOfLists.hashCode() 86 | // Thread.sleep(1000) 87 | // delay(1000) 88 | } -------------------------------------------------------------------------------- /src/coffee/CoffeeDispatchers.kt: -------------------------------------------------------------------------------- 1 | package coffee.dispatchers 2 | 3 | import kotlinx.coroutines.* 4 | import java.util.concurrent.Executors 5 | 6 | val dispatcher = Dispatchers.IO.limitedParallelism(1) 7 | //val dispatcher = Dispatchers.Default 8 | //val dispatcher = Dispatchers.IO 9 | //val dispatcher = Dispatchers.IO.limitedParallelism(100) 10 | 11 | val longOperation = ::cpu1 12 | //val longOperation = ::blocking 13 | //val longOperation = ::suspending 14 | 15 | fun cpu1() { 16 | var i = Int.MAX_VALUE / 10 17 | while (i > 0) { 18 | i -= if (i % 2 == 0) 1 else 2 19 | } 20 | } 21 | 22 | fun blocking() { 23 | Thread.sleep(1000) 24 | } 25 | 26 | suspend fun suspending() { 27 | delay(1000) 28 | } 29 | 30 | suspend fun serveOrders(orders: List) = 31 | coroutineScope { 32 | for (order in orders) { 33 | launch(dispatcher) { 34 | val coffee = makeCoffee(order) 35 | println("Coffee $coffee for ${order.customer} made by ${Thread.currentThread().name}") 36 | } 37 | } 38 | } 39 | 40 | suspend fun main() = withContext(dispatcher) { 41 | val orders = List(100) { Order("Customer$it", CoffeeType.values().random()) } 42 | val startTime = System.currentTimeMillis() 43 | 44 | serveOrders(orders) 45 | 46 | val endTime = System.currentTimeMillis() 47 | println("Done, took ${endTime - startTime}") 48 | } 49 | 50 | private suspend fun makeCoffee(order: Order): Coffee { 51 | val groundCoffee = groundCoffee() 52 | val espresso = makeEspresso(groundCoffee) 53 | val coffee = when (order.type) { 54 | CoffeeType.ESPRESSO -> espresso 55 | CoffeeType.LATE -> { 56 | val milk = brewMilk() 57 | Latte(milk, espresso) 58 | } 59 | } 60 | return coffee 61 | } 62 | 63 | suspend fun groundCoffee(): GroundCoffee { 64 | longOperation() 65 | return GroundCoffee() 66 | } 67 | 68 | suspend fun brewMilk(): Milk { 69 | longOperation() 70 | return Milk() 71 | } 72 | 73 | 74 | suspend fun makeEspresso(ground: GroundCoffee): Espresso { 75 | longOperation() 76 | return Espresso(ground) 77 | } 78 | 79 | data class Order(val customer: String, val type: CoffeeType) 80 | enum class CoffeeType { ESPRESSO, LATE } 81 | class Milk 82 | class GroundCoffee 83 | 84 | sealed class Coffee 85 | class Espresso(ground: GroundCoffee) : Coffee() { 86 | override fun toString(): String = "Espresso" 87 | } 88 | 89 | class Latte(milk: Milk, espresso: Espresso) : Coffee() { 90 | override fun toString(): String = "Latte" 91 | } -------------------------------------------------------------------------------- /src/comment/CommentFactory.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | class CommentFactory( 4 | private val uuidProvider: UuidProvider, 5 | private val timeProvider: TimeProvider, 6 | ) { 7 | fun toCommentDocument(userId: String, collectionKey: String, body: AddComment) = CommentDocument( 8 | _id = uuidProvider.next(), 9 | collectionKey = collectionKey, 10 | userId = userId, 11 | comment = body.comment, 12 | date = timeProvider.now() 13 | ) 14 | } -------------------------------------------------------------------------------- /src/comment/CommentService.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | 5 | class CommentsService( 6 | private val commentsRepository: CommentsRepository, 7 | private val userService: UserService, 8 | private val commentFactory: CommentFactory 9 | ) { 10 | // TODO: Should read user id from token, transform body to comment document, and add it to comments repository 11 | suspend fun addComment(token: String, collectionKey: String, body: AddComment) { 12 | TODO() 13 | } 14 | 15 | // TODO: Should get comments with users and return them as a collection 16 | suspend fun getComments(collectionKey: String): CommentsCollection { 17 | TODO() 18 | } 19 | 20 | suspend fun deleteComment(token: String, commentId: String) = coroutineScope { 21 | val userId = userService.readUserId(token) 22 | 23 | val comment = commentsRepository.getComment(commentId) 24 | requireNotNull(comment) { "Comment does not exist" } 25 | require(comment.userId == userId) { "Not an owner" } 26 | 27 | commentsRepository.deleteComment(commentId) 28 | } 29 | 30 | private suspend fun makeCommentsCollection( 31 | commentDocuments: List, 32 | collectionKey: String 33 | ): CommentsCollection = coroutineScope { 34 | CommentsCollection( 35 | collectionKey = collectionKey, 36 | elements = makeCommentsElements(commentDocuments) 37 | ) 38 | } 39 | 40 | // TODO: Should concurrently transform comment documents to comment elements 41 | private suspend fun makeCommentsElements(commentDocuments: List) = 42 | commentDocuments 43 | .map { makeCommentElement(it) } 44 | 45 | private suspend fun makeCommentElement(commentDocument: CommentDocument) = CommentElement( 46 | id = commentDocument._id, 47 | collectionKey = commentDocument.collectionKey, 48 | user = userService.findUserById(commentDocument.userId), 49 | comment = commentDocument.comment, 50 | date = commentDocument.date, 51 | ) 52 | } -------------------------------------------------------------------------------- /src/comment/CommentServiceTests.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package domain.comment 4 | 5 | import comment.FakeCommentsRepository 6 | import comment.FakeUserService 7 | import kotlinx.coroutines.ExperimentalCoroutinesApi 8 | import kotlinx.coroutines.test.currentTime 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.After 11 | import org.junit.Before 12 | import org.junit.Test 13 | import java.time.Instant 14 | import kotlin.test.assertEquals 15 | import kotlin.test.assertNotNull 16 | 17 | class CommentServiceTests { 18 | private val commentsRepository = FakeCommentsRepository() 19 | private val userService = FakeUserService() 20 | private val uuidProvider = FakeUuidProvider() 21 | private val timeProvider = FakeTimeProvider() 22 | private val commentsFactory: CommentFactory = CommentFactory(uuidProvider, timeProvider) 23 | private val commentsService: CommentsService = CommentsService(commentsRepository, userService, commentsFactory) 24 | 25 | @Before 26 | fun setup() { 27 | 28 | } 29 | 30 | @After 31 | fun cleanup() { 32 | timeProvider.clean() 33 | uuidProvider.clean() 34 | commentsRepository.clean() 35 | userService.clear() 36 | } 37 | 38 | @Test 39 | fun `Should add comment`() = runTest { 40 | // given 41 | commentsRepository.has( 42 | commentDocument1, 43 | ) 44 | userService.hasUsers( 45 | user1, 46 | user2 47 | ) 48 | userService.hasToken(aToken, user2.id) 49 | uuidProvider.alwaysReturn(commentDocument2._id) 50 | timeProvider.advanceTimeTo(commentDocument2.date) 51 | 52 | // when 53 | commentsService.addComment(aToken, collectionKey2, AddComment(commentDocument2.comment)) 54 | 55 | // then 56 | assertEquals(commentDocument2, commentsRepository.getComment(commentDocument2._id)) 57 | } 58 | 59 | @Test 60 | fun `Should get comments by collection key`() = runTest { 61 | // given 62 | commentsRepository.has( 63 | commentDocument1, 64 | commentDocument2, 65 | commentDocument3 66 | ) 67 | userService.hasUsers( 68 | user1, 69 | user2 70 | ) 71 | 72 | // when 73 | val result: CommentsCollection = commentsService.getComments(collectionKey1) 74 | 75 | // then 76 | with(result) { 77 | assertEquals(collectionKey1, collectionKey) 78 | assertEquals(listOf(commentElement1, commentElement3), elements) 79 | } 80 | } 81 | 82 | @Test 83 | fun `Should concurrently find users when getting comments`() = runTest { 84 | // given 85 | commentsRepository.has( 86 | commentDocument1, 87 | commentDocument1, 88 | commentDocument1, 89 | commentDocument2, 90 | commentDocument3, 91 | ) 92 | userService.hasUsers( 93 | user1, 94 | user2 95 | ) 96 | userService.findUserDelay = 1000 97 | 98 | // when 99 | commentsService.getComments(collectionKey1) 100 | 101 | // then 102 | assertEquals(1000, currentTime) 103 | } 104 | 105 | // Fake Data 106 | private val aToken = "SOME_TOKEN" 107 | private val collectionKey1 = "SOME_COLLECTION_KEY_1" 108 | private val collectionKey2 = "SOME_COLLECTION_KEY_2" 109 | private val date1 = Instant.parse("2018-11-30T18:35:24.00Z") 110 | private val date2 = Instant.parse("2019-11-30T18:35:24.00Z") 111 | private val userDocument1 = UserDocument( 112 | _id = "U_ID_1", 113 | email = "user1@email.com", 114 | imageUrl = "some_image_1", 115 | displayName = "some_display_name_1", 116 | bio = "some bio 1" 117 | ) 118 | private val userDocument2 = UserDocument( 119 | _id = "U_ID_2", 120 | email = "user2@email.com", 121 | imageUrl = "some_image_2", 122 | displayName = "some_display_name_2", 123 | bio = "some bio 2" 124 | ) 125 | private val user1 = userDocument1.toUser() 126 | private val user2 = userDocument2.toUser() 127 | private val commentDocument1 = CommentDocument( 128 | _id = "C_ID_1", 129 | collectionKey = collectionKey1, 130 | userId = user1.id, 131 | comment = "Some comment 1", 132 | date = date1, 133 | ) 134 | private val commentDocument2 = CommentDocument( 135 | _id = "C_ID_2", 136 | collectionKey = collectionKey2, 137 | userId = user2.id, 138 | comment = "Some comment 2", 139 | date = date2, 140 | ) 141 | private val commentDocument3 = CommentDocument( 142 | _id = "C_ID_3", 143 | collectionKey = collectionKey1, 144 | userId = user2.id, 145 | comment = "Some comment 3", 146 | date = date2, 147 | ) 148 | private val commentElement1 = CommentElement( 149 | id = "C_ID_1", 150 | collectionKey = collectionKey1, 151 | user = user1, 152 | comment = "Some comment 1", 153 | date = date1, 154 | ) 155 | private val commentElement2 = CommentElement( 156 | id = "C_ID_2", 157 | collectionKey = collectionKey2, 158 | user = user2, 159 | comment = "Some comment 2", 160 | date = date2, 161 | ) 162 | private val commentElement3 = CommentElement( 163 | id = "C_ID_3", 164 | collectionKey = collectionKey1, 165 | user = user2, 166 | comment = "Some comment 3", 167 | date = date2, 168 | ) 169 | } -------------------------------------------------------------------------------- /src/comment/Data.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | import java.time.Instant 4 | 5 | interface CommentsRepository { 6 | suspend fun getComments(collectionKey: String): List 7 | suspend fun getComment(id: String): CommentDocument? 8 | suspend fun addComment(comment: CommentDocument) 9 | suspend fun deleteComment(commentId: String) 10 | } 11 | 12 | data class CommentDocument( 13 | val _id: String, 14 | val collectionKey: String, 15 | val userId: String, 16 | val comment: String?, 17 | val date: Instant, 18 | ) 19 | 20 | interface UserService { 21 | fun readUserId(token: String): String 22 | suspend fun findUser(token: String): User 23 | suspend fun findUserById(id: String): User 24 | } 25 | 26 | object NoSuchUserException: Exception("No such user") 27 | 28 | data class CommentsCollection( 29 | val collectionKey: String, 30 | val elements: List, 31 | ) 32 | 33 | data class CommentElement( 34 | val id: String, 35 | val collectionKey: String, 36 | val user: User?, 37 | val comment: String?, 38 | val date: Instant, 39 | ) 40 | 41 | data class AddComment( 42 | val comment: String?, 43 | ) 44 | 45 | data class EditComment( 46 | val comment: String?, 47 | ) 48 | 49 | data class User( 50 | val id: String, 51 | val email: String, 52 | val imageUrl: String, 53 | val displayName: String? = null, 54 | val bio: String? = null, 55 | ) 56 | 57 | data class UserDocument( 58 | val _id: String, 59 | val email: String, 60 | val imageUrl: String, 61 | val displayName: String? = null, 62 | val bio: String? = null, 63 | ) 64 | 65 | fun UserDocument.toUser() = User( 66 | id = _id, 67 | email = email, 68 | imageUrl = imageUrl, 69 | displayName = displayName, 70 | bio = bio 71 | ) 72 | 73 | fun User.toUserDocument() = UserDocument( 74 | _id = id, 75 | email = email, 76 | imageUrl = imageUrl, 77 | displayName = displayName, 78 | bio = bio 79 | ) -------------------------------------------------------------------------------- /src/comment/FakeCommentsRepository.kt: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | import domain.comment.CommentDocument 4 | import domain.comment.CommentsRepository 5 | 6 | class FakeCommentsRepository: CommentsRepository { 7 | private var comments = listOf() 8 | 9 | fun has(vararg comment: CommentDocument) { 10 | comments = comments + comment 11 | } 12 | 13 | fun clean() { 14 | comments = emptyList() 15 | } 16 | 17 | override suspend fun getComments(collectionKey: String): List = 18 | comments.filter { it.collectionKey == collectionKey } 19 | 20 | override suspend fun getComment(id: String): CommentDocument? = 21 | comments.find { it._id == id } 22 | 23 | override suspend fun addComment(comment: CommentDocument) { 24 | comments = comments + comment 25 | } 26 | 27 | override suspend fun deleteComment(commentId: String) { 28 | TODO("Not yet implemented") 29 | } 30 | } -------------------------------------------------------------------------------- /src/comment/FakeTimeProvider.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | import java.time.Instant 4 | 5 | class FakeTimeProvider : TimeProvider { 6 | private var currentTime = DEFAULT_START 7 | 8 | override fun now(): Instant = currentTime 9 | 10 | fun advanceTimeTo(instant: Instant) { 11 | currentTime = instant 12 | } 13 | 14 | fun advanceTimeByDays(days: Int) { 15 | currentTime = currentTime.plusSeconds(1L * days * 60 * 60 * 24) 16 | } 17 | 18 | fun clean() { 19 | currentTime = DEFAULT_START 20 | } 21 | 22 | fun advanceTime() { 23 | currentTime = currentTime.plusSeconds(10) 24 | } 25 | 26 | companion object { 27 | val DEFAULT_START = Instant.parse("2018-11-30T18:35:24.00Z") 28 | } 29 | } -------------------------------------------------------------------------------- /src/comment/FakeUserService.kt: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | import domain.comment.NoSuchUserException 4 | import domain.comment.User 5 | import domain.comment.UserService 6 | import kotlinx.coroutines.delay 7 | 8 | class FakeUserService : UserService { 9 | var findUserDelay: Long? = null 10 | private var users = listOf() 11 | private var tokens = mapOf() 12 | 13 | fun hasUsers(vararg user: User) { 14 | users = users + user 15 | } 16 | 17 | fun hasToken(token: String, userId: String) { 18 | tokens = tokens + (token to userId) 19 | } 20 | 21 | fun clear() { 22 | users = emptyList() 23 | tokens = mapOf() 24 | findUserDelay = null 25 | } 26 | 27 | override fun readUserId(token: String): String = 28 | tokens[token] ?: throw NoSuchUserException 29 | 30 | override suspend fun findUser(token: String): User { 31 | findUserDelay?.let { delay(it) } 32 | return findUserById(readUserId(token)) 33 | } 34 | 35 | override suspend fun findUserById(id: String): User { 36 | findUserDelay?.let { delay(it) } 37 | return users.find { it.id == id } ?: throw NoSuchUserException 38 | } 39 | } -------------------------------------------------------------------------------- /src/comment/FakeUuidProvider.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | class FakeUuidProvider: UuidProvider { 4 | private var counter = 1 5 | private var constantReturn: String? = null 6 | 7 | override fun next(): String = constantReturn ?: "UUID#" + (counter++) 8 | 9 | fun clean() { 10 | counter = 1 11 | constantReturn = null 12 | } 13 | 14 | fun alwaysReturn(value: String) { 15 | constantReturn = value 16 | } 17 | } -------------------------------------------------------------------------------- /src/comment/TimeProvider.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | import java.time.Instant 4 | 5 | interface TimeProvider { 6 | fun now(): Instant 7 | } -------------------------------------------------------------------------------- /src/comment/UuidProvider.kt: -------------------------------------------------------------------------------- 1 | package domain.comment 2 | 3 | interface UuidProvider { 4 | fun next(): String 5 | } -------------------------------------------------------------------------------- /src/continuation/ContinuationSteal.kt: -------------------------------------------------------------------------------- 1 | package continuation 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.coroutines.Continuation 5 | import kotlin.coroutines.resume 6 | import kotlin.coroutines.suspendCoroutine 7 | 8 | fun main(): Unit = runBlocking { 9 | launch { 10 | continuationSteal() 11 | } 12 | delay(1000) 13 | continuation?.resume("This is some text") 14 | } 15 | 16 | var continuation: Continuation? = null 17 | 18 | suspend fun continuationSteal(console: Console = Console()) { 19 | console.println("Before") 20 | // TODO: Suspend in here and store continuation in the `continuation` variable. 21 | // USE suspendCancellableCoroutine instead of suspendCoroutine 22 | // TODO: After continuation resume, print using `console` the value that was passed. 23 | console.println("After") 24 | } 25 | 26 | open class Console { 27 | 28 | open fun println(text: Any?) { 29 | kotlin.io.println(text) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/continuation/ContinuationStealTests.kt: -------------------------------------------------------------------------------- 1 | package continuation 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.test.UnconfinedTestDispatcher 7 | import kotlinx.coroutines.test.runTest 8 | import org.junit.Test 9 | import kotlin.coroutines.resume 10 | import kotlin.test.assertEquals 11 | import kotlin.test.assertNotNull 12 | 13 | @Suppress("FunctionName") 14 | class ContinuationStealTests { 15 | 16 | private val fakeText = "This is some text" 17 | 18 | class FakeConsole : Console() { 19 | val printed = mutableListOf() 20 | 21 | override fun println(text: Any?) { 22 | printed += text 23 | } 24 | } 25 | 26 | @Test 27 | fun `At the beginning function says Before`() = runTest(UnconfinedTestDispatcher()) { 28 | val fakeConsole = FakeConsole() 29 | val job = launch { 30 | continuationSteal(fakeConsole) 31 | } 32 | delay(100) 33 | assertEquals("Before", fakeConsole.printed.first()) 34 | job.cancel() 35 | } 36 | 37 | @Test 38 | fun `At the end function says After`() = runTest(UnconfinedTestDispatcher()) { 39 | val fakeConsole = FakeConsole() 40 | val job = launch { 41 | continuationSteal(fakeConsole) 42 | } 43 | continuation?.resume(fakeText) 44 | assertEquals("After", fakeConsole.printed.last()) 45 | } 46 | 47 | @Test 48 | fun `In the middle, we suspend function`() = runTest(UnconfinedTestDispatcher()) { 49 | val fakeConsole = FakeConsole() 50 | val job = launch { 51 | continuationSteal(fakeConsole) 52 | } 53 | assertEquals(mutableListOf("Before"), fakeConsole.printed) 54 | job.cancel() 55 | } 56 | 57 | @Test 58 | fun `Function should return continuation`() = runTest(UnconfinedTestDispatcher()) { 59 | val fakeConsole = FakeConsole() 60 | launch { 61 | continuationSteal(fakeConsole) 62 | } 63 | assertNotNull(continuation).resume(fakeText) 64 | assertEquals("After", fakeConsole.printed.last()) 65 | } 66 | 67 | @Test 68 | fun `Only Before is printed before resume`() = runTest(UnconfinedTestDispatcher()) { 69 | val fakeConsole = FakeConsole() 70 | val job = launch { 71 | continuationSteal(fakeConsole) 72 | } 73 | assertEquals("Before", fakeConsole.printed.first()) 74 | job.cancel() 75 | } 76 | 77 | @Test 78 | fun `After resume function should print text to resume`() = runTest(UnconfinedTestDispatcher()) { 79 | val fakeConsole = FakeConsole() 80 | launch { 81 | continuationSteal(fakeConsole) 82 | } 83 | continuation?.resume(fakeText) 84 | assertEquals(3, fakeConsole.printed.size) 85 | assertEquals(fakeText, fakeConsole.printed[1]) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/examples/1.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.GlobalScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.launch 4 | 5 | fun main() { 6 | GlobalScope.launch { 7 | delay(1000L) 8 | println("World!") 9 | } 10 | println("Hello,") 11 | Thread.sleep(2000L) 12 | } -------------------------------------------------------------------------------- /src/examples/10.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.async 4 | import kotlinx.coroutines.coroutineScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.runBlocking 7 | 8 | import kotlinx.coroutines.* 9 | 10 | class User() 11 | 12 | suspend fun fetchUser(): User { 13 | // Runs forever 14 | while (true) { 15 | yield() 16 | } 17 | } 18 | 19 | suspend fun getUserOrNull(): User? = withTimeoutOrNull(1000) { 20 | fetchUser() 21 | } 22 | 23 | suspend fun main(): Unit = coroutineScope { 24 | val user = getUserOrNull() 25 | println("User: $user") 26 | } -------------------------------------------------------------------------------- /src/examples/2.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() { 9 | GlobalScope.launch { 10 | delay(1000L) 11 | println("World!") 12 | } 13 | println("Hello,") 14 | runBlocking { 15 | delay(2000L) 16 | } 17 | } -------------------------------------------------------------------------------- /src/examples/3.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | import org.junit.Test 8 | 9 | fun main() = runBlocking { 10 | GlobalScope.launch { 11 | delay(1000L) 12 | println("World!") 13 | } 14 | println("Hello,") 15 | delay(2000L) 16 | } 17 | 18 | class MyTest { 19 | @Test 20 | fun testMySuspendingFunction() = runBlocking { 21 | var a = "AA" 22 | delay(1000) 23 | assert(1 == 1) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/examples/4.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun main() { 6 | val a = coroutineScope { 7 | delay(1000) 8 | 10 9 | } 10 | println("a is calculated") 11 | val b = coroutineScope { 12 | delay(1000) 13 | 20 14 | } 15 | println(a) // 10 16 | println(b) // 20 17 | } 18 | -------------------------------------------------------------------------------- /src/examples/5.kt: -------------------------------------------------------------------------------- 1 | package examples.e5 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun longTask() = coroutineScope { 6 | launch { 7 | delay(1000) 8 | val name = coroutineContext[CoroutineName]?.name 9 | println("[$name] Finished task 1") 10 | } 11 | launch { 12 | delay(2000) 13 | val name = coroutineContext[CoroutineName]?.name 14 | println("[$name] Finished task 2") 15 | } 16 | } 17 | 18 | fun main() = runBlocking(CoroutineName("Parent")) { 19 | println("Before") 20 | longTask() 21 | 22 | // val job = launch(CoroutineName("Parent")) { 23 | // longTask() 24 | // } 25 | // launch { 26 | // delay(1500) 27 | // job.cancel() 28 | // } 29 | 30 | println("After") 31 | } -------------------------------------------------------------------------------- /src/examples/6.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun CoroutineScope.log(msg: String) { 6 | val name = coroutineContext[CoroutineName]?.name 7 | println("[$name] $msg") 8 | } 9 | 10 | fun main() = runBlocking(CoroutineName("Parent")) { 11 | log("Before") 12 | 13 | withContext(CoroutineName("Child 1")) { 14 | delay(1000) 15 | log("Hello 1") 16 | } 17 | 18 | withContext(CoroutineName("Child 2")) { 19 | delay(1000) 20 | log("Hello 2") 21 | } 22 | 23 | log("After") 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/7.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import java.util.* 5 | 6 | fun main() = runBlocking { 7 | println("Before") 8 | supervisorScope { 9 | launch { 10 | delay(1000) 11 | throw Error() 12 | } 13 | launch { 14 | delay(2000) 15 | println("Done") 16 | } 17 | } 18 | println("After") 19 | } 20 | -------------------------------------------------------------------------------- /src/examples/8.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun test(): Int = withTimeout(1500) { 6 | delay(1000) 7 | println("Still thinking") 8 | delay(1000) 9 | println("Done!") 10 | 42 11 | } 12 | 13 | suspend fun main(): Unit = coroutineScope { 14 | try { 15 | test() 16 | } catch (e: TimeoutCancellationException) { 17 | println("Cancelled") 18 | } 19 | } -------------------------------------------------------------------------------- /src/examples/9.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun main(): Unit = coroutineScope { 6 | launch { 7 | launch { 8 | delay(2000) 9 | println("Will not be printed") 10 | } 11 | withTimeout(1000) { 12 | delay(1500) 13 | } 14 | } 15 | launch { 16 | delay(2000) 17 | println("Done") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/examples/CombiningFlows.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.combine 6 | import kotlinx.coroutines.flow.flattenConcat 7 | import kotlinx.coroutines.flow.flattenMerge 8 | import kotlinx.coroutines.flow.flowOf 9 | import kotlinx.coroutines.flow.onEach 10 | import kotlinx.coroutines.flow.zip 11 | 12 | suspend fun main() { 13 | val f1 = flowOf(1, 2, 3).onEach { delay(1000) } 14 | val f2 = flowOf("A", "B", "C").onEach { delay(800) } 15 | 16 | f1.zip(f2) { t1, t2 -> t2 + t1 } 17 | .collect { print("$it ") } // A1 B2 C3 18 | 19 | f1.combine(f2) { t1, t2 -> t2 + t1 } 20 | .collect { print("$it ") } // A1 B1 B2 C2 C3 21 | 22 | flowOf(f1, f2).flattenConcat() 23 | .collect { print("$it ") } // 1 2 3 A B C 24 | 25 | flowOf(f1, f2).flattenMerge() 26 | .collect { print("$it ") } // A 1 B 2 C 3 27 | } -------------------------------------------------------------------------------- /src/examples/Continuation.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.util.concurrent.Executors 4 | import java.util.concurrent.TimeUnit.SECONDS 5 | import kotlin.coroutines.Continuation 6 | import kotlin.coroutines.CoroutineContext 7 | import kotlin.coroutines.resume 8 | 9 | //suspend fun getConfig(): String { 10 | // println("A") 11 | // delay(1000) 12 | // println("B") 13 | // return "Some config" 14 | //} 15 | 16 | fun getConfig(continuation: Continuation): Any /* String & COROUTINE_SUSPENDED */ { 17 | val cont = continuation as? `CoroutineExampleKt$getConfig` ?: `CoroutineExampleKt$getConfig`(continuation) 18 | 19 | if (cont.label == 0) { 20 | println("A") 21 | cont.label = 1 22 | if (_delay(cont, 1000) == COROUTINE_SUSPENDED) { 23 | return COROUTINE_SUSPENDED 24 | } 25 | } 26 | if (cont.label == 1) { 27 | println("B") 28 | return "Some config" 29 | } 30 | error("Impossible") 31 | } 32 | 33 | class `CoroutineExampleKt$getConfig`(val continuation: Continuation<*>) : Continuation { 34 | var label = 0 35 | 36 | override val context: CoroutineContext 37 | get() = continuation.context 38 | 39 | override fun resumeWith(result: Result) { 40 | // ??? 41 | } 42 | } 43 | 44 | // Coroutine internals 45 | val COROUTINE_SUSPENDED = Any() 46 | 47 | val EXECUTOR = Executors.newSingleThreadScheduledExecutor { 48 | Thread(it, "scheduler").apply { isDaemon = true } 49 | } 50 | 51 | fun _delay(continuation: Continuation<*>, time: Long): Any? { 52 | val cont = continuation as? `CoroutineExampleKt$delay` ?: `CoroutineExampleKt$delay`(continuation) 53 | EXECUTOR.schedule({ cont.resume(Unit) }, time, SECONDS) 54 | return COROUTINE_SUSPENDED 55 | } 56 | 57 | class `CoroutineExampleKt$delay`(val continuation: Continuation<*>) : Continuation { 58 | var label = 0 59 | 60 | override val context: CoroutineContext 61 | get() = continuation.context 62 | 63 | override fun resumeWith(result: Result) { 64 | // continuation.resume() 65 | } 66 | } -------------------------------------------------------------------------------- /src/examples/Delay.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.util.concurrent.Executors 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.coroutines.resume 6 | import kotlin.coroutines.suspendCoroutine 7 | 8 | private val excecutor = Executors.newSingleThreadScheduledExecutor { 9 | Thread(it, "scheduler").apply { isDaemon = true } 10 | }!! 11 | 12 | suspend fun customDelay(time: Long): Unit = suspendCoroutine { cont -> 13 | excecutor.schedule({ cont.resume(Unit) }, time, TimeUnit.MILLISECONDS) 14 | } -------------------------------------------------------------------------------- /src/examples/Exceptions.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.async 4 | import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED 5 | import kotlinx.coroutines.channels.produce 6 | import kotlinx.coroutines.coroutineScope 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.flow.collect 9 | import kotlinx.coroutines.flow.flow 10 | import kotlinx.coroutines.launch 11 | import kotlinx.coroutines.runBlocking 12 | 13 | fun main() = runBlocking { 14 | try { 15 | launch { 16 | throwing() 17 | } 18 | } catch (e: IllegalStateException) { 19 | print("Caught") 20 | } 21 | print("Done") 22 | } 23 | 24 | //fun main() = runBlocking { 25 | // try { 26 | // val async = async { 27 | // throwing() 28 | // } 29 | // async.await() 30 | // } catch (e: IllegalStateException) { 31 | // print("Caught") 32 | // } 33 | // print("Done") 34 | //} 35 | // 36 | //fun main() = runBlocking { 37 | // try { 38 | // coroutineScope { 39 | // throwing() 40 | // } 41 | // } catch (e: IllegalStateException) { 42 | // print("Caught") 43 | // } 44 | //} 45 | // 46 | //fun main() = runBlocking { 47 | // val channel = produce(capacity = UNLIMITED) { 48 | // send(1) 49 | // send(2) 50 | // throwing() 51 | // } 52 | // delay(100) 53 | // try { 54 | // for (e in channel) { 55 | // println("Got it") 56 | // } 57 | // } catch (e: IllegalStateException) { 58 | // println("Caught") 59 | // } 60 | //} 61 | // 62 | //fun main() = runBlocking { 63 | // val flow = flow { 64 | // emit(1) 65 | // emit(2) 66 | // throwing() 67 | // } 68 | // try { 69 | // flow.collect { println("Got it") } 70 | // } catch (e: IllegalStateException) { 71 | // println("Caught") 72 | // } 73 | //} 74 | 75 | fun throwing() { 76 | throw IllegalStateException() 77 | } 78 | -------------------------------------------------------------------------------- /src/examples/Massive.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | import kotlin.concurrent.thread 7 | 8 | //fun main() = runBlocking { 9 | // repeat(100_000) { 10 | // launch { 11 | // delay(1000L) 12 | // print(".") 13 | // } 14 | // } 15 | //} 16 | 17 | // No! Don't do it! Very bad idea on threads 18 | fun main() { 19 | repeat(100_000) { 20 | thread { 21 | Thread.sleep(1000L) 22 | print(".") 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/examples/Numbers.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | val childNumbers = sequence { 4 | println("Um, first number is... one!") 5 | yield(1) 6 | 7 | println("Next is... eeeee two!") 8 | yield(2) 9 | 10 | println("twotwotwo... ummmm three!") 11 | yield(3) 12 | 13 | println("That's all I've learned") 14 | } 15 | 16 | fun main() { 17 | val iterator = childNumbers.iterator() 18 | println("What is first?") 19 | println("Yes, it is ${iterator.next()}") 20 | println("What is next?") 21 | println("Good, it is ${iterator.next()}") 22 | } -------------------------------------------------------------------------------- /src/examples/b1.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.GlobalScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.launch 4 | import kotlin.concurrent.thread 5 | 6 | fun main() { 7 | GlobalScope.launch { 8 | delay(1000L) 9 | println("World!") 10 | } 11 | GlobalScope.launch { 12 | delay(1000L) 13 | println("World!") 14 | } 15 | GlobalScope.launch { 16 | delay(1000L) 17 | println("World!") 18 | } 19 | println("Hello,") 20 | Thread.sleep(2000L) 21 | } 22 | 23 | //fun main() { 24 | // thread(isDaemon = true) { 25 | // Thread.sleep(1000L) 26 | // println("World!") 27 | // } 28 | // thread(isDaemon = true) { 29 | // Thread.sleep(1000L) 30 | // println("World!") 31 | // } 32 | // thread(isDaemon = true) { 33 | // Thread.sleep(1000L) 34 | // println("World!") 35 | // } 36 | // println("Hello,") 37 | // Thread.sleep(2000L) 38 | //} 39 | -------------------------------------------------------------------------------- /src/examples/b2.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.delay 2 | import kotlinx.coroutines.runBlocking 3 | 4 | fun main() { 5 | runBlocking { 6 | delay(1000L) 7 | println("World!") 8 | } 9 | runBlocking { 10 | delay(1000L) 11 | println("World!") 12 | } 13 | runBlocking { 14 | delay(1000L) 15 | println("World!") 16 | } 17 | println("Hello,") 18 | } 19 | 20 | //fun main() { 21 | // Thread.sleep(1000L) 22 | // println("World!") 23 | // Thread.sleep(1000L) 24 | // println("World!") 25 | // Thread.sleep(1000L) 26 | // println("World!") 27 | // println("Hello,") 28 | //} 29 | -------------------------------------------------------------------------------- /src/examples/b3.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.* 2 | import kotlin.concurrent.thread 3 | 4 | fun main() { 5 | val value1 = GlobalScope.async { 6 | delay(1000L) 7 | 1 8 | } 9 | val value2 = GlobalScope.async { 10 | delay(1000L) 11 | 20 12 | } 13 | val value3 = GlobalScope.async { 14 | delay(1000L) 15 | 300 16 | } 17 | println("Calculating") 18 | runBlocking { 19 | print(value1.await() + value2.await() + value3.await()) 20 | } 21 | } 22 | 23 | //fun main() { 24 | // thread(isDaemon = true) { 25 | // Thread.sleep(1000L) 26 | // println("World!") 27 | // } 28 | // thread(isDaemon = true) { 29 | // Thread.sleep(1000L) 30 | // println("World!") 31 | // } 32 | // thread(isDaemon = true) { 33 | // Thread.sleep(1000L) 34 | // println("World!") 35 | // } 36 | // println("Hello,") 37 | // Thread.sleep(2000L) 38 | //} 39 | -------------------------------------------------------------------------------- /src/examples/c1.kt: -------------------------------------------------------------------------------- 1 | package examples.c1 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main(): Unit = runBlocking { 9 | val channel = Channel() 10 | launch { 11 | repeat(5) { index -> 12 | println("Producing next one") 13 | channel.send(index * 2) 14 | delay(1000) 15 | } 16 | } 17 | 18 | repeat(5) { 19 | val received = channel.receive() 20 | println(received) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/examples/c10.kt: -------------------------------------------------------------------------------- 1 | package examples.c6 2 | 3 | import kotlinx.coroutines.channels.ticker 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.withTimeoutOrNull 7 | 8 | fun main() = runBlocking { 9 | val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) 10 | var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() } 11 | println("Initial element is available immediately: $nextElement") 12 | 13 | nextElement = withTimeoutOrNull(50) { tickerChannel.receive() } 14 | println("Next element is not ready in 50 ms: $nextElement") 15 | 16 | nextElement = withTimeoutOrNull(60) { tickerChannel.receive() } 17 | println("Next element is ready in 100 ms: $nextElement") 18 | 19 | println("Consumer pauses for 150ms") 20 | delay(150) 21 | 22 | nextElement = withTimeoutOrNull(1) { tickerChannel.receive() } 23 | println("Next element is available immediately after large consumer delay: $nextElement") 24 | nextElement = withTimeoutOrNull(60) { tickerChannel.receive() } 25 | println("Next element is ready in 50ms after consumer pause in 150ms: $nextElement") 26 | tickerChannel.cancel() 27 | } 28 | -------------------------------------------------------------------------------- /src/examples/c11.kt: -------------------------------------------------------------------------------- 1 | package examples.c7 2 | 3 | import examples.massiveRun 4 | import kotlinx.coroutines.CompletableDeferred 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.channels.Channel 7 | import kotlinx.coroutines.launch 8 | import kotlinx.coroutines.runBlocking 9 | 10 | sealed class CounterMsg 11 | object IncCounter : CounterMsg() 12 | class GetCounter(val response: CompletableDeferred) : CounterMsg() 13 | 14 | fun CoroutineScope.counterActor(): Channel { 15 | val channel = Channel() 16 | launch { 17 | var counter = 0 18 | for (msg in channel) { 19 | when (msg) { 20 | is IncCounter -> counter++ 21 | is GetCounter -> msg.response.complete(counter) 22 | } 23 | } 24 | } 25 | return channel 26 | } 27 | 28 | fun main() = runBlocking { 29 | val channel = counterActor() 30 | massiveRun { channel.send(IncCounter) } 31 | val response = CompletableDeferred() 32 | channel.send(GetCounter(response)) 33 | println("Counter = ${response.await()}") 34 | channel.close() 35 | } 36 | -------------------------------------------------------------------------------- /src/examples/c12.kt: -------------------------------------------------------------------------------- 1 | package examples.c8 2 | 3 | import examples.massiveRun 4 | import kotlinx.coroutines.CompletableDeferred 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.channels.SendChannel 7 | import kotlinx.coroutines.channels.actor 8 | import kotlinx.coroutines.runBlocking 9 | 10 | sealed class CounterMsg 11 | object IncCounter : CounterMsg() 12 | class GetCounter(val response: CompletableDeferred) : CounterMsg() 13 | 14 | fun CoroutineScope.counterActor() = actor { 15 | var counter = 0 16 | for (msg in channel) { 17 | when (msg) { 18 | is IncCounter -> counter++ 19 | is GetCounter -> msg.response.complete(counter) 20 | } 21 | } 22 | } 23 | 24 | fun main(args: Array) = runBlocking { 25 | val counter: SendChannel = counterActor() 26 | massiveRun { counter.send(IncCounter) } 27 | val response = CompletableDeferred() 28 | counter.send(GetCounter(response)) 29 | println("Counter = ${response.await()}") 30 | counter.close() 31 | } 32 | -------------------------------------------------------------------------------- /src/examples/c13.kt: -------------------------------------------------------------------------------- 1 | package examples.c9 2 | 3 | import kotlinx.coroutines.channels.consumeEach 4 | import kotlinx.coroutines.channels.produce 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | println("Started producing") 10 | val channel = produce { 11 | println("Channel started") 12 | for (i in 1..3) { 13 | delay(100) 14 | send(i) 15 | } 16 | } 17 | 18 | delay(150) 19 | println("Calling channel...") 20 | channel.consumeEach { value -> println(value) } 21 | println("Consuming again...") 22 | channel.consumeEach { value -> println(value) } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/examples/c2.kt: -------------------------------------------------------------------------------- 1 | package examples.c2 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | val channel = Channel() 10 | launch { 11 | repeat(5) { index -> 12 | channel.send(index * 2) 13 | delay(1000) 14 | } 15 | channel.close() 16 | } 17 | 18 | for (i in channel) { 19 | print(i) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/c3.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | val channel = Channel() 10 | // Same as Channel(Channel.RENDEZVOUS) 11 | 12 | launch { 13 | repeat(5) { 14 | channel.send("Ping $it") 15 | println("Message sent") 16 | } 17 | channel.close() 18 | } 19 | 20 | // Listener 21 | launch { 22 | delay(1000) 23 | for (text in channel) { 24 | println(text) 25 | delay(1000) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/examples/c4.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | val channel = Channel(Channel.UNLIMITED) 10 | 11 | launch { 12 | var i = 1 13 | repeat(5) { 14 | channel.send("Ping ${i++}") 15 | println("Message sent") 16 | } 17 | channel.close() 18 | } 19 | 20 | // Listener 21 | launch { 22 | var i = 1 23 | for (text in channel) { 24 | println(text) 25 | delay(1000) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/examples/c5.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | val channel = Channel(3) 10 | 11 | launch { 12 | var i = 1 13 | repeat(5) { 14 | channel.send("Ping ${i++}") 15 | println("Message sent") 16 | } 17 | channel.close() 18 | } 19 | 20 | // Listener 21 | launch { 22 | var i = 1 23 | for (text in channel) { 24 | println(text) 25 | delay(1000) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/examples/c6.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | val channel = Channel(Channel.CONFLATED) 10 | 11 | launch { 12 | var i = 1 13 | repeat(5) { 14 | channel.send("Ping ${i++}") 15 | println("Message sent") 16 | } 17 | } 18 | 19 | // Listener 20 | launch { 21 | var i = 1 22 | for (text in channel) { 23 | println(text) 24 | delay(1000) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/examples/c7.kt: -------------------------------------------------------------------------------- 1 | package examples.c3 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.cancelChildren 5 | import kotlinx.coroutines.channels.ReceiveChannel 6 | import kotlinx.coroutines.channels.produce 7 | import kotlinx.coroutines.runBlocking 8 | 9 | fun CoroutineScope.produceNumbers(): ReceiveChannel = produce { 10 | var x = 1 11 | while (true) send(x++) // infinite stream of integers starting from 1 12 | } 13 | 14 | fun CoroutineScope.square(numbers: ReceiveChannel): ReceiveChannel = produce { 15 | for (x in numbers) send(x * x) 16 | } 17 | 18 | fun main() = runBlocking { 19 | val numbers = produceNumbers() 20 | val squares = square(numbers) 21 | for (i in 1..5) println(squares.receive()) 22 | println("Done!") 23 | coroutineContext.cancelChildren() 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/c8.kt: -------------------------------------------------------------------------------- 1 | package examples.c4 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.channels.ReceiveChannel 5 | import kotlinx.coroutines.channels.produce 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | import kotlinx.coroutines.runBlocking 9 | 10 | fun CoroutineScope.produceNumbers() = produce { 11 | var x = 1 // start from 1 12 | while (true) { 13 | delay(100) 14 | send(x++) 15 | } 16 | } 17 | 18 | fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel) = launch { 19 | for (msg in channel) { 20 | println("Processor #$id received $msg") 21 | } 22 | } 23 | 24 | fun main() = runBlocking { 25 | val producer = produceNumbers() 26 | repeat(5) { launchProcessor(it, producer) } 27 | delay(9050) 28 | producer.cancel() 29 | } 30 | -------------------------------------------------------------------------------- /src/examples/c9.kt: -------------------------------------------------------------------------------- 1 | package examples.c5 2 | 3 | import kotlinx.coroutines.cancelChildren 4 | import kotlinx.coroutines.channels.Channel 5 | import kotlinx.coroutines.channels.SendChannel 6 | import kotlinx.coroutines.delay 7 | import kotlinx.coroutines.launch 8 | import kotlinx.coroutines.runBlocking 9 | 10 | suspend fun sendString(channel: SendChannel, s: String, time: Long) { 11 | while (true) { 12 | delay(time) 13 | channel.send(s) 14 | } 15 | } 16 | 17 | fun main() = runBlocking { 18 | val channel = Channel() 19 | launch { sendString(channel, "foo", 200L) } 20 | launch { sendString(channel, "BAR!", 500L) } 21 | repeat(1000) { 22 | println(channel.receive()) 23 | } 24 | coroutineContext.cancelChildren() 25 | } -------------------------------------------------------------------------------- /src/examples/d1.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.launch 5 | import kotlin.random.Random 6 | 7 | suspend fun main() = coroutineScope { 8 | repeat(1000) { 9 | launch { // or launch(Dispatchers.Default) { 10 | // To make it busy 11 | List(1000) { Random.nextLong() }.maxOrNull() 12 | 13 | val threadName = Thread.currentThread().name 14 | println("Running on thread: $threadName") 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/examples/d2.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.coroutineScope 5 | import kotlinx.coroutines.launch 6 | 7 | suspend fun main() = coroutineScope { 8 | repeat(1000) { 9 | launch(Dispatchers.IO) { 10 | Thread.sleep(200) 11 | 12 | val threadName = Thread.currentThread().name 13 | println("Running on thread: $threadName") 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/examples/d3.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun main(): Unit = coroutineScope { 6 | launch(Dispatchers.Default) { 7 | println(Thread.currentThread().name) 8 | withContext(Dispatchers.IO) { 9 | println(Thread.currentThread().name) 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/examples/d4.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.asCoroutineDispatcher 4 | import kotlinx.coroutines.coroutineScope 5 | import kotlinx.coroutines.launch 6 | import java.util.concurrent.Executors 7 | 8 | suspend fun main() = coroutineScope { 9 | val dispatcher = Executors.newFixedThreadPool(5) 10 | .asCoroutineDispatcher() 11 | repeat(1000) { 12 | launch(dispatcher) { 13 | Thread.sleep(200) 14 | 15 | val threadName = Thread.currentThread().name 16 | println("Running on thread: $threadName") 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/examples/d5.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import java.util.concurrent.Executors 5 | 6 | var i = 0 7 | 8 | val dispatcher = Executors.newSingleThreadExecutor() 9 | .asCoroutineDispatcher() 10 | // val dispatcher = newSingleThreadContext("My name") 11 | 12 | suspend fun main(): Unit = coroutineScope { 13 | repeat(10_000) { 14 | launch(Dispatchers.IO) { // or Default 15 | i++ 16 | } 17 | } 18 | delay(1000) 19 | println(i) // ~9930 20 | } -------------------------------------------------------------------------------- /src/examples/d6.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.coroutines.* 5 | 6 | suspend fun main(): Unit = withContext(newSingleThreadContext("Name1")) { 7 | var continuation: Continuation? = null 8 | 9 | launch(newSingleThreadContext("Name2")) { 10 | delay(1000) 11 | continuation?.resume(Unit) 12 | } 13 | 14 | launch(Dispatchers.Unconfined) { 15 | println(Thread.currentThread().name) // Name1 16 | 17 | suspendCoroutine { continuation = it } 18 | 19 | println(Thread.currentThread().name) // Name2 20 | 21 | delay(1000) 22 | 23 | println(Thread.currentThread().name) // kotlinx.coroutines.DefaultExecutor 24 | } 25 | } -------------------------------------------------------------------------------- /src/examples/e1.kt: -------------------------------------------------------------------------------- 1 | package examples.c1 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun main() = runBlocking { 8 | launch { 9 | launch { 10 | delay(1_000) 11 | throw Error() 12 | } 13 | launch { 14 | delay(2_000) 15 | println("Done") 16 | } 17 | } 18 | launch { 19 | delay(3_000) 20 | println("Done2") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/examples/e2.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main() = runBlocking { 6 | val supervisor = SupervisorJob() 7 | with(CoroutineScope(coroutineContext + supervisor)) { 8 | launch(CoroutineExceptionHandler { _, _ -> }) { 9 | delay(1000) 10 | throw AssertionError("Cancelled") 11 | } 12 | launch { 13 | delay(2000) 14 | println("AAA") 15 | } 16 | } 17 | supervisor.join() 18 | } 19 | -------------------------------------------------------------------------------- /src/examples/f1.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.flowOf 4 | import kotlinx.coroutines.runBlocking 5 | 6 | fun main() = runBlocking { 7 | flowOf(1,2,3) 8 | .collect { print(it) } 9 | } 10 | -------------------------------------------------------------------------------- /src/examples/f10.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.flowOf 6 | import kotlinx.coroutines.flow.onEach 7 | 8 | suspend fun main() = coroutineScope { 9 | flowOf("A", "B", "C") 10 | .onEach { println("onEach $it") } 11 | .collect { println("collect $it") } 12 | } 13 | 14 | //suspend fun main() = coroutineScope { 15 | // flowOf("A", "B", "C") 16 | // .onEach { println("onEach $it") } 17 | // .buffer(100) 18 | // .collect { println("collect $it") } 19 | //} 20 | -------------------------------------------------------------------------------- /src/examples/f11.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.flow.conflate 6 | import kotlinx.coroutines.flow.flow 7 | import kotlinx.coroutines.flow.onEach 8 | import kotlinx.coroutines.flow.toList 9 | 10 | suspend fun main() = coroutineScope { 11 | val flow = flow { 12 | for (i in 1..30) { 13 | delay(10) 14 | emit(i) 15 | } 16 | } 17 | 18 | print(flow.onEach { delay(100) }.toList()) 19 | // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30] 20 | 21 | print(flow.conflate().onEach { delay(100) }.toList()) 22 | // [1, 10, 20, 30] 23 | } 24 | -------------------------------------------------------------------------------- /src/examples/f12.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.* 5 | 6 | suspend fun main() { 7 | val nums = (1..3).asFlow().onEach { delay(300) } 8 | val strs = flowOf("one", "two", "three").onEach { delay(400) } 9 | 10 | println("Zip:") 11 | val startTime = ct 12 | nums.zip(strs) { a, b -> "$a -> $b" } 13 | .collect { value -> 14 | println("$value at ${ct - startTime} ms") 15 | } 16 | 17 | println("Combine:") 18 | val startTime2 = ct 19 | nums.combine(strs) { a, b -> "$a -> $b" } 20 | .collect { value -> 21 | println("$value at ${ct - startTime2} ms") 22 | } 23 | } 24 | 25 | val ct: Long get() = System.currentTimeMillis() -------------------------------------------------------------------------------- /src/examples/f13.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.* 5 | import kotlinx.coroutines.newSingleThreadContext 6 | import kotlinx.coroutines.withContext 7 | 8 | suspend fun main() { 9 | val UI = newSingleThreadContext("UI") 10 | val IO = newSingleThreadContext("IO") 11 | fun logThread(taskName: String) { 12 | println("Doing $taskName on ${Thread.currentThread().name}") 13 | } 14 | withContext(UI) { 15 | val singleValue = 16 | flow { logThread("flow"); emit("A") } // will be executed on IO if context wasn't specified before 17 | .map { logThread("map"); it } // Will be executed in IO 18 | .flowOn(IO) 19 | .filter { logThread("filter"); it != null } // Will be executed in Default 20 | .flowOn(Dispatchers.Default) 21 | .collect { logThread("collect") } 22 | } 23 | } -------------------------------------------------------------------------------- /src/examples/f14.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.* 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun main() = runBlocking { 8 | flow { 9 | (1..5).forEach { 10 | delay(1000) 11 | emit(it) 12 | if (it == 2) throw RuntimeException("Error on $it") 13 | } 14 | }.onEach { println("On each $it") } 15 | .onStart { println("Starting flow") } 16 | .onCompletion { println("Flow completed") } 17 | .catch { ex -> println("Exception message: ${ex.message}") } 18 | .toList() 19 | } 20 | -------------------------------------------------------------------------------- /src/examples/f15.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.asFlow 5 | import kotlinx.coroutines.flow.collect 6 | import kotlinx.coroutines.flow.onEach 7 | import kotlinx.coroutines.runBlocking 8 | import kotlin.system.measureTimeMillis 9 | 10 | fun main() = runBlocking { 11 | measureTimeMillis { 12 | (1..5).asFlow() 13 | .onEach { event -> delay(100) } 14 | .collect() // We wait 500ms 15 | 16 | (1..5).asFlow() 17 | .onEach { event -> delay(100) } 18 | .collect() // We wait 500ms 19 | 20 | }.let(::print) // 1049 21 | } -------------------------------------------------------------------------------- /src/examples/f16.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.asFlow 5 | import kotlinx.coroutines.flow.launchIn 6 | import kotlinx.coroutines.flow.onEach 7 | import kotlinx.coroutines.runBlocking 8 | import kotlin.system.measureTimeMillis 9 | 10 | fun main() = measureTimeMillis { 11 | runBlocking { 12 | measureTimeMillis { 13 | (1..5).asFlow() 14 | .onEach { event -> delay(100) } 15 | .launchIn(this) 16 | 17 | (1..5).asFlow() 18 | .onEach { event -> delay(100) } 19 | .launchIn(this) 20 | 21 | }.let(::print) // 15 22 | } 23 | }.let(::print) // 591 24 | -------------------------------------------------------------------------------- /src/examples/f17.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.catch 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.flowOf 6 | import kotlinx.coroutines.flow.map 7 | import kotlinx.coroutines.runBlocking 8 | 9 | fun main() = runBlocking { 10 | flowOf(1, 2, 3) 11 | .map { it / 0 } 12 | .catch { emit(-1) } 13 | .collect { it / 0 } 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/f18.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.catch 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.flowOf 6 | import kotlinx.coroutines.flow.map 7 | import kotlinx.coroutines.runBlocking 8 | 9 | fun main() = runBlocking { 10 | try { 11 | flowOf(1, 2, 3) 12 | .map { it / 0 } 13 | .catch { emit(-1) } 14 | .collect { it / 0 } 15 | } catch (e: ArithmeticException) { 16 | print("Got it") 17 | } 18 | } -------------------------------------------------------------------------------- /src/examples/f19.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.catch 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.flowOf 6 | import kotlinx.coroutines.flow.map 7 | import kotlinx.coroutines.flow.onEach 8 | import kotlinx.coroutines.runBlocking 9 | 10 | fun main() = runBlocking { 11 | flowOf(1, 2, 3) 12 | .map { it / 0 } 13 | .catch { emit(-1) } 14 | .onEach { it / 0 } 15 | .catch { print("Got it") } 16 | .collect() 17 | } -------------------------------------------------------------------------------- /src/examples/f2.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.collect 4 | import kotlinx.coroutines.flow.flowOf 5 | import kotlinx.coroutines.flow.map 6 | import kotlinx.coroutines.flow.onEach 7 | import kotlinx.coroutines.runBlocking 8 | 9 | fun main() = runBlocking { 10 | flowOf(1, 2, 3) 11 | .onEach { print(it) } // 123 12 | .map { it * 10 } 13 | .collect {} 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/f3.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.collect 4 | import kotlinx.coroutines.flow.flowOf 5 | import kotlinx.coroutines.flow.map 6 | import kotlinx.coroutines.flow.onEach 7 | import kotlinx.coroutines.runBlocking 8 | 9 | fun main() = runBlocking { 10 | flowOf(1, 2, 3) 11 | .onEach { print(it) } // 123 12 | .map { it * 10 } 13 | .collect { print(it) } // 102030 14 | } -------------------------------------------------------------------------------- /src/examples/f4.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.collect 4 | import kotlinx.coroutines.flow.filter 5 | import kotlinx.coroutines.flow.flowOf 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | flowOf(1, 2, 3) 10 | .filter { it % 2 == 1 } 11 | .collect { print(it) } // 13 12 | } 13 | -------------------------------------------------------------------------------- /src/examples/f5.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.asFlow 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.scan 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | (1..10).asFlow() 10 | .scan(0) { acc, v -> acc + v } 11 | .collect { println(it) } 12 | } 13 | -------------------------------------------------------------------------------- /src/examples/f6.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.* 5 | import kotlin.system.measureTimeMillis 6 | 7 | suspend fun main() { 8 | measureTimeMillis { 9 | ('A'..'C').asFlow() 10 | .flatMapConcat { flowFrom(it) } 11 | .collect { print(it) } // 0_A 1_A 2_A 0_B 1_B 2_B 0_C 1_C 2_C 12 | }.let(::print) // 9060 13 | } 14 | 15 | fun flowFrom(elem: Any) = flowOf(0, 1, 2) 16 | .onEach { delay(1000) } 17 | .map { "${it}_${elem} " } 18 | -------------------------------------------------------------------------------- /src/examples/f7.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.asFlow 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.flatMapMerge 6 | import kotlin.system.measureTimeMillis 7 | 8 | suspend fun main() { 9 | measureTimeMillis { 10 | ('A'..'C').asFlow() 11 | .flatMapMerge { flowFrom(it) } 12 | .collect { print(it) } // 0_A 0_C 0_B 1_C 1_B 1_A 2_B 2_A 2_C 13 | }.let(::print) // 3117 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/f8.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.flow.asFlow 4 | import kotlinx.coroutines.flow.collect 5 | import kotlinx.coroutines.flow.flatMapMerge 6 | import kotlin.system.measureTimeMillis 7 | 8 | suspend fun main() { 9 | measureTimeMillis { 10 | ('A'..'C').asFlow() 11 | .flatMapMerge(concurrency = 2) { flowFrom(it) } 12 | .collect { print(it) } // 0_A 0_B 1_B 1_A 2_B 2_A 0_C 1_C 2_C 13 | }.let(::print) // 6129 14 | } -------------------------------------------------------------------------------- /src/examples/f9.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.flow.asFlow 5 | import kotlinx.coroutines.flow.collect 6 | import kotlinx.coroutines.flow.flatMapLatest 7 | import kotlinx.coroutines.flow.onEach 8 | import kotlin.system.measureTimeMillis 9 | 10 | suspend fun main() { 11 | measureTimeMillis { 12 | ('A'..'C').asFlow() 13 | .onEach { delay(1500) } 14 | .flatMapLatest { flowFrom(it) } 15 | .collect { print(it) } // 0_A 0_B 0_C 1_C 2_C 16 | }.let(::print) // 7656 17 | } 18 | -------------------------------------------------------------------------------- /src/examples/hc1.kt: -------------------------------------------------------------------------------- 1 | package examples.hc1 2 | 3 | import kotlin.* 4 | 5 | fun main() { 6 | val l = buildList { 7 | repeat(3) { 8 | add("User$it") 9 | println("L: Added User") 10 | } 11 | } 12 | 13 | val l2 = l.map { 14 | println("L: Processing") 15 | "Processed $it" 16 | } 17 | 18 | val s = sequence { 19 | repeat(3) { 20 | yield("User$it") 21 | println("S: Added User") 22 | } 23 | } 24 | 25 | val s2 = s.map { 26 | println("S: Processing") 27 | "Processed $it" 28 | } 29 | } -------------------------------------------------------------------------------- /src/examples/hc2.kt: -------------------------------------------------------------------------------- 1 | package examples.hc2 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.channels.produce 5 | import kotlinx.coroutines.coroutineScope 6 | import kotlinx.coroutines.delay 7 | 8 | private fun CoroutineScope.makeChannel() = produce { 9 | println("Channel started") 10 | for (i in 1..3) { 11 | delay(1000) 12 | send(i) 13 | } 14 | } 15 | 16 | suspend fun main() = coroutineScope { 17 | val channel = makeChannel() 18 | 19 | delay(1000) 20 | println("Calling channel...") 21 | for (value in channel) { 22 | println(value) 23 | } 24 | println("Consuming again...") 25 | for (value in channel) { 26 | println(value) 27 | } 28 | } -------------------------------------------------------------------------------- /src/examples/hc3.kt: -------------------------------------------------------------------------------- 1 | package examples.hc3 2 | 3 | import kotlinx.coroutines.coroutineScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.flow.flow 6 | 7 | private fun makeFlow() = flow { 8 | println("Flow started") 9 | for (i in 1..3) { 10 | delay(1000) 11 | emit(i) 12 | } 13 | } 14 | 15 | suspend fun main() = coroutineScope { 16 | val flow = makeFlow() 17 | 18 | delay(1000) 19 | println("Calling flow...") 20 | flow.collect { value -> println(value) } 21 | println("Consuming again...") 22 | flow.collect { value -> println(value) } 23 | } -------------------------------------------------------------------------------- /src/examples/j1.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.CoroutineStart 4 | import kotlinx.coroutines.Job 5 | import kotlinx.coroutines.coroutineScope 6 | import kotlinx.coroutines.launch 7 | 8 | suspend fun main() = coroutineScope { 9 | val job = Job() 10 | println(job) // JobImpl{Active}@ADD 11 | job.complete() 12 | println(job) // JobImpl{Completed}@ADD 13 | 14 | val activeJob = launch { 15 | // no-op 16 | } 17 | println(activeJob) // StandaloneCoroutine{Active}@ADD 18 | activeJob.join() 19 | println(activeJob) // StandaloneCoroutine{Completed}@ADD 20 | 21 | val lazyJob = launch(start = CoroutineStart.LAZY) { 22 | // no-op 23 | } 24 | println(lazyJob) // LazyStandaloneCoroutine{New}@ADD 25 | lazyJob.start() 26 | println(lazyJob) // LazyStandaloneCoroutine{Active}@ADD 27 | lazyJob.join() 28 | println(lazyJob) //LazyStandaloneCoroutine{Completed}@ADD 29 | } -------------------------------------------------------------------------------- /src/examples/j10.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun main(): Unit = coroutineScope { 6 | val job = Job() 7 | launch(job) { 8 | try { 9 | repeat(1_000) { i -> 10 | delay(200) 11 | println("Printing $i") 12 | } 13 | } catch (e: CancellationException) { 14 | println(e) 15 | throw e 16 | } 17 | } 18 | delay(1100) 19 | job.cancelAndJoin() 20 | println("Cancelled successfully") 21 | delay(1000) 22 | } -------------------------------------------------------------------------------- /src/examples/j11.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.random.Random 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val job = Job() 8 | launch(job) { 9 | try { 10 | delay(Random.nextLong(2000)) 11 | println("Done") 12 | } finally { 13 | print("Will always be printed") 14 | } 15 | } 16 | delay(1000) 17 | job.cancelAndJoin() 18 | } -------------------------------------------------------------------------------- /src/examples/j12.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.random.Random 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val job = Job() 8 | launch(job) { 9 | try { 10 | delay(200) 11 | println("Job is done") 12 | } finally { 13 | println("Finally") 14 | launch { // will be ignored 15 | println("Will not be printed") 16 | } 17 | delay(100) // here exception is thrown 18 | println("Will not be printed") 19 | } 20 | } 21 | delay(100) 22 | job.cancelAndJoin() 23 | println("Cancel done") 24 | } -------------------------------------------------------------------------------- /src/examples/j13.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.random.Random 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val job = Job() 8 | launch(job) { 9 | try { 10 | delay(200) 11 | println("Coroutine finished") 12 | } finally { 13 | println("Finally") 14 | withContext(NonCancellable) { 15 | delay(1000L) 16 | println("Cleanup done") 17 | } 18 | } 19 | } 20 | delay(100) 21 | job.cancelAndJoin() 22 | println("Done") 23 | } -------------------------------------------------------------------------------- /src/examples/j14.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | import kotlin.random.Random 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val job = Job() 8 | launch(job) { 9 | repeat(1_000) { i -> 10 | Thread.sleep(200) 11 | // yield() 12 | // or ensureActive() 13 | println("Printing $i") 14 | } 15 | } 16 | delay(1100) 17 | job.cancelAndJoin() 18 | println("Cancelled successfully") 19 | delay(1000) 20 | } -------------------------------------------------------------------------------- /src/examples/j15.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun main(): Unit = runBlocking { 8 | launch { 9 | launch { 10 | delay(1000) 11 | throw Error("Some error") 12 | } 13 | launch { 14 | delay(2000) 15 | println("Will not be printed") 16 | } 17 | launch { 18 | delay(500) // faster than the exception 19 | println("Will be printed") 20 | } 21 | } 22 | 23 | launch { 24 | delay(2000) 25 | println("Will not be printed") 26 | } 27 | } -------------------------------------------------------------------------------- /src/examples/j16.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | val scope = CoroutineScope(SupervisorJob()) 7 | scope.launch { 8 | delay(1000) 9 | throw Error("Some error") 10 | } 11 | 12 | scope.launch { 13 | delay(2000) 14 | println("Will be printed") 15 | } 16 | 17 | delay(3000) 18 | } -------------------------------------------------------------------------------- /src/examples/j17.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | val job = SupervisorJob() 7 | launch(job) { 8 | delay(1000) 9 | throw Error("Some error") 10 | } 11 | launch(job) { 12 | delay(2000) 13 | println("Will be printed") 14 | } 15 | job.join() 16 | } -------------------------------------------------------------------------------- /src/examples/j18.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | supervisorScope { 7 | launch { 8 | delay(1000) 9 | throw Error("Some error") 10 | } 11 | 12 | launch { 13 | delay(2000) 14 | println("Will be printed") 15 | } 16 | } 17 | delay(1000) 18 | println("Done") 19 | } -------------------------------------------------------------------------------- /src/examples/j19.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.CoroutineExceptionHandler 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | 8 | fun main() = runBlocking { 9 | val handler = CoroutineExceptionHandler { _, exception -> 10 | println("Caught $exception") 11 | } 12 | val job = GlobalScope.launch(handler) { 13 | throw AssertionError() 14 | } 15 | job.join() // Caught java.lang.AssertionError 16 | } 17 | -------------------------------------------------------------------------------- /src/examples/j2.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | val job: Job = launch { 7 | delay(1000) 8 | println("Test") 9 | } 10 | 11 | val ret: Deferred = async { 12 | delay(1000) 13 | "Test" 14 | } 15 | val job2: Job = ret 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/examples/j3.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | val job: Job = launch { 7 | delay(1000) 8 | } 9 | 10 | val parentJob: Job = coroutineContext.job 11 | // or coroutineContext[Job]!! 12 | println(job == parentJob) // false 13 | val parentChildren: Sequence = parentJob.children 14 | println(parentChildren.first() == job) // true 15 | } -------------------------------------------------------------------------------- /src/examples/j4.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | val job1 = launch { 7 | delay(1000) 8 | println("Test1") 9 | } 10 | val job2 = launch { 11 | delay(1000) 12 | println("Test2") 13 | } 14 | 15 | job1.join() 16 | job2.join() 17 | println("All tests are done") 18 | } -------------------------------------------------------------------------------- /src/examples/j5.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main(): Unit = runBlocking { 6 | launch { 7 | delay(1000) 8 | println("Test1") 9 | } 10 | launch { 11 | delay(1000) 12 | println("Test2") 13 | } 14 | 15 | coroutineContext[Job] 16 | ?.children 17 | ?.forEach { it.join() } 18 | println("All tests are done") 19 | } -------------------------------------------------------------------------------- /src/examples/j6.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun main(): Unit = coroutineScope { 6 | val job = Job() 7 | launch(job) { // the new job replaces one from parent 8 | delay(1000) 9 | println("Text 1") 10 | } 11 | launch(job) { // the new job replaces one from parent 12 | delay(2000) 13 | println("Text 2") 14 | } 15 | job.join() // Here we will await forever 16 | // better: job.children.forEach { it.join() } 17 | // or: job.complete() 18 | } -------------------------------------------------------------------------------- /src/examples/j7.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | fun main() = runBlocking { 6 | val job = Job() 7 | launch(job) { 8 | repeat(5) { num -> 9 | delay(200) 10 | println("Rep$num") 11 | } 12 | } 13 | launch { 14 | delay(500) 15 | job.complete() 16 | } 17 | job.join() 18 | launch(job) { 19 | println("Will not be printed") 20 | } 21 | println("Done") 22 | } -------------------------------------------------------------------------------- /src/examples/j8.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun main() = runBlocking { 8 | val job = launch { 9 | repeat(1_000) { i -> 10 | delay(200) 11 | println("Printing $i") 12 | } 13 | } 14 | 15 | delay(1100) 16 | job.cancel() 17 | job.join() 18 | println("Cancelled successfully") 19 | } -------------------------------------------------------------------------------- /src/examples/j9.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.* 4 | 5 | suspend fun main(): Unit = coroutineScope { 6 | val job = Job() 7 | launch(job) { 8 | repeat(1_000) { i -> 9 | delay(200) 10 | println("Printing $i") 11 | } 12 | } 13 | delay(1100) 14 | job.cancelAndJoin() 15 | println("Cancelled successfully") 16 | } -------------------------------------------------------------------------------- /src/examples/s1.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.withContext 7 | 8 | var counter = 0 9 | 10 | fun main() = runBlocking { 11 | massiveRun { 12 | counter++ 13 | } 14 | println("Counter = $counter") 15 | } 16 | 17 | suspend fun massiveRun(action: suspend () -> Unit) = withContext(Dispatchers.Default) { 18 | List(1000) { 19 | launch { 20 | repeat(1000) { action() } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/examples/s2.kt: -------------------------------------------------------------------------------- 1 | package examples.n1 2 | 3 | import examples.massiveRun 4 | import kotlinx.coroutines.GlobalScope 5 | import kotlinx.coroutines.runBlocking 6 | import java.util.concurrent.atomic.AtomicInteger 7 | 8 | private var counter = AtomicInteger() 9 | 10 | fun main() = runBlocking { 11 | massiveRun { 12 | counter.incrementAndGet() 13 | } 14 | println("Counter = ${counter.get()}") 15 | } 16 | -------------------------------------------------------------------------------- /src/examples/s3.kt: -------------------------------------------------------------------------------- 1 | package examples.n2 2 | 3 | import examples.massiveRun 4 | import kotlinx.coroutines.asCoroutineDispatcher 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.withContext 7 | import java.util.concurrent.Executors 8 | 9 | private var counter = 0 10 | 11 | fun main() = runBlocking { 12 | val counterContext = Executors.newSingleThreadExecutor() 13 | .asCoroutineDispatcher() 14 | 15 | massiveRun { 16 | withContext(counterContext) { 17 | counter++ 18 | } 19 | } 20 | println("Counter = $counter") 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/s4.kt: -------------------------------------------------------------------------------- 1 | package examples.n3 2 | 3 | import examples.massiveRun 4 | import kotlinx.coroutines.runBlocking 5 | import kotlinx.coroutines.sync.Mutex 6 | import kotlinx.coroutines.sync.withLock 7 | 8 | private val mutex = Mutex() 9 | private var counter = 0 10 | 11 | fun main() = runBlocking { 12 | massiveRun { 13 | mutex.withLock { 14 | counter++ 15 | } 16 | } 17 | println("Counter = $counter") 18 | } 19 | -------------------------------------------------------------------------------- /src/examples/select1.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlinx.coroutines.channels.* 4 | import kotlinx.coroutines.* 5 | import kotlinx.coroutines.selects.select 6 | 7 | suspend fun CoroutineScope.produceString(s: String, time: Long) = produce { 8 | while (true) { 9 | delay(time) 10 | send(s) 11 | } 12 | } 13 | 14 | fun main() = runBlocking { 15 | val fooChannel = produceString("foo", 200L) 16 | val barChannel = produceString("BAR", 500L) 17 | 18 | repeat(50) { 19 | select { 20 | fooChannel.onReceive { println("From fooChannel: $it") } 21 | barChannel.onReceive { println("From barChannel: $it") } 22 | } 23 | } 24 | 25 | coroutineContext.cancelChildren() 26 | } 27 | -------------------------------------------------------------------------------- /src/examples/select2.kt: -------------------------------------------------------------------------------- 1 | package examples.select2 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.InternalCoroutinesApi 5 | import kotlinx.coroutines.channels.onReceiveOrNull 6 | import kotlinx.coroutines.channels.produce 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.runBlocking 9 | import kotlinx.coroutines.selects.select 10 | 11 | suspend private fun CoroutineScope.produceString(s: String, time: Long) = produce { 12 | repeat(20) { 13 | delay(time) 14 | send(s) 15 | } 16 | } 17 | 18 | @Suppress("DEPRECATION") 19 | fun main() = runBlocking { 20 | val fooChannel = produceString("foo", 200L) 21 | val barChannel = produceString("BAR", 500L) 22 | 23 | repeat(40) { 24 | delay(200) 25 | select { // selectUnbiased 26 | fooChannel.onReceive { println("From fooChannel: $it") } 27 | barChannel.onReceive { println("From barChannel: $it") } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/examples/select3.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.* 2 | import kotlinx.coroutines.channels.* 3 | import kotlinx.coroutines.selects.* 4 | 5 | fun CoroutineScope.produceNumbers(side: SendChannel) = produce { 6 | for (num in 1..100) { 7 | delay(100) 8 | send(num) 9 | // select { 10 | // onSend(num) {} 11 | // side.onSend(num) {} 12 | // } 13 | } 14 | } 15 | 16 | fun main() = runBlocking { 17 | val side = Channel() 18 | launch { 19 | side.consumeEach { println("Side channel has $it") } 20 | } 21 | produceNumbers(side).consumeEach { 22 | println("Consuming $it") 23 | delay(250) 24 | } 25 | println("Done consuming") 26 | coroutineContext.cancelChildren() 27 | } -------------------------------------------------------------------------------- /src/examples/sf1.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.coroutineScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.flow.MutableSharedFlow 4 | import kotlinx.coroutines.launch 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val mutableSharedFlow = 8 | MutableSharedFlow(replay = 0) 9 | // or MutableSharedFlow() 10 | 11 | launch { 12 | mutableSharedFlow.collect { 13 | println("#1 received $it") 14 | } 15 | } 16 | launch { 17 | mutableSharedFlow.collect { 18 | println("#2 received $it") 19 | } 20 | } 21 | 22 | delay(1000) 23 | mutableSharedFlow.emit("Message1") 24 | mutableSharedFlow.emit("Message2") 25 | } -------------------------------------------------------------------------------- /src/examples/sf2.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.coroutineScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.flow.* 4 | import kotlinx.coroutines.launch 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val flow = flowOf("A", "B", "C") 8 | .onEach { delay(1000) } 9 | 10 | val sharedFlow: SharedFlow = flow.shareIn( 11 | scope = this, 12 | started = SharingStarted.Eagerly, 13 | // replay = 0 (default) 14 | ) 15 | 16 | delay(500) 17 | 18 | launch { 19 | sharedFlow.collect { println("#1 $it") } 20 | } 21 | 22 | delay(1000) 23 | 24 | launch { 25 | sharedFlow.collect { println("#2 $it") } 26 | } 27 | 28 | delay(1000) 29 | 30 | launch { 31 | sharedFlow.collect { println("#3 $it") } 32 | } 33 | } -------------------------------------------------------------------------------- /src/examples/sf3.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.coroutineScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.launch 5 | 6 | suspend fun main() = coroutineScope { 7 | val state = MutableStateFlow(1) 8 | println(state.value) // 1 9 | 10 | delay(1000) 11 | 12 | launch { 13 | state.collect { println("Value changed to $it") } // Value changed to 1 14 | } 15 | 16 | delay(1000) 17 | 18 | state.value = 2 // Value changed to 2 19 | 20 | delay(1000) 21 | 22 | launch { 23 | state.collect { println("and now it is $it") } // and now it is 2 24 | } 25 | 26 | delay(1000) 27 | 28 | state.value = 3 // Value changed to 3 and now it is 3 29 | } 30 | -------------------------------------------------------------------------------- /src/examples/sf4.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.coroutineScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.flow.* 4 | import kotlinx.coroutines.launch 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val flow = flowOf("A", "B") 8 | .onEach { delay(1000) } 9 | .onEach { println("Produced $it") } 10 | 11 | val stateFlow: StateFlow = flow.stateIn(this) 12 | 13 | println(stateFlow.value) 14 | 15 | delay(2000) 16 | stateFlow.collect { println("Received $it") } 17 | } -------------------------------------------------------------------------------- /src/examples/sf5.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.coroutines.coroutineScope 2 | import kotlinx.coroutines.delay 3 | import kotlinx.coroutines.flow.* 4 | import kotlinx.coroutines.launch 5 | 6 | suspend fun main(): Unit = coroutineScope { 7 | val flow = flowOf("A", "B") 8 | .onEach { delay(1000) } 9 | .onEach { println("Produced $it") } 10 | 11 | val stateFlow: StateFlow = flow.stateIn( 12 | scope = this, 13 | started = SharingStarted.Lazily, 14 | initialValue = "Empty" 15 | ) 16 | 17 | println(stateFlow.value) 18 | 19 | delay(2000) 20 | stateFlow.collect { println("Received $it") } 21 | } -------------------------------------------------------------------------------- /src/examples/sus.kt: -------------------------------------------------------------------------------- 1 | package examples.sus 2 | 3 | import java.util.concurrent.Executors 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.concurrent.thread 6 | import kotlin.coroutines.resume 7 | import kotlin.coroutines.suspendCoroutine 8 | import kotlin.random.Random 9 | 10 | suspend fun main() { 11 | println("Before") 12 | 13 | 14 | 15 | println("After") 16 | } 17 | 18 | //private val executor = Executors.newSingleThreadScheduledExecutor { 19 | // Thread(it, "scheduler").apply { isDaemon = true } 20 | //} 21 | 22 | //executor.schedule({}, 1000, TimeUnit.MILLISECONDS) 23 | 24 | 25 | 26 | //fun fetchUser(callback: (User) -> Unit) { 27 | // thread { 28 | // Thread.sleep(1000) 29 | // callback(User("Test")) 30 | // } 31 | //} 32 | //fun fetchUser(callback: (User) -> Unit): Call { 33 | // thread { 34 | // Thread.sleep(1000) 35 | // callback(User("Test")) 36 | // } 37 | // return Call() 38 | //} 39 | //fun fetchUser(onSuccess: (User) -> Unit, onError: (Throwable) -> Unit): Call { 40 | // thread { 41 | // Thread.sleep(1000) 42 | // if (Random.nextBoolean()) { 43 | // onSuccess(User("Test")) 44 | // } else { 45 | // onError(ApiException()) 46 | // } 47 | // } 48 | // return Call() 49 | //} 50 | 51 | class User(val name: String) 52 | class ApiException: Throwable() 53 | class Call { 54 | fun cancel() {} 55 | } -------------------------------------------------------------------------------- /src/examples/sus1.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | suspend fun main() { 4 | println("Before") 5 | 6 | println("After") 7 | } -------------------------------------------------------------------------------- /src/examples/sus2.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlin.coroutines.resume 4 | import kotlin.coroutines.suspendCoroutine 5 | 6 | suspend fun main() { 7 | println("Before") 8 | 9 | suspendCoroutine { continuation -> 10 | continuation.resume(Unit) 11 | } 12 | 13 | println("After") 14 | } -------------------------------------------------------------------------------- /src/examples/sus3.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlin.concurrent.thread 4 | import kotlin.coroutines.resume 5 | import kotlin.coroutines.suspendCoroutine 6 | 7 | suspend fun main() { 8 | println("Before") 9 | 10 | suspendCoroutine { continuation -> 11 | thread { 12 | Thread.sleep(1000) 13 | continuation.resume(Unit) 14 | } 15 | } 16 | 17 | println("After") 18 | } -------------------------------------------------------------------------------- /src/examples/sus4.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import java.util.concurrent.Executors 4 | import java.util.concurrent.TimeUnit 5 | import kotlin.concurrent.thread 6 | import kotlin.coroutines.resume 7 | import kotlin.coroutines.suspendCoroutine 8 | 9 | private val executor = Executors.newSingleThreadScheduledExecutor { 10 | Thread(it, "scheduler").apply { isDaemon = true } 11 | } 12 | 13 | suspend fun main() { 14 | println("Before") 15 | 16 | suspendCoroutine { continuation -> 17 | executor.schedule({ 18 | continuation.resume(Unit) 19 | }, 1000, TimeUnit.MILLISECONDS) 20 | } 21 | 22 | println("After") 23 | } -------------------------------------------------------------------------------- /src/examples/sus5.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlin.coroutines.resume 4 | import kotlin.coroutines.suspendCoroutine 5 | 6 | suspend fun main() { 7 | val i: Int = suspendCoroutine { cont -> 8 | cont.resume(42) 9 | } 10 | println(i) // 42 11 | 12 | val str: String = suspendCoroutine { cont -> 13 | cont.resume("Some text") 14 | } 15 | println(str) // Some text 16 | 17 | val b: Boolean = suspendCoroutine { cont -> 18 | cont.resume(true) 19 | } 20 | println(b) // true 21 | } -------------------------------------------------------------------------------- /src/examples/sus6.kt: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import kotlin.coroutines.Continuation 4 | import kotlin.coroutines.resume 5 | import kotlin.coroutines.suspendCoroutine 6 | 7 | var continuation: Continuation? = null 8 | 9 | suspend fun suspendAndSetContinuation() { 10 | suspendCoroutine { cont -> 11 | continuation = cont 12 | } 13 | } 14 | 15 | suspend fun main() { 16 | println("Before") 17 | 18 | suspendAndSetContinuation() 19 | continuation?.resume(Unit) 20 | 21 | println("After") 22 | } -------------------------------------------------------------------------------- /src/examples/t1.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package examples.t1 4 | 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.test.TestCoroutineScheduler 7 | 8 | fun main() { 9 | val scheduler = TestCoroutineScheduler() 10 | 11 | println(scheduler.currentTime) // 0 12 | scheduler.advanceTimeBy(1_000) 13 | println(scheduler.currentTime) // 1000 14 | scheduler.advanceTimeBy(1_000) 15 | println(scheduler.currentTime) // 2000 16 | } 17 | -------------------------------------------------------------------------------- /src/examples/t2.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package examples.t2 4 | 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.ExperimentalCoroutinesApi 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.launch 9 | import kotlinx.coroutines.test.StandardTestDispatcher 10 | import kotlinx.coroutines.test.TestCoroutineScheduler 11 | 12 | fun main() { 13 | val scheduler = TestCoroutineScheduler() 14 | val testDispatcher = StandardTestDispatcher(scheduler) 15 | 16 | CoroutineScope(testDispatcher).launch { 17 | println("Some work 1") 18 | delay(1000) 19 | println("Some work 2") 20 | delay(1000) 21 | println("Coroutine done") 22 | } 23 | 24 | println("[${scheduler.currentTime}] Before") 25 | scheduler.advanceUntilIdle() 26 | println("[${scheduler.currentTime}] After") 27 | } 28 | -------------------------------------------------------------------------------- /src/examples/t3.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package examples.t3 4 | 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.coroutineScope 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.launch 9 | import kotlinx.coroutines.test.currentTime 10 | import kotlinx.coroutines.test.runTest 11 | import kotlin.test.assertEquals 12 | 13 | fun main() = runTest { 14 | assertEquals(0, currentTime) 15 | coroutineScope { 16 | launch { delay(1000) } 17 | launch { delay(1500) } 18 | launch { delay(2000) } 19 | } 20 | assertEquals(2000, currentTime) 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/t4.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package examples.t4 4 | 5 | import kotlinx.coroutines.ExperimentalCoroutinesApi 6 | import kotlinx.coroutines.async 7 | import kotlinx.coroutines.coroutineScope 8 | import kotlinx.coroutines.delay 9 | import kotlinx.coroutines.test.currentTime 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | import kotlin.test.assertEquals 13 | 14 | class FetchUserUseCase( 15 | private val repo: UserDataRepository, 16 | ) { 17 | 18 | suspend fun fetchUserData(): User = coroutineScope { 19 | val name = async { repo.getName() } 20 | val friends = async { repo.getFriends() } 21 | val profile = async { repo.getProfile() } 22 | User( 23 | name = name.await(), 24 | friends = friends.await(), 25 | profile = profile.await() 26 | ) 27 | } 28 | } 29 | 30 | class FetchUserDataTest { 31 | 32 | @Test 33 | fun `should load data concurrently`() = runTest { 34 | // given 35 | val userRepo = FakeUserDataRepository() 36 | val useCase = FetchUserUseCase(userRepo) 37 | 38 | // when 39 | useCase.fetchUserData() 40 | 41 | // then 42 | assertEquals(1000, currentTime) 43 | } 44 | 45 | @Test 46 | fun `should construct user`() = runTest { 47 | // given 48 | val userRepo = FakeUserDataRepository() 49 | val useCase = FetchUserUseCase(userRepo) 50 | 51 | // when 52 | val result = useCase.fetchUserData() 53 | 54 | // then 55 | val expectedUser = User( 56 | name = "Ben", 57 | friends = listOf(Friend("some-friend-id-1")), 58 | profile = Profile("Example description") 59 | ) 60 | assertEquals(expectedUser, result) 61 | } 62 | 63 | class FakeUserDataRepository : UserDataRepository { 64 | override suspend fun getName(): String { 65 | delay(1000) 66 | return "Ben" 67 | } 68 | 69 | override suspend fun getFriends(): List { 70 | delay(1000) 71 | return listOf(Friend("some-friend-id-1")) 72 | } 73 | 74 | override suspend fun getProfile(): Profile { 75 | delay(1000) 76 | return Profile("Example description") 77 | } 78 | } 79 | } 80 | 81 | interface UserDataRepository { 82 | suspend fun getName(): String 83 | suspend fun getFriends(): List 84 | suspend fun getProfile(): Profile 85 | } 86 | 87 | data class User( 88 | val name: String, 89 | val friends: List, 90 | val profile: Profile 91 | ) 92 | 93 | data class Friend(val id: String) 94 | data class Profile(val description: String) -------------------------------------------------------------------------------- /src/examples/t5.kt: -------------------------------------------------------------------------------- 1 | package examples.t5 2 | 3 | import kotlinx.coroutines.* 4 | import kotlinx.coroutines.test.* 5 | import org.junit.Before 6 | import org.junit.Test 7 | import java.time.Instant 8 | import java.util.* 9 | import kotlin.test.assertEquals 10 | 11 | interface UserRepository { 12 | suspend fun getUser(): UserData 13 | } 14 | 15 | interface NewsRepository { 16 | suspend fun getNews(): List 17 | } 18 | 19 | data class UserData(val name: String) 20 | data class News(val date: Date) 21 | 22 | interface LiveData { 23 | val value: T? 24 | } 25 | 26 | class MutableLiveData : LiveData { 27 | override var value: T? = null 28 | } 29 | 30 | abstract class ViewModel() 31 | 32 | class MainViewModel( 33 | private val userRepo: UserRepository, 34 | private val newsRepo: NewsRepository 35 | ) : BaseViewModel() { 36 | 37 | private val _userName = MutableLiveData() 38 | val userName: LiveData = _userName 39 | private val _news = MutableLiveData>() 40 | val news: LiveData> = _news 41 | 42 | fun onCreate() { 43 | viewModelScope.launch { 44 | val user = userRepo.getUser() 45 | _userName.value = user.name 46 | } 47 | viewModelScope.launch { 48 | _news.value = newsRepo.getNews() 49 | .sortedByDescending { it.date } 50 | } 51 | } 52 | } 53 | 54 | abstract class BaseViewModel : ViewModel() { 55 | private val context = Dispatchers.Main.immediate + SupervisorJob() 56 | val viewModelScope = CoroutineScope(context) 57 | 58 | fun onDestroy() { 59 | context.cancelChildren() 60 | } 61 | } 62 | 63 | private val date1 = Date 64 | .from(Instant.now().minusSeconds(10)) 65 | private val date2 = Date 66 | .from(Instant.now().minusSeconds(20)) 67 | private val date3 = Date 68 | .from(Instant.now().minusSeconds(30)) 69 | 70 | private val aName = "Some name" 71 | private val someNews = 72 | listOf(News(date3), News(date1), News(date2)) 73 | private val viewModel = MainViewModel( 74 | userRepo = FakeUserRepository(aName), 75 | newsRepo = FakeNewsRepository(someNews) 76 | ) 77 | 78 | class FakeUserRepository(val name: String) : UserRepository { 79 | override suspend fun getUser(): UserData { 80 | delay(1000) 81 | return UserData(name) 82 | } 83 | } 84 | 85 | class FakeNewsRepository(val news: List) : NewsRepository { 86 | override suspend fun getNews(): List { 87 | delay(1000) 88 | return news 89 | } 90 | } 91 | 92 | @ExperimentalCoroutinesApi 93 | class MainViewModelTests { 94 | private lateinit var scheduler: TestCoroutineScheduler 95 | 96 | @Before 97 | fun setUp() { 98 | scheduler = TestCoroutineScheduler() 99 | Dispatchers.setMain(StandardTestDispatcher(scheduler)) 100 | } 101 | 102 | @Test 103 | fun `user name is shown`() { 104 | // when 105 | viewModel.onCreate() 106 | scheduler.advanceUntilIdle() 107 | 108 | // then 109 | assertEquals(aName, viewModel.userName.value) 110 | } 111 | 112 | @Test 113 | fun `sorted news are shown`() { 114 | // when 115 | viewModel.onCreate() 116 | scheduler.advanceUntilIdle() 117 | 118 | // then 119 | val someNewsSorted = 120 | listOf(News(date1), News(date2), News(date3)) 121 | assertEquals(someNewsSorted, viewModel.news.value) 122 | } 123 | 124 | @Test 125 | fun `user and news are called concurrently`() { 126 | // when 127 | viewModel.onCreate() 128 | scheduler.advanceUntilIdle() 129 | 130 | // then 131 | assertEquals(1000, scheduler.currentTime) 132 | } 133 | } -------------------------------------------------------------------------------- /src/examples/t6.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package examples 4 | 5 | import kotlinx.coroutines.* 6 | import kotlinx.coroutines.test.* 7 | import org.junit.Rule 8 | import org.junit.Test 9 | import org.junit.rules.TestWatcher 10 | import org.junit.runner.Description 11 | import java.time.Instant 12 | import java.util.* 13 | import kotlin.test.assertEquals 14 | 15 | interface UserRepository { 16 | suspend fun getUser(): UserData 17 | } 18 | 19 | interface NewsRepository { 20 | suspend fun getNews(): List 21 | } 22 | 23 | data class UserData(val name: String) 24 | data class News(val date: Date) 25 | 26 | interface LiveData { 27 | val value: T? 28 | } 29 | 30 | class MutableLiveData : LiveData { 31 | override var value: T? = null 32 | } 33 | 34 | abstract class ViewModel() 35 | 36 | class MainViewModel( 37 | private val userRepo: UserRepository, 38 | private val newsRepo: NewsRepository 39 | ) : BaseViewModel() { 40 | 41 | private val _userName = MutableLiveData() 42 | val userName: LiveData = _userName 43 | private val _news = MutableLiveData>() 44 | val news: LiveData> = _news 45 | 46 | fun onCreate() { 47 | scope.launch { 48 | val user = userRepo.getUser() 49 | _userName.value = user.name 50 | } 51 | scope.launch { 52 | _news.value = newsRepo.getNews() 53 | .sortedByDescending { it.date } 54 | } 55 | } 56 | } 57 | 58 | abstract class BaseViewModel : ViewModel() { 59 | private val context = Dispatchers.Main + SupervisorJob() 60 | val scope = CoroutineScope(context) 61 | 62 | fun onDestroy() { 63 | context.cancelChildren() 64 | } 65 | } 66 | 67 | class MainCoroutineRule : TestWatcher() { 68 | lateinit var scheduler: TestCoroutineScheduler 69 | private set 70 | lateinit var dispatcher: TestDispatcher 71 | private set 72 | 73 | override fun starting(description: Description) { 74 | scheduler = TestCoroutineScheduler() 75 | dispatcher = StandardTestDispatcher(scheduler) 76 | Dispatchers.setMain(dispatcher) 77 | } 78 | 79 | override fun finished(description: Description) { 80 | Dispatchers.resetMain() 81 | } 82 | } 83 | 84 | private val date1 = Date.from(Instant.now().minusSeconds(10)) 85 | private val date2 = Date.from(Instant.now().minusSeconds(20)) 86 | private val date3 = Date.from(Instant.now().minusSeconds(30)) 87 | 88 | val aName = "Some name" 89 | val someNews = listOf(News(date3), News(date1), News(date2)) 90 | val viewModel = MainViewModel( 91 | userRepo = FakeUserRepository(aName), 92 | newsRepo = FakeNewsRepository(someNews) 93 | ) 94 | 95 | class FakeUserRepository(val name: String) : UserRepository { 96 | override suspend fun getUser(): UserData { 97 | delay(1000) 98 | return UserData(name) 99 | } 100 | } 101 | 102 | class FakeNewsRepository(val news: List) : NewsRepository { 103 | override suspend fun getNews(): List { 104 | delay(1000) 105 | return news 106 | } 107 | } 108 | 109 | class MainViewModelTests { 110 | 111 | @get:Rule 112 | var mainCoroutineRule = MainCoroutineRule() 113 | 114 | @Test 115 | fun `user name is shown`() { 116 | // when 117 | viewModel.onCreate() 118 | mainCoroutineRule.scheduler.advanceUntilIdle() 119 | 120 | // then 121 | assertEquals(aName, viewModel.userName.value) 122 | } 123 | 124 | @Test 125 | fun `sorted news are shown`() { 126 | // when 127 | viewModel.onCreate() 128 | mainCoroutineRule.scheduler.advanceUntilIdle() 129 | 130 | // then 131 | val someNewsSorted = 132 | listOf(News(date1), News(date2), News(date3)) 133 | assertEquals(someNewsSorted, viewModel.news.value) 134 | } 135 | 136 | @Test 137 | fun `user and news are called concurrently`() { 138 | // when 139 | viewModel.onCreate() 140 | mainCoroutineRule.scheduler.advanceUntilIdle() 141 | 142 | 143 | // then 144 | assertEquals(1000, mainCoroutineRule.scheduler.currentTime) 145 | } 146 | } -------------------------------------------------------------------------------- /src/flow/Factory.kt: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import java.util.Random 5 | 6 | // Finish the below implementation using a flow. 7 | // 8 | // Implement a factory using a flow. You should start by creating 5 machines, 9 | // each every 800 ms, and those machines should produce codes every second. 10 | // You should produce 20 codes in total. Each code should be consumed using control.storeCode. 11 | 12 | fun main() = runBlocking { 13 | setupFactory(StandardFactoryControl()) 14 | } 15 | 16 | suspend fun setupFactory(control: FactoryControl) { 17 | // TODO 18 | } 19 | 20 | interface FactoryControl { 21 | fun makeMachine(): Machine 22 | fun storeCode(code: String) 23 | } 24 | 25 | class StandardFactoryControl : FactoryControl { 26 | private var broken = false 27 | private var waiting = false 28 | private var codes = listOf() 29 | 30 | override fun makeMachine(): Machine = StandardMachine() 31 | .also { println("Newly created machine") } 32 | 33 | override fun storeCode(code: String) { 34 | if (waiting || broken) { 35 | println("Factory control is broken due to 2 attempts to store code at the same time") 36 | broken = true 37 | throw BrokenMachineError() 38 | } 39 | waiting = true 40 | Thread.sleep(500) 41 | waiting = false 42 | codes = codes + code 43 | println("Newly stored code is $code") 44 | } 45 | } 46 | 47 | interface Machine { 48 | fun produce(): String 49 | } 50 | 51 | class StandardMachine : Machine { 52 | private var broken = false 53 | 54 | override fun produce(): String = 55 | if (broken) throw BrokenMachineError() 56 | else (1..5).map { letters[random.nextInt(letters.size)] }.joinToString(separator = "") 57 | .also { println("Newly produced code $it") } 58 | 59 | companion object { 60 | private val letters = ('a'..'z') + ('0'..'9') 61 | private val random = Random() 62 | } 63 | } 64 | 65 | class ProductionError : Throwable() 66 | class BrokenMachineError : Throwable() 67 | -------------------------------------------------------------------------------- /src/flow/FactoryTests.kt: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | import kotlinx.coroutines.ObsoleteCoroutinesApi 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.test.UnconfinedTestDispatcher 7 | import kotlinx.coroutines.test.currentTime 8 | import kotlinx.coroutines.test.runTest 9 | import org.junit.Test 10 | import kotlin.test.assertEquals 11 | 12 | @ObsoleteCoroutinesApi 13 | @Suppress("FunctionName") 14 | class FactoryTests { 15 | 16 | class FakeFactoryControl : FactoryControl { 17 | var createdMachines = listOf() 18 | var codesStored = listOf() 19 | 20 | override fun makeMachine(): Machine { 21 | return PerfectMachine() 22 | .also { createdMachines = createdMachines + it } 23 | } 24 | 25 | override fun storeCode(code: String) { 26 | codesStored = codesStored + code 27 | } 28 | 29 | fun countCreatedCodes(): Int = createdMachines.sumBy { it.timesUsed } 30 | } 31 | 32 | class PerfectMachine : Machine { 33 | var timesUsed = 0 34 | 35 | override fun produce(): String { 36 | return (timesUsed++).toString() 37 | } 38 | } 39 | 40 | @Test 41 | fun `Function produces 20 codes in total`() = runTest(UnconfinedTestDispatcher()) { 42 | val control = FakeFactoryControl() 43 | 44 | setupFactory(control) 45 | assertEquals(20, control.codesStored.size) 46 | assertEquals(20, control.countCreatedCodes()) 47 | } 48 | 49 | 50 | @Test 51 | fun `There are 5 machines created in total`() = runTest(UnconfinedTestDispatcher()) { 52 | val control = FakeFactoryControl() 53 | 54 | setupFactory(control) 55 | assertEquals(5, control.createdMachines.count()) 56 | } 57 | 58 | /* 59 | 800 1600 1800 2400 2600 2800 3200 3400 3600 3800 60 | m1 ----------> CODE --------------> CODE --------------> CODE 61 | m1 -----------------> CODE ---------------> CODE ----- 62 | m3 ------------------> CODE ---------- 63 | m4 ---------------- 64 | Codes 1 2 3 4 5 6 65 | */ 66 | @Test 67 | fun `Machines are produced every 800ms and codes every second`() = runTest(UnconfinedTestDispatcher()) { 68 | val control = FakeFactoryControl() 69 | 70 | suspend fun checkAfter(timeMillis: Long, codes: Int) { 71 | delay(timeMillis - currentTime) 72 | assertEquals( 73 | codes, 74 | control.countCreatedCodes(), 75 | "After $timeMillis (is $currentTime) there should be $codes produced but is ${control.countCreatedCodes()}" 76 | ) 77 | assertEquals( 78 | codes, 79 | control.codesStored.size, 80 | "After $timeMillis (is $currentTime) there should be $codes stored but is ${control.countCreatedCodes()}" 81 | ) 82 | } 83 | 84 | launch { 85 | setupFactory(control) 86 | } 87 | checkAfter(800, 0) 88 | checkAfter(1600, 0) 89 | checkAfter(1800, 1) 90 | checkAfter(2400, 1) 91 | checkAfter(2600, 2) 92 | checkAfter(2800, 3) 93 | checkAfter(3200, 3) 94 | checkAfter(3400, 4) 95 | checkAfter(3600, 5) 96 | checkAfter(3800, 6) 97 | } 98 | } -------------------------------------------------------------------------------- /src/flow/ScanElements.kt: -------------------------------------------------------------------------------- 1 | package flow 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.asFlow 5 | import kotlinx.coroutines.flow.collect 6 | import kotlinx.coroutines.flow.flow 7 | import kotlinx.coroutines.flow.scan 8 | import kotlinx.coroutines.flow.toList 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Test 11 | import kotlin.test.assertEquals 12 | 13 | fun Flow.scanElements(initial: R, operation: suspend (accumulator: R, value: T) -> R): Flow = TODO() 14 | 15 | @Suppress("FunctionName") 16 | class ScanElementsTests { 17 | 18 | @Test() 19 | fun scanElementsTests() = runTest { 20 | assertEquals(listOf(), emptyList().asFlow().scanElements(0) { acc, elem -> acc + elem }.toList()) 21 | assertEquals(listOf(1), (1..1).asFlow().scanElements(0) { acc, elem -> acc + elem }.toList()) 22 | assertEquals(listOf(1, 3, 6, 10, 15), (1..5).asFlow().scanElements(0) { acc, elem -> acc + elem }.toList()) 23 | assertEquals( 24 | listOf("A", "AB", "ABC", "ABCD"), 25 | ('A'..'D').asFlow().scanElements("") { acc, elem -> acc + elem }.toList() 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /src/github/Aggregated.kt: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import kotlinx.coroutines.runBlocking 4 | 5 | fun main() = runBlocking() { 6 | val username = "" 7 | // Link https://github.com/settings/tokens/new 8 | // No permissions needed 9 | val token = "" 10 | val service: GitHubService = createGitHubService(username, token) 11 | 12 | val users = getAggregatedContributions(service) 13 | val sortedUsers = users.sortedByDescending { it.contributions } 14 | println("Aggregated contributions:") 15 | for ((index, user) in sortedUsers.withIndex()) { 16 | println("$index: ${user.login} with ${user.contributions} contributions") 17 | } 18 | } 19 | 20 | suspend fun getAggregatedContributions(service: GitHubService): List = TODO() 21 | -------------------------------------------------------------------------------- /src/github/AggregatedTest.kt: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.test.runTest 5 | import org.junit.Test 6 | import kotlin.test.assertEquals 7 | 8 | @Suppress("FunctionName") 9 | internal class AggregatedTest { 10 | private val user1 = User("AAA", 123) 11 | private val user2 = User("BBB", 1) 12 | private val repo1 = Repo(10, "R1") 13 | private val repo2 = Repo(11, "R2") 14 | 15 | @Test 16 | fun `Function works without errors`() = runTest { 17 | getAggregatedContributions(EmptyService) 18 | } 19 | 20 | @Test 21 | fun `When no repositories or no users, returns empty lists`() = runTest { 22 | val list1 = getAggregatedContributions(EmptyService) 23 | assertEquals(emptyList(), list1) 24 | val list2 = getAggregatedContributions(FakeStaticSyncService(listOf(), listOf(user1))) 25 | assertEquals(emptyList(), list2) 26 | val list3 = getAggregatedContributions(FakeStaticSyncService(listOf(repo1), listOf())) 27 | assertEquals(emptyList(), list3) 28 | val list4 = getAggregatedContributions(FakeStaticSyncService(listOf(repo1), listOf())) 29 | assertEquals(emptyList(), list4) 30 | } 31 | 32 | @Test 33 | fun `Lists all unique users`() = runTest { 34 | val list = getAggregatedContributions(FakeStaticSyncService(listOf(repo1), listOf(user1, user2))) 35 | assertEquals(listOf(user1, user2), list) 36 | } 37 | 38 | @Test 39 | fun `Accumulates contributions of a single user`() = runTest { 40 | val list = getAggregatedContributions(FakeStaticSyncService(listOf(repo1), listOf(user1, user1))) 41 | assertEquals(listOf(User(user1.login, user1.contributions * 2)), list) 42 | } 43 | 44 | @Test 45 | fun `Accumulates contributions of multiple users user`() = runTest { 46 | val list = getAggregatedContributions(FakeStaticSyncService(listOf(repo1, repo2), listOf(user1, user1, user2))) 47 | val expected = listOf( 48 | User(user1.login, user1.contributions * 4), 49 | User(user2.login, user2.contributions * 2) 50 | ).sortedBy { it.contributions } 51 | assertEquals(expected, list.sortedBy { it.contributions }) 52 | } 53 | 54 | @Test 55 | fun `Prepared for multithreading`() = runTest { 56 | val service = FakeDelayedAsyncService(List(100) { repo1 }, List(100) { user1 }) 57 | var res = getAggregatedContributions(service) 58 | delay(500) 59 | assertEquals(listOf(User(user1.login, user1.contributions * 100 * 100)), res) 60 | } 61 | 62 | class FakeStaticSyncService(private val repos: List, private val users: List) : GitHubService { 63 | override suspend fun getOrgRepos() = repos 64 | 65 | override suspend fun getRepoContributors(repo: String) = users 66 | } 67 | 68 | class FakeDelayedAsyncService(private val repos: List, private val users: List) : GitHubService { 69 | 70 | override suspend fun getOrgRepos(): List { 71 | delay(DELAY_TIME_MS) 72 | return repos 73 | } 74 | 75 | override suspend fun getRepoContributors(repo: String): List { 76 | delay(DELAY_TIME_MS) 77 | return users 78 | } 79 | 80 | companion object { 81 | val DELAY_TIME_MS = 30L 82 | } 83 | } 84 | 85 | object EmptyService : GitHubService { 86 | override suspend fun getOrgRepos() = emptyList() 87 | 88 | override suspend fun getRepoContributors(repo: String) = emptyList() 89 | } 90 | } -------------------------------------------------------------------------------- /src/github/Channels.kt: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.channels.ReceiveChannel 6 | import kotlinx.coroutines.channels.produce 7 | import kotlinx.coroutines.coroutineScope 8 | import kotlinx.coroutines.launch 9 | import kotlinx.coroutines.runBlocking 10 | import kotlinx.coroutines.sync.Mutex 11 | import kotlinx.coroutines.sync.withLock 12 | 13 | private const val username = "marcinmoskala" 14 | private const val token = "f79e01cd92d606a8369c4523d22a384ef4f16b71" 15 | private val service: GitHubService = createGitHubService(username, token) 16 | 17 | 18 | fun main(): Unit = runBlocking(Dispatchers.Default) { 19 | val usersChannel = getContributionsChannel(service) 20 | for (contributions in usersChannel) { 21 | println(contributions) 22 | } 23 | 24 | val aggregatedUsersChannel = getAggregatedContributionsChannel(service) 25 | for (aggregatedContributions in aggregatedUsersChannel) { 26 | println(aggregatedContributions.sortedByDescending { it.contributions }) 27 | } 28 | } 29 | 30 | suspend fun CoroutineScope.getContributionsChannel(service: GitHubService): ReceiveChannel> = produce { 31 | // TODO 32 | } 33 | 34 | suspend fun CoroutineScope.getAggregatedContributionsChannel(service: GitHubService): ReceiveChannel> = 35 | produce { 36 | // TODO 37 | } 38 | -------------------------------------------------------------------------------- /src/github/ChannelsTests.kt: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import kotlinx.coroutines.channels.toList 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.test.runTest 7 | import org.junit.Test 8 | import kotlin.test.assertEquals 9 | 10 | @Suppress("FunctionName") 11 | internal class ChannelsTest { 12 | private val user1 = User("AAA", 123) 13 | private val user2 = User("BBB", 1) 14 | private val user1Doubled = User("AAA", 246) 15 | private val user2Doubled = User("BBB", 2) 16 | private val repo1 = Repo(10, "R1") 17 | private val repo2 = Repo(11, "R2") 18 | 19 | private val service = FakeStaticSyncService(listOf(repo1, repo2), listOf(user1, user2)) 20 | 21 | @Test 22 | fun getContributionsTest() = runTest { 23 | val channel = getContributionsChannel(service) 24 | assertEquals(listOf(listOf(user1, user2), listOf(user1, user2)), channel.toList()) 25 | } 26 | 27 | @Test 28 | fun getAggregatedContributionsChannelTest() = runTest { 29 | val channel = getAggregatedContributionsChannel(service) 30 | assertEquals(listOf(listOf(user1, user2), listOf(user1Doubled, user2Doubled)), channel.toList()) 31 | } 32 | 33 | class FakeStaticSyncService(private val repos: List, private val users: List) : GitHubService { 34 | override suspend fun getOrgRepos() = repos 35 | 36 | override suspend fun getRepoContributors(repo: String) = users 37 | } 38 | 39 | class FakeDelayedAsyncService(private val repos: List, private val users: List) : GitHubService { 40 | 41 | override suspend fun getOrgRepos(): List { 42 | delay(DELAY_TIME_MS) 43 | return repos 44 | } 45 | 46 | override suspend fun getRepoContributors(repo: String): List { 47 | delay(DELAY_TIME_MS) 48 | return users 49 | } 50 | 51 | companion object { 52 | val DELAY_TIME_MS = 30L 53 | } 54 | } 55 | 56 | object EmptyService : GitHubService { 57 | override suspend fun getOrgRepos() = emptyList() 58 | 59 | override suspend fun getRepoContributors(repo: String) = emptyList() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/github/GithubRepo.kt: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties 4 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper 5 | import okhttp3.OkHttpClient 6 | import retrofit2.Call 7 | import retrofit2.Callback 8 | import retrofit2.Response 9 | import retrofit2.Retrofit 10 | import retrofit2.converter.jackson.JacksonConverterFactory 11 | import retrofit2.http.GET 12 | import retrofit2.http.Path 13 | import java.util.* 14 | 15 | interface GitHubService { 16 | suspend fun getOrgRepos(): List 17 | suspend fun getRepoContributors(repo: String): List 18 | } 19 | 20 | fun createGitHubService(username: String, password: String): GitHubService { 21 | val authToken = "Basic " + Base64.getEncoder().encode("$username:$password".toByteArray()).toString(Charsets.UTF_8) 22 | val httpClient = OkHttpClient.Builder() 23 | .addInterceptor { chain -> 24 | val original = chain.request() 25 | val builder = original.newBuilder() 26 | .header("Accept", "application/vnd.github.v3+json") 27 | .header("Authorization", authToken) 28 | val request = builder.build() 29 | chain.proceed(request) 30 | } 31 | .build() 32 | 33 | return Retrofit.Builder() 34 | .baseUrl("https://api.github.com") 35 | .addConverterFactory(JacksonConverterFactory.create(jacksonObjectMapper())) 36 | .client(httpClient) 37 | .build() 38 | .create(GitHubServiceApiDef::class.java) 39 | .let(::GitHubServiceImpl) 40 | } 41 | 42 | class GitHubServiceImpl(private val apiService: GitHubServiceApiDef) : GitHubService { 43 | override suspend fun getOrgRepos(): List = TODO() 44 | 45 | override suspend fun getRepoContributors(repo: String): List = TODO() 46 | } 47 | 48 | interface GitHubServiceApiDef { 49 | @GET("orgs/jetbrains/repos?per_page=100") 50 | fun getOrgReposCall(): Call> 51 | 52 | @GET("repos/jetbrains/{repo}/contributors?per_page=100") 53 | fun getRepoContributorsCall(@Path("repo") repo: String): Call> 54 | } 55 | 56 | @JsonIgnoreProperties(ignoreUnknown = true) 57 | data class Repo( 58 | val id: Long, 59 | val name: String 60 | ) 61 | 62 | @JsonIgnoreProperties(ignoreUnknown = true) 63 | data class User( 64 | val login: String, 65 | val contributions: Int 66 | ) 67 | 68 | inline fun Call.onResponse(crossinline onSuccess: (Response) -> Unit, crossinline onError: (Throwable) -> Unit) { 69 | enqueue(object : Callback { 70 | override fun onResponse(call: Call, response: Response) { 71 | onSuccess(response) 72 | } 73 | 74 | override fun onFailure(call: Call, t: Throwable) { 75 | onError(t) 76 | } 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /src/notification/NotificationSender.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalCoroutinesApi::class) 2 | 3 | package notification 4 | 5 | import kotlinx.coroutines.* 6 | import kotlinx.coroutines.test.StandardTestDispatcher 7 | import kotlinx.coroutines.test.TestScope 8 | import org.junit.Test 9 | import kotlin.test.assertEquals 10 | 11 | class NotificationsSender( 12 | private val client: NotificationsClient, 13 | private val exceptionCollector: ExceptionCollector, 14 | dispatcher: CoroutineDispatcher, 15 | ) { 16 | val scope: CoroutineScope = TODO() 17 | 18 | fun sendNotifications(notifications: List) { 19 | // TODO 20 | } 21 | 22 | fun cancel() { 23 | // TODO 24 | } 25 | } 26 | 27 | data class Notification(val id: String) 28 | 29 | interface NotificationsClient { 30 | suspend fun send(notification: Notification) 31 | } 32 | 33 | interface ExceptionCollector { 34 | fun collectException(throwable: Throwable) 35 | } 36 | 37 | class NotificationsSenderTest { 38 | 39 | @Test 40 | fun `should send 20 notifications concurrently`() { 41 | val fakeNotificationsClient = FakeNotificationsClient(delayTime = 200) 42 | val fakeExceptionCollector = FakeExceptionCollector() 43 | val testDispatcher = StandardTestDispatcher() 44 | val sender = NotificationsSender(fakeNotificationsClient, fakeExceptionCollector, testDispatcher) 45 | val notifications = List(20) { Notification("ID$it") } 46 | 47 | // when 48 | sender.sendNotifications(notifications) 49 | testDispatcher.scheduler.advanceUntilIdle() 50 | 51 | // then 52 | assertEquals(notifications, fakeNotificationsClient.sent) 53 | assertEquals(200, testDispatcher.scheduler.currentTime, "Notifications should be sent concurrently") 54 | } 55 | 56 | @Test 57 | fun `should support cancellation`() { 58 | val fakeNotificationsClient = FakeNotificationsClient(delayTime = 1000) 59 | val fakeExceptionCollector = FakeExceptionCollector() 60 | val testDispatcher = StandardTestDispatcher() 61 | val sender = NotificationsSender(fakeNotificationsClient, fakeExceptionCollector, testDispatcher) 62 | val notifications = List(20) { Notification("ID$it") } 63 | 64 | // when 65 | sender.sendNotifications(notifications) 66 | testDispatcher.scheduler.advanceTimeBy(500) 67 | sender.cancel() 68 | 69 | // then 70 | assert(sender.scope.coroutineContext.job.children.all { it.isCancelled }) 71 | 72 | // and scope should still be active 73 | assert(sender.scope.isActive) 74 | } 75 | 76 | @Test 77 | fun `should not cancel other notifications, when one has exception`() { 78 | val fakeNotificationsClient = FakeNotificationsClient(delayTime = 100, failEvery = 10) 79 | val fakeExceptionCollector = FakeExceptionCollector() 80 | val testDispatcher = StandardTestDispatcher() 81 | val sender = NotificationsSender(fakeNotificationsClient, fakeExceptionCollector, testDispatcher) 82 | val notifications = List(100) { Notification("ID$it") } 83 | 84 | // when 85 | sender.sendNotifications(notifications) 86 | testDispatcher.scheduler.advanceUntilIdle() 87 | 88 | // then 89 | assertEquals(90, fakeNotificationsClient.sent.size) 90 | } 91 | 92 | @Test 93 | fun `should send info about failed notifications`() { 94 | val fakeNotificationsClient = FakeNotificationsClient(delayTime = 100, failEvery = 10) 95 | val fakeExceptionCollector = FakeExceptionCollector() 96 | val testDispatcher = StandardTestDispatcher() 97 | val sender = NotificationsSender(fakeNotificationsClient, fakeExceptionCollector, testDispatcher) 98 | val notifications = List(100) { Notification("ID$it") } 99 | 100 | // when 101 | sender.sendNotifications(notifications) 102 | testDispatcher.scheduler.advanceUntilIdle() 103 | 104 | // then 105 | assertEquals(10, fakeExceptionCollector.collected.size) 106 | } 107 | } 108 | 109 | class FakeNotificationsClient( 110 | val delayTime: Long = 0L, 111 | val failEvery: Int = Int.MAX_VALUE 112 | ) : NotificationsClient { 113 | var sent = emptyList() 114 | var counter = 0 115 | var usedThreads = emptyList() 116 | 117 | override suspend fun send(notification: Notification) { 118 | if (delayTime > 0) delay(delayTime) 119 | usedThreads += Thread.currentThread().name 120 | counter++ 121 | if (counter % failEvery == 0) { 122 | throw FakeFailure(notification) 123 | } 124 | sent += notification 125 | } 126 | } 127 | 128 | class FakeFailure(val notification: Notification) : Throwable("Planned fail for notification ${notification.id}") 129 | 130 | class FakeExceptionCollector : ExceptionCollector { 131 | var collected = emptyList() 132 | 133 | override fun collectException(throwable: Throwable) = synchronized(this) { 134 | collected += throwable 135 | } 136 | } -------------------------------------------------------------------------------- /src/request/Request.kt: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.launch 5 | import kotlinx.coroutines.runBlocking 6 | import kotlinx.coroutines.sync.Mutex 7 | import kotlinx.coroutines.sync.withLock 8 | import org.junit.Test 9 | import kotlin.system.measureTimeMillis 10 | import kotlin.test.assertEquals 11 | 12 | /* 13 | TODO: This function should return the best student on the [semester]. 14 | */ 15 | suspend fun getBestStudent(semester: String, repo: StudentsRepository): Student = TODO() 16 | 17 | data class Student(val id: Int, val result: Double, val semester: String) 18 | 19 | interface StudentsRepository { 20 | suspend fun getStudentIds(semester: String): List 21 | suspend fun getStudent(id: Int): Student 22 | } 23 | 24 | class RequestTest { 25 | 26 | @Test 27 | fun `Function does return the best student in the semester`() = runBlocking { 28 | // given 29 | val semester = "19L" 30 | val best = Student(2, 95.0, semester) 31 | val repo = ImmediateFakeStudentRepo( 32 | listOf( 33 | Student(1, 90.0, semester), 34 | best, 35 | Student(3, 50.0, semester) 36 | ) 37 | ) 38 | 39 | // when 40 | val chosen = getBestStudent(semester, repo) 41 | 42 | // then 43 | assertEquals(best, chosen) 44 | } 45 | 46 | @Test 47 | fun `When no students, correct error is thrown`() = runBlocking { 48 | // given 49 | val semester = "19L" 50 | val repo = ImmediateFakeStudentRepo(listOf()) 51 | 52 | // when and then 53 | assertThrowsError { 54 | getBestStudent(semester, repo) 55 | } 56 | } 57 | 58 | @Test 59 | fun `Requests do not wait for each other`() = runBlocking { 60 | // given 61 | val repo = WaitingFakeStudentRepo() 62 | 63 | // when and then 64 | assertTimeAround(1200) { 65 | getBestStudent("AAA", repo) 66 | } 67 | } 68 | 69 | @Test 70 | fun `Cancellation works fine`() = runBlocking { 71 | // given 72 | val repo = WaitingFakeStudentRepo() 73 | 74 | // when 75 | val job = launch { 76 | getBestStudent("AAA", repo) 77 | } 78 | delay(300) 79 | job.cancel() 80 | 81 | // then 82 | assertEquals(0, repo.returnedStudents) 83 | } 84 | 85 | @Test 86 | fun `When one request has error, all are stopped and error is thrown`() = runBlocking { 87 | // given 88 | val repo = FirstFailingFakeStudentRepo() 89 | 90 | // when and then 91 | assertThrowsError { 92 | getBestStudent("AAA", repo) 93 | } 94 | 95 | // then 96 | assertEquals( 97 | 0, 98 | repo.studentsReturned, 99 | "Looks like some requests were still running after the first one had an error" 100 | ) 101 | } 102 | } 103 | 104 | class ImmediateFakeStudentRepo( 105 | private val students: List 106 | ) : StudentsRepository { 107 | 108 | override suspend fun getStudentIds(semester: String): List = 109 | students.filter { it.semester == semester } 110 | .map { it.id } 111 | 112 | override suspend fun getStudent(id: Int): Student = 113 | students.first { it.id == id } 114 | } 115 | 116 | inline fun assertTimeAround(expectedTime: Int, upperMargin: Int = 100, body: () -> Unit) { 117 | val actualTime = measureTimeMillis(body) 118 | assert(actualTime in expectedTime..(expectedTime + upperMargin)) { 119 | "Operation should take around $expectedTime, but it took $actualTime" 120 | } 121 | } 122 | 123 | inline fun assertThrowsError(body: () -> Unit) { 124 | try { 125 | body() 126 | assert(false) { "There should be an error of type ${T::class.simpleName}" } 127 | } catch (throwable: Throwable) { 128 | if (throwable !is T) { 129 | throw throwable 130 | } 131 | } 132 | } 133 | 134 | class WaitingFakeStudentRepo : StudentsRepository { 135 | var returnedStudents = 0 136 | 137 | override suspend fun getStudentIds(semester: String): List { 138 | delay(200) 139 | return (1..5).toList() 140 | } 141 | 142 | override suspend fun getStudent(id: Int): Student { 143 | delay(1000) 144 | returnedStudents++ 145 | return Student(12, 12.0, "AAA") 146 | } 147 | } 148 | 149 | class FirstFailingFakeStudentRepo : StudentsRepository { 150 | var first = true 151 | var studentsReturned = 0 152 | val mutex = Mutex() 153 | 154 | override suspend fun getStudentIds(semester: String): List { 155 | delay(200) 156 | return (1..5).toList() 157 | } 158 | 159 | override suspend fun getStudent(id: Int): Student { 160 | delay(100) 161 | mutex.withLock { 162 | // To prevent more than one throwing 163 | if (first) { 164 | first = false 165 | throw FirstFailingError() 166 | } 167 | } 168 | delay(100) 169 | studentsReturned++ 170 | return Student(12, 12.0, "AAA") 171 | } 172 | 173 | class FirstFailingError() : Error() 174 | } 175 | -------------------------------------------------------------------------------- /src/structured/Structured.kt: -------------------------------------------------------------------------------- 1 | package structured 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.coroutineScope 5 | import kotlinx.coroutines.launch 6 | import kotlinx.coroutines.runBlocking 7 | import java.time.ZonedDateTime 8 | import java.util.Random 9 | 10 | // We have a worker who makes machines every 800ms as long as there is less than 5 of them. 11 | // He won't produce more than 1000 machines. Please, use `repeat(1000)` instead of `while(true)` 12 | // Every machine produces a code using `structured.produce` function every second. It saves this code to shared space. 13 | // In case of an error, it stops working. 14 | // Machine won't produce more than 1000 codes. Please, use `repeat(1000)` instead of `while(true)` 15 | // We have a single manager that takes codes one after another and stores them using `control.storeCode`. 16 | // Note that is it time consuming operation. 17 | // He is the only one who can do that. 18 | // In case of no codes, he sleeps for 100ms 19 | // He ends everything when there are 20 codes stored. 20 | // He won't do it more than 1000 times. Please, use `repeat(1000)` instead of `while(true)` 21 | 22 | fun main() = runBlocking { 23 | setupFactory(StandardFactoryControl()) 24 | } 25 | 26 | fun CoroutineScope.setupFactory(control: FactoryControl) = launch { 27 | val factory = StructuredFactory() 28 | launch { 29 | factory.makeWorker(control) 30 | } 31 | factory.makeManager(this, control) 32 | } 33 | 34 | class StructuredFactory { 35 | private val codes = mutableListOf() 36 | 37 | // Make machine using `control.makeMachine()` and then use it to create codes in a separate coroutine every 1000 ms. 38 | // Codes should be stored in the `codes`. Should first wait, and then produce. 39 | @Throws(ProductionError::class) 40 | suspend fun makeMachine(control: FactoryControl): Unit = coroutineScope { 41 | // TODO 42 | } 43 | 44 | // Makes machines every 800ms, but there should be no more than 5 active machines at the same time. 45 | suspend fun makeWorker(control: FactoryControl): Unit = coroutineScope { 46 | // TODO 47 | } 48 | 49 | // Checks out the codes and if there is no, waits for 100ms. Otherwise takes the code and stores it using `control.storeCode(code)`. 50 | // When 20'th code were sent, ends the whole process. 51 | suspend fun makeManager(scope: CoroutineScope, control: FactoryControl): Unit = coroutineScope { 52 | // TODO 53 | } 54 | } 55 | 56 | interface FactoryControl { 57 | fun makeMachine(): Machine 58 | fun storeCode(code: String) 59 | } 60 | 61 | class StandardFactoryControl : FactoryControl { 62 | private var broken = false 63 | private var waiting = false 64 | private var codes = listOf() 65 | private var lastMachineProducedTimestamp: ZonedDateTime? = null 66 | 67 | override fun makeMachine(): Machine = when { 68 | lastMachineProducedTimestamp?.let { ZonedDateTime.now() > it.plusNanos(700_000_000) } == false -> 69 | throw IncorrectUseError("Need to wait 800ms between making machines") 70 | else -> StandardMachine() 71 | .also { lastMachineProducedTimestamp = ZonedDateTime.now() } 72 | .also { println("Newly created machine") } 73 | } 74 | 75 | override fun storeCode(code: String) { 76 | if (waiting || broken) { 77 | println("Factory control is broken due to 2 attempts to store code at the same time") 78 | broken = true 79 | throw BrokenMachineError() 80 | } 81 | waiting = true 82 | Thread.sleep(500) 83 | waiting = false 84 | codes = codes + code 85 | println("Newly stored code is $code") 86 | } 87 | } 88 | 89 | interface Machine { 90 | @Throws(ProductionError::class) 91 | fun produce(): String 92 | } 93 | 94 | class StandardMachine : Machine { 95 | private var broken = false 96 | private var lastCodeProducedTimestamp: ZonedDateTime? = null 97 | 98 | override fun produce(): String = when { 99 | broken -> 100 | throw BrokenMachineError() 101 | lastCodeProducedTimestamp?.let { ZonedDateTime.now() > it.plusSeconds(1) } == false -> 102 | throw IncorrectUseError("Need to wait 1s between uses of the same machine") 103 | random.nextInt(8) == 0 -> { 104 | broken = true 105 | println("Machine broken") 106 | throw ProductionError() 107 | } 108 | else -> (1..5).map { letters[random.nextInt(letters.size)] }.joinToString(separator = "") 109 | .also { lastCodeProducedTimestamp = ZonedDateTime.now() } 110 | .also { println("Newly produced code $it") } 111 | } 112 | 113 | companion object { 114 | private val letters = ('a'..'z') + ('0'..'9') 115 | private val random = Random() 116 | } 117 | } 118 | 119 | class ProductionError() : Throwable() 120 | class BrokenMachineError() : Throwable() 121 | class IncorrectUseError(message: String) : Throwable(message) 122 | -------------------------------------------------------------------------------- /src/structured/StructuredTests.kt: -------------------------------------------------------------------------------- 1 | package structured 2 | 3 | import kotlinx.coroutines.ObsoleteCoroutinesApi 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.test.currentTime 6 | import kotlinx.coroutines.test.runTest 7 | import org.junit.Test 8 | import kotlin.test.assertEquals 9 | 10 | @ObsoleteCoroutinesApi 11 | @Suppress("FunctionName") 12 | class StructuredTests { 13 | 14 | class FakeFactoryControl( 15 | val machineProducer: () -> Machine 16 | ) : FactoryControl { 17 | var createdMachines = listOf() 18 | var codesStored = listOf() 19 | private var finished = false 20 | 21 | override fun makeMachine(): Machine { 22 | require(!finished) 23 | return machineProducer() 24 | .also { createdMachines = createdMachines + it } 25 | } 26 | 27 | override fun storeCode(code: String) { 28 | require(!finished) 29 | codesStored = codesStored + code 30 | } 31 | 32 | fun finish() { 33 | finished = true 34 | } 35 | } 36 | 37 | class PerfectMachine : Machine { 38 | var timesUsed = 0 39 | private var finished = false 40 | 41 | override fun produce(): String { 42 | require(!finished) 43 | return (timesUsed++).toString() 44 | } 45 | 46 | fun finish() { 47 | finished = true 48 | } 49 | } 50 | 51 | class FailingMachine : Machine { 52 | override fun produce(): String = throw ProductionError() 53 | } 54 | 55 | @Test(timeout = 500) 56 | fun `PerfectMachine produces next numbers`() { 57 | val machine = PerfectMachine() 58 | assertEquals("0", machine.produce()) 59 | assertEquals("1", machine.produce()) 60 | assertEquals("2", machine.produce()) 61 | assertEquals("3", machine.produce()) 62 | assertEquals("4", machine.produce()) 63 | } 64 | 65 | @Test(timeout = 500) 66 | fun `FakeFactoryControl produces machines using producer`() { 67 | val perfectFactoryControl = FakeFactoryControl(machineProducer = ::PerfectMachine) 68 | val machine1 = perfectFactoryControl.makeMachine() 69 | assertEquals("0", machine1.produce()) 70 | assertEquals("1", machine1.produce()) 71 | assertEquals("2", machine1.produce()) 72 | val machine2 = perfectFactoryControl.makeMachine() 73 | assertEquals("0", machine2.produce()) 74 | assertEquals("1", machine2.produce()) 75 | assertEquals("2", machine2.produce()) 76 | 77 | val failingFactoryControl = FakeFactoryControl(machineProducer = ::FailingMachine) 78 | val machine3 = failingFactoryControl.makeMachine() 79 | assertThrows { machine3.produce() } 80 | } 81 | 82 | @Test 83 | fun `Function creates a new machine every 800ms up to 5 and no more if they are all perfect`() = runTest { 84 | val control = FakeFactoryControl(machineProducer = ::PerfectMachine) 85 | setupFactory(control) 86 | for (i in 0..5) { 87 | assertEquals(i, control.createdMachines.size) 88 | delay(800) 89 | } 90 | for (i in 0..10) { 91 | assertEquals(5, control.createdMachines.size) 92 | delay(800) 93 | } 94 | } 95 | 96 | @Test 97 | fun `Function creates a new machine every 800ms every time if all machines are failing`() = runTest { 98 | val control = FakeFactoryControl(machineProducer = ::FailingMachine) 99 | setupFactory(control) 100 | for (i in 0..100) { 101 | assertEquals(i, control.createdMachines.size) 102 | delay(800) 103 | } 104 | } 105 | 106 | @Test 107 | fun `Function creates a new machine after 800ms if less then 5`() = runTest { 108 | var correctMachines = 0 109 | var nextIsCorrect = false 110 | val control = FakeFactoryControl(machineProducer = { 111 | val next = if (nextIsCorrect) { 112 | correctMachines++ 113 | PerfectMachine() 114 | } else { 115 | FailingMachine() 116 | } 117 | nextIsCorrect = !nextIsCorrect 118 | next 119 | }) 120 | 121 | setupFactory(control) 122 | delay(20_000) 123 | 124 | assertEquals(5, control.createdMachines.filterIsInstance().size) 125 | 126 | // Is not producing any new 127 | val producedPre = control.createdMachines.size 128 | delay(2_000) 129 | 130 | val producedPost = control.createdMachines.size 131 | assertEquals( 132 | producedPre, 133 | producedPost, 134 | "It should not produce any new machines when there are already 5 perfect" 135 | ) 136 | } 137 | 138 | @Test 139 | fun `The first code should be created after time to create machine and time to produce code (+10ms margin)`() = 140 | runTest { 141 | val perfectMachine = PerfectMachine() 142 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 143 | 144 | setupFactory(control) 145 | delay(800 + 1000 + 10) 146 | 147 | assertEquals(1, perfectMachine.timesUsed) 148 | } 149 | 150 | /* 151 | 800 1600 1800 2400 2600 2800 3200 3400 3600 3800 152 | m1 ----------> CODE --------------> CODE --------------> CODE 153 | m1 -----------------> CODE ---------------> CODE ----- 154 | m3 ------------------> CODE ---------- 155 | m4 ---------------- 156 | Codes 1 2 3 4 5 6 157 | */ 158 | @Test 159 | fun `Every machine produces code every second`() = runTest { 160 | val perfectMachine = PerfectMachine() 161 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 162 | suspend fun checkAt(timeMillis: Long, codes: Int) { 163 | delay(timeMillis - currentTime) 164 | assertEquals(codes, perfectMachine.timesUsed) 165 | } 166 | 167 | setupFactory(control) 168 | checkAt(800, 0) 169 | checkAt(1600, 0) 170 | checkAt(1800, 1) 171 | checkAt(2400, 1) 172 | checkAt(2600, 2) 173 | checkAt(2800, 3) 174 | checkAt(3200, 3) 175 | checkAt(3400, 4) 176 | checkAt(3600, 5) 177 | checkAt(3800, 6) 178 | } 179 | 180 | @Test 181 | fun `Created codes are stored no later then 100ms after created`() = runTest { 182 | val perfectMachine = PerfectMachine() 183 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 184 | suspend fun checkAt(timeMillis: Long, codes: Int) { 185 | delay(timeMillis - currentTime) 186 | 187 | assertEquals(codes, control.codesStored.size) 188 | } 189 | setupFactory(control) 190 | checkAt(900, 0) 191 | checkAt(1700, 0) 192 | checkAt(1900, 1) 193 | checkAt(2500, 1) 194 | checkAt(2700, 2) 195 | checkAt(2900, 3) 196 | checkAt(3300, 3) 197 | checkAt(3500, 4) 198 | checkAt(3700, 5) 199 | checkAt(3900, 6) 200 | } 201 | 202 | @Test 203 | fun `When there are 20 codes stored, process ends`() = runTest { 204 | val perfectMachine = PerfectMachine() 205 | val control = FakeFactoryControl(machineProducer = { perfectMachine }) 206 | setupFactory(control) 207 | delay(6810) // Time when 20'th code is produced 208 | 209 | assertEquals(20, control.codesStored.size) 210 | perfectMachine.finish() // To not let it be used anymore 211 | control.finish() // To not let it be used anymore 212 | delay(1_000) 213 | 214 | assertEquals(20, control.codesStored.size) 215 | } 216 | 217 | private inline fun assertThrows(body: () -> Unit) { 218 | val error = try { 219 | body() 220 | Any() 221 | } catch (t: Throwable) { 222 | t 223 | } 224 | assertEquals(T::class, error::class) 225 | } 226 | } -------------------------------------------------------------------------------- /src/ui/BasePresenter.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import kotlinx.coroutines.* 4 | import kotlinx.coroutines.test.setMain 5 | import org.junit.Before 6 | import org.junit.Test 7 | import kotlin.test.assertEquals 8 | import kotlin.test.assertTrue 9 | 10 | abstract class BasePresenter( 11 | private val onError: (Throwable) -> Unit = {} 12 | ) { 13 | val scope: CoroutineScope = TODO() 14 | 15 | fun onDestroy() {} 16 | } 17 | 18 | @Suppress("FunctionName") 19 | class BasePresenterTests { 20 | 21 | private val UI = newSingleThreadContext("UIThread") // Normally it will be Dispatchers.Main 22 | 23 | @Before 24 | fun setUp() { 25 | Dispatchers.setMain(UI) 26 | } 27 | 28 | class FakePresenter( 29 | private val jobInterceptor: (() -> Unit)? = null, 30 | onError: (Throwable) -> Unit = {} 31 | ) : BasePresenter(onError) { 32 | 33 | var cancelledJobs = 0 34 | 35 | fun onCreate() { 36 | scope.launch { 37 | try { 38 | delay(100) 39 | jobInterceptor?.invoke() 40 | delay(2000) 41 | } finally { 42 | cancelledJobs += 1 43 | } 44 | } 45 | scope.launch { 46 | try { 47 | delay(100) 48 | jobInterceptor?.invoke() 49 | delay(2000) 50 | } finally { 51 | cancelledJobs += 1 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Test 58 | fun `onDestroy cancels all jobs`() = runBlocking { 59 | val presenter = FakePresenter() 60 | presenter.onCreate() 61 | delay(200) 62 | presenter.onDestroy() 63 | delay(200) 64 | assertEquals(2, presenter.cancelledJobs) 65 | } 66 | 67 | @Test 68 | fun `Coroutines run on main thread`() = runBlocking { 69 | var threads = listOf() 70 | val presenter = FakePresenter( 71 | jobInterceptor = { 72 | threads += Thread.currentThread() 73 | } 74 | ) 75 | presenter.onCreate() 76 | delay(100) 77 | presenter.onDestroy() 78 | delay(100) 79 | threads.forEach { 80 | assert(it.name.startsWith("UIThread")) { "We should switch to UI thread, and now we are on ${it.name}" } 81 | } 82 | assert(threads.isNotEmpty()) 83 | } 84 | 85 | @Test 86 | fun `When a job throws an error, it is handled`(): Unit = runBlocking { 87 | val error = Error() 88 | var errors = listOf() 89 | val presenter = FakePresenter( 90 | jobInterceptor = { throw error }, 91 | onError = { errors += it } 92 | ) 93 | presenter.onCreate() 94 | delay(200) 95 | assertEquals(error, errors.first()) 96 | } 97 | 98 | class FakePresenterForSingleExceptionHandling(val onSecondAction: () -> Unit) : BasePresenter() { 99 | 100 | var cancelledJobs = 0 101 | 102 | fun onCreate() { 103 | scope.launch { 104 | delay(100) 105 | throw Error() 106 | } 107 | scope.launch { 108 | delay(200) 109 | onSecondAction() 110 | } 111 | } 112 | } 113 | 114 | @Test 115 | fun `Error on a single coroutine, does not cancel others`() = runBlocking { 116 | var called = false 117 | val presenter = FakePresenterForSingleExceptionHandling( 118 | onSecondAction = { called = true } 119 | ) 120 | presenter.onCreate() 121 | delay(300) 122 | assertTrue(called) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/ui/MainPresenter.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import kotlinx.coroutines.launch 4 | import java.util.* 5 | 6 | class MainPresenter( 7 | private val view: MainView, 8 | private val userRepo: UserRepository, 9 | private val newsRepo: NewsRepository 10 | ) : BasePresenter(view::onError) { 11 | 12 | fun onCreate() { 13 | scope.launch { 14 | val user = userRepo.getUser() 15 | view.showUserData(user) 16 | } 17 | scope.launch { 18 | val news = newsRepo.getNews() 19 | .sortedByDescending { it.date } 20 | view.showNews(news) 21 | } 22 | } 23 | } 24 | 25 | interface MainView { 26 | fun onError(throwable: Throwable): Unit 27 | fun showUserData(user: UserData) 28 | fun showNews(news: List) 29 | } 30 | 31 | interface UserRepository { 32 | suspend fun getUser(): UserData 33 | } 34 | 35 | interface NewsRepository { 36 | suspend fun getNews(): List 37 | } 38 | 39 | data class UserData(val name: String) 40 | data class News(val date: Date) 41 | --------------------------------------------------------------------------------