Files
stack/tests/dev/test_nb_integration.py
kert cc094bd1bb
All checks were successful
CI / lint (push) Successful in 31s
CI / notebooks-smoke (push) Successful in 1m28s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / mc (push) Has been skipped
Deploy / report (push) Successful in 13s
CI / test (push) Successful in 13m32s
fix(notebooks): drop cascade errors from snapshot parsing
marimo marks every descendant of a failed or stopped cell with its own
error output ('An ancestor raised an exception', 'ancestor-stopped').
The root cause is always present as a non-ancestor error in the same
snapshot, so cascades only inflate the report — and each one filed its
own deduplicated issue (6 of the 12 nb issues from the first nightly
run were cascade noise). Also makes intentional mo.stop() flow control
count as a pass instead of a failure.
2026-07-10 11:38:30 -04:00

152 lines
4.7 KiB
Python

"""Tests for dev/scripts/nb_integration.py — session snapshot parsing and
pass/fail classification.
The snapshot fixture mirrors the real shape marimo 0.23.13 writes to
__marimo__/session/<nb>.py.json (verified empirically): cell errors appear
as outputs with type "error" plus ename/evalue.
"""
from __future__ import annotations
import importlib.util
import json
import sys
from pathlib import Path
_SCRIPT = Path(__file__).resolve().parents[2] / "dev" / "scripts" / "nb_integration.py"
_spec = importlib.util.spec_from_file_location("_nb_integration", _SCRIPT)
assert _spec and _spec.loader
nbi = importlib.util.module_from_spec(_spec)
sys.modules["_nb_integration"] = nbi
_spec.loader.exec_module(nbi)
SNAPSHOT_OK = {
"version": "1",
"metadata": {"marimo_version": "0.23.13"},
"cells": [
{
"id": "Hbol",
"outputs": [{"type": "data", "data": {"text/html": "<pre>2</pre>"}}],
"console": [],
}
],
}
SNAPSHOT_ERR = {
"version": "1",
"metadata": {"marimo_version": "0.23.13"},
"cells": [
{
"id": "Hbol",
"outputs": [{"type": "data", "data": {"text/html": "<pre>2</pre>"}}],
"console": [],
},
{
"id": "MJUe",
"outputs": [
{
"type": "error",
"ename": "exception",
"evalue": "intentional failure 2",
"traceback": None,
}
],
"console": [
{
"type": "stream",
"name": "stderr",
"text": "<pre>ValueError: intentional failure 2</pre>",
"mimetype": "application/vnd.marimo+traceback",
}
],
},
],
}
def _write(tmp_path: Path, name: str, snap: dict) -> Path:
p = tmp_path / name
p.write_text(json.dumps(snap))
return p
def test_parse_snapshot_clean(tmp_path):
errors = nbi.parse_snapshot(_write(tmp_path, "ok.py.json", SNAPSHOT_OK))
assert errors == []
def test_parse_snapshot_extracts_errors_with_console_detail(tmp_path):
errors = nbi.parse_snapshot(_write(tmp_path, "err.py.json", SNAPSHOT_ERR))
assert len(errors) == 1
e = errors[0]
assert e["cell"] == "MJUe"
assert e["ename"] == "exception"
assert e["evalue"] == "intentional failure 2"
assert "intentional failure" in e["detail"]
def test_parse_snapshot_drops_cascade_errors(tmp_path):
"""Descendants of a failed/stopped cell get their own error outputs;
only the root-cause error should survive parsing."""
snap = {
"version": "1",
"metadata": {"marimo_version": "0.23.13"},
"cells": [
SNAPSHOT_ERR["cells"][1],
{
"id": "ecfG",
"outputs": [
{
"type": "error",
"ename": "exception",
"evalue": "An ancestor raised an exception (ValueError): ",
"traceback": None,
}
],
"console": [],
},
{
"id": "xXTn",
"outputs": [
{
"type": "error",
"ename": "ancestor-stopped",
"evalue": "This cell wasn't run because an ancestor "
"was stopped with `mo.stop`: ",
"traceback": None,
}
],
"console": [],
},
],
}
errors = nbi.parse_snapshot(_write(tmp_path, "cascade.py.json", snap))
assert [e["cell"] for e in errors] == ["MJUe"]
def test_parse_snapshot_missing_file_reports_export_error(tmp_path):
errors = nbi.parse_snapshot(tmp_path / "never-written.py.json")
assert len(errors) == 1
assert errors[0]["ename"] == "snapshot-missing"
def test_classify_pass_fail_and_expected():
expected = {"known_bad.py": "duckdb lock (#508)"}
assert nbi.classify("clean.py", [], expected) == "pass"
err = [{"cell": "x", "ename": "exception", "evalue": "y", "detail": ""}]
assert nbi.classify("clean.py", err, expected) == "fail"
assert nbi.classify("known_bad.py", err, expected) == "expected-fail"
# an expected-failure notebook that passes should surface as pass
assert nbi.classify("known_bad.py", [], expected) == "pass"
def test_report_exit_code():
results = {
"a.py": {"status": "pass", "errors": []},
"b.py": {"status": "expected-fail", "errors": [{"ename": "e"}]},
}
assert nbi.exit_code(results) == 0
results["c.py"] = {"status": "fail", "errors": [{"ename": "e"}]}
assert nbi.exit_code(results) == 1