CommandLineInterface: Update control flow to accommodate the new way of reporting errors

This commit is contained in:
Kamil Śliwak 2021-10-12 14:16:29 +02:00
parent e829bcd933
commit c8380c25bb
7 changed files with 196 additions and 158 deletions

View File

@ -404,7 +404,7 @@ void CommandLineInterface::handleGasEstimation(string const& _contract)
} }
} }
bool CommandLineInterface::readInputFiles() void CommandLineInterface::readInputFiles()
{ {
solAssert(!m_standardJsonInput.has_value(), ""); solAssert(!m_standardJsonInput.has_value(), "");
@ -413,7 +413,7 @@ bool CommandLineInterface::readInputFiles()
m_options.input.mode == InputMode::License || m_options.input.mode == InputMode::License ||
m_options.input.mode == InputMode::Version m_options.input.mode == InputMode::Version
) )
return true; return;
m_fileReader.setBasePath(m_options.input.basePath); m_fileReader.setBasePath(m_options.input.basePath);
@ -501,8 +501,6 @@ bool CommandLineInterface::readInputFiles()
if (m_fileReader.sourceCodes().empty() && !m_standardJsonInput.has_value()) if (m_fileReader.sourceCodes().empty() && !m_standardJsonInput.has_value())
solThrow(CommandLineValidationError, "All specified input files either do not exist or are not regular files."); solThrow(CommandLineValidationError, "All specified input files either do not exist or are not regular files.");
return true;
} }
map<string, Json::Value> CommandLineInterface::parseAstFromInput() map<string, Json::Value> CommandLineInterface::parseAstFromInput()
@ -568,6 +566,30 @@ void CommandLineInterface::createJson(string const& _fileName, string const& _js
createFile(boost::filesystem::basename(_fileName) + string(".json"), _json); createFile(boost::filesystem::basename(_fileName) + string(".json"), _json);
} }
bool CommandLineInterface::run(int _argc, char const* const* _argv)
{
try
{
if (!parseArguments(_argc, _argv))
return false;
readInputFiles();
processInput();
return true;
}
catch (CommandLineError const& _exception)
{
m_hasOutput = true;
// There might be no message in the exception itself if the error output is bulky and has
// already been printed to stderr (this happens e.g. for compiler errors).
if (_exception.what() != ""s)
serr() << _exception.what() << endl;
return false;
}
}
bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv) bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv)
{ {
CommandLineParser parser; CommandLineParser parser;
@ -581,22 +603,13 @@ bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv)
return false; return false;
} }
try
{
parser.parse(_argc, _argv); parser.parse(_argc, _argv);
}
catch (CommandLineValidationError const& _exception)
{
serr() << _exception.what() << endl;
return false;
}
m_options = parser.options(); m_options = parser.options();
return true; return true;
} }
bool CommandLineInterface::processInput() void CommandLineInterface::processInput()
{ {
switch (m_options.input.mode) switch (m_options.input.mode)
{ {
@ -619,18 +632,15 @@ bool CommandLineInterface::processInput()
break; break;
} }
case InputMode::Assembler: case InputMode::Assembler:
if (!assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine)) assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine);
return false;
break; break;
case InputMode::Linker: case InputMode::Linker:
if (!link()) link();
return false;
writeLinkedFiles(); writeLinkedFiles();
break; break;
case InputMode::Compiler: case InputMode::Compiler:
case InputMode::CompilerWithASTImport: case InputMode::CompilerWithASTImport:
if (!compile()) compile();
return false;
outputCompilationResults(); outputCompilationResults();
} }
@ -651,7 +661,7 @@ void CommandLineInterface::printLicense()
sout() << licenseText << endl; sout() << licenseText << endl;
} }
bool CommandLineInterface::compile() void CommandLineInterface::compile()
{ {
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, "");
@ -714,6 +724,8 @@ bool CommandLineInterface::compile()
} }
catch (Exception const& _exc) catch (Exception const& _exc)
{ {
// FIXME: AST import is missing proper validations. This hack catches failing
// assertions and presents them as if they were compiler errors.
solThrow(CommandLineExecutionError, "Failed to import AST: "s + _exc.what()); solThrow(CommandLineExecutionError, "Failed to import AST: "s + _exc.what());
} }
} }
@ -754,8 +766,6 @@ bool CommandLineInterface::compile()
solThrow(CommandLineExecutionError, ""); solThrow(CommandLineExecutionError, "");
} }
} }
return true;
} }
void CommandLineInterface::handleCombinedJSON() void CommandLineInterface::handleCombinedJSON()
@ -884,7 +894,7 @@ void CommandLineInterface::handleAst()
} }
} }
bool CommandLineInterface::link() void CommandLineInterface::link()
{ {
solAssert(m_options.input.mode == InputMode::Linker, ""); solAssert(m_options.input.mode == InputMode::Linker, "");
@ -945,8 +955,6 @@ bool CommandLineInterface::link()
src.second.resize(src.second.size() - 1); src.second.resize(src.second.size() - 1);
} }
m_fileReader.setSources(move(sourceCodes)); m_fileReader.setSources(move(sourceCodes));
return true;
} }
void CommandLineInterface::writeLinkedFiles() void CommandLineInterface::writeLinkedFiles()
@ -986,7 +994,7 @@ string CommandLineInterface::objectWithLinkRefsHex(evmasm::LinkerObject const& _
return out; return out;
} }
bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine) void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine)
{ {
solAssert(m_options.input.mode == InputMode::Assembler, ""); solAssert(m_options.input.mode == InputMode::Assembler, "");
@ -1090,8 +1098,6 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
serr() << "No text representation found." << endl; serr() << "No text representation found." << endl;
} }
} }
return true;
} }
void CommandLineInterface::outputCompilationResults() void CommandLineInterface::outputCompilationResults()
@ -1128,14 +1134,10 @@ void CommandLineInterface::outputCompilationResults()
ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes()); ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes());
if (!m_options.output.dir.empty()) if (!m_options.output.dir.empty())
{
createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret);
}
else else
{
sout() << "EVM assembly:" << endl << ret << endl; sout() << "EVM assembly:" << endl << ret << endl;
} }
}
if (m_options.compiler.estimateGas) if (m_options.compiler.estimateGas)
handleGasEstimation(contract); handleGasEstimation(contract);

View File

@ -51,12 +51,28 @@ public:
m_options(_options) m_options(_options)
{} {}
/// Parse command line arguments and return false if we should not continue /// Parses command-line arguments, executes the requested operation and handles validation and
/// execution errors.
/// @returns false if it catches a @p CommandLineValidationError or if the application is
/// expected to exit with a non-zero exit code despite there being no error.
bool run(int _argc, char const* const* _argv);
/// Parses command line arguments and stores the result in @p m_options.
/// @throws CommandLineValidationError if command-line arguments are invalid.
/// @returns false if the application is expected to exit with a non-zero exit code despite
/// there being no error.
bool parseArguments(int _argc, char const* const* _argv); bool parseArguments(int _argc, char const* const* _argv);
/// Read the content of all input files and initialize the file reader.
bool readInputFiles(); /// Reads the content of all input files and initializes the file reader.
/// Parse the files, create source code objects, print the output. /// @throws CommandLineValidationError if it fails to read the input files (invalid paths,
bool processInput(); /// non-existent files, not enough or too many input files, etc.).
void readInputFiles();
/// Executes the requested operation (compilation, assembling, standard JSON, etc.) and prints
/// results to the terminal.
/// @throws CommandLineExecutionError if execution fails due to errors in the input files.
/// @throws CommandLineOutputError if creating output files or writing to them fails.
void processInput();
CommandLineOptions const& options() const { return m_options; } CommandLineOptions const& options() const { return m_options; }
FileReader const& fileReader() const { return m_fileReader; } FileReader const& fileReader() const { return m_fileReader; }
@ -65,15 +81,15 @@ public:
private: private:
void printVersion(); void printVersion();
void printLicense(); void printLicense();
bool compile(); void compile();
bool link(); void link();
void writeLinkedFiles(); void writeLinkedFiles();
/// @returns the ``// <identifier> -> name`` hint for library placeholders. /// @returns the ``// <identifier> -> name`` hint for library placeholders.
static std::string libraryPlaceholderHint(std::string const& _libraryName); static std::string libraryPlaceholderHint(std::string const& _libraryName);
/// @returns the full object with library placeholder hints in hex. /// @returns the full object with library placeholder hints in hex.
static std::string objectWithLinkRefsHex(evmasm::LinkerObject const& _obj); static std::string objectWithLinkRefsHex(evmasm::LinkerObject const& _obj);
bool assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine); void assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine);
void outputCompilationResults(); void outputCompilationResults();

View File

@ -62,12 +62,7 @@ int main(int argc, char** argv)
{ {
setDefaultOrCLocale(); setDefaultOrCLocale();
solidity::frontend::CommandLineInterface cli(cin, cout, cerr); solidity::frontend::CommandLineInterface cli(cin, cout, cerr);
bool success = return cli.run(argc, argv) ? 0 : 1;
cli.parseArguments(argc, argv) &&
cli.readInputFiles() &&
cli.processInput();
return success ? 0 : 1;
} }
catch (smtutil::SMTLogicError const& _exception) catch (smtutil::SMTLogicError const& _exception)
{ {

View File

@ -19,6 +19,7 @@
/// Unit tests for solc/CommandLineInterface.h /// Unit tests for solc/CommandLineInterface.h
#include <solc/CommandLineInterface.h> #include <solc/CommandLineInterface.h>
#include <solc/Exceptions.h>
#include <test/solc/Common.h> #include <test/solc/Common.h>
@ -114,7 +115,7 @@ BOOST_AUTO_TEST_SUITE(CommandLineInterfaceTest)
BOOST_AUTO_TEST_CASE(help) BOOST_AUTO_TEST_CASE(help)
{ {
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--help"}, "", /* _processInput */ true); OptionsReaderAndMessages result = runCLI({"solc", "--help"}, "");
BOOST_TEST(result.success); BOOST_TEST(result.success);
BOOST_TEST(boost::starts_with(result.stdoutContent, "solc, the Solidity commandline compiler.")); BOOST_TEST(boost::starts_with(result.stdoutContent, "solc, the Solidity commandline compiler."));
@ -124,7 +125,7 @@ BOOST_AUTO_TEST_CASE(help)
BOOST_AUTO_TEST_CASE(license) BOOST_AUTO_TEST_CASE(license)
{ {
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--license"}, "", /* _processInput */ true); OptionsReaderAndMessages result = runCLI({"solc", "--license"}, "");
BOOST_TEST(result.success); BOOST_TEST(result.success);
BOOST_TEST(boost::starts_with(result.stdoutContent, "Most of the code is licensed under GPLv3")); BOOST_TEST(boost::starts_with(result.stdoutContent, "Most of the code is licensed under GPLv3"));
@ -134,7 +135,7 @@ BOOST_AUTO_TEST_CASE(license)
BOOST_AUTO_TEST_CASE(version) BOOST_AUTO_TEST_CASE(version)
{ {
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--version"}, "", /* _processInput */ true); OptionsReaderAndMessages result = runCLI({"solc", "--version"}, "");
BOOST_TEST(result.success); BOOST_TEST(result.success);
BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n")); BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n"));
@ -158,17 +159,16 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes)
string expectedMessage = string expectedMessage =
"The following options are mutually exclusive: " "The following options are mutually exclusive: "
"--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. " "--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. "
"Select at most one.\n"; "Select at most one.";
for (string const& mode1: inputModeOptions) for (string const& mode1: inputModeOptions)
for (string const& mode2: inputModeOptions) for (string const& mode2: inputModeOptions)
if (mode1 != mode2) if (mode1 != mode2)
{ BOOST_CHECK_EXCEPTION(
vector<string> commandLine = {"solc", mode1, mode2}; parseCommandLineAndReadInputFiles({"solc", mode1, mode2}),
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); CommandLineValidationError,
BOOST_TEST(!result.success); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
BOOST_TEST(result.stderrContent == expectedMessage); );
}
} }
BOOST_AUTO_TEST_CASE(cli_input) BOOST_AUTO_TEST_CASE(cli_input)
@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(cli_ignore_missing_no_files_exist)
"\"" + (tempDir.path() / "input2.sol").string() + "\" is not found. Skipping.\n" "\"" + (tempDir.path() / "input2.sol").string() + "\" is not found. Skipping.\n"
"All specified input files either do not exist or are not regular files.\n"; "All specified input files either do not exist or are not regular files.\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ OptionsReaderAndMessages result = runCLI({
"solc", "solc",
(tempDir.path() / "input1.sol").string(), (tempDir.path() / "input1.sol").string(),
(tempDir.path() / "input2.sol").string(), (tempDir.path() / "input2.sol").string(),
@ -267,11 +267,13 @@ BOOST_AUTO_TEST_CASE(cli_not_a_file)
{ {
TemporaryDirectory tempDir(TEST_CASE_NAME); TemporaryDirectory tempDir(TEST_CASE_NAME);
string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file.\n"; string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file.";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}); BOOST_CHECK_EXCEPTION(
BOOST_TEST(!result.success); parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}),
BOOST_TEST(result.stderrContent == expectedMessage); CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_base_path) BOOST_AUTO_TEST_CASE(standard_json_base_path)
@ -336,24 +338,26 @@ BOOST_AUTO_TEST_CASE(standard_json_two_input_files)
{ {
string expectedMessage = string expectedMessage =
"Too many input files for --standard-json.\n" "Too many input files for --standard-json.\n"
"Please either specify a single file name or provide its content on standard input.\n"; "Please either specify a single file name or provide its content on standard input.";
vector<string> commandLine = {"solc", "--standard-json", "input1.json", "input2.json"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "input2.json"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_one_input_file_and_stdin) BOOST_AUTO_TEST_CASE(standard_json_one_input_file_and_stdin)
{ {
string expectedMessage = string expectedMessage =
"Too many input files for --standard-json.\n" "Too many input files for --standard-json.\n"
"Please either specify a single file name or provide its content on standard input.\n"; "Please either specify a single file name or provide its content on standard input.";
vector<string> commandLine = {"solc", "--standard-json", "input1.json", "-"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "-"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_ignore_missing) BOOST_AUTO_TEST_CASE(standard_json_ignore_missing)
@ -362,29 +366,31 @@ BOOST_AUTO_TEST_CASE(standard_json_ignore_missing)
// This option is pretty much useless Standard JSON mode. // This option is pretty much useless Standard JSON mode.
string expectedMessage = string expectedMessage =
"\"" + (tempDir.path() / "input.json").string() + "\" is not found. Skipping.\n" "All specified input files either do not exist or are not regular files.";
"All specified input files either do not exist or are not regular files.\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc", "solc",
"--standard-json", "--standard-json",
(tempDir.path() / "input.json").string(), (tempDir.path() / "input.json").string(),
"--ignore-missing", "--ignore-missing",
}); }),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_remapping) BOOST_AUTO_TEST_CASE(standard_json_remapping)
{ {
string expectedMessage = string expectedMessage =
"Import remappings are not accepted on the command line in Standard JSON mode.\n" "Import remappings are not accepted on the command line in Standard JSON mode.\n"
"Please put them under 'settings.remappings' in the JSON input.\n"; "Please put them under 'settings.remappings' in the JSON input.";
vector<string> commandLine = {"solc", "--standard-json", "a=b"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--standard-json", "a=b"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path) BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path)
@ -997,11 +1003,7 @@ BOOST_AUTO_TEST_CASE(cli_include_paths)
canonicalWorkDir / "lib", canonicalWorkDir / "lib",
}; };
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( OptionsReaderAndMessages result = runCLI(commandLine, "");
commandLine,
"",
true /* _processInput */
);
BOOST_TEST(result.stderrContent == ""); BOOST_TEST(result.stderrContent == "");
BOOST_TEST(result.stdoutContent == ""); BOOST_TEST(result.stdoutContent == "");
@ -1087,11 +1089,7 @@ BOOST_AUTO_TEST_CASE(standard_json_include_paths)
FileReader::FileSystemPathSet expectedAllowedDirectories = {}; FileReader::FileSystemPathSet expectedAllowedDirectories = {};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( OptionsReaderAndMessages result = runCLI(commandLine, standardJsonInput);
commandLine,
standardJsonInput,
true /* _processInput */
);
Json::Value parsedStdout; Json::Value parsedStdout;
string jsonParsingErrors; string jsonParsingErrors;
@ -1119,18 +1117,19 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_empty_path)
TemporaryWorkingDirectory tempWorkDir(tempDir); TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "base/main.sol"}); createFilesWithParentDirs({tempDir.path() / "base/main.sol"});
string expectedMessage = "Empty values are not allowed in --include-path.\n"; string expectedMessage = "Empty values are not allowed in --include-path.";
vector<string> commandLine = { BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc", "solc",
"--base-path=base/", "--base-path=base/",
"--include-path", "include/", "--include-path", "include/",
"--include-path", "", "--include-path", "",
"base/main.sol", "base/main.sol",
}; }),
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); CommandLineValidationError,
BOOST_TEST(!result.success); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
BOOST_TEST(result.stderrContent == expectedMessage); );
} }
BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path) BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path)
@ -1139,12 +1138,13 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path)
TemporaryWorkingDirectory tempWorkDir(tempDir); TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "contract.sol"}); createFilesWithParentDirs({tempDir.path() / "contract.sol"});
string expectedMessage = "--include-path option requires a non-empty base path.\n"; string expectedMessage = "--include-path option requires a non-empty base path.";
vector<string> commandLine = {"solc", "--include-path", "include/", "contract.sol"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--include-path", "include/", "contract.sol"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions) BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions)
@ -1173,7 +1173,8 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions
{ {
// import "contract1.sol" and import "contract2.sol" would be ambiguous: // import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = { BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc", "solc",
"--base-path=dir1/", "--base-path=dir1/",
"--include-path=dir2/", "--include-path=dir2/",
@ -1181,15 +1182,16 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions
"dir2/contract1.sol", "dir2/contract1.sol",
"dir1/contract2.sol", "dir1/contract2.sol",
"dir2/contract2.sol", "dir2/contract2.sol",
}; }),
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
BOOST_REQUIRE(!result.success); );
} }
{ {
// import "contract1.sol" and import "contract2.sol" would be ambiguous: // import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = { BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc", "solc",
"--base-path=dir3/", "--base-path=dir3/",
"--include-path=dir1/", "--include-path=dir1/",
@ -1198,10 +1200,10 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions
"dir2/contract1.sol", "dir2/contract1.sol",
"dir1/contract2.sol", "dir1/contract2.sol",
"dir2/contract2.sol", "dir2/contract2.sol",
}; }),
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
BOOST_REQUIRE(!result.success); );
} }
{ {
@ -1316,12 +1318,7 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_ambiguous_import)
"3 | import \"contract.sol\";\n" "3 | import \"contract.sol\";\n"
" | ^^^^^^^^^^^^^^^^^^^^^^\n\n"; " | ^^^^^^^^^^^^^^^^^^^^^^\n\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( OptionsReaderAndMessages result = runCLI(commandLine, mainContractSource);
commandLine,
mainContractSource,
true /* _processInput */
);
BOOST_TEST(result.stderrContent == expectedMessage); BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success); BOOST_REQUIRE(!result.success);
} }

View File

@ -95,11 +95,7 @@ ImportCheck checkImport(
"pragma solidity >=0.0;\n" + "pragma solidity >=0.0;\n" +
_import + ";"; _import + ";";
test::OptionsReaderAndMessages cliResult = test::parseCommandLineAndReadInputFiles( test::OptionsReaderAndMessages cliResult = test::runCLI(commandLine, standardInputContent);
commandLine,
standardInputContent,
true /* processInput */
);
if (cliResult.success) if (cliResult.success)
return ImportCheck::OK(); return ImportCheck::OK();

View File

@ -41,18 +41,34 @@ vector<char const*> test::makeArgv(vector<string> const& _commandLine)
test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles( test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles(
vector<string> const& _commandLine, vector<string> const& _commandLine,
string const& _standardInputContent, string const& _standardInputContent
bool _processInput
) )
{ {
vector<char const*> argv = makeArgv(_commandLine); vector<char const*> argv = makeArgv(_commandLine);
stringstream sin(_standardInputContent), sout, serr; stringstream sin(_standardInputContent), sout, serr;
CommandLineInterface cli(sin, sout, serr); CommandLineInterface cli(sin, sout, serr);
bool success = cli.parseArguments(static_cast<int>(_commandLine.size()), argv.data()); bool success = cli.parseArguments(static_cast<int>(_commandLine.size()), argv.data());
if (success) cli.readInputFiles();
success = cli.readInputFiles();
if (success && _processInput) return {
success = cli.processInput(); success,
cli.options(),
cli.fileReader(),
cli.standardJsonInput(),
sout.str(),
stripPreReleaseWarning(serr.str()),
};
}
test::OptionsReaderAndMessages test::runCLI(
vector<string> const& _commandLine,
string const& _standardInputContent
)
{
vector<char const*> argv = makeArgv(_commandLine);
stringstream sin(_standardInputContent), sout, serr;
CommandLineInterface cli(sin, sout, serr);
bool success = cli.run(static_cast<int>(_commandLine.size()), argv.data());
return { return {
success, success,

View File

@ -44,10 +44,26 @@ struct OptionsReaderAndMessages
std::vector<char const*> makeArgv(std::vector<std::string> const& _commandLine); std::vector<char const*> makeArgv(std::vector<std::string> const& _commandLine);
/// Runs only command-line parsing, without compilation, assembling or any other input processing.
/// Lets through any @a CommandLineErrors throw by the CLI.
/// Note: This uses the @a CommandLineInterface class and does not actually spawn a new process.
/// @param _commandLine Arguments in the form of strings that would be specified on the command-line.
/// You must specify the program name as the first item.
/// @param _standardInputContent Content that the CLI will be able to read from its standard input.
OptionsReaderAndMessages parseCommandLineAndReadInputFiles( OptionsReaderAndMessages parseCommandLineAndReadInputFiles(
std::vector<std::string> const& _commandLine, std::vector<std::string> const& _commandLine,
std::string const& _standardInputContent = "", std::string const& _standardInputContent = ""
bool _processInput = false );
/// Runs all stages of command-line interface processing, including error handling.
/// Never throws @a CommandLineError - validation errors are included in the returned stderr content.
/// Note: This uses the @a CommandLineInterface class and does not actually spawn a new process.
/// @param _commandLine Arguments in the form of strings that would be specified on the command-line.
/// You must specify the program name as the first item.
/// @param _standardInputContent Content that the CLI will be able to read from its standard input.
OptionsReaderAndMessages runCLI(
std::vector<std::string> const& _commandLine,
std::string const& _standardInputContent = ""
); );
std::string stripPreReleaseWarning(std::string const& _stderrContent); std::string stripPreReleaseWarning(std::string const& _stderrContent);