//
// Copyright (C) 2017 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 "update_engine/payload_consumer/cached_file_descriptor.h"
#include <fcntl.h>
#include <algorithm>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"
using chromeos_update_engine::test_utils::ExpectVectorsEq;
using std::min;
using std::string;
using std::vector;
namespace chromeos_update_engine {
namespace {
const size_t kCacheSize = 100;
const size_t kFileSize = 1024;
const size_t kRandomIterations = 1000;
} // namespace
class CachedFileDescriptorTest : public ::testing::Test {
public:
void Open() {
cfd_.reset(new CachedFileDescriptor(fd_, kCacheSize));
EXPECT_TRUE(cfd_->Open(temp_file_.path().c_str(), O_RDWR, 0600));
}
void Write(uint8_t* buffer, size_t count) {
size_t total_bytes_wrote = 0;
while (total_bytes_wrote < count) {
auto bytes_wrote =
cfd_->Write(buffer + total_bytes_wrote, count - total_bytes_wrote);
ASSERT_NE(bytes_wrote, -1);
total_bytes_wrote += bytes_wrote;
}
}
void Close() { EXPECT_TRUE(cfd_->Close()); }
void SetUp() override {
brillo::Blob zero_blob(kFileSize, 0);
EXPECT_TRUE(utils::WriteFile(
temp_file_.path().c_str(), zero_blob.data(), zero_blob.size()));
Open();
}
void TearDown() override {
Close();
EXPECT_FALSE(cfd_->IsOpen());
}
protected:
FileDescriptorPtr fd_{new EintrSafeFileDescriptor};
test_utils::ScopedTempFile temp_file_{"CachedFileDescriptor-file.XXXXXX"};
int value_{1};
FileDescriptorPtr cfd_;
};
TEST_F(CachedFileDescriptorTest, IsOpenTest) {
EXPECT_TRUE(cfd_->IsOpen());
}
TEST_F(CachedFileDescriptorTest, SimpleWriteTest) {
EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
brillo::Blob blob_in(kFileSize, value_);
Write(blob_in.data(), blob_in.size());
EXPECT_TRUE(cfd_->Flush());
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_EQ(blob_in, blob_out);
}
TEST_F(CachedFileDescriptorTest, OneBytePerWriteTest) {
EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
brillo::Blob blob_in(kFileSize, value_);
for (size_t idx = 0; idx < blob_in.size(); idx++) {
Write(&blob_in[idx], 1);
}
EXPECT_TRUE(cfd_->Flush());
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_EQ(blob_in, blob_out);
}
TEST_F(CachedFileDescriptorTest, RandomWriteTest) {
EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
brillo::Blob blob_in(kFileSize, 0);
srand(time(nullptr));
uint32_t rand_seed;
for (size_t idx = 0; idx < kRandomIterations; idx++) {
// zero to full size available.
size_t start = rand_r(&rand_seed) % blob_in.size();
size_t size = rand_r(&rand_seed) % (blob_in.size() - start);
std::fill_n(&blob_in[start], size, idx % 256);
EXPECT_EQ(cfd_->Seek(start, SEEK_SET), static_cast<off64_t>(start));
Write(&blob_in[start], size);
}
EXPECT_TRUE(cfd_->Flush());
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_EQ(blob_in, blob_out);
}
TEST_F(CachedFileDescriptorTest, SeekTest) {
EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
EXPECT_EQ(cfd_->Seek(1, SEEK_SET), 1);
EXPECT_EQ(cfd_->Seek(kFileSize - 1, SEEK_SET),
static_cast<off64_t>(kFileSize - 1));
EXPECT_EQ(cfd_->Seek(kFileSize, SEEK_SET), static_cast<off64_t>(kFileSize));
EXPECT_EQ(cfd_->Seek(kFileSize + 1, SEEK_SET),
static_cast<off64_t>(kFileSize + 1));
EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), 1);
EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), 2);
EXPECT_EQ(cfd_->Seek(kFileSize - 1, SEEK_SET),
static_cast<off64_t>(kFileSize - 1));
EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), static_cast<off64_t>(kFileSize));
EXPECT_EQ(cfd_->Seek(1, SEEK_CUR), static_cast<off64_t>(kFileSize + 1));
}
TEST_F(CachedFileDescriptorTest, NoFlushTest) {
EXPECT_EQ(cfd_->Seek(0, SEEK_SET), 0);
brillo::Blob blob_in(kFileSize, value_);
Write(blob_in.data(), blob_in.size());
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_NE(blob_in, blob_out);
}
TEST_F(CachedFileDescriptorTest, CacheSizeWriteTest) {
off64_t seek = 10;
brillo::Blob blob_in(kFileSize, 0);
std::fill_n(&blob_in[seek], kCacheSize, value_);
// We are writing exactly one cache size; Then it should be commited.
EXPECT_EQ(cfd_->Seek(seek, SEEK_SET), seek);
Write(&blob_in[seek], kCacheSize);
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_EQ(blob_in, blob_out);
}
TEST_F(CachedFileDescriptorTest, UnderCacheSizeWriteTest) {
off64_t seek = 100;
size_t less_than_cache_size = kCacheSize - 1;
EXPECT_EQ(cfd_->Seek(seek, SEEK_SET), seek);
brillo::Blob blob_in(kFileSize, 0);
std::fill_n(&blob_in[seek], less_than_cache_size, value_);
// We are writing less than one cache size; then it should not be commited.
Write(&blob_in[seek], less_than_cache_size);
// Revert the changes in |blob_in|.
std::fill_n(&blob_in[seek], less_than_cache_size, 0);
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_EQ(blob_in, blob_out);
}
TEST_F(CachedFileDescriptorTest, SeekAfterWriteTest) {
off64_t seek = 100;
size_t less_than_cache_size = kCacheSize - 3;
EXPECT_EQ(cfd_->Seek(seek, SEEK_SET), seek);
brillo::Blob blob_in(kFileSize, 0);
std::fill_n(&blob_in[seek], less_than_cache_size, value_);
// We are writing less than one cache size; then it should not be commited.
Write(&blob_in[seek], less_than_cache_size);
// Then we seek, it should've written the cache after seek.
EXPECT_EQ(cfd_->Seek(200, SEEK_SET), 200);
brillo::Blob blob_out;
EXPECT_TRUE(utils::ReadFile(temp_file_.path(), &blob_out));
EXPECT_EQ(blob_in, blob_out);
}
} // namespace chromeos_update_engine