lsp.py: Port to support running on Windows & adapt to changes due to prior merged PR.

- lsp.py: Fixes invalid-syntax by Python interpreter on Windows CI (older Python version).
- lsp.py: Savely strip CRLF from right side of the string, ignoring accidental multiple occurrences of \r (such as \r\r\n).
- lsp.py: Fixes reading single character from stdin (wrt. Windows platform).
- lsp.py: Adds header line reading to I/O tracing (useful for debugging).
- lsp.py: When running the tests on Windows, don't care test file content's newlines but simply expect LFs (instead of CRLF for example).
- Apply pylint notes.
- Fixing use of @functools.lru_cache for older python versions (CircleCI Windows)
This commit is contained in:
Christian Parpart 2022-04-25 17:21:45 +02:00
parent c2f245b40a
commit e8d07772d9
2 changed files with 53 additions and 20 deletions

View File

@ -1283,6 +1283,9 @@ jobs:
- run: - run:
name: Install LSP test dependencies name: Install LSP test dependencies
command: python -m pip install --user deepdiff colorama command: python -m pip install --user deepdiff colorama
- run:
name: Inspect lsp.py
command: Get-Content ./test/lsp.py
- run: - run:
name: Executing solc LSP test suite name: Executing solc LSP test suite
command: python ./test/lsp.py .\build\solc\Release\solc.exe command: python ./test/lsp.py .\build\solc\Release\solc.exe

View File

@ -1,26 +1,57 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# pragma pylint: disable=too-many-lines # pragma pylint: disable=too-many-lines
# test line 1
import argparse import argparse
import fnmatch import fnmatch
import functools
import json import json
import os import os
import re
import subprocess import subprocess
import sys import sys
import traceback import traceback
import re
import tty
import functools
from collections import namedtuple from collections import namedtuple
from copy import deepcopy from copy import deepcopy
from enum import Enum, auto
from itertools import islice
from pathlib import PurePath from pathlib import PurePath
from typing import Any, List, Optional, Tuple, Union from typing import Any, List, Optional, Tuple, Union
from itertools import islice
from enum import Enum, auto
import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows. import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows.
from deepdiff import DeepDiff from deepdiff import DeepDiff
if os.name == 'nt':
# pragma pylint: disable=import-error
import msvcrt
else:
import tty
# Turn off user input buffering so we get the input immediately,
# not only after a line break
tty.setcbreak(sys.stdin.fileno())
def escape_string(text: str) -> str:
"""
Trivially escapes given input string's \r \n and \\.
"""
return text.translate(str.maketrans({
"\r": r"\r",
"\n": r"\n",
"\\": r"\\"
}))
def getCharFromStdin():
"""
Gets a single character from stdin without line-buffering.
"""
if os.name == 'nt':
# pragma pylint: disable=import-error
return msvcrt.getch().decode("utf-8")
else:
return sys.stdin.buffer.read(1)
""" """
Named tuple that holds various regexes used to parse the test specification. Named tuple that holds various regexes used to parse the test specification.
""" """
@ -101,7 +132,6 @@ class BadHeader(Exception):
def __init__(self, msg: str): def __init__(self, msg: str):
super().__init__("Bad header: " + msg) super().__init__("Bad header: " + msg)
class JsonRpcProcess: class JsonRpcProcess:
exe_path: str exe_path: str
exe_args: List[str] exe_args: List[str]
@ -144,10 +174,12 @@ class JsonRpcProcess:
# server quit # server quit
return None return None
line = line.decode("utf-8") line = line.decode("utf-8")
if self.trace_io:
print(f"Received header-line: {escape_string(line)}")
if not line.endswith("\r\n"): if not line.endswith("\r\n"):
raise BadHeader("missing newline") raise BadHeader("missing newline")
# remove the "\r\n" # Safely remove the "\r\n".
line = line[:-2] line = line.rstrip("\r\n")
if line == '': if line == '':
break # done with the headers break # done with the headers
if line.startswith(CONTENT_LENGTH_HEADER): if line.startswith(CONTENT_LENGTH_HEADER):
@ -589,7 +621,7 @@ class FileTestRunner:
while True: while True:
print("(u)pdate/(r)etry/(i)gnore?") print("(u)pdate/(r)etry/(i)gnore?")
user_response = sys.stdin.read(1) user_response = getCharFromStdin()
if user_response == "i": if user_response == "i":
return self.TestResult.SuccessOrIgnored return self.TestResult.SuccessOrIgnored
@ -787,7 +819,7 @@ class SolidityLSPTestSuite: # {{{
in the test path (test/libsolidity/lsp). in the test path (test/libsolidity/lsp).
""" """
with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f: with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f:
return f.read() return f.read().replace("\r\n", "\n")
def require_params_for_method(self, method_name: str, message: dict) -> Any: def require_params_for_method(self, method_name: str, message: dict) -> Any:
""" """
@ -1059,7 +1091,7 @@ class SolidityLSPTestSuite: # {{{
""" """
while True: while True:
print("(u)pdate/(r)etry/(s)kip file?") print("(u)pdate/(r)etry/(s)kip file?")
user_response = sys.stdin.read(1) user_response = getCharFromStdin()
if user_response == "u": if user_response == "u":
while True: while True:
try: try:
@ -1068,8 +1100,8 @@ class SolidityLSPTestSuite: # {{{
# pragma pylint: disable=broad-except # pragma pylint: disable=broad-except
except Exception as e: except Exception as e:
print(e) print(e)
if ret := self.user_interaction_failed_autoupdate(test): if self.user_interaction_failed_autoupdate(test):
return ret return True
elif user_response == 's': elif user_response == 's':
return True return True
elif user_response == 'r': elif user_response == 'r':
@ -1077,7 +1109,7 @@ class SolidityLSPTestSuite: # {{{
def user_interaction_failed_autoupdate(self, test): def user_interaction_failed_autoupdate(self, test):
print("(e)dit/(r)etry/(s)kip file?") print("(e)dit/(r)etry/(s)kip file?")
user_response = sys.stdin.read(1) user_response = getCharFromStdin()
if user_response == "r": if user_response == "r":
print("retrying...") print("retrying...")
# pragma pylint: disable=no-member # pragma pylint: disable=no-member
@ -1142,7 +1174,7 @@ class SolidityLSPTestSuite: # {{{
marker = self.get_file_tags("lib")["@diagnostics"] marker = self.get_file_tags("lib")["@diagnostics"]
self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker)
@functools.lru_cache # pragma pylint: disable=lru-cache-decorating-method @functools.lru_cache() # pragma pylint: disable=lru-cache-decorating-method
def get_file_tags(self, test_name: str, verbose=False): def get_file_tags(self, test_name: str, verbose=False):
""" """
Finds all tags (e.g. @tagname) in the given test and returns them as a Finds all tags (e.g. @tagname) in the given test and returns them as a
@ -1653,10 +1685,8 @@ class SolidityLSPTestSuite: # {{{
# }}} # }}}
# }}} # }}}
if __name__ == "__main__": if __name__ == "__main__":
# Turn off user input buffering so we get the input immediately,
# not only after a line break
tty.setcbreak(sys.stdin.fileno())
suite = SolidityLSPTestSuite() suite = SolidityLSPTestSuite()
exit_code = suite.main() exit_code = suite.main()
sys.exit(exit_code) sys.exit(exit_code)