From 618a107e11cbb0d69add4ac73f27a8a0774ff99b Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 25 Jun 2018 13:20:22 -0700 Subject: [PATCH] Compatibility python 3.4 (#519) * Test compat fixes for python 3.4 tests * Enable python 3.4 CI in travis * Fix for 3.4 missing text in traceback list * Fix linter * Add python 3.4 to classifiers * Fix for line text --- .travis.yml | 1 + ptvsd/_util.py | 12 +++ ptvsd/wrapper.py | 25 ++++--- setup.py | 1 + .../debugger_protocol/schema/test___main__.py | 32 +++++++- tests/system_tests/test_schema.py | 75 +++++++++++++------ 6 files changed, 111 insertions(+), 35 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2c29c99..2a8663cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: python cache: pip python: - "2.7" + - "3.4" - "3.5-dev" # 3.6 is tested via coverage - "3.7-dev" diff --git a/ptvsd/_util.py b/ptvsd/_util.py index 069d90d0..25493af4 100644 --- a/ptvsd/_util.py +++ b/ptvsd/_util.py @@ -337,3 +337,15 @@ class Startable(object): def _stop(self): raise NotImplementedError + + +def is_py34(): + return sys.version_info >= (3, 4,) and sys.version_info < (3, 5,) + + +def get_line_for_traceback(file_path, line_no): + try: + with open(file_path, 'r') as f: + return f.readlines()[line_no - 1] + except Exception: + return None diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py index 6fce36ac..f23e218f 100644 --- a/ptvsd/wrapper.py +++ b/ptvsd/wrapper.py @@ -2234,15 +2234,22 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor): xml = self.parse_xml_response(resp_args) text = unquote(xml.var[1]['type']) description = unquote(xml.var[1]['value']) - frame_data = (( - unquote(f['file']), - int(f['line']), - unquote(f['name']), - None) - for f in xframes - if not self.internals_filter.is_internal_path( - unquote(f['file'])) - ) + frame_data = [] + for f in xframes: + file_path = unquote(f['file']) + if not self.internals_filter.is_internal_path(file_path): + line_no = int(f['line']) + func_name = unquote(f['name']) + if _util.is_py34(): + # NOTE: In 3.4.* format_list requires the text + # to be passed in the tuple list. + line_text = _util.get_line_for_traceback(file_path, + line_no) + frame_data.append((file_path, line_no, + func_name, line_text)) + else: + frame_data.append((file_path, line_no, + func_name, None)) stack = ''.join(traceback.format_list(frame_data)) source = unquote(xframe['file']) if self.internals_filter.is_internal_path(source): diff --git a/setup.py b/setup.py index 0c0042de..05684f8f 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ if __name__ == '__main__': classifiers=[ 'Development Status :: 3 - Alpha', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', diff --git a/tests/debugger_protocol/schema/test___main__.py b/tests/debugger_protocol/schema/test___main__.py index 7ff5bb4c..e5694484 100644 --- a/tests/debugger_protocol/schema/test___main__.py +++ b/tests/debugger_protocol/schema/test___main__.py @@ -1,5 +1,6 @@ import contextlib import io +import sys from textwrap import dedent import unittest @@ -34,6 +35,23 @@ class CommandRegistryTests(unittest.TestCase): }) +class internal_redirect_stderr: + """Context manager for temporarily redirecting stderr to another file + """ + + def __init__(self, new_target): + self._new_target = new_target + self._old_targets = [] + + def __enter__(self): + self._old_targets.append(sys.stderr) + sys.stderr = self._new_target + return self._new_target + + def __exit__(self, exctype, excinst, exctb): + sys.stderr = self._old_targets.pop() + + class HandleDownloadTests(unittest.TestCase): def test_default_args(self): @@ -45,8 +63,13 @@ class HandleDownloadTests(unittest.TestCase): opener = StubOpener(schemafile, outfile, buf, metafile) stdout = io.StringIO() + try: + redirect_stderr = contextlib.redirect_stderr + except AttributeError: + redirect_stderr = internal_redirect_stderr + with contextlib.redirect_stdout(stdout): - with contextlib.redirect_stderr(stdout): + with redirect_stderr(stdout): handle_download( _open=opener.open, _open_url=opener.open) metadata = '\n'.join(line @@ -86,8 +109,13 @@ class HandleCheckTests(unittest.TestCase): ) stdout = io.StringIO() + try: + redirect_stderr = contextlib.redirect_stderr + except AttributeError: + redirect_stderr = internal_redirect_stderr + with contextlib.redirect_stdout(stdout): - with contextlib.redirect_stderr(stdout): + with redirect_stderr(stdout): handle_check( _open=opener.open, _open_url=opener.open) diff --git a/tests/system_tests/test_schema.py b/tests/system_tests/test_schema.py index a645fb1a..a11d0f4a 100644 --- a/tests/system_tests/test_schema.py +++ b/tests/system_tests/test_schema.py @@ -13,6 +13,33 @@ from tests.helpers import http from debugger_protocol.schema.__main__ import handle_check +class InternalSubprocessResult: + def __init__(self): + self.stdout = b'' + self.stderr = b'' + self.returncode = 0 + + +def _run_subprocess(args, stdout, stderr): + if hasattr(subprocess, 'run'): + res = subprocess.run(args, + stdout=stdout, + stderr=stderr) + return res + else: + with tempfile.TemporaryFile() as tempf: + res = InternalSubprocessResult() + try: + res.stdout = subprocess.check_output(args, stderr=tempf) + except subprocess.CalledProcessError as err: + res.returncode = err.returncode + res.stdout = err.output + tempf.flush() + tempf.seek(0) + res.stderr = tempf.read() + return res + + class VendoredSchemaTests(unittest.TestCase): """Tests to make sure our vendored schema is up-to-date.""" @@ -57,9 +84,9 @@ class DownloadCommandTests(unittest.TestCase): @unittest.skipUnless(os.environ.get('HAS_NETWORK'), 'no network') def test_default_source(self): - res = subprocess.run(self.args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(self.args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) self.assertEqual(res.returncode, 0) self.assertEqual(res.stdout.decode(), self.get_expected_stdout([ @@ -73,9 +100,9 @@ class DownloadCommandTests(unittest.TestCase): handler = http.json_file_handler(b'') with http.Server(handler) as srv: upstream = 'http://{}/schema.json'.format(srv.address) - res = subprocess.run(self.args + ['--source', upstream], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(self.args + ['--source', upstream], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else '' @@ -155,9 +182,9 @@ class CheckCommandTests(unittest.TestCase): '--schemafile', schemafile, '--upstream', upstream, ] - res = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else '' @@ -181,9 +208,9 @@ class CheckCommandTests(unittest.TestCase): '--schemafile', schemafile, '--upstream', '', ] - res = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else '' @@ -199,9 +226,9 @@ class CheckCommandTests(unittest.TestCase): '--schemafile', schemafile, '--upstream', '', ] - res = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else '' @@ -223,9 +250,9 @@ class CheckCommandTests(unittest.TestCase): '--schemafile', schemafile, '--upstream', '', ] - res = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else '' @@ -250,9 +277,9 @@ class CheckCommandTests(unittest.TestCase): '--schemafile', schemafile, '--upstream', upstream, ] - res = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else '' @@ -278,9 +305,9 @@ class CheckCommandTests(unittest.TestCase): '--schemafile', schemafile, '--upstream', upstream, ] - res = subprocess.run(args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + res = _run_subprocess(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) stdout = res.stdout.decode() if res.stdout else '' stderr = res.stderr.decode() if res.stderr else ''