Skip to content

New Java executor #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 21, 2019
Merged
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ Tasks:
14) move `lib` to config props (+)
15) add coroutines (+)
16) add coroutines test (+)
17) test junit exception in executor
17) test junit exception in executor (+)
18) js env from ErrAnalyzer to EnvManager (+)
19) Get kotlin plugin form marketplace (+)
20) Bug with exception in executor. See coroutines test 13
21) Fix InterruptExecutionTest
22) Tests on exeptions
20) Bug with exception in executor. See coroutines test 13 (+)
21) Fix InterruptExecutionTest (+)
22) Tests on exception (+)
23) Validate compiler exceptions (+)
35 changes: 19 additions & 16 deletions executors/src/main/kotlin/FailureSerializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,39 @@ package executors

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.module.SimpleModule
import junit.framework.ComparisonFailure
import java.io.IOException

class JunitFrameworkComparisonFailureSerializer : JsonSerializer<ComparisonFailure?>() {
val mapper = ObjectMapper().apply {
registerModule(SimpleModule().apply {
addSerializer(Throwable::class.java, ThrowableSerializer())
addSerializer(ComparisonFailure::class.java, JunitFrameworkComparisonFailureSerializer())
addSerializer(org.junit.ComparisonFailure::class.java, OrgJunitComparisonFailureSerializer())
})
}

class JunitFrameworkComparisonFailureSerializer : JsonSerializer<ComparisonFailure>() {
@Throws(IOException::class)
override fun serialize(value: ComparisonFailure?, gen: JsonGenerator?, serializers: SerializerProvider?) {
if (gen == null || value == null) return
override fun serialize(value: ComparisonFailure, gen: JsonGenerator, serializers: SerializerProvider?) {
gen.apply {
writeStartObject()
writeStringField("message", value.message)
writeStringField("expected", value.expected)
writeStringField("actual", value.actual)
writeStringField("fullName", value.javaClass.name)
writeObjectField("stackTrace", value.stackTrace)
writeObjectField("cause", if (value.cause !== value) value.cause else null)
writeObjectField("cause", if (value.cause != value) value.cause else null)
writeEndObject()
}
}
}

class OrgJunitComparisonFailureSerializer : JsonSerializer<org.junit.ComparisonFailure?>() {
class OrgJunitComparisonFailureSerializer : JsonSerializer<org.junit.ComparisonFailure>() {
@Throws(IOException::class)
override fun serialize(
value: org.junit.ComparisonFailure?,
gen: JsonGenerator?,
serializers: SerializerProvider?
) {
if (gen == null || value == null) return
override fun serialize(value: org.junit.ComparisonFailure, gen: JsonGenerator, serializers: SerializerProvider?) {
gen.apply {
writeStartObject()
writeStringField("message", value.message)
Expand All @@ -44,15 +48,14 @@ class OrgJunitComparisonFailureSerializer : JsonSerializer<org.junit.ComparisonF
}
}

class ThrowableSerializer : JsonSerializer<Throwable?>() {
class ThrowableSerializer : JsonSerializer<Throwable>() {
@Throws(IOException::class)
override fun serialize(value: Throwable?, gen: JsonGenerator?, serializers: SerializerProvider?) {
if (gen == null || value == null) return
override fun serialize(value: Throwable, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeStartObject()
gen.writeStringField("message", value.message)
gen.writeStringField("fullName", value.javaClass.name)
gen.writeObjectField("stackTrace", value.stackTrace)
gen.writeObjectField("cause", if (value.cause !== value) value.cause else null)
gen.writeObjectField("stackTrace", value.stackTrace?.take(3))
gen.writeObjectField("cause", if (value.cause != value) value.cause else null)
gen.writeEndObject()
}
}
10 changes: 0 additions & 10 deletions executors/src/main/kotlin/JUnitExecutors.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package executors

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import junit.framework.ComparisonFailure
import org.junit.Test
import org.junit.runner.JUnitCore
import org.junit.runner.Request
Expand All @@ -13,13 +10,6 @@ class JUnitExecutors {
companion object {
var output: MutableList<TestRunInfo> = ArrayList()
private val standardOutput = System.out
private val mapper = ObjectMapper().apply {
registerModule(SimpleModule().apply {
addSerializer(Throwable::class.java, ThrowableSerializer())
addSerializer(ComparisonFailure::class.java, JunitFrameworkComparisonFailureSerializer())
addSerializer(org.junit.ComparisonFailure::class.java, OrgJunitComparisonFailureSerializer())
})
}

@JvmStatic
fun main(args: Array<String>) {
Expand Down
60 changes: 60 additions & 0 deletions executors/src/main/kotlin/JavaRunnerExecutor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package executors

import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.reflect.InvocationTargetException

const val NO_MAIN_METHOD = "No main method found in project."

class JavaRunnerExecutor {
companion object {
private val outputStream = ByteArrayOutputStream()
private val errorOutputStream = ErrorStream(outputStream)
private val standardOutputStream = OutStream(outputStream)
@JvmStatic
fun main(args: Array<String>) {
val defaultOutputStream = System.out
try {
System.setOut(PrintStream(standardOutputStream))
System.setErr(PrintStream(errorOutputStream))
val outputObj = RunOutput()
val className: String
if (args.isNotEmpty()) {
className = args[0]
try {
val mainMethod = Class.forName(className)
.getMethod("main", Array<String>::class.java)
mainMethod.invoke(null, args.copyOfRange(1, args.size) as Any)
}
catch (e: InvocationTargetException) {
outputObj.exception = e.cause
}
catch (e: NoSuchMethodException) {
System.err.println(NO_MAIN_METHOD)
}
catch (e: ClassNotFoundException) {
System.err.println(NO_MAIN_METHOD)
}
}
else {
System.err.println(NO_MAIN_METHOD)
}
System.out.flush()
System.err.flush()
System.setOut(defaultOutputStream)
outputObj.text = outputStream.toString()
.replace("</errStream><errStream>".toRegex(), "")
.replace("</outStream><outStream>".toRegex(), "")
print(mapper.writeValueAsString(outputObj))
}
catch (e: Throwable) {
System.setOut(defaultOutputStream)
println("{\"text\":\"<errStream>" + e.javaClass.name + ": " + e.message)
e.printStackTrace()
print("</errStream>\"}")
}
}
}
}

data class RunOutput(var text: String = "", var exception: Throwable? = null)
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.cli.jvm.compiler.CliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.container.*
import org.jetbrains.kotlin.context.ContextForNewModule
Expand All @@ -28,7 +26,6 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.frontend.di.configureModule
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices
import org.jetbrains.kotlin.name.Name
Expand All @@ -43,7 +40,6 @@ import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactory
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.springframework.stereotype.Component
import java.io.File
import java.util.ArrayList
import kotlin.Comparator

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package com.compiler.server.compiler.components

import com.compiler.server.configuration.LibrariesFolderProperties
import com.compiler.server.executor.CommandLineArgument
import com.compiler.server.executor.ExecutorMessages
import com.compiler.server.executor.JavaExecutor
import com.compiler.server.model.ExecutionResult
import com.compiler.server.model.OutputDirectory
import com.compiler.server.model.ProgramOutput
import com.compiler.server.model.toExceptionDescriptor
import executors.JUnitExecutors
import executors.JavaRunnerExecutor
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
Expand Down Expand Up @@ -33,8 +37,9 @@ class KotlinCompiler(

fun run(files: List<KtFile>, args: String): ExecutionResult {
return execute(files) { output, compiled ->
val arguments = args.split(" ")
javaExecutor.execute(argsFrom(compiled.mainClass, output, arguments))
val mainClass = JavaRunnerExecutor::class.java.name
val arguments = listOfNotNull(compiled.mainClass) + args.split(" ")
javaExecutor.execute(argsFrom(mainClass, output, arguments))
.asExecutionResult()
}
}
Expand Down Expand Up @@ -63,22 +68,27 @@ class KotlinCompiler(
files: List<KtFile>,
block: (output: OutputDirectory, compilation: Compiled) -> ExecutionResult
): ExecutionResult {
val errors = errorAnalyzer.errorsFrom(files)
return if (errorAnalyzer.isOnlyWarnings(errors)) {
val compilation = compile(files)
if (compilation.files.isEmpty())
return ExecutionResult(mapOf("No compilation files found!" to emptyList()))
val output = write(compilation)
try {
block(output, compilation).also {
it.addWarnings(errors)
return try {
val errors = errorAnalyzer.errorsFrom(files)
return if (errorAnalyzer.isOnlyWarnings(errors)) {
val compilation = compile(files)
if (compilation.files.isEmpty())
return ProgramOutput(restriction = ExecutorMessages.NO_COMPILERS_FILE_FOUND).asExecutionResult()
val output = write(compilation)
try {
block(output, compilation).also {
it.addWarnings(errors)
}
}
finally {
output.path.toAbsolutePath().toFile().deleteRecursively()
}
}
finally {
output.path.toAbsolutePath().toFile().deleteRecursively()
}
else ExecutionResult(errors)
}
catch (e: Exception) {
ExecutionResult(exception = e.toExceptionDescriptor())
}
else ExecutionResult(errors)
}

private fun write(compiled: Compiled): OutputDirectory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.compiler.server.compiler.components

import com.compiler.server.model.ErrorDescriptor
import com.compiler.server.model.TranslationJSResult
import com.compiler.server.model.toExceptionDescriptor
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.facade.K2JSTranslator
import org.jetbrains.kotlin.js.facade.MainCallParameters
Expand Down Expand Up @@ -32,8 +33,8 @@ class KotlinToJSTranslator(
TranslationJSResult(errors = errors)
}
}
catch (e: Throwable) {
throw Exception(e)
catch (e: Exception) {
TranslationJSResult(exception = e.toExceptionDescriptor())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package com.compiler.server.executor
object ExecutorMessages {
const val TIMEOUT_MESSAGE = "Evaluation stopped while it's taking too long ⌛️"
const val TOO_LONG_OUTPUT_MESSAGE = "Evaluation stopped while log size exceeded max size"
const val NO_COMPILERS_FILE_FOUND = "No compilation files found!"
}
20 changes: 5 additions & 15 deletions src/main/kotlin/com/compiler/server/executor/JavaExecutor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class JavaExecutor {
try {
val currTime = System.currentTimeMillis()
val futuresList = Executors.newFixedThreadPool(2) // one thread per output
.invokeAll(listOf(standardOutput, errorOutput), EXECUTION_TIMEOUT, TimeUnit.MILLISECONDS)
.invokeAll(listOf(standardOutput, errorOutput), EXECUTION_TIMEOUT, TimeUnit.MILLISECONDS)
// we do not wait for process to end, while either time-limit or output-limit triggered
// program itself will be destroyed right after we'll leave this method
println("Script execution time in millis: " + (System.currentTimeMillis() - currTime))
Expand All @@ -46,35 +46,25 @@ class JavaExecutor {
}
}

val (standardText, errorText) = outputResults
val exception = if (errorText.isNotEmpty()) Exception(errorText) else null
val (standardText, _) = outputResults

when {
futuresList.any { it.isCancelled } -> {
// execution timeout. Both Future objects must be in 'done' state, to say that process finished
ProgramOutput(
ExecutorMessages.TIMEOUT_MESSAGE,
errorText,
exception
)
ProgramOutput(restriction = ExecutorMessages.TIMEOUT_MESSAGE)
}
outputResults.any { it.length >= MAX_OUTPUT_SIZE } -> {
// log-limit exceeded
ProgramOutput(
ExecutorMessages.TOO_LONG_OUTPUT_MESSAGE,
errorText,
exception
)
ProgramOutput(restriction = ExecutorMessages.TOO_LONG_OUTPUT_MESSAGE)
}
else -> {
// normal exit
ProgramOutput(standardText, errorText, exception)
ProgramOutput(standardText)
}
}
}
catch (any: Exception) {
// all sort of things may happen, so we better be aware
any.printStackTrace()
ProgramOutput(exception = any)
}
finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.compiler.server.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties


@JsonIgnoreProperties(ignoreUnknown = true)
open class ExceptionDescriptor(
val message: String? = null,
Expand All @@ -10,3 +11,8 @@ open class ExceptionDescriptor(
val cause: ExceptionDescriptor? = null,
val localizedMessage: String? = null
)

fun Throwable.toExceptionDescriptor(): ExceptionDescriptor {
val rawException = outputMapper.writeValueAsString(this)
return outputMapper.readValue(rawException, ExceptionDescriptor::class.java)
}
Loading