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

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

package org.jetbrains.kotlinx.jupyter.test

import org.jetbrains.kotlinx.jupyter.LoggingManager
import org.jetbrains.kotlinx.jupyter.api.libraries.LibraryDefinition
import org.jetbrains.kotlinx.jupyter.compiler.util.CodeInterval
import org.jetbrains.kotlinx.jupyter.compiler.util.SourceCodeImpl
import org.jetbrains.kotlinx.jupyter.config.defaultRuntimeProperties
import org.jetbrains.kotlinx.jupyter.libraries.AbstractLibraryResolutionInfo
import org.jetbrains.kotlinx.jupyter.libraries.EmptyResolutionInfoProvider
import org.jetbrains.kotlinx.jupyter.libraries.LibrariesProcessorImpl
import org.jetbrains.kotlinx.jupyter.libraries.ResolutionInfoSwitcher
import org.jetbrains.kotlinx.jupyter.libraries.createLibraryHttpUtil
import org.jetbrains.kotlinx.jupyter.libraries.getDefinitions
import org.jetbrains.kotlinx.jupyter.magics.FullMagicsHandler
import org.jetbrains.kotlinx.jupyter.magics.MagicsProcessor
import org.jetbrains.kotlinx.jupyter.magics.NoopMagicsHandler
import org.jetbrains.kotlinx.jupyter.repl.ExecutedCodeLogging
import org.jetbrains.kotlinx.jupyter.repl.OutputConfig
import org.jetbrains.kotlinx.jupyter.repl.ReplOptions
import org.jetbrains.kotlinx.jupyter.util.toSourceCodePositionWithNewAbsolute
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertFalse
import kotlin.test.assertTrue

private typealias MagicsAndCodeIntervals = Pair, List>

class ParseArgumentsTests {
    private val httpUtil = createLibraryHttpUtil(testLoggerFactory)

    private fun parseReferenceWithArgs(ref: String) = httpUtil.libraryReferenceParser.parseReferenceWithArgs(ref)

    @Test
    fun test1() {
        val (ref, args) = parseReferenceWithArgs(" lib ")
        assertEquals("lib", ref.name)
        assertEquals(0, args.count())
    }

    @Test
    fun test2() {
        val (ref, args) = parseReferenceWithArgs("lib(arg1)")
        assertEquals("lib", ref.name)
        assertEquals(1, args.count())
        assertEquals("arg1", args[0].value)
        assertEquals("", args[0].name)
    }

    @Test
    fun test3() {
        val (ref, args) = parseReferenceWithArgs("lib (arg1 = 1.2, arg2 = val2)")
        assertEquals("lib", ref.name)
        assertEquals(2, args.count())
        assertEquals("arg1", args[0].name)
        assertEquals("1.2", args[0].value)
        assertEquals("arg2", args[1].name)
        assertEquals("val2", args[1].value)
    }

    @Test
    fun test4() {
        val (ref, args) = parseReferenceWithArgs("""lets-plot(api="[1.0,)")""")
        assertEquals("lets-plot", ref.name)
        assertEquals(1, args.count())
        assertEquals("api", args[0].name)
        assertEquals("[1.0,)", args[0].value)
    }

    @Test
    fun test5() {
        val (ref, args) = parseReferenceWithArgs("""lets-plot(api = "[1.0,)"   , lib=1.5.3 )""")
        assertEquals("lets-plot", ref.name)
        assertEquals(2, args.count())
        assertEquals("api", args[0].name)
        assertEquals("[1.0,)", args[0].value)
        assertEquals("lib", args[1].name)
        assertEquals("1.5.3", args[1].value)
    }

    @Test
    fun test6() {
        val (ref, args) = parseReferenceWithArgs("""spark(my.api.version = 1.0   , lib-x=foo )""")
        assertEquals("spark", ref.name)
        assertEquals(2, args.count())
        assertEquals("my.api.version", args[0].name)
        assertEquals("1.0", args[0].value)
        assertEquals("lib-x", args[1].name)
        assertEquals("foo", args[1].value)
    }

    @Test
    fun testInfo1() {
        val requestUrl = "https://raw.githubusercontent.com/Kotlin/kotlin-jupyter/master/libraries/default.json"
        val (ref, args) = parseReferenceWithArgs("lib_name@url[$requestUrl]")
        assertEquals("lib_name", ref.name)

        val info = ref.info
        assertTrue(info is AbstractLibraryResolutionInfo.ByURL)
        assertEquals(requestUrl, info.url.toString())
        assertEquals(0, args.size)
    }

    @Test
    fun testInfo2() {
        val file = File("libraries/default.json").toString()
        val (ref, args) = parseReferenceWithArgs("@file[$file](param=val)")
        assertEquals("", ref.name)

        val info = ref.info
        assertTrue(info is AbstractLibraryResolutionInfo.ByFile)
        assertEquals(file, info.file.toString())
        assertEquals(1, args.size)
        assertEquals("param", args[0].name)
        assertEquals("val", args[0].value)
    }

    @Test
    fun testInfo3() {
        val (ref, args) = parseReferenceWithArgs("krangl@ref[0.8.2.5]")
        assertEquals("krangl", ref.name)

        val info = ref.info
        assertTrue(info is AbstractLibraryResolutionInfo.ByGitRef)
        assertEquals(40, info.sha.length, "Expected commit SHA, but was `${info.sha}`")
        assertEquals(0, args.size)
    }

    @Test
    fun testInfo4() {
        val (ref, _) = parseReferenceWithArgs("[email protected]")
        assertEquals("krangl", ref.name)
        assertTrue(ref.info is AbstractLibraryResolutionInfo.Default)
    }
}

class ParseMagicsTests {
    private class TestReplOptions : ReplOptions {
        override var trackClasspath = false
        override var executedCodeLogging = ExecutedCodeLogging.OFF
        override var writeCompiledClasses = false
        override var outputConfig = OutputConfig()
    }

    private val options = TestReplOptions()

    private fun test(
        code: String,
        expectedProcessedCode: String,
        librariesChecker: (List) -> Unit = {},
    ) {
        val httpUtil = createLibraryHttpUtil(testLoggerFactory)
        val switcher = ResolutionInfoSwitcher.noop(EmptyResolutionInfoProvider(httpUtil.libraryInfoCache))
        val magicsHandler =
            FullMagicsHandler(
                options,
                LibrariesProcessorImpl(testLibraryResolver, httpUtil.libraryReferenceParser, defaultRuntimeProperties.version),
                switcher,
                LoggingManager(testLoggerFactory),
            )
        val processor = MagicsProcessor(magicsHandler)
        with(processor.processMagics(code, tryIgnoreErrors = true)) {
            assertEquals(expectedProcessedCode, this.code)
            librariesChecker(libraries.getDefinitions(NotebookMock))
        }
    }

    private fun intervals(
        code: String,
        parseOutCellMarker: Boolean,
    ): MagicsAndCodeIntervals {
        val processor = MagicsProcessor(NoopMagicsHandler, parseOutCellMarker)

        val magicsIntervals = processor.magicsIntervals(code)
        val codeIntervals = processor.codeIntervals(code, magicsIntervals)

        return magicsIntervals.toList() to codeIntervals.toList()
    }

    private fun getParsedText(
        code: String,
        intervals: List,
    ): String {
        return intervals.joinToString("") {
            code.substring(it.from, it.to)
        }
    }

    @Test
    fun `single magic`() {
        test("%use dataframe", "") { libs ->
            assertEquals(1, libs.size)
        }
    }

    @Test
    fun `trailing newlines should be left`() {
        test("\n%use dataframe\n\n", "\n\n\n") { libs ->
            assertEquals(1, libs.size)
        }
    }

    @Test
    fun `multiple magics`() {
        test(
            """
            %use lets-plot, dataframe
            
            fun f() = 42
            %trackClasspath
            val x = 9
            
            """.trimIndent(),
            """
            
            
            fun f() = 42
            
            val x = 9
            
            """.trimIndent(),
        ) { libs ->
            assertEquals(2, libs.size)
        }

        assertTrue(options.trackClasspath)

        test(
            """
            %trackClasspath off
            """.trimIndent(),
            "",
        )

        assertFalse(options.trackClasspath)
    }

    @Test
    fun `wrong magics should be tolerated`() {
        test(
            """
            %use lets-plot
            %use wrongLib
            val x = 9
            %wrongMagic
            fun f() = 42
            %trackExecution generated
            """.trimIndent(),
            """
            
            
            val x = 9
            
            fun f() = 42
            
            """.trimIndent(),
        ) { libs ->
            assertEquals(1, libs.size)
        }

        assertEquals(ExecutedCodeLogging.GENERATED, options.executedCodeLogging)
    }

    @Test
    fun `source location is correctly transformed`() {
        val sourceText =
            """
            fun g() = 99
            %use lets-plot
            %use wrongLib
            val x = 9
            """.trimIndent()

        val resultText =
            """
            fun g() = 99
            
            
            val x = 9
            """.trimIndent()

        test(sourceText, resultText) { libs ->
            assertEquals(1, libs.size)
        }

        val source = SourceCodeImpl(1, sourceText)
        val result = SourceCodeImpl(1, resultText)
        val cursor = sourceText.indexOf("lets-plot")

        val actualPos = cursor.toSourceCodePositionWithNewAbsolute(source, result)
        assertNull(actualPos)
    }

    @Test
    fun `cell marker is recognised if specified`() {
        val (magicsIntervals, codeIntervals) =
            intervals(
                """
                #%% some cell description
                
                fun f() = "pay respect"
                % some magic
                
                %some another magic
                """.trimIndent(),
                true,
            )

        assertEquals(3, magicsIntervals.size)
        assertEquals(2, codeIntervals.size)
    }

    @Test
    fun `cell marker in the middle is not recognised`() {
        val text =
            """
            #%% some cell description
            
            fun f() = "pay respect"
            % some magic
            #%% some another description - not recognised
            val x = 42
            %some another magic
            """.trimIndent()

        val (magicsIntervals, codeIntervals) = intervals(text, true)

        assertEquals(3, magicsIntervals.size)
        assertEquals(2, codeIntervals.size)

        val parsedMagics = getParsedText(text, magicsIntervals)
        assertEquals(
            """
            #%% some cell description
            % some magic
            %some another magic
            """.trimIndent(),
            parsedMagics,
        )
    }

    @Test
    fun `cell marker is not recognised if not specified`() {
        val (magicsIntervals, codeIntervals) =
            intervals(
                """
                #%% some cell description
                
                fun f() = "pay respect"
                % some magic
                
                %some another magic
                """.trimIndent(),
                false,
            )

        assertEquals(2, magicsIntervals.size)
        assertEquals(2, codeIntervals.size)
    }

    @Test
    fun `percent sign alone is parsed well`() {
        val (magicsIntervals, codeIntervals) =
            intervals(
                "%",
                false,
            )

        assertEquals(1, magicsIntervals.size)
        assertEquals(0, codeIntervals.size)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy