Repository navigation
Add option to generate test operations in diffs - #248
Merged
Merged
Conversation
make_patch, JsonPatch.from_diff and DiffBuilder take a new keyword argument generate_test_ops (default False). When set, each remove and replace operation of the generated patch is preceded by a test operation that checks the value it removes or replaces. The patch then records the previous values and fails with JsonPatchTestFailed when applied to a document that differs there. Removals that the diff turns into moves are not tested. jsondiff gets a matching -t/--test-ops option. Based on #154 by Robert Cochran, reimplemented on top of the current diff engine, which resolves array indices when replaying operations, so the test operations get the same indices as the operations they guard. The new argument is added after the existing ones, so positional callers (including of `optimization`) are unaffected. Co-Authored-By: Robert Cochran <robert.cochran@peachfinance.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PewzpidDeSzMcCa4Krt54E
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The generated test value aliases mutable source containers, allowing later source mutations to alter the integrity check; it needs a deep copy to preserve the original snapshot.
1 open issue
What changed in this PR
Adds optional test operations to generated JSON patches, allowing callers to check previous values before removing or replacing them while preserving default behavior.
Changes:
- Adds
generate_test_opsto the diff APIs, with handling for moves and shifting array indices. - Exposes the feature through
jsondiff -t/--test-opsand documents its usage. - Adds unit, CLI, and property-based coverage.
| File | Description |
|---|---|
tests.py |
Tests guarded diffs, array indices, moves, and CLI options. |
property_tests.py |
Checks operation ordering and round trips with test generation enabled. |
jsonpatch.py |
Implements optional test generation in the diff engine and public APIs. |
jsonpatch_cli.py |
Adds and forwards the --test-ops option. |
doc/tutorial.rst |
Demonstrates guarded patches and failures on changed values. |
doc/commandline.rst |
Documents the new CLI option. |
Review effort: Balanced
The test operations referred to the values in src, so changing src after diffing changed what the patch tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PewzpidDeSzMcCa4Krt54E
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Supersedes #154 by @RobertCochran, which no longer applied to master. The commit credits them as co-author.
What it does
make_patch,JsonPatch.from_diffandDiffBuildertake a new keyword argument,generate_test_ops(defaultFalse). When it is set, atestoperation comes before eachremoveandreplaceand checks the value about to be removed or replaced. The patch then records the previous values, and applying it to a document that differs there fails withJsonPatchTestFailed.jsondiffgets a matching-t/--test-opsoption.optimization=False, every remove is tested.replace, with onetestin front of it.Differences from #154
testoperations in diff #154 was opened. Its code (_on_undo_*,TestOperationobjects in the linked list) no longer exists, so this PR implements the feature again. Test operations use the same locations as other operations and get their array indices when the operations are replayed. Their paths therefore stay correct when earlier operations shift items.testoperations in diff #154 droppedoptimizationfromfrom_diffand put the new argument beforepointer_clsinmake_patch, which broke callers passing those by position.testoperations in diff #154 instead checked whether the previous node in the list was a test.Tests
GenerateTestOpsTestsintests.py: checks exact outputs, including array indices, moves,optimization=False, a replace at the document root, and failing on changed values.jsondiff --test-ops.testat the same path before eachremove/replace, and it still turnssrcintodst. It is checked with and withoutoptimization.doc/tutorial.rstruns as a doctest, anddoc/commandline.rstshows the new option.python tests.pyandpytestpass.Generated by Claude Code