pytest icon indicating copy to clipboard operation
pytest copied to clipboard

Document way to customize abbreviation (elipsis) on AssertionError

Open ssbarnea opened this issue 4 years ago • 8 comments

I keep getting errors that I cannot debug because the printed AssertionError is censored so much that makes impossible to see what was the difference.

Adding -vvvv did not help at all changing the way exceptions are rendered.

      assert l in out

E AssertionError: assert 'instance docker ansible default false false' in 'Instance Name Driver Name Provisioner Name Scenario Name Created Converged\n--------------- ---------...i-node false false\ninstance-2 docker ansible multi-node false false\n'

I did try to research the subject a lot online but I was not able to find any solution that would allow me to disable or customize when abbreviation happens, to make the limit upwards.

I raised this to PyTest because I am looking for a solution that can enable this at the entire test-suite and not on a specific file with tests, being able to do it globally is key for big projects.

Some resources I found:

  • https://stackoverflow.com/questions/50608443/how-do-i-get-pycharm-to-show-entire-error-diffs-from-pytest -- I was not able to find a way to hack DEFAULT_MAX_CHARS
  • https://stackoverflow.com/questions/38000993/how-can-i-get-my-assertions-in-pytest-to-stop-being-abbreviated-with-ellipsis

ssbarnea avatar Feb 06 '20 09:02 ssbarnea

~~Try env CI=true …, but it does not help here.~~ (It is an annoying thing, I know - there are some issues/ideas in that regard open already) Related: https://github.com/blueyed/pytest/pull/63 The problem here is that the internal pytest_assertrepr_compare hook does nothing here (for `op = "in"), and the assertion rewrite having it truncated already anyway. Somehow related docs: https://github.com/blueyed/pytest/blob/49f6aaf725610acf67087b0a38dc69e636c7402f/doc/en/assert.rst#defining-your-own-explanation-for-failed-assertions

blueyed avatar Feb 06 '20 23:02 blueyed

One year later and I am facing again the same issue, a bug caused by an incomplete feature, one that cannot be disabled, the truncation.

Can we please do something about it? For some projects this is testing PITA as the comparisons are almost always bigger than the limit and the user is left clueless to face the what is behind those ellipses? questions.

ssbarnea avatar Mar 02 '21 15:03 ssbarnea

Indeed this is frustrating, thanks @ssbarnea for bringing it to light again.

I've opened #8391 as an attempt to fix this, feedback welcome.

nicoddemus avatar Mar 03 '21 00:03 nicoddemus

I think it was #8391 -- going to test it now.

ssbarnea avatar Mar 03 '21 10:03 ssbarnea

Oops yes, fixed my comment.

nicoddemus avatar Mar 03 '21 11:03 nicoddemus

@nicoddemus I think that we need to reopen this issue because current behavior is still not ok. I found multiple similar unanswered question online such https://stackoverflow.com/questions/38000993/how-can-i-get-my-assertions-in-pytest-to-stop-being-abbreviated-with-ellipsis

IMHO, I think that we need to make these values configurable regardless of the verbosity level, especially as we know that changing general verbosity level has other side effects.

My opinion is that most people would want to run in minimal verbosity level (especially) on ci but have verbose errors for failed tests.

If I run in verbose by default displaying the console log in the browser can be problematic, true for github actions and also most CI/CD system I know (none behaves nice with very long console logs).

ssbarnea avatar Mar 12 '24 12:03 ssbarnea

@ssbarnea Check out https://docs.pytest.org/en/stable/reference/reference.html#confval-verbosity_assertions, added in pytest 8.

bluetech avatar Mar 12 '24 12:03 bluetech

@bluetech That issue seems to never want to be go away... :p --- I tried, still same output with ellipses, in fact even calling pytest -vvvv does ellipsis....

[tool.pytest.ini_options]
verbosity_assertions = 4
verbosity_test_cases = 4

Output:

>       assert len(results) == len(expected), results
E       AssertionError: [[no-tabs] (Most files should not contain tabs.) matched examples/playbooks/rule-no-tabs.yml:10 Task/Handler: Foo, [no...hould not contain tabs.) matched examples/playbooks/rule-no-tabs.yml:37 Task/Handler: Should not trigger no-tabs rules]
E       assert 3 == 2
E        +  where 3 = len([[no-tabs] (Most files should not contain tabs.) matched examples/playbooks/rule-no-tabs.yml:10 Task/Handler: Foo, [no-tabs] (Most files should not contain tabs.) matched examples/playbooks/rule-no-tabs.yml:13 Task/Handler: Key has a tab, [no-tabs] (Most files should not contain tabs.) matched examples/playbooks/rule-no-tabs.yml:37 Task/Handler: Should not trigger no-tabs rules])
E        +  and   2 = len((13, 'Most files should not contain tabs.'))

Using latest 8.2.0. I put some breakpoints and tried to find what happens. Apparently the DEFAULT_REPR_MAX_SIZE = 240 and saferepr() is causing it to happen anyway:

  /Users/ssbarnea/c/os/pytest/src/_pytest/python.py(162)pytest_pyfunc_call()
    161     testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
--> 162     result = testfunction(**testargs)
    163     if hasattr(result, "__await__") or hasattr(result, "__aiter__"):

  /Users/ssbarnea/c/a/ansible-lint/src/ansiblelint/rules/no_tabs.py(92)test_no_tabs_rule()
     90             assert results[i].lineno == expected[0]
     91             assert results[i].message == expected[1]
---> 92         assert len(results) == len(expected), results

  /Users/ssbarnea/c/os/pytest/src/_pytest/assertion/rewrite.py(454)_format_assertmsg()
    453     if not isinstance(obj, str):
--> 454         obj = saferepr(obj)
    455         replaces.append(("\\n", "\n~"))

  /Users/ssbarnea/c/os/pytest/src/_pytest/_io/saferepr.py(112)saferepr()
    111     """
--> 112     return SafeRepr(maxsize, use_ascii).repr(obj)
    113 

  /Users/ssbarnea/c/os/pytest/src/_pytest/_io/saferepr.py(62)repr()
     61             else:
---> 62                 s = super().repr(x)
     63 

  /Users/ssbarnea/.asdf/installs/python/3.12.2/lib/python3.12/reprlib.py(59)repr()
     58     def repr(self, x):
---> 59         return self.repr1(x, self.maxlevel)
     60 

  /Users/ssbarnea/.asdf/installs/python/3.12.2/lib/python3.12/reprlib.py(67)repr1()
     66         if hasattr(self, 'repr_' + typename):
---> 67             return getattr(self, 'repr_' + typename)(x, level)
     68         else:

  /Users/ssbarnea/.asdf/installs/python/3.12.2/lib/python3.12/reprlib.py(110)repr_list()
    109     def repr_list(self, x, level):
--> 110         return self._repr_iterable(x, level, '[', ']', self.maxlist)
    111 

  /Users/ssbarnea/.asdf/installs/python/3.12.2/lib/python3.12/reprlib.py(98)_repr_iterable()
     97             repr1 = self.repr1
---> 98             pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)]
     99             if n > maxiter:

  /Users/ssbarnea/.asdf/installs/python/3.12.2/lib/python3.12/reprlib.py(69)repr1()
     68         else:
---> 69             return self.repr_instance(x, level)
     70 

  /Users/ssbarnea/c/os/pytest/src/_pytest/_io/saferepr.py(80)repr_instance()
     79         if self.maxsize is not None:
---> 80             s = _ellipsize(s, self.maxsize)
     81         return s

> /Users/ssbarnea/c/os/pytest/src/_pytest/_io/saferepr.py(29)_ellipsize()
     28     breakpoint()
---> 29     if len(s) > maxsize:
     30         i = max(0, (maxsize - 3) // 2)

ssbarnea avatar May 09 '24 16:05 ssbarnea