Merge pull request #13864 from bluetech/config-cleanups-2 · pytest-dev/pytest@adb3658
@@ -142,6 +142,29 @@ def filter_traceback_for_conftest_import_failure(
142142return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep)
143143144144145+def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None:
146+exc_info = ExceptionInfo.from_exception(e.cause)
147+tw = TerminalWriter(file)
148+tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
149+exc_info.traceback = exc_info.traceback.filter(
150+filter_traceback_for_conftest_import_failure
151+ )
152+exc_repr = (
153+exc_info.getrepr(style="short", chain=False)
154+if exc_info.traceback
155+else exc_info.exconly()
156+ )
157+formatted_tb = str(exc_repr)
158+for line in formatted_tb.splitlines():
159+tw.line(line.rstrip(), red=True)
160+161+162+def print_usage_error(e: UsageError, file: TextIO) -> None:
163+tw = TerminalWriter(file)
164+for msg in e.args:
165+tw.line(f"ERROR: {msg}\n", red=True)
166+167+145168def main(
146169args: list[str] | os.PathLike[str] | None = None,
147170plugins: Sequence[str | _PluggyPlugin] | None = None,
@@ -167,34 +190,19 @@ def main(
167190try:
168191config = _prepareconfig(new_args, plugins)
169192except ConftestImportFailure as e:
170-exc_info = ExceptionInfo.from_exception(e.cause)
171-tw = TerminalWriter(sys.stderr)
172-tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
173-exc_info.traceback = exc_info.traceback.filter(
174-filter_traceback_for_conftest_import_failure
175- )
176-exc_repr = (
177-exc_info.getrepr(style="short", chain=False)
178-if exc_info.traceback
179-else exc_info.exconly()
180- )
181-formatted_tb = str(exc_repr)
182-for line in formatted_tb.splitlines():
183-tw.line(line.rstrip(), red=True)
193+print_conftest_import_error(e, file=sys.stderr)
184194return ExitCode.USAGE_ERROR
185-else:
195+196+try:
197+ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
186198try:
187-ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
188-try:
189-return ExitCode(ret)
190-except ValueError:
191-return ret
192-finally:
193-config._ensure_unconfigure()
199+return ExitCode(ret)
200+except ValueError:
201+return ret
202+finally:
203+config._ensure_unconfigure()
194204except UsageError as e:
195-tw = TerminalWriter(sys.stderr)
196-for msg in e.args:
197-tw.line(f"ERROR: {msg}\n", red=True)
205+print_usage_error(e, file=sys.stderr)
198206return ExitCode.USAGE_ERROR
199207finally:
200208if old_pytest_version is None:
@@ -1006,7 +1014,7 @@ class InvocationParams:
10061014plugins: Sequence[str | _PluggyPlugin] | None
10071015"""Extra plugins, might be `None`."""
10081016dir: pathlib.Path
1009-"""The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
1017+"""The directory from which :func:`pytest.main` was invoked."""
1010101810111019def __init__(
10121020self,
@@ -1042,9 +1050,6 @@ def __init__(
10421050*,
10431051invocation_params: InvocationParams | None = None,
10441052 ) -> None:
1045-from .argparsing import FILE_OR_DIR
1046-from .argparsing import Parser
1047-10481053if invocation_params is None:
10491054invocation_params = self.InvocationParams(
10501055args=(), plugins=None, dir=pathlib.Path.cwd()
@@ -1062,9 +1067,8 @@ def __init__(
10621067 :type: InvocationParams
10631068 """
106410691065-_a = FILE_OR_DIR
10661070self._parser = Parser(
1067-usage=f"%(prog)s [options] [{_a}] [{_a}] [...]",
1071+usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]",
10681072processopt=self._processopt,
10691073_ispytest=True,
10701074 )
@@ -1100,8 +1104,6 @@ def __init__(
11001104def rootpath(self) -> pathlib.Path:
11011105"""The path to the :ref:`rootdir <rootdir>`.
110211061103- :type: pathlib.Path
1104-11051107 .. versionadded:: 6.1
11061108 """
11071109return self._rootpath
@@ -1164,7 +1166,7 @@ def pytest_cmdline_parse(
11641166elif (
11651167getattr(self.option, "help", False) or "--help" in args or "-h" in args
11661168 ):
1167-self._parser._getparser().print_help()
1169+self._parser.optparser.print_help()
11681170sys.stdout.write(
11691171"\nNOTE: displaying only minimal help due to UsageError.\n\n"
11701172 )
@@ -1247,19 +1249,18 @@ def pytest_load_initial_conftests(self, early_config: Config) -> None:
12471249 ),
12481250 )
124912511250-def _consider_importhook(self, args: Sequence[str]) -> None:
1252+def _consider_importhook(self) -> None:
12511253"""Install the PEP 302 import hook if using assertion rewriting.
1252125412531255 Needs to parse the --assert=<mode> option from the commandline
12541256 and find all the installed plugins to mark them for rewriting
12551257 by the importhook.
12561258 """
1257-ns, _unknown_args = self._parser.parse_known_and_unknown_args(args)
1258-mode = getattr(ns, "assertmode", "plain")
1259+mode = getattr(self.known_args_namespace, "assertmode", "plain")
125912601260-disable_autoload = getattr(ns, "disable_plugin_autoload", False) or bool(
1261-os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1262- )
1261+disable_autoload = getattr(
1262+self.known_args_namespace, "disable_plugin_autoload", False
1263+ ) or bool(os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"))
12631264if mode == "rewrite":
12641265import _pytest.assertion
12651266@@ -1363,94 +1364,6 @@ def _decide_args(
13631364result = [str(invocation_dir)]
13641365return result, source
136513661366-def _preparse(self, args: list[str], addopts: bool = True) -> None:
1367-if addopts:
1368-env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
1369-if len(env_addopts):
1370-args[:] = (
1371-self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
1372-+ args
1373- )
1374-1375-ns, unknown_args = self._parser.parse_known_and_unknown_args(
1376-args, namespace=copy.copy(self.option)
1377- )
1378-rootpath, inipath, inicfg, ignored_config_files = determine_setup(
1379-inifile=ns.inifilename,
1380-override_ini=ns.override_ini,
1381-args=ns.file_or_dir + unknown_args,
1382-rootdir_cmd_arg=ns.rootdir or None,
1383-invocation_dir=self.invocation_params.dir,
1384- )
1385-self._rootpath = rootpath
1386-self._inipath = inipath
1387-self._ignored_config_files = ignored_config_files
1388-self.inicfg = inicfg
1389-self._parser.extra_info["rootdir"] = str(self.rootpath)
1390-self._parser.extra_info["inifile"] = str(self.inipath)
1391-self._parser.addini("addopts", "Extra command line options", "args")
1392-self._parser.addini("minversion", "Minimally required pytest version")
1393-self._parser.addini(
1394-"pythonpath", type="paths", help="Add paths to sys.path", default=[]
1395- )
1396-self._parser.addini(
1397-"required_plugins",
1398-"Plugins that must be present for pytest to run",
1399-type="args",
1400-default=[],
1401- )
1402-1403-if addopts:
1404-args[:] = (
1405-self._validate_args(self.getini("addopts"), "via addopts config") + args
1406- )
1407-1408-self.known_args_namespace = self._parser.parse_known_args(
1409-args, namespace=copy.copy(self.option)
1410- )
1411-self._checkversion()
1412-self._consider_importhook(args)
1413-self._configure_python_path()
1414-self.pluginmanager.consider_preparse(args, exclude_only=False)
1415-if (
1416-not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1417-and not self.known_args_namespace.disable_plugin_autoload
1418- ):
1419-# Autoloading from distribution package entry point has
1420-# not been disabled.
1421-self.pluginmanager.load_setuptools_entrypoints("pytest11")
1422-# Otherwise only plugins explicitly specified in PYTEST_PLUGINS
1423-# are going to be loaded.
1424-self.pluginmanager.consider_env()
1425-1426-self.known_args_namespace = self._parser.parse_known_args(
1427-args, namespace=copy.copy(self.known_args_namespace)
1428- )
1429-1430-self._validate_plugins()
1431-self._warn_about_skipped_plugins()
1432-1433-if self.known_args_namespace.confcutdir is None:
1434-if self.inipath is not None:
1435-confcutdir = str(self.inipath.parent)
1436-else:
1437-confcutdir = str(self.rootpath)
1438-self.known_args_namespace.confcutdir = confcutdir
1439-try:
1440-self.hook.pytest_load_initial_conftests(
1441-early_config=self, args=args, parser=self._parser
1442- )
1443-except ConftestImportFailure as e:
1444-if self.known_args_namespace.help or self.known_args_namespace.version:
1445-# we don't want to prevent --help/--version to work
1446-# so just let it pass and print a warning at the end
1447-self.issue_config_time_warning(
1448-PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1449-stacklevel=2,
1450- )
1451-else:
1452-raise
1453-14541367@hookimpl(wrapper=True)
14551368def pytest_collection(self) -> Generator[None, object, object]:
14561369# Validate invalid configuration keys after collection is done so we
@@ -1534,18 +1447,103 @@ def parse(self, args: list[str], addopts: bool = True) -> None:
15341447assert self.args == [], (
15351448"can only parse cmdline args at most once per Config object"
15361449 )
1450+15371451self.hook.pytest_addhooks.call_historic(
15381452kwargs=dict(pluginmanager=self.pluginmanager)
15391453 )
1540-self._preparse(args, addopts=addopts)
1541-self._parser.after_preparse = True # type: ignore
1454+1455+if addopts:
1456+env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
1457+if len(env_addopts):
1458+args[:] = (
1459+self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
1460++ args
1461+ )
1462+1463+ns = self._parser.parse_known_args(args, namespace=copy.copy(self.option))
1464+rootpath, inipath, inicfg, ignored_config_files = determine_setup(
1465+inifile=ns.inifilename,
1466+override_ini=ns.override_ini,
1467+args=ns.file_or_dir,
1468+rootdir_cmd_arg=ns.rootdir or None,
1469+invocation_dir=self.invocation_params.dir,
1470+ )
1471+self._rootpath = rootpath
1472+self._inipath = inipath
1473+self._ignored_config_files = ignored_config_files
1474+self.inicfg = inicfg
1475+self._parser.extra_info["rootdir"] = str(self.rootpath)
1476+self._parser.extra_info["inifile"] = str(self.inipath)
1477+1478+self._parser.addini("addopts", "Extra command line options", "args")
1479+self._parser.addini("minversion", "Minimally required pytest version")
1480+self._parser.addini(
1481+"pythonpath", type="paths", help="Add paths to sys.path", default=[]
1482+ )
1483+self._parser.addini(
1484+"required_plugins",
1485+"Plugins that must be present for pytest to run",
1486+type="args",
1487+default=[],
1488+ )
1489+1490+if addopts:
1491+args[:] = (
1492+self._validate_args(self.getini("addopts"), "via addopts config") + args
1493+ )
1494+1495+self.known_args_namespace = self._parser.parse_known_args(
1496+args, namespace=copy.copy(self.option)
1497+ )
1498+self._checkversion()
1499+self._consider_importhook()
1500+self._configure_python_path()
1501+self.pluginmanager.consider_preparse(args, exclude_only=False)
1502+if (
1503+not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
1504+and not self.known_args_namespace.disable_plugin_autoload
1505+ ):
1506+# Autoloading from distribution package entry point has
1507+# not been disabled.
1508+self.pluginmanager.load_setuptools_entrypoints("pytest11")
1509+# Otherwise only plugins explicitly specified in PYTEST_PLUGINS
1510+# are going to be loaded.
1511+self.pluginmanager.consider_env()
1512+1513+self._parser.parse_known_args(args, namespace=self.known_args_namespace)
1514+1515+self._validate_plugins()
1516+self._warn_about_skipped_plugins()
1517+1518+if self.known_args_namespace.confcutdir is None:
1519+if self.inipath is not None:
1520+confcutdir = str(self.inipath.parent)
1521+else:
1522+confcutdir = str(self.rootpath)
1523+self.known_args_namespace.confcutdir = confcutdir
1524+try:
1525+self.hook.pytest_load_initial_conftests(
1526+early_config=self, args=args, parser=self._parser
1527+ )
1528+except ConftestImportFailure as e:
1529+if self.known_args_namespace.help or self.known_args_namespace.version:
1530+# we don't want to prevent --help/--version to work
1531+# so just let it pass and print a warning at the end
1532+self.issue_config_time_warning(
1533+PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1534+stacklevel=2,
1535+ )
1536+else:
1537+raise
1538+15421539try:
1543-parsed = self._parser.parse(args, namespace=self.option)
1540+self._parser.parse(args, namespace=self.option)
15441541except PrintHelp:
15451542return
1543+15461544self.args, self.args_source = self._decide_args(
1547-args=getattr(parsed, FILE_OR_DIR),
1548-pyargs=self.known_args_namespace.pyargs,
1545+args=getattr(self.option, FILE_OR_DIR),
1546+pyargs=self.option.pyargs,
15491547testpaths=self.getini("testpaths"),
15501548invocation_dir=self.invocation_params.dir,
15511549rootpath=self.rootpath,