99from pytest_lsp import ClientServerConfig , LanguageClient
1010
1111
12- def to_dict (obj : Any ) -> dict | list : # noqa: ANN401
12+ def asdict (obj : Any ) -> dict [ str , Any ] : # noqa: ANN401
1313 """Recursively convert namedtuple Objects to dicts."""
1414 if hasattr (obj , "_asdict" ):
15- return {k : to_dict (v ) for k , v in obj ._asdict ().items ()}
15+ return {k : asdict (v ) for k , v in obj ._asdict ().items ()}
1616 if isinstance (obj , list ):
17- return [to_dict (item ) for item in obj ]
17+ # Just used recursively
18+ return [asdict (item ) for item in obj ] # pyright: ignore[reportReturnType]
1819 if isinstance (obj , dict ):
19- return {k : to_dict (v ) for k , v in obj .items ()}
20+ return {k : asdict (v ) for k , v in obj .items ()}
2021 return obj
2122
2223
@@ -298,6 +299,13 @@ def __():
298299@pytest .mark .asyncio
299300async def test_simple_marimo_run (client : LanguageClient ) -> None :
300301 """Test that we can collect marimo operations until cell reaches idle state."""
302+ code = """\
303+ import sys
304+
305+ print("hello, world")
306+ print("error message", file=sys.stderr)
307+ x = 42\
308+ """
301309
302310 client .notebook_document_did_open (
303311 lsp .DidOpenNotebookDocumentParams (
@@ -317,7 +325,7 @@ async def test_simple_marimo_run(client: LanguageClient) -> None:
317325 uri = "file:///exec_test.py#cell1" ,
318326 language_id = "python" ,
319327 version = 1 ,
320- text = "x = 42" ,
328+ text = code ,
321329 )
322330 ],
323331 ),
@@ -327,11 +335,20 @@ async def test_simple_marimo_run(client: LanguageClient) -> None:
327335 completion_event = asyncio .Event ()
328336
329337 @client .feature ("marimo/operation" )
330- def on_marimo_operation (params : Any ) -> None : # noqa: ANN401
338+ async def on_marimo_operation (params : Any ) -> None : # noqa: ANN401
331339 # pygls dynamically makes an `Object` named tuple which makes snapshotting hard
332340 # we just convert to a regular dict here for snapshotting
333- messages .append (to_dict (params ))
341+ msg = asdict (params )
342+ # Sort variables lists for consistent ordering in tests
343+ if msg .get ("op" ) in ("variables" , "variable-values" ):
344+ msg ["data" ]["variables" ] = sorted (
345+ msg ["data" ]["variables" ], key = lambda x : x ["name" ]
346+ )
347+ messages .append (msg )
334348 if params .op == "completed-run" :
349+ # FIXME: stdin/stdout are flushed every 10ms, so wait 100ms to ensure
350+ # all related events. The frontend uses the same workaround.
351+ await asyncio .sleep (0.1 )
335352 completion_event .set ()
336353
337354 await client .workspace_execute_command_async (
@@ -341,7 +358,7 @@ def on_marimo_operation(params: Any) -> None: # noqa: ANN401
341358 {
342359 "notebook_uri" : "file:///exec_test.py" ,
343360 "cell_ids" : ["cell1" ],
344- "codes" : ["x = 42" ],
361+ "codes" : [code ],
345362 }
346363 ],
347364 )
@@ -355,7 +372,15 @@ def on_marimo_operation(params: Any) -> None: # noqa: ANN401
355372 "op" : "update-cell-codes" ,
356373 "data" : {
357374 "cell_ids" : ["cell1" ],
358- "codes" : ["x = 42" ],
375+ "codes" : [
376+ """\
377+ import sys
378+
379+ print("hello, world")
380+ print("error message", file=sys.stderr)
381+ x = 42\
382+ """
383+ ],
359384 "code_is_stale" : False ,
360385 },
361386 },
@@ -369,7 +394,8 @@ def on_marimo_operation(params: Any) -> None: # noqa: ANN401
369394 "op" : "variables" ,
370395 "data" : {
371396 "variables" : [
372- {"name" : "x" , "declared_by" : ["cell1" ], "used_by" : []}
397+ {"name" : "sys" , "declared_by" : ["cell1" ], "used_by" : []},
398+ {"name" : "x" , "declared_by" : ["cell1" ], "used_by" : []},
373399 ]
374400 },
375401 },
@@ -410,7 +436,10 @@ def on_marimo_operation(params: Any) -> None: # noqa: ANN401
410436 "notebookUri" : "file:///exec_test.py" ,
411437 "op" : "variable-values" ,
412438 "data" : {
413- "variables" : [{"name" : "x" , "value" : "42" , "datatype" : "int" }]
439+ "variables" : [
440+ {"name" : "sys" , "value" : "sys" , "datatype" : "module" },
441+ {"name" : "x" , "value" : "42" , "datatype" : "int" },
442+ ]
414443 },
415444 },
416445 {
@@ -446,6 +475,48 @@ def on_marimo_operation(params: Any) -> None: # noqa: ANN401
446475 "timestamp" : IsFloat (),
447476 },
448477 },
449- {"notebookUri" : "file:///exec_test.py" , "op" : "completed-run" , "data" : {}},
478+ {
479+ "notebookUri" : "file:///exec_test.py" ,
480+ "op" : "completed-run" ,
481+ "data" : {},
482+ },
483+ {
484+ "notebookUri" : "file:///exec_test.py" ,
485+ "op" : "cell-op" ,
486+ "data" : {
487+ "cell_id" : "cell1" ,
488+ "output" : None ,
489+ "console" : {
490+ "channel" : "stdout" ,
491+ "mimetype" : "text/plain" ,
492+ "data" : "hello, world\n " ,
493+ "timestamp" : IsFloat (),
494+ },
495+ "status" : None ,
496+ "stale_inputs" : None ,
497+ "run_id" : None ,
498+ "serialization" : None ,
499+ "timestamp" : IsFloat (),
500+ },
501+ },
502+ {
503+ "notebookUri" : "file:///exec_test.py" ,
504+ "op" : "cell-op" ,
505+ "data" : {
506+ "cell_id" : "cell1" ,
507+ "output" : None ,
508+ "console" : {
509+ "channel" : "stderr" ,
510+ "mimetype" : "text/plain" ,
511+ "data" : "error message\n " ,
512+ "timestamp" : IsFloat (),
513+ },
514+ "status" : None ,
515+ "stale_inputs" : None ,
516+ "run_id" : None ,
517+ "serialization" : None ,
518+ "timestamp" : IsFloat (),
519+ },
520+ },
450521 ]
451522 )
0 commit comments