diff --git a/scripts/AntlrBatchTestRig.java b/scripts/AntlrBatchTestRig.java new file mode 100644 index 000000000000..91a924c33563 --- /dev/null +++ b/scripts/AntlrBatchTestRig.java @@ -0,0 +1,104 @@ +import org.antlr.v4.runtime.*; +import java.io.*; +import java.nio.file.*; + +public class AntlrBatchTestRig { + public static void main(String[] args) throws Exception { + if (args.length < 1) { + System.err.println("Usage: AntlrBatchTestRig [file2] ..."); + System.exit(1); + } + + int exitCode = 0; + for (String filePath: args) { + try { + if (!testFile(filePath)) + exitCode = 1; + } catch (Exception e) { + System.err.println("ERROR:" + filePath + ":" + e.getMessage()); + exitCode = 1; + } + } + System.exit(exitCode); + } + + private static boolean testFile(String filePath) throws Exception { + // Detect mode based on file extension + String mode = filePath.endsWith(".yul") ? "yul" : "sol"; + String content = new String(Files.readAllBytes(Paths.get(filePath))); + + // Check if file expects parser error + boolean expectsError = content.contains("// ParserError"); + + CharStream input; + if (mode.equals("sol")) { + // Remove ExternalSource lines for Solidity files + content = content.replaceAll("(?m)^==== ExternalSource:.*$", ""); + input = CharStreams.fromString(content); + } else { + // Wrap Yul in assembly statement + input = CharStreams.fromString("assembly " + content); + } + + SolidityLexer lexer = new SolidityLexer(input); + CommonTokenStream tokens = new CommonTokenStream(lexer); + SolidityParser parser = new SolidityParser(tokens); + + // Remove default error listeners and add custom one + parser.removeErrorListeners(); + lexer.removeErrorListeners(); + + ErrorCollector errorCollector = new ErrorCollector(); + parser.addErrorListener(errorCollector); + lexer.addErrorListener(errorCollector); + + // Parse based on mode + if (mode.equals("sol")) { + parser.sourceUnit(); + } else { + parser.assemblyStatement(); + } + + boolean hasErrors = errorCollector.hasErrors(); + + // Output result + if (expectsError) { + if (hasErrors) { + System.out.println("PASS:" + filePath + ":FAILED_AS_EXPECTED"); + } else { + System.out.println("FAIL:" + filePath + ":SUCCEEDED_DESPITE_PARSER_ERROR"); + return false; + } + } else { + if (!hasErrors) { + System.out.println("PASS:" + filePath + ":OK"); + } else { + System.out.println("FAIL:" + filePath + ":" + errorCollector.getErrors()); + return false; + } + } + return true; + } + + static class ErrorCollector extends BaseErrorListener { + private StringBuilder errors = new StringBuilder(); + private boolean hasErrors = false; + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, + int line, int charPositionInLine, + String msg, RecognitionException e) { + hasErrors = true; + errors.append("line ").append(line).append(":").append(charPositionInLine) + .append(" ").append(msg).append("\n"); + } + + public boolean hasErrors() { + return hasErrors; + } + + public String getErrors() { + return errors.toString(); + } + } +} diff --git a/scripts/test_antlr_grammar.sh b/scripts/test_antlr_grammar.sh index 02e9a634f9fd..c8e008c06d4b 100755 --- a/scripts/test_antlr_grammar.sh +++ b/scripts/test_antlr_grammar.sh @@ -10,6 +10,7 @@ ROOT_DIR=$(${READLINK} -f "$(dirname "$0")"/..) WORKDIR="${ROOT_DIR}/build/antlr" ANTLR_JAR="${ROOT_DIR}/build/deps/antlr4.jar" ANTLR_JAR_URI="https://www.antlr.org/download/antlr-4.8-complete.jar" +BATCH_SIZE=100 # Process 100 files per JVM invocation SGR_RESET="\033[0m" SGR_BOLD="\033[1m" @@ -17,9 +18,6 @@ SGR_GREEN="\033[32m" SGR_RED="\033[31m" SGR_BLUE="\033[34m" -vt_cursor_up() { echo -ne "\033[A"; } -vt_cursor_begin_of_line() { echo -ne "\r"; } - function download_antlr4 { if [[ ! -e "$ANTLR_JAR" ]] @@ -47,61 +45,10 @@ java -jar "${ANTLR_JAR}" SolidityParser.g4 SolidityLexer.g4 -o "${WORKDIR}/src/" # Compile lexer/parser sources javac -classpath "${ANTLR_JAR}" "${WORKDIR}/src/"*.java -d "${WORKDIR}/target/" -) -# Run tests -failed_count=0 -function test_file -{ - local SOL_FILE - SOL_FILE="$(${READLINK} -m "${1}")" - local cur=${2} - local max=${3} - local solOrYul=${4} - - echo -e "${SGR_BLUE}[${cur}/${max}] Testing ${SOL_FILE}${SGR_RESET} ..." - local output - if [[ "${solOrYul}" == "sol" ]]; then - output=$( - grep -v "^==== ExternalSource:" "${SOL_FILE}" | java \ - -classpath "${ANTLR_JAR}:${WORKDIR}/target/" \ - "org.antlr.v4.gui.TestRig" \ - Solidity \ - sourceUnit 2>&1 - ) - else - output=$( - echo "assembly $(cat "${SOL_FILE}")" | java \ - -classpath "${ANTLR_JAR}:${WORKDIR}/target/" \ - "org.antlr.v4.gui.TestRig" \ - Solidity \ - assemblyStatement 2>&1 - ) - fi - vt_cursor_up - vt_cursor_begin_of_line - if grep -qE "^\/\/ ParserError" "${SOL_FILE}"; then - if [[ "${output}" != "" ]] - then - echo -e "${SGR_BLUE}[${cur}/${max}] Testing ${SOL_FILE}${SGR_RESET} ${SGR_BOLD}${SGR_GREEN}FAILED AS EXPECTED${SGR_RESET}" - else - echo -e "${SGR_BLUE}[${cur}/${max}] Testing ${SOL_FILE}${SGR_RESET} ${SGR_BOLD}${SGR_RED}SUCCEEDED DESPITE PARSER ERROR${SGR_RESET}" - echo "${output}" - failed_count=$((failed_count + 1)) - exit 1 - fi - else - if [[ "${output}" == "" ]] - then - echo -e "${SGR_BLUE}[${cur}/${max}] Testing ${SOL_FILE}${SGR_RESET} ${SGR_BOLD}${SGR_GREEN}OK${SGR_RESET}" - else - echo -e "${SGR_BLUE}[${cur}/${max}] Testing ${SOL_FILE}${SGR_RESET} ${SGR_BOLD}${SGR_RED}FAILED${SGR_RESET}" - echo "${output}" - failed_count=$((failed_count + 1)) - exit 1 - fi - fi -} +# Compile AntlrBatchTestRig +javac -classpath "${ANTLR_JAR}:${WORKDIR}/target/" "${ROOT_DIR}/scripts/AntlrBatchTestRig.java" -d "${WORKDIR}/target/" +) # we only want to use files that do not contain excluded parser errors, analysis errors or multi-source files. SOL_FILES=() @@ -154,17 +101,54 @@ done < <( "${ROOT_DIR}/test/libyul/yulOptimizerTests" ) -num_tests=$((${#SOL_FILES[*]} + ${#YUL_FILES[*]})) +# Combine all files into one array +ALL_FILES=("${SOL_FILES[@]}" "${YUL_FILES[@]}") +num_tests=${#ALL_FILES[@]} +failed_count=0 test_count=0 -for SOL_FILE in "${SOL_FILES[@]}" -do - test_count=$((test_count + 1)) - test_file "${SOL_FILE}" ${test_count} $num_tests "sol" -done -for YUL_FILE in "${YUL_FILES[@]}" -do - test_count=$((test_count + 1)) - test_file "${YUL_FILE}" ${test_count} $num_tests "yul" + +echo "Testing $num_tests files..." + +# Process all files in batches +for ((i=0; i<${#ALL_FILES[@]}; i+=BATCH_SIZE)); do + batch=("${ALL_FILES[@]:i:BATCH_SIZE}") + batch_size=${#batch[@]} + + echo "Processing batch $((i/BATCH_SIZE + 1)) (${batch_size} files)..." + + # Run batch + output=$(java -classpath "${ANTLR_JAR}:${WORKDIR}/target/" AntlrBatchTestRig "${batch[@]}" 2>&1) || true + + # Parse output + while IFS= read -r line; do + if [[ -z "$line" ]]; then + continue + fi + + test_count=$((test_count + 1)) + + if [[ "$line" =~ ^PASS:(.+):(.*) ]]; then + file="${BASH_REMATCH[1]}" + status="${BASH_REMATCH[2]}" + if [[ "$status" == "FAILED_AS_EXPECTED" ]]; then + echo -e "${SGR_BLUE}[${test_count}/${num_tests}] ${file}${SGR_RESET} ${SGR_BOLD}${SGR_GREEN}FAILED AS EXPECTED${SGR_RESET}" + else + echo -e "${SGR_BLUE}[${test_count}/${num_tests}] ${file}${SGR_RESET} ${SGR_BOLD}${SGR_GREEN}OK${SGR_RESET}" + fi + elif [[ "$line" =~ ^FAIL:(.+):(.*) ]]; then + file="${BASH_REMATCH[1]}" + error="${BASH_REMATCH[2]}" + echo -e "${SGR_BLUE}[${test_count}/${num_tests}] ${file}${SGR_RESET} ${SGR_BOLD}${SGR_RED}FAILED${SGR_RESET}" + echo "$error" + failed_count=$((failed_count + 1)) + elif [[ "$line" =~ ^ERROR:(.+):(.*) ]]; then + file="${BASH_REMATCH[1]}" + error="${BASH_REMATCH[2]}" + echo -e "${SGR_BLUE}[${test_count}/${num_tests}] ${file}${SGR_RESET} ${SGR_BOLD}${SGR_RED}ERROR${SGR_RESET}" + echo "$error" + failed_count=$((failed_count + 1)) + fi + done <<< "$output" done echo "Summary: ${failed_count} of $num_tests sources failed."