importorg.junit.rules.TestRuleimportorg.junit.runner.Descriptionimportorg.junit.runners.model.Statement/** * @property reason The reason explaining what is flaky in the test, and any corresponding JIRA ticket. * @property retry The number of retry allowed to make the test pass successfully. */annotationclassFlaky(valreason:String,valretry:Int=DEFAULT_RETRY_COUNT,){companionobject{internalconstvalDEFAULT_RETRY_COUNT=3}}/** * Repeat a test multiple times and ignore errors until the retry count is reached. * * ``` * @get:Rule * val rule = FlakyTestRule() * * @Test @Flaky(retry = 10) * fun test() { ... } * ``` */classFlakyTestRule(privatevalapplyToAll:Boolean=false):TestRule{privatefunDescription.iterations()=annotations.filterIsInstance<Flaky>().takeUnless{it.isEmpty()}?.sumOf{it.retry}?.also{require(it>0){"Flaky retry count must be > 0"}}?.inc()?:DEFAULT_RETRY_COUNT.takeIf{applyToAll}overridefunapply(statement:Statement,description:Description,):Statement=when(valiterations=description.iterations()){null->statementelse->object:Statement(){overridefunevaluate(){varcause:Throwable? =nullrepeat(iterations){iteration->runCatching{statement.evaluate()}.fold(onSuccess={returnprintln("FlakyTestRule: [${description.displayName}] ${"succeededafter${iteration.inc()}iterations."}")},onFailure={cause=it.also{it.printStackTrace()}},)}throwFlakyTestRuleException(iterations,description,cause)}}}privateclassFlakyTestRuleException(iterations:Int,description:Description,cause:Throwable?):IllegalStateException("Giving up on test [${description.displayName}] after $iterations iterations.",cause)}
importorg.junit.rules.TestRuleimportorg.junit.runner.Descriptionimportorg.junit.runners.model.Statement/** * @property iterations The number to repeated iterations required to let a [org.junit.Test] pass successfully. */annotationclassRepeat(valiterations:Int)/** * Repeat a test multiple times and throw any occurring error. * Inspiration taken from [ShampooRule](https://gist.github.com/JakeWharton/7fe7deb1f7f4a795c120) by Jake Wharton. * * ``` * @get:Rule * val rule = RepeatTestRule() * * @Test @Repeat(iterations = 10) * fun test() { ... } * ``` */classRepeatTestRule:TestRule{privatefunDescription.iterations()=annotations.filterIsInstance<Repeat>().takeUnless{it.isEmpty()}?.sumOf{it.iterations}?.also{require(it>0){"Repeat count must be > 0"}}?.inc()overridefunapply(statement:Statement,description:Description,):Statement=when(valiterations=description.iterations()){null->statementelse->object:Statement(){overridefunevaluate()=repeat(iterations){iteration->kotlin.runCatching{statement.evaluate()}.onFailure{throwRepeatTestRuleException(iteration.inc(),description,cause=it)}}}}privateclassRepeatTestRuleException(iterations:Int,description:Description,cause:Throwable?):IllegalStateException("[${description.displayName}] failed after $iterations iterations.",cause)}