blob: c351113bd8fcb92f28b19679a602f41d3aa09093 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.
*/
// Author: jmarantz@google.com (Joshua Marantz)
#include "pagespeed/kernel/base/file_system_test_base.h"
#include <cstddef>
#include <algorithm>
#include <vector>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/file_system.h"
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/timer.h"
namespace net_instaweb {
// TODO(huibao): Rename FileSystemTest to FileSystemTestBase.
FileSystemTest::FileSystemTest()
: test_tmpdir_(StrCat(GTestTempDir(), "/file_system_test_base")) {}
FileSystemTest::~FileSystemTest() {}
// Used by TestDirInfo to make sure vector of FileInfos can be compared
// consistently
struct CompareByName {
public:
bool operator()(const FileSystem::FileInfo& one,
const FileSystem::FileInfo& two) const {
return one.name < two.name;
}
};
GoogleString FileSystemTest::WriteNewFile(const StringPiece& suffix,
const GoogleString& content) {
GoogleString filename = StrCat(test_tmpdir(), suffix);
// Make sure we don't read an old file.
DeleteRecursively(filename);
EXPECT_TRUE(file_system()->WriteFile(filename.c_str(), content, &handler_));
return filename;
}
// Check that a file has been read.
void FileSystemTest::CheckRead(const GoogleString& filename,
const GoogleString& expected_contents) {
GoogleString buffer;
ASSERT_TRUE(file_system()->ReadFile(filename.c_str(), &buffer, &handler_));
EXPECT_EQ(buffer, expected_contents);
}
// Check that a file has been read.
void FileSystemTest::CheckInputFileRead(const GoogleString& filename,
const GoogleString& expected_contents) {
FileSystem::InputFile* file =
file_system()->OpenInputFile(filename.c_str(), &handler_);
ASSERT_TRUE(file != NULL);
GoogleString buffer;
ASSERT_TRUE(file_system()->ReadFile(file, &buffer, &handler_));
EXPECT_EQ(buffer, expected_contents);
}
// Make sure we can no longer read the file by the old name. Note
// that this will spew some error messages into the log file, and
// we can add a null_message_handler implementation to
// swallow them, if they become annoying.
void FileSystemTest::CheckDoesNotExist(const GoogleString& filename) {
GoogleString read_buffer;
EXPECT_FALSE(file_system()->ReadFile(filename.c_str(), &read_buffer,
&handler_));
EXPECT_TRUE(file_system()->Exists(filename.c_str(), &handler_).is_false());
}
// Write a named file, then read it.
void FileSystemTest::TestWriteRead() {
GoogleString filename = StrCat(test_tmpdir(), "/write.txt");
GoogleString msg("Hello, world!");
DeleteRecursively(filename);
FileSystem::OutputFile* ofile = file_system()->OpenOutputFile(
filename.c_str(), &handler_);
ASSERT_TRUE(ofile != NULL);
EXPECT_TRUE(ofile->Write(msg, &handler_));
EXPECT_TRUE(file_system()->Close(ofile, &handler_));
CheckRead(filename, msg);
CheckInputFileRead(filename, msg);
}
// Write a temp file, then read it.
void FileSystemTest::TestTemp() {
GoogleString prefix = StrCat(test_tmpdir(), "/temp_prefix");
FileSystem::OutputFile* ofile = file_system()->OpenTempFile(
prefix, &handler_);
ASSERT_TRUE(ofile != NULL);
GoogleString filename(ofile->filename());
GoogleString msg("Hello, world!");
EXPECT_TRUE(ofile->Write(msg, &handler_));
EXPECT_TRUE(file_system()->Close(ofile, &handler_));
CheckRead(filename, msg);
}
// Write a temp file, close it, append to it, then read it.
void FileSystemTest::TestAppend() {
GoogleString prefix = StrCat(test_tmpdir(), "/temp_prefix");
FileSystem::OutputFile* ofile = file_system()->OpenTempFile(
prefix, &handler_);
ASSERT_TRUE(ofile != NULL);
const GoogleString filename(ofile->filename());
EXPECT_TRUE(ofile->Write("Hello", &handler_));
EXPECT_TRUE(file_system()->Close(ofile, &handler_));
ofile = file_system()->OpenOutputFileForAppend(filename.c_str(), &handler_);
EXPECT_TRUE(ofile->Write(" world!", &handler_));
EXPECT_TRUE(file_system()->Close(ofile, &handler_));
CheckRead(filename, "Hello world!");
}
// Write a temp file, rename it, then read it.
void FileSystemTest::TestRename() {
GoogleString from_text = "Now is time time";
GoogleString to_file = StrCat(test_tmpdir(), "/to.txt");
DeleteRecursively(to_file);
GoogleString from_file = WriteNewFile("/from.txt", from_text);
ASSERT_TRUE(file_system()->RenameFile(from_file.c_str(), to_file.c_str(),
&handler_));
CheckDoesNotExist(from_file);
CheckRead(to_file, from_text);
}
// Write a file and successfully delete it.
void FileSystemTest::TestRemove() {
GoogleString filename = WriteNewFile("/remove.txt", "Goodbye, world!");
ASSERT_TRUE(file_system()->RemoveFile(filename.c_str(), &handler_));
CheckDoesNotExist(filename);
}
// Write a file and check that it exists.
void FileSystemTest::TestExists() {
GoogleString filename = WriteNewFile("/exists.txt", "I'm here.");
ASSERT_TRUE(file_system()->Exists(filename.c_str(), &handler_).is_true());
}
// Create a file along with its directory which does not exist.
void FileSystemTest::TestCreateFileInDir() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
GoogleString filename = dir_name + "/file-in-dir.txt";
FileSystem::OutputFile* file =
file_system()->OpenOutputFile(filename.c_str(), &handler_);
ASSERT_TRUE(file != NULL);
file_system()->Close(file, &handler_);
}
// Make a directory and check that files may be placed in it.
void FileSystemTest::TestMakeDir() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
GoogleString filename = dir_name + "/file-in-dir.txt";
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
// ... but we can open a file after we've created the directory.
FileSystem::OutputFile* file =
file_system()->OpenOutputFile(filename.c_str(), &handler_);
ASSERT_TRUE(file != NULL);
file_system()->Close(file, &handler_);
}
// Make a directory and then remove it.
void FileSystemTest::TestRemoveDir() {
// mem_file_system depends on dir_names ending with a '/'
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir/");
DeleteRecursively(dir_name);
GoogleString filename = dir_name + "file-in-dir.txt";
EXPECT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
EXPECT_TRUE(file_system()->Exists(dir_name.c_str(), &handler_).is_true());
// First test that non-empty directories don't get deleted
FileSystem::OutputFile* file =
file_system()->OpenOutputFile(filename.c_str(), &handler_);
EXPECT_TRUE(file != NULL);
file_system()->Close(file, &handler_);
EXPECT_FALSE(file_system()->RemoveDir(dir_name.c_str(), &handler_));
EXPECT_TRUE(file_system()->Exists(filename.c_str(), &handler_).is_true());
EXPECT_TRUE(file_system()->Exists(dir_name.c_str(), &handler_).is_true());
// Then test that empty directories do get deleted
EXPECT_TRUE(file_system()->RemoveFile(filename.c_str(), &handler_));
EXPECT_TRUE(file_system()->RemoveDir(dir_name.c_str(), &handler_));
EXPECT_TRUE(file_system()->Exists(filename.c_str(), &handler_).is_false());
EXPECT_TRUE(file_system()->Exists(dir_name.c_str(), &handler_).is_false());
}
// Make a directory and check that it is a directory.
void FileSystemTest::TestIsDir() {
GoogleString dir_name = StrCat(test_tmpdir(), "/this_is_a_dir");
DeleteRecursively(dir_name);
// Make sure we don't think the directory is there when it isn't ...
ASSERT_TRUE(file_system()->IsDir(dir_name.c_str(), &handler_).is_false());
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
// ... and that we do think it's there when it is.
ASSERT_TRUE(file_system()->IsDir(dir_name.c_str(), &handler_).is_true());
// Make sure that we don't think a regular file is a directory.
GoogleString filename = dir_name + "/this_is_a_file.txt";
GoogleString content = "I'm not a directory.";
ASSERT_TRUE(file_system()->WriteFile(filename.c_str(), content, &handler_));
ASSERT_TRUE(file_system()->IsDir(filename.c_str(), &handler_).is_false());
}
// Recursively make directories and check that it worked.
void FileSystemTest::TestRecursivelyMakeDir() {
GoogleString base = StrCat(test_tmpdir(), "/base");
GoogleString long_path = base + "/dir/of/a/really/deep/hierarchy";
DeleteRecursively(base);
// Make sure we don't think the directory is there when it isn't ...
ASSERT_TRUE(file_system()->IsDir(long_path.c_str(), &handler_).is_false());
ASSERT_TRUE(file_system()->RecursivelyMakeDir(long_path, &handler_));
// ... and that we do think it's there when it is.
ASSERT_TRUE(file_system()->IsDir(long_path.c_str(), &handler_).is_true());
}
// Check that we cannot create a directory we do not have permissions for.
// Note: depends upon root dir not being writable.
void FileSystemTest::TestRecursivelyMakeDir_NoPermission() {
GoogleString base = "/bogus-dir";
GoogleString path = base + "/no/permission/to/make/this/dir";
// Make sure the bogus bottom level directory is not there.
ASSERT_TRUE(file_system()->Exists(base.c_str(), &handler_).is_false());
// We do not have permission to create it.
ASSERT_FALSE(file_system()->RecursivelyMakeDir(path, &handler_));
}
// Check that we cannot create a directory below a file.
void FileSystemTest::TestRecursivelyMakeDir_FileInPath() {
GoogleString base = StrCat(test_tmpdir(), "/file-in-path");
GoogleString filename = base + "/this-is-a-file";
GoogleString bad_path = filename + "/some/more/path";
DeleteRecursively(base);
GoogleString content = "Your path must end here. You shall not pass!";
ASSERT_TRUE(file_system()->MakeDir(base.c_str(), &handler_));
ASSERT_TRUE(file_system()->WriteFile(filename.c_str(), content, &handler_));
ASSERT_FALSE(file_system()->RecursivelyMakeDir(bad_path, &handler_));
}
void FileSystemTest::TestListContents() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
GoogleString filename1 = dir_name + "/file-in-dir.txt";
GoogleString filename2 = dir_name + "/another-file-in-dir.txt";
GoogleString content = "Lorem ipsum dolor sit amet";
StringVector mylist;
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
ASSERT_TRUE(file_system()->WriteFile(filename1.c_str(), content, &handler_));
ASSERT_TRUE(file_system()->WriteFile(filename2.c_str(), content, &handler_));
EXPECT_TRUE(file_system()->ListContents(dir_name, &mylist, &handler_));
EXPECT_EQ(size_t(2), mylist.size());
// Make sure our filenames are in there
EXPECT_FALSE(filename1.compare(mylist.at(0))
&& filename1.compare(mylist.at(1)));
EXPECT_FALSE(filename2.compare(mylist.at(0))
&& filename2.compare(mylist.at(1)));
}
void FileSystemTest::TestAtime() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
GoogleString filename1 = "file-in-dir.txt";
GoogleString filename2 = "another-file-in-dir.txt";
GoogleString full_path1 = dir_name + "/" + filename1;
GoogleString full_path2 = dir_name + "/" + filename2;
GoogleString content = "Lorem ipsum dolor sit amet";
// We need to sleep a bit between accessing files so that the
// difference shows up in in atimes which are measured in seconds.
unsigned int sleep_us = 1500000;
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(), content, &handler_));
ASSERT_TRUE(file_system()->WriteFile(full_path2.c_str(), content, &handler_));
int64 atime1, atime2;
CheckRead(full_path1, content);
timer()->SleepUs(sleep_us);
CheckRead(full_path2, content);
ASSERT_TRUE(file_system()->Atime(full_path1, &atime1, &handler_));
ASSERT_TRUE(file_system()->Atime(full_path2, &atime2, &handler_));
EXPECT_LT(atime1, atime2);
CheckRead(full_path2, content);
timer()->SleepUs(sleep_us);
CheckRead(full_path1, content);
ASSERT_TRUE(file_system()->Atime(full_path1, &atime1, &handler_));
ASSERT_TRUE(file_system()->Atime(full_path2, &atime2, &handler_));
EXPECT_LT(atime2, atime1);
}
void FileSystemTest::TestMtime() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
GoogleString filename1 = "file-in-dir.txt";
GoogleString filename2 = "another-file-in-dir.txt";
GoogleString full_path1 = dir_name + "/" + filename1;
GoogleString full_path2 = dir_name + "/" + filename2;
GoogleString content = "Lorem ipsum dolor sit amet";
// We need to sleep a bit between accessing files so that the
// difference shows up in in atimes which are measured in seconds.
unsigned int sleep_us = 1500000;
// Setup directory to play in.
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
// Write two files with pause between
ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(), content, &handler_));
timer()->SleepUs(sleep_us);
ASSERT_TRUE(file_system()->WriteFile(full_path2.c_str(), content, &handler_));
int64 mtime1_orig, mtime2_orig;
// Check that File1 was created before File2.
ASSERT_TRUE(file_system()->Mtime(full_path1, &mtime1_orig, &handler_));
ASSERT_TRUE(file_system()->Mtime(full_path2, &mtime2_orig, &handler_));
EXPECT_LT(mtime1_orig, mtime2_orig);
int64 mtime1_read, mtime2_read;
// And that even if you read from File1 later, the C-time is still preserved.
timer()->SleepUs(sleep_us);
CheckRead(full_path1, content);
ASSERT_TRUE(file_system()->Mtime(full_path1, &mtime1_read, &handler_));
ASSERT_TRUE(file_system()->Mtime(full_path2, &mtime2_read, &handler_));
EXPECT_EQ(mtime1_orig, mtime1_read);
EXPECT_EQ(mtime2_orig, mtime2_read);
int64 mtime1_recreate, mtime2_recreate;
// But if we delete File1 and re-create it, the C-time is updated.
timer()->SleepUs(sleep_us);
ASSERT_TRUE(file_system()->RemoveFile(full_path1.c_str(), &handler_));
ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(), content, &handler_));
ASSERT_TRUE(file_system()->Mtime(full_path1, &mtime1_recreate, &handler_));
ASSERT_TRUE(file_system()->Mtime(full_path2, &mtime2_recreate, &handler_));
EXPECT_LT(mtime1_orig, mtime1_recreate);
EXPECT_EQ(mtime2_orig, mtime2_recreate);
EXPECT_GT(mtime1_recreate, mtime2_recreate);
}
void FileSystemTest::TestDirInfo() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
GoogleString dir_name2 = dir_name + "/make_dir2";
GoogleString dir_name3 = dir_name + "/make_dir3/";
GoogleString filename1 = "another-file-in-dir.txt";
GoogleString filename2 = "file-in-dir.txt";
GoogleString full_path1 = dir_name2 + "/" + filename1;
GoogleString full_path2 = dir_name2 + "/" + filename2;
GoogleString content1 = "12345";
// Make content2 longer than the default block size of 4096 to make sure disk
// based file systems are calculating the on-disk size, not just apparent size
// of file. stdio and apr file systems should report 8192 as the size of
// content2.
GoogleString content2(4097, 'a');
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
ASSERT_TRUE(file_system()->MakeDir(dir_name2.c_str(), &handler_));
ASSERT_TRUE(file_system()->MakeDir(dir_name3.c_str(), &handler_));
ASSERT_TRUE(file_system()->WriteFile(full_path1.c_str(),
content1, &handler_));
ASSERT_TRUE(file_system()->WriteFile(full_path2.c_str(),
content2, &handler_));
int64 size;
EXPECT_TRUE(file_system()->Size(full_path1, &size, &handler_));
EXPECT_EQ(FileSize(content1), size);
EXPECT_TRUE(file_system()->Size(full_path2, &size, &handler_));
EXPECT_EQ(FileSize(content2), size);
FileSystem::DirInfo dir_info;
FileSystem::DirInfo dir_info2;
file_system()->GetDirInfo(dir_name2, &dir_info2, &handler_);
EXPECT_EQ(FileSize(content1) + FileSize(content2), dir_info2.size_bytes);
EXPECT_EQ(2, dir_info2.inode_count);
EXPECT_EQ(static_cast<size_t>(2), dir_info2.files.size());
// dir_info.files is not guaranteed to be in any particular order, and in fact
// come back in different order for mem and apr filesystems, so sort it so
// that the comparison is consistent.
std::sort(dir_info2.files.begin(), dir_info2.files.end(), CompareByName());
EXPECT_STREQ(full_path1, dir_info2.files[0].name);
EXPECT_STREQ(full_path2, dir_info2.files[1].name);
EXPECT_EQ(static_cast<size_t>(0), dir_info2.empty_dirs.size());
file_system()->GetDirInfo(dir_name, &dir_info, &handler_);
int dir_size = DefaultDirSize();
EXPECT_EQ(dir_size * 2 + FileSize(content1) + FileSize(content2),
dir_info.size_bytes);
EXPECT_EQ(4, dir_info.inode_count);
std::sort(dir_info.files.begin(), dir_info.files.end(), CompareByName());
EXPECT_STREQ(full_path1, dir_info.files[0].name);
EXPECT_STREQ(full_path2, dir_info.files[1].name);
EXPECT_EQ(static_cast<size_t>(1), dir_info.empty_dirs.size());
}
void FileSystemTest::TestLock() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
GoogleString lock_name = dir_name + "/lock";
// Acquire the lock
EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_true());
// Can't re-acquire the lock
EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_false());
// Release the lock
EXPECT_TRUE(file_system()->Unlock(lock_name, &handler_));
// Do it all again to make sure the release worked.
EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_true());
EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_false());
EXPECT_TRUE(file_system()->Unlock(lock_name, &handler_));
}
// Test lock timeout; assumes the file system has at least 1-second creation
// granularity.
void FileSystemTest::TestLockTimeout() {
GoogleString dir_name = StrCat(test_tmpdir(), "/make_dir");
DeleteRecursively(dir_name);
ASSERT_TRUE(file_system()->MakeDir(dir_name.c_str(), &handler_));
GoogleString lock_name = dir_name + "/lock";
// Acquire the lock
EXPECT_TRUE(file_system()->TryLockWithTimeout(lock_name, Timer::kSecondMs,
timer(), &handler_).is_true());
// Immediate re-acquire should fail. Steal time deliberately long so we don't
// steal by mistake (since we're running in non-mock time).
EXPECT_TRUE(file_system()->TryLockWithTimeout(lock_name, Timer::kMinuteMs,
timer(), &handler_).is_false());
// Wait 1 second so that we're definitely different from ctime.
// Now we should seize lock.
timer()->SleepMs(Timer::kSecondMs);
EXPECT_TRUE(file_system()->TryLockWithTimeout(lock_name, Timer::kSecondMs,
timer(), &handler_).is_true());
// Lock should still be held.
EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_false());
EXPECT_TRUE(file_system()->Unlock(lock_name, &handler_));
// The result of this second unlock is unknown, but it ought not to crash.
file_system()->Unlock(lock_name, &handler_);
// Lock should now be unambiguously unlocked.
EXPECT_TRUE(file_system()->TryLock(lock_name, &handler_).is_true());
}
} // namespace net_instaweb