Add source locations to syntax test expectations.

This commit is contained in:
Daniel Kirchner 2018-04-06 18:37:01 +02:00
parent 2bc4ec31e2
commit f03695731b
55 changed files with 216 additions and 108 deletions

View File

@ -11,6 +11,7 @@ Features:
* Optimizer: Optimize across ``mload`` if ``msize()`` is not used.
* Static Analyzer: Error on duplicated super constructor calls as experimental 0.5.0 feature.
* Syntax Checker: Issue warning for empty structs (or error as experimental 0.5.0 feature).
* Syntax Tests: Add source locations to syntax test expectations.
* General: Introduce new constructor syntax using the ``constructor`` keyword as experimental 0.5.0 feature.
* Inheritance: Error when using empty parentheses for base class constructors that require arguments as experimental 0.5.0 feature.
* Inheritance: Error when using no parentheses in modifier-style constructor calls as experimental 0.5.0 feature.

View File

@ -38,6 +38,8 @@ static constexpr char const* GREEN = "\033[1;32m";
static constexpr char const* YELLOW = "\033[1;33m";
static constexpr char const* CYAN = "\033[1;36m";
static constexpr char const* BOLD = "\033[1m";
static constexpr char const* RED_BACKGROUND = "\033[48;5;160m";
static constexpr char const* ORANGE_BACKGROUND = "\033[48;5;166m";
static constexpr char const* INVERSE = "\033[7m";
}

View File

@ -34,17 +34,38 @@ namespace fs = boost::filesystem;
using namespace boost::unit_test;
template<typename IteratorType>
void skipWhitespace(IteratorType& it, IteratorType end)
void skipWhitespace(IteratorType& _it, IteratorType _end)
{
while (it != end && isspace(*it))
++it;
while (_it != _end && isspace(*_it))
++_it;
}
template<typename IteratorType>
void skipSlashes(IteratorType& it, IteratorType end)
void skipSlashes(IteratorType& _it, IteratorType _end)
{
while (it != end && *it == '/')
++it;
while (_it != _end && *_it == '/')
++_it;
}
void expect(string::iterator& _it, string::iterator _end, string::value_type _c)
{
if (_it == _end || *_it != _c)
throw runtime_error(string("Invalid test expectation. Expected: \"") + _c + "\".");
++_it;
}
int parseUnsignedInteger(string::iterator &_it, string::iterator _end)
{
if (_it == _end || !isdigit(*_it))
throw runtime_error("Invalid test expectation. Source location expected.");
int result = 0;
while (_it != _end && isdigit(*_it))
{
result *= 10;
result += *_it - '0';
++_it;
}
return result;
}
SyntaxTest::SyntaxTest(string const& _filename)
@ -60,22 +81,39 @@ SyntaxTest::SyntaxTest(string const& _filename)
bool SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted)
{
string const versionPragma = "pragma solidity >=0.0;\n";
m_compiler.reset();
m_compiler.addSource("", "pragma solidity >=0.0;\n" + m_source);
m_compiler.addSource("", versionPragma + m_source);
m_compiler.setEVMVersion(dev::test::Options::get().evmVersion());
if (m_compiler.parse())
m_compiler.analyze();
for (auto const& currentError: filterErrors(m_compiler.errors(), true))
m_errorList.emplace_back(SyntaxTestError{currentError->typeName(), errorMessage(*currentError)});
{
int locationStart = -1, locationEnd = -1;
if (auto location = boost::get_error_info<errinfo_sourceLocation>(*currentError))
{
// ignore the version pragma inserted by the testing tool when calculating locations.
if (location->start >= static_cast<int>(versionPragma.size()))
locationStart = location->start - versionPragma.size();
if (location->end >= static_cast<int>(versionPragma.size()))
locationEnd = location->end - versionPragma.size();
}
m_errorList.emplace_back(SyntaxTestError{
currentError->typeName(),
errorMessage(*currentError),
locationStart,
locationEnd
});
}
if (m_expectations != m_errorList)
{
string nextIndentLevel = _linePrefix + " ";
FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl;
printErrorList(_stream, m_expectations, nextIndentLevel, _formatted);
FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:\n";
FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl;
printErrorList(_stream, m_errorList, nextIndentLevel, _formatted);
return false;
}
@ -99,6 +137,16 @@ void SyntaxTest::printErrorList(
_stream << _linePrefix;
_stream << error.type << ": ";
}
if (error.locationStart >= 0 || error.locationEnd >= 0)
{
_stream << "(";
if (error.locationStart >= 0)
_stream << error.locationStart;
_stream << "-";
if (error.locationEnd >= 0)
_stream << error.locationEnd;
_stream << "): ";
}
_stream << error.message << endl;
}
}
@ -147,8 +195,28 @@ vector<SyntaxTestError> SyntaxTest::parseExpectations(istream& _stream)
skipWhitespace(it, line.end());
int locationStart = -1;
int locationEnd = -1;
if (it != line.end() && *it == '(')
{
++it;
locationStart = parseUnsignedInteger(it, line.end());
expect(it, line.end(), '-');
locationEnd = parseUnsignedInteger(it, line.end());
expect(it, line.end(), ')');
expect(it, line.end(), ':');
}
skipWhitespace(it, line.end());
string errorMessage(it, line.end());
expectations.emplace_back(SyntaxTestError{move(errorType), move(errorMessage)});
expectations.emplace_back(SyntaxTestError{
move(errorType),
move(errorMessage),
locationStart,
locationEnd
});
}
return expectations;
}

View File

@ -40,9 +40,14 @@ struct SyntaxTestError
{
std::string type;
std::string message;
int locationStart;
int locationEnd;
bool operator==(SyntaxTestError const& _rhs) const
{
return type == _rhs.type && message == _rhs.message;
return type == _rhs.type &&
message == _rhs.message &&
locationStart == _rhs.locationStart &&
locationEnd == _rhs.locationEnd;
}
};

View File

@ -2,4 +2,4 @@ contract C {
uint constant a = a;
}
// ----
// TypeError: The value of the constant a has a cyclic dependency via a.
// TypeError: (17-36): The value of the constant a has a cyclic dependency via a.

View File

@ -5,6 +5,6 @@ contract C {
uint constant d = 2 + a;
}
// ----
// TypeError: The value of the constant a has a cyclic dependency via c.
// TypeError: The value of the constant c has a cyclic dependency via d.
// TypeError: The value of the constant d has a cyclic dependency via a.
// TypeError: (17-40): The value of the constant a has a cyclic dependency via c.
// TypeError: (71-111): The value of the constant c has a cyclic dependency via d.
// TypeError: (117-140): The value of the constant d has a cyclic dependency via a.

View File

@ -5,7 +5,7 @@ contract C {
uint constant c = b;
}
// ----
// TypeError: The value of the constant x has a cyclic dependency via a.
// TypeError: The value of the constant a has a cyclic dependency via b.
// TypeError: The value of the constant b has a cyclic dependency via c.
// TypeError: The value of the constant c has a cyclic dependency via b.
// TypeError: (17-36): The value of the constant x has a cyclic dependency via a.
// TypeError: (42-65): The value of the constant a has a cyclic dependency via b.
// TypeError: (71-90): The value of the constant b has a cyclic dependency via c.
// TypeError: (96-115): The value of the constant c has a cyclic dependency via b.

View File

@ -3,4 +3,4 @@ contract test {
uint128 variable;
}
// ----
// DeclarationError: Identifier already declared.
// DeclarationError: (36-52): Identifier already declared.

View File

@ -5,4 +5,4 @@ contract test {
}
}
// ----
// DeclarationError: Identifier already declared.
// DeclarationError: (71-80): Identifier already declared.

View File

@ -6,6 +6,6 @@ contract test {
}
}
// ----
// Warning: This declaration shadows an existing declaration.
// Warning: Unused local variable.
// Warning: Unused local variable.
// Warning: (101-110): This declaration shadows an existing declaration.
// Warning: (76-85): Unused local variable.
// Warning: (101-110): Unused local variable.

View File

@ -2,4 +2,4 @@ contract test {
struct A {}
}
// ----
// Warning: Defining empty structs is deprecated.
// Warning: (17-28): Defining empty structs is deprecated.

View File

@ -3,4 +3,4 @@ contract test {
struct A {}
}
// ----
// SyntaxError: Defining empty structs is disallowed.
// SyntaxError: (47-58): Defining empty structs is disallowed.

View File

@ -4,4 +4,4 @@ contract Base {
contract Derived is Base(2) { }
contract Derived2 is Base(), Derived() { }
// ----
// Warning: Wrong argument count for constructor call: 0 arguments given but expected 1.
// Warning: (101-107): Wrong argument count for constructor call: 0 arguments given but expected 1.

View File

@ -6,4 +6,4 @@ contract Base {
contract Derived is Base(2) { }
contract Derived2 is Base(), Derived() { }
// ----
// TypeError: Wrong argument count for constructor call: 0 arguments given but expected 1.
// TypeError: (132-138): Wrong argument count for constructor call: 0 arguments given but expected 1.

View File

@ -6,4 +6,4 @@ contract Derived is Base, Base1 {
constructor(uint i) Base(i) public {}
}
// ----
// Warning: Base constructor arguments given twice.
// Warning: (138-145): Base constructor arguments given twice.

View File

@ -1,4 +1,4 @@
contract A { constructor() public { } }
contract B is A { constructor() A public { } }
// ----
// Warning: Modifier-style base constructor call without arguments.
// Warning: (72-73): Modifier-style base constructor call without arguments.

View File

@ -2,4 +2,4 @@ contract A { constructor(uint) public { } }
contract B is A(2) { constructor() public { } }
contract C is B { constructor() A(3) public { } }
// ----
// Warning: Base constructor arguments given twice.
// Warning: (125-129): Base constructor arguments given twice.

View File

@ -4,4 +4,4 @@ contract A { constructor(uint) public { } }
contract B is A(2) { constructor() public { } }
contract C is B { constructor() A(3) public { } }
// ----
// DeclarationError: Base constructor arguments given twice.
// DeclarationError: (156-160): Base constructor arguments given twice.

View File

@ -1,4 +1,4 @@
contract A { constructor(uint) public { } }
contract B is A(2) { constructor() A(3) public { } }
// ----
// Warning: Base constructor arguments given twice.
// Warning: (79-83): Base constructor arguments given twice.

View File

@ -3,4 +3,4 @@ pragma experimental "v0.5.0";
contract A { constructor(uint) public { } }
contract B is A(2) { constructor() A(3) public { } }
// ----
// DeclarationError: Base constructor arguments given twice.
// DeclarationError: (110-114): Base constructor arguments given twice.

View File

@ -3,5 +3,5 @@ contract A is C(2) {}
contract B is C(2) {}
contract D is A, B { constructor() C(3) public {} }
// ----
// Warning: Base constructor arguments given twice.
// Warning: Base constructor arguments given twice.
// Warning: (122-126): Base constructor arguments given twice.
// Warning: (122-126): Base constructor arguments given twice.

View File

@ -3,4 +3,4 @@ contract A is C(2) {}
contract B is C(2) {}
contract D is A, B {}
// ----
// Warning: Base constructor arguments given twice.
// Warning: (87-108): Base constructor arguments given twice.

View File

@ -3,4 +3,4 @@ contract A is C { constructor() C(2) public {} }
contract B is C { constructor() C(2) public {} }
contract D is A, B { }
// ----
// Warning: Base constructor arguments given twice.
// Warning: (141-163): Base constructor arguments given twice.

View File

@ -6,5 +6,5 @@ contract Derived2 is Base {
constructor() Base(2) public { }
}
// ----
// TypeError: Wrong argument count for constructor call: 1 arguments given but expected 2.
// TypeError: Wrong argument count for modifier invocation: 1 arguments given but expected 2.
// TypeError: (74-81): Wrong argument count for constructor call: 1 arguments given but expected 2.
// TypeError: (130-137): Wrong argument count for modifier invocation: 1 arguments given but expected 2.

View File

@ -2,4 +2,4 @@ contract test {
uint256 ;
}
// ----
// ParserError: Expected identifier, got 'Semicolon'
// ParserError: (28-28): Expected identifier, got 'Semicolon'

View File

@ -3,4 +3,4 @@ contract test {
function fun() public { }
}
// ----
// DeclarationError: Function with same name and arguments defined twice.
// DeclarationError: (20-45): Function with same name and arguments defined twice.

View File

@ -5,4 +5,4 @@ contract test {
}
}
// ----
// DeclarationError: Identifier already declared.
// DeclarationError: (77-83): Identifier already declared.

View File

@ -6,5 +6,5 @@ contract test {
}
}
// ----
// Warning: Unused local variable.
// Warning: Unused local variable.
// Warning: (87-93): Unused local variable.
// Warning: (107-113): Unused local variable.

View File

@ -5,4 +5,4 @@ contract test {
}
}
// ----
// DeclarationError: Identifier already declared.
// DeclarationError: (75-81): Identifier already declared.

View File

@ -6,5 +6,5 @@ contract test {
}
}
// ----
// Warning: Unused local variable.
// Warning: Unused local variable.
// Warning: (87-93): Unused local variable.
// Warning: (105-111): Unused local variable.

View File

@ -3,5 +3,4 @@ contract test {
function f() pure public { uint32 variable; variable = 2; }
}
// ----
// Warning: This declaration shadows an existing declaration.
// Warning: (69-84): This declaration shadows an existing declaration.

View File

@ -8,4 +8,4 @@ contract test {
}
}
// ----
// DeclarationError: Undeclared identifier.
// DeclarationError: (123-124): Undeclared identifier.

View File

@ -6,4 +6,4 @@ contract test {
}
}
// ----
// DeclarationError: Undeclared identifier. Did you mean "x"?
// DeclarationError: (85-86): Undeclared identifier. Did you mean "x"?

View File

@ -8,4 +8,4 @@ contract test {
}
}
// ----
// DeclarationError: Undeclared identifier.
// DeclarationError: (154-155): Undeclared identifier.

View File

@ -7,4 +7,4 @@ contract test {
}
}
// ----
// DeclarationError: Undeclared identifier.
// DeclarationError: (93-94): Undeclared identifier.

View File

@ -5,4 +5,4 @@ contract test {
}
}
// ----
// DeclarationError: Undeclared identifier. Did you mean "a"?
// DeclarationError: (94-95): Undeclared identifier. Did you mean "a"?

View File

@ -3,4 +3,4 @@ contract test {
function fun(uint256 arg1) public { uint256 y; y = arg1; }
}
// ----
// Warning: Function state mutability can be restricted to pure
// Warning: (42-100): Function state mutability can be restricted to pure

View File

@ -5,7 +5,7 @@ contract C {
}
}
// ----
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: (74-83): This type cannot be encoded.
// TypeError: (85-86): This type cannot be encoded.
// TypeError: (88-98): This type cannot be encoded.
// TypeError: (100-115): This type cannot be encoded.

View File

@ -6,8 +6,8 @@ contract C {
}
}
// ----
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: (80-106): This type cannot be encoded.
// TypeError: (108-113): This type cannot be encoded.
// TypeError: (160-164): This type cannot be encoded.
// TypeError: (166-168): This type cannot be encoded.
// TypeError: (170-176): This type cannot be encoded.

View File

@ -9,6 +9,6 @@ contract C {
}
}
// ----
// Warning: Defining empty structs is deprecated.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// Warning: (51-63): Defining empty structs is deprecated.
// TypeError: (131-132): This type cannot be encoded.
// TypeError: (134-135): This type cannot be encoded.

View File

@ -9,9 +9,9 @@ contract C {
}
}
// ----
// Warning: Defining empty structs is deprecated.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// TypeError: This type cannot be encoded.
// Warning: (51-63): Defining empty structs is deprecated.
// TypeError: (168-169): This type cannot be encoded.
// TypeError: (171-172): This type cannot be encoded.
// TypeError: (179-180): This type cannot be encoded.
// TypeError: (182-186): This type cannot be encoded.
// TypeError: (188-194): This type cannot be encoded.

View File

@ -12,4 +12,4 @@ contract C {
function f(T) public pure { }
}
// ----
// Warning: Experimental features are turned on. Do not use experimental features on live deployments.
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.

View File

@ -12,4 +12,4 @@ contract TestContract
function addTestStruct(TestStruct) public pure {}
}
// ----
// Warning: Experimental features are turned on. Do not use experimental features on live deployments.
// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments.

View File

@ -4,4 +4,4 @@ contract C {
}
}
// ----
// TypeError: Internal or recursive type is not allowed for public or external functions.
// TypeError: (91-92): Internal or recursive type is not allowed for public or external functions.

View File

@ -4,4 +4,4 @@ contract C {
}
}
// ----
// TypeError: Internal or recursive type is not allowed for public or external functions.
// TypeError: (94-95): Internal or recursive type is not allowed for public or external functions.

View File

@ -5,4 +5,4 @@ contract C {
}
}
// ----
// TypeError: Internal or recursive type is not allowed for public or external functions.
// TypeError: (119-122): Internal or recursive type is not allowed for public or external functions.

View File

@ -5,4 +5,4 @@ contract Test {
}
}
// ----
// TypeError: Recursive struct definition.
// TypeError: (20-93): Recursive struct definition.

View File

@ -9,4 +9,4 @@ contract Test {
}
}
// ----
// TypeError: Recursive struct definition.
// TypeError: (20-118): Recursive struct definition.

View File

@ -2,5 +2,5 @@ interface I {
function f();
}
// ----
// Warning: Functions in interfaces should be declared external.
// Warning: No visibility specified. Defaulting to "public". In interfaces it defaults to external.
// Warning: (15-28): Functions in interfaces should be declared external.
// Warning: (15-28): No visibility specified. Defaulting to "public". In interfaces it defaults to external.

View File

@ -3,5 +3,5 @@ interface I {
function f();
}
// ----
// SyntaxError: No visibility specified.
// TypeError: Functions in interfaces must be declared external.
// SyntaxError: (45-58): No visibility specified.
// TypeError: (45-58): Functions in interfaces must be declared external.

View File

@ -2,4 +2,4 @@ interface I {
function f() internal;
}
// ----
// TypeError: Functions in interfaces cannot be internal or private.
// TypeError: (15-37): Functions in interfaces cannot be internal or private.

View File

@ -2,4 +2,4 @@ interface I {
function f() private;
}
// ----
// TypeError: Functions in interfaces cannot be internal or private.
// TypeError: (15-36): Functions in interfaces cannot be internal or private.

View File

@ -2,4 +2,4 @@ interface I {
function f() public;
}
// ----
// Warning: Functions in interfaces should be declared external.
// Warning: (15-35): Functions in interfaces should be declared external.

View File

@ -3,4 +3,4 @@ interface I {
function f() public;
}
// ----
// TypeError: Functions in interfaces must be declared external.
// TypeError: (45-65): Functions in interfaces must be declared external.

View File

@ -55,7 +55,6 @@ public:
{
Success,
Failure,
InputOutputError,
Exception
};
@ -90,11 +89,53 @@ string SyntaxTestTool::editor;
void SyntaxTestTool::printContract() const
{
stringstream stream(m_test->source());
string line;
while (getline(stream, line))
cout << " " << line << endl;
cout << endl;
if (m_formatted)
{
string const& source = m_test->source();
if (source.empty())
return;
std::vector<char const*> sourceFormatting(source.length(), formatting::RESET);
for (auto const& error: m_test->errorList())
if (error.locationStart >= 0 && error.locationEnd >= 0)
{
assert(static_cast<size_t>(error.locationStart) < source.length());
assert(static_cast<size_t>(error.locationEnd) < source.length());
bool isWarning = error.type == "Warning";
for (int i = error.locationStart; i < error.locationEnd; i++)
if (isWarning)
{
if (sourceFormatting[i] == formatting::RESET)
sourceFormatting[i] = formatting::ORANGE_BACKGROUND;
}
else
sourceFormatting[i] = formatting::RED_BACKGROUND;
}
cout << " " << sourceFormatting.front() << source.front();
for (size_t i = 1; i < source.length(); i++)
{
if (sourceFormatting[i] != sourceFormatting[i - 1])
cout << sourceFormatting[i];
if (source[i] != '\n')
cout << source[i];
else
{
cout << formatting::RESET << endl;
if (i + 1 < source.length())
cout << " " << sourceFormatting[i];
}
}
cout << formatting::RESET << endl;
}
else
{
stringstream stream(m_test->source());
string line;
while (getline(stream, line))
cout << " " << line << endl;
cout << endl;
}
}
SyntaxTestTool::Result SyntaxTestTool::process()
@ -107,15 +148,6 @@ SyntaxTestTool::Result SyntaxTestTool::process()
try
{
m_test = unique_ptr<SyntaxTest>(new SyntaxTest(m_path.string()));
}
catch (std::exception const& _e)
{
FormattedScope(cout, m_formatted, {BOLD, RED}) << "cannot read test: " << _e.what() << endl;
return Result::InputOutputError;
}
try
{
success = m_test->run(outputMessages, " ", m_formatted);
}
catch(CompilerError const& _e)
@ -142,6 +174,11 @@ SyntaxTestTool::Result SyntaxTestTool::process()
"UnimplementedFeatureError: " << SyntaxTest::errorMessage(_e) << endl;
return Result::Exception;
}
catch (std::exception const& _e)
{
FormattedScope(cout, m_formatted, {BOLD, RED}) << "Exception: " << _e.what() << endl;
return Result::Exception;
}
catch(...)
{
FormattedScope(cout, m_formatted, {BOLD, RED}) <<
@ -262,10 +299,6 @@ SyntaxTestStats SyntaxTestTool::processPath(
paths.pop();
++successCount;
break;
default:
// non-recoverable error; continue with next test case
paths.pop();
break;
}
}
}