Skip to content

Commit

Permalink
Fix test hang and improve Fray gradle plugin. (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
aoli-al authored Jan 24, 2025
1 parent 57586a3 commit 2c16633
Show file tree
Hide file tree
Showing 17 changed files with 119 additions and 40 deletions.
11 changes: 11 additions & 0 deletions core/src/main/kotlin/org/pastalab/fray/core/RuntimeDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -401,15 +401,26 @@ class RuntimeDelegate(val context: RunContext) : org.pastalab.fray.runtime.Deleg
}
}

val onSkipRecursion = ThreadLocal.withInitial { false }

override fun onSkipMethod(signature: String) {
if (onSkipRecursion.get()) {
return
}
onSkipRecursion.set(true)
if (!context.registeredThreads.containsKey(Thread.currentThread().id)) {
onSkipRecursion.set(false)
return
}
stackTrace.get().add(signature)
skipFunctionEntered.set(1 + skipFunctionEntered.get())
onSkipRecursion.set(false)
}

override fun onSkipMethodDone(signature: String): Boolean {
if (onSkipRecursion.get()) {
return false
}
if (!context.registeredThreads.containsKey(Thread.currentThread().id)) {
return false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@ import org.pastalab.fray.core.scheduler.*
data class ExecutionInfo(
@Polymorphic val executor: Executor,
val ignoreUnhandledExceptions: Boolean,
val timedOpAsYield: Boolean,
val interleaveMemoryOps: Boolean,
val maxScheduledStep: Int,
)

sealed class ExecutionConfig(name: String) : OptionGroup(name) {
open fun getExecutionInfo(): ExecutionInfo {
return ExecutionInfo(
MethodExecutor("", "", emptyList(), emptyList(), emptyMap()), false, false, false, 10000000)
MethodExecutor("", "", emptyList(), emptyList(), emptyMap()), false, false, 10000000)
}
}

Expand All @@ -57,7 +56,6 @@ class CliExecutionConfig : ExecutionConfig("cli") {
option("-cp", "--classpath", help = "Arguments passed to target application")
.split(":")
.default(emptyList())
val timedOpAsYield by option("-y", "--timed-op-as-yield").flag()
val ignoreUnhandledExceptions by option("-e", "--ignore-unhandled-exceptions").flag()
val interleaveMemoryOps by option("-m", "--memory").flag()
val maxScheduledStep by option("-s", "--max-scheduled-step").int().default(10000)
Expand All @@ -68,7 +66,6 @@ class CliExecutionConfig : ExecutionConfig("cli") {
return ExecutionInfo(
MethodExecutor(clazz, method, targetArgs, classpaths, propertyMap),
ignoreUnhandledExceptions,
timedOpAsYield,
interleaveMemoryOps,
maxScheduledStep)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public TestRunner buildRunner(Function0<Unit> exec, Scheduler scheduler, int ite
return null;
}),
false,
true,
false,
-1
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,22 +119,25 @@ class ThreadInstrumenter(cv: ClassVisitor) : ClassVisitorBase(cv, Thread::class.
}

if (name == "interrupt") {
return object : GeneratorAdapter(ASM9, mv, access, name, descriptor) {
val eMv =
MethodEnterVisitor(
mv,
org.pastalab.fray.runtime.Runtime::onThreadInterrupt,
access,
name,
descriptor,
true,
false)

return object : GeneratorAdapter(ASM9, eMv, access, name, descriptor) {

override fun visitMethodInsn(
opcode: Int,
owner: String,
name: String,
descriptor: String,
isInterface: Boolean
) {
if (name == "interrupt0") {
loadThis()
invokeStatic(
Type.getObjectType(
org.pastalab.fray.runtime.Runtime::class.java.name.replace(".", "/")),
Utils.kFunctionToASMMethod(org.pastalab.fray.runtime.Runtime::onThreadInterrupt),
)
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
if (name == "interrupt0") {
loadThis()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.pastalab.fray.core.scheduler.RandomScheduler;
import org.pastalab.fray.test.fail.cdl.CountDownLatchDeadlockUnblockMultiThread;
import org.pastalab.fray.test.fail.rwlock.ReentrantReadWriteLockDeadlock;
import org.pastalab.fray.test.success.condition.ConditionAwaitTimeoutNotifyInterrupt;
import org.pastalab.fray.test.success.rwlock.ReentrantReadWriteLockDowngradingNoDeadlock;
import org.pastalab.fray.test.success.rwlock.ReentrantReadWriteLockNoDeadlock;
import org.pastalab.fray.test.success.stampedlock.StampedLockTryLockNoDeadlock;
Expand All @@ -36,7 +37,6 @@ private DynamicTest populateTest(String className, boolean testShouldFail) {
new HashMap<>()
),
false,
true,
false,
-1
),
Expand Down Expand Up @@ -70,19 +70,18 @@ public void testOne() throws Throwable {
new ExecutionInfo(
new LambdaExecutor(() -> {
try {
ThreadInterruptionWithoutStart.main(new String[]{});
ConditionAwaitTimeoutNotifyInterrupt.main(new String[]{});
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}),
false,
true,
false,
-1
),
"/tmp/report2",
10000,
200,
60,
new RandomScheduler(),
new ControlledRandom(),
Expand Down
11 changes: 7 additions & 4 deletions junit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ 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.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")
compileOnly("org.junit.platform:junit-platform-engine:1.11.3")
compileOnly("org.junit.platform:junit-platform-commons:1.11.3")
compileOnly("org.junit.jupiter:junit-jupiter-api:5.11.3")
compileOnly("junit:junit:4.13.2")
compileOnly("com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2")
testCompileOnly("org.junit.jupiter:junit-jupiter-api:5.11.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.11.3")
}

tasks.test {
Expand Down
7 changes: 7 additions & 0 deletions junit/src/main/kotlin/org/pastalab/fray/junit/Common.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.pastalab.fray.junit

import java.nio.file.Paths

object Common {
val WORK_DIR = Paths.get(System.getProperty("fray.workDir", "build/fray/fray-report"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.pastalab.fray.junit.junit4

import org.junit.runner.notification.RunNotifier
import org.junit.runners.BlockJUnit4ClassRunner
import org.junit.runners.model.FrameworkMethod

class FrayRunner(clazz: Class<Any>) : BlockJUnit4ClassRunner(clazz) {
override fun runChild(method: FrameworkMethod?, notifier: RunNotifier?) {}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.pastalab.fray.junit
package org.pastalab.fray.junit.junit5

import java.lang.reflect.Constructor
import org.junit.jupiter.api.extension.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.pastalab.fray.junit
package org.pastalab.fray.junit.junit5

class FrayJupiterContext {
var bugFound = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.pastalab.fray.junit
package org.pastalab.fray.junit.junit5

import java.io.File
import java.lang.reflect.Method
import java.nio.file.Paths
import java.util.stream.Stream
import kotlin.io.path.absolutePathString
import kotlinx.serialization.json.Json
Expand All @@ -18,10 +17,10 @@ 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.Scheduler
import org.pastalab.fray.junit.annotations.ConcurrencyTest
import org.pastalab.fray.junit.Common.WORK_DIR
import org.pastalab.fray.junit.junit5.annotations.ConcurrencyTest

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

override fun supportsTestTemplate(context: ExtensionContext): Boolean {
return isAnnotated(context.testMethod, ConcurrencyTest::class.java)
Expand All @@ -38,8 +37,8 @@ class FrayTestExtension : TestTemplateInvocationContextProvider {
ConcurrencyTest::class.java,
)
.get()
if (!workDir.toFile().exists()) {
workDir.toFile().mkdirs()
if (!WORK_DIR.toFile().exists()) {
WORK_DIR.toFile().mkdirs()
}
val (scheduler, random) =
if (concurrencyTest.replay.isNotEmpty()) {
Expand All @@ -59,11 +58,10 @@ class FrayTestExtension : TestTemplateInvocationContextProvider {
ExecutionInfo(
LambdaExecutor {},
false,
true,
false,
-1,
),
workDir.absolutePathString(),
WORK_DIR.absolutePathString(),
totalRepetition(concurrencyTest, testMethod),
60,
scheduler,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.pastalab.fray.junit
package org.pastalab.fray.junit.junit5

import org.junit.jupiter.api.extension.Extension
import org.junit.jupiter.api.extension.TestTemplateInvocationContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.pastalab.fray.junit.annotations
package org.pastalab.fray.junit.junit5.annotations

import kotlin.reflect.KClass
import org.junit.jupiter.api.TestTemplate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.pastalab.fray.junit.plain

import java.io.File
import kotlin.io.path.absolutePathString
import kotlinx.serialization.json.Json
import org.pastalab.fray.core.TestRunner
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.PCTScheduler
import org.pastalab.fray.core.scheduler.Scheduler
import org.pastalab.fray.junit.Common.WORK_DIR

object FrayInTestLauncher {

fun launchFray(runnable: Runnable, scheduler: Scheduler, randomnessProvider: ControlledRandom) {
val config =
Configuration(
ExecutionInfo(
LambdaExecutor(
{ runnable.run() },
),
false,
false,
-1),
WORK_DIR.absolutePathString(),
10000,
60,
scheduler,
randomnessProvider,
true,
false,
true,
false,
false,
false,
)
val runner = TestRunner(config)
runner.run()?.let { throw it }
}

fun launchFrayTest(test: Runnable) {
launchFray(test, PCTScheduler(ControlledRandom(), 15, 0), ControlledRandom())
}

fun launchFrayReplay(test: Runnable, path: String) {
val randomPath = "${path}/random.json"
val schedulerPath = "${path}/schedule.json"
val randomnessProvider = Json.decodeFromString<ControlledRandom>(File(randomPath).readText())
val scheduler = Json.decodeFromString<Scheduler>(File(schedulerPath).readText())
launchFray(test, scheduler, randomnessProvider)
}
}
6 changes: 3 additions & 3 deletions junit/src/test/java/DeadlockTest.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.pastalab.fray.junit.FrayTestExtension;
import org.pastalab.fray.junit.annotations.ConcurrencyTest;
import org.pastalab.fray.junit.junit5.FrayTestExtension;
import org.pastalab.fray.junit.junit5.annotations.ConcurrencyTest;

@ExtendWith(FrayTestExtension.class)
public class DeadlockTest {

@ConcurrencyTest(iterations = 100)
@org.pastalab.fray.junit.junit5.annotations.ConcurrencyTest(iterations = 100)
public void deadlockInMainThread() throws InterruptedException {
Object o = new Object();
synchronized (o) {
Expand Down
4 changes: 2 additions & 2 deletions junit/src/test/java/DummyTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.pastalab.fray.junit.FrayTestExtension;
import org.pastalab.fray.junit.annotations.ConcurrencyTest;
import org.pastalab.fray.junit.junit5.FrayTestExtension;
import org.pastalab.fray.junit.junit5.annotations.ConcurrencyTest;

@ExtendWith(FrayTestExtension.class)
public class DummyTest {
Expand Down
1 change: 0 additions & 1 deletion plugins/gradle/src/main/kotlin/FrayPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class FrayPlugin : Plugin<Project> {
this.frayVersion.set(frayVersion)
}
target.tasks.register("frayTest", Test::class.java) {
it.useJUnitPlatform()
it.executable(javaPath)
it.jvmArgs("-agentpath:$jvmtiPath/libjvmti.so")
it.jvmArgs(
Expand Down

0 comments on commit 2c16633

Please sign in to comment.