Skip to content

Commit 93cf9b2

Browse files
committed
Implements infrastructure for semantic tests.
1 parent bda5619 commit 93cf9b2

15 files changed

+358
-15
lines changed

Changelog.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Bugfixes:
1919
Build System:
2020
* Add support for continuous fuzzing via Google oss-fuzz
2121
* Add parser that is used in the file-based unit test environment.
22-
22+
* Tests: Add infrastructure for file-based semantic tests.
2323

2424
### 0.5.3 (2019-01-22)
2525

test/ExecutionFramework.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ string getIPCSocketPath()
4949

5050
}
5151

52-
ExecutionFramework::ExecutionFramework() :
53-
m_rpc(RPCSession::instance(getIPCSocketPath())),
52+
ExecutionFramework::ExecutionFramework():
53+
ExecutionFramework(getIPCSocketPath())
54+
{
55+
}
56+
57+
ExecutionFramework::ExecutionFramework(string const& _ipcPath):
58+
m_rpc(RPCSession::instance(_ipcPath)),
5459
m_evmVersion(dev::test::Options::get().evmVersion()),
5560
m_optimize(dev::test::Options::get().optimize),
5661
m_showMessages(dev::test::Options::get().showMessages),

test/ExecutionFramework.h

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class ExecutionFramework
5353

5454
public:
5555
ExecutionFramework();
56+
explicit ExecutionFramework(std::string const& _ipcPath);
5657
virtual ~ExecutionFramework() = default;
5758

5859
virtual bytes const& compileAndRunWithoutCheck(

test/InteractiveTests.h

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <test/TestCase.h>
2121
#include <test/libsolidity/ASTJSONTest.h>
2222
#include <test/libsolidity/SyntaxTest.h>
23+
#include <test/libsolidity/SemanticTest.h>
2324
#include <test/libsolidity/SMTCheckerJSONTest.h>
2425
#include <test/libyul/YulOptimizerTest.h>
2526
#include <test/libyul/ObjectCompilerTest.h>
@@ -52,6 +53,7 @@ Testsuite const g_interactiveTestsuites[] = {
5253
{"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
5354
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},
5455
{"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create},
56+
{"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create},
5557
{"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create},
5658
{"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SyntaxTest::create},
5759
{"SMT Checker JSON", "libsolidity", "smtCheckerTestsJSON", true, false, &SMTCheckerTest::create}

test/TestCase.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ namespace test
3434
class TestCase
3535
{
3636
public:
37-
using TestCaseCreator = std::unique_ptr<TestCase>(*)(std::string const&);
37+
struct Config
38+
{
39+
std::string filename;
40+
std::string ipcPath;
41+
};
42+
43+
using TestCaseCreator = std::unique_ptr<TestCase>(*)(Config const&);
3844

3945
virtual ~TestCase() = default;
4046

test/libsolidity/ASTJSONTest.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ namespace test
3535
class ASTJSONTest: public TestCase
3636
{
3737
public:
38-
static std::unique_ptr<TestCase> create(std::string const& _filename)
39-
{ return std::unique_ptr<TestCase>(new ASTJSONTest(_filename)); }
38+
static std::unique_ptr<TestCase> create(Config const& _config)
39+
{ return std::unique_ptr<TestCase>(new ASTJSONTest(_config.filename)); }
4040
ASTJSONTest(std::string const& _filename);
4141

4242
bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override;

test/libsolidity/SMTCheckerJSONTest.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ namespace test
3333
class SMTCheckerTest: public SyntaxTest
3434
{
3535
public:
36-
static std::unique_ptr<TestCase> create(std::string const& _filename)
36+
static std::unique_ptr<TestCase> create(Config const& _config)
3737
{
38-
return std::unique_ptr<TestCase>(new SMTCheckerTest(_filename));
38+
return std::unique_ptr<TestCase>(new SMTCheckerTest(_config.filename));
3939
}
4040
SMTCheckerTest(std::string const& _filename);
4141

test/libsolidity/SemanticTest.cpp

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
This file is part of solidity.
3+
solidity is free software: you can redistribute it and/or modify
4+
it under the terms of the GNU General Public License as published by
5+
the Free Software Foundation, either version 3 of the License, or
6+
(at your option) any later version.
7+
solidity is distributed in the hope that it will be useful,
8+
but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
GNU General Public License for more details.
11+
You should have received a copy of the GNU General Public License
12+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
13+
*/
14+
15+
#include <test/libsolidity/SemanticTest.h>
16+
#include <test/Options.h>
17+
#include <boost/algorithm/string.hpp>
18+
#include <boost/algorithm/string/predicate.hpp>
19+
#include <boost/algorithm/string/trim.hpp>
20+
#include <boost/throw_exception.hpp>
21+
22+
#include <algorithm>
23+
#include <cctype>
24+
#include <fstream>
25+
#include <memory>
26+
#include <stdexcept>
27+
28+
using namespace dev;
29+
using namespace solidity;
30+
using namespace dev::solidity::test;
31+
using namespace dev::solidity::test::formatting;
32+
using namespace std;
33+
namespace fs = boost::filesystem;
34+
using namespace boost;
35+
using namespace boost::algorithm;
36+
using namespace boost::unit_test;
37+
38+
namespace
39+
{
40+
using ParamList = dev::solidity::test::ParameterList;
41+
using FunctionCallTest = dev::solidity::test::SemanticTest::FunctionCallTest;
42+
using FunctionCall = dev::solidity::test::FunctionCall;
43+
44+
string formatBytes(bytes const& _bytes, ParamList const& _params, bool const _formatInvalid = false)
45+
{
46+
stringstream resultStream;
47+
if (_bytes.empty())
48+
resultStream.str();
49+
auto it = _bytes.begin();
50+
for (auto const& param: _params)
51+
{
52+
bytes byteRange{it, it + param.abiType.size};
53+
// FIXME Check range
54+
// TODO Check range
55+
switch (param.abiType.type)
56+
{
57+
case ABIType::SignedDec:
58+
if (*byteRange.begin() & 0x80)
59+
resultStream << u2s(fromBigEndian<u256>(byteRange));
60+
else
61+
resultStream << fromBigEndian<u256>(byteRange);
62+
break;
63+
case ABIType::UnsignedDec:
64+
// Check if the detected type was wrong and if this could
65+
// be signed. If an unsigned was detected in the expectations,
66+
// but the actual result returned a signed, it would be formatted
67+
// incorrectly.
68+
if (*byteRange.begin() & 0x80)
69+
resultStream << u2s(fromBigEndian<u256>(byteRange));
70+
else
71+
resultStream << fromBigEndian<u256>(byteRange);
72+
break;
73+
case ABIType::Failure:
74+
// If expectations are empty, the encoding type is invalid.
75+
// In order to still print the actual result even if
76+
// empty expectations were detected, it must be forced.
77+
if (_formatInvalid)
78+
resultStream << fromBigEndian<u256>(byteRange);
79+
break;
80+
case ABIType::None:
81+
// If expectations are empty, the encoding type is NONE.
82+
if (_formatInvalid)
83+
resultStream << fromBigEndian<u256>(byteRange);
84+
break;
85+
}
86+
it += param.abiType.size;
87+
if (it != _bytes.end() && !(param.abiType.type == ABIType::None))
88+
resultStream << ", ";
89+
}
90+
return resultStream.str();
91+
}
92+
93+
string formatFunctionCallTest(
94+
FunctionCallTest const& _test,
95+
string const& _linePrefix = "",
96+
bool const _renderResult = false,
97+
bool const _higlight = false
98+
)
99+
{
100+
stringstream _stream;
101+
FunctionCall call = _test.call;
102+
bool hightlight = !_test.matchesExpectation() && _higlight;
103+
104+
auto formatOutput = [&](bool const _singleLine)
105+
{
106+
_stream << _linePrefix << "// " << call.signature;
107+
if (call.value > u256(0))
108+
_stream << TestFileParser::formatToken(SoltToken::Comma)
109+
<< call.value << " "
110+
<< TestFileParser::formatToken(SoltToken::Ether);
111+
if (!call.arguments.rawBytes().empty())
112+
_stream << ": "
113+
<< formatBytes(call.arguments.rawBytes(), call.arguments.parameters);
114+
if (!_singleLine)
115+
_stream << endl << _linePrefix << "// ";
116+
if (_singleLine)
117+
_stream << " ";
118+
_stream << "-> ";
119+
if (!_singleLine)
120+
_stream << endl << _linePrefix << "// ";
121+
if (hightlight)
122+
_stream << formatting::RED_BACKGROUND;
123+
bytes output;
124+
if (_renderResult)
125+
output = call.expectations.rawBytes();
126+
else
127+
output = _test.rawBytes;
128+
if (!output.empty())
129+
_stream << formatBytes(output, call.expectations.result);
130+
if (hightlight)
131+
_stream << formatting::RESET;
132+
};
133+
134+
if (call.displayMode == FunctionCall::DisplayMode::SingleLine)
135+
formatOutput(true);
136+
else
137+
formatOutput(false);
138+
_stream << endl;
139+
140+
return _stream.str();
141+
}
142+
}
143+
144+
SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath):
145+
SolidityExecutionFramework(_ipcPath)
146+
{
147+
ifstream file(_filename);
148+
if (!file)
149+
BOOST_THROW_EXCEPTION(runtime_error("Cannot open test contract: \"" + _filename + "\"."));
150+
file.exceptions(ios::badbit);
151+
152+
m_source = parseSource(file);
153+
parseExpectations(file);
154+
}
155+
156+
bool SemanticTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted)
157+
{
158+
if (!deploy("", 0, bytes()))
159+
BOOST_THROW_EXCEPTION(runtime_error("Failed to deploy contract."));
160+
161+
bool success = true;
162+
for (auto& test: m_tests)
163+
test.reset();
164+
165+
for (auto& test: m_tests)
166+
{
167+
bytes output = callContractFunctionWithValueNoEncoding(
168+
test.call.signature,
169+
test.call.value,
170+
test.call.arguments.rawBytes()
171+
);
172+
173+
if ((m_transactionSuccessful == test.call.expectations.failure) || (output != test.call.expectations.rawBytes()))
174+
success = false;
175+
176+
test.failure = !m_transactionSuccessful;
177+
test.rawBytes = std::move(output);
178+
}
179+
180+
if (!success)
181+
{
182+
FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl;
183+
for (auto const& test: m_tests)
184+
_stream << formatFunctionCallTest(test, _linePrefix, false, true);
185+
186+
FormattedScope(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl;
187+
for (auto const& test: m_tests)
188+
_stream << formatFunctionCallTest(test, _linePrefix, true, true);
189+
190+
FormattedScope(_stream, _formatted, {BOLD, RED}) << _linePrefix
191+
<< "Attention: Updates on the test will apply the detected format displayed." << endl;
192+
return false;
193+
}
194+
return true;
195+
}
196+
197+
void SemanticTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const
198+
{
199+
stringstream stream(m_source);
200+
string line;
201+
while (getline(stream, line))
202+
_stream << _linePrefix << line << endl;
203+
}
204+
205+
void SemanticTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const
206+
{
207+
for (auto const& test: m_tests)
208+
_stream << formatFunctionCallTest(test, _linePrefix, false, false);
209+
}
210+
211+
void SemanticTest::parseExpectations(istream& _stream)
212+
{
213+
TestFileParser parser{_stream};
214+
for (auto const& call: parser.parseFunctionCalls())
215+
m_tests.emplace_back(FunctionCallTest{call, bytes{}, string{}});
216+
}
217+
218+
bool SemanticTest::deploy(string const& _contractName, u256 const& _value, bytes const& _arguments)
219+
{
220+
auto output = compileAndRunWithoutCheck(m_source, _value, _contractName, _arguments);
221+
return !output.empty() && m_transactionSuccessful;
222+
}

0 commit comments

Comments
 (0)