mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Implements a test file parser.
This commit is contained in:
parent
42240a69e9
commit
f90c6f57bb
@ -21,6 +21,7 @@ Bugfixes:
|
|||||||
Build System:
|
Build System:
|
||||||
* Add support for continuous fuzzing via Google oss-fuzz
|
* Add support for continuous fuzzing via Google oss-fuzz
|
||||||
* Ubuntu PPA Packages: Use CVC4 as SMT solver instead of Z3
|
* Ubuntu PPA Packages: Use CVC4 as SMT solver instead of Z3
|
||||||
|
* Soltest: Add parser that is used in the file-based unit test environment.
|
||||||
|
|
||||||
|
|
||||||
### 0.5.3 (2019-01-22)
|
### 0.5.3 (2019-01-22)
|
||||||
|
@ -17,6 +17,8 @@ if (LLL)
|
|||||||
endif()
|
endif()
|
||||||
file(GLOB libsolidity_sources "libsolidity/*.cpp")
|
file(GLOB libsolidity_sources "libsolidity/*.cpp")
|
||||||
file(GLOB libsolidity_headers "libsolidity/*.h")
|
file(GLOB libsolidity_headers "libsolidity/*.h")
|
||||||
|
file(GLOB libsolidity_util_sources "libsolidity/util/*.cpp")
|
||||||
|
file(GLOB libsolidity_util_headers "libsolidity/util/*.h")
|
||||||
|
|
||||||
add_executable(soltest ${sources} ${headers}
|
add_executable(soltest ${sources} ${headers}
|
||||||
${contracts_sources} ${contracts_headers}
|
${contracts_sources} ${contracts_headers}
|
||||||
@ -26,6 +28,7 @@ add_executable(soltest ${sources} ${headers}
|
|||||||
${libyul_sources} ${libyul_headers}
|
${libyul_sources} ${libyul_headers}
|
||||||
${liblll_sources} ${liblll_headers}
|
${liblll_sources} ${liblll_headers}
|
||||||
${libsolidity_sources} ${libsolidity_headers}
|
${libsolidity_sources} ${libsolidity_headers}
|
||||||
|
${libsolidity_util_sources} ${libsolidity_util_headers}
|
||||||
)
|
)
|
||||||
target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
||||||
|
|
||||||
|
376
test/libsolidity/util/TestFileParser.cpp
Normal file
376
test/libsolidity/util/TestFileParser.cpp
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <test/libsolidity/util/TestFileParser.h>
|
||||||
|
|
||||||
|
#include <test/Options.h>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/throw_exception.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
using namespace dev;
|
||||||
|
using namespace langutil;
|
||||||
|
using namespace solidity;
|
||||||
|
using namespace dev::solidity::test;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool isDecimalDigit(char c)
|
||||||
|
{
|
||||||
|
return '0' <= c && c <= '9';
|
||||||
|
}
|
||||||
|
bool isWhiteSpace(char c)
|
||||||
|
{
|
||||||
|
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
|
||||||
|
}
|
||||||
|
bool isIdentifierStart(char c)
|
||||||
|
{
|
||||||
|
return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||||
|
}
|
||||||
|
bool isIdentifierPart(char c)
|
||||||
|
{
|
||||||
|
return isIdentifierStart(c) || isDecimalDigit(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls()
|
||||||
|
{
|
||||||
|
vector<FunctionCall> calls;
|
||||||
|
if (!accept(SoltToken::EOS))
|
||||||
|
{
|
||||||
|
// TODO: check initial token state
|
||||||
|
expect(SoltToken::Unknown);
|
||||||
|
while (!accept(SoltToken::EOS))
|
||||||
|
{
|
||||||
|
if (!accept(SoltToken::Whitespace))
|
||||||
|
{
|
||||||
|
FunctionCall call;
|
||||||
|
|
||||||
|
// f()
|
||||||
|
expect(SoltToken::Newline);
|
||||||
|
call.signature = parseFunctionSignature();
|
||||||
|
|
||||||
|
// f(), 314 ether
|
||||||
|
if (accept(SoltToken::Comma, true))
|
||||||
|
call.value = parseFunctionCallValue();
|
||||||
|
|
||||||
|
// f(), 314 ether: 1, 1
|
||||||
|
if (accept(SoltToken::Colon, true))
|
||||||
|
call.arguments = parseFunctionCallArguments();
|
||||||
|
|
||||||
|
string comment = m_scanner.currentLiteral();
|
||||||
|
if (accept(SoltToken::Comment, true))
|
||||||
|
call.arguments.comment = comment;
|
||||||
|
|
||||||
|
// -> 1
|
||||||
|
expect(SoltToken::Newline);
|
||||||
|
expect(SoltToken::Arrow);
|
||||||
|
if (m_scanner.peekToken() != SoltToken::Newline)
|
||||||
|
{
|
||||||
|
call.expectations = parseFunctionCallExpectations();
|
||||||
|
|
||||||
|
string comment = m_scanner.currentLiteral();
|
||||||
|
if (accept(SoltToken::Comment, true))
|
||||||
|
call.expectations.comment = comment;
|
||||||
|
}
|
||||||
|
calls.emplace_back(std::move(call));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_scanner.scanNextToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
string TestFileParser::formatToken(SoltToken _token)
|
||||||
|
{
|
||||||
|
switch (_token)
|
||||||
|
{
|
||||||
|
#define T(name, string, precedence) case SoltToken::name: return string;
|
||||||
|
SOLT_TOKEN_LIST(T, T)
|
||||||
|
#undef T
|
||||||
|
default: // Token::NUM_TOKENS:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestFileParser::accept(SoltToken _token, bool const _expect)
|
||||||
|
{
|
||||||
|
if (m_scanner.currentToken() == _token)
|
||||||
|
{
|
||||||
|
if (_expect)
|
||||||
|
expect(_token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestFileParser::expect(SoltToken _token, bool const _advance)
|
||||||
|
{
|
||||||
|
if (m_scanner.currentToken() != _token)
|
||||||
|
throw Error(Error::Type::ParserError,
|
||||||
|
"Unexpected " + formatToken(m_scanner.currentToken()) + ": \"" +
|
||||||
|
m_scanner.currentLiteral() + "\". " +
|
||||||
|
"Expected \"" + formatToken(_token) + "\"."
|
||||||
|
);
|
||||||
|
if (_advance)
|
||||||
|
m_scanner.scanNextToken();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string TestFileParser::parseFunctionSignature()
|
||||||
|
{
|
||||||
|
string signature = m_scanner.currentLiteral();
|
||||||
|
expect(SoltToken::Identifier);
|
||||||
|
|
||||||
|
signature += formatToken(SoltToken::LParen);
|
||||||
|
expect(SoltToken::LParen);
|
||||||
|
|
||||||
|
while (!accept(SoltToken::RParen))
|
||||||
|
{
|
||||||
|
signature += m_scanner.currentLiteral();
|
||||||
|
expect(SoltToken::UInt);
|
||||||
|
while (accept(SoltToken::Comma))
|
||||||
|
{
|
||||||
|
signature += m_scanner.currentLiteral();
|
||||||
|
expect(SoltToken::Comma);
|
||||||
|
signature += m_scanner.currentLiteral();
|
||||||
|
expect(SoltToken::UInt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signature += formatToken(SoltToken::RParen);
|
||||||
|
expect(SoltToken::RParen);
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
u256 TestFileParser::parseFunctionCallValue()
|
||||||
|
{
|
||||||
|
u256 value;
|
||||||
|
string literal = m_scanner.currentLiteral();
|
||||||
|
expect(SoltToken::Number);
|
||||||
|
value = convertNumber(literal);
|
||||||
|
expect(SoltToken::Ether);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionCallArgs TestFileParser::parseFunctionCallArguments()
|
||||||
|
{
|
||||||
|
FunctionCallArgs arguments;
|
||||||
|
|
||||||
|
auto formattedBytes = parseABITypeLiteral();
|
||||||
|
arguments.rawBytes += formattedBytes.first;
|
||||||
|
arguments.formats.emplace_back(std::move(formattedBytes.second));
|
||||||
|
while (accept(SoltToken::Comma, true))
|
||||||
|
{
|
||||||
|
auto formattedBytes = parseABITypeLiteral();
|
||||||
|
arguments.rawBytes += formattedBytes.first;
|
||||||
|
arguments.formats.emplace_back(std::move(formattedBytes.second));
|
||||||
|
}
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionCallExpectations TestFileParser::parseFunctionCallExpectations()
|
||||||
|
{
|
||||||
|
FunctionCallExpectations expectations;
|
||||||
|
string token = m_scanner.currentLiteral();
|
||||||
|
|
||||||
|
if (accept(SoltToken::Failure, true))
|
||||||
|
expectations.status = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto formattedBytes = parseABITypeLiteral();
|
||||||
|
expectations.rawBytes += formattedBytes.first;
|
||||||
|
expectations.formats.emplace_back(std::move(formattedBytes.second));
|
||||||
|
|
||||||
|
while (accept(SoltToken::Comma, true))
|
||||||
|
{
|
||||||
|
auto formattedBytes = parseABITypeLiteral();
|
||||||
|
expectations.rawBytes += formattedBytes.first;
|
||||||
|
expectations.formats.emplace_back(std::move(formattedBytes.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expectations;
|
||||||
|
}
|
||||||
|
|
||||||
|
pair<bytes, ABIType> TestFileParser::parseABITypeLiteral()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
u256 number;
|
||||||
|
ABIType abiType;
|
||||||
|
if (accept(SoltToken::Sub))
|
||||||
|
{
|
||||||
|
abiType.type = ABIType::Type::SignedDec;
|
||||||
|
abiType.size = 32;
|
||||||
|
|
||||||
|
expect(SoltToken::Sub);
|
||||||
|
number = convertNumber(parseNumber()) * -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (accept(SoltToken::Number))
|
||||||
|
{
|
||||||
|
abiType.type = ABIType::Type::UnsignedDec;
|
||||||
|
abiType.size = 32;
|
||||||
|
|
||||||
|
number = convertNumber(parseNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_pair(toBigEndian(number), abiType);
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
throw Error(Error::Type::ParserError, "Number encoding invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string TestFileParser::parseNumber()
|
||||||
|
{
|
||||||
|
string literal = m_scanner.currentLiteral();
|
||||||
|
expect(SoltToken::Number);
|
||||||
|
return literal;
|
||||||
|
}
|
||||||
|
|
||||||
|
u256 TestFileParser::convertNumber(string const& _literal)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return u256{_literal};
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
throw Error(Error::Type::ParserError, "Number encoding invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestFileParser::Scanner::readStream(istream& _stream)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(_stream, line))
|
||||||
|
m_line += line;
|
||||||
|
m_char = m_line.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestFileParser::Scanner::scanNextToken()
|
||||||
|
{
|
||||||
|
auto detectToken = [](std::string const& _literal = "") -> TokenDesc {
|
||||||
|
if (_literal == "ether") return TokenDesc{SoltToken::Ether, _literal};
|
||||||
|
if (_literal == "uint256") return TokenDesc{SoltToken::UInt, _literal};
|
||||||
|
if (_literal == "FAILURE") return TokenDesc{SoltToken::Failure, _literal};
|
||||||
|
return TokenDesc{SoltToken::Identifier, _literal};
|
||||||
|
};
|
||||||
|
|
||||||
|
auto selectToken = [this](SoltToken _token, std::string const& _literal = "") -> TokenDesc {
|
||||||
|
advance();
|
||||||
|
return make_pair(_token, !_literal.empty() ? _literal : formatToken(_token));
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenDesc token = make_pair(SoltToken::Unknown, "");
|
||||||
|
do {
|
||||||
|
switch(current())
|
||||||
|
{
|
||||||
|
case '/':
|
||||||
|
advance();
|
||||||
|
if (current() == '/')
|
||||||
|
token = selectToken(SoltToken::Newline);
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
if (peek() == '>')
|
||||||
|
{
|
||||||
|
advance();
|
||||||
|
token = selectToken(SoltToken::Arrow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
token = selectToken(SoltToken::Sub);
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
token = selectToken(SoltToken::Colon);
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
token = selectToken(SoltToken::Comment, scanComment());
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
token = selectToken(SoltToken::Comma);
|
||||||
|
break;
|
||||||
|
case '(':
|
||||||
|
token = selectToken(SoltToken::LParen);
|
||||||
|
break;
|
||||||
|
case ')':
|
||||||
|
token = selectToken(SoltToken::RParen);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (isIdentifierStart(current()))
|
||||||
|
{
|
||||||
|
TokenDesc detectedToken = detectToken(scanIdentifierOrKeyword());
|
||||||
|
token = selectToken(detectedToken.first, detectedToken.second);
|
||||||
|
}
|
||||||
|
else if (isDecimalDigit(current()))
|
||||||
|
token = selectToken(SoltToken::Number, scanNumber());
|
||||||
|
else if (isWhiteSpace(current()))
|
||||||
|
token = selectToken(SoltToken::Whitespace);
|
||||||
|
else if (isEndOfLine())
|
||||||
|
token = selectToken(SoltToken::EOS);
|
||||||
|
else
|
||||||
|
token = selectToken(SoltToken::Invalid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (token.first == SoltToken::Whitespace);
|
||||||
|
|
||||||
|
m_nextToken = token;
|
||||||
|
m_currentToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
string TestFileParser::Scanner::scanComment()
|
||||||
|
{
|
||||||
|
string comment;
|
||||||
|
advance();
|
||||||
|
|
||||||
|
while (current() != '#')
|
||||||
|
{
|
||||||
|
comment += current();
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
string TestFileParser::Scanner::scanIdentifierOrKeyword()
|
||||||
|
{
|
||||||
|
string identifier;
|
||||||
|
identifier += current();
|
||||||
|
while (isIdentifierPart(peek()))
|
||||||
|
{
|
||||||
|
advance();
|
||||||
|
identifier += current();
|
||||||
|
}
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
string TestFileParser::Scanner::scanNumber()
|
||||||
|
{
|
||||||
|
string number;
|
||||||
|
number += current();
|
||||||
|
while (isDecimalDigit(peek()))
|
||||||
|
{
|
||||||
|
advance();
|
||||||
|
number += current();
|
||||||
|
}
|
||||||
|
return number;
|
||||||
|
}
|
277
test/libsolidity/util/TestFileParser.h
Normal file
277
test/libsolidity/util/TestFileParser.h
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libdevcore/CommonData.h>
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
|
#include <liblangutil/Exceptions.h>
|
||||||
|
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace dev
|
||||||
|
{
|
||||||
|
namespace solidity
|
||||||
|
{
|
||||||
|
namespace test
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All SOLT (or SOLTest) tokens.
|
||||||
|
*/
|
||||||
|
#define SOLT_TOKEN_LIST(T, K) \
|
||||||
|
T(Unknown, "unknown", 0) \
|
||||||
|
T(Invalid, "invalid", 0) \
|
||||||
|
T(EOS, "EOS", 0) \
|
||||||
|
T(Whitespace, "_", 0) \
|
||||||
|
/* punctuations */ \
|
||||||
|
T(LParen, "(", 0) \
|
||||||
|
T(RParen, ")", 0) \
|
||||||
|
T(LBrack, "[", 0) \
|
||||||
|
T(RBrack, "]", 0) \
|
||||||
|
T(LBrace, "{", 0) \
|
||||||
|
T(RBrace, "}", 0) \
|
||||||
|
T(Sub, "-", 0) \
|
||||||
|
T(Colon, ":", 0) \
|
||||||
|
T(Comma, ",", 0) \
|
||||||
|
T(Period, ".", 0) \
|
||||||
|
T(Arrow, "->", 0) \
|
||||||
|
T(Newline, "//", 0) \
|
||||||
|
/* Literals & identifier */ \
|
||||||
|
T(Comment, "comment", 0) \
|
||||||
|
T(Number, "number", 0) \
|
||||||
|
T(Identifier, "identifier", 0) \
|
||||||
|
/* type keywords */ \
|
||||||
|
K(Ether, "ether", 0) \
|
||||||
|
K(UInt, "uint256", 0) \
|
||||||
|
/* special keywords */ \
|
||||||
|
K(Failure, "FAILURE", 0) \
|
||||||
|
|
||||||
|
|
||||||
|
enum class SoltToken : unsigned int {
|
||||||
|
#define T(name, string, precedence) name,
|
||||||
|
SOLT_TOKEN_LIST(T, T)
|
||||||
|
NUM_TOKENS
|
||||||
|
#undef T
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of the ABI type is the storage of type information
|
||||||
|
* retrieved while parsing a test. This information is used
|
||||||
|
* for the conversion of human-readable function arguments and
|
||||||
|
* return values to `bytes` and vice-versa.
|
||||||
|
* Defaults to an invalid 0-byte representation.
|
||||||
|
*/
|
||||||
|
struct ABIType
|
||||||
|
{
|
||||||
|
enum Type {
|
||||||
|
UnsignedDec,
|
||||||
|
SignedDec,
|
||||||
|
Invalid
|
||||||
|
};
|
||||||
|
ABIType(): type(ABIType::Invalid), size(0) { }
|
||||||
|
ABIType(Type _type, size_t _size): type(_type), size(_size) { }
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ABITypeList = std::vector<ABIType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* formats used to convert the values to `bytes` needed for the comparison with the actual result
|
||||||
|
* of a call. In addition to that, it also stores the expected transaction status.
|
||||||
|
* An optional comment can be assigned.
|
||||||
|
*/
|
||||||
|
struct FunctionCallExpectations
|
||||||
|
{
|
||||||
|
/// ABI encoded `bytes` of parsed expectations. This `bytes`
|
||||||
|
/// is compared to the actual result of a function call
|
||||||
|
/// and is taken into account while validating it.
|
||||||
|
bytes rawBytes;
|
||||||
|
/// Types that were used to encode `rawBytes`. Expectations
|
||||||
|
/// are usually comma seperated literals. Their type is auto-
|
||||||
|
/// detected and retained in order to format them later on.
|
||||||
|
ABITypeList formats;
|
||||||
|
/// Expected status of the transaction. It can be either
|
||||||
|
/// a REVERT or a different EVM failure (e.g. out-of-gas).
|
||||||
|
bool status = true;
|
||||||
|
/// A Comment that can be attached to the expectations,
|
||||||
|
/// that is retained and can be displayed.
|
||||||
|
std::string comment;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the arguments passed to a function call. This can be a single
|
||||||
|
* argument or a comma-separated list of arguments. It also contains the detected input
|
||||||
|
* formats used to convert the arguments to `bytes` needed for the call.
|
||||||
|
* An optional comment can be assigned.
|
||||||
|
*/
|
||||||
|
struct FunctionCallArgs
|
||||||
|
{
|
||||||
|
/// ABI encoded `bytes` of parsed parameters. This `bytes`
|
||||||
|
/// passed to the function call.
|
||||||
|
bytes rawBytes;
|
||||||
|
/// Types that were used to encode `rawBytes`. Parameters
|
||||||
|
/// are usually comma seperated literals. Their type is auto-
|
||||||
|
/// detected and retained in order to format them later on.
|
||||||
|
ABITypeList formats;
|
||||||
|
/// A Comment that can be attached to the expectations,
|
||||||
|
/// that is retained and can be displayed.
|
||||||
|
std::string comment;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a function call read from an input stream. It contains the signature, the
|
||||||
|
* arguments, an optional ether value and an expected execution result.
|
||||||
|
*/
|
||||||
|
struct FunctionCall
|
||||||
|
{
|
||||||
|
/// Signature of the function call, e.g. `f(uint256, uint256)`.
|
||||||
|
std::string signature;
|
||||||
|
/// Optional `ether` value that can be send with the call.
|
||||||
|
u256 value;
|
||||||
|
/// Object that holds all function parameters in their `bytes`
|
||||||
|
/// representations given by the contract ABI.
|
||||||
|
FunctionCallArgs arguments;
|
||||||
|
/// Object that holds all function call expectation in
|
||||||
|
/// their `bytes` representations given by the contract ABI.
|
||||||
|
/// They are checked against the actual results and their
|
||||||
|
/// `bytes` representation, as well as the transaction status.
|
||||||
|
FunctionCallExpectations expectations;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that is able to parse an additional and well-formed comment section in a Solidity
|
||||||
|
* source file used by the file-based unit test environment. For now, it parses function
|
||||||
|
* calls and their expected result after the call was made.
|
||||||
|
*
|
||||||
|
* - Function calls defined in blocks:
|
||||||
|
* // f(uint256, uint256): 1, 1 # Signature and comma-separated list of arguments #
|
||||||
|
* // -> 1, 1 # Expected result value #
|
||||||
|
* // g(), 2 ether # (Optional) Ether to be send with the call #
|
||||||
|
* // -> 2, 3
|
||||||
|
* // h(uint256), 1 ether: 42
|
||||||
|
* // -> FAILURE # If REVERT or other EVM failure was detected #
|
||||||
|
* ...
|
||||||
|
*/
|
||||||
|
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) {}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// incorrect structure, an invalid or unsupported encoding
|
||||||
|
/// of its arguments or expected results.
|
||||||
|
std::vector<FunctionCall> parseFunctionCalls();
|
||||||
|
|
||||||
|
/// Prints a friendly string representation of \param _token.
|
||||||
|
static std::string formatToken(SoltToken _token);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Token scanner that is used internally to abstract away character traversal.
|
||||||
|
*/
|
||||||
|
class Scanner
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor that takes an input stream \param _stream to operate on.
|
||||||
|
Scanner(std::istream& _stream) { readStream(_stream); }
|
||||||
|
|
||||||
|
/// Reads input stream into a single line and resets the current iterator.
|
||||||
|
void readStream(std::istream& _stream);
|
||||||
|
|
||||||
|
/// Reads character stream and creates token.
|
||||||
|
void scanNextToken();
|
||||||
|
|
||||||
|
SoltToken currentToken() { return m_currentToken.first; }
|
||||||
|
SoltToken peekToken() { return m_nextToken.first; }
|
||||||
|
|
||||||
|
std::string currentLiteral() { return m_currentToken.second; }
|
||||||
|
|
||||||
|
std::string scanComment();
|
||||||
|
std::string scanIdentifierOrKeyword();
|
||||||
|
std::string scanNumber();
|
||||||
|
|
||||||
|
private:
|
||||||
|
using TokenDesc = std::pair<SoltToken, std::string>;
|
||||||
|
|
||||||
|
/// Advances current position in the input stream.
|
||||||
|
void advance() { ++m_char; }
|
||||||
|
/// Returns the current character.
|
||||||
|
char current() const { return *m_char; }
|
||||||
|
/// Peeks the next character.
|
||||||
|
char peek() const { auto it = m_char; return *(it + 1); }
|
||||||
|
/// Returns true if the end of a line is reached, false otherwise.
|
||||||
|
bool isEndOfLine() const { return m_char == m_line.end(); }
|
||||||
|
|
||||||
|
std::string m_line;
|
||||||
|
std::string::iterator m_char;
|
||||||
|
|
||||||
|
std::string m_currentLiteral;
|
||||||
|
|
||||||
|
TokenDesc m_currentToken;
|
||||||
|
TokenDesc m_nextToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool accept(SoltToken _token, bool const _expect = false);
|
||||||
|
bool expect(SoltToken _token, bool const _advance = true);
|
||||||
|
|
||||||
|
/// Parses a function call signature in the form of f(uint256, ...).
|
||||||
|
std::string parseFunctionSignature();
|
||||||
|
|
||||||
|
/// Parses the optional ether value that can be passed alongside the
|
||||||
|
/// function call arguments. Throws an InvalidEtherValueEncoding exception
|
||||||
|
/// if given value cannot be converted to `u256`.
|
||||||
|
u256 parseFunctionCallValue();
|
||||||
|
|
||||||
|
/// Parses a comma-separated list of arguments passed with a function call.
|
||||||
|
/// Does not check for a potential mismatch between the signature and the number
|
||||||
|
/// or types of arguments.
|
||||||
|
FunctionCallArgs parseFunctionCallArguments();
|
||||||
|
|
||||||
|
/// Parses the expected result of a function call execution.
|
||||||
|
FunctionCallExpectations parseFunctionCallExpectations();
|
||||||
|
|
||||||
|
/// Parses and converts the current literal to its byte representation and
|
||||||
|
/// preserves the chosen ABI type. Based on that type information, the driver of
|
||||||
|
/// this parser can format arguments, expectations and results. Supported types:
|
||||||
|
/// - unsigned and signed decimal number literals
|
||||||
|
/// Throws a ParserError if data is encoded incorrectly or
|
||||||
|
/// if data type is not supported.
|
||||||
|
std::pair<bytes, ABIType> parseABITypeLiteral();
|
||||||
|
|
||||||
|
/// Parses the current number literal.
|
||||||
|
std::string parseNumber();
|
||||||
|
|
||||||
|
/// Tries to convert \param _literal to `uint256` and throws if
|
||||||
|
/// if conversion failed.
|
||||||
|
u256 convertNumber(std::string const& _literal);
|
||||||
|
|
||||||
|
/// A scanner instance
|
||||||
|
Scanner m_scanner;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
205
test/libsolidity/util/TestFileParserTests.cpp
Normal file
205
test/libsolidity/util/TestFileParserTests.cpp
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Unit tests for Solidity's test expectation parser.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
#include <liblangutil/Exceptions.h>
|
||||||
|
#include <test/libsolidity/SolidityExecutionFramework.h>
|
||||||
|
|
||||||
|
#include <test/libsolidity/util/TestFileParser.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dev::test;
|
||||||
|
|
||||||
|
namespace dev
|
||||||
|
{
|
||||||
|
namespace solidity
|
||||||
|
{
|
||||||
|
namespace test
|
||||||
|
{
|
||||||
|
|
||||||
|
vector<FunctionCall> parse(string const& _source)
|
||||||
|
{
|
||||||
|
istringstream stream{_source, ios_base::out};
|
||||||
|
TestFileParser parser{stream};
|
||||||
|
return parser.parseFunctionCalls();
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(TestFileParserTest)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(smoke_test)
|
||||||
|
{
|
||||||
|
char const* source = R"()";
|
||||||
|
BOOST_CHECK_EQUAL(parse(source).size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(simple_call_succees)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(uint256, uint256): 1, 1
|
||||||
|
// ->
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto call = calls.at(0);
|
||||||
|
ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}) + toBigEndian(u256{1}));
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "f(uint256,uint256)");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(non_existent_call_revert)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// i_am_not_there()
|
||||||
|
// -> FAILURE
|
||||||
|
)";
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto const& call = calls.at(0);
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "i_am_not_there()");
|
||||||
|
BOOST_CHECK_EQUAL(call.expectations.status, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_comments)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f() # This is a comment #
|
||||||
|
// -> 1 # This is another comment #
|
||||||
|
)";
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto const& call = calls.at(0);
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "f()");
|
||||||
|
BOOST_CHECK_EQUAL(call.arguments.comment, " This is a comment ");
|
||||||
|
BOOST_CHECK_EQUAL(call.expectations.comment, " This is another comment ");
|
||||||
|
ABI_CHECK(call.expectations.rawBytes, toBigEndian(u256{1}));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_arguments)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(uint256), 314 ether: 5 # optional ether value #
|
||||||
|
// -> 4
|
||||||
|
)";
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto const& call = calls.at(0);
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "f(uint256)");
|
||||||
|
BOOST_CHECK_EQUAL(call.value, u256{314});
|
||||||
|
ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{5}));
|
||||||
|
ABI_CHECK(call.expectations.rawBytes, toBigEndian(u256{4}));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_expectations_missing)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f())";
|
||||||
|
BOOST_CHECK_THROW(parse(source), langutil::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_ether_value_expectations_missing)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(), 0)";
|
||||||
|
BOOST_CHECK_THROW(parse(source), langutil::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_arguments_invalid)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(uint256): abc
|
||||||
|
// -> 1
|
||||||
|
)";
|
||||||
|
BOOST_CHECK_THROW(parse(source), langutil::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_ether_value_invalid)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(uint256), abc : 1
|
||||||
|
// -> 1
|
||||||
|
)";
|
||||||
|
BOOST_CHECK_THROW(parse(source), langutil::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_ether_type_invalid)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(uint256), 2 btc : 1
|
||||||
|
// -> 1
|
||||||
|
)";
|
||||||
|
BOOST_CHECK_THROW(parse(source), langutil::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_arguments_mismatch)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// f(uint256, uint256): 1 # This only throws at runtime #
|
||||||
|
// -> 1
|
||||||
|
)";
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto const& call = calls.at(0);
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "f(uint256,uint256)");
|
||||||
|
ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_multiple_arguments)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// test(uint256, uint256): 1, 2
|
||||||
|
// -> 1, 1
|
||||||
|
)";
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto const& call = calls.at(0);
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "test(uint256,uint256)");
|
||||||
|
ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}) + toBigEndian(u256{2}));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(call_multiple_arguments_mixed_format)
|
||||||
|
{
|
||||||
|
char const* source = R"(
|
||||||
|
// test(uint256, uint256),314 ether: 1, -2
|
||||||
|
// -> -1, 2
|
||||||
|
)";
|
||||||
|
auto const calls = parse(source);
|
||||||
|
BOOST_CHECK_EQUAL(calls.size(), 1);
|
||||||
|
|
||||||
|
auto const& call = calls.at(0);
|
||||||
|
BOOST_CHECK_EQUAL(call.signature, "test(uint256,uint256)");
|
||||||
|
BOOST_CHECK_EQUAL(call.value, u256{314});
|
||||||
|
ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}) + toBigEndian(u256{-2}));
|
||||||
|
ABI_CHECK(call.expectations.rawBytes, toBigEndian(u256{-1}) + toBigEndian(u256{2}));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user