File size: 2,311 Bytes
c964813
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""Replay each contract example in ../contract against a fresh Manager.

Each example lists calls in order, keyed by method name. A call has "args"
(omitted for methods that take none) and either "result" (the expected return
value as JSON; floats are compared to 6 significant figures) or "error" (a
UserError of "kind": "user", whose text contains "message" if given). This
mirrors what @elvis/pyodide's worker sends to the UI.
"""

import json
from pathlib import Path

import pytest

from manager import Manager

CONTRACT_DIR = Path(__file__).parent.parent / "contract"
EXAMPLES = sorted(CONTRACT_DIR.glob("*.json"))


def same(actual, expected) -> bool:
    """Equal JSON values, with floats compared approximately."""
    if isinstance(expected, float) or isinstance(actual, float):
        return isinstance(actual, (int, float)) and actual == pytest.approx(expected, rel=1e-6, abs=1e-12)
    if isinstance(expected, dict):
        return isinstance(actual, dict) and actual.keys() == expected.keys() and all(same(actual[k], expected[k]) for k in expected)
    if isinstance(expected, list):
        return isinstance(actual, list) and len(actual) == len(expected) and all(same(a, e) for a, e in zip(actual, expected))
    return actual == expected


@pytest.mark.parametrize("path", EXAMPLES, ids=[p.stem for p in EXAMPLES])
def test_contract_example(path: Path) -> None:
    example = json.loads(path.read_text())
    manager = Manager()

    for method, call in example["calls"].items():
        fn = getattr(manager, method)
        try:
            data = fn(call["args"]) if "args" in call else fn()
        except Exception as e:
            # The worker treats only UserError as a user error; anything else is a bug
            if type(e).__name__ != "UserError":
                raise
            assert "error" in call, f"{method} raised UserError: {e}"
            assert call["error"]["kind"] == "user"
            assert call["error"].get("message", "") in str(e)
            continue

        assert "error" not in call, f"{method} returned {data!r} instead of raising UserError"
        # Results must be plain JSON values (no NumPy types) to reach the UI unchanged
        actual = json.loads(json.dumps(data))
        assert same(actual, call["result"]), f"{method} returned {actual!r}"