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