Skip to content

Conversation

mirageoasis
Copy link
Contributor

@mirageoasis mirageoasis commented May 14, 2024

will resolve #3988

this is draft for eventually

I made some senario for this new eventually

  1. duration is set to positve -> works well
  2. duration is set to minus-> error (this pr)
  3. retries is set to minus -> error (this pr)
  4. user do not set duration -> which make infinite loop (this pr)

and there are some other feature I introduced in this pr

  1. if duration millisecond overflowed it is set to Duration.INFINITE and prints out waring

by the way can we change retries to Long? I think it doesn't matter to change Int to Long

sorry found out reason why retries cannot be Long in ExponentialIntervalFn.kt

@mirageoasis mirageoasis marked this pull request as ready for review May 14, 2024 16:04
@mirageoasis
Copy link
Contributor Author

currently working on test code
Any suggestions are welcome!

@@ -205,7 +211,7 @@ object NoopEventuallyListener : EventuallyListener {
private class EventuallyControl(val config: EventuallyConfiguration) {

val start = timeInMillis()
val end = start + config.duration.inWholeMilliseconds
val end = addSafely()
Copy link
Contributor

@OliverO2 OliverO2 May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried to run this? The local start variable is not visible in addSafely(), right?
[Edit: It is visible. My bad. Just needed to look at the whole file instead of the GitHub diff.]

What about expressing more clearly what happens, so that readers don't have to look up the implementation of addSafely()? For example, mirroring the coerce... functions in the stdilb, we could use:

val end = (start + config.duration.inWholeMilliseconds).orInfinityIfNegative()

with:

fun Long.orInfinityIfNegative() = if (this < 0) Duration.INFINITE.inWholeMilliseconds else this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks to your advice I refactored code! thanks a lot!

fun hasAttemptsRemaining() = timeInMillis() < end && iterations < config.retries
fun hasAttemptsRemaining() : Boolean {
// If the duration is infinite, it is considered as default value, which means it will never stop
val isInfinite = config.duration == INFINITE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this case already covered by isWithinDuration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well

Isn't this case already covered by isWithinDuration?

isWithinDuration only checks time depletion
isInfinte checks if config.duration if is set to default which is INFINITE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh maybe since Duration.INFINITE is almost same as INFINITE I think I can rollback this expression

@@ -121,7 +121,7 @@ data class EventuallyConfiguration(
}

object EventuallyConfigurationDefaults {
var duration: Duration = Duration.INFINITE
var duration: Duration = INFINITE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned over this approach in general. This thing is shared (because object) and mutable (because var). As such, there will be race conditions. We should refactor away the very need for shared mutable things. WDYT @OliverO2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is default setting I think it is ok to change var to val

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexCue987 I agree.

All properties should really be val (though I wonder how this would affect backward compatibility in this case). Global state is almost never a good idea and in particular with the option of running parallel tests and concurrent coroutines, any context-induced configuration should be coroutine-local. See #2791 for an example.

I also wonder why that object is part of the public API. It might better be private.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its appropriate to make another pr and deal with this issue
on this pr I will only focus on eventually's function
I will raise another pr for this issue!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. To make this PR ready for review, we'd need some test code covering the proposed fixes. Would also be good to update the initial PR comment with references to issues resolved by this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its appropriate to make another pr and deal with this issue on this pr I will only focus on eventually's function I will raise another pr for this issue!

sure - this is a preexisting condition, not something introduced in this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. To make this PR ready for review, we'd need some test code covering the proposed fixes. Would also be good to update the initial PR comment with references to issues resolved by this PR.

done with test!

@mirageoasis mirageoasis changed the title draft for eventually eventually refactored May 19, 2024
Copy link
Contributor

@kshired kshired left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mirageoasis

  • I commented some suggestions.
  • Why not modify the comment of EventuallyConfigurationBuilder.duration that the constraint has been added?

return when {
this in 0..INFINITE.inWholeMilliseconds -> this
else -> INFINITE.inWholeMilliseconds.also {
println("[WARN] end value $this is out of the valid range (0 to ${INFINITE.inWholeMilliseconds}), value is fixed to Duration.INFINITE.inWholeMilliseconds")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
println("[WARN] end value $this is out of the valid range (0 to ${INFINITE.inWholeMilliseconds}), value is fixed to Duration.INFINITE.inWholeMilliseconds")
println("[WARN] end value $this is out of the valid range (0 to $it), value is fixed to Duration.INFINITE.inWholeMilliseconds")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not just error out and throw if the duration is invalid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think throwing error is more clear then changing value since people will not set duration close to inifnite

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And no one is going to spot the warn in the logs unless they look closely.
Failing the test makes more sense if its a bug, which having an incorrect duration is.

@mirageoasis
Copy link
Contributor Author

Can someone help me with test case to trigger Duration overflow?

}

test("when duration is set to default it cannot end test until iteration is done") {
val finalCount = 10000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also work with a count of 10, and faster, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

@@ -205,7 +211,7 @@ object NoopEventuallyListener : EventuallyListener {
private class EventuallyControl(val config: EventuallyConfiguration) {

val start = timeInMillis()
val end = start + config.duration.inWholeMilliseconds
val end = (start.milliseconds + config.duration).inWholeMilliseconds
Copy link
Contributor Author

@mirageoasis mirageoasis May 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here's how it works

Duration's single value cannot be Bigger than 2 ^ 63 - 1

if we add two Duration and if it's value's result is bigger than 2 ^ 63 -1 then we set Duration to Duration.INFINITE

so

this

val end = (start.milliseconds + config.duration).inWholeMilliseconds
is 100% safe!

I've come a long way round :(

@mirageoasis mirageoasis changed the title eventually refactored eventually fixed May 23, 2024
@Kantis Kantis added the assertions 🔍 Related to the assertion mechanisms within the testing framework. label Jun 5, 2024
@mirageoasis
Copy link
Contributor Author

mirageoasis commented Jul 2, 2024

#4114
since this pr collides with this pr I will check what has changed!

@mirageoasis
Copy link
Contributor Author

think conflicting pr now resolves overflow issue I think job of this pr is to limit duration and retries from being negative

@mirageoasis
Copy link
Contributor Author

@aSemy
thank to your PR duration overflow issue has been solved
but I think retries and duration should not be negative since it can cause some error
can you review if my fixed pr does not collide with pr of your's?

@aSemy
Copy link
Contributor

aSemy commented Jul 9, 2024

@aSemy thank to your PR duration overflow issue has been solved but I think retries and duration should not be negative since it can cause some error can you review if my fixed pr does not collide with pr of your's?

It looks like there are no conflicts 👍

However, there's lots of formatting changes that could cause conflicts in other PRs, and it makes the changes in the PR more noisy and harder to review. Would you mind reverting the formatting changes?

E.g. Revert

val result =
   testEventually(5.days) {

back to

val result = testEventually(5.days) {
image

@@ -133,7 +138,7 @@ object EventuallyConfigurationDefaults {
class EventuallyConfigurationBuilder {

/**
* The total time that the [eventually] function can take to complete successfully.
* The total time that the [eventually] function can take to complete successfully. Must be greater than or equal to 0.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just "Must be non-negative"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix this on evening!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh I find "Must be greater than or equal to zero" easier to read, but "Must be non-negative" is fine too.

@mirageoasis

This comment was marked as outdated.

@mirageoasis
Copy link
Contributor Author

think it is now okay to merge can someone approve my change?

@mirageoasis mirageoasis requested review from AlexCue987 and aSemy July 10, 2024 11:50
Copy link
Contributor

@aSemy aSemy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

I have some minor nitpicks. Please double check them, and if they make sense to you, apply them before merging.

@@ -202,7 +207,7 @@ class EventuallyConfigurationBuilder {
typealias EventuallyListener = suspend (Int, Throwable) -> Unit

object NoopEventuallyListener : EventuallyListener {
override suspend fun invoke(iteration: Int, error: Throwable) {}
override suspend fun invoke(iteration: Int, error: Throwable, ) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
override suspend fun invoke(iteration: Int, error: Throwable, ) {}
override suspend fun invoke(iteration: Int, error: Throwable) {}

formatting nitpick

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally like trailling comma but since it ruin's commit's visibility I will fix this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like trailing commas too, but only if the parameters are on separate lines :)

@@ -156,7 +161,7 @@ class EventuallyConfigurationBuilder {
var intervalFn: DurationFn? = EventuallyConfigurationDefaults.intervalFn

/**
* The maximum number of invocations regardless of durations. By default, this is set to max retries.
* The maximum number of invocations regardless of durations. By default, this is set to max retries. And Must be greater than or equal to 0.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* The maximum number of invocations regardless of durations. By default, this is set to max retries. And Must be greater than or equal to 0.
* The maximum number of invocations regardless of durations. By default, this is set to max retries. Must be non-negative.

nitpick: match the KDoc of duration.

@mirageoasis
Copy link
Contributor Author

should I fix docs or leave docs cause my change and limitation is too obvious?

@mirageoasis
Copy link
Contributor Author

@Kantis Can you tell me why my test fails? is it my fault? or problem with configuration?

when I see logs it says some kind of kotlin dependency

Copy link
Contributor

@OliverO2 OliverO2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, ready to merge. Thank you for this contribution!

@OliverO2 OliverO2 added this pull request to the merge queue Jul 20, 2024
Merged via the queue into kotest:master with commit 0988a45 Jul 20, 2024
7 checks passed
@mirageoasis mirageoasis deleted the new_eventually branch July 21, 2024 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assertions 🔍 Related to the assertion mechanisms within the testing framework.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

eventually without customization never runs test method
7 participants