/* * 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