1 #include "filesystem_test.h"
2 
3 #include <glib.h>
4 #include "pbd/gstdio_compat.h"
5 
6 #include <unistd.h>
7 #include <stdlib.h>
8 
9 #include <fcntl.h>
10 
11 #ifdef COMPILER_MSVC
12 #include <sys/utime.h>
13 #else
14 #include <utime.h>
15 #endif
16 
17 #include <glibmm/miscutils.h>
18 #include <glibmm/fileutils.h>
19 #include <glibmm/convert.h>
20 #include <glibmm/timer.h>
21 
22 #include "pbd/file_utils.h"
23 #include "pbd/pathexpand.h"
24 
25 #include "test_common.h"
26 
27 using namespace std;
28 using namespace PBD;
29 
30 CPPUNIT_TEST_SUITE_REGISTRATION (FilesystemTest);
31 
32 namespace {
33 
34 class PwdReset
35 {
36 public:
37 
PwdReset(const string & new_pwd)38 	PwdReset(const string& new_pwd)
39 		: m_old_pwd(Glib::get_current_dir()) {
40 		CPPUNIT_ASSERT (g_chdir (new_pwd.c_str()) == 0);
41 	}
42 
~PwdReset()43 	~PwdReset()
44 	{
45 		CPPUNIT_ASSERT (g_chdir (m_old_pwd.c_str()) == 0);
46 	}
47 
48 private:
49 
50 	string m_old_pwd;
51 
52 };
53 
54 } // anon
55 
56 void
testPathIsWithin()57 FilesystemTest::testPathIsWithin ()
58 {
59 #ifndef PLATFORM_WINDOWS
60 	string output_path = test_output_directory ("testPathIsWithin");
61 	PwdReset pwd_reset(output_path);
62 
63 	CPPUNIT_ASSERT (g_mkdir_with_parents ("foo/bar/baz", 0755) == 0);
64 
65 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/bar/baz"), Glib::build_filename(output_path, "foo/bar/baz")));
66 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/bar"),     Glib::build_filename(output_path, "foo/bar/baz")));
67 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo"),         Glib::build_filename(output_path, "foo/bar/baz")));
68 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/bar"),     Glib::build_filename(output_path, "foo/bar/baz")));
69 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/bar"),     Glib::build_filename(output_path, "foo/bar")));
70 
71 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/bar/baz"), Glib::build_filename(output_path, "frobozz")) == false);
72 
73 	int const r = symlink ("bar", "foo/jim");
74 	CPPUNIT_ASSERT (r == 0);
75 
76 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/jim/baz"), Glib::build_filename(output_path, "foo/bar/baz")));
77 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/jim"),     Glib::build_filename(output_path, "foo/bar/baz")));
78 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo"),         Glib::build_filename(output_path, "foo/bar/baz")));
79 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/jim"),     Glib::build_filename(output_path, "foo/bar/baz")));
80 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/jim"),     Glib::build_filename(output_path, "foo/bar")));
81 
82 	CPPUNIT_ASSERT (PBD::path_is_within (Glib::build_filename(output_path, "foo/jim/baz"), Glib::build_filename(output_path, "frobozz")) == false);
83 #endif
84 }
85 
86 void
testCopyFileASCIIFilename()87 FilesystemTest::testCopyFileASCIIFilename ()
88 {
89 	string testdata_path;
90 	CPPUNIT_ASSERT (find_file (test_search_path (), "RosegardenPatchFile.xml", testdata_path));
91 
92 	string output_path = test_output_directory ("CopyFile");
93 
94 	output_path = Glib::build_filename (output_path, "RosegardenPatchFile.xml");
95 
96 	cerr << endl;
97 	cerr << "CopyFile test output path: " << output_path << endl;
98 
99 	CPPUNIT_ASSERT (PBD::copy_file (testdata_path, output_path));
100 }
101 
102 void
testCopyFileUTF8Filename()103 FilesystemTest::testCopyFileUTF8Filename ()
104 {
105 	vector<string> i18n_files;
106 
107 	Searchpath i18n_path(test_search_path());
108 	i18n_path.add_subdirectory_to_paths("i18n_test");
109 
110 	PBD::find_files_matching_pattern (i18n_files, i18n_path, "*.tst");
111 
112 	CPPUNIT_ASSERT (i18n_files.size() == 8);
113 
114 	cerr << endl;
115 	cerr << "Copying " << i18n_files.size() << " test files from: "
116 	     << i18n_path.to_string () << endl;
117 
118 	string output_dir = test_output_directory ("CopyFile");
119 
120 	for (vector<string>::iterator i = i18n_files.begin(); i != i18n_files.end(); ++i) {
121 		string input_path = *i;
122 		string output_file = Glib::path_get_basename(*i);
123 		string output_path = Glib::build_filename (output_dir, output_file);
124 
125 		cerr << "Copying test file: " << input_path
126 		     << " To " << output_path << endl;
127 
128 		CPPUNIT_ASSERT (PBD::copy_file (input_path, output_path));
129 	}
130 }
131 
132 void
testOpenFileUTF8Filename()133 FilesystemTest::testOpenFileUTF8Filename ()
134 {
135 	vector<string> i18n_files;
136 
137 	Searchpath i18n_path (test_search_path ());
138 	i18n_path.add_subdirectory_to_paths ("i18n_test");
139 
140 	PBD::find_files_matching_pattern (i18n_files, i18n_path, "*.tst");
141 
142 	CPPUNIT_ASSERT (i18n_files.size () == 8);
143 
144 	cerr << endl;
145 	cerr << "Opening " << i18n_files.size ()
146 	     << " test files from: " << i18n_path.to_string () << endl;
147 
148 	// check that g_open will successfully open all the test files
149 	for (vector<string>::iterator i = i18n_files.begin (); i != i18n_files.end ();
150 	     ++i) {
151 		string input_path = *i;
152 
153 		cerr << "Opening file: " << input_path << " with g_open" << endl;
154 
155 		int fdgo = g_open (input_path.c_str(), O_RDONLY, 0444);
156 
157 		CPPUNIT_ASSERT (fdgo != -1);
158 
159 		if (fdgo >= 0) {
160 			::close (fdgo);
161 		}
162 	}
163 
164 #ifdef PLATFORM_WINDOWS
165 	// This test is here to prove and remind us that using Glib::locale_from_utf8
166 	// to convert a utf-8 encoded file path for use with ::open will not work
167 	// for all file paths.
168 	//
169 	// It may be possible to convert a string that is utf-8 encoded that will not
170 	// work with ::open(on windows) to a string that will work with ::open using
171 	// Glib::locale_from_utf8 string if all the characters that are contained
172 	// in the utf-8 string can be found/mapped in the system code page.
173 	//
174 	// European locales that only have a small amount of extra characters with
175 	// accents/umlauts I'm guessing will be more likely succeed but CJK locales
176 	// will almost certainly fail
177 
178 	bool conversion_failed = false;
179 
180 	for (vector<string>::iterator i = i18n_files.begin (); i != i18n_files.end ();
181 	     ++i) {
182 		string input_path = *i;
183 		cerr << "Opening file: " << input_path << " with locale_from_utf8 and ::open "
184 		     << endl;
185 		string converted_input_path;
186 		int fdo;
187 
188 		try {
189 			// this will fail for utf8 that contains characters that aren't
190 			// representable in the system code page
191 			converted_input_path = Glib::locale_from_utf8 (input_path);
192 			// conversion succeeded so we expect ::open to be successful if the
193 			// current C library locale is the same as the system locale, which
194 			// it should be as we haven't changed it.
195 			fdo = ::open (converted_input_path.c_str (), O_RDONLY, 0444);
196 			CPPUNIT_ASSERT (fdo != -1);
197 
198 			if (converted_input_path != input_path) {
199 				cerr << "Character set conversion succeeded and strings differ for input "
200 				        "string: " << input_path << endl;
201 				// file path must have contained non-ASCII characters that were mapped
202 				// from the system code page so we would expect the original
203 				// utf-8 file path to fail with ::open
204 				int fd2 = ::open (input_path.c_str (), O_RDONLY, 0444);
205 				CPPUNIT_ASSERT (fd2 == -1);
206 			}
207 
208 		} catch (const Glib::ConvertError& err) {
209 			cerr << "Character set conversion failed: " << err.what () << endl;
210 			// I am confident that on Windows with the test data that no locale will
211 			// have a system code page containing all the characters required
212 			// and conversion will fail for at least one of the filenames
213 			conversion_failed = true;
214 			// CPPUNIT_ASSERT (err.code() == ?);
215 
216 			// conversion failed so we expect the original utf-8 string to fail
217 			// with ::open on Windows as the file path will not exist
218 			fdo = ::open (input_path.c_str (), O_RDONLY, 0444);
219 			CPPUNIT_ASSERT (fdo == -1);
220 		}
221 
222 		if (fdo >= 0) {
223 			::close (fdo);
224 		}
225 	}
226 	// we expect at least one conversion failure with the filename test data
227 	CPPUNIT_ASSERT (conversion_failed);
228 #endif
229 }
230 
231 void
testFindFilesMatchingPattern()232 FilesystemTest::testFindFilesMatchingPattern ()
233 {
234 	vector<string> patch_files;
235 
236 	PBD::find_files_matching_pattern (patch_files, test_search_path (), "*PatchFile*");
237 
238 	CPPUNIT_ASSERT(test_search_path ().size() == 1);
239 
240 	CPPUNIT_ASSERT(patch_files.size() == 2);
241 }
242 
243 string
create_test_directory(std::string test_dir)244 create_test_directory (std::string test_dir)
245 {
246 	vector<string> test_files;
247 	vector<string> i18n_files;
248 
249 	Searchpath spath(test_search_path());
250 	PBD::get_files (test_files, spath);
251 
252 	spath.add_subdirectory_to_paths("i18n_test");
253 
254 	PBD::get_files (i18n_files, spath);
255 
256 	string output_dir = test_output_directory (test_dir);
257 
258 	CPPUNIT_ASSERT (test_search_path().size () != 0);
259 
260 	string test_dir_path = test_search_path()[0];
261 
262 	cerr << endl;
263 	cerr << "Copying " << test_files.size() << " test files from: "
264 	     << test_dir_path << " to " << output_dir << endl;
265 
266 	CPPUNIT_ASSERT (test_files.size() != 0);
267 
268 	PBD::copy_files (test_dir_path, output_dir);
269 
270 	vector<string> copied_files;
271 
272 	PBD::get_files (copied_files, output_dir);
273 
274 	CPPUNIT_ASSERT (copied_files.size() == test_files.size());
275 
276 	string subdir_path = Glib::build_filename (output_dir, "subdir");
277 
278 	CPPUNIT_ASSERT (g_mkdir_with_parents (subdir_path.c_str(), 0755) == 0);
279 
280 	cerr << endl;
281 	cerr << "Copying " << i18n_files.size() << " i18n test files to: "
282 	     << subdir_path << endl;
283 
284 	for (vector<string>::iterator i = i18n_files.begin(); i != i18n_files.end(); ++i) {
285 		string input_filepath = *i;
286 		string output_filename = Glib::path_get_basename(*i);
287 		string output_filepath = Glib::build_filename (subdir_path, output_filename);
288 
289 		CPPUNIT_ASSERT (PBD::copy_file (input_filepath, output_filepath));
290 	}
291 
292 	copied_files.clear();
293 	PBD::get_files (copied_files, subdir_path);
294 
295 	CPPUNIT_ASSERT (copied_files.size() == i18n_files.size());
296 
297 	return output_dir;
298 }
299 
300 void
testClearDirectory()301 FilesystemTest::testClearDirectory ()
302 {
303 	string output_dir_path = create_test_directory ("ClearDirectory");
304 
305 	vector<string> files_in_output_dir;
306 
307 	PBD::get_paths (files_in_output_dir, output_dir_path, true, true);
308 
309 	size_t removed_file_size = 0;
310 	vector<string> removed_files;
311 
312 	CPPUNIT_ASSERT (PBD::clear_directory (output_dir_path, &removed_file_size, &removed_files) ==0);
313 
314 	cerr << "Removed " << removed_files.size() << " files of total size: "
315 	     << removed_file_size << endl;
316 
317 	CPPUNIT_ASSERT (removed_files.size () == files_in_output_dir.size ());
318 
319 	string subdir_path = Glib::build_filename (output_dir_path, "subdir");
320 
321 	// make sure the directory structure is still there
322 	CPPUNIT_ASSERT (Glib::file_test (subdir_path, Glib::FILE_TEST_IS_DIR));
323 }
324 
325 void
testRemoveDirectory()326 FilesystemTest::testRemoveDirectory ()
327 {
328 	string output_dir_path = create_test_directory ("RemoveDirectory");
329 
330 	vector<string> files_in_output_dir;
331 
332 	PBD::get_paths (files_in_output_dir, output_dir_path, false, true);
333 
334 	CPPUNIT_ASSERT (files_in_output_dir.size () != 0);
335 
336 	PBD::remove_directory (output_dir_path);
337 
338 	CPPUNIT_ASSERT (!Glib::file_test (output_dir_path, Glib::FILE_TEST_EXISTS));
339 }
340 
341 void
testCanonicalPathASCII()342 FilesystemTest::testCanonicalPathASCII ()
343 {
344 	string top_dir = test_output_directory ("testCanonicalPathASCII");
345 	PwdReset pwd_reset(top_dir);
346 
347 	string pwd = Glib::get_current_dir ();
348 
349 	CPPUNIT_ASSERT (!pwd.empty());
350 	CPPUNIT_ASSERT (pwd == top_dir);
351 
352 	string relative_path = ".";
353 	string canonical_path = PBD::canonical_path (relative_path);
354 
355 	CPPUNIT_ASSERT (pwd == canonical_path);
356 
357 	const std::string dir1 = Glib::build_filename (top_dir, "dir1");
358 	const std::string dir2 = Glib::build_filename (top_dir, "dir2");
359 
360 	CPPUNIT_ASSERT (g_mkdir (dir1.c_str(), 0755) == 0);
361 	CPPUNIT_ASSERT (g_mkdir (dir2.c_str(), 0755) == 0);
362 
363 	CPPUNIT_ASSERT (Glib::file_test (dir1, Glib::FILE_TEST_IS_DIR));
364 	CPPUNIT_ASSERT (Glib::file_test (dir2, Glib::FILE_TEST_IS_DIR));
365 
366 	relative_path = Glib::build_filename (".", "dir1", "..", "dir2");
367 	canonical_path = PBD::canonical_path (relative_path);
368 	string absolute_path = Glib::build_filename (top_dir, "dir2");
369 
370 	CPPUNIT_ASSERT (canonical_path == absolute_path);
371 }
372 
373 void
testCanonicalPathUTF8()374 FilesystemTest::testCanonicalPathUTF8 ()
375 {
376 	string top_dir = test_output_directory ("testCanonicalPathUTF8");
377 	PwdReset pwd_reset(top_dir);
378 
379 	string pwd = Glib::get_current_dir ();
380 
381 	CPPUNIT_ASSERT (!pwd.empty());
382 	CPPUNIT_ASSERT (pwd == top_dir);
383 
384 // We are using glib for file I/O on windows and the character encoding is
385 // guaranteed to be utf-8 as glib does the conversion for us. This is not the
386 // case on linux/OS X where the encoding is probably utf-8, but it is not
387 // ensured so we can't really enable this part of the test for now, but it is
388 // only really important to test on Windows.
389 #ifdef PLATFORM_WINDOWS
390 
391 	std::vector<std::string> utf8_strings;
392 
393 	get_utf8_test_strings (utf8_strings);
394 
395 	for (std::vector<std::string>::const_iterator i = utf8_strings.begin (); i != utf8_strings.end ();
396 	     ++i) {
397 
398 		const std::string absolute_path = Glib::build_filename (top_dir, *i);
399 		// make a directory in the current test/working directory
400 		CPPUNIT_ASSERT (g_mkdir (absolute_path.c_str (), 0755) == 0);
401 
402 		string relative_path = Glib::build_filename (".", *i);
403 
404 		string canonical_path = PBD::canonical_path (relative_path);
405 
406 		// Check that it resolved to the absolute path
407 		CPPUNIT_ASSERT (absolute_path == canonical_path);
408 	}
409 
410 #endif
411 }
412 
413 void
testTouchFile()414 FilesystemTest::testTouchFile ()
415 {
416 	const string filename = "touch.me";
417 
418 	const string test_dir = test_output_directory ("testTouchFile");
419 	const string touch_file_path = Glib::build_filename (test_dir, filename);
420 
421 	CPPUNIT_ASSERT (touch_file(touch_file_path));
422 
423 	CPPUNIT_ASSERT (Glib::file_test (touch_file_path, Glib::FILE_TEST_EXISTS));
424 }
425 
426 void
testStatFile()427 FilesystemTest::testStatFile ()
428 {
429 	const string filename1 = "touch.me";
430 	const string filename2 = "touch.me.2";
431 
432 	const string test_dir = test_output_directory ("testStatFile");
433 
434 	const string path1 = Glib::build_filename (test_dir, filename1);
435 	const string path2 = Glib::build_filename (test_dir, filename2);
436 
437 	CPPUNIT_ASSERT (touch_file(path1));
438 
439 	Glib::usleep (2000000);
440 
441 	CPPUNIT_ASSERT (touch_file(path2));
442 
443 	GStatBuf gsb1;
444 	GStatBuf gsb2;
445 
446 	CPPUNIT_ASSERT (g_stat (path1.c_str(), &gsb1) == 0);
447 	CPPUNIT_ASSERT (g_stat (path2.c_str(), &gsb2) == 0);
448 
449 	cerr << endl;
450 	cerr << "StatFile: " << path1 << " access time: " << gsb1.st_atime << endl;
451 	cerr << "StatFile: " << path1 << " modification time: " << gsb1.st_mtime << endl;
452 	cerr << "StatFile: " << path2 << " access time: " << gsb2.st_atime << endl;
453 	cerr << "StatFile: " << path2 << " modification time: " << gsb2.st_mtime << endl;
454 
455 	CPPUNIT_ASSERT (gsb1.st_atime == gsb1.st_mtime);
456 	CPPUNIT_ASSERT (gsb2.st_atime == gsb2.st_mtime);
457 
458 	// at least access time works on windows(or at least on ntfs)
459 	CPPUNIT_ASSERT (gsb1.st_atime < gsb2.st_atime);
460 	CPPUNIT_ASSERT (gsb1.st_mtime < gsb2.st_mtime);
461 
462 	struct utimbuf tbuf;
463 
464 	tbuf.actime = gsb1.st_atime;
465 	tbuf.modtime = gsb1.st_mtime;
466 
467 	// update the file access/modification times to be the same
468 	CPPUNIT_ASSERT (g_utime (path2.c_str(), &tbuf) == 0);
469 
470 	CPPUNIT_ASSERT (g_stat (path2.c_str(), &gsb2) == 0);
471 
472 	cerr << endl;
473 	cerr << "StatFile: " << path1 << " access time: " << gsb1.st_atime << endl;
474 	cerr << "StatFile: " << path1 << " modification time: " << gsb1.st_mtime << endl;
475 	cerr << "StatFile: " << path2 << " access time: " << gsb2.st_atime << endl;
476 	cerr << "StatFile: " << path2 << " modification time: " << gsb2.st_mtime << endl;
477 
478 	CPPUNIT_ASSERT (gsb1.st_atime == gsb2.st_atime);
479 	CPPUNIT_ASSERT (gsb1.st_mtime == gsb2.st_mtime);
480 }
481