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)