From 099c0a49907dbd0a06348aa57c741cf9d943012a Mon Sep 17 00:00:00 2001 From: Ilya Muradyan Date: Fri, 18 Oct 2024 02:21:44 +0200 Subject: [PATCH] KTNB-794: Provide not only classpath, but the whole REPL state on kernel_info_reply --- .../kotlinx/jupyter/api/KernelRunMode.kt | 8 +- .../jetbrains/kotlinx/jupyter/api/Notebook.kt | 17 ++- .../IdeCompatibleMessageRequestProcessor.kt | 2 +- .../kotlinx/jupyter/messaging/MessageTypes.kt | 3 +- .../kotlinx/jupyter/repl/ClasspathProvider.kt | 7 ++ .../kotlinx/jupyter/repl/ReplForJupyter.kt | 2 + .../kotlinx/jupyter/repl/SharedReplContext.kt | 3 + .../repl/notebook/impl/NotebookImpl.kt | 9 ++ .../org/jetbrains/kotlinx/jupyter/Ikotlin.kt | 3 +- .../jupyter/repl/impl/ReplForJupyterImpl.kt | 32 +++++ .../kotlinx/jupyter/test/TestUtil.kt | 6 + .../jupyter/test/protocol/ExecuteTests.kt | 116 +++++++++++++----- 12 files changed, 169 insertions(+), 39 deletions(-) create mode 100644 jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ClasspathProvider.kt diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/KernelRunMode.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/KernelRunMode.kt index cdf1d636..46b2b9d0 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/KernelRunMode.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/KernelRunMode.kt @@ -3,7 +3,7 @@ package org.jetbrains.kotlinx.jupyter.api import org.jetbrains.kotlinx.jupyter.util.createDefaultDelegatingClassLoader /** - * Represents settings that depend on the environment in which kernel is running + * Represents settings that depend on the environment in which the kernel is running */ interface KernelRunMode { val name: String @@ -22,6 +22,8 @@ interface KernelRunMode { val isRunInsideIntellijProcess: Boolean val streamSubstitutionType: StreamSubstitutionType + + fun initializeSession(notebook: Notebook) = Unit } abstract class AbstractKernelRunMode(override val name: String) : KernelRunMode { @@ -48,4 +50,8 @@ object EmbeddedKernelRunMode : AbstractKernelRunMode("Embedded") { override val isRunInsideIntellijProcess: Boolean get() = false override val streamSubstitutionType: StreamSubstitutionType get() = StreamSubstitutionType.BLOCKING + + override fun initializeSession(notebook: Notebook) { + notebook.sessionOptions.serializeScriptData = true + } } diff --git a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt index 1cbbb412..324cc72d 100644 --- a/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt +++ b/jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/Notebook.kt @@ -27,6 +27,19 @@ interface Notebook { */ val variablesState: Map + /** + * Current session classpath + */ + val currentClasspath: List + + /** + * Configuration options for the current notebook session. + * + * Provides various settings that control the behavior of the session, such as + * resolving sources, handling multi-platform projects, and serializing script data. + */ + val sessionOptions: SessionOptions + /** * Stores info about useful variables in a cell. * Key: cellId; @@ -49,7 +62,7 @@ interface Notebook { fun getCell(id: Int): CodeCell /** - * Mapping allowing to get result by execution number + * Mapping allowing to get a result by execution number */ @Throws(IndexOutOfBoundsException::class) fun getResult(id: Int): Any? @@ -122,7 +135,7 @@ interface Notebook { val kernelRunMode: KernelRunMode /** - * Renderers processor gives an ability to render values and + * Renderers processor gives an ability to render values * and add new renderers */ val renderersProcessor: RenderersProcessor diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/IdeCompatibleMessageRequestProcessor.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/IdeCompatibleMessageRequestProcessor.kt index 5dcf1db3..ba331688 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/IdeCompatibleMessageRequestProcessor.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/IdeCompatibleMessageRequestProcessor.kt @@ -220,7 +220,7 @@ open class IdeCompatibleMessageRequestProcessor( ), metadata = Json.encodeToJsonElement( - KernelInfoReplyMetadata(repl.currentClasspath.toList()), + KernelInfoReplyMetadata(repl.currentSessionState), ), ), ) diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/MessageTypes.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/MessageTypes.kt index e7906fc0..b804bcc2 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/MessageTypes.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/messaging/MessageTypes.kt @@ -28,6 +28,7 @@ import kotlinx.serialization.serializer import org.jetbrains.kotlinx.jupyter.config.LanguageInfo import org.jetbrains.kotlinx.jupyter.exceptions.ReplException import org.jetbrains.kotlinx.jupyter.protocol.messageDataJson +import org.jetbrains.kotlinx.jupyter.repl.EvaluatedSnippetMetadata import org.jetbrains.kotlinx.jupyter.util.EMPTY import org.jetbrains.kotlinx.jupyter.util.toUpperCaseAsciiOnly import java.util.concurrent.ConcurrentHashMap @@ -363,7 +364,7 @@ class KernelInfoReply( @Serializable class KernelInfoReplyMetadata( - val classpath: List, + val state: EvaluatedSnippetMetadata, ) @Serializable diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ClasspathProvider.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ClasspathProvider.kt new file mode 100644 index 00000000..46d20639 --- /dev/null +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ClasspathProvider.kt @@ -0,0 +1,7 @@ +package org.jetbrains.kotlinx.jupyter.repl + +import org.jetbrains.kotlinx.jupyter.repl.result.Classpath + +fun interface ClasspathProvider { + fun provideClasspath(): Classpath +} diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ReplForJupyter.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ReplForJupyter.kt index 028a03e7..30236f2b 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ReplForJupyter.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/ReplForJupyter.kt @@ -39,6 +39,8 @@ interface ReplForJupyter { val options: ReplOptions + val currentSessionState: EvaluatedSnippetMetadata + val currentClasspath: Collection val currentClassLoader: ClassLoader diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/SharedReplContext.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/SharedReplContext.kt index 76b3a963..f28901dc 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/SharedReplContext.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/SharedReplContext.kt @@ -4,6 +4,7 @@ import org.jetbrains.kotlinx.jupyter.api.ExecutionCallback import org.jetbrains.kotlinx.jupyter.api.ExtensionsProcessor import org.jetbrains.kotlinx.jupyter.api.KernelLoggerFactory import org.jetbrains.kotlinx.jupyter.api.Notebook +import org.jetbrains.kotlinx.jupyter.api.SessionOptions import org.jetbrains.kotlinx.jupyter.api.ThrowableRenderersProcessor import org.jetbrains.kotlinx.jupyter.api.outputs.DisplayHandler import org.jetbrains.kotlinx.jupyter.codegen.ClassAnnotationsProcessor @@ -43,4 +44,6 @@ data class SharedReplContext( val colorSchemeChangeCallbacksProcessor: ColorSchemeChangeCallbacksProcessor, val displayHandler: DisplayHandler, val inMemoryReplResultsHolder: InMemoryReplResultsHolder, + val sessionOptions: SessionOptions, + val currentClasspathProvider: ClasspathProvider, ) diff --git a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/notebook/impl/NotebookImpl.kt b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/notebook/impl/NotebookImpl.kt index c53a040f..a8f0b626 100644 --- a/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/notebook/impl/NotebookImpl.kt +++ b/jupyter-lib/shared-compiler/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/notebook/impl/NotebookImpl.kt @@ -17,6 +17,7 @@ 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.ResultsAccessor +import org.jetbrains.kotlinx.jupyter.api.SessionOptions import org.jetbrains.kotlinx.jupyter.api.ThrowableRenderersProcessor import org.jetbrains.kotlinx.jupyter.api.VariableState import org.jetbrains.kotlinx.jupyter.api.libraries.ColorScheme @@ -86,6 +87,14 @@ class NotebookImpl( ?: throw IllegalStateException("Evaluator is not initialized yet") } + override val currentClasspath: List + get() = sharedReplContext?.currentClasspathProvider?.provideClasspath() ?: emptyList() + + override val sessionOptions: SessionOptions get() { + return sharedReplContext?.sessionOptions + ?: throw IllegalStateException("Session options are not initialized yet") + } + override val resultsAccessor = ResultsAccessor { getResult(it) } override fun getCell(id: Int): MutableCodeCell { diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/Ikotlin.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/Ikotlin.kt index 1e3e217f..e273aef0 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/Ikotlin.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/Ikotlin.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.jupyter import org.jetbrains.kotlinx.jupyter.api.EmbeddedKernelRunMode import org.jetbrains.kotlinx.jupyter.api.KernelLoggerFactory import org.jetbrains.kotlinx.jupyter.api.KernelRunMode +import org.jetbrains.kotlinx.jupyter.api.StandaloneKernelRunMode import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterConnection import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterSocketType import org.jetbrains.kotlinx.jupyter.api.libraries.rawMessageCallback @@ -97,7 +98,7 @@ fun main(vararg args: String) { val kernelConfig = kernelArgs.getConfig() startKernel( loggerFactory, - EmbeddedKernelRunMode, + StandaloneKernelRunMode, kernelConfig, ) } catch (e: Exception) { diff --git a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/ReplForJupyterImpl.kt b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/ReplForJupyterImpl.kt index e205de27..c390e271 100644 --- a/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/ReplForJupyterImpl.kt +++ b/src/main/kotlin/org/jetbrains/kotlinx/jupyter/repl/impl/ReplForJupyterImpl.kt @@ -85,6 +85,7 @@ import org.jetbrains.kotlinx.jupyter.messaging.comms.installCommHandler import org.jetbrains.kotlinx.jupyter.messaging.comms.requireUniqueTargets import org.jetbrains.kotlinx.jupyter.registerDefaultRenderers import org.jetbrains.kotlinx.jupyter.repl.BaseKernelHost +import org.jetbrains.kotlinx.jupyter.repl.ClasspathProvider import org.jetbrains.kotlinx.jupyter.repl.CompletionResult import org.jetbrains.kotlinx.jupyter.repl.ContextUpdater import org.jetbrains.kotlinx.jupyter.repl.EvalData @@ -116,6 +117,7 @@ import org.jetbrains.kotlinx.jupyter.repl.result.InternalMetadata import org.jetbrains.kotlinx.jupyter.repl.result.InternalMetadataImpl import org.jetbrains.kotlinx.jupyter.repl.result.InternalReplResult import org.jetbrains.kotlinx.jupyter.repl.result.SerializedCompiledScriptsData +import org.jetbrains.kotlinx.jupyter.repl.result.buildScriptsData import java.io.Closeable import java.io.File import java.net.URLClassLoader @@ -261,6 +263,30 @@ class ReplForJupyterImpl( override val currentClasspath = compilerConfiguration.classpath.map { it.canonicalPath }.toMutableSet() private val currentSources = mutableSetOf() + private val evaluatedSnippetsMetadata = mutableListOf() + + private val allEvaluatedSnippetsMetadata: InternalMetadata get() { + val allCompiledData = + buildScriptsData { + for (metadata in evaluatedSnippetsMetadata) { + addData(metadata.compiledData) + } + } + val allImports = evaluatedSnippetsMetadata.flatMap { it.newImports } + + return InternalMetadataImpl( + allCompiledData, + allImports, + ) + } + + override val currentSessionState: EvaluatedSnippetMetadata get() { + return EvaluatedSnippetMetadata( + currentClasspath.toList(), + currentSources.toList(), + allEvaluatedSnippetsMetadata, + ) + } private val evaluatorConfiguration = ScriptEvaluationConfiguration { @@ -355,6 +381,8 @@ class ReplForJupyterImpl( private val afterCellExecutionsProcessor = AfterCellExecutionsProcessor(loggerFactory) private val shutdownExecutionsProcessor = ShutdownExecutionsProcessor(loggerFactory) + private val classpathProvider = ClasspathProvider { currentClasspath.toList() } + override fun checkComplete(code: String) = jupyterCompiler.checkComplete(code) internal val sharedContext = @@ -381,8 +409,11 @@ class ReplForJupyterImpl( colorSchemeChangeCallbacksProcessor, displayHandler, inMemoryReplResultsHolder, + sessionOptions, + classpathProvider, ).also { notebook.sharedReplContext = it + kernelRunMode.initializeSession(notebook) commHandlers.requireUniqueTargets() commHandlers.forEach { handler -> installCommHandler(handler) } } @@ -582,6 +613,7 @@ class ReplForJupyterImpl( } val metadata = InternalMetadataImpl(compiledData, newImports) + evaluatedSnippetsMetadata.add(metadata) return if (throwable != null) { InternalReplResult.Error(throwable, metadata) } else { diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/TestUtil.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/TestUtil.kt index 9236385b..af28ad68 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/TestUtil.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/TestUtil.kt @@ -28,6 +28,7 @@ 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 @@ -279,6 +280,11 @@ object NotebookMock : Notebook { 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 diff --git a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/protocol/ExecuteTests.kt b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/protocol/ExecuteTests.kt index 9586a10e..2e09e887 100644 --- a/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/protocol/ExecuteTests.kt +++ b/src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/protocol/ExecuteTests.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.jupyter.test.protocol import ch.qos.logback.classic.Level.DEBUG import ch.qos.logback.classic.Level.OFF import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.comparables.shouldBeGreaterThan import io.kotest.matchers.paths.shouldBeAFile import io.kotest.matchers.shouldBe @@ -34,6 +35,9 @@ import org.jetbrains.kotlinx.jupyter.messaging.InputRequest import org.jetbrains.kotlinx.jupyter.messaging.InterruptRequest import org.jetbrains.kotlinx.jupyter.messaging.IsCompleteReply import org.jetbrains.kotlinx.jupyter.messaging.IsCompleteRequest +import org.jetbrains.kotlinx.jupyter.messaging.KernelInfoReply +import org.jetbrains.kotlinx.jupyter.messaging.KernelInfoReplyMetadata +import org.jetbrains.kotlinx.jupyter.messaging.KernelInfoRequest import org.jetbrains.kotlinx.jupyter.messaging.KernelStatus import org.jetbrains.kotlinx.jupyter.messaging.Message import org.jetbrains.kotlinx.jupyter.messaging.MessageStatus @@ -86,6 +90,11 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { private var ioPub: JupyterSocket? = null private var stdin: JupyterSocket? = null + private val shellSocket get() = shell!! + private val controlSocket get() = control!! + private val ioPubSocket get() = ioPub!! + private val stdinSocket get() = stdin!! + override fun beforeEach() { try { _context = ZMQ.context(1) @@ -128,41 +137,38 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { storeHistory: Boolean = true, ): Any? { try { - val shell = this.shell!! - val ioPub = this.ioPub!! - val stdin = this.stdin!! - shell.sendMessage( + shellSocket.sendMessage( MessageType.EXECUTE_REQUEST, content = ExecuteRequest(code, allowStdin = allowStdin, storeHistory = storeHistory), ) executeRequestSent() inputs.forEach { - val request = stdin.receiveMessage() + val request = stdinSocket.receiveMessage() request.content.shouldBeTypeOf() - stdin.sendMessage(MessageType.INPUT_REPLY, InputReply(it)) + stdinSocket.sendMessage(MessageType.INPUT_REPLY, InputReply(it)) } - var msg = shell.receiveMessage() + var msg = shellSocket.receiveMessage() assertEquals(MessageType.EXECUTE_REPLY, msg.type) executeReplyChecker(msg) - msg = ioPub.receiveMessage() + msg = ioPubSocket.receiveMessage() assertEquals(MessageType.STATUS, msg.type) assertEquals(KernelStatus.BUSY, (msg.content as StatusReply).status) - msg = ioPub.receiveMessage() + msg = ioPubSocket.receiveMessage() assertEquals(MessageType.EXECUTE_INPUT, msg.type) - ioPubChecker(ioPub) + ioPubChecker(ioPubSocket) var response: Any? = null if (hasResult) { - msg = ioPub.receiveMessage() + msg = ioPubSocket.receiveMessage() val content = msg.content as ExecutionResultMessage assertEquals(MessageType.EXECUTE_RESULT, msg.type) response = content.data } - msg = ioPub.receiveMessage() + msg = ioPubSocket.receiveMessage() assertEquals(MessageType.STATUS, msg.type) assertEquals(KernelStatus.IDLE, (msg.content as StatusReply).status) return response @@ -187,10 +193,9 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { private fun doIsComplete(code: String): String { try { - val shell = this.shell!! - shell.sendMessage(MessageType.IS_COMPLETE_REQUEST, content = IsCompleteRequest(code)) + shellSocket.sendMessage(MessageType.IS_COMPLETE_REQUEST, content = IsCompleteRequest(code)) - val responseMsg = shell.receiveMessage() + val responseMsg = shellSocket.receiveMessage() assertEquals(MessageType.IS_COMPLETE_REPLY, responseMsg.type) val content = responseMsg.content as IsCompleteReply @@ -202,7 +207,16 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { } private fun interruptExecution() { - control!!.sendMessage(MessageType.INTERRUPT_REQUEST, InterruptRequest()) + controlSocket.sendMessage(MessageType.INTERRUPT_REQUEST, InterruptRequest()) + } + + private fun requestKernelInfo(): Message { + shellSocket.sendMessage(MessageType.KERNEL_INFO_REQUEST, KernelInfoRequest()) + val responseMsg = shellSocket.receiveMessage() + responseMsg.type shouldBe MessageType.KERNEL_INFO_REPLY + val content = responseMsg.content + content.shouldBeTypeOf() + return responseMsg } private inline fun JupyterSocket.receiveMessageOfType(messageType: MessageType): T { @@ -458,9 +472,6 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { @Test fun testComms() { - val shell = shell!! - val iopub = ioPub!! - val targetName = "my_comm" val commId = "xyz" @@ -491,9 +502,9 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { """.trimIndent() doExecute(registerCode, false) - shell.sendMessage(MessageType.COMM_OPEN, CommOpen(commId, targetName)) + shellSocket.sendMessage(MessageType.COMM_OPEN, CommOpen(commId, targetName)) - iopub.receiveMessage().apply { + ioPubSocket.receiveMessage().apply { val c = content.shouldBeTypeOf() c.commId shouldBe commId c.data["xo"]!!.jsonPrimitive.content shouldBe commId @@ -501,7 +512,7 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { // Thread.sleep(5000) - shell.sendMessage( + shellSocket.sendMessage( MessageType.COMM_MSG, CommMsg( commId, @@ -513,8 +524,8 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { ), ) - iopub.wrapActionInBusyIdleStatusChange { - iopub.receiveMessage().apply { + ioPubSocket.wrapActionInBusyIdleStatusChange { + ioPubSocket.receiveMessage().apply { val c = content.shouldBeTypeOf() c.commId shouldBe commId c.data["y"]!!.jsonPrimitive.content shouldBe "received: 4321" @@ -524,14 +535,11 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { @Test fun testDebugPortCommHandler() { - val shell = shell!! - val iopub = ioPub!! - val targetName = ProvidedCommMessages.OPEN_DEBUG_PORT_TARGET val commId = "some" val actualDebugPort = kernelConfig.debugPort - shell.sendMessage( + shellSocket.sendMessage( MessageType.COMM_OPEN, CommOpen( commId, @@ -539,15 +547,16 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { ), ) - shell.sendMessage( + shellSocket.sendMessage( MessageType.COMM_MSG, CommMsg(commId), ) - iopub.wrapActionInBusyIdleStatusChange { - iopub.receiveMessage().apply { + ioPubSocket.wrapActionInBusyIdleStatusChange { + ioPubSocket.receiveMessage().apply { val c = content.shouldBeTypeOf() - val data = MessageFormat.decodeFromJsonElement(c.data).shouldBeTypeOf() + val data = + MessageFormat.decodeFromJsonElement(c.data).shouldBeTypeOf() c.commId shouldBe commId data.port shouldBe actualDebugPort data.status shouldBe MessageStatus.OK @@ -679,7 +688,7 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { // In case of an exception happening in generated code. This code isn't visible to // the user. In that case, these things happen: - // - Any stack trace line that reference outside the visible code range only show the request count. + // - Any stack trace line that references outside the visible code range only shows the request count. // - We find the first "visible" error and promote that at the end of error output instead of the // first error. This means that the user can hopefully find the point in their code that triggers // the behavior @@ -773,4 +782,45 @@ class ExecuteTests : KernelServerTestsBase(runServerInSeparateProcess = true) { }, ) } + + @Test + fun `kernel_info_reply should contain info about session state`() { + doExecute( + """ + SessionOptions.serializeScriptData = true + """.trimIndent(), + hasResult = false, + ) + + doExecute( + """ + %use ktor-client + """.trimIndent(), + hasResult = false, + ) + + doExecute( + """ + import java.util.concurrent.ConcurrentHashMap + + buildJsonObject { + put("a", JsonPrimitive(1)) + put("b", JsonPrimitive(2)) + } + """.trimIndent(), + ) + + val infoResponse = requestKernelInfo() + val metadataObject = infoResponse.data.metadata + metadataObject.shouldBeTypeOf() + val metadata = MessageFormat.decodeFromJsonElement(metadataObject) + + with(metadata.state) { + assertTrue { newClasspath.any { "kotlin-jupyter-ktor-client" in it } } + newImports shouldContain "org.jetbrains.kotlinx.jupyter.serialization.UntypedAny" + newImports shouldContain "java.util.concurrent.ConcurrentHashMap" + + compiledData.scripts.shouldNotBeEmpty() + } + } }