diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cc7a61bae..2fcea2269 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -73,6 +73,8 @@ set(libsolidity_sources libsolidity/Assembly.cpp libsolidity/ASTJSONTest.cpp libsolidity/ASTJSONTest.h + libsolidity/ASTJSONPropertyTest.cpp + libsolidity/ASTJSONPropertyTest.h libsolidity/ErrorCheck.cpp libsolidity/ErrorCheck.h libsolidity/GasCosts.cpp diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index d359a815a..7d1f6192e 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, + {"JSON AST Properties", "libsolidity", "ASTJSONProperties", false, false, &ASTJSONPropertyTest::create}, {"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create}, {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create}, {"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create}, diff --git a/test/libsolidity/ASTJSONProperties/simple_for_loop.sol b/test/libsolidity/ASTJSONProperties/simple_for_loop.sol new file mode 100644 index 000000000..c221542da --- /dev/null +++ b/test/libsolidity/ASTJSONProperties/simple_for_loop.sol @@ -0,0 +1,16 @@ +function f() pure { + /// @custom:test { "isSimpleCounterLoop": true } + for (uint i = 0; i < 32; ++i) { + } + /// @custom:test { "isSimpleCounterLoop": true } + for (uint i = 0; i < 32; i++) { + } + /// @custom:test { "isSimpleCounterLoop": true } + for (uint i = 0; i < i; ++i) { + } + /// @custom:test { "isSimpleCounterLoop": false } + for (uint i = 0; 0 < i; ++i) { + } +} + +// ---- diff --git a/test/libsolidity/ASTJSONPropertyTest.cpp b/test/libsolidity/ASTJSONPropertyTest.cpp new file mode 100644 index 000000000..0eba92c73 --- /dev/null +++ b/test/libsolidity/ASTJSONPropertyTest.cpp @@ -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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace solidity::langutil; +using namespace solidity::frontend; +using namespace solidity::frontend::test; +using namespace solidity::util::formatting; +using namespace solidity::util; +using namespace solidity; +using namespace std; +namespace fs = boost::filesystem; +using namespace boost::unit_test; + +namespace +{ + +string const sourceDelimiter("==== Source: "); + +} + +void ASTJSONPropertyTest::fillSources(string const& _filename) +{ + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test contract: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + string sourceName; + string source; + string line; + string const delimiter("// ----"); + while (getline(file, line)) + { + if (boost::algorithm::starts_with(line, sourceDelimiter)) + { + if (!sourceName.empty()) + m_sources.emplace_back(sourceName, source); + + sourceName = line.substr( + sourceDelimiter.size(), + line.size() - " ===="s.size() - sourceDelimiter.size() + ); + source = string(); + } + else if (!line.empty() && !boost::algorithm::starts_with(line, delimiter)) + source += line + "\n"; + } + m_sources.emplace_back(sourceName.empty() ? "a" : sourceName, source); + file.close(); +} + +ASTJSONPropertyTest::ASTJSONPropertyTest(string const& _filename) +{ + if (!boost::algorithm::ends_with(_filename, ".sol")) + BOOST_THROW_EXCEPTION(runtime_error("Invalid test contract file name: \"" + _filename + "\".")); + + fillSources(_filename); +} + +TestCase::TestResult ASTJSONPropertyTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + CompilerStack c; + + StringMap sources; + map sourceIndices; + for (size_t i = 0; i < m_sources.size(); i++) + { + sources[m_sources[i].first] = m_sources[i].second; + sourceIndices[m_sources[i].first] = static_cast(i + 1); + } + + c.reset(); + c.setSources(sources); + c.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); + + if (!c.parseAndAnalyze()) + return TestResult::FatalError; + + return runTest( + sourceIndices, + c, + _stream, + _linePrefix, + _formatted + ) ? TestResult::Success : TestResult::Failure; +} + +bool ASTJSONPropertyTest::checkASTProperty(std::string _property, Json::Value const& _node) +{ + Json::Value properties; + std::string error; + if (!jsonParseStrict(_property, properties, &error)) + BOOST_THROW_EXCEPTION(runtime_error("Invalid test property: \"" + _property + "\": " + error)); + + for (auto propertyName: properties.getMemberNames()) + { + if (_node[propertyName] != properties[propertyName]) + { + std::cout << jsonCompactPrint(_node[propertyName]) << " != " << jsonCompactPrint(properties[propertyName]) << std::endl; + return false; + } + } + + return true; +} + +bool ASTJSONPropertyTest::runTest( + map const& _sourceIndices, + CompilerStack& _compiler, + ostream& /*_stream*/, + string const& /*_linePrefix*/, + bool const /*_formatted*/ +) +{ + for (size_t i = 0; i < m_sources.size(); i++) + { + auto jsonAST = ASTJsonExporter(_compiler.state(), _sourceIndices).toJson(_compiler.ast(m_sources[i].first)); + + auto walkAST = [this](Json::Value const& _node, auto _recurse) -> bool { + if (_node.type() == Json::ValueType::objectValue && _node.isMember("documentation")) + { + std::string docString = _node.get("documentation", {}).asString(); + static constexpr std::string_view testMarker = "@custom:test "; + if (boost::starts_with(docString, testMarker)) + if (!checkASTProperty(docString.substr(testMarker.size()), _node)) + return false; + } + for (auto const& member: _node) + if (!_recurse(member, _recurse)) + return false; + return true; + }; + + if (!walkAST(jsonAST, walkAST)) + return false; + } + + return true; +} + +void ASTJSONPropertyTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + for (auto const& source: m_sources) + { + if (m_sources.size() > 1 || source.first != "a") + _stream << _linePrefix << sourceDelimiter << source.first << " ====" << endl << endl; + stringstream stream(source.second); + string line; + while (getline(stream, line)) + _stream << _linePrefix << line << endl; + _stream << endl; + } +} + +void ASTJSONPropertyTest::printUpdatedExpectations(std::ostream&, std::string const&) const +{ +} + +void ASTJSONPropertyTest::updateExpectation(string const& _filename, string const& _expectation, string const& _variant) const +{ + ofstream file(_filename.c_str()); + if (!file) BOOST_THROW_EXCEPTION(runtime_error("Cannot write " + _variant + "AST expectation to \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + string replacedResult = _expectation; + + file << replacedResult; + file.flush(); + file.close(); +} diff --git a/test/libsolidity/ASTJSONPropertyTest.h b/test/libsolidity/ASTJSONPropertyTest.h new file mode 100644 index 000000000..9c90a3820 --- /dev/null +++ b/test/libsolidity/ASTJSONPropertyTest.h @@ -0,0 +1,71 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace solidity::frontend +{ +class CompilerStack; +} + +namespace solidity::frontend::test +{ + +class ASTJSONPropertyTest: public TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + ASTJSONPropertyTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + + void printSource(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; +private: + bool checkASTProperty(std::string _property, Json::Value const& _node); + bool runTest( + std::map const& _sourceIndices, + CompilerStack& _compiler, + std::ostream& _stream, + std::string const& _linePrefix = "", + bool const _formatted = false + ); + void updateExpectation( + std::string const& _filename, + std::string const& _expectation, + std::string const& _variant + ) const; + + void fillSources(std::string const& _filename); + + std::vector> m_sources; +}; + +} diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 5971341ec..a2a755f78 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(isoltest ../ExecutionFramework.cpp ../libsolidity/ABIJsonTest.cpp ../libsolidity/ASTJSONTest.cpp + ../libsolidity/ASTJSONPropertyTest.cpp ../libsolidity/SMTCheckerTest.cpp ../libyul/Common.cpp ../libyul/ControlFlowGraphTest.cpp