Skip to content

Commit 2cfe670

Browse files
committed
Merge remote-tracking branch 'upstream/main' into bad-raven
# Conflicts: # python_files/vscode_pytest/__init__.py # src/client/testing/testController/common/testDiscoveryHandler.ts # src/test/testing/testController/common/testDiscoveryHandler.unit.test.ts
2 parents abcba83 + 03f6e24 commit 2cfe670

24 files changed

Lines changed: 474 additions & 121 deletions

.github/commands.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,14 @@
153153
"addLabel": "info-needed",
154154
"removeLabel": "~confirmation-needed",
155155
"comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!"
156+
},
157+
{
158+
"type": "label",
159+
"name": "~spam",
160+
"removeLabel": "~spam",
161+
"addLabel": "spam",
162+
"action": "close",
163+
"reason": "not_planned",
164+
"comment": "Thank you for your submission. This issue has been closed as it doesn't meet our community guidelines or appears to be spam.\n\n**If you believe this was closed in error:**\n- Please review our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/)\n- Ensure your issue contains a clear description of the problem or feature request\n- Feel free to open a new issue with appropriate detail if this was a legitimate concern\n\n**For legitimate issues, please include:**\n- Clear description of the problem\n- Steps to reproduce (for bugs)\n- Expected vs actual behavior\n- VS Code version and environment details\n\nThank you for helping us maintain a welcoming and productive community."
156165
}
157166
]

.github/workflows/lock-issues.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: ubuntu-latest
1717
steps:
1818
- name: 'Lock Issues'
19-
uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0
19+
uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6.0.2
2020
with:
2121
github-token: ${{ github.token }}
2222
issue-inactive-days: '30'

package-lock.json

Lines changed: 35 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@
17231723
"semver": "^7.5.2",
17241724
"stack-trace": "0.0.10",
17251725
"sudo-prompt": "^9.2.1",
1726-
"tmp": "^0.2.5",
1726+
"tmp": "^0.2.7",
17271727
"uint64be": "^3.0.0",
17281728
"unicode": "^14.0.0",
17291729
"vscode-debugprotocol": "^1.28.0",

python_files/vscode_pytest/__init__.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,21 @@ class TestItem(TestData):
6363
class TestNode(TestData):
6464
"""A general class that handles all test data which contains children."""
6565

66-
children: list[TestNode | TestItem | None]
66+
children: Children
6767
lineno: NotRequired[str] # Optional field for class/function nodes
6868

6969

70+
class Children:
71+
def __init__(self, init=None):
72+
self._children = dict(init) if init is not None else {}
73+
74+
def add(self, child: TestNode | TestItem):
75+
self._children[child["id_"]] = child
76+
77+
def values(self):
78+
return list(self._children.values())
79+
80+
7081
class VSCodePytestError(Exception):
7182
"""A custom exception class for pytest errors."""
7283

@@ -441,7 +452,7 @@ def pytest_sessionfinish(session, exitstatus):
441452
"name": "",
442453
"path": test_root_path,
443454
"type_": "error",
444-
"children": [],
455+
"children": Children(),
445456
"id_": "",
446457
}
447458
send_discovery_message(os.fsdecode(test_root_path), error_node)
@@ -461,7 +472,7 @@ def pytest_sessionfinish(session, exitstatus):
461472
"name": "",
462473
"path": test_root_path,
463474
"type_": "error",
464-
"children": [],
475+
"children": Children(),
465476
"id_": "",
466477
}
467478
send_discovery_message(os.fsdecode(test_root_path), error_node)
@@ -666,8 +677,7 @@ def process_parameterized_test(
666677
)
667678
function_nodes_dict[parent_id] = function_test_node
668679

669-
if test_node not in function_test_node["children"]:
670-
function_test_node["children"].append(test_node)
680+
function_test_node["children"].add(test_node)
671681

672682
# Check if the parent node of the function is file, if so create/add to this file node.
673683
if isinstance(test_case.parent, pytest.File):
@@ -678,8 +688,7 @@ def process_parameterized_test(
678688
if parent_test_case is None:
679689
parent_test_case = create_file_node(parent_path)
680690
file_nodes_dict[parent_path_key] = parent_test_case
681-
if function_test_node not in parent_test_case["children"]:
682-
parent_test_case["children"].append(function_test_node)
691+
parent_test_case["children"].add(function_test_node)
683692

684693
# Return the function node as the test node to handle subsequent nesting
685694
return function_test_node
@@ -728,8 +737,7 @@ def build_test_tree(session: pytest.Session) -> TestNode:
728737
test_class_node = create_class_node(case_iter)
729738
class_nodes_dict[case_iter.nodeid] = test_class_node
730739
# Check if the class already has the child node. This will occur if the test is parameterized.
731-
if node_child_iter not in test_class_node["children"]:
732-
test_class_node["children"].append(node_child_iter)
740+
test_class_node["children"].add(node_child_iter)
733741
# Iterate up.
734742
node_child_iter = test_class_node
735743
case_iter = case_iter.parent
@@ -747,8 +755,8 @@ def build_test_tree(session: pytest.Session) -> TestNode:
747755
test_file_node = create_file_node(parent_path)
748756
file_nodes_dict[parent_path_key] = test_file_node
749757
# Check if the class is already a child of the file node.
750-
if test_class_node is not None and test_class_node not in test_file_node["children"]:
751-
test_file_node["children"].append(test_class_node)
758+
if test_class_node is not None:
759+
test_file_node["children"].add(test_class_node)
752760
elif not hasattr(test_case, "callspec"):
753761
# This includes test cases that are pytest functions or a doctests.
754762
if test_case.parent is None:
@@ -765,12 +773,13 @@ def build_test_tree(session: pytest.Session) -> TestNode:
765773
if parent_test_case is None:
766774
parent_test_case = create_file_node(parent_path)
767775
file_nodes_dict[parent_path_key] = parent_test_case
768-
parent_test_case["children"].append(test_node)
776+
parent_test_case["children"].add(test_node)
769777
# Process all files and construct them into nested folders
770778
session_children_dict = construct_nested_folders(
771779
file_nodes_dict, session_node, session_children_dict
772780
)
773-
session_node["children"] = list(session_children_dict.values())
781+
session_node["children"] = Children(session_children_dict)
782+
774783
return session_node
775784

776785

@@ -810,8 +819,7 @@ def build_nested_folders(
810819
if curr_folder_node is None:
811820
curr_folder_node = create_folder_node(curr_folder_name, iterator_path)
812821
created_files_folders_dict[iterator_path_key] = curr_folder_node
813-
if prev_folder_node not in curr_folder_node["children"]:
814-
curr_folder_node["children"].append(prev_folder_node)
822+
curr_folder_node["children"].add(prev_folder_node)
815823
iterator_path = iterator_path.parent
816824
prev_folder_node = curr_folder_node
817825
# Handles error where infinite loop occurs.
@@ -860,7 +868,7 @@ def create_session_node(session: pytest.Session) -> TestNode:
860868
"name": node_path.name,
861869
"path": node_path,
862870
"type_": "folder",
863-
"children": [],
871+
"children": Children(),
864872
"id_": os.fspath(node_path),
865873
}
866874

@@ -887,7 +895,7 @@ def create_class_node(class_module: Any) -> TestNode:
887895
"name": class_module.name,
888896
"path": get_node_path(class_module),
889897
"type_": "class",
890-
"children": [],
898+
"children": Children(),
891899
"id_": get_absolute_test_id(class_module.nodeid, get_node_path(class_module)),
892900
"lineno": class_line,
893901
}
@@ -908,7 +916,7 @@ def create_parameterized_function_node(
908916
"name": function_name,
909917
"path": test_path,
910918
"type_": "function",
911-
"children": [],
919+
"children": Children(),
912920
"id_": function_id,
913921
}
914922

@@ -924,7 +932,7 @@ def create_file_node(calculated_node_path: pathlib.Path) -> TestNode:
924932
"path": calculated_node_path,
925933
"type_": "file",
926934
"id_": os.fspath(calculated_node_path),
927-
"children": [],
935+
"children": Children(),
928936
}
929937

930938

@@ -940,7 +948,7 @@ def create_folder_node(folder_name: str, path_iterator: pathlib.Path) -> TestNod
940948
"path": path_iterator,
941949
"type_": "folder",
942950
"id_": os.fspath(path_iterator),
943-
"children": [],
951+
"children": Children(),
944952
}
945953

946954

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ microvenv==2025.0 \
88
--hash=sha256:568155ec18af01c89f270d35d123ab803b09672b480c3702d15fd69e9cc5bd1e \
99
--hash=sha256:8a2568a8390a4ffb5af2f05e7642454e03b887e582d192b6316326974eab5d0f
1010
# via -r requirements.in
11-
packaging==26.1 \
12-
--hash=sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f \
13-
--hash=sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de
11+
packaging==26.2 \
12+
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
13+
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
1414
# via -r requirements.in
1515
tomli==2.4.1 \
1616
--hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \

src/client/interpreter/configuration/recommededEnvironmentService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ const MEMENTO_KEY = 'userSelectedEnvPath';
1717
@injectable()
1818
export class RecommendedEnvironmentService implements IRecommendedEnvironmentService, IExtensionActivationService {
1919
private api?: PythonExtension['environments'];
20+
private hasRegisteredCommand = false;
2021
constructor(@inject(IExtensionContext) private readonly extensionContext: IExtensionContext) {}
2122
supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean } = {
2223
untrustedWorkspace: true,
2324
virtualWorkspace: false,
2425
};
2526

2627
async activate(_resource: Resource, _startupStopWatch?: StopWatch): Promise<void> {
28+
if (this.hasRegisteredCommand) {
29+
return;
30+
}
31+
this.hasRegisteredCommand = true;
2732
this.extensionContext.subscriptions.push(
2833
commands.registerCommand('python.getRecommendedEnvironment', async (resource: Resource) => {
2934
return this.getRecommededEnvironment(resource);

src/client/telemetry/constants.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export enum EventName {
4848
UNITTEST_DISCOVERY_DONE = 'UNITTEST.DISCOVERY.DONE',
4949
UNITTEST_RUN_STOP = 'UNITTEST.RUN.STOP',
5050
UNITTEST_RUN = 'UNITTEST.RUN',
51+
UNITTEST_RUN_DONE = 'UNITTEST.RUN.DONE',
5152
UNITTEST_RUN_ALL_FAILED = 'UNITTEST.RUN_ALL_FAILED',
5253
UNITTEST_DISABLED = 'UNITTEST.DISABLED',
5354

@@ -101,6 +102,17 @@ export enum EventName {
101102
ENVIRONMENT_TERMINAL_GLOBAL_PIP = 'ENVIRONMENT.TERMINAL.GLOBAL_PIP',
102103
}
103104

105+
export const UNITTEST_RUN_FAILURE_CATEGORIES = [
106+
'pipe-cancelled',
107+
'subprocess-crash',
108+
'no-results',
109+
'env-mismatch',
110+
'cancelled',
111+
'unknown',
112+
] as const;
113+
114+
export type UnitTestRunFailureCategory = typeof UNITTEST_RUN_FAILURE_CATEGORIES[number];
115+
104116
export enum PlatformErrors {
105117
FailedToParseVersion = 'FailedToParseVersion',
106118
FailedToDetermineOS = 'FailedToDetermineOS',

0 commit comments

Comments
 (0)