1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/nacl/browser/pnacl_translation_cache.h"
6
7#include "base/files/file_path.h"
8#include "base/files/scoped_temp_dir.h"
9#include "base/message_loop/message_loop.h"
10#include "base/run_loop.h"
11#include "components/nacl/common/pnacl_types.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/test/test_browser_thread_bundle.h"
14#include "net/base/io_buffer.h"
15#include "net/base/test_completion_callback.h"
16#include "testing/gtest/include/gtest/gtest.h"
17
18using content::BrowserThread;
19using base::FilePath;
20
21namespace pnacl {
22
23const int kTestDiskCacheSize = 16 * 1024 * 1024;
24
25class PnaclTranslationCacheTest : public testing::Test {
26 protected:
27  PnaclTranslationCacheTest()
28      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
29  virtual ~PnaclTranslationCacheTest() {}
30  virtual void SetUp() { cache_.reset(new PnaclTranslationCache()); }
31  virtual void TearDown() {
32    // The destructor of PnaclTranslationCacheWriteEntry posts a task to the IO
33    // thread to close the backend cache entry. We want to make sure the entries
34    // are closed before we delete the backend (and in particular the destructor
35    // for the memory backend has a DCHECK to verify this), so we run the loop
36    // here to ensure the task gets processed.
37    base::RunLoop().RunUntilIdle();
38    cache_.reset();
39  }
40
41  void InitBackend(bool in_mem);
42  void StoreNexe(const std::string& key, const std::string& nexe);
43  std::string GetNexe(const std::string& key);
44
45  scoped_ptr<PnaclTranslationCache> cache_;
46  content::TestBrowserThreadBundle thread_bundle_;
47  base::ScopedTempDir temp_dir_;
48};
49
50void PnaclTranslationCacheTest::InitBackend(bool in_mem) {
51  net::TestCompletionCallback init_cb;
52  if (!in_mem) {
53    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
54  }
55  // Use the private init method so we can control the size
56  int rv = cache_->Init(in_mem ? net::MEMORY_CACHE : net::PNACL_CACHE,
57                        temp_dir_.path(),
58                        in_mem ? kMaxMemCacheSize : kTestDiskCacheSize,
59                        init_cb.callback());
60  if (in_mem)
61    ASSERT_EQ(net::OK, rv);
62  ASSERT_EQ(net::OK, init_cb.GetResult(rv));
63  ASSERT_EQ(0, cache_->Size());
64}
65
66void PnaclTranslationCacheTest::StoreNexe(const std::string& key,
67                                          const std::string& nexe) {
68  net::TestCompletionCallback store_cb;
69  scoped_refptr<net::DrainableIOBuffer> nexe_buf(
70      new net::DrainableIOBuffer(new net::StringIOBuffer(nexe), nexe.size()));
71  cache_->StoreNexe(key, nexe_buf.get(), store_cb.callback());
72  // Using ERR_IO_PENDING here causes the callback to wait for the result
73  // which should be harmless even if it returns OK immediately. This is because
74  // we don't plumb the intermediate writing stages all the way out.
75  EXPECT_EQ(net::OK, store_cb.GetResult(net::ERR_IO_PENDING));
76}
77
78// Inspired by net::TestCompletionCallback. Instantiate a TestNexeCallback and
79// pass the GetNexeCallback returned by the callback() method to GetNexe.
80// Then call GetResult, which will pump the message loop until it gets a result,
81// return the resulting IOBuffer and fill in the return value
82class TestNexeCallback {
83 public:
84  TestNexeCallback()
85      : have_result_(false),
86        result_(-1),
87        cb_(base::Bind(&TestNexeCallback::SetResult, base::Unretained(this))) {}
88  GetNexeCallback callback() { return cb_; }
89  net::DrainableIOBuffer* GetResult(int* result) {
90    while (!have_result_)
91      base::RunLoop().RunUntilIdle();
92    have_result_ = false;
93    *result = result_;
94    return buf_.get();
95  }
96
97 private:
98  void SetResult(int rv, scoped_refptr<net::DrainableIOBuffer> buf) {
99    have_result_ = true;
100    result_ = rv;
101    buf_ = buf;
102  }
103  bool have_result_;
104  int result_;
105  scoped_refptr<net::DrainableIOBuffer> buf_;
106  const GetNexeCallback cb_;
107};
108
109std::string PnaclTranslationCacheTest::GetNexe(const std::string& key) {
110  TestNexeCallback load_cb;
111  cache_->GetNexe(key, load_cb.callback());
112  int rv;
113  scoped_refptr<net::DrainableIOBuffer> buf(load_cb.GetResult(&rv));
114  EXPECT_EQ(net::OK, rv);
115  if (buf.get() == NULL) // for some reason ASSERT macros don't work here.
116    return std::string();
117  std::string nexe(buf->data(), buf->size());
118  return nexe;
119}
120
121static const std::string test_key("1");
122static const std::string test_store_val("testnexe");
123static const int kLargeNexeSize = 8 * 1024 * 1024;
124
125TEST(PnaclTranslationCacheKeyTest, CacheKeyTest) {
126  nacl::PnaclCacheInfo info;
127  info.pexe_url = GURL("http://www.google.com");
128  info.abi_version = 0;
129  info.opt_level = 0;
130  info.sandbox_isa = "x86-32";
131  std::string test_time("Wed, 15 Nov 1995 06:25:24 GMT");
132  base::Time::FromString(test_time.c_str(), &info.last_modified);
133  // Basic check for URL and time components
134  EXPECT_EQ("ABI:0;opt:0;URL:http://www.google.com/;"
135            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
136            "sandbox:x86-32;extra_flags:;",
137            PnaclTranslationCache::GetKey(info));
138  // Check that query portion of URL is not stripped
139  info.pexe_url = GURL("http://www.google.com/?foo=bar");
140  EXPECT_EQ("ABI:0;opt:0;URL:http://www.google.com/?foo=bar;"
141            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
142            "sandbox:x86-32;extra_flags:;",
143            PnaclTranslationCache::GetKey(info));
144  // Check that username, password, and normal port are stripped
145  info.pexe_url = GURL("https://user:host@www.google.com:443/");
146  EXPECT_EQ("ABI:0;opt:0;URL:https://www.google.com/;"
147            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
148            "sandbox:x86-32;extra_flags:;",
149            PnaclTranslationCache::GetKey(info));
150  // Check that unusual port is not stripped but ref is stripped
151  info.pexe_url = GURL("https://www.google.com:444/#foo");
152  EXPECT_EQ("ABI:0;opt:0;URL:https://www.google.com:444/;"
153            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
154            "sandbox:x86-32;extra_flags:;",
155            PnaclTranslationCache::GetKey(info));
156  // Check chrome-extesnsion scheme
157  info.pexe_url = GURL("chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/");
158  EXPECT_EQ("ABI:0;opt:0;"
159            "URL:chrome-extension://ljacajndfccfgnfohlgkdphmbnpkjflk/;"
160            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
161            "sandbox:x86-32;extra_flags:;",
162            PnaclTranslationCache::GetKey(info));
163  // Check that ABI version, opt level, and etag are in the key
164  info.pexe_url = GURL("http://www.google.com/");
165  info.abi_version = 2;
166  EXPECT_EQ("ABI:2;opt:0;URL:http://www.google.com/;"
167            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
168            "sandbox:x86-32;extra_flags:;",
169            PnaclTranslationCache::GetKey(info));
170  info.opt_level = 2;
171  EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
172            "modified:1995:11:15:6:25:24:0:UTC;etag:;"
173            "sandbox:x86-32;extra_flags:;",
174            PnaclTranslationCache::GetKey(info));
175  info.etag = std::string("etag");
176  EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
177            "modified:1995:11:15:6:25:24:0:UTC;etag:etag;"
178            "sandbox:x86-32;extra_flags:;",
179            PnaclTranslationCache::GetKey(info));
180
181  info.extra_flags = "-mavx-neon";
182  EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
183            "modified:1995:11:15:6:25:24:0:UTC;etag:etag;"
184            "sandbox:x86-32;extra_flags:-mavx-neon;",
185            PnaclTranslationCache::GetKey(info));
186
187  // Check for all the time components, and null time
188  info.last_modified = base::Time();
189  EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
190            "modified:0:0:0:0:0:0:0:UTC;etag:etag;"
191            "sandbox:x86-32;extra_flags:-mavx-neon;",
192            PnaclTranslationCache::GetKey(info));
193  test_time.assign("Fri, 29 Feb 2008 13:04:12 GMT");
194  base::Time::FromString(test_time.c_str(), &info.last_modified);
195  EXPECT_EQ("ABI:2;opt:2;URL:http://www.google.com/;"
196            "modified:2008:2:29:13:4:12:0:UTC;etag:etag;"
197            "sandbox:x86-32;extra_flags:-mavx-neon;",
198            PnaclTranslationCache::GetKey(info));
199}
200
201TEST_F(PnaclTranslationCacheTest, StoreSmallInMem) {
202  // Test that a single store puts something in the mem backend
203  InitBackend(true);
204  StoreNexe(test_key, test_store_val);
205  EXPECT_EQ(1, cache_->Size());
206}
207
208TEST_F(PnaclTranslationCacheTest, StoreSmallOnDisk) {
209  // Test that a single store puts something in the disk backend
210  InitBackend(false);
211  StoreNexe(test_key, test_store_val);
212  EXPECT_EQ(1, cache_->Size());
213}
214
215TEST_F(PnaclTranslationCacheTest, StoreLargeOnDisk) {
216  // Test a value too large(?) for a single I/O operation
217  InitBackend(false);
218  const std::string large_buffer(kLargeNexeSize, 'a');
219  StoreNexe(test_key, large_buffer);
220  EXPECT_EQ(1, cache_->Size());
221}
222
223TEST_F(PnaclTranslationCacheTest, InMemSizeLimit) {
224  InitBackend(true);
225  scoped_refptr<net::DrainableIOBuffer> large_buffer(new net::DrainableIOBuffer(
226      new net::StringIOBuffer(std::string(kMaxMemCacheSize + 1, 'a')),
227      kMaxMemCacheSize + 1));
228  net::TestCompletionCallback store_cb;
229  cache_->StoreNexe(test_key, large_buffer.get(), store_cb.callback());
230  EXPECT_EQ(net::ERR_FAILED, store_cb.GetResult(net::ERR_IO_PENDING));
231  base::RunLoop().RunUntilIdle();  // Ensure the entry is closed.
232  EXPECT_EQ(0, cache_->Size());
233}
234
235TEST_F(PnaclTranslationCacheTest, GetOneInMem) {
236  InitBackend(true);
237  StoreNexe(test_key, test_store_val);
238  EXPECT_EQ(1, cache_->Size());
239  EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val));
240}
241
242TEST_F(PnaclTranslationCacheTest, GetOneOnDisk) {
243  InitBackend(false);
244  StoreNexe(test_key, test_store_val);
245  EXPECT_EQ(1, cache_->Size());
246  EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val));
247}
248
249TEST_F(PnaclTranslationCacheTest, GetLargeOnDisk) {
250  InitBackend(false);
251  const std::string large_buffer(kLargeNexeSize, 'a');
252  StoreNexe(test_key, large_buffer);
253  EXPECT_EQ(1, cache_->Size());
254  EXPECT_EQ(0, GetNexe(test_key).compare(large_buffer));
255}
256
257TEST_F(PnaclTranslationCacheTest, StoreTwice) {
258  // Test that storing twice with the same key overwrites
259  InitBackend(true);
260  StoreNexe(test_key, test_store_val);
261  StoreNexe(test_key, test_store_val + "aaa");
262  EXPECT_EQ(1, cache_->Size());
263  EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val + "aaa"));
264}
265
266TEST_F(PnaclTranslationCacheTest, StoreTwo) {
267  InitBackend(true);
268  StoreNexe(test_key, test_store_val);
269  StoreNexe(test_key + "a", test_store_val + "aaa");
270  EXPECT_EQ(2, cache_->Size());
271  EXPECT_EQ(0, GetNexe(test_key).compare(test_store_val));
272  EXPECT_EQ(0, GetNexe(test_key + "a").compare(test_store_val + "aaa"));
273}
274
275TEST_F(PnaclTranslationCacheTest, GetMiss) {
276  InitBackend(true);
277  StoreNexe(test_key, test_store_val);
278  TestNexeCallback load_cb;
279  std::string nexe;
280  cache_->GetNexe(test_key + "a", load_cb.callback());
281  int rv;
282  scoped_refptr<net::DrainableIOBuffer> buf(load_cb.GetResult(&rv));
283  EXPECT_EQ(net::ERR_FAILED, rv);
284}
285
286}  // namespace pnacl
287