Merge pull request #12283 from ethereum/soltest-graceful-error-handling

Graceful error handling in soltest/isoltest + improved soltestAssert()
This commit is contained in:
Christian Parpart 2022-04-07 13:12:43 +02:00 committed by GitHub
commit e74f03056c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 165 additions and 110 deletions

View File

@ -58,13 +58,13 @@ struct InvalidAstError: virtual util::Exception {};
#endif #endif
#define solAssert_1(CONDITION) \ #define solAssert_1(CONDITION) \
solAssert_2(CONDITION, "") solAssert_2((CONDITION), "")
#define solAssert_2(CONDITION, DESCRIPTION) \ #define solAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \ assertThrowWithDefaultDescription( \
CONDITION, \ (CONDITION), \
::solidity::langutil::InternalCompilerError, \ ::solidity::langutil::InternalCompilerError, \
DESCRIPTION, \ (DESCRIPTION), \
"Solidity assertion failed" \ "Solidity assertion failed" \
) )
@ -77,13 +77,13 @@ struct InvalidAstError: virtual util::Exception {};
#endif #endif
#define solUnimplementedAssert_1(CONDITION) \ #define solUnimplementedAssert_1(CONDITION) \
solUnimplementedAssert_2(CONDITION, "") solUnimplementedAssert_2((CONDITION), "")
#define solUnimplementedAssert_2(CONDITION, DESCRIPTION) \ #define solUnimplementedAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \ assertThrowWithDefaultDescription( \
CONDITION, \ (CONDITION), \
::solidity::langutil::UnimplementedFeatureError, \ ::solidity::langutil::UnimplementedFeatureError, \
DESCRIPTION, \ (DESCRIPTION), \
"Unimplemented feature" \ "Unimplemented feature" \
) )
@ -105,9 +105,9 @@ struct InvalidAstError: virtual util::Exception {};
#define astAssert_2(CONDITION, DESCRIPTION) \ #define astAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \ assertThrowWithDefaultDescription( \
CONDITION, \ (CONDITION), \
::solidity::langutil::InvalidAstError, \ ::solidity::langutil::InvalidAstError, \
DESCRIPTION, \ (DESCRIPTION), \
"AST assertion failed" \ "AST assertion failed" \
) )

View File

@ -38,13 +38,13 @@ struct SMTLogicError: virtual util::Exception {};
#endif #endif
#define smtAssert_1(CONDITION) \ #define smtAssert_1(CONDITION) \
smtAssert_2(CONDITION, "") smtAssert_2((CONDITION), "")
#define smtAssert_2(CONDITION, DESCRIPTION) \ #define smtAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \ assertThrowWithDefaultDescription( \
CONDITION, \ (CONDITION), \
::solidity::smtutil::SMTLogicError, \ ::solidity::smtutil::SMTLogicError, \
DESCRIPTION, \ (DESCRIPTION), \
"SMT assertion failed" \ "SMT assertion failed" \
) )

View File

@ -63,7 +63,7 @@ inline std::string stringOrDefault(std::string _string, std::string _defaultStri
if (!(_condition)) \ if (!(_condition)) \
solThrow( \ solThrow( \
_exceptionType, \ _exceptionType, \
::solidity::util::assertions::stringOrDefault(_description, _defaultDescription) \ ::solidity::util::assertions::stringOrDefault((_description), (_defaultDescription)) \
); \ ); \
} \ } \
while (false) while (false)
@ -72,6 +72,6 @@ inline std::string stringOrDefault(std::string _string, std::string _defaultStri
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong."); /// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
/// The second parameter must be an exception class (rather than an instance). /// The second parameter must be an exception class (rather than an instance).
#define assertThrow(_condition, _exceptionType, _description) \ #define assertThrow(_condition, _exceptionType, _description) \
assertThrowWithDefaultDescription(_condition, _exceptionType, _description, "Assertion failed") assertThrowWithDefaultDescription((_condition), _exceptionType, (_description), "Assertion failed")
} }

View File

@ -48,7 +48,7 @@ struct Exception: virtual std::exception, virtual boost::exception
#define solThrow(_exceptionType, _description) \ #define solThrow(_exceptionType, _description) \
::boost::throw_exception( \ ::boost::throw_exception( \
_exceptionType() << \ _exceptionType() << \
::solidity::util::errinfo_comment(_description) << \ ::solidity::util::errinfo_comment((_description)) << \
::boost::throw_function(ETH_FUNC) << \ ::boost::throw_function(ETH_FUNC) << \
::boost::throw_file(__FILE__) << \ ::boost::throw_file(__FILE__) << \
::boost::throw_line(__LINE__) \ ::boost::throw_line(__LINE__) \

View File

@ -63,13 +63,13 @@ struct StackTooDeepError: virtual YulException
#endif #endif
#define yulAssert_1(CONDITION) \ #define yulAssert_1(CONDITION) \
yulAssert_2(CONDITION, "") yulAssert_2((CONDITION), "")
#define yulAssert_2(CONDITION, DESCRIPTION) \ #define yulAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \ assertThrowWithDefaultDescription( \
CONDITION, \ (CONDITION), \
::solidity::yul::YulAssertion, \ ::solidity::yul::YulAssertion, \
DESCRIPTION, \ (DESCRIPTION), \
"Yul assertion failed" \ "Yul assertion failed" \
) )

View File

@ -159,26 +159,33 @@ bool CommonOptions::parse(int argc, char const* const* argv)
po::variables_map arguments; po::variables_map arguments;
addOptions(); addOptions();
po::command_line_parser cmdLineParser(argc, argv); try
cmdLineParser.options(options); {
auto parsedOptions = cmdLineParser.run(); po::command_line_parser cmdLineParser(argc, argv);
po::store(parsedOptions, arguments); cmdLineParser.options(options);
po::notify(arguments); auto parsedOptions = cmdLineParser.run();
po::store(parsedOptions, arguments);
po::notify(arguments);
for (auto const& parsedOption: parsedOptions.options) for (auto const& parsedOption: parsedOptions.options)
if (parsedOption.position_key >= 0) if (parsedOption.position_key >= 0)
{ {
if ( if (
parsedOption.original_tokens.empty() || parsedOption.original_tokens.empty() ||
(parsedOption.original_tokens.size() == 1 && parsedOption.original_tokens.front().empty()) (parsedOption.original_tokens.size() == 1 && parsedOption.original_tokens.front().empty())
) )
continue; // ignore empty options continue; // ignore empty options
std::stringstream errorMessage; std::stringstream errorMessage;
errorMessage << "Unrecognized option: "; errorMessage << "Unrecognized option: ";
for (auto const& token: parsedOption.original_tokens) for (auto const& token: parsedOption.original_tokens)
errorMessage << token; errorMessage << token;
BOOST_THROW_EXCEPTION(std::runtime_error(errorMessage.str())); BOOST_THROW_EXCEPTION(std::runtime_error(errorMessage.str()));
} }
}
catch (po::error const& exception)
{
solThrow(ConfigException, exception.what());
}
if (vmPaths.empty()) if (vmPaths.empty())
{ {

View File

@ -75,6 +75,9 @@ struct CommonOptions
langutil::EVMVersion evmVersion() const; langutil::EVMVersion evmVersion() const;
virtual void addOptions(); virtual void addOptions();
// @returns true if the program should continue, false if it should exit immediately without
// reporting an error.
// Throws ConfigException or std::runtime_error if parsing fails.
virtual bool parse(int argc, char const* const* argv); virtual bool parse(int argc, char const* const* argv);
// Throws a ConfigException on error // Throws a ConfigException on error
virtual void validate() const; virtual void validate() const;

View File

@ -204,88 +204,106 @@ int registerTests(
return numTestsAdded; return numTestsAdded;
} }
void initializeOptions() bool initializeOptions()
{ {
auto const& suite = boost::unit_test::framework::master_test_suite(); auto const& suite = boost::unit_test::framework::master_test_suite();
auto options = std::make_unique<solidity::test::CommonOptions>(); auto options = std::make_unique<solidity::test::CommonOptions>();
solAssert(options->parse(suite.argc, suite.argv), "Failed to parse options!"); bool shouldContinue = options->parse(suite.argc, suite.argv);
if (!shouldContinue)
return false;
options->validate(); options->validate();
solidity::test::CommonOptions::setSingleton(std::move(options)); solidity::test::CommonOptions::setSingleton(std::move(options));
return true;
} }
} }
// TODO: Prototype -- why isn't this declared in the boost headers? // TODO: Prototype -- why isn't this declared in the boost headers?
// TODO: replace this with a (global) fixture. // TODO: replace this with a (global) fixture.
test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ); test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]);
test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[])
{ {
using namespace solidity::test; using namespace solidity::test;
master_test_suite_t& master = framework::master_test_suite(); master_test_suite_t& master = framework::master_test_suite();
master.p_name.value = "SolidityTests"; master.p_name.value = "SolidityTests";
initializeOptions(); try
if (!solidity::test::loadVMs(solidity::test::CommonOptions::get()))
exit(1);
if (solidity::test::CommonOptions::get().disableSemanticTests)
cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl;
if (!solidity::test::CommonOptions::get().enforceGasTest)
cout << endl << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl;
Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches);
if (CommonOptions::get().batches > 1)
cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl;
// Batch the boost tests
BoostBatcher boostBatcher(batcher);
traverse_test_tree(master, boostBatcher, true);
// Include the interactive tests in the automatic tests as well
for (auto const& ts: g_interactiveTestsuites)
{ {
auto const& options = solidity::test::CommonOptions::get(); bool shouldContinue = initializeOptions();
if (!shouldContinue)
exit(EXIT_SUCCESS);
if (ts.smt && options.disableSMT) if (!solidity::test::loadVMs(solidity::test::CommonOptions::get()))
continue; exit(EXIT_FAILURE);
if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests) if (solidity::test::CommonOptions::get().disableSemanticTests)
continue; cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl;
//TODO if (!solidity::test::CommonOptions::get().enforceGasTest)
//solAssert( cout << endl << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl;
registerTests(
master, Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches);
options.testPath / ts.path, if (CommonOptions::get().batches > 1)
ts.subpath, cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl;
options.enforceViaYul,
options.enforceCompileToEwasm, // Batch the boost tests
ts.labels, BoostBatcher boostBatcher(batcher);
ts.testCaseCreator, traverse_test_tree(master, boostBatcher, true);
batcher
); // Include the interactive tests in the automatic tests as well
// > 0, std::string("no ") + ts.title + " tests found"); for (auto const& ts: g_interactiveTestsuites)
{
auto const& options = solidity::test::CommonOptions::get();
if (ts.smt && options.disableSMT)
continue;
if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests)
continue;
//TODO
//solAssert(
registerTests(
master,
options.testPath / ts.path,
ts.subpath,
options.enforceViaYul,
options.enforceCompileToEwasm,
ts.labels,
ts.testCaseCreator,
batcher
);
// > 0, std::string("no ") + ts.title + " tests found");
}
if (solidity::test::CommonOptions::get().disableSemanticTests)
{
for (auto suite: {
"ABIDecoderTest",
"ABIEncoderTest",
"SolidityAuctionRegistrar",
"SolidityWallet",
"GasMeterTests",
"GasCostTests",
"SolidityEndToEndTest",
"SolidityOptimizer"
})
removeTestSuite(suite);
}
} }
catch (solidity::test::ConfigException const& exception)
if (solidity::test::CommonOptions::get().disableSemanticTests)
{ {
for (auto suite: { cerr << exception.what() << endl;
"ABIDecoderTest", exit(EXIT_FAILURE);
"ABIEncoderTest", }
"SolidityAuctionRegistrar", catch (std::runtime_error const& exception)
"SolidityWallet", {
"GasMeterTests", cerr << exception.what() << endl;
"GasCostTests", exit(EXIT_FAILURE);
"SolidityEndToEndTest",
"SolidityOptimizer"
})
removeTestSuite(suite);
} }
return nullptr; return nullptr;

View File

@ -15,20 +15,35 @@
#pragma once #pragma once
#include <libsolutil/AnsiColorized.h> #include <libsolutil/AnsiColorized.h>
#include <libsolutil/Assertions.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/Exceptions.h> #include <libsolutil/Exceptions.h>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/facilities/overload.hpp>
namespace solidity::frontend::test namespace solidity::frontend::test
{ {
#define soltestAssert(CONDITION, DESCRIPTION) \ struct InternalSoltestError: virtual util::Exception {};
do \
{ \
if (!(CONDITION)) \
BOOST_THROW_EXCEPTION(std::runtime_error(DESCRIPTION)); \
} \
while (false)
#if !BOOST_PP_VARIADICS_MSVC
#define soltestAssert(...) BOOST_PP_OVERLOAD(soltestAssert_,__VA_ARGS__)(__VA_ARGS__)
#else
#define soltestAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(soltestAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
#endif
#define soltestAssert_1(CONDITION) \
soltestAssert_2((CONDITION), "")
#define soltestAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
(CONDITION), \
::solidity::frontend::test::InternalSoltestError, \
(DESCRIPTION), \
"Soltest assertion failed" \
)
class TestParserError: virtual public util::Exception class TestParserError: virtual public util::Exception
{ {

View File

@ -75,9 +75,9 @@ void IsolTestOptions::addOptions()
bool IsolTestOptions::parse(int _argc, char const* const* _argv) bool IsolTestOptions::parse(int _argc, char const* const* _argv)
{ {
bool const res = CommonOptions::parse(_argc, _argv); bool const shouldContinue = CommonOptions::parse(_argc, _argv);
if (showHelp || !res) if (showHelp || !shouldContinue)
{ {
std::cout << options << std::endl; std::cout << options << std::endl;
return false; return false;
@ -85,7 +85,7 @@ bool IsolTestOptions::parse(int _argc, char const* const* _argv)
enforceGasTest = enforceGasTest || (evmVersion() == langutil::EVMVersion{} && !useABIEncoderV1); enforceGasTest = enforceGasTest || (evmVersion() == langutil::EVMVersion{} && !useABIEncoderV1);
return res; return shouldContinue;
} }
void IsolTestOptions::validate() const void IsolTestOptions::validate() const

View File

@ -433,8 +433,9 @@ int main(int argc, char const *argv[])
{ {
auto options = std::make_unique<IsolTestOptions>(); auto options = std::make_unique<IsolTestOptions>();
if (!options->parse(argc, argv)) bool shouldContinue = options->parse(argc, argv);
return -1; if (!shouldContinue)
return EXIT_SUCCESS;
options->validate(); options->validate();
CommonOptions::setSingleton(std::move(options)); CommonOptions::setSingleton(std::move(options));
@ -443,7 +444,7 @@ int main(int argc, char const *argv[])
auto& options = dynamic_cast<IsolTestOptions const&>(CommonOptions::get()); auto& options = dynamic_cast<IsolTestOptions const&>(CommonOptions::get());
if (!solidity::test::loadVMs(options)) if (!solidity::test::loadVMs(options))
return 1; return EXIT_FAILURE;
if (options.disableSemanticTests) if (options.disableSemanticTests)
cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl; cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl;
@ -479,7 +480,7 @@ int main(int argc, char const *argv[])
if (stats) if (stats)
global_stats += *stats; global_stats += *stats;
else else
return 1; return EXIT_FAILURE;
} }
cout << endl << "Summary: "; cout << endl << "Summary: ";
@ -497,11 +498,22 @@ int main(int argc, char const *argv[])
if (options.disableSemanticTests) if (options.disableSemanticTests)
cout << "\nNOTE: Skipped semantics tests.\n" << endl; cout << "\nNOTE: Skipped semantics tests.\n" << endl;
return global_stats ? 0 : 1; return global_stats ? EXIT_SUCCESS : EXIT_FAILURE;
} }
catch (std::exception const& _exception) catch (boost::program_options::error const& exception)
{ {
cerr << _exception.what() << endl; cerr << exception.what() << endl;
return 1; return EXIT_FAILURE;
}
catch (std::runtime_error const& exception)
{
cerr << exception.what() << endl;
return EXIT_FAILURE;
}
catch (...)
{
cerr << "Unhandled exception caught." << endl;
cerr << boost::current_exception_diagnostic_information() << endl;
return EXIT_FAILURE;
} }
} }