mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Added new kind of test: check for specific properties of AST
This commit is contained in:
		
							parent
							
								
									ee21b03e6c
								
							
						
					
					
						commit
						969aea6d33
					
				| @ -144,4 +144,19 @@ bool jsonParseStrict(std::string const& _input, Json::Value& _json, std::string* | ||||
| 	return parse(readerBuilder, _input, _json, _errs); | ||||
| } | ||||
| 
 | ||||
| std::optional<Json::Value> jsonValueByPath(Json::Value const& _node, std::string_view _jsonPath) | ||||
| { | ||||
| 	if (!_node.isObject() || _jsonPath.empty()) | ||||
| 		return {}; | ||||
| 
 | ||||
| 	std::string memberName = std::string(_jsonPath.substr(0, _jsonPath.find_first_of('.'))); | ||||
| 	if (!_node.isMember(memberName)) | ||||
| 		return {}; | ||||
| 
 | ||||
| 	if (memberName == _jsonPath) | ||||
| 		return _node[memberName]; | ||||
| 
 | ||||
| 	return jsonValueByPath(_node[memberName], _jsonPath.substr(memberName.size() + 1)); | ||||
| } | ||||
| 
 | ||||
| } // namespace solidity::util
 | ||||
|  | ||||
| @ -26,6 +26,8 @@ | ||||
| #include <json/json.h> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <optional> | ||||
| 
 | ||||
| namespace solidity::util | ||||
| { | ||||
| @ -67,6 +69,13 @@ std::string jsonPrint(Json::Value const& _input, JsonFormat const& _format); | ||||
| /// \return \c true if the document was successfully parsed, \c false if an error occurred.
 | ||||
| bool jsonParseStrict(std::string const& _input, Json::Value& _json, std::string* _errs = nullptr); | ||||
| 
 | ||||
| /// Retrieves the value specified by @p _jsonPath by from a series of nested JSON dictionaries.
 | ||||
| /// @param _jsonPath A dot-separated series of dictionary keys.
 | ||||
| /// @param _node The node representing the start of the path.
 | ||||
| /// @returns The value of the last key on the path. @a nullptr if any node on the path descends
 | ||||
| /// into something that is not a dictionary or the key is not present.
 | ||||
| std::optional<Json::Value> jsonValueByPath(Json::Value const& _node, std::string_view _jsonPath); | ||||
| 
 | ||||
| namespace detail | ||||
| { | ||||
| 
 | ||||
|  | ||||
| @ -105,6 +105,8 @@ set(libsolidity_sources | ||||
|     libsolidity/ViewPureChecker.cpp | ||||
|     libsolidity/analysis/FunctionCallGraph.cpp | ||||
|     libsolidity/interface/FileReader.cpp | ||||
|     libsolidity/ASTPropertyTest.h | ||||
|     libsolidity/ASTPropertyTest.cpp | ||||
| ) | ||||
| detect_stray_source_files("${libsolidity_sources}" "libsolidity/") | ||||
| 
 | ||||
|  | ||||
| @ -21,6 +21,7 @@ | ||||
| #include <test/TestCase.h> | ||||
| #include <test/libsolidity/ABIJsonTest.h> | ||||
| #include <test/libsolidity/ASTJSONTest.h> | ||||
| #include <test/libsolidity/ASTPropertyTest.h> | ||||
| #include <test/libsolidity/GasTest.h> | ||||
| #include <test/libsolidity/MemoryGuardTest.h> | ||||
| #include <test/libsolidity/SyntaxTest.h> | ||||
| @ -76,7 +77,8 @@ Testsuite const g_interactiveTestsuites[] = { | ||||
| 	{"JSON ABI",               "libsolidity", "ABIJson",               false, false, &ABIJsonTest::create}, | ||||
| 	{"SMT Checker",            "libsolidity", "smtCheckerTests",       true,  false, &SMTCheckerTest::create}, | ||||
| 	{"Gas Estimates",          "libsolidity", "gasTests",              false, false, &GasTest::create}, | ||||
| 	{"Memory Guard Tests",     "libsolidity", "memoryGuardTests",     false, false, &MemoryGuardTest::create}, | ||||
| 	{"Memory Guard",           "libsolidity", "memoryGuardTests",      false, false, &MemoryGuardTest::create}, | ||||
| 	{"AST Properties",         "libsolidity", "astPropertyTests",      false, false, &ASTPropertyTest::create}, | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										204
									
								
								test/libsolidity/ASTPropertyTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								test/libsolidity/ASTPropertyTest.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,204 @@ | ||||
| /*
 | ||||
| 	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
 | ||||
| 
 | ||||
| #include <test/libsolidity/ASTPropertyTest.h> | ||||
| #include <test/Common.h> | ||||
| 
 | ||||
| #include <libsolidity/ast/ASTJsonExporter.h> | ||||
| #include <libsolidity/interface/CompilerStack.h> | ||||
| 
 | ||||
| #include <liblangutil/Common.h> | ||||
| #include <liblangutil/SourceReferenceFormatter.h> | ||||
| #include <libsolutil/JSON.h> | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include <boost/throw_exception.hpp> | ||||
| 
 | ||||
| #include <range/v3/algorithm/find_if.hpp> | ||||
| #include <range/v3/range/conversion.hpp> | ||||
| #include <range/v3/view/split.hpp> | ||||
| 
 | ||||
| #include <queue> | ||||
| 
 | ||||
| using namespace solidity::util; | ||||
| using namespace solidity::langutil; | ||||
| using namespace solidity::frontend; | ||||
| using namespace solidity::frontend::test; | ||||
| using namespace solidity; | ||||
| using namespace std; | ||||
| 
 | ||||
| ASTPropertyTest::ASTPropertyTest(string const& _filename): | ||||
| 	TestCase(_filename) | ||||
| { | ||||
| 	if (!boost::algorithm::ends_with(_filename, ".sol")) | ||||
| 		BOOST_THROW_EXCEPTION(runtime_error("Not a Solidity file: \"" + _filename + "\".")); | ||||
| 
 | ||||
| 	m_source = m_reader.source(); | ||||
| 	readExpectations(); | ||||
| 	soltestAssert(m_tests.size() > 0, "No tests specified in " + _filename); | ||||
| } | ||||
| 
 | ||||
| string ASTPropertyTest::formatExpectations(bool _obtainedResult) | ||||
| { | ||||
| 	string expectations; | ||||
| 	for (string const& testId: m_testOrder) | ||||
| 	{ | ||||
| 		soltestAssert(m_tests.count(testId) > 0); | ||||
| 		expectations += | ||||
| 			testId + | ||||
| 			": " + | ||||
| 			(_obtainedResult ? m_tests[testId].obtainedValue : m_tests[testId].expectedValue) | ||||
| 			+ "\n"; | ||||
| 	} | ||||
| 	return expectations; | ||||
| } | ||||
| 
 | ||||
| vector<StringPair> ASTPropertyTest::readKeyValuePairs(string const& _input) | ||||
| { | ||||
| 	vector<StringPair> result; | ||||
| 	for (string line: _input | ranges::views::split('\n') | ranges::to<vector<string>>) | ||||
| 	{ | ||||
| 		boost::trim(line); | ||||
| 		if (line.empty()) | ||||
| 			continue; | ||||
| 
 | ||||
| 		soltestAssert( | ||||
| 			ranges::all_of(line, [](char c) { return isprint(c); }), | ||||
| 			"Non-printable character(s) found in property test: " + line | ||||
| 		); | ||||
| 
 | ||||
| 		auto colonPosition = line.find_first_of(':'); | ||||
| 		soltestAssert(colonPosition != string::npos, "Property test is missing a colon: " + line); | ||||
| 
 | ||||
| 		StringPair pair{ | ||||
| 			boost::trim_copy(line.substr(0, colonPosition)), | ||||
| 			boost::trim_copy(line.substr(colonPosition + 1)) | ||||
| 		}; | ||||
| 		soltestAssert(!get<0>(pair).empty() != false, "Empty key in property test: " + line); | ||||
| 		soltestAssert(!get<1>(pair).empty() != false, "Empty value in property test: " + line); | ||||
| 
 | ||||
| 		result.push_back(pair); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| void ASTPropertyTest::readExpectations() | ||||
| { | ||||
| 	for (auto const& [testId, testExpectation]: readKeyValuePairs(m_reader.simpleExpectations())) | ||||
| 	{ | ||||
| 		soltestAssert(m_tests.count(testId) == 0, "More than one expectation for test \"" + testId + "\""); | ||||
| 		m_tests.emplace(testId, Test{"", testExpectation, ""}); | ||||
| 		m_testOrder.push_back(testId); | ||||
| 	} | ||||
| 	m_expectation = formatExpectations(false /* _obtainedResult */); | ||||
| } | ||||
| 
 | ||||
| void ASTPropertyTest::extractTestsFromAST(Json::Value const& _astJson) | ||||
| { | ||||
| 	queue<Json::Value> nodesToVisit; | ||||
| 	nodesToVisit.push(_astJson); | ||||
| 
 | ||||
| 	while (!nodesToVisit.empty()) | ||||
| 	{ | ||||
| 		Json::Value& node = nodesToVisit.front(); | ||||
| 
 | ||||
| 		if (node.isArray()) | ||||
| 			for (auto&& member: node) | ||||
| 				nodesToVisit.push(member); | ||||
| 		else if (node.isObject()) | ||||
| 			for (string const& memberName: node.getMemberNames()) | ||||
| 			{ | ||||
| 				if (memberName != "documentation") | ||||
| 				{ | ||||
| 					nodesToVisit.push(node[memberName]); | ||||
| 					continue; | ||||
| 				} | ||||
| 
 | ||||
| 				string nodeDocstring = node["documentation"].isObject() ? | ||||
| 					node["documentation"]["text"].asString() : | ||||
| 					node["documentation"].asString(); | ||||
| 				soltestAssert(!nodeDocstring.empty()); | ||||
| 
 | ||||
| 				vector<StringPair> pairs = readKeyValuePairs(nodeDocstring); | ||||
| 				if (pairs.empty()) | ||||
| 					continue; | ||||
| 
 | ||||
| 				for (auto const& [testId, testedProperty]: pairs) | ||||
| 				{ | ||||
| 					soltestAssert( | ||||
| 						m_tests.count(testId) > 0, | ||||
| 						"Test \"" + testId + "\" does not have a corresponding expected value." | ||||
| 					); | ||||
| 					soltestAssert( | ||||
| 						m_tests[testId].property.empty(), | ||||
| 						"Test \"" + testId + "\" was already defined before." | ||||
| 					); | ||||
| 					m_tests[testId].property = testedProperty; | ||||
| 
 | ||||
| 					soltestAssert(node.isMember("nodeType")); | ||||
| 					optional<Json::Value> propertyNode = jsonValueByPath(node, testedProperty); | ||||
| 					soltestAssert( | ||||
| 						propertyNode.has_value(), | ||||
| 						node["nodeType"].asString() + " node does not have a property named \""s + testedProperty + "\"" | ||||
| 					); | ||||
| 					soltestAssert( | ||||
| 						!propertyNode->isObject() && !propertyNode->isArray(), | ||||
| 						"Property \"" + testedProperty + "\" is an object or an array." | ||||
| 					); | ||||
| 					m_tests[testId].obtainedValue = propertyNode->asString(); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 		nodesToVisit.pop(); | ||||
| 	} | ||||
| 
 | ||||
| 	auto firstTestWithoutProperty = ranges::find_if( | ||||
| 		m_tests, | ||||
| 		[&](auto const& _testCase) { return _testCase.second.property.empty(); } | ||||
| 	); | ||||
| 	soltestAssert( | ||||
| 		firstTestWithoutProperty == ranges::end(m_tests), | ||||
| 		"AST property not defined for test \"" + firstTestWithoutProperty->first + "\"" | ||||
| 	); | ||||
| 
 | ||||
| 	m_obtainedResult = formatExpectations(true /* _obtainedResult */); | ||||
| } | ||||
| 
 | ||||
| TestCase::TestResult ASTPropertyTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) | ||||
| { | ||||
| 	CompilerStack compiler; | ||||
| 
 | ||||
| 	compiler.setSources({{ | ||||
| 		"A", | ||||
| 		"pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n" + m_source | ||||
| 	}}); | ||||
| 	compiler.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); | ||||
| 	compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize); | ||||
| 	if (!compiler.parseAndAnalyze()) | ||||
| 		BOOST_THROW_EXCEPTION(runtime_error( | ||||
| 			"Parsing contract failed" + | ||||
| 			SourceReferenceFormatter::formatErrorInformation(compiler.errors(), compiler, _formatted) | ||||
| 		)); | ||||
| 
 | ||||
| 	Json::Value astJson = ASTJsonExporter(compiler.state()).toJson(compiler.ast("A")); | ||||
| 	soltestAssert(astJson); | ||||
| 
 | ||||
| 	extractTestsFromAST(astJson); | ||||
| 
 | ||||
| 	return checkResult(_stream, _linePrefix, _formatted); | ||||
| } | ||||
							
								
								
									
										66
									
								
								test/libsolidity/ASTPropertyTest.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								test/libsolidity/ASTPropertyTest.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| /*
 | ||||
| 	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
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <test/TestCase.h> | ||||
| 
 | ||||
| #include <libsolutil/JSON.h> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace solidity::frontend | ||||
| { | ||||
| class CompilerStack; | ||||
| } | ||||
| 
 | ||||
| namespace solidity::frontend::test | ||||
| { | ||||
| 
 | ||||
| using StringPair = std::pair<std::string, std::string>; | ||||
| 
 | ||||
| class ASTPropertyTest: public TestCase | ||||
| { | ||||
| public: | ||||
| 	static std::unique_ptr<TestCase> create(Config const& _config) | ||||
| 	{ | ||||
| 		return std::make_unique<ASTPropertyTest>(_config.filename); | ||||
| 	} | ||||
| 	ASTPropertyTest(std::string const& _filename); | ||||
| 
 | ||||
| 	TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; | ||||
| 
 | ||||
| private: | ||||
| 	struct Test | ||||
| 	{ | ||||
| 		std::string property; | ||||
| 		std::string expectedValue; | ||||
| 		std::string obtainedValue; | ||||
| 	}; | ||||
| 
 | ||||
| 	void readExpectations(); | ||||
| 	std::vector<StringPair> readKeyValuePairs(std::string const& _input); | ||||
| 	void extractTestsFromAST(Json::Value const& _astJson); | ||||
| 	std::string formatExpectations(bool _obtainedResult = true); | ||||
| 
 | ||||
| 	std::vector<std::string> m_testOrder; | ||||
| 	std::map<std::string, Test> m_tests; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										9
									
								
								test/libsolidity/astPropertyTests/blank_test_case.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								test/libsolidity/astPropertyTests/blank_test_case.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| /// TestCase1: name | ||||
| contract C { | ||||
|     /// | ||||
|     /// | ||||
|     function f() public pure { } | ||||
| } | ||||
| // ---- | ||||
| // TestCase1: C | ||||
| // | ||||
| @ -0,0 +1,13 @@ | ||||
| contract C { | ||||
|     function f() public pure { | ||||
|         /// TestCase1: condition.operator | ||||
|         /// TestCase2: initializationExpression.initialValue.value | ||||
|         /// TestCase3: loopExpression.expression.subExpression.name | ||||
|         for(uint i = 1; i < 42; i++) { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // TestCase1: < | ||||
| // TestCase2: 1 | ||||
| // TestCase3: i | ||||
| @ -0,0 +1,11 @@ | ||||
| contract C { | ||||
|     /// TestCase1: name | ||||
|     /// TestCase2: functionSelector | ||||
|     /// TestCase3: visibility | ||||
|     function singleFunction() public pure { | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // TestCase1: singleFunction | ||||
| // TestCase2: 3d33252c | ||||
| // TestCase3: public | ||||
							
								
								
									
										17
									
								
								test/libsolidity/astPropertyTests/nested_properties.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/libsolidity/astPropertyTests/nested_properties.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| contract C { | ||||
|     function f() public pure { | ||||
|         /// TestCase1: condition.operator | ||||
|         for(uint i = 0; i < 42; ++i) { | ||||
|         } | ||||
|         /// TestCase2: initializationExpression.initialValue.value | ||||
|         for(uint i = 1; i < 42; i = i * 2) { | ||||
|         } | ||||
|         /// TestCase3: loopExpression.expression.subExpression.name | ||||
|         for(uint i = 0; i < 42; i++) { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // TestCase1: < | ||||
| // TestCase2: 1 | ||||
| // TestCase3: i | ||||
| @ -0,0 +1,9 @@ | ||||
| /// TestCase1: nameLocation | ||||
| /// TestCase2: src | ||||
| contract C { | ||||
|     function f() public pure { | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // TestCase1: 115:1:-1 | ||||
| // TestCase2: 106:51:-1 | ||||
							
								
								
									
										12
									
								
								test/libsolidity/astPropertyTests/simple_properties.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								test/libsolidity/astPropertyTests/simple_properties.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| /// TestContractC: name | ||||
| contract C { | ||||
|     /// TestStateVarX: stateVariable | ||||
|     uint x; | ||||
|     /// TestFunctionF: visibility | ||||
|     function f() public pure { | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // TestContractC: C | ||||
| // TestStateVarX: true | ||||
| // TestFunctionF: public | ||||
| @ -32,6 +32,7 @@ add_executable(isoltest | ||||
| 	../ExecutionFramework.cpp | ||||
| 	../libsolidity/ABIJsonTest.cpp | ||||
| 	../libsolidity/ASTJSONTest.cpp | ||||
| 	../libsolidity/ASTPropertyTest.cpp | ||||
| 	../libsolidity/SMTCheckerTest.cpp | ||||
| 	../libyul/Common.cpp | ||||
| 	../libyul/ControlFlowGraphTest.cpp | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user