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_host.h"
6
7#include <stdio.h>
8#include <string>
9
10#include "base/bind.h"
11#include "base/files/scoped_temp_dir.h"
12#include "base/run_loop.h"
13#include "base/threading/sequenced_worker_pool.h"
14#include "components/nacl/browser/pnacl_translation_cache.h"
15#include "content/public/browser/browser_thread.h"
16#include "content/public/test/test_browser_thread_bundle.h"
17#include "content/public/test/test_utils.h"
18#include "net/base/test_completion_callback.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21#if defined(OS_WIN)
22#define snprintf _snprintf
23#endif
24
25namespace pnacl {
26namespace {
27
28// Size of a buffer used for writing and reading from a file.
29const size_t kBufferSize = 16u;
30
31}  // namespace
32
33class PnaclHostTest : public testing::Test {
34 protected:
35  PnaclHostTest()
36      : host_(NULL),
37        temp_callback_count_(0),
38        write_callback_count_(0),
39        thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
40  virtual void SetUp() {
41    host_ = new PnaclHost();
42    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
43    host_->InitForTest(temp_dir_.path(), true);
44    base::RunLoop().RunUntilIdle();
45    EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_);
46  }
47  virtual void TearDown() {
48    EXPECT_EQ(0U, host_->pending_translations());
49    // Give the host a chance to de-init the backend, and then delete it.
50    host_->RendererClosing(0);
51    content::RunAllBlockingPoolTasksUntilIdle();
52    EXPECT_EQ(PnaclHost::CacheUninitialized, host_->cache_state_);
53    delete host_;
54  }
55  int GetCacheSize() { return host_->disk_cache_->Size(); }
56  int CacheIsInitialized() {
57    return host_->cache_state_ == PnaclHost::CacheReady;
58  }
59  void ReInitBackend() {
60    host_->InitForTest(temp_dir_.path(), true);
61    base::RunLoop().RunUntilIdle();
62    EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_);
63  }
64
65 public:  // Required for derived classes to bind this method
66          // Callbacks used by tests which call GetNexeFd.
67  // CallbackExpectMiss checks that the fd is valid and a miss is reported,
68  // and also writes some data into the file, which is read back by
69  // CallbackExpectHit
70  void CallbackExpectMiss(const base::File& file, bool is_hit) {
71    EXPECT_FALSE(is_hit);
72    ASSERT_TRUE(file.IsValid());
73    base::File::Info info;
74    base::File* mutable_file = const_cast<base::File*>(&file);
75    EXPECT_TRUE(mutable_file->GetInfo(&info));
76    EXPECT_FALSE(info.is_directory);
77    EXPECT_EQ(0LL, info.size);
78    char str[kBufferSize];
79    memset(str, 0x0, kBufferSize);
80    snprintf(str, kBufferSize, "testdata%d", ++write_callback_count_);
81    EXPECT_EQ(kBufferSize,
82              static_cast<size_t>(mutable_file->Write(0, str, kBufferSize)));
83    temp_callback_count_++;
84  }
85  void CallbackExpectHit(const base::File& file, bool is_hit) {
86    EXPECT_TRUE(is_hit);
87    ASSERT_TRUE(file.IsValid());
88    base::File::Info info;
89    base::File* mutable_file = const_cast<base::File*>(&file);
90    EXPECT_TRUE(mutable_file->GetInfo(&info));
91    EXPECT_FALSE(info.is_directory);
92    EXPECT_EQ(kBufferSize, static_cast<size_t>(info.size));
93    char data[kBufferSize];
94    memset(data, 0x0, kBufferSize);
95    char str[kBufferSize];
96    memset(str, 0x0, kBufferSize);
97    snprintf(str, kBufferSize, "testdata%d", write_callback_count_);
98    EXPECT_EQ(kBufferSize,
99              static_cast<size_t>(mutable_file->Read(0, data, kBufferSize)));
100    EXPECT_STREQ(str, data);
101    temp_callback_count_++;
102  }
103
104 protected:
105  PnaclHost* host_;
106  int temp_callback_count_;
107  int write_callback_count_;
108  content::TestBrowserThreadBundle thread_bundle_;
109  base::ScopedTempDir temp_dir_;
110};
111
112static nacl::PnaclCacheInfo GetTestCacheInfo() {
113  nacl::PnaclCacheInfo info;
114  info.pexe_url = GURL("http://www.google.com");
115  info.abi_version = 0;
116  info.opt_level = 0;
117  info.has_no_store_header = false;
118  return info;
119}
120
121#define GET_NEXE_FD(renderer, instance, incognito, info, expect_hit) \
122  do {                                                               \
123    SCOPED_TRACE("");                                                \
124    host_->GetNexeFd(                                                \
125        renderer,                                                    \
126        0, /* ignore render_view_id for now */                       \
127        instance,                                                    \
128        incognito,                                                   \
129        info,                                                        \
130        base::Bind(expect_hit ? &PnaclHostTest::CallbackExpectHit    \
131                              : &PnaclHostTest::CallbackExpectMiss,  \
132                   base::Unretained(this)));                         \
133  } while (0)
134
135TEST_F(PnaclHostTest, BasicMiss) {
136  nacl::PnaclCacheInfo info = GetTestCacheInfo();
137  // Test cold miss.
138  GET_NEXE_FD(0, 0, false, info, false);
139  EXPECT_EQ(1U, host_->pending_translations());
140  content::RunAllBlockingPoolTasksUntilIdle();
141  EXPECT_EQ(1U, host_->pending_translations());
142  EXPECT_EQ(1, temp_callback_count_);
143  host_->TranslationFinished(0, 0, true);
144  content::RunAllBlockingPoolTasksUntilIdle();
145  EXPECT_EQ(0U, host_->pending_translations());
146  // Test that a different cache info field also misses.
147  info.etag = std::string("something else");
148  GET_NEXE_FD(0, 0, false, info, false);
149  content::RunAllBlockingPoolTasksUntilIdle();
150  EXPECT_EQ(2, temp_callback_count_);
151  EXPECT_EQ(1U, host_->pending_translations());
152  host_->RendererClosing(0);
153  content::RunAllBlockingPoolTasksUntilIdle();
154  // Check that the cache has de-initialized after the last renderer goes away.
155  EXPECT_FALSE(CacheIsInitialized());
156}
157
158TEST_F(PnaclHostTest, BadArguments) {
159  nacl::PnaclCacheInfo info = GetTestCacheInfo();
160  GET_NEXE_FD(0, 0, false, info, false);
161  EXPECT_EQ(1U, host_->pending_translations());
162  host_->TranslationFinished(0, 1, true);  // nonexistent translation
163  EXPECT_EQ(1U, host_->pending_translations());
164  host_->RendererClosing(1);  // nonexistent renderer
165  EXPECT_EQ(1U, host_->pending_translations());
166  content::RunAllBlockingPoolTasksUntilIdle();
167  EXPECT_EQ(1, temp_callback_count_);
168  host_->RendererClosing(0);  // close without finishing
169}
170
171TEST_F(PnaclHostTest, BasicHit) {
172  nacl::PnaclCacheInfo info = GetTestCacheInfo();
173  GET_NEXE_FD(0, 0, false, info, false);
174  content::RunAllBlockingPoolTasksUntilIdle();
175  EXPECT_EQ(1, temp_callback_count_);
176  host_->TranslationFinished(0, 0, true);
177  content::RunAllBlockingPoolTasksUntilIdle();
178  GET_NEXE_FD(0, 1, false, info, true);
179  content::RunAllBlockingPoolTasksUntilIdle();
180  EXPECT_EQ(2, temp_callback_count_);
181  EXPECT_EQ(0U, host_->pending_translations());
182}
183
184TEST_F(PnaclHostTest, TranslationErrors) {
185  nacl::PnaclCacheInfo info = GetTestCacheInfo();
186  GET_NEXE_FD(0, 0, false, info, false);
187  // Early abort, before temp file request returns
188  host_->TranslationFinished(0, 0, false);
189  content::RunAllBlockingPoolTasksUntilIdle();
190  EXPECT_EQ(0U, host_->pending_translations());
191  EXPECT_EQ(0, temp_callback_count_);
192  // The backend will have been freed when the query comes back and there
193  // are no pending translations.
194  EXPECT_FALSE(CacheIsInitialized());
195  ReInitBackend();
196  // Check that another request for the same info misses successfully.
197  GET_NEXE_FD(0, 0, false, info, false);
198  content::RunAllBlockingPoolTasksUntilIdle();
199  host_->TranslationFinished(0, 0, true);
200  content::RunAllBlockingPoolTasksUntilIdle();
201  EXPECT_EQ(1, temp_callback_count_);
202  EXPECT_EQ(0U, host_->pending_translations());
203
204  // Now try sending the error after the temp file request returns
205  info.abi_version = 222;
206  GET_NEXE_FD(0, 0, false, info, false);
207  content::RunAllBlockingPoolTasksUntilIdle();
208  EXPECT_EQ(2, temp_callback_count_);
209  host_->TranslationFinished(0, 0, false);
210  content::RunAllBlockingPoolTasksUntilIdle();
211  EXPECT_EQ(0U, host_->pending_translations());
212  // Check another successful miss
213  GET_NEXE_FD(0, 0, false, info, false);
214  content::RunAllBlockingPoolTasksUntilIdle();
215  EXPECT_EQ(3, temp_callback_count_);
216  host_->TranslationFinished(0, 0, false);
217  EXPECT_EQ(0U, host_->pending_translations());
218}
219
220TEST_F(PnaclHostTest, OverlappedMissesAfterTempReturn) {
221  nacl::PnaclCacheInfo info = GetTestCacheInfo();
222  GET_NEXE_FD(0, 0, false, info, false);
223  content::RunAllBlockingPoolTasksUntilIdle();
224  EXPECT_EQ(1, temp_callback_count_);
225  EXPECT_EQ(1U, host_->pending_translations());
226  // Test that a second request for the same nexe while the first one is still
227  // outstanding eventually hits.
228  GET_NEXE_FD(0, 1, false, info, true);
229  content::RunAllBlockingPoolTasksUntilIdle();
230  EXPECT_EQ(2U, host_->pending_translations());
231  // The temp file should not be returned to the second request until after the
232  // first is finished translating.
233  EXPECT_EQ(1, temp_callback_count_);
234  host_->TranslationFinished(0, 0, true);
235  content::RunAllBlockingPoolTasksUntilIdle();
236  EXPECT_EQ(2, temp_callback_count_);
237  EXPECT_EQ(0U, host_->pending_translations());
238}
239
240TEST_F(PnaclHostTest, OverlappedMissesBeforeTempReturn) {
241  nacl::PnaclCacheInfo info = GetTestCacheInfo();
242  GET_NEXE_FD(0, 0, false, info, false);
243  // Send the 2nd fd request before the first one returns a temp file.
244  GET_NEXE_FD(0, 1, false, info, true);
245  content::RunAllBlockingPoolTasksUntilIdle();
246  EXPECT_EQ(1, temp_callback_count_);
247  EXPECT_EQ(2U, host_->pending_translations());
248  content::RunAllBlockingPoolTasksUntilIdle();
249  EXPECT_EQ(2U, host_->pending_translations());
250  EXPECT_EQ(1, temp_callback_count_);
251  host_->TranslationFinished(0, 0, true);
252  content::RunAllBlockingPoolTasksUntilIdle();
253  EXPECT_EQ(2, temp_callback_count_);
254  EXPECT_EQ(0U, host_->pending_translations());
255}
256
257TEST_F(PnaclHostTest, OverlappedHitsBeforeTempReturn) {
258  nacl::PnaclCacheInfo info = GetTestCacheInfo();
259  // Store one in the cache and complete it.
260  GET_NEXE_FD(0, 0, false, info, false);
261  content::RunAllBlockingPoolTasksUntilIdle();
262  EXPECT_EQ(1, temp_callback_count_);
263  host_->TranslationFinished(0, 0, true);
264  content::RunAllBlockingPoolTasksUntilIdle();
265  EXPECT_EQ(0U, host_->pending_translations());
266  GET_NEXE_FD(0, 0, false, info, true);
267  // Request the second before the first temp file returns.
268  GET_NEXE_FD(0, 1, false, info, true);
269  content::RunAllBlockingPoolTasksUntilIdle();
270  EXPECT_EQ(3, temp_callback_count_);
271  EXPECT_EQ(0U, host_->pending_translations());
272}
273
274TEST_F(PnaclHostTest, OverlappedHitsAfterTempReturn) {
275  nacl::PnaclCacheInfo info = GetTestCacheInfo();
276  // Store one in the cache and complete it.
277  GET_NEXE_FD(0, 0, false, info, false);
278  content::RunAllBlockingPoolTasksUntilIdle();
279  EXPECT_EQ(1, temp_callback_count_);
280  host_->TranslationFinished(0, 0, true);
281  content::RunAllBlockingPoolTasksUntilIdle();
282  EXPECT_EQ(0U, host_->pending_translations());
283  GET_NEXE_FD(0, 0, false, info, true);
284  content::RunAllBlockingPoolTasksUntilIdle();
285  GET_NEXE_FD(0, 1, false, info, true);
286  content::RunAllBlockingPoolTasksUntilIdle();
287  EXPECT_EQ(3, temp_callback_count_);
288  EXPECT_EQ(0U, host_->pending_translations());
289}
290
291TEST_F(PnaclHostTest, OverlappedMissesRendererClosing) {
292  nacl::PnaclCacheInfo info = GetTestCacheInfo();
293  GET_NEXE_FD(0, 0, false, info, false);
294  // Send the 2nd fd request from a different renderer.
295  // Test that it eventually gets an fd after the first renderer closes.
296  GET_NEXE_FD(1, 1, false, info, false);
297  content::RunAllBlockingPoolTasksUntilIdle();
298  EXPECT_EQ(1, temp_callback_count_);
299  EXPECT_EQ(2U, host_->pending_translations());
300  content::RunAllBlockingPoolTasksUntilIdle();
301  EXPECT_EQ(2U, host_->pending_translations());
302  EXPECT_EQ(1, temp_callback_count_);
303  host_->RendererClosing(0);
304  content::RunAllBlockingPoolTasksUntilIdle();
305  EXPECT_EQ(2, temp_callback_count_);
306  EXPECT_EQ(1U, host_->pending_translations());
307  host_->RendererClosing(1);
308}
309
310TEST_F(PnaclHostTest, Incognito) {
311  nacl::PnaclCacheInfo info = GetTestCacheInfo();
312  GET_NEXE_FD(0, 0, true, info, false);
313  content::RunAllBlockingPoolTasksUntilIdle();
314  EXPECT_EQ(1, temp_callback_count_);
315  host_->TranslationFinished(0, 0, true);
316  content::RunAllBlockingPoolTasksUntilIdle();
317  // Check that an incognito translation is not stored in the cache
318  GET_NEXE_FD(0, 0, false, info, false);
319  content::RunAllBlockingPoolTasksUntilIdle();
320  EXPECT_EQ(2, temp_callback_count_);
321  host_->TranslationFinished(0, 0, true);
322  content::RunAllBlockingPoolTasksUntilIdle();
323  // Check that an incognito translation can hit from a normal one.
324  GET_NEXE_FD(0, 0, true, info, true);
325  content::RunAllBlockingPoolTasksUntilIdle();
326  EXPECT_EQ(3, temp_callback_count_);
327}
328
329TEST_F(PnaclHostTest, IncognitoOverlappedMiss) {
330  nacl::PnaclCacheInfo info = GetTestCacheInfo();
331  GET_NEXE_FD(0, 0, true, info, false);
332  GET_NEXE_FD(0, 1, false, info, false);
333  content::RunAllBlockingPoolTasksUntilIdle();
334  // Check that both translations have returned misses, (i.e. that the
335  // second one has not blocked on the incognito one)
336  EXPECT_EQ(2, temp_callback_count_);
337  host_->TranslationFinished(0, 0, true);
338  host_->TranslationFinished(0, 1, true);
339  content::RunAllBlockingPoolTasksUntilIdle();
340  EXPECT_EQ(0U, host_->pending_translations());
341
342  // Same test, but issue the 2nd request after the first has returned a miss.
343  info.abi_version = 222;
344  GET_NEXE_FD(0, 0, true, info, false);
345  content::RunAllBlockingPoolTasksUntilIdle();
346  EXPECT_EQ(3, temp_callback_count_);
347  GET_NEXE_FD(0, 1, false, info, false);
348  content::RunAllBlockingPoolTasksUntilIdle();
349  EXPECT_EQ(4, temp_callback_count_);
350  host_->RendererClosing(0);
351}
352
353TEST_F(PnaclHostTest, IncognitoSecondOverlappedMiss) {
354  // If the non-incognito request comes first, it should
355  // behave exactly like OverlappedMissBeforeTempReturn
356  nacl::PnaclCacheInfo info = GetTestCacheInfo();
357  GET_NEXE_FD(0, 0, false, info, false);
358  // Send the 2nd fd request before the first one returns a temp file.
359  GET_NEXE_FD(0, 1, true, info, true);
360  content::RunAllBlockingPoolTasksUntilIdle();
361  EXPECT_EQ(1, temp_callback_count_);
362  EXPECT_EQ(2U, host_->pending_translations());
363  content::RunAllBlockingPoolTasksUntilIdle();
364  EXPECT_EQ(2U, host_->pending_translations());
365  EXPECT_EQ(1, temp_callback_count_);
366  host_->TranslationFinished(0, 0, true);
367  content::RunAllBlockingPoolTasksUntilIdle();
368  EXPECT_EQ(2, temp_callback_count_);
369  EXPECT_EQ(0U, host_->pending_translations());
370}
371
372// Test that pexes with the no-store header do not get cached.
373TEST_F(PnaclHostTest, CacheControlNoStore) {
374  nacl::PnaclCacheInfo info = GetTestCacheInfo();
375  info.has_no_store_header = true;
376  GET_NEXE_FD(0, 0, false, info, false);
377  content::RunAllBlockingPoolTasksUntilIdle();
378  EXPECT_EQ(1, temp_callback_count_);
379  host_->TranslationFinished(0, 0, true);
380  content::RunAllBlockingPoolTasksUntilIdle();
381  EXPECT_EQ(0U, host_->pending_translations());
382  EXPECT_EQ(0, GetCacheSize());
383}
384
385// Test that no-store pexes do not wait, but do duplicate translations
386TEST_F(PnaclHostTest, NoStoreOverlappedMiss) {
387  nacl::PnaclCacheInfo info = GetTestCacheInfo();
388  info.has_no_store_header = true;
389  GET_NEXE_FD(0, 0, false, info, false);
390  GET_NEXE_FD(0, 1, false, info, false);
391  content::RunAllBlockingPoolTasksUntilIdle();
392  // Check that both translations have returned misses, (i.e. that the
393  // second one has not blocked on the first one)
394  EXPECT_EQ(2, temp_callback_count_);
395  host_->TranslationFinished(0, 0, true);
396  host_->TranslationFinished(0, 1, true);
397  content::RunAllBlockingPoolTasksUntilIdle();
398  EXPECT_EQ(0U, host_->pending_translations());
399
400  // Same test, but issue the 2nd request after the first has returned a miss.
401  info.abi_version = 222;
402  GET_NEXE_FD(0, 0, false, info, false);
403  content::RunAllBlockingPoolTasksUntilIdle();
404  EXPECT_EQ(3, temp_callback_count_);
405  GET_NEXE_FD(0, 1, false, info, false);
406  content::RunAllBlockingPoolTasksUntilIdle();
407  EXPECT_EQ(4, temp_callback_count_);
408  host_->RendererClosing(0);
409}
410
411TEST_F(PnaclHostTest, ClearTranslationCache) {
412  nacl::PnaclCacheInfo info = GetTestCacheInfo();
413  // Add 2 entries in the cache
414  GET_NEXE_FD(0, 0, false, info, false);
415  info.abi_version = 222;
416  GET_NEXE_FD(0, 1, false, info, false);
417  content::RunAllBlockingPoolTasksUntilIdle();
418  EXPECT_EQ(2, temp_callback_count_);
419  host_->TranslationFinished(0, 0, true);
420  host_->TranslationFinished(0, 1, true);
421  content::RunAllBlockingPoolTasksUntilIdle();
422  EXPECT_EQ(0U, host_->pending_translations());
423  EXPECT_EQ(2, GetCacheSize());
424  net::TestCompletionCallback cb;
425  // Since we are using a memory backend, the clear should happen immediately.
426  host_->ClearTranslationCacheEntriesBetween(
427      base::Time(), base::Time(), base::Bind(cb.callback(), 0));
428  // Check that the translation cache has been cleared before flushing the
429  // queues, because the backend will be freed once it is.
430  EXPECT_EQ(0, GetCacheSize());
431  EXPECT_EQ(0, cb.GetResult(net::ERR_IO_PENDING));
432  // Now check that the backend has been freed.
433  EXPECT_FALSE(CacheIsInitialized());
434}
435
436// A version of PnaclHostTest that initializes cache on disk.
437class PnaclHostTestDisk : public PnaclHostTest {
438 protected:
439  virtual void SetUp() {
440    host_ = new PnaclHost();
441    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
442    host_->InitForTest(temp_dir_.path(), false);
443    EXPECT_EQ(PnaclHost::CacheInitializing, host_->cache_state_);
444  }
445  void DeInit() {
446    host_->DeInitIfSafe();
447  }
448};
449TEST_F(PnaclHostTestDisk, DeInitWhileInitializing) {
450  // Since there's no easy way to pump message queues one message at a time, we
451  // have to simulate what would happen if 1 DeInitIfsafe task gets queued, then
452  // a GetNexeFd gets queued, and then another DeInitIfSafe gets queued before
453  // the first one runs. We can just shortcut and call DeInitIfSafe while the
454  // cache is still initializing.
455  DeInit();
456  base::RunLoop().RunUntilIdle();
457}
458
459}  // namespace pnacl
460