blob: a423ecd0152c77e7563e149835501f02f6f44ba6 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// std::unique_ptr<TemporaryDir> temp_dir;
// 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 <algorithm>
#include <atomic>
#include <cerrno>
#include <limits>
#include <sstream>
#include <vector>
#include <signal.h>
#ifndef _WIN32
#include <pthread.h>
#endif
#include <gtest/gtest.h>
#include "arrow/testing/gtest_util.h"
#include "arrow/util/bit_util.h"
#include "arrow/util/io_util.h"
#include "arrow/util/logging.h"
#include "arrow/util/windows_compatibility.h"
#include "arrow/util/windows_fixup.h"
namespace arrow {
namespace internal {
void AssertExists(const PlatformFilename& path) {
bool exists = false;
ASSERT_OK_AND_ASSIGN(exists, FileExists(path));
ASSERT_TRUE(exists) << "Path '" << path.ToString() << "' doesn't exist";
}
void AssertNotExists(const PlatformFilename& path) {
bool exists = true;
ASSERT_OK_AND_ASSIGN(exists, FileExists(path));
ASSERT_FALSE(exists) << "Path '" << path.ToString() << "' exists";
}
TEST(ErrnoFromStatus, Basics) {
Status st;
st = Status::OK();
ASSERT_EQ(ErrnoFromStatus(st), 0);
st = Status::KeyError("foo");
ASSERT_EQ(ErrnoFromStatus(st), 0);
st = Status::IOError("foo");
ASSERT_EQ(ErrnoFromStatus(st), 0);
st = StatusFromErrno(EINVAL, StatusCode::KeyError, "foo");
ASSERT_EQ(ErrnoFromStatus(st), EINVAL);
st = IOErrorFromErrno(EPERM, "foo");
ASSERT_EQ(ErrnoFromStatus(st), EPERM);
st = IOErrorFromErrno(6789, "foo");
ASSERT_EQ(ErrnoFromStatus(st), 6789);
st = CancelledFromSignal(SIGINT, "foo");
ASSERT_EQ(ErrnoFromStatus(st), 0);
}
TEST(SignalFromStatus, Basics) {
Status st;
st = Status::OK();
ASSERT_EQ(SignalFromStatus(st), 0);
st = Status::KeyError("foo");
ASSERT_EQ(SignalFromStatus(st), 0);
st = Status::Cancelled("foo");
ASSERT_EQ(SignalFromStatus(st), 0);
st = StatusFromSignal(SIGINT, StatusCode::KeyError, "foo");
ASSERT_EQ(SignalFromStatus(st), SIGINT);
ASSERT_EQ(st.ToString(),
"Key error: foo. Detail: received signal " + std::to_string(SIGINT));
st = CancelledFromSignal(SIGINT, "bar");
ASSERT_EQ(SignalFromStatus(st), SIGINT);
ASSERT_EQ(st.ToString(),
"Cancelled: bar. Detail: received signal " + std::to_string(SIGINT));
st = IOErrorFromErrno(EINVAL, "foo");
ASSERT_EQ(SignalFromStatus(st), 0);
}
TEST(GetPageSize, Basics) {
const auto page_size = GetPageSize();
ASSERT_GE(page_size, 4096);
// It's a power of 2
ASSERT_EQ((page_size - 1) & page_size, 0);
}
TEST(MemoryAdviseWillNeed, Basics) {
ASSERT_OK_AND_ASSIGN(auto buf1, AllocateBuffer(8192));
ASSERT_OK_AND_ASSIGN(auto buf2, AllocateBuffer(1024 * 1024));
const auto addr1 = buf1->mutable_data();
const auto size1 = static_cast<size_t>(buf1->size());
const auto addr2 = buf2->mutable_data();
const auto size2 = static_cast<size_t>(buf2->size());
ASSERT_OK(MemoryAdviseWillNeed({}));
ASSERT_OK(MemoryAdviseWillNeed({{addr1, size1}, {addr2, size2}}));
ASSERT_OK(MemoryAdviseWillNeed({{addr1 + 1, size1 - 1}, {addr2 + 4095, size2 - 4095}}));
ASSERT_OK(MemoryAdviseWillNeed({{addr1, 13}, {addr2, 1}}));
ASSERT_OK(MemoryAdviseWillNeed({{addr1, 0}, {addr2 + 1, 0}}));
// Should probably fail
// (but on Windows, MemoryAdviseWillNeed can be a no-op)
#ifndef _WIN32
ASSERT_RAISES(IOError,
MemoryAdviseWillNeed({{nullptr, std::numeric_limits<size_t>::max()}}));
#endif
}
#if _WIN32
TEST(WinErrorFromStatus, Basics) {
Status st;
st = Status::OK();
ASSERT_EQ(WinErrorFromStatus(st), 0);
st = Status::KeyError("foo");
ASSERT_EQ(WinErrorFromStatus(st), 0);
st = Status::IOError("foo");
ASSERT_EQ(WinErrorFromStatus(st), 0);
st = StatusFromWinError(ERROR_FILE_NOT_FOUND, StatusCode::KeyError, "foo");
ASSERT_EQ(WinErrorFromStatus(st), ERROR_FILE_NOT_FOUND);
st = IOErrorFromWinError(ERROR_ACCESS_DENIED, "foo");
ASSERT_EQ(WinErrorFromStatus(st), ERROR_ACCESS_DENIED);
st = IOErrorFromWinError(6789, "foo");
ASSERT_EQ(WinErrorFromStatus(st), 6789);
}
#endif
TEST(PlatformFilename, RoundtripAscii) {
PlatformFilename fn;
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b"));
ASSERT_EQ(fn.ToString(), "a/b");
#if _WIN32
ASSERT_EQ(fn.ToNative(), L"a\\b");
#else
ASSERT_EQ(fn.ToNative(), "a/b");
#endif
}
TEST(PlatformFilename, RoundtripUtf8) {
PlatformFilename fn;
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("h\xc3\xa9h\xc3\xa9"));
ASSERT_EQ(fn.ToString(), "h\xc3\xa9h\xc3\xa9");
#if _WIN32
ASSERT_EQ(fn.ToNative(), L"h\u00e9h\u00e9");
#else
ASSERT_EQ(fn.ToNative(), "h\xc3\xa9h\xc3\xa9");
#endif
}
#if _WIN32
TEST(PlatformFilename, Separators) {
PlatformFilename fn;
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("C:/foo/bar"));
ASSERT_EQ(fn.ToString(), "C:/foo/bar");
ASSERT_EQ(fn.ToNative(), L"C:\\foo\\bar");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("C:\\foo\\bar"));
ASSERT_EQ(fn.ToString(), "C:/foo/bar");
ASSERT_EQ(fn.ToNative(), L"C:\\foo\\bar");
}
#endif
TEST(PlatformFilename, Invalid) {
std::string s = "foo";
s += '\x00';
ASSERT_RAISES(Invalid, PlatformFilename::FromString(s));
}
TEST(PlatformFilename, Join) {
PlatformFilename fn, joined;
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b"));
ASSERT_OK_AND_ASSIGN(joined, fn.Join("c/d"));
ASSERT_EQ(joined.ToString(), "a/b/c/d");
#if _WIN32
ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d");
#else
ASSERT_EQ(joined.ToNative(), "a/b/c/d");
#endif
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b/"));
ASSERT_OK_AND_ASSIGN(joined, fn.Join("c/d"));
ASSERT_EQ(joined.ToString(), "a/b/c/d");
#if _WIN32
ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d");
#else
ASSERT_EQ(joined.ToNative(), "a/b/c/d");
#endif
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString(""));
ASSERT_OK_AND_ASSIGN(joined, fn.Join("c/d"));
ASSERT_EQ(joined.ToString(), "c/d");
#if _WIN32
ASSERT_EQ(joined.ToNative(), L"c\\d");
#else
ASSERT_EQ(joined.ToNative(), "c/d");
#endif
#if _WIN32
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a\\b"));
ASSERT_OK_AND_ASSIGN(joined, fn.Join("c\\d"));
ASSERT_EQ(joined.ToString(), "a/b/c/d");
ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a\\b\\"));
ASSERT_OK_AND_ASSIGN(joined, fn.Join("c\\d"));
ASSERT_EQ(joined.ToString(), "a/b/c/d");
ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d");
#endif
}
TEST(PlatformFilename, JoinInvalid) {
PlatformFilename fn;
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b"));
std::string s = "foo";
s += '\x00';
ASSERT_RAISES(Invalid, fn.Join(s));
}
TEST(PlatformFilename, Parent) {
PlatformFilename fn;
// Relative
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab/cd"));
ASSERT_EQ(fn.ToString(), "ab/cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
#if _WIN32
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab/cd\\ef"));
ASSERT_EQ(fn.ToString(), "ab/cd/ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab/cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
#endif
// Absolute
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("/ab/cd/ef"));
ASSERT_EQ(fn.ToString(), "/ab/cd/ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab/cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/");
#if _WIN32
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\ab\\cd/ef"));
ASSERT_EQ(fn.ToString(), "/ab/cd/ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab/cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/");
#endif
// Empty
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString(""));
ASSERT_EQ(fn.ToString(), "");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "");
// Multiple separators, relative
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab//cd///ef"));
ASSERT_EQ(fn.ToString(), "ab//cd///ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab//cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
#if _WIN32
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab\\\\cd\\\\\\ef"));
ASSERT_EQ(fn.ToString(), "ab//cd///ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab//cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab");
#endif
// Multiple separators, absolute
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("//ab//cd///ef"));
ASSERT_EQ(fn.ToString(), "//ab//cd///ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//ab//cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//");
#if _WIN32
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\\\ab\\cd\\ef"));
ASSERT_EQ(fn.ToString(), "//ab/cd/ef");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//ab/cd");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//ab");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "//");
#endif
// Trailing slashes
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("/ab/cd/ef/"));
ASSERT_EQ(fn.ToString(), "/ab/cd/ef/");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab/cd");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("/ab/cd/ef//"));
ASSERT_EQ(fn.ToString(), "/ab/cd/ef//");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab/cd");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab/"));
ASSERT_EQ(fn.ToString(), "ab/");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab/");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab//"));
ASSERT_EQ(fn.ToString(), "ab//");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab//");
#if _WIN32
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\ab\\cd\\ef\\"));
ASSERT_EQ(fn.ToString(), "/ab/cd/ef/");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab/cd");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\ab\\cd\\ef\\\\"));
ASSERT_EQ(fn.ToString(), "/ab/cd/ef//");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "/ab/cd");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab\\"));
ASSERT_EQ(fn.ToString(), "ab/");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab/");
ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab\\\\"));
ASSERT_EQ(fn.ToString(), "ab//");
fn = fn.Parent();
ASSERT_EQ(fn.ToString(), "ab//");
#endif
}
TEST(CreateDirDeleteDir, Basics) {
std::unique_ptr<TemporaryDir> temp_dir;
ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("deletedirtest-"));
const std::string BASE =
temp_dir->path().Join("xxx-io-util-test-dir2").ValueOrDie().ToString();
bool created, deleted;
PlatformFilename parent, child;
ASSERT_OK_AND_ASSIGN(parent, PlatformFilename::FromString(BASE));
ASSERT_EQ(parent.ToString(), BASE);
// Make sure the directory doesn't exist already
ARROW_UNUSED(DeleteDirTree(parent));
AssertNotExists(parent);
ASSERT_OK_AND_ASSIGN(created, CreateDir(parent));
ASSERT_TRUE(created);
AssertExists(parent);
ASSERT_OK_AND_ASSIGN(created, CreateDir(parent));
ASSERT_FALSE(created); // already exists
AssertExists(parent);
ASSERT_OK_AND_ASSIGN(child, PlatformFilename::FromString(BASE + "/some-child"));
ASSERT_OK_AND_ASSIGN(created, CreateDir(child));
ASSERT_TRUE(created);
AssertExists(child);
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(parent));
ASSERT_TRUE(deleted);
AssertNotExists(parent);
AssertNotExists(child);
// Parent is deleted, cannot create child again
ASSERT_RAISES(IOError, CreateDir(child));
// It's not an error to call DeleteDirTree on a nonexistent path.
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(parent));
ASSERT_FALSE(deleted);
// ... unless asked so
auto status = DeleteDirTree(parent, /*allow_not_found=*/false).status();
ASSERT_RAISES(IOError, status);
#ifdef _WIN32
ASSERT_EQ(WinErrorFromStatus(status), ERROR_FILE_NOT_FOUND);
#else
ASSERT_EQ(ErrnoFromStatus(status), ENOENT);
#endif
}
TEST(DeleteDirContents, Basics) {
std::unique_ptr<TemporaryDir> temp_dir;
ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("deletedirtest-"));
const std::string BASE =
temp_dir->path().Join("xxx-io-util-test-dir2").ValueOrDie().ToString();
bool created, deleted;
PlatformFilename parent, child1, child2;
ASSERT_OK_AND_ASSIGN(parent, PlatformFilename::FromString(BASE));
ASSERT_EQ(parent.ToString(), BASE);
// Make sure the directory doesn't exist already
ARROW_UNUSED(DeleteDirTree(parent));
AssertNotExists(parent);
// Create the parent, a child dir and a child file
ASSERT_OK_AND_ASSIGN(created, CreateDir(parent));
ASSERT_TRUE(created);
ASSERT_OK_AND_ASSIGN(child1, PlatformFilename::FromString(BASE + "/child-dir"));
ASSERT_OK_AND_ASSIGN(child2, PlatformFilename::FromString(BASE + "/child-file"));
ASSERT_OK_AND_ASSIGN(created, CreateDir(child1));
ASSERT_TRUE(created);
int fd = -1;
ASSERT_OK_AND_ASSIGN(fd, FileOpenWritable(child2));
ASSERT_OK(FileClose(fd));
AssertExists(child1);
AssertExists(child2);
// Cannot call DeleteDirContents on a file
ASSERT_RAISES(IOError, DeleteDirContents(child2));
AssertExists(child2);
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(parent));
ASSERT_TRUE(deleted);
AssertExists(parent);
AssertNotExists(child1);
AssertNotExists(child2);
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(parent));
ASSERT_TRUE(deleted);
AssertExists(parent);
// It's not an error to call DeleteDirContents on a nonexistent path.
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(child1));
ASSERT_FALSE(deleted);
// ... unless asked so
auto status = DeleteDirContents(child1, /*allow_not_found=*/false).status();
ASSERT_RAISES(IOError, status);
#ifdef _WIN32
ASSERT_EQ(WinErrorFromStatus(status), ERROR_FILE_NOT_FOUND);
#else
ASSERT_EQ(ErrnoFromStatus(status), ENOENT);
#endif
// Now actually delete the test directory
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(parent));
ASSERT_TRUE(deleted);
}
TEST(TemporaryDir, Basics) {
std::unique_ptr<TemporaryDir> temp_dir;
PlatformFilename fn;
ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("some-prefix-"));
fn = temp_dir->path();
// Path has a trailing separator, for convenience
ASSERT_EQ(fn.ToString().back(), '/');
#if defined(_WIN32)
ASSERT_EQ(fn.ToNative().back(), L'\\');
#else
ASSERT_EQ(fn.ToNative().back(), '/');
#endif
AssertExists(fn);
ASSERT_NE(fn.ToString().find("some-prefix-"), std::string::npos);
// Create child contents to check that they're cleaned up at the end
#if defined(_WIN32)
PlatformFilename child(fn.ToNative() + L"some-child");
#else
PlatformFilename child(fn.ToNative() + "some-child");
#endif
ASSERT_OK(CreateDir(child));
AssertExists(child);
temp_dir.reset();
AssertNotExists(fn);
AssertNotExists(child);
}
TEST(CreateDirTree, Basics) {
std::unique_ptr<TemporaryDir> temp_dir;
PlatformFilename fn;
bool created;
ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("io-util-test-"));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/CD"));
ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn));
ASSERT_TRUE(created);
ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn));
ASSERT_FALSE(created);
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB"));
ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn));
ASSERT_FALSE(created);
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("EF"));
ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn));
ASSERT_TRUE(created);
}
TEST(ListDir, Basics) {
std::unique_ptr<TemporaryDir> temp_dir;
PlatformFilename fn;
std::vector<PlatformFilename> entries;
auto check_entries = [](const std::vector<PlatformFilename>& entries,
std::vector<std::string> expected) -> void {
std::vector<std::string> actual(entries.size());
std::transform(entries.begin(), entries.end(), actual.begin(),
[](const PlatformFilename& fn) { return fn.ToString(); });
// Sort results for deterministic testing
std::sort(actual.begin(), actual.end());
ASSERT_EQ(actual, expected);
};
ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("io-util-test-"));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/CD"));
ASSERT_OK(CreateDirTree(fn));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/EF/GH"));
ASSERT_OK(CreateDirTree(fn));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/ghi.txt"));
int fd = -1;
ASSERT_OK_AND_ASSIGN(fd, FileOpenWritable(fn));
ASSERT_OK(FileClose(fd));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB"));
ASSERT_OK_AND_ASSIGN(entries, ListDir(fn));
ASSERT_EQ(entries.size(), 3);
check_entries(entries, {"CD", "EF", "ghi.txt"});
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/EF/GH"));
ASSERT_OK_AND_ASSIGN(entries, ListDir(fn));
check_entries(entries, {});
// Errors
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("nonexistent"));
ASSERT_RAISES(IOError, ListDir(fn));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/ghi.txt"));
ASSERT_RAISES(IOError, ListDir(fn));
}
TEST(DeleteFile, Basics) {
std::unique_ptr<TemporaryDir> temp_dir;
PlatformFilename fn;
int fd;
bool deleted;
ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("io-util-test-"));
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("test-file"));
AssertNotExists(fn);
ASSERT_OK_AND_ASSIGN(fd, FileOpenWritable(fn));
ASSERT_OK(FileClose(fd));
AssertExists(fn);
ASSERT_OK_AND_ASSIGN(deleted, DeleteFile(fn));
ASSERT_TRUE(deleted);
AssertNotExists(fn);
ASSERT_OK_AND_ASSIGN(deleted, DeleteFile(fn));
ASSERT_FALSE(deleted);
AssertNotExists(fn);
auto status = DeleteFile(fn, /*allow_not_found=*/false).status();
ASSERT_RAISES(IOError, status);
#ifdef _WIN32
ASSERT_EQ(WinErrorFromStatus(status), ERROR_FILE_NOT_FOUND);
#else
ASSERT_EQ(ErrnoFromStatus(status), ENOENT);
#endif
// Cannot call DeleteFile on directory
ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("test-temp_dir"));
ASSERT_OK(CreateDir(fn));
AssertExists(fn);
ASSERT_RAISES(IOError, DeleteFile(fn));
}
#ifndef __APPLE__
TEST(FileUtils, LongPaths) {
// ARROW-8477: check using long file paths under Windows (> 260 characters).
bool created, deleted;
#ifdef _WIN32
const char* kRegKeyName = R"(SYSTEM\CurrentControlSet\Control\FileSystem)";
const char* kRegValueName = "LongPathsEnabled";
DWORD value = 0;
DWORD size = sizeof(value);
LSTATUS status = RegGetValueA(HKEY_LOCAL_MACHINE, kRegKeyName, kRegValueName,
RRF_RT_REG_DWORD, NULL, &value, &size);
bool test_long_paths = (status == ERROR_SUCCESS && value == 1);
if (!test_long_paths) {
ARROW_LOG(WARNING)
<< "Tests for accessing files with long path names have been disabled. "
<< "To enable these tests, set the value of " << kRegValueName
<< " in registry key \\HKEY_LOCAL_MACHINE\\" << kRegKeyName
<< " to 1 on the test host.";
return;
}
#endif
const std::string BASE = "xxx-io-util-test-dir-long";
PlatformFilename base_path, long_path, long_filename;
int fd = -1;
std::stringstream fs;
fs << BASE;
for (int i = 0; i < 64; ++i) {
fs << "/123456789ABCDEF";
}
ASSERT_OK_AND_ASSIGN(base_path,
PlatformFilename::FromString(BASE)); // long_path length > 1024
ASSERT_OK_AND_ASSIGN(
long_path, PlatformFilename::FromString(fs.str())); // long_path length > 1024
ASSERT_OK_AND_ASSIGN(created, CreateDirTree(long_path));
ASSERT_TRUE(created);
AssertExists(long_path);
ASSERT_OK_AND_ASSIGN(long_filename,
PlatformFilename::FromString(fs.str() + "/file.txt"));
ASSERT_OK_AND_ASSIGN(fd, FileOpenWritable(long_filename));
ASSERT_OK(FileClose(fd));
AssertExists(long_filename);
fd = -1;
ASSERT_OK_AND_ASSIGN(fd, FileOpenReadable(long_filename));
ASSERT_OK(FileClose(fd));
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(long_path));
ASSERT_TRUE(deleted);
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(long_path));
ASSERT_TRUE(deleted);
// Now delete the whole test directory tree
ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(base_path));
ASSERT_TRUE(deleted);
}
#endif
static std::atomic<int> signal_received;
static void handle_signal(int signum) {
ReinstateSignalHandler(signum, &handle_signal);
signal_received.store(signum);
}
TEST(SendSignal, Generic) {
signal_received.store(0);
SignalHandlerGuard guard(SIGINT, &handle_signal);
ASSERT_EQ(signal_received.load(), 0);
ASSERT_OK(SendSignal(SIGINT));
BusyWait(1.0, [&]() { return signal_received.load() != 0; });
ASSERT_EQ(signal_received.load(), SIGINT);
// Re-try (exercise ReinstateSignalHandler)
signal_received.store(0);
ASSERT_OK(SendSignal(SIGINT));
BusyWait(1.0, [&]() { return signal_received.load() != 0; });
ASSERT_EQ(signal_received.load(), SIGINT);
}
TEST(SendSignal, ToThread) {
#ifdef _WIN32
uint64_t dummy_thread_id = 42;
ASSERT_RAISES(NotImplemented, SendSignalToThread(SIGINT, dummy_thread_id));
#else
// Have to use a C-style cast because pthread_t can be a pointer *or* integer type
uint64_t thread_id = (uint64_t)(pthread_self()); // NOLINT readability-casting
signal_received.store(0);
SignalHandlerGuard guard(SIGINT, &handle_signal);
ASSERT_EQ(signal_received.load(), 0);
ASSERT_OK(SendSignalToThread(SIGINT, thread_id));
BusyWait(1.0, [&]() { return signal_received.load() != 0; });
ASSERT_EQ(signal_received.load(), SIGINT);
#endif
}
} // namespace internal
} // namespace arrow