mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			211 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
	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;
 | 
						|
}
 |