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)