From 423a3e68240abfd07cb85d56653d4420230f9cca Mon Sep 17 00:00:00 2001 From: "Evgeniy.Zhelenskiy" Date: Thu, 28 Dec 2023 21:10:51 +0100 Subject: [PATCH 1/2] Add JVM bytecode generation for Kotlin/JVM --- .../compiler/components/KotlinCompiler.kt | 40 ++++++++++++++----- .../compiler/server/model/ExecutionResult.kt | 13 ++++-- .../compiler/server/model/ProgramOutput.kt | 13 +++--- .../com/compiler/server/CompilerAPITest.kt | 4 +- .../com/compiler/server/JvmRunnerTest.kt | 10 ++++- 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt index a969ea1c8..0f48e1237 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt @@ -2,7 +2,7 @@ package com.compiler.server.compiler.components import com.compiler.server.executor.CommandLineArgument import com.compiler.server.executor.JavaExecutor -import com.compiler.server.model.ExecutionResult +import com.compiler.server.model.JvmExecutionResult import com.compiler.server.model.OutputDirectory import com.compiler.server.model.bean.LibrariesFile import com.compiler.server.model.toExceptionDescriptor @@ -16,9 +16,12 @@ import org.jetbrains.org.objectweb.asm.ClassReader.* import org.jetbrains.org.objectweb.asm.ClassVisitor import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.Opcodes.* +import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import java.io.File +import java.io.PrintWriter +import java.io.StringWriter import java.nio.file.FileVisitResult import java.nio.file.Files import java.nio.file.Path @@ -38,16 +41,34 @@ class KotlinCompiler( val mainClasses: Set = emptySet() ) - fun run(files: List, args: String): ExecutionResult { + private fun ByteArray.asHumanReadable(): String { + val classReader = ClassReader(this) + val stringWriter = StringWriter() + val printWriter = PrintWriter(stringWriter) + val traceClassVisitor = TraceClassVisitor(printWriter) + + classReader.accept(traceClassVisitor, 0) + + return stringWriter.toString() + } + + private fun JvmExecutionResult.addByteCode(compiled: JvmClasses) { + jvmByteCode = compiled.files + .mapNotNull { (_, bytes) -> runCatching { bytes.asHumanReadable() }.getOrNull() } + .takeUnless { it.isEmpty() } + ?.joinToString("\n\n") + } + + fun run(files: List, args: String): JvmExecutionResult { return execute(files) { output, compiled -> val mainClass = JavaRunnerExecutor::class.java.name val compiledMainClass = when (compiled.mainClasses.size) { - 0 -> return@execute ExecutionResult( + 0 -> return@execute JvmExecutionResult( exception = IllegalArgumentException("No main method found in project").toExceptionDescriptor() ) 1 -> compiled.mainClasses.single() - else -> return@execute ExecutionResult( + else -> return@execute JvmExecutionResult( exception = IllegalArgumentException( "Multiple classes in project contain main methods found: ${compiled.mainClasses.joinToString()}" ).toExceptionDescriptor() @@ -59,7 +80,7 @@ class KotlinCompiler( } } - fun test(files: List): ExecutionResult { + fun test(files: List): JvmExecutionResult { return execute(files) { output, _ -> val mainClass = JUnitExecutors::class.java.name javaExecutor.execute(argsFrom(mainClass, output, listOf(output.path.toString()))) @@ -117,22 +138,23 @@ class KotlinCompiler( private fun execute( files: List, - block: (output: OutputDirectory, compilation: JvmClasses) -> ExecutionResult - ): ExecutionResult = try { + block: (output: OutputDirectory, compilation: JvmClasses) -> JvmExecutionResult + ): JvmExecutionResult = try { when (val compilationResult = compile(files)) { is Compiled -> { usingTempDirectory { outputDir -> val output = write(compilationResult.result, outputDir) block(output, compilationResult.result).also { it.addWarnings(compilationResult.compilerDiagnostics) + it.addByteCode(compilationResult.result) } } } - is NotCompiled -> ExecutionResult(compilerDiagnostics = compilationResult.compilerDiagnostics) + is NotCompiled -> JvmExecutionResult(compilerDiagnostics = compilationResult.compilerDiagnostics) } } catch (e: Exception) { - ExecutionResult(exception = e.toExceptionDescriptor()) + JvmExecutionResult(exception = e.toExceptionDescriptor()) } private fun write(classes: JvmClasses, outputDir: Path): OutputDirectory { diff --git a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt index 9d7691206..fddd35b2a 100644 --- a/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt +++ b/src/main/kotlin/com/compiler/server/model/ExecutionResult.kt @@ -12,7 +12,7 @@ import com.fasterxml.jackson.databind.SerializerProvider import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize -open class ExecutionResult( +sealed class ExecutionResult( @field:JsonProperty("errors") open var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics(), open var exception: ExceptionDescriptor? = null @@ -51,6 +51,12 @@ data class CompilerDiagnostics( val map: Map> = mapOf() ): List by map.values.flatten() +open class JvmExecutionResult( + compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics(), + exception: ExceptionDescriptor? = null, + var jvmByteCode: String? = null, +): ExecutionResult(compilerDiagnostics, exception) + abstract class TranslationResultWithJsCode( open val jsCode: String?, compilerDiagnostics: CompilerDiagnostics, @@ -79,8 +85,9 @@ class JunitExecutionResult( val testResults: Map> = emptyMap(), override var exception: ExceptionDescriptor? = null, @field:JsonProperty("errors") - override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics() -) : ExecutionResult(compilerDiagnostics, exception) + override var compilerDiagnostics: CompilerDiagnostics = CompilerDiagnostics(), + jvmBytecode: String? = null, +) : JvmExecutionResult(compilerDiagnostics, exception, jvmBytecode) private fun unEscapeOutput(value: String) = value.replace("&lt;".toRegex(), "<") .replace("&gt;".toRegex(), ">") diff --git a/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt b/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt index 477caae26..a92916db5 100644 --- a/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt +++ b/src/main/kotlin/com/compiler/server/model/ProgramOutput.kt @@ -16,19 +16,20 @@ const val ERROR_STREAM_END = "" data class ProgramOutput( val standardOutput: String = "", + val jvmByteCode: String? = null, val exception: Exception? = null, val restriction: String? = null ) { - fun asExecutionResult(): ExecutionResult { + fun asExecutionResult(): JvmExecutionResult { return when { - restriction != null -> ExecutionResult().apply { text = buildRestriction(restriction) } - exception != null -> ExecutionResult(exception = exception.toExceptionDescriptor()) - standardOutput.isBlank() -> ExecutionResult() + restriction != null -> JvmExecutionResult().apply { text = buildRestriction(restriction) } + exception != null -> JvmExecutionResult(exception = exception.toExceptionDescriptor()) + standardOutput.isBlank() -> JvmExecutionResult() else -> { try { - outputMapper.readValue(standardOutput, ExecutionResult::class.java) + outputMapper.readValue(standardOutput, JvmExecutionResult::class.java) } catch (e: Exception) { - ExecutionResult(exception = e.toExceptionDescriptor()) + JvmExecutionResult(exception = e.toExceptionDescriptor()) } } } diff --git a/src/test/kotlin/com/compiler/server/CompilerAPITest.kt b/src/test/kotlin/com/compiler/server/CompilerAPITest.kt index 610d9378b..9418ab395 100644 --- a/src/test/kotlin/com/compiler/server/CompilerAPITest.kt +++ b/src/test/kotlin/com/compiler/server/CompilerAPITest.kt @@ -1,7 +1,7 @@ package com.compiler.server import com.compiler.server.generator.generateSingleProject -import com.compiler.server.model.ExecutionResult +import com.compiler.server.model.JvmExecutionResult import com.compiler.server.model.bean.VersionInfo import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.junit.jupiter.api.Test @@ -51,7 +51,7 @@ class CompilerAPITest { ), headers ), - ExecutionResult::class.java + JvmExecutionResult::class.java ) assertNotNull(response, "Empty response!") assertContains( diff --git a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt index 733b067af..f3faa0220 100644 --- a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt @@ -1,17 +1,25 @@ package com.compiler.server import com.compiler.server.base.BaseExecutorTest +import com.compiler.server.model.JvmExecutionResult import org.junit.jupiter.api.Test +import kotlin.test.assertContains import kotlin.test.assertEquals class JvmRunnerTest : BaseExecutorTest() { @Test fun `base execute test JVM`() { - run( + val executionResult = run( code = "fun main() {\n println(\"Hello, world!!!\")\n}", contains = "Hello, world!!!" ) + + val byteCode = (executionResult as JvmExecutionResult).jvmByteCode!! + assertContains(byteCode, "public static synthetic main([Ljava/lang/String;)V", message = byteCode) + assertContains(byteCode, "public final static main()V", message = byteCode) + assertContains(byteCode, "LDC \"Hello, world!!!\"", message = byteCode) + assertContains(byteCode, "INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V", message = byteCode) } @Test From e02ceada158f293c768462cc514316109b673e81 Mon Sep 17 00:00:00 2001 From: "Evgeniy.Zhelenskiy" Date: Sat, 11 May 2024 04:06:33 +0200 Subject: [PATCH 2/2] Make JVM bytecode generation for Kotlin/JVM optional --- .../compiler/components/KotlinCompiler.kt | 13 ++-- .../controllers/CompilerRestController.kt | 14 +++-- .../KotlinPlaygroundRestController.kt | 7 ++- .../server/service/KotlinProjectExecutor.kt | 8 +-- .../compiler/server/JUnitTestsRunnerTest.kt | 59 ++++++++++++++++-- .../com/compiler/server/JvmRunnerTest.kt | 14 ++++- .../compiler/server/base/BaseExecutorTest.kt | 20 ++++--- .../com/compiler/server/base/BaseJUnitTest.kt | 10 ++-- .../server/generator/TestProjectRunner.kt | 60 +++++++++++++------ 9 files changed, 152 insertions(+), 53 deletions(-) diff --git a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt index 0f48e1237..73dae1511 100644 --- a/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt +++ b/src/main/kotlin/com/compiler/server/compiler/components/KotlinCompiler.kt @@ -59,8 +59,8 @@ class KotlinCompiler( ?.joinToString("\n\n") } - fun run(files: List, args: String): JvmExecutionResult { - return execute(files) { output, compiled -> + fun run(files: List, addByteCode: Boolean, args: String): JvmExecutionResult { + return execute(files, addByteCode) { output, compiled -> val mainClass = JavaRunnerExecutor::class.java.name val compiledMainClass = when (compiled.mainClasses.size) { 0 -> return@execute JvmExecutionResult( @@ -80,8 +80,8 @@ class KotlinCompiler( } } - fun test(files: List): JvmExecutionResult { - return execute(files) { output, _ -> + fun test(files: List, addByteCode: Boolean): JvmExecutionResult { + return execute(files, addByteCode) { output, _ -> val mainClass = JUnitExecutors::class.java.name javaExecutor.execute(argsFrom(mainClass, output, listOf(output.path.toString()))) .asJUnitExecutionResult() @@ -138,6 +138,7 @@ class KotlinCompiler( private fun execute( files: List, + addByteCode: Boolean, block: (output: OutputDirectory, compilation: JvmClasses) -> JvmExecutionResult ): JvmExecutionResult = try { when (val compilationResult = compile(files)) { @@ -146,7 +147,9 @@ class KotlinCompiler( val output = write(compilationResult.result, outputDir) block(output, compilationResult.result).also { it.addWarnings(compilationResult.compilerDiagnostics) - it.addByteCode(compilationResult.result) + if (addByteCode) { + it.addByteCode(compilationResult.result) + } } } } diff --git a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt index 2b646d8f4..92c219c54 100644 --- a/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/CompilerRestController.kt @@ -9,13 +9,19 @@ import org.springframework.web.bind.annotation.* @RequestMapping(value = ["/api/compiler", "/api/**/compiler"]) class CompilerRestController(private val kotlinProjectExecutor: KotlinProjectExecutor) { @PostMapping("/run") - fun executeKotlinProjectEndpoint(@RequestBody project: Project): ExecutionResult { - return kotlinProjectExecutor.run(project) + fun executeKotlinProjectEndpoint( + @RequestBody project: Project, + @RequestParam(defaultValue = "false") addByteCode: Boolean, + ): ExecutionResult { + return kotlinProjectExecutor.run(project, addByteCode) } @PostMapping("/test") - fun testKotlinProjectEndpoint(@RequestBody project: Project): ExecutionResult { - return kotlinProjectExecutor.test(project) + fun testKotlinProjectEndpoint( + @RequestBody project: Project, + @RequestParam(defaultValue = "false") addByteCode: Boolean, + ): ExecutionResult { + return kotlinProjectExecutor.test(project, addByteCode) } @PostMapping("/translate") diff --git a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt index 737db2cef..ffcc3d6f2 100644 --- a/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt +++ b/src/main/kotlin/com/compiler/server/controllers/KotlinPlaygroundRestController.kt @@ -30,7 +30,8 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr @RequestParam type: String, @RequestParam(required = false) line: Int?, @RequestParam(required = false) ch: Int?, - @RequestParam(required = false) project: Project? + @RequestParam(required = false) project: Project?, + @RequestParam(defaultValue = "false") addByteCode: Boolean, ): ResponseEntity<*> { val result = when (type) { "getKotlinVersions" -> listOf(kotlinProjectExecutor.getVersion()) @@ -39,7 +40,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr when (type) { "run" -> { when (project.confType) { - ProjectType.JAVA -> kotlinProjectExecutor.run(project) + ProjectType.JAVA -> kotlinProjectExecutor.run(project, addByteCode) ProjectType.JS -> throw LegacyJsException() ProjectType.JS_IR, ProjectType.CANVAS -> kotlinProjectExecutor.convertToJsIr( @@ -49,7 +50,7 @@ class KotlinPlaygroundRestController(private val kotlinProjectExecutor: KotlinPr project, debugInfo = false, ) - ProjectType.JUNIT -> kotlinProjectExecutor.test(project) + ProjectType.JUNIT -> kotlinProjectExecutor.test(project, addByteCode) } } diff --git a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt index 327cf4266..923d63b81 100644 --- a/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt +++ b/src/main/kotlin/com/compiler/server/service/KotlinProjectExecutor.kt @@ -23,17 +23,17 @@ class KotlinProjectExecutor( private val log = LoggerFactory.getLogger(KotlinProjectExecutor::class.java) - fun run(project: Project): ExecutionResult { + fun run(project: Project, addByteCode: Boolean): ExecutionResult { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } - kotlinCompiler.run(files, project.args) + kotlinCompiler.run(files, addByteCode, project.args) }.also { logExecutionResult(project, it) } } - fun test(project: Project): ExecutionResult { + fun test(project: Project, addByteCode: Boolean): ExecutionResult { return kotlinEnvironment.environment { environment -> val files = getFilesFrom(project, environment).map { it.kotlinFile } - kotlinCompiler.test(files) + kotlinCompiler.test(files, addByteCode) }.also { logExecutionResult(project, it) } } diff --git a/src/test/kotlin/com/compiler/server/JUnitTestsRunnerTest.kt b/src/test/kotlin/com/compiler/server/JUnitTestsRunnerTest.kt index 8d01d313d..462fe9227 100644 --- a/src/test/kotlin/com/compiler/server/JUnitTestsRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/JUnitTestsRunnerTest.kt @@ -3,8 +3,11 @@ package com.compiler.server import com.compiler.server.base.BaseJUnitTest import com.compiler.server.executor.ExecutorMessages import com.compiler.server.model.TestStatus +import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import kotlin.test.assertContains +import kotlin.test.assertNull class JUnitTestsRunnerTest : BaseJUnitTest() { @@ -25,11 +28,20 @@ class JUnitTestsRunnerTest : BaseJUnitTest() { @Test fun `base fail junit test`() { - val test = test( - "fun start(): String = \"OP\"", - "import org.junit.Assert\nimport org.junit.Test\n\nclass TestStart {\n @Test fun testOk() {\n Assert.assertEquals(\"OK\", start())\n }\n}", - koansUtilsFile - ) + @Language("kotlin") + val testCode = """ + import org.junit.Assert + import org.junit.Test + + class TestStart { + @Test fun testOk() { + Assert.assertEquals("OK", start()) + } + } + """.trimIndent() + val sourceCode = """fun start(): String = "OP"""" + + val test = test(sourceCode, testCode, koansUtilsFile) val fail = test.first() Assertions.assertTrue(fail.status == TestStatus.FAIL) Assertions.assertNotNull(fail.comparisonFailure, "comparisonFailure should not be a null") @@ -38,4 +50,39 @@ class JUnitTestsRunnerTest : BaseJUnitTest() { Assertions.assertTrue(it.expected == "OK") } } -} \ No newline at end of file + + @Test + fun `no bytecode`() { + @Language("kotlin") + val testCode = """ + import org.junit.Assert + import org.junit.Test + + class TestStart { + @Test fun testOk() { + Assert.assertEquals("OK", "OK") + } + } + """.trimIndent() + val testResults = testRaw(testCode, addByteCode = false) + assertNull(testResults!!.jvmByteCode, "Bytecode should not be generated") + } + + @Test + fun `with bytecode`() { + @Language("kotlin") + val testCode = """ + import org.junit.Assert + import org.junit.Test + + class TestStart { + @Test fun testOk() { + Assert.assertEquals("OK", "OK") + } + } + """.trimIndent() + val testResults = testRaw(testCode, addByteCode = true) + val byteCode = testResults!!.jvmByteCode!! + assertContains(byteCode, "public final testOk()V") + } +} diff --git a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt index f3faa0220..45753e3c8 100644 --- a/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt +++ b/src/test/kotlin/com/compiler/server/JvmRunnerTest.kt @@ -5,6 +5,7 @@ import com.compiler.server.model.JvmExecutionResult import org.junit.jupiter.api.Test import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertNull class JvmRunnerTest : BaseExecutorTest() { @@ -12,7 +13,18 @@ class JvmRunnerTest : BaseExecutorTest() { fun `base execute test JVM`() { val executionResult = run( code = "fun main() {\n println(\"Hello, world!!!\")\n}", - contains = "Hello, world!!!" + contains = "Hello, world!!!", + addByteCode = false, + ) + assertNull((executionResult as JvmExecutionResult).jvmByteCode, "Bytecode should not be generated") + } + + @Test + fun `jvm bytecode`() { + val executionResult = run( + code = "fun main() {\n println(\"Hello, world!!!\")\n}", + contains = "Hello, world!!!", + addByteCode = true, ) val byteCode = (executionResult as JvmExecutionResult).jvmByteCode!! diff --git a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt index 6ce60c7af..bc689bb16 100644 --- a/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt +++ b/src/test/kotlin/com/compiler/server/base/BaseExecutorTest.kt @@ -1,6 +1,7 @@ package com.compiler.server.base import com.compiler.server.generator.TestProjectRunner +import org.intellij.lang.annotations.Language import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -10,6 +11,7 @@ class BaseExecutorTest { private lateinit var testRunner: TestProjectRunner fun complete( + @Language("kotlin") code: String, line: Int, character: Int, @@ -18,23 +20,25 @@ class BaseExecutorTest { ) = testRunner.complete(code, line, character, completions, isJs) fun getCompletions( + @Language("kotlin") code: String, line: Int, character: Int, isJs: Boolean = false ) = testRunner.getCompletions(code, line, character, isJs) - fun highlight(code: String) = testRunner.highlight(code) + fun highlight(@Language("kotlin") code: String) = testRunner.highlight(code) - fun highlightJS(code: String) = testRunner.highlightJS(code) + fun highlightJS(@Language("kotlin") code: String) = testRunner.highlightJS(code) - fun highlightWasm(code: String) = testRunner.highlightWasm(code) + fun highlightWasm(@Language("kotlin") code: String) = testRunner.highlightWasm(code) - fun run(code: String, contains: String, args: String = "") = testRunner.run(code, contains, args) + fun run(@Language("kotlin") code: String, contains: String, args: String = "", addByteCode: Boolean = false) = testRunner.run(code, contains, args, addByteCode) - fun run(code: List, contains: String) = testRunner.multiRun(code, contains) + fun run(code: List, contains: String, addByteCode: Boolean = false) = testRunner.multiRun(code, contains, addByteCode) fun runJsIr( + @Language("kotlin") code: String, contains: String, args: String = "" @@ -54,13 +58,15 @@ class BaseExecutorTest { } fun runWasm( + @Language("kotlin") code: String, contains: String ) = testRunner.runWasm(code, contains) - fun translateToJsIr(code: String) = testRunner.translateToJsIr(code) + fun translateToJsIr(@Language("kotlin") code: String) = testRunner.translateToJsIr(code) - fun runWithException(code: String, contains: String, message: String? = null) = testRunner.runWithException(code, contains, message) + fun runWithException(@Language("kotlin") code: String, contains: String, message: String? = null, addByteCode: Boolean = false) = + testRunner.runWithException(code, contains, message, addByteCode) fun version() = testRunner.getVersion() } diff --git a/src/test/kotlin/com/compiler/server/base/BaseJUnitTest.kt b/src/test/kotlin/com/compiler/server/base/BaseJUnitTest.kt index 4380f344e..164e890c6 100644 --- a/src/test/kotlin/com/compiler/server/base/BaseJUnitTest.kt +++ b/src/test/kotlin/com/compiler/server/base/BaseJUnitTest.kt @@ -3,6 +3,7 @@ package com.compiler.server.base import com.compiler.server.generator.TestProjectRunner import com.compiler.server.model.TestDescription import com.compiler.server.model.TestStatus +import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -13,14 +14,15 @@ class BaseJUnitTest { @Autowired private lateinit var testRunner: TestProjectRunner - fun test(vararg test: String) = testRunner.test(*test) + fun test(@Language("kotlin") vararg test: String, addByteCode: Boolean = false) = testRunner.test(*test, addByteCode = addByteCode) - fun testRaw(vararg test: String) = testRunner.testRaw(*test) + fun testRaw(@Language("kotlin") vararg test: String, addByteCode: Boolean = false) = testRunner.testRaw(*test, addByteCode = addByteCode) - fun runKoanTest(vararg testFile: String) { + fun runKoanTest(@Language("kotlin") vararg testFile: String, addByteCode: Boolean = false) { val test = test( *testFile, - koansUtilsFile + koansUtilsFile, + addByteCode = addByteCode, ) successTestCheck(test) } diff --git a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt index ec35e598d..805280472 100644 --- a/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt +++ b/src/test/kotlin/com/compiler/server/generator/TestProjectRunner.kt @@ -6,11 +6,15 @@ import com.compiler.server.base.renderErrorDescriptors import com.compiler.server.model.* import com.compiler.server.service.KotlinProjectExecutor import model.Completion +import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component import java.io.IOException -import kotlin.io.path.* +import kotlin.io.path.absolutePathString +import kotlin.io.path.createTempDirectory +import kotlin.io.path.writeBytes +import kotlin.io.path.writeText import kotlin.test.assertTrue @@ -19,17 +23,24 @@ class TestProjectRunner { @Autowired private lateinit var kotlinProjectExecutor: KotlinProjectExecutor - fun run(code: String, contains: String, args: String = ""): ExecutionResult { + fun run( + @Language("kotlin") + code: String, + contains: String, + args: String = "", + addByteCode: Boolean, + ): ExecutionResult { val project = generateSingleProject(text = code, args = args) - return runAndTest(project, contains) + return runAndTest(project, contains, addByteCode) } - fun multiRun(code: List, contains: String) { + fun multiRun(code: List, contains: String, addByteCode: Boolean) { val project = generateMultiProject(*code.toTypedArray()) - runAndTest(project, contains) + runAndTest(project, contains, addByteCode) } fun runJs( + @Language("kotlin") code: String, contains: String, args: String = "", @@ -49,6 +60,7 @@ class TestProjectRunner { } fun runWasm( + @Language("kotlin") code: String, contains: String, ) { @@ -56,16 +68,22 @@ class TestProjectRunner { convertWasmAndTest(project, contains) } - fun translateToJsIr(code: String): TranslationResultWithJsCode { + fun translateToJsIr(@Language("kotlin") code: String): TranslationResultWithJsCode { val project = generateSingleProject(text = code, projectType = ProjectType.JS_IR) return kotlinProjectExecutor.convertToJsIr( project, ) } - fun runWithException(code: String, contains: String, message: String? = null): ExecutionResult { + fun runWithException( + @Language("kotlin") + code: String, + contains: String, + message: String? = null, + addByteCode: Boolean + ): ExecutionResult { val project = generateSingleProject(text = code) - val result = kotlinProjectExecutor.run(project) + val result = kotlinProjectExecutor.run(project, addByteCode) Assertions.assertNotNull(result.exception, "Test result should no be a null") Assertions.assertTrue( result.exception?.fullName?.contains(contains) == true, @@ -75,16 +93,18 @@ class TestProjectRunner { return result } - fun test(vararg test: String): List { + fun test(@Language("kotlin") vararg test: String, addByteCode: Boolean): List { val project = generateMultiProject(*test, projectType = ProjectType.JUNIT) - val result = kotlinProjectExecutor.test(project) as? JunitExecutionResult + val result = kotlinProjectExecutor.test(project, addByteCode) as? JunitExecutionResult Assertions.assertNotNull(result?.testResults, "Test result should no be a null") return result?.testResults?.values?.flatten() ?: emptyList() } - fun testRaw(vararg test: String): JunitExecutionResult? = executeTest(*test) + fun testRaw(@Language("kotlin") vararg test: String, addByteCode: Boolean): JunitExecutionResult? = + executeTest(*test, addByteCode = addByteCode) fun complete( + @Language("kotlin") code: String, line: Int, character: Int, @@ -102,6 +122,7 @@ class TestProjectRunner { } fun getCompletions( + @Language("kotlin") code: String, line: Int, character: Int, @@ -112,30 +133,30 @@ class TestProjectRunner { return kotlinProjectExecutor.complete(project, line, character) } - fun highlight(code: String): CompilerDiagnostics { + fun highlight(@Language("kotlin") code: String): CompilerDiagnostics { val project = generateSingleProject(text = code) return kotlinProjectExecutor.highlight(project) } - fun highlightJS(code: String): CompilerDiagnostics { + fun highlightJS(@Language("kotlin") code: String): CompilerDiagnostics { val project = generateSingleProject(text = code, projectType = ProjectType.JS_IR) return kotlinProjectExecutor.highlight(project) } - fun highlightWasm(code: String): CompilerDiagnostics { + fun highlightWasm(@Language("kotlin") code: String): CompilerDiagnostics { val project = generateSingleProject(text = code, projectType = ProjectType.WASM) return kotlinProjectExecutor.highlight(project) } fun getVersion() = kotlinProjectExecutor.getVersion().version - private fun executeTest(vararg test: String): JunitExecutionResult? { + private fun executeTest(@Language("kotlin") vararg test: String, addByteCode: Boolean): JunitExecutionResult? { val project = generateMultiProject(*test, projectType = ProjectType.JUNIT) - return kotlinProjectExecutor.test(project) as? JunitExecutionResult + return kotlinProjectExecutor.test(project, addByteCode) as? JunitExecutionResult } - private fun runAndTest(project: Project, contains: String): ExecutionResult { - val result = kotlinProjectExecutor.run(project) + private fun runAndTest(project: Project, contains: String, addByteCode: Boolean): ExecutionResult { + val result = kotlinProjectExecutor.run(project, addByteCode) Assertions.assertNotNull(result, "Test result should no be a null") Assertions.assertTrue( result.text.contains(contains), """ @@ -193,7 +214,8 @@ class TestProjectRunner { val wat = result.wat val maxWatLengthInMessage = 97 - val formattedWat = wat?.let { if (it.length > maxWatLengthInMessage) "${it.take(maxWatLengthInMessage)}..." else it } + val formattedWat = + wat?.let { if (it.length > maxWatLengthInMessage) "${it.take(maxWatLengthInMessage)}..." else it } assertTrue( actual = wat != null && wat.dropWhile { it.isWhitespace() }.startsWith("(module"), message = "wat is expected to start with \"(module\" but is $formattedWat"