diff --git a/test/TestCaseReader.h b/test/TestCaseReader.h index 5397020c80cb..321bf2472e51 100644 --- a/test/TestCaseReader.h +++ b/test/TestCaseReader.h @@ -22,14 +22,19 @@ #include +#include #include +#include +#include #include #include #include +#include #include +#include #include #include @@ -73,6 +78,10 @@ class TestCaseReader template E enumSetting(std::string const& _name, std::map const& _choices, std::string const& _defaultChoice); + template + std::vector enumListSetting(std::string const& _name, std::map const& _choices, std::vector const& _defaultValue); + template + std::set enumSetSetting(std::string const& _name, std::map const& _choices, std::set const& _defaultValue); void ensureAllSettingsRead() const; @@ -105,4 +114,52 @@ E TestCaseReader::enumSetting(std::string const& _name, std::map return _choices.at(value); } +template +std::vector TestCaseReader::enumListSetting(std::string const& _name, std::map const& _choices, std::vector const& _defaultValue) +{ + std::map const labelToItem = util::invertMap(_choices); + auto const translateToLabel = [&](E _item) { return _choices.at(_item); }; + + for (std::string const& label: labelToItem | ranges::views::keys) + soltestAssert(label.find(",") == std::string::npos && boost::algorithm::trim_copy(label) == label); + + for (E item: _defaultValue) + soltestAssert(_choices.contains(item)); + + std::string value = stringSetting( + _name, + util::joinHumanReadable(_defaultValue | ranges::views::transform(translateToLabel) | ranges::to) + ); + + std::vector selectedLabels; + boost::split(selectedLabels, value, boost::is_any_of(",")); + + std::vector selectedItems; + std::vector invalidLabels; + for (std::string const& label: selectedLabels) + { + std::string trimmedLabel = boost::trim_copy(label); + if (labelToItem.contains(trimmedLabel)) + selectedItems.push_back(labelToItem.at(trimmedLabel)); + else + invalidLabels.push_back(trimmedLabel); + } + + if (!invalidLabels.empty()) + solThrow(solidity::test::ValidationError, fmt::format( + "Invalid choices in '{}' setting: {}.\nAvailable choices: {}.", + _name, + util::joinHumanReadable(invalidLabels), + util::joinHumanReadable(labelToItem | ranges::views::keys) + )); + + return selectedItems; +} + +template +std::set TestCaseReader::enumSetSetting(std::string const& _name, std::map const& _choices, std::set const& _defaultValue) +{ + return enumListSetting(_name, _choices, _defaultValue | ranges::to) | ranges::to; +} + } diff --git a/test/libevmasm/EVMAssemblyTest.cpp b/test/libevmasm/EVMAssemblyTest.cpp index efb737cc47eb..3af4af1ea626 100644 --- a/test/libevmasm/EVMAssemblyTest.cpp +++ b/test/libevmasm/EVMAssemblyTest.cpp @@ -25,7 +25,6 @@ #include #include -#include #include #include @@ -40,12 +39,12 @@ using namespace solidity::frontend::test; using namespace solidity::langutil; using namespace solidity::util; -std::vector const EVMAssemblyTest::c_outputLabels = { - "InputAssemblyJSON", - "Assembly", - "Bytecode", - "Opcodes", - "SourceMappings", +std::map const EVMAssemblyTest::c_outputLabels = { + {EVMAssemblyTest::Output::InputAssemblyJSON, "InputAssemblyJSON"}, + {EVMAssemblyTest::Output::Assembly, "Assembly"}, + {EVMAssemblyTest::Output::Bytecode, "Bytecode"}, + {EVMAssemblyTest::Output::Opcodes, "Opcodes"}, + {EVMAssemblyTest::Output::SourceMappings, "SourceMappings"}, }; std::unique_ptr EVMAssemblyTest::create(Config const& _config) @@ -66,7 +65,11 @@ EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename): else solThrow(ValidationError, "Not an assembly test: \"" + _filename + "\". Allowed extensions: .asm, .asmjson."); - m_selectedOutputs = m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings"); + m_selectedOutputs = m_reader.enumSetSetting( + "outputs", + c_outputLabels, + {Output::Assembly, Output::Bytecode, Output::Opcodes, Output::SourceMappings} + ); OptimisationPreset optimizationPreset = m_reader.enumSetting( "optimizationPreset", { @@ -145,25 +148,20 @@ TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string con } soltestAssert(evmAssemblyStack.compilationSuccessful()); - auto const produceOutput = [&](std::string const& _output) { - if (_output == "InputAssemblyJSON") - return assemblyJSON; - if (_output == "Assembly") - return evmAssemblyStack.assemblyString({{m_reader.fileName().filename().string(), m_source}}); - if (_output == "Bytecode") - return util::toHex(evmAssemblyStack.object().bytecode); - if (_output == "Opcodes") - return disassemble(evmAssemblyStack.object().bytecode, CommonOptions::get().evmVersion()); - if (_output == "SourceMappings") - return evmAssemblyStack.sourceMapping(); - soltestAssert(false); + auto const produceOutput = [&](Output _output) { + switch (_output) + { + case Output::InputAssemblyJSON: return assemblyJSON; + case Output::Assembly: return evmAssemblyStack.assemblyString({{m_reader.fileName().filename().string(), m_source}}); + case Output::Bytecode: return util::toHex(evmAssemblyStack.object().bytecode); + case Output::Opcodes: return disassemble(evmAssemblyStack.object().bytecode, CommonOptions::get().evmVersion()); + case Output::SourceMappings: return evmAssemblyStack.sourceMapping(); + } unreachable(); }; - std::set selectedOutputSet; - boost::split(selectedOutputSet, m_selectedOutputs, boost::is_any_of(",")); - for (std::string const& output: c_outputLabels) - if (selectedOutputSet.contains(output)) + for (Output output: c_outputLabels | ranges::views::keys) + if (m_selectedOutputs.contains(output)) { if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') m_obtainedResult += "\n"; @@ -171,8 +169,8 @@ TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string con // Don't trim on the left to avoid stripping indentation. std::string content = produceOutput(output); boost::trim_right(content); - std::string separator = (content.empty() ? "" : (output == "Assembly" ? "\n" : " ")); - m_obtainedResult += output + ":" + separator + content; + std::string separator = (content.empty() ? "" : (output == Output::Assembly ? "\n" : " ")); + m_obtainedResult += c_outputLabels.at(output) + ":" + separator + content; } return checkResult(_stream, _linePrefix, _formatted); diff --git a/test/libevmasm/EVMAssemblyTest.h b/test/libevmasm/EVMAssemblyTest.h index 041409393fdc..63e21d9d4aca 100644 --- a/test/libevmasm/EVMAssemblyTest.h +++ b/test/libevmasm/EVMAssemblyTest.h @@ -24,10 +24,11 @@ #include +#include #include #include +#include #include -#include namespace solidity::evmasm::test { @@ -48,10 +49,19 @@ class EVMAssemblyTest: public frontend::test::EVMVersionRestrictedTestCase Plain, }; - static std::vector const c_outputLabels; + enum class Output + { + InputAssemblyJSON, + Assembly, + Bytecode, + Opcodes, + SourceMappings, + }; + + static std::map const c_outputLabels; AssemblyFormat m_assemblyFormat{}; - std::string m_selectedOutputs; + std::set m_selectedOutputs; evmasm::Assembly::OptimiserSettings m_optimizerSettings; }; diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index c5714192ed6e..dc3ca19b3d79 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -44,6 +44,13 @@ using namespace solidity::frontend; using namespace solidity::frontend::test; using namespace solidity::test; +std::map const ObjectCompilerTest::c_outputLabels = { + {ObjectCompilerTest::Output::Assembly, "Assembly"}, + {ObjectCompilerTest::Output::Bytecode, "Bytecode"}, + {ObjectCompilerTest::Output::Opcodes, "Opcodes"}, + {ObjectCompilerTest::Output::SourceMappings, "SourceMappings"}, +}; + ObjectCompilerTest::ObjectCompilerTest(std::string const& _filename): EVMVersionRestrictedTestCase(_filename) { @@ -59,11 +66,11 @@ ObjectCompilerTest::ObjectCompilerTest(std::string const& _filename): "minimal" ); - constexpr std::array allowedOutputs = {"Assembly", "Bytecode", "Opcodes", "SourceMappings"}; - boost::split(m_outputSetting, m_reader.stringSetting("outputs", "Assembly,Bytecode,Opcodes,SourceMappings"), boost::is_any_of(",")); - for (auto const& output: m_outputSetting) - if (std::find(allowedOutputs.begin(), allowedOutputs.end(), output) == allowedOutputs.end()) - solThrow(ValidationError, "Invalid output type: \"" + output + "\""); + m_selectedOutputs = m_reader.enumSetSetting( + "outputs", + c_outputLabels, + c_outputLabels | ranges::views::keys | ranges::to + ); m_expectation = m_reader.simpleExpectations(); } @@ -86,26 +93,29 @@ TestCase::TestResult ObjectCompilerTest::run(std::ostream& _stream, std::string solAssert(obj.bytecode); solAssert(obj.sourceMappings); - if (std::find(m_outputSetting.begin(), m_outputSetting.end(), "Assembly") != m_outputSetting.end()) - m_obtainedResult = "Assembly:\n" + obj.assembly->assemblyString(yulStack.debugInfoSelection()); - if (obj.bytecode->bytecode.empty()) - m_obtainedResult += "-- empty bytecode --\n"; - else - { - if (std::find(m_outputSetting.begin(), m_outputSetting.end(), "Bytecode") != m_outputSetting.end()) - m_obtainedResult += "Bytecode: " + util::toHex(obj.bytecode->bytecode); - if (std::find(m_outputSetting.begin(), m_outputSetting.end(), "Opcodes") != m_outputSetting.end()) + auto const produceOutput = [&](Output _output) { + switch (_output) { - m_obtainedResult += (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') ? "\n" : ""; - m_obtainedResult += "Opcodes: " + - boost::trim_copy(evmasm::disassemble(obj.bytecode->bytecode, CommonOptions::get().evmVersion())); + case Output::Assembly: return obj.assembly->assemblyString(yulStack.debugInfoSelection()); + case Output::Bytecode: return util::toHex(obj.bytecode->bytecode); + case Output::Opcodes: return evmasm::disassemble(obj.bytecode->bytecode, CommonOptions::get().evmVersion()); + case Output::SourceMappings: return *obj.sourceMappings; } - if (std::find(m_outputSetting.begin(), m_outputSetting.end(), "SourceMappings") != m_outputSetting.end()) + unreachable(); + }; + + for (Output output: c_outputLabels | ranges::views::keys) + if (m_selectedOutputs.contains(output)) { - m_obtainedResult += (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') ? "\n" : ""; - m_obtainedResult += "SourceMappings:" + (obj.sourceMappings->empty() ? "" : " " + *obj.sourceMappings) + "\n"; + if (!m_obtainedResult.empty() && m_obtainedResult.back() != '\n') + m_obtainedResult += "\n"; + + // Don't trim on the left to avoid stripping indentation. + std::string content = produceOutput(output); + boost::trim_right(content); + std::string separator = (content.empty() ? "" : (output == Output::Assembly ? "\n" : " ")); + m_obtainedResult += c_outputLabels.at(output) + ":" + separator + content; } - } return checkResult(_stream, _linePrefix, _formatted); } diff --git a/test/libyul/ObjectCompilerTest.h b/test/libyul/ObjectCompilerTest.h index 7a7941a69f95..f9782dee6e82 100644 --- a/test/libyul/ObjectCompilerTest.h +++ b/test/libyul/ObjectCompilerTest.h @@ -51,10 +51,20 @@ class ObjectCompilerTest: public solidity::frontend::test::EVMVersionRestrictedT TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; private: + enum class Output + { + Assembly, + Bytecode, + Opcodes, + SourceMappings, + }; + void disambiguate(); + static std::map const c_outputLabels; + frontend::OptimisationPreset m_optimisationPreset; - std::vector m_outputSetting; + std::set m_selectedOutputs; }; }