diff --git a/build/test-requirements.txt b/build/test-requirements.txt index 8b0ea1636157..e92467468589 100644 --- a/build/test-requirements.txt +++ b/build/test-requirements.txt @@ -36,6 +36,7 @@ pytest-json # for pytest-describe related tests pytest-describe +pytest-subtests # for pytest-ruff related tests pytest-ruff diff --git a/python_files/tests/pytestadapter/.data/test_pytest_subtests_plugin.py b/python_files/tests/pytestadapter/.data/test_pytest_subtests_plugin.py new file mode 100644 index 000000000000..28615ce450f3 --- /dev/null +++ b/python_files/tests/pytestadapter/.data/test_pytest_subtests_plugin.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +def test_a(subtests): + with subtests.test(msg="test_a"): + assert 1 == 1 + with subtests.test(msg="Second subtest"): + assert 2 == 1 diff --git a/python_files/tests/pytestadapter/expected_execution_test_output.py b/python_files/tests/pytestadapter/expected_execution_test_output.py index fa6743d0e112..a9a2180a344c 100644 --- a/python_files/tests/pytestadapter/expected_execution_test_output.py +++ b/python_files/tests/pytestadapter/expected_execution_test_output.py @@ -6,6 +6,7 @@ TEST_ADD_FUNCTION = "unittest_folder/test_add.py::TestAddFunction::" SUCCESS = "success" FAILURE = "failure" +SUBTEST_FAILURE = "subtest-failure" # This is the expected output for the unittest_folder execute tests # └── unittest_folder @@ -735,6 +736,41 @@ }, } +# This is the expected output for the test_pytest_subtests_plugin.py file. +# └── test_pytest_subtests_plugin.py +# └── test_a +# └── test_a [test_a]: subtest-success +# └── test_a [Second subtest]: subtest-failure +test_pytest_subtests_plugin_path = TEST_DATA_PATH / "test_pytest_subtests_plugin.py" +pytest_subtests_plugin_expected_execution_output = { + get_absolute_test_id( + "test_pytest_subtests_plugin.py::test_a**{test_a [test_a]}**", + test_pytest_subtests_plugin_path, + ): { + "test": get_absolute_test_id( + "test_pytest_subtests_plugin.py::test_a**{test_a [test_a]}**", + test_pytest_subtests_plugin_path, + ), + "outcome": "subtest-success", + "message": None, + "traceback": None, + "subtest": None, + }, + get_absolute_test_id( + "test_pytest_subtests_plugin.py::test_a**{test_a [Second subtest]}**", + test_pytest_subtests_plugin_path, + ): { + "test": get_absolute_test_id( + "test_pytest_subtests_plugin.py::test_a**{test_a [Second subtest]}**", + test_pytest_subtests_plugin_path, + ), + "outcome": SUBTEST_FAILURE, + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, +} + skip_test_fixture_path = TEST_DATA_PATH / "skip_test_fixture.py" skip_test_fixture_execution_expected_output = { get_absolute_test_id("skip_test_fixture.py::test_docker_client", skip_test_fixture_path): { diff --git a/python_files/tests/pytestadapter/test_execution.py b/python_files/tests/pytestadapter/test_execution.py index 95a66e0e7b87..c59d00d76298 100644 --- a/python_files/tests/pytestadapter/test_execution.py +++ b/python_files/tests/pytestadapter/test_execution.py @@ -232,6 +232,34 @@ def test_pytest_execution(test_ids, expected_const): assert actual_result_dict == expected_const +def test_pytest_subtest_plugin(): + test_subtest_plugin_path = TEST_DATA_PATH / "test_pytest_subtests_plugin.py" + + test_a_id = get_absolute_test_id( + "test_subtest_plugin_path::test_a", + test_subtest_plugin_path, + ) + args = [test_a_id] + expected_const = expected_execution_test_output.pytest_subtests_plugin_expected_execution_output + actual = runner(args) + assert actual + actual_list: List[Dict[str, Dict[str, Any]]] = actual + assert len(actual_list) == len(expected_const) + actual_result_dict = {} + if actual_list is not None: + for actual_item in actual_list: + assert all(item in actual_item for item in ("status", "cwd", "result")) + assert actual_item.get("status") == "success" + assert actual_item.get("cwd") == os.fspath(TEST_DATA_PATH) + actual_result_dict.update(actual_item["result"]) + for key in actual_result_dict: + if actual_result_dict[key]["outcome"] == "subtest-failure": + actual_result_dict[key]["message"] = "ERROR MESSAGE" + if actual_result_dict[key]["traceback"] is not None: + actual_result_dict[key]["traceback"] = "TRACEBACK" + assert actual_result_dict == expected_const + + def test_symlink_run(): """Test to test pytest discovery with the command line arg --rootdir specified as a symlink path. diff --git a/src/client/testing/testController/common/utils.ts b/src/client/testing/testController/common/utils.ts index 68e10a2213d6..7ed4ab307ea0 100644 --- a/src/client/testing/testController/common/utils.ts +++ b/src/client/testing/testController/common/utils.ts @@ -89,6 +89,7 @@ export async function startRunResultNamedPipe( if (cancellationToken) { disposables.push( cancellationToken?.onCancellationRequested(() => { + traceLog(`Test Result named pipe ${pipeName} cancelled`); traceLog(`Test Result named pipe ${pipeName} cancelled`); disposable.dispose(); }), @@ -195,6 +196,10 @@ export function populateTestTree( const testItem = testController.createTestItem(child.id_, child.name, Uri.file(child.path)); testItem.tags = [RunTestTag, DebugTestTag]; + let range: Range | undefined; + if (child.lineno) { + range = new Range(new Position(Number(child.lineno) - 1, 0), new Position(Number(child.lineno), 0)); + } let range: Range | undefined; if (child.lineno) { range = new Range(new Position(Number(child.lineno) - 1, 0), new Position(Number(child.lineno), 0)); @@ -345,6 +350,7 @@ export async function hasSymlinkParent(currentPath: string): Promise { // Recurse up the directory tree return await hasSymlinkParent(parentDirectory); } catch (error) { + traceError('Error checking symlinks:', error); traceError('Error checking symlinks:', error); return false; } diff --git a/src/test/testing/testController/utils.unit.test.ts b/src/test/testing/testController/utils.unit.test.ts index b871d18348e2..8f013da2ae8c 100644 --- a/src/test/testing/testController/utils.unit.test.ts +++ b/src/test/testing/testController/utils.unit.test.ts @@ -1,5 +1,5 @@ -// // Copyright (c) Microsoft Corporation. All rights reserved. -// // Licensed under the MIT License. +// // // Copyright (c) Microsoft Corporation. All rights reserved. +// // // Licensed under the MIT License. // import * as assert from 'assert'; // import { @@ -106,7 +106,7 @@ // assert.deepStrictEqual(rpcContent.remainingRawData, ''); // }); -// suite('Test Controller Utils: Other', () => { +// suite('Test Controller Utils: Unittest', () => { // interface TestCase { // name: string; // input: string;