[isoltest] Add support for builtin functions.

This commit is contained in:
Alexander Arlt 2021-01-07 22:45:19 -05:00
parent be5647735e
commit 94895822d2
6 changed files with 75 additions and 18 deletions

View File

@ -13,9 +13,12 @@
*/ */
#include <test/libsolidity/SemanticTest.h> #include <test/libsolidity/SemanticTest.h>
#include <libsolutil/Whiskers.h> #include <libsolutil/Whiskers.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <test/Common.h> #include <test/Common.h>
#include <test/libsolidity/util/BytesUtils.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp> #include <boost/algorithm/string/trim.hpp>
@ -25,7 +28,9 @@
#include <cctype> #include <cctype>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <optional>
#include <stdexcept> #include <stdexcept>
#include <utility>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
@ -119,7 +124,13 @@ TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePref
return result; 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; bool success = true;
@ -142,21 +153,25 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
if (_compileViaYul) if (_compileViaYul)
AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul:" << endl; AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul:" << endl;
for (auto& test: m_tests) for (TestFunctionCall& test: m_tests)
test.reset(); test.reset();
map<string, solidity::test::Address> libraries; map<string, solidity::test::Address> libraries;
bool constructed = false; bool constructed = false;
for (auto& test: m_tests) for (TestFunctionCall& test: m_tests)
{ {
if (constructed) 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( soltestAssert(
test.call().kind != FunctionCall::Kind::Constructor, 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) else if (test.call().kind == FunctionCall::Kind::Library)
{ {
@ -197,6 +212,17 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
bytes output; bytes output;
if (test.call().kind == FunctionCall::Kind::LowLevel) if (test.call().kind == FunctionCall::Kind::LowLevel)
output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value); output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value);
else if (test.call().kind == FunctionCall::Kind::Builtin)
{
std::optional<bytes> builtinOutput = m_builtins.at(test.call().signature)(test.call());
if (builtinOutput.has_value())
{
m_transactionSuccessful = true;
output = builtinOutput.value();
}
else
m_transactionSuccessful = false;
}
else else
{ {
soltestAssert( soltestAssert(
@ -241,7 +267,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
if (!success && (m_runWithYul || !_compileViaYul)) if (!success && (m_runWithYul || !_compileViaYul))
{ {
AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl;
for (auto const& test: m_tests) for (TestFunctionCall const& test: m_tests)
{ {
ErrorReporter errorReporter; ErrorReporter errorReporter;
_stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl; _stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl;
@ -249,7 +275,7 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
} }
_stream << endl; _stream << endl;
AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl;
for (auto const& test: m_tests) for (TestFunctionCall const& test: m_tests)
{ {
ErrorReporter errorReporter; ErrorReporter errorReporter;
_stream << test.format(errorReporter, _linePrefix, true, _formatted) << endl; _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 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; _stream << test.format("", true, false) << endl;
} }
@ -340,12 +366,16 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre
void SemanticTest::parseExpectations(istream& _stream) void SemanticTest::parseExpectations(istream& _stream)
{ {
TestFileParser parser{_stream}; TestFileParser parser{_stream, m_builtins};
auto functionCalls = parser.parseFunctionCalls(m_lineOffset); m_tests += parser.parseFunctionCalls(m_lineOffset);
std::move(functionCalls.begin(), functionCalls.end(), back_inserter(m_tests));
} }
bool SemanticTest::deploy(string const& _contractName, u256 const& _value, bytes const& _arguments, map<string, solidity::test::Address> const& _libraries) bool SemanticTest::deploy(
string const& _contractName,
u256 const& _value,
bytes const& _arguments,
map<string, solidity::test::Address> const& _libraries
)
{ {
auto output = compileAndRunWithoutCheck(m_sources.sources, _value, _contractName, _arguments, _libraries); auto output = compileAndRunWithoutCheck(m_sources.sources, _value, _contractName, _arguments, _libraries);
return !output.empty() && m_transactionSuccessful; return !output.empty() && m_transactionSuccessful;

View File

@ -58,6 +58,7 @@ public:
/// Compiles and deploys currently held source. /// Compiles and deploys currently held source.
/// Returns true if deployment was successful, false otherwise. /// Returns true if deployment was successful, false otherwise.
bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {}); bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {});
private: private:
TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm); TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm);
SourceMap m_sources; SourceMap m_sources;
@ -70,6 +71,7 @@ private:
bool m_runWithABIEncoderV1Only = false; bool m_runWithABIEncoderV1Only = false;
bool m_allowNonExistingFunctions = false; bool m_allowNonExistingFunctions = false;
bool m_compileViaYulCanBeSet = false; bool m_compileViaYulCanBeSet = false;
std::map<std::string, Builtin> m_builtins{};
}; };
} }

View File

@ -17,6 +17,8 @@
#include <libsolutil/AnsiColorized.h> #include <libsolutil/AnsiColorized.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <test/ExecutionFramework.h>
namespace solidity::frontend::test namespace solidity::frontend::test
{ {
@ -174,6 +176,8 @@ struct Parameter
}; };
using ParameterList = std::vector<Parameter>; using ParameterList = std::vector<Parameter>;
struct FunctionCall;
/** /**
* Represents the expected result of a function call after it has been executed. This may be a single * 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 * 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, /// A Comment that can be attached to the expectations,
/// that is retained and can be displayed. /// that is retained and can be displayed.
std::string comment; std::string comment;
/// ABI encoded `bytes` of parsed expected return values. It is checked /// ABI encoded `bytes` of parsed expected return values. It is checked
/// against the actual result of a function call when used in test framework. /// against the actual result of a function call when used in test framework.
bytes rawBytes() const bytes rawBytes() const
@ -286,7 +291,9 @@ struct FunctionCall
/// Marks a library deployment call. /// Marks a library deployment call.
Library, Library,
/// Check that the storage of the current contract is empty or non-empty. /// Check that the storage of the current contract is empty or non-empty.
Storage Storage,
/// Call to a builtin.
Builtin
}; };
Kind kind = Kind::Regular; Kind kind = Kind::Regular;
/// Marks this function call as "short-handed", meaning /// Marks this function call as "short-handed", meaning
@ -294,4 +301,6 @@ struct FunctionCall
bool omitsArrow = true; bool omitsArrow = true;
}; };
using Builtin = std::function<std::optional<bytes>(FunctionCall const&)>;
} }

View File

@ -55,7 +55,7 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
vector<FunctionCall> calls; vector<FunctionCall> calls;
if (!accept(Token::EOS)) if (!accept(Token::EOS))
{ {
assert(m_scanner.currentToken() == Token::Unknown); soltestAssert(m_scanner.currentToken() == Token::Unknown, "");
m_scanner.scanNextToken(); m_scanner.scanNextToken();
while (!accept(Token::EOS)) while (!accept(Token::EOS))
@ -106,6 +106,8 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
tie(call.signature, lowLevelCall) = parseFunctionSignature(); tie(call.signature, lowLevelCall) = parseFunctionSignature();
if (lowLevelCall) if (lowLevelCall)
call.kind = FunctionCall::Kind::LowLevel; call.kind = FunctionCall::Kind::LowLevel;
else if (isBuiltinFunction(call.signature))
call.kind = FunctionCall::Kind::Builtin;
if (accept(Token::Comma, true)) if (accept(Token::Comma, true))
call.value = parseFunctionCallValue(); call.value = parseFunctionCallValue();
@ -195,6 +197,9 @@ pair<string, bool> TestFileParser::parseFunctionSignature()
expect(Token::Identifier); expect(Token::Identifier);
} }
if (isBuiltinFunction(signature))
return {signature, false};
signature += formatToken(Token::LParen); signature += formatToken(Token::LParen);
expect(Token::LParen); expect(Token::LParen);
@ -488,7 +493,7 @@ void TestFileParser::Scanner::readStream(istream& _stream)
void TestFileParser::Scanner::scanNextToken() void TestFileParser::Scanner::scanNextToken()
{ {
// Make code coverage happy. // Make code coverage happy.
assert(formatToken(Token::NUM_TOKENS) == ""); soltestAssert(formatToken(Token::NUM_TOKENS).empty(), "");
auto detectKeyword = [](std::string const& _literal = "") -> std::pair<Token, std::string> { auto detectKeyword = [](std::string const& _literal = "") -> std::pair<Token, std::string> {
if (_literal == "true") return {Token::Boolean, "true"}; if (_literal == "true") return {Token::Boolean, "true"};
@ -712,3 +717,8 @@ char TestFileParser::Scanner::scanHexPart()
return static_cast<char>(value); return static_cast<char>(value);
} }
bool TestFileParser::isBuiltinFunction(std::string const& signature)
{
return m_builtins.count(signature) > 0;
}

View File

@ -14,8 +14,8 @@
#pragma once #pragma once
#include <libsolutil/CommonData.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <libsolutil/CommonData.h>
#include <test/libsolidity/util/SoltestTypes.h> #include <test/libsolidity/util/SoltestTypes.h>
#include <iosfwd> #include <iosfwd>
@ -52,7 +52,7 @@ class TestFileParser
public: public:
/// Constructor that takes an input stream \param _stream to operate on /// Constructor that takes an input stream \param _stream to operate on
/// and creates the internal scanner. /// and creates the internal scanner.
TestFileParser(std::istream& _stream): m_scanner(_stream) {} explicit TestFileParser(std::istream& _stream, std::map<std::string, Builtin> const& _builtins): m_scanner(_stream), m_builtins(_builtins) {}
/// Parses function calls blockwise and returns a list of function calls found. /// 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 /// Throws an exception if a function call cannot be parsed because of its
@ -177,12 +177,18 @@ private:
/// Parses the current string literal. /// Parses the current string literal.
std::string parseString(); 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 /// A scanner instance
Scanner m_scanner; Scanner m_scanner;
/// The current line number. Incremented when Token::Newline (//) is found and /// The current line number. Incremented when Token::Newline (//) is found and
/// used to enhance parser error messages. /// used to enhance parser error messages.
size_t m_lineNumber = 0; size_t m_lineNumber = 0;
std::map<std::string, Builtin> const& m_builtins;
}; };
} }

View File

@ -45,7 +45,7 @@ namespace
vector<FunctionCall> parse(string const& _source) vector<FunctionCall> parse(string const& _source)
{ {
istringstream stream{_source, ios_base::out}; istringstream stream{_source, ios_base::out};
TestFileParser parser{stream}; TestFileParser parser{stream, {}};
return parser.parseFunctionCalls(0); return parser.parseFunctionCalls(0);
} }