Skip to content

Commit e31bbe9

Browse files
committed
fixup! Migrate ethdebug CLI tests to isolest
1 parent bd3cc61 commit e31bbe9

6 files changed

Lines changed: 70 additions & 58 deletions

File tree

test/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ set(libsolidity_sources
8989
libsolidity/GasTest.h
9090
libsolidity/Imports.cpp
9191
libsolidity/InlineAssembly.cpp
92+
libsolidity/JSONExpectationTest.cpp
93+
libsolidity/JSONExpectationTest.h
9294
libsolidity/LibSolc.cpp
9395
libsolidity/Metadata.cpp
9496
libsolidity/MemoryGuardTest.cpp
@@ -102,8 +104,6 @@ set(libsolidity_sources
102104
libsolidity/SemVerMatcher.cpp
103105
libsolidity/SMTCheckerTest.cpp
104106
libsolidity/SMTCheckerTest.h
105-
libsolidity/StandardJSONTest.cpp
106-
libsolidity/StandardJSONTest.h
107107
libsolidity/SolidityCompiler.cpp
108108
libsolidity/SolidityEndToEndTest.cpp
109109
libsolidity/SolidityExecutionFramework.cpp

test/libsolidity/EthdebugTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ using namespace solidity::frontend::test;
3333
using namespace solidity::langutil;
3434

3535
EthdebugTest::EthdebugTest(std::string const& _filename):
36-
StandardJSONTest(_filename)
36+
JSONExpectationTest(_filename)
3737
{
3838
m_optimise = m_reader.boolSetting("optimize", false);
3939
m_optimiseYul = m_reader.boolSetting("optimize-yul", false);

test/libsolidity/EthdebugTest.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
#pragma once
2020

21-
#include <test/libsolidity/StandardJSONTest.h>
21+
#include <test/libsolidity/JSONExpectationTest.h>
2222

2323
#include <libsolidity/interface/DebugSettings.h>
2424

@@ -42,7 +42,7 @@ namespace solidity::frontend::test
4242
/// - Per contract: `Contract.creation`, `Contract.runtime`, `Contract.contract`.
4343
/// - Source-qualified per contract (when needed to disambiguate same-named
4444
/// contracts in different sources): `source.sol:Contract.creation`, etc.
45-
class EthdebugTest: public StandardJSONTest
45+
class EthdebugTest: public JSONExpectationTest
4646
{
4747
public:
4848
static std::unique_ptr<TestCase> create(Config const& _config)
Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
// SPDX-License-Identifier: GPL-3.0
1818

19-
#include <test/libsolidity/StandardJSONTest.h>
19+
#include <test/libsolidity/JSONExpectationTest.h>
2020
#include <test/libsolidity/util/Common.h>
2121

2222
#include <libsolutil/CommonIO.h>
@@ -33,14 +33,14 @@ using namespace solidity::frontend::test;
3333
using namespace solidity::util;
3434
using namespace std::string_literals;
3535

36-
StandardJSONTest::StandardJSONTest(std::string const& _filename):
36+
JSONExpectationTest::JSONExpectationTest(std::string const& _filename):
3737
EVMVersionRestrictedTestCase(_filename),
3838
m_sources(m_reader.sources())
3939
{
4040
parseExpectations(m_reader.stream());
4141
}
4242

43-
void StandardJSONTest::parseExpectations(std::istream& _stream)
43+
void JSONExpectationTest::parseExpectations(std::istream& _stream)
4444
{
4545
std::string line;
4646
while (getline(_stream, line))
@@ -75,7 +75,10 @@ void StandardJSONTest::parseExpectations(std::istream& _stream)
7575
}
7676
}
7777

78-
std::string StandardJSONTest::extractExpectationJSON(std::istream& _stream)
78+
// TODO: Current implementation has quadratic complexity due to Json::accept()-ing check after every line is consumed.
79+
// It's only an issue for multi line object matching, especially when it comes to very large objects. Should be
80+
// swapped to a custom parser if it becomes problematic performance wise at some point.
81+
std::string JSONExpectationTest::extractExpectationJSON(std::istream& _stream)
7982
{
8083
std::string rawJSON;
8184
std::string line;
@@ -100,7 +103,7 @@ std::string StandardJSONTest::extractExpectationJSON(std::istream& _stream)
100103
));
101104
}
102105

103-
StandardJSONTest::Expectation StandardJSONTest::parseExpectationLine(std::string_view _line)
106+
JSONExpectationTest::Expectation JSONExpectationTest::parseExpectationLine(std::string_view _line)
104107
{
105108
auto const colonPos = _line.find(": ");
106109
if (colonPos == std::string_view::npos)
@@ -116,7 +119,7 @@ StandardJSONTest::Expectation StandardJSONTest::parseExpectationLine(std::string
116119
return result;
117120
}
118121

119-
StandardJSONTest::Expectation StandardJSONTest::parsePathPart(std::string_view _pathPart)
122+
JSONExpectationTest::Expectation JSONExpectationTest::parsePathPart(std::string_view _pathPart)
120123
{
121124
Expectation result;
122125

@@ -131,7 +134,7 @@ StandardJSONTest::Expectation StandardJSONTest::parsePathPart(std::string_view _
131134
return result;
132135
}
133136

134-
std::pair<std::string, std::string> StandardJSONTest::resolveOutputKey(std::string_view _path) const
137+
std::pair<std::string, std::string> JSONExpectationTest::resolveOutputKey(std::string_view _path) const
135138
{
136139
if (_path.empty())
137140
BOOST_THROW_EXCEPTION(std::runtime_error("Empty path in expectation"));
@@ -149,7 +152,7 @@ std::pair<std::string, std::string> StandardJSONTest::resolveOutputKey(std::stri
149152
return {std::string(scope), std::string(jsonPath)};
150153
}
151154

152-
std::pair<std::string_view, std::string_view> StandardJSONTest::splitNonGlobalPath(std::string_view _path) const
155+
std::pair<std::string_view, std::string_view> JSONExpectationTest::splitNonGlobalPath(std::string_view _path) const
153156
{
154157
// Default: first segment is the scope key, the rest is the JSON path.
155158
auto const firstDot = _path.find('.');
@@ -180,41 +183,46 @@ nlohmann::json::json_pointer pathToPointer(std::string_view _path)
180183
}
181184
}
182185

183-
std::optional<Json> StandardJSONTest::resolvePath(Json const& _json, std::string const& _path) const
186+
std::optional<Json> JSONExpectationTest::resolvePath(Json const& _json, std::string const& _path) const
184187
{
185188
auto const ptr = pathToPointer(_path);
186189
if (!_json.contains(ptr))
187190
return std::nullopt;
188191
return _json.at(ptr);
189192
}
190193

191-
Json StandardJSONTest::applyFilter(Json const& _json, std::string const& _filter) const
194+
Json JSONExpectationTest::applyFilter(Json const& _json, std::string const& _filter) const
192195
{
193196
if (_filter == "length")
194197
{
195-
if (_json.is_array())
196-
return Json(static_cast<uint64_t>(_json.size()));
197-
if (_json.is_object())
198+
if (_json.is_array() || _json.is_object())
198199
return Json(static_cast<uint64_t>(_json.size()));
199200
if (_json.is_string())
200201
return Json(static_cast<uint64_t>(_json.get<std::string>().size()));
201-
solAssert(false, "Cannot apply 'length' filter to this JSON type");
202+
BOOST_THROW_EXCEPTION(std::runtime_error(
203+
"'length' filter requires an array, object, or string; got "s + _json.type_name()
204+
));
202205
}
203206
if (_filter == "type")
204207
return Json(_json.type_name());
205208
if (_filter == "keys")
206209
{
207-
solAssert(_json.is_object(), "Cannot apply 'keys' filter to non-object");
210+
if (!_json.is_object())
211+
BOOST_THROW_EXCEPTION(std::runtime_error(
212+
"'keys' filter requires an object; got "s + _json.type_name()
213+
));
208214
Json result = Json::array();
215+
// Json uses std::map for object storage, so items() iterates keys in
216+
// lexicographic order. Tests rely on this for stable output.
209217
for (auto const& [key, _]: _json.items())
210218
result.push_back(key);
211219
return result;
212220
}
213221

214-
solAssert(false, "Unknown filter: " + _filter);
222+
BOOST_THROW_EXCEPTION(std::runtime_error("Unknown filter: " + _filter));
215223
}
216224

217-
std::string StandardJSONTest::formatValue(Json const& _json) const
225+
std::string JSONExpectationTest::formatValue(Json const& _json) const
218226
{
219227
if (_json.is_string())
220228
return _json.get<std::string>();
@@ -223,12 +231,12 @@ std::string StandardJSONTest::formatValue(Json const& _json) const
223231
return _json.dump();
224232
}
225233

226-
bool StandardJSONTest::isPlaceholder(std::string const& _value) const
234+
bool JSONExpectationTest::isPlaceholder(std::string const& _value) const
227235
{
228236
return placeholders().count(_value) > 0;
229237
}
230238

231-
void StandardJSONTest::applyPlaceholders(Json const& _expected, Json& _obtained) const
239+
void JSONExpectationTest::applyPlaceholders(Json const& _expected, Json& _obtained) const
232240
{
233241
if (_expected.type() != _obtained.type())
234242
return;
@@ -251,39 +259,39 @@ void StandardJSONTest::applyPlaceholders(Json const& _expected, Json& _obtained)
251259
}
252260
}
253261

254-
TestCase::TestResult StandardJSONTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted)
262+
TestCase::TestResult JSONExpectationTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted)
255263
{
256264
if (!runFramework(withPreamble(m_sources.sources), PipelineStage::Compilation))
257265
{
258266
printPrefixed(_stream, formatErrors(filteredErrors(false /* _includeWarningsAndInfos */), _formatted), _linePrefix);
259267
return TestResult::FatalError;
260268
}
261269

262-
auto const outputs = collectScopes();
270+
auto const scopeOutputs = collectScopes();
263271
bool allMatch = true;
264272

265273
for (auto const& expectation: m_expectations)
266274
{
267-
auto const [outputKey, jsonPath] = resolveOutputKey(expectation.fullPath);
268-
auto const it = outputs.find(outputKey);
269-
if (it == outputs.end())
275+
auto const [scopeKey, jsonPath] = resolveOutputKey(expectation.fullPath);
276+
auto const scopeIt = scopeOutputs.find(scopeKey);
277+
if (scopeIt == scopeOutputs.end())
270278
{
271279
allMatch = false;
272280
continue;
273281
}
274282

275-
auto const resolved = resolvePath(it->second, jsonPath);
276-
if (!resolved)
283+
auto const actualLookup = resolvePath(scopeIt->second, jsonPath);
284+
if (!actualLookup)
277285
{
278286
allMatch = false;
279287
continue;
280288
}
281289

282-
Json value = *resolved;
290+
Json actualJson = *actualLookup;
283291
if (!expectation.filter.empty())
284-
value = applyFilter(value, expectation.filter);
292+
actualJson = applyFilter(actualJson, expectation.filter);
285293

286-
std::string const obtained = formatValue(value);
294+
std::string const obtained = formatValue(actualJson);
287295

288296
if (expectation.value.find('\n') != std::string::npos)
289297
{
@@ -294,7 +302,7 @@ TestCase::TestResult StandardJSONTest::run(std::ostream& _stream, std::string co
294302
allMatch = false;
295303
continue;
296304
}
297-
Json patchedObtained = value;
305+
Json patchedObtained = actualJson;
298306
applyPlaceholders(expectedJson, patchedObtained);
299307

300308
if (jsonPrint(patchedObtained, {JsonFormat::Pretty, 4}) !=
@@ -303,16 +311,16 @@ TestCase::TestResult StandardJSONTest::run(std::ostream& _stream, std::string co
303311
}
304312
else if (isPlaceholder(expectation.value))
305313
{
306-
if (!value.is_string())
314+
if (!actualJson.is_string())
307315
allMatch = false;
308316
}
309-
else if (value.is_object() || value.is_array())
317+
else if (actualJson.is_object() || actualJson.is_array())
310318
{
311319
// Compound single-line value: parse expected as JSON and compare structurally
312320
Json expectedJson;
313321
std::string errors;
314322
if (!jsonParseStrict(expectation.value, expectedJson, &errors) ||
315-
value != expectedJson)
323+
actualJson != expectedJson)
316324
allMatch = false;
317325
}
318326
else if (obtained != expectation.value)
@@ -329,7 +337,7 @@ TestCase::TestResult StandardJSONTest::run(std::ostream& _stream, std::string co
329337
return TestResult::Failure;
330338
}
331339

332-
void StandardJSONTest::printSource(std::ostream& _stream, std::string const& _linePrefix, bool /*_formatted*/) const
340+
void JSONExpectationTest::printSource(std::ostream& _stream, std::string const& _linePrefix, bool /*_formatted*/) const
333341
{
334342
if (m_sources.sources.empty())
335343
return;
@@ -343,7 +351,7 @@ void StandardJSONTest::printSource(std::ostream& _stream, std::string const& _li
343351
}
344352
}
345353

346-
std::string StandardJSONTest::formatExpectations(std::vector<Expectation> const& _expectations) const
354+
std::string JSONExpectationTest::formatExpectations(std::vector<Expectation> const& _expectations) const
347355
{
348356
std::string output;
349357
for (auto const& exp: _expectations)
@@ -363,7 +371,7 @@ std::string StandardJSONTest::formatExpectations(std::vector<Expectation> const&
363371
return output;
364372
}
365373

366-
void StandardJSONTest::printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const
374+
void JSONExpectationTest::printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const
367375
{
368376
auto const outputs = collectScopes();
369377

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,30 +36,34 @@ namespace solidity::frontend::test
3636
/// Base class for isoltest test cases that drive a Solidity compilation and assert
3737
/// against a JSON output.
3838
///
39-
/// Expectations
40-
/// ---------------
41-
/// Single-line:
42-
/// // path: expected
43-
/// // path | filter: expected
39+
/// Each expectation has the form:
40+
/// // path [| filter]: expected
4441
///
45-
/// Multi-line (for whole objects/arrays):
46-
/// // path:
42+
/// `expected` is a literal value or a JSON literal. When the JSON spans multiple
43+
/// lines, the path ends with `:` and the JSON continues on subsequent `// ` lines.
44+
///
45+
/// Example:
46+
/// // C.instructions[0].offset: 0
47+
/// // C.instructions | length: 3
48+
/// // C.instructions | keys: ["context", "mnemonic", "offset"]
49+
/// // C.compilation:
4750
/// // {
48-
/// // "expected": "json spread across multiple lines"
51+
/// // "compiler": {"name": "solc", "version": "<VERSION>"},
52+
/// // "settings": {"optimizer": {"enabled": false}}
4953
/// // }
5054
///
5155
/// Filters (applied to the resolved value before comparison):
52-
/// - `| length` — array/object/string size as a number
53-
/// - `| type` — JSON type name ("object", "array", "number", …)
54-
/// - `| keys` — list of keys (object only)
56+
/// - `| length` — array/object/string size as a number
57+
/// - `| type` — JSON type name ("object", "array", "number", …)
58+
/// - `| keys` — list of keys (object only); always lexicographically sorted
5559
///
56-
/// Placeholders:
57-
/// - `<VERSION>` (default) at a leaf matches any string. Subclasses may extend
58-
/// the recognized set via `placeholders()`.
59-
class StandardJSONTest: public AnalysisFramework, public EVMVersionRestrictedTestCase
60+
/// Placeholders at string leaves match any string. Subclasses extend the
61+
/// recognized set via `placeholders()`. The placeholder name is descriptive
62+
/// only — content is not validated beyond JSON type.
63+
class JSONExpectationTest: public AnalysisFramework, public EVMVersionRestrictedTestCase
6064
{
6165
public:
62-
StandardJSONTest(std::string const& _filename);
66+
JSONExpectationTest(std::string const& _filename);
6367

6468
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
6569
void printSource(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) const override;

test/tools/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ add_executable(isoltest
4040
../libsolidity/ASTJSONTest.cpp
4141
../libsolidity/ASTPropertyTest.cpp
4242
../libsolidity/FunctionDependencyGraphTest.cpp
43+
../libsolidity/JSONExpectationTest.cpp
4344
../libsolidity/SMTCheckerTest.cpp
44-
../libsolidity/StandardJSONTest.cpp
4545
../libyul/Common.cpp
4646
../libyul/ControlFlowGraphTest.cpp
4747
../libyul/ControlFlowSideEffectsTest.cpp

0 commit comments

Comments
 (0)