Skip to content

Commit 13d1fc9

Browse files
authored
Merge pull request #11579 from ethereum/better-errors-about-bad-paths-in-tests-with-external-sources
Better errors about bad paths in tests with external sources
2 parents 54b1c66 + e841479 commit 13d1fc9

File tree

12 files changed

+210
-2
lines changed

12 files changed

+210
-2
lines changed

libsolutil/CommonIO.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,17 @@ namespace
4343
template <typename T>
4444
inline T readFile(std::string const& _file)
4545
{
46+
assertThrow(boost::filesystem::exists(_file), FileNotFound, _file);
47+
48+
// ifstream does not always fail when the path leads to a directory. Instead it might succeed
49+
// with tellg() returning a nonsensical value so that std::length_error gets raised in resize().
50+
assertThrow(boost::filesystem::is_regular_file(_file), NotAFile, _file);
51+
4652
T ret;
4753
size_t const c_elementSize = sizeof(typename T::value_type);
4854
std::ifstream is(_file, std::ifstream::binary);
55+
56+
// Technically, this can still fail even though we checked above because FS content can change at any time.
4957
assertThrow(is, FileNotFound, _file);
5058

5159
// get length of file:

libsolutil/CommonIO.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace solidity::util
3333

3434
/// Retrieve and returns the contents of the given file as a std::string.
3535
/// If the file doesn't exist, it will throw a FileNotFound exception.
36+
/// If the file exists but is not a regular file, it will throw NotAFile exception.
3637
/// If the file is empty, returns an empty string.
3738
std::string readFileAsString(std::string const& _file);
3839

libsolutil/Exceptions.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ DEV_SIMPLE_EXCEPTION(InvalidAddress);
4949
DEV_SIMPLE_EXCEPTION(BadHexCharacter);
5050
DEV_SIMPLE_EXCEPTION(BadHexCase);
5151
DEV_SIMPLE_EXCEPTION(FileNotFound);
52+
DEV_SIMPLE_EXCEPTION(NotAFile);
5253
DEV_SIMPLE_EXCEPTION(DataTooLong);
5354
DEV_SIMPLE_EXCEPTION(StringTooLong);
5455

solc/CommandLineInterface.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,10 @@ bool CommandLineInterface::parseLibraryOption(string const& _input)
668668
{
669669
// Should not happen if `fs::is_regular_file` is correct.
670670
}
671+
catch (NotAFile const&)
672+
{
673+
// Should not happen if `fs::is_regular_file` is correct.
674+
}
671675

672676
vector<string> libraries;
673677
boost::split(libraries, data, boost::is_space() || boost::is_any_of(","), boost::token_compress_on);
@@ -1263,6 +1267,11 @@ bool CommandLineInterface::processInput()
12631267
serr() << "File not found: " << jsonFile << endl;
12641268
return false;
12651269
}
1270+
catch (NotAFile const&)
1271+
{
1272+
serr() << "Not a regular file: " << jsonFile << endl;
1273+
return false;
1274+
}
12661275
}
12671276
StandardCompiler compiler(m_fileReader.reader());
12681277
sout() << compiler.compile(std::move(input)) << endl;

test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ set(sources
88
EVMHost.h
99
ExecutionFramework.cpp
1010
ExecutionFramework.h
11+
FilesystemUtils.cpp
12+
FilesystemUtils.h
1113
InteractiveTests.h
1214
Metadata.cpp
1315
Metadata.h
@@ -31,6 +33,7 @@ detect_stray_source_files("${contracts_sources}" "contracts/")
3133
set(libsolutil_sources
3234
libsolutil/Checksum.cpp
3335
libsolutil/CommonData.cpp
36+
libsolutil/CommonIO.cpp
3437
libsolutil/FixedHash.cpp
3538
libsolutil/IndentedWriter.cpp
3639
libsolutil/IpfsHash.cpp

test/FilesystemUtils.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <test/FilesystemUtils.h>
20+
21+
#include <test/libsolidity/util/SoltestErrors.h>
22+
23+
using namespace std;
24+
using namespace solidity;
25+
using namespace solidity::test;
26+
27+
void solidity::test::createFileWithContent(boost::filesystem::path const& _path, string const& content)
28+
{
29+
if (boost::filesystem::is_regular_file(_path))
30+
BOOST_THROW_EXCEPTION(runtime_error("File already exists: \"" + _path.string() + "\".")); \
31+
32+
// Use binary mode to avoid line ending conversion on Windows.
33+
ofstream newFile(_path.string(), std::ofstream::binary);
34+
if (newFile.fail() || !boost::filesystem::is_regular_file(_path))
35+
BOOST_THROW_EXCEPTION(runtime_error("Failed to create a file: \"" + _path.string() + "\".")); \
36+
37+
newFile << content;
38+
}
39+
40+
bool solidity::test::createSymlinkIfSupportedByFilesystem(
41+
boost::filesystem::path const& _targetPath,
42+
boost::filesystem::path const& _linkName
43+
)
44+
{
45+
boost::system::error_code symlinkCreationError;
46+
boost::filesystem::create_symlink(_targetPath, _linkName, symlinkCreationError);
47+
48+
if (!symlinkCreationError)
49+
return true;
50+
else if (
51+
symlinkCreationError == boost::system::errc::not_supported ||
52+
symlinkCreationError == boost::system::errc::operation_not_supported
53+
)
54+
return false;
55+
else
56+
BOOST_THROW_EXCEPTION(runtime_error(
57+
"Failed to create a symbolic link: \"" + _linkName.string() + "\""
58+
" -> " + _targetPath.string() + "\"."
59+
));
60+
}

test/FilesystemUtils.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
/**
19+
* Helpers for common filesystem operations used in multiple tests.
20+
*/
21+
22+
#pragma once
23+
24+
#include <boost/filesystem.hpp>
25+
26+
#include <string>
27+
28+
namespace solidity::test
29+
{
30+
31+
/// Creates a file with the exact content specified in the second argument.
32+
/// Throws an exception if the file already exists or if the parent directory of the file does not.
33+
void createFileWithContent(boost::filesystem::path const& _path, std::string const& content);
34+
35+
/// Creates a symlink between two paths.
36+
/// The target does not have to exist.
37+
/// @returns true if the symlink has been successfully created, false if the filesystem does not
38+
/// support symlinks.
39+
/// Throws an exception of the operation fails for a different reason.
40+
bool createSymlinkIfSupportedByFilesystem(
41+
boost::filesystem::path const& _targetPath,
42+
boost::filesystem::path const& _linkName
43+
);
44+
45+
}

test/TestCaseReader.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ pair<SourceMap, size_t> TestCaseReader::parseSourcesAndSettingsWithLineNumber(is
159159
soltestAssert(!externalSourceName.empty(), "");
160160
fs::path externalSourceTarget(externalSourceString);
161161
fs::path testCaseParentDir = m_fileName.parent_path();
162-
if (!externalSourceTarget.is_relative())
162+
if (!externalSourceTarget.is_relative() || !externalSourceTarget.root_path().empty())
163+
// NOTE: UNC paths (ones starting with // or \\) are considered relative by Boost
164+
// since they have an empty root directory (but non-empty root name).
163165
BOOST_THROW_EXCEPTION(runtime_error("External Source paths need to be relative to the location of the test case."));
164166
fs::path externalSourceFullPath = testCaseParentDir / externalSourceTarget;
165167
string externalSourceContent;

test/libsolutil/CommonData.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
// SPDX-License-Identifier: GPL-3.0
1818
/**
19-
* Unit tests for the StringUtils routines.
19+
* Unit tests for the CommonData routines.
2020
*/
2121

2222
#include <libsolutil/Common.h>

test/libsolutil/CommonIO.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
/// Unit tests for the CommonIO routines.
20+
21+
#include <libsolutil/CommonIO.h>
22+
23+
#include <test/Common.h>
24+
#include <test/FilesystemUtils.h>
25+
#include <test/TemporaryDirectory.h>
26+
#include <test/libsolidity/util/SoltestErrors.h>
27+
28+
#include <boost/test/unit_test.hpp>
29+
#include <boost/filesystem.hpp>
30+
31+
#include <fstream>
32+
#include <string>
33+
34+
using namespace std;
35+
using namespace solidity::test;
36+
37+
namespace solidity::util::test
38+
{
39+
40+
BOOST_AUTO_TEST_SUITE(CommonIOTest)
41+
42+
BOOST_AUTO_TEST_CASE(readFileAsString_regular_file)
43+
{
44+
TemporaryDirectory tempDir("common-io-test-");
45+
createFileWithContent(tempDir.path() / "test.txt", "ABC\ndef\n");
46+
47+
BOOST_TEST(readFileAsString((tempDir.path() / "test.txt").string()) == "ABC\ndef\n");
48+
}
49+
50+
BOOST_AUTO_TEST_CASE(readFileAsString_directory)
51+
{
52+
TemporaryDirectory tempDir("common-io-test-");
53+
BOOST_CHECK_THROW(readFileAsString(tempDir.path().string()), NotAFile);
54+
}
55+
56+
BOOST_AUTO_TEST_CASE(readFileAsString_symlink)
57+
{
58+
TemporaryDirectory tempDir("common-io-test-");
59+
createFileWithContent(tempDir.path() / "test.txt", "ABC\ndef\n");
60+
61+
if (!createSymlinkIfSupportedByFilesystem("test.txt", tempDir.path() / "symlink.txt"))
62+
return;
63+
64+
BOOST_TEST(readFileAsString((tempDir.path() / "symlink.txt").string()) == "ABC\ndef\n");
65+
}
66+
67+
BOOST_AUTO_TEST_SUITE_END()
68+
69+
} // namespace solidity::util::test

0 commit comments

Comments
 (0)