Merge pull request #12007 from ethereum/include-path-option

`--include-path` option (revived)
This commit is contained in:
chriseth
2021-09-27 15:40:14 +02:00
committed by GitHub
12 changed files with 722 additions and 39 deletions
+2 -1
View File
@@ -31,7 +31,8 @@ void solidity::test::createFilesWithParentDirs(set<boost::filesystem::path> cons
if (!path.parent_path().empty())
boost::filesystem::create_directories(path.parent_path());
ofstream newFile(path.string());
// Use binary mode to avoid line ending conversion on Windows.
ofstream newFile(path.string(), std::ofstream::binary);
newFile << _content;
if (newFile.fail() || !boost::filesystem::exists(path))
+423
View File
@@ -27,6 +27,8 @@
#include <test/FilesystemUtils.h>
#include <test/TemporaryDirectory.h>
#include <libsolutil/JSON.h>
#include <range/v3/view/transform.hpp>
#include <map>
@@ -868,6 +870,427 @@ BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_and_stdin)
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base");
}
BOOST_AUTO_TEST_CASE(cli_include_paths)
{
TemporaryDirectory tempDir({"base/", "include/", "lib/nested/"}, TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
string const preamble =
"// SPDX-License-Identifier: GPL-3.0\n"
"pragma solidity >=0.0;\n";
string const mainContractSource = preamble +
"import \"contract.sol\";\n"
"import \"contract_via_callback.sol\";\n"
"import \"include.sol\";\n"
"import \"include_via_callback.sol\";\n"
"import \"nested.sol\";\n"
"import \"nested_via_callback.sol\";\n"
"import \"lib.sol\";\n"
"import \"lib_via_callback.sol\";\n";
createFilesWithParentDirs(
{
tempDir.path() / "base/contract.sol",
tempDir.path() / "base/contract_via_callback.sol",
tempDir.path() / "include/include.sol",
tempDir.path() / "include/include_via_callback.sol",
tempDir.path() / "lib/nested/nested.sol",
tempDir.path() / "lib/nested/nested_via_callback.sol",
tempDir.path() / "lib/lib.sol",
tempDir.path() / "lib/lib_via_callback.sol",
},
preamble
);
createFilesWithParentDirs({tempDir.path() / "base/main.sol"}, mainContractSource);
boost::filesystem::path canonicalWorkDir = boost::filesystem::canonical(tempDir);
boost::filesystem::path expectedWorkDir = "/" / canonicalWorkDir.relative_path();
vector<string> commandLine = {
"solc",
"--no-color",
"--base-path=base/",
"--include-path=include/",
"--include-path=lib/nested",
"--include-path=lib/",
"base/main.sol",
"base/contract.sol",
"include/include.sol",
"lib/nested/nested.sol",
"lib/lib.sol",
};
CommandLineOptions expectedOptions;
expectedOptions.input.paths = {
"base/main.sol",
"base/contract.sol",
"include/include.sol",
"lib/nested/nested.sol",
"lib/lib.sol",
};
expectedOptions.input.basePath = "base/";
expectedOptions.input.includePaths = {
"include/",
"lib/nested",
"lib/",
};
expectedOptions.formatting.coloredOutput = false;
expectedOptions.modelChecker.initialize = true;
map<string, string> expectedSources = {
{"main.sol", mainContractSource},
{"contract.sol", preamble},
{"contract_via_callback.sol", preamble},
{"include.sol", preamble},
{"include_via_callback.sol", preamble},
{"nested.sol", preamble},
{"nested_via_callback.sol", preamble},
{"lib.sol", preamble},
{"lib_via_callback.sol", preamble},
};
vector<boost::filesystem::path> expectedIncludePaths = {
expectedWorkDir / "include/",
expectedWorkDir / "lib/nested",
expectedWorkDir / "lib/",
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {
canonicalWorkDir / "base",
canonicalWorkDir / "include",
canonicalWorkDir / "lib/nested",
canonicalWorkDir / "lib",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(
commandLine,
"",
true /* _processInput */
);
BOOST_TEST(result.stderrContent == "");
BOOST_TEST(result.stdoutContent == "");
BOOST_REQUIRE(result.success);
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.includePaths() == expectedIncludePaths);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base/");
}
BOOST_AUTO_TEST_CASE(standard_json_include_paths)
{
TemporaryDirectory tempDir({"base/", "include/", "lib/nested/"}, TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
string const preamble =
"// SPDX-License-Identifier: GPL-3.0\n"
"pragma solidity >=0.0;\n";
string const mainContractSource = preamble +
"import 'contract_via_callback.sol';\n"
"import 'include_via_callback.sol';\n"
"import 'nested_via_callback.sol';\n"
"import 'lib_via_callback.sol';\n";
string const standardJsonInput = R"(
{
"language": "Solidity",
"sources": {
"main.sol": {"content": ")" + mainContractSource + R"("}
}
}
)";
createFilesWithParentDirs(
{
tempDir.path() / "base/contract_via_callback.sol",
tempDir.path() / "include/include_via_callback.sol",
tempDir.path() / "lib/nested/nested_via_callback.sol",
tempDir.path() / "lib/lib_via_callback.sol",
},
preamble
);
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::canonical(tempDir).relative_path();
vector<string> commandLine = {
"solc",
"--base-path=base/",
"--include-path=include/",
"--include-path=lib/nested",
"--include-path=lib/",
"--standard-json",
};
CommandLineOptions expectedOptions;
expectedOptions.input.mode = InputMode::StandardJson;
expectedOptions.input.paths = {};
expectedOptions.input.addStdin = true;
expectedOptions.input.basePath = "base/";
expectedOptions.input.includePaths = {
"include/",
"lib/nested",
"lib/",
};
expectedOptions.modelChecker.initialize = false;
// NOTE: Source code from Standard JSON does not end up in FileReader. This is not a problem
// because FileReader is only used once to initialize the compiler stack and after that
// its sources are irrelevant (even though the callback still stores everything it loads).
map<string, string> expectedSources = {
{"contract_via_callback.sol", preamble},
{"include_via_callback.sol", preamble},
{"nested_via_callback.sol", preamble},
{"lib_via_callback.sol", preamble},
};
vector<boost::filesystem::path> expectedIncludePaths = {
expectedWorkDir / "include/",
expectedWorkDir / "lib/nested",
expectedWorkDir / "lib/",
};
FileReader::FileSystemPathSet expectedAllowedDirectories = {};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(
commandLine,
standardJsonInput,
true /* _processInput */
);
Json::Value parsedStdout;
string jsonParsingErrors;
BOOST_TEST(util::jsonParseStrict(result.stdoutContent, parsedStdout, &jsonParsingErrors));
BOOST_TEST(jsonParsingErrors == "");
for (Json::Value const& errorDict: parsedStdout["errors"])
// The error list might contain pre-release compiler warning
BOOST_TEST(errorDict["severity"] != "error");
BOOST_TEST(
(parsedStdout["sources"].getMemberNames() | ranges::to<set>) ==
(expectedSources | ranges::views::keys | ranges::to<set>) + set<string>{"main.sol"}
);
BOOST_REQUIRE(result.success);
BOOST_TEST(result.options == expectedOptions);
BOOST_TEST(result.reader.sourceCodes() == expectedSources);
BOOST_TEST(result.reader.includePaths() == expectedIncludePaths);
BOOST_TEST(result.reader.allowedDirectories() == expectedAllowedDirectories);
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base/");
}
BOOST_AUTO_TEST_CASE(cli_include_paths_empty_path)
{
TemporaryDirectory tempDir({"base/", "include/"}, TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "base/main.sol"});
string expectedMessage = "Empty values are not allowed in --include-path.\n";
vector<string> commandLine = {
"solc",
"--base-path=base/",
"--include-path", "include/",
"--include-path", "",
"base/main.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
}
BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "contract.sol"});
string expectedMessage = "--include-path option requires a non-empty base path.\n";
vector<string> commandLine = {"solc", "--include-path", "include/", "contract.sol"};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
}
BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions)
{
TemporaryDirectory tempDir({"dir1/", "dir2/", "dir3/"}, TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({
"dir1/contract1.sol",
"dir1/contract2.sol",
"dir2/contract1.sol",
"dir2/contract2.sol",
});
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::canonical(tempDir).relative_path();
string expectedMessage =
"Source unit name collision detected. "
"The specified values of base path and/or include paths would result in multiple "
"input files being assigned the same source unit name:\n"
"contract1.sol matches: "
"\"" + (expectedWorkDir / "dir1/contract1.sol").generic_string() + "\", "
"\"" + (expectedWorkDir / "dir2/contract1.sol").generic_string() + "\"\n"
"contract2.sol matches: "
"\"" + (expectedWorkDir / "dir1/contract2.sol").generic_string() + "\", "
"\"" + (expectedWorkDir / "dir2/contract2.sol").generic_string() + "\"\n";
{
// import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = {
"solc",
"--base-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir2/contract1.sol",
"dir1/contract2.sol",
"dir2/contract2.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success);
}
{
// import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = {
"solc",
"--base-path=dir3/",
"--include-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir2/contract1.sol",
"dir1/contract2.sol",
"dir2/contract2.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success);
}
{
// No conflict if files with the same name exist but only one is given to the compiler.
vector<string> commandLine = {
"solc",
"--base-path=dir3/",
"--include-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir1/contract2.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == "");
BOOST_REQUIRE(result.success);
}
{
// The same file specified multiple times is not a conflict.
vector<string> commandLine = {
"solc",
"--base-path=dir3/",
"--include-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir1/contract1.sol",
"./dir1/contract1.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == "");
BOOST_REQUIRE(result.success);
}
}
BOOST_AUTO_TEST_CASE(cli_include_paths_should_allow_duplicate_paths)
{
TemporaryDirectory tempDir({"dir1/", "dir2/"}, TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({"dir1/contract.sol"});
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::canonical(tempDir).relative_path();
boost::filesystem::path expectedTempDir = "/" / tempDir.path().relative_path();
vector<string> commandLine = {
"solc",
"--base-path=dir1/",
"--include-path", "dir1",
"--include-path", "dir1",
"--include-path", "dir1/",
"--include-path", "dir1/",
"--include-path", "./dir1/",
"--include-path", "dir2/../dir1/",
"--include-path", (tempDir.path() / "dir1/").string(),
"--include-path", (expectedWorkDir / "dir1/").string(),
"--include-path", "dir1/",
"dir1/contract.sol",
};
// Duplicates do not affect the result but are not removed from the include path list.
vector<boost::filesystem::path> expectedIncludePaths = {
expectedWorkDir / "dir1",
expectedWorkDir / "dir1",
expectedWorkDir / "dir1/",
expectedWorkDir / "dir1/",
expectedWorkDir / "dir1/",
expectedWorkDir / "dir1/",
// NOTE: On macOS expectedTempDir usually contains a symlink and therefore for us it's
// different from expectedWorkDir.
expectedTempDir / "dir1/",
expectedWorkDir / "dir1/",
expectedWorkDir / "dir1/",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == "");
BOOST_REQUIRE(result.success);
BOOST_TEST(result.reader.includePaths() == expectedIncludePaths);
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "dir1/");
}
BOOST_AUTO_TEST_CASE(cli_include_paths_ambiguous_import)
{
TemporaryDirectory tempDir({"base/", "include/"}, TEST_CASE_NAME);
TemporaryWorkingDirectory tempWorkDir(tempDir);
string const preamble =
"// SPDX-License-Identifier: GPL-3.0\n"
"pragma solidity >=0.0;\n";
string const mainContractSource = preamble +
// Ambiguous: both base/contract.sol and include/contract.sol match the import.
"import \"contract.sol\";";
createFilesWithParentDirs({"base/contract.sol", "include/contract.sol"}, preamble);
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::canonical(tempDir).relative_path();
vector<string> commandLine = {
"solc",
"--no-color",
"--base-path=base/",
"--include-path=include/",
"-",
};
string expectedMessage =
"Error: Source \"contract.sol\" not found: Ambiguous import. "
"Multiple matching files found inside base path and/or include paths: \"" +
(expectedWorkDir / "base/contract.sol").generic_string() + "\", \"" +
(expectedWorkDir / "include/contract.sol").generic_string() + "\".\n"
" --> <stdin>:3:1:\n"
" |\n"
"3 | import \"contract.sol\";\n"
" | ^^^^^^^^^^^^^^^^^^^^^^\n\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(
commandLine,
mainContractSource,
true /* _processInput */
);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success);
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace solidity::frontend::test
+39 -2
View File
@@ -76,6 +76,7 @@ ImportCheck checkImport(
for (string const& option: _cliOptions)
soltestAssert(
boost::starts_with(option, "--base-path") ||
boost::starts_with(option, "--include-path") ||
boost::starts_with(option, "--allow-paths") ||
!boost::starts_with(option, "--"),
""
@@ -146,6 +147,7 @@ protected:
m_codeDir / "X/bc/d.sol",
m_codeDir / "x/y/z.sol",
m_codeDir / "1/2/3.sol",
m_codeDir / "contract.sol",
m_workDir / "a/b/c/d.sol",
@@ -342,10 +344,14 @@ BOOST_FIXTURE_TEST_CASE(allow_path_should_work_with_various_import_forms, AllowP
BOOST_TEST(checkImport("import 'a/../../code/a/../a/b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}));
BOOST_TEST(checkImport("import 'a/../../code/a///b/c.sol'", {"--allow-paths", "../code/a/b/c.sol"}));
// UNC paths in imports
#if !defined(_WIN32)
// UNC paths in imports.
// Unfortunately can't test it on Windows without having an existing UNC path. On Linux we can
// at least rely on the fact that `//` works like `/`.
string uncImportPath = "/" + m_portablePrefix + "/a/b/c.sol";
soltestAssert(FileReader::isUNCPath(uncImportPath), "");
BOOST_TEST(checkImport("import '" + uncImportPath + "'", {"--allow-paths", "../code/a/b/c.sol"}) == ImportCheck::PathDisallowed());
#endif
}
BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_input_files, AllowPathsFixture)
@@ -419,7 +425,7 @@ BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_remappings, AllowPaths
BOOST_TEST(checkImport("import '/../../../work/a/b/c.sol'", {"x=contract.sol/", "--base-path=../code/a/b/"}) == ImportCheck::PathDisallowed());
// Adding a remapping with an empty target does not whitelist anything
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "="}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + m_portablePrefix + "/a/b/c.sol'", {m_portablePrefix + "="}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b/c.sol'", {"../code/="}) == ImportCheck::PathDisallowed());
BOOST_TEST(checkImport("import '/../work/a/b/c.sol'", {"../code/=", "--base-path", m_portablePrefix}) == ImportCheck::PathDisallowed());
@@ -508,6 +514,37 @@ BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_work_dir, AllowPathsFi
BOOST_TEST(checkImport("import 'a/X/c.sol'", {"--base-path", ""}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_automatic_whitelisting_include_paths, AllowPathsFixture)
{
// Relative include path whitelists its content
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=a/b/c", "--include-path=../code/a"}));
BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=a/b/c", "--include-path=../code/a"}));
BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=a/b/c", "--include-path=../code/a"}));
BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=a/b/c", "--include-path=../code/a"}));
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=a/b/c", "--include-path=../code/a/"}));
BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=a/b/c", "--include-path=../code/a/"}));
BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=a/b/c", "--include-path=../code/a/"}));
BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=a/b/c", "--include-path=../code/a/"}));
BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=a/b/c", "--include-path=../code/."}));
BOOST_TEST(checkImport("import 'a/b/c.sol'", {"--base-path=a/b/c", "--include-path=../code/./"}));
BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=a/b/c", "--include-path=.."}));
BOOST_TEST(checkImport("import 'code/a/b/c.sol'", {"--base-path=a/b/c", "--include-path=../"}));
// Absolute include path whitelists its content
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=a/b/c", "--include-path", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport("import 'b/c/d.sol'", {"--base-path=a/b/c", "--include-path", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport("import 'b/X.sol'", {"--base-path=a/b/c", "--include-path", m_codeDir.string() + "/a"}));
BOOST_TEST(checkImport("import 'X/c.sol'", {"--base-path=a/b/c", "--include-path", m_codeDir.string() + "/a"}));
// If there are multiple include paths, all of them get whitelisted
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=a/b/c", "--include-path=../code/a", "--include-path=../code/1"}));
BOOST_TEST(checkImport("import '2/3.sol'", {"--base-path=a/b/c", "--include-path=../code/a", "--include-path=../code/1"}));
BOOST_TEST(checkImport("import 'b/c.sol'", {"--base-path=a/b/c", "--include-path=../code/1", "--include-path=../code/a"}));
BOOST_TEST(checkImport("import '2/3.sol'", {"--base-path=a/b/c", "--include-path=../code/1", "--include-path=../code/a"}));
}
BOOST_FIXTURE_TEST_CASE(allow_path_symlinks_within_whitelisted_dir, AllowPathsFixture)
{
BOOST_TEST(checkImport("import '" + m_portablePrefix + "/a/b_sym/c.sol'", {"--allow-paths=../code/a/b/"}));
+10
View File
@@ -117,6 +117,8 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
"a:b=c/d",
":contract.sol=",
"--base-path=/home/user/",
"--include-path=/usr/lib/include/",
"--include-path=/home/user/include",
"--allow-paths=/tmp,/home,project,../contracts",
"--ignore-missing",
"--error-recovery",
@@ -168,6 +170,8 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
expectedOptions.input.addStdin = true;
expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.includePaths = {"/usr/lib/include/", "/home/user/include"};
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "c", "/usr/lib"};
expectedOptions.input.ignoreMissingFiles = true;
expectedOptions.input.errorRecovery = (inputMode == InputMode::Compiler);
@@ -257,6 +261,8 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options)
"a:b=c/d",
":contract.yul=",
"--base-path=/home/user/",
"--include-path=/usr/lib/include/",
"--include-path=/home/user/include",
"--allow-paths=/tmp,/home,project,../contracts",
"--ignore-missing",
"--error-recovery", // Ignored in assembly mode
@@ -307,6 +313,7 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options)
};
expectedOptions.input.addStdin = true;
expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.includePaths = {"/usr/lib/include/", "/home/user/include"};
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "c", "/usr/lib"};
expectedOptions.input.ignoreMissingFiles = true;
expectedOptions.output.overwriteFiles = true;
@@ -350,6 +357,8 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
"input.json",
"--standard-json",
"--base-path=/home/user/",
"--include-path=/usr/lib/include/",
"--include-path=/home/user/include",
"--allow-paths=/tmp,/home,project,../contracts",
"--ignore-missing",
"--error-recovery", // Ignored in Standard JSON mode
@@ -390,6 +399,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
expectedOptions.input.mode = InputMode::StandardJson;
expectedOptions.input.paths = {"input.json"};
expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.includePaths = {"/usr/lib/include/", "/home/user/include"};
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts"};
expectedOptions.input.ignoreMissingFiles = true;
expectedOptions.output.dir = "/tmp/out";