-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Preserve insertion order for extra dict items in assertion output #14066
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?
Conversation
for more information, see https://pre-commit.ci
nicoddemus
left a comment
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.
Thanks @RafiB982, please take a look at my comments.
src/_pytest/assertion/util.py
Outdated
| + highlighter(saferepr({k: right[k]})) | ||
| ] | ||
| extra_left = set_left - set_right | ||
| extra_left = [k for k in left if k not in right] |
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.
It is more efficient to reuse the set for the containment check:
| extra_left = [k for k in left if k not in right] | |
| extra_left = [k for k in left if k not in set_right] |
src/_pytest/assertion/util.py
Outdated
| highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines() | ||
| ) | ||
| extra_right = set_right - set_left | ||
| extra_right = [k for k in right if k not in left] |
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.
| extra_right = [k for k in right if k not in left] | |
| extra_right = [k for k in right if k not in set_left] |
testing/test_assertion.py
Outdated
| test_order=""" | ||
| def test_order(): | ||
| a = { | ||
| "first": 1, |
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.
Please let's use a dict with more keys for this test, only 2 seems few to catch any regressions. I suggest using simply "a", "b", etc, inserted in non-alphabetical order.
testing/test_assertion.py
Outdated
| stdout = re.sub(r"\x1b\[[0-9;]*m", "", stdout) | ||
|
|
||
| assert "Left contains 2 more items:" in stdout | ||
|
|
||
| dict_line = next( | ||
| line | ||
| for line in stdout.splitlines() | ||
| if "{" in line and "}" in line and "first" in line | ||
| ) | ||
|
|
||
| first = dict_line.find("first") | ||
| second = dict_line.find("second") | ||
|
|
||
| assert first != -1 and second != -1 | ||
| assert first < second |
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.
Could we use just result.stdout.fnmatch_lines instead to verify the output, instead of this custom parsing logic?
|
Thanks for the review @nicoddemus I’ve addressed the feedback by:
Please let me know if you’d like any further adjustments. |
testing/test_assertion.py
Outdated
| def test_dict_extra_items_preserve_insertion_order(pytester): | ||
| pytester.makepyfile( | ||
| test_order=""" | ||
| def test_order(): | ||
| a = { | ||
| "b": 2, | ||
| "a": 1, | ||
| "d": 4, | ||
| "e": 5, | ||
| "c": 3, | ||
| } | ||
| assert a == {} | ||
| """ | ||
| ) | ||
|
|
||
| result = pytester.runpytest("-vv") | ||
|
|
||
| result.stdout.fnmatch_lines( | ||
| [ | ||
| "*Left contains 5 more items:*", | ||
| "*'b': 2*", | ||
| "*'a': 1*", | ||
| "*'d': 4*", | ||
| "*'e': 5*", | ||
| "*'c': 3*", | ||
| ] | ||
| ) |
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.
Thanks, but I noticed the test as written still passes in main, because it ends up matching against several lines of the output by chance.
Here is the output of the internal test_order in your branch:
================================ FAILURES =================================
_______________________________ test_order ________________________________
def test_order():
a = {
"b": 2,
"a": 1,
"d": 4,
"e": 5,
"c": 3,
}
> assert a == {}
E AssertionError: assert {'b': 2, 'a': 1, 'd': 4, 'e': 5, 'c': 3} == {}
E
E Left contains 5 more items:
E {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
E
E Full diff:
E - {}
E + {
E + 'a': 1,
E + 'b': 2,
E + 'c': 3,
E + 'd': 4,
E + 'e': 5,
E + }
.tmp\test_order.py:12: AssertionError
========================= short test summary info =========================
FAILED .tmp\test_order.py::test_order - AssertionError: assert {'b': 2, 'a': 1, 'd': 4, 'e': 5, 'c': 3} == {}
Left contains 5 more items:
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
Full diff:
- {}
+ {
+ 'a': 1,
+ 'b': 2,
+ 'c': 3,
+ 'd': 4,
+ 'e': 5,
+ }
============================ 1 failed in 0.19s ============================
As we can see, the full diff still shows the keys in sorted order.
The problem is that the keys (for example 'a': 1) appear many times in the output and the test as written ends up matching that in different places, even though the "Full diff" output is still sorted.
Here is an improved version of the test which fails in main:
| def test_dict_extra_items_preserve_insertion_order(pytester): | |
| pytester.makepyfile( | |
| test_order=""" | |
| def test_order(): | |
| a = { | |
| "b": 2, | |
| "a": 1, | |
| "d": 4, | |
| "e": 5, | |
| "c": 3, | |
| } | |
| assert a == {} | |
| """ | |
| ) | |
| result = pytester.runpytest("-vv") | |
| result.stdout.fnmatch_lines( | |
| [ | |
| "*Left contains 5 more items:*", | |
| "*'b': 2*", | |
| "*'a': 1*", | |
| "*'d': 4*", | |
| "*'e': 5*", | |
| "*'c': 3*", | |
| ] | |
| ) | |
| def test_dict_extra_items_preserve_insertion_order(pytester: Pytester) -> None: | |
| """Assertion output of dict diff shows keys in insertion order (#13503).""" | |
| pytester.makepyfile( | |
| test_order=""" | |
| def test_order(): | |
| a = { | |
| "b": 2, | |
| "a": 1, | |
| "d": 4, | |
| "e": 5, | |
| "c": 3, | |
| } | |
| assert a == {} | |
| """ | |
| ) | |
| result = pytester.runpytest("-vv") | |
| result.stdout.fnmatch_lines( | |
| [ | |
| "*Left contains 5 more items:*", | |
| "*Full diff:", | |
| "* + *'b': 2,", | |
| "* + *'a': 1,", | |
| "* + *'d': 4,", | |
| "* + *'e': 5,", | |
| "* + *'c': 3,", | |
| "test_order.py:*: AssertionError", | |
| ] | |
| ) |
The test above fails in main and also fails in this branch. It seems more work is needed to handle the Full diff output.
changelog/13503.bugfix.rst
Outdated
| @@ -0,0 +1 @@ | |||
| Preserved insertion order for extra dictionary items in assertion failure output. | |||
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.
| Preserved insertion order for extra dictionary items in assertion failure output. | |
| Display dictionary differences in assertion failures using the original key insertion order instead of sorted order. |
|
The documentation failure is being addressed in #14067. |
for more information, see https://pre-commit.ci
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](actions/cache@v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…pytest-dev#14059) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.9 to 8.0.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](peter-evans/create-pull-request@84ae59a...98357b1) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…4060) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…dev#14061) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.1 to 5.5.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](codecov/codecov-action@5a10915...671740a) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pytest bot <[email protected]>
Pinning to `<9.0` to fix doc building. Related to python-trio/sphinxcontrib-trio#399.
updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.9 → v0.14.10](astral-sh/ruff-pre-commit@v0.14.9...v0.14.10) - [github.com/woodruffw/zizmor-pre-commit: v1.18.0 → v1.19.0](zizmorcore/zizmor-pre-commit@v1.18.0...v1.19.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
|
Hi @nicoddemus ,
I’ve rebased the branch and pushed the changes. Please let me know if anything else should be adjusted. |
When dict assertions fail, pytest currently displays extra items using
unordered set iteration, which loses insertion order.
This change preserves insertion order for the
"Left contains N more items" and "Right contains N more items" sections
by iterating keys in mapping order instead of set order.
A focused regression test is included.
Closes #13503