mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			166 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/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)
 | 
						|
        hash = hashlib.sha256(test.encode("utf-8")).hexdigest()
 | 
						|
        sol_filename = f'test_{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)
 |