Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
03c0c27
Adding execution fixes.
anthonykim1 Jun 29, 2022
c07c5ac
add map of runIDToTestItem for workspaceTestAdapter instance
anthonykim1 Jun 30, 2022
4313014
update UI accordingly using new execution method
anthonykim1 Jul 5, 2022
d204688
attempt to fix invalid argument
anthonykim1 Jul 12, 2022
221086e
fix invalid argument
anthonykim1 Jul 14, 2022
9d695e2
add traceback to the message
anthonykim1 Jul 15, 2022
b405c71
attempt to fix runID
anthonykim1 Jul 16, 2022
6f2d0b0
remove import Codetypes
anthonykim1 Jul 16, 2022
fb083d8
change runtest pattern to non optional
anthonykim1 Jul 17, 2022
7719e8f
remove unnecessary import comments
anthonykim1 Jul 18, 2022
e15222c
add code for unittest execution debugger
anthonykim1 Jul 21, 2022
6da14e3
add noop,traceError to solve mergeConflict
anthonykim1 Jul 21, 2022
090dfc6
fix malfunctioning execution, skipped and expected failure outcome
anthonykim1 Jul 22, 2022
6080872
allow cherry pick test runs for efficiency
anthonykim1 Jul 24, 2022
8da4c3e
add subtest update and directory search
anthonykim1 Jul 26, 2022
79c8874
leave new debugLauncher dormant and try to fix python type
anthonykim1 Jul 27, 2022
eeafaef
ignore type for runner.run as in the past
anthonykim1 Jul 27, 2022
004cf5b
cleanup execution.py
anthonykim1 Jul 27, 2022
275bcde
typescript side cleanup
anthonykim1 Jul 27, 2022
0da5a85
move the runInstance starting up before executing
anthonykim1 Jul 28, 2022
3e590ec
remove unnecessary debugLauncher check
anthonykim1 Jul 28, 2022
134b36b
get rid of TypeAlias
anthonykim1 Jul 28, 2022
50877bf
move uuid to header instead of inside payload
anthonykim1 Jul 29, 2022
37c56fc
fix python test and look server unittest
anthonykim1 Jul 29, 2022
1fc0f80
test out timeout
anthonykim1 Jul 29, 2022
99dd17b
try adding requestuuid to avoid timeout
anthonykim1 Jul 29, 2022
006204f
try using rawHeaders.push
anthonykim1 Jul 29, 2022
4c5c398
try inserting uuid inside option command
anthonykim1 Jul 29, 2022
f5559e9
fix all server.unit.test.ts problem caused by moving uuid to header
anthonykim1 Jul 29, 2022
6c4f45d
get rid of unused variable warning
anthonykim1 Jul 29, 2022
6879a2c
windows python test server
anthonykim1 Jul 29, 2022
419a266
pass in debuglauncher to new python test server in all of server.unit…
anthonykim1 Jul 29, 2022
6f0a2b4
make debugLauncher non optional on server.ts
anthonykim1 Jul 29, 2022
2f901d1
fix unnecessary comments on discovery
anthonykim1 Jul 30, 2022
2732471
rename requestuuid to request-uuid
anthonykim1 Jul 30, 2022
3d1dad7
rename parse_unittest_discovery_args and move to utils
anthonykim1 Jul 30, 2022
d1da2ed
attempt to fix the moduleNotFound for utils
anthonykim1 Jul 30, 2022
a4062ec
attempt to fix moduleNotFound2
anthonykim1 Jul 30, 2022
e490446
rearrange os.path
anthonykim1 Jul 30, 2022
3215418
undo rearrange os.path
anthonykim1 Jul 30, 2022
0202291
test that were not found mark as skipped
anthonykim1 Aug 1, 2022
4bf9199
fix set subtraction
anthonykim1 Aug 1, 2022
9c4ddfd
remove unnecessary not found which is handled earlier by tailor run a…
anthonykim1 Aug 1, 2022
80f7b88
fix broken discovery refresh
anthonykim1 Aug 1, 2022
ef716ba
fix warnings regarding unused
anthonykim1 Aug 1, 2022
94794e0
remove unused variable
anthonykim1 Aug 1, 2022
679c174
fix testData doesnt exist error
anthonykim1 Aug 1, 2022
db8afc8
remove unused methods
anthonykim1 Aug 1, 2022
581184b
comment new unittest
anthonykim1 Aug 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions pythonFiles/tests/unittestadapter/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
DEFAULT_PORT,
discover_tests,
parse_discovery_cli_args,
parse_unittest_discovery_args,
)
from unittestadapter.utils import TestNodeTypeEnum
from unittestadapter.utils import TestNodeTypeEnum, parse_unittest_args

from .helpers import TEST_DATA_PATH, is_same_tree

Expand Down Expand Up @@ -68,7 +67,7 @@ def test_parse_unittest_args(args: List[str], expected: List[str]) -> None:
"""The parse_unittest_args function should return values for the start_dir, pattern, and top_level_dir arguments
when passed as command-line options, and ignore unrecognized arguments.
"""
actual = parse_unittest_discovery_args(args)
actual = parse_unittest_args(args)

assert actual == expected

Expand Down Expand Up @@ -132,7 +131,6 @@ def test_simple_discovery() -> None:
actual = discover_tests(start_dir, pattern, None, uuid)

assert actual["status"] == "success"
assert actual["uuid"] == uuid
assert is_same_tree(actual.get("tests"), expected)
assert "errors" not in actual

Expand All @@ -148,7 +146,6 @@ def test_empty_discovery() -> None:
actual = discover_tests(start_dir, pattern, None, uuid)

assert actual["status"] == "success"
assert actual["uuid"] == uuid
assert "tests" not in actual
assert "errors" not in actual

Expand Down Expand Up @@ -215,6 +212,5 @@ def test_error_discovery() -> None:
actual = discover_tests(start_dir, pattern, None, uuid)

assert actual["status"] == "error"
assert actual["uuid"] == uuid
assert is_same_tree(expected, actual.get("tests"))
assert len(actual.get("errors", [])) == 1
4 changes: 4 additions & 0 deletions pythonFiles/tests/unittestadapter/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,15 @@ def test_build_simple_tree() -> None:
"type_": TestNodeTypeEnum.test,
"lineno": "13",
"id_": file_path + "\\" + "TreeOne" + "\\" + "test_one",
"runID": "utils_simple_tree.TreeOne.test_one",
},
{
"name": "test_two",
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "16",
"id_": file_path + "\\" + "TreeOne" + "\\" + "test_two",
"runID": "utils_simple_tree.TreeOne.test_two",
},
],
"id_": file_path + "\\" + "TreeOne",
Expand Down Expand Up @@ -253,13 +255,15 @@ def test_build_decorated_tree() -> None:
"type_": TestNodeTypeEnum.test,
"lineno": "24",
"id_": file_path + "\\" + "TreeOne" + "\\" + "test_one",
"runID": "utils_decorated_tree.TreeOne.test_one",
},
{
"name": "test_two",
"path": file_path,
"type_": TestNodeTypeEnum.test,
"lineno": "28",
"id_": file_path + "\\" + "TreeOne" + "\\" + "test_two",
"runID": "utils_decorated_tree.TreeOne.test_two",
},
],
"id_": file_path + "\\" + "TreeOne",
Expand Down
34 changes: 4 additions & 30 deletions pythonFiles/unittestadapter/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from testing_tools import socket_manager

# If I use from utils then there will be an import error in test_discovery.py.
from unittestadapter.utils import TestNode, build_test_tree
from unittestadapter.utils import TestNode, build_test_tree, parse_unittest_args

# Add the lib path to sys.path to find the typing_extensions module.
sys.path.insert(0, os.path.join(PYTHON_FILES, "lib", "python"))
Expand All @@ -44,35 +44,8 @@ def parse_discovery_cli_args(args: List[str]) -> Tuple[int, Union[str, None]]:
return int(parsed_args.port), parsed_args.uuid


def parse_unittest_discovery_args(args: List[str]) -> Tuple[str, str, Union[str, None]]:
"""Parse command-line arguments that should be forwarded to unittest to perform discovery.

Valid unittest arguments are: -v, -s, -p, -t and their long-form counterparts,
however we only care about the last three.

The returned tuple contains the following items
- start_directory: The directory where to start discovery, defaults to .
- pattern: The pattern to match test files, defaults to test*.py
- top_level_directory: The top-level directory of the project, defaults to None, and unittest will use start_directory behind the scenes.
"""

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("--start-directory", "-s", default=".")
arg_parser.add_argument("--pattern", "-p", default="test*.py")
arg_parser.add_argument("--top-level-directory", "-t", default=None)

parsed_args, _ = arg_parser.parse_known_args(args)

return (
parsed_args.start_directory,
parsed_args.pattern,
parsed_args.top_level_directory,
)


class PayloadDict(TypedDict):
cwd: str
uuid: Union[str, None]
status: Literal["success", "error"]
tests: NotRequired[TestNode]
errors: NotRequired[List[str]]
Expand Down Expand Up @@ -112,7 +85,7 @@ def discover_tests(
}
"""
cwd = os.path.abspath(start_dir)
payload: PayloadDict = {"cwd": cwd, "status": "success", "uuid": uuid}
payload: PayloadDict = {"cwd": cwd, "status": "success"}
tests = None
errors: List[str] = []

Expand Down Expand Up @@ -140,7 +113,7 @@ def discover_tests(
argv = sys.argv[1:]
index = argv.index("--udiscovery")

start_dir, pattern, top_level_dir = parse_unittest_discovery_args(argv[index + 1 :])
start_dir, pattern, top_level_dir = parse_unittest_args(argv[index + 1 :])

# Perform test discovery.
port, uuid = parse_discovery_cli_args(argv[:index])
Expand All @@ -154,6 +127,7 @@ def discover_tests(
Host: localhost:{port}
Content-Length: {len(data)}
Content-Type: application/json
Request-uuid: {uuid}

{data}"""
result = s.socket.sendall(request.encode("utf-8")) # type: ignore
83 changes: 33 additions & 50 deletions pythonFiles/unittestadapter/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,14 @@
from types import TracebackType
from typing import Dict, List, Optional, Tuple, Type, TypeAlias, TypedDict

from typing_extensions import NotRequired

from .discovery import parse_unittest_discovery_args

# Add the path to pythonFiles to sys.path to find testing_tools.socket_manager.
PYTHON_FILES = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, PYTHON_FILES)

# from testing_tools import socket_manager

# Add the lib path to sys.path to find the typing_extensions module.
sys.path.insert(0, os.path.join(PYTHON_FILES, "lib", "python"))
from testing_tools import socket_manager
from typing_extensions import NotRequired
from unittestadapter.utils import parse_unittest_args

DEFAULT_PORT = "45454"

Expand All @@ -41,15 +37,13 @@ def parse_execution_cli_args(args: List[str]) -> Tuple[int, str | None, List[str
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("--port", default=DEFAULT_PORT)
arg_parser.add_argument("--uuid")
arg_parser.add_argument("--testids")
arg_parser.add_argument("--testids", nargs="+")
parsed_args, _ = arg_parser.parse_known_args(args)

test_ids: List[str] = parsed_args.testids.split(",") if parsed_args.testids else []

return (int(parsed_args.port), parsed_args.uuid, test_ids)
return (int(parsed_args.port), parsed_args.uuid, parsed_args.testids)


ErrorType: TypeAlias = (
ErrorType = (
Tuple[Type[BaseException], BaseException, TracebackType] | Tuple[None, None, None]
)

Expand All @@ -66,6 +60,7 @@ class TestOutcomeEnum(str, enum.Enum):


class UnittestTestResult(unittest.TextTestResult):

formatted: Dict[str, Dict[str, str | None]] = dict()

def startTest(self, test: unittest.TestCase):
Expand Down Expand Up @@ -152,7 +147,6 @@ class TestExecutionStatus(str, enum.Enum):

class PayloadDict(TypedDict):
cwd: str
uuid: str | None
status: TestExecutionStatus
result: NotRequired[TestResultTypeAlias]
not_found: NotRequired[List[str]]
Expand All @@ -167,14 +161,14 @@ class PayloadDict(TypedDict):
def run_tests(
start_dir: str,
test_ids: List[str],
pattern: Optional[str],
pattern: str,
top_level_dir: Optional[str],
uuid: Optional[str],
) -> PayloadDict:
cwd = os.path.abspath(start_dir)
status = TestExecutionStatus.success
status = TestExecutionStatus.error
error = None
payload: PayloadDict = {"cwd": cwd, "uuid": uuid, "status": status}
payload: PayloadDict = {"cwd": cwd, "status": status}

try:
# If it's a file, split path and file name.
Expand All @@ -191,20 +185,17 @@ def run_tests(
"pattern": pattern,
"top_level_dir": top_level_dir,
}
suite = loader.discover(**{k: v for k, v in args.items() if v is not None})
suite = loader.discover(start_dir, pattern, top_level_dir)

# Run tests.
runner = unittest.TextTestRunner(resultclass=UnittestTestResult)
result: UnittestTestResult = runner.run(suite) # type: ignore
# lets try to tailer our own suite so we can figure out running only the ones we want
loader = unittest.TestLoader()
tailor: unittest.TestSuite = loader.loadTestsFromNames(test_ids)
result: UnittestTestResult = runner.run(tailor) # type: ignore

# Filter tests by id.
filtered_results = {k: v for k, v in result.formatted.items() if k in test_ids}
payload["result"] = filtered_results
payload["result"] = result.formatted

# Add a payload entry with the list of test ids for tests that weren't found.
not_found = set(test_ids) - set(filtered_results.keys())
if not_found:
payload["not_found"] = list(not_found)
except Exception:
status = TestExecutionStatus.error
error = traceback.format_exc()
Expand All @@ -214,7 +205,7 @@ def run_tests(

payload["status"] = status

print(f"payload: \n{json.dumps(payload, indent=4)}")
# print(f"payload: \n{json.dumps(payload, indent=4)}")

return payload

Expand All @@ -224,29 +215,21 @@ def run_tests(
argv = sys.argv[1:]
index = argv.index("--udiscovery")

start_dir, pattern, top_level_dir = parse_unittest_discovery_args(argv[index + 1 :])

# start_path = pathlib.Path.home() / "Documents" / "Sandbox" / "unittest-subtest"
# test_ids = [
# "subfolder.test_two.TestClassTwo.test_two_two",
# "test_one.TestClassOne.test_func_one",
# "test_eight.TestClassEight.test_func_eight",
# ]
# uuid = "abcd"
start_dir, pattern, top_level_dir = parse_unittest_args(argv[index + 1 :])

# Perform test execution.
port, uuid, test_ids = parse_execution_cli_args(argv[:index])
run_tests(start_dir, test_ids, pattern, top_level_dir, uuid)


# # Build the request data (it has to be a POST request or the Node side will not process it), and send it.
# addr = ("localhost", port)
# with socket_manager.SocketManager(addr) as s:
# data = json.dumps(payload)
# request = f"""POST / HTTP/1.1
# Host: localhost:{port}
# Content-Length: {len(data)}
# Content-Type: application/json

# {data}"""
# result = s.socket.sendall(request.encode("utf-8")) # type: ignore
port, uuid, testids = parse_execution_cli_args(argv[:index])
payload = run_tests(start_dir, testids, pattern, top_level_dir, uuid)

# Build the request data (it has to be a POST request or the Node side will not process it), and send it.
addr = ("localhost", port)
with socket_manager.SocketManager(addr) as s:
data = json.dumps(payload)
request = f"""POST / HTTP/1.1
Host: localhost:{port}
Content-Length: {len(data)}
Content-Type: application/json
Request-uuid: {uuid}

{data}"""
result = s.socket.sendall(request.encode("utf-8")) # type: ignore
29 changes: 29 additions & 0 deletions pythonFiles/unittestadapter/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import argparse
import enum
import inspect
import os
Expand Down Expand Up @@ -28,6 +29,7 @@ class TestData(TypedDict):

class TestItem(TestData):
lineno: str
runID: str


class TestNode(TestData):
Expand Down Expand Up @@ -190,10 +192,37 @@ def build_test_tree(
"lineno": lineno,
"type_": TestNodeTypeEnum.test,
"id_": file_path + "\\" + class_name + "\\" + function_name,
"runID": test_id,
} # concatenate class name and function test name
current_node["children"].append(test_node)

if not root["children"]:
root = None

return root, errors


def parse_unittest_args(args: List[str]) -> Tuple[str, str, Union[str, None]]:
"""Parse command-line arguments that should be forwarded to unittest to perform discovery.

Valid unittest arguments are: -v, -s, -p, -t and their long-form counterparts,
however we only care about the last three.

The returned tuple contains the following items
- start_directory: The directory where to start discovery, defaults to .
- pattern: The pattern to match test files, defaults to test*.py
- top_level_directory: The top-level directory of the project, defaults to None, and unittest will use start_directory behind the scenes.
"""

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("--start-directory", "-s", default=".")
arg_parser.add_argument("--pattern", "-p", default="test*.py")
arg_parser.add_argument("--top-level-directory", "-t", default=None)

parsed_args, _ = arg_parser.parse_known_args(args)

return (
parsed_args.start_directory,
parsed_args.pattern,
parsed_args.top_level_directory,
)
7 changes: 7 additions & 0 deletions src/client/common/process/internal/scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ export function visualstudio_py_testlauncher(testArgs: string[]): string[] {
return [script, ...testArgs];
}

// execution.py
// eslint-disable-next-line camelcase
export function execution_py_testlauncher(testArgs: string[]): string[] {
const script = path.join(SCRIPTS_DIR, 'unittestadapter', 'execution.py');
return [script, ...testArgs];
}

// tensorboard_launcher.py

export function tensorboardLauncher(args: string[]): string[] {
Expand Down
5 changes: 5 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,11 @@ export namespace Testing {
);
export const errorUnittestDiscovery = localize('Testing.errorUnittestDiscovery', 'Unittest test discovery error');
export const seePythonOutput = localize('Testing.seePythonOutput', '(see Output > Python)');
export const cancelUnittestExecution = localize(
'Testing.cancelUnittestExecution',
'Canceled unittest test execution',
);
export const errorUnittestExecution = localize('Testing.errorUnittestExecution', 'Unittest test execution error');
}

export namespace OutdatedDebugger {
Expand Down
3 changes: 2 additions & 1 deletion src/client/testing/common/debugLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ export class DebugLauncher implements ITestDebugLauncher {
private getTestLauncherScript(testProvider: TestProvider) {
switch (testProvider) {
case 'unittest': {
return internalScripts.visualstudio_py_testlauncher;
return internalScripts.visualstudio_py_testlauncher; // old way unittest execution, debugger
// return internalScripts.execution_py_testlauncher; // this is the new way to run unittest execution, debugger
}
case 'pytest': {
return internalScripts.testlauncher;
Expand Down
Loading