Move caching tests into a dedicated class
plus some tests refactorings
This commit is contained in:
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.nio.file.Files
|
||||
import java.security.MessageDigest
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
|
||||
import kotlin.script.experimental.jvmhost.*
|
||||
import kotlin.script.templates.standard.SimpleScriptTemplate
|
||||
|
||||
class CachingTest : TestCase() {
|
||||
|
||||
val simpleScript = "val x = 1\nprintln(\"x = \$x\")"
|
||||
val simpleScriptExpectedOutput = listOf("x = 1")
|
||||
|
||||
val scriptWithImport = "println(\"Hello from imported \$helloScriptName script!\")"
|
||||
val scriptWithImportExpectedOutput = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!")
|
||||
|
||||
@Test
|
||||
fun testMemoryCache() {
|
||||
val cache = SimpleMemoryScriptsCache()
|
||||
checkWithCache(cache, simpleScript, simpleScriptExpectedOutput)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleImportWithMemoryCache() {
|
||||
val cache = SimpleMemoryScriptsCache()
|
||||
checkWithCache(cache, scriptWithImport, scriptWithImportExpectedOutput) { makeSimpleConfigurationWithTestImport() }
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testFileCache() {
|
||||
withTempDir("scriptingTestCache") { cacheDir ->
|
||||
val cache = FileBasedScriptCache(cacheDir)
|
||||
Assert.assertTrue(cache.baseDir.listFiles().isEmpty())
|
||||
|
||||
checkWithCache(cache, simpleScript, simpleScriptExpectedOutput)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleImportWithFileCache() {
|
||||
withTempDir("scriptingTestCache") { cacheDir ->
|
||||
val cache = FileBasedScriptCache(cacheDir)
|
||||
Assert.assertTrue(cache.baseDir.listFiles().isEmpty())
|
||||
|
||||
checkWithCache(cache, scriptWithImport, scriptWithImportExpectedOutput) { makeSimpleConfigurationWithTestImport() }
|
||||
}
|
||||
}
|
||||
|
||||
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.valueOrNull()
|
||||
val cachedScriptClass = cachedScriptClassRes.valueOrNull()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 class FileBasedScriptCache(val baseDir: File) : ScriptingCacheWithCounters {
|
||||
|
||||
override fun get(script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration): CompiledScript<*>? {
|
||||
val file = File(baseDir, uniqueScriptHash(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, uniqueScriptHash(script, scriptCompilationConfiguration))
|
||||
file.outputStream().use { fs ->
|
||||
ObjectOutputStream(fs).use { os ->
|
||||
os.writeObject(compiledScript)
|
||||
}
|
||||
}
|
||||
storedScripts++
|
||||
}
|
||||
|
||||
override var storedScripts: Int = 0
|
||||
private set
|
||||
|
||||
override var retrievedScripts: Int = 0
|
||||
private set
|
||||
}
|
||||
|
||||
internal fun uniqueScriptHash(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()
|
||||
}
|
||||
|
||||
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 fun <R> withTempDir(keyName: String = "tmp", body: (File) -> R) {
|
||||
val tempDir = Files.createTempDirectory(keyName).toFile()
|
||||
try {
|
||||
body(tempDir)
|
||||
} finally {
|
||||
tempDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
+21
-201
@@ -18,7 +18,6 @@ import java.io.*
|
||||
import java.lang.RuntimeException
|
||||
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
|
||||
@@ -26,7 +25,6 @@ import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.BasicScriptingHost
|
||||
import kotlin.script.experimental.host.FileBasedScriptSource
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
|
||||
import kotlin.script.experimental.jvm.updateClasspath
|
||||
@@ -162,21 +160,7 @@ class ScriptingHostTest : TestCase() {
|
||||
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()
|
||||
val outputFromProcess = runScriptFromJar(outJar)
|
||||
Assert.assertEquals(greeting, outputFromProcess)
|
||||
}
|
||||
|
||||
@@ -356,103 +340,6 @@ class ScriptingHostTest : TestCase() {
|
||||
jvmTargetTestImpl("9", 53)
|
||||
}
|
||||
|
||||
@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.valueOrNull()
|
||||
val cachedScriptClass = cachedScriptClassRes.valueOrNull()
|
||||
|
||||
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"
|
||||
@@ -486,6 +373,24 @@ class ScriptingHostTest : TestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun runScriptFromJar(jar: File): String {
|
||||
val javaExecutable = File(File(System.getProperty("java.home"), "bin"), "java")
|
||||
val args = listOf(javaExecutable.absolutePath, "-jar", jar.path)
|
||||
val processBuilder = ProcessBuilder(args)
|
||||
processBuilder.redirectErrorStream(true)
|
||||
return 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()
|
||||
}
|
||||
|
||||
fun <T> ResultWithDiagnostics<T>.throwOnFailure(): ResultWithDiagnostics<T> = apply {
|
||||
if (this is ResultWithDiagnostics.Failure) {
|
||||
val firstExceptionFromReports = reports.find { it.exception != null }?.exception
|
||||
@@ -506,7 +411,7 @@ private fun evalScriptWithResult(
|
||||
): ResultValue =
|
||||
evalScriptWithConfiguration(script, host, body).throwOnFailure().valueOrNull()!!.returnValue
|
||||
|
||||
private fun evalScriptWithConfiguration(
|
||||
internal fun evalScriptWithConfiguration(
|
||||
script: String,
|
||||
host: BasicScriptingHost = BasicJvmScriptingHost(),
|
||||
body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
|
||||
@@ -515,40 +420,7 @@ private fun evalScriptWithConfiguration(
|
||||
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() {
|
||||
internal fun ScriptCompilationConfiguration.Builder.makeSimpleConfigurationWithTestImport() {
|
||||
refineConfiguration {
|
||||
beforeCompiling { ctx ->
|
||||
val importedScript = File(ScriptingHostTest.TEST_DATA_DIR, "importTest/helloWithVal.kts")
|
||||
@@ -563,58 +435,6 @@ private fun ScriptCompilationConfiguration.Builder.makeSimpleConfigurationWithTe
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
Reference in New Issue
Block a user