diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index da2834b83..d30908627 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -13,9 +13,12 @@ */ #include + #include #include #include +#include + #include #include #include @@ -25,7 +28,9 @@ #include #include #include +#include #include +#include using namespace std; using namespace solidity; @@ -119,7 +124,13 @@ TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePref return result; } -TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm) +TestCase::TestResult SemanticTest::runTest( + ostream& _stream, + string const& _linePrefix, + bool _formatted, + bool _compileViaYul, + bool _compileToEwasm +) { bool success = true; @@ -142,21 +153,25 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line if (_compileViaYul) AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul:" << endl; - for (auto& test: m_tests) + for (TestFunctionCall& test: m_tests) test.reset(); map libraries; bool constructed = false; - for (auto& test: m_tests) + for (TestFunctionCall& test: m_tests) { if (constructed) { - soltestAssert(test.call().kind != FunctionCall::Kind::Library, "Libraries have to be deployed before any other call."); + soltestAssert( + test.call().kind != FunctionCall::Kind::Library, + "Libraries have to be deployed before any other call." + ); soltestAssert( test.call().kind != FunctionCall::Kind::Constructor, - "Constructor has to be the first function call expect for library deployments."); + "Constructor has to be the first function call expect for library deployments." + ); } else if (test.call().kind == FunctionCall::Kind::Library) { @@ -197,6 +212,17 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line bytes output; if (test.call().kind == FunctionCall::Kind::LowLevel) output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value); + else if (test.call().kind == FunctionCall::Kind::Builtin) + { + std::optional builtinOutput = m_builtins.at(test.call().signature)(test.call()); + if (builtinOutput.has_value()) + { + m_transactionSuccessful = true; + output = builtinOutput.value(); + } + else + m_transactionSuccessful = false; + } else { soltestAssert( @@ -241,7 +267,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line if (!success && (m_runWithYul || !_compileViaYul)) { AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; - for (auto const& test: m_tests) + for (TestFunctionCall const& test: m_tests) { ErrorReporter errorReporter; _stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl; @@ -249,7 +275,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line } _stream << endl; AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; - for (auto const& test: m_tests) + for (TestFunctionCall const& test: m_tests) { ErrorReporter errorReporter; _stream << test.format(errorReporter, _linePrefix, true, _formatted) << endl; @@ -320,7 +346,7 @@ void SemanticTest::printSource(ostream& _stream, string const& _linePrefix, bool void SemanticTest::printUpdatedExpectations(ostream& _stream, string const&) const { - for (auto const& test: m_tests) + for (TestFunctionCall const& test: m_tests) _stream << test.format("", true, false) << endl; } @@ -340,12 +366,16 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre void SemanticTest::parseExpectations(istream& _stream) { - TestFileParser parser{_stream}; - auto functionCalls = parser.parseFunctionCalls(m_lineOffset); - std::move(functionCalls.begin(), functionCalls.end(), back_inserter(m_tests)); + TestFileParser parser{_stream, m_builtins}; + m_tests += parser.parseFunctionCalls(m_lineOffset); } -bool SemanticTest::deploy(string const& _contractName, u256 const& _value, bytes const& _arguments, map const& _libraries) +bool SemanticTest::deploy( + string const& _contractName, + u256 const& _value, + bytes const& _arguments, + map const& _libraries +) { auto output = compileAndRunWithoutCheck(m_sources.sources, _value, _contractName, _arguments, _libraries); return !output.empty() && m_transactionSuccessful; diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index acb656763..6ba62c9f4 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -58,6 +58,7 @@ public: /// Compiles and deploys currently held source. /// Returns true if deployment was successful, false otherwise. bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map const& _libraries = {}); + private: TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm); SourceMap m_sources; @@ -70,6 +71,7 @@ private: bool m_runWithABIEncoderV1Only = false; bool m_allowNonExistingFunctions = false; bool m_compileViaYulCanBeSet = false; + std::map m_builtins{}; }; } diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index e0f6caa1f..fc8c1dfc1 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -17,6 +17,8 @@ #include #include +#include + namespace solidity::frontend::test { @@ -174,6 +176,8 @@ struct Parameter }; using ParameterList = std::vector; +struct FunctionCall; + /** * Represents the expected result of a function call after it has been executed. This may be a single * return value or a comma-separated list of return values. It also contains the detected input @@ -193,6 +197,7 @@ struct FunctionCallExpectations /// A Comment that can be attached to the expectations, /// that is retained and can be displayed. std::string comment; + /// ABI encoded `bytes` of parsed expected return values. It is checked /// against the actual result of a function call when used in test framework. bytes rawBytes() const @@ -286,7 +291,9 @@ struct FunctionCall /// Marks a library deployment call. Library, /// Check that the storage of the current contract is empty or non-empty. - Storage + Storage, + /// Call to a builtin. + Builtin }; Kind kind = Kind::Regular; /// Marks this function call as "short-handed", meaning @@ -294,4 +301,6 @@ struct FunctionCall bool omitsArrow = true; }; +using Builtin = std::function(FunctionCall const&)>; + } diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index 37f32ccee..2ba3a9cad 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -55,7 +55,7 @@ vector TestFileParser::parseFunctionCall vector calls; if (!accept(Token::EOS)) { - assert(m_scanner.currentToken() == Token::Unknown); + soltestAssert(m_scanner.currentToken() == Token::Unknown, ""); m_scanner.scanNextToken(); while (!accept(Token::EOS)) @@ -106,6 +106,8 @@ vector TestFileParser::parseFunctionCall tie(call.signature, lowLevelCall) = parseFunctionSignature(); if (lowLevelCall) call.kind = FunctionCall::Kind::LowLevel; + else if (isBuiltinFunction(call.signature)) + call.kind = FunctionCall::Kind::Builtin; if (accept(Token::Comma, true)) call.value = parseFunctionCallValue(); @@ -195,6 +197,9 @@ pair TestFileParser::parseFunctionSignature() expect(Token::Identifier); } + if (isBuiltinFunction(signature)) + return {signature, false}; + signature += formatToken(Token::LParen); expect(Token::LParen); @@ -488,7 +493,7 @@ void TestFileParser::Scanner::readStream(istream& _stream) void TestFileParser::Scanner::scanNextToken() { // Make code coverage happy. - assert(formatToken(Token::NUM_TOKENS) == ""); + soltestAssert(formatToken(Token::NUM_TOKENS).empty(), ""); auto detectKeyword = [](std::string const& _literal = "") -> std::pair { if (_literal == "true") return {Token::Boolean, "true"}; @@ -712,3 +717,8 @@ char TestFileParser::Scanner::scanHexPart() return static_cast(value); } + +bool TestFileParser::isBuiltinFunction(std::string const& signature) +{ + return m_builtins.count(signature) > 0; +} diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index 63a95dd4a..ff28c432a 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -14,8 +14,8 @@ #pragma once -#include #include +#include #include #include @@ -52,7 +52,7 @@ class TestFileParser public: /// Constructor that takes an input stream \param _stream to operate on /// and creates the internal scanner. - TestFileParser(std::istream& _stream): m_scanner(_stream) {} + explicit TestFileParser(std::istream& _stream, std::map const& _builtins): m_scanner(_stream), m_builtins(_builtins) {} /// Parses function calls blockwise and returns a list of function calls found. /// Throws an exception if a function call cannot be parsed because of its @@ -177,12 +177,18 @@ private: /// Parses the current string literal. std::string parseString(); + /// Checks whether a builtin function with the given signature exist. + /// @returns true, if builtin found, false otherwise + bool isBuiltinFunction(std::string const& signature); + /// A scanner instance Scanner m_scanner; /// The current line number. Incremented when Token::Newline (//) is found and /// used to enhance parser error messages. size_t m_lineNumber = 0; + + std::map const& m_builtins; }; } diff --git a/test/libsolidity/util/TestFileParserTests.cpp b/test/libsolidity/util/TestFileParserTests.cpp index 242b721b9..30d501669 100644 --- a/test/libsolidity/util/TestFileParserTests.cpp +++ b/test/libsolidity/util/TestFileParserTests.cpp @@ -45,7 +45,7 @@ namespace vector parse(string const& _source) { istringstream stream{_source, ios_base::out}; - TestFileParser parser{stream}; + TestFileParser parser{stream, {}}; return parser.parseFunctionCalls(0); }