blob: 52903094ed91887e1122bca730ff6be8196d4e79 [file] [log] [blame]
// Licensed 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 <list>
#include <map>
#include <set>
#include <string>
#include <stout/foreach.hpp>
#include <stout/fs.hpp>
#include <stout/hashset.hpp>
#include <stout/path.hpp>
#include <stout/try.hpp>
#include <stout/uuid.hpp>
#include <stout/os/access.hpp>
#include <stout/os/dup.hpp>
#include <stout/os/find.hpp>
#include <stout/os/getcwd.hpp>
#include <stout/os/int_fd.hpp>
#include <stout/os/ls.hpp>
#include <stout/os/lseek.hpp>
#include <stout/os/mkdir.hpp>
#include <stout/os/pipe.hpp>
#include <stout/os/read.hpp>
#include <stout/os/realpath.hpp>
#include <stout/os/rename.hpp>
#include <stout/os/rm.hpp>
#include <stout/os/touch.hpp>
#include <stout/os/write.hpp>
#include <stout/os/xattr.hpp>
#include <stout/tests/utils.hpp>
using std::list;
using std::set;
using std::string;
static hashset<string> listfiles(const string& directory)
{
hashset<string> fileset;
Try<list<string>> entries = os::ls(directory);
if (entries.isSome()) {
foreach (const string& entry, entries.get()) {
fileset.insert(entry);
}
}
return fileset;
}
class FsTest : public TemporaryDirectoryTest {};
TEST_F(FsTest, Find)
{
const string testdir =
path::join(os::getcwd(), id::UUID::random().toString());
const string subdir = path::join(testdir, "test1");
ASSERT_SOME(os::mkdir(subdir)); // Create the directories.
// Now write some files.
const string file1 = path::join(testdir, "file1.txt");
const string file2 = path::join(subdir, "file2.txt");
const string file3 = path::join(subdir, "file3.jpg");
ASSERT_SOME(os::touch(file1));
ASSERT_SOME(os::touch(file2));
ASSERT_SOME(os::touch(file3));
// Find "*.txt" files.
Try<list<string>> result = os::find(testdir, ".txt");
ASSERT_SOME(result);
hashset<string> files;
foreach (const string& file, result.get()) {
files.insert(file);
}
ASSERT_EQ(2u, files.size());
ASSERT_TRUE(files.contains(file1));
ASSERT_TRUE(files.contains(file2));
}
TEST_F(FsTest, ReadWriteString)
{
const string testfile =
path::join(os::getcwd(), id::UUID::random().toString());
const string teststr = "line1\nline2";
ASSERT_SOME(os::write(testfile, teststr));
Try<string> readstr = os::read(testfile);
EXPECT_SOME_EQ(teststr, readstr);
}
TEST_F(FsTest, Mkdir)
{
const hashset<string> EMPTY;
const string tmpdir = os::getcwd();
hashset<string> expectedListing = EMPTY;
EXPECT_EQ(expectedListing, listfiles(tmpdir));
ASSERT_SOME(os::mkdir(path::join(tmpdir, "a", "b", "c")));
ASSERT_SOME(os::mkdir(path::join(tmpdir, "a", "b", "d")));
ASSERT_SOME(os::mkdir(path::join(tmpdir, "e", "f")));
expectedListing = EMPTY;
expectedListing.insert("a");
expectedListing.insert("e");
EXPECT_EQ(expectedListing, listfiles(tmpdir));
expectedListing = EMPTY;
expectedListing.insert("b");
EXPECT_EQ(expectedListing, listfiles(path::join(tmpdir, "a")));
expectedListing = EMPTY;
expectedListing.insert("c");
expectedListing.insert("d");
EXPECT_EQ(expectedListing, listfiles(path::join(tmpdir, "a", "b")));
expectedListing = EMPTY;
EXPECT_EQ(expectedListing, listfiles(path::join(tmpdir, "a", "b", "c")));
EXPECT_EQ(expectedListing, listfiles(path::join(tmpdir, "a", "b", "d")));
expectedListing.insert("f");
EXPECT_EQ(expectedListing, listfiles(path::join(tmpdir, "e")));
expectedListing = EMPTY;
EXPECT_EQ(expectedListing, listfiles(path::join(tmpdir, "e", "f")));
}
TEST_F(FsTest, Exists)
{
const hashset<string> EMPTY;
const string tmpdir = os::getcwd();
hashset<string> expectedListing = EMPTY;
ASSERT_EQ(expectedListing, listfiles(tmpdir));
// Create simple directory structure.
ASSERT_SOME(os::mkdir(path::join(tmpdir, "a", "b", "c")));
// Expect all the directories exist.
EXPECT_TRUE(os::exists(tmpdir));
EXPECT_TRUE(os::exists(path::join(tmpdir, "a")));
EXPECT_TRUE(os::exists(path::join(tmpdir, "a", "b")));
EXPECT_TRUE(os::exists(path::join(tmpdir, "a", "b", "c")));
// Return false if a component of the path does not exist.
EXPECT_FALSE(os::exists(path::join(tmpdir, "a", "fakeDir")));
EXPECT_FALSE(os::exists(path::join(tmpdir, "a", "fakeDir", "c")));
// Add file to directory tree.
ASSERT_SOME(os::touch(path::join(tmpdir, "a", "b", "c", "yourFile")));
// Assert it exists.
EXPECT_TRUE(os::exists(path::join(tmpdir, "a", "b", "c", "yourFile")));
// Return false if file is wrong.
EXPECT_FALSE(os::exists(path::join(tmpdir, "a", "b", "c", "yourFakeFile")));
}
TEST_F(FsTest, Touch)
{
const string testfile =
path::join(os::getcwd(), id::UUID::random().toString());
ASSERT_SOME(os::touch(testfile));
ASSERT_TRUE(os::exists(testfile));
}
#ifdef __WINDOWS__
// This tests the expected behavior of the `longpath` helper.
TEST_F(FsTest, WindowsInternalLongPath)
{
using ::internal::windows::longpath;
// Not absolute.
EXPECT_EQ(longpath("path"), wide_stringify("path"));
// Absolute, but short.
EXPECT_EQ(longpath("C:\\path"), wide_stringify("C:\\path"));
// Edge case exactly one under `max_path_length`.
const size_t max_path_length = 248;
const string root = "C:\\";
string path = root + string(max_path_length - root.length() - 1, 'c');
EXPECT_EQ(path.length(), max_path_length - 1);
EXPECT_EQ(longpath(path), wide_stringify(path));
// Edge case exactly at `max_path_length`.
path += "c";
EXPECT_EQ(path.length(), max_path_length);
EXPECT_EQ(longpath(path), wide_stringify(os::LONGPATH_PREFIX + path));
// Edge case exactly one over `max_path_length`.
path += "c";
EXPECT_EQ(path.length(), max_path_length + 1);
EXPECT_EQ(longpath(path), wide_stringify(os::LONGPATH_PREFIX + path));
// Idempotency.
EXPECT_EQ(longpath(os::LONGPATH_PREFIX + path),
wide_stringify(os::LONGPATH_PREFIX + path));
}
#endif // __WINDOWS__
// This test attempts to perform some basic file operations on a file
// with an absolute path at exactly the internal `MAX_PATH` of 248.
//
// NOTE: This tests an edge case on Windows, but is a cross-platform test.
TEST_F(FsTest, CreateDirectoryAtMaxPath)
{
const size_t max_path_length = 248;
const string testdir = path::join(
sandbox.get(),
string(max_path_length - sandbox->length() - 1 /* separator */, 'c'));
EXPECT_EQ(testdir.length(), max_path_length);
ASSERT_SOME(os::mkdir(testdir));
const string testfile = path::join(testdir, "file.txt");
EXPECT_SOME(os::touch(testfile));
EXPECT_TRUE(os::exists(testfile));
EXPECT_SOME_TRUE(os::access(testfile, R_OK | W_OK));
EXPECT_SOME_EQ(testfile, os::realpath(testfile));
}
// This test attempts to perform some basic file operations on a file
// with an absolute path longer than the `MAX_PATH`.
//
// NOTE: This tests an edge case on Windows, but is a cross-platform test.
TEST_F(FsTest, CreateDirectoryLongerThanMaxPath)
{
string testdir = sandbox.get();
const size_t max_path_length = 260;
while (testdir.length() <= max_path_length) {
testdir = path::join(testdir, id::UUID::random().toString());
}
EXPECT_TRUE(testdir.length() > max_path_length);
ASSERT_SOME(os::mkdir(testdir));
const string testfile = path::join(testdir, "file.txt");
EXPECT_SOME(os::touch(testfile));
EXPECT_TRUE(os::exists(testfile));
EXPECT_SOME_TRUE(os::access(testfile, R_OK | W_OK));
EXPECT_SOME_EQ(testfile, os::realpath(testfile));
}
// This test ensures that `os::realpath` will work on open files.
//
// NOTE: This tests an edge case on Windows, but is a cross-platform test.
TEST_F(FsTest, RealpathValidationOnOpenFile)
{
// Open a file to write, with "SHARE" read/write permissions,
// then call `os::realpath` on that file.
const string file = path::join(sandbox.get(), id::UUID::random().toString());
const Try<int_fd> fd = os::open(file, O_CREAT | O_RDWR);
ASSERT_SOME(fd);
EXPECT_SOME(os::write(fd.get(), "data"));
// Verify that `os::realpath` (which calls `CreateFileW` on Windows) is
// successful even though the file is open elsewhere.
EXPECT_SOME_EQ(file, os::realpath(file));
EXPECT_SOME(os::close(fd.get()));
}
TEST_F(FsTest, SYMLINK_Symlink)
{
const string temp_path = os::getcwd();
const string link = path::join(temp_path, "sym.link");
const string file = path::join(temp_path, id::UUID::random().toString());
// Create file
ASSERT_SOME(os::touch(file))
<< "Failed to create file '" << file << "'";
ASSERT_TRUE(os::exists(file));
// Create symlink
ASSERT_SOME(fs::symlink(file, link));
// Test symlink
EXPECT_TRUE(os::stat::islink(link));
}
TEST_F(FsTest, SYMLINK_Rm)
{
const string tmpdir = os::getcwd();
hashset<string> expectedListing;
EXPECT_EQ(expectedListing, listfiles(tmpdir));
const string file1 = path::join(tmpdir, "file1.txt");
const string directory1 = path::join(tmpdir, "directory1");
const string fileSymlink1 = path::join(tmpdir, "fileSymlink1");
const string fileToSymlink = path::join(tmpdir, "fileToSymlink.txt");
const string directorySymlink1 = path::join(tmpdir, "directorySymlink1");
const string directoryToSymlink = path::join(tmpdir, "directoryToSymlink");
// Create a file, a directory, and a symlink to a file and a directory.
ASSERT_SOME(os::touch(file1));
expectedListing.insert("file1.txt");
ASSERT_SOME(os::mkdir(directory1));
expectedListing.insert("directory1");
ASSERT_SOME(os::touch(fileToSymlink));
ASSERT_SOME(fs::symlink(fileToSymlink, fileSymlink1));
expectedListing.insert("fileToSymlink.txt");
expectedListing.insert("fileSymlink1");
ASSERT_SOME(os::mkdir(directoryToSymlink));
ASSERT_SOME(fs::symlink(directoryToSymlink, directorySymlink1));
expectedListing.insert("directoryToSymlink");
expectedListing.insert("directorySymlink1");
EXPECT_EQ(expectedListing, listfiles(tmpdir));
// Verify `rm` of non-empty directory fails.
EXPECT_ERROR(os::rm(tmpdir));
EXPECT_EQ(expectedListing, listfiles(tmpdir));
// Remove all, and verify.
EXPECT_SOME(os::rm(file1));
expectedListing.erase("file1.txt");
EXPECT_SOME(os::rm(directory1));
expectedListing.erase("directory1");
EXPECT_SOME(os::rm(fileSymlink1));
expectedListing.erase("fileSymlink1");
EXPECT_SOME(os::rm(directorySymlink1));
expectedListing.erase("directorySymlink1");
// `os::rm` doesn't act on the target, therefore we must verify they each
// still exist.
EXPECT_EQ(expectedListing, listfiles(tmpdir));
// Verify that we error out for paths that don't exist.
EXPECT_ERROR(os::rm("fakeFile"));
}
TEST_F(FsTest, List)
{
const string testdir =
path::join(os::getcwd(), id::UUID::random().toString());
ASSERT_SOME(os::mkdir(testdir)); // Create the directories.
// Now write some files.
const string file1 = path::join(testdir, "file1.txt");
const string file2 = path::join(testdir, "file2.txt");
const string file3 = path::join(testdir, "file3.jpg");
ASSERT_SOME(os::touch(file1));
ASSERT_SOME(os::touch(file2));
ASSERT_SOME(os::touch(file3));
// Search all files in folder
Try<list<string>> allFiles = fs::list(path::join(testdir, "*"));
ASSERT_SOME(allFiles);
EXPECT_EQ(3u, allFiles->size());
// Search .jpg files in folder
Try<list<string>> jpgFiles = fs::list(path::join(testdir, "*.jpg"));
ASSERT_SOME(jpgFiles);
EXPECT_EQ(1u, jpgFiles->size());
// Search test*.txt files in folder
Try<list<string>> testTxtFiles = fs::list(path::join(testdir, "*.txt"));
ASSERT_SOME(testTxtFiles);
EXPECT_EQ(2u, testTxtFiles->size());
// Verify that we return empty list when we provide an invalid path.
Try<list<string>> noFiles = fs::list("this_path_does_not_exist");
ASSERT_SOME(noFiles);
EXPECT_TRUE(noFiles->empty());
}
TEST_F(FsTest, Rename)
{
const string testdir =
path::join(os::getcwd(), id::UUID::random().toString());
ASSERT_SOME(os::mkdir(testdir)); // Create the directories.
// Now write some files.
const string file1 = path::join(testdir, "file1.txt");
const string file2 = path::join(testdir, "file2.txt");
const string file3 = path::join(testdir, "file3.jpg");
ASSERT_SOME(os::touch(file1));
ASSERT_SOME(os::touch(file2));
// Write something to `file1`.
const string message = "hello world!";
ASSERT_SOME(os::write(file1, message));
// Search all files in folder
Try<list<string>> allFiles = fs::list(path::join(testdir, "*"));
ASSERT_SOME(allFiles);
EXPECT_EQ(2u, allFiles->size());
// Rename a `file1` to `file3`, which does not exist yet. Verify `file3`
// contains the text that was in `file1`, and make sure the count of files in
// the directory has stayed the same.
EXPECT_SOME(os::rename(file1, file3));
Try<string> file3Contents = os::read(file3);
ASSERT_SOME(file3Contents);
EXPECT_EQ(message, file3Contents.get());
allFiles = fs::list(path::join(testdir, "*"));
ASSERT_SOME(allFiles);
EXPECT_EQ(2u, allFiles->size());
// Rename `file3` -> `file2`. `file2` exists, so this will replace it. Verify
// text in the file, and that the count of files in the directory have gone
// down.
EXPECT_SOME(os::rename(file3, file2));
Try<string> file2Contents = os::read(file2);
ASSERT_SOME(file2Contents);
EXPECT_EQ(message, file2Contents.get());
allFiles = fs::list(path::join(testdir, "*"));
ASSERT_SOME(allFiles);
EXPECT_EQ(1u, allFiles->size());
// Rename a fake file, verify failure.
const string fakeFile = testdir + "does_not_exist";
EXPECT_ERROR(os::rename(fakeFile, file1));
EXPECT_FALSE(os::exists(file1));
allFiles = fs::list(path::join(testdir, "*"));
ASSERT_SOME(allFiles);
EXPECT_EQ(1u, allFiles->size());
}
#ifdef __WINDOWS__
TEST_F(FsTest, IntFD)
{
const int_fd fd(INVALID_HANDLE_VALUE);
EXPECT_EQ(int_fd::Type::HANDLE, fd.type());
EXPECT_FALSE(fd.is_valid());
EXPECT_EQ(fd, int_fd(-1));
EXPECT_EQ(-1, fd);
EXPECT_LT(fd, 0);
EXPECT_GT(0, fd);
}
#endif // __WINDOWS__
// NOTE: These tests may not make a lot of sense on Linux, as `open`
// is expected to be implemented correctly by the system. However, on
// Windows we map the POSIX semantics of `open` to `CreateFile`, which
// this checks. These tests passing on Linux assert that the tests
// themselves are correct.
TEST_F(FsTest, Open)
{
const string testfile =
path::join(os::getcwd(), id::UUID::random().toString());
const string data = "data";
// Without `O_CREAT`, opening a non-existing file should fail.
EXPECT_FALSE(os::exists(testfile));
EXPECT_ERROR(os::open(testfile, O_RDONLY));
#ifdef __WINDOWS__
// `O_EXCL` without `O_CREAT` is undefined, but on Windows, we error.
EXPECT_ERROR(os::open(testfile, O_RDONLY | O_EXCL));
EXPECT_ERROR(os::open(testfile, O_RDONLY | O_EXCL | O_TRUNC));
#endif // __WINDOWS__
EXPECT_ERROR(os::open(testfile, O_RDONLY | O_TRUNC));
// With `O_CREAT | O_EXCL`, open a non-existing file should succeed.
Try<int_fd> fd = os::open(testfile, O_CREAT | O_EXCL | O_RDONLY, S_IRWXU);
ASSERT_SOME(fd);
EXPECT_TRUE(os::exists(testfile));
EXPECT_SOME(os::close(fd.get()));
// File already exists, so `O_EXCL` should fail.
EXPECT_ERROR(os::open(testfile, O_CREAT | O_EXCL | O_RDONLY));
EXPECT_ERROR(os::open(testfile, O_CREAT | O_EXCL | O_TRUNC | O_RDONLY));
// With `O_CREAT` but no `O_EXCL`, it should still open.
fd = os::open(testfile, O_CREAT | O_RDONLY);
ASSERT_SOME(fd);
EXPECT_SOME(os::close(fd.get()));
// `O_RDWR` should be able to write data, and read it back.
fd = os::open(testfile, O_RDWR);
ASSERT_SOME(fd);
EXPECT_SOME(os::write(fd.get(), data));
// Seek back to beginning to read the written data.
EXPECT_SOME_EQ(0, os::lseek(fd.get(), 0, SEEK_SET));
EXPECT_SOME_EQ(data, os::read(fd.get(), data.size()));
EXPECT_SOME(os::close(fd.get()));
// `O_RDONLY` should be able to read the previously written data,
// but fail writing more.
fd = os::open(testfile, O_RDONLY);
ASSERT_SOME(fd);
EXPECT_SOME_EQ(data, os::read(fd.get(), data.size()));
EXPECT_ERROR(os::write(fd.get(), data));
EXPECT_SOME(os::close(fd.get()));
// `O_WRONLY` should be able to overwrite the data, but fail reading.
fd = os::open(testfile, O_WRONLY);
ASSERT_SOME(fd);
EXPECT_SOME(os::write(fd.get(), data));
EXPECT_ERROR(os::read(fd.get(), data.size()));
EXPECT_SOME(os::close(fd.get()));
// `O_APPEND` should write to an existing file.
fd = os::open(testfile, O_APPEND | O_RDWR);
ASSERT_SOME(fd);
EXPECT_SOME_EQ(data, os::read(fd.get(), data.size()));
EXPECT_SOME(os::write(fd.get(), data));
const string datadata = "datadata";
// Seek back to beginning to read the written data.
EXPECT_SOME_EQ(0, os::lseek(fd.get(), 0, SEEK_SET));
EXPECT_SOME_EQ(datadata, os::read(fd.get(), datadata.size()));
EXPECT_SOME(os::close(fd.get()));
// `O_TRUNC` should truncate an existing file.
fd = os::open(testfile, O_TRUNC | O_RDWR);
ASSERT_SOME(fd);
EXPECT_NONE(os::read(fd.get(), 1));
EXPECT_SOME(os::write(fd.get(), data));
// Seek back to beginning to read the written data.
EXPECT_SOME_EQ(0, os::lseek(fd.get(), 0, SEEK_SET));
EXPECT_SOME_EQ(data, os::read(fd.get(), data.size()));
EXPECT_SOME(os::close(fd.get()));
// `O_CREAT | O_TRUNC` should create an empty file.
const string testtruncfile =
path::join(os::getcwd(), id::UUID::random().toString());
fd = os::open(testtruncfile, O_CREAT | O_TRUNC | O_RDWR, S_IRWXU);
ASSERT_SOME(fd);
EXPECT_NONE(os::read(fd.get(), 1));
EXPECT_SOME(os::write(fd.get(), data));
// Seek back to beginning to read the written data.
EXPECT_SOME_EQ(0, os::lseek(fd.get(), 0, SEEK_SET));
EXPECT_SOME_EQ(data, os::read(fd.get(), data.size()));
EXPECT_SOME(os::close(fd.get()));
}
TEST_F(FsTest, Close)
{
const string testfile =
path::join(os::getcwd(), id::UUID::random().toString());
ASSERT_SOME(os::touch(testfile));
ASSERT_TRUE(os::exists(testfile));
const string test_message1 = "test1";
const string error_message = "should not be written";
// Open a file, and verify that writing to that file descriptor succeeds
// before we close it, and fails after.
const Try<int_fd> fd = os::open(testfile, O_CREAT | O_RDWR);
ASSERT_SOME(fd);
#ifdef __WINDOWS__
ASSERT_EQ(fd->type(), os::WindowsFD::Type::HANDLE);
ASSERT_TRUE(fd->is_valid());
#endif // __WINDOWS__
ASSERT_SOME(os::write(fd.get(), test_message1));
EXPECT_SOME(os::close(fd.get()));
EXPECT_ERROR(os::write(fd.get(), error_message));
const Result<string> read = os::read(testfile);
EXPECT_SOME(read);
ASSERT_EQ(test_message1, read.get());
// Try `close` with invalid file descriptor.
// NOTE: This should work on both Windows and POSIX because the implicit
// conversion to `int_fd` maps `-1` to `INVALID_HANDLE_VALUE` on Windows.
EXPECT_ERROR(os::close(static_cast<int>(-1)));
#ifdef __WINDOWS__
// Try `close` with invalid `HANDLE` and `SOCKET`.
EXPECT_ERROR(os::close(int_fd(INVALID_HANDLE_VALUE)));
EXPECT_ERROR(os::close(int_fd(INVALID_SOCKET)));
#endif // __WINDOWS__
}
#if defined(__linux__) || defined(__APPLE__)
TEST_F(FsTest, Xattr)
{
const string file = path::join(os::getcwd(), id::UUID::random().toString());
// Create file.
ASSERT_SOME(os::touch(file));
ASSERT_TRUE(os::exists(file));
// Set an extended attribute.
Try<Nothing> setxattr = os::setxattr(
file,
"user.mesos.test",
"y",
0);
// Only run this test if extended attribute is supported.
if (setxattr.isError() && setxattr.error() == os::strerror(ENOTSUP)) {
return;
}
ASSERT_SOME(setxattr);
// Get the extended attribute.
Try<string> value = os::getxattr(file, "user.mesos.test");
ASSERT_SOME(value);
EXPECT_EQ(value.get(), "y");
// Remove the extended attribute.
ASSERT_SOME(os::removexattr(file, "user.mesos.test"));
// Get the extended attribute again which should not exist.
ASSERT_ERROR(os::getxattr(file, "user.mesos.test"));
}
#endif // __linux__ || __APPLE__
#ifdef __WINDOWS__
// Check if the overlapped field is set properly on Windows.
TEST_F(FsTest, Overlapped)
{
const string testfile =
path::join(sandbox.get(), id::UUID::random().toString());
// Case 1: `os::open` should return non-overlapped handles.
const Try<int_fd> fd1 = os::open(testfile, O_CREAT | O_TRUNC | O_RDWR);
ASSERT_SOME(fd1);
ASSERT_FALSE(fd1->is_overlapped());
const Try<int_fd> fd2 = os::dup(fd1.get());
ASSERT_SOME(fd2);
ASSERT_FALSE(fd2->is_overlapped());
EXPECT_SOME(os::close(fd1.get()));
EXPECT_SOME(os::close(fd2.get()));
// Case 2: `net::socket` should return overlapped handles.
const Try<int_fd> socket1 = net::socket(AF_INET, SOCK_STREAM, 0);
ASSERT_SOME(socket1);
ASSERT_TRUE(socket1->is_overlapped());
const Try<int_fd> socket2 = os::dup(socket1.get());
ASSERT_SOME(socket2);
ASSERT_TRUE(socket2->is_overlapped());
EXPECT_SOME(os::close(socket1.get()));
EXPECT_SOME(os::close(socket2.get()));
// Case 3: `os::pipe` should return overlapped values depending on the
// parameters given.
const Try<std::array<int_fd, 2>> pipes = os::pipe(true, false);
ASSERT_SOME(pipes);
const int_fd pipe1 = pipes.get()[0];
const int_fd pipe2 = pipes.get()[1];
ASSERT_TRUE(pipe1.is_overlapped());
ASSERT_FALSE(pipe2.is_overlapped());
Try<int_fd> pipe3 = os::dup(pipe1);
ASSERT_SOME(pipe3);
ASSERT_TRUE(pipe3->is_overlapped());
Try<int_fd> pipe4 = os::dup(pipe2);
ASSERT_SOME(pipe4);
ASSERT_FALSE(pipe4->is_overlapped());
EXPECT_SOME(os::close(pipe1));
EXPECT_SOME(os::close(pipe2));
EXPECT_SOME(os::close(pipe3.get()));
EXPECT_SOME(os::close(pipe4.get()));
}
TEST_F(FsTest, ReadWriteAsync)
{
const Try<std::array<int_fd, 2>> pipes = os::pipe(true, true);
ASSERT_SOME(pipes);
OVERLAPPED read_overlapped = {};
OVERLAPPED write_overlapped = {};
std::vector<char> write_buffer(64, 'A');
std::vector<char> read_buffer(write_buffer.size());
// Do an async read. This should return that the IO is pending.
const Result<size_t> result_read = os::read_async(
pipes.get()[0],
read_buffer.data(),
read_buffer.size(),
&read_overlapped);
ASSERT_NONE(result_read);
// Do an async write. This will return immediately.
const Result<size_t> result_write = os::write_async(
pipes.get()[1],
write_buffer.data(),
write_buffer.size(),
&write_overlapped);
ASSERT_SOME(result_write);
// Wait for read to finish.
DWORD bytes;
ASSERT_EQ(
TRUE,
::GetOverlappedResult(pipes.get()[0], &read_overlapped, &bytes, TRUE));
ASSERT_GT(bytes, static_cast<DWORD>(0));
ASSERT_EQ(result_write.get(), bytes);
ASSERT_EQ(
string(write_buffer.data(), result_write.get()),
string(read_buffer.data(), bytes));
EXPECT_SOME(os::close(pipes.get()[0]));
EXPECT_SOME(os::close(pipes.get()[1]));
}
TEST_F(FsTest, ReadWriteAsyncLargeBuffer)
{
const Try<std::array<int_fd, 2>> pipes = os::pipe(true, true);
ASSERT_SOME(pipes);
OVERLAPPED read_overlapped = {};
OVERLAPPED write_overlapped = {};
std::vector<char> write_buffer(1024 * 1024, 'A');
std::vector<char> read_buffer(write_buffer.size());
// This should return IO pending because it's larger than the internal pipe
// buffer.
const Result<size_t> result_write = os::write_async(
pipes.get()[1],
write_buffer.data(),
write_buffer.size(),
&write_overlapped);
ASSERT_NONE(result_write);
// This should return immediately.
const Result<size_t> result_read = os::read_async(
pipes.get()[0],
read_buffer.data(),
read_buffer.size(),
&read_overlapped);
ASSERT_SOME(result_read);
// Wait for write to finish.
DWORD bytes;
ASSERT_EQ(
TRUE,
::GetOverlappedResult(pipes.get()[1], &write_overlapped, &bytes, TRUE));
ASSERT_GT(bytes, static_cast<DWORD>(0));
ASSERT_EQ(result_read.get(), bytes);
ASSERT_EQ(
string(write_buffer.data(), result_read.get()),
string(read_buffer.data(), bytes));
EXPECT_SOME(os::close(pipes.get()[0]));
EXPECT_SOME(os::close(pipes.get()[1]));
}
#endif // __WINDOWS__
#ifndef __WINDOWS__
// This test verifies that the file descriptors returned by `os::lsof()`
// are all open file descriptors and contains stdin, stdout and stderr.
TEST_F(FsTest, Lsof)
{
Try<std::vector<int_fd>> fds = os::lsof();
ASSERT_SOME(fds);
// Verify each `fd` is an open file descriptor.
foreach (int_fd fd, fds.get()) {
EXPECT_NE(-1, ::fcntl(fd, F_GETFD));
}
EXPECT_NE(std::find(fds->begin(), fds->end(), 0), fds->end());
EXPECT_NE(std::find(fds->begin(), fds->end(), 1), fds->end());
EXPECT_NE(std::find(fds->begin(), fds->end(), 2), fds->end());
}
#endif // __WINDOWS__