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
## 0.10.0
## 0.9.0
* Versions

View File

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

View File

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

View File

@ -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<DateTimeTz> = channelFlow {
doInfinityTz {
send(it)
fun KronScheduler.asTzFlowWithoutDelays(since: DateTimeTz = DateTime.nowLocal()): Flow<DateTimeTz> = 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<DateTime> = channelFlow {
doInfinity {
send(it)
fun KronScheduler.asTzFlowWithDelays(): Flow<DateTimeTz> = 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<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.days
import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.test.runTest
import kotlin.test.*
class CheckMonthsAndDaysCorrectWork {

View File

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

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

View File

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

View File

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

View File

@ -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.*

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)