Skip to content

Commit

Permalink
Implement FrayExtension for Junit Jupiter (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
aoli-al authored Dec 2, 2024
1 parent 1d17478 commit 6cca8d2
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 294 deletions.
7 changes: 5 additions & 2 deletions junit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ repositories {

dependencies {
implementation(project(":core"))
compileOnly(project(":runtime"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("org.junit.platform:junit-platform-engine:1.10.3")
implementation("org.junit.platform:junit-platform-commons:1.10.3")
implementation("org.junit.platform:junit-platform-engine:1.11.3")
implementation("org.junit.platform:junit-platform-commons:1.11.3")
implementation("org.junit.jupiter:junit-jupiter-api:5.11.3")
testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
useJUnitPlatform {
includeEngines("junit-jupiter", "fray")
}
ignoreFailures = true
dependsOn(":instrumentation:jdk:build")
val instrumentationTask = evaluationDependsOn(":instrumentation:agent")
.tasks.named("shadowJar").get()
Expand Down
49 changes: 0 additions & 49 deletions junit/src/main/kotlin/ClassTestDescriptor.kt

This file was deleted.

7 changes: 0 additions & 7 deletions junit/src/main/kotlin/FrayEngineDescriptor.kt

This file was deleted.

61 changes: 61 additions & 0 deletions junit/src/main/kotlin/FrayExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.pastalab.fray.junit

import java.lang.reflect.Constructor
import org.junit.jupiter.api.extension.*
import org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled
import org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled
import org.pastalab.fray.core.RunContext
import org.pastalab.fray.core.RuntimeDelegate
import org.pastalab.fray.core.randomness.ControlledRandom
import org.pastalab.fray.runtime.Delegate
import org.pastalab.fray.runtime.Runtime

class FrayExtension(
val index: Int,
val frayContext: RunContext,
val frayJupiterContext: FrayJupiterContext
) : BeforeEachCallback, AfterEachCallback, TestWatcher, ExecutionCondition, InvocationInterceptor {

override fun <T : Any?> interceptTestClassConstructor(
invocation: InvocationInterceptor.Invocation<T>,
invocationContext: ReflectiveInvocationContext<Constructor<T>>,
extensionContext: ExtensionContext
): T {
frayContext.config.currentIteration = index
if (frayContext.config.currentIteration != 0) {
frayContext.config.scheduler = frayContext.config.scheduler.nextIteration()
frayContext.config.randomnessProvider = ControlledRandom()
}
Runtime.DELEGATE = RuntimeDelegate(frayContext)
Runtime.start()
val result = invocation.proceed()
Runtime.onSkipMethod("JUnit internal")
return result
}

override fun beforeEach(context: ExtensionContext?) {
Runtime.onSkipMethodDone("JUnit internal")
}

override fun afterEach(context: ExtensionContext) {
try {
Runtime.onMainExit()
} finally {
Runtime.DELEGATE = Delegate()
}
if (frayContext.bugFound != null) {
throw frayContext.bugFound!!
}
}

override fun testFailed(context: ExtensionContext, cause: Throwable) {
frayJupiterContext.bugFound = true
}

override fun evaluateExecutionCondition(context: ExtensionContext?): ConditionEvaluationResult {
if (frayJupiterContext.bugFound) {
return disabled("Bug found in previous iteration.")
}
return enabled("No bug found in previous iteration.")
}
}
5 changes: 5 additions & 0 deletions junit/src/main/kotlin/FrayJupiterContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.pastalab.fray.junit

class FrayJupiterContext {
var bugFound = false
}
52 changes: 0 additions & 52 deletions junit/src/main/kotlin/FrayTestEngine.kt

This file was deleted.

111 changes: 0 additions & 111 deletions junit/src/main/kotlin/FrayTestExecutor.kt

This file was deleted.

78 changes: 78 additions & 0 deletions junit/src/main/kotlin/FrayTestExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.pastalab.fray.junit

import java.lang.reflect.Method
import java.nio.file.Paths
import java.util.stream.Stream
import kotlin.io.path.absolutePathString
import kotlin.io.path.createTempDirectory
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.TestTemplateInvocationContext
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider
import org.junit.platform.commons.support.AnnotationSupport
import org.junit.platform.commons.support.AnnotationSupport.isAnnotated
import org.junit.platform.commons.util.Preconditions
import org.pastalab.fray.core.RunContext
import org.pastalab.fray.core.command.Configuration
import org.pastalab.fray.core.command.ExecutionInfo
import org.pastalab.fray.core.command.LambdaExecutor
import org.pastalab.fray.core.randomness.ControlledRandom
import org.pastalab.fray.core.scheduler.POSScheduler
import org.pastalab.fray.junit.annotations.ConcurrencyTest

class FrayTestExtension : TestTemplateInvocationContextProvider {
val workDir = Paths.get(System.getProperty("fray.workDir", "fray/fray-report"))

override fun supportsTestTemplate(context: ExtensionContext): Boolean {
return isAnnotated(context.testMethod, ConcurrencyTest::class.java)
}

override fun provideTestTemplateInvocationContexts(
context: ExtensionContext
): Stream<TestTemplateInvocationContext> {
val testMethod = context.requiredTestMethod
val displayName = context.displayName
val concurrencyTest: ConcurrencyTest =
AnnotationSupport.findAnnotation(
testMethod,
ConcurrencyTest::class.java,
)
.get()
val workDir = createTempDirectory(this.workDir).absolutePathString()
val schedulerInfo = concurrencyTest.scheduler
val config =
Configuration(
ExecutionInfo(
LambdaExecutor {},
false,
true,
false,
-1,
),
workDir,
totalRepetition(concurrencyTest, testMethod),
60,
POSScheduler(),
ControlledRandom(),
false,
false,
true,
false,
false,
true,
)
val frayContext = RunContext(config)
val frayJupiterContext = FrayJupiterContext()
return Stream.iterate(1, { it + 1 })
.sequential()
.limit(totalRepetition(concurrencyTest, testMethod).toLong())
.map { FrayTestInvocationContext(it, displayName, frayContext, frayJupiterContext) }
}

private fun totalRepetition(concurrencyTest: ConcurrencyTest, method: Method): Int {
val repetition = concurrencyTest.value
Preconditions.condition(repetition > 0) {
"Configuration error: @ConcurrencyTest on method [$method] must be declared with a positive 'value'."
}
return repetition
}
}
Loading

0 comments on commit 6cca8d2

Please sign in to comment.