start 0.10.0 + rework of flows

This commit is contained in:
InsanusMokrassar 2023-03-18 12:23:53 +06:00
parent 7da67386cf
commit 5a1ed2f933
13 changed files with 105 additions and 54 deletions

View File

@ -1,5 +1,7 @@
# Changelog # Changelog
## 0.10.0
## 0.9.0 ## 0.9.0
* Versions * Versions

View File

@ -10,7 +10,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_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 { android {
publishAllLibraryVariants() publishAllLibraryVariants()
} }
linuxX64()
sourceSets { sourceSets {
@ -79,6 +80,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
} }
} }
jvmTest { jvmTest {

View File

@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx512m
kotlin.code.style=official kotlin.code.style=official
org.gradle.parallel=true org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
@ -33,5 +34,5 @@ androidx_work_version=2.8.0
## Common ## Common
version=0.9.0 version=0.10.0
android_code_version=23 android_code_version=24

View File

@ -2,35 +2,96 @@ package dev.inmo.krontab.utils
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz import com.soywiz.klock.DateTimeTz
import com.soywiz.klock.milliseconds
import dev.inmo.krontab.* import dev.inmo.krontab.*
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
/** /**
* This [Flow] will trigger emitting each near time which will be returned from [this] [KronScheduler] with attention to * **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
* time zones
* *
* @see channelFlow * Will emit all the [KronScheduler.next] as soon as possible. In case [KronScheduler.next] return null, flow will
* @see KronSchedulerTz.doInfinityTz * be completed
*
* @param since Will be used as the first parameter for [KronScheduler.next] fun
*/ */
@FlowPreview @FlowPreview
fun KronScheduler.asTzFlow(): Flow<DateTimeTz> = channelFlow { fun KronScheduler.asTzFlowWithoutDelays(since: DateTimeTz = DateTime.nowLocal()): Flow<DateTimeTz> = flow {
doInfinityTz { var previous = since
send(it) 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 * This [Flow] will use [asTzFlowWithoutDelays], but stop on each time until this time will happen
* @see KronScheduler.doInfinity
*/ */
@FlowPreview @FlowPreview
fun KronScheduler.asFlow(): Flow<DateTime> = channelFlow { fun KronScheduler.asTzFlowWithDelays(): Flow<DateTimeTz> = asTzFlowWithoutDelays().onEach { futureHappenTime ->
doInfinity { val now = DateTime.nowLocal()
send(it)
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<DateTimeTz> = 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<DateTime> = 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<DateTime> = 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<DateTime> = asFlowWithDelays()

View File

@ -3,6 +3,7 @@ package dev.inmo.krontab.utils
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import com.soywiz.klock.days import com.soywiz.klock.days
import dev.inmo.krontab.buildSchedule import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.test.runTest
import kotlin.test.* import kotlin.test.*
class CheckMonthsAndDaysCorrectWork { class CheckMonthsAndDaysCorrectWork {

View File

@ -1,6 +1,8 @@
package dev.inmo.krontab.utils 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 { fun CoroutineScope.createFailJob(forTimeMillis: Long) = launch {
delay(forTimeMillis) delay(forTimeMillis)

View File

@ -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)

View File

@ -2,8 +2,8 @@ package dev.inmo.krontab.utils
import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.builder.buildSchedule
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -18,7 +18,7 @@ class SchedulerFlowTests {
} }
} }
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val mustBeCollected = 10 val mustBeCollected = 10
@ -40,14 +40,14 @@ class SchedulerFlowTests {
} }
} }
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val testsCount = 10 val testsCount = 10
val failJob = it.createFailJob((testsCount * 2) * 1000L) val failJob = createFailJob((testsCount * 2) * 1000L)
val mustBeCollected = 10 val mustBeCollected = 10
val answers = (0 until testsCount).map { _ -> val answers = (0 until testsCount).map { _ ->
it.async { async {
var collected = 0 var collected = 0
flow.takeWhile { flow.takeWhile {
collected < mustBeCollected collected < mustBeCollected

View File

@ -4,9 +4,8 @@ import com.soywiz.klock.*
import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.KronSchedulerTz
import dev.inmo.krontab.buildSchedule import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlin.math.floor import kotlinx.coroutines.test.runTest
import kotlin.test.* import kotlin.test.*
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@ -16,7 +15,7 @@ class StringParseTest {
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * *") val kronScheduler = buildSchedule("*/1 * * * *")
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val mustBeCollected = 10 val mustBeCollected = 10
@ -33,7 +32,7 @@ class StringParseTest {
fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * * 500ms") val kronScheduler = buildSchedule("*/1 * * * * 500ms")
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val mustBeCollected = 10 val mustBeCollected = 10
@ -51,14 +50,14 @@ class StringParseTest {
fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * *") val kronScheduler = buildSchedule("*/1 * * * *")
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val testsCount = 10 val testsCount = 10
val failJob = it.createFailJob((testsCount * 2) * 1000L) val failJob = createFailJob((testsCount * 2) * 1000L)
val mustBeCollected = 10 val mustBeCollected = 10
val answers = (0 until testsCount).map { _ -> val answers = (0 until testsCount).map { _ ->
it.async { async {
var collected = 0 var collected = 0
flow.takeWhile { flow.takeWhile {
collected < mustBeCollected collected < mustBeCollected
@ -81,7 +80,7 @@ class StringParseTest {
val rangesEnds = listOf(0 to 5, 30 to 35) val rangesEnds = listOf(0 to 5, 30 to 35)
val kronScheduler = buildSchedule("${rangesEnds.joinToString(",") { "${it.first}-${it.second}" }} * * * *") val kronScheduler = buildSchedule("${rangesEnds.joinToString(",") { "${it.first}-${it.second}" }} * * * *")
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlowWithoutDelays()
runTest { runTest {
val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList() val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList()
@ -91,7 +90,10 @@ class StringParseTest {
flow.takeWhile { ranges.isNotEmpty() }.collect { flow.takeWhile { ranges.isNotEmpty() }.collect {
ranges.remove(it.seconds) ranges.remove(it.seconds)
collected++ collected++
assertTrue(collected <= expectedCollects) assertTrue(
collected <= expectedCollects,
"Expected value should be less than $expectedCollects, but was $collected. Ranges state: $ranges"
)
} }
assertEquals(expectedCollects, collected) assertEquals(expectedCollects, collected)
} }

View File

@ -3,6 +3,7 @@ package dev.inmo.krontab.utils
import com.soywiz.klock.* import com.soywiz.klock.*
import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.builder.buildSchedule
import dev.inmo.krontab.next import dev.inmo.krontab.next
import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -2,6 +2,7 @@ package dev.inmo.krontab.utils
import com.soywiz.klock.* import com.soywiz.klock.*
import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.builder.buildSchedule
import kotlinx.coroutines.test.runTest
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.test.* import kotlin.test.*

View File

@ -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) }

View File

@ -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)