Skip to content

Commit c3f2332

Browse files
committed
Rewrite test factory DSL
1 parent 31d2b8c commit c3f2332

File tree

7 files changed

+507
-278
lines changed

7 files changed

+507
-278
lines changed

usvm-ts-dataflow/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencies {
3030

3131
testFixturesImplementation(Libs.kotlin_logging)
3232
testFixturesImplementation(Libs.junit_jupiter_api)
33+
testFixturesImplementation(Libs.kotlinx_coroutines_core)
3334
}
3435

3536
tasks.withType<Test> {

usvm-ts-dataflow/src/test/kotlin/org/usvm/dataflow/ts/test/EtsTypeInferenceTest.kt

Lines changed: 198 additions & 203 deletions
Large diffs are not rendered by default.
Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,77 @@
11
package org.usvm.dataflow.ts
22

3+
import kotlinx.coroutines.ExperimentalCoroutinesApi
4+
import kotlinx.coroutines.channels.Channel
35
import org.junit.jupiter.api.DynamicContainer
46
import org.junit.jupiter.api.DynamicNode
57
import org.junit.jupiter.api.DynamicTest
6-
import org.junit.jupiter.api.function.Executable
7-
import java.util.stream.Stream
88

9-
private interface TestProvider {
10-
fun test(name: String, test: () -> Unit)
11-
}
12-
13-
private interface ContainerProvider {
14-
fun container(name: String, init: TestContainerBuilder.() -> Unit)
15-
}
9+
@DslMarker
10+
annotation class TestFactoryDsl
1611

17-
class TestContainerBuilder(var name: String) : TestProvider, ContainerProvider {
18-
private val nodes: MutableList<DynamicNode> = mutableListOf()
12+
@TestFactoryDsl
13+
abstract class TestNodeBuilder {
14+
private val nodeChannel = Channel<() -> DynamicNode>(Channel.UNLIMITED)
1915

20-
override fun test(name: String, test: () -> Unit) {
21-
nodes += dynamicTest(name, test)
16+
fun test(name: String, test: () -> Unit) {
17+
nodeChannel.trySend { dynamicTest(name, test) }
2218
}
2319

24-
override fun container(name: String, init: TestContainerBuilder.() -> Unit) {
25-
nodes += containerBuilder(name, init)
20+
fun container(name: String, init: TestContainerBuilder.() -> Unit) {
21+
nodeChannel.trySend { dynamicContainer(name, init) }
2622
}
2723

28-
fun build(): DynamicContainer = DynamicContainer.dynamicContainer(name, nodes)
29-
}
24+
protected fun createNodes(): Iterable<DynamicNode> =
25+
Iterable { DynamicNodeIterator() }
3026

31-
private fun containerBuilder(name: String, init: TestContainerBuilder.() -> Unit): DynamicContainer =
32-
TestContainerBuilder(name).apply(init).build()
27+
private inner class DynamicNodeIterator : Iterator<DynamicNode> {
28+
@OptIn(ExperimentalCoroutinesApi::class)
29+
override fun hasNext(): Boolean = !nodeChannel.isEmpty
3330

34-
class TestFactoryBuilder : TestProvider, ContainerProvider {
35-
private val nodes: MutableList<DynamicNode> = mutableListOf()
36-
37-
override fun test(name: String, test: () -> Unit) {
38-
nodes += dynamicTest(name, test)
31+
override fun next(): DynamicNode {
32+
val node = nodeChannel.tryReceive().getOrThrow()
33+
return node()
34+
}
3935
}
36+
}
4037

41-
override fun container(name: String, init: TestContainerBuilder.() -> Unit) {
42-
nodes += containerBuilder(name, init)
38+
class TestContainerBuilder(var name: String) : TestNodeBuilder() {
39+
fun build(): DynamicContainer {
40+
return DynamicContainer.dynamicContainer(name, createNodes())
4341
}
42+
}
4443

45-
fun build(): Stream<out DynamicNode> = nodes.stream()
44+
class TestFactoryBuilder : TestNodeBuilder() {
45+
fun build(): Iterable<DynamicNode> {
46+
return createNodes()
47+
}
4648
}
4749

48-
fun testFactory(init: TestFactoryBuilder.() -> Unit): Stream<out DynamicNode> =
50+
inline fun testFactory(init: TestFactoryBuilder.() -> Unit): Iterable<DynamicNode> =
4951
TestFactoryBuilder().apply(init).build()
5052

5153
private fun dynamicTest(name: String, test: () -> Unit): DynamicTest =
52-
DynamicTest.dynamicTest(name, Executable(test))
54+
DynamicTest.dynamicTest(name, test)
55+
56+
private fun dynamicContainer(name: String, init: TestContainerBuilder.() -> Unit): DynamicContainer =
57+
TestContainerBuilder(name).apply(init).build()
58+
59+
inline fun <reified T> TestNodeBuilder.testForEach(
60+
data: Iterable<T>,
61+
crossinline nameProvider: (T) -> String = { it.toString() },
62+
crossinline test: (T) -> Unit,
63+
) {
64+
data.forEach { item ->
65+
test(nameProvider(item)) { test(item) }
66+
}
67+
}
68+
69+
inline fun <reified T> TestNodeBuilder.containerForEach(
70+
data: Iterable<T>,
71+
crossinline nameProvider: (T) -> String = { it.toString() },
72+
crossinline init: TestContainerBuilder.(T) -> Unit,
73+
) {
74+
data.forEach { item ->
75+
container(nameProvider(item)) { init(item) }
76+
}
77+
}

usvm-ts/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525
testImplementation(Libs.mockk)
2626
testImplementation(Libs.junit_jupiter_params)
2727
testImplementation(Libs.logback)
28+
testImplementation(testFixtures(project(":usvm-ts-dataflow")))
2829

2930
// https://mvnrepository.com/artifact/org.burningwave/core
3031
// Use it to export all modules to all

usvm-ts/src/test/kotlin/org/usvm/project/DemoCalc.kt

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package org.usvm.project
22

3+
import mu.KotlinLogging
34
import org.jacodb.ets.model.EtsScene
45
import org.jacodb.ets.utils.ANONYMOUS_CLASS_PREFIX
56
import org.jacodb.ets.utils.ANONYMOUS_METHOD_PREFIX
67
import org.jacodb.ets.utils.CONSTRUCTOR_NAME
78
import org.jacodb.ets.utils.INSTANCE_INIT_METHOD_NAME
89
import org.jacodb.ets.utils.STATIC_INIT_METHOD_NAME
9-
import org.jacodb.ets.utils.loadEtsProjectFromIR
10+
import org.jacodb.ets.utils.loadEtsProjectAutoConvert
1011
import org.junit.jupiter.api.condition.EnabledIf
1112
import org.usvm.machine.TsMachine
1213
import org.usvm.machine.TsOptions
@@ -15,61 +16,68 @@ import org.usvm.util.getResourcePath
1516
import org.usvm.util.getResourcePathOrNull
1617
import kotlin.test.Test
1718

19+
private val logger = KotlinLogging.logger {}
20+
1821
@EnabledIf("projectAvailable")
1922
class RunOnDemoCalcProject : TsMethodTestRunner() {
2023

2124
companion object {
22-
private const val PROJECT_PATH = "/projects/Demo_Calc/etsir/entry"
23-
private const val SDK_PATH = "/sdk/ohos/etsir"
25+
private const val PROJECT_PATH = "/projects/Demo_Calc/source/entry"
26+
private const val SDK_TS_PATH = "/sdk/typescript"
27+
private const val SDK_OHOS_PATH = "/sdk/ohos/5.0.1.111/ets"
2428

2529
@JvmStatic
2630
private fun projectAvailable(): Boolean {
2731
val isProjectPresent = getResourcePathOrNull(PROJECT_PATH) != null
28-
val isSdkPreset = getResourcePathOrNull(SDK_PATH) != null
29-
return isProjectPresent && isSdkPreset
32+
val isProjectTestsEnabled = System.getenv("USVM_TS_TEST_PROJECTS")?.toBoolean() ?: false
33+
return isProjectPresent && isProjectTestsEnabled
3034
}
3135
}
3236

3337
override val scene: EtsScene = run {
34-
val projectPath = getResourcePath(PROJECT_PATH)
35-
val sdkPath = getResourcePathOrNull(SDK_PATH)
36-
?: error(
37-
"Could not load SDK from resources '$SDK_PATH'. " +
38-
"Try running './gradlew generateSdkIR' to generate it."
39-
)
40-
loadEtsProjectFromIR(projectPath, sdkPath)
38+
val project = loadEtsProjectAutoConvert(getResourcePath(PROJECT_PATH))
39+
val sdkFiles = listOf(SDK_TS_PATH, SDK_OHOS_PATH).flatMap { sdk ->
40+
val sdkPath = getResourcePath(sdk)
41+
val sdkProject = loadEtsProjectAutoConvert(sdkPath, useArkAnalyzerTypeInference = null)
42+
sdkProject.projectFiles
43+
}
44+
EtsScene(project.projectFiles, sdkFiles, projectName = project.projectName)
4145
}
4246

4347
@Test
44-
fun `test run on each method`() {
48+
fun `test run on each class`() {
4549
val exceptions = mutableListOf<Throwable>()
46-
val classes = scene.projectClasses.filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) }
50+
val classes = scene.projectClasses
51+
.filterNot { it.name.startsWith(ANONYMOUS_CLASS_PREFIX) }
4752

4853
println("Total classes: ${classes.size}")
4954

50-
classes
51-
.forEach { cls ->
52-
val methods = cls.methods
53-
.filterNot { it.cfg.stmts.isEmpty() }
54-
.filterNot { it.isStatic }
55-
.filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) }
56-
.filterNot { it.name == "build" }
57-
.filterNot { it.name == INSTANCE_INIT_METHOD_NAME }
58-
.filterNot { it.name == STATIC_INIT_METHOD_NAME }
59-
.filterNot { it.name == CONSTRUCTOR_NAME }
60-
61-
if (methods.isEmpty()) return@forEach
62-
63-
runCatching {
64-
val tsOptions = TsOptions()
65-
TsMachine(scene, options, tsOptions).use { machine ->
66-
val states = machine.analyze(methods)
67-
states.let {}
68-
}
69-
}.onFailure {
70-
exceptions += it
55+
for (cls in classes) {
56+
logger.info {
57+
"Analyzing class ${cls.name} with ${cls.methods.size} methods"
58+
}
59+
60+
val methods = cls.methods
61+
.filterNot { it.cfg.stmts.isEmpty() }
62+
.filterNot { it.isStatic }
63+
.filterNot { it.name.startsWith(ANONYMOUS_METHOD_PREFIX) }
64+
.filterNot { it.name == "build" }
65+
.filterNot { it.name == INSTANCE_INIT_METHOD_NAME }
66+
.filterNot { it.name == STATIC_INIT_METHOD_NAME }
67+
.filterNot { it.name == CONSTRUCTOR_NAME }
68+
69+
if (methods.isEmpty()) continue
70+
71+
runCatching {
72+
val tsOptions = TsOptions()
73+
TsMachine(scene, options, tsOptions).use { machine ->
74+
val states = machine.analyze(methods)
75+
states.let {}
7176
}
77+
}.onFailure {
78+
exceptions += it
7279
}
80+
}
7381

7482
val exc = exceptions.groupBy { it }
7583
println("Total exceptions: ${exc.size}")

usvm-ts/src/test/kotlin/org/usvm/project/DemoPhotos.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,25 @@ class RunOnDemoPhotosProject : TsMethodTestRunner() {
2323

2424
companion object {
2525
private const val PROJECT_PATH = "/projects/Demo_Photos/source/entry"
26-
private const val SDK_PATH = "/sdk/ohos/etsir"
26+
private const val SDK_TS_PATH = "/sdk/typescript"
27+
private const val SDK_OHOS_PATH = "/sdk/ohos/5.0.1.111/ets"
2728

2829
@JvmStatic
2930
private fun projectAvailable(): Boolean {
3031
val isProjectPresent = getResourcePathOrNull(PROJECT_PATH) != null
31-
val isSdkPreset = getResourcePathOrNull(SDK_PATH) != null
32-
return isProjectPresent && isSdkPreset
32+
val isProjectTestsEnabled = System.getenv("USVM_TS_TEST_PROJECTS")?.toBoolean() ?: false
33+
return isProjectPresent && isProjectTestsEnabled
3334
}
3435
}
3536

3637
override val scene: EtsScene = run {
37-
val projectPath = getResourcePath(PROJECT_PATH)
38-
val sdkPath = getResourcePathOrNull(SDK_PATH)
39-
?: error(
40-
"Could not load SDK from resources '$SDK_PATH'. " +
41-
"Try running './gradlew generateSdkIR' to generate it."
42-
)
43-
loadEtsProjectAutoConvert(projectPath, sdkPath)
38+
val project = loadEtsProjectAutoConvert(getResourcePath(PROJECT_PATH))
39+
val sdkFiles = listOf(SDK_TS_PATH, SDK_OHOS_PATH).flatMap { sdk ->
40+
val sdkPath = getResourcePath(sdk)
41+
val sdkProject = loadEtsProjectAutoConvert(sdkPath, useArkAnalyzerTypeInference = null)
42+
sdkProject.projectFiles
43+
}
44+
EtsScene(project.projectFiles, sdkFiles, projectName = project.projectName)
4445
}
4546

4647
@Test
@@ -112,8 +113,9 @@ class RunOnDemoPhotosProject : TsMethodTestRunner() {
112113
@Test
113114
fun `test on particular method`() {
114115
val method = scene.projectClasses
116+
.filter { it.toString() == "@entry/utils/ResourceUtils: %dflt" }
115117
.flatMap { it.methods }
116-
.single { it.name == "onCreate" && it.enclosingClass?.name == "EntryAbility" }
118+
.single { it.name == "getResourceString" && it.enclosingClass?.name == "%dflt" }
117119

118120
val tsOptions = TsOptions()
119121
TsMachine(scene, options, tsOptions).use { machine ->

0 commit comments

Comments
 (0)