GeezerVison is showing github.com — Pass PTVSD folder name to the new debug adapter executable (#7152) · hypostulate/vscode-python@ed38ea2
File treeExpand file treeCollapse file tree Open diff view settings test/debugger/extension/adapter
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: build/ci/templates/steps/build_compile.yml -9Lines changed: 0 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -36,15 +36,6 @@ steps: | 36 | 36 | displayName: "pip install requirements" | 37 | 37 | condition: and(succeeded(), eq(variables['build'], 'true')) | 38 | 38 |
| 39 | | - - task: PythonScript@0 | 40 | | - displayName: "Install PTVSD wheels" | 41 | | - inputs: | 42 | | - scriptSource: "filePath" | 43 | | - scriptPath: "./pythonFiles/install_ptvsd.py" | 44 | | - arguments: "--ci" | 45 | | - failOnStderr: true | 46 | | - condition: and(succeeded(), eq(variables['build'], 'true')) | 47 | | - | 48 | 39 | - bash: npm run clean | 49 | 40 | displayName: "Clean" | 50 | 41 | condition: and(succeeded(), eq(variables['build'], 'true')) | |
Collapse file Copy file name to clipboardExpand all lines: build/ci/templates/test_phases.yml +9Lines changed: 9 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -138,6 +138,15 @@ steps: | 138 | 138 | displayName: 'pip install ipython requirements' | 139 | 139 | condition: and(succeeded(), eq(variables['NeedsIPythonReqs'], 'true')) | 140 | 140 |
| | 141 | + - task: PythonScript@0 | | 142 | + displayName: "Install PTVSD wheels" | | 143 | + inputs: | | 144 | + scriptSource: "filePath" | | 145 | + scriptPath: "./pythonFiles/install_ptvsd.py" | | 146 | + arguments: "--ci" | | 147 | + failOnStderr: true | | 148 | + condition: contains(variables['TestsToRun'], 'testUnitTests') | | 149 | + | 141 | 150 | # Run the Python unit tests in our codebase. Produces a JUnit-style log file that | 142 | 151 | # will be uploaded after all tests are complete. | 143 | 152 | # | |
Collapse file Copy file name to clipboardExpand all lines: build/test-requirements.txt +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 15 | 15 | flask | 16 | 16 | django | 17 | 17 | isort | | 18 | +packaging==19.2 |
Collapse file Copy file name to clipboardExpand all lines: pythonFiles/install_ptvsd.py +25-19Lines changed: 25 additions & 19 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 5 | 5 | import urllib.request | 6 | 6 | import sys | 7 | 7 |
| 8 | | -ROOT_DIRNAME = path.dirname(path.dirname(path.abspath(__file__))) | 9 | | -REQUIREMENTS_PATH = path.join(ROOT_DIRNAME, "requirements.txt") | 10 | | -PYTHONFILES_PATH = path.join(ROOT_DIRNAME, "pythonFiles", "lib", "python") | | 8 | +ROOT = path.dirname(path.dirname(path.abspath(__file__))) | | 9 | +REQUIREMENTS = path.join(ROOT, "requirements.txt") | | 10 | +PYTHONFILES = path.join(ROOT, "pythonFiles", "lib", "python") | 11 | 11 | PYPI_PTVSD_URL = "https://pypi.org/pypi/ptvsd/json" | 12 | 12 |
| 13 | 13 |
| 14 | 14 | def install_ptvsd(): | 15 | | - # If we are in CI use the packaging module installed in PYTHONFILES_PATH. | | 15 | + # If we are in CI use the packaging module installed in PYTHONFILES. | 16 | 16 | if len(sys.argv) == 2 and sys.argv[1] == "--ci": | 17 | | - sys.path.insert(0, PYTHONFILES_PATH) | | 17 | + sys.path.insert(0, PYTHONFILES) | 18 | 18 | from packaging.requirements import Requirement | 19 | 19 |
| 20 | | - with open(REQUIREMENTS_PATH, "r", encoding="utf-8") as requirements: | 21 | | - for line in requirements: | 22 | | - package_requirement = Requirement(line) | 23 | | - if package_requirement.name != "ptvsd": | 24 | | - continue | 25 | | - requirement_specifier = package_requirement.specifier | 26 | | - ptvsd_version = next(requirement_specifier.__iter__()).version | | 20 | + with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: | | 21 | + for line in reqsfile: | | 22 | + pkgreq = Requirement(line) | | 23 | + if pkgreq.name == "ptvsd": | | 24 | + specs = pkgreq.specifier | | 25 | + version = next(iter(specs)).version | | 26 | + break | | 27 | + | | 28 | + try: | | 29 | + version | | 30 | + except NameError: | | 31 | + raise Exception("ptvsd requirement not found.") | 27 | 32 |
| 28 | 33 | # Response format: https://warehouse.readthedocs.io/api-reference/json/#project | 29 | 34 | with urllib.request.urlopen(PYPI_PTVSD_URL) as response: | 30 | 35 | json_response = json.loads(response.read()) | 31 | 36 | releases = json_response["releases"] | 32 | 37 |
| 33 | 38 | # Release metadata format: https://github.com/pypa/interoperability-peps/blob/master/pep-0426-core-metadata.rst | 34 | | - for wheel_info in releases[ptvsd_version]: | | 39 | + for wheel_info in releases[version]: | 35 | 40 | # Download only if it's a 3.7 wheel. | 36 | 41 | if not wheel_info["python_version"].endswith(("37", "3.7")): | 37 | 42 | continue | 38 | 43 | filename = wheel_info["filename"].rpartition(".")[0] # Trim the file extension. | 39 | | - ptvsd_path = path.join(PYTHONFILES_PATH, filename) | | 44 | + ptvsd_path = path.join(PYTHONFILES, filename) | 40 | 45 |
| 41 | 46 | with urllib.request.urlopen(wheel_info["url"]) as wheel_response: | 42 | 47 | wheel_file = BytesIO(wheel_response.read()) | 43 | | - # Extract only the contents of the ptvsd subfolder. | 44 | | - prefix = path.join(f"ptvsd-{ptvsd_version}.data", "purelib", "ptvsd") | | 48 | + # Extract only the contents of the purelib subfolder (parent folder of ptvsd), | | 49 | + # since ptvsd files rely on the presence of a 'ptvsd' folder. | | 50 | + prefix = path.join(f"ptvsd-{version}.data", "purelib") | 45 | 51 |
| 46 | 52 | with ZipFile(wheel_file, "r") as wheel: | 47 | 53 | for zip_info in wheel.infolist(): | 48 | | - if not zip_info.filename.startswith(prefix): | 49 | | - continue | | 54 | + # Normalize path for Windows, the wheel folder structure uses forward slashes. | | 55 | + normalized = path.normpath(zip_info.filename) | 50 | 56 | # Flatten the folder structure. | 51 | | - zip_info.filename = zip_info.filename.split(prefix)[-1] | | 57 | + zip_info.filename = normalized.split(prefix)[-1] | 52 | 58 | wheel.extract(zip_info, ptvsd_path) | 53 | 59 |
| 54 | 60 |
| |
Collapse file Copy file name to clipboard +53Lines changed: 53 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +# Copyright (c) Microsoft Corporation. All rights reserved. | | 2 | +# Licensed under the MIT License. | | 3 | + | | 4 | +import sys | | 5 | +import os.path | | 6 | + | | 7 | +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | | 8 | +PYTHONFILES = os.path.join(ROOT, "pythonFiles", "lib", "python") | | 9 | +REQUIREMENTS = os.path.join(ROOT, "requirements.txt") | | 10 | + | | 11 | +sys.path.insert(0, PYTHONFILES) | | 12 | + | | 13 | +from packaging.requirements import Requirement | | 14 | +from packaging.tags import sys_tags | | 15 | + | | 16 | +sys.path.remove(PYTHONFILES) | | 17 | + | | 18 | + | | 19 | +def ptvsd_folder_name(): | | 20 | + """Return the folder name for the bundled PTVSD wheel compatible with the new debug adapter.""" | | 21 | + | | 22 | + with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: | | 23 | + for line in reqsfile: | | 24 | + pkgreq = Requirement(line) | | 25 | + if pkgreq.name == "ptvsd": | | 26 | + specs = pkgreq.specifier | | 27 | + try: | | 28 | + spec, = specs | | 29 | + version = spec.version | | 30 | + except: | | 31 | + # Fallpack to use base PTVSD path. | | 32 | + print(PYTHONFILES, end="") | | 33 | + return | | 34 | + break | | 35 | + | | 36 | + try: | | 37 | + for tag in sys_tags(): | | 38 | + folder_name = f"ptvsd-{version}-{tag.interpreter}-{tag.abi}-{tag.platform}" | | 39 | + folder_path = os.path.join(PYTHONFILES, folder_name) | | 40 | + if os.path.exists(folder_path): | | 41 | + print(folder_path, end="") | | 42 | + return | | 43 | + except: | | 44 | + # Fallback to use base PTVSD path no matter the exception. | | 45 | + print(PYTHONFILES, end="") | | 46 | + return | | 47 | + | | 48 | + # Default fallback to use base PTVSD path. | | 49 | + print(PYTHONFILES, end="") | | 50 | + | | 51 | + | | 52 | +if __name__ == "__main__": | | 53 | + ptvsd_folder_name() |
Collapse file Copy file name to clipboard +11Lines changed: 11 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | # Copyright (c) Microsoft Corporation. All rights reserved. | 2 | 2 | # Licensed under the MIT License. | | 3 | +import os.path | | 4 | + | | 5 | +TEST_ROOT = os.path.dirname(__file__) | | 6 | +SRC_ROOT = os.path.dirname(TEST_ROOT) | | 7 | +PROJECT_ROOT = os.path.dirname(SRC_ROOT) | | 8 | +IPYTHON_ROOT = os.path.join(SRC_ROOT, "ipython") | | 9 | +TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, "testing_tools") | | 10 | +DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, "debug_adapter") | | 11 | + | | 12 | +PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") | | 13 | +REQUIREMENTS = os.path.join(PROJECT_ROOT, "requirements.txt") |
Collapse file Copy file name to clipboardExpand all lines: pythonFiles/tests/__main__.py +11-17Lines changed: 11 additions & 17 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 | # Licensed under the MIT License. | 3 | 3 |
| 4 | 4 | import argparse | 5 | | -import os.path | 6 | 5 | import sys | 7 | 6 |
| 8 | 7 | import pytest | 9 | 8 |
| 10 | | - | 11 | | -TEST_ROOT = os.path.dirname(__file__) | 12 | | -SRC_ROOT = os.path.dirname(TEST_ROOT) | 13 | | -PROJECT_ROOT = os.path.dirname(SRC_ROOT) | 14 | | -IPYTHON_ROOT = os.path.join(SRC_ROOT, 'ipython') | 15 | | -TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, 'testing_tools') | | 9 | +from . import DEBUG_ADAPTER_ROOT, IPYTHON_ROOT, SRC_ROOT, TEST_ROOT, TESTING_TOOLS_ROOT | 16 | 10 |
| 17 | 11 |
| 18 | 12 | def parse_args(): | 19 | 13 | parser = argparse.ArgumentParser() | 20 | 14 | # To mark a test as functional: (decorator) @pytest.mark.functional | 21 | | - parser.add_argument('--functional', dest='markers', | 22 | | - action='append_const', const='functional') | 23 | | - parser.add_argument('--no-functional', dest='markers', | 24 | | - action='append_const', const='not functional') | | 15 | + parser.add_argument( | | 16 | + "--functional", dest="markers", action="append_const", const="functional" | | 17 | + ) | | 18 | + parser.add_argument( | | 19 | + "--no-functional", dest="markers", action="append_const", const="not functional" | | 20 | + ) | 25 | 21 | args, remainder = parser.parse_known_args() | 26 | 22 |
| 27 | 23 | ns = vars(args) | @@ -32,20 +28,18 @@ def parse_args(): | 32 | 28 | def main(pytestargs, markers=None): | 33 | 29 | sys.path.insert(1, IPYTHON_ROOT) | 34 | 30 | sys.path.insert(1, TESTING_TOOLS_ROOT) | | 31 | + sys.path.insert(1, DEBUG_ADAPTER_ROOT) | 35 | 32 |
| 36 | | - pytestargs = [ | 37 | | - '--rootdir', SRC_ROOT, | 38 | | - TEST_ROOT, | 39 | | - ] + pytestargs | | 33 | + pytestargs = ["--rootdir", SRC_ROOT, TEST_ROOT] + pytestargs | 40 | 34 | for marker in reversed(markers or ()): | 41 | 35 | pytestargs.insert(0, marker) | 42 | | - pytestargs.insert(0, '-m') | | 36 | + pytestargs.insert(0, "-m") | 43 | 37 |
| 44 | 38 | ec = pytest.main(pytestargs) | 45 | 39 | return ec | 46 | 40 |
| 47 | 41 |
| 48 | | -if __name__ == '__main__': | | 42 | +if __name__ == "__main__": | 49 | 43 | mainkwargs, pytestargs = parse_args() | 50 | 44 | ec = main(pytestargs, **mainkwargs) | 51 | 45 | sys.exit(ec) |
Collapse file Copy file name to clipboard +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +# Copyright (c) Microsoft Corporation. All rights reserved. | | 2 | +# Licensed under the MIT License. |
Collapse file Copy file name to clipboard +81Lines changed: 81 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +# Copyright (c) Microsoft Corporation. All rights reserved. | | 2 | +# Licensed under the MIT License. | | 3 | +import sys | | 4 | + | | 5 | +if sys.version_info[:2] != (3, 7): | | 6 | + import unittest | | 7 | + | | 8 | + raise unittest.SkipTest("PTVSD wheels shipped for Python 3.7 only") | | 9 | + | | 10 | +import os.path | | 11 | +import pytest | | 12 | +import re | | 13 | + | | 14 | +from unittest.mock import patch, mock_open | | 15 | +from packaging.tags import sys_tags | | 16 | +from ptvsd_folder_name import ptvsd_folder_name | | 17 | + | | 18 | +from .. import PYTHONFILES, REQUIREMENTS | | 19 | + | | 20 | + | | 21 | +def open_requirements_with_ptvsd(): | | 22 | + return patch( | | 23 | + "ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\nptvsd==5.0.0") | | 24 | + ) | | 25 | + | | 26 | + | | 27 | +def open_requirements_without_ptvsd(): | | 28 | + return patch("ptvsd_folder_name.open", mock_open(read_data="jedi==0.15.1\n")) | | 29 | + | | 30 | + | | 31 | +class TestPtvsdFolderName: | | 32 | + """Unit tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" | | 33 | + | | 34 | + def test_requirement_exists_folder_exists(self, capsys): | | 35 | + # Return the first constructed folder path as existing. | | 36 | + | | 37 | + patcher = patch("os.path.exists") | | 38 | + mock_exists = patcher.start() | | 39 | + mock_exists.side_effect = lambda p: True | | 40 | + tag = next(sys_tags()) | | 41 | + folder = "ptvsd-5.0.0-{}-{}-{}".format(tag.interpreter, tag.abi, tag.platform) | | 42 | + | | 43 | + with open_requirements_with_ptvsd(): | | 44 | + ptvsd_folder_name() | | 45 | + | | 46 | + patcher.stop() | | 47 | + expected = os.path.join(PYTHONFILES, folder) | | 48 | + captured = capsys.readouterr() | | 49 | + assert captured.out == expected | | 50 | + | | 51 | + def test_ptvsd_requirement_once(self): | | 52 | + reqs = [ | | 53 | + line | | 54 | + for line in open(REQUIREMENTS, "r", encoding="utf-8") | | 55 | + if re.match("ptvsd==", line) | | 56 | + ] | | 57 | + assert len(reqs) == 1 | | 58 | + | | 59 | + def test_no_ptvsd_requirement(self, capsys): | | 60 | + with open_requirements_without_ptvsd() as p: | | 61 | + ptvsd_folder_name() | | 62 | + | | 63 | + expected = PYTHONFILES | | 64 | + captured = capsys.readouterr() | | 65 | + assert captured.out == expected | | 66 | + | | 67 | + def test_no_wheel_folder(self, capsys): | | 68 | + # Return none of of the constructed paths as existing, | | 69 | + # ptvsd_folder_name() should return the path to default ptvsd. | | 70 | + patcher = patch("os.path.exists") | | 71 | + mock_no_exist = patcher.start() | | 72 | + mock_no_exist.side_effect = lambda p: False | | 73 | + | | 74 | + with open_requirements_with_ptvsd() as p: | | 75 | + ptvsd_folder_name() | | 76 | + | | 77 | + patcher.stop() | | 78 | + expected = PYTHONFILES | | 79 | + captured = capsys.readouterr() | | 80 | + assert captured.out == expected | | 81 | + |
Collapse file Copy file name to clipboard +59Lines changed: 59 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +# Copyright (c) Microsoft Corporation. All rights reserved. | | 2 | +# Licensed under the MIT License. | | 3 | + | | 4 | +import sys | | 5 | + | | 6 | +if sys.version_info[:2] != (3, 7): | | 7 | + import unittest | | 8 | + | | 9 | + raise unittest.SkipTest("PTVSD wheels shipped for Python 3.7 only") | | 10 | + | | 11 | +import os.path | | 12 | +import pytest | | 13 | +import subprocess | | 14 | + | | 15 | +from packaging.requirements import Requirement | | 16 | +from .. import PYTHONFILES, REQUIREMENTS, SRC_ROOT | | 17 | + | | 18 | +ARGV = ["python", os.path.join(SRC_ROOT, "ptvsd_folder_name.py")] | | 19 | +PREFIX = "ptvsd==" | | 20 | + | | 21 | +with open(REQUIREMENTS, "r", encoding="utf-8") as reqsfile: | | 22 | + for line in reqsfile: | | 23 | + if line.startswith(PREFIX): | | 24 | + VERSION = line[len(PREFIX) :].strip() | | 25 | + break | | 26 | + | | 27 | + | | 28 | +def ptvsd_paths(*platforms): | | 29 | + paths = set() | | 30 | + for platform in platforms: | | 31 | + folder = "ptvsd-{}-cp37-cp37m-{}".format(VERSION, platform) | | 32 | + paths.add(os.path.join(PYTHONFILES, folder)) | | 33 | + return paths | | 34 | + | | 35 | + | | 36 | +@pytest.mark.functional | | 37 | +class TestPtvsdFolderNameFunctional: | | 38 | + """Functional tests for the script retrieving the PTVSD folder name for the PTVSD wheels experiment.""" | | 39 | + | | 40 | + def test_ptvsd_folder_name_nofail(self): | | 41 | + output = subprocess.check_output(ARGV, universal_newlines=True) | | 42 | + assert output != PYTHONFILES | | 43 | + | | 44 | + @pytest.mark.skipif(sys.platform != "darwin", reason="macOS functional test") | | 45 | + def test_ptvsd_folder_name_macos(self): | | 46 | + output = subprocess.check_output(ARGV, universal_newlines=True) | | 47 | + assert output in ptvsd_paths("macosx_10_13_x86_64") | | 48 | + | | 49 | + @pytest.mark.skipif(sys.platform != "win32", reason="Windows functional test") | | 50 | + def test_ptvsd_folder_name_windows(self): | | 51 | + output = subprocess.check_output(ARGV, universal_newlines=True) | | 52 | + assert output in ptvsd_paths("win32", "win_amd64") | | 53 | + | | 54 | + @pytest.mark.skipif(sys.platform != "linux", reason="Linux functional test") | | 55 | + def test_ptvsd_folder_name_linux(self): | | 56 | + output = subprocess.check_output(ARGV, universal_newlines=True) | | 57 | + assert output in ptvsd_paths( | | 58 | + "manylinux1_i686", "manylinux1_x86_64", "manylinux2010_x86_64" | | 59 | + ) |
|