
org.jetbrains.kotlinx.jupyter.test.repl.CustomLibraryResolverTests.kt Maven / Gradle / Ivy
package org.jetbrains.kotlinx.jupyter.test.repl
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.kotest.matchers.types.shouldBeTypeOf
import jupyter.kotlin.receivers.TempAnnotation
import jupyter.kotlin.variablesReport
import kotlinx.serialization.SerializationException
import org.jetbrains.kotlinx.jupyter.api.DisplayResult
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion.Companion.toMaybeUnspecifiedString
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
import org.jetbrains.kotlinx.jupyter.api.MimeTypes
import org.jetbrains.kotlinx.jupyter.api.VariableDeclaration
import org.jetbrains.kotlinx.jupyter.api.declare
import org.jetbrains.kotlinx.jupyter.api.libraries.ResourceType
import org.jetbrains.kotlinx.jupyter.api.textResult
import org.jetbrains.kotlinx.jupyter.defaultRuntimeProperties
import org.jetbrains.kotlinx.jupyter.exceptions.LibraryProblemPart
import org.jetbrains.kotlinx.jupyter.exceptions.ReplEvalRuntimeException
import org.jetbrains.kotlinx.jupyter.exceptions.ReplException
import org.jetbrains.kotlinx.jupyter.exceptions.ReplLibraryException
import org.jetbrains.kotlinx.jupyter.exceptions.ReplPreprocessingException
import org.jetbrains.kotlinx.jupyter.libraries.parseLibraryDescriptor
import org.jetbrains.kotlinx.jupyter.test.evalRaw
import org.jetbrains.kotlinx.jupyter.test.evalRendered
import org.jetbrains.kotlinx.jupyter.test.library
import org.jetbrains.kotlinx.jupyter.test.toLibraries
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import java.io.File
import kotlin.reflect.typeOf
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class CustomLibraryResolverTests : AbstractReplTest() {
@Test
fun testUseMagic() {
val lib1 = "mylib" to """
{
"properties": {
"v1": "0.2"
},
"dependencies": [
"artifact1:${'$'}v1",
"artifact2:${'$'}v1"
],
"imports": [
"package1",
"package2"
],
"init": [
"code1",
"code2"
]
}
""".trimIndent()
val lib2 = "other" to """
{
"properties": {
"a": "temp",
"b": "test"
},
"repositories": [
"repo-${'$'}a"
],
"dependencies": [
"path-${'$'}b"
],
"imports": [
"otherPackage"
],
"init": [
"otherInit"
]
}
""".trimIndent()
val lib3 = "another" to """
{
"properties": {
"v": "1"
},
"dependencies": [
"anotherDep"
],
"imports": [
"anotherPackage${'$'}v"
],
"init": [
"%use other(b=release, a=debug)",
"anotherInit"
]
}
""".trimIndent()
val libs = listOf(lib1, lib2, lib3).toLibraries()
val executor = makeReplWithLibraries(libs).mockExecution()
executor.execute("%use mylib(1.0), another")
val executedCodes = executor.executedCodes
executedCodes.forEach(::println)
val expectedCodes = arrayOf(
"""
@file:DependsOn("artifact1:1.0")
@file:DependsOn("artifact2:1.0")
import package1
import package2
""",
"code1",
"code2",
"""
@file:DependsOn("anotherDep")
import anotherPackage1
""",
"""
@file:Repository("repo-debug")
@file:DependsOn("path-release")
import otherPackage
""",
"otherInit",
"anotherInit",
).map { it.trimIndent() }
executedCodes shouldBe expectedCodes
}
@Test
fun testLibraryOnShutdown() {
val lib1 = "mylib" to """
{
"shutdown": [
"14 * 3",
"throw RuntimeException()",
"21 + 22"
]
}
""".trimIndent()
val lib2 = "mylib2" to """
{
"shutdown": [
"100"
]
}
""".trimIndent()
val libs = listOf(lib1, lib2).toLibraries()
val replWithResolver = makeReplWithLibraries(libs)
replWithResolver.evalRaw("%use mylib, mylib2")
val results = replWithResolver.evalOnShutdown()
assertEquals(4, results.size)
assertEquals(42, results[0].resultValue)
assertNull(results[1].resultValue)
assertEquals(43, results[2].resultValue)
assertEquals(100, results[3].resultValue)
}
@Test
fun testTransitiveRendering() {
val lib = "mylib" to """
{
"renderers": {
"java.lang.String": "HTML(\"\" + ${"$"}it + \"\")"
}
}
""".trimIndent()
val libs = listOf(lib).toLibraries()
val repl = makeReplWithLibraries(libs)
repl.evalRaw(
"""
%use mylib
USE {
render { (it * 2).toString() }
}
""".trimIndent(),
)
val result = repl.evalRendered("13")
result.shouldBeTypeOf()
result[MimeTypes.HTML] shouldBe "26"
}
@Test
fun testLibraryKernelVersionRequirements() {
val minRequiredVersion = "999.42.0.1"
val kernelVersion = defaultRuntimeProperties.version.toMaybeUnspecifiedString()
val lib1 = "mylib" to """
{
"minKernelVersion": "$minRequiredVersion"
}
""".trimIndent()
val libs = listOf(lib1).toLibraries()
val replWithResolver = makeReplWithLibraries(libs)
val exception = assertThrows { replWithResolver.evalRaw("%use mylib") }
val message = exception.message!!
Assertions.assertTrue(message.contains(minRequiredVersion))
Assertions.assertTrue(message.contains(kernelVersion))
}
annotation class TestAnnotation
@Test
fun testExecutionOrder() {
val lib1 = "lib1" to library {
onLoaded {
scheduleExecution("3")
execute(
"""
%use lib2
EXECUTE("4")
@TestAnnotation
class Temp
""".trimIndent(),
)
}
import("java.*")
}
val lib2 = "lib2" to library {
onLoaded {
execute(
"""
EXECUTE("2")
1
""".trimIndent(),
)
}
import()
onClassAnnotation {
execute(
"""
EXECUTE("3")
""".trimIndent(),
)
}
}
val repl = makeReplWithLibraries(lib1, lib2).trackExecution()
val code = """
%use lib1
5
""".trimIndent()
repl.execute(code)
val expectedCodes = listOf(
"import java.*",
"import ${TestAnnotation::class.qualifiedName!!}",
"""
EXECUTE("2")
1
""".trimIndent(),
"2",
"""
EXECUTE("4")
@TestAnnotation
class Temp
""".trimIndent(),
"""EXECUTE("3")""",
"3",
"4",
"5",
)
assertEquals(expectedCodes, repl.executedCodes)
val expectedResults = (1..5).toList()
val actualResults = repl.results.filter { it != null && it != Unit }.map { it as Int }
assertEquals(expectedResults, actualResults)
}
@Test
fun testFileAnnotations() {
val lib = "lib" to library {
import()
onFileAnnotation {
scheduleExecution("val b = a")
}
}
val repl = makeReplWithLibraries(lib).trackExecution()
repl.execute("1")
repl.execute(
"""
%use lib
""".trimIndent(),
)
repl.execute(
"""
@file:TempAnnotation
val a = 1
""".trimIndent(),
)
val res = repl.execute(
"""
b
""".trimIndent(),
)
assertEquals(1, res.result.value)
val expected = listOf(
"1",
"import jupyter.kotlin.receivers.TempAnnotation",
"@file:TempAnnotation\nval a = 1",
"val b = a",
"b",
)
assertEquals(expected, repl.executedCodes)
}
@Test
fun testIncorrectDescriptors() {
val ex1 = assertThrows {
parseLibraryDescriptor(
"""
{
"imports": []
""".trimIndent(),
)
}
assertTrue(ex1.cause is SerializationException)
val ex2 = assertThrows {
parseLibraryDescriptor(
"""
{
"imports2": []
}
""".trimIndent(),
)
}
assertTrue(ex2.cause is SerializationException)
assertDoesNotThrow {
parseLibraryDescriptor("{}")
}
}
@Test
fun testLibraryWithResourcesDescriptorParsing() {
val descriptor = parseLibraryDescriptor(File("src/test/testData/lib-with-resources.json").readText())
val resources = descriptor.resources
assertEquals(1, resources.size)
val jsResource = resources.single()
assertEquals(ResourceType.JS, jsResource.type)
val bundles = jsResource.bundles
assertEquals(2, bundles.size)
assertEquals(1, bundles[0].locations.size)
assertEquals(1, bundles[1].locations.size)
}
@Test
fun testLibraryWithIncorrectImport() {
val e = assertThrows {
makeReplEnablingSingleLibrary(
library {
import("ru.incorrect")
},
)
}
assertEquals(LibraryProblemPart.PREBUILT, e.part)
}
@Test
fun testLibraryWithIncorrectDependency() {
val e = assertThrows {
makeReplEnablingSingleLibrary(
library {
dependencies("org.foo:bar:42")
},
)
}
assertEquals(LibraryProblemPart.PREBUILT, e.part)
}
@Test
fun testLibraryWithIncorrectInitCode() {
val e = assertThrows {
makeReplEnablingSingleLibrary(
library {
onLoaded {
null!!
}
},
)
}
assertEquals(LibraryProblemPart.INIT, e.part)
}
@Test
fun testBeforeExecutionException() {
val repl = makeReplEnablingSingleLibrary(
library {
beforeCellExecution {
throw NullPointerException()
}
},
)
val e = assertThrows {
repl.evalRaw("7")
}
assertEquals(LibraryProblemPart.BEFORE_CELL_CALLBACKS, e.part)
}
@Test
fun `multiple before-cell executions should be executed in the order of declaration`() {
val builder = StringBuilder()
val repl = makeReplEnablingSingleLibrary(
library {
beforeCellExecution {
builder.append("1")
}
beforeCellExecution {
builder.append("2")
}
},
)
repl.evalRaw("0")
repl.evalRaw("0")
builder.toString() shouldBe "1212"
}
@Test
fun testExceptionRendering() {
val repl = makeReplEnablingSingleLibrary(
library {
renderThrowable { textResult(it.message.orEmpty()) }
},
)
val processor = repl.throwableRenderersProcessor
val e1 = assertThrows {
repl.evalRaw("throw IllegalArgumentException(\"42\")")
}
val display1 = processor.renderThrowable(e1.cause!!)
display1.shouldBeInstanceOf()
val json = display1.toJson(overrideId = null).toString()
json shouldBe """{"data":{"text/plain":"42"},"metadata":{}}"""
val e2 = assertThrows {
repl.evalRaw("throw IndexOutOfBoundsException()")
}
val display2 = processor.renderThrowable(e2.cause!!)
display2 shouldBe null
}
@Test
@ExperimentalStdlibApi
fun testLibraryProperties() {
val mutProp = arrayListOf(1)
val repl = makeReplEnablingSingleLibrary(
library {
onLoaded {
declare(
"x1" to 22,
"x2" to 20,
)
declare(
VariableDeclaration("x3", mutProp, typeOf>()),
)
}
},
)
val result = repl.evalRaw(
"""
x3.add(2)
x1 + x2
""".trimIndent(),
)
assertEquals(42, result)
assertEquals(listOf(1, 2), mutProp)
}
@Test
fun testInternalMarkers() {
val repl = makeReplEnablingSingleLibrary(
library {
onLoaded {
declare(
"x1" to 22,
"_x2" to 20,
)
}
markVariableInternal {
it.name.startsWith("_")
}
},
)
repl.evalRaw(
"""
var a = 42
val _b = 11
""".trimIndent(),
)
assertEquals(
"""
Visible vars:
${'\t'}x1 : 22
${'\t'}a : 42
""".trimIndent(),
repl.notebook.variablesReport,
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy