update semantics of PBD::remove_directory()
[ardour.git] / libs / pbd / test / filesystem_test.cc
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
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
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
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
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
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
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
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
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
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
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
342 FilesystemTest::testCanonicalPath ()
343 {
344 #ifndef PLATFORM_WINDOWS
345         string top_dir = test_output_directory ("testCanonicalPath");
346         PwdReset pwd_reset(top_dir);
347
348         string pwd = Glib::get_current_dir ();
349
350         CPPUNIT_ASSERT (!pwd.empty());
351         CPPUNIT_ASSERT (pwd == top_dir);
352
353         CPPUNIT_ASSERT (g_mkdir ("gtk2_ardour", 0755) == 0);
354         CPPUNIT_ASSERT (g_mkdir_with_parents ("libs/pbd/test", 0755) == 0);
355
356         const char* relative_path = "./gtk2_ardour/../libs/pbd/test";
357         string canonical_path = PBD::canonical_path (relative_path);
358         // no expansion expected in this case
359         string expanded_path = PBD::path_expand (relative_path);
360         string expected_path = top_dir + string("/libs/pbd/test");
361
362         CPPUNIT_ASSERT (canonical_path == expected_path);
363         CPPUNIT_ASSERT (expanded_path == expected_path);
364 #endif
365 }
366
367 void
368 FilesystemTest::testTouchFile ()
369 {
370         const string filename = "touch.me";
371
372         const string test_dir = test_output_directory ("testTouchFile");
373         const string touch_file_path = Glib::build_filename (test_dir, filename);
374
375         CPPUNIT_ASSERT (touch_file(touch_file_path));
376
377         CPPUNIT_ASSERT (Glib::file_test (touch_file_path, Glib::FILE_TEST_EXISTS));
378 }
379
380 void
381 FilesystemTest::testStatFile ()
382 {
383         const string filename1 = "touch.me";
384         const string filename2 = "touch.me.2";
385
386         const string test_dir = test_output_directory ("testStatFile");
387
388         const string path1 = Glib::build_filename (test_dir, filename1);
389         const string path2 = Glib::build_filename (test_dir, filename2);
390
391         CPPUNIT_ASSERT (touch_file(path1));
392
393         Glib::usleep (2000000);
394
395         CPPUNIT_ASSERT (touch_file(path2));
396
397         GStatBuf gsb1;
398         GStatBuf gsb2;
399
400         CPPUNIT_ASSERT (g_stat (path1.c_str(), &gsb1) == 0);
401         CPPUNIT_ASSERT (g_stat (path2.c_str(), &gsb2) == 0);
402
403         cerr << endl;
404         cerr << "StatFile: " << path1 << " access time: " << gsb1.st_atime << endl;
405         cerr << "StatFile: " << path1 << " modification time: " << gsb1.st_mtime << endl;
406         cerr << "StatFile: " << path2 << " access time: " << gsb2.st_atime << endl;
407         cerr << "StatFile: " << path2 << " modification time: " << gsb2.st_mtime << endl;
408
409         CPPUNIT_ASSERT (gsb1.st_atime == gsb1.st_mtime);
410         CPPUNIT_ASSERT (gsb2.st_atime == gsb2.st_mtime);
411
412         // at least access time works on windows(or at least on ntfs)
413         CPPUNIT_ASSERT (gsb1.st_atime < gsb2.st_atime);
414         CPPUNIT_ASSERT (gsb1.st_mtime < gsb2.st_mtime);
415
416         struct utimbuf tbuf;
417
418         tbuf.actime = gsb1.st_atime;
419         tbuf.modtime = gsb1.st_mtime;
420
421         // update the file access/modification times to be the same
422         CPPUNIT_ASSERT (g_utime (path2.c_str(), &tbuf) == 0);
423
424         CPPUNIT_ASSERT (g_stat (path2.c_str(), &gsb2) == 0);
425
426         cerr << endl;
427         cerr << "StatFile: " << path1 << " access time: " << gsb1.st_atime << endl;
428         cerr << "StatFile: " << path1 << " modification time: " << gsb1.st_mtime << endl;
429         cerr << "StatFile: " << path2 << " access time: " << gsb2.st_atime << endl;
430         cerr << "StatFile: " << path2 << " modification time: " << gsb2.st_mtime << endl;
431
432         CPPUNIT_ASSERT (gsb1.st_atime == gsb2.st_atime);
433         CPPUNIT_ASSERT (gsb1.st_mtime == gsb2.st_mtime);
434 }