Skip to content

Commit

Permalink
Add bytecode test for multiple sources compiler at same time
Browse files Browse the repository at this point in the history
  • Loading branch information
r0qs committed Sep 18, 2023
1 parent 0f07e2c commit b7abadf
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 16 deletions.
28 changes: 12 additions & 16 deletions scripts/splitSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
import os
import traceback

hasMultipleSources = False
createdSources = []


def uncaught_exception_hook(exc_type, exc_value, exc_traceback):
# The script `scripts/ASTImportTest.sh` will interpret return code 3
Expand All @@ -36,7 +33,7 @@ def extractSourceName(line):

# expects the first line of lines to be "==== Source: sourceName ===="
# writes the following source into a file named sourceName
def writeSourceToFile(lines):
def writeSourceToFile(lines, createdSources = []):
filePath, srcName = extractSourceName(lines[0])
# print("sourceName is ", srcName)
# print("filePath is", filePath)
Expand All @@ -51,33 +48,32 @@ def writeSourceToFile(lines):

# recursive call if there is another source
else:
writeSourceToFile(lines[1+idx:])
writeSourceToFile(lines[1+idx:], createdSources)
break
return createdSources


if __name__ == '__main__':
filePath = sys.argv[1]
def split_sources(filePath):
sys.excepthook = uncaught_exception_hook

try:
# decide if file has multiple sources
with open(filePath, mode='r', encoding='utf8', newline='') as f:
lines = f.read().splitlines()
if len(lines) >= 1 and lines[0][:12] == "==== Source:":
hasMultipleSources = True
writeSourceToFile(lines)

if hasMultipleSources:
srcString = ""
createdSources = writeSourceToFile(lines)
for src in createdSources:
srcString += src + ' '
print(srcString)
sys.exit(0)
else:
sys.exit(1)
return 0
return 1

except UnicodeDecodeError as ude:
print("UnicodeDecodeError in '" + filePath + "': " + str(ude))
print("This is expected for some tests containing invalid utf8 sequences. "
"Exception will be ignored.")
sys.exit(2)
return 2


if __name__ == '__main__':
sys.exit(split_sources(sys.argv[1]))
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
==== Source: A.sol ====
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.0;

import "D.sol";
import "B.sol";

contract A is B {
function a() public pure {
f();
}
}

==== Source: B.sol ====
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.0;

import "C.sol";

abstract contract B is C {}

==== Source: C.sol ====
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.0;

abstract contract C
{
function c() public pure returns (uint) {
return 0;
}
}

==== Source: D.sol ====
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.0;

import "F.sol";

==== Source: F.sol ====
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.0;

function f() pure returns (bytes memory returndata) {
return "";
}
86 changes: 86 additions & 0 deletions test/cmdlineTests/~bytecode_equivalence_multiple_sources/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3

import os
import re
import sys
import subprocess
from shutil import which
from tempfile import mkdtemp
from pathlib import Path

PROJECT_ROOT = Path(__file__).parents[3]
CMDLINE_TEST_DIR = f"{PROJECT_ROOT}/test/cmdlineTests"

sys.path.insert(0, f"{PROJECT_ROOT}/scripts")
from bytecodecompare.prepare_report import FileReport
from bytecodecompare.prepare_report import parse_cli_output
from splitSources import split_sources


class FileMismatchError(Exception):
def __str__(self):
return f"Git diff mismatch:\n {self.args[0]}"


# diff will return no output if there is no difference between the two inputs,
# or throw CalledProcessError otherwise, i.e. if the process exits with a non-zero exit code.
def diff(a: str, b: str):
try:
if which("git") is None:
raise RuntimeError("git not found.")
subprocess.check_output(
f"git diff --color --word-diff=plain --word-diff-regex=. --ignore-space-change --ignore-blank-lines <(echo \"{a}\") <(echo \"{b}\")",
shell=True,
universal_newlines=True
)
except subprocess.CalledProcessError as e:
raise FileMismatchError(e.output)


def run_solc(report_file_path: str, solc_binary_path: str, args=[]):
output = subprocess.check_output(
[solc_binary_path, *args],
shell=False,
encoding="utf8"
)
return parse_cli_output(Path(report_file_path), output)


def filter_bytecode_report(contract: str, reports: FileReport):
for report in reports.contract_reports:
if report.contract_name == contract:
return report.bytecode if report.bytecode is not None else '<NO BYTECODE>'


def test_bytecode_equivalence(tmp_dir: str):
os.chdir(tmp_dir)

source_file_name = f"{CMDLINE_TEST_DIR}/~bytecode_equivalence_multiple_sources/inputs.sol"
split_sources(source_file_name)

solc_binary = os.environ.get("SOLC")
if solc_binary is None:
raise RuntimeError("solc compiler not found.")

# Compiling multiple files at same time should not affect bytecode generation although it changes AST IDs.
a_bin_output = run_solc(f"{tmp_dir}/A.report", solc_binary, ["--via-ir", "--bin", "A.sol", "B.sol"])
ab_bin_output = run_solc(f"{tmp_dir}/AB.report", solc_binary, ["--via-ir", "--bin", "A.sol"])

diff(
filter_bytecode_report("A", a_bin_output),
filter_bytecode_report("A", ab_bin_output)
)


def main():
try:
tmp_dir = mkdtemp(prefix=f"cmdline-test-bytecode-equivalence-multiple-sources")
test_bytecode_equivalence(tmp_dir)
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1


if __name__ == "__main__":
sys.exit(main())

0 comments on commit b7abadf

Please sign in to comment.