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;
3333using namespace solidity ::util;
3434using 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
0 commit comments