From 5a1ed2f933b5b69874c9b7d0af6de0b5a5106e44 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 18 Mar 2023 12:23:53 +0600 Subject: [PATCH 1/3] start 0.10.0 + rework of flows --- CHANGELOG.md | 2 + build.gradle | 4 +- gradle.properties | 5 +- .../dev/inmo/krontab/utils/SchedulerFlow.kt | 89 ++++++++++++++++--- .../utils/CheckMonthsAndDaysCorrectWork.kt | 1 + .../kotlin/dev/inmo/krontab/utils/FailJob.kt | 4 +- .../kotlin/dev/inmo/krontab/utils/RunTest.kt | 8 -- .../dev/inmo/krontab/utils/SchedulerFlow.kt | 10 +-- .../dev/inmo/krontab/utils/StringParseTest.kt | 20 +++-- .../dev/inmo/krontab/utils/TimeZoneTest.kt | 1 + .../dev/inmo/krontab/utils/WeekDaysTest.kt | 1 + .../kotlin/dev/inmo/krontab/utils/RunTest.kt | 5 -- .../kotlin/dev/inmo/krontab/utils/RunTest.kt | 9 -- 13 files changed, 105 insertions(+), 54 deletions(-) delete mode 100644 src/commonTest/kotlin/dev/inmo/krontab/utils/RunTest.kt delete mode 100644 src/jsTest/kotlin/dev/inmo/krontab/utils/RunTest.kt delete mode 100644 src/jvmTest/kotlin/dev/inmo/krontab/utils/RunTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f0e2c..5793f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.10.0 + ## 0.9.0 * Versions diff --git a/build.gradle b/build.gradle index 5a203f3..6fd4cd9 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" - classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.android.tools.build:gradle:7.3.1' } } @@ -57,6 +57,7 @@ kotlin { android { publishAllLibraryVariants() } + linuxX64() sourceSets { @@ -79,6 +80,7 @@ kotlin { dependencies { implementation kotlin('test-common') implementation kotlin('test-annotations-common') + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version" } } jvmTest { diff --git a/gradle.properties b/gradle.properties index e83b6aa..3253a88 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ +org.gradle.jvmargs=-Xmx512m kotlin.code.style=official org.gradle.parallel=true kotlin.js.generate.externals=true @@ -33,5 +34,5 @@ androidx_work_version=2.8.0 ## Common -version=0.9.0 -android_code_version=23 +version=0.10.0 +android_code_version=24 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt index 55f8989..17b1c57 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt @@ -2,35 +2,96 @@ package dev.inmo.krontab.utils import com.soywiz.klock.DateTime import com.soywiz.klock.DateTimeTz +import com.soywiz.klock.milliseconds import dev.inmo.krontab.* import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive /** - * This [Flow] will trigger emitting each near time which will be returned from [this] [KronScheduler] with attention to - * time zones + * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)** * - * @see channelFlow - * @see KronSchedulerTz.doInfinityTz + * Will emit all the [KronScheduler.next] as soon as possible. In case [KronScheduler.next] return null, flow will + * be completed + * + * @param since Will be used as the first parameter for [KronScheduler.next] fun */ @FlowPreview -fun KronScheduler.asTzFlow(): Flow = channelFlow { - doInfinityTz { - send(it) +fun KronScheduler.asTzFlowWithoutDelays(since: DateTimeTz = DateTime.nowLocal()): Flow = flow { + var previous = since + while (currentCoroutineContext().isActive) { + val next = next(previous) ?: break + emit(next) + previous = next + 1.milliseconds } } /** - * This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s + * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)** * - * @see channelFlow - * @see KronScheduler.doInfinity + * This [Flow] will use [asTzFlowWithoutDelays], but stop on each time until this time will happen */ @FlowPreview -fun KronScheduler.asFlow(): Flow = channelFlow { - doInfinity { - send(it) +fun KronScheduler.asTzFlowWithDelays(): Flow = asTzFlowWithoutDelays().onEach { futureHappenTime -> + val now = DateTime.nowLocal() + + delay((futureHappenTime - now).millisecondsLong) +} + +/** + * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)** + * + * This [Flow] will use [asTzFlowWithoutDelays], but stop on each time until this time will happen + */ +@Deprecated( + "Behaviour will be changed. In some of near versions this flow will not delay executions", + ReplaceWith("this.asTzFlowWithDelays()", "dev.inmo.krontab.utils.asTzFlowWithDelays") +) +@FlowPreview +fun KronScheduler.asTzFlow(): Flow = asTzFlowWithDelays() + +/** + * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)** + * + * Will emit all the [KronScheduler.next] as soon as possible. In case [KronScheduler.next] return null, flow will + * be completed + * + * @param since Will be used as the first parameter for [KronScheduler.next] fun + */ +@FlowPreview +fun KronScheduler.asFlowWithoutDelays(since: DateTime = DateTime.now()): Flow = flow { + var previous = since + while (currentCoroutineContext().isActive) { + val next = next(previous) ?: break + emit(next) + previous = next + 1.milliseconds } } + +/** + * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)** + * + * This [Flow] will use [asFlowWithoutDelays], but stop on each time until this time will happen + */ +@FlowPreview +fun KronScheduler.asFlowWithDelays(): Flow = asFlowWithoutDelays().onEach { futureHappenTime -> + val now = DateTime.now() + + delay((futureHappenTime - now).millisecondsLong) +} + +/** + * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)** + * + * This [Flow] will use [asFlowWithDelays], but stop on each time until this time will happen + */ +@Deprecated( + "Behaviour will be changed. In some of near versions this flow will not delay executions", + ReplaceWith("this.asFlowWithDelays()", "dev.inmo.krontab.utils.asFlowWithDelays") +) +@FlowPreview +fun KronScheduler.asFlow(): Flow = asFlowWithDelays() diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/CheckMonthsAndDaysCorrectWork.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/CheckMonthsAndDaysCorrectWork.kt index 60c7d03..f62841e 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/CheckMonthsAndDaysCorrectWork.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/CheckMonthsAndDaysCorrectWork.kt @@ -3,6 +3,7 @@ package dev.inmo.krontab.utils import com.soywiz.klock.DateTime import com.soywiz.klock.days import dev.inmo.krontab.buildSchedule +import kotlinx.coroutines.test.runTest import kotlin.test.* class CheckMonthsAndDaysCorrectWork { diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/FailJob.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/FailJob.kt index 836c38b..cf2aa9e 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/FailJob.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/FailJob.kt @@ -1,6 +1,8 @@ package dev.inmo.krontab.utils -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch fun CoroutineScope.createFailJob(forTimeMillis: Long) = launch { delay(forTimeMillis) diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/RunTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/RunTest.kt deleted file mode 100644 index 871bb2e..0000000 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/RunTest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.inmo.krontab.utils - -import kotlinx.coroutines.CoroutineScope - -/** - * Workaround to use suspending functions in unit tests - */ -expect fun runTest(block: suspend (scope : CoroutineScope) -> Unit) diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt index 0b305fd..f05972c 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt @@ -2,8 +2,8 @@ package dev.inmo.krontab.utils import dev.inmo.krontab.builder.buildSchedule import kotlinx.coroutines.* -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -18,7 +18,7 @@ class SchedulerFlowTests { } } - val flow = kronScheduler.asFlow() + val flow = kronScheduler.asFlowWithoutDelays() runTest { val mustBeCollected = 10 @@ -40,14 +40,14 @@ class SchedulerFlowTests { } } - val flow = kronScheduler.asFlow() + val flow = kronScheduler.asFlowWithoutDelays() runTest { val testsCount = 10 - val failJob = it.createFailJob((testsCount * 2) * 1000L) + val failJob = createFailJob((testsCount * 2) * 1000L) val mustBeCollected = 10 val answers = (0 until testsCount).map { _ -> - it.async { + async { var collected = 0 flow.takeWhile { collected < mustBeCollected diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt index b4b0ffe..cc8e9ed 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt @@ -4,9 +4,8 @@ import com.soywiz.klock.* import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.buildSchedule import kotlinx.coroutines.* -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.takeWhile -import kotlin.math.floor +import kotlinx.coroutines.test.runTest import kotlin.test.* @ExperimentalCoroutinesApi @@ -16,7 +15,7 @@ class StringParseTest { fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() { val kronScheduler = buildSchedule("*/1 * * * *") - val flow = kronScheduler.asFlow() + val flow = kronScheduler.asFlowWithoutDelays() runTest { val mustBeCollected = 10 @@ -33,7 +32,7 @@ class StringParseTest { fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() { val kronScheduler = buildSchedule("*/1 * * * * 500ms") - val flow = kronScheduler.asFlow() + val flow = kronScheduler.asFlowWithoutDelays() runTest { val mustBeCollected = 10 @@ -51,14 +50,14 @@ class StringParseTest { fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() { val kronScheduler = buildSchedule("*/1 * * * *") - val flow = kronScheduler.asFlow() + val flow = kronScheduler.asFlowWithoutDelays() runTest { val testsCount = 10 - val failJob = it.createFailJob((testsCount * 2) * 1000L) + val failJob = createFailJob((testsCount * 2) * 1000L) val mustBeCollected = 10 val answers = (0 until testsCount).map { _ -> - it.async { + async { var collected = 0 flow.takeWhile { collected < mustBeCollected @@ -81,7 +80,7 @@ class StringParseTest { val rangesEnds = listOf(0 to 5, 30 to 35) val kronScheduler = buildSchedule("${rangesEnds.joinToString(",") { "${it.first}-${it.second}" }} * * * *") - val flow = kronScheduler.asFlow() + val flow = kronScheduler.asFlowWithoutDelays() runTest { val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList() @@ -91,7 +90,10 @@ class StringParseTest { flow.takeWhile { ranges.isNotEmpty() }.collect { ranges.remove(it.seconds) collected++ - assertTrue(collected <= expectedCollects) + assertTrue( + collected <= expectedCollects, + "Expected value should be less than $expectedCollects, but was $collected. Ranges state: $ranges" + ) } assertEquals(expectedCollects, collected) } diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt index 97e03e9..119a7fb 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt @@ -3,6 +3,7 @@ package dev.inmo.krontab.utils import com.soywiz.klock.* import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.next +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/WeekDaysTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/WeekDaysTest.kt index 9b8d8b7..be346b4 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/WeekDaysTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/WeekDaysTest.kt @@ -2,6 +2,7 @@ package dev.inmo.krontab.utils import com.soywiz.klock.* import dev.inmo.krontab.builder.buildSchedule +import kotlinx.coroutines.test.runTest import kotlin.math.ceil import kotlin.test.* diff --git a/src/jsTest/kotlin/dev/inmo/krontab/utils/RunTest.kt b/src/jsTest/kotlin/dev/inmo/krontab/utils/RunTest.kt deleted file mode 100644 index 2318d43..0000000 --- a/src/jsTest/kotlin/dev/inmo/krontab/utils/RunTest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package dev.inmo.krontab.utils - -import kotlinx.coroutines.* - -actual fun runTest(block: suspend (scope : CoroutineScope) -> Unit): dynamic = GlobalScope.promise { block(this) } diff --git a/src/jvmTest/kotlin/dev/inmo/krontab/utils/RunTest.kt b/src/jvmTest/kotlin/dev/inmo/krontab/utils/RunTest.kt deleted file mode 100644 index aee7680..0000000 --- a/src/jvmTest/kotlin/dev/inmo/krontab/utils/RunTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.inmo.krontab.utils - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking - -/** - * Workaround to use suspending functions in unit tests - */ -actual fun runTest(block: suspend (scope: CoroutineScope) -> Unit) = runBlocking(block = block) From 890ab5b15d57daa973753209a51a1f1f86b8dfb5 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 18 Mar 2023 12:24:25 +0600 Subject: [PATCH 2/3] fill changelogs --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5793f35..b89593e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## 0.10.0 +* New extensions for `KronScheduler`: + * `asTzFlowWithoutDelays`/`asFlowWithoutDelays` + * `asTzFlowWithDelays`/`asFlowWithDelays` +* Old `KronScheduler.asFlow` and `KronScheduler.asTzFlow` temporarily marked as deprecated: after several versions their +behaviour will be changed to undelayed one +* All the flow extensions now use `cold` non-channel flows. Potentially it should increase performance and decrease memory usage + ## 0.9.0 * Versions From ede47ae664c2367cb8b6d1f3cc09b2b4949e11f5 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 18 Mar 2023 12:34:06 +0600 Subject: [PATCH 3/3] remove redundant FlowPrevies on flows API --- .../kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt index 17b1c57..61d4e46 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt @@ -3,8 +3,8 @@ package dev.inmo.krontab.utils import com.soywiz.klock.DateTime import com.soywiz.klock.DateTimeTz import com.soywiz.klock.milliseconds -import dev.inmo.krontab.* -import kotlinx.coroutines.FlowPreview +import dev.inmo.krontab.KronScheduler +import dev.inmo.krontab.next import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -20,7 +20,6 @@ import kotlinx.coroutines.isActive * * @param since Will be used as the first parameter for [KronScheduler.next] fun */ -@FlowPreview fun KronScheduler.asTzFlowWithoutDelays(since: DateTimeTz = DateTime.nowLocal()): Flow = flow { var previous = since while (currentCoroutineContext().isActive) { @@ -35,7 +34,6 @@ fun KronScheduler.asTzFlowWithoutDelays(since: DateTimeTz = DateTime.nowLocal()) * * This [Flow] will use [asTzFlowWithoutDelays], but stop on each time until this time will happen */ -@FlowPreview fun KronScheduler.asTzFlowWithDelays(): Flow = asTzFlowWithoutDelays().onEach { futureHappenTime -> val now = DateTime.nowLocal() @@ -51,7 +49,6 @@ fun KronScheduler.asTzFlowWithDelays(): Flow = asTzFlowWithoutDelays "Behaviour will be changed. In some of near versions this flow will not delay executions", ReplaceWith("this.asTzFlowWithDelays()", "dev.inmo.krontab.utils.asTzFlowWithDelays") ) -@FlowPreview fun KronScheduler.asTzFlow(): Flow = asTzFlowWithDelays() /** @@ -62,7 +59,6 @@ fun KronScheduler.asTzFlow(): Flow = asTzFlowWithDelays() * * @param since Will be used as the first parameter for [KronScheduler.next] fun */ -@FlowPreview fun KronScheduler.asFlowWithoutDelays(since: DateTime = DateTime.now()): Flow = flow { var previous = since while (currentCoroutineContext().isActive) { @@ -77,7 +73,6 @@ fun KronScheduler.asFlowWithoutDelays(since: DateTime = DateTime.now()): Flow = asFlowWithoutDelays().onEach { futureHappenTime -> val now = DateTime.now() @@ -93,5 +88,4 @@ fun KronScheduler.asFlowWithDelays(): Flow = asFlowWithoutDelays().onE "Behaviour will be changed. In some of near versions this flow will not delay executions", ReplaceWith("this.asFlowWithDelays()", "dev.inmo.krontab.utils.asFlowWithDelays") ) -@FlowPreview fun KronScheduler.asFlow(): Flow = asFlowWithDelays()