Skip to content

Commit

Permalink
Refactored Theta runner to use docker image instead of downloaded jars
Browse files Browse the repository at this point in the history
  • Loading branch information
arminzavada committed Sep 14, 2024
1 parent 01e1187 commit 17cb5f5
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 157 deletions.
7 changes: 2 additions & 5 deletions engine/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@
#
# SPDX-License-Identifier: EPL-2.0

/Test Models/**/model.xsts
/Test Models/**/artifacts/
/theta/
/input/
/output/
/TestModels/**/model.xsts
/TestModels/**/artifacts/
37 changes: 8 additions & 29 deletions engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,8 @@ plugins {
alias(libs.plugins.kotlin.jvm)
}

val downloadTheta = tasks.create<Exec>("downloadTheta") {
inputs.files("scripts/Get-Theta.ps1")
inputs.files("scripts/get-theta.sh")
outputs.dir("theta")

workingDir = File("theta")
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("powershell", "../scripts/Get-Theta.ps1")
} else {
commandLine("sh", "../scripts/get-theta.sh")
}
}

fun Test.addToVariable(key: String, value: String) {
val pathSeparator = File.pathSeparator
val old = environment[key]?.toString() ?: ""
val newValue = if (old.isBlank()) value else "$old$pathSeparator$value"
environment(key, newValue)
}

tasks.withType(Test::class.java) {
inputs.dir("Test Models")
inputs.files(downloadTheta.outputs)

val thetaDir = layout.projectDirectory.dir("theta").toString()

addToVariable("LD_LIBRARY_PATH", thetaDir)
addToVariable("PATH", thetaDir)
inputs.dir("TestModels")
}

repositories {
Expand All @@ -51,13 +25,18 @@ dependencies {
implementation(project(":oxsts.model"))

implementation("com.google.inject:guice:7.0.0")
implementation(libs.slf4j.api)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.cli)
implementation(libs.ecore.codegen)
implementation(libs.viatra.query.language) {
exclude("com.google.inject", "guice")
}
implementation(libs.viatra.query.runtime)
implementation(libs.viatra.transformation.runtime)

runtimeOnly(libs.viatra.query.runtime)
runtimeOnly(libs.viatra.transformation.runtime)
runtimeOnly(libs.slf4j.simple)
runtimeOnly(libs.slf4j.log4j)

testFixturesApi("commons-io:commons-io:2.14.0")
testFixturesApi(project(":oxsts.lang"))
Expand Down
44 changes: 0 additions & 44 deletions engine/scripts/Get-Theta.ps1

This file was deleted.

13 changes: 0 additions & 13 deletions engine/scripts/get-theta.sh

This file was deleted.

122 changes: 122 additions & 0 deletions engine/src/testFixtures/kotlin/ThetaExecutor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2023-2024 The Semantifyr Authors
*
* SPDX-License-Identifier: EPL-2.0
*/

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
import kotlin.io.path.absolute

suspend fun <T> List<Deferred<T>>.awaitAny(): T {
val firstCompleted = CompletableDeferred<T>()

forEach { job ->
job.invokeOnCompletion { exception ->
if (exception == null && !firstCompleted.isCompleted) {
firstCompleted.complete(job.getCompleted())
}
}
}

return firstCompleted.await()
}

class ThetaExecutionResult(
val exitCode: Int,
val id: Int,
val modelPath: String,
val propertyPath: String,
val cexPath: String,
val logPath: String,
val errPath: String
) {
val isSafe: Boolean = Files.exists(Paths.get(cexPath))
}

class ThetaExecutor(
private val version: String = "latest",
private val parameters: List<String>,
private val timeout: Long = 60,
private val timeUnit: TimeUnit = TimeUnit.MINUTES
) {

private suspend fun runTheta(
workingDirectory: String,
name: String,
parameter: String,
id: Int
) = withContext(Dispatchers.IO) {
run {
val model = "$name.xsts"
val property = "$name.prop"
val cex = "$name$id.cex"
val logName = "theta$id.out"
val errName = "theta$id.err"

val process = ProcessBuilder(
"docker",
"run",
"--rm",
"--mount", "type=bind,source=$workingDirectory,target=/host",
"ftsrg/theta-xsts-cli:$version",
"CEGAR",
"--model", """"/host/$model"""",
"--property", """"/host/$property"""",
"--cexfile", """"/host/$cex"""",
*parameter.split(" ").toTypedArray(),
)
.redirectOutput(File(workingDirectory, logName))
.redirectError(File(workingDirectory, errName))
.start()

if (!process.waitFor(timeout, timeUnit)) {
process.destroyForcibly()
}

ThetaExecutionResult(
exitCode = process.waitFor(),
id = id,
modelPath = "$workingDirectory${File.separator}$model",
propertyPath = "$workingDirectory${File.separator}$property",
cexPath = "$workingDirectory${File.separator}$cex",
logPath = "$workingDirectory${File.separator}$logName",
errPath = "$workingDirectory${File.separator}$errName"
)
}
}

suspend fun runWorkflow(workingDirectory: String, name: String) = coroutineScope {
val jobs = parameters.indices.map { index ->
async {
runTheta(workingDirectory, name, parameters[index], index)
}
}

val finishedJob = jobs.awaitAny()

jobs.forEach {
it.cancelAndJoin()
}

finishedJob
}

fun run(workingDirectory: String, name: String) = runBlocking {
val absoluteDirectory = Path.of(workingDirectory).absolute().toString()

runWorkflow(absoluteDirectory, name)
}

}
85 changes: 19 additions & 66 deletions engine/src/testFixtures/kotlin/VerificationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import hu.bme.mit.semantifyr.oxsts.engine.serialization.Serializer
import hu.bme.mit.semantifyr.oxsts.engine.transformation.XstsTransformer
import hu.bme.mit.semantifyr.oxsts.model.oxsts.Target
import org.junit.jupiter.api.Assertions
import org.slf4j.LoggerFactory
import java.io.File
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.stream.Stream
import kotlin.streams.asStream

Expand All @@ -27,7 +26,18 @@ class TargetDefinition(
}

open class VerificationTest {

val logger = LoggerFactory.getLogger(this.javaClass)

companion object {
val thetaExecutor = ThetaExecutor(
"6.5.2",
listOf(
"--domain EXPL --refinement SEQ_ITP --maxenum 250 --initprec CTRL --stacktrace",
"--domain EXPL_PRED_COMBINED --autoexpl NEWOPERANDS --initprec CTRL --stacktrace",
),
)

fun streamTargetsFromFolder(
directory: String,
library: String,
Expand Down Expand Up @@ -67,76 +77,19 @@ open class VerificationTest {
transformTargetToTheta(directory, library, targetName, targetDirectory)

val modelPath = "$targetDirectory/$targetName.xsts"
val propertyPath = "$targetDirectory/$targetName.prop"
val tracePath = "$targetDirectory/$targetName.cex"

println("Executing theta on $modelPath")
logger.info("Executing theta on $modelPath")

try {
executeTheta(modelPath, propertyPath, tracePath)
} catch (e: Exception) {
e.printStackTrace()
throw e
}
val result = thetaExecutor.run(targetDirectory, targetName)

println("Checking results of Theta")
logger.info("Checking results of Theta")

Assertions.assertTrue(result.exitCode == 0, "Theta exited with code ${result.exitCode}. See $targetDirectory/theta.err")

if (targetName.contains("Unsafe")) {
Assertions.assertTrue(File(tracePath).exists(), "$targetName failed!")
Assertions.assertTrue(result.isSafe, "$targetName failed!")
} else if (targetName.contains("Safe")) {
Assertions.assertFalse(File(tracePath).exists(), "$targetName failed!")
}
}

private fun executeTheta(
modelPath: String, propertyPath: String, tracePath: String,
timeout: Long = 60, timeUnit: TimeUnit = TimeUnit.MINUTES
) {
val process1 = ProcessBuilder(
"java",
"-jar", "theta/theta-xsts-cli.jar",
"--domain", "EXPL",
"--refinement", "SEQ_ITP",
"--maxenum", "250",
"--initprec", "CTRL",
"--model", modelPath,
"--property", propertyPath,
"--cex", tracePath,
"--stacktrace",
)
.inheritIO()
.start()


val process2 = ProcessBuilder(
"java",
"-jar", "theta/theta-xsts-cli.jar",
"--domain", "EXPL_PRED_COMBINED",
"--autoexpl", "NEWOPERANDS ",
"--initprec", "CTRL",
"--model", modelPath,
"--property", propertyPath,
"--cex", tracePath,
"--stacktrace",
)
.inheritIO()
.start()

val future1 = CompletableFuture.supplyAsync {
process1.waitFor(timeout, timeUnit)
}

val future2 = CompletableFuture.supplyAsync {
process2.waitFor(timeout, timeUnit)
}

CompletableFuture.anyOf(future1, future2).join()

process1.destroy()
process2.destroy()

if (!future1.getNow(true) && !future1.getNow(true)) {
throw TimeoutException("Verification stopped due to reaching timeout $timeout $timeUnit")
Assertions.assertFalse(result.isSafe, "$targetName failed!")
}
}

Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mwe2 = "2.15.0"
slf4j = "2.0.9"
xtext = "2.32.0"
kotlin = '2.0.0'
kotlinxCoroutines = "1.9.0"
viatra = '2.8.0'

[libraries]
Expand All @@ -36,6 +37,7 @@ xtext-testing = { group = "org.eclipse.xtext", name = "org.eclipse.xtext.testing
xtext-xbase = { group = "org.eclipse.xtext", name = "org.eclipse.xtext.xbase", version.ref = "xtext" }
xtext-xbase-ide = { group = "org.eclipse.xtext", name = "org.eclipse.xtext.xbase.ide", version.ref = "xtext" }
kotlinx-cli = { group = "org.jetbrains.kotlinx", name = "kotlinx-cli", version = "0.3.6" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
viatra-query-language = { group = 'org.eclipse.viatra', name = 'viatra-query-language', version.ref = "viatra" }
viatra-query-runtime = { group = 'org.eclipse.viatra', name = 'viatra-query-runtime', version.ref = "viatra" }
viatra-transformation-runtime = { group = 'org.eclipse.viatra', name = 'viatra-transformation-runtime', version.ref = "viatra" }
Expand Down

0 comments on commit 17cb5f5

Please sign in to comment.