391 lines
15 KiB
Diff
391 lines
15 KiB
Diff
# pytest-dev__pytest-7220
|
|
# Loki Mode Multi-Agent Patch
|
|
# Attempts: 1
|
|
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -1,4 +1,5 @@
|
|
import inspect
|
|
+import os
|
|
import re
|
|
import sys
|
|
import traceback
|
|
@@ -551,13 +552,27 @@ class ReprFileLocation(TerminalRepr):
|
|
self.lineno = lineno
|
|
self.message = message
|
|
|
|
- def toterminal(self, tw: TerminalWriter) -> None:
|
|
+ def toterminal(self, tw: TerminalWriter, invocation_dir=None) -> None:
|
|
# filename and samples of underline
|
|
msg = self.message
|
|
- i = msg.find("\n")
|
|
- if i != -1:
|
|
- msg = msg[:i]
|
|
- tw.write(self.path, bold=True, red=True)
|
|
+ lineno = self.lineno
|
|
+ path = self.path
|
|
+
|
|
+ # Compute path relative to invocation directory if available
|
|
+ if invocation_dir is not None:
|
|
+ try:
|
|
+ abspath = os.path.abspath(path)
|
|
+ relpath = os.path.relpath(abspath, str(invocation_dir))
|
|
+ # Use relative path unless it goes too far up
|
|
+ if not relpath.startswith(".."):
|
|
+ path = relpath
|
|
+ else:
|
|
+ path = abspath
|
|
+ except (ValueError, OSError):
|
|
+ pass
|
|
+
|
|
+ tw.write(path, bold=True, red=True)
|
|
tw.line(":{}: {}".format(self.lineno, msg))
|
|
|
|
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -558,6 +558,10 @@ class ReprFileLocation(TerminalRepr):
|
|
lineno = self.lineno
|
|
path = self.path
|
|
|
|
+ i = msg.find("\n")
|
|
+ if i != -1:
|
|
+ msg = msg[:i]
|
|
+
|
|
# Compute path relative to invocation directory if available
|
|
if invocation_dir is not None:
|
|
try:
|
|
--- a/src/_pytest/terminal.py
|
|
+++ b/src/_pytest/terminal.py
|
|
@@ -992,7 +992,7 @@ class TerminalReporter:
|
|
for reprlocation_lines in rep.longrepr.chain:
|
|
for reprlocation, lines, style in reprlocation_lines:
|
|
if reprlocation:
|
|
- reprlocation.toterminal(self._tw)
|
|
+ reprlocation.toterminal(self._tw, self.config.invocation_dir)
|
|
for line in lines:
|
|
self._tw.line(line)
|
|
|
|
@@ -1003,6 +1003,9 @@ class TerminalReporter:
|
|
for line in rep.longrepr.splitlines():
|
|
self._tw.line(line)
|
|
else:
|
|
- rep.longrepr.toterminal(self._tw)
|
|
+ if hasattr(rep.longrepr, 'toterminal'):
|
|
+ rep.longrepr.toterminal(self._tw)
|
|
+ else:
|
|
+ self._tw.line(str(rep.longrepr))
|
|
|
|
def _folded_skips(self) -> List[Tuple[int, str, Optional[int], str]]:
|
|
--- a/src/_pytest/terminal.py
|
|
+++ b/src/_pytest/terminal.py
|
|
@@ -991,16 +991,7 @@ class TerminalReporter:
|
|
if hasattr(rep.longrepr, "chain"):
|
|
for reprlocation_lines in rep.longrepr.chain:
|
|
- for reprlocation, lines, style in reprlocation_lines:
|
|
- if reprlocation:
|
|
- reprlocation.toterminal(self._tw, self.config.invocation_dir)
|
|
- for line in lines:
|
|
- self._tw.line(line)
|
|
-
|
|
- for line in rep.longrepr.chain[-1][1]:
|
|
- self._tw.line(line)
|
|
- elif isinstance(rep.longrepr, str):
|
|
- for line in rep.longrepr.splitlines():
|
|
- self._tw.line(line)
|
|
- else:
|
|
- if hasattr(rep.longrepr, 'toterminal'):
|
|
- rep.longrepr.toterminal(self._tw)
|
|
- else:
|
|
- self._tw.line(str(rep.longrepr))
|
|
+ rep.longrepr.toterminal(self._tw)
|
|
+ elif isinstance(rep.longrepr, str):
|
|
+ for line in rep.longrepr.splitlines():
|
|
+ self._tw.line(line)
|
|
+ else:
|
|
+ rep.longrepr.toterminal(self._tw)
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -476,6 +476,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
super().__init__()
|
|
self.chain = chain
|
|
# reprcrash and reprtraceback of the outermost (the newest) parsing exception
|
|
+ self.invocation_dir = None
|
|
self.reprtraceback = chain[-1][0]
|
|
self.reprcrash = chain[-1][1]
|
|
|
|
@@ -485,6 +486,8 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
return "<{} tbstyle={} {!r}>".format(cls, style, self.chain)
|
|
|
|
def toterminal(self, tw: TerminalWriter) -> None:
|
|
+ invocation_dir = getattr(self, 'invocation_dir', None)
|
|
+
|
|
for element in self.chain:
|
|
reprtraceback, reprcrash, descr = element
|
|
if descr:
|
|
@@ -492,7 +495,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
reprtraceback.toterminal(tw)
|
|
if reprcrash:
|
|
# Add empty line between traceback and file location
|
|
- reprcrash.toterminal(tw)
|
|
+ reprcrash.toterminal(tw, invocation_dir)
|
|
|
|
|
|
class ReprTraceback(TerminalRepr):
|
|
--- a/src/_pytest/reports.py
|
|
+++ b/src/_pytest/reports.py
|
|
@@ -5,6 +5,7 @@ from typing import List
|
|
from typing import Optional
|
|
from typing import Tuple
|
|
from typing import Union
|
|
+from pathlib import Path
|
|
|
|
import py
|
|
|
|
@@ -131,6 +132,17 @@ class BaseReport:
|
|
writer.line("")
|
|
self.longrepr.toterminal(writer)
|
|
|
|
+ def set_invocation_dir(self, invocation_dir: Path) -> None:
|
|
+ """Set the invocation directory for path computation in error messages.
|
|
+
|
|
+ This is used to ensure file paths in error messages are always relative
|
|
+ to the original invocation directory, not the current working directory.
|
|
+ """
|
|
+ if hasattr(self.longrepr, 'chain'):
|
|
+ self.longrepr.invocation_dir = invocation_dir
|
|
+ if hasattr(self.longrepr, 'reprcrash'):
|
|
+ self.longrepr.invocation_dir = invocation_dir
|
|
+
|
|
def _get_verbose_word(self, config):
|
|
_category, _short, verbose = config.hook.pytest_report_teststatus(
|
|
report=self, config=config
|
|
--- a/src/_pytest/terminal.py
|
|
+++ b/src/_pytest/terminal.py
|
|
@@ -994,6 +994,9 @@ class TerminalReporter:
|
|
for rep in reports:
|
|
if rep.outcome == "failed":
|
|
self._outrep_summary(rep)
|
|
+ if hasattr(rep.longrepr, 'invocation_dir'):
|
|
+ rep.longrepr.invocation_dir = self.config.invocation_dir
|
|
+ self._outrep_summary(rep)
|
|
--- a/src/_pytest/terminal.py
|
|
+++ b/src/_pytest/terminal.py
|
|
@@ -991,9 +991,11 @@ class TerminalReporter:
|
|
def short_test_summary(self) -> None:
|
|
if not self.reportchars:
|
|
return
|
|
-
|
|
+
|
|
for rep in self._getfailures():
|
|
msg = self._getfailrepr(rep)
|
|
+ if hasattr(rep.longrepr, 'invocation_dir'):
|
|
+ rep.longrepr.invocation_dir = self.config.invocation_dir
|
|
self.write_sep("_", "FAILURES")
|
|
for rep in self.stats.get("failed", []):
|
|
if self.verbosity <= 0:
|
|
@@ -1001,6 +1003,8 @@ class TerminalReporter:
|
|
else:
|
|
self._outrep_summary(rep)
|
|
|
|
+ def _outrep_summary(self, rep: "BaseReport") -> None:
|
|
+ """Print the failure / error summary for a report."""
|
|
if hasattr(rep.longrepr, "chain"):
|
|
rep.longrepr.toterminal(self._tw)
|
|
elif isinstance(rep.longrepr, str):
|
|
--- a/src/_pytest/terminal.py
|
|
+++ b/src/_pytest/terminal.py
|
|
@@ -982,6 +982,8 @@ class TerminalReporter:
|
|
self._tw.line(line)
|
|
|
|
def _outrep_summary(self, rep: "BaseReport") -> None:
|
|
+ if hasattr(rep.longrepr, 'invocation_dir') and rep.longrepr.invocation_dir is None:
|
|
+ rep.longrepr.invocation_dir = self.config.invocation_dir
|
|
rep.toterminal(self._tw)
|
|
|
|
def summary_failures(self) -> None:
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -551,7 +551,7 @@ class ReprFileLocation(TerminalRepr):
|
|
self.lineno = lineno
|
|
self.message = message
|
|
|
|
- def toterminal(self, tw: TerminalWriter, invocation_dir=None) -> None:
|
|
+ def toterminal(self, tw: TerminalWriter) -> None:
|
|
# filename and samples of underline
|
|
msg = self.message
|
|
lineno = self.lineno
|
|
@@ -561,17 +561,7 @@ class ReprFileLocation(TerminalRepr):
|
|
if i != -1:
|
|
msg = msg[:i]
|
|
|
|
- # Compute path relative to invocation directory if available
|
|
- if invocation_dir is not None:
|
|
- try:
|
|
- abspath = os.path.abspath(path)
|
|
- relpath = os.path.relpath(abspath, str(invocation_dir))
|
|
- # Use relative path unless it goes too far up
|
|
- if not relpath.startswith(".."):
|
|
- path = relpath
|
|
- else:
|
|
- path = abspath
|
|
- except (ValueError, OSError):
|
|
- pass
|
|
-
|
|
tw.write(path, bold=True, red=True)
|
|
tw.line(":{}: {}".format(self.lineno, msg))
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -485,7 +485,6 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
return "<{} tbstyle={} {!r}>".format(cls, style, self.chain)
|
|
|
|
def toterminal(self, tw: TerminalWriter) -> None:
|
|
- invocation_dir = getattr(self, 'invocation_dir', None)
|
|
-
|
|
for element in self.chain:
|
|
reprtraceback, reprcrash, descr = element
|
|
if descr:
|
|
@@ -493,7 +492,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
reprtraceback.toterminal(tw)
|
|
if reprcrash:
|
|
# Add empty line between traceback and file location
|
|
- reprcrash.toterminal(tw, invocation_dir)
|
|
+ reprcrash.toterminal(tw)
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -475,7 +475,6 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
) -> None:
|
|
super().__init__()
|
|
self.chain = chain
|
|
- self.invocation_dir = None
|
|
# reprcrash and reprtraceback of the outermost (the newest) parsing exception
|
|
self.reprtraceback = chain[-1][0]
|
|
self.reprcrash = chain[-1][1]
|
|
--- a/src/_pytest/reports.py
|
|
+++ b/src/_pytest/reports.py
|
|
@@ -5,7 +5,6 @@ from typing import List
|
|
from typing import Optional
|
|
from typing import Tuple
|
|
from typing import Union
|
|
-from pathlib import Path
|
|
|
|
import py
|
|
|
|
@@ -132,17 +131,6 @@ class BaseReport:
|
|
writer.line("")
|
|
self.longrepr.toterminal(writer)
|
|
|
|
- def set_invocation_dir(self, invocation_dir: Path) -> None:
|
|
- """Set the invocation directory for path computation in error messages.
|
|
-
|
|
- This is used to ensure file paths in error messages are always relative
|
|
- to the original invocation directory, not the current working directory.
|
|
- """
|
|
- if hasattr(self.longrepr, 'chain'):
|
|
- self.longrepr.invocation_dir = invocation_dir
|
|
- if hasattr(self.longrepr, 'reprcrash'):
|
|
- self.longrepr.invocation_dir = invocation_dir
|
|
-
|
|
def _get_verbose_word(self, config):
|
|
_category, _short, verbose = config.hook.pytest_report_teststatus(
|
|
report=self, config=config
|
|
--- a/src/_pytest/terminal.py
|
|
+++ b/src/_pytest/terminal.py
|
|
@@ -982,8 +982,6 @@ class TerminalReporter:
|
|
self._tw.line(line)
|
|
|
|
def _outrep_summary(self, rep: "BaseReport") -> None:
|
|
- if hasattr(rep.longrepr, 'invocation_dir') and rep.longrepr.invocation_dir is None:
|
|
- rep.longrepr.invocation_dir = self.config.invocation_dir
|
|
rep.toterminal(self._tw)
|
|
|
|
def summary_failures(self) -> None:
|
|
--- a/src/_pytest/nodes.py
|
|
+++ b/src/_pytest/nodes.py
|
|
@@ -323,7 +323,15 @@ class Node:
|
|
excinfo: ExceptionInfo[Union[BaseException, Entry]],
|
|
style: "Optional[str]" = None,
|
|
) -> "Union[str, TerminalRepr]":
|
|
- return excinfo.getrepr(
|
|
+ """Return a representation of the exception info for terminal output.
|
|
+
|
|
+ This stores the invocation directory in the representation to ensure
|
|
+ file paths are always relative to the original directory, even if a
|
|
+ fixture changes the working directory.
|
|
+ """
|
|
+ invocation_dir = self.config.invocation_dir
|
|
+
|
|
+ repr_result = excinfo.getrepr(
|
|
funcargs=True,
|
|
abspath=False,
|
|
showlocals=self.config.option.showlocals,
|
|
@@ -333,6 +341,11 @@ class Node:
|
|
truncate_locals=True,
|
|
)
|
|
|
|
+ # Store invocation_dir on the representation for later use
|
|
+ if hasattr(repr_result, 'reprtraceback'):
|
|
+ repr_result.invocation_dir = invocation_dir
|
|
+ return repr_result
|
|
+
|
|
# Methods for ordering nodes
|
|
@property
|
|
def nodeid(self) -> str:
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -475,6 +475,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
) -> None:
|
|
super().__init__()
|
|
self.chain = chain
|
|
+ self.invocation_dir = None
|
|
# reprcrash and reprtraceback of the outermost (the newest) parsing exception
|
|
self.reprtraceback = chain[-1][0]
|
|
self.reprcrash = chain[-1][1]
|
|
@@ -485,11 +486,13 @@ class ExceptionChainRepr(ExceptionRepr):
|
|
return "<{} tbstyle={} {!r}>".format(cls, style, self.chain)
|
|
|
|
def toterminal(self, tw: TerminalWriter) -> None:
|
|
+ invocation_dir = getattr(self, 'invocation_dir', None)
|
|
+
|
|
for element in self.chain:
|
|
reprtraceback, reprcrash, descr = element
|
|
if descr:
|
|
tw.line(descr, yellow=True)
|
|
reprtraceback.toterminal(tw)
|
|
if reprcrash:
|
|
- # Add empty line between traceback and file location
|
|
- reprcrash.toterminal(tw)
|
|
+ reprcrash.toterminal(tw, invocation_dir)
|
|
|
|
|
|
class ReprTraceback(TerminalRepr):
|
|
--- a/src/_pytest/_code/code.py
|
|
+++ b/src/_pytest/_code/code.py
|
|
@@ -551,7 +551,7 @@ class ReprFileLocation(TerminalRepr):
|
|
self.lineno = lineno
|
|
self.message = message
|
|
|
|
- def toterminal(self, tw: TerminalWriter) -> None:
|
|
+ def toterminal(self, tw: TerminalWriter, invocation_dir=None) -> None:
|
|
# filename and samples of underline
|
|
msg = self.message
|
|
lineno = self.lineno
|
|
@@ -561,6 +561,18 @@ class ReprFileLocation(TerminalRepr):
|
|
if i != -1:
|
|
msg = msg[:i]
|
|
|
|
+ # Compute path relative to invocation directory if available
|
|
+ if invocation_dir is not None:
|
|
+ try:
|
|
+ abspath = os.path.abspath(path)
|
|
+ relpath = os.path.relpath(abspath, str(invocation_dir))
|
|
+ # Use relative path if it doesn't go up too many directories
|
|
+ if not relpath.startswith(".."):
|
|
+ path = relpath
|
|
+ else:
|
|
+ path = abspath
|
|
+ except (ValueError, OSError):
|
|
+ pass
|
|
+
|
|
tw.write(path, bold=True, red=True)
|
|
tw.line(":{}: {}".format(self.lineno, msg))
|