blob: f3eae2fe79a7e4d5617f53f44057c50d26f0ca02 [file] [log] [blame]
// Copyright (c) 2016 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "filesystem.h"
#include "test.h"
#include <wchar.h>
namespace kj {
namespace {
KJ_TEST("Path") {
KJ_EXPECT(Path(nullptr).toString() == ".");
KJ_EXPECT(Path(nullptr).toString(true) == "/");
KJ_EXPECT(Path("foo").toString() == "foo");
KJ_EXPECT(Path("foo").toString(true) == "/foo");
KJ_EXPECT(Path({"foo", "bar"}).toString() == "foo/bar");
KJ_EXPECT(Path({"foo", "bar"}).toString(true) == "/foo/bar");
KJ_EXPECT(Path::parse("foo/bar").toString() == "foo/bar");
KJ_EXPECT(Path::parse("foo//bar").toString() == "foo/bar");
KJ_EXPECT(Path::parse("foo/./bar").toString() == "foo/bar");
KJ_EXPECT(Path::parse("foo/../bar").toString() == "bar");
KJ_EXPECT(Path::parse("foo/bar/..").toString() == "foo");
KJ_EXPECT(Path::parse("foo/bar/../..").toString() == ".");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz").toString() == "foo/bar/baz");
KJ_EXPECT(Path({"foo", "bar"}).eval("./baz").toString() == "foo/bar/baz");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz/qux").toString() == "foo/bar/baz/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz//qux").toString() == "foo/bar/baz/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz/./qux").toString() == "foo/bar/baz/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz/../qux").toString() == "foo/bar/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz/qux/..").toString() == "foo/bar/baz");
KJ_EXPECT(Path({"foo", "bar"}).eval("../baz").toString() == "foo/baz");
KJ_EXPECT(Path({"foo", "bar"}).eval("baz/../../qux/").toString() == "foo/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("/baz/qux").toString() == "baz/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("//baz/qux").toString() == "baz/qux");
KJ_EXPECT(Path({"foo", "bar"}).eval("/baz/../qux").toString() == "qux");
KJ_EXPECT(Path({"foo", "bar"}).basename()[0] == "bar");
KJ_EXPECT(Path({"foo", "bar", "baz"}).parent().toString() == "foo/bar");
KJ_EXPECT(Path({"foo", "bar"}).append("baz").toString() == "foo/bar/baz");
KJ_EXPECT(Path({"foo", "bar"}).append(Path({"baz", "qux"})).toString() == "foo/bar/baz/qux");
{
// Test methods which are overloaded for && on a non-rvalue path.
Path path({"foo", "bar"});
KJ_EXPECT(path.eval("baz").toString() == "foo/bar/baz");
KJ_EXPECT(path.eval("./baz").toString() == "foo/bar/baz");
KJ_EXPECT(path.eval("baz/qux").toString() == "foo/bar/baz/qux");
KJ_EXPECT(path.eval("baz//qux").toString() == "foo/bar/baz/qux");
KJ_EXPECT(path.eval("baz/./qux").toString() == "foo/bar/baz/qux");
KJ_EXPECT(path.eval("baz/../qux").toString() == "foo/bar/qux");
KJ_EXPECT(path.eval("baz/qux/..").toString() == "foo/bar/baz");
KJ_EXPECT(path.eval("../baz").toString() == "foo/baz");
KJ_EXPECT(path.eval("baz/../../qux/").toString() == "foo/qux");
KJ_EXPECT(path.eval("/baz/qux").toString() == "baz/qux");
KJ_EXPECT(path.eval("/baz/../qux").toString() == "qux");
KJ_EXPECT(path.basename()[0] == "bar");
KJ_EXPECT(path.parent().toString() == "foo");
KJ_EXPECT(path.append("baz").toString() == "foo/bar/baz");
KJ_EXPECT(path.append(Path({"baz", "qux"})).toString() == "foo/bar/baz/qux");
}
KJ_EXPECT(kj::str(Path({"foo", "bar"})) == "foo/bar");
}
KJ_TEST("Path comparisons") {
KJ_EXPECT(Path({"foo", "bar"}) == Path({"foo", "bar"}));
KJ_EXPECT(!(Path({"foo", "bar"}) != Path({"foo", "bar"})));
KJ_EXPECT(Path({"foo", "bar"}) != Path({"foo", "baz"}));
KJ_EXPECT(!(Path({"foo", "bar"}) == Path({"foo", "baz"})));
KJ_EXPECT(Path({"foo", "bar"}) != Path({"fob", "bar"}));
KJ_EXPECT(Path({"foo", "bar"}) != Path({"foo", "bar", "baz"}));
KJ_EXPECT(Path({"foo", "bar", "baz"}) != Path({"foo", "bar"}));
KJ_EXPECT(Path({"foo", "bar"}) <= Path({"foo", "bar"}));
KJ_EXPECT(Path({"foo", "bar"}) >= Path({"foo", "bar"}));
KJ_EXPECT(!(Path({"foo", "bar"}) < Path({"foo", "bar"})));
KJ_EXPECT(!(Path({"foo", "bar"}) > Path({"foo", "bar"})));
KJ_EXPECT(Path({"foo", "bar"}) < Path({"foo", "bar", "baz"}));
KJ_EXPECT(!(Path({"foo", "bar"}) > Path({"foo", "bar", "baz"})));
KJ_EXPECT(Path({"foo", "bar", "baz"}) > Path({"foo", "bar"}));
KJ_EXPECT(!(Path({"foo", "bar", "baz"}) < Path({"foo", "bar"})));
KJ_EXPECT(Path({"foo", "bar"}) < Path({"foo", "baz"}));
KJ_EXPECT(Path({"foo", "bar"}) > Path({"foo", "baa"}));
KJ_EXPECT(Path({"foo", "bar"}) > Path({"foo"}));
KJ_EXPECT(Path({"foo", "bar"}).startsWith(Path({})));
KJ_EXPECT(Path({"foo", "bar"}).startsWith(Path({"foo"})));
KJ_EXPECT(Path({"foo", "bar"}).startsWith(Path({"foo", "bar"})));
KJ_EXPECT(!Path({"foo", "bar"}).startsWith(Path({"foo", "bar", "baz"})));
KJ_EXPECT(!Path({"foo", "bar"}).startsWith(Path({"foo", "baz"})));
KJ_EXPECT(!Path({"foo", "bar"}).startsWith(Path({"baz", "foo", "bar"})));
KJ_EXPECT(!Path({"foo", "bar"}).startsWith(Path({"baz"})));
KJ_EXPECT(Path({"foo", "bar"}).endsWith(Path({})));
KJ_EXPECT(Path({"foo", "bar"}).endsWith(Path({"bar"})));
KJ_EXPECT(Path({"foo", "bar"}).endsWith(Path({"foo", "bar"})));
KJ_EXPECT(!Path({"foo", "bar"}).endsWith(Path({"baz", "foo", "bar"})));
KJ_EXPECT(!Path({"foo", "bar"}).endsWith(Path({"fob", "bar"})));
KJ_EXPECT(!Path({"foo", "bar"}).endsWith(Path({"foo", "bar", "baz"})));
KJ_EXPECT(!Path({"foo", "bar"}).endsWith(Path({"baz"})));
}
KJ_TEST("Path exceptions") {
KJ_EXPECT_THROW_MESSAGE("invalid path component", Path(""));
KJ_EXPECT_THROW_MESSAGE("invalid path component", Path("."));
KJ_EXPECT_THROW_MESSAGE("invalid path component", Path(".."));
KJ_EXPECT_THROW_MESSAGE("NUL character", Path(StringPtr("foo\0bar", 7)));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting", Path::parse(".."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting", Path::parse("../foo"));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting", Path::parse("foo/../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("expected a relative path", Path::parse("/foo"));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("NUL character", Path::parse(kj::StringPtr("foo\0bar", 7)));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).eval("../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).eval("../baz/../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).eval("baz/../../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).eval("/.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).eval("/baz/../.."));
KJ_EXPECT_THROW_MESSAGE("root path has no basename", Path(nullptr).basename());
KJ_EXPECT_THROW_MESSAGE("root path has no parent", Path(nullptr).parent());
}
constexpr kj::ArrayPtr<const wchar_t> operator "" _a(const wchar_t* str, size_t n) {
return { str, n };
}
KJ_TEST("Win32 Path") {
KJ_EXPECT(Path({"foo", "bar"}).toWin32String() == "foo\\bar");
KJ_EXPECT(Path({"foo", "bar"}).toWin32String(true) == "\\\\foo\\bar");
KJ_EXPECT(Path({"c:", "foo", "bar"}).toWin32String(true) == "c:\\foo\\bar");
KJ_EXPECT(Path({"A:", "foo", "bar"}).toWin32String(true) == "A:\\foo\\bar");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz").toWin32String() == "foo\\bar\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("./baz").toWin32String() == "foo\\bar\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz/qux").toWin32String() == "foo\\bar\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz//qux").toWin32String() == "foo\\bar\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz/./qux").toWin32String() == "foo\\bar\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz/../qux").toWin32String() == "foo\\bar\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz/qux/..").toWin32String() == "foo\\bar\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("../baz").toWin32String() == "foo\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz/../../qux/").toWin32String() == "foo\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32(".\\baz").toWin32String() == "foo\\bar\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\qux").toWin32String() == "foo\\bar\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\\\qux").toWin32String() == "foo\\bar\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\.\\qux").toWin32String() == "foo\\bar\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\..\\qux").toWin32String() == "foo\\bar\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\qux\\..").toWin32String() == "foo\\bar\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("..\\baz").toWin32String() == "foo\\baz");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\..\\..\\qux\\").toWin32String() == "foo\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("baz\\../..\\qux/").toWin32String() == "foo\\qux");
KJ_EXPECT(Path({"c:", "foo", "bar"}).evalWin32("/baz/qux")
.toWin32String(true) == "c:\\baz\\qux");
KJ_EXPECT(Path({"c:", "foo", "bar"}).evalWin32("\\baz\\qux")
.toWin32String(true) == "c:\\baz\\qux");
KJ_EXPECT(Path({"c:", "foo", "bar"}).evalWin32("d:\\baz\\qux")
.toWin32String(true) == "d:\\baz\\qux");
KJ_EXPECT(Path({"c:", "foo", "bar"}).evalWin32("d:\\baz\\..\\qux")
.toWin32String(true) == "d:\\qux");
KJ_EXPECT(Path({"c:", "foo", "bar"}).evalWin32("\\\\baz\\qux")
.toWin32String(true) == "\\\\baz\\qux");
KJ_EXPECT(Path({"foo", "bar"}).evalWin32("d:\\baz\\..\\qux")
.toWin32String(true) == "d:\\qux");
KJ_EXPECT(Path({"foo", "bar", "baz"}).evalWin32("\\qux")
.toWin32String(true) == "\\\\foo\\bar\\qux");
KJ_EXPECT(Path({"foo", "bar"}).forWin32Api(false) == L"foo\\bar");
KJ_EXPECT(Path({"foo", "bar"}).forWin32Api(true) == L"\\\\?\\UNC\\foo\\bar");
KJ_EXPECT(Path({"c:", "foo", "bar"}).forWin32Api(true) == L"\\\\?\\c:\\foo\\bar");
KJ_EXPECT(Path({"A:", "foo", "bar"}).forWin32Api(true) == L"\\\\?\\A:\\foo\\bar");
KJ_EXPECT(Path::parseWin32Api(L"\\\\?\\c:\\foo\\bar"_a).toString() == "c:/foo/bar");
KJ_EXPECT(Path::parseWin32Api(L"\\\\?\\UNC\\foo\\bar"_a).toString() == "foo/bar");
KJ_EXPECT(Path::parseWin32Api(L"c:\\foo\\bar"_a).toString() == "c:/foo/bar");
KJ_EXPECT(Path::parseWin32Api(L"\\\\foo\\bar"_a).toString() == "foo/bar");
}
KJ_TEST("Win32 Path exceptions") {
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("colons are prohibited",
Path({"c:", "foo", "bar"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("colons are prohibited",
Path({"c:", "foo:bar"}).toWin32String(true));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"con"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"CON", "bar"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"foo", "cOn"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"prn"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"aux"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"NUL"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"nul.txt"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"com3"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"lpt9"}).toWin32String());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("DOS reserved name", Path({"com1.hello"}).toWin32String());
KJ_EXPECT_THROW_MESSAGE("drive letter or netbios", Path({"?", "foo"}).toWin32String(true));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).evalWin32("../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).evalWin32("../baz/../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).evalWin32("baz/../../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"foo", "bar"}).evalWin32("c:\\..\\.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("break out of starting",
Path({"c:", "foo", "bar"}).evalWin32("/baz/../../.."));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("must specify drive letter",
Path({"foo"}).evalWin32("\\baz\\qux"));
}
KJ_TEST("WriteMode operators") {
WriteMode createOrModify = WriteMode::CREATE | WriteMode::MODIFY;
KJ_EXPECT(has(createOrModify, WriteMode::MODIFY));
KJ_EXPECT(has(createOrModify, WriteMode::CREATE));
KJ_EXPECT(!has(createOrModify, WriteMode::CREATE_PARENT));
KJ_EXPECT(has(createOrModify, createOrModify));
KJ_EXPECT(!has(createOrModify, createOrModify | WriteMode::CREATE_PARENT));
KJ_EXPECT(!has(createOrModify, WriteMode::CREATE | WriteMode::CREATE_PARENT));
KJ_EXPECT(!has(WriteMode::CREATE, createOrModify));
KJ_EXPECT(createOrModify != WriteMode::MODIFY);
KJ_EXPECT(createOrModify != WriteMode::CREATE);
KJ_EXPECT(createOrModify - WriteMode::CREATE == WriteMode::MODIFY);
KJ_EXPECT(WriteMode::CREATE + WriteMode::MODIFY == createOrModify);
// Adding existing bit / subtracting non-existing bit are no-ops.
KJ_EXPECT(createOrModify + WriteMode::MODIFY == createOrModify);
KJ_EXPECT(createOrModify - WriteMode::CREATE_PARENT == createOrModify);
}
// ======================================================================================
class TestClock final: public Clock {
public:
void tick() {
time += 1 * SECONDS;
}
Date now() const override { return time; }
void expectChanged(const FsNode& file) {
KJ_EXPECT(file.stat().lastModified == time);
time += 1 * SECONDS;
}
void expectUnchanged(const FsNode& file) {
KJ_EXPECT(file.stat().lastModified != time);
}
private:
Date time = UNIX_EPOCH + 1 * SECONDS;
};
KJ_TEST("InMemoryFile") {
TestClock clock;
auto file = newInMemoryFile(clock);
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == "");
clock.expectUnchanged(*file);
file->writeAll("foo");
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == "foo");
file->write(3, StringPtr("bar").asBytes());
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == "foobar");
file->write(3, StringPtr("baz").asBytes());
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == "foobaz");
file->write(9, StringPtr("qux").asBytes());
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == kj::StringPtr("foobaz\0\0\0qux", 12));
file->truncate(6);
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == "foobaz");
file->truncate(18);
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == kj::StringPtr("foobaz\0\0\0\0\0\0\0\0\0\0\0\0", 18));
{
auto mapping = file->mmap(0, 18);
auto privateMapping = file->mmapPrivate(0, 18);
auto writableMapping = file->mmapWritable(0, 18);
clock.expectUnchanged(*file);
KJ_EXPECT(mapping.size() == 18);
KJ_EXPECT(privateMapping.size() == 18);
KJ_EXPECT(writableMapping->get().size() == 18);
clock.expectUnchanged(*file);
KJ_EXPECT(writableMapping->get().begin() == mapping.begin());
KJ_EXPECT(privateMapping.begin() != mapping.begin());
KJ_EXPECT(kj::str(mapping.slice(0, 6).asChars()) == "foobaz");
KJ_EXPECT(kj::str(privateMapping.slice(0, 6).asChars()) == "foobaz");
clock.expectUnchanged(*file);
file->write(0, StringPtr("qux").asBytes());
clock.expectChanged(*file);
KJ_EXPECT(kj::str(mapping.slice(0, 6).asChars()) == "quxbaz");
KJ_EXPECT(kj::str(privateMapping.slice(0, 6).asChars()) == "foobaz");
file->write(12, StringPtr("corge").asBytes());
KJ_EXPECT(kj::str(mapping.slice(12, 17).asChars()) == "corge");
// Can shrink.
file->truncate(6);
KJ_EXPECT(kj::str(mapping.slice(12, 17).asChars()) == kj::StringPtr("\0\0\0\0\0", 5));
// Can regrow.
file->truncate(18);
KJ_EXPECT(kj::str(mapping.slice(12, 17).asChars()) == kj::StringPtr("\0\0\0\0\0", 5));
// Can't grow past previoous capacity.
KJ_EXPECT_THROW_MESSAGE("cannot resize the file backing store", file->truncate(100));
clock.expectChanged(*file);
writableMapping->changed(writableMapping->get().slice(0, 3));
clock.expectChanged(*file);
writableMapping->sync(writableMapping->get().slice(0, 3));
clock.expectChanged(*file);
}
// But now we can since the mapping is gone.
file->truncate(100);
file->truncate(6);
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == "quxbaz");
file->zero(3, 3);
clock.expectChanged(*file);
KJ_EXPECT(file->readAllText() == StringPtr("qux\0\0\0", 6));
}
KJ_TEST("InMemoryFile::copy()") {
TestClock clock;
auto source = newInMemoryFile(clock);
source->writeAll("foobarbaz");
auto dest = newInMemoryFile(clock);
dest->writeAll("quxcorge");
clock.expectChanged(*dest);
KJ_EXPECT(dest->copy(3, *source, 6, kj::maxValue) == 3);
clock.expectChanged(*dest);
KJ_EXPECT(dest->readAllText() == "quxbazge");
KJ_EXPECT(dest->copy(0, *source, 3, 4) == 4);
clock.expectChanged(*dest);
KJ_EXPECT(dest->readAllText() == "barbazge");
KJ_EXPECT(dest->copy(0, *source, 128, kj::maxValue) == 0);
clock.expectUnchanged(*dest);
KJ_EXPECT(dest->copy(4, *source, 3, 0) == 0);
clock.expectUnchanged(*dest);
String bigString = strArray(repeat("foobar", 10000), "");
source->truncate(bigString.size() + 1000);
source->write(123, bigString.asBytes());
dest->copy(321, *source, 123, bigString.size());
KJ_EXPECT(dest->readAllText().slice(321) == bigString);
}
KJ_TEST("File::copy()") {
TestClock clock;
auto source = newInMemoryFile(clock);
source->writeAll("foobarbaz");
auto dest = newInMemoryFile(clock);
dest->writeAll("quxcorge");
clock.expectChanged(*dest);
KJ_EXPECT(dest->File::copy(3, *source, 6, kj::maxValue) == 3);
clock.expectChanged(*dest);
KJ_EXPECT(dest->readAllText() == "quxbazge");
KJ_EXPECT(dest->File::copy(0, *source, 3, 4) == 4);
clock.expectChanged(*dest);
KJ_EXPECT(dest->readAllText() == "barbazge");
KJ_EXPECT(dest->File::copy(0, *source, 128, kj::maxValue) == 0);
clock.expectUnchanged(*dest);
KJ_EXPECT(dest->File::copy(4, *source, 3, 0) == 0);
clock.expectUnchanged(*dest);
String bigString = strArray(repeat("foobar", 10000), "");
source->truncate(bigString.size() + 1000);
source->write(123, bigString.asBytes());
dest->File::copy(321, *source, 123, bigString.size());
KJ_EXPECT(dest->readAllText().slice(321) == bigString);
}
KJ_TEST("InMemoryDirectory") {
TestClock clock;
auto dir = newInMemoryDirectory(clock);
clock.expectChanged(*dir);
KJ_EXPECT(dir->listNames() == nullptr);
KJ_EXPECT(dir->listEntries() == nullptr);
KJ_EXPECT(!dir->exists(Path("foo")));
KJ_EXPECT(dir->tryOpenFile(Path("foo")) == nullptr);
KJ_EXPECT(dir->tryOpenFile(Path("foo"), WriteMode::MODIFY) == nullptr);
clock.expectUnchanged(*dir);
{
auto file = dir->openFile(Path("foo"), WriteMode::CREATE);
clock.expectChanged(*dir);
file->writeAll("foobar");
clock.expectUnchanged(*dir);
}
clock.expectUnchanged(*dir);
KJ_EXPECT(dir->exists(Path("foo")));
clock.expectUnchanged(*dir);
{
auto stats = dir->lstat(Path("foo"));
clock.expectUnchanged(*dir);
KJ_EXPECT(stats.type == FsNode::Type::FILE);
KJ_EXPECT(stats.size == 6);
}
{
auto list = dir->listNames();
clock.expectUnchanged(*dir);
KJ_ASSERT(list.size() == 1);
KJ_EXPECT(list[0] == "foo");
}
{
auto list = dir->listEntries();
clock.expectUnchanged(*dir);
KJ_ASSERT(list.size() == 1);
KJ_EXPECT(list[0].name == "foo");
KJ_EXPECT(list[0].type == FsNode::Type::FILE);
}
KJ_EXPECT(dir->openFile(Path("foo"))->readAllText() == "foobar");
clock.expectUnchanged(*dir);
KJ_EXPECT(dir->tryOpenFile(Path({"foo", "bar"}), WriteMode::MODIFY) == nullptr);
KJ_EXPECT(dir->tryOpenFile(Path({"bar", "baz"}), WriteMode::MODIFY) == nullptr);
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("parent is not a directory",
dir->tryOpenFile(Path({"bar", "baz"}), WriteMode::CREATE));
clock.expectUnchanged(*dir);
{
auto file = dir->openFile(Path({"bar", "baz"}), WriteMode::CREATE | WriteMode::CREATE_PARENT);
clock.expectChanged(*dir);
file->writeAll("bazqux");
clock.expectUnchanged(*dir);
}
clock.expectUnchanged(*dir);
KJ_EXPECT(dir->openFile(Path({"bar", "baz"}))->readAllText() == "bazqux");
clock.expectUnchanged(*dir);
{
auto stats = dir->lstat(Path("bar"));
clock.expectUnchanged(*dir);
KJ_EXPECT(stats.type == FsNode::Type::DIRECTORY);
}
{
auto list = dir->listNames();
clock.expectUnchanged(*dir);
KJ_ASSERT(list.size() == 2);
KJ_EXPECT(list[0] == "bar");
KJ_EXPECT(list[1] == "foo");
}
{
auto list = dir->listEntries();
clock.expectUnchanged(*dir);
KJ_ASSERT(list.size() == 2);
KJ_EXPECT(list[0].name == "bar");
KJ_EXPECT(list[0].type == FsNode::Type::DIRECTORY);
KJ_EXPECT(list[1].name == "foo");
KJ_EXPECT(list[1].type == FsNode::Type::FILE);
}
{
auto subdir = dir->openSubdir(Path("bar"));
clock.expectUnchanged(*dir);
clock.expectUnchanged(*subdir);
KJ_EXPECT(subdir->openFile(Path("baz"))->readAllText() == "bazqux");
clock.expectUnchanged(*subdir);
}
auto subdir = dir->openSubdir(Path("corge"), WriteMode::CREATE);
clock.expectChanged(*dir);
subdir->openFile(Path("grault"), WriteMode::CREATE)->writeAll("garply");
clock.expectUnchanged(*dir);
clock.expectChanged(*subdir);
KJ_EXPECT(dir->openFile(Path({"corge", "grault"}))->readAllText() == "garply");
dir->openFile(Path({"corge", "grault"}), WriteMode::CREATE | WriteMode::MODIFY)
->write(0, StringPtr("rag").asBytes());
KJ_EXPECT(dir->openFile(Path({"corge", "grault"}))->readAllText() == "ragply");
clock.expectUnchanged(*dir);
{
auto replacer =
dir->replaceFile(Path({"corge", "grault"}), WriteMode::CREATE | WriteMode::MODIFY);
clock.expectUnchanged(*subdir);
replacer->get().writeAll("rag");
clock.expectUnchanged(*subdir);
// Don't commit.
}
clock.expectUnchanged(*subdir);
KJ_EXPECT(dir->openFile(Path({"corge", "grault"}))->readAllText() == "ragply");
{
auto replacer =
dir->replaceFile(Path({"corge", "grault"}), WriteMode::CREATE | WriteMode::MODIFY);
clock.expectUnchanged(*subdir);
replacer->get().writeAll("rag");
clock.expectUnchanged(*subdir);
replacer->commit();
clock.expectChanged(*subdir);
KJ_EXPECT(dir->openFile(Path({"corge", "grault"}))->readAllText() == "rag");
}
KJ_EXPECT(dir->openFile(Path({"corge", "grault"}))->readAllText() == "rag");
{
auto appender = dir->appendFile(Path({"corge", "grault"}), WriteMode::MODIFY);
appender->write("waldo", 5);
appender->write("fred", 4);
}
KJ_EXPECT(dir->openFile(Path({"corge", "grault"}))->readAllText() == "ragwaldofred");
KJ_EXPECT(dir->exists(Path("foo")));
clock.expectUnchanged(*dir);
dir->remove(Path("foo"));
clock.expectChanged(*dir);
KJ_EXPECT(!dir->exists(Path("foo")));
KJ_EXPECT(!dir->tryRemove(Path("foo")));
clock.expectUnchanged(*dir);
KJ_EXPECT(dir->exists(Path({"bar", "baz"})));
clock.expectUnchanged(*dir);
dir->remove(Path({"bar", "baz"}));
clock.expectUnchanged(*dir);
KJ_EXPECT(!dir->exists(Path({"bar", "baz"})));
KJ_EXPECT(dir->exists(Path("bar")));
KJ_EXPECT(!dir->tryRemove(Path({"bar", "baz"})));
clock.expectUnchanged(*dir);
KJ_EXPECT(dir->exists(Path("corge")));
KJ_EXPECT(dir->exists(Path({"corge", "grault"})));
clock.expectUnchanged(*dir);
dir->remove(Path("corge"));
clock.expectChanged(*dir);
KJ_EXPECT(!dir->exists(Path("corge")));
KJ_EXPECT(!dir->exists(Path({"corge", "grault"})));
KJ_EXPECT(!dir->tryRemove(Path("corge")));
clock.expectUnchanged(*dir);
}
KJ_TEST("InMemoryDirectory symlinks") {
TestClock clock;
auto dir = newInMemoryDirectory(clock);
clock.expectChanged(*dir);
dir->symlink(Path("foo"), "bar/qux/../baz", WriteMode::CREATE);
clock.expectChanged(*dir);
KJ_EXPECT(!dir->trySymlink(Path("foo"), "bar/qux/../baz", WriteMode::CREATE));
clock.expectUnchanged(*dir);
{
auto stats = dir->lstat(Path("foo"));
clock.expectUnchanged(*dir);
KJ_EXPECT(stats.type == FsNode::Type::SYMLINK);
}
KJ_EXPECT(dir->readlink(Path("foo")) == "bar/qux/../baz");
// Broken link into non-existing directory cannot be opened in any mode.
KJ_EXPECT(dir->tryOpenFile(Path("foo")) == nullptr);
KJ_EXPECT(dir->tryOpenFile(Path("foo"), WriteMode::CREATE) == nullptr);
KJ_EXPECT(dir->tryOpenFile(Path("foo"), WriteMode::MODIFY) == nullptr);
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("parent is not a directory",
dir->tryOpenFile(Path("foo"), WriteMode::CREATE | WriteMode::MODIFY));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("parent is not a directory",
dir->tryOpenFile(Path("foo"),
WriteMode::CREATE | WriteMode::MODIFY | WriteMode::CREATE_PARENT));
// Create the directory.
auto subdir = dir->openSubdir(Path("bar"), WriteMode::CREATE);
clock.expectChanged(*dir);
// Link still points to non-existing file so cannot be open in most modes.
KJ_EXPECT(dir->tryOpenFile(Path("foo")) == nullptr);
KJ_EXPECT(dir->tryOpenFile(Path("foo"), WriteMode::CREATE) == nullptr);
KJ_EXPECT(dir->tryOpenFile(Path("foo"), WriteMode::MODIFY) == nullptr);
clock.expectUnchanged(*dir);
// But... CREATE | MODIFY works.
dir->openFile(Path("foo"), WriteMode::CREATE | WriteMode::MODIFY)
->writeAll("foobar");
clock.expectUnchanged(*dir); // Change is only to subdir!
KJ_EXPECT(dir->openFile(Path({"bar", "baz"}))->readAllText() == "foobar");
KJ_EXPECT(dir->openFile(Path("foo"))->readAllText() == "foobar");
KJ_EXPECT(dir->openFile(Path("foo"), WriteMode::MODIFY)->readAllText() == "foobar");
// operations that modify the symlink
dir->symlink(Path("foo"), "corge", WriteMode::MODIFY);
KJ_EXPECT(dir->openFile(Path({"bar", "baz"}))->readAllText() == "foobar");
KJ_EXPECT(dir->readlink(Path("foo")) == "corge");
KJ_EXPECT(!dir->exists(Path("foo")));
KJ_EXPECT(dir->lstat(Path("foo")).type == FsNode::Type::SYMLINK);
KJ_EXPECT(dir->tryOpenFile(Path("foo")) == nullptr);
dir->remove(Path("foo"));
KJ_EXPECT(!dir->exists(Path("foo")));
KJ_EXPECT(dir->tryOpenFile(Path("foo")) == nullptr);
}
KJ_TEST("InMemoryDirectory link") {
TestClock clock;
auto src = newInMemoryDirectory(clock);
auto dst = newInMemoryDirectory(clock);
src->openFile(Path({"foo", "bar"}), WriteMode::CREATE | WriteMode::CREATE_PARENT)
->writeAll("foobar");
src->openFile(Path({"foo", "baz", "qux"}), WriteMode::CREATE | WriteMode::CREATE_PARENT)
->writeAll("bazqux");
clock.expectChanged(*src);
clock.expectUnchanged(*dst);
dst->transfer(Path("link"), WriteMode::CREATE, *src, Path("foo"), TransferMode::LINK);
clock.expectUnchanged(*src);
clock.expectChanged(*dst);
KJ_EXPECT(dst->openFile(Path({"link", "bar"}))->readAllText() == "foobar");
KJ_EXPECT(dst->openFile(Path({"link", "baz", "qux"}))->readAllText() == "bazqux");
KJ_EXPECT(dst->exists(Path({"link", "bar"})));
src->remove(Path({"foo", "bar"}));
KJ_EXPECT(!dst->exists(Path({"link", "bar"})));
}
KJ_TEST("InMemoryDirectory copy") {
TestClock clock;
auto src = newInMemoryDirectory(clock);
auto dst = newInMemoryDirectory(clock);
src->openFile(Path({"foo", "bar"}), WriteMode::CREATE | WriteMode::CREATE_PARENT)
->writeAll("foobar");
src->openFile(Path({"foo", "baz", "qux"}), WriteMode::CREATE | WriteMode::CREATE_PARENT)
->writeAll("bazqux");
clock.expectChanged(*src);
clock.expectUnchanged(*dst);
dst->transfer(Path("link"), WriteMode::CREATE, *src, Path("foo"), TransferMode::COPY);
clock.expectUnchanged(*src);
clock.expectChanged(*dst);
KJ_EXPECT(src->openFile(Path({"foo", "bar"}))->readAllText() == "foobar");
KJ_EXPECT(src->openFile(Path({"foo", "baz", "qux"}))->readAllText() == "bazqux");
KJ_EXPECT(dst->openFile(Path({"link", "bar"}))->readAllText() == "foobar");
KJ_EXPECT(dst->openFile(Path({"link", "baz", "qux"}))->readAllText() == "bazqux");
KJ_EXPECT(dst->exists(Path({"link", "bar"})));
src->remove(Path({"foo", "bar"}));
KJ_EXPECT(dst->openFile(Path({"link", "bar"}))->readAllText() == "foobar");
}
KJ_TEST("InMemoryDirectory move") {
TestClock clock;
auto src = newInMemoryDirectory(clock);
auto dst = newInMemoryDirectory(clock);
src->openFile(Path({"foo", "bar"}), WriteMode::CREATE | WriteMode::CREATE_PARENT)
->writeAll("foobar");
src->openFile(Path({"foo", "baz", "qux"}), WriteMode::CREATE | WriteMode::CREATE_PARENT)
->writeAll("bazqux");
clock.expectChanged(*src);
clock.expectUnchanged(*dst);
dst->transfer(Path("link"), WriteMode::CREATE, *src, Path("foo"), TransferMode::MOVE);
clock.expectChanged(*src);
KJ_EXPECT(!src->exists(Path({"foo"})));
KJ_EXPECT(dst->openFile(Path({"link", "bar"}))->readAllText() == "foobar");
KJ_EXPECT(dst->openFile(Path({"link", "baz", "qux"}))->readAllText() == "bazqux");
}
KJ_TEST("InMemoryDirectory createTemporary") {
TestClock clock;
auto dir = newInMemoryDirectory(clock);
auto file = dir->createTemporary();
file->writeAll("foobar");
KJ_EXPECT(file->readAllText() == "foobar");
KJ_EXPECT(dir->listNames() == nullptr);
}
} // namespace
} // namespace kj