solidity/scripts/isolate_tests.py

166 lines
5.5 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
2016-09-30 11:09:45 +00:00
#
# This script reads C++ or RST source files and writes all
2016-09-30 11:09:45 +00:00
# multi-line strings into individual files.
# This can be used to extract the Solidity test cases
2016-10-10 20:04:11 +00:00
# into files for e.g. fuzz testing as
# scripts/isolate_tests.py test/libsolidity/*
2016-09-30 11:09:45 +00:00
import re
import os
import hashlib
2021-07-05 17:38:41 +00:00
from os.path import join, isfile, basename
from argparse import ArgumentParser
2021-07-05 17:38:41 +00:00
from textwrap import indent, dedent
def extract_test_cases(path):
2021-06-30 08:21:41 +00:00
with open(path, encoding="utf8", errors='ignore', mode='r', newline='') as file:
lines = file.read().splitlines()
inside = False
delimiter = ''
tests = []
for l in lines:
if inside:
if l.strip().endswith(')' + delimiter + '";'):
inside = False
else:
tests[-1] += l + '\n'
else:
m = re.search(r'R"([^(]*)\($', l.strip())
if m:
inside = True
delimiter = m.group(1)
tests += ['']
return tests
2021-07-05 17:38:41 +00:00
def extract_solidity_docs_cases(path):
tests = extract_docs_cases(path, [".. code-block:: solidity", '::'])
codeStart = "(// SPDX-License-Identifier:|pragma solidity|contract.*{|library.*{|interface.*{)"
# Filter out tests that are not supposed to be compilable.
return [
test.lstrip("\n")
for test in tests
if re.search(r'^\s{4}' + codeStart, test, re.MULTILINE) is not None
]
def extract_yul_docs_cases(path):
tests = extract_docs_cases(path, [".. code-block:: yul"])
def wrap_in_object(code):
for line in code.splitlines():
line = line.lstrip()
if line.startswith("//"):
continue
if not line.startswith("object") and not line.startswith("{"):
return indent(f"{{\n{code.rstrip()}\n}}\n\n", " ")
2021-07-05 17:38:41 +00:00
break
return code
return [
wrap_in_object(test)
for test in tests
if test.strip() != ""
]
# Extract code examples based on the 'beginMarker' parameter
# up until we reach EOF or a line that is not empty and doesn't start with 4
# spaces.
2021-07-05 17:38:41 +00:00
def extract_docs_cases(path, beginMarkers):
immediatelyAfterMarker = False
insideBlock = False
tests = []
# Collect all snippets of indented blocks
2021-06-30 08:21:41 +00:00
with open(path, mode='r', errors='ignore', encoding='utf8', newline='') as f:
lines = f.read().splitlines()
for line in lines:
if insideBlock:
if immediatelyAfterMarker:
# Skip Sphinx instructions and empty lines between them
if line == '' or line.lstrip().startswith(":"):
continue
if line == '' or line.startswith(" "):
tests[-1] += line + "\n"
immediatelyAfterMarker = False
2021-07-05 17:38:41 +00:00
continue
insideBlock = False
if any(map(line.lower().startswith, beginMarkers)):
insideBlock = True
immediatelyAfterMarker = True
tests += ['']
2019-10-23 20:13:17 +00:00
2021-07-05 17:38:41 +00:00
return tests
2021-07-05 17:38:41 +00:00
def write_cases(f, solidityTests, yulTests):
cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower()
2021-07-05 17:38:41 +00:00
for language, test in [("sol", t) for t in solidityTests] + [("yul", t) for t in yulTests]:
2021-03-10 19:40:18 +00:00
# When code examples are extracted they are indented by 8 spaces, which violates the style guide,
# so before checking remove 4 spaces from each line.
2021-07-05 17:38:41 +00:00
remainder = dedent(test)
source_code_hash = hashlib.sha256(test.encode("utf-8")).hexdigest()
sol_filename = f'test_{source_code_hash}_{cleaned_filename}.{language}'
2021-06-30 08:21:41 +00:00
with open(sol_filename, mode='w', encoding='utf8', newline='') as fi:
fi.write(remainder)
2021-07-05 17:38:41 +00:00
def extract_and_write(path, language):
assert language in ["solidity", "yul", ""]
yulCases = []
cases = []
if path.lower().endswith('.rst'):
2021-07-05 17:38:41 +00:00
if language in ("solidity", ""):
cases = extract_solidity_docs_cases(path)
if language in ("yul", ""):
yulCases = extract_yul_docs_cases(path)
elif path.endswith('.sol'):
2021-07-05 17:38:41 +00:00
if language in ("solidity", ""):
with open(path, mode='r', encoding='utf8', newline='') as f:
cases = [f.read()]
else:
cases = extract_test_cases(path)
2021-07-05 17:38:41 +00:00
write_cases(basename(path), cases, yulCases)
if __name__ == '__main__':
script_description = (
"Reads Solidity, C++ or RST source files and extracts compilable solidity and yul code blocks from them. "
"Can be used to generate test cases to validate code examples. "
)
parser = ArgumentParser(description=script_description)
parser.add_argument(dest='path', help='Path to file or directory to look for code in.')
2021-07-05 17:38:41 +00:00
parser.add_argument(
'-l', '--language',
dest='language',
choices=["yul", "solidity"],
default="",
action='store',
help="Extract only code blocks in the given language"
)
options = parser.parse_args()
path = options.path
if isfile(path):
2021-07-05 17:38:41 +00:00
extract_and_write(path, options.language)
else:
for root, subdirs, files in os.walk(path):
if '_build' in subdirs:
subdirs.remove('_build')
if 'compilationTests' in subdirs:
subdirs.remove('compilationTests')
for f in files:
if basename(f) == "invalid_utf8_sequence.sol":
continue # ignore the test with broken utf-8 encoding
path = join(root, f)
2021-07-05 17:38:41 +00:00
extract_and_write(path, options.language)