mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Implements a test file parser.
This commit is contained in:
		
							parent
							
								
									42240a69e9
								
							
						
					
					
						commit
						f90c6f57bb
					
				| @ -21,6 +21,7 @@ Bugfixes: | |||||||
| Build System: | Build System: | ||||||
|  * Add support for continuous fuzzing via Google oss-fuzz |  * Add support for continuous fuzzing via Google oss-fuzz | ||||||
|  * Ubuntu PPA Packages: Use CVC4 as SMT solver instead of Z3 |  * Ubuntu PPA Packages: Use CVC4 as SMT solver instead of Z3 | ||||||
|  |  * Soltest: Add parser that is used in the file-based unit test environment. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ### 0.5.3 (2019-01-22) | ### 0.5.3 (2019-01-22) | ||||||
|  | |||||||
| @ -17,6 +17,8 @@ if (LLL) | |||||||
| endif() | endif() | ||||||
| file(GLOB libsolidity_sources "libsolidity/*.cpp") | file(GLOB libsolidity_sources "libsolidity/*.cpp") | ||||||
| file(GLOB libsolidity_headers "libsolidity/*.h") | file(GLOB libsolidity_headers "libsolidity/*.h") | ||||||
|  | file(GLOB libsolidity_util_sources "libsolidity/util/*.cpp") | ||||||
|  | file(GLOB libsolidity_util_headers "libsolidity/util/*.h") | ||||||
| 
 | 
 | ||||||
| add_executable(soltest ${sources} ${headers} | add_executable(soltest ${sources} ${headers} | ||||||
|     ${contracts_sources} ${contracts_headers} |     ${contracts_sources} ${contracts_headers} | ||||||
| @ -26,6 +28,7 @@ add_executable(soltest ${sources} ${headers} | |||||||
|     ${libyul_sources} ${libyul_headers} |     ${libyul_sources} ${libyul_headers} | ||||||
|     ${liblll_sources} ${liblll_headers} |     ${liblll_sources} ${liblll_headers} | ||||||
|     ${libsolidity_sources} ${libsolidity_headers} |     ${libsolidity_sources} ${libsolidity_headers} | ||||||
|  |     ${libsolidity_util_sources} ${libsolidity_util_headers} | ||||||
| ) | ) | ||||||
| target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) | target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										376
									
								
								test/libsolidity/util/TestFileParser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										376
									
								
								test/libsolidity/util/TestFileParser.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,376 @@ | |||||||
|  | /*
 | ||||||
|  | 	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/>.
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | #include <test/libsolidity/util/TestFileParser.h> | ||||||
|  | 
 | ||||||
|  | #include <test/Options.h> | ||||||
|  | #include <boost/algorithm/string.hpp> | ||||||
|  | #include <boost/algorithm/string/predicate.hpp> | ||||||
|  | #include <boost/optional.hpp> | ||||||
|  | #include <boost/throw_exception.hpp> | ||||||
|  | #include <fstream> | ||||||
|  | #include <memory> | ||||||
|  | #include <stdexcept> | ||||||
|  | 
 | ||||||
|  | using namespace dev; | ||||||
|  | using namespace langutil; | ||||||
|  | using namespace solidity; | ||||||
|  | using namespace dev::solidity::test; | ||||||
|  | using namespace std; | ||||||
|  | 
 | ||||||
|  | namespace | ||||||
|  | { | ||||||
|  | 	bool isDecimalDigit(char c) | ||||||
|  | 	{ | ||||||
|  | 		return '0' <= c && c <= '9'; | ||||||
|  | 	} | ||||||
|  | 	bool isWhiteSpace(char c) | ||||||
|  | 	{ | ||||||
|  | 		return c == ' ' || c == '\n' || c == '\t' || c == '\r'; | ||||||
|  | 	} | ||||||
|  | 	bool isIdentifierStart(char c) | ||||||
|  | 	{ | ||||||
|  | 		return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); | ||||||
|  | 	} | ||||||
|  | 	bool isIdentifierPart(char c) | ||||||
|  | 	{ | ||||||
|  | 		return isIdentifierStart(c) || isDecimalDigit(c); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls() | ||||||
|  | { | ||||||
|  | 	vector<FunctionCall> calls; | ||||||
|  | 	if (!accept(SoltToken::EOS)) | ||||||
|  | 	{ | ||||||
|  | 		// TODO: check initial token state
 | ||||||
|  | 		expect(SoltToken::Unknown); | ||||||
|  | 		while (!accept(SoltToken::EOS)) | ||||||
|  | 		{ | ||||||
|  | 			if (!accept(SoltToken::Whitespace)) | ||||||
|  | 			{ | ||||||
|  | 				FunctionCall call; | ||||||
|  | 
 | ||||||
|  | 				// f()
 | ||||||
|  | 				expect(SoltToken::Newline); | ||||||
|  | 				call.signature = parseFunctionSignature(); | ||||||
|  | 
 | ||||||
|  | 				// f(), 314 ether
 | ||||||
|  | 				if (accept(SoltToken::Comma, true)) | ||||||
|  | 					call.value = parseFunctionCallValue(); | ||||||
|  | 
 | ||||||
|  | 				// f(), 314 ether: 1, 1
 | ||||||
|  | 				if (accept(SoltToken::Colon, true)) | ||||||
|  | 					call.arguments = parseFunctionCallArguments(); | ||||||
|  | 
 | ||||||
|  | 				string comment = m_scanner.currentLiteral(); | ||||||
|  | 				if (accept(SoltToken::Comment, true)) | ||||||
|  | 					call.arguments.comment = comment; | ||||||
|  | 
 | ||||||
|  | 				// -> 1
 | ||||||
|  | 				expect(SoltToken::Newline); | ||||||
|  | 				expect(SoltToken::Arrow); | ||||||
|  | 				if (m_scanner.peekToken() != SoltToken::Newline) | ||||||
|  | 				{ | ||||||
|  | 					call.expectations = parseFunctionCallExpectations(); | ||||||
|  | 
 | ||||||
|  | 					string comment = m_scanner.currentLiteral(); | ||||||
|  | 					if (accept(SoltToken::Comment, true)) | ||||||
|  | 						call.expectations.comment = comment; | ||||||
|  | 				} | ||||||
|  | 				calls.emplace_back(std::move(call)); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 				m_scanner.scanNextToken(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return calls; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string TestFileParser::formatToken(SoltToken _token) | ||||||
|  | { | ||||||
|  | 	switch (_token) | ||||||
|  | 	{ | ||||||
|  | #define T(name, string, precedence) case SoltToken::name: return string; | ||||||
|  | 		SOLT_TOKEN_LIST(T, T) | ||||||
|  | #undef T | ||||||
|  | 		default: // Token::NUM_TOKENS:
 | ||||||
|  | 			return ""; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool TestFileParser::accept(SoltToken _token, bool const _expect) | ||||||
|  | { | ||||||
|  | 	if (m_scanner.currentToken() == _token) | ||||||
|  | 	{ | ||||||
|  | 		if (_expect) | ||||||
|  | 			expect(_token); | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool TestFileParser::expect(SoltToken _token, bool const _advance) | ||||||
|  | { | ||||||
|  | 	if (m_scanner.currentToken() != _token) | ||||||
|  | 		throw Error(Error::Type::ParserError, | ||||||
|  | 					"Unexpected " + formatToken(m_scanner.currentToken()) + ": \"" + | ||||||
|  | 					m_scanner.currentLiteral() + "\". " + | ||||||
|  | 					"Expected \"" + formatToken(_token) + "\"." | ||||||
|  | 					); | ||||||
|  | 	if (_advance) | ||||||
|  | 		m_scanner.scanNextToken(); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string TestFileParser::parseFunctionSignature() | ||||||
|  | { | ||||||
|  | 	string signature = m_scanner.currentLiteral(); | ||||||
|  | 	expect(SoltToken::Identifier); | ||||||
|  | 
 | ||||||
|  | 	signature += formatToken(SoltToken::LParen); | ||||||
|  | 	expect(SoltToken::LParen); | ||||||
|  | 
 | ||||||
|  | 	while (!accept(SoltToken::RParen)) | ||||||
|  | 	{ | ||||||
|  | 		signature += m_scanner.currentLiteral(); | ||||||
|  | 		expect(SoltToken::UInt); | ||||||
|  | 		while (accept(SoltToken::Comma)) | ||||||
|  | 		{ | ||||||
|  | 			signature += m_scanner.currentLiteral(); | ||||||
|  | 			expect(SoltToken::Comma); | ||||||
|  | 			signature += m_scanner.currentLiteral(); | ||||||
|  | 			expect(SoltToken::UInt); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	signature += formatToken(SoltToken::RParen); | ||||||
|  | 	expect(SoltToken::RParen); | ||||||
|  | 	return signature; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u256 TestFileParser::parseFunctionCallValue() | ||||||
|  | { | ||||||
|  | 	u256 value; | ||||||
|  | 	string literal = m_scanner.currentLiteral(); | ||||||
|  | 	expect(SoltToken::Number); | ||||||
|  | 	value = convertNumber(literal); | ||||||
|  | 	expect(SoltToken::Ether); | ||||||
|  | 	return value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FunctionCallArgs TestFileParser::parseFunctionCallArguments() | ||||||
|  | { | ||||||
|  | 	FunctionCallArgs arguments; | ||||||
|  | 
 | ||||||
|  | 	auto formattedBytes = parseABITypeLiteral(); | ||||||
|  | 	arguments.rawBytes += formattedBytes.first; | ||||||
|  | 	arguments.formats.emplace_back(std::move(formattedBytes.second)); | ||||||
|  | 	while (accept(SoltToken::Comma, true)) | ||||||
|  | 	{ | ||||||
|  | 		auto formattedBytes = parseABITypeLiteral(); | ||||||
|  | 		arguments.rawBytes += formattedBytes.first; | ||||||
|  | 		arguments.formats.emplace_back(std::move(formattedBytes.second)); | ||||||
|  | 	} | ||||||
|  | 	return arguments; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FunctionCallExpectations TestFileParser::parseFunctionCallExpectations() | ||||||
|  | { | ||||||
|  | 	FunctionCallExpectations expectations; | ||||||
|  | 	string token = m_scanner.currentLiteral(); | ||||||
|  | 
 | ||||||
|  | 	if (accept(SoltToken::Failure, true)) | ||||||
|  | 		expectations.status = false; | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		auto formattedBytes = parseABITypeLiteral(); | ||||||
|  | 		expectations.rawBytes += formattedBytes.first; | ||||||
|  | 		expectations.formats.emplace_back(std::move(formattedBytes.second)); | ||||||
|  | 
 | ||||||
|  | 		while (accept(SoltToken::Comma, true)) | ||||||
|  | 		{ | ||||||
|  | 			auto formattedBytes = parseABITypeLiteral(); | ||||||
|  | 			expectations.rawBytes += formattedBytes.first; | ||||||
|  | 			expectations.formats.emplace_back(std::move(formattedBytes.second)); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return expectations; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pair<bytes, ABIType> TestFileParser::parseABITypeLiteral() | ||||||
|  | { | ||||||
|  | 	try | ||||||
|  | 	{ | ||||||
|  | 		u256 number; | ||||||
|  | 		ABIType abiType; | ||||||
|  | 		if (accept(SoltToken::Sub)) | ||||||
|  | 		{ | ||||||
|  | 			abiType.type = ABIType::Type::SignedDec; | ||||||
|  | 			abiType.size = 32; | ||||||
|  | 
 | ||||||
|  | 			expect(SoltToken::Sub); | ||||||
|  | 			number = convertNumber(parseNumber()) * -1; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 			if (accept(SoltToken::Number)) | ||||||
|  | 			{ | ||||||
|  | 				abiType.type = ABIType::Type::UnsignedDec; | ||||||
|  | 				abiType.size = 32; | ||||||
|  | 
 | ||||||
|  | 				number = convertNumber(parseNumber()); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		return make_pair(toBigEndian(number), abiType); | ||||||
|  | 	} | ||||||
|  | 	catch (std::exception const&) | ||||||
|  | 	{ | ||||||
|  | 		throw Error(Error::Type::ParserError, "Number encoding invalid."); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string TestFileParser::parseNumber() | ||||||
|  | { | ||||||
|  | 	string literal = m_scanner.currentLiteral(); | ||||||
|  | 	expect(SoltToken::Number); | ||||||
|  | 	return literal; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u256 TestFileParser::convertNumber(string const& _literal) | ||||||
|  | { | ||||||
|  | 	try { | ||||||
|  | 		return u256{_literal}; | ||||||
|  | 	} | ||||||
|  | 	catch (std::exception const&) | ||||||
|  | 	{ | ||||||
|  | 		throw Error(Error::Type::ParserError, "Number encoding invalid."); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void TestFileParser::Scanner::readStream(istream& _stream) | ||||||
|  | { | ||||||
|  | 	std::string line; | ||||||
|  | 	while (std::getline(_stream, line)) | ||||||
|  | 		m_line += line; | ||||||
|  | 	m_char = m_line.begin(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void TestFileParser::Scanner::scanNextToken() | ||||||
|  | { | ||||||
|  | 	auto detectToken = [](std::string const& _literal = "") -> TokenDesc { | ||||||
|  | 		if (_literal == "ether") return TokenDesc{SoltToken::Ether, _literal}; | ||||||
|  | 		if (_literal == "uint256") return TokenDesc{SoltToken::UInt, _literal}; | ||||||
|  | 		if (_literal == "FAILURE") return TokenDesc{SoltToken::Failure, _literal}; | ||||||
|  | 		return TokenDesc{SoltToken::Identifier, _literal}; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	auto selectToken = [this](SoltToken _token, std::string const& _literal = "") -> TokenDesc { | ||||||
|  | 		advance(); | ||||||
|  | 		return make_pair(_token, !_literal.empty() ? _literal : formatToken(_token)); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	TokenDesc token = make_pair(SoltToken::Unknown, ""); | ||||||
|  | 	do { | ||||||
|  | 		switch(current()) | ||||||
|  | 		{ | ||||||
|  | 		case '/': | ||||||
|  | 			advance(); | ||||||
|  | 			if (current() == '/') | ||||||
|  | 				token = selectToken(SoltToken::Newline); | ||||||
|  | 			break; | ||||||
|  | 		case '-': | ||||||
|  | 			if (peek() == '>') | ||||||
|  | 			{ | ||||||
|  | 				advance(); | ||||||
|  | 				token = selectToken(SoltToken::Arrow); | ||||||
|  | 			} | ||||||
|  | 			else | ||||||
|  | 				token = selectToken(SoltToken::Sub); | ||||||
|  | 			break; | ||||||
|  | 		case ':': | ||||||
|  | 			token = selectToken(SoltToken::Colon); | ||||||
|  | 			break; | ||||||
|  | 		case '#': | ||||||
|  | 			token = selectToken(SoltToken::Comment, scanComment()); | ||||||
|  | 			break; | ||||||
|  | 		case ',': | ||||||
|  | 			token = selectToken(SoltToken::Comma); | ||||||
|  | 			break; | ||||||
|  | 		case '(': | ||||||
|  | 			token = selectToken(SoltToken::LParen); | ||||||
|  | 			break; | ||||||
|  | 		case ')': | ||||||
|  | 			token = selectToken(SoltToken::RParen); | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			if (isIdentifierStart(current())) | ||||||
|  | 			{ | ||||||
|  | 				TokenDesc detectedToken = detectToken(scanIdentifierOrKeyword()); | ||||||
|  | 				token = selectToken(detectedToken.first, detectedToken.second); | ||||||
|  | 			} | ||||||
|  | 			else if (isDecimalDigit(current())) | ||||||
|  | 				token = selectToken(SoltToken::Number, scanNumber()); | ||||||
|  | 			else if (isWhiteSpace(current())) | ||||||
|  | 				token = selectToken(SoltToken::Whitespace); | ||||||
|  | 			else if (isEndOfLine()) | ||||||
|  | 				token = selectToken(SoltToken::EOS); | ||||||
|  | 			else | ||||||
|  | 				token = selectToken(SoltToken::Invalid); | ||||||
|  | 			break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	while (token.first == SoltToken::Whitespace); | ||||||
|  | 
 | ||||||
|  | 	m_nextToken = token; | ||||||
|  | 	m_currentToken = token; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string TestFileParser::Scanner::scanComment() | ||||||
|  | { | ||||||
|  | 	string comment; | ||||||
|  | 	advance(); | ||||||
|  | 
 | ||||||
|  | 	while (current() != '#') | ||||||
|  | 	{ | ||||||
|  | 		comment += current(); | ||||||
|  | 		advance(); | ||||||
|  | 	} | ||||||
|  | 	return comment; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string TestFileParser::Scanner::scanIdentifierOrKeyword() | ||||||
|  | { | ||||||
|  | 	string identifier; | ||||||
|  | 	identifier += current(); | ||||||
|  | 	while (isIdentifierPart(peek())) | ||||||
|  | 	{ | ||||||
|  | 		advance(); | ||||||
|  | 		identifier += current(); | ||||||
|  | 	} | ||||||
|  | 	return identifier; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | string TestFileParser::Scanner::scanNumber() | ||||||
|  | { | ||||||
|  | 	string number; | ||||||
|  | 	number += current(); | ||||||
|  | 	while (isDecimalDigit(peek())) | ||||||
|  | 	{ | ||||||
|  | 		advance(); | ||||||
|  | 		number += current(); | ||||||
|  | 	} | ||||||
|  | 	return number; | ||||||
|  | } | ||||||
							
								
								
									
										277
									
								
								test/libsolidity/util/TestFileParser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								test/libsolidity/util/TestFileParser.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,277 @@ | |||||||
|  | /*
 | ||||||
|  | 	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/>.
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <libdevcore/CommonData.h> | ||||||
|  | #include <libsolidity/ast/Types.h> | ||||||
|  | #include <liblangutil/Exceptions.h> | ||||||
|  | 
 | ||||||
|  | #include <iosfwd> | ||||||
|  | #include <stdexcept> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  | #include <utility> | ||||||
|  | 
 | ||||||
|  | namespace dev | ||||||
|  | { | ||||||
|  | namespace solidity | ||||||
|  | { | ||||||
|  | namespace test | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * All SOLT (or SOLTest) tokens. | ||||||
|  |  */ | ||||||
|  | #define SOLT_TOKEN_LIST(T, K)      \ | ||||||
|  | 	T(Unknown, "unknown", 0)       \ | ||||||
|  | 	T(Invalid, "invalid", 0)       \ | ||||||
|  | 	T(EOS, "EOS", 0)               \ | ||||||
|  | 	T(Whitespace, "_", 0)          \ | ||||||
|  | 	/* punctuations */             \ | ||||||
|  | 	T(LParen, "(", 0)              \ | ||||||
|  | 	T(RParen, ")", 0)              \ | ||||||
|  | 	T(LBrack, "[", 0)              \ | ||||||
|  | 	T(RBrack, "]", 0)              \ | ||||||
|  | 	T(LBrace, "{", 0)              \ | ||||||
|  | 	T(RBrace, "}", 0)              \ | ||||||
|  | 	T(Sub,    "-", 0)              \ | ||||||
|  | 	T(Colon,  ":", 0)              \ | ||||||
|  | 	T(Comma,  ",", 0)              \ | ||||||
|  | 	T(Period, ".", 0)              \ | ||||||
|  | 	T(Arrow, "->", 0)              \ | ||||||
|  | 	T(Newline, "//", 0)            \ | ||||||
|  | 	/* Literals & identifier */    \ | ||||||
|  | 	T(Comment, "comment", 0)       \ | ||||||
|  | 	T(Number, "number", 0)         \ | ||||||
|  | 	T(Identifier, "identifier", 0) \ | ||||||
|  | 	/* type keywords */            \ | ||||||
|  | 	K(Ether, "ether", 0)           \ | ||||||
|  | 	K(UInt, "uint256", 0)          \ | ||||||
|  | 	/* special keywords */         \ | ||||||
|  | 	K(Failure, "FAILURE", 0)       \ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | enum class SoltToken : unsigned int { | ||||||
|  | #define T(name, string, precedence) name, | ||||||
|  | 	SOLT_TOKEN_LIST(T, T) | ||||||
|  | 	NUM_TOKENS | ||||||
|  | #undef T | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * The purpose of the ABI type is the storage of type information | ||||||
|  |  * retrieved while parsing a test. This information is used | ||||||
|  |  * for the conversion of human-readable function arguments and | ||||||
|  |  * return values to `bytes` and vice-versa. | ||||||
|  |  * Defaults to an invalid 0-byte representation. | ||||||
|  |  */ | ||||||
|  | struct ABIType | ||||||
|  | { | ||||||
|  | 	enum Type { | ||||||
|  | 		UnsignedDec, | ||||||
|  | 		SignedDec, | ||||||
|  | 		Invalid | ||||||
|  | 	}; | ||||||
|  | 	ABIType(): type(ABIType::Invalid), size(0) { } | ||||||
|  | 	ABIType(Type _type, size_t _size): type(_type), size(_size) { } | ||||||
|  | 
 | ||||||
|  | 	Type type; | ||||||
|  | 	size_t size; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | using ABITypeList = std::vector<ABIType>; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Represents the expected result of a function call after it has been executed. This may be a single | ||||||
|  |  * return value or a comma-separated list of return values. It also contains the detected input | ||||||
|  |  * formats used to convert the values to `bytes` needed for the comparison with the actual result | ||||||
|  |  * of a call. In addition to that, it also stores the expected transaction status. | ||||||
|  |  * An optional comment can be assigned. | ||||||
|  |  */ | ||||||
|  | struct FunctionCallExpectations | ||||||
|  | { | ||||||
|  | 	/// ABI encoded `bytes` of parsed expectations. This `bytes`
 | ||||||
|  | 	/// is compared to the actual result of a function call
 | ||||||
|  | 	/// and is taken into account while validating it.
 | ||||||
|  | 	bytes rawBytes; | ||||||
|  | 	/// Types that were used to encode `rawBytes`. Expectations
 | ||||||
|  | 	/// are usually comma seperated literals. Their type is auto-
 | ||||||
|  | 	/// detected and retained in order to format them later on.
 | ||||||
|  | 	ABITypeList formats; | ||||||
|  | 	/// Expected status of the transaction. It can be either
 | ||||||
|  | 	/// a REVERT or a different EVM failure (e.g. out-of-gas).
 | ||||||
|  | 	bool status = true; | ||||||
|  | 	/// A Comment that can be attached to the expectations,
 | ||||||
|  | 	/// that is retained and can be displayed.
 | ||||||
|  | 	std::string comment; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Represents the arguments passed to a function call. This can be a single | ||||||
|  |  * argument or a comma-separated list of arguments. It also contains the detected input | ||||||
|  |  * formats used to convert the arguments to `bytes` needed for the call. | ||||||
|  |  * An optional comment can be assigned. | ||||||
|  |  */ | ||||||
|  | struct FunctionCallArgs | ||||||
|  | { | ||||||
|  | 	/// ABI encoded `bytes` of parsed parameters. This `bytes`
 | ||||||
|  | 	/// passed to the function call.
 | ||||||
|  | 	bytes rawBytes; | ||||||
|  | 	/// Types that were used to encode `rawBytes`. Parameters
 | ||||||
|  | 	/// are usually comma seperated literals. Their type is auto-
 | ||||||
|  | 	/// detected and retained in order to format them later on.
 | ||||||
|  | 	ABITypeList formats; | ||||||
|  | 	/// A Comment that can be attached to the expectations,
 | ||||||
|  | 	/// that is retained and can be displayed.
 | ||||||
|  | 	std::string comment; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Represents a function call read from an input stream. It contains the signature, the | ||||||
|  |  * arguments, an optional ether value and an expected execution result. | ||||||
|  |  */ | ||||||
|  | struct FunctionCall | ||||||
|  | { | ||||||
|  | 	/// Signature of the function call, e.g. `f(uint256, uint256)`.
 | ||||||
|  | 	std::string signature; | ||||||
|  | 	/// Optional `ether` value that can be send with the call.
 | ||||||
|  | 	u256 value; | ||||||
|  | 	/// Object that holds all function parameters in their `bytes`
 | ||||||
|  | 	/// representations given by the contract ABI.
 | ||||||
|  | 	FunctionCallArgs arguments; | ||||||
|  | 	/// Object that holds all function call expectation in
 | ||||||
|  | 	/// their `bytes` representations given by the contract ABI.
 | ||||||
|  | 	/// They are checked against the actual results and their
 | ||||||
|  | 	/// `bytes` representation, as well as the transaction status.
 | ||||||
|  | 	FunctionCallExpectations expectations; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Class that is able to parse an additional and well-formed comment section in a Solidity | ||||||
|  |  * source file used by the file-based unit test environment. For now, it parses function | ||||||
|  |  * calls and their expected result after the call was made. | ||||||
|  |  * | ||||||
|  |  * - Function calls defined in blocks: | ||||||
|  |  * // f(uint256, uint256): 1, 1 # Signature and comma-separated list of arguments #
 | ||||||
|  |  * // -> 1, 1                   # Expected result value #
 | ||||||
|  |  * // g(), 2 ether              # (Optional) Ether to be send with the call #
 | ||||||
|  |  * // -> 2, 3
 | ||||||
|  |  * // h(uint256), 1 ether: 42
 | ||||||
|  |  * // -> FAILURE                # If REVERT or other EVM failure was detected #
 | ||||||
|  |  * ... | ||||||
|  |  */ | ||||||
|  | class TestFileParser | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	/// Constructor that takes an input stream \param _stream to operate on
 | ||||||
|  | 	/// and creates the internal scanner.
 | ||||||
|  | 	TestFileParser(std::istream& _stream): m_scanner(_stream) {} | ||||||
|  | 
 | ||||||
|  | 	/// Parses function calls blockwise and returns a list of function calls found.
 | ||||||
|  | 	/// Throws an exception if a function call cannot be parsed because of its
 | ||||||
|  | 	/// incorrect structure, an invalid or unsupported encoding
 | ||||||
|  | 	/// of its arguments or expected results.
 | ||||||
|  | 	std::vector<FunctionCall> parseFunctionCalls(); | ||||||
|  | 
 | ||||||
|  | 	/// Prints a friendly string representation of \param _token.
 | ||||||
|  | 	static std::string formatToken(SoltToken _token); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	/**
 | ||||||
|  | 	 * Token scanner that is used internally to abstract away character traversal. | ||||||
|  | 	 */ | ||||||
|  | 	class Scanner | ||||||
|  | 	{ | ||||||
|  | 	public: | ||||||
|  | 		/// Constructor that takes an input stream \param _stream to operate on.
 | ||||||
|  | 		Scanner(std::istream& _stream) { readStream(_stream); } | ||||||
|  | 
 | ||||||
|  | 		/// Reads input stream into a single line and resets the current iterator.
 | ||||||
|  | 		void readStream(std::istream& _stream); | ||||||
|  | 
 | ||||||
|  | 		/// Reads character stream and creates token.
 | ||||||
|  | 		void scanNextToken(); | ||||||
|  | 
 | ||||||
|  | 		SoltToken currentToken() { return m_currentToken.first; } | ||||||
|  | 		SoltToken peekToken() { return m_nextToken.first; } | ||||||
|  | 
 | ||||||
|  | 		std::string currentLiteral() { return m_currentToken.second; } | ||||||
|  | 
 | ||||||
|  | 		std::string scanComment(); | ||||||
|  | 		std::string scanIdentifierOrKeyword(); | ||||||
|  | 		std::string scanNumber(); | ||||||
|  | 
 | ||||||
|  | 	private: | ||||||
|  | 		using TokenDesc = std::pair<SoltToken, std::string>; | ||||||
|  | 
 | ||||||
|  | 		/// Advances current position in the input stream.
 | ||||||
|  | 		void advance() { ++m_char; } | ||||||
|  | 		/// Returns the current character.
 | ||||||
|  | 		char current() const { return *m_char; } | ||||||
|  | 		/// Peeks the next character.
 | ||||||
|  | 		char peek() const { auto it = m_char; return *(it + 1); } | ||||||
|  | 		/// Returns true if the end of a line is reached, false otherwise.
 | ||||||
|  | 		bool isEndOfLine() const { return m_char == m_line.end(); } | ||||||
|  | 
 | ||||||
|  | 		std::string m_line; | ||||||
|  | 		std::string::iterator m_char; | ||||||
|  | 
 | ||||||
|  | 		std::string m_currentLiteral; | ||||||
|  | 
 | ||||||
|  | 		TokenDesc m_currentToken; | ||||||
|  | 		TokenDesc m_nextToken; | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	bool accept(SoltToken _token, bool const _expect = false); | ||||||
|  | 	bool expect(SoltToken _token, bool const _advance = true); | ||||||
|  | 
 | ||||||
|  | 	/// Parses a function call signature in the form of f(uint256, ...).
 | ||||||
|  | 	std::string parseFunctionSignature(); | ||||||
|  | 
 | ||||||
|  | 	/// Parses the optional ether value that can be passed alongside the
 | ||||||
|  | 	/// function call arguments. Throws an InvalidEtherValueEncoding exception
 | ||||||
|  | 	/// if given value cannot be converted to `u256`.
 | ||||||
|  | 	u256 parseFunctionCallValue(); | ||||||
|  | 
 | ||||||
|  | 	/// Parses a comma-separated list of arguments passed with a function call.
 | ||||||
|  | 	/// Does not check for a potential mismatch between the signature and the number
 | ||||||
|  | 	/// or types of arguments.
 | ||||||
|  | 	FunctionCallArgs parseFunctionCallArguments(); | ||||||
|  | 
 | ||||||
|  | 	/// Parses the expected result of a function call execution.
 | ||||||
|  | 	FunctionCallExpectations parseFunctionCallExpectations(); | ||||||
|  | 
 | ||||||
|  | 	/// Parses and converts the current literal to its byte representation and
 | ||||||
|  | 	/// preserves the chosen ABI type. Based on that type information, the driver of
 | ||||||
|  | 	/// this parser can format arguments, expectations and results. Supported types:
 | ||||||
|  | 	/// - unsigned and signed decimal number literals
 | ||||||
|  | 	/// Throws a ParserError if data is encoded incorrectly or
 | ||||||
|  | 	/// if data type is not supported.
 | ||||||
|  | 	std::pair<bytes, ABIType> parseABITypeLiteral(); | ||||||
|  | 
 | ||||||
|  | 	/// Parses the current number literal.
 | ||||||
|  | 	std::string parseNumber(); | ||||||
|  | 
 | ||||||
|  | 	/// Tries to convert \param _literal to `uint256` and throws if
 | ||||||
|  | 	/// if conversion failed.
 | ||||||
|  | 	u256 convertNumber(std::string const& _literal); | ||||||
|  | 
 | ||||||
|  | 	/// A scanner instance
 | ||||||
|  | 	Scanner m_scanner; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
							
								
								
									
										205
									
								
								test/libsolidity/util/TestFileParserTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								test/libsolidity/util/TestFileParserTests.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | |||||||
|  | /*
 | ||||||
|  | 	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 Solidity's test expectation parser. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <functional> | ||||||
|  | #include <string> | ||||||
|  | #include <tuple> | ||||||
|  | #include <boost/test/unit_test.hpp> | ||||||
|  | #include <liblangutil/Exceptions.h> | ||||||
|  | #include <test/libsolidity/SolidityExecutionFramework.h> | ||||||
|  | 
 | ||||||
|  | #include <test/libsolidity/util/TestFileParser.h> | ||||||
|  | 
 | ||||||
|  | using namespace std; | ||||||
|  | using namespace dev::test; | ||||||
|  | 
 | ||||||
|  | namespace dev | ||||||
|  | { | ||||||
|  | namespace solidity | ||||||
|  | { | ||||||
|  | namespace test | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | vector<FunctionCall> parse(string const& _source) | ||||||
|  | { | ||||||
|  | 	istringstream stream{_source, ios_base::out}; | ||||||
|  | 	TestFileParser parser{stream}; | ||||||
|  | 	return parser.parseFunctionCalls(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_SUITE(TestFileParserTest) | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(smoke_test) | ||||||
|  | { | ||||||
|  | 	char const* source = R"()"; | ||||||
|  | 	BOOST_CHECK_EQUAL(parse(source).size(), 0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(simple_call_succees) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(uint256, uint256): 1, 1
 | ||||||
|  | 		// ->
 | ||||||
|  | 	)"; | ||||||
|  | 
 | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto call = calls.at(0); | ||||||
|  | 	ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}) + toBigEndian(u256{1})); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "f(uint256,uint256)"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(non_existent_call_revert) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// i_am_not_there()
 | ||||||
|  | 		// -> FAILURE
 | ||||||
|  | 	)"; | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto const& call = calls.at(0); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "i_am_not_there()"); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.expectations.status, false); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_comments) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f() # This is a comment #
 | ||||||
|  | 		// -> 1 # This is another comment #
 | ||||||
|  | 	)"; | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto const& call = calls.at(0); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "f()"); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.arguments.comment, " This is a comment "); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.expectations.comment, " This is another comment "); | ||||||
|  | 	ABI_CHECK(call.expectations.rawBytes, toBigEndian(u256{1})); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_arguments) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(uint256), 314 ether: 5 # optional ether value #
 | ||||||
|  | 		// -> 4
 | ||||||
|  | 	)"; | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto const& call = calls.at(0); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "f(uint256)"); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.value, u256{314}); | ||||||
|  | 	ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{5})); | ||||||
|  | 	ABI_CHECK(call.expectations.rawBytes, toBigEndian(u256{4})); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_expectations_missing) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f())";
 | ||||||
|  | 	BOOST_CHECK_THROW(parse(source), langutil::Error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_ether_value_expectations_missing) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(), 0)";
 | ||||||
|  | 	BOOST_CHECK_THROW(parse(source), langutil::Error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_arguments_invalid) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(uint256): abc
 | ||||||
|  | 		// -> 1
 | ||||||
|  | 	)"; | ||||||
|  | 	BOOST_CHECK_THROW(parse(source), langutil::Error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_ether_value_invalid) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(uint256), abc : 1
 | ||||||
|  | 		// -> 1
 | ||||||
|  | 	)"; | ||||||
|  | 	BOOST_CHECK_THROW(parse(source), langutil::Error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_ether_type_invalid) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(uint256), 2 btc : 1
 | ||||||
|  | 		// -> 1
 | ||||||
|  | 	)"; | ||||||
|  | 	BOOST_CHECK_THROW(parse(source), langutil::Error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_arguments_mismatch) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// f(uint256, uint256): 1 # This only throws at runtime #
 | ||||||
|  | 		// -> 1
 | ||||||
|  | 	)"; | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto const& call = calls.at(0); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "f(uint256,uint256)"); | ||||||
|  | 	ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1})); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_multiple_arguments) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// test(uint256, uint256): 1, 2
 | ||||||
|  | 		// -> 1, 1
 | ||||||
|  | 	)"; | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto const& call = calls.at(0); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "test(uint256,uint256)"); | ||||||
|  | 	ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}) + toBigEndian(u256{2})); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_CASE(call_multiple_arguments_mixed_format) | ||||||
|  | { | ||||||
|  | 	char const* source = R"( | ||||||
|  | 		// test(uint256, uint256),314 ether: 1, -2
 | ||||||
|  | 		// -> -1, 2
 | ||||||
|  | 	)"; | ||||||
|  | 	auto const calls = parse(source); | ||||||
|  | 	BOOST_CHECK_EQUAL(calls.size(), 1); | ||||||
|  | 
 | ||||||
|  | 	auto const& call = calls.at(0); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.signature, "test(uint256,uint256)"); | ||||||
|  | 	BOOST_CHECK_EQUAL(call.value, u256{314}); | ||||||
|  | 	ABI_CHECK(call.arguments.rawBytes, toBigEndian(u256{1}) + toBigEndian(u256{-2})); | ||||||
|  | 	ABI_CHECK(call.expectations.rawBytes, toBigEndian(u256{-1}) + toBigEndian(u256{2})); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BOOST_AUTO_TEST_SUITE_END() | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user