{-# LANGUAGE OverloadedStrings #-} module Jupyter.Test.Client (clientTests) where -- Imports from 'base' import Control.Exception (throwIO, bracket) import Control.Monad (forM_, void) import Data.Monoid ((<>)) import Control.Concurrent (threadDelay) import Data.Maybe (isJust) -- Imports from 'transformers' import Control.Monad.IO.Class (liftIO) -- Imports from 'tasty' import Test.Tasty (TestTree, testGroup) -- Imports from 'tasty-hunit' import Test.Tasty.HUnit (testCase, testCaseSteps, (@=?), assertBool, assertFailure) -- Imports from 'text' import Data.Text (Text) import qualified Data.Text as T -- Imports from 'aeson' import Data.Aeson (ToJSON(..), object, (.=)) -- Imports from 'process' import System.Process (terminateProcess, ProcessHandle) -- Imports from 'jupyter' import Jupyter.Client import Jupyter.Kernel import Jupyter.Messages import Jupyter.Test.MessageExchange import Jupyter.Test.Utils (inTempDir, shouldThrow, HandlerException(..)) clientTests :: TestTree clientTests = testGroup "Client Tests" [ testBasic , testStdin , testCalculator , testClientPortsTaken , testClient , testHandlerExceptions , testFindingKernelspecs ] -- | Test that all the demo kernelspecs are found using the 'findKernel' and 'findKernels' commands. -- -- This test succeeding relies upon the kernels installing them prior to this test suite being run, -- or running the Python test suite before this one. testFindingKernelspecs :: TestTree testFindingKernelspecs = testCase "Finding Kernelspecs" $ do -- Test 'findKernels' kernels <- findKernels let kernelNames = map kernelspecDisplayName kernels assertBool "Basic kernelspec not found" $ "Basic" `elem` kernelNames assertBool "Calculator kernelspec not found" $ "Calculator" `elem` kernelNames assertBool "Python 3 kernelspec not found" $ "Python 3" `elem` kernelNames assertBool "Stdin kernelspec not found" $ "Stdin" `elem` kernelNames -- Test that a nonexistent kernel returns nothing. kernelM <- findKernel "xyz-not-a-kernel-nope-#@" case kernelM of Nothing -> return () Just _ -> assertFailure "Found a kernel that should not exist" -- Test 'findKernel' let expectedKernels = [ ("basic", "basic", "Basic", False, False) , ("stdin", "stdin", "Stdin", False, False) , ("calculator", "calculator", "Calculator", False, False) , ("python3", "python", "Python 3", True, False) ] forM_ expectedKernels $ \(name, lang, displayName, hasLogo, hasJs) -> do Just kernel <- findKernel name kernelspecLanguage kernel @=? lang kernelspecDisplayName kernel @=? displayName -- Check that the files are not included if they don't exist. if hasLogo then assertBool "Logo file should be provided" $ isJust $ kernelspecLogoFile kernel else kernelspecLogoFile kernel @=? Nothing if hasJs then assertBool "kernel.js file should be provided" $ isJust $ kernelspecJsFile kernel else kernelspecJsFile kernel @=? Nothing assertBool "Connection file command doesn't include connection file" $ "abcxyz" `elem` kernelspecCommand kernel "" "abcxyz" -- | Test that the @basic@ kernel responds to all the standard messages with empty replies. testBasic :: TestTree testBasic = testMessageExchange "Basic Kernel" (commandFromKernelspec "basic") "" $ \_ _ profile -> [ MessageExchange { exchangeName = "execute_request" , exchangeRequest = ExecuteRequest "some input" defaultExecuteOptions , exchangeReply = ExecuteReply 0 ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, ExecuteInputOutput 0 "some input", kernelIdle] } , MessageExchange { exchangeName = "inspect_request" , exchangeRequest = InspectRequest "3" 1 DetailLow , exchangeReply = InspectReply (InspectOk Nothing) , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "complete_request" , exchangeRequest = CompleteRequest "prinx" 5 , exchangeReply = CompleteReply $ CompleteOk [] (CursorRange 5 5) mempty , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } ] ++ defaultMessageExchange "Basic" profile -- | A set of default message exchanges that hold for any simple kernels. defaultMessageExchange :: Text -> KernelProfile -> [MessageExchange] defaultMessageExchange name profile = [ MessageExchange { exchangeName = "connect_request" , exchangeRequest = ConnectRequest , exchangeReply = ConnectReply ConnectInfo { connectShellPort = profileShellPort profile , connectIopubPort = profileIopubPort profile , connectStdinPort = profileStdinPort profile , connectHeartbeatPort = profileHeartbeatPort profile , connectControlPort = profileControlPort profile } , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "kernel_info_request" , exchangeRequest = KernelInfoRequest , exchangeReply = KernelInfoReply $ simpleKernelInfo name , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request" , exchangeRequest = HistoryRequest $ HistoryOptions False True $ HistoryTail 3 , exchangeReply = HistoryReply [] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "shutdown (restart)" , exchangeRequest = ShutdownRequest Restart , exchangeReply = ShutdownReply Restart , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, ShutdownNotificationOutput Restart, kernelIdle] } ] -- | Test that the stdin kernel requests stdin from the client as desired. testStdin :: TestTree testStdin = testMessageExchange "Stdin Kernel" (commandFromKernelspec "stdin") "skip" $ \_ _ profile -> [ MessageExchange { exchangeName = "execute_request (input)" , exchangeRequest = ExecuteRequest "prompt" defaultExecuteOptions { executeAllowStdin = True } , exchangeReply = ExecuteReply 1 ExecuteOk , exchangeKernelRequests = [ (InputRequest InputOptions { inputPassword = False , inputPrompt = "prompt" }, InputReply "stdin") ] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput 1 "prompt" , DisplayDataOutput $ displayPlain "stdin" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (skip input)" , exchangeRequest = ExecuteRequest "skip" defaultExecuteOptions { executeAllowStdin = True } , exchangeReply = ExecuteReply 1 ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput 1 "skip" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (input password)" , exchangeRequest = ExecuteRequest "password" defaultExecuteOptions { executeAllowStdin = True } , exchangeReply = ExecuteReply 1 ExecuteOk , exchangeKernelRequests = [ (InputRequest InputOptions { inputPassword = True , inputPrompt = "password" }, InputReply "stdin two") ] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput 1 "password" , DisplayDataOutput $ displayPlain "stdin two" , kernelIdle ] } ] ++ defaultMessageExchange "Stdin" profile -- | Test the @calculator@ kernel, which should do execution, completion, and inspection, as well -- as all the default messages. testCalculator :: TestTree testCalculator = testMessageExchange "Calculator Kernel" (commandFromKernelspec "calculator") "Lit 5" $ \_ execCount profile -> [ MessageExchange { exchangeName = "connect_request" , exchangeRequest = ConnectRequest , exchangeReply = ConnectReply ConnectInfo { connectShellPort = profileShellPort profile , connectIopubPort = profileIopubPort profile , connectStdinPort = profileStdinPort profile , connectHeartbeatPort = profileHeartbeatPort profile , connectControlPort = profileControlPort profile } , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "execute_request (compute)" , exchangeRequest = ExecuteRequest "Compute [('x', 3)] (Add (Divide (Lit 100) (Lit 5)) (Multiply (Lit 10) (Var 'x')))" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 1) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 1) "Compute [('x', 3)] (Add (Divide (Lit 100) (Lit 5)) (Multiply (Lit 10) (Var 'x')))" , DisplayDataOutput $ displayPlain "50" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (compute)" , exchangeRequest = ExecuteRequest "Print (Add (Divide (Lit 100) (Lit 5)) (Multiply (Lit 10) (Var 'x')))" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 2) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 2) "Print (Add (Divide (Lit 100) (Lit 5)) (Multiply (Lit 10) (Var 'x')))" , DisplayDataOutput $ displayPlain "((100 / 5) + (10 * x))" <> displayLatex "(\\frac{100}{5} + (10 \\cdot x))" , kernelIdle ] } , MessageExchange { exchangeName = "complete_request" , exchangeRequest = CompleteRequest "Computx" 6 , exchangeReply = CompleteReply $ CompleteOk ["Compute"] (CursorRange 0 6) mempty , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "inspect_request (low)" , exchangeRequest = InspectRequest "Printblahblah" 5 DetailLow , exchangeReply = InspectReply $ InspectOk $ Just $ displayPlain "Print: Print an expression as text or LaTeX." , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "kernel_info_request" , exchangeRequest = KernelInfoRequest , exchangeReply = KernelInfoReply $ KernelInfo { kernelProtocolVersion = "5.0" , kernelBanner = "Welcome to the Haskell Calculator Test Kernel!" , kernelImplementation = "Calculator-Kernel" , kernelImplementationVersion = "1.0" , kernelLanguageInfo = LanguageInfo { languageName = "calculator" , languageVersion = "1.0" , languageMimetype = "text/plain" , languageFileExtension = ".txt" , languagePygmentsLexer = Nothing , languageCodeMirrorMode = Nothing , languageNbconvertExporter = Nothing } , kernelHelpLinks = [ HelpLink { helpLinkText = "jupyter package doc" , helpLinkURL = "http://github.com/gibiansky/jupyter-haskell" } ] } , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request" , exchangeRequest = HistoryRequest $ HistoryOptions False True $ HistoryTail 3 , exchangeReply = HistoryReply [] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "shutdown (restart)" , exchangeRequest = ShutdownRequest Restart , exchangeReply = ShutdownReply Restart , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, ShutdownNotificationOutput Restart, kernelIdle] } ] -- | Given the name of a kernel, find it's kernelspec and return a function which, given -- a path to the connection file, returns the kernel command invocation. For example, -- for the @python3@ kernel, something like the following could be the return value: -- -- >>> cmd <- commandFromKernelspec "python3" -- >>> cmd "{connection_file}" == ["python", "-m", "ipykernel", "-f", "{connection_file}"] commandFromKernelspec :: Text -> IO (FilePath -> [String]) commandFromKernelspec name = do kernel <- findKernel name case kernel of Nothing -> fail $ "Could not find kernelspec " ++ T.unpack name Just spec -> return $ kernelspecCommand spec "" -- | Start the IPython kernel and return a 'ProcessHandle' for the started process. startIPythonKernel :: KernelProfile -> IO ProcessHandle startIPythonKernel = startKernel $ \profileFile -> ["python", "-m", "ipykernel", "-f", profileFile] -- | Test that the client interface behaves as expected when the handlers throw exceptions. -- -- Namely, the exceptions should be reraised (once) on the main thread. testHandlerExceptions :: TestTree testHandlerExceptions = testCaseSteps "Client Handler Exceptions" $ \step -> do let exception = const $ const $ throwIO HandlerException returnStdin = const . const . return $ InputReply "<>" handlerKernelRequestException = ClientHandlers exception defaultClientCommHandler defaultKernelOutputHandler handlerCommException = ClientHandlers returnStdin exception defaultKernelOutputHandler handlerKernelOutputException = ClientHandlers returnStdin defaultClientCommHandler exception -- ConnectRequest results in status updates, so erroring on the kernel output -- should raise an exception in the main thread. step "...exception on kernel output..." raisesHandlerException $ runIPython handlerKernelOutputException $ \_ _ connection -> do void $ sendClientRequest connection ConnectRequest -- Since we might not get the kernel output until the connect reply, wait for -- a while to ensure we get the kernel output before the client exits. This doesn't -- slow down the test suite since an exception gets thrown and we exit this thread -- without finishing the waiting. liftIO $ threadDelay $ 1000 * 1000 -- ConnectRequest does not sent any stdin messages, so clients that error -- when handling stdin messages should not crash here. step "...no exception on kernel output..." void $ runIPython handlerKernelRequestException $ \_ _ connection -> sendClientRequest connection ConnectRequest -- This particular ExecuteRequest should reply with comm messages, and -- so a comm handler that raises an exception should cause the main thread to crash. step "...exception on comm..." raisesHandlerException $ runIPython handlerCommException $ \_ _ connection -> void $ sendClientRequest connection $ ExecuteRequest "import ipywidgets as widgets\nwidgets.FloatSlider()" defaultExecuteOptions -- This particular ExecuteRequest should reply with kernel requests for stdin, and -- so a kernel request handler that raises an exception should cause the main thread to crash. step "...exception on stdin..." raisesHandlerException $ runIPython handlerKernelRequestException $ \_ _ connection -> -- If we connect too quickly the kernel sometimes misses our message, leaving us -- in a stalled state. Wait to ensure that the kernel is ready. (We could also listen -- on iopub for a kernel status message if we wanted to.) void $ sendClientRequest connection $ ExecuteRequest "print(input())" defaultExecuteOptions { executeAllowStdin = True } where runIPython = runKernelAndClient startIPythonKernel raisesHandlerException io = io `shouldThrow` [HandlerException] defaultKernelOutputHandler :: (Comm -> IO ()) -> KernelOutput -> IO () defaultKernelOutputHandler _ _ = return () testClientPortsTaken :: TestTree testClientPortsTaken = testCase "Client Ports Taken" $ inTempDir $ \_ -> runClient Nothing Nothing emptyHandler $ \profile1 -> liftIO $ bracket (startIPythonKernel profile1) terminateProcess $ const $ delay 500 $ runClient Nothing Nothing emptyHandler $ \profile2 -> liftIO $ bracket (startIPythonKernel profile2) terminateProcess $ const $ delay 500 $ runClient Nothing Nothing emptyHandler $ \profile3 -> liftIO $ do 1 + profileShellPort profile1 @=? profileShellPort profile2 1 + profileHeartbeatPort profile1 @=? profileHeartbeatPort profile2 1 + profileControlPort profile1 @=? profileControlPort profile2 1 + profileStdinPort profile1 @=? profileStdinPort profile2 1 + profileIopubPort profile1 @=? profileIopubPort profile2 1 + profileShellPort profile2 @=? profileShellPort profile3 1 + profileHeartbeatPort profile2 @=? profileHeartbeatPort profile3 1 + profileControlPort profile2 @=? profileControlPort profile3 1 + profileStdinPort profile2 @=? profileStdinPort profile3 1 + profileIopubPort profile2 @=? profileIopubPort profile3 where emptyHandler = ClientHandlers (const . const . return $ InputReply "") (const . const $ return ()) (const . const $ return ()) delay ms act = do threadDelay $ 1000 * ms act -- Test that messages can be sent and received on the heartbeat socket. testClient :: TestTree testClient = testMessageExchange "Communicate with IPython Kernel" (return $ \prof -> ["python", "-m", "ipykernel", "-f", prof]) "3 + 3" $ \sessionNum execCount profile -> [ MessageExchange { exchangeName = "connect_request" , exchangeRequest = ConnectRequest , exchangeReply = ConnectReply ConnectInfo { connectShellPort = profileShellPort profile , connectIopubPort = profileIopubPort profile , connectStdinPort = profileStdinPort profile , connectHeartbeatPort = profileHeartbeatPort profile , connectControlPort = profileControlPort profile } , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "execute_request (stream output)" , exchangeRequest = ExecuteRequest "import sys\nprint(sys.version.split()[0][:3])" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 1) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 1) "import sys\nprint(sys.version.split()[0][:3])" , StreamOutput StreamStdout "3.5\n" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (expr)" , exchangeRequest = ExecuteRequest "3 + 3" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 2) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 2) "3 + 3" , ExecuteResultOutput (execCount + 2) $ displayPlain "6" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (none)" , exchangeRequest = ExecuteRequest "" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 2) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, ExecuteInputOutput (execCount + 3) "", kernelIdle] } , MessageExchange { exchangeName = "execute_request (clear)" , exchangeRequest = ExecuteRequest "from IPython.display import clear_output\nclear_output()" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 3) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 3) "from IPython.display import clear_output\nclear_output()" , ClearOutput ClearImmediately , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (comms)" , exchangeRequest = ExecuteRequest "import ipywidgets as widgets\nwidgets.FloatSlider()" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 4) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [ CommOpen fakeUUID (object [ "align_items" .= str "" , "_view_module" .= str "jupyter-js-widgets" , "height" .= str "" , "bottom" .= str "" , "display" .= str "" , "overflow_y" .= str "" , "min_height" .= str "" , "_view_name" .= str "LayoutView" , "justify_content" .= str "" , "left" .= str "" , "min_width" .= str "" , "overflow_x" .= str "" , "width" .= str "" , "margin" .= str "" , "visibility" .= str "" , "msg_throttle" .= toJSON (3 :: Int) , "overflow" .= str "" , "border" .= str "" , "max_height" .= str "" , "flex" .= str "" , "flex_flow" .= str "" , "max_width" .= str "" , "_model_module" .= str "jupyter-js-widgets" , "right" .= str "" , "_model_name" .= str "LayoutModel" , "top" .= str "" , "align_content" .= str "" , "align_self" .= str "" , "padding" .= str "" ]) "jupyter.widget" Nothing , CommOpen fakeUUID (object [ "max" .= toJSON (100 :: Int) , "readout" .= True , "background_color" .= (Nothing :: Maybe ()) , "slider_color" .= (Nothing :: Maybe ()) , "_view_module" .= str "jupyter-js-widgets" , "font_family" .= str "" , "_view_name" .= str "FloatSliderView" , "color" .= (Nothing :: Maybe ()) , "disabled" .= False , "value" .= toJSON (0 :: Int) , "visible" .= True , "msg_throttle" .= toJSON (3 :: Int) , "font_weight" .= str "" , "step" .= toJSON (0.1 :: Float) , "min" .= toJSON (0 :: Int) , "_model_module" .= str "jupyter-js-widgets" , "readout_format" .= str ".2f" , "_model_name" .= str "FloatSliderModel" , "_range" .= False , "continuous_update" .= True , "font_style" .= str "" , "orientation" .= str "horizontal" , "_dom_classes" .= ([] :: [()]) , "description" .= str "" , "font_size" .= str "" ]) "jupyter.widget" Nothing , CommMessage fakeUUID (object ["method" .= str "display"]) ] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 4) "import ipywidgets as widgets\nwidgets.FloatSlider()" , StreamOutput StreamStderr $ T.unwords [ "Widget Javascript not detected. " , "It may not be installed properly." , "Did you enable the widgetsnbextension?" , "If not, then run" , "\"jupyter nbextension enable --py --sys-prefix widgetsnbextension\"\n" ] , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (display)" , exchangeRequest = ExecuteRequest "from IPython.display import *\ndisplay(HTML('Hi'))" defaultExecuteOptions , exchangeReply = ExecuteReply (execCount + 5) ExecuteOk , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 5) "from IPython.display import *\ndisplay(HTML('Hi'))" , DisplayDataOutput $ displayPlain "" <> displayHtml "Hi" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (input)" , exchangeRequest = ExecuteRequest "x = input('Hello')\nprint(x)\nx" defaultExecuteOptions { executeAllowStdin = True } , exchangeReply = ExecuteReply (execCount + 6) ExecuteOk , exchangeKernelRequests = [ (InputRequest InputOptions { inputPassword = False, inputPrompt = "Hello" }, InputReply "stdin") ] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 6) "x = input('Hello')\nprint(x)\nx" , StreamOutput StreamStdout "stdin\n" , ExecuteResultOutput (execCount + 6) $ displayPlain "'stdin'" , kernelIdle ] } , MessageExchange { exchangeName = "execute_request (password)" , exchangeRequest = ExecuteRequest "import getpass\nprint(getpass.getpass('Hello'))" defaultExecuteOptions { executeAllowStdin = True } , exchangeReply = ExecuteReply (execCount + 7) ExecuteOk , exchangeKernelRequests = [ (InputRequest InputOptions { inputPassword = True, inputPrompt = "Hello" }, InputReply "stdin") ] , exchangeComms = [] , exchangeKernelOutputs = [ kernelBusy , ExecuteInputOutput (execCount + 7) "import getpass\nprint(getpass.getpass('Hello'))" , StreamOutput StreamStdout "stdin\n" , kernelIdle ] } , MessageExchange { exchangeName = "is_complete_request (complete)" , exchangeRequest = IsCompleteRequest "import getpass" , exchangeReply = IsCompleteReply CodeComplete , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "is_complete_request (incomplete)" , exchangeRequest = IsCompleteRequest "for x in [1, 2, 3]:\n" , exchangeReply = IsCompleteReply (CodeIncomplete " ") , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "is_complete_request (invalid)" , exchangeRequest = IsCompleteRequest "x =" , exchangeReply = IsCompleteReply CodeInvalid , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "inspect_request (empty)" , exchangeRequest = InspectRequest "3" 1 DetailLow , exchangeReply = InspectReply (InspectOk Nothing) , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "inspect_request (low)" , exchangeRequest = InspectRequest "print" 5 DetailLow , exchangeReply = InspectReply $ InspectOk $ Just $ displayPlain $ T.unlines [ "\ESC[0;31mDocstring:\ESC[0m" , "print(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)" , "" , "Prints the values to a stream, or to sys.stdout by default." , "Optional keyword arguments:" , "file: a file-like object (stream); defaults to the current sys.stdout." , "sep: string inserted between values, default a space." , "end: string appended after the last value, default a newline." , "flush: whether to forcibly flush the stream." , "\ESC[0;31mType:\ESC[0m builtin_function_or_method" ] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "inspect_request (high)" , exchangeRequest = InspectRequest "print" 5 DetailHigh , exchangeReply = InspectReply $ InspectOk $ Just $ displayPlain "\ESC[0;31mType:\ESC[0m builtin_function_or_method\n" , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "inspect_request (missing)" , exchangeRequest = InspectRequest "p" 1 DetailHigh , exchangeReply = InspectReply $ InspectOk Nothing , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "complete_request" , exchangeRequest = CompleteRequest "prinx" 4 , exchangeReply = CompleteReply $ CompleteOk ["print"] (CursorRange 0 4) mempty , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "complete_request (missing)" , exchangeRequest = CompleteRequest "prinx" 5 , exchangeReply = CompleteReply $ CompleteOk [] (CursorRange 0 5) mempty , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request (tail)" , exchangeRequest = HistoryRequest $ HistoryOptions False True $ HistoryTail 3 , exchangeReply = HistoryReply $ [ HistoryItem sessionNum 6 "from IPython.display import *\ndisplay(HTML('Hi'))" Nothing , HistoryItem sessionNum 7 "x = input('Hello')\nprint(x)\nx" Nothing , HistoryItem sessionNum 8 "import getpass\nprint(getpass.getpass('Hello'))" Nothing ] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request (tail output)" , exchangeRequest = HistoryRequest $ HistoryOptions True True $ HistoryTail 3 , exchangeReply = HistoryReply $ [ HistoryItem sessionNum 6 "from IPython.display import *\ndisplay(HTML('Hi'))" Nothing , HistoryItem sessionNum 7 "x = input('Hello')\nprint(x)\nx" Nothing , HistoryItem sessionNum 8 "import getpass\nprint(getpass.getpass('Hello'))" Nothing ] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request (range)" , exchangeRequest = HistoryRequest $ HistoryOptions False True $ HistoryRange $ HistoryRangeOptions sessionNum 6 8 , exchangeReply = HistoryReply $ [ HistoryItem 0 6 "from IPython.display import *\ndisplay(HTML('Hi'))" Nothing , HistoryItem 0 7 "x = input('Hello')\nprint(x)\nx" Nothing ] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request (range output)" , exchangeRequest = HistoryRequest $ HistoryOptions True True $ HistoryRange $ HistoryRangeOptions sessionNum 6 8 , exchangeReply = HistoryReply $ [ HistoryItem 0 6 "from IPython.display import *\ndisplay(HTML('Hi'))" Nothing , HistoryItem 0 7 "x = input('Hello')\nprint(x)\nx" (Just "'stdin'") ] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request (search)" , exchangeRequest = HistoryRequest $ HistoryOptions True True $ HistorySearch $ HistorySearchOptions 1 "x = input('Hello')\nprint(?)\nx" False , exchangeReply = HistoryReply $ [HistoryItem sessionNum 7 "x = input('Hello')\nprint(x)\nx" Nothing] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "history_request (search output)" , exchangeRequest = HistoryRequest $ HistoryOptions True True $ HistorySearch $ HistorySearchOptions 1 "x = input('Hello')\nprint(?)\nx" False , exchangeReply = HistoryReply $ [HistoryItem sessionNum 7 "x = input('Hello')\nprint(x)\nx" Nothing] , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, kernelIdle] } , MessageExchange { exchangeName = "shutdown (restart)" , exchangeRequest = ShutdownRequest Restart , exchangeReply = ShutdownReply Restart , exchangeKernelRequests = [] , exchangeComms = [] , exchangeKernelOutputs = [kernelBusy, ShutdownNotificationOutput Restart, kernelIdle] } ] kernelIdle, kernelBusy :: KernelOutput kernelIdle = KernelStatusOutput KernelIdle kernelBusy = KernelStatusOutput KernelBusy str :: String -> String str = id