Move scripting jvm host tests to a separate project...

to solve problems with testing with direct dependency on proguarded compiler
This commit is contained in:
Ilya Chernikov
2019-05-17 14:41:47 +02:00
parent 1527ef7640
commit 37d859c122
13 changed files with 32 additions and 13 deletions
+3 -10
View File
@@ -10,29 +10,22 @@ dependencies {
compile(kotlinStdlib())
compile(project(":kotlin-scripting-common"))
compile(project(":kotlin-scripting-jvm"))
compile(project(":kotlin-scripting-compiler"))
compile(project(":kotlin-scripting-compiler-impl"))
compileOnly(project(":kotlin-scripting-compiler"))
compileOnly(project(":compiler:cli"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep())
runtime(projectRuntimeJar(":kotlin-compiler"))
runtime(project(":kotlin-scripting-compiler"))
runtime(project(":kotlin-reflect"))
runtime(commonDep("org.jetbrains.intellij.deps", "trove4j"))
testCompile(projectTests(":compiler:tests-common"))
testCompile(commonDep("junit"))
testCompile(project(":daemon-common")) // TODO: fix import (workaround for jps build)
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
"test" {}
}
publish()
standardPublicJars()
projectTest(parallel = true) {
workingDir = rootDir
}
@@ -11,7 +11,7 @@ import java.io.InputStream
import java.net.URL
import java.util.*
internal class CompiledScriptClassLoader(parent: ClassLoader?, private val entries: Map<String, ByteArray>) : ClassLoader(parent) {
class CompiledScriptClassLoader(parent: ClassLoader?, private val entries: Map<String, ByteArray>) : ClassLoader(parent) {
override fun findClass(name: String): Class<*>? {
val classPathName = name.replace('.', '/') + ".class"
@@ -1,52 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.test
import org.junit.Test
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.implicitReceivers
import kotlin.script.experimental.api.providedProperties
import kotlin.test.assertTrue
class ConstructorArgumentsOrderTest {
@Test
fun testScriptWithProvidedProperties() {
val res = evalString<ScriptWithProvidedProperties>("""println(providedString)""") {
providedProperties("providedString" to "Hello Provided!")
}
assertTrue(
res is ResultWithDiagnostics.Success,
"test failed:\n ${res.reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception}" }}"
)
}
@Test
fun testScriptWithImplicitReceiver() {
val res = evalString<ScriptWithImplicitReceiver>("""println(receiverString)""") {
implicitReceivers(ImplicitReceiverClass("Hello Receiver!"))
}
assertTrue(
res is ResultWithDiagnostics.Success,
"test failed:\n ${res.reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception}" }}"
)
}
@Test
fun testScriptWithBoth() {
val res = evalString<ScriptWithBoth>("""println(providedString + receiverString)""") {
providedProperties("providedString" to "Hello")
implicitReceivers(ImplicitReceiverClass(" Both!"))
}
assertTrue(
res is ResultWithDiagnostics.Success,
"test failed:\n ${res.reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception}" }}"
)
}
}
@@ -1,182 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.test
import com.intellij.openapi.application.ApplicationManager
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.resetApplicationToNull
import java.io.Closeable
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.script.experimental.jvmhost.repl.JvmReplCompiler
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
// Adapted form GenericReplTest
// Artificial split into several testsuites, to speed up parallel testing
class LegacyReplTest : TestCase() {
fun testReplBasics() {
LegacyTestRepl().use { repl ->
val res1 = repl.replCompiler.check(repl.state, ReplCodeLine(0, 0, "val x ="))
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
assertEvalUnit(repl, "val x = 5")
assertEvalResult(repl, "x + 2", 7)
}
}
fun testReplErrors() {
LegacyTestRepl().use { repl ->
repl.compileAndEval(repl.nextCodeLine("val x = 10"))
val res = repl.compileAndEval(repl.nextCodeLine("java.util.fish"))
TestCase.assertTrue("Expected compile error", res.first is ReplCompileResult.Error)
val result = repl.compileAndEval(repl.nextCodeLine("x"))
assertEquals(res.second.toString(), 10, (result.second as? ReplEvalResult.ValueResult)?.value)
}
}
fun testReplCodeFormat() {
LegacyTestRepl().use { repl ->
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
val res0 = repl.replCompiler.check(repl.state, codeLine0)
val res0c = res0 as? ReplCheckResult.Ok
TestCase.assertNotNull("Unexpected compile result: $res0", res0c)
}
}
fun testRepPackage() {
LegacyTestRepl().use { repl ->
assertEvalResult(repl, "package mypackage\n\nval x = 1\nx+2", 3)
assertEvalResult(repl, "x+4", 5)
}
}
fun testReplResultFieldWithFunction() {
LegacyTestRepl().use { repl ->
assertEvalResultIs<Function0<Int>>(repl, "{ 1 + 2 }")
assertEvalResultIs<Function0<Int>>(repl, "res0")
assertEvalResult(repl, "res0()", 3)
}
}
fun testReplResultField() {
LegacyTestRepl().use { repl ->
assertEvalResult(repl, "5 * 4", 20)
assertEvalResult(repl, "res0 + 3", 23)
}
}
}
// Artificial split into several testsuites, to speed up parallel testing
class LegacyReplTestLong1 : TestCase() {
fun test256Evals() {
LegacyTestRepl().use { repl ->
repl.compileAndEval(ReplCodeLine(0, 0, "val x0 = 0"))
val evals = 256
for (i in 1..evals) {
repl.compileAndEval(ReplCodeLine(i, 0, "val x$i = x${i-1} + 1"))
}
val res = repl.compileAndEval(ReplCodeLine(evals + 1, 0, "x$evals"))
assertEquals(res.second.toString(), evals, (res.second as? ReplEvalResult.ValueResult)?.value)
}
}
}
// Artificial split into several testsuites, to speed up parallel testing
class LegacyReplTestLong2 : TestCase() {
fun testReplSlowdownKt22740() {
LegacyTestRepl().use { repl ->
repl.compileAndEval(ReplCodeLine(0, 0, "class Test<T>(val x: T) { fun <R> map(f: (T) -> R): R = f(x) }".trimIndent()))
// We expect that analysis time is not exponential
for (i in 1..60) {
repl.compileAndEval(ReplCodeLine(i, 0, "fun <T> Test<T>.map(f: (T) -> Double): List<Double> = listOf(f(this.x))"))
}
}
}
}
internal class LegacyTestRepl : Closeable {
val application = ApplicationManager.getApplication()
val currentLineCounter = AtomicInteger()
fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code)
val replCompiler: JvmReplCompiler by lazy {
JvmReplCompiler(simpleScriptompilationConfiguration)
}
val compiledEvaluator: ReplEvaluator by lazy {
JvmReplEvaluator(simpleScriptEvaluationConfiguration)
}
val state by lazy {
val stateLock = ReentrantReadWriteLock()
AggregatedReplStageState(replCompiler.createState(stateLock), compiledEvaluator.createState(stateLock), stateLock)
}
override fun close() {
state.dispose()
resetApplicationToNull(application)
}
}
private fun LegacyTestRepl.compileAndEval(codeLine: ReplCodeLine): Pair<ReplCompileResult, ReplEvalResult?> {
val compRes = replCompiler.compile(state, codeLine)
val evalRes = (compRes as? ReplCompileResult.CompiledClasses)?.let {
compiledEvaluator.eval(state, it)
}
return compRes to evalRes
}
private fun assertEvalUnit(repl: LegacyTestRepl, line: String) {
val compiledClasses = checkCompile(repl, line)
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
val unitResult = evalResult as? ReplEvalResult.UnitResult
TestCase.assertNotNull("Unexpected eval result: $evalResult", unitResult)
}
private fun<R> assertEvalResult(repl: LegacyTestRepl, line: String, expectedResult: R) {
val compiledClasses = checkCompile(repl, line)
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
val valueResult = evalResult as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertEquals(expectedResult, valueResult!!.value)
}
private inline fun<reified R> assertEvalResultIs(repl: LegacyTestRepl, line: String) {
val compiledClasses = checkCompile(repl, line)
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
val valueResult = evalResult as? ReplEvalResult.ValueResult
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
TestCase.assertTrue(valueResult!!.value is R)
}
private fun checkCompile(repl: LegacyTestRepl, line: String): ReplCompileResult.CompiledClasses? {
val codeLine = repl.nextCodeLine(line)
val compileResult = repl.replCompiler.compile(repl.state, codeLine)
val compiledClasses = compileResult as? ReplCompileResult.CompiledClasses
TestCase.assertNotNull("Unexpected compile result: $compileResult", compiledClasses)
return compiledClasses
}
@@ -1,112 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.test
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.junit.Assert
import org.junit.Test
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.*
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.impl.KJvmReplCompilerImpl
class ReplTest : TestCase() {
companion object {
const val TEST_DATA_DIR = "libraries/scripting/jvm-host/testData"
}
@Test
fun testCompileAndEval() {
val out = captureOut {
chechEvaluateInReplNoErrors(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
sequenceOf(
"val x = 3",
"x + 4",
"println(\"x = \$x\")"
),
sequenceOf(null, 7, null)
)
}
Assert.assertEquals("x = 3", out)
}
fun evaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
snippets: Sequence<String>
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
val replCompilerProxy = KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
val replEvaluator = BasicJvmScriptEvaluator()
var currentEvalConfig = evaluationConfiguration
return snippets.mapIndexed { snippetNo, snippetText ->
val snippetSource = snippetText.toScriptSource("Line_$snippetNo.simplescript.kts")
val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource)
replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory)
.onSuccess {
runBlocking {
replEvaluator(it, currentEvalConfig)
}
}
.onSuccess {
val snippetInstance = when (val retVal = it.returnValue) {
is ResultValue.Value -> retVal.scriptInstance
is ResultValue.UnitValue -> retVal.scriptInstance
else -> throw IllegalStateException("Expecting value with script instance, got $it")
}
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
previousSnippets.append(snippetInstance)
jvm {
baseClassLoader(snippetInstance::class.java.classLoader)
}
}
it.asSuccess()
}
}
}
fun chechEvaluateInReplNoErrors(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
snippets: Sequence<String>,
expected: Sequence<Any?>
) {
val expectedIter = expected.iterator()
evaluateInRepl(compilationConfiguration, evaluationConfiguration, snippets).forEachIndexed { index, res ->
when (res) {
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
is ResultWithDiagnostics.Success -> {
val expectedVal = expectedIter.next()
val resVal = res.value.returnValue
if (resVal is ResultValue.Value && resVal.type.isNotBlank()) // TODO: the latter check is temporary while the result is used to return the instance too
Assert.assertEquals("#$index: Expected $expectedVal, got $resVal", expectedVal, resVal.value)
else
Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
}
}
}
}
}
@KotlinScript(fileExtension = "simplescript.kts")
abstract class SimpleScript
val simpleScriptompilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScript> {
jvm {
dependenciesFromCurrentContext(wholeClasspath = true)
}
}
val simpleScriptEvaluationConfiguration = ScriptEvaluationConfiguration()
@@ -1,548 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.test
import junit.framework.TestCase
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
import java.io.*
import java.net.URLClassLoader
import java.nio.file.Files
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
import java.util.jar.JarFile
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.BasicScriptingHost
import kotlin.script.experimental.host.FileScriptSource
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.*
import kotlin.script.experimental.jvmhost.impl.CompiledScriptClassLoader
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.templates.standard.SimpleScriptTemplate
class ScriptingHostTest : TestCase() {
companion object {
const val TEST_DATA_DIR = "libraries/scripting/jvm-host/testData"
}
@Test
fun testSimpleUsage() {
val greeting = "Hello from script!"
val output = captureOut {
evalScript("println(\"$greeting\")").throwOnFailure()
}
Assert.assertEquals(greeting, output)
// another API
val output2 = captureOut {
BasicJvmScriptingHost().evalWithTemplate<SimpleScriptTemplate>("println(\"$greeting\")".toScriptSource()).throwOnFailure()
}
Assert.assertEquals(greeting, output2)
}
@Test
fun testSaveToClasses() {
val greeting = "Hello from script classes!"
val outDir = Files.createTempDirectory("saveToClassesOut").toFile()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val host = BasicJvmScriptingHost(evaluator = BasicJvmScriptClassFilesGenerator(outDir))
host.eval("println(\"$greeting\")".toScriptSource(name = "SavedScript.kts"), compilationConfiguration, null).throwOnFailure()
val classloader = URLClassLoader(arrayOf(outDir.toURI().toURL()), ScriptingHostTest::class.java.classLoader)
val scriptClass = classloader.loadClass("SavedScript")
val output = captureOut {
scriptClass.newInstance()
}
Assert.assertEquals(greeting, output)
}
@Test
fun testSaveToJar() {
val greeting = "Hello from script jar!"
val outJar = Files.createTempFile("saveToJar", ".jar").toFile()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val host = BasicJvmScriptingHost(evaluator = BasicJvmScriptJarGenerator(outJar))
host.eval("println(\"$greeting\")".toScriptSource(name = "SavedScript.kts"), compilationConfiguration, null).throwOnFailure()
val classloader = URLClassLoader(arrayOf(outJar.toURI().toURL()), ScriptingHostTest::class.java.classLoader)
val scriptClass = classloader.loadClass("SavedScript")
val output = captureOut {
scriptClass.newInstance()
}
Assert.assertEquals(greeting, output)
}
@Test
fun testSaveToRunnableJar() {
val greeting = "Hello from script jar!"
val outJar = Files.createTempFile("saveToRunnableJar", ".jar").toFile()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val compiler = JvmScriptCompiler(defaultJvmScriptingHostConfiguration)
val scriptName = "SavedRunnableScript"
val compiledScript = runBlocking {
compiler("println(\"$greeting\")".toScriptSource(name = "$scriptName.kts"), compilationConfiguration).throwOnFailure()
.resultOrNull()!!
}
val saver = BasicJvmScriptJarGenerator(outJar)
runBlocking {
saver(compiledScript, ScriptEvaluationConfiguration.Default).throwOnFailure()
}
val classpathFromJar = run {
val manifest = JarFile(outJar).manifest
manifest.mainAttributes.getValue("Class-Path").split(" ") // TODO: quoted paths
.map { File(it).toURI().toURL() }
} + outJar.toURI().toURL()
fun checkInvokeMain(baseClassLoader: ClassLoader?) {
val classloader = URLClassLoader(classpathFromJar.toTypedArray(), baseClassLoader)
val scriptClass = classloader.loadClass(scriptName)
val mainMethod = scriptClass.methods.find { it.name == "main" }
Assert.assertNotNull(mainMethod)
val output = captureOutAndErr {
mainMethod!!.invoke(null, emptyArray<String>())
}.toList().filterNot(String::isEmpty).joinToString("\n")
Assert.assertEquals(greeting, output)
}
checkInvokeMain(null) // isolated
checkInvokeMain(Thread.currentThread().contextClassLoader)
val javaExecutable = File(File(System.getProperty("java.home"), "bin"), "java")
val args = listOf(javaExecutable.absolutePath, "-jar", outJar.path)
val processBuilder = ProcessBuilder(args)
processBuilder.redirectErrorStream(true)
val outputFromProcess = run {
val process = processBuilder.start()
process.waitFor(10, TimeUnit.SECONDS)
val out = process.inputStream.reader().readText()
if (process.isAlive) {
process.destroyForcibly()
"Error: timeout, killing script process\n$out"
} else {
out
}
}.trim()
Assert.assertEquals(greeting, outputFromProcess)
}
@Test
fun testSimpleRequire() {
val greeting = "Hello from required!"
val script = "val subj = RequiredClass().value\nprintln(\"Hello from \$subj!\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
importScripts(File(TEST_DATA_DIR, "importTest/requiredSrc.kt").toScriptSource())
}
val output = captureOut {
BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null).throwOnFailure()
}
Assert.assertEquals(greeting, output)
}
@Test
fun testSimpleImport() {
val greeting = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!")
val script = "println(\"Hello from imported \$helloScriptName script!\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
makeSimpleConfigurationWithTestImport()
}
val output = captureOut {
BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null).throwOnFailure()
}.lines()
Assert.assertEquals(greeting, output)
}
@Test
fun testDiamondImportWithoutSharing() {
val greeting = listOf("Hi from common", "Hi from middle", "Hi from common", "sharedVar == 3")
val output = doDiamondImportTest()
Assert.assertEquals(greeting, output)
}
@Test
fun testDiamondImportWithSharing() {
val greeting = listOf("Hi from common", "Hi from middle", "sharedVar == 5")
val output = doDiamondImportTest(
ScriptEvaluationConfiguration {
enableScriptsInstancesSharing()
}
)
Assert.assertEquals(greeting, output)
}
private fun doDiamondImportTest(evaluationConfiguration: ScriptEvaluationConfiguration? = null): List<String> {
val mainScript = "sharedVar += 1\nprintln(\"sharedVar == \$sharedVar\")".toScriptSource("main.kts")
val middleScript = File(TEST_DATA_DIR, "importTest/diamondImportMiddle.kts").toScriptSource()
val commonScript = File(TEST_DATA_DIR, "importTest/diamondImportCommon.kts").toScriptSource()
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
refineConfiguration {
beforeCompiling { ctx ->
when (ctx.script.name) {
"main.kts" -> ScriptCompilationConfiguration(ctx.compilationConfiguration) {
importScripts(middleScript, commonScript)
}
"diamondImportMiddle.kts" -> ScriptCompilationConfiguration(ctx.compilationConfiguration) {
importScripts(commonScript)
}
else -> ctx.compilationConfiguration
}.asSuccess()
}
}
}
val output = captureOut {
BasicJvmScriptingHost().eval(mainScript, compilationConfiguration, evaluationConfiguration).throwOnFailure()
}.lines()
return output
}
@Test
fun testImportError() {
val script = "println(\"Hello from imported \$helloScriptName script!\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
refineConfiguration {
beforeCompiling { ctx ->
ScriptCompilationConfiguration(ctx.compilationConfiguration) {
importScripts(File(TEST_DATA_DIR, "missing_script.kts").toScriptSource())
}.asSuccess()
}
}
}
val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null)
assertTrue(res is ResultWithDiagnostics.Failure)
val report = res.reports.find { it.message.startsWith("Source file or directory not found") }
assertNotNull(report)
assertEquals("/script.kts", report?.sourcePath)
}
@Test
fun testCompileOptionsLanguageVersion() {
val script = "typealias MyInt = Int\nval x: MyInt = 3"
val compilationConfiguration1 = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
compilerOptions("-language-version", "1.0")
}
val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration1, null)
assertTrue(res is ResultWithDiagnostics.Failure)
res.reports.find { it.message.startsWith("The feature \"type aliases\" is only available since language version 1.1") }
?: fail("Error report about language version not found. Reported:\n ${res.reports.joinToString("\n ") { it.message }}")
}
@Test
fun testCompileOptionsNoStdlib() {
val script = "println(\"Hi\")"
val res1 = evalScriptWithConfiguration(script) {
compilerOptions("-no-stdlib")
}
assertTrue(res1 is ResultWithDiagnostics.Failure)
res1.reports.find { it.message.startsWith("Unresolved reference: println") }
?: fail("Expected unresolved reference report. Reported:\n ${res1.reports.joinToString("\n ") { it.message }}")
val res2 = evalScriptWithConfiguration(script) {
refineConfiguration {
beforeCompiling { ctx ->
ScriptCompilationConfiguration(ctx.compilationConfiguration) {
compilerOptions("-no-stdlib")
}.asSuccess()
}
}
}
// -no-stdlib in refined configuration has no effect
assertTrue(res2 is ResultWithDiagnostics.Success)
}
@Test
fun testIgnoredOptionsWarning() {
val script = "println(\"Hi\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
compilerOptions("-version", "-d", "destDir", "-Xreport-perf", "-no-reflect")
refineConfiguration {
beforeCompiling { ctx ->
ScriptCompilationConfiguration(ctx.compilationConfiguration) {
compilerOptions.append("-no-jdk", "-version", "-no-stdlib", "-Xdump-perf", "-no-inline")
}.asSuccess()
}
}
}
val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null)
assertTrue(res is ResultWithDiagnostics.Success)
assertNotNull(res.reports.find { it.message == "The following compiler arguments are ignored on script compilation: -version, -d, -Xreport-perf" })
assertNotNull(res.reports.find { it.message == "The following compiler arguments are ignored on script compilation: -Xdump-perf" })
assertNotNull(res.reports.find { it.message == "The following compiler arguments are ignored when configured from refinement callbacks: -no-jdk, -no-stdlib" })
}
@Test
fun testMemoryCache() {
val script = "val x = 1\nprintln(\"x = \$x\")"
val expectedOutput = listOf("x = 1")
val cache = SimpleMemoryScriptsCache()
checkWithCache(cache, script, expectedOutput)
}
@Test
fun testSimpleImportWithMemoryCache() {
val script = "println(\"Hello from imported \$helloScriptName script!\")"
val expectedOutput = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!")
val cache = SimpleMemoryScriptsCache()
checkWithCache(cache, script, expectedOutput) { makeSimpleConfigurationWithTestImport() }
}
@Test
fun testFileCache() {
val script = "val x = 1\nprintln(\"x = \$x\")"
val expectedOutput = listOf("x = 1")
val cacheDir = Files.createTempDirectory("scriptingTestCache").toFile()
try {
val cache = FileBasedScriptCache(cacheDir)
Assert.assertTrue(cache.baseDir.listFiles().isEmpty())
checkWithCache(cache, script, expectedOutput)
} finally {
cacheDir.deleteRecursively()
}
}
@Test
fun testSimpleImportWithFileCache() {
val script = "println(\"Hello from imported \$helloScriptName script!\")"
val expectedOutput = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!")
val cacheDir = Files.createTempDirectory("scriptingTestCache").toFile()
try {
val cache = FileBasedScriptCache(cacheDir)
Assert.assertTrue(cache.baseDir.listFiles().isEmpty())
checkWithCache(cache, script, expectedOutput) { makeSimpleConfigurationWithTestImport() }
} finally {
cacheDir.deleteRecursively()
}
}
private fun checkWithCache(
cache: ScriptingCacheWithCounters, script: String, expectedOutput: List<String>,
configurationBuilder: ScriptCompilationConfiguration.Builder.() -> Unit = {}
) {
val compiler = JvmScriptCompiler(defaultJvmScriptingHostConfiguration, cache = cache)
val evaluator = BasicJvmScriptEvaluator()
val host = BasicJvmScriptingHost(compiler = compiler, evaluator = evaluator)
val scriptCompilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>(body = configurationBuilder)
Assert.assertEquals(0, cache.storedScripts)
var compiledScript: CompiledScript<*>? = null
val output = captureOut {
runBlocking {
compiler(script.toScriptSource(), scriptCompilationConfiguration).onSuccess {
compiledScript = it
evaluator(it)
}.throwOnFailure()
}
}.lines()
Assert.assertEquals(expectedOutput, output)
Assert.assertEquals(1, cache.storedScripts)
Assert.assertEquals(0, cache.retrievedScripts)
val cachedScript = cache.get(script.toScriptSource(), scriptCompilationConfiguration)
Assert.assertNotNull(cachedScript)
Assert.assertEquals(1, cache.retrievedScripts)
val compiledScriptClassRes = runBlocking { compiledScript!!.getClass(null) }
val cachedScriptClassRes = runBlocking { cachedScript!!.getClass(null) }
val compiledScriptClass = compiledScriptClassRes.resultOrNull()
val cachedScriptClass = cachedScriptClassRes.resultOrNull()
Assert.assertEquals(compiledScriptClass!!.qualifiedName, cachedScriptClass!!.qualifiedName)
Assert.assertEquals(compiledScriptClass!!.supertypes, cachedScriptClass!!.supertypes)
val output2 = captureOut {
runBlocking {
evaluator(cachedScript!!).throwOnFailure()
}
}.lines()
Assert.assertEquals(output, output2)
val output3 = captureOut { evalScriptWithConfiguration(script, host, configurationBuilder).throwOnFailure() }.lines()
Assert.assertEquals(2, cache.retrievedScripts)
Assert.assertEquals(output, output3)
}
@Test
fun testCompiledScriptClassLoader() {
val script = "val x = 1"
val scriptCompilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
val compiler = JvmScriptCompiler(defaultJvmScriptingHostConfiguration)
val compiledScript = runBlocking {
val res = compiler(script.toScriptSource(), scriptCompilationConfiguration).throwOnFailure()
(res as ResultWithDiagnostics.Success<CompiledScript<*>>).value
}
val compiledScriptClass = runBlocking { compiledScript.getClass(null).throwOnFailure().resultOrNull()!! as KClass<*> }
val classLoader = compiledScriptClass.java.classLoader
Assert.assertTrue(classLoader is CompiledScriptClassLoader)
val anotherClass = classLoader.loadClass(compiledScriptClass.qualifiedName)
Assert.assertEquals(compiledScriptClass.java, anotherClass)
val classResourceName = compiledScriptClass.qualifiedName!!.replace('.', '/') + ".class"
val classAsResourceUrl = classLoader.getResource(classResourceName)
val classAssResourceStream = classLoader.getResourceAsStream(classResourceName)
Assert.assertNotNull(classAsResourceUrl)
Assert.assertNotNull(classAssResourceStream)
val classAsResourceData = classAsResourceUrl.openConnection().getInputStream().readBytes()
val classAsResourceStreamData = classAssResourceStream.readBytes()
Assert.assertArrayEquals(classAsResourceData, classAsResourceStreamData)
// TODO: consider testing getResources as well
}
}
fun <T> ResultWithDiagnostics<T>.throwOnFailure(): ResultWithDiagnostics<T> = apply {
if (this is ResultWithDiagnostics.Failure) {
val firstExceptionFromReports = reports.find { it.exception != null }?.exception
throw Exception(
"Compilation/evaluation failed:\n ${reports.joinToString("\n ") { it.exception?.toString() ?: it.message }}",
firstExceptionFromReports
)
}
}
private fun evalScript(script: String, host: BasicScriptingHost = BasicJvmScriptingHost()): ResultWithDiagnostics<*> =
evalScriptWithConfiguration(script, host)
private fun evalScriptWithConfiguration(
script: String,
host: BasicScriptingHost = BasicJvmScriptingHost(),
body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ResultWithDiagnostics<EvaluationResult> {
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>(body = body)
return host.eval(script.toScriptSource(), compilationConfiguration, null)
}
private interface ScriptingCacheWithCounters : CompiledJvmScriptsCache {
val storedScripts: Int
val retrievedScripts: Int
}
private class SimpleMemoryScriptsCache : ScriptingCacheWithCounters {
internal val data = hashMapOf<Pair<SourceCode, ScriptCompilationConfiguration>, CompiledScript<*>>()
private var _storedScripts = 0
private var _retrievedScripts = 0
override val storedScripts: Int
get() = _storedScripts
override val retrievedScripts: Int
get() = _retrievedScripts
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript<*>? =
data[script to scriptCompilationConfiguration]?.also { _retrievedScripts++ }
override fun store(
compiledScript: CompiledScript<*>,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
data[script to scriptCompilationConfiguration] = compiledScript
_storedScripts++
}
}
private fun ScriptCompilationConfiguration.Builder.makeSimpleConfigurationWithTestImport() {
refineConfiguration {
beforeCompiling { ctx ->
val importedScript = File(ScriptingHostTest.TEST_DATA_DIR, "importTest/helloWithVal.kts")
if ((ctx.script as? FileScriptSource)?.file?.canonicalFile == importedScript.canonicalFile) {
ctx.compilationConfiguration
} else {
ScriptCompilationConfiguration(ctx.compilationConfiguration) {
importScripts(importedScript.toScriptSource())
}
}.asSuccess()
}
}
}
private fun File.readCompiledScript(scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript<*> {
return inputStream().use { fs ->
ObjectInputStream(fs).use {
it.readObject() as KJvmCompiledScript<*>
}
}
}
private fun ByteArray.toHexString(): String = joinToString("", transform = { "%02x".format(it) })
private class FileBasedScriptCache(val baseDir: File) : ScriptingCacheWithCounters {
internal fun uniqueHash(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): String {
val digestWrapper = MessageDigest.getInstance("MD5")
digestWrapper.update(script.text.toByteArray())
scriptCompilationConfiguration.entries().sortedBy { it.key.name }.forEach {
digestWrapper.update(it.key.name.toByteArray())
digestWrapper.update(it.value.toString().toByteArray())
}
return digestWrapper.digest().toHexString()
}
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript<*>? {
val file = File(baseDir, uniqueHash(script, scriptCompilationConfiguration))
return if (!file.exists()) null else file.readCompiledScript(scriptCompilationConfiguration)?.also { _retrievedScripts++ }
}
override fun store(
compiledScript: CompiledScript<*>,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
) {
val file = File(baseDir, uniqueHash(script, scriptCompilationConfiguration))
file.outputStream().use { fs ->
ObjectOutputStream(fs).use { os ->
os.writeObject(compiledScript)
}
}
_storedScripts++
}
private var _storedScripts = 0
private var _retrievedScripts = 0
override val storedScripts: Int
get() = _storedScripts
override val retrievedScripts: Int
get() = _retrievedScripts
}
internal fun captureOut(body: () -> Unit): String = captureOutAndErr(body).first
internal fun captureOutAndErr(body: () -> Unit): Pair<String, String> {
val outStream = ByteArrayOutputStream()
val errStream = ByteArrayOutputStream()
val prevOut = System.out
val prevErr = System.err
System.setOut(PrintStream(outStream))
System.setErr(PrintStream(errStream))
try {
body()
} finally {
System.out.flush()
System.err.flush()
System.setOut(prevOut)
System.setErr(prevErr)
}
return outStream.toString().trim() to errStream.toString().trim()
}
@@ -1,61 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.test
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
@KotlinScript(fileExtension = "withboth.kts", compilationConfiguration = ReceiverAndPropertiesConfiguration::class)
abstract class ScriptWithBoth
@KotlinScript(fileExtension = "withproperties.kts", compilationConfiguration = ProvidedPropertiesConfiguration::class)
abstract class ScriptWithProvidedProperties
@KotlinScript(fileExtension = "withreceiver.kts", compilationConfiguration = ImplicitReceiverConfiguration::class)
abstract class ScriptWithImplicitReceiver
object ReceiverAndPropertiesConfiguration : ScriptCompilationConfiguration(
{
jvm { dependenciesFromCurrentContext(wholeClasspath = true) }
providedProperties("providedString" to String::class)
implicitReceivers(ImplicitReceiverClass::class)
}
)
object ProvidedPropertiesConfiguration : ScriptCompilationConfiguration(
{
jvm { dependenciesFromCurrentContext(wholeClasspath = true) }
providedProperties("providedString" to String::class)
}
)
object ImplicitReceiverConfiguration : ScriptCompilationConfiguration(
{
jvm { dependenciesFromCurrentContext(wholeClasspath = true) }
implicitReceivers(ImplicitReceiverClass::class)
}
)
class ImplicitReceiverClass(val receiverString: String)
inline fun <reified T : Any> evalString(
source: String,
noinline configure: ScriptEvaluationConfiguration.Builder.() -> Unit
): ResultWithDiagnostics<EvaluationResult> {
val actualConfiguration = createJvmCompilationConfigurationFromTemplate<T>()
return BasicJvmScriptingHost()
.eval(source.toScriptSource(), actualConfiguration, ScriptEvaluationConfiguration(configure))
}
@@ -1,4 +0,0 @@
println("Hi from common")
var sharedVar = 2
@@ -1,4 +0,0 @@
println("Hi from middle")
sharedVar *= 2
@@ -1,4 +0,0 @@
val helloScriptName = "helloWithVal"
println("Hello from $helloScriptName script!")
@@ -1,5 +0,0 @@
class RequiredClass {
val value = "required"
}