forked from ethereum/solidity
-
Notifications
You must be signed in to change notification settings - Fork 0
/
isolate_tests.py
executable file
·165 lines (139 loc) · 5.5 KB
/
isolate_tests.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python3
#
# This script reads C++ or RST source files and writes all
# multi-line strings into individual files.
# This can be used to extract the Solidity test cases
# into files for e.g. fuzz testing as
# scripts/isolate_tests.py test/libsolidity/*
import re
import os
import hashlib
from os.path import join, isfile, basename
from argparse import ArgumentParser
from textwrap import indent, dedent
def extract_test_cases(path):
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
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", " ")
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.
def extract_docs_cases(path, beginMarkers):
immediatelyAfterMarker = False
insideBlock = False
tests = []
# Collect all snippets of indented blocks
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
continue
insideBlock = False
if any(map(line.lower().startswith, beginMarkers)):
insideBlock = True
immediatelyAfterMarker = True
tests += ['']
return tests
def write_cases(f, solidityTests, yulTests):
cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower()
for language, test in [("sol", t) for t in solidityTests] + [("yul", t) for t in yulTests]:
# 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.
remainder = dedent(test)
source_code_hash = hashlib.sha256(test.encode("utf-8")).hexdigest()
sol_filename = f'test_{source_code_hash}_{cleaned_filename}.{language}'
with open(sol_filename, mode='w', encoding='utf8', newline='') as fi:
fi.write(remainder)
def extract_and_write(path, language):
assert language in ["solidity", "yul", ""]
yulCases = []
cases = []
if path.lower().endswith('.rst'):
if language in ("solidity", ""):
cases = extract_solidity_docs_cases(path)
if language in ("yul", ""):
yulCases = extract_yul_docs_cases(path)
elif path.endswith('.sol'):
if language in ("solidity", ""):
with open(path, mode='r', encoding='utf8', newline='') as f:
cases = [f.read()]
else:
cases = extract_test_cases(path)
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.')
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):
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)
extract_and_write(path, options.language)