Spaces:
Running
Running
Download tests/test_codegen.py from jkorstad/daggr-studio: direct link, hf CLI and curl.
- Browser
- Download file 6.13 kB
-
https://huggingface.co/spaces/jkorstad/daggr-studio/resolve/main/tests/test_codegen.py
- Command line
-
hf download hf://spaces/jkorstad/daggr-studio/tests/test_codegen.py
-
curl -L -o test_codegen.py https://huggingface.co/spaces/jkorstad/daggr-studio/resolve/main/tests/test_codegen.py
6.13 kB
| """Codegen must be deterministic and produce code that is (a) valid Python and (b) a | |
| working daggr graph. Both are asserted here - offline, with no network calls.""" | |
| from __future__ import annotations | |
| import ast | |
| import json | |
| from daggrstudio.codegen import ( | |
| build_graph, | |
| infer_postprocess_hint, | |
| node_name_for, | |
| render_app_py, | |
| resolve_postprocess, | |
| ) | |
| from daggrstudio.registry.bricks import get_registry | |
| from daggrstudio.spec import Binding, Step, WorkflowSpec | |
| from tests.conftest import concept_spec | |
| def test_build_graph_creates_nodes_and_edges(): | |
| graph = build_graph(concept_spec()) | |
| names = set(graph.nodes) | |
| assert {"concept", "cutout"}.issubset(names) # space steps keep their id | |
| assert any(n.startswith("organize_outputs") for n in names) # fn step named after fn | |
| connections = graph.get_connections() | |
| endpoints = {(c[0], c[1], c[2], c[3]) for c in connections} if connections else set() | |
| assert connections, "expected at least one edge" | |
| assert any("concept" in str(c) and "cutout" in str(c) for c in connections), endpoints | |
| def test_graph_has_a_stable_name_and_persist_key(): | |
| graph = build_graph(concept_spec()) | |
| assert graph.name == "Concept to sprite" | |
| def test_build_graph_rejects_unknown_brick(): | |
| spec = concept_spec() | |
| spec.steps[0].brick_id = "not-a-real-brick" | |
| spec.steps[0].source = "owner/does-not-exist" | |
| import pytest | |
| with pytest.raises(KeyError, match="unknown brick"): | |
| build_graph(spec) | |
| def test_seed_registry_entries_carry_provenance(): | |
| reg = get_registry() | |
| seed = reg.by_id("flux1-schnell") | |
| assert seed is not None and seed.verified_at # introspected in-session | |
| unverified = reg.by_id("wan21") | |
| assert unverified is not None | |
| assert not unverified.is_live_verified or unverified.verified_at | |
| def test_node_names_are_unique_and_url_safe(): | |
| used: set[str] = set() | |
| a = node_name_for(Step(id="Concept Art", kind="space"), used) | |
| b = node_name_for(Step(id="concept art", kind="space"), used) | |
| assert a == "concept_art" and b == "concept_art_2" | |
| assert node_name_for(Step(id="x", kind="fn"), used) is None | |
| def test_resolve_postprocess_hints(): | |
| first = resolve_postprocess("first") | |
| assert first("img", 42) == "img" | |
| second = resolve_postprocess("tuple_index:1") | |
| assert second("original", "processed") == "processed" | |
| assert second("only") == "only" # tolerant of single-value returns | |
| dp = resolve_postprocess("dict_path:path") | |
| assert dp({"path": "/tmp/a.png"}) == "/tmp/a.png" | |
| assert dp("/tmp/a.png") == "/tmp/a.png" | |
| assert resolve_postprocess(None) is None | |
| assert resolve_postprocess("nonsense") is None | |
| def test_postprocess_hint_comes_from_the_registry(): | |
| spec = concept_spec() | |
| registry = get_registry() | |
| cutout = spec.steps[1] | |
| brick = registry.by_id(cutout.brick_id) | |
| # whatever the registry verified for this brick is what codegen must use | |
| assert infer_postprocess_hint(brick, cutout) == (brick.postprocess_hint | |
| if brick.postprocess_hint else None) | |
| # an explicit step-level hint always overrides the registry | |
| cutout.postprocess = "tuple_index:1" | |
| assert infer_postprocess_hint(brick, cutout) == "tuple_index:1" | |
| def test_multi_value_bricks_get_a_postprocess_hint(): | |
| """A brick that returns (value, extra) must never be used without unpacking.""" | |
| registry = get_registry() | |
| multi = [b for b in registry.all() if b.postprocess_hint and b.status == "running"] | |
| assert multi, "registry should expose at least one multi-return brick" | |
| for brick in multi: | |
| hint = brick.postprocess_hint | |
| assert hint.startswith("tuple_index:") or hint.startswith("dict_path:") | |
| def test_render_app_py_is_valid_python_and_self_contained(): | |
| spec = concept_spec() | |
| registry = get_registry() | |
| source = render_app_py(spec, registry) | |
| ast.parse(source) # raises SyntaxError if codegen emits broken code | |
| assert "from daggr import FnNode, GradioNode, InferenceNode, InputNode, Graph" in source | |
| gen = registry.by_id(spec.steps[0].brick_id) | |
| cut = registry.by_id(spec.steps[1].brick_id) | |
| assert gen.source in source | |
| assert f'api_name="{gen.api_name}"' in source | |
| assert cut.source in source | |
| assert "def organize_outputs(" in source # helper inlined | |
| assert "daggrstudio" not in source # no dependency on this package | |
| assert "graph.launch()" in source | |
| def test_render_is_deterministic_and_tracks_the_spec(): | |
| spec = concept_spec() | |
| one = render_app_py(spec) | |
| two = render_app_py(spec) | |
| # identical except for the generated timestamp line | |
| assert "\n".join(l for l in one.splitlines() if "Generated:" not in l) == \ | |
| "\n".join(l for l in two.splitlines() if "Generated:" not in l) | |
| spec.name = "Renamed" | |
| assert "Renamed" in render_app_py(spec) | |
| def test_rendered_app_reparses_into_an_equivalent_graph(): | |
| """The downloaded code and the in-Space graph share one spec - prove the spec survives | |
| a JSON round trip through the render path.""" | |
| spec = concept_spec() | |
| again = WorkflowSpec.from_dict(json.loads(json.dumps(spec.to_dict()))) | |
| assert render_app_py(again).replace("Generated: ", "").split("\n")[0:3] == \ | |
| render_app_py(spec).replace("Generated: ", "").split("\n")[0:3] | |
| def test_fn_step_uses_the_real_function_signature(): | |
| graph = build_graph(concept_spec()) | |
| fn_nodes = [n for name, n in graph.nodes.items() if name.startswith("organize_outputs")] | |
| assert fn_nodes, "fn step should produce a node" | |
| node = fn_nodes[0] | |
| assert "source" in node._input_ports | |
| assert "output_dir" in node._input_ports | |
| def test_build_graph_handles_a_spec_with_no_inputs(): | |
| spec = WorkflowSpec(name="No inputs", steps=[ | |
| Step(id="bg", kind="space", brick_id="background-removal", | |
| inputs={"image": Binding(value="/tmp/x.png")}, | |
| outputs={"image": {"component": "image"}}), | |
| ]) | |
| graph = build_graph(spec) | |
| assert graph is not None and len(graph.nodes) == 1 |