NatspecJSONTest based on SyntaxTest

This commit is contained in:
Kamil Śliwak 2023-07-31 19:52:27 +02:00
parent 34c86d90be
commit 91cc72bcd4
5 changed files with 297 additions and 0 deletions

View File

@ -84,6 +84,8 @@ set(libsolidity_sources
libsolidity/Metadata.cpp
libsolidity/MemoryGuardTest.cpp
libsolidity/MemoryGuardTest.h
libsolidity/NatspecJSONTest.cpp
libsolidity/NatspecJSONTest.h
libsolidity/SemanticTest.cpp
libsolidity/SemanticTest.h
libsolidity/SemVerMatcher.cpp

View File

@ -24,6 +24,7 @@
#include <test/libsolidity/ASTPropertyTest.h>
#include <test/libsolidity/GasTest.h>
#include <test/libsolidity/MemoryGuardTest.h>
#include <test/libsolidity/NatspecJSONTest.h>
#include <test/libsolidity/SyntaxTest.h>
#include <test/libsolidity/SemanticTest.h>
#include <test/libsolidity/SMTCheckerTest.h>
@ -74,6 +75,7 @@ Testsuite const g_interactiveTestsuites[] = {
{"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create},
{"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create},
{"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create},
{"JSON Natspec", "libsolidity", "natspecJSON", false, false, &NatspecJSONTest::create},
{"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create},
{"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create},
{"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create},

View File

@ -0,0 +1,210 @@
/*
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 the solidity compiler ABI JSON Interface output.
*/
#include <test/libsolidity/NatspecJSONTest.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string/predicate.hpp>
#include <fmt/format.h>
#include <vector>
using namespace std;
using namespace solidity::frontend::test;
using namespace solidity::util;
ostream& solidity::frontend::test::operator<<(ostream& _output, NatspecJSONKind _kind)
{
switch (_kind) {
case NatspecJSONKind::Devdoc: _output << "devdoc"; break;
case NatspecJSONKind::Userdoc: _output << "userdoc"; break;
}
return _output;
}
unique_ptr<TestCase> NatspecJSONTest::create(Config const& _config)
{
return make_unique<NatspecJSONTest>(_config.filename, _config.evmVersion);
}
void NatspecJSONTest::parseCustomExpectations(istream& _stream)
{
soltestAssert(m_expectedNatspecJSON.empty());
// We expect a series of expectations in the following format:
//
// // <qualified contract name> <devdoc|userdoc>
// // <json>
string line;
while (getline(_stream, line))
{
string_view strippedLine = expectLinePrefix(line);
if (strippedLine.empty())
continue;
auto [contractName, kind] = parseExpectationHeader(strippedLine);
string rawJSON = extractExpectationJSON(_stream);
string jsonErrors;
Json::Value parsedJSON;
bool jsonParsingSuccessful = jsonParseStrict(rawJSON, parsedJSON, &jsonErrors);
if (!jsonParsingSuccessful)
BOOST_THROW_EXCEPTION(runtime_error(fmt::format(
"Malformed JSON in {} expectation for contract {}.\n"
"Note that JSON expectations must be pretty-printed to be split correctly. "
"The object is assumed to and at the first unindented closing brace.\n"
"{}",
toString(kind),
contractName,
rawJSON
)));
m_expectedNatspecJSON[string(contractName)][kind] = parsedJSON;
}
}
bool NatspecJSONTest::expectationsMatch()
{
// NOTE: Comparing pretty printed Json::Values to avoid using its operator==, which fails to
// compare equal numbers as equal. For example, for 'version' field the value is sometimes int,
// sometimes uint and they compare as different even when both are 1.
return
SyntaxTest::expectationsMatch() &&
prettyPrinted(obtainedNatspec()) == prettyPrinted(m_expectedNatspecJSON);
}
void NatspecJSONTest::printExpectedResult(ostream& _stream, string const& _linePrefix, bool _formatted) const
{
SyntaxTest::printExpectedResult(_stream, _linePrefix, _formatted);
if (!m_expectedNatspecJSON.empty())
{
_stream << _linePrefix << "----" << endl;
printIndented(_stream, formatNatspecExpectations(m_expectedNatspecJSON), _linePrefix);
}
}
void NatspecJSONTest::printObtainedResult(ostream& _stream, string const& _linePrefix, bool _formatted) const
{
SyntaxTest::printObtainedResult(_stream, _linePrefix, _formatted);
NatspecMap natspecJSON = obtainedNatspec();
if (!natspecJSON.empty())
{
_stream << _linePrefix << "----" << endl;
// TODO: Diff both versions and highlight differences.
// We should have a helper for doing that in newly defined test cases without much effort.
printIndented(_stream, formatNatspecExpectations(natspecJSON), _linePrefix);
}
}
tuple<string_view, NatspecJSONKind> NatspecJSONTest::parseExpectationHeader(string_view _line)
{
for (NatspecJSONKind kind: {NatspecJSONKind::Devdoc, NatspecJSONKind::Userdoc})
{
string kindSuffix = " " + toString(kind);
if (boost::algorithm::ends_with(_line, kindSuffix))
return {_line.substr(0, _line.size() - kindSuffix.size()), kind};
}
BOOST_THROW_EXCEPTION(runtime_error(
"Natspec kind (devdoc/userdoc) not present in the expectation: "s.append(_line)
));
}
string NatspecJSONTest::extractExpectationJSON(istream& _stream)
{
string rawJSON;
string line;
while (getline(_stream, line))
{
string_view strippedLine = expectLinePrefix(line);
rawJSON += strippedLine;
rawJSON += "\n";
if (boost::algorithm::starts_with(strippedLine, "}"))
break;
}
return rawJSON;
}
string_view NatspecJSONTest::expectLinePrefix(string_view _line)
{
size_t startPosition = 0;
if (!boost::algorithm::starts_with(_line, "//"))
BOOST_THROW_EXCEPTION(runtime_error(
"Expectation line is not a comment: "s.append(_line)
));
startPosition += 2;
if (startPosition < _line.size() && _line[startPosition] == ' ')
++startPosition;
return _line.substr(startPosition, _line.size() - startPosition);
}
string NatspecJSONTest::formatNatspecExpectations(NatspecMap const& _expectations) const
{
string output;
bool first = true;
// NOTE: Not sorting explicitly because CompilerStack seems to put contracts roughly in the
// order in which they appear in the source, which is much better than alphabetical order.
for (auto const& [contractName, expectationsForAllKinds]: _expectations)
for (auto const& [jsonKind, natspecJSON]: expectationsForAllKinds)
{
if (!first)
output += "\n\n";
first = false;
output += contractName + " " + toString(jsonKind) + "\n";
output += jsonPrint(natspecJSON, {JsonFormat::Pretty, 4});
}
return output;
}
NatspecMap NatspecJSONTest::obtainedNatspec() const
{
if (compiler().state() < CompilerStack::AnalysisSuccessful)
return {};
NatspecMap result;
for (string contractName: compiler().contractNames())
{
result[contractName][NatspecJSONKind::Devdoc] = compiler().natspecDev(contractName);
result[contractName][NatspecJSONKind::Userdoc] = compiler().natspecUser(contractName);
}
return result;
}
SerializedNatspecMap NatspecJSONTest::prettyPrinted(NatspecMap const& _expectations) const
{
SerializedNatspecMap result;
for (auto const& [contractName, expectationsForAllKinds]: _expectations)
for (auto const& [jsonKind, natspecJSON]: expectationsForAllKinds)
result[contractName][jsonKind] = jsonPrint(natspecJSON, {JsonFormat::Pretty, 4});
return result;
}

View File

@ -0,0 +1,82 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Unit tests for the Natspec userdoc and devdoc JSON output.
*/
#pragma once
#include <test/libsolidity/SyntaxTest.h>
#include <libsolutil/JSON.h>
#include <istream>
#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <tuple>
namespace solidity::frontend::test
{
enum class NatspecJSONKind
{
Devdoc,
Userdoc,
};
std::ostream& operator<<(std::ostream& _output, NatspecJSONKind _kind);
using NatspecMap = std::map<std::string, std::map<NatspecJSONKind, Json::Value>>;
using SerializedNatspecMap = std::map<std::string, std::map<NatspecJSONKind, std::string>>;
class NatspecJSONTest: public SyntaxTest
{
public:
static std::unique_ptr<TestCase> create(Config const& _config);
NatspecJSONTest(std::string const& _filename, langutil::EVMVersion _evmVersion):
SyntaxTest(
_filename,
_evmVersion,
langutil::Error::Severity::Error // _minSeverity
)
{}
protected:
void parseCustomExpectations(std::istream& _stream) override;
bool expectationsMatch() override;
void printExpectedResult(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) const override;
void printObtainedResult(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) const override;
NatspecMap m_expectedNatspecJSON;
private:
static std::tuple<std::string_view, NatspecJSONKind> parseExpectationHeader(std::string_view _line);
static std::string extractExpectationJSON(std::istream& _stream);
static std::string_view expectLinePrefix(std::string_view _line);
std::string formatNatspecExpectations(NatspecMap const& _expectations) const;
SerializedNatspecMap prettyPrinted(NatspecMap const& _expectations) const;
NatspecMap obtainedNatspec() const;
};
}

View File

@ -25,6 +25,7 @@ add_executable(isoltest
../libsolidity/util/TestFunctionCall.cpp
../libsolidity/GasTest.cpp
../libsolidity/MemoryGuardTest.cpp
../libsolidity/NatspecJSONTest.cpp
../libsolidity/SyntaxTest.cpp
../libsolidity/SemanticTest.cpp
../libsolidity/AnalysisFramework.cpp