From 8a0458c17bcf0500118ca6a4792e597a70ec79f5 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Mon, 21 Dec 2015 16:20:34 -0800 Subject: [PATCH] Added MemoryFile, a File type stored in memory. This can be used for unit tests to reduce the need for temporary files. A MemoryFile acts the same as any other File object, but stores the contents in memory. The memory pointer can be accessed directly. A file with the 'memory://' prefix will be a memory file. Change-Id: I10ae3ed5391c8fd838e6dfb33106dec959bdc224 --- packager/media/file/file.cc | 22 ++++ packager/media/file/file.gyp | 3 + packager/media/file/memory_file.cc | 124 ++++++++++++++++++++ packager/media/file/memory_file.h | 60 ++++++++++ packager/media/file/memory_file_unittest.cc | 91 ++++++++++++++ 5 files changed, 300 insertions(+) create mode 100644 packager/media/file/memory_file.cc create mode 100644 packager/media/file/memory_file.h create mode 100644 packager/media/file/memory_file_unittest.cc diff --git a/packager/media/file/file.cc b/packager/media/file/file.cc index a2929de0c3..bbe9cbbf55 100644 --- a/packager/media/file/file.cc +++ b/packager/media/file/file.cc @@ -12,6 +12,7 @@ #include "packager/base/logging.h" #include "packager/base/memory/scoped_ptr.h" #include "packager/media/file/local_file.h" +#include "packager/media/file/memory_file.h" #include "packager/media/file/threaded_io_file.h" #include "packager/media/file/udp_file.h" #include "packager/base/strings/string_util.h" @@ -29,6 +30,7 @@ namespace media { const char* kLocalFilePrefix = "file://"; const char* kUdpFilePrefix = "udp://"; +const char* kMemoryFilePrefix = "memory://"; namespace { @@ -58,6 +60,15 @@ File* CreateUdpFile(const char* file_name, const char* mode) { return new UdpFile(file_name); } +File* CreateMemoryFile(const char* file_name, const char* mode) { + return new MemoryFile(file_name); +} + +bool DeleteMemoryFile(const char* file_name) { + MemoryFile::Delete(file_name); + return true; +} + static const SupportedTypeInfo kSupportedTypeInfo[] = { { kLocalFilePrefix, @@ -71,6 +82,12 @@ static const SupportedTypeInfo kSupportedTypeInfo[] = { &CreateUdpFile, NULL }, + { + kMemoryFilePrefix, + strlen(kMemoryFilePrefix), + &CreateMemoryFile, + &DeleteMemoryFile + }, }; } // namespace @@ -79,6 +96,11 @@ File* File::Create(const char* file_name, const char* mode) { scoped_ptr internal_file( CreateInternalFile(file_name, mode)); + if (!strncmp(file_name, kMemoryFilePrefix, strlen(kMemoryFilePrefix))) { + // Disable caching for memory files. + return internal_file.release(); + } + if (FLAGS_io_cache_size) { // Enable threaded I/O for "r", "w", and "a" modes only. if (!strcmp(mode, "r")) { diff --git a/packager/media/file/file.gyp b/packager/media/file/file.gyp index 41396d899a..54f922f7a0 100644 --- a/packager/media/file/file.gyp +++ b/packager/media/file/file.gyp @@ -20,6 +20,8 @@ 'io_cache.h', 'local_file.cc', 'local_file.h', + 'memory_file.cc', + 'memory_file.h', 'threaded_io_file.cc', 'threaded_io_file.h', 'udp_file.h', @@ -47,6 +49,7 @@ 'sources': [ 'file_unittest.cc', 'io_cache_unittest.cc', + 'memory_file_unittest.cc', ], 'dependencies': [ '../../testing/gtest.gyp:gtest', diff --git a/packager/media/file/memory_file.cc b/packager/media/file/memory_file.cc new file mode 100644 index 0000000000..8c07ad87b4 --- /dev/null +++ b/packager/media/file/memory_file.cc @@ -0,0 +1,124 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/file/memory_file.h" + +#include // for memcpy + +#include + +#include "packager/base/logging.h" +#include "packager/base/memory/scoped_ptr.h" + +namespace edash_packager { +namespace media { +namespace { + +// A helper filesystem object. This holds the data for the memory files. +class FileSystem { + public: + ~FileSystem() {} + + static FileSystem* Instance() { + if (!g_file_system_) + g_file_system_.reset(new FileSystem()); + + return g_file_system_.get(); + } + + std::vector* GetFile(const std::string& file_name) { + return &files_[file_name]; + } + + void Delete(const std::string& file_name) { files_.erase(file_name); } + + void DeleteAll() { files_.clear(); } + + private: + FileSystem() {} + + static scoped_ptr g_file_system_; + + std::map > files_; + DISALLOW_COPY_AND_ASSIGN(FileSystem); +}; + +scoped_ptr FileSystem::g_file_system_; + +} // namespace + +MemoryFile::MemoryFile(const std::string& file_name) + : File(file_name), + file_(FileSystem::Instance()->GetFile(file_name)), + position_(0) {} + +MemoryFile::~MemoryFile() {} + +bool MemoryFile::Close() { + delete this; + return true; +} + +int64_t MemoryFile::Read(void* buffer, uint64_t length) { + const uint64_t size = Size(); + DCHECK_LE(position_, size); + if (position_ >= size) + return 0; + + const uint64_t bytes_to_read = std::min(length, size - position_); + memcpy(buffer, &(*file_)[position_], bytes_to_read); + position_ += bytes_to_read; + return bytes_to_read; +} + +int64_t MemoryFile::Write(const void* buffer, uint64_t length) { + const uint64_t size = Size(); + if (size < position_ + length) { + file_->resize(position_ + length); + } + + memcpy(&(*file_)[position_], buffer, length); + position_ += length; + return length; +} + +int64_t MemoryFile::Size() { + return file_->size(); +} + +bool MemoryFile::Flush() { + return true; +} + +bool MemoryFile::Seek(uint64_t position) { + if (Size() < static_cast(position)) + return false; + + position_ = position; + return true; +} + +bool MemoryFile::Tell(uint64_t* position) { + *position = position_; + return true; +} + +bool MemoryFile::Open() { + position_ = 0; + return true; +} + +void MemoryFile::DeleteAll() { + FileSystem::Instance()->DeleteAll(); +} + +void MemoryFile::Delete(const std::string& file_name) { + FileSystem::Instance()->Delete(file_name); +} + +} // namespace media +} // namespace edash_packager + diff --git a/packager/media/file/memory_file.h b/packager/media/file/memory_file.h new file mode 100644 index 0000000000..be2b009c2a --- /dev/null +++ b/packager/media/file/memory_file.h @@ -0,0 +1,60 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef MEDIA_FILE_MEDIA_FILE_H_ +#define MEDIA_FILE_MEDIA_FILE_H_ + +#include + +#include +#include + +#include "packager/media/file/file.h" + +namespace edash_packager { +namespace media { + +/// Implements a File that is stored in memory. This should be only used for +/// testing, since this does not support larger files. +class MemoryFile : public File { + public: + MemoryFile(const std::string& file_name); + + /// @name File implementation overrides. + /// @{ + bool Close() override; + int64_t Read(void* buffer, uint64_t length) override; + int64_t Write(const void* buffer, uint64_t length) override; + int64_t Size() override; + bool Flush() override; + bool Seek(uint64_t position) override; + bool Tell(uint64_t* position) override; + /// @} + + /// Deletes all memory file data created. This assumes that there are no + /// MemoryFile objects alive. Any alive objects will be in an undefined + /// state. + static void DeleteAll(); + /// Deletes the memory file data with the given file_name. Any objects open + /// with that file name will be in an undefined state. + static void Delete(const std::string& file_name); + + protected: + ~MemoryFile() override; + bool Open() override; + + private: + std::vector* file_; + uint64_t position_; + + DISALLOW_COPY_AND_ASSIGN(MemoryFile); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FILE_MEDIA_FILE_H_ + diff --git a/packager/media/file/memory_file_unittest.cc b/packager/media/file/memory_file_unittest.cc new file mode 100644 index 0000000000..ff968d51eb --- /dev/null +++ b/packager/media/file/memory_file_unittest.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/file/file.h" +#include "packager/media/file/file_closer.h" +#include "packager/media/file/memory_file.h" + +namespace edash_packager { +namespace media { +namespace { + +const uint8_t kWriteBuffer[] = {1, 2, 3, 4, 5, 6, 7, 8}; +const int64_t kWriteBufferSize = sizeof(kWriteBuffer); + +} + +class MemoryFileTest : public testing::Test { + protected: + void TearDown() override { + MemoryFile::DeleteAll(); + } +}; + +TEST_F(MemoryFileTest, ModifiesSameFile) { + scoped_ptr writer(File::Open("memory://file1", "w")); + ASSERT_EQ(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize)); + + // Since File::Open should not create a ThreadedIoFile so there should be + // no cache. + + scoped_ptr reader(File::Open("memory://file1", "r")); + + uint8_t read_buffer[kWriteBufferSize]; + ASSERT_EQ(kWriteBufferSize, reader->Read(read_buffer, kWriteBufferSize)); + EXPECT_EQ(0, memcmp(kWriteBuffer, read_buffer, kWriteBufferSize)); +} + +TEST_F(MemoryFileTest, SupportsDifferentFiles) { + scoped_ptr writer(new MemoryFile("memory://file1")); + scoped_ptr reader(new MemoryFile("memory://file2")); + + ASSERT_EQ(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize)); + ASSERT_EQ(0, reader->Size()); +} + +TEST_F(MemoryFileTest, SeekAndTell) { + scoped_ptr file(new MemoryFile("memory://file1")); + ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize)); + ASSERT_TRUE(file->Seek(0)); + + const int64_t seek_pos = kWriteBufferSize / 2; + ASSERT_TRUE(file->Seek(seek_pos)); + + uint64_t size; + ASSERT_TRUE(file->Tell(&size)); + EXPECT_EQ(seek_pos, static_cast(size)); +} + +TEST_F(MemoryFileTest, EndOfFile) { + scoped_ptr file(new MemoryFile("memory://file1")); + ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize)); + ASSERT_TRUE(file->Seek(0)); + + uint8_t read_buffer[kWriteBufferSize]; + const int64_t seek_pos = kWriteBufferSize / 2; + const int64_t read_size = kWriteBufferSize - seek_pos; + ASSERT_TRUE(file->Seek(seek_pos)); + EXPECT_EQ(read_size, file->Read(read_buffer, kWriteBufferSize)); + EXPECT_EQ(0, memcmp(read_buffer, kWriteBuffer + seek_pos, read_size)); + EXPECT_EQ(0, file->Read(read_buffer, kWriteBufferSize)); +} + +TEST_F(MemoryFileTest, ExtendsSize) { + scoped_ptr file(new MemoryFile("memory://file1")); + ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize)); + + ASSERT_EQ(kWriteBufferSize, file->Size()); + ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize)); + EXPECT_EQ(2 * kWriteBufferSize, file->Size()); + + uint64_t size; + ASSERT_TRUE(file->Tell(&size)); + EXPECT_EQ(2 * kWriteBufferSize, static_cast(size)); +} + +} // namespace media +} // namespace edash_packager