Skip to content

Commit 1efe0db

Browse files
committed
main: put parametrization in a separate field on CollectionArgument
This is for the benefit of the next commit. That commit wants to check whether a CollectionArgument is subsumed by another. According to pytest semantics: `test_it.py::TestIt::test_it[a]` subsumed by `test_it.py::TestIt::test_it` However the `parts` are ["TestIt", test_it[a]"] ["TestIt", test_it"] which means a simple list prefix cannot be used. By splitting the parametrization `"[a]"` part to its own attribute, it can be handled cleanly. I also think this is a reasonable change regardless. We'd probably want something like this when the "collection structure contains parametrization" TODO is tackled.
1 parent d2d57e3 commit 1efe0db

File tree

2 files changed

+40
-12
lines changed

2 files changed

+40
-12
lines changed

src/_pytest/main.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,7 @@ def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
859859

860860
argpath = collection_argument.path
861861
names = collection_argument.parts
862+
parametrization = collection_argument.parametrization
862863
module_name = collection_argument.module_name
863864

864865
# resolve_collection_argument() ensures this.
@@ -943,12 +944,18 @@ def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
943944

944945
# Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
945946
else:
946-
# TODO: Remove parametrized workaround once collection structure contains
947-
# parametrization.
948-
is_match = (
949-
node.name == matchparts[0]
950-
or node.name.split("[")[0] == matchparts[0]
951-
)
947+
if len(matchparts) == 1:
948+
# This the last part, one parametrization goes.
949+
if parametrization is not None:
950+
# A parametrized arg must match exactly.
951+
is_match = node.name == matchparts[0] + parametrization
952+
else:
953+
# A non-parameterized arg matches all parametrizations (if any).
954+
# TODO: Remove the hacky split once the collection structure
955+
# contains parametrization.
956+
is_match = node.name.split("[")[0] == matchparts[0]
957+
else:
958+
is_match = node.name == matchparts[0]
952959
if is_match:
953960
work.append((node, matchparts[1:]))
954961
any_matched_in_collector = True
@@ -1024,6 +1031,7 @@ class CollectionArgument:
10241031

10251032
path: Path
10261033
parts: Sequence[str]
1034+
parametrization: str | None
10271035
module_name: str | None
10281036

10291037

@@ -1052,14 +1060,15 @@ def resolve_collection_argument(
10521060
When as_pypath is True, expects that the command-line argument actually contains
10531061
module paths instead of file-system paths:
10541062
1055-
"pkg.tests.test_foo::TestClass::test_foo"
1063+
"pkg.tests.test_foo::TestClass::test_foo[a,b]"
10561064
10571065
In which case we search sys.path for a matching module, and then return the *path* to the
10581066
found module, which may look like this:
10591067
10601068
CollectionArgument(
10611069
path=Path("/home/u/myvenv/lib/site-packages/pkg/tests/test_foo.py"),
10621070
parts=["TestClass", "test_foo"],
1071+
parametrization="[a,b]",
10631072
module_name="pkg.tests.test_foo",
10641073
)
10651074
@@ -1068,10 +1077,9 @@ def resolve_collection_argument(
10681077
"""
10691078
base, squacket, rest = arg.partition("[")
10701079
strpath, *parts = base.split("::")
1071-
if squacket:
1072-
if not parts:
1073-
raise UsageError(f"path cannot contain [] parametrization: {arg}")
1074-
parts[-1] = f"{parts[-1]}{squacket}{rest}"
1080+
if squacket and not parts:
1081+
raise UsageError(f"path cannot contain [] parametrization: {arg}")
1082+
parametrization = f"{squacket}{rest}" if squacket else None
10751083
module_name = None
10761084
if as_pypath:
10771085
pyarg_strpath = search_pypath(
@@ -1099,5 +1107,6 @@ def resolve_collection_argument(
10991107
return CollectionArgument(
11001108
path=fspath,
11011109
parts=parts,
1110+
parametrization=parametrization,
11021111
module_name=module_name,
11031112
)

testing/test_main.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,27 +125,39 @@ def test_file(self, invocation_path: Path) -> None:
125125
) == CollectionArgument(
126126
path=invocation_path / "src/pkg/test.py",
127127
parts=[],
128+
parametrization=None,
128129
module_name=None,
129130
)
130131
assert resolve_collection_argument(
131132
invocation_path, "src/pkg/test.py::"
132133
) == CollectionArgument(
133134
path=invocation_path / "src/pkg/test.py",
134135
parts=[""],
136+
parametrization=None,
135137
module_name=None,
136138
)
137139
assert resolve_collection_argument(
138140
invocation_path, "src/pkg/test.py::foo::bar"
139141
) == CollectionArgument(
140142
path=invocation_path / "src/pkg/test.py",
141143
parts=["foo", "bar"],
144+
parametrization=None,
142145
module_name=None,
143146
)
144147
assert resolve_collection_argument(
145148
invocation_path, "src/pkg/test.py::foo::bar::"
146149
) == CollectionArgument(
147150
path=invocation_path / "src/pkg/test.py",
148151
parts=["foo", "bar", ""],
152+
parametrization=None,
153+
module_name=None,
154+
)
155+
assert resolve_collection_argument(
156+
invocation_path, "src/pkg/test.py::foo::bar[a,b,c]"
157+
) == CollectionArgument(
158+
path=invocation_path / "src/pkg/test.py",
159+
parts=["foo", "bar"],
160+
parametrization="[a,b,c]",
149161
module_name=None,
150162
)
151163

@@ -156,6 +168,7 @@ def test_dir(self, invocation_path: Path) -> None:
156168
) == CollectionArgument(
157169
path=invocation_path / "src/pkg",
158170
parts=[],
171+
parametrization=None,
159172
module_name=None,
160173
)
161174

@@ -181,13 +194,15 @@ def test_pypath(self, namespace_package: bool, invocation_path: Path) -> None:
181194
) == CollectionArgument(
182195
path=invocation_path / "src/pkg/test.py",
183196
parts=[],
197+
parametrization=None,
184198
module_name="pkg.test",
185199
)
186200
assert resolve_collection_argument(
187201
invocation_path, "pkg.test::foo::bar", as_pypath=True
188202
) == CollectionArgument(
189203
path=invocation_path / "src/pkg/test.py",
190204
parts=["foo", "bar"],
205+
parametrization=None,
191206
module_name="pkg.test",
192207
)
193208
assert resolve_collection_argument(
@@ -198,6 +213,7 @@ def test_pypath(self, namespace_package: bool, invocation_path: Path) -> None:
198213
) == CollectionArgument(
199214
path=invocation_path / "src/pkg",
200215
parts=[],
216+
parametrization=None,
201217
module_name="pkg",
202218
)
203219

@@ -216,7 +232,8 @@ def test_parametrized_name_with_colons(self, invocation_path: Path) -> None:
216232
invocation_path, "src/pkg/test.py::test[a::b]"
217233
) == CollectionArgument(
218234
path=invocation_path / "src/pkg/test.py",
219-
parts=["test[a::b]"],
235+
parts=["test"],
236+
parametrization="[a::b]",
220237
module_name=None,
221238
)
222239

@@ -257,6 +274,7 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N
257274
) == CollectionArgument(
258275
path=Path(os.path.abspath("src")),
259276
parts=[],
277+
parametrization=None,
260278
module_name=None,
261279
)
262280

@@ -268,6 +286,7 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N
268286
) == CollectionArgument(
269287
path=Path(os.path.abspath("src")),
270288
parts=[],
289+
parametrization=None,
271290
module_name=None,
272291
)
273292

0 commit comments

Comments
 (0)