blob: 8df84ff91e65fff7879623845d3be514b14782f8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "arrow/filesystem/filesystem.h"
#include "arrow/filesystem/mockfs.h"
#include "arrow/filesystem/path_util.h"
#include "arrow/filesystem/test_util.h"
#include "arrow/io/interfaces.h"
#include "arrow/testing/gtest_util.h"
namespace arrow {
namespace fs {
namespace internal {
void AssertPartsEqual(const std::vector<std::string>& parts,
const std::vector<std::string>& expected) {
ASSERT_EQ(parts, expected);
}
void AssertPairEqual(const std::pair<std::string, std::string>& pair,
const std::pair<std::string, std::string>& expected) {
ASSERT_EQ(pair, expected);
}
TEST(FileInfo, BaseName) {
auto info = FileInfo();
ASSERT_EQ(info.base_name(), "");
info.set_path("foo");
ASSERT_EQ(info.base_name(), "foo");
info.set_path("foo/bar/baz.qux");
ASSERT_EQ(info.base_name(), "baz.qux");
}
TEST(PathUtil, SplitAbstractPath) {
std::vector<std::string> parts;
parts = SplitAbstractPath("");
AssertPartsEqual(parts, {});
parts = SplitAbstractPath("abc");
AssertPartsEqual(parts, {"abc"});
parts = SplitAbstractPath("abc/def.ghi");
AssertPartsEqual(parts, {"abc", "def.ghi"});
parts = SplitAbstractPath("abc/def/ghi");
AssertPartsEqual(parts, {"abc", "def", "ghi"});
parts = SplitAbstractPath("abc\\def\\ghi");
AssertPartsEqual(parts, {"abc\\def\\ghi"});
// Trailing slash
parts = SplitAbstractPath("abc/");
AssertPartsEqual(parts, {"abc"});
parts = SplitAbstractPath("abc/def.ghi/");
AssertPartsEqual(parts, {"abc", "def.ghi"});
parts = SplitAbstractPath("abc/def.ghi\\");
AssertPartsEqual(parts, {"abc", "def.ghi\\"});
// Leading slash
parts = SplitAbstractPath("/");
AssertPartsEqual(parts, {});
parts = SplitAbstractPath("/abc");
AssertPartsEqual(parts, {"abc"});
parts = SplitAbstractPath("/abc/def.ghi");
AssertPartsEqual(parts, {"abc", "def.ghi"});
parts = SplitAbstractPath("/abc/def.ghi/");
AssertPartsEqual(parts, {"abc", "def.ghi"});
}
TEST(PathUtil, GetAbstractPathExtension) {
ASSERT_EQ(GetAbstractPathExtension("abc.txt"), "txt");
ASSERT_EQ(GetAbstractPathExtension("dir/abc.txt"), "txt");
ASSERT_EQ(GetAbstractPathExtension("/dir/abc.txt"), "txt");
ASSERT_EQ(GetAbstractPathExtension("dir/abc.txt.gz"), "gz");
ASSERT_EQ(GetAbstractPathExtension("/run.d/abc.txt"), "txt");
ASSERT_EQ(GetAbstractPathExtension("abc"), "");
ASSERT_EQ(GetAbstractPathExtension("/dir/abc"), "");
ASSERT_EQ(GetAbstractPathExtension("/run.d/abc"), "");
}
TEST(PathUtil, GetAbstractPathParent) {
std::pair<std::string, std::string> pair;
pair = GetAbstractPathParent("");
AssertPairEqual(pair, {"", ""});
pair = GetAbstractPathParent("abc");
AssertPairEqual(pair, {"", "abc"});
pair = GetAbstractPathParent("abc/def/ghi");
AssertPairEqual(pair, {"abc/def", "ghi"});
pair = GetAbstractPathParent("abc/def\\ghi");
AssertPairEqual(pair, {"abc", "def\\ghi"});
}
TEST(PathUtil, ValidateAbstractPathParts) {
ASSERT_OK(ValidateAbstractPathParts({}));
ASSERT_OK(ValidateAbstractPathParts({"abc"}));
ASSERT_OK(ValidateAbstractPathParts({"abc", "def"}));
ASSERT_OK(ValidateAbstractPathParts({"abc", "def.ghi"}));
ASSERT_OK(ValidateAbstractPathParts({"abc", "def\\ghi"}));
// Empty path component
ASSERT_RAISES(Invalid, ValidateAbstractPathParts({""}));
ASSERT_RAISES(Invalid, ValidateAbstractPathParts({"abc", "", "def"}));
// Separator in component
ASSERT_RAISES(Invalid, ValidateAbstractPathParts({"/"}));
ASSERT_RAISES(Invalid, ValidateAbstractPathParts({"abc/def"}));
}
TEST(PathUtil, ConcatAbstractPath) {
ASSERT_EQ("abc", ConcatAbstractPath("", "abc"));
ASSERT_EQ("abc/def", ConcatAbstractPath("abc", "def"));
ASSERT_EQ("abc/def/ghi", ConcatAbstractPath("abc/def", "ghi"));
ASSERT_EQ("abc/def", ConcatAbstractPath("abc/", "def"));
ASSERT_EQ("abc/def/ghi", ConcatAbstractPath("abc/def/", "ghi"));
ASSERT_EQ("/abc", ConcatAbstractPath("/", "abc"));
ASSERT_EQ("/abc/def", ConcatAbstractPath("/abc", "def"));
ASSERT_EQ("/abc/def", ConcatAbstractPath("/abc/", "def"));
}
TEST(PathUtil, JoinAbstractPath) {
std::vector<std::string> parts = {"abc", "def", "ghi", "", "jkl"};
ASSERT_EQ("abc/def/ghi/jkl", JoinAbstractPath(parts.begin(), parts.end()));
ASSERT_EQ("def/ghi", JoinAbstractPath(parts.begin() + 1, parts.begin() + 3));
ASSERT_EQ("", JoinAbstractPath(parts.begin(), parts.begin()));
}
TEST(PathUtil, EnsureTrailingSlash) {
ASSERT_EQ("", EnsureTrailingSlash(""));
ASSERT_EQ("/", EnsureTrailingSlash("/"));
ASSERT_EQ("abc/", EnsureTrailingSlash("abc"));
ASSERT_EQ("abc/", EnsureTrailingSlash("abc/"));
ASSERT_EQ("/abc/", EnsureTrailingSlash("/abc"));
ASSERT_EQ("/abc/", EnsureTrailingSlash("/abc/"));
}
TEST(PathUtil, RemoveTrailingSlash) {
ASSERT_EQ("", std::string(RemoveTrailingSlash("")));
ASSERT_EQ("", std::string(RemoveTrailingSlash("/")));
ASSERT_EQ("", std::string(RemoveTrailingSlash("//")));
ASSERT_EQ("abc/def", std::string(RemoveTrailingSlash("abc/def")));
ASSERT_EQ("abc/def", std::string(RemoveTrailingSlash("abc/def/")));
ASSERT_EQ("abc/def", std::string(RemoveTrailingSlash("abc/def//")));
ASSERT_EQ("/abc/def", std::string(RemoveTrailingSlash("/abc/def")));
ASSERT_EQ("/abc/def", std::string(RemoveTrailingSlash("/abc/def/")));
ASSERT_EQ("/abc/def", std::string(RemoveTrailingSlash("/abc/def//")));
}
TEST(PathUtil, EnsureLeadingSlash) {
ASSERT_EQ("/", EnsureLeadingSlash(""));
ASSERT_EQ("/", EnsureLeadingSlash("/"));
ASSERT_EQ("/abc", EnsureLeadingSlash("abc"));
ASSERT_EQ("/abc/", EnsureLeadingSlash("abc/"));
ASSERT_EQ("/abc", EnsureLeadingSlash("/abc"));
ASSERT_EQ("/abc/", EnsureLeadingSlash("/abc/"));
}
TEST(PathUtil, RemoveLeadingSlash) {
ASSERT_EQ("", std::string(RemoveLeadingSlash("")));
ASSERT_EQ("", std::string(RemoveLeadingSlash("/")));
ASSERT_EQ("", std::string(RemoveLeadingSlash("//")));
ASSERT_EQ("abc/def", std::string(RemoveLeadingSlash("abc/def")));
ASSERT_EQ("abc/def", std::string(RemoveLeadingSlash("/abc/def")));
ASSERT_EQ("abc/def", std::string(RemoveLeadingSlash("//abc/def")));
ASSERT_EQ("abc/def/", std::string(RemoveLeadingSlash("abc/def/")));
ASSERT_EQ("abc/def/", std::string(RemoveLeadingSlash("/abc/def/")));
ASSERT_EQ("abc/def/", std::string(RemoveLeadingSlash("//abc/def/")));
}
TEST(PathUtil, IsAncestorOf) {
ASSERT_TRUE(IsAncestorOf("", ""));
ASSERT_TRUE(IsAncestorOf("", "/hello"));
ASSERT_TRUE(IsAncestorOf("/hello", "/hello"));
ASSERT_FALSE(IsAncestorOf("/hello", "/world"));
ASSERT_TRUE(IsAncestorOf("/hello", "/hello/world"));
ASSERT_TRUE(IsAncestorOf("/hello", "/hello/world/how/are/you"));
ASSERT_FALSE(IsAncestorOf("/hello/w", "/hello/world"));
}
TEST(PathUtil, MakeAbstractPathRelative) {
ASSERT_OK_AND_EQ("", MakeAbstractPathRelative("/", "/"));
ASSERT_OK_AND_EQ("foo/bar", MakeAbstractPathRelative("/", "/foo/bar"));
ASSERT_OK_AND_EQ("", MakeAbstractPathRelative("/foo", "/foo"));
ASSERT_OK_AND_EQ("", MakeAbstractPathRelative("/foo/", "/foo"));
ASSERT_OK_AND_EQ("", MakeAbstractPathRelative("/foo", "/foo/"));
ASSERT_OK_AND_EQ("", MakeAbstractPathRelative("/foo/", "/foo/"));
ASSERT_OK_AND_EQ("bar", MakeAbstractPathRelative("/foo", "/foo/bar"));
ASSERT_OK_AND_EQ("bar", MakeAbstractPathRelative("/foo/", "/foo/bar"));
ASSERT_OK_AND_EQ("bar/", MakeAbstractPathRelative("/foo/", "/foo/bar/"));
// Not relative to base
ASSERT_RAISES(Invalid, MakeAbstractPathRelative("/xxx", "/foo/bar"));
ASSERT_RAISES(Invalid, MakeAbstractPathRelative("/xxx", "/xxxx"));
// Base is not absolute
ASSERT_RAISES(Invalid, MakeAbstractPathRelative("foo/bar", "foo/bar/baz"));
ASSERT_RAISES(Invalid, MakeAbstractPathRelative("", "foo/bar/baz"));
}
TEST(PathUtil, AncestorsFromBasePath) {
using V = std::vector<std::string>;
// Not relative to base
ASSERT_EQ(AncestorsFromBasePath("xxx", "foo/bar"), V{});
ASSERT_EQ(AncestorsFromBasePath("xxx", "xxxx"), V{});
ASSERT_EQ(AncestorsFromBasePath("foo", "foo/bar"), V{});
ASSERT_EQ(AncestorsFromBasePath("foo", "foo/bar/baz"), V({"foo/bar"}));
ASSERT_EQ(AncestorsFromBasePath("foo", "foo/bar/baz/quux"),
V({"foo/bar", "foo/bar/baz"}));
}
TEST(PathUtil, MinimalCreateDirSet) {
using V = std::vector<std::string>;
ASSERT_EQ(MinimalCreateDirSet({}), V{});
ASSERT_EQ(MinimalCreateDirSet({"foo"}), V{"foo"});
ASSERT_EQ(MinimalCreateDirSet({"foo", "foo/bar"}), V{"foo/bar"});
ASSERT_EQ(MinimalCreateDirSet({"foo", "foo/bar/baz"}), V{"foo/bar/baz"});
ASSERT_EQ(MinimalCreateDirSet({"foo", "foo/bar", "foo/bar"}), V{"foo/bar"});
ASSERT_EQ(MinimalCreateDirSet({"foo", "foo/bar", "foo", "foo/baz", "foo/baz/quux"}),
V({"foo/bar", "foo/baz/quux"}));
ASSERT_EQ(MinimalCreateDirSet({""}), V{});
ASSERT_EQ(MinimalCreateDirSet({"", "/foo"}), V{"/foo"});
}
TEST(PathUtil, ToBackslashes) {
ASSERT_EQ(ToBackslashes("foo/bar"), "foo\\bar");
ASSERT_EQ(ToBackslashes("//foo/bar/"), "\\\\foo\\bar\\");
ASSERT_EQ(ToBackslashes("foo\\bar"), "foo\\bar");
}
TEST(PathUtil, ToSlashes) {
#ifdef _WIN32
ASSERT_EQ(ToSlashes("foo\\bar"), "foo/bar");
ASSERT_EQ(ToSlashes("\\\\foo\\bar\\"), "//foo/bar/");
#else
ASSERT_EQ(ToSlashes("foo\\bar"), "foo\\bar");
ASSERT_EQ(ToSlashes("\\\\foo\\bar\\"), "\\\\foo\\bar\\");
#endif
}
////////////////////////////////////////////////////////////////////////////
// Generic MockFileSystem tests
template <typename MockFileSystemType>
class TestMockFSGeneric : public ::testing::Test, public GenericFileSystemTest {
public:
void SetUp() override {
time_ = TimePoint(TimePoint::duration(42));
fs_ = std::make_shared<MockFileSystemType>(time_);
}
protected:
std::shared_ptr<FileSystem> GetEmptyFileSystem() override { return fs_; }
TimePoint time_;
std::shared_ptr<FileSystem> fs_;
};
using MockFileSystemTypes = ::testing::Types<MockFileSystem, MockAsyncFileSystem>;
TYPED_TEST_SUITE(TestMockFSGeneric, MockFileSystemTypes);
GENERIC_FS_TYPED_TEST_FUNCTIONS(TestMockFSGeneric);
////////////////////////////////////////////////////////////////////////////
// Concrete MockFileSystem tests
class TestMockFS : public ::testing::Test {
public:
void SetUp() override {
time_ = TimePoint(TimePoint::duration(42));
fs_ = std::make_shared<MockFileSystem>(time_);
}
Status WriteString(io::OutputStream* stream, const std::string& s) {
return stream->Write(s.data(), static_cast<int64_t>(s.length()));
}
std::vector<MockDirInfo> AllDirs() {
return arrow::internal::checked_pointer_cast<MockFileSystem>(fs_)->AllDirs();
}
std::vector<MockFileInfo> AllFiles() {
return arrow::internal::checked_pointer_cast<MockFileSystem>(fs_)->AllFiles();
}
void CheckDirs(const std::vector<MockDirInfo>& expected) {
ASSERT_EQ(AllDirs(), expected);
}
void CheckDirPaths(const std::vector<std::string>& expected) {
std::vector<MockDirInfo> infos;
infos.reserve(expected.size());
for (const auto& s : expected) {
infos.push_back({s, time_});
}
ASSERT_EQ(AllDirs(), infos);
}
void CheckFiles(const std::vector<MockFileInfo>& expected) {
ASSERT_EQ(AllFiles(), expected);
}
void CreateFile(const std::string& path, const std::string& data) {
::arrow::fs::CreateFile(fs_.get(), path, data);
}
protected:
TimePoint time_;
std::shared_ptr<FileSystem> fs_;
};
TEST_F(TestMockFS, Empty) {
CheckDirs({});
CheckFiles({});
}
TEST_F(TestMockFS, CreateDir) {
ASSERT_OK(fs_->CreateDir("AB"));
ASSERT_OK(fs_->CreateDir("AB/CD/EF")); // Recursive
// Non-recursive, parent doesn't exist
ASSERT_RAISES(IOError, fs_->CreateDir("AB/GH/IJ", false /* recursive */));
ASSERT_OK(fs_->CreateDir("AB/GH", false /* recursive */));
ASSERT_OK(fs_->CreateDir("AB/GH/IJ", false /* recursive */));
// Idempotency
ASSERT_OK(fs_->CreateDir("AB/GH/IJ", false /* recursive */));
ASSERT_OK(fs_->CreateDir("XY"));
CheckDirs({{"AB", time_},
{"AB/CD", time_},
{"AB/CD/EF", time_},
{"AB/GH", time_},
{"AB/GH/IJ", time_},
{"XY", time_}});
CheckFiles({});
}
TEST_F(TestMockFS, DeleteDir) {
ASSERT_OK(fs_->CreateDir("AB/CD/EF"));
ASSERT_OK(fs_->CreateDir("AB/GH/IJ"));
ASSERT_OK(fs_->DeleteDir("AB/CD"));
ASSERT_OK(fs_->DeleteDir("AB/GH/IJ"));
CheckDirs({{"AB", time_}, {"AB/GH", time_}});
CheckFiles({});
ASSERT_RAISES(IOError, fs_->DeleteDir("AB/CD"));
ASSERT_OK(fs_->DeleteDir("AB"));
CheckDirs({});
CheckFiles({});
}
TEST_F(TestMockFS, DeleteFile) {
ASSERT_OK(fs_->CreateDir("AB"));
CreateFile("AB/cd", "data");
CheckDirs({{"AB", time_}});
CheckFiles({{"AB/cd", time_, "data"}});
ASSERT_OK(fs_->DeleteFile("AB/cd"));
CheckDirs({{"AB", time_}});
CheckFiles({});
CreateFile("ab", "data");
CheckDirs({{"AB", time_}});
CheckFiles({{"ab", time_, "data"}});
ASSERT_OK(fs_->DeleteFile("ab"));
CheckDirs({{"AB", time_}});
CheckFiles({});
}
TEST_F(TestMockFS, GetFileInfo) {
ASSERT_OK(fs_->CreateDir("AB/CD"));
CreateFile("AB/CD/ef", "some data");
FileInfo info;
ASSERT_OK_AND_ASSIGN(info, fs_->GetFileInfo("AB"));
AssertFileInfo(info, "AB", FileType::Directory, time_);
ASSERT_EQ(info.base_name(), "AB");
ASSERT_OK_AND_ASSIGN(info, fs_->GetFileInfo("AB/CD/ef"));
AssertFileInfo(info, "AB/CD/ef", FileType::File, time_, 9);
ASSERT_EQ(info.base_name(), "ef");
// Invalid path
ASSERT_RAISES(Invalid, fs_->GetFileInfo("//foo//bar//baz//"));
}
TEST_F(TestMockFS, GetFileInfoVector) {
ASSERT_OK(fs_->CreateDir("AB/CD"));
CreateFile("AB/CD/ef", "some data");
std::vector<FileInfo> infos;
ASSERT_OK_AND_ASSIGN(
infos, fs_->GetFileInfo({"AB", "AB/CD", "AB/zz", "zz", "XX/zz", "AB/CD/ef"}));
ASSERT_EQ(infos.size(), 6);
AssertFileInfo(infos[0], "AB", FileType::Directory, time_);
AssertFileInfo(infos[1], "AB/CD", FileType::Directory, time_);
AssertFileInfo(infos[2], "AB/zz", FileType::NotFound);
AssertFileInfo(infos[3], "zz", FileType::NotFound);
AssertFileInfo(infos[4], "XX/zz", FileType::NotFound);
AssertFileInfo(infos[5], "AB/CD/ef", FileType::File, time_, 9);
// Invalid path
ASSERT_RAISES(Invalid, fs_->GetFileInfo({"AB", "AB/CD", "//foo//bar//baz//"}));
}
TEST_F(TestMockFS, GetFileInfoSelector) {
ASSERT_OK(fs_->CreateDir("AB/CD"));
CreateFile("ab", "data");
FileSelector s;
s.base_dir = "";
std::vector<FileInfo> infos;
ASSERT_OK_AND_ASSIGN(infos, fs_->GetFileInfo(s));
ASSERT_EQ(infos.size(), 2);
AssertFileInfo(infos[0], "AB", FileType::Directory, time_);
AssertFileInfo(infos[1], "ab", FileType::File, time_, 4);
s.recursive = true;
ASSERT_OK_AND_ASSIGN(infos, fs_->GetFileInfo(s));
ASSERT_EQ(infos.size(), 3);
AssertFileInfo(infos[0], "AB", FileType::Directory, time_);
AssertFileInfo(infos[1], "AB/CD", FileType::Directory, time_);
AssertFileInfo(infos[2], "ab", FileType::File, time_, 4);
// Invalid path
s.base_dir = "//foo//bar//baz//";
ASSERT_RAISES(Invalid, fs_->GetFileInfo(s));
}
TEST_F(TestMockFS, OpenOutputStream) {
ASSERT_OK_AND_ASSIGN(auto stream, fs_->OpenOutputStream("ab"));
ASSERT_OK(stream->Close());
CheckDirs({});
CheckFiles({{"ab", time_, ""}});
}
TEST_F(TestMockFS, OpenAppendStream) {
ASSERT_OK_AND_ASSIGN(auto stream, fs_->OpenAppendStream("ab"));
ASSERT_OK(WriteString(stream.get(), "some "));
ASSERT_OK(stream->Close());
ASSERT_OK_AND_ASSIGN(stream, fs_->OpenAppendStream("ab"));
ASSERT_OK(WriteString(stream.get(), "data"));
ASSERT_OK(stream->Close());
CheckDirs({});
CheckFiles({{"ab", time_, "some data"}});
}
TEST_F(TestMockFS, Make) {
ASSERT_OK_AND_ASSIGN(fs_, MockFileSystem::Make(time_, {}));
CheckDirs({});
CheckFiles({});
ASSERT_OK_AND_ASSIGN(fs_, MockFileSystem::Make(time_, {Dir("A/B/C"), File("A/a")}));
CheckDirs({{"A", time_}, {"A/B", time_}, {"A/B/C", time_}});
CheckFiles({{"A/a", time_, ""}});
}
TEST_F(TestMockFS, FileSystemFromUri) {
std::string path;
ASSERT_OK_AND_ASSIGN(fs_, FileSystemFromUri("mock:", &path));
ASSERT_EQ(path, "");
CheckDirs({}); // Ensures it's a MockFileSystem
ASSERT_OK_AND_ASSIGN(fs_, FileSystemFromUri("mock:foo/bar", &path));
ASSERT_EQ(path, "foo/bar");
CheckDirs({});
ASSERT_OK_AND_ASSIGN(fs_, FileSystemFromUri("mock:/foo/bar", &path));
ASSERT_EQ(path, "foo/bar");
CheckDirs({});
ASSERT_OK_AND_ASSIGN(fs_, FileSystemFromUri("mock:/foo/bar/?q=xxx", &path));
ASSERT_EQ(path, "foo/bar/");
CheckDirs({});
ASSERT_OK_AND_ASSIGN(fs_, FileSystemFromUri("mock:///foo/bar", &path));
ASSERT_EQ(path, "foo/bar");
CheckDirs({});
ASSERT_OK_AND_ASSIGN(fs_, FileSystemFromUri("mock:///foo/bar?q=zzz", &path));
ASSERT_EQ(path, "foo/bar");
CheckDirs({});
}
////////////////////////////////////////////////////////////////////////////
// Concrete SubTreeFileSystem tests
class TestSubTreeFileSystem : public TestMockFS {
public:
void SetUp() override {
TestMockFS::SetUp();
ASSERT_OK(fs_->CreateDir("sub/tree"));
subfs_ = std::make_shared<SubTreeFileSystem>("sub/tree", fs_);
}
void CreateFile(const std::string& path, const std::string& data) {
::arrow::fs::CreateFile(subfs_.get(), path, data);
}
protected:
std::shared_ptr<SubTreeFileSystem> subfs_;
};
TEST_F(TestSubTreeFileSystem, CreateDir) {
ASSERT_OK(subfs_->CreateDir("AB"));
ASSERT_OK(subfs_->CreateDir("AB/CD/EF")); // Recursive
// Non-recursive, parent doesn't exist
ASSERT_RAISES(IOError, subfs_->CreateDir("AB/GH/IJ", false /* recursive */));
ASSERT_OK(subfs_->CreateDir("AB/GH", false /* recursive */));
ASSERT_OK(subfs_->CreateDir("AB/GH/IJ", false /* recursive */));
// Can't create root dir
ASSERT_RAISES(IOError, subfs_->CreateDir(""));
CheckDirs({{"sub", time_},
{"sub/tree", time_},
{"sub/tree/AB", time_},
{"sub/tree/AB/CD", time_},
{"sub/tree/AB/CD/EF", time_},
{"sub/tree/AB/GH", time_},
{"sub/tree/AB/GH/IJ", time_}});
CheckFiles({});
}
TEST_F(TestSubTreeFileSystem, DeleteDir) {
ASSERT_OK(subfs_->CreateDir("AB/CD/EF"));
ASSERT_OK(subfs_->CreateDir("AB/GH/IJ"));
ASSERT_OK(subfs_->DeleteDir("AB/CD"));
ASSERT_OK(subfs_->DeleteDir("AB/GH/IJ"));
CheckDirs({{"sub", time_},
{"sub/tree", time_},
{"sub/tree/AB", time_},
{"sub/tree/AB/GH", time_}});
CheckFiles({});
ASSERT_RAISES(IOError, subfs_->DeleteDir("AB/CD"));
ASSERT_OK(subfs_->DeleteDir("AB"));
CheckDirs({{"sub", time_}, {"sub/tree", time_}});
CheckFiles({});
// Can't delete root dir
ASSERT_RAISES(IOError, subfs_->DeleteDir(""));
CheckDirs({{"sub", time_}, {"sub/tree", time_}});
CheckFiles({});
}
TEST_F(TestSubTreeFileSystem, DeleteFile) {
ASSERT_OK(subfs_->CreateDir("AB"));
CreateFile("ab", "");
CheckFiles({{"sub/tree/ab", time_, ""}});
ASSERT_OK(subfs_->DeleteFile("ab"));
CheckFiles({});
CreateFile("AB/cd", "");
CheckFiles({{"sub/tree/AB/cd", time_, ""}});
ASSERT_OK(subfs_->DeleteFile("AB/cd"));
CheckFiles({});
ASSERT_RAISES(IOError, subfs_->DeleteFile("nonexistent"));
ASSERT_RAISES(IOError, subfs_->DeleteFile(""));
}
TEST_F(TestSubTreeFileSystem, MoveFile) {
CreateFile("ab", "");
CheckFiles({{"sub/tree/ab", time_, ""}});
ASSERT_OK(subfs_->Move("ab", "cd"));
CheckFiles({{"sub/tree/cd", time_, ""}});
ASSERT_OK(subfs_->CreateDir("AB"));
ASSERT_OK(subfs_->Move("cd", "AB/ef"));
CheckFiles({{"sub/tree/AB/ef", time_, ""}});
ASSERT_RAISES(IOError, subfs_->Move("AB/ef", ""));
ASSERT_RAISES(IOError, subfs_->Move("", "xxx"));
CheckFiles({{"sub/tree/AB/ef", time_, ""}});
CheckDirs({{"sub", time_}, {"sub/tree", time_}, {"sub/tree/AB", time_}});
}
TEST_F(TestSubTreeFileSystem, MoveDir) {
ASSERT_OK(subfs_->CreateDir("AB/CD/EF"));
ASSERT_OK(subfs_->Move("AB/CD", "GH"));
CheckDirs({{"sub", time_},
{"sub/tree", time_},
{"sub/tree/AB", time_},
{"sub/tree/GH", time_},
{"sub/tree/GH/EF", time_}});
ASSERT_RAISES(IOError, subfs_->Move("AB", ""));
}
TEST_F(TestSubTreeFileSystem, CopyFile) {
CreateFile("ab", "data");
CheckFiles({{"sub/tree/ab", time_, "data"}});
ASSERT_OK(subfs_->CopyFile("ab", "cd"));
CheckFiles({{"sub/tree/ab", time_, "data"}, {"sub/tree/cd", time_, "data"}});
ASSERT_OK(subfs_->CreateDir("AB"));
ASSERT_OK(subfs_->CopyFile("cd", "AB/ef"));
CheckFiles({{"sub/tree/AB/ef", time_, "data"},
{"sub/tree/ab", time_, "data"},
{"sub/tree/cd", time_, "data"}});
ASSERT_RAISES(IOError, subfs_->CopyFile("ab", ""));
ASSERT_RAISES(IOError, subfs_->CopyFile("", "xxx"));
CheckFiles({{"sub/tree/AB/ef", time_, "data"},
{"sub/tree/ab", time_, "data"},
{"sub/tree/cd", time_, "data"}});
}
TEST_F(TestSubTreeFileSystem, CopyFiles) {
ASSERT_OK(subfs_->CreateDir("AB"));
ASSERT_OK(subfs_->CreateDir("CD/CD"));
ASSERT_OK(subfs_->CreateDir("EF/EF/EF"));
CreateFile("AB/ab", "ab");
CreateFile("CD/CD/cd", "cd");
CreateFile("EF/EF/EF/ef", "ef");
ASSERT_OK(fs_->CreateDir("sub/copy"));
auto dest_fs = std::make_shared<SubTreeFileSystem>("sub/copy", fs_);
FileSelector sel;
sel.recursive = true;
ASSERT_OK(CopyFiles(subfs_, sel, dest_fs, ""));
CheckFiles({
{"sub/copy/AB/ab", time_, "ab"},
{"sub/copy/CD/CD/cd", time_, "cd"},
{"sub/copy/EF/EF/EF/ef", time_, "ef"},
{"sub/tree/AB/ab", time_, "ab"},
{"sub/tree/CD/CD/cd", time_, "cd"},
{"sub/tree/EF/EF/EF/ef", time_, "ef"},
});
}
TEST_F(TestSubTreeFileSystem, OpenInputStream) {
std::shared_ptr<io::InputStream> stream;
CreateFile("ab", "data");
ASSERT_OK_AND_ASSIGN(stream, subfs_->OpenInputStream("ab"));
ASSERT_OK_AND_ASSIGN(auto buffer, stream->Read(4));
AssertBufferEqual(*buffer, "data");
ASSERT_OK(stream->Close());
ASSERT_RAISES(IOError, subfs_->OpenInputStream("nonexistent"));
ASSERT_RAISES(IOError, subfs_->OpenInputStream(""));
}
TEST_F(TestSubTreeFileSystem, OpenInputFile) {
std::shared_ptr<io::RandomAccessFile> stream;
CreateFile("ab", "some data");
ASSERT_OK_AND_ASSIGN(stream, subfs_->OpenInputFile("ab"));
ASSERT_OK_AND_ASSIGN(auto buffer, stream->ReadAt(5, 4));
AssertBufferEqual(*buffer, "data");
ASSERT_OK(stream->Close());
ASSERT_RAISES(IOError, subfs_->OpenInputFile("nonexistent"));
ASSERT_RAISES(IOError, subfs_->OpenInputFile(""));
}
TEST_F(TestSubTreeFileSystem, OpenOutputStream) {
std::shared_ptr<io::OutputStream> stream;
ASSERT_OK_AND_ASSIGN(stream, subfs_->OpenOutputStream("ab"));
ASSERT_OK(stream->Write("data"));
ASSERT_OK(stream->Close());
CheckFiles({{"sub/tree/ab", time_, "data"}});
ASSERT_OK(subfs_->CreateDir("AB"));
ASSERT_OK_AND_ASSIGN(stream, subfs_->OpenOutputStream("AB/cd"));
ASSERT_OK(stream->Write("other"));
ASSERT_OK(stream->Close());
CheckFiles({{"sub/tree/AB/cd", time_, "other"}, {"sub/tree/ab", time_, "data"}});
ASSERT_RAISES(IOError, subfs_->OpenOutputStream("nonexistent/xxx"));
ASSERT_RAISES(IOError, subfs_->OpenOutputStream("AB"));
ASSERT_RAISES(IOError, subfs_->OpenOutputStream(""));
CheckFiles({{"sub/tree/AB/cd", time_, "other"}, {"sub/tree/ab", time_, "data"}});
}
TEST_F(TestSubTreeFileSystem, OpenAppendStream) {
std::shared_ptr<io::OutputStream> stream;
ASSERT_OK_AND_ASSIGN(stream, subfs_->OpenAppendStream("ab"));
ASSERT_OK(stream->Write("some"));
ASSERT_OK(stream->Close());
CheckFiles({{"sub/tree/ab", time_, "some"}});
ASSERT_OK_AND_ASSIGN(stream, subfs_->OpenAppendStream("ab"));
ASSERT_OK(stream->Write(" data"));
ASSERT_OK(stream->Close());
CheckFiles({{"sub/tree/ab", time_, "some data"}});
}
TEST_F(TestSubTreeFileSystem, GetFileInfo) {
ASSERT_OK(subfs_->CreateDir("AB/CD"));
AssertFileInfo(subfs_.get(), "AB", FileType::Directory, time_);
AssertFileInfo(subfs_.get(), "AB/CD", FileType::Directory, time_);
CreateFile("ab", "data");
AssertFileInfo(subfs_.get(), "ab", FileType::File, time_, 4);
AssertFileInfo(subfs_.get(), "nonexistent", FileType::NotFound);
}
TEST_F(TestSubTreeFileSystem, GetFileInfoVector) {
std::vector<FileInfo> infos;
ASSERT_OK(subfs_->CreateDir("AB/CD"));
CreateFile("ab", "data");
CreateFile("AB/cd", "other data");
ASSERT_OK_AND_ASSIGN(infos, subfs_->GetFileInfo({"ab", "AB", "AB/cd", "nonexistent"}));
ASSERT_EQ(infos.size(), 4);
AssertFileInfo(infos[0], "ab", FileType::File, time_, 4);
AssertFileInfo(infos[1], "AB", FileType::Directory, time_);
AssertFileInfo(infos[2], "AB/cd", FileType::File, time_, 10);
AssertFileInfo(infos[3], "nonexistent", FileType::NotFound);
}
TEST_F(TestSubTreeFileSystem, GetFileInfoSelector) {
std::vector<FileInfo> infos;
FileSelector selector;
ASSERT_OK(subfs_->CreateDir("AB/CD"));
CreateFile("ab", "data");
CreateFile("AB/cd", "data2");
CreateFile("AB/CD/ef", "data34");
selector.base_dir = "AB";
selector.recursive = false;
ASSERT_OK_AND_ASSIGN(infos, subfs_->GetFileInfo(selector));
ASSERT_EQ(infos.size(), 2);
AssertFileInfo(infos[0], "AB/CD", FileType::Directory, time_);
AssertFileInfo(infos[1], "AB/cd", FileType::File, time_, 5);
selector.recursive = true;
ASSERT_OK_AND_ASSIGN(infos, subfs_->GetFileInfo(selector));
ASSERT_EQ(infos.size(), 3);
AssertFileInfo(infos[0], "AB/CD", FileType::Directory, time_);
AssertFileInfo(infos[1], "AB/CD/ef", FileType::File, time_, 6);
AssertFileInfo(infos[2], "AB/cd", FileType::File, time_, 5);
selector.base_dir = "";
selector.recursive = false;
ASSERT_OK_AND_ASSIGN(infos, subfs_->GetFileInfo(selector));
ASSERT_EQ(infos.size(), 2);
AssertFileInfo(infos[0], "AB", FileType::Directory, time_);
AssertFileInfo(infos[1], "ab", FileType::File, time_, 4);
selector.recursive = true;
ASSERT_OK_AND_ASSIGN(infos, subfs_->GetFileInfo(selector));
ASSERT_EQ(infos.size(), 5);
AssertFileInfo(infos[0], "AB", FileType::Directory, time_);
AssertFileInfo(infos[1], "AB/CD", FileType::Directory, time_);
AssertFileInfo(infos[2], "AB/CD/ef", FileType::File, time_, 6);
AssertFileInfo(infos[3], "AB/cd", FileType::File, time_, 5);
AssertFileInfo(infos[4], "ab", FileType::File, time_, 4);
selector.base_dir = "nonexistent";
ASSERT_RAISES(IOError, subfs_->GetFileInfo(selector));
selector.allow_not_found = true;
ASSERT_OK_AND_ASSIGN(infos, subfs_->GetFileInfo(selector));
ASSERT_EQ(infos.size(), 0);
}
////////////////////////////////////////////////////////////////////////////
// Generic SlowFileSystem tests
class TestSlowFSGeneric : public ::testing::Test, public GenericFileSystemTest {
public:
void SetUp() override {
time_ = TimePoint(TimePoint::duration(42));
fs_ = std::make_shared<MockFileSystem>(time_);
slow_fs_ = std::make_shared<SlowFileSystem>(fs_, 0.001);
}
protected:
std::shared_ptr<FileSystem> GetEmptyFileSystem() override { return slow_fs_; }
TimePoint time_;
std::shared_ptr<MockFileSystem> fs_;
std::shared_ptr<SlowFileSystem> slow_fs_;
};
GENERIC_FS_TEST_FUNCTIONS(TestSlowFSGeneric);
} // namespace internal
} // namespace fs
} // namespace arrow