Don't create default importing scopes for REPL snippets

Default scopes should be created only once, for the first snippet.
All following snippets should not create new default importing scopes.
#KT-35651 fixed
This commit is contained in:
Ilya Muradyan
2020-06-04 14:17:43 +03:00
committed by Ilya Chernikov
parent c3cbfe34c4
commit 743abea690
7 changed files with 160 additions and 9 deletions
@@ -20,6 +20,8 @@ dependencies {
allTestsRuntime(commonDep("junit"))
testCompile(project(":kotlin-scripting-ide-services-unshaded"))
testCompile(project(":kotlin-scripting-compiler"))
testCompile(project(":kotlin-scripting-dependencies"))
testCompile(project(":kotlin-main-kts"))
testCompile(project(":compiler:cli-common"))
testRuntimeOnly(project(":kotlin-compiler"))
@@ -7,15 +7,19 @@ package org.jetbrains.kotlin.scripting.ide_services
import junit.framework.TestCase
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocationWithRange
import org.jetbrains.kotlin.scripting.ide_services.test_util.*
import org.jetbrains.kotlin.scripting.ide_services.test_util.JvmTestRepl
import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl
import java.io.File
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.script.experimental.util.get
import kotlin.script.experimental.jvm.util.isError
import kotlin.script.experimental.jvm.util.isIncomplete
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
// Adapted form GenericReplTest
@@ -268,6 +272,50 @@ class JvmIdeServicesTest : TestCase() {
)
}
}
fun testDependency() {
val resolver = ScriptDependenciesResolver()
val conf = ScriptCompilationConfiguration {
jvm {
updateClasspath(scriptCompilationClasspathFromContext("test", classLoader = DependsOn::class.java.classLoader))
}
defaultImports(DependsOn::class)
refineConfiguration {
onAnnotations(DependsOn::class, handler = { configureMavenDepsOnAnnotations(it, resolver) })
}
}
JvmTestRepl(conf)
.use { repl ->
/*
The only source file in test.jar contains following code:
package example.dependency
infix fun String.to(that: String) = this + that
*/
assertEvalUnit(
repl, """
@file:DependsOn("plugins/scripting/scripting-ide-services-test/testData/KT-35651-test.jar")
import example.dependency.*
val x = listOf<String>()
""".trimIndent()
)
// This snippet is needed to be evaluated to ensure that importing scopes were created
// (but default ones were not)
assertEvalUnit(
repl, """
import kotlin.math.*
val y = listOf<String>()
""".trimIndent()
)
assertEvalResult(repl, """ "a" to "a" """, "aa")
}
}
}
class LegacyReplTestLong : TestCase() {
@@ -332,6 +332,27 @@ class ReplCompletionAndErrorsAnalysisTest : TestCase() {
run(setupDefaultImportsCompletionRun)
}
@Test
fun testLongCompilationsWithImport() = test {
// This test normally completes in about 5-10s
// Log should show slow _linear_ compilation time growth
val compileWriter = System.out.writer()
for (i in 1..120) {
run {
code = """
import kotlin.math.*
val dataFrame = mapOf("x" to sin(3.0))
val e = "str"
""".trimIndent()
doCompile
loggingInfo = CSVLoggingInfo(compile = CSVLoggingInfoItem(compileWriter, i, "compile;"))
}
}
}
@Test
fun testLongRunningCompilationWithReceiver() = test {
// This test normally completes in about 8-13s
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2020 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 org.jetbrains.kotlin.scripting.ide_services.test_util
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.mainKts.impl.IvyResolver
import java.io.File
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.*
import kotlin.script.experimental.jvm.withUpdatedClasspath
// in case of flat or direct resolvers the value should be a direct path or file name of a jar respectively
// in case of maven resolver the maven coordinates string is accepted (resolved with com.jcabi.aether library)
@Target(AnnotationTarget.FILE)
@Repeatable
@Retention(AnnotationRetention.SOURCE)
annotation class DependsOn(val value: String = "")
open class ScriptDependenciesResolver {
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), IvyResolver())
private val addedClasspath = mutableListOf<File>()
fun resolveFromAnnotations(script: ScriptContents): ResultWithDiagnostics<List<File>> {
val scriptDiagnostics = mutableListOf<ScriptDiagnostic>()
val classpath = mutableListOf<File>()
script.annotations.forEach { annotation ->
when (annotation) {
is DependsOn -> {
try {
when (val result = runBlocking { resolver.resolve(annotation.value) }) {
is ResultWithDiagnostics.Failure -> {
val diagnostics = ScriptDiagnostic(
ScriptDiagnostic.unspecifiedError,
"Failed to resolve ${annotation.value}:\n" + result.reports.joinToString("\n") { it.message })
scriptDiagnostics.add(diagnostics)
}
is ResultWithDiagnostics.Success -> {
addedClasspath.addAll(result.value)
classpath.addAll(result.value)
}
}
} catch (e: Exception) {
val diagnostic =
ScriptDiagnostic(ScriptDiagnostic.unspecifiedError, "Unhandled exception during resolve", exception = e)
scriptDiagnostics.add(diagnostic)
}
}
else -> throw Exception("Unknown annotation ${annotation.javaClass}")
}
}
return if (scriptDiagnostics.isEmpty()) classpath.asSuccess()
else makeFailureResult(scriptDiagnostics)
}
}
fun configureMavenDepsOnAnnotations(
context: ScriptConfigurationRefinementContext,
resolver: ScriptDependenciesResolver
): ResultWithDiagnostics<ScriptCompilationConfiguration> {
val annotations = context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.takeIf { it.isNotEmpty() }
?: return context.compilationConfiguration.asSuccess()
val scriptContents = object : ScriptContents {
override val annotations: Iterable<Annotation> = annotations
override val file: File? = null
override val text: CharSequence? = null
}
return try {
resolver.resolveFromAnnotations(scriptContents)
.onSuccess { classpath ->
context.compilationConfiguration
.let { if (classpath.isEmpty()) it else it.withUpdatedClasspath(classpath) }
.asSuccess()
}
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(e.asDiagnostics(path = context.script.locationId))
}
}
@@ -9,20 +9,17 @@ import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
import java.io.Closeable
import java.util.concurrent.atomic.AtomicInteger
import kotlin.script.experimental.api.CompiledSnippet
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.valueOrNull
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
import kotlin.script.experimental.util.LinkedSnippet
import kotlin.script.experimental.jvm.util.toSourceCodePosition
internal class JvmTestRepl : Closeable {
internal class JvmTestRepl (
private val compileConfiguration: ScriptCompilationConfiguration = simpleScriptCompilationConfiguration,
private val evalConfiguration: ScriptEvaluationConfiguration = simpleScriptEvaluationConfiguration,
) : Closeable {
private val currentLineCounter = AtomicInteger(0)
private val compileConfiguration = simpleScriptCompilationConfiguration
private val evalConfiguration = simpleScriptEvaluationConfiguration
fun nextCodeLine(code: String): SourceCode =
SourceCodeTestImpl(
currentLineCounter.getAndIncrement(),