mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #11545 from ethereum/stripping-base-path-from-cli
Stripping base path from CLI paths
This commit is contained in:
commit
5229ace936
@ -4,6 +4,7 @@ Language Features:
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path.
|
||||
* Immutable variables can be read at construction time once they are initialized.
|
||||
* SMTChecker: Support low level ``call`` as external calls to unknown code.
|
||||
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
|
||||
|
@ -71,8 +71,10 @@ The initial content of the VFS depends on how you invoke the compiler:
|
||||
|
||||
solc contract.sol /usr/local/dapp-bin/token.sol
|
||||
|
||||
The source unit name of a file loaded this way is simply the specified path after shell expansion
|
||||
and with platform-specific separators converted to forward slashes.
|
||||
The source unit name of a file loaded this way is constructed by converting its path to a
|
||||
canonical form and making it relative to the base path if it is located inside.
|
||||
See :ref:`Base Path Normalization and Stripping <base-path-normalization-and-stripping>` for
|
||||
a detailed description of this process.
|
||||
|
||||
.. index:: standard JSON
|
||||
|
||||
@ -309,9 +311,67 @@ When the source unit name is a relative path, this results in the file being loo
|
||||
directory the compiler has been invoked from.
|
||||
It is also the only value that results in absolute paths in source unit names being actually
|
||||
interpreted as absolute paths on disk.
|
||||
If the base path itself is relative, it is interpreted as relative to the current working directory
|
||||
of the compiler.
|
||||
|
||||
If the base path itself is relative, it is also interpreted as relative to the current working
|
||||
directory of the compiler.
|
||||
.. _base-path-normalization-and-stripping:
|
||||
|
||||
Base Path Normalization and Stripping
|
||||
-------------------------------------
|
||||
|
||||
On the command line the compiler behaves just as you would expect from any other program:
|
||||
it accepts paths in a format native to the platform and relative paths are relative to the current
|
||||
working directory.
|
||||
The source unit names assigned to files whose paths are specified on the command line, however,
|
||||
should not change just because the project is being compiled on a different platform or because the
|
||||
compiler happens to have been invoked from a different directory.
|
||||
To achieve this, paths to source files coming from the command line must be converted to a canonical
|
||||
form, and, if possible, made relative to the base path.
|
||||
|
||||
The normalization rules are as follows:
|
||||
|
||||
- If a path is relative, it is made absolute by prepending the current working directory to it.
|
||||
- Internal ``.`` and ``..`` segments are collapsed.
|
||||
- Platform-specific path separators are replaced with forward slashes.
|
||||
- Sequences of multiple consecutive path separators are squashed into a single separator (unless
|
||||
they are the leading slashes of an `UNC path <https://en.wikipedia.org/wiki/Path_(computing)#UNC>`_).
|
||||
- If the path includes a root name (e.g. a drive letter on Windows) and the root is the same as the
|
||||
root of the current working directory, the root is replaced with ``/``.
|
||||
- Symbolic links in the path are **not** resolved.
|
||||
|
||||
- The only exception is the path to the current working directory prepended to relative paths in
|
||||
the process of making them absolute.
|
||||
On some platforms the working directory is reported always with symbolic links resolved so for
|
||||
consistency the compiler resolves them everywhere.
|
||||
|
||||
- The original case of the path is preserved even if the filesystem is case-insensitive but
|
||||
`case-preserving <https://en.wikipedia.org/wiki/Case_preservation>`_ and the actual case on
|
||||
disk is different.
|
||||
|
||||
.. note::
|
||||
|
||||
There are situations where paths cannot be made platform-independent.
|
||||
For example on Windows the compiler can avoid using drive letters by referring to the root
|
||||
directory of the current drive as ``/`` but drive letters are still necessary for paths leading
|
||||
to other drives.
|
||||
You can avoid such situations by ensuring that all the files are available within a single
|
||||
directory tree on the same drive.
|
||||
|
||||
Once canonicalized, the base path is stripped from all source file paths that start with it.
|
||||
If the base path is empty or not specified, it is treated as if it was equal to the path to the
|
||||
current working directory (with all symbolic links resolved).
|
||||
The result is accepted only if the normalized directory path is the exact prefix of the normalized
|
||||
file path.
|
||||
Otherwise the file path remains absolute.
|
||||
This makes the conversion unambiguous and ensures that the relative path does not start with ``../``.
|
||||
The resulting file path becomes the source unit name.
|
||||
|
||||
.. note::
|
||||
|
||||
Prior to version 0.8.8, CLI path stripping was not performed and the only normalization applied
|
||||
was the conversion of path separators.
|
||||
When working with older versions of the compiler it is recommended to invoke the compiler from
|
||||
the base path and to only use relative paths on the command line.
|
||||
|
||||
.. index:: ! remapping; import, ! import; remapping, ! remapping; context, ! remapping; prefix, ! remapping; target
|
||||
.. _import-remapping:
|
||||
@ -414,7 +474,7 @@ Here are the detailed rules governing the behaviour of remappings:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
solc /project/=/contracts/ /project/contract.sol --base-path /project # source unit name: /project/contract.sol
|
||||
solc /project/=/contracts/ /project/contract.sol --base-path /project # source unit name: contract.sol
|
||||
|
||||
.. code-block:: solidity
|
||||
:caption: /project/contract.sol
|
||||
|
@ -22,6 +22,8 @@
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <libsolutil/Exceptions.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
using solidity::frontend::ReadCallback;
|
||||
using solidity::langutil::InternalCompilerError;
|
||||
using solidity::util::errinfo_comment;
|
||||
@ -31,9 +33,22 @@ using std::string;
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
void FileReader::setBasePath(boost::filesystem::path const& _path)
|
||||
{
|
||||
m_basePath = (_path.empty() ? "" : normalizeCLIPathForVFS(_path));
|
||||
}
|
||||
|
||||
void FileReader::setSource(boost::filesystem::path const& _path, SourceCode _source)
|
||||
{
|
||||
m_sourceCodes[_path.generic_string()] = std::move(_source);
|
||||
boost::filesystem::path normalizedPath = normalizeCLIPathForVFS(_path);
|
||||
boost::filesystem::path prefix = (m_basePath.empty() ? normalizeCLIPathForVFS(".") : m_basePath);
|
||||
|
||||
m_sourceCodes[stripPrefixIfPresent(prefix, normalizedPath).generic_string()] = std::move(_source);
|
||||
}
|
||||
|
||||
void FileReader::setStdin(SourceCode _source)
|
||||
{
|
||||
m_sourceCodes["<stdin>"] = std::move(_source);
|
||||
}
|
||||
|
||||
void FileReader::setSources(StringMap _sources)
|
||||
@ -92,5 +107,138 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
|
||||
}
|
||||
}
|
||||
|
||||
boost::filesystem::path FileReader::normalizeCLIPathForVFS(boost::filesystem::path const& _path)
|
||||
{
|
||||
// Detailed normalization rules:
|
||||
// - Makes the path either be absolute or have slash as root (note that on Windows paths with
|
||||
// slash as root are not considered absolute by Boost). If it is empty, it becomes
|
||||
// the current working directory.
|
||||
// - Collapses redundant . and .. segments.
|
||||
// - Removes leading .. segments from an absolute path (i.e. /../../ becomes just /).
|
||||
// - Squashes sequences of multiple path separators into one.
|
||||
// - Ensures that forward slashes are used as path separators on all platforms.
|
||||
// - Removes the root name (e.g. drive letter on Windows) when it matches the root name in the
|
||||
// path to the current working directory.
|
||||
//
|
||||
// Also note that this function:
|
||||
// - Does NOT resolve symlinks (except for symlinks in the path to the current working directory).
|
||||
// - Does NOT check if the path refers to a file or a directory. If the path ends with a slash,
|
||||
// the slash is preserved even if it's a file.
|
||||
// - The only exception are paths where the file name is a dot (e.g. '.' or 'a/b/.'). These
|
||||
// always have a trailing slash after normalization.
|
||||
// - Preserves case. Even if the filesystem is case-insensitive but case-preserving and the
|
||||
// case differs, the actual case from disk is NOT detected.
|
||||
|
||||
boost::filesystem::path canonicalWorkDir = boost::filesystem::weakly_canonical(boost::filesystem::current_path());
|
||||
|
||||
// NOTE: On UNIX systems the path returned from current_path() has symlinks resolved while on
|
||||
// Windows it does not. To get consistent results we resolve them on all platforms.
|
||||
boost::filesystem::path absolutePath = boost::filesystem::absolute(_path, canonicalWorkDir);
|
||||
|
||||
// NOTE: boost path preserves certain differences that are ignored by its operator ==.
|
||||
// E.g. "a//b" vs "a/b" or "a/b/" vs "a/b/.". lexically_normal() does remove these differences.
|
||||
boost::filesystem::path normalizedPath = absolutePath.lexically_normal();
|
||||
solAssert(normalizedPath.is_absolute() || normalizedPath.root_path() == "/", "");
|
||||
|
||||
// If the path is on the same drive as the working dir, for portability we prefer not to
|
||||
// include the root name. Do this only for non-UNC paths - my experiments show that on Windows
|
||||
// when the working dir is an UNC path, / does not not actually refer to the root of the UNC path.
|
||||
boost::filesystem::path normalizedRootPath = normalizedPath.root_path();
|
||||
if (!isUNCPath(normalizedPath))
|
||||
{
|
||||
boost::filesystem::path workingDirRootPath = canonicalWorkDir.root_path();
|
||||
if (normalizedRootPath == workingDirRootPath)
|
||||
normalizedRootPath = "/";
|
||||
}
|
||||
|
||||
// lexically_normal() will not squash paths like "/../../" into "/". We have to do it manually.
|
||||
boost::filesystem::path dotDotPrefix = absoluteDotDotPrefix(normalizedPath);
|
||||
|
||||
boost::filesystem::path normalizedPathNoDotDot = normalizedPath;
|
||||
if (dotDotPrefix.empty())
|
||||
normalizedPathNoDotDot = normalizedRootPath / normalizedPath.relative_path();
|
||||
else
|
||||
normalizedPathNoDotDot = normalizedRootPath / normalizedPath.lexically_relative(normalizedPath.root_path() / dotDotPrefix);
|
||||
solAssert(!hasDotDotSegments(normalizedPathNoDotDot), "");
|
||||
|
||||
// NOTE: On Windows lexically_normal() converts all separators to forward slashes. Convert them back.
|
||||
// Separators do not affect path comparison but remain in internal representation returned by native().
|
||||
// This will also normalize the root name to start with // in UNC paths.
|
||||
normalizedPathNoDotDot = normalizedPathNoDotDot.generic_string();
|
||||
|
||||
// For some reason boost considers "/." different than "/" even though for other directories
|
||||
// the trailing dot is ignored.
|
||||
if (normalizedPathNoDotDot == "/.")
|
||||
return "/";
|
||||
|
||||
return normalizedPathNoDotDot;
|
||||
}
|
||||
|
||||
bool FileReader::isPathPrefix(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path)
|
||||
{
|
||||
solAssert(!_prefix.empty() && !_path.empty(), "");
|
||||
// NOTE: On Windows paths starting with a slash (rather than a drive letter) are considered relative by boost.
|
||||
solAssert(_prefix.is_absolute() || isUNCPath(_prefix) || _prefix.root_path() == "/", "");
|
||||
solAssert(_path.is_absolute() || isUNCPath(_path) || _path.root_path() == "/", "");
|
||||
solAssert(_prefix == _prefix.lexically_normal() && _path == _path.lexically_normal(), "");
|
||||
solAssert(!hasDotDotSegments(_prefix) && !hasDotDotSegments(_path), "");
|
||||
|
||||
boost::filesystem::path strippedPath = _path.lexically_relative(
|
||||
// Before 1.72.0 lexically_relative() was not handling paths with empty, dot and dot dot segments
|
||||
// correctly (see https://github.com/boostorg/filesystem/issues/76). The only case where this
|
||||
// is possible after our normalization is a directory name ending in a slash (filename is a dot).
|
||||
_prefix.filename_is_dot() ? _prefix.parent_path() : _prefix
|
||||
);
|
||||
return !strippedPath.empty() && *strippedPath.begin() != "..";
|
||||
}
|
||||
|
||||
boost::filesystem::path FileReader::stripPrefixIfPresent(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path)
|
||||
{
|
||||
if (!isPathPrefix(_prefix, _path))
|
||||
return _path;
|
||||
|
||||
boost::filesystem::path strippedPath = _path.lexically_relative(
|
||||
_prefix.filename_is_dot() ? _prefix.parent_path() : _prefix
|
||||
);
|
||||
solAssert(strippedPath.empty() || *strippedPath.begin() != "..", "");
|
||||
return strippedPath;
|
||||
}
|
||||
|
||||
boost::filesystem::path FileReader::absoluteDotDotPrefix(boost::filesystem::path const& _path)
|
||||
{
|
||||
solAssert(_path.is_absolute() || _path.root_path() == "/", "");
|
||||
|
||||
boost::filesystem::path _pathWithoutRoot = _path.relative_path();
|
||||
boost::filesystem::path prefix;
|
||||
for (boost::filesystem::path const& segment: _pathWithoutRoot)
|
||||
if (segment.filename_is_dot_dot())
|
||||
prefix /= segment;
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
bool FileReader::hasDotDotSegments(boost::filesystem::path const& _path)
|
||||
{
|
||||
for (boost::filesystem::path const& segment: _path)
|
||||
if (segment.filename_is_dot_dot())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileReader::isUNCPath(boost::filesystem::path const& _path)
|
||||
{
|
||||
string rootName = _path.root_name().string();
|
||||
|
||||
return (
|
||||
rootName.size() == 2 ||
|
||||
(rootName.size() > 2 && rootName[2] != rootName[1])
|
||||
) && (
|
||||
(rootName[0] == '/' && rootName[1] == '/')
|
||||
#if defined(_WIN32)
|
||||
|| (rootName[0] == '\\' && rootName[1] == '\\')
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -45,12 +45,13 @@ public:
|
||||
boost::filesystem::path _basePath = {},
|
||||
FileSystemPathSet _allowedDirectories = {}
|
||||
):
|
||||
m_basePath(std::move(_basePath)),
|
||||
m_allowedDirectories(std::move(_allowedDirectories)),
|
||||
m_sourceCodes()
|
||||
{}
|
||||
{
|
||||
setBasePath(_basePath);
|
||||
}
|
||||
|
||||
void setBasePath(boost::filesystem::path _path) { m_basePath = std::move(_path); }
|
||||
void setBasePath(boost::filesystem::path const& _path);
|
||||
boost::filesystem::path const& basePath() const noexcept { return m_basePath; }
|
||||
|
||||
void allowDirectory(boost::filesystem::path _path) { m_allowedDirectories.insert(std::move(_path)); }
|
||||
@ -58,17 +59,21 @@ public:
|
||||
|
||||
StringMap const& sourceCodes() const noexcept { return m_sourceCodes; }
|
||||
|
||||
/// Retrieves the source code for a given source unit ID.
|
||||
/// Retrieves the source code for a given source unit name.
|
||||
SourceCode const& sourceCode(SourceUnitName const& _sourceUnitName) const { return m_sourceCodes.at(_sourceUnitName); }
|
||||
|
||||
/// Resets all sources to the given map of source unit ID to source codes.
|
||||
/// Resets all sources to the given map of source unit name to source codes.
|
||||
/// Does not enforce @a allowedDirectories().
|
||||
void setSources(StringMap _sources);
|
||||
|
||||
/// Adds the source code for a given source unit ID.
|
||||
/// Adds the source code under a source unit name created by normalizing the file path.
|
||||
/// Does not enforce @a allowedDirectories().
|
||||
void setSource(boost::filesystem::path const& _path, SourceCode _source);
|
||||
|
||||
/// Adds the source code under the source unit name of @a <stdin>.
|
||||
/// Does not enforce @a allowedDirectories().
|
||||
void setStdin(SourceCode _source);
|
||||
|
||||
/// Receives a @p _sourceUnitName that refers to a source unit in compiler's virtual filesystem
|
||||
/// and attempts to interpret it as a path and read the corresponding file from disk.
|
||||
/// The read will only succeed if the canonical path of the file is within one of the @a allowedDirectories().
|
||||
@ -83,7 +88,43 @@ public:
|
||||
return [this](std::string const& _kind, std::string const& _path) { return readFile(_kind, _path); };
|
||||
}
|
||||
|
||||
/// Normalizes a filesystem path to make it include all components up to the filesystem root,
|
||||
/// remove small, inconsequential differences that do not affect the meaning and make it look
|
||||
/// the same on all platforms (if possible). Symlinks in the path are not resolved.
|
||||
/// The resulting path uses forward slashes as path separators, has no redundant separators,
|
||||
/// has no redundant . or .. segments and has no root name if removing it does not change the meaning.
|
||||
/// The path does not have to actually exist.
|
||||
static boost::filesystem::path normalizeCLIPathForVFS(boost::filesystem::path const& _path);
|
||||
|
||||
/// @returns true if all the path components of @a _prefix are present at the beginning of @a _path.
|
||||
/// Both paths must be absolute (or have slash as root) and normalized (no . or .. segments, no
|
||||
/// multiple consecutive slashes).
|
||||
/// Paths are treated as case-sensitive. Does not require the path to actually exist in the
|
||||
/// filesystem and does not follow symlinks. Only considers whole segments, e.g. /abc/d is not
|
||||
/// considered a prefix of /abc/def. Both paths must be non-empty.
|
||||
/// Ignores the trailing slash, i.e. /a/b/c.sol/ is treated as a valid prefix of /a/b/c.sol.
|
||||
static bool isPathPrefix(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path);
|
||||
|
||||
/// If @a _prefix is actually a prefix of @p _path, removes it from @a _path to make it relative.
|
||||
/// @returns The path without the prefix or unchanged path if there is not prefix.
|
||||
/// If @a _path and @_prefix are identical, the result is '.'.
|
||||
static boost::filesystem::path stripPrefixIfPresent(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path);
|
||||
|
||||
/// @returns true if the specified path is an UNC path.
|
||||
/// UNC paths start with // followed by a name (on Windows they can also start with \\).
|
||||
/// They are used for network shares on Windows. On UNIX systems they do not have the same
|
||||
/// functionality but usually they are still recognized and treated in a special way.
|
||||
static bool isUNCPath(boost::filesystem::path const& _path);
|
||||
|
||||
private:
|
||||
/// If @a _path starts with a number of .. segments, returns a path consisting only of those
|
||||
/// segments (root name is not included). Otherwise returns an empty path. @a _path must be
|
||||
/// absolute (or have slash as root).
|
||||
static boost::filesystem::path absoluteDotDotPrefix(boost::filesystem::path const& _path);
|
||||
|
||||
/// @returns true if the path contains any .. segments.
|
||||
static bool hasDotDotSegments(boost::filesystem::path const& _path);
|
||||
|
||||
/// Base path, used for resolving relative paths in imports.
|
||||
boost::filesystem::path m_basePath;
|
||||
|
||||
|
@ -451,7 +451,7 @@ bool CommandLineInterface::readInputFiles()
|
||||
m_standardJsonInput = readUntilEnd(m_sin);
|
||||
}
|
||||
else
|
||||
m_fileReader.setSource(g_stdinFileName, readUntilEnd(m_sin));
|
||||
m_fileReader.setStdin(readUntilEnd(m_sin));
|
||||
}
|
||||
|
||||
if (m_fileReader.sourceCodes().empty() && !m_standardJsonInput.has_value())
|
||||
|
@ -103,6 +103,7 @@ set(libsolidity_sources
|
||||
libsolidity/SyntaxTest.h
|
||||
libsolidity/ViewPureChecker.cpp
|
||||
libsolidity/analysis/FunctionCallGraph.cpp
|
||||
libsolidity/interface/FileReader.cpp
|
||||
)
|
||||
detect_stray_source_files("${libsolidity_sources}" "libsolidity/")
|
||||
|
||||
|
@ -39,7 +39,7 @@ void solidity::test::createFilesWithParentDirs(set<boost::filesystem::path> cons
|
||||
}
|
||||
}
|
||||
|
||||
void solidity::test::createFileWithContent(boost::filesystem::path const& _path, string const& content)
|
||||
void solidity::test::createFileWithContent(boost::filesystem::path const& _path, string const& _content)
|
||||
{
|
||||
if (boost::filesystem::is_regular_file(_path))
|
||||
BOOST_THROW_EXCEPTION(runtime_error("File already exists: \"" + _path.string() + "\".")); \
|
||||
@ -49,16 +49,21 @@ void solidity::test::createFileWithContent(boost::filesystem::path const& _path,
|
||||
if (newFile.fail() || !boost::filesystem::is_regular_file(_path))
|
||||
BOOST_THROW_EXCEPTION(runtime_error("Failed to create a file: \"" + _path.string() + "\".")); \
|
||||
|
||||
newFile << content;
|
||||
newFile << _content;
|
||||
}
|
||||
|
||||
bool solidity::test::createSymlinkIfSupportedByFilesystem(
|
||||
boost::filesystem::path const& _targetPath,
|
||||
boost::filesystem::path const& _linkName
|
||||
boost::filesystem::path const& _linkName,
|
||||
bool _directorySymlink
|
||||
)
|
||||
{
|
||||
boost::system::error_code symlinkCreationError;
|
||||
boost::filesystem::create_symlink(_targetPath, _linkName, symlinkCreationError);
|
||||
|
||||
if (_directorySymlink)
|
||||
boost::filesystem::create_directory_symlink(_targetPath, _linkName, symlinkCreationError);
|
||||
else
|
||||
boost::filesystem::create_symlink(_targetPath, _linkName, symlinkCreationError);
|
||||
|
||||
if (!symlinkCreationError)
|
||||
return true;
|
||||
|
@ -35,16 +35,20 @@ void createFilesWithParentDirs(std::set<boost::filesystem::path> const& _paths,
|
||||
|
||||
/// Creates a file with the exact content specified in the second argument.
|
||||
/// Throws an exception if the file already exists or if the parent directory of the file does not.
|
||||
void createFileWithContent(boost::filesystem::path const& _path, std::string const& content);
|
||||
void createFileWithContent(boost::filesystem::path const& _path, std::string const& _content);
|
||||
|
||||
/// Creates a symlink between two paths.
|
||||
/// The target does not have to exist.
|
||||
/// If @p directorySymlink is true, indicate to the operating system that this is a directory
|
||||
/// symlink. On some systems (e.g. Windows) it's possible to create a non-directory symlink pointing
|
||||
/// at a directory, which makes such a symlinks unusable.
|
||||
/// @returns true if the symlink has been successfully created, false if the filesystem does not
|
||||
/// support symlinks.
|
||||
/// Throws an exception of the operation fails for a different reason.
|
||||
bool createSymlinkIfSupportedByFilesystem(
|
||||
boost::filesystem::path const& _targetPath,
|
||||
boost::filesystem::path const& _linkName
|
||||
boost::filesystem::path const& _linkName,
|
||||
bool _directorySymlink
|
||||
);
|
||||
|
||||
}
|
||||
|
450
test/libsolidity/interface/FileReader.cpp
Normal file
450
test/libsolidity/interface/FileReader.cpp
Normal file
@ -0,0 +1,450 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
/// Unit tests for libsolidity/interface/FileReader.h
|
||||
|
||||
#include <libsolidity/interface/FileReader.h>
|
||||
|
||||
#include <test/Common.h>
|
||||
#include <test/FilesystemUtils.h>
|
||||
#include <test/TemporaryDirectory.h>
|
||||
#include <test/libsolidity/util/SoltestErrors.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::test;
|
||||
|
||||
#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name)
|
||||
|
||||
namespace solidity::frontend::test
|
||||
{
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(FileReaderTest)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_absolute_path)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/"), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./"), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./."), "/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a"), "/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/"), "/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/."), "/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a"), "/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/"), "/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/./a/."), "/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b"), "/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/"), "/a/b/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/./b/"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../a/b/"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/.."), "/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../"), "/a/b/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b/c/../../../"), "/");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_relative_path)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
boost::filesystem::create_directories(tempDir.path() / "x/y/z");
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "x/y/z");
|
||||
|
||||
// NOTE: If path to work dir contains symlinks (often the case on macOS), boost might resolve
|
||||
// them, making the path different from tempDirPath.
|
||||
boost::filesystem::path expectedPrefix = boost::filesystem::current_path().parent_path().parent_path().parent_path();
|
||||
// On Windows tempDir.path() normally contains the drive letter while the normalized path should not.
|
||||
expectedPrefix = "/" / expectedPrefix.relative_path();
|
||||
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("."), expectedPrefix / "x/y/z/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./"), expectedPrefix / "x/y/z/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".//"), expectedPrefix / "x/y/z/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".."), expectedPrefix / "x/y");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../"), expectedPrefix / "x/y/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("..//"), expectedPrefix / "x/y/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a"), expectedPrefix / "x/y/z/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/."), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a"), expectedPrefix / "x/y/z/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/."), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/.//"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/./."), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/././/"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b"), expectedPrefix / "x/y/z/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/"), expectedPrefix / "x/y/z/a/b/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/b"), expectedPrefix / "x/y/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/b"), expectedPrefix / "x/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("./a/b"), expectedPrefix / "x/y/z/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("././a/b"), expectedPrefix / "x/y/z/a/b");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/./b/"), expectedPrefix / "x/y/z/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../a/b/"), expectedPrefix / "x/y/z/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/.."), expectedPrefix / "x/y/z/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../"), expectedPrefix / "x/y/z/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/..//"), expectedPrefix / "x/y/z/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../.."), expectedPrefix / "x/y/z/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../../"), expectedPrefix / "x/y/z/a/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/b/c/../..//"), expectedPrefix / "x/y/z/a/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.././../p/../q/../a/b"), expectedPrefix / "a/b");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_redundant_slashes)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("///"), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////"), "/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("////a/b/"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a//b/"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a////b/"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b//"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/b////"), "/a/b/");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_unc_path)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
|
||||
|
||||
// On Windows tempDir.path() normally contains the drive letter while the normalized path should not.
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
|
||||
// UNC paths start with // or \\ followed by a name. They are used for network shares on Windows.
|
||||
// On UNIX systems they are not supported but still treated in a special way.
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/"), "//host/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b"), "//host/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host/a/b/"), "//host/a/b/");
|
||||
|
||||
#if defined(_WIN32)
|
||||
// On Windows an UNC path can also start with \\ instead of //
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/"), "//host/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b"), "//host/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/"), "//host/a/b/");
|
||||
#else
|
||||
// On UNIX systems it's just a fancy relative path instead
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/"), expectedWorkDir / "\\\\host/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b"), expectedWorkDir / "\\\\host/a/b");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("\\\\host/a/b/"), expectedWorkDir / "\\\\host/a/b/");
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_root_name_only)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
|
||||
// A root **path** consists of a directory name (typically / or \) and the root name (drive
|
||||
// letter (C:), UNC host name (//host), etc.). Either can be empty. Root path as a whole may be
|
||||
// an absolute path but root name on its own is considered relative. For example on Windows
|
||||
// C:\ represents the root directory of drive C: but C: on its own refers to the current working
|
||||
// directory.
|
||||
|
||||
// UNC paths
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//"), "//" / expectedWorkDir);
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("//host"), "//host" / expectedWorkDir);
|
||||
|
||||
// On UNIX systems root name is empty.
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(""), expectedWorkDir);
|
||||
|
||||
#if defined(_WIN32)
|
||||
boost::filesystem::path driveLetter = boost::filesystem::current_path().root_name();
|
||||
solAssert(!driveLetter.empty(), "");
|
||||
solAssert(driveLetter.is_relative(), "");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(driveLetter), expectedWorkDir);
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_stripping_root_name)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
|
||||
|
||||
soltestAssert(boost::filesystem::current_path().is_absolute(), "");
|
||||
#if defined(_WIN32)
|
||||
soltestAssert(!boost::filesystem::current_path().root_name().empty(), "");
|
||||
#endif
|
||||
|
||||
boost::filesystem::path normalizedPath = FileReader::normalizeCLIPathForVFS(boost::filesystem::current_path());
|
||||
BOOST_CHECK_EQUAL(normalizedPath, "/" / boost::filesystem::current_path().relative_path());
|
||||
BOOST_TEST(normalizedPath.root_name().empty());
|
||||
BOOST_CHECK_EQUAL(normalizedPath.root_directory(), "/");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_path_beyond_root)
|
||||
{
|
||||
TemporaryWorkingDirectory tempWorkDir("/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../"), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a"), "/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../a/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a"), "/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/../../a/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("/a/../../b/../.."), "/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(".."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../"), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a"), "/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../a/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a"), "/a");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("../../a/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../.."), "/");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("a/../../b/../.."), "/");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_case_sensitivity)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
|
||||
|
||||
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
|
||||
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
|
||||
|
||||
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc") == expectedPrefix / "abc");
|
||||
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc") != expectedPrefix / "ABC");
|
||||
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "ABC") != expectedPrefix / "abc");
|
||||
BOOST_TEST(FileReader::normalizeCLIPathForVFS(tempDir.path() / "ABC") == expectedPrefix / "ABC");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_path_separators)
|
||||
{
|
||||
// Even on Windows we want / as a separator.
|
||||
BOOST_TEST((FileReader::normalizeCLIPathForVFS("/a/b/c").native() == boost::filesystem::path("/a/b/c").native()));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_not_resolve_symlinks)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
soltestAssert(tempDir.path().is_absolute(), "");
|
||||
boost::filesystem::create_directories(tempDir.path() / "abc");
|
||||
|
||||
if (!createSymlinkIfSupportedByFilesystem(tempDir.path() / "abc", tempDir.path() / "sym", true))
|
||||
return;
|
||||
|
||||
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
|
||||
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol"), expectedPrefix / "sym/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol"), expectedPrefix / "abc/contract.sol");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(normalizeCLIPathForVFS_should_resolve_symlinks_in_workdir_when_path_is_relative)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
soltestAssert(tempDir.path().is_absolute(), "");
|
||||
boost::filesystem::create_directories(tempDir.path() / "abc");
|
||||
|
||||
if (!createSymlinkIfSupportedByFilesystem(tempDir.path() / "abc", tempDir.path() / "sym", true))
|
||||
return;
|
||||
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "sym");
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::weakly_canonical(boost::filesystem::current_path()).relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
|
||||
boost::filesystem::path expectedPrefix = "/" / tempDir.path().relative_path();
|
||||
soltestAssert(expectedPrefix.is_absolute() || expectedPrefix.root_path() == "/", "");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS("contract.sol"), expectedWorkDir / "contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "sym/contract.sol"), expectedPrefix / "sym/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::normalizeCLIPathForVFS(tempDir.path() / "abc/contract.sol"), expectedPrefix / "abc/contract.sol");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(isPathPrefix_file_prefix)
|
||||
{
|
||||
BOOST_TEST(FileReader::isPathPrefix("/", "/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/contract.sol", "/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/contract.sol/", "/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/contract.sol/.", "/contract.sol"));
|
||||
|
||||
BOOST_TEST(FileReader::isPathPrefix("/", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/bc", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/bc/def/contract.sol", "/a/bc/def/contract.sol"));
|
||||
|
||||
BOOST_TEST(FileReader::isPathPrefix("/", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/bc", "/a/bc/def/contract.sol"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/bc/def/contract.sol", "/a/bc/def/contract.sol"));
|
||||
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/contract.sol", "/token.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/contract", "/contract.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/contract.sol", "/contract"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/contract.so", "/contract.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/contract.sol", "/contract.so"));
|
||||
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/contract.sol", "/a/b/contract.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/contract.sol", "/a/b/c/contract.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/contract.sol", "/a/b/c/d/contract.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/d/contract.sol", "/a/b/c/contract.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/contract.sol", "/contract.sol"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(isPathPrefix_directory_prefix)
|
||||
{
|
||||
BOOST_TEST(FileReader::isPathPrefix("/", "/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/", "/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c", "/"));
|
||||
|
||||
BOOST_TEST(FileReader::isPathPrefix("/", "/a/bc/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a", "/a/bc/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/", "/a/bc/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/bc", "/a/bc/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("/a/bc/", "/a/bc/"));
|
||||
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a", "/b/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/", "/b/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/contract.sol", "/a/b/"));
|
||||
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/", "/a/b/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c", "/a/b/"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(isPathPrefix_unc_path)
|
||||
{
|
||||
BOOST_TEST(FileReader::isPathPrefix("//host/a/b/", "//host/a/b/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("//host/a/b", "//host/a/b/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("//host/a/", "//host/a/b/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("//host/a", "//host/a/b/"));
|
||||
BOOST_TEST(FileReader::isPathPrefix("//host/", "//host/a/b/"));
|
||||
|
||||
// NOTE: //host and // cannot be passed to isPathPrefix() because they are considered relative.
|
||||
|
||||
BOOST_TEST(!FileReader::isPathPrefix("//host1/", "//host2/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("//host1/a/b/", "//host2/a/b/"));
|
||||
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/b/c/", "//a/b/c/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("//a/b/c/", "/a/b/c/"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(isPathPrefix_case_sensitivity)
|
||||
{
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a.sol", "/A.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/A.sol", "/a.sol"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/A/", "/a/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/", "/A/"));
|
||||
BOOST_TEST(!FileReader::isPathPrefix("/a/BC/def/", "/a/bc/def/contract.sol"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(stripPrefixIfPresent_file_prefix)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/contract.sol"), "contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol", "/contract.sol"), ".");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol/", "/contract.sol"), ".");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol/.", "/contract.sol"), ".");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/a/bc/def/contract.sol"), "a/bc/def/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a", "/a/bc/def/contract.sol"), "bc/def/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/", "/a/bc/def/contract.sol"), "bc/def/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc", "/a/bc/def/contract.sol"), "def/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc/def/", "/a/bc/def/contract.sol"), "contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc/def/contract.sol", "/a/bc/def/contract.sol"), ".");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol", "/token.sol"), "/token.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract", "/contract.sol"), "/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/contract.sol", "/contract"), "/contract");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/c/contract.sol", "/a/b/contract.sol"), "/a/b/contract.sol");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/contract.sol", "/a/b/c/contract.sol"), "/a/b/c/contract.sol");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(stripPrefixIfPresent_directory_prefix)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/"), ".");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/", "/a/bc/def/"), "a/bc/def/");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a", "/a/bc/def/"), "bc/def/");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/", "/a/bc/def/"), "bc/def/");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc", "/a/bc/def/"), "def/");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/bc/def/", "/a/bc/def/"), ".");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a", "/b/"), "/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/", "/b/"), "/b/");
|
||||
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/c/", "/a/b/"), "/a/b/");
|
||||
BOOST_CHECK_EQUAL(FileReader::stripPrefixIfPresent("/a/b/c", "/a/b/"), "/a/b/");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(isUNCPath)
|
||||
{
|
||||
BOOST_TEST(FileReader::isUNCPath("//"));
|
||||
BOOST_TEST(FileReader::isUNCPath("//root"));
|
||||
BOOST_TEST(FileReader::isUNCPath("//root/"));
|
||||
|
||||
#if defined(_WIN32)
|
||||
// On Windows boost sees these as ///, which is equivalent to /
|
||||
BOOST_TEST(!FileReader::isUNCPath("//\\"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\\\/"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\/\\"));
|
||||
|
||||
BOOST_TEST(FileReader::isUNCPath("\\\\"));
|
||||
BOOST_TEST(FileReader::isUNCPath("\\\\root"));
|
||||
BOOST_TEST(FileReader::isUNCPath("\\\\root/"));
|
||||
#else
|
||||
// On UNIX it's actually an UNC path
|
||||
BOOST_TEST(FileReader::isUNCPath("//\\"));
|
||||
|
||||
// On UNIX these are just weird relative directory names consisting only of backslashes.
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\\\/"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\/\\"));
|
||||
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\\\"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\\\root"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\\\root/"));
|
||||
#endif
|
||||
|
||||
BOOST_TEST(!FileReader::isUNCPath("\\/"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("/\\"));
|
||||
|
||||
BOOST_TEST(!FileReader::isUNCPath(""));
|
||||
BOOST_TEST(!FileReader::isUNCPath("."));
|
||||
BOOST_TEST(!FileReader::isUNCPath(".."));
|
||||
BOOST_TEST(!FileReader::isUNCPath("/"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("a"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("a/b/c"));
|
||||
BOOST_TEST(!FileReader::isUNCPath("contract.sol"));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
} // namespace solidity::frontend::test
|
@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE(readFileAsString_symlink)
|
||||
TemporaryDirectory tempDir("common-io-test-");
|
||||
createFileWithContent(tempDir.path() / "test.txt", "ABC\ndef\n");
|
||||
|
||||
if (!createSymlinkIfSupportedByFilesystem("test.txt", tempDir.path() / "symlink.txt"))
|
||||
if (!createSymlinkIfSupportedByFilesystem("test.txt", tempDir.path() / "symlink.txt", false))
|
||||
return;
|
||||
|
||||
BOOST_TEST(readFileAsString(tempDir.path() / "symlink.txt") == "ABC\ndef\n");
|
||||
|
@ -141,14 +141,19 @@ BOOST_AUTO_TEST_CASE(cli_input)
|
||||
createFilesWithParentDirs({tempDir1.path() / "input1.sol"});
|
||||
createFilesWithParentDirs({tempDir2.path() / "input2.sol"});
|
||||
|
||||
boost::filesystem::path expectedDir1 = "/" / tempDir1.path().relative_path();
|
||||
boost::filesystem::path expectedDir2 = "/" / tempDir2.path().relative_path();
|
||||
soltestAssert(expectedDir1.is_absolute() || expectedDir1.root_path() == "/", "");
|
||||
soltestAssert(expectedDir2.is_absolute() || expectedDir2.root_path() == "/", "");
|
||||
|
||||
vector<ImportRemapper::Remapping> expectedRemappings = {
|
||||
{"", "a", "b/c/d"},
|
||||
{"a", "b", "c/d/e/"},
|
||||
};
|
||||
map<string, string> expectedSources = {
|
||||
{"<stdin>", "\n"},
|
||||
{(tempDir1.path() / "input1.sol").generic_string(), ""},
|
||||
{(tempDir2.path() / "input2.sol").generic_string(), ""},
|
||||
{(expectedDir1 / "input1.sol").generic_string(), ""},
|
||||
{(expectedDir2 / "input2.sol").generic_string(), ""},
|
||||
};
|
||||
PathSet expectedAllowedPaths = {
|
||||
boost::filesystem::canonical(tempDir1.path()),
|
||||
@ -181,8 +186,11 @@ BOOST_AUTO_TEST_CASE(cli_ignore_missing_some_files_exist)
|
||||
TemporaryDirectory tempDir2(TEST_CASE_NAME);
|
||||
createFilesWithParentDirs({tempDir1.path() / "input1.sol"});
|
||||
|
||||
boost::filesystem::path expectedDir1 = "/" / tempDir1.path().relative_path();
|
||||
soltestAssert(expectedDir1.is_absolute() || expectedDir1.root_path() == "/", "");
|
||||
|
||||
// NOTE: Allowed paths should not be added for skipped files.
|
||||
map<string, string> expectedSources = {{(tempDir1.path() / "input1.sol").generic_string(), ""}};
|
||||
map<string, string> expectedSources = {{(expectedDir1 / "input1.sol").generic_string(), ""}};
|
||||
PathSet expectedAllowedPaths = {boost::filesystem::canonical(tempDir1.path())};
|
||||
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({
|
||||
@ -232,6 +240,7 @@ BOOST_AUTO_TEST_CASE(cli_not_a_file)
|
||||
BOOST_AUTO_TEST_CASE(standard_json_base_path)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path().root_path());
|
||||
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({
|
||||
"solc",
|
||||
@ -245,7 +254,7 @@ BOOST_AUTO_TEST_CASE(standard_json_base_path)
|
||||
BOOST_TEST(result.options.input.paths.empty());
|
||||
BOOST_TEST(result.reader.sourceCodes().empty());
|
||||
BOOST_TEST(result.reader.allowedDirectories().empty());
|
||||
BOOST_TEST(result.reader.basePath() == tempDir.path());
|
||||
BOOST_TEST(result.reader.basePath() == "/" / tempDir.path().relative_path());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(standard_json_no_input_file)
|
||||
@ -341,6 +350,531 @@ BOOST_AUTO_TEST_CASE(standard_json_remapping)
|
||||
BOOST_TEST(result.stderrContent == expectedMessage);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path)
|
||||
{
|
||||
TemporaryDirectory tempDirCurrent(TEST_CASE_NAME);
|
||||
TemporaryDirectory tempDirOther(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDirCurrent.path());
|
||||
soltestAssert(tempDirCurrent.path().is_absolute(), "");
|
||||
soltestAssert(tempDirOther.path().is_absolute(), "");
|
||||
|
||||
// NOTE: On macOS the path usually contains symlinks which prevents base path from being stripped.
|
||||
// Use canonical() to resolve symnlinks and get consistent results on all platforms.
|
||||
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
|
||||
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
|
||||
|
||||
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
|
||||
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
|
||||
|
||||
vector<string> commandLine = {
|
||||
"solc",
|
||||
"contract1.sol", // Relative path
|
||||
"c/d/contract2.sol", // Relative path with subdirectories
|
||||
currentDirNoSymlinks.string() + "/contract3.sol", // Absolute path inside working dir
|
||||
otherDirNoSymlinks.string() + "/contract4.sol", // Absolute path outside of working dir
|
||||
};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.paths = {
|
||||
"contract1.sol",
|
||||
"c/d/contract2.sol",
|
||||
currentDirNoSymlinks / "contract3.sol",
|
||||
otherDirNoSymlinks / "contract4.sol",
|
||||
};
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
{"contract1.sol", ""},
|
||||
{"c/d/contract2.sol", ""},
|
||||
{"contract3.sol", ""},
|
||||
{expectedOtherDir.generic_string() + "/contract4.sol", ""},
|
||||
};
|
||||
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {
|
||||
currentDirNoSymlinks / "c/d",
|
||||
currentDirNoSymlinks,
|
||||
otherDirNoSymlinks,
|
||||
};
|
||||
|
||||
createFilesWithParentDirs(expectedOptions.input.paths);
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_same_as_work_dir)
|
||||
{
|
||||
TemporaryDirectory tempDirCurrent(TEST_CASE_NAME);
|
||||
TemporaryDirectory tempDirOther(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDirCurrent.path());
|
||||
soltestAssert(tempDirCurrent.path().is_absolute(), "");
|
||||
soltestAssert(tempDirOther.path().is_absolute(), "");
|
||||
|
||||
// NOTE: On macOS the path usually contains symlinks which prevents base path from being stripped.
|
||||
// Use canonical() to resolve symnlinks and get consistent results on all platforms.
|
||||
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
|
||||
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
|
||||
|
||||
vector<string> commandLine = {
|
||||
"solc",
|
||||
"--base-path=" + currentDirNoSymlinks.string(),
|
||||
"contract1.sol", // Relative path
|
||||
"c/d/contract2.sol", // Relative path with subdirectories
|
||||
currentDirNoSymlinks.string() + "/contract3.sol", // Absolute path inside working dir
|
||||
otherDirNoSymlinks.string() + "/contract4.sol", // Absolute path outside of working dir
|
||||
};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.paths = {
|
||||
"contract1.sol",
|
||||
"c/d/contract2.sol",
|
||||
currentDirNoSymlinks / "contract3.sol",
|
||||
otherDirNoSymlinks / "contract4.sol",
|
||||
};
|
||||
expectedOptions.input.basePath = currentDirNoSymlinks;
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
{"contract1.sol", ""},
|
||||
{"c/d/contract2.sol", ""},
|
||||
{"contract3.sol", ""},
|
||||
{expectedOtherDir.generic_string() + "/contract4.sol", ""},
|
||||
};
|
||||
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {
|
||||
currentDirNoSymlinks / "c/d",
|
||||
currentDirNoSymlinks,
|
||||
otherDirNoSymlinks,
|
||||
};
|
||||
|
||||
createFilesWithParentDirs(expectedOptions.input.paths);
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == expectedWorkDir);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_different_from_work_dir)
|
||||
{
|
||||
TemporaryDirectory tempDirCurrent(TEST_CASE_NAME);
|
||||
TemporaryDirectory tempDirOther(TEST_CASE_NAME);
|
||||
TemporaryDirectory tempDirBase(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDirCurrent.path());
|
||||
soltestAssert(tempDirCurrent.path().is_absolute(), "");
|
||||
soltestAssert(tempDirOther.path().is_absolute(), "");
|
||||
soltestAssert(tempDirBase.path().is_absolute(), "");
|
||||
|
||||
// NOTE: On macOS the path usually contains symlinks which prevents base path from being stripped.
|
||||
// Use canonical() to resolve symnlinks and get consistent results on all platforms.
|
||||
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
|
||||
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
|
||||
boost::filesystem::path baseDirNoSymlinks = boost::filesystem::canonical(tempDirBase.path());
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
boost::filesystem::path expectedCurrentDir = "/" / currentDirNoSymlinks.relative_path();
|
||||
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
|
||||
boost::filesystem::path expectedBaseDir = "/" / baseDirNoSymlinks.relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
soltestAssert(expectedCurrentDir.is_absolute() || expectedCurrentDir.root_path() == "/", "");
|
||||
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
|
||||
soltestAssert(expectedBaseDir.is_absolute() || expectedBaseDir.root_path() == "/", "");
|
||||
|
||||
vector<string> commandLine = {
|
||||
"solc",
|
||||
"--base-path=" + baseDirNoSymlinks.string(),
|
||||
"contract1.sol", // Relative path
|
||||
"c/d/contract2.sol", // Relative path with subdirectories
|
||||
currentDirNoSymlinks.string() + "/contract3.sol", // Absolute path inside working dir
|
||||
otherDirNoSymlinks.string() + "/contract4.sol", // Absolute path outside of working dir
|
||||
baseDirNoSymlinks.string() + "/contract5.sol", // Absolute path inside base path
|
||||
};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.paths = {
|
||||
"contract1.sol",
|
||||
"c/d/contract2.sol",
|
||||
currentDirNoSymlinks / "contract3.sol",
|
||||
otherDirNoSymlinks / "contract4.sol",
|
||||
baseDirNoSymlinks / "contract5.sol",
|
||||
};
|
||||
expectedOptions.input.basePath = baseDirNoSymlinks;
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
{expectedWorkDir.generic_string() + "/contract1.sol", ""},
|
||||
{expectedWorkDir.generic_string() + "/c/d/contract2.sol", ""},
|
||||
{expectedCurrentDir.generic_string() + "/contract3.sol", ""},
|
||||
{expectedOtherDir.generic_string() + "/contract4.sol", ""},
|
||||
{"contract5.sol", ""},
|
||||
};
|
||||
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {
|
||||
currentDirNoSymlinks / "c/d",
|
||||
currentDirNoSymlinks,
|
||||
otherDirNoSymlinks,
|
||||
baseDirNoSymlinks,
|
||||
};
|
||||
|
||||
createFilesWithParentDirs(expectedOptions.input.paths);
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == expectedBaseDir);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_relative_base_path)
|
||||
{
|
||||
TemporaryDirectory tempDirCurrent(TEST_CASE_NAME);
|
||||
TemporaryDirectory tempDirOther(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDirCurrent.path());
|
||||
soltestAssert(tempDirCurrent.path().is_absolute(), "");
|
||||
soltestAssert(tempDirOther.path().is_absolute(), "");
|
||||
|
||||
// NOTE: On macOS the path usually contains symlinks which prevents base path from being stripped.
|
||||
// Use canonical() to resolve symnlinks and get consistent results on all platforms.
|
||||
boost::filesystem::path currentDirNoSymlinks = boost::filesystem::canonical(tempDirCurrent.path());
|
||||
boost::filesystem::path otherDirNoSymlinks = boost::filesystem::canonical(tempDirOther.path());
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
boost::filesystem::path expectedOtherDir = "/" / otherDirNoSymlinks.relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
soltestAssert(expectedOtherDir.is_absolute() || expectedOtherDir.root_path() == "/", "");
|
||||
|
||||
vector<string> commandLine = {
|
||||
"solc",
|
||||
"--base-path=base",
|
||||
"contract1.sol", // Relative path outside of base path
|
||||
"base/contract2.sol", // Relative path inside base path
|
||||
currentDirNoSymlinks.string() + "/contract3.sol", // Absolute path inside working dir
|
||||
currentDirNoSymlinks.string() + "/base/contract4.sol", // Absolute path inside base path
|
||||
otherDirNoSymlinks.string() + "/contract5.sol", // Absolute path outside of working dir
|
||||
otherDirNoSymlinks.string() + "/base/contract6.sol", // Absolute path outside of working dir
|
||||
};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.paths = {
|
||||
"contract1.sol",
|
||||
"base/contract2.sol",
|
||||
currentDirNoSymlinks / "contract3.sol",
|
||||
currentDirNoSymlinks / "base/contract4.sol",
|
||||
otherDirNoSymlinks / "contract5.sol",
|
||||
otherDirNoSymlinks / "base/contract6.sol",
|
||||
};
|
||||
expectedOptions.input.basePath = "base";
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
{expectedWorkDir.generic_string() + "/contract1.sol", ""},
|
||||
{"contract2.sol", ""},
|
||||
{expectedWorkDir.generic_string() + "/contract3.sol", ""},
|
||||
{"contract4.sol", ""},
|
||||
{expectedOtherDir.generic_string() + "/contract5.sol", ""},
|
||||
{expectedOtherDir.generic_string() + "/base/contract6.sol", ""},
|
||||
};
|
||||
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {
|
||||
currentDirNoSymlinks / "base",
|
||||
currentDirNoSymlinks,
|
||||
otherDirNoSymlinks,
|
||||
otherDirNoSymlinks / "base",
|
||||
};
|
||||
|
||||
createFilesWithParentDirs(expectedOptions.input.paths);
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_normalization_and_weird_names)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
boost::filesystem::create_directories(tempDir.path() / "x/y/z");
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "x/y/z");
|
||||
soltestAssert(tempDir.path().is_absolute(), "");
|
||||
|
||||
string uncPath = "//" + tempDir.path().relative_path().generic_string();
|
||||
soltestAssert(FileReader::isUNCPath(uncPath), "");
|
||||
|
||||
boost::filesystem::path tempDirNoSymlinks = boost::filesystem::canonical(tempDir.path());
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
|
||||
vector<string> commandLine = {
|
||||
"solc",
|
||||
|
||||
#if !defined(_WIN32)
|
||||
// URLs. We interpret them as local paths.
|
||||
// Note that : is not allowed in file names on Windows.
|
||||
"file://c/d/contract1.sol",
|
||||
"file:///c/d/contract2.sol",
|
||||
"https://example.com/contract3.sol",
|
||||
#endif
|
||||
|
||||
// Redundant slashes
|
||||
"a/b//contract4.sol",
|
||||
"a/b///contract5.sol",
|
||||
"a/b////contract6.sol",
|
||||
|
||||
// Dot segments
|
||||
"./a/b/contract7.sol",
|
||||
"././a/b/contract8.sol",
|
||||
"a/./b/contract9.sol",
|
||||
"a/././b/contract10.sol",
|
||||
|
||||
// Dot dot segments
|
||||
"../a/b/contract11.sol",
|
||||
"../../a/b/contract12.sol",
|
||||
"a/../b/contract13.sol",
|
||||
"a/b/../../contract14.sol",
|
||||
tempDirNoSymlinks.string() + "/x/y/z/a/../b/contract15.sol",
|
||||
tempDirNoSymlinks.string() + "/x/y/z/a/b/../../contract16.sol",
|
||||
|
||||
// Dot dot segments going beyond filesystem root
|
||||
"/../" + tempDir.path().relative_path().generic_string() + "/contract17.sol",
|
||||
"/../../" + tempDir.path().relative_path().generic_string() + "/contract18.sol",
|
||||
|
||||
#if !defined(_WIN32)
|
||||
// Name conflict with source unit name of stdin.
|
||||
// Note that < and > are not allowed in file names on Windows.
|
||||
"<stdin>",
|
||||
|
||||
// UNC paths on UNIX just resolve into normal paths. On Windows this would be an network
|
||||
// share (and an error unless the share actually exists so I can't test it here).
|
||||
uncPath + "/contract19.sol",
|
||||
|
||||
// Windows paths on non-Windows systems.
|
||||
// Note that on Windows we tested them already just by using absolute paths.
|
||||
"a\\b\\contract20.sol",
|
||||
"C:\\a\\b\\contract21.sol",
|
||||
#endif
|
||||
};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.paths = {
|
||||
#if !defined(_WIN32)
|
||||
"file://c/d/contract1.sol",
|
||||
"file:///c/d/contract2.sol",
|
||||
"https://example.com/contract3.sol",
|
||||
#endif
|
||||
|
||||
"a/b//contract4.sol",
|
||||
"a/b///contract5.sol",
|
||||
"a/b////contract6.sol",
|
||||
|
||||
"./a/b/contract7.sol",
|
||||
"././a/b/contract8.sol",
|
||||
"a/./b/contract9.sol",
|
||||
"a/././b/contract10.sol",
|
||||
|
||||
"../a/b/contract11.sol",
|
||||
"../../a/b/contract12.sol",
|
||||
"a/../b/contract13.sol",
|
||||
"a/b/../../contract14.sol",
|
||||
tempDirNoSymlinks.string() + "/x/y/z/a/../b/contract15.sol",
|
||||
tempDirNoSymlinks.string() + "/x/y/z/a/b/../../contract16.sol",
|
||||
|
||||
"/../" + tempDir.path().relative_path().string() + "/contract17.sol",
|
||||
"/../../" + tempDir.path().relative_path().string() + "/contract18.sol",
|
||||
|
||||
#if !defined(_WIN32)
|
||||
"<stdin>",
|
||||
|
||||
uncPath + "/contract19.sol",
|
||||
|
||||
"a\\b\\contract20.sol",
|
||||
"C:\\a\\b\\contract21.sol",
|
||||
#endif
|
||||
};
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
#if !defined(_WIN32)
|
||||
{"file:/c/d/contract1.sol", ""},
|
||||
{"file:/c/d/contract2.sol", ""},
|
||||
{"https:/example.com/contract3.sol", ""},
|
||||
#endif
|
||||
|
||||
{"a/b/contract4.sol", ""},
|
||||
{"a/b/contract5.sol", ""},
|
||||
{"a/b/contract6.sol", ""},
|
||||
|
||||
{"a/b/contract7.sol", ""},
|
||||
{"a/b/contract8.sol", ""},
|
||||
{"a/b/contract9.sol", ""},
|
||||
{"a/b/contract10.sol", ""},
|
||||
|
||||
{expectedWorkDir.parent_path().generic_string() + "/a/b/contract11.sol", ""},
|
||||
{expectedWorkDir.parent_path().parent_path().generic_string() + "/a/b/contract12.sol", ""},
|
||||
{"b/contract13.sol", ""},
|
||||
{"contract14.sol", ""},
|
||||
{"b/contract15.sol", ""},
|
||||
{"contract16.sol", ""},
|
||||
|
||||
{"/" + tempDir.path().relative_path().generic_string() + "/contract17.sol", ""},
|
||||
{"/" + tempDir.path().relative_path().generic_string() + "/contract18.sol", ""},
|
||||
|
||||
#if !defined(_WIN32)
|
||||
{"<stdin>", ""},
|
||||
|
||||
{uncPath + "/contract19.sol", ""},
|
||||
|
||||
{"a\\b\\contract20.sol", ""},
|
||||
{"C:\\a\\b\\contract21.sol", ""},
|
||||
#endif
|
||||
};
|
||||
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {
|
||||
#if !defined(_WIN32)
|
||||
tempDirNoSymlinks / "x/y/z/file:/c/d",
|
||||
tempDirNoSymlinks / "x/y/z/https:/example.com",
|
||||
#endif
|
||||
tempDirNoSymlinks / "x/y/z/a/b",
|
||||
tempDirNoSymlinks / "x/y/z",
|
||||
tempDirNoSymlinks / "x/y/z/b",
|
||||
tempDirNoSymlinks / "x/y/a/b",
|
||||
tempDirNoSymlinks / "x/a/b",
|
||||
tempDirNoSymlinks,
|
||||
#if !defined(_WIN32)
|
||||
boost::filesystem::canonical(uncPath),
|
||||
#endif
|
||||
};
|
||||
|
||||
createFilesWithParentDirs(expectedOptions.input.paths);
|
||||
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == expectedOptions.input.basePath);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_symlinks)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
createFilesWithParentDirs({tempDir.path() / "x/y/z/contract.sol"});
|
||||
boost::filesystem::create_directories(tempDir.path() / "r");
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path() / "r");
|
||||
|
||||
if (
|
||||
#if !defined(_WIN32)
|
||||
!createSymlinkIfSupportedByFilesystem("../x/y", tempDir.path() / "r/sym", true) ||
|
||||
#else
|
||||
// NOTE: On Windows / works as a separator in a symlink target only if the target is absolute
|
||||
!createSymlinkIfSupportedByFilesystem("..\\x\\y", tempDir.path() / "r/sym", true) ||
|
||||
#endif
|
||||
!createSymlinkIfSupportedByFilesystem("contract.sol", tempDir.path() / "x/y/z/contract_symlink.sol", false)
|
||||
)
|
||||
return;
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
soltestAssert(expectedWorkDir.is_absolute() || expectedWorkDir.root_path() == "/", "");
|
||||
|
||||
vector<string> commandLine = {
|
||||
"solc",
|
||||
|
||||
"--base-path=../r/sym/z/",
|
||||
"sym/z/contract.sol", // File accessed directly + same dir symlink as base path
|
||||
"../x/y/z/contract.sol", // File accessed directly + different dir symlink than base path
|
||||
"sym/z/contract_symlink.sol", // File accessed via symlink + same dir symlink as base path
|
||||
"../x/y/z/contract_symlink.sol", // File accessed via symlink + different dir symlink than base path
|
||||
};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.paths = {
|
||||
"sym/z/contract.sol",
|
||||
"../x/y/z/contract.sol",
|
||||
"sym/z/contract_symlink.sol",
|
||||
"../x/y/z/contract_symlink.sol",
|
||||
};
|
||||
expectedOptions.input.basePath = "../r/sym/z/";
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
{"contract.sol", ""},
|
||||
{(expectedWorkDir.parent_path() / "x/y/z/contract.sol").generic_string(), ""},
|
||||
{"contract_symlink.sol", ""},
|
||||
{(expectedWorkDir.parent_path() / "x/y/z/contract_symlink.sol").generic_string(), ""},
|
||||
};
|
||||
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {
|
||||
boost::filesystem::canonical(tempDir.path()) / "x/y/z",
|
||||
};
|
||||
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "sym/z/");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_base_path_and_stdin)
|
||||
{
|
||||
TemporaryDirectory tempDir(TEST_CASE_NAME);
|
||||
TemporaryWorkingDirectory tempWorkDir(tempDir.path());
|
||||
boost::filesystem::create_directories(tempDir.path() / "base");
|
||||
|
||||
boost::filesystem::path expectedWorkDir = "/" / boost::filesystem::current_path().relative_path();
|
||||
|
||||
vector<string> commandLine = {"solc", "--base-path=base", "-"};
|
||||
|
||||
CommandLineOptions expectedOptions;
|
||||
expectedOptions.input.addStdin = true;
|
||||
expectedOptions.input.basePath = "base";
|
||||
expectedOptions.modelChecker.initialize = true;
|
||||
|
||||
map<string, string> expectedSources = {
|
||||
{"<stdin>", "\n"},
|
||||
};
|
||||
FileReader::FileSystemPathSet expectedAllowedDirectories = {};
|
||||
|
||||
createFilesWithParentDirs(expectedOptions.input.paths);
|
||||
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
|
||||
|
||||
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.allowedDirectories() == expectedAllowedDirectories);
|
||||
BOOST_TEST(result.reader.basePath() == expectedWorkDir / "base");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
} // namespace solidity::frontend::test
|
||||
|
Loading…
Reference in New Issue
Block a user