-
Notifications
You must be signed in to change notification settings - Fork 151
test(workflow): pin replace_ops autogrow replay fidelity; flag the apply_specs gap (BE-10726) #848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2166,6 +2166,160 @@ def test_p9_autogrow_names_template_converges(self): | |
| assert names == {"images.first", "images.second"}, names | ||
| assert ops.canonical(ab) == ops.canonical(ba) | ||
|
|
||
| # --------------------------------------------------------------------- | ||
| # replace_ops on autogrow canvases: the CLI's repeated-edit | ||
| # paths leave sparse numbering where the FE compacts; the bulk-writer | ||
| # batch must round-trip both shapes. | ||
| # --------------------------------------------------------------------- | ||
|
|
||
| @staticmethod | ||
| def _autogrow_replace_canvas(n_sources: int = 3) -> dict: | ||
| """A BatchImagesNode whose autogrow base carries ``n_sources`` wired | ||
| connects (the CLI's repeated-edit path), ready to feed | ||
| ``replace_ops`` as ``new``.""" | ||
| from comfy_cli import workflow_ops | ||
|
|
||
| g = _graph() | ||
| wf = _autogrow_workflow() | ||
| wf["nodes"].append( | ||
| { | ||
| "id": 22, | ||
| "type": "VAEDecode", | ||
| "pos": [0, 200], | ||
| "inputs": [ | ||
| {"name": "samples", "type": "LATENT", "link": None}, | ||
| {"name": "vae", "type": "VAE", "link": None}, | ||
| ], | ||
| "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": []}], | ||
| "widgets_values": [], | ||
| } | ||
| ) | ||
| wf["last_node_id"] = 22 | ||
| for src in range(n_sources): | ||
| wf, _ = workflow_ops.connect(wf, g, 20 + src, "IMAGE", 10, "images", actor="a") | ||
| return wf | ||
|
|
||
| @staticmethod | ||
| def _batchimages_slots(workflow: dict) -> list[tuple[str, bool]]: | ||
| node = next(n for n in workflow["nodes"] if n["type"] == "BatchImagesNode") | ||
| return [(i["name"], i.get("link") is not None) for i in node["inputs"]] | ||
|
|
||
| @staticmethod | ||
| def _replay_ops(batch: list[dict], graph) -> dict: | ||
| from comfy_cli import workflow_ops | ||
|
|
||
| doc = {"nodes": [], "links": [], "last_node_id": 0, "last_link_id": 0} | ||
| for op in batch: | ||
| doc = workflow_ops.apply_op(doc, op, graph) | ||
| return doc | ||
|
|
||
| def test_replace_ops_replays_autogrow_wiring_verbatim(self): | ||
| """replace_ops + apply_op reproduces a contiguous autogrow canvas | ||
| exactly: every grown slot keeps its name and its link, no slot is | ||
| re-grown, and no link is dropped (the FE-compacted repeated-edit | ||
| shape).""" | ||
| from comfy_cli import workflow_ops | ||
|
|
||
| g = _graph() | ||
| new = self._autogrow_replace_canvas() | ||
| old = {"nodes": [], "links": [], "last_node_id": 0, "last_link_id": 0} | ||
| batch = workflow_ops.replace_ops(old, new) | ||
| doc = self._replay_ops(batch, g) | ||
| assert self._batchimages_slots(doc) == self._batchimages_slots(new) | ||
| assert len(doc["links"]) == len(new["links"]) | ||
|
|
||
| def test_replace_ops_replays_a_holed_autogrow_canvas_verbatim(self): | ||
| """The CLI's repeated-edit numbering is sparse where the FE compacts: | ||
| a canvas carrying images.image0 + images.image2 with NO image1 must | ||
| survive a replace_ops replay byte-for-byte in its slot names — the | ||
| replay must neither renumber the grown slots (silent compaction the | ||
| CLI never performed) nor mint a gap-filled duplicate for the hole.""" | ||
| from comfy_cli import workflow_ops | ||
|
|
||
| g = _graph() | ||
| # A faithful holed canvas: sparse NAMES, dense slot array (the FE | ||
| # keeps the inputs array packed; only the element numbering gaps). | ||
| new = { | ||
| "last_node_id": 22, | ||
| "last_link_id": 2, | ||
| "nodes": [ | ||
| { | ||
| "id": 10, | ||
| "type": "BatchImagesNode", | ||
| "pos": [200, 0], | ||
| "inputs": [ | ||
| {"name": "images", "type": "COMFY_AUTOGROW_V3", "link": None}, | ||
| {"name": "images.image0", "type": "IMAGE", "link": 1}, | ||
| {"name": "images.image2", "type": "IMAGE", "link": 2}, | ||
| ], | ||
| "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": []}], | ||
| "widgets_values": [], | ||
| }, | ||
| { | ||
| "id": 20, | ||
| "type": "VAEDecode", | ||
| "pos": [0, 0], | ||
| "inputs": [ | ||
| {"name": "samples", "type": "LATENT", "link": None}, | ||
| {"name": "vae", "type": "VAE", "link": None}, | ||
| ], | ||
| "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [1]}], | ||
| "widgets_values": [], | ||
| }, | ||
| { | ||
| "id": 22, | ||
| "type": "VAEDecode", | ||
| "pos": [0, 200], | ||
| "inputs": [ | ||
| {"name": "samples", "type": "LATENT", "link": None}, | ||
| {"name": "vae", "type": "VAE", "link": None}, | ||
| ], | ||
| "outputs": [{"name": "IMAGE", "type": "IMAGE", "links": [2]}], | ||
| "widgets_values": [], | ||
| }, | ||
| ], | ||
| "links": [[1, 20, 0, 10, 1, "IMAGE"], [2, 22, 0, 10, 2, "IMAGE"]], | ||
| } | ||
| old = {"nodes": [], "links": [], "last_node_id": 0, "last_link_id": 0} | ||
| batch = workflow_ops.replace_ops(old, new) | ||
| doc = self._replay_ops(batch, g) | ||
| assert self._batchimages_slots(doc) == [ | ||
| ("images", False), | ||
| ("images.image0", True), | ||
| ("images.image2", True), | ||
| ] | ||
| assert len(doc["links"]) == len(new["links"]) | ||
|
|
||
| @pytest.mark.xfail( | ||
| reason=( | ||
| "replace_ops mints connect spec refs as `$alias.<slot index>`, but" | ||
| " apply_specs re-mints each node from the live catalog, so a freshly" | ||
| " added BatchImagesNode has only the bare `images` input and the index" | ||
| " refs resolve to nothing (ValueError: input '1' not found). The spec" | ||
| " path of the §8.8 one-artifact/two-consumers contract cannot replay" | ||
| " ANY autogrow wiring — holed or contiguous. Known defect, raised for" | ||
| " an owner ruling; unblocks when the mint addresses grown slots in a" | ||
| " form the spec path can resolve." | ||
| ), | ||
| strict=True, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://docs.pytest.org/en/stable/reference/reference.html |
rg -n 'xfail|raises'Repository: Comfy-Org/comfy-cli Length of output: 38370 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- target test ---'
sed -n '2275,2330p' tests/comfy_cli/command/test_workflow_edit.py
printf '%s\n' '--- pytest configuration and dependency declarations ---'
rg -n -C 3 'pytest|xfail_strict|strict_xfail' pyproject.toml pytest.ini setup.cfg tox.ini noxfile.py requirements*.txt 2>/dev/null || trueRepository: Comfy-Org/comfy-cli Length of output: 3441 Restrict the expected failure to
🤖 Prompt for AI Agents |
||
| ) | ||
| def test_replace_ops_batch_is_replayable_through_apply_specs(self): | ||
| """The other half of §8.8: the same array must be accepted verbatim by | ||
| apply_specs. The op path reproduces autogrow wiring (tests above); the | ||
| spec path currently discards the whole batch. Structure-only assertion: | ||
| every grown slot lands wired; per §8.8 the spec path reproduces the | ||
| STRUCTURE, so a compaction-renamed slot is acceptable there.""" | ||
| from comfy_cli import workflow_ops | ||
|
|
||
| g = _graph() | ||
| new = self._autogrow_replace_canvas() | ||
| old = {"nodes": [], "links": [], "last_node_id": 0, "last_link_id": 0} | ||
| batch = workflow_ops.replace_ops(old, new) | ||
| wf2, _applied, _aliases = workflow_ops.apply_specs(copy.deepcopy(old), g, batch) | ||
| wired = [wired for name, wired in self._batchimages_slots(wf2) if name != "images"] | ||
| assert wired == [True, True, True] | ||
| assert len(wf2["links"]) == len(new["links"]) | ||
|
|
||
| def test_p9_autogrow_grow_id_survives_api_conversion(self): | ||
| """The ``grow_id`` bookkeeping (persisted on grown slots as their | ||
| convergence identity) must not break API conversion — both wired sources | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Assert the source-to-slot wiring in both replay tests.
Both tests only verify slot names, whether slots are linked, and the link count. A replay that swaps the sources for
images.image0andimages.image2, or maps every slot to one valid source, still passes. Compare normalized link topology, such as slot name plus source-node position and output index, betweendocandnew.tests/comfy_cli/command/test_workflow_edit.py#L2228-L2229: compare contiguous source-to-slot wiring, not only linked-state and count.tests/comfy_cli/command/test_workflow_edit.py#L2286-L2291: compare sparse source-to-slot wiring, including theimage0andimage2mapping.📍 Affects 1 file
tests/comfy_cli/command/test_workflow_edit.py#L2228-L2229(this comment)tests/comfy_cli/command/test_workflow_edit.py#L2286-L2291🤖 Prompt for AI Agents