Add repeated local import regression coverage

This commit is contained in:
Sergey Chernov 2026-04-12 11:11:32 +03:00
parent 47eb2d7d61
commit ab39110834
3 changed files with 317 additions and 20 deletions

View File

@ -1,40 +1,92 @@
package net.sergeych package net.sergeych
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjString
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.nio.file.Files import java.nio.file.Files
import kotlin.io.path.writeText import kotlin.io.path.writeText
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals
class CliLocalModuleImportRegressionJvmTest { class CliLocalModuleImportRegressionJvmTest {
private fun writeTransitiveImportTree(root: java.nio.file.Path) {
val packageDir = Files.createDirectories(root.resolve("package1"))
val nestedDir = Files.createDirectories(packageDir.resolve("nested"))
packageDir.resolve("alpha.lyng").writeText(
"""
package package1.alpha
import lyng.stdlib
import lyng.io.net
class Alpha {
val headers = Map<String, String>()
fun makeTask(port: Int, host: String): Deferred = launch {
host + ":" + port
}
fun netModule() = Net
}
fun alphaValue() = "alpha"
""".trimIndent()
)
packageDir.resolve("beta.lyng").writeText(
"""
package package1.beta
import lyng.stdlib
import package1.alpha
fun betaValue() = alphaValue() + "|beta"
""".trimIndent()
)
nestedDir.resolve("gamma.lyng").writeText(
"""
package package1.nested.gamma
import lyng.io.net
import package1.alpha
import package1.beta
val String.gammaTag get() = this + "|gamma"
fun gammaValue() = betaValue().gammaTag
fun netModule() = Net
""".trimIndent()
)
packageDir.resolve("entry.lyng").writeText(
"""
package package1.entry
import lyng.stdlib
import lyng.io.net
import package1.alpha
import package1.beta
import package1.nested.gamma
fun report() = gammaValue() + "|entry"
""".trimIndent()
)
}
@Test @Test
fun localModuleUsingLaunchAndNetImportsWithoutStdlibRedefinition() = runBlocking { fun localModuleUsingLaunchAndNetImportsWithoutStdlibRedefinition() = runBlocking {
val root = Files.createTempDirectory("lyng-cli-import-regression") val root = Files.createTempDirectory("lyng-cli-import-regression")
try { try {
val packageDir = Files.createDirectories(root.resolve("package1"))
val mainFile = root.resolve("main.lyng") val mainFile = root.resolve("main.lyng")
val alphaFile = packageDir.resolve("alpha.lyng") writeTransitiveImportTree(root)
mainFile.writeText( mainFile.writeText(
""" """
import package1.alpha import package1.entry
import package1.beta
import package1.nested.gamma
println("ok") println(report())
""".trimIndent()
)
alphaFile.writeText(
"""
import lyng.io.net
class Alpha {
val headers = Map<String, String>()
fn startListen(port, host) {
launch {
println(port, host)
}
}
}
""".trimIndent() """.trimIndent()
) )
@ -43,4 +95,43 @@ class CliLocalModuleImportRegressionJvmTest {
root.toFile().deleteRecursively() root.toFile().deleteRecursively()
} }
} }
@Test
fun localModuleImportsAreNoOpsWhenEvaldRepeatedlyOnSameCliContext() = runBlocking {
val root = Files.createTempDirectory("lyng-cli-import-regression-repeat")
try {
val mainFile = root.resolve("main.lyng")
writeTransitiveImportTree(root)
mainFile.writeText("println(\"bootstrap\")")
val session = EvalSession(newCliScope(emptyList(), mainFile.toString()))
try {
repeat(5) { index ->
val result = evalOnCliDispatcher(
session,
Source(
"<repeat-local-import-$index>",
"""
import package1.entry
import package1.nested.gamma
import package1.beta
import package1.alpha
report()
""".trimIndent()
)
) as ObjString
assertEquals(
"alpha|beta|gamma|entry",
result.value
)
}
} finally {
session.cancelAndJoin()
}
} finally {
root.toFile().deleteRecursively()
}
}
} }

View File

@ -64,6 +64,73 @@ class CliLocalImportsJvmTest {
return CliResult(outBuf.toString("UTF-8"), errBuf.toString("UTF-8"), exitCode) return CliResult(outBuf.toString("UTF-8"), errBuf.toString("UTF-8"), exitCode)
} }
private fun writeTransitiveImportTree(root: java.nio.file.Path) {
val packageDir = Files.createDirectories(root.resolve("package1"))
val nestedDir = Files.createDirectories(packageDir.resolve("nested"))
Files.writeString(
packageDir.resolve("alpha.lyng"),
"""
package package1.alpha
import lyng.stdlib
import lyng.io.net
class Alpha {
val headers = Map<String, String>()
fun makeTask(port: Int, host: String): Deferred = launch {
host + ":" + port
}
fun netModule() = Net
}
fun alphaValue() = "alpha"
""".trimIndent()
)
Files.writeString(
packageDir.resolve("beta.lyng"),
"""
package package1.beta
import lyng.stdlib
import package1.alpha
fun betaValue() = alphaValue() + "|beta"
""".trimIndent()
)
Files.writeString(
nestedDir.resolve("gamma.lyng"),
"""
package package1.nested.gamma
import lyng.io.net
import package1.alpha
import package1.beta
val String.gammaTag get() = this + "|gamma"
fun gammaValue() = betaValue().gammaTag
fun netModule() = Net
""".trimIndent()
)
Files.writeString(
packageDir.resolve("entry.lyng"),
"""
package package1.entry
import lyng.stdlib
import lyng.io.net
import package1.alpha
import package1.beta
import package1.nested.gamma
fun report() = gammaValue() + "|entry"
""".trimIndent()
)
}
@Test @Test
fun cliDiscoversSiblingAndNestedLocalImportsFromEntryRoot() { fun cliDiscoversSiblingAndNestedLocalImportsFromEntryRoot() {
val dir = Files.createTempDirectory("lyng_cli_local_imports_") val dir = Files.createTempDirectory("lyng_cli_local_imports_")
@ -134,4 +201,37 @@ class CliLocalImportsJvmTest {
dir.toFile().deleteRecursively() dir.toFile().deleteRecursively()
} }
} }
@Test
fun cliHandlesOverlappingDirectoryImportsWithTransitiveStdlibAndNetSymbols() {
val dir = Files.createTempDirectory("lyng_cli_local_imports_transitive_")
try {
val mainFile = dir.resolve("main.lyng")
writeTransitiveImportTree(dir)
Files.writeString(
mainFile,
"""
import package1.entry
import package1.beta
import package1.nested.gamma
println(report())
println(gammaValue())
""".trimIndent()
)
val result = runCli(mainFile.toString())
assertTrue(result.err, result.err.isBlank())
assertTrue(
result.out,
result.out.contains("alpha|beta|gamma|entry")
)
assertTrue(
result.out,
result.out.contains("alpha|beta|gamma")
)
} finally {
dir.toFile().deleteRecursively()
}
}
} }

View File

@ -16,17 +16,60 @@
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertSame
class ScriptImportPreparationTest { class ScriptImportPreparationTest {
private fun nestedImportSources(prefix: String): Array<String> =
arrayOf(
"""
package $prefix.alpha
class Alpha {
val headers = Map<String, String>()
fun tagged(port: Int, host: String): String {
val task: Deferred = launch {
host + ":" + port + ":" + headers.size
}
return task.await()
}
}
fun alphaValue() = Alpha().tagged(7, "alpha")
""".trimIndent(),
"""
package $prefix.beta
import $prefix.alpha
fun betaValue() = alphaValue() + "|" + Alpha().tagged(8, "beta")
""".trimIndent(),
"""
package $prefix.gamma
import $prefix.alpha
import $prefix.beta
val String.gammaTag get() = this + ":gamma"
fun gammaValue() = betaValue() + "|" + "done".gammaTag
""".trimIndent()
)
private fun nestedImportManager(prefix: String = "tree"): ImportManager =
Script.defaultImportManager.copy().apply {
addTextPackages(*nestedImportSources(prefix))
}
@Test @Test
fun scriptImportIntoExplicitlyPreparesExistingScope() = runTest { fun scriptImportIntoExplicitlyPreparesExistingScope() = runTest {
val manager = ImportManager() val manager = ImportManager()
@ -84,4 +127,67 @@ class ScriptImportPreparationTest {
val record = assertNotNull(module["answer"]) val record = assertNotNull(module["answer"])
assertEquals(42, module.resolve(record, "answer").toInt()) assertEquals(42, module.resolve(record, "answer").toInt())
} }
@Test
fun repeatedImportIntoOnSameScopeIsIdempotentForNestedPackageGraph() = runTest {
val manager = nestedImportManager()
val script = Compiler.compile(
Source(
"<repeat-import-into>",
"""
import tree.gamma
import tree.beta
import tree.alpha
gammaValue()
""".trimIndent()
),
manager
)
val scope = manager.newStdScope()
script.importInto(scope)
val importedGammaValue = assertNotNull(scope["gammaValue"])
val importedAlpha = assertNotNull(scope["Alpha"])
repeat(5) {
script.importInto(scope)
}
assertSame(importedGammaValue, scope["gammaValue"])
assertSame(importedAlpha, scope["Alpha"])
assertEquals(
"alpha:7:0|beta:8:0|done:gamma",
(script.execute(scope) as ObjString).value
)
}
@Test
fun repeatedEvalOnSameSessionCanReimportNestedPackageGraph() = runTest {
val prefix = "repeattree"
val manager = nestedImportManager(prefix)
val scope = manager.newModule()
val session = EvalSession(scope)
try {
repeat(5) { index ->
val result = session.eval(
Source(
"<repeat-eval-$index>",
"""
import $prefix.gamma
import $prefix.beta
import $prefix.alpha
gammaValue()
""".trimIndent()
)
) as ObjString
assertEquals("alpha:7:0|beta:8:0|done:gamma", result.value)
}
} finally {
session.cancelAndJoin()
}
}
} }