Skip to content

Commit

Permalink
🐛 (#47): Ensure tests are reproducible in JUnit5
Browse files Browse the repository at this point in the history
  • Loading branch information
xgouchet committed Nov 27, 2019
1 parent 3e2a7b1 commit b5e2d96
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ import org.junit.platform.commons.annotation.Testable
@MustBeDocumented
@Inherited
annotation class ForgeConfiguration(
val value: KClass<out ForgeConfigurator>
val value: KClass<out ForgeConfigurator>,
val seed: Long = 0L
)
34 changes: 25 additions & 9 deletions junit5/src/main/kotlin/fr/xgouchet/elmyr/junit5/ForgeExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import fr.xgouchet.elmyr.junit5.params.LongForgeryParamResolver
import java.lang.reflect.Constructor
import java.util.Locale
import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
Expand All @@ -32,6 +33,7 @@ import org.junit.platform.commons.support.AnnotationSupport
*/
class ForgeExtension :
BeforeAllCallback,
BeforeEachCallback,
BeforeTestExecutionCallback,
TestExecutionExceptionHandler,
ParameterResolver {
Expand Down Expand Up @@ -62,17 +64,25 @@ class ForgeExtension :

// endregion

// region BeforeTestExecutionCallback
// region BeforeEachCallback

/** @inheritdoc */
override fun beforeTestExecution(context: ExtensionContext) {
override fun beforeEach(context: ExtensionContext) {
resetSeed(context)
val target = context.requiredTestInstance
injector.inject(instanceForge, target)
}

// endregion

// region BeforeTestExecutionCallback

/** @inheritdoc */
override fun beforeTestExecution(context: ExtensionContext) {
}

// endregion

// region TestExecutionExceptionHandler

/** @inheritdoc */
Expand Down Expand Up @@ -131,13 +141,15 @@ class ForgeExtension :

// region Internal

private fun resetSeed(extensionContext: ExtensionContext) {
val seed = (System.nanoTime() and SEED_MASK)
instanceForge.seed = seed
private fun resetSeed(context: ExtensionContext) {
val configurations = getConfigurations(context)
val seed = configurations.map { it.seed }
.firstOrNull { it != 0L }
instanceForge.seed = seed ?: (System.nanoTime() and SEED_MASK)
}

private fun getConfigurators(context: ExtensionContext): List<ForgeConfigurator> {
val result = mutableListOf<ForgeConfigurator>()
private fun getConfigurations(context: ExtensionContext): List<ForgeConfiguration> {
val result = mutableListOf<ForgeConfiguration>()
var currentContext = context

while (currentContext != context.root) {
Expand All @@ -148,8 +160,7 @@ class ForgeExtension :
)

if (annotation.isPresent) {
val configurator = annotation.get().value.java.newInstance()
result.add(configurator)
result.add(annotation.get())
}

currentContext = currentContext.parent.get()
Expand All @@ -158,6 +169,11 @@ class ForgeExtension :
return result
}

private fun getConfigurators(context: ExtensionContext): List<ForgeConfigurator> {
return getConfigurations(context)
.map { it.value.java.newInstance() }
}

// endregion

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package fr.xgouchet.elmyr.junit5;

import fr.xgouchet.elmyr.Forge;
import fr.xgouchet.elmyr.annotation.FloatForgery;
import fr.xgouchet.elmyr.annotation.Forgery;
import fr.xgouchet.elmyr.annotation.IntForgery;
import fr.xgouchet.elmyr.junit5.dummy.Bar;
import fr.xgouchet.elmyr.junit5.dummy.Foo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.assertj.core.api.Assertions.assertThat;


@ExtendWith(ForgeExtension.class)
@ForgeConfiguration(value = JavaAnnotationTest.Configurator.class, seed = JavaReproducibilityTest.SEED)
public class JavaReproducibilityTest {

public static final long SEED = 0x85F04771A07L;

@Forgery
private Foo fakeFoo = null;

@Forgery
public Bar fakeBar = null;

@BeforeEach
public void setUp(Forge forge) {
checkSeedNotChanged(forge);
checkForgeryInjected();
}

@Test
public void testRun1() {
}

@Test
public void testRun2() {
}

@Test
public void testRun3(@Forgery Foo foo, @Forgery Bar bar) {
assertThat(foo.getI()).isEqualTo(-1314996441);
assertThat(bar.getS()).isEqualTo("xiqcgqfmbjoaevbo");
}

@Test
public void testRun4(@FloatForgery float f, @IntForgery int i) {
assertThat(f).isEqualTo(3.1084288E38f);
assertThat(i).isEqualTo(177874237);
}

private void checkSeedNotChanged(Forge forge) {
assertThat(forge.getSeed()).isEqualTo(SEED);
}

private void checkForgeryInjected() {
assertThat(fakeFoo.getI()).isEqualTo(-119626925);
assertThat(fakeBar.getS()).isEqualTo("ttcji");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package fr.xgouchet.elmyr.junit5

import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.ForgeConfigurator
import fr.xgouchet.elmyr.annotation.FloatForgery
import fr.xgouchet.elmyr.annotation.Forgery
import fr.xgouchet.elmyr.annotation.IntForgery
import fr.xgouchet.elmyr.junit5.dummy.Bar
import fr.xgouchet.elmyr.junit5.dummy.BarFactory
import fr.xgouchet.elmyr.junit5.dummy.Foo
import fr.xgouchet.elmyr.junit5.dummy.FooFactory
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(ForgeExtension::class)
@ForgeConfiguration(value = KotlinReproducibilityTest.Configurator::class, seed = KotlinReproducibilityTest.SEED)
class KotlinReproducibilityTest {

@Forgery
private lateinit var fakeFoo: Foo
@Forgery
private lateinit var fakeBar: Bar

@BeforeEach
fun setUp(forge: Forge) {
checkSeedNotChanged(forge)
checkForgeryInjected()
}

@Test
fun testRun1() {
}

@Test
fun testRun2() {
}

@Test
fun testRun3(@Forgery foo: Foo, @Forgery bar: Bar) {
assertThat(foo.i).isEqualTo(1834174735)
assertThat(bar.s).isEqualTo("wfpwhlvm")
}

@Test
fun testRun4(@FloatForgery f: Float, @IntForgery i: Int) {
assertThat(f).isEqualTo(2.5332062E38f)
assertThat(i).isEqualTo(-1217237951)
}

private fun checkSeedNotChanged(forge: Forge) {
assertThat(forge.seed).isEqualTo(SEED)
}

private fun checkForgeryInjected() {
assertThat(fakeFoo.i).isEqualTo(1596512190)
assertThat(fakeBar.s).isEqualTo("grquxsqwqccdwk")
}

class Configurator : ForgeConfigurator {
override fun configure(forge: Forge) {
forge.addFactory(Foo::class.java, FooFactory())
forge.addFactory(Bar::class.java, BarFactory())
}
}
companion object {
const val SEED = 0xD774670189EL
}
}

0 comments on commit b5e2d96

Please sign in to comment.