diff options
author | alexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-18 16:26:11 +0000 |
---|---|---|
committer | alexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-18 16:26:11 +0000 |
commit | 30a9071ad365fc3141a5cd30a01559e508e9a891 (patch) | |
tree | 78d2517bb5dffaadf5f57e2be1eea33d51fc248d /components | |
parent | 715d972e1cd8f4606bb6fe20db4326aca15f248c (diff) | |
download | chromium_src-30a9071ad365fc3141a5cd30a01559e508e9a891.zip chromium_src-30a9071ad365fc3141a5cd30a01559e508e9a891.tar.gz chromium_src-30a9071ad365fc3141a5cd30a01559e508e9a891.tar.bz2 |
Move chrome/browser/nacl_host/pnacl_translation_cache.* to components/nacl/browser
These files have no dependencies on chrome/ so they can be safely moved
to the components/ directory.
This is part of an effort to componentize NaCl code.
BUG=244791
Review URL: https://codereview.chromium.org/61763026
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235735 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r-- | components/components_tests.gyp | 2 | ||||
-rw-r--r-- | components/nacl.gyp | 2 | ||||
-rw-r--r-- | components/nacl/browser/DEPS | 2 | ||||
-rw-r--r-- | components/nacl/browser/pnacl_translation_cache.cc | 440 | ||||
-rw-r--r-- | components/nacl/browser/pnacl_translation_cache.h | 106 | ||||
-rw-r--r-- | components/nacl/browser/pnacl_translation_cache_unittest.cc | 269 | ||||
-rw-r--r-- | components/nacl_common.gyp | 2 |
7 files changed, 823 insertions, 0 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 1ec3ed5..990c653 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -135,9 +135,11 @@ ['disable_nacl==0', { 'sources': [ 'nacl/browser/nacl_validation_cache_unittest.cc', + 'nacl/browser/pnacl_translation_cache_unittest.cc', ], 'dependencies': [ 'nacl.gyp:nacl_browser', + 'nacl_common.gyp:nacl_common', ], }], ['OS == "android"', { diff --git a/components/nacl.gyp b/components/nacl.gyp index 5ec4fce..4e1f804 100644 --- a/components/nacl.gyp +++ b/components/nacl.gyp @@ -107,6 +107,8 @@ 'nacl/browser/nacl_browser.h', 'nacl/browser/nacl_validation_cache.cc', 'nacl/browser/nacl_validation_cache.h', + 'nacl/browser/pnacl_translation_cache.cc', + 'nacl/browser/pnacl_translation_cache.h', ], 'include_dirs': [ '..', diff --git a/components/nacl/browser/DEPS b/components/nacl/browser/DEPS index 1c35d9c..67b5b47 100644 --- a/components/nacl/browser/DEPS +++ b/components/nacl/browser/DEPS @@ -1,3 +1,5 @@ include_rules = [ "+content/public/browser", + "+content/public/test", + "+net", ] diff --git a/components/nacl/browser/pnacl_translation_cache.cc b/components/nacl/browser/pnacl_translation_cache.cc new file mode 100644 index 0000000..e27103a --- /dev/null +++ b/components/nacl/browser/pnacl_translation_cache.cc @@ -0,0 +1,440 @@ +// Copyright 2013 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 "components/nacl/browser/pnacl_translation_cache.h" + +#include <string> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread_checker.h" +#include "components/nacl/common/pnacl_types.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/disk_cache/disk_cache.h" + +using base::IntToString; +using content::BrowserThread; + +namespace { + +void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); } + +} // namespace + +namespace pnacl { +// This is in pnacl namespace instead of static so they can be used +// by the unit test. +const int kMaxMemCacheSize = 100 * 1024 * 1024; + +////////////////////////////////////////////////////////////////////// +// Handle Reading/Writing to Cache. + +// PnaclTranslationCacheEntry is a shim that provides storage for the +// 'key' and 'data' strings as the disk_cache is performing various async +// operations. It also tracks the open disk_cache::Entry +// and ensures that the entry is closed. +class PnaclTranslationCacheEntry + : public base::RefCounted<PnaclTranslationCacheEntry> { + public: + static PnaclTranslationCacheEntry* GetReadEntry( + base::WeakPtr<PnaclTranslationCache> cache, + const std::string& key, + const GetNexeCallback& callback); + static PnaclTranslationCacheEntry* GetWriteEntry( + base::WeakPtr<PnaclTranslationCache> cache, + const std::string& key, + net::DrainableIOBuffer* write_nexe, + const CompletionCallback& callback); + + void Start(); + + // Writes: --- + // v | + // Start -> Open Existing --------------> Write ---> Close + // \ ^ + // \ / + // --> Create -- + // Reads: + // Start -> Open --------Read ----> Close + // | ^ + // |__| + enum CacheStep { + UNINITIALIZED, + OPEN_ENTRY, + CREATE_ENTRY, + TRANSFER_ENTRY, + CLOSE_ENTRY, + FINISHED + }; + + private: + friend class base::RefCounted<PnaclTranslationCacheEntry>; + PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache, + const std::string& key, + bool is_read); + ~PnaclTranslationCacheEntry(); + + // Try to open an existing entry in the backend + void OpenEntry(); + // Create a new entry in the backend (for writes) + void CreateEntry(); + // Write |len| bytes to the backend, starting at |offset| + void WriteEntry(int offset, int len); + // Read |len| bytes from the backend, starting at |offset| + void ReadEntry(int offset, int len); + // If there was an error, doom the entry. Then post a task to the IO + // thread to close (and delete) it. + void CloseEntry(int rv); + // Call the user callback, and signal to the cache to delete this. + void Finish(int rv); + // Used as the callback for all operations to the backend. Handle state + // transitions, track bytes transferred, and call the other helper methods. + void DispatchNext(int rv); + + base::WeakPtr<PnaclTranslationCache> cache_; + std::string key_; + disk_cache::Entry* entry_; + CacheStep step_; + bool is_read_; + GetNexeCallback read_callback_; + CompletionCallback write_callback_; + scoped_refptr<net::DrainableIOBuffer> io_buf_; + base::ThreadChecker thread_checker_; + DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry); +}; + +// static +PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry( + base::WeakPtr<PnaclTranslationCache> cache, + const std::string& key, + const GetNexeCallback& callback) { + PnaclTranslationCacheEntry* entry( + new PnaclTranslationCacheEntry(cache, key, true)); + entry->read_callback_ = callback; + return entry; +} + +// static +PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry( + base::WeakPtr<PnaclTranslationCache> cache, + const std::string& key, + net::DrainableIOBuffer* write_nexe, + const CompletionCallback& callback) { + PnaclTranslationCacheEntry* entry( + new PnaclTranslationCacheEntry(cache, key, false)); + entry->io_buf_ = write_nexe; + entry->write_callback_ = callback; + return entry; +} + +PnaclTranslationCacheEntry::PnaclTranslationCacheEntry( + base::WeakPtr<PnaclTranslationCache> cache, + const std::string& key, + bool is_read) + : cache_(cache), + key_(key), + entry_(NULL), + step_(UNINITIALIZED), + is_read_(is_read) {} + +PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() { + // Ensure we have called the user's callback + if (step_ != FINISHED) { + if (!read_callback_.is_null()) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(read_callback_, + net::ERR_ABORTED, + scoped_refptr<net::DrainableIOBuffer>())); + } + if (!write_callback_.is_null()) { + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(write_callback_, net::ERR_ABORTED)); + } + } +} + +void PnaclTranslationCacheEntry::Start() { + DCHECK(thread_checker_.CalledOnValidThread()); + step_ = OPEN_ENTRY; + OpenEntry(); +} + +// OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called +// from DispatchNext, so they know that cache_ is still valid. +void PnaclTranslationCacheEntry::OpenEntry() { + int rv = cache_->backend()->OpenEntry( + key_, + &entry_, + base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); + if (rv != net::ERR_IO_PENDING) + DispatchNext(rv); +} + +void PnaclTranslationCacheEntry::CreateEntry() { + int rv = cache_->backend()->CreateEntry( + key_, + &entry_, + base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); + if (rv != net::ERR_IO_PENDING) + DispatchNext(rv); +} + +void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) { + DCHECK(io_buf_->BytesRemaining() == len); + int rv = entry_->WriteData( + 1, + offset, + io_buf_.get(), + len, + base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this), + false); + if (rv != net::ERR_IO_PENDING) + DispatchNext(rv); +} + +void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) { + int rv = entry_->ReadData( + 1, + offset, + io_buf_.get(), + len, + base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); + if (rv != net::ERR_IO_PENDING) + DispatchNext(rv); +} + +void PnaclTranslationCacheEntry::CloseEntry(int rv) { + DCHECK(entry_); + if (rv < 0) { + LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv); + entry_->Doom(); + } + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_)); + Finish(rv); +} + +void PnaclTranslationCacheEntry::Finish(int rv) { + step_ = FINISHED; + if (is_read_) { + if (!read_callback_.is_null()) { + BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(read_callback_, rv, io_buf_)); + } + } else { + if (!write_callback_.is_null()) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind(write_callback_, rv)); + } + } + cache_->OpComplete(this); +} + +void PnaclTranslationCacheEntry::DispatchNext(int rv) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!cache_) + return; + + switch (step_) { + case UNINITIALIZED: + case FINISHED: + LOG(ERROR) << "DispatchNext called uninitialized"; + break; + + case OPEN_ENTRY: + if (rv == net::OK) { + step_ = TRANSFER_ENTRY; + if (is_read_) { + int bytes_to_transfer = entry_->GetDataSize(1); + io_buf_ = new net::DrainableIOBuffer( + new net::IOBuffer(bytes_to_transfer), bytes_to_transfer); + ReadEntry(0, bytes_to_transfer); + } else { + WriteEntry(0, io_buf_->size()); + } + } else { + if (rv != net::ERR_FAILED) { + // ERROR_FAILED is what we expect if the entry doesn't exist. + LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv); + } + if (is_read_) { + // Just a cache miss, not necessarily an error. + entry_ = NULL; + Finish(rv); + } else { + step_ = CREATE_ENTRY; + CreateEntry(); + } + } + break; + + case CREATE_ENTRY: + if (rv == net::OK) { + step_ = TRANSFER_ENTRY; + WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); + } else { + LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv); + Finish(rv); + } + break; + + case TRANSFER_ENTRY: + if (rv < 0) { + // We do not call DispatchNext directly if WriteEntry/ReadEntry returns + // ERR_IO_PENDING, and the callback should not return that value either. + LOG(ERROR) << "Failed to complete write to entry: " + << net::ErrorToString(rv); + step_ = CLOSE_ENTRY; + CloseEntry(rv); + break; + } else if (rv > 0) { + io_buf_->DidConsume(rv); + if (io_buf_->BytesRemaining() > 0) { + is_read_ + ? ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()) + : WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining()); + break; + } + } + // rv == 0 or we fell through (i.e. we have transferred all the bytes) + step_ = CLOSE_ENTRY; + DCHECK(io_buf_->BytesConsumed() == io_buf_->size()); + if (is_read_) + io_buf_->SetOffset(0); + CloseEntry(0); + break; + + case CLOSE_ENTRY: + step_ = UNINITIALIZED; + break; + } +} + +////////////////////////////////////////////////////////////////////// +void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) { + open_entries_.erase(entry); +} + +////////////////////////////////////////////////////////////////////// +// Construction and cache backend initialization +PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {} + +PnaclTranslationCache::~PnaclTranslationCache() {} + +int PnaclTranslationCache::Init(net::CacheType cache_type, + const base::FilePath& cache_dir, + int cache_size, + const CompletionCallback& callback) { + int rv = disk_cache::CreateCacheBackend( + cache_type, + net::CACHE_BACKEND_DEFAULT, + cache_dir, + cache_size, + true /* force_initialize */, + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), + NULL, /* dummy net log */ + &disk_cache_, + base::Bind(&PnaclTranslationCache::OnCreateBackendComplete, AsWeakPtr())); + if (rv == net::ERR_IO_PENDING) { + init_callback_ = callback; + } + return rv; +} + +void PnaclTranslationCache::OnCreateBackendComplete(int rv) { + if (rv < 0) { + LOG(ERROR) << "Backend init failed:" << net::ErrorToString(rv); + } + // Invoke our client's callback function. + if (!init_callback_.is_null()) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, base::Bind(init_callback_, rv)); + } +} + +////////////////////////////////////////////////////////////////////// +// High-level API + +void PnaclTranslationCache::StoreNexe(const std::string& key, + net::DrainableIOBuffer* nexe_data, + const CompletionCallback& callback) { + PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry( + AsWeakPtr(), key, nexe_data, callback); + open_entries_[entry] = entry; + entry->Start(); +} + +void PnaclTranslationCache::GetNexe(const std::string& key, + const GetNexeCallback& callback) { + PnaclTranslationCacheEntry* entry = + PnaclTranslationCacheEntry::GetReadEntry(AsWeakPtr(), key, callback); + open_entries_[entry] = entry; + entry->Start(); +} + +int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory, + const CompletionCallback& callback) { + in_memory_ = false; + return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */, callback); +} + +int PnaclTranslationCache::InitInMemory(const CompletionCallback& callback) { + in_memory_ = true; + return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize, callback); +} + +int PnaclTranslationCache::Size() { + if (!disk_cache_) + return -1; + return disk_cache_->GetEntryCount(); +} + +// static +std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) { + if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0) + return std::string(); + std::string retval("ABI:"); + retval += IntToString(info.abi_version) + ";" + "opt:" + + IntToString(info.opt_level) + ";" + "URL:"; + // Filter the username, password, and ref components from the URL + GURL::Replacements replacements; + replacements.ClearUsername(); + replacements.ClearPassword(); + replacements.ClearRef(); + GURL key_url(info.pexe_url.ReplaceComponents(replacements)); + retval += key_url.spec() + ";"; + // You would think that there is already code to format base::Time values + // somewhere, but I haven't found it yet. In any case, doing it ourselves + // here means we can keep the format stable. + base::Time::Exploded exploded; + info.last_modified.UTCExplode(&exploded); + if (info.last_modified.is_null() || !exploded.HasValidValues()) { + memset(&exploded, 0, sizeof(exploded)); + } + retval += "modified:" + IntToString(exploded.year) + ":" + + IntToString(exploded.month) + ":" + + IntToString(exploded.day_of_month) + ":" + + IntToString(exploded.hour) + ":" + IntToString(exploded.minute) + + ":" + IntToString(exploded.second) + ":" + + IntToString(exploded.millisecond) + ":UTC;"; + retval += "etag:" + info.etag; + return retval; +} + +int PnaclTranslationCache::DoomEntriesBetween( + base::Time initial, + base::Time end, + const CompletionCallback& callback) { + return disk_cache_->DoomEntriesBetween(initial, end, callback); +} + +} // namespace pnacl diff --git a/components/nacl/browser/pnacl_translation_cache.h b/components/nacl/browser/pnacl_translation_cache.h new file mode 100644 index 0000000..9dd528c --- /dev/null +++ b/components/nacl/browser/pnacl_translation_cache.h @@ -0,0 +1,106 @@ +// Copyright 2013 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. + +#ifndef COMPONENTS_NACL_BROWSER_PNACL_TRANSLATION_CACHE_H_ +#define COMPONENTS_NACL_BROWSER_PNACL_TRANSLATION_CACHE_H_ + +#include <map> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "net/base/cache_type.h" + +namespace base { +class MessageLoopProxy; +} + +namespace disk_cache { +class Backend; +} + +namespace nacl { +struct PnaclCacheInfo; +} + +namespace net { +class DrainableIOBuffer; +} + +namespace pnacl { +typedef base::Callback<void(int)> CompletionCallback; +typedef base::Callback<void(int, scoped_refptr<net::DrainableIOBuffer>)> + GetNexeCallback; +class PnaclTranslationCacheEntry; +extern const int kMaxMemCacheSize; + +class PnaclTranslationCache + : public base::SupportsWeakPtr<PnaclTranslationCache> { + public: + PnaclTranslationCache(); + virtual ~PnaclTranslationCache(); + + // Initialize the translation cache in |cache_dir|. If the return value is + // net::ERR_IO_PENDING, |callback| will be called with a 0 argument on sucess + // and <0 otherwise. + int InitOnDisk(const base::FilePath& cache_dir, + const CompletionCallback& callback); + + // Initialize the translation cache in memory. If the return value is + // net::ERR_IO_PENDING, |callback| will be called with a 0 argument on sucess + // and <0 otherwise. + int InitInMemory(const CompletionCallback& callback); + + // Store the nexe in the translation cache, and call |callback| with + // the result. The result passed to the callback is 0 on success and + // <0 otherwise. A reference to |nexe_data| is held until completion + // or cancellation. + void StoreNexe(const std::string& key, + net::DrainableIOBuffer* nexe_data, + const CompletionCallback& callback); + + // Retrieve the nexe from the translation cache. Write the data into |nexe| + // and call |callback|, passing a result code (0 on success and <0 otherwise), + // and a DrainableIOBuffer with the data. + void GetNexe(const std::string& key, const GetNexeCallback& callback); + + // Return the number of entries in the cache backend. + int Size(); + + // Return the cache key for |info| + static std::string GetKey(const nacl::PnaclCacheInfo& info); + + // Doom all entries between |initial| and |end|. If the return value is + // net::ERR_IO_PENDING, |callback| will be invoked when the operation + // completes. + int DoomEntriesBetween(base::Time initial, base::Time end, + const CompletionCallback& callback); + + private: + friend class PnaclTranslationCacheEntry; + friend class PnaclTranslationCacheTest; + // PnaclTranslationCacheEntry should only use the + // OpComplete and backend methods on PnaclTranslationCache. + void OpComplete(PnaclTranslationCacheEntry* entry); + disk_cache::Backend* backend() { return disk_cache_.get(); } + + int Init(net::CacheType, + const base::FilePath& directory, + int cache_size, + const CompletionCallback& callback); + + void OnCreateBackendComplete(int rv); + + scoped_ptr<disk_cache::Backend> disk_cache_; + CompletionCallback init_callback_; + bool in_memory_; + std::map<void*, scoped_refptr<PnaclTranslationCacheEntry> > open_entries_; + + DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCache); +}; + +} // namespace pnacl + +#endif // COMPONENTS_NACL_BROWSER_PNACL_TRANSLATION_CACHE_H_ diff --git a/components/nacl/browser/pnacl_translation_cache_unittest.cc b/components/nacl/browser/pnacl_translation_cache_unittest.cc new file mode 100644 index 0000000..94cd681 --- /dev/null +++ b/components/nacl/browser/pnacl_translation_cache_unittest.cc @@ -0,0 +1,269 @@ +// Copyright 2013 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 "components/nacl/browser/pnacl_translation_cache.h" + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "components/nacl/common/pnacl_types.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; +using base::FilePath; + +namespace pnacl { + +const int kTestDiskCacheSize = 16 * 1024 * 1024; + +class PnaclTranslationCacheTest : public testing::Test { + protected: + PnaclTranslationCacheTest() + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} + virtual ~PnaclTranslationCacheTest() {} + virtual void SetUp() { cache_.reset(new PnaclTranslationCache()); } + virtual void TearDown() { + // The destructor of PnaclTranslationCacheWriteEntry posts a task to the IO + // thread to close the backend cache entry. We want to make sure the entries + // are closed before we delete the backend (and in particular the destructor + // for the memory backend has a DCHECK to verify this), so we run the loop + // here to ensure the task gets processed. + base::RunLoop().RunUntilIdle(); + cache_.reset(); + } + + void InitBackend(bool in_mem); + void StoreNexe(const std::string& key, const std::string& nexe); + std::string GetNexe(const std::string& key); + + scoped_ptr<PnaclTranslationCache> cache_; + content::TestBrowserThreadBundle thread_bundle_; + base::ScopedTempDir temp_dir_; +}; + +void PnaclTranslationCacheTest::InitBackend(bool in_mem) { + net::TestCompletionCallback init_cb; + if (!in_mem) { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + // Use the private init method so we can control the size + int rv = cache_->Init(in_mem ? net::MEMORY_CACHE : net::PNACL_CACHE, + temp_dir_.path(), + in_mem ? kMaxMemCacheSize : kTestDiskCacheSize, + init_cb.callback()); + if (in_mem) + ASSERT_EQ(net::OK, rv); + ASSERT_EQ(net::OK, init_cb.GetResult(rv)); + ASSERT_EQ(0, cache_->Size()); +} + +void PnaclTranslationCacheTest::StoreNexe(const std::string& key, + const std::string& nexe) { + net::TestCompletionCallback store_cb; + scoped_refptr<net::DrainableIOBuffer> nexe_buf( + new net::DrainableIOBuffer(new net::StringIOBuffer(nexe), nexe.size())); + cache_->StoreNexe(key, nexe_buf, store_cb.callback()); + // Using ERR_IO_PENDING here causes the callback to wait for the result + // which should be harmless even if it returns OK immediately. This is because + // we don't plumb the intermediate writing stages all the way out. + EXPECT_EQ(net::OK, store_cb.GetResult(net::ERR_IO_PENDING)); +} + +// Inspired by net::TestCompletionCallback. Instantiate a TestNexeCallback and +// pass the GetNexeCallback returned by the callback() method to GetNexe. +// Then call GetResult, which will pump the message loop until it gets a result, +// return the resulting IOBuffer and fill in the return value +class TestNexeCallback { + public: + TestNexeCallback() + : have_result_(false), + result_(-1), + cb_(base::Bind(&TestNexeCallback::SetResult, base::Unretained(this))) {} + GetNexeCallback callback() { return cb_; } + net::DrainableIOBuffer* GetResult(int* result) { + while (!have_result_) + base::RunLoop().RunUntilIdle(); + have_result_ = false; + *result = result_; + return buf_.get(); + } + + private: + void SetResult(int rv, scoped_refptr<net::DrainableIOBuffer> buf) { + have_result_ = true; + result_ = rv; + buf_ = buf; + } + bool have_result_; + int result_; + scoped_refptr<net::DrainableIOBuffer> buf_; + const GetNexeCallback cb_; +}; + +std::string PnaclTranslationCacheTest::GetNexe(const std::string& key) { + TestNexeCallback load_cb; + cache_->GetNexe(key, load_cb.callback()); + int rv; + scoped_refptr<net::DrainableIOBuffer> buf(load_cb.GetResult(&rv)); + EXPECT_EQ(net::OK, rv); + if (buf.get() == NULL) // for some reason ASSERT macros don't work here. + return std::string(); + std::string nexe(buf->data(), buf->size()); + return nexe; +} + +static const std::string test_key("1"); +static const std::string test_store_val("testnexe"); +static const int kLargeNexeSize = 8 * 1024 * 1024; + +TEST(PnaclTranslationCacheKeyTest, CacheKeyTest) { + nacl::PnaclCacheInfo info; + info.pexe_url = GURL("http://www.google.com"); + info.abi_version = 0; + info.opt_level = 0; + std::string test_time("Wed, 15 Nov 1995 06:25:24 GMT"); + base::Time::FromString(test_time.c_str(), &info.last_modified); + // Basic check for URL and time components + EXPECT_EQ("ABI:0;opt:0;URL:http://www.google.com/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + // Check that query portion of URL is not stripped + info.pexe_url = GURL("http://www.google.com/?foo=bar"); + EXPECT_EQ("ABI:0;opt:0;URL:http://www.google.com/?foo=bar;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + // Check that username, password, and normal port are stripped + info.pexe_url = GURL("https://user:host@www.google.com:443/"); + EXPECT_EQ("ABI:0;opt:0;URL:https://www.google.com/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + // Check that unusual port is not stripped but ref is stripped + info.pexe_url = GURL("https://www.google.com:444/#foo"); + EXPECT_EQ("ABI:0;opt:0;URL:https://www.google.com:444/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + // Check chrome-extesnsion scheme + info.pexe_url = GURL("chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/"); + EXPECT_EQ("ABI:0;opt:0;" + "URL:chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + // Check that ABI version, opt level, and etag are in the key + info.pexe_url = GURL("http://www.google.com/"); + info.abi_version = 2; + EXPECT_EQ("ABI:2;opt:0;URL:http://www.google.com/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + info.opt_level = 2; + EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:", + PnaclTranslationCache::GetKey(info)); + info.etag = std::string("etag"); + EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;" + "modified:1995:11:15:6:25:24:0:UTC;etag:etag", + PnaclTranslationCache::GetKey(info)); + + // Check for all the time components, and null time + info.last_modified = base::Time(); + EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;" + "modified:0:0:0:0:0:0:0:UTC;etag:etag", + PnaclTranslationCache::GetKey(info)); + test_time.assign("Fri, 29 Feb 2008 13:04:12 GMT"); + base::Time::FromString(test_time.c_str(), &info.last_modified); + EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;" + "modified:2008:2:29:13:4:12:0:UTC;etag:etag", + PnaclTranslationCache::GetKey(info)); +} + +TEST_F(PnaclTranslationCacheTest, StoreSmallInMem) { + // Test that a single store puts something in the mem backend + InitBackend(true); + StoreNexe(test_key, test_store_val); + EXPECT_EQ(1, cache_->Size()); +} + +TEST_F(PnaclTranslationCacheTest, StoreSmallOnDisk) { + // Test that a single store puts something in the disk backend + InitBackend(false); + StoreNexe(test_key, test_store_val); + EXPECT_EQ(1, cache_->Size()); +} + +TEST_F(PnaclTranslationCacheTest, StoreLargeOnDisk) { + // Test a value too large(?) for a single I/O operation + InitBackend(false); + const std::string large_buffer(kLargeNexeSize, 'a'); + StoreNexe(test_key, large_buffer); + EXPECT_EQ(1, cache_->Size()); +} + +TEST_F(PnaclTranslationCacheTest, InMemSizeLimit) { + InitBackend(true); + scoped_refptr<net::DrainableIOBuffer> large_buffer(new net::DrainableIOBuffer( + new net::StringIOBuffer(std::string(kMaxMemCacheSize + 1, 'a')), + kMaxMemCacheSize + 1)); + net::TestCompletionCallback store_cb; + cache_->StoreNexe(test_key, large_buffer, store_cb.callback()); + EXPECT_EQ(net::ERR_FAILED, store_cb.GetResult(net::ERR_IO_PENDING)); + base::RunLoop().RunUntilIdle(); // Ensure the entry is closed. + EXPECT_EQ(0, cache_->Size()); +} + +TEST_F(PnaclTranslationCacheTest, GetOneInMem) { + InitBackend(true); + StoreNexe(test_key, test_store_val); + EXPECT_EQ(1, cache_->Size()); + EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val)); +} + +TEST_F(PnaclTranslationCacheTest, GetOneOnDisk) { + InitBackend(false); + StoreNexe(test_key, test_store_val); + EXPECT_EQ(1, cache_->Size()); + EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val)); +} + +TEST_F(PnaclTranslationCacheTest, GetLargeOnDisk) { + InitBackend(false); + const std::string large_buffer(kLargeNexeSize, 'a'); + StoreNexe(test_key, large_buffer); + EXPECT_EQ(1, cache_->Size()); + EXPECT_EQ(0, GetNexe(test_key).compare(large_buffer)); +} + +TEST_F(PnaclTranslationCacheTest, StoreTwice) { + // Test that storing twice with the same key overwrites + InitBackend(true); + StoreNexe(test_key, test_store_val); + StoreNexe(test_key, test_store_val + "aaa"); + EXPECT_EQ(1, cache_->Size()); + EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val + "aaa")); +} + +TEST_F(PnaclTranslationCacheTest, StoreTwo) { + InitBackend(true); + StoreNexe(test_key, test_store_val); + StoreNexe(test_key + "a", test_store_val + "aaa"); + EXPECT_EQ(2, cache_->Size()); + EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val)); + EXPECT_EQ(0, GetNexe(test_key + "a").compare(test_store_val + "aaa")); +} + +TEST_F(PnaclTranslationCacheTest, GetMiss) { + InitBackend(true); + StoreNexe(test_key, test_store_val); + TestNexeCallback load_cb; + std::string nexe; + cache_->GetNexe(test_key + "a", load_cb.callback()); + int rv; + scoped_refptr<net::DrainableIOBuffer> buf(load_cb.GetResult(&rv)); + EXPECT_EQ(net::ERR_FAILED, rv); +} + +} // namespace pnacl diff --git a/components/nacl_common.gyp b/components/nacl_common.gyp index cedf675..960eb1c 100644 --- a/components/nacl_common.gyp +++ b/components/nacl_common.gyp @@ -25,6 +25,8 @@ 'nacl/common/nacl_messages.h', 'nacl/common/nacl_types.cc', 'nacl/common/nacl_types.h', + 'nacl/common/pnacl_types.cc', + 'nacl/common/pnacl_types.h', ], 'include_dirs': [ '..', |