diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index ebd4a2a9f..3851c8d8e 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -50,6 +50,9 @@ jobs:
# Builds on other branches will only read existing entries from the cache.
cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }}
+ - name: Set up GraphViz
+ uses: ts-graphviz/setup-graphviz@v2
+
- name: Set up Node
uses: actions/setup-node@v4
with:
diff --git a/buildSrc/src/main/kotlin/Tests.kt b/buildSrc/src/main/kotlin/Tests.kt
index 109b15574..aff1eb7ac 100644
--- a/buildSrc/src/main/kotlin/Tests.kt
+++ b/buildSrc/src/main/kotlin/Tests.kt
@@ -1,6 +1,7 @@
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.testing.Test
-import java.util.*
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import java.util.StringTokenizer
object Tests {
val lifecycleTag = "lifecycle"
@@ -10,6 +11,7 @@ object Tests {
fun Test.setup(jacocoTestReport: TaskProvider<*>) {
testLogging {
events("passed", "skipped", "failed")
+ exceptionFormat = TestExceptionFormat.FULL
}
finalizedBy(jacocoTestReport) // report is always generated after tests run
val majorJavaVersion =
diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Dot.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Dot.kt
new file mode 100644
index 000000000..36e9f4de9
--- /dev/null
+++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Dot.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 UnitTestBot contributors (utbot.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jacodb.ets.utils
+
+import mu.KotlinLogging
+import java.nio.file.Path
+import kotlin.io.path.absolute
+import kotlin.io.path.createDirectories
+import kotlin.io.path.div
+import kotlin.io.path.nameWithoutExtension
+import kotlin.time.Duration.Companion.seconds
+
+private val logger = KotlinLogging.logger {}
+
+fun render(
+ dotDir: Path,
+ relative: Path,
+ formats: List = listOf("pdf"), // See: https://graphviz.org/docs/outputs/
+) {
+ val dotPath = dotDir / relative
+ for (format in formats) {
+ logger.info { "Rendering DOT to ${format.uppercase()}..." }
+ val formatFile = (dotDir.resolveSibling(format) / relative)
+ .resolveSibling("${relative.nameWithoutExtension}.$format")
+ formatFile.parent?.createDirectories()
+ val cmd: List = listOf(
+ "dot",
+ "-T$format",
+ "$dotPath",
+ "-o",
+ "$formatFile"
+ )
+ runProcess(cmd, 10.seconds)
+ logger.info { "Generated ${format.uppercase()} file: ${formatFile.absolute()}" }
+ }
+}
diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt
index d692b9867..a96dcde95 100644
--- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt
+++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileDtoToDot.kt
@@ -24,8 +24,10 @@ import org.jacodb.ets.dto.MethodDto
import org.jacodb.ets.dto.NopStmtDto
import org.jacodb.ets.dto.StmtDto
import org.jacodb.ets.dto.SwitchStmtDto
+import java.io.BufferedWriter
import java.io.File
import java.nio.file.Path
+import kotlin.io.path.createDirectories
import kotlin.io.path.writeText
fun EtsFileDto.toDot(useLR: Boolean = false): String {
@@ -182,14 +184,15 @@ fun EtsFileDto.toDot(useLR: Boolean = false): String {
return lines.joinToString("\n")
}
-fun EtsFileDto.dumpDot(file: File) {
- file.writeText(toDot())
-}
-
fun EtsFileDto.dumpDot(path: Path) {
+ path.parent?.createDirectories()
path.writeText(toDot())
}
+fun EtsFileDto.dumpDot(file: File) {
+ dumpDot(file.toPath())
+}
+
fun EtsFileDto.dumpDot(path: String) {
dumpDot(File(path))
}
diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToDot.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToDot.kt
index f29a63b9d..7344e9134 100644
--- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToDot.kt
+++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/EtsFileToDot.kt
@@ -20,8 +20,10 @@ import org.jacodb.ets.base.EtsStmt
import org.jacodb.ets.model.EtsClass
import org.jacodb.ets.model.EtsFile
import org.jacodb.ets.model.EtsMethod
+import java.io.BufferedWriter
import java.io.File
import java.nio.file.Path
+import kotlin.io.path.createDirectories
import kotlin.io.path.writeText
fun EtsFile.toDot(useLR: Boolean = false): String {
@@ -146,14 +148,15 @@ fun EtsFile.toDot(useLR: Boolean = false): String {
return lines.joinToString("\n")
}
-fun EtsFile.dumpDot(file: File) {
- file.writeText(toDot())
-}
-
fun EtsFile.dumpDot(path: Path) {
+ path.parent?.createDirectories()
path.writeText(toDot())
}
+fun EtsFile.dumpDot(file: File) {
+ dumpDot(file.toPath())
+}
+
fun EtsFile.dumpDot(path: String) {
dumpDot(File(path))
}
diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt
index fcfec2ed7..3bc696992 100644
--- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt
+++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/LoadEtsFile.kt
@@ -16,20 +16,20 @@
package org.jacodb.ets.utils
-import mu.KotlinLogging
import org.jacodb.ets.dto.EtsFileDto
import org.jacodb.ets.dto.convertToEtsFile
import org.jacodb.ets.model.EtsFile
-import java.io.File
import java.io.FileNotFoundException
-import java.nio.file.Paths
-import java.util.concurrent.TimeUnit
+import java.nio.file.Path
+import kotlin.io.path.Path
+import kotlin.io.path.createTempDirectory
+import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.inputStream
-import kotlin.io.path.notExists
+import kotlin.io.path.nameWithoutExtension
import kotlin.io.path.pathString
-
-private val logger = KotlinLogging.logger {}
+import kotlin.io.path.relativeTo
+import kotlin.time.Duration.Companion.seconds
private const val ENV_VAR_ARK_ANALYZER_DIR = "ARKANALYZER_DIR"
private const val DEFAULT_ARK_ANALYZER_DIR = "arkanalyzer"
@@ -40,46 +40,62 @@ private const val DEFAULT_SERIALIZE_SCRIPT_PATH = "out/src/save/serializeArkIR.j
private const val ENV_VAR_NODE_EXECUTABLE = "NODE_EXECUTABLE"
private const val DEFAULT_NODE_EXECUTABLE = "node"
-fun loadEtsFileAutoConvert(tsPath: String): EtsFile {
- val arkAnalyzerDir = Paths.get(System.getenv(ENV_VAR_ARK_ANALYZER_DIR) ?: DEFAULT_ARK_ANALYZER_DIR)
+fun generateEtsFileIR(tsPath: Path): Path {
+ val arkAnalyzerDir = Path(System.getenv(ENV_VAR_ARK_ANALYZER_DIR) ?: DEFAULT_ARK_ANALYZER_DIR)
if (!arkAnalyzerDir.exists()) {
- throw FileNotFoundException("ArkAnalyzer directory does not exist: '$arkAnalyzerDir'. Did you forget to set the '$ENV_VAR_ARK_ANALYZER_DIR' environment variable? Current value is '${System.getenv(ENV_VAR_ARK_ANALYZER_DIR)}', current dir is '${Paths.get("").toAbsolutePath()}'.")
+ throw FileNotFoundException(
+ "ArkAnalyzer directory does not exist: '$arkAnalyzerDir'. " +
+ "Did you forget to set the '$ENV_VAR_ARK_ANALYZER_DIR' environment variable? " +
+ "Current value is '${System.getenv(ENV_VAR_ARK_ANALYZER_DIR)}', " +
+ "current dir is '${Path("").toAbsolutePath()}'."
+ )
}
val scriptPath = System.getenv(ENV_VAR_SERIALIZE_SCRIPT_PATH) ?: DEFAULT_SERIALIZE_SCRIPT_PATH
val script = arkAnalyzerDir.resolve(scriptPath)
if (!script.exists()) {
- throw FileNotFoundException("Script file not found: '$script'. Did you forget to execute 'npm run build' in the arkanalyzer project?")
+ throw FileNotFoundException(
+ "Script file not found: '$script'. " +
+ "Did you forget to execute 'npm run build' in the arkanalyzer project?"
+ )
}
val node = System.getenv(ENV_VAR_NODE_EXECUTABLE) ?: DEFAULT_NODE_EXECUTABLE
- val output = kotlin.io.path.createTempFile(prefix = File(tsPath).nameWithoutExtension + "_", suffix = ".json")
+ val output = kotlin.io.path.createTempFile(prefix = tsPath.nameWithoutExtension, suffix = ".json")
val cmd: List = listOf(
node,
script.pathString,
- tsPath,
+ tsPath.pathString,
output.pathString,
)
- logger.info { "Running: '${cmd.joinToString(" ")}'" }
- val process = ProcessBuilder(cmd).start()
- val ok = process.waitFor(1, TimeUnit.MINUTES)
+ runProcess(cmd, 60.seconds)
+ return output
+}
- val stdout = process.inputStream.bufferedReader().readText().trim()
- if (stdout.isNotBlank()) {
- logger.info { "STDOUT:\n$stdout" }
- }
- val stderr = process.errorStream.bufferedReader().readText().trim()
- if (stderr.isNotBlank()) {
- logger.info { "STDERR:\n$stderr" }
+fun loadEtsFileAutoConvert(tsPath: Path): EtsFile {
+ val irFilePath = generateEtsFileIR(tsPath)
+ irFilePath.inputStream().use { stream ->
+ val etsFileDto = EtsFileDto.loadFromJson(stream)
+ val etsFile = convertToEtsFile(etsFileDto)
+ return etsFile
}
+}
- if (!ok) {
- logger.info { "Timeout!" }
- process.destroy()
- }
+fun loadEtsFileAutoConvertWithDot(tsPath: Path): EtsFile {
+ val irPath = generateEtsFileIR(tsPath)
+ val dotDir = createTempDirectory(prefix = "dot_" + tsPath.nameWithoutExtension)
- output.inputStream().use { stream ->
- val etsFileDto = EtsFileDto.loadFromJson(stream)
- return convertToEtsFile(etsFileDto)
+ val etsFileDto = irPath.inputStream().use { stream ->
+ EtsFileDto.loadFromJson(stream)
}
+ val etsFileDtoDotPath = dotDir / (tsPath.nameWithoutExtension + "_DTO.dot")
+ etsFileDto.dumpDot(etsFileDtoDotPath)
+ render(dotDir, etsFileDtoDotPath.relativeTo(dotDir))
+
+ val etsFile = convertToEtsFile(etsFileDto)
+ val etsFileDotPath = dotDir / (tsPath.nameWithoutExtension + ".dot")
+ etsFile.dumpDot(etsFileDotPath)
+ render(dotDir, etsFileDotPath.relativeTo(dotDir))
+
+ return etsFile
}
diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Utils.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Utils.kt
new file mode 100644
index 000000000..569b984f5
--- /dev/null
+++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/utils/Utils.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2022 UnitTestBot contributors (utbot.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jacodb.ets.utils
+
+import mu.KotlinLogging
+import org.jacodb.ets.dto.EtsFileDto
+import org.jacodb.ets.model.EtsFile
+import java.util.concurrent.TimeUnit
+import kotlin.time.Duration
+
+private val logger = KotlinLogging.logger {}
+
+internal fun runProcess(cmd: List, timeout: Duration? = null) {
+ logger.info { "Running: '${cmd.joinToString(" ")}'" }
+ val process = ProcessBuilder(cmd).start()
+ val ok = if (timeout == null) {
+ process.waitFor()
+ true
+ } else {
+ process.waitFor(timeout.inWholeNanoseconds, TimeUnit.NANOSECONDS)
+ }
+
+ val stdout = process.inputStream.bufferedReader().readText().trim()
+ if (stdout.isNotBlank()) {
+ logger.info { "STDOUT:\n$stdout" }
+ }
+ val stderr = process.errorStream.bufferedReader().readText().trim()
+ if (stderr.isNotBlank()) {
+ logger.info { "STDERR:\n$stderr" }
+ }
+
+ if (!ok) {
+ logger.info { "Timeout!" }
+ process.destroy()
+ }
+}
+
+fun EtsFileDto.toText(): String {
+ val lines: MutableList = mutableListOf()
+ lines += "EtsFileDto '${name}':"
+ classes.forEach { clazz ->
+ lines += "= CLASS '${clazz.signature}':"
+ lines += " superClass = '${clazz.superClassName}'"
+ lines += " typeParameters = ${clazz.typeParameters}"
+ lines += " modifiers = ${clazz.modifiers}"
+ lines += " fields: ${clazz.fields.size}"
+ clazz.fields.forEach { field ->
+ lines += " - FIELD '${field.signature}'"
+ lines += " typeParameters = ${field.typeParameters}"
+ lines += " modifiers = ${field.modifiers}"
+ lines += " isOptional = ${field.isOptional}"
+ lines += " isDefinitelyAssigned = ${field.isDefinitelyAssigned}"
+ }
+ lines += " methods: ${clazz.methods.size}"
+ clazz.methods.forEach { method ->
+ lines += " - METHOD '${method.signature}'"
+ lines += " locals = ${method.body.locals}"
+ lines += " typeParameters = ${method.typeParameters}"
+ lines += " blocks: ${method.body.cfg.blocks.size}"
+ method.body.cfg.blocks.forEach { block ->
+ lines += " - BLOCK ${block.id}"
+ lines += " successors = ${block.successors}"
+ lines += " predecessors = ${block.predecessors}"
+ lines += " statements: ${block.stmts.size}"
+ block.stmts.forEachIndexed { i, stmt ->
+ lines += " ${i + 1}. $stmt"
+ }
+ }
+ }
+ }
+ return lines.joinToString("\n")
+}
+
+fun EtsFile.toText(): String {
+ val lines: MutableList = mutableListOf()
+ lines += "EtsFile '${name}':"
+ classes.forEach { clazz ->
+ lines += "= CLASS '${clazz.signature}':"
+ lines += " superClass = '${clazz.superClass}'"
+ lines += " fields: ${clazz.fields.size}"
+ clazz.fields.forEach { field ->
+ lines += " - FIELD '${field.signature}'"
+ }
+ lines += " constructor = '${clazz.ctor.signature}'"
+ lines += " stmts: ${clazz.ctor.cfg.stmts.size}"
+ clazz.ctor.cfg.stmts.forEach { stmt ->
+ lines += " ${stmt.location.index}. $stmt"
+ val pad = " ".repeat("${stmt.location.index}".length + 2) // number + dot + space
+ lines += " ${pad}successors = ${clazz.ctor.cfg.successors(stmt).map { it.location.index }}"
+ }
+ lines += " methods: ${clazz.methods.size}"
+ clazz.methods.forEach { method ->
+ lines += " - METHOD '${method.signature}':"
+ lines += " locals = ${method.localsCount}"
+ lines += " stmts: ${method.cfg.stmts.size}"
+ method.cfg.stmts.forEach { stmt ->
+ lines += " ${stmt.location.index}. $stmt"
+ val pad = " ".repeat("${stmt.location.index}".length + 2) // number + dot + space
+ lines += " ${pad}successors = ${method.cfg.successors(stmt).map { it.location.index }}"
+ }
+ }
+ }
+ return lines.joinToString("\n")
+}
diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt
index 9cf77e3e7..43a951249 100644
--- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt
+++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt
@@ -41,15 +41,13 @@ import org.jacodb.ets.model.EtsClassSignature
import org.jacodb.ets.model.EtsMethodSignature
import org.jacodb.ets.test.utils.loadEtsFileDtoFromResource
import org.jacodb.ets.utils.loadEtsFileAutoConvert
+import org.jacodb.ets.utils.loadEtsFileAutoConvertWithDot
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
+import kotlin.io.path.toPath
class EtsFromJsonTest {
- companion object {
- private const val BASE_PATH = "etsir/samples"
- }
-
private val json = Json {
// classDiscriminator = "_"
prettyPrint = true
@@ -64,7 +62,7 @@ class EtsFromJsonTest {
@Test
fun testLoadEtsFileFromJson() {
- val etsDto = loadEtsFileDtoFromResource("/$BASE_PATH/basic.ts.json")
+ val etsDto = loadEtsFileDtoFromResource("/etsir/samples/basic.ts.json")
println("etsDto = $etsDto")
val ets = convertToEtsFile(etsDto)
println("ets = $ets")
@@ -72,10 +70,19 @@ class EtsFromJsonTest {
@Test
fun testLoadEtsFileAutoConvert() {
- val path = "source/example.ts"
- val res = this::class.java.getResource("/$path")
+ val path = "/source/example.ts"
+ val res = this::class.java.getResource(path)?.toURI()?.toPath()
+ ?: error("Resource not found: $path")
+ val etsFile = loadEtsFileAutoConvert(res)
+ println("etsFile = $etsFile")
+ }
+
+ @Test
+ fun testLoadEtsFileAutoConvertWithDot() {
+ val path = "/source/example.ts"
+ val res = this::class.java.getResource(path)?.toURI()?.toPath()
?: error("Resource not found: $path")
- val etsFile = loadEtsFileAutoConvert(res.path)
+ val etsFile = loadEtsFileAutoConvertWithDot(res)
println("etsFile = $etsFile")
}
diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Dot.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Dot.kt
deleted file mode 100644
index cf6d764e6..000000000
--- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Dot.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2022 UnitTestBot contributors (utbot.org)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.jacodb.ets.test.utils
-
-import org.jacodb.ets.dto.EtsFileDto
-import org.jacodb.ets.utils.dumpDot
-import java.io.File
-
-/**
- * Visualize classes and methods in [EtsFileDto].
- */
-object DumpEtsFileDtoToDot {
- private const val BASE_PATH = "/etsir/samples"
- private const val NAME = "basic" // <-- change it
- private const val DOT_PATH = "ir.dot"
-
- @JvmStatic
- fun main(args: Array) {
- val path = "$BASE_PATH/${NAME}.ts.json"
- val etsFileDto: EtsFileDto = loadEtsFileDtoFromResource(path)
-
- println("EtsFileDto '${etsFileDto.name}':")
- etsFileDto.classes.forEach { clazz ->
- println("= CLASS '${clazz.signature}':")
- println(" superClass = '${clazz.superClassName}'")
- println(" typeParameters = ${clazz.typeParameters}")
- println(" modifiers = ${clazz.modifiers}")
- println(" fields: ${clazz.fields.size}")
- clazz.fields.forEach { field ->
- println(" - FIELD '${field.signature}'")
- println(" typeParameters = ${field.typeParameters}")
- println(" modifiers = ${field.modifiers}")
- println(" isOptional = ${field.isOptional}")
- println(" isDefinitelyAssigned = ${field.isDefinitelyAssigned}")
- }
- println(" methods: ${clazz.methods.size}")
- clazz.methods.forEach { method ->
- println(" - METHOD '${method.signature}':")
- println(" locals = ${method.body.locals}")
- println(" typeParameters = ${method.typeParameters}")
- println(" blocks: ${method.body.cfg.blocks.size}")
- method.body.cfg.blocks.forEach { block ->
- println(" - BLOCK ${block.id} with ${block.stmts.size} statements:")
- block.stmts.forEachIndexed { i, inst ->
- println(" ${i + 1}. $inst")
- }
- }
- }
- }
-
- println("Rendering EtsFileDto to DOT...")
- render(DOT_PATH) { file ->
- etsFileDto.dumpDot(file)
- }
- }
-}
-
-object DumpEtsFileToDot {
- private const val BASE_PATH = "/etsir/samples"
- private const val NAME = "basic" // <-- change it
- private const val DOT_PATH = "ets.dot"
-
- @JvmStatic
- fun main(args: Array) {
- val path = "$BASE_PATH/${NAME}.ts.json"
- val etsFile = loadEtsFileFromResource(path)
-
- println("EtsFile '${etsFile.name}':")
- etsFile.classes.forEach { clazz ->
- println("= CLASS '${clazz.signature}':")
- println(" superClass = '${clazz.superClass}'")
- println(" fields: ${clazz.fields.size}")
- clazz.fields.forEach { field ->
- println(" - FIELD '${field.signature}'")
- }
- println(" constructor = '${clazz.ctor.signature}'")
- println(" stmts: ${clazz.ctor.cfg.stmts.size}")
- clazz.ctor.cfg.stmts.forEachIndexed { i, stmt ->
- println(" ${i + 1}. $stmt")
- }
- println(" methods: ${clazz.methods.size}")
- clazz.methods.forEach { method ->
- println(" - METHOD '${method.signature}':")
- println(" locals = ${method.localsCount}")
- println(" stmts: ${method.cfg.stmts.size}")
- method.cfg.stmts.forEachIndexed { i, stmt ->
- println(" ${i + 1}. $stmt")
- }
- }
- }
-
- println("Rendering EtsFile to DOT...")
- render(DOT_PATH) { file ->
- etsFile.dumpDot(file)
- }
- }
-}
-
-private fun render(path: String, dump: (File) -> Unit) {
- val dotFile = File(path)
- dump(dotFile)
- println("Generated DOT file: ${dotFile.absolutePath}")
- for (format in listOf("pdf")) {
- val formatFile = dotFile.resolveSibling(dotFile.nameWithoutExtension + ".$format")
- val p = Runtime.getRuntime().exec("dot -T$format $dotFile -o $formatFile")
- p.waitFor()
- print(p.inputStream.bufferedReader().readText())
- print(p.errorStream.bufferedReader().readText())
- println("Generated ${format.uppercase()} file: ${formatFile.absolutePath}")
- }
-}
diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt
new file mode 100644
index 000000000..134665611
--- /dev/null
+++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/Entrypoints.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2022 UnitTestBot contributors (utbot.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jacodb.ets.test.utils
+
+import org.jacodb.ets.dto.EtsFileDto
+import org.jacodb.ets.model.EtsFile
+import org.jacodb.ets.utils.dumpDot
+import org.jacodb.ets.utils.render
+import org.jacodb.ets.utils.toText
+import java.nio.file.Path
+import kotlin.io.path.ExperimentalPathApi
+import kotlin.io.path.Path
+import kotlin.io.path.div
+import kotlin.io.path.name
+import kotlin.io.path.nameWithoutExtension
+import kotlin.io.path.relativeTo
+import kotlin.io.path.toPath
+import kotlin.io.path.walk
+
+private val logger = mu.KotlinLogging.logger {}
+
+/**
+ * Visualize classes and methods in [EtsFileDto].
+ */
+object DumpEtsFileDtoToDot {
+ private const val NAME = "basic"
+ private const val PATH = "/etsir/samples/$NAME.ts.json"
+ private val DOT_DIR = Path("dot")
+ private val DOT_PATH = Path("$NAME.dto.dot") // relative to DOT_DIR
+
+ @JvmStatic
+ fun main(args: Array) {
+ val etsFileDto: EtsFileDto = loadEtsFileDtoFromResource(PATH)
+
+ val text = etsFileDto.toText()
+ logger.info { "Text representation of EtsFileDto:\n$text" }
+
+ etsFileDto.dumpDot(DOT_DIR / DOT_PATH)
+ render(DOT_DIR, DOT_PATH)
+ }
+}
+
+/**
+ * Visualize classes and methods in [EtsFile].
+ */
+object DumpEtsFileToDot {
+ private const val NAME = "basic"
+ private const val PATH = "/etsir/samples/$NAME.ts.json"
+ private val DOT_DIR = Path("dot")
+ private val DOT_PATH = Path("$NAME.dto.dot") // relative to DOT_DIR
+
+ @JvmStatic
+ fun main(args: Array) {
+ val etsFile = loadEtsFileFromResource(PATH)
+
+ val text = etsFile.toText()
+ logger.info { "Text representation of EtsFile:\n$text" }
+
+ etsFile.dumpDot(DOT_DIR / DOT_PATH)
+ render(DOT_DIR, DOT_PATH)
+ }
+}
+
+/**
+ * Visualize classes and methods in [EtsFileDto] and [EtsFile] from directory.
+ */
+@OptIn(ExperimentalPathApi::class)
+object DumpEtsFilesToDot {
+ private const val ETSIR_BASE = "/etsir"
+ private const val ETSIR_DIR = "samples" // relative to BASE
+ private val DOT_DIR = Path("generated/dot")
+
+ @JvmStatic
+ fun main(args: Array) {
+ val resPath = "$ETSIR_BASE/$ETSIR_DIR"
+ val etsirDir = object {}::class.java.getResource(resPath)?.toURI()?.toPath()
+ ?: error("Resource not found: '$resPath'")
+ logger.info { "baseDir = $etsirDir" }
+
+ etsirDir.walk()
+ .filter { it.name.endsWith(".json") }
+ .forEach { path ->
+ val relative = path.relativeTo(etsirDir)
+
+ process(relative, ".dto") {
+ loadEtsFileDtoFromResource(it)
+ }
+ process(relative, "") {
+ loadEtsFileFromResource(it)
+ }
+
+ logger.info { "Processed: $path" }
+ }
+ }
+
+ private fun process(
+ relative: Path,
+ suffix: String,
+ load: (String) -> T,
+ ) {
+ val resourcePath = "$ETSIR_BASE/$ETSIR_DIR/$relative"
+ val relativeDot = (Path(ETSIR_DIR) / relative)
+ .resolveSibling("${relative.nameWithoutExtension}$suffix.dot")
+ val dotPath = DOT_DIR / relativeDot
+ when (val f = load(resourcePath)) {
+ is EtsFileDto -> f.dumpDot(dotPath)
+ is EtsFile -> f.dumpDot(dotPath)
+ else -> error("Unknown type: $f")
+ }
+ render(DOT_DIR, relativeDot)
+ }
+}
diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/LoadEts.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/LoadEts.kt
index c0226b457..1e693df76 100644
--- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/LoadEts.kt
+++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/utils/LoadEts.kt
@@ -21,6 +21,7 @@ import org.jacodb.ets.dto.convertToEtsFile
import org.jacodb.ets.model.EtsFile
fun loadEtsFileDtoFromResource(jsonPath: String): EtsFileDto {
+ require(jsonPath.startsWith("/")) { "Resource path must start with '/': $jsonPath" }
val sampleFilePath = object {}::class.java.getResourceAsStream(jsonPath)
?: error("Resource not found: $jsonPath")
return EtsFileDto.loadFromJson(sampleFilePath)