1 // Copyright Andrey Semashev 2020.
2
3 // Distributed under the Boost Software License, Version 1.0.
4 // See http://www.boost.org/LICENSE_1_0.txt
5
6 // Library home page: http://www.boost.org/libs/filesystem
7
8 // This test verifies copy operation behavior.
9
10 #include <boost/filesystem/operations.hpp>
11 #include <boost/filesystem/path.hpp>
12 #include <boost/filesystem/directory.hpp>
13 #include <boost/filesystem/exception.hpp>
14 #include <boost/system/error_code.hpp>
15
16 #include <set>
17 #include <string>
18 #include <fstream>
19 #include <iostream>
20 #include <stdexcept>
21
22 #include <boost/throw_exception.hpp>
23 #include <boost/exception/diagnostic_information.hpp>
24 #include <boost/core/lightweight_test.hpp>
25
26 // on Windows, except for standard libaries known to have wchar_t overloads for
27 // file stream I/O, use path::string() to get a narrow character c_str()
28 #if defined(BOOST_WINDOWS_API) \
29 && (!defined(_CPPLIB_VER) || _CPPLIB_VER < 405) // not Dinkumware || no wide overloads
30 # define BOOST_FILESYSTEM_C_STR string().c_str() // use narrow, since wide not available
31 #else // use the native c_str, which will be narrow on POSIX, wide on Windows
32 # define BOOST_FILESYSTEM_C_STR c_str()
33 #endif
34
35 namespace fs = boost::filesystem;
36
37 namespace {
38
create_file(fs::path const & ph,std::string const & contents=std::string ())39 void create_file(fs::path const& ph, std::string const& contents = std::string())
40 {
41 std::ofstream f(ph.BOOST_FILESYSTEM_C_STR, std::ios_base::out | std::ios_base::trunc);
42 if (!f)
43 BOOST_THROW_EXCEPTION(std::runtime_error("Failed to create file: " + ph.string()));
44 if (!contents.empty())
45 f << contents;
46 }
47
verify_file(fs::path const & ph,std::string const & expected)48 void verify_file(fs::path const& ph, std::string const& expected)
49 {
50 std::ifstream f(ph.BOOST_FILESYSTEM_C_STR);
51 if (!f)
52 BOOST_THROW_EXCEPTION(std::runtime_error("Failed to open file: " + ph.string()));
53 std::string contents;
54 f >> contents;
55 BOOST_TEST_EQ(contents, expected);
56 if (contents != expected)
57 {
58 BOOST_THROW_EXCEPTION(std::runtime_error("verify_file failed: contents \"" + contents + "\" != \"" + expected + "\" in " + ph.string()));
59 }
60 }
61
create_tree()62 fs::path create_tree()
63 {
64 fs::path root_dir = fs::unique_path();
65
66 fs::create_directory(root_dir);
67 create_file(root_dir / "f1", "f1");
68 create_file(root_dir / "f2", "f2");
69
70 fs::create_directory(root_dir / "d1");
71 create_file(root_dir / "d1/f1", "d1f1");
72
73 fs::create_directory(root_dir / "d1/d1");
74 create_file(root_dir / "d1/d1/f1", "d1d1f1");
75
76 fs::create_directory(root_dir / "d1/d2");
77
78 fs::create_directory(root_dir / "d2");
79 create_file(root_dir / "d2/f1", "d2f1");
80
81 return root_dir;
82 }
83
84 typedef std::set< fs::path > directory_tree;
85
collect_directory_tree(fs::path const & root_dir)86 directory_tree collect_directory_tree(fs::path const& root_dir)
87 {
88 std::cout << "Collecting directory tree in: " << root_dir << '\n';
89
90 directory_tree tree;
91 fs::recursive_directory_iterator it(root_dir, fs::directory_options::skip_permission_denied | fs::directory_options::follow_directory_symlink | fs::directory_options::skip_dangling_symlinks), end;
92 while (it != end)
93 {
94 fs::path p = fs::relative(it->path(), root_dir);
95 std::cout << p << '\n';
96 tree.insert(p);
97 ++it;
98 }
99
100 std::cout << "done." << std::endl;
101
102 return tree;
103 }
104
test_copy_file_default(fs::path const & root_dir)105 void test_copy_file_default(fs::path const& root_dir)
106 {
107 std::cout << "test_copy_file_default" << std::endl;
108
109 fs::path target_dir = fs::unique_path();
110 fs::create_directory(target_dir);
111
112 fs::copy(root_dir / "f1", target_dir);
113 fs::copy(root_dir / "f2", target_dir / "f3");
114
115 directory_tree tree = collect_directory_tree(target_dir);
116
117 BOOST_TEST_EQ(tree.size(), 2u);
118 BOOST_TEST(tree.find("f1") != tree.end());
119 BOOST_TEST(tree.find("f3") != tree.end());
120
121 verify_file(target_dir / "f1", "f1");
122 verify_file(target_dir / "f3", "f2");
123
124 fs::remove_all(target_dir);
125 }
126
test_copy_dir_default(fs::path const & root_dir,bool with_symlinks)127 void test_copy_dir_default(fs::path const& root_dir, bool with_symlinks)
128 {
129 std::cout << "test_copy_dir_default" << std::endl;
130
131 fs::path target_dir = fs::unique_path();
132
133 fs::copy(root_dir, target_dir);
134
135 directory_tree tree = collect_directory_tree(target_dir);
136
137 BOOST_TEST_EQ(tree.size(), 4u + with_symlinks);
138 BOOST_TEST(tree.find("f1") != tree.end());
139 BOOST_TEST(tree.find("f2") != tree.end());
140 BOOST_TEST(tree.find("d1") != tree.end());
141 BOOST_TEST(tree.find("d2") != tree.end());
142 if (with_symlinks)
143 {
144 BOOST_TEST(tree.find("s1") != tree.end());
145 }
146
147 verify_file(target_dir / "f1", "f1");
148 verify_file(target_dir / "f2", "f2");
149
150 fs::remove_all(target_dir);
151 }
152
test_copy_dir_default_ec(fs::path const & root_dir,bool with_symlinks)153 void test_copy_dir_default_ec(fs::path const& root_dir, bool with_symlinks)
154 {
155 // This test is similar to test_copy_dir_default, but uses an error_code overload of the operation.
156 // Tests for https://github.com/boostorg/filesystem/issues/152 fix.
157
158 std::cout << "test_copy_dir_default_ec" << std::endl;
159
160 fs::path target_dir = fs::unique_path();
161
162 boost::system::error_code ec;
163 fs::copy(root_dir, target_dir, ec);
164 BOOST_TEST(!ec);
165
166 directory_tree tree = collect_directory_tree(target_dir);
167
168 BOOST_TEST_EQ(tree.size(), 4u + with_symlinks);
169 BOOST_TEST(tree.find("f1") != tree.end());
170 BOOST_TEST(tree.find("f2") != tree.end());
171 BOOST_TEST(tree.find("d1") != tree.end());
172 BOOST_TEST(tree.find("d2") != tree.end());
173 if (with_symlinks)
174 {
175 BOOST_TEST(tree.find("s1") != tree.end());
176 }
177
178 verify_file(target_dir / "f1", "f1");
179 verify_file(target_dir / "f2", "f2");
180
181 fs::remove_all(target_dir);
182 }
183
test_copy_dir_recursive(fs::path const & root_dir)184 void test_copy_dir_recursive(fs::path const& root_dir)
185 {
186 std::cout << "test_copy_dir_recursive" << std::endl;
187
188 fs::path target_dir = fs::unique_path();
189
190 fs::copy(root_dir, target_dir, fs::copy_options::recursive);
191
192 directory_tree tree = collect_directory_tree(target_dir);
193
194 BOOST_TEST_EQ(tree.size(), 9u);
195 BOOST_TEST(tree.find("f1") != tree.end());
196 BOOST_TEST(tree.find("f2") != tree.end());
197 BOOST_TEST(tree.find("d1") != tree.end());
198 BOOST_TEST(tree.find(fs::path("d1") / "f1") != tree.end());
199 BOOST_TEST(tree.find(fs::path("d1") / "d1") != tree.end());
200 BOOST_TEST(tree.find(fs::path("d1") / "d1" / "f1") != tree.end());
201 BOOST_TEST(tree.find(fs::path("d1") / "d2") != tree.end());
202 BOOST_TEST(tree.find("d2") != tree.end());
203 BOOST_TEST(tree.find(fs::path("d2") / "f1") != tree.end());
204
205 verify_file(target_dir / "f1", "f1");
206 verify_file(target_dir / "f2", "f2");
207 verify_file(target_dir / "d1/f1", "d1f1");
208 verify_file(target_dir / "d1/d1/f1", "d1d1f1");
209 verify_file(target_dir / "d2/f1", "d2f1");
210
211 fs::remove_all(target_dir);
212 }
213
test_copy_dir_recursive_tree(fs::path const & root_dir)214 void test_copy_dir_recursive_tree(fs::path const& root_dir)
215 {
216 std::cout << "test_copy_dir_recursive_tree" << std::endl;
217
218 fs::path target_dir = fs::unique_path();
219
220 fs::copy(root_dir, target_dir, fs::copy_options::recursive | fs::copy_options::directories_only);
221
222 directory_tree tree = collect_directory_tree(target_dir);
223
224 BOOST_TEST_EQ(tree.size(), 4u);
225 BOOST_TEST(tree.find("d1") != tree.end());
226 BOOST_TEST(tree.find(fs::path("d1") / "d1") != tree.end());
227 BOOST_TEST(tree.find(fs::path("d1") / "d2") != tree.end());
228 BOOST_TEST(tree.find("d2") != tree.end());
229
230 fs::remove_all(target_dir);
231 }
232
test_copy_file_symlinks(fs::path const & root_dir)233 void test_copy_file_symlinks(fs::path const& root_dir)
234 {
235 std::cout << "test_copy_file_symlinks" << std::endl;
236
237 fs::path target_dir = fs::unique_path();
238 fs::create_directory(target_dir);
239
240 fs::copy(root_dir / "f1", target_dir);
241
242 fs::path prev_cur_dir = fs::current_path();
243 fs::current_path(target_dir);
244 fs::copy(".." / root_dir / "f2", "f2", fs::copy_options::create_symlinks);
245 fs::current_path(prev_cur_dir);
246
247 // Copying from a relative path with copy_options::create_symlinks to a directory other than current directory is a non-standard extension
248 fs::copy(target_dir / "f1", target_dir / "f3", fs::copy_options::create_symlinks);
249
250 verify_file(target_dir / "f1", "f1");
251
252 fs::path link_target = fs::read_symlink(target_dir / "f2");
253 if (link_target != (".." / root_dir / "f2"))
254 {
255 BOOST_ERROR("Incorrect f2 symlink in test_copy_file_symlinks");
256 std::cout << (target_dir / "f2") << " => " << link_target << std::endl;
257 }
258
259 link_target = fs::read_symlink(target_dir / "f3");
260 if (link_target != "f1" && link_target != (fs::path(".") / "f1"))
261 {
262 BOOST_ERROR("Incorrect f3 symlink in test_copy_file_symlinks");
263 std::cout << (target_dir / "f3") << " => " << link_target << std::endl;
264 }
265
266 fs::remove_all(target_dir);
267 }
268
test_copy_errors(fs::path const & root_dir,bool symlinks_supported)269 void test_copy_errors(fs::path const& root_dir, bool symlinks_supported)
270 {
271 std::cout << "test_copy_errors" << std::endl;
272
273 fs::path target_dir = fs::unique_path();
274 fs::create_directory(target_dir);
275
276 BOOST_TEST_THROWS(fs::copy(root_dir / "non-existing", target_dir), fs::filesystem_error);
277
278 create_file(target_dir / "f1");
279
280 BOOST_TEST_THROWS(fs::copy(root_dir / "f1", target_dir), fs::filesystem_error);
281 BOOST_TEST_THROWS(fs::copy(root_dir / "f1", target_dir / "f1"), fs::filesystem_error);
282 BOOST_TEST_THROWS(fs::copy(root_dir / "d1", target_dir / "f1"), fs::filesystem_error);
283
284 BOOST_TEST_THROWS(fs::copy(target_dir, target_dir), fs::filesystem_error);
285 BOOST_TEST_THROWS(fs::copy(target_dir / "f1", target_dir / "f1"), fs::filesystem_error);
286
287 if (symlinks_supported)
288 {
289 // Should fail with is_a_directory error code
290 BOOST_TEST_THROWS(fs::copy(root_dir, target_dir, fs::copy_options::create_symlinks), fs::filesystem_error);
291 }
292
293 fs::remove_all(target_dir);
294 }
295
296 } // namespace
297
main()298 int main()
299 {
300 try
301 {
302 fs::path root_dir = create_tree();
303
304 test_copy_file_default(root_dir);
305 test_copy_dir_default(root_dir, false);
306 test_copy_dir_default_ec(root_dir, false);
307 test_copy_dir_recursive(root_dir);
308 test_copy_dir_recursive_tree(root_dir);
309
310 bool symlinks_supported = false;
311 try
312 {
313 fs::create_symlink("f1", root_dir / "s1");
314 symlinks_supported = true;
315 std::cout <<
316 " *** For information only ***\n"
317 " create_symlink() attempt succeeded" << std::endl;
318 }
319 catch (fs::filesystem_error& e)
320 {
321 std::cout <<
322 " *** For information only ***\n"
323 " create_symlink() attempt failed\n"
324 " filesystem_error.what() reports: " << e.what() << "\n"
325 " create_symlink() may not be supported on this operating system or file system" << std::endl;
326 }
327
328 if (symlinks_supported)
329 {
330 test_copy_dir_default(root_dir, true);
331 test_copy_file_symlinks(root_dir);
332 }
333
334 test_copy_errors(root_dir, symlinks_supported);
335
336 fs::remove_all(root_dir);
337
338 return boost::report_errors();
339 }
340 catch (std::exception& e)
341 {
342 std::cout << "FAIL, exception caught: " << boost::diagnostic_information(e) << std::endl;
343 return 1;
344 }
345 }
346