utapyngo/pytest-unordered

list vs tuples

Closed this issue · 8 comments

Hi,

I recently played around with my own order ignoring pytest assert helper: https://gist.github.com/soxofaan/8e6512f765f0f0df697311c2561be57e . I then polled the pytest devs in pytest-dev/pytest#7899 if it would be interesting to create a PR about this. This turned out to be a duplicate of pytest-dev/pytest#5548 which led to pytest-unordered :)

pytest-unordered seems to do more than what I did in my prototype, except checking the type of the sequence container.
Through the design of unordered(*args), you can not express that you expect for example a list and it should fail on a tuple (or vice versa).

A sequence is not necessarily either a list or a tuple. You are missing strings and generators. Do you want exact type checking for them?

unordered(*'abc') == 'bca'
Out[6]: True
unordered(0, 1, 2, 3) == range(4)
Out[7]: True

Although unordered string comparison seems pretty useless, I want to support generators.

If you want exact type checking you can do it explicitly:

actual = (1, 2)
assert unordered(*actual) == (2, 1) and isinstance(actual, tuple) 

I played with IgnoreOrder a bit. It seems it can only compare sequences that can be sorted. This leads to a TypeError:
[3, 2, {1: ['b', 'a']}] == IgnoreOrder([{1: unordered('a', 'b')}, 2, 3])

You are missing strings and generators

good point, I didn't think of generators

Do you want exact type checking for them?

I would expect the type checking to be the default with unordered because it is the default with regular asserts.

For example, when working with standard pytest, one expects this assert

assert actual == {"foo": [1,2,3]}

to pass on {"foo": [1,2,3]} and fail on {"foo": (1,2,3)}

When adding unordered one probably wants to ignore the order, but keep the type checking.

If you want exact type checking you can do it explicitly

yes, but that does not scale well if you are using unordered in a nested way, e.g.

actual = {"foo": [2, 1], "bar": (4, 3)}
assert actual == {"foo": unordered(1,2), "bar": unordered(4,3)} and isinstance(actual["foo"], list) and isinstance(actual["bar"], tuple)

It seems it can only compare sequences that can be sorted.

indeed that is a problem in my prototype that I quickfixed somewhat dirty with the key argument of sorted.
for example, by using repr as key function to make the items sortable:

def test_ignore_order_key():
    assert [{"f": "b"}, {"x": "y:"}] == IgnoreOrder([{"x": "y:"}, {"f": "b"}], key=repr)

apparently you added some special case handling for generators recently:

def unordered(*args) -> UnorderedList:
if len(args) == 1 and isinstance(args[0], Generator):
return UnorderedList(args[0])
return UnorderedList(args)

why not generalize it to:

  • if you use it *args-style like unordered(1,2,3) you don't care about type checking
  • if you use it single arg-style like unordered([1,2,3]) or unordered((1,2,3)) or ... you do care a bout type checking

why not generalize it to:

* if you use it `*args`-style like `unordered(1,2,3)`  you don't care about type checking

* if you use it single arg-style like `unordered([1,2,3])` or `unordered((1,2,3))` or ... you do care a bout type checking

Good point. Can you create a pull request?

Thank you. It looks easier than I initially thought.