[isoltest] Support FAILURE with reason.

This commit is contained in:
Erik Kundt 2019-08-07 11:24:02 +02:00
parent fd7215dad0
commit 30483acc42
6 changed files with 145 additions and 44 deletions

View File

@ -18,9 +18,12 @@ contract C {
function balance() payable public returns (uint256) { function balance() payable public returns (uint256) {
return address(this).balance; return address(this).balance;
} }
function e(uint a) public { function d(uint a) public {
state = a; state = a;
} }
function e() public {
revert("Transaction failed.");
}
function f() payable public returns (uint) { function f() payable public returns (uint) {
return 2; return 2;
} }
@ -88,12 +91,15 @@ contract C {
return (["any", "any"], ["any", "any", "any"]); return (["any", "any"], ["any", "any", "any"]);
} }
} }
// ====
// EVMVersion: >homestead
// ---- // ----
// constructor(), 2 ether: 3 -> // constructor(), 2 ether: 3 ->
// state() -> 3 // state() -> 3
// balance() -> 2 // balance() -> 2
// _() -> FAILURE // _() -> FAILURE
// e(uint256): 4 // d(uint256): 4
// e() -> FAILURE, hex"08c379a0", 0x20, 19, "Transaction failed."
// f() -> 2 // f() -> 2
// f(uint256): 3 -> 3, 3 // f(uint256): 3 -> 3, 3
// f(), 1 ether -> 2 // f(), 1 ether -> 2

View File

@ -202,21 +202,28 @@ string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff)
return os.str(); return os.str();
} }
string BytesUtils::formatRawBytes(bytes const& _bytes) string BytesUtils::formatRawBytes(bytes const& _bytes, string _linePrefix, bool _withSignature)
{ {
if (_bytes.empty()) if (_bytes.empty())
return "[]"; return _linePrefix + "[]";
stringstream os; stringstream os;
auto it = _bytes.begin(); auto it = _bytes.begin();
for (size_t i = 0; i < _bytes.size(); i += 32)
if (_withSignature)
{ {
bytes byteRange{it, it + 32}; os << _linePrefix << bytes{it, it + 4} << endl;
it += 4;
}
os << " " << byteRange; bytes tail{it, _bytes.end()};
it = tail.begin();
for (size_t i = 0; i < tail.size(); i += 32)
{
os << _linePrefix << bytes{it, it + 32};
it += 32; it += 32;
if (it != _bytes.end()) if (it != tail.end())
os << endl; os << endl;
} }
@ -287,11 +294,12 @@ string BytesUtils::formatBytesRange(
else else
os << parameter.rawString; os << parameter.rawString;
it += static_cast<long>(parameter.abiType.size);
if (&parameter != &_parameters.back()) if (&parameter != &_parameters.back())
os << ", "; os << ", ";
it += static_cast<long>(parameter.abiType.size);
} }
return os.str(); return os.str();
} }

View File

@ -101,10 +101,14 @@ public:
return formatString(_bytes, _bytes.size()); return formatString(_bytes, _bytes.size());
} }
/// Returns a string representation of given _bytes. Adds a newline
/// every 32 bytes to increase readability.
/// Used to print returned bytes from function calls to the commandline. /// Used to print returned bytes from function calls to the commandline.
static std::string formatRawBytes(bytes const& _bytes); /// Returns a string representation of given _bytes in ranges of 32 bytes.
/// If _withSignature is true, the first 4 bytes will be formatted separately.
static std::string formatRawBytes(
bytes const& _bytes,
std::string _linePrefix = "",
bool _withSignature = false
);
/// Formats given _bytes with type information passed in _abiType. /// Formats given _bytes with type information passed in _abiType.
static std::string formatBytes(bytes const& _bytes, ABIType const& _abiType); static std::string formatBytes(bytes const& _bytes, ABIType const& _abiType);

View File

@ -219,6 +219,23 @@ BOOST_AUTO_TEST_CASE(non_existent_call_revert)
testFunctionCall(calls.at(0), Mode::MultiLine, "i_am_not_there()", true); testFunctionCall(calls.at(0), Mode::MultiLine, "i_am_not_there()", true);
} }
BOOST_AUTO_TEST_CASE(call_revert_message)
{
char const* source = R"(
// f() -> FAILURE, hex"08c379a0", 0x20, 6, "Revert"
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 1);
testFunctionCall(
calls.at(0),
Mode::SingleLine,
"f()",
true,
fmt::encodeArgs(),
fromHex("08c379a0") + fmt::encodeDyn(string{"Revert"})
);
}
BOOST_AUTO_TEST_CASE(call_expectations_empty_single_line) BOOST_AUTO_TEST_CASE(call_expectations_empty_single_line)
{ {
char const* source = R"( char const* source = R"(

View File

@ -101,7 +101,7 @@ string TestFunctionCall::format(
{ {
bool const isFailure = m_call.expectations.failure; bool const isFailure = m_call.expectations.failure;
result = isFailure ? result = isFailure ?
failure : formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) :
formatRawParameters(m_call.expectations.result); formatRawParameters(m_call.expectations.result);
if (!result.empty()) if (!result.empty())
AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << ws << result; AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << ws << result;
@ -111,7 +111,7 @@ string TestFunctionCall::format(
bytes output = m_rawBytes; bytes output = m_rawBytes;
bool const isFailure = m_failure; bool const isFailure = m_failure;
result = isFailure ? result = isFailure ?
failure : formatFailure(_errorReporter, m_call, output, _renderResult, highlight) :
matchesExpectation() ? matchesExpectation() ?
formatRawParameters(m_call.expectations.result) : formatRawParameters(m_call.expectations.result) :
formatBytesParameters( formatBytesParameters(
@ -122,6 +122,14 @@ string TestFunctionCall::format(
highlight highlight
); );
if (!matchesExpectation())
{
_errorReporter.warning(
"The call to \"" + m_call.signature + "\" returned \n" +
BytesUtils::formatRawBytes(output, _linePrefix, isFailure && !output.empty())
);
}
if (isFailure) if (isFailure)
AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << ws << result; AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << ws << result;
else else
@ -155,49 +163,97 @@ string TestFunctionCall::formatBytesParameters(
bytes const& _bytes, bytes const& _bytes,
string const& _signature, string const& _signature,
dev::solidity::test::ParameterList const& _parameters, dev::solidity::test::ParameterList const& _parameters,
bool _highlight bool _highlight,
bool _failure
) const ) const
{ {
using ParameterList = dev::solidity::test::ParameterList; using ParameterList = dev::solidity::test::ParameterList;
stringstream os; stringstream os;
if (_bytes.empty()) if (_bytes.empty())
return {}; return {};
_errorReporter.warning("The call to \"" + _signature + "\" returned \n" + BytesUtils::formatRawBytes(_bytes)); if (_failure)
boost::optional<ParameterList> abiParams = ContractABIUtils::parametersFromJsonOutputs(
_errorReporter,
m_contractABI,
_signature
);
if (abiParams)
{ {
boost::optional<ParameterList> preferredParams = ContractABIUtils::preferredParameters( ParameterList defaultParameters;
_errorReporter,
_parameters,
abiParams.get(),
_bytes
);
if (preferredParams) defaultParameters.push_back(Parameter{bytes(), "", ABIType{ABIType::HexString, ABIType::AlignNone, 4}, FormatInfo{}});
{ defaultParameters.push_back(Parameter{bytes(), "", ABIType{ABIType::Hex}, FormatInfo{}});
ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.get(), abiParams.get()); defaultParameters.push_back(Parameter{bytes(), "", ABIType{ABIType::UnsignedDec}, FormatInfo{}});
os << BytesUtils::formatBytesRange(_bytes, preferredParams.get(), _highlight); defaultParameters.push_back(Parameter{bytes(), "", ABIType{ABIType::String}, FormatInfo{}});
}
os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight);
return os.str();
} }
else else
{ {
ParameterList defaultParameters; boost::optional<ParameterList> abiParams = ContractABIUtils::parametersFromJsonOutputs(
fill_n( _errorReporter,
back_inserter(defaultParameters), m_contractABI,
ceil(_bytes.size() / 32), _signature
Parameter{bytes(), "", ABIType{ABIType::Hex}, FormatInfo{}}
); );
ContractABIUtils::overwriteParameters(_errorReporter, defaultParameters, _parameters);
os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight); if (abiParams)
{
boost::optional<ParameterList> preferredParams = ContractABIUtils::preferredParameters(
_errorReporter,
_parameters,
abiParams.get(),
_bytes
);
if (preferredParams)
{
ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.get(), abiParams.get());
os << BytesUtils::formatBytesRange(_bytes, preferredParams.get(), _highlight);
}
}
else
{
ParameterList defaultParameters;
fill_n(
back_inserter(defaultParameters),
ceil(_bytes.size() / 32),
Parameter{bytes(), "", ABIType{ABIType::UnsignedDec}, FormatInfo{}}
);
ContractABIUtils::overwriteParameters(_errorReporter, defaultParameters, _parameters);
os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight);
}
return os.str();
} }
}
string TestFunctionCall::formatFailure(
ErrorReporter& _errorReporter,
dev::solidity::test::FunctionCall const& _call,
bytes const& _output,
bool _renderResult,
bool _highlight
) const
{
using Token = soltest::Token;
stringstream os;
os << formatToken(Token::Failure);
if (!_output.empty())
os << ", ";
if (_renderResult)
os << formatBytesParameters(
_errorReporter,
_output,
_call.signature,
_call.expectations.result,
_highlight,
true
);
else
os << formatRawParameters(_call.expectations.result);
return os.str(); return os.str();
} }

View File

@ -95,7 +95,8 @@ private:
bytes const& _bytes, bytes const& _bytes,
std::string const& _signature, std::string const& _signature,
ParameterList const& _params, ParameterList const& _params,
bool highlight = false bool highlight = false,
bool failure = false
) const; ) const;
/// Formats a given _bytes applying the _abiType. /// Formats a given _bytes applying the _abiType.
@ -104,6 +105,15 @@ private:
ABIType const& _abiType ABIType const& _abiType
) const; ) const;
/// Formats a FAILURE plus additional parameters, if e.g. a revert message was returned.
std::string formatFailure(
ErrorReporter& _errorReporter,
FunctionCall const& _call,
bytes const& _output,
bool _renderResult,
bool _highlight
) const;
/// Formats the given parameters using their raw string representation. /// Formats the given parameters using their raw string representation.
std::string formatRawParameters( std::string formatRawParameters(
ParameterList const& _params, ParameterList const& _params,