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: | ||||
|  * Add support for continuous fuzzing via Google oss-fuzz | ||||
|  * 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) | ||||
|  | ||||
| @ -17,6 +17,8 @@ if (LLL) | ||||
| endif() | ||||
| file(GLOB libsolidity_sources "libsolidity/*.cpp") | ||||
| 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} | ||||
|     ${contracts_sources} ${contracts_headers} | ||||
| @ -26,6 +28,7 @@ add_executable(soltest ${sources} ${headers} | ||||
|     ${libyul_sources} ${libyul_headers} | ||||
|     ${liblll_sources} ${liblll_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}) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										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