All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlinx.jupyter.test.TestUtil.kt Maven / Gradle / Ivy

package org.jetbrains.kotlinx.jupyter.test

import io.kotest.assertions.fail
import io.kotest.matchers.types.shouldBeInstanceOf
import io.kotest.matchers.types.shouldBeTypeOf
import jupyter.kotlin.JavaRuntime
import org.jetbrains.kotlinx.jupyter.api.AfterCellExecutionCallback
import org.jetbrains.kotlinx.jupyter.api.Code
import org.jetbrains.kotlinx.jupyter.api.CodeCell
import org.jetbrains.kotlinx.jupyter.api.CodePreprocessor
import org.jetbrains.kotlinx.jupyter.api.DisplayContainer
import org.jetbrains.kotlinx.jupyter.api.DisplayResult
import org.jetbrains.kotlinx.jupyter.api.DisplayResultWithCell
import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback
import org.jetbrains.kotlinx.jupyter.api.ExtensionsProcessor
import org.jetbrains.kotlinx.jupyter.api.FieldsProcessor
import org.jetbrains.kotlinx.jupyter.api.HtmlData
import org.jetbrains.kotlinx.jupyter.api.InterruptionCallback
import org.jetbrains.kotlinx.jupyter.api.JREInfoProvider
import org.jetbrains.kotlinx.jupyter.api.JupyterClientType
import org.jetbrains.kotlinx.jupyter.api.KernelLoggerFactory
import org.jetbrains.kotlinx.jupyter.api.KernelRunMode
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelVersion
import org.jetbrains.kotlinx.jupyter.api.LibraryLoader
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
import org.jetbrains.kotlinx.jupyter.api.MimeTypes
import org.jetbrains.kotlinx.jupyter.api.Notebook
import org.jetbrains.kotlinx.jupyter.api.RenderersProcessor
import org.jetbrains.kotlinx.jupyter.api.ResultsAccessor
import org.jetbrains.kotlinx.jupyter.api.SessionOptions
import org.jetbrains.kotlinx.jupyter.api.StandaloneKernelRunMode
import org.jetbrains.kotlinx.jupyter.api.TextRenderersProcessor
import org.jetbrains.kotlinx.jupyter.api.ThrowableRenderersProcessor
import org.jetbrains.kotlinx.jupyter.api.VariableState
import org.jetbrains.kotlinx.jupyter.api.VariableStateImpl
import org.jetbrains.kotlinx.jupyter.api.libraries.ColorScheme
import org.jetbrains.kotlinx.jupyter.api.libraries.ColorSchemeChangedCallback
import org.jetbrains.kotlinx.jupyter.api.libraries.CommManager
import org.jetbrains.kotlinx.jupyter.api.libraries.ExecutionHost
import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryReference
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryResolutionRequest
import org.jetbrains.kotlinx.jupyter.api.libraries.Variable
import org.jetbrains.kotlinx.jupyter.api.libraries.createLibrary
import org.jetbrains.kotlinx.jupyter.api.outputs.DisplayHandler
import org.jetbrains.kotlinx.jupyter.api.withId
import org.jetbrains.kotlinx.jupyter.common.LibraryDescriptorsManager
import org.jetbrains.kotlinx.jupyter.common.SimpleHttpClient
import org.jetbrains.kotlinx.jupyter.config.DefaultKernelLoggerFactory
import org.jetbrains.kotlinx.jupyter.config.defaultRepositoriesCoordinates
import org.jetbrains.kotlinx.jupyter.config.defaultRuntimeProperties
import org.jetbrains.kotlinx.jupyter.config.errorForUser
import org.jetbrains.kotlinx.jupyter.libraries.AbstractLibraryResolutionInfo
import org.jetbrains.kotlinx.jupyter.libraries.ChainedLibraryResolver
import org.jetbrains.kotlinx.jupyter.libraries.LibraryDescriptor
import org.jetbrains.kotlinx.jupyter.libraries.LibraryResolver
import org.jetbrains.kotlinx.jupyter.libraries.parseLibraryDescriptors
import org.jetbrains.kotlinx.jupyter.messaging.CommunicationFacilityMock
import org.jetbrains.kotlinx.jupyter.messaging.comms.CommManagerImpl
import org.jetbrains.kotlinx.jupyter.repl.CompletionResult
import org.jetbrains.kotlinx.jupyter.repl.EvalRequestData
import org.jetbrains.kotlinx.jupyter.repl.ReplForJupyter
import org.jetbrains.kotlinx.jupyter.repl.ReplRuntimeProperties
import org.jetbrains.kotlinx.jupyter.repl.creating.ReplComponentsProviderBase
import org.jetbrains.kotlinx.jupyter.repl.notebook.DisplayResultWrapper
import org.jetbrains.kotlinx.jupyter.repl.notebook.MutableCodeCell
import org.jetbrains.kotlinx.jupyter.repl.notebook.MutableDisplayResultWithCell
import org.jetbrains.kotlinx.jupyter.repl.notebook.MutableNotebook
import org.jetbrains.kotlinx.jupyter.repl.renderValue
import org.jetbrains.kotlinx.jupyter.repl.result.EvalResultEx
import org.jetbrains.kotlinx.jupyter.util.asCommonFactory
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.typeOf
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
import kotlin.test.assertEquals

val testDataDir = File("src/test/testData")

const val STANDARD_RESOLVER_BRANCH = "master"

val testRepositories = defaultRepositoriesCoordinates

val standardResolverRuntimeProperties =
    object : ReplRuntimeProperties by defaultRuntimeProperties {
        override val currentBranch: String
            get() = STANDARD_RESOLVER_BRANCH
    }

val testLoggerFactory: KernelLoggerFactory = DefaultKernelLoggerFactory

val classpath =
    scriptCompilationClasspathFromContext(
        "lib",
        "api",
        "shared-compiler",
        "kotlin-stdlib",
        "kotlin-reflect",
        "kotlin-script-runtime",
        "kotlinx-serialization-json-jvm",
        "kotlinx-serialization-core-jvm",
        classLoader = TestDisplayHandler::class.java.classLoader,
    )

val KClass<*>.classPathEntry: File get() {
    return File(this.java.protectionDomain.codeSource.location.toURI().path)
}

inline fun  classPathEntry(): File {
    return (typeOf().classifier as KClass<*>).classPathEntry
}

val testLibraryResolver: LibraryResolver
    get() =
        getResolverFromNamesMap(
            parseLibraryDescriptors(testLoggerFactory, readLibraries()),
        )

val KERNEL_LIBRARIES =
    LibraryDescriptorsManager.getInstance(
        SimpleHttpClient,
        testLoggerFactory.asCommonFactory(),
    ) { logger, message, exception ->
        logger.errorForUser(message = message, throwable = exception)
    }

fun assertUnit(value: Any?) = assertEquals(Unit, value)

fun assertStartsWith(
    expectedPrefix: String,
    actual: String,
) {
    if (actual.startsWith(expectedPrefix)) return
    val actualStart = actual.substring(0, minOf(expectedPrefix.length, actual.length))
    throw AssertionError("Expected a string to start with '$expectedPrefix', but it starts with '$actualStart")
}

fun Collection>.toLibraries(): LibraryResolver {
    val libJsons = associate { it.first to it.second }
    return getResolverFromNamesMap(
        parseLibraryDescriptors(testLoggerFactory, libJsons),
    )
}

@JvmName("toLibrariesStringLibraryDefinition")
fun Collection>.toLibraries() = getResolverFromNamesMap(definitions = toMap())

fun getResolverFromNamesMap(
    descriptors: Map? = null,
    definitions: Map? = null,
): LibraryResolver {
    return InMemoryLibraryResolver(
        null,
        descriptors?.mapKeys { entry -> LibraryReference(AbstractLibraryResolutionInfo.Default(), entry.key) },
        definitions?.mapKeys { entry -> LibraryReference(AbstractLibraryResolutionInfo.Default(), entry.key) },
    )
}

fun readLibraries(basePath: String? = null): Map {
    val logger = testLoggerFactory.getLogger("test")

    return KERNEL_LIBRARIES.homeLibrariesDir(basePath?.let(::File))
        .listFiles()?.filter(KERNEL_LIBRARIES::isLibraryDescriptor)
        ?.map {
            logger.info("Loading '${it.nameWithoutExtension}' descriptor from '${it.canonicalPath}'")
            it.nameWithoutExtension to it.readText()
        }
        .orEmpty()
        .toMap()
}

fun CompletionResult.getOrFail(): CompletionResult.Success =
    when (this) {
        is CompletionResult.Success -> this
        else -> fail("Result should be success")
    }

fun Map.mapToStringValues(): Map {
    return mapValues { it.value.stringValue }
}

fun Map.getStringValue(variableName: String): String? {
    return get(variableName)?.stringValue
}

fun Map.getValue(variableName: String): Any? {
    return get(variableName)?.value?.getOrNull()
}

class InMemoryLibraryResolver(
    parent: LibraryResolver?,
    initialDescriptorsCache: Map? = null,
    initialDefinitionsCache: Map? = null,
) : ChainedLibraryResolver(parent) {
    private val definitionsCache = hashMapOf()
    private val descriptorsCache = hashMapOf()

    init {
        initialDescriptorsCache?.forEach { (key, value) ->
            descriptorsCache[key] = value
        }
        initialDefinitionsCache?.forEach { (key, value) ->
            definitionsCache[key] = value
        }
    }

    override fun shouldResolve(reference: LibraryReference): Boolean {
        return reference.shouldBeCachedInMemory
    }

    override fun tryResolve(
        reference: LibraryReference,
        arguments: List,
    ): LibraryDefinition? {
        return definitionsCache[reference] ?: descriptorsCache[reference]?.convertToDefinition(arguments)
    }

    override fun save(
        reference: LibraryReference,
        definition: LibraryDefinition,
    ) {
        definitionsCache[reference] = definition
    }
}

open class TestDisplayHandler(val list: MutableList = mutableListOf()) : DisplayHandler {
    override fun handleDisplay(
        value: Any,
        host: ExecutionHost,
        id: String?,
    ) {
        list.add(value)
    }

    override fun handleUpdate(
        value: Any,
        host: ExecutionHost,
        id: String?,
    ) {
        TODO("Not yet implemented")
    }
}

open class TestDisplayHandlerWithRendering(
    private val notebook: MutableNotebook,
) : DisplayHandler {
    override fun handleDisplay(
        value: Any,
        host: ExecutionHost,
        id: String?,
    ) {
        val display = renderValue(notebook, host, value, id)?.let { if (id != null) it.withId(id) else it } ?: return
        notebook.currentCell?.addDisplay(display)
    }

    override fun handleUpdate(
        value: Any,
        host: ExecutionHost,
        id: String?,
    ) {
        val display = renderValue(notebook, host, value, id) ?: return
        val container = notebook.displays
        container.update(id, display)
        container.getById(id).distinctBy { it.cell.id }.forEach {
            it.cell.displays.update(id, display)
        }
    }
}

object NotebookMock : Notebook {
    override val executionHost: KotlinKernelHost
        get() = notImplemented()

    private val cells = hashMapOf()

    override val cellsList: Collection
        get() = emptyList()
    override val variablesState = mutableMapOf()
    override val cellVariables = mapOf>()
    override val resultsAccessor = ResultsAccessor { getResult(it) }
    override val currentClasspath: List
        get() = notImplemented()

    override val sessionOptions: SessionOptions
        get() = notImplemented()

    override val loggerFactory: KernelLoggerFactory get() = DefaultKernelLoggerFactory

    override fun getCell(id: Int): MutableCodeCell {
        return cells[id] ?: throw ArrayIndexOutOfBoundsException(
            "There is no cell with number '$id'",
        )
    }

    override fun getResult(id: Int): Any? {
        return getCell(id).result
    }

    override val displays: DisplayContainer
        get() = notImplemented()

    override fun getAllDisplays(): List {
        return displays.getAll()
    }

    override fun getDisplaysById(id: String?): List {
        return displays.getById(id)
    }

    override fun history(before: Int): CodeCell? {
        notImplemented()
    }

    override val currentColorScheme: ColorScheme?
        get() = null

    override fun changeColorScheme(newScheme: ColorScheme) {
        notImplemented()
    }

    override fun renderHtmlAsIFrame(data: HtmlData): MimeTypedResult {
        notImplemented()
    }

    override val kernelVersion: KotlinKernelVersion
        get() = defaultRuntimeProperties.version!!
    override val jreInfo: JREInfoProvider
        get() = JavaRuntime

    override val renderersProcessor: RenderersProcessor
        get() = notImplemented()

    override val textRenderersProcessor: TextRenderersProcessor
        get() = notImplemented()

    override val throwableRenderersProcessor: ThrowableRenderersProcessor
        get() = notImplemented()

    override val fieldsHandlersProcessor: FieldsProcessor
        get() = notImplemented()

    override val beforeCellExecutionsProcessor: ExtensionsProcessor>
        get() = notImplemented()

    override val afterCellExecutionsProcessor: ExtensionsProcessor
        get() = notImplemented()

    override val shutdownExecutionsProcessor: ExtensionsProcessor>
        get() = notImplemented()

    override val codePreprocessorsProcessor: ExtensionsProcessor
        get() = notImplemented()
    override val interruptionCallbacksProcessor: ExtensionsProcessor
        get() = notImplemented()
    override val colorSchemeChangeCallbacksProcessor: ExtensionsProcessor
        get() = notImplemented()

    override val libraryRequests: Collection
        get() = notImplemented()

    override val libraryLoader: LibraryLoader
        get() = notImplemented()

    override fun getLibraryFromDescriptor(
        descriptorText: String,
        options: Map,
    ): LibraryDefinition {
        notImplemented()
    }

    private fun notImplemented(): Nothing {
        error("Not supposed to be called")
    }

    override val jupyterClientType: JupyterClientType
        get() = JupyterClientType.UNKNOWN

    override val kernelRunMode: KernelRunMode
        get() = StandaloneKernelRunMode

    override val commManager: CommManager
        get() = CommManagerImpl(CommunicationFacilityMock)

    override fun prompt(
        prompt: String,
        isPassword: Boolean,
    ): String {
        notImplemented()
    }
}

object ReplComponentsProviderMock : ReplComponentsProviderBase()

fun library(builder: JupyterIntegration.Builder.() -> Unit) = createLibrary(NotebookMock, builder)

fun ReplForJupyter.evalEx(code: Code) = evalEx(EvalRequestData(code))

inline fun  ReplForJupyter.evalError(code: Code): T {
    val result = evalEx(code)
    result.shouldBeInstanceOf()
    return result.error.shouldBeTypeOf()
}

inline fun  ReplForJupyter.evalRenderedError(code: Code): DisplayResult? {
    val result = evalEx(code)
    result.shouldBeInstanceOf()
    result.error.shouldBeTypeOf()

    return result.displayError
}

fun ReplForJupyter.evalInterrupted(code: Code) {
    val result = evalEx(code)
    result.shouldBeInstanceOf()
}

fun ReplForJupyter.evalExSuccess(code: Code): EvalResultEx.Success {
    val result = evalEx(code)
    result.shouldBeInstanceOf()
    return result
}

fun ReplForJupyter.evalRaw(code: Code): Any? {
    return evalExSuccess(code).internalResult.result.value
}

fun ReplForJupyter.evalRendered(code: Code): Any? {
    return evalExSuccess(code).renderedValue
}

val EvalResultEx.rawValue get(): Any? {
    this.shouldBeTypeOf()
    return this.internalResult.result.value
}

val EvalResultEx.renderedValue get(): Any? {
    this.shouldBeTypeOf()
    return this.renderedValue
}

val EvalResultEx.displayValue get(): Any? {
    this.shouldBeTypeOf()
    return this.displayValue
}

fun EvalResultEx.assertSuccess() {
    when (this) {
        is EvalResultEx.AbstractError -> throw error
        is EvalResultEx.Interrupted -> throw InterruptedException()
        is EvalResultEx.Success -> {}
    }
}

val MimeTypedResult.text get() = this[MimeTypes.PLAIN_TEXT] as String

fun MutableDisplayResultWithCell.shouldBeText(): String {
    shouldBeInstanceOf()

    val display = display
    display.shouldBeInstanceOf()

    return display.text
}

fun Any?.shouldBeInstanceOf(kclass: KClass<*>) {
    if (this == null) {
        throw AssertionError("Expected instance of ${kclass.qualifiedName}, but got null")
    }
    if (!kclass.isInstance(this)) {
        throw AssertionError("Expected instance of ${kclass.qualifiedName}, but got ${this::class.qualifiedName}")
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy