Skip to content

Commit 854ef00

Browse files
Unittest discovery using the new test adapter (#18253)
* Basic test discovery (no server yet) * Clean up test discovery script * Add default test results array value (py side) * Add localization * Build tree on the Python side + move utils out * Basic test discovery * Add test tree update support * Multiroot workspace support * Make sure we don't shadow the "type" keyword * Move discovery in a function * Use pathlib everywhere * News entry * Add docstrings to utils tests + fix nested case * Rename utils tests data + delete unused files * Discovery tests * Update comments + requirements * Do not run test discovery for now * Re-generate requirements using python 3.7 * Sort Python imports on save * Get new server architecture up * Add comment for tornado * Apply Python suggestions from code review Co-authored-by: Brett Cannon <[email protected]> * Pass test discovery adapter to workspace adapter * Wrap nodes under test provider * Let the OS pick a port * traceLog when response processing errors out Co-authored-by: Brett Cannon <[email protected]>
1 parent c7af135 commit 854ef00

35 files changed

+2266
-19
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
".vscode test": true
2424
},
2525
"[python]": {
26-
"editor.formatOnSave": true
26+
"editor.formatOnSave": true,
27+
"editor.codeActionsOnSave": {
28+
"source.organizeImports": true
29+
}
2730
},
2831
"[typescript]": {
2932
"editor.defaultFormatter": "esbenp.prettier-vscode",

news/1 Enhancements/17242.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Rewrite support for unittest test discovery.

package.nls.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"Common.moreInfo": "More Info",
7272
"Common.and": "and",
7373
"Common.ok": "Ok",
74+
"Common.error": "Error",
7475
"Common.install": "Install",
7576
"Common.learnMore": "Learn more",
7677
"Common.reportThisIssue": "Report this issue",
@@ -157,6 +158,9 @@
157158
"debug.pyramidEnterDevelopmentIniPathInvalidFilePathError": "Enter a valid file path",
158159
"Testing.configureTests": "Configure Test Framework",
159160
"Testing.testNotConfigured": "No test framework configured.",
161+
"Testing.cancelUnittestDiscovery": "Canceled unittest test discovery",
162+
"Testing.errorUnittestDiscovery": "Unittest test discovery error",
163+
"Testing.seePythonOutput": "(see Output > Python)",
160164
"Common.openOutputPanel": "Show output",
161165
"LanguageService.statusItem.name": "Python IntelliSense Status",
162166
"LanguageService.statusItem.text": "Partial Mode",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import socket
5+
import sys
6+
7+
8+
class SocketManager(object):
9+
"""Create a socket and connect to the given address.
10+
11+
The address is a (host: str, port: int) tuple.
12+
Example usage:
13+
14+
```
15+
with SocketManager(("localhost", 6767)) as sock:
16+
request = json.dumps(payload)
17+
result = s.socket.sendall(request.encode("utf-8"))
18+
```
19+
"""
20+
21+
def __init__(self, addr):
22+
self.addr = addr
23+
self.socket = None
24+
25+
def __enter__(self):
26+
self.socket = socket.socket(
27+
socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP
28+
)
29+
if sys.platform == "win32":
30+
addr_use = socket.SO_EXCLUSIVEADDRUSE
31+
else:
32+
addr_use = socket.SO_REUSEADDR
33+
self.socket.setsockopt(socket.SOL_SOCKET, addr_use, 1)
34+
self.socket.connect(self.addr)
35+
36+
return self
37+
38+
def __exit__(self, *_):
39+
if self.socket:
40+
try:
41+
self.socket.shutdown(socket.SHUT_RDWR)
42+
except Exception:
43+
pass
44+
self.socket.close()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class DiscoveryEmpty(unittest.TestCase):
8+
"""Test class for the test_empty_discovery test.
9+
10+
The discover_tests function should return a dictionary with a "success" status, no errors, and no test tree
11+
if unittest discovery was performed successfully but no tests were found.
12+
"""
13+
14+
def something(self) -> bool:
15+
return True
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
import something_else # type: ignore
7+
8+
9+
class DiscoveryErrorOne(unittest.TestCase):
10+
"""Test class for the test_error_discovery test.
11+
12+
The discover_tests function should return a dictionary with an "error" status, the discovered tests, and a list of errors
13+
if unittest discovery failed at some point.
14+
"""
15+
16+
def test_one(self) -> None:
17+
self.assertGreater(2, 1)
18+
19+
def test_two(self) -> None:
20+
self.assertNotEqual(2, 1)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class DiscoveryErrorTwo(unittest.TestCase):
8+
"""Test class for the test_error_discovery test.
9+
10+
The discover_tests function should return a dictionary with an "error" status, the discovered tests, and a list of errors
11+
if unittest discovery failed at some point.
12+
"""
13+
14+
def test_one(self) -> None:
15+
self.assertGreater(2, 1)
16+
17+
def test_two(self) -> None:
18+
self.assertNotEqual(2, 1)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class DiscoverySimple(unittest.TestCase):
8+
"""Test class for the test_simple_discovery test.
9+
10+
The discover_tests function should return a dictionary with a "success" status, no errors, and a test tree
11+
if unittest discovery was performed successfully.
12+
"""
13+
14+
def test_one(self) -> None:
15+
self.assertGreater(2, 1)
16+
17+
def test_two(self) -> None:
18+
self.assertNotEqual(2, 1)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
from functools import wraps
6+
7+
8+
def my_decorator(f):
9+
@wraps(f)
10+
def wrapper(*args, **kwds):
11+
print("Calling decorated function")
12+
return f(*args, **kwds)
13+
14+
return wrapper
15+
16+
17+
class TreeOne(unittest.TestCase):
18+
"""Test class for the test_build_decorated_tree test.
19+
20+
build_test_tree should build a test tree with these test cases.
21+
"""
22+
23+
@my_decorator
24+
def test_one(self) -> None:
25+
self.assertGreater(2, 1)
26+
27+
@my_decorator
28+
def test_two(self) -> None:
29+
self.assertNotEqual(2, 1)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class CaseTwoFileOne(unittest.TestCase):
8+
"""Test class for the test_nested_test_cases test.
9+
10+
get_test_case should return tests from the test suites in this folder.
11+
"""
12+
13+
def test_one(self) -> None:
14+
self.assertGreater(2, 1)
15+
16+
def test_two(self) -> None:
17+
self.assertNotEqual(2, 1)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class CaseTwoFileTwo(unittest.TestCase):
8+
"""Test class for the test_nested_test_cases test.
9+
10+
get_test_case should return tests from the test suites in this folder.
11+
"""
12+
13+
def test_one(self) -> None:
14+
self.assertGreater(2, 1)
15+
16+
def test_two(self) -> None:
17+
self.assertNotEqual(2, 1)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class CaseOne(unittest.TestCase):
8+
"""Test class for the test_simple_test_cases test.
9+
10+
get_test_case should return tests from the test suite.
11+
"""
12+
13+
def test_one(self) -> None:
14+
self.assertGreater(2, 1)
15+
16+
def test_two(self) -> None:
17+
self.assertNotEqual(2, 1)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
7+
class TreeOne(unittest.TestCase):
8+
"""Test class for the test_build_simple_tree test.
9+
10+
build_test_tree should build a test tree with these test cases.
11+
"""
12+
13+
def test_one(self) -> None:
14+
self.assertGreater(2, 1)
15+
16+
def test_two(self) -> None:
17+
self.assertNotEqual(2, 1)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import sys
5+
6+
# Ignore the contents of this folder for Python 2 tests.
7+
if sys.version_info[0] < 3:
8+
collect_ignore_glob = ["*.py"]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import pathlib
5+
6+
TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data"
7+
8+
9+
def is_same_tree(tree1, tree2) -> bool:
10+
"""Helper function to test if two test trees are the same.
11+
12+
`is_same_tree` starts by comparing the root attributes, and then checks if all children are the same.
13+
"""
14+
# Compare the root.
15+
if any(tree1[key] != tree2[key] for key in ["path", "name", "type_"]):
16+
return False
17+
18+
# Compare child test nodes if they exist, otherwise compare test items.
19+
if "children" in tree1 and "children" in tree2:
20+
children1 = tree1["children"]
21+
children2 = tree2["children"]
22+
23+
# Compare test nodes.
24+
if len(children1) != len(children2):
25+
return False
26+
else:
27+
return all(is_same_tree(*pair) for pair in zip(children1, children2))
28+
elif "id_" in tree1 and "id_" in tree2:
29+
# Compare test items.
30+
return all(tree1[key] == tree2[key] for key in ["id_", "lineno"])
31+
32+
return False

0 commit comments

Comments
 (0)