[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 <libsolutil/Whiskers.h>
#include <libyul/Exceptions.h>
#include <test/Common.h>
#include <test/libsolidity/util/BytesUtils.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
@ -25,7 +28,9 @@
#include <cctype>
#include <fstream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <utility>
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<string, solidity::test::Address> 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<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
{
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<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);
return !output.empty() && m_transactionSuccessful;

View File

@ -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<std::string, solidity::test::Address> 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<std::string, Builtin> m_builtins{};
};
}

View File

@ -17,6 +17,8 @@
#include <libsolutil/AnsiColorized.h>
#include <libsolutil/CommonData.h>
#include <test/ExecutionFramework.h>
namespace solidity::frontend::test
{
@ -174,6 +176,8 @@ struct 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
* 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<std::optional<bytes>(FunctionCall const&)>;
}

View File

@ -55,7 +55,7 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
vector<FunctionCall> 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<solidity::frontend::test::FunctionCall> 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<string, bool> 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<Token, std::string> {
if (_literal == "true") return {Token::Boolean, "true"};
@ -712,3 +717,8 @@ char TestFileParser::Scanner::scanHexPart()
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
#include <libsolutil/CommonData.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/CommonData.h>
#include <test/libsolidity/util/SoltestTypes.h>
#include <iosfwd>
@ -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<std::string, Builtin> 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<std::string, Builtin> const& m_builtins;
};
}

View File

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