-
-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(core): execution strategy #758
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #758 +/- ##
=============================================
+ Coverage 59.06% 59.55% +0.49%
+ Complexity 832 820 -12
=============================================
Files 210 208 -2
Lines 4241 4285 +44
Branches 669 698 +29
=============================================
+ Hits 2505 2552 +47
+ Misses 1407 1406 -1
+ Partials 329 327 -2
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
private val poolId: DevicePoolId, | ||
attachmentProviders: List<AttachmentProvider>, | ||
private val attachmentCollector: AttachmentCollector = AttachmentCollector(attachmentProviders), | ||
) : AccumulatingResultTestRunListener(timer), AttachmentListener by attachmentCollector { | ||
|
||
private val logger = MarathonLogging.logger("TestRunResultsListener") | ||
private val progressReporter = ProgressReporter(testBatch, poolId, device.toDeviceInfo()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need this? I see that you dropped line 102 and looks like it is unused now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great question. So currently printing any of these doesn't mean marathon's core will actually see these events. Only after the batch results are calculated. I think these would confuse users so I'll remove them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iOS implementation should probably also not print these. If users want this level of information it's accessible using debug mode
} | ||
onTransition { | ||
if (it as? StateMachine.Transition.Valid !is StateMachine.Transition.Valid) { | ||
logger.error { "from ${it.fromState} event ${it.event}" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to add more context here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point such transition is impossible: all states respond to every event
private val poolingStrategy = configuration.poolingStrategy.toPoolingStrategy() | ||
|
||
private val logger = MarathonLogging.logger("Scheduler") | ||
|
||
suspend fun execute() { | ||
suspend fun execute() : Boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear what this bool
means
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added return quickdoc
fun testEnded(device: DeviceInfo, testResult: TestResult, final: Boolean = false): TestAction? { | ||
return when (testResult.status) { | ||
TestStatus.FAILURE -> { | ||
println("${toPercent(progress())} | [${poolId.name}]-[${device.serialNumber}] ${testResult.test.toTestName()} failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace with logger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is intended: it's the default way marathon prints progress when users have debug==false. We don't have an abstraction for this
* Should always be called before testEnded, otherwise the FSM might transition into a terminal state prematurely | ||
*/ | ||
fun retryTest(test: Test): TestAction? { | ||
return tests[test.toTestName()]?.transition(TestEvent.AddRetry).sideffect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do warn log when tests returns null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, reworked
fun addRetries(poolId: DevicePoolId, count: Int) { | ||
execute(poolId) { it.addTestRetries(count) } | ||
fun testFailed(test: Test) { | ||
println("${batch.id} | [${poolId.name}]-[${device.serialNumber}] ${test.toTestName()} failed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above. this is the old mechanism to show progress in stdout. I've removed it since most of the info should come from poolprogressaccumulator now
sealed class TestAction { | ||
data class SaveReport(val deviceInfo: DeviceInfo, val testResult: TestResult) : TestAction() | ||
object Conclude : TestAction() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use most common name for that action. End
or Complete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or Finish
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of the hardest problems in CS :) Let's use 'Complete'
val device: DeviceInfo, | ||
val testResult: TestResult, | ||
val count: Int | ||
data class Failing( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now it's not fully cleared difference between Failed and Failing.
Maybe we could use additional differentiation: Terminal
States and NotTerminal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My impression is that using adjectives ending with ing
is quite common for this purpose in English i.e. https://buildkite.com/docs/pipelines/defining-steps#build-states
executionStrategy: | ||
mode: ANY_SUCCESS | ||
fast: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you be updating the documentation in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, already updated
* @property fast fail-fast or success-fast depending on the value of [mode]. This doesn't affect the result | ||
* of the run, it only saves compute time | ||
*/ | ||
data class ExecutionStrategyConfiguration( | ||
@JsonProperty("mode") val mode: ExecutionMode = ExecutionMode.ANY_SUCCESS, | ||
@JsonProperty("fast") val fast: Boolean = true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given it doesn't affect the result of the run, why is fast
configurable by the user?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is explained in the docs, short version is you might want to run other retries anyways in some scenarios because test status is not the only output of a test, test probability of passing is one of the examples
* @property ANY_SUCCESS test passes if any of executions is passing | ||
* this mode works only if there is no complex sharding strategy applied | ||
* | ||
* Why: it doesn't make sense when user asks for N number of tests | ||
* to run explicitly, and we pass on the first one. Sharding used to verify probability of passing with an explicit boundary for precision | ||
* @property ALL_SUCCESS test passes if and only if all the executions are passing | ||
* this mode works only if there are no retries, i.e. no complex flakiness strategy, no retry strategy | ||
* | ||
* Why: when adding retries to tests with retry+flakiness strategies users want to trade-off cost for reliability, i.e. add more retries | ||
* and pass if one of them passes, so retries only make sense for the [ANY_SUCCESS] mode. When we use [ALL_SUCCESS] mode it means user | ||
* wants to verify each test with a number of tries (they are not retries per se) and pass only if all of them succeed. This is the case | ||
* when fixing a flaky test or adding a new test, and we want to have a signal that the test is fixed/not flaky. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a file where the full configuration defined by the user is parsed and potentially conflicting configurations such as the ones described here are identified? If so, perhaps this explanation belongs there. Furthermore, would such conflicts be presented to the user at runtime, either as a warning or error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Configuration consists of multiple files, there is no single place to define all of it. Defaults should work for most people, if something is not right marathon will produce logical validation error during runtime. Doing so in yaml is not possible unless we have custom linting for yaml.
I've updated LogicalConfigurationValidator
to fail in runtime under the edge-cases outlined here
val name: String, | ||
val outputDir: File, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't really matter but should these spaces have been added?
Also, I think there should be a new line above this class between lines 144 & 145.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not everything is reformatted according to the embedded code style. It works as an incremental code style formatter - you change something in a file and once you commit it will have approriate format
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a newline
val state = it.state | ||
when (state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Today I learned you could do this when the IDE recommended it 🙂
val state = it.state | |
when (state) { | |
when (val state = it.state) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also ignore IDE and language features for the sake of simplicity and readability ;)
I prefer to define variables separately from the branching logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
docs/docs/intro/configure.md
Outdated
executed and all non-started retries are removed. | ||
|
||
#### Any success | ||
Test passes if any of executions is passing. This mode works only if there is no complex sharding strategy applied. This is the default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test passes if any of executions is passing. This mode works only if there is no complex sharding strategy applied. This is the default. | |
Test passes if any of its executions are passing. This mode works only if there is no complex sharding strategy applied. This is the default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
docs/docs/intro/configure.md
Outdated
::: | ||
|
||
#### All success | ||
Test passes if and only if all the executions are passing. This mode works only if there are no retries, i.e. no complex flakiness strategy, no retry strategy. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test passes if and only if all the executions are passing. This mode works only if there are no retries, i.e. no complex flakiness strategy, no retry strategy. | |
Test passes if and only if all its executions are passing. This mode works only if there are no retries, i.e. no complex flakiness strategy, no retry strategy. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
docs/docs/intro/configure.md
Outdated
|
||
:::info | ||
|
||
Adding retries with retry/flakiness strategies means users wants to trade-off cost for reliability, i.e. add more retries and pass if one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding retries with retry/flakiness strategies means users wants to trade-off cost for reliability, i.e. add more retries and pass if one | |
Adding retries with retry/flakiness strategies means users want to trade off cost for reliability, i.e. add more retries and pass if one |
Also, Is execution time the only "cost" being referred to here and elsewhere on this page? If so, perhaps we can be more specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cost is how much one pays with $ for the test runs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed the wording as suggested
docs/docs/intro/configure.md
Outdated
Adding retries with retry/flakiness strategies means users wants to trade-off cost for reliability, i.e. add more retries and pass if one | ||
of test retries passes, so retries only make sense for any success mode. | ||
|
||
When we use all success mode it means user wants to verify each test with a number of tries (they are not retries per se) and pass only if |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we use all success mode it means user wants to verify each test with a number of tries (they are not retries per se) and pass only if | |
When we use all success mode it means the user wants to verify each test with a number of tries (they are not retries per se) and pass only if |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
docs/docs/intro/configure.md
Outdated
::: | ||
|
||
#### Fast execution mode | ||
When test reaches a state when a decision about it's state can be made marathon can remove additional in-progress retries. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When test reaches a state when a decision about it's state can be made marathon can remove additional in-progress retries. | |
When the test reaches a state where a decision about its state can be made, marathon can remove additional in-progress retries. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
docs/docs/intro/configure.md
Outdated
This decision point is different depending on the execution mode used. Let's walk through two examples. | ||
|
||
Assume any success strategy is used and 100 retries are scheduled for a test A via flakiness strategy. Let's say first 3 failed and the 4th attempt succeeded. At | ||
this point the test should already be considered passed since any success out of all retries leads to the result by definition of any successs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except for the first use of "any success" on line 468, is it worth changing all instances of "any success" & "all success" in this doc to ANY_SUCCESS
& ALL_SUCCESS
for easier reading?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replaced
Removes
strictMode
in favor ofexecutionMode
consisting of:mode
:ANY_SUCCESS test passes if any of executions is passing this mode works only if there is no complex sharding strategy applied
Why: it doesn't make sense when user asks for N number of tests to run explicitly, and we pass on the first one. Sharding used to verify probability of passing with an explicit boundary for precision
ALL_SUCCESS test passes if and only if all the executions are passing this mode works only if there are no retries, i.e. no complex flakiness strategy, no retry strategy
Why: when adding retries to tests with retry+flakiness strategies users want to trade-off cost for reliability, i.e. add more retries
and pass if one of them passes, so retries only make sense for the [ANY_SUCCESS] mode. When we use [ALL_SUCCESS] mode it means user wants to verify each test with a number of tries (they are not retries per se) and pass only if all of them succeed. This is the case when fixing a flaky test or adding a new test, and we want to have a signal that the test is fixed/not flaky.
fast
: fail-fast or success-fast depending on the value of [mode]. This doesn't affect the result of the run, it only saves compute time