Skip to content

Commit 64a0f62

Browse files
authored
Merge pull request ethereum#14506 from ethereum/extracted-natspec-json-tests
Replace Boost-based Natspec test case with one derived from `SyntaxTest`
2 parents 34c86d9 + b63a940 commit 64a0f62

File tree

134 files changed

+5543
-3446
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

134 files changed

+5543
-3446
lines changed

scripts/error_codes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ def print_ids_per_file(ids, id_to_file_names, top_dir):
171171

172172
def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False):
173173
test_sub_dirs = [
174+
path.join("test", "libsolidity", "natspecJSON"),
174175
path.join("test", "libsolidity", "smtCheckerTests"),
175176
path.join("test", "libsolidity", "syntaxTests"),
176177
path.join("test", "libyul", "yulSyntaxTests")

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ set(libsolidity_sources
8484
libsolidity/Metadata.cpp
8585
libsolidity/MemoryGuardTest.cpp
8686
libsolidity/MemoryGuardTest.h
87+
libsolidity/NatspecJSONTest.cpp
88+
libsolidity/NatspecJSONTest.h
8789
libsolidity/SemanticTest.cpp
8890
libsolidity/SemanticTest.h
8991
libsolidity/SemVerMatcher.cpp
@@ -95,7 +97,6 @@ set(libsolidity_sources
9597
libsolidity/SolidityExecutionFramework.h
9698
libsolidity/SolidityExpressionCompiler.cpp
9799
libsolidity/SolidityNameAndTypeResolution.cpp
98-
libsolidity/SolidityNatspecJSON.cpp
99100
libsolidity/SolidityOptimizer.cpp
100101
libsolidity/SolidityParser.cpp
101102
libsolidity/SolidityTypes.cpp

test/InteractiveTests.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <test/libsolidity/ASTPropertyTest.h>
2525
#include <test/libsolidity/GasTest.h>
2626
#include <test/libsolidity/MemoryGuardTest.h>
27+
#include <test/libsolidity/NatspecJSONTest.h>
2728
#include <test/libsolidity/SyntaxTest.h>
2829
#include <test/libsolidity/SemanticTest.h>
2930
#include <test/libsolidity/SMTCheckerTest.h>
@@ -74,6 +75,7 @@ Testsuite const g_interactiveTestsuites[] = {
7475
{"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create},
7576
{"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create},
7677
{"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create},
78+
{"JSON Natspec", "libsolidity", "natspecJSON", false, false, &NatspecJSONTest::create},
7779
{"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create},
7880
{"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create},
7981
{"Memory Guard", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create},

test/libsolidity/NatspecJSONTest.cpp

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
/**
18+
* Unit tests for the solidity compiler ABI JSON Interface output.
19+
*/
20+
21+
#include <test/libsolidity/NatspecJSONTest.h>
22+
23+
#include <libsolutil/CommonIO.h>
24+
#include <libsolutil/StringUtils.h>
25+
26+
#include <boost/algorithm/string/predicate.hpp>
27+
28+
#include <fmt/format.h>
29+
30+
#include <vector>
31+
32+
using namespace std;
33+
using namespace solidity::frontend::test;
34+
using namespace solidity::util;
35+
36+
ostream& solidity::frontend::test::operator<<(ostream& _output, NatspecJSONKind _kind)
37+
{
38+
switch (_kind) {
39+
case NatspecJSONKind::Devdoc: _output << "devdoc"; break;
40+
case NatspecJSONKind::Userdoc: _output << "userdoc"; break;
41+
}
42+
return _output;
43+
}
44+
45+
unique_ptr<TestCase> NatspecJSONTest::create(Config const& _config)
46+
{
47+
return make_unique<NatspecJSONTest>(_config.filename, _config.evmVersion);
48+
}
49+
50+
void NatspecJSONTest::parseCustomExpectations(istream& _stream)
51+
{
52+
soltestAssert(m_expectedNatspecJSON.empty());
53+
54+
// We expect a series of expectations in the following format:
55+
//
56+
// // <qualified contract name> <devdoc|userdoc>
57+
// // <json>
58+
59+
string line;
60+
while (getline(_stream, line))
61+
{
62+
string_view strippedLine = expectLinePrefix(line);
63+
if (strippedLine.empty())
64+
continue;
65+
66+
auto [contractName, kind] = parseExpectationHeader(strippedLine);
67+
68+
string rawJSON = extractExpectationJSON(_stream);
69+
string jsonErrors;
70+
Json::Value parsedJSON;
71+
bool jsonParsingSuccessful = jsonParseStrict(rawJSON, parsedJSON, &jsonErrors);
72+
if (!jsonParsingSuccessful)
73+
BOOST_THROW_EXCEPTION(runtime_error(fmt::format(
74+
"Malformed JSON in {} expectation for contract {}.\n"
75+
"Note that JSON expectations must be pretty-printed to be split correctly. "
76+
"The object is assumed to and at the first unindented closing brace.\n"
77+
"{}",
78+
toString(kind),
79+
contractName,
80+
rawJSON
81+
)));
82+
83+
m_expectedNatspecJSON[string(contractName)][kind] = parsedJSON;
84+
}
85+
}
86+
87+
bool NatspecJSONTest::expectationsMatch()
88+
{
89+
// NOTE: Comparing pretty printed Json::Values to avoid using its operator==, which fails to
90+
// compare equal numbers as equal. For example, for 'version' field the value is sometimes int,
91+
// sometimes uint and they compare as different even when both are 1.
92+
return
93+
SyntaxTest::expectationsMatch() &&
94+
prettyPrinted(obtainedNatspec()) == prettyPrinted(m_expectedNatspecJSON);
95+
}
96+
97+
void NatspecJSONTest::printExpectedResult(ostream& _stream, string const& _linePrefix, bool _formatted) const
98+
{
99+
SyntaxTest::printExpectedResult(_stream, _linePrefix, _formatted);
100+
if (!m_expectedNatspecJSON.empty())
101+
{
102+
_stream << _linePrefix << "----" << endl;
103+
printIndented(_stream, formatNatspecExpectations(m_expectedNatspecJSON), _linePrefix);
104+
}
105+
}
106+
107+
void NatspecJSONTest::printObtainedResult(ostream& _stream, string const& _linePrefix, bool _formatted) const
108+
{
109+
SyntaxTest::printObtainedResult(_stream, _linePrefix, _formatted);
110+
111+
NatspecMap natspecJSON = obtainedNatspec();
112+
if (!natspecJSON.empty())
113+
{
114+
_stream << _linePrefix << "----" << endl;
115+
// TODO: Diff both versions and highlight differences.
116+
// We should have a helper for doing that in newly defined test cases without much effort.
117+
printIndented(_stream, formatNatspecExpectations(natspecJSON), _linePrefix);
118+
}
119+
}
120+
121+
tuple<string_view, NatspecJSONKind> NatspecJSONTest::parseExpectationHeader(string_view _line)
122+
{
123+
for (NatspecJSONKind kind: {NatspecJSONKind::Devdoc, NatspecJSONKind::Userdoc})
124+
{
125+
string kindSuffix = " " + toString(kind);
126+
if (boost::algorithm::ends_with(_line, kindSuffix))
127+
return {_line.substr(0, _line.size() - kindSuffix.size()), kind};
128+
}
129+
130+
BOOST_THROW_EXCEPTION(runtime_error(
131+
"Natspec kind (devdoc/userdoc) not present in the expectation: "s.append(_line)
132+
));
133+
}
134+
135+
string NatspecJSONTest::extractExpectationJSON(istream& _stream)
136+
{
137+
string rawJSON;
138+
string line;
139+
while (getline(_stream, line))
140+
{
141+
string_view strippedLine = expectLinePrefix(line);
142+
rawJSON += strippedLine;
143+
rawJSON += "\n";
144+
145+
if (boost::algorithm::starts_with(strippedLine, "}"))
146+
break;
147+
}
148+
149+
return rawJSON;
150+
}
151+
152+
string_view NatspecJSONTest::expectLinePrefix(string_view _line)
153+
{
154+
size_t startPosition = 0;
155+
if (!boost::algorithm::starts_with(_line, "//"))
156+
BOOST_THROW_EXCEPTION(runtime_error(
157+
"Expectation line is not a comment: "s.append(_line)
158+
));
159+
160+
startPosition += 2;
161+
if (startPosition < _line.size() && _line[startPosition] == ' ')
162+
++startPosition;
163+
164+
return _line.substr(startPosition, _line.size() - startPosition);
165+
}
166+
167+
string NatspecJSONTest::formatNatspecExpectations(NatspecMap const& _expectations) const
168+
{
169+
string output;
170+
bool first = true;
171+
// NOTE: Not sorting explicitly because CompilerStack seems to put contracts roughly in the
172+
// order in which they appear in the source, which is much better than alphabetical order.
173+
for (auto const& [contractName, expectationsForAllKinds]: _expectations)
174+
for (auto const& [jsonKind, natspecJSON]: expectationsForAllKinds)
175+
{
176+
if (!first)
177+
output += "\n\n";
178+
first = false;
179+
180+
output += contractName + " " + toString(jsonKind) + "\n";
181+
output += jsonPrint(natspecJSON, {JsonFormat::Pretty, 4});
182+
}
183+
184+
return output;
185+
}
186+
187+
NatspecMap NatspecJSONTest::obtainedNatspec() const
188+
{
189+
if (compiler().state() < CompilerStack::AnalysisSuccessful)
190+
return {};
191+
192+
NatspecMap result;
193+
for (string contractName: compiler().contractNames())
194+
{
195+
result[contractName][NatspecJSONKind::Devdoc] = compiler().natspecDev(contractName);
196+
result[contractName][NatspecJSONKind::Userdoc] = compiler().natspecUser(contractName);
197+
}
198+
199+
return result;
200+
}
201+
202+
SerializedNatspecMap NatspecJSONTest::prettyPrinted(NatspecMap const& _expectations) const
203+
{
204+
SerializedNatspecMap result;
205+
for (auto const& [contractName, expectationsForAllKinds]: _expectations)
206+
for (auto const& [jsonKind, natspecJSON]: expectationsForAllKinds)
207+
result[contractName][jsonKind] = jsonPrint(natspecJSON, {JsonFormat::Pretty, 4});
208+
209+
return result;
210+
}

test/libsolidity/NatspecJSONTest.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
/**
19+
* Unit tests for the Natspec userdoc and devdoc JSON output.
20+
*/
21+
22+
#pragma once
23+
24+
#include <test/libsolidity/SyntaxTest.h>
25+
26+
#include <libsolutil/JSON.h>
27+
28+
#include <istream>
29+
#include <map>
30+
#include <memory>
31+
#include <ostream>
32+
#include <string>
33+
#include <string_view>
34+
#include <tuple>
35+
36+
namespace solidity::frontend::test
37+
{
38+
39+
enum class NatspecJSONKind
40+
{
41+
Devdoc,
42+
Userdoc,
43+
};
44+
45+
std::ostream& operator<<(std::ostream& _output, NatspecJSONKind _kind);
46+
47+
using NatspecMap = std::map<std::string, std::map<NatspecJSONKind, Json::Value>>;
48+
using SerializedNatspecMap = std::map<std::string, std::map<NatspecJSONKind, std::string>>;
49+
50+
class NatspecJSONTest: public SyntaxTest
51+
{
52+
public:
53+
54+
static std::unique_ptr<TestCase> create(Config const& _config);
55+
56+
NatspecJSONTest(std::string const& _filename, langutil::EVMVersion _evmVersion):
57+
SyntaxTest(
58+
_filename,
59+
_evmVersion,
60+
langutil::Error::Severity::Error // _minSeverity
61+
)
62+
{}
63+
64+
protected:
65+
void parseCustomExpectations(std::istream& _stream) override;
66+
bool expectationsMatch() override;
67+
void printExpectedResult(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) const override;
68+
void printObtainedResult(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) const override;
69+
70+
NatspecMap m_expectedNatspecJSON;
71+
72+
private:
73+
static std::tuple<std::string_view, NatspecJSONKind> parseExpectationHeader(std::string_view _line);
74+
static std::string extractExpectationJSON(std::istream& _stream);
75+
static std::string_view expectLinePrefix(std::string_view _line);
76+
77+
std::string formatNatspecExpectations(NatspecMap const& _expectations) const;
78+
SerializedNatspecMap prettyPrinted(NatspecMap const& _expectations) const;
79+
NatspecMap obtainedNatspec() const;
80+
};
81+
82+
}

0 commit comments

Comments
 (0)