code: do not truncate args in output when running with -vvv

We recently ran into the issue that it would have been useful to
get more details from a pytest exception print. With `-vv` one
gets all the untruncated local vars. However sometimes it's
useful to also get the untruncated arguments. Especially when
when working with `subprocess.run()` and `capture_output=True`.

We are doing something like:
```
$ cat test/test_foo.py
import subprocess

def test_foo():
	subprocess.run(["sh","-c","seq 400|xargs echo; false"], capture_output=True, text=True, check=True)

$ pytest-3 -vv ./test/test_foo.py
============================= test session starts ==============================
platform linux -- Python 3.11.8, pytest-7.4.4, pluggy-1.3.0 -- /usr/bin/python3
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/home/mvogt/devel/osbuild/osbuild/.hypothesis/examples'))
rootdir: /home/mvogt/devel/osbuild/osbuild
plugins: anyio-4.2.0, xdist-3.5.0, hypothesis-6.100.1
collected 1 item

test/test_foo.py::test_foo FAILED                                        [100%]

=================================== FAILURES ===================================
___________________________________ test_foo ___________________________________

    def test_foo():
>   	subprocess.run(["sh","-c","seq 400|xargs echo; false"], capture_output=True, text=True, check=True)

test/test_foo.py:4:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = True, timeout = None, check = True
popenargs = (['sh', '-c', 'seq 400|xargs echo; false'],)
kwargs = {'stderr': -1, 'stdout': -1, 'text': True}
process = <Popen: returncode: 1 args: ['sh', '-c', 'seq 400|xargs echo; false']>
stdout = '1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 ... 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400\n'
stderr = '', retcode = 1
```
And some useful information was hidden in the middle of stdout.

This commit adds a new `truncate_args` argument similar to the
`truncate_locals` in PR#3681 that gets activated with `-vvv`
(this is a bit of a strawman, we could add it to `-vv` too or
just fold it into `truncate_locals` but it seemed cleaner this way).

With the diff the output is now:
```
 pytest-3 -vvv ./test/test_foo.py
============================= test session starts ==============================
platform linux -- Python 3.11.8, pytest-7.4.4, pluggy-1.3.0 -- /usr/bin/python3
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/home/mvogt/devel/osbuild/osbuild/.hypothesis/examples'))
rootdir: /home/mvogt/devel/osbuild/osbuild
plugins: anyio-4.2.0, xdist-3.5.0, hypothesis-6.100.1
collected 1 item

test/test_foo.py::test_foo FAILED                                        [100%]

=================================== FAILURES ===================================
___________________________________ test_foo ___________________________________

    def test_foo():
>   	subprocess.run(["sh","-c","seq 400|xargs echo; false"], capture_output=True, text=True, check=True)

test/test_foo.py:4:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

input = None, capture_output = True, timeout = None, check = True
popenargs = (['sh', '-c', 'seq 400|xargs echo; false'],)
kwargs = {'stderr': -1, 'stdout': -1, 'text': True}
process = <Popen: returncode: 1 args: ['sh', '-c', 'seq 400|xargs echo; false']>
stdout = ('1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 '
 '29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 '
 '54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 '
 '79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 '
 '103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 '
 '122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 '
 '141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 '
 '160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 '
 '179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 '
 '198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 '
 '217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 '
 '236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 '
 '255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 '
 '274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 '
 '293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 '
 '312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 '
 '331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 '
 '350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 '
 '369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 '
 '388 389 390 391 392 393 394 395 396 397 398 399 400\n')
stderr = '', retcode = 1
```
This commit is contained in:
Michael Vogt 2024-04-24 14:51:50 +02:00
parent 1d322ffb48
commit 6aca93bcd3
4 changed files with 40 additions and 1 deletions

View File

@ -276,6 +276,7 @@ Michael Droettboom
Michael Goerz
Michael Krebs
Michael Seifert
Michael Vogt
Michal Wajszczuk
Michał Górny
Michał Zięba

View File

@ -636,6 +636,7 @@ class ExceptionInfo(Generic[E]):
] = True,
funcargs: bool = False,
truncate_locals: bool = True,
truncate_args: bool = True,
chain: bool = True,
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
"""Return str()able representation of this exception info.
@ -666,6 +667,9 @@ class ExceptionInfo(Generic[E]):
:param bool truncate_locals:
With ``showlocals==True``, make sure locals can be safely represented as strings.
:param bool truncate_args:
With ``showargs==True``, make sure args can be safely represented as strings.
:param bool chain:
If chained exceptions in Python 3 should be shown.
@ -692,6 +696,7 @@ class ExceptionInfo(Generic[E]):
tbfilter=tbfilter,
funcargs=funcargs,
truncate_locals=truncate_locals,
truncate_args=truncate_args,
chain=chain,
)
return fmt.repr_excinfo(self)
@ -810,6 +815,7 @@ class FormattedExcinfo:
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
funcargs: bool = False
truncate_locals: bool = True
truncate_args: bool = True
chain: bool = True
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
default_factory=dict, init=False, repr=False
@ -840,7 +846,11 @@ class FormattedExcinfo:
if self.funcargs:
args = []
for argname, argvalue in entry.frame.getargs(var=True):
args.append((argname, saferepr(argvalue)))
if self.truncate_args:
str_repr = saferepr(argvalue)
else:
str_repr = safeformat(argvalue)
args.append((argname, str_repr))
return ReprFuncArgs(args)
return None

View File

@ -448,6 +448,11 @@ class Node(abc.ABC, metaclass=NodeMeta):
else:
truncate_locals = True
if self.config.getoption("verbose", 0) > 2:
truncate_args = False
else:
truncate_args = True
# excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.
# It is possible for a fixture/test to change the CWD while this code runs, which
# would then result in the user seeing confusing paths in the failure message.
@ -466,6 +471,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
style=style,
tbfilter=tbfilter,
truncate_locals=truncate_locals,
truncate_args=truncate_args,
)
def repr_failure(

View File

@ -708,6 +708,28 @@ raise ValueError()
assert full_reprlocals.lines
assert full_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
def test_repr_args_not_truncated(self, importasmod) -> None:
mod = importasmod(
"""
def func1(m):
raise ValueError("hello\\nworld")
"""
)
excinfo = pytest.raises(ValueError, mod.func1, "m" * 500)
excinfo.traceback = excinfo.traceback.filter(excinfo)
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True, truncate_args=True)
reprfuncargs = p.repr_args(entry)
assert reprfuncargs is not None
assert len(reprfuncargs.args[0][1]) < 500
assert "..." in reprfuncargs.args[0][1]
# again without truncate
p = FormattedExcinfo(funcargs=True, truncate_args=False)
reprfuncargs = p.repr_args(entry)
assert reprfuncargs is not None
assert reprfuncargs.args[0] == ("m", repr("m" * 500))
assert "..." not in reprfuncargs.args[0][1]
def test_repr_tracebackentry_lines(self, importasmod) -> None:
mod = importasmod(
"""