Skip to content

Commit 85599ff

Browse files
authored
fix to support classes with parameterized tests inside (microsoft/vscode-python#23238)
fixes microsoft/vscode-python#22870
1 parent 8c529de commit 85599ff

File tree

5 files changed

+150
-106
lines changed

5 files changed

+150
-106
lines changed

extensions/positron-python/python_files/tests/pytestadapter/.data/parametrize_tests.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
import pytest
55

66

7-
# Testing pytest with parametrized tests. The first two pass, the third fails.
8-
# The tests ids are parametrize_tests.py::test_adding[3+5-8] and so on.
9-
@pytest.mark.parametrize( # test_marker--test_adding
10-
"actual, expected", [("3+5", 8), ("2+4", 6), ("6+9", 16)]
11-
)
12-
def test_adding(actual, expected):
13-
assert eval(actual) == expected
7+
class TestClass:
8+
# Testing pytest with parametrized tests. The first two pass, the third fails.
9+
# The tests ids are parametrize_tests.py::test_adding[3+5-8] and so on.
10+
@pytest.mark.parametrize( # test_marker--test_adding
11+
"actual, expected", [("3+5", 8), ("2+4", 6), ("6+9", 16)]
12+
)
13+
def test_adding(self, actual, expected):
14+
assert eval(actual) == expected
1415

1516

1617
# Testing pytest with parametrized tests. All three pass.

extensions/positron-python/python_files/tests/pytestadapter/expected_discovery_test_output.py

Lines changed: 83 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -511,10 +511,14 @@
511511

512512
# This is the expected output for the nested_folder tests.
513513
# └── parametrize_tests.py
514-
# └── test_adding
515-
# └── [3+5-8]
516-
# └── [2+4-6]
517-
# └── [6+9-16]
514+
# └── TestClass
515+
# └── test_adding
516+
# └── [3+5-8]
517+
# └── [2+4-6]
518+
# └── [6+9-16]
519+
# └── test_string
520+
# └── [hello]
521+
# └── [complicated split [] ()]
518522
parameterize_tests_path = TEST_DATA_PATH / "parametrize_tests.py"
519523
parametrize_tests_expected_output = {
520524
"name": ".data",
@@ -528,61 +532,69 @@
528532
"id_": os.fspath(parameterize_tests_path),
529533
"children": [
530534
{
531-
"name": "test_adding",
535+
"name": "TestClass",
532536
"path": os.fspath(parameterize_tests_path),
533-
"type_": "function",
534-
"id_": "parametrize_tests.py::test_adding",
537+
"type_": "class",
538+
"id_": "parametrize_tests.py::TestClass",
535539
"children": [
536540
{
537-
"name": "[3+5-8]",
538-
"path": os.fspath(parameterize_tests_path),
539-
"lineno": find_test_line_number(
540-
"test_adding[3+5-8]",
541-
parameterize_tests_path,
542-
),
543-
"type_": "test",
544-
"id_": get_absolute_test_id(
545-
"parametrize_tests.py::test_adding[3+5-8]",
546-
parameterize_tests_path,
547-
),
548-
"runID": get_absolute_test_id(
549-
"parametrize_tests.py::test_adding[3+5-8]",
550-
parameterize_tests_path,
551-
),
552-
},
553-
{
554-
"name": "[2+4-6]",
541+
"name": "test_adding",
555542
"path": os.fspath(parameterize_tests_path),
556-
"lineno": find_test_line_number(
557-
"test_adding[2+4-6]",
558-
parameterize_tests_path,
559-
),
560-
"type_": "test",
561-
"id_": get_absolute_test_id(
562-
"parametrize_tests.py::test_adding[2+4-6]",
563-
parameterize_tests_path,
564-
),
565-
"runID": get_absolute_test_id(
566-
"parametrize_tests.py::test_adding[2+4-6]",
567-
parameterize_tests_path,
568-
),
569-
},
570-
{
571-
"name": "[6+9-16]",
572-
"path": os.fspath(parameterize_tests_path),
573-
"lineno": find_test_line_number(
574-
"test_adding[6+9-16]",
575-
parameterize_tests_path,
576-
),
577-
"type_": "test",
578-
"id_": get_absolute_test_id(
579-
"parametrize_tests.py::test_adding[6+9-16]",
580-
parameterize_tests_path,
581-
),
582-
"runID": get_absolute_test_id(
583-
"parametrize_tests.py::test_adding[6+9-16]",
584-
parameterize_tests_path,
585-
),
543+
"type_": "function",
544+
"id_": "parametrize_tests.py::TestClass::test_adding",
545+
"children": [
546+
{
547+
"name": "[3+5-8]",
548+
"path": os.fspath(parameterize_tests_path),
549+
"lineno": find_test_line_number(
550+
"test_adding[3+5-8]",
551+
parameterize_tests_path,
552+
),
553+
"type_": "test",
554+
"id_": get_absolute_test_id(
555+
"parametrize_tests.py::TestClass::test_adding[3+5-8]",
556+
parameterize_tests_path,
557+
),
558+
"runID": get_absolute_test_id(
559+
"parametrize_tests.py::TestClass::test_adding[3+5-8]",
560+
parameterize_tests_path,
561+
),
562+
},
563+
{
564+
"name": "[2+4-6]",
565+
"path": os.fspath(parameterize_tests_path),
566+
"lineno": find_test_line_number(
567+
"test_adding[2+4-6]",
568+
parameterize_tests_path,
569+
),
570+
"type_": "test",
571+
"id_": get_absolute_test_id(
572+
"parametrize_tests.py::TestClass::test_adding[2+4-6]",
573+
parameterize_tests_path,
574+
),
575+
"runID": get_absolute_test_id(
576+
"parametrize_tests.py::TestClass::test_adding[2+4-6]",
577+
parameterize_tests_path,
578+
),
579+
},
580+
{
581+
"name": "[6+9-16]",
582+
"path": os.fspath(parameterize_tests_path),
583+
"lineno": find_test_line_number(
584+
"test_adding[6+9-16]",
585+
parameterize_tests_path,
586+
),
587+
"type_": "test",
588+
"id_": get_absolute_test_id(
589+
"parametrize_tests.py::TestClass::test_adding[6+9-16]",
590+
parameterize_tests_path,
591+
),
592+
"runID": get_absolute_test_id(
593+
"parametrize_tests.py::TestClass::test_adding[6+9-16]",
594+
parameterize_tests_path,
595+
),
596+
},
597+
],
586598
},
587599
],
588600
},
@@ -872,7 +884,15 @@
872884
"id_": os.fspath(tests_path),
873885
}
874886
TEST_MULTI_CLASS_NEST_PATH = TEST_DATA_PATH / "test_multi_class_nest.py"
875-
887+
# This is the expected output for the nested_classes tests.
888+
# └── test_multi_class_nest.py
889+
# └── TestFirstClass
890+
# └── TestSecondClass
891+
# └── test_second
892+
# └── test_first
893+
# └── TestSecondClass2
894+
# └── test_second2
895+
# └── test_independent
876896
nested_classes_expected_test_output = {
877897
"name": ".data",
878898
"path": TEST_DATA_PATH_STR,
@@ -984,6 +1004,13 @@
9841004
SYMLINK_FOLDER_PATH_TESTS_TEST_A = TEST_DATA_PATH / "symlink_folder" / "tests" / "test_a.py"
9851005
SYMLINK_FOLDER_PATH_TESTS_TEST_B = TEST_DATA_PATH / "symlink_folder" / "tests" / "test_b.py"
9861006

1007+
# This is the expected output for the symlink_folder tests.
1008+
# └── symlink_folder
1009+
# └── tests
1010+
# └── test_a.py
1011+
# └── test_a_function
1012+
# └── test_b.py
1013+
# └── test_b_function
9871014
symlink_expected_discovery_output = {
9881015
"name": "symlink_folder",
9891016
"path": str(SYMLINK_FOLDER_PATH),

extensions/positron-python/python_files/tests/pytestadapter/expected_execution_test_output.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -370,33 +370,40 @@
370370
}
371371
# This is the expected output for the nested_folder tests.
372372
# └── parametrize_tests.py
373+
# └── TestClass
373374
# └── test_adding[3+5-8]: success
374375
# └── test_adding[2+4-6]: success
375376
# └── test_adding[6+9-16]: failure
376377
parametrize_tests_path = TEST_DATA_PATH / "parametrize_tests.py"
377378

378379
parametrize_tests_expected_execution_output = {
379-
get_absolute_test_id("parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path): {
380+
get_absolute_test_id(
381+
"parametrize_tests.py::TestClass::test_adding[3+5-8]", parametrize_tests_path
382+
): {
380383
"test": get_absolute_test_id(
381-
"parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path
384+
"parametrize_tests.py::TestClass::test_adding[3+5-8]", parametrize_tests_path
382385
),
383386
"outcome": "success",
384387
"message": None,
385388
"traceback": None,
386389
"subtest": None,
387390
},
388-
get_absolute_test_id("parametrize_tests.py::test_adding[2+4-6]", parametrize_tests_path): {
391+
get_absolute_test_id(
392+
"parametrize_tests.py::TestClass::test_adding[2+4-6]", parametrize_tests_path
393+
): {
389394
"test": get_absolute_test_id(
390-
"parametrize_tests.py::test_adding[2+4-6]", parametrize_tests_path
395+
"parametrize_tests.py::TestClass::test_adding[2+4-6]", parametrize_tests_path
391396
),
392397
"outcome": "success",
393398
"message": None,
394399
"traceback": None,
395400
"subtest": None,
396401
},
397-
get_absolute_test_id("parametrize_tests.py::test_adding[6+9-16]", parametrize_tests_path): {
402+
get_absolute_test_id(
403+
"parametrize_tests.py::TestClass::test_adding[6+9-16]", parametrize_tests_path
404+
): {
398405
"test": get_absolute_test_id(
399-
"parametrize_tests.py::test_adding[6+9-16]", parametrize_tests_path
406+
"parametrize_tests.py::TestClass::test_adding[6+9-16]", parametrize_tests_path
400407
),
401408
"outcome": "failure",
402409
"message": "ERROR MESSAGE",
@@ -407,11 +414,14 @@
407414

408415
# This is the expected output for the single parameterized tests.
409416
# └── parametrize_tests.py
417+
# └── TestClass
410418
# └── test_adding[3+5-8]: success
411419
single_parametrize_tests_expected_execution_output = {
412-
get_absolute_test_id("parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path): {
420+
get_absolute_test_id(
421+
"parametrize_tests.py::TestClass::test_adding[3+5-8]", parametrize_tests_path
422+
): {
413423
"test": get_absolute_test_id(
414-
"parametrize_tests.py::test_adding[3+5-8]", parametrize_tests_path
424+
"parametrize_tests.py::TestClass::test_adding[3+5-8]", parametrize_tests_path
415425
),
416426
"outcome": "success",
417427
"message": None,

extensions/positron-python/python_files/tests/pytestadapter/test_execution.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,20 +200,20 @@ def test_bad_id_error_execution():
200200
expected_execution_test_output.dual_level_nested_folder_execution_expected_output,
201201
),
202202
(
203-
["folder_a/folder_b/folder_a/test_nest.py::test_function"],
203+
["folder_a/folder_b/folder_a/test_nest.py::test_function"], ##
204204
expected_execution_test_output.double_nested_folder_expected_execution_output,
205205
),
206206
(
207207
[
208-
"parametrize_tests.py::test_adding[3+5-8]",
209-
"parametrize_tests.py::test_adding[2+4-6]",
210-
"parametrize_tests.py::test_adding[6+9-16]",
208+
"parametrize_tests.py::TestClass::test_adding[3+5-8]", ##
209+
"parametrize_tests.py::TestClass::test_adding[2+4-6]",
210+
"parametrize_tests.py::TestClass::test_adding[6+9-16]",
211211
],
212212
expected_execution_test_output.parametrize_tests_expected_execution_output,
213213
),
214214
(
215215
[
216-
"parametrize_tests.py::test_adding[3+5-8]",
216+
"parametrize_tests.py::TestClass::test_adding[3+5-8]",
217217
],
218218
expected_execution_test_output.single_parametrize_tests_expected_execution_output,
219219
),

extensions/positron-python/python_files/vscode_pytest/__init__.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,37 @@ def build_test_tree(session: pytest.Session) -> TestNode:
412412

413413
for test_case in session.items:
414414
test_node = create_test_node(test_case)
415+
if hasattr(test_case, "callspec"): # This means it is a parameterized test.
416+
function_name: str = ""
417+
# parameterized test cases cut the repetitive part of the name off.
418+
parent_part, parameterized_section = test_node["name"].split("[", 1)
419+
test_node["name"] = "[" + parameterized_section
420+
parent_path = os.fspath(get_node_path(test_case)) + "::" + parent_part
421+
try:
422+
function_name = test_case.originalname # type: ignore
423+
function_test_node = function_nodes_dict[parent_path]
424+
except AttributeError: # actual error has occurred
425+
ERRORS.append(
426+
f"unable to find original name for {test_case.name} with parameterization detected."
427+
)
428+
raise VSCodePytestError("Unable to find original name for parameterized test case")
429+
except KeyError:
430+
function_test_node: TestNode = create_parameterized_function_node(
431+
function_name, get_node_path(test_case), test_case.nodeid
432+
)
433+
function_nodes_dict[parent_path] = function_test_node
434+
function_test_node["children"].append(test_node)
435+
# Check if the parent node of the function is file, if so create/add to this file node.
436+
if isinstance(test_case.parent, pytest.File):
437+
try:
438+
parent_test_case = file_nodes_dict[test_case.parent]
439+
except KeyError:
440+
parent_test_case = create_file_node(test_case.parent)
441+
file_nodes_dict[test_case.parent] = parent_test_case
442+
if function_test_node not in parent_test_case["children"]:
443+
parent_test_case["children"].append(function_test_node)
444+
# If the parent is not a file, it is a class, add the function node as the test node to handle subsequent nesting.
445+
test_node = function_test_node
415446
if isinstance(test_case.parent, pytest.Class):
416447
case_iter = test_case.parent
417448
node_child_iter = test_node
@@ -423,7 +454,9 @@ def build_test_tree(session: pytest.Session) -> TestNode:
423454
except KeyError:
424455
test_class_node = create_class_node(case_iter)
425456
class_nodes_dict[case_iter.nodeid] = test_class_node
426-
test_class_node["children"].append(node_child_iter)
457+
# Check if the class already has the child node. This will occur if the test is parameterized.
458+
if node_child_iter not in test_class_node["children"]:
459+
test_class_node["children"].append(node_child_iter)
427460
# Iterate up.
428461
node_child_iter = test_class_node
429462
case_iter = case_iter.parent
@@ -442,35 +475,8 @@ def build_test_tree(session: pytest.Session) -> TestNode:
442475
# Check if the class is already a child of the file node.
443476
if test_class_node is not None and test_class_node not in test_file_node["children"]:
444477
test_file_node["children"].append(test_class_node)
445-
elif hasattr(test_case, "callspec"): # This means it is a parameterized test.
446-
function_name: str = ""
447-
# parameterized test cases cut the repetitive part of the name off.
448-
parent_part, parameterized_section = test_node["name"].split("[", 1)
449-
test_node["name"] = "[" + parameterized_section
450-
parent_path = os.fspath(get_node_path(test_case)) + "::" + parent_part
451-
try:
452-
function_name = test_case.originalname # type: ignore
453-
function_test_case = function_nodes_dict[parent_path]
454-
except AttributeError: # actual error has occurred
455-
ERRORS.append(
456-
f"unable to find original name for {test_case.name} with parameterization detected."
457-
)
458-
raise VSCodePytestError("Unable to find original name for parameterized test case")
459-
except KeyError:
460-
function_test_case: TestNode = create_parameterized_function_node(
461-
function_name, get_node_path(test_case), test_case.nodeid
462-
)
463-
function_nodes_dict[parent_path] = function_test_case
464-
function_test_case["children"].append(test_node)
465-
# Now, add the function node to file node.
466-
try:
467-
parent_test_case = file_nodes_dict[test_case.parent]
468-
except KeyError:
469-
parent_test_case = create_file_node(test_case.parent)
470-
file_nodes_dict[test_case.parent] = parent_test_case
471-
if function_test_case not in parent_test_case["children"]:
472-
parent_test_case["children"].append(function_test_case)
473-
else: # This includes test cases that are pytest functions or a doctests.
478+
elif not hasattr(test_case, "callspec"):
479+
# This includes test cases that are pytest functions or a doctests.
474480
try:
475481
parent_test_case = file_nodes_dict[test_case.parent]
476482
except KeyError:

0 commit comments

Comments
 (0)