diff --git a/aegisub/build/tests/tests.vcxproj b/aegisub/build/tests/tests.vcxproj
index ff4b5fdff..e4c401b99 100644
--- a/aegisub/build/tests/tests.vcxproj
+++ b/aegisub/build/tests/tests.vcxproj
@@ -42,6 +42,7 @@
+
diff --git a/aegisub/build/tests/tests.vcxproj.filters b/aegisub/build/tests/tests.vcxproj.filters
index ad542727b..559651e9d 100644
--- a/aegisub/build/tests/tests.vcxproj.filters
+++ b/aegisub/build/tests/tests.vcxproj.filters
@@ -29,6 +29,9 @@
Tests
+
+ Tests
+
Tests
diff --git a/aegisub/tests/Makefile b/aegisub/tests/Makefile
index a62bb01d9..0f3ae6438 100644
--- a/aegisub/tests/Makefile
+++ b/aegisub/tests/Makefile
@@ -21,6 +21,7 @@ SRC = \
tests/cajun.cpp \
tests/color.cpp \
tests/dialogue_lexer.cpp \
+ tests/fs.cpp \
tests/hotkey.cpp \
tests/iconv.cpp \
tests/ifind.cpp \
diff --git a/aegisub/tests/setup.bat b/aegisub/tests/setup.bat
index 6e242c7fe..9886040e5 100644
--- a/aegisub/tests/setup.bat
+++ b/aegisub/tests/setup.bat
@@ -24,10 +24,17 @@ echo {"Valid" : ["Entry One", "Entry Two"]} > data/mru_ok.json
echo {"Invalid" : [1, 3]} > data/mru_invalid.json
-echo > data/rename_me
+echo '1234567890' > data\ten_bytes
+echo '' > data\touch_mod_time
-echo > data/rename_me_overwrite
-echo > data/rename_me_overwrite_renamed
+mkdir data/options
+cp options/* data/options
+
+mkdir data\dir_iterator
+echo '' > data\dir_iterator\1.a
+echo '' > data\dir_iterator\2.a
+echo '' > data\dir_iterator\1.b
+echo '' > data\dir_iterator\2.b
mkdir data\options
xcopy "%~dp0\options" data\options
diff --git a/aegisub/tests/setup.sh b/aegisub/tests/setup.sh
index 99b14ff7c..127ddb827 100755
--- a/aegisub/tests/setup.sh
+++ b/aegisub/tests/setup.sh
@@ -24,14 +24,18 @@ echo '{"Valid" : ["Entry One", "Entry Two"]}' > data/mru_ok.json
echo '{"Invalid" : [1, 3]}' > data/mru_invalid.json
-touch data/rename_me
-
-touch data/rename_me_overwrite
-touch data/rename_me_overwrite_renamed
+printf %s '1234567890' > data/ten_bytes
+touch data/touch_mod_time
mkdir data/options
cp options/* data/options
+mkdir data/dir_iterator
+touch data/dir_iterator/1.a
+touch data/dir_iterator/2.a
+touch data/dir_iterator/1.b
+touch data/dir_iterator/2.b
+
mkdir data/vfr
mkdir data/vfr/in
mkdir data/vfr/out
diff --git a/aegisub/tests/support/main.cpp b/aegisub/tests/support/main.cpp
index 3f0808246..8b86c29f8 100644
--- a/aegisub/tests/support/main.cpp
+++ b/aegisub/tests/support/main.cpp
@@ -19,6 +19,8 @@
#include
#include
+#include
+#include
int main(int argc, char **argv) {
agi::dispatch::Init([](agi::dispatch::Thunk f) { });
@@ -29,6 +31,8 @@ int main(int argc, char **argv) {
agi::log::log->Subscribe(new agi::log::JsonEmitter("./"));
::testing::InitGoogleTest(&argc, argv);
+ srand(time(nullptr));
+
retval = RUN_ALL_TESTS();
delete agi::log::log;
diff --git a/aegisub/tests/support/util.cpp b/aegisub/tests/support/util.cpp
index 6061081d7..532ce9be2 100644
--- a/aegisub/tests/support/util.cpp
+++ b/aegisub/tests/support/util.cpp
@@ -16,6 +16,7 @@
#include "util.h"
+#include
#include
#include
@@ -28,4 +29,18 @@ bool compare(const std::string &file1, const std::string &file2) {
return ss1.str() == ss2.str();
}
+int write_rand(const char *path) {
+ int value = rand();
+ std::ofstream of(path);
+ of << value;
+ return value;
+}
+
+int read_written_rand(const char *path) {
+ int value;
+ std::ifstream i(path);
+ i >> value;
+ return value;
+}
+
}
diff --git a/aegisub/tests/support/util.h b/aegisub/tests/support/util.h
index 738b12ecb..d95491eec 100644
--- a/aegisub/tests/support/util.h
+++ b/aegisub/tests/support/util.h
@@ -35,4 +35,7 @@ static std::vector make_vector(int len, ...) {
return vec;
}
+int write_rand(const char *path);
+int read_written_rand(const char *path);
+
}
diff --git a/aegisub/tests/tests/fs.cpp b/aegisub/tests/tests/fs.cpp
new file mode 100644
index 000000000..84073f9b6
--- /dev/null
+++ b/aegisub/tests/tests/fs.cpp
@@ -0,0 +1,197 @@
+// Copyright (c) 2013, Thomas Goyne
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "main.h"
+#include "util.h"
+
+#include
+
+using namespace agi::fs;
+
+TEST(lagi_fs, exists) {
+ EXPECT_TRUE(Exists("data/file"));
+ EXPECT_TRUE(Exists("data/dir"));
+ EXPECT_FALSE(Exists("data/nonexistent"));
+}
+
+TEST(lagi_fs, exists_does_not_throw) {
+ EXPECT_NO_THROW(Exists("data/file"));
+ EXPECT_NO_THROW(Exists("data/dir"));
+ EXPECT_NO_THROW(Exists("data/nonexistent"));
+ EXPECT_NO_THROW(Exists("data/dir_access_denied"));
+ EXPECT_NO_THROW(Exists("data/file_access_denied"));
+ EXPECT_NO_THROW(Exists("schema://host/file"));
+}
+
+TEST(lagi_fs, file_exists) {
+ EXPECT_TRUE(FileExists("data/file"));
+ EXPECT_FALSE(FileExists("data/dir"));
+}
+
+TEST(lagi_fs, dir_exists) {
+ EXPECT_FALSE(DirectoryExists("data/file"));
+ EXPECT_TRUE(DirectoryExists("data/dir"));
+}
+
+#ifdef _WIN32
+#else
+TEST(lagi_fs, short_name_is_a_no_op) {
+ EXPECT_STREQ("a b c d", ShortName("a b c d").c_str());
+}
+#endif
+
+TEST(lagi_fs, free_space_returns_a_value) {
+ uintmax_t space = 0;
+ ASSERT_NO_THROW(space = FreeSpace("."));
+ EXPECT_LT(0, space);
+}
+
+TEST(lagi_fs, file_size) {
+ EXPECT_EQ(0u, Size("data/file"));
+ EXPECT_EQ(10u, Size("data/ten_bytes"));
+ EXPECT_THROW(Size("data/dir"), NotAFile);
+}
+
+TEST(lagi_fs, touch_creates_file) {
+ Remove("data/touch_tmp");
+ ASSERT_FALSE(Exists("data/touch_tmp"));
+ Touch("data/touch_tmp");
+ EXPECT_TRUE(Exists("data/touch_tmp"));
+ Remove("data/touch_tmp");
+}
+
+TEST(lagi_fs, touch_updates_modified_time) {
+ time_t mod_time = ModifiedTime("data/touch_mod_time");
+ Touch("data/touch_mod_time");
+ EXPECT_LT(mod_time, ModifiedTime("data/touch_mod_time"));
+}
+
+TEST(lagi_fs, touch_does_not_modify_contents) {
+ int expected_value = util::write_rand("data/tmp");
+
+ Touch("data/tmp");
+
+ EXPECT_EQ(expected_value, util::read_written_rand("data/tmp"));
+}
+
+TEST(lagi_fs, rename) {
+ ASSERT_NO_THROW(Touch("data/rename_in"));
+ ASSERT_NO_THROW(Remove("data/rename_out"));
+ ASSERT_NO_THROW(Rename("data/rename_in", "data/rename_out"));
+ EXPECT_FALSE(FileExists("data/rename_in"));
+ EXPECT_TRUE(FileExists("data/rename_out"));
+}
+
+TEST(lagi_fs, rename_overwrites) {
+ int expected_value = util::write_rand("data/rename_in");
+ ASSERT_NO_THROW(Remove("data/rename_out"));
+ ASSERT_NO_THROW(Touch("data/rename_out"));
+
+ ASSERT_NO_THROW(Rename("data/rename_in", "data/rename_out"));
+
+ EXPECT_FALSE(FileExists("data/rename_in"));
+ EXPECT_TRUE(FileExists("data/rename_out"));
+ EXPECT_EQ(expected_value, util::read_written_rand("data/rename_out"));
+}
+
+TEST(lagi_fs, copy) {
+ int expected_value = util::write_rand("data/copy_in");
+ ASSERT_NO_THROW(Remove("data/copy_out"));
+
+ ASSERT_NO_THROW(Copy("data/copy_in", "data/copy_out"));
+
+ EXPECT_TRUE(FileExists("data/copy_in"));
+ EXPECT_TRUE(FileExists("data/copy_out"));
+ EXPECT_EQ(expected_value, util::read_written_rand("data/copy_out"));
+}
+
+TEST(lagi_fs, copy_overwrites) {
+ int expected_value = util::write_rand("data/copy_in");
+ ASSERT_NO_THROW(Remove("data/copy_out"));
+ ASSERT_NO_THROW(Touch("data/copy_out"));
+
+ ASSERT_NO_THROW(Copy("data/copy_in", "data/copy_out"));
+
+ EXPECT_TRUE(FileExists("data/copy_in"));
+ EXPECT_TRUE(FileExists("data/copy_out"));
+ EXPECT_EQ(expected_value, util::read_written_rand("data/copy_out"));
+}
+
+TEST(lagi_fs, copy_creates_path) {
+}
+
+TEST(lagi_fs, has_extension) {
+ EXPECT_TRUE(HasExtension("foo.txt", "txt"));
+ EXPECT_TRUE(HasExtension("foo.TXT", "txt"));
+ EXPECT_TRUE(HasExtension("foo.tar.gz", "gz"));
+ EXPECT_TRUE(HasExtension("foo.tar.gz", "tar.gz"));
+ EXPECT_TRUE(HasExtension("foo.\xC3\x9F", "\xC3\x9F")); // sharp s
+
+ EXPECT_FALSE(HasExtension("foo.tx", "txt"));
+ EXPECT_FALSE(HasExtension("footxt", "txt"));
+ EXPECT_FALSE(HasExtension("foo.txt/", "txt"));
+ EXPECT_FALSE(HasExtension("foo.tar.gz", "tar"));
+}
+
+TEST(lagi_fs, dir_iterator_bad_directory) {
+ std::vector files;
+ ASSERT_NO_THROW(DirectoryIterator("data/nonexistent", "*.*").GetAll(files));
+ EXPECT_TRUE(files.empty());
+}
+
+TEST(lagi_fs, dir_iterator_no_filter) {
+ std::vector files;
+ ASSERT_NO_THROW(DirectoryIterator("data/dir_iterator", "").GetAll(files));
+ ASSERT_EQ(4u, files.size());
+ sort(begin(files), end(files)); // order is not guaranteed
+ EXPECT_STREQ("1.a", files[0].c_str());
+ EXPECT_STREQ("1.b", files[1].c_str());
+ EXPECT_STREQ("2.a", files[2].c_str());
+ EXPECT_STREQ("2.b", files[3].c_str());
+}
+
+TEST(lagi_fs, dir_iterator_ext_filter) {
+ std::vector files;
+ ASSERT_NO_THROW(DirectoryIterator("data/dir_iterator", "*.a").GetAll(files));
+ ASSERT_EQ(2u, files.size());
+ sort(begin(files), end(files)); // order is not guaranteed
+ EXPECT_STREQ("1.a", files[0].c_str());
+ EXPECT_STREQ("2.a", files[1].c_str());
+}
+
+TEST(lagi_fs, dir_iterator_ext_filter_skipping_first_works) {
+ std::vector files;
+ ASSERT_NO_THROW(DirectoryIterator("data/dir_iterator", "*.b").GetAll(files));
+ ASSERT_EQ(2u, files.size());
+ sort(begin(files), end(files)); // order is not guaranteed
+ EXPECT_STREQ("1.b", files[0].c_str());
+ EXPECT_STREQ("2.b", files[1].c_str());
+}
+
+TEST(lagi_fs, dir_iterator_fn_filter) {
+ std::vector files;
+ ASSERT_NO_THROW(DirectoryIterator("data/dir_iterator", "1.*").GetAll(files));
+ ASSERT_EQ(2u, files.size());
+ sort(begin(files), end(files)); // order is not guaranteed
+ EXPECT_STREQ("1.a", files[0].c_str());
+ EXPECT_STREQ("1.b", files[1].c_str());
+}
+
+TEST(lagi_fs, dir_iterator_all_filtered_out) {
+ std::vector files;
+ ASSERT_NO_THROW(DirectoryIterator("data/dir_iterator", "*.c").GetAll(files));
+ EXPECT_TRUE(files.empty());
+}
diff --git a/aegisub/tests/tests/ifind.cpp b/aegisub/tests/tests/ifind.cpp
index baad59913..8c0b75656 100644
--- a/aegisub/tests/tests/ifind.cpp
+++ b/aegisub/tests/tests/ifind.cpp
@@ -14,6 +14,8 @@
//
// Aegisub Project http://www.aegisub.org/
+#include "../../libaegisub/config.h"
+
#include
#include "main.h"
diff --git a/aegisub/tests/tests/path.cpp b/aegisub/tests/tests/path.cpp
index b20a9e66d..ce93c76e9 100644
--- a/aegisub/tests/tests/path.cpp
+++ b/aegisub/tests/tests/path.cpp
@@ -130,3 +130,43 @@ TEST(lagi_path, platform_paths_have_values_and_exist) {
TEST_PLATFORM_PATH_TOKEN("?local");
TEST_PLATFORM_PATH_TOKEN("?temp");
}
+
+TEST(lagi_path, making_empty_absolute_gives_empty) {
+ Path p;
+ ASSERT_NO_THROW(p.MakeAbsolute("", "?data"));
+ EXPECT_TRUE(p.MakeAbsolute("", "?data").empty());
+}
+
+TEST(lagi_path, making_empty_relative_gives_empty) {
+ Path p;
+ ASSERT_NO_THROW(p.MakeRelative("", "?data"));
+ EXPECT_TRUE(p.MakeRelative("", "?data").empty());
+}
+
+#ifdef _WIN32
+TEST(lagi_path, make_absolute_on_network_path) {
+ Path p;
+ ASSERT_NO_THROW(p.MakeAbsolute("//foo/bar", "?data"));
+ EXPECT_STREQ("\\\\foo\\bar", p.MakeAbsolute("//foo/bar", "?data").string().c_str());
+}
+
+TEST(lagi_path, make_relative_on_network_path) {
+ Path p;
+ ASSERT_NO_THROW(p.MakeRelative("\\\\foo\\bar", "?data"));
+ EXPECT_STREQ("\\\\foo\\bar", p.MakeRelative("\\\\foo\\bar", "?data").string().c_str());
+}
+#endif
+
+#define EXPECT_UNCHANGED(url, func) EXPECT_STREQ(url, p.func(url, "?data").string().c_str())
+
+TEST(lagi_path, make_absolute_on_dummy_url) {
+ Path p;
+ EXPECT_UNCHANGED("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000", MakeAbsolute);
+ EXPECT_UNCHANGED("?dummy:23.976000:40000:1280:720:47:163:254:", MakeAbsolute);
+}
+
+TEST(lagi_path, make_relative_on_dummy_url) {
+ Path p;
+ EXPECT_UNCHANGED("dummy-audio:silence?sr=44100&bd=16&ch=1&ln=396900000", MakeRelative);
+ EXPECT_UNCHANGED("?dummy:23.976000:40000:1280:720:47:163:254:", MakeRelative);
+}