/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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 "base/unix_file/mapped_file.h"
#include "base/casts.h"
#include "base/logging.h"
#include "base/unix_file/fd_file.h"
#include "base/unix_file/random_access_file_test.h"
#include "base/unix_file/random_access_file_utils.h"
#include "base/unix_file/string_file.h"
#include "gtest/gtest.h"
namespace unix_file {
class MappedFileTest : public RandomAccessFileTest {
protected:
MappedFileTest() : kContent("some content") {
}
void SetUp() {
RandomAccessFileTest::SetUp();
good_path_ = GetTmpPath("some-file.txt");
int fd = TEMP_FAILURE_RETRY(open(good_path_.c_str(), O_CREAT|O_RDWR, 0666));
FdFile dst(fd, false);
StringFile src;
src.Assign(kContent);
ASSERT_TRUE(CopyFile(src, &dst));
ASSERT_EQ(dst.FlushClose(), 0);
}
void TearDown() {
ASSERT_EQ(unlink(good_path_.c_str()), 0);
RandomAccessFileTest::TearDown();
}
virtual RandomAccessFile* MakeTestFile() {
TEMP_FAILURE_RETRY(truncate(good_path_.c_str(), 0));
MappedFile* f = new MappedFile;
CHECK(f->Open(good_path_, MappedFile::kReadWriteMode));
return f;
}
void CleanUp(RandomAccessFile* file) OVERRIDE {
if (file == nullptr) {
return;
}
MappedFile* f = ::art::down_cast<MappedFile*>(file);
UNUSED(f->Flush());
UNUSED(f->Close());
}
const std::string kContent;
std::string good_path_;
};
TEST_F(MappedFileTest, OkayToNotUse) {
MappedFile file;
EXPECT_EQ(-1, file.Fd());
EXPECT_FALSE(file.IsOpened());
EXPECT_FALSE(file.IsMapped());
}
TEST_F(MappedFileTest, OpenClose) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
EXPECT_GE(file.Fd(), 0);
EXPECT_TRUE(file.IsOpened());
EXPECT_EQ(kContent.size(), static_cast<uint64_t>(file.size()));
EXPECT_EQ(0, file.Close());
EXPECT_EQ(-1, file.Fd());
EXPECT_FALSE(file.IsOpened());
}
TEST_F(MappedFileTest, OpenFdClose) {
FILE* f = tmpfile();
ASSERT_TRUE(f != NULL);
MappedFile file(fileno(f), false);
EXPECT_GE(file.Fd(), 0);
EXPECT_TRUE(file.IsOpened());
EXPECT_EQ(0, file.Close());
}
TEST_F(MappedFileTest, CanUseAfterMapReadOnly) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
EXPECT_FALSE(file.IsMapped());
EXPECT_TRUE(file.MapReadOnly());
EXPECT_TRUE(file.IsMapped());
EXPECT_EQ(kContent.size(), static_cast<uint64_t>(file.size()));
ASSERT_TRUE(file.data());
EXPECT_EQ(0, memcmp(kContent.c_str(), file.data(), file.size()));
EXPECT_EQ(0, file.Flush());
}
TEST_F(MappedFileTest, CanUseAfterMapReadWrite) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode));
EXPECT_FALSE(file.IsMapped());
EXPECT_TRUE(file.MapReadWrite(1));
EXPECT_TRUE(file.IsMapped());
EXPECT_EQ(1, file.size());
ASSERT_TRUE(file.data());
EXPECT_EQ(kContent[0], *file.data());
EXPECT_EQ(0, file.Flush());
file.Close();
}
TEST_F(MappedFileTest, CanWriteNewData) {
const std::string new_path(GetTmpPath("new-file.txt"));
ASSERT_EQ(-1, unlink(new_path.c_str()));
ASSERT_EQ(ENOENT, errno);
MappedFile file;
ASSERT_TRUE(file.Open(new_path, MappedFile::kReadWriteMode));
EXPECT_TRUE(file.MapReadWrite(kContent.size()));
EXPECT_TRUE(file.IsMapped());
EXPECT_EQ(kContent.size(), static_cast<uint64_t>(file.size()));
ASSERT_TRUE(file.data());
memcpy(file.data(), kContent.c_str(), kContent.size());
EXPECT_EQ(0, file.Flush());
EXPECT_EQ(0, file.Close());
EXPECT_FALSE(file.IsMapped());
FdFile new_file(TEMP_FAILURE_RETRY(open(new_path.c_str(), O_RDONLY)), false);
StringFile buffer;
ASSERT_TRUE(CopyFile(new_file, &buffer));
EXPECT_EQ(kContent, buffer.ToStringPiece());
EXPECT_EQ(0, unlink(new_path.c_str()));
}
TEST_F(MappedFileTest, FileMustExist) {
const std::string bad_path(GetTmpPath("does-not-exist.txt"));
MappedFile file;
EXPECT_FALSE(file.Open(bad_path, MappedFile::kReadOnlyMode));
EXPECT_EQ(-1, file.Fd());
}
TEST_F(MappedFileTest, FileMustBeWritable) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
EXPECT_FALSE(file.MapReadWrite(10));
}
TEST_F(MappedFileTest, RemappingAllowedUntilSuccess) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
EXPECT_FALSE(file.MapReadWrite(10));
EXPECT_FALSE(file.MapReadWrite(10));
}
TEST_F(MappedFileTest, ResizeMappedFile) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode));
ASSERT_TRUE(file.MapReadWrite(10));
EXPECT_EQ(10, file.GetLength());
EXPECT_TRUE(file.Unmap());
EXPECT_TRUE(file.MapReadWrite(20));
EXPECT_EQ(20, file.GetLength());
EXPECT_EQ(0, file.Flush());
EXPECT_TRUE(file.Unmap());
EXPECT_EQ(0, file.Flush());
EXPECT_EQ(0, file.SetLength(5));
EXPECT_TRUE(file.MapReadOnly());
EXPECT_EQ(5, file.GetLength());
}
TEST_F(MappedFileTest, ReadNotMapped) {
TestRead();
}
TEST_F(MappedFileTest, SetLengthNotMapped) {
TestSetLength();
}
TEST_F(MappedFileTest, WriteNotMapped) {
TestWrite();
}
TEST_F(MappedFileTest, ReadMappedReadOnly) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
ASSERT_TRUE(file.MapReadOnly());
TestReadContent(kContent, &file);
}
TEST_F(MappedFileTest, ReadMappedReadWrite) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode));
ASSERT_TRUE(file.MapReadWrite(kContent.size()));
TestReadContent(kContent, &file);
UNUSED(file.FlushClose());
}
TEST_F(MappedFileTest, WriteMappedReadWrite) {
TEMP_FAILURE_RETRY(unlink(good_path_.c_str()));
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode));
ASSERT_TRUE(file.MapReadWrite(kContent.size()));
// Can't write to a negative offset.
EXPECT_EQ(-EINVAL, file.Write(kContent.c_str(), 0, -123));
// A zero-length write is a no-op.
EXPECT_EQ(0, file.Write(kContent.c_str(), 0, 0));
// But the file size is as given when mapped.
EXPECT_EQ(kContent.size(), static_cast<uint64_t>(file.GetLength()));
// Data written past the end are discarded.
EXPECT_EQ(kContent.size() - 1,
static_cast<uint64_t>(file.Write(kContent.c_str(), kContent.size(), 1)));
EXPECT_EQ(0, memcmp(kContent.c_str(), file.data() + 1, kContent.size() - 1));
// Data can be overwritten.
EXPECT_EQ(kContent.size(),
static_cast<uint64_t>(file.Write(kContent.c_str(), kContent.size(), 0)));
EXPECT_EQ(0, memcmp(kContent.c_str(), file.data(), kContent.size()));
UNUSED(file.FlushClose());
}
#if 0 // death tests don't work on android yet
class MappedFileDeathTest : public MappedFileTest {};
TEST_F(MappedFileDeathTest, MustMapBeforeUse) {
MappedFile file;
EXPECT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
EXPECT_DEATH(file.data(), "mapped_");
}
TEST_F(MappedFileDeathTest, RemappingNotAllowedReadOnly) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
ASSERT_TRUE(file.MapReadOnly());
EXPECT_DEATH(file.MapReadOnly(), "mapped_");
}
TEST_F(MappedFileDeathTest, RemappingNotAllowedReadWrite) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode));
ASSERT_TRUE(file.MapReadWrite(10));
EXPECT_DEATH(file.MapReadWrite(10), "mapped_");
}
TEST_F(MappedFileDeathTest, SetLengthMappedReadWrite) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadWriteMode));
ASSERT_TRUE(file.MapReadWrite(10));
EXPECT_EQ(10, file.GetLength());
EXPECT_DEATH(file.SetLength(0), ".*");
}
TEST_F(MappedFileDeathTest, SetLengthMappedReadOnly) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
ASSERT_TRUE(file.MapReadOnly());
EXPECT_EQ(kContent.size(), file.GetLength());
EXPECT_DEATH(file.SetLength(0), ".*");
}
TEST_F(MappedFileDeathTest, WriteMappedReadOnly) {
MappedFile file;
ASSERT_TRUE(file.Open(good_path_, MappedFile::kReadOnlyMode));
ASSERT_TRUE(file.MapReadOnly());
char buf[10];
EXPECT_DEATH(file.Write(buf, 0, 0), ".*");
}
#endif
} // namespace unix_file