file_cache_unittest.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright (c) 2012 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 "chrome/browser/chromeos/drive/file_cache.h"
6
7#include <string>
8#include <vector>
9
10#include "base/file_util.h"
11#include "base/files/file_enumerator.h"
12#include "base/files/scoped_temp_dir.h"
13#include "base/run_loop.h"
14#include "base/threading/sequenced_worker_pool.h"
15#include "chrome/browser/chromeos/drive/drive.pb.h"
16#include "chrome/browser/chromeos/drive/fake_free_disk_space_getter.h"
17#include "chrome/browser/chromeos/drive/file_system_util.h"
18#include "chrome/browser/chromeos/drive/test_util.h"
19#include "chrome/browser/google_apis/test_util.h"
20#include "content/public/browser/browser_thread.h"
21#include "content/public/test/test_browser_thread_bundle.h"
22#include "testing/gtest/include/gtest/gtest.h"
23
24namespace drive {
25namespace internal {
26namespace {
27
28// Bitmask of cache states in FileCacheEntry.
29enum TestFileCacheState {
30  TEST_CACHE_STATE_NONE       = 0,
31  TEST_CACHE_STATE_PINNED     = 1 << 0,
32  TEST_CACHE_STATE_PRESENT    = 1 << 1,
33  TEST_CACHE_STATE_DIRTY      = 1 << 2,
34};
35
36// Copies results from Iterate().
37void OnIterate(std::vector<std::string>* out_resource_ids,
38               std::vector<FileCacheEntry>* out_cache_entries,
39               const std::string& resource_id,
40               const FileCacheEntry& cache_entry) {
41  out_resource_ids->push_back(resource_id);
42  out_cache_entries->push_back(cache_entry);
43}
44
45// Called upon completion of Iterate().
46void OnIterateCompleted(bool* out_is_called) {
47  *out_is_called = true;
48}
49
50}  // namespace
51
52// Tests FileCache methods from UI thread. It internally uses a real blocking
53// pool and tests the interaction among threads.
54// TODO(hashimoto): remove this class. crbug.com/231221.
55class FileCacheTestOnUIThread : public testing::Test {
56 protected:
57  FileCacheTestOnUIThread() : expected_error_(FILE_ERROR_OK),
58                              expected_cache_state_(0) {
59  }
60
61  virtual void SetUp() OVERRIDE {
62    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
63    ASSERT_TRUE(file_util::CreateDirectory(
64        temp_dir_.path().Append(util::kMetadataDirectory)));
65    ASSERT_TRUE(file_util::CreateDirectory(
66        temp_dir_.path().Append(util::kCacheFileDirectory)));
67
68    ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
69                                                    &dummy_file_path_));
70    fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
71
72    scoped_refptr<base::SequencedWorkerPool> pool =
73        content::BrowserThread::GetBlockingPool();
74    blocking_task_runner_ =
75        pool->GetSequencedTaskRunner(pool->GetSequenceToken());
76    cache_.reset(new FileCache(
77        temp_dir_.path().Append(util::kMetadataDirectory),
78        temp_dir_.path().Append(util::kCacheFileDirectory),
79        blocking_task_runner_.get(),
80        fake_free_disk_space_getter_.get()));
81
82    bool success = false;
83    base::PostTaskAndReplyWithResult(
84        blocking_task_runner_,
85        FROM_HERE,
86        base::Bind(&FileCache::Initialize,
87                   base::Unretained(cache_.get())),
88        google_apis::test_util::CreateCopyResultCallback(&success));
89    google_apis::test_util::RunBlockingPoolTask();
90    ASSERT_TRUE(success);
91  }
92
93  virtual void TearDown() OVERRIDE {
94    cache_.reset();
95  }
96
97  void TestGetFileFromCacheByResourceIdAndMd5(
98      const std::string& resource_id,
99      const std::string& md5,
100      FileError expected_error,
101      const std::string& expected_file_extension) {
102    FileError error = FILE_ERROR_OK;
103    base::FilePath cache_file_path;
104    cache_->GetFileOnUIThread(resource_id, md5,
105                              google_apis::test_util::CreateCopyResultCallback(
106                                  &error, &cache_file_path));
107    google_apis::test_util::RunBlockingPoolTask();
108
109    EXPECT_EQ(expected_error, error);
110    if (error == FILE_ERROR_OK) {
111      // Verify filename of |cache_file_path|.
112      base::FilePath base_name = cache_file_path.BaseName();
113      EXPECT_EQ(util::EscapeCacheFileName(resource_id) +
114                base::FilePath::kExtensionSeparator +
115                util::EscapeCacheFileName(
116                    expected_file_extension.empty() ?
117                    md5 : expected_file_extension),
118                base_name.value());
119    } else {
120      EXPECT_TRUE(cache_file_path.empty());
121    }
122  }
123
124  void TestStoreToCache(const std::string& resource_id,
125                        const std::string& md5,
126                        const base::FilePath& source_path,
127                        FileError expected_error,
128                        int expected_cache_state) {
129    expected_error_ = expected_error;
130    expected_cache_state_ = expected_cache_state;
131
132    FileError error = FILE_ERROR_OK;
133    cache_->StoreOnUIThread(
134        resource_id, md5, source_path,
135        FileCache::FILE_OPERATION_COPY,
136        google_apis::test_util::CreateCopyResultCallback(&error));
137    google_apis::test_util::RunBlockingPoolTask();
138    VerifyCacheFileState(error, resource_id, md5);
139  }
140
141  void TestRemoveFromCache(const std::string& resource_id,
142                           FileError expected_error) {
143    expected_error_ = expected_error;
144
145    FileError error = FILE_ERROR_OK;
146    cache_->RemoveOnUIThread(
147        resource_id,
148        google_apis::test_util::CreateCopyResultCallback(&error));
149    google_apis::test_util::RunBlockingPoolTask();
150    VerifyRemoveFromCache(error, resource_id, "");
151  }
152
153  // Returns number of files matching to |path_pattern|.
154  int CountFilesWithPathPattern(const base::FilePath& path_pattern) {
155    int result = 0;
156    base::FileEnumerator enumerator(
157        path_pattern.DirName(), false /* not recursive*/,
158        base::FileEnumerator::FILES,
159        path_pattern.BaseName().value());
160    for (base::FilePath current = enumerator.Next(); !current.empty();
161         current = enumerator.Next())
162      ++result;
163    return result;
164  }
165
166  void VerifyRemoveFromCache(FileError error,
167                             const std::string& resource_id,
168                             const std::string& md5) {
169    EXPECT_EQ(expected_error_, error);
170
171    FileCacheEntry cache_entry;
172    if (!GetCacheEntryFromOriginThread(resource_id, md5, &cache_entry)) {
173      EXPECT_EQ(FILE_ERROR_OK, error);
174
175      // Verify that no files with "<resource_id>.*" exist.
176      const base::FilePath path_pattern = cache_->GetCacheFilePath(
177          resource_id, util::kWildCard, FileCache::CACHED_FILE_FROM_SERVER);
178      EXPECT_EQ(0, CountFilesWithPathPattern(path_pattern));
179    }
180  }
181
182  void TestPin(const std::string& resource_id,
183               FileError expected_error,
184               int expected_cache_state) {
185    expected_error_ = expected_error;
186    expected_cache_state_ = expected_cache_state;
187
188    FileError error = FILE_ERROR_OK;
189    cache_->PinOnUIThread(
190        resource_id,
191        google_apis::test_util::CreateCopyResultCallback(&error));
192    google_apis::test_util::RunBlockingPoolTask();
193    VerifyCacheFileState(error, resource_id, std::string());
194  }
195
196  void TestUnpin(const std::string& resource_id,
197                 FileError expected_error,
198                 int expected_cache_state) {
199    expected_error_ = expected_error;
200    expected_cache_state_ = expected_cache_state;
201
202    FileError error = FILE_ERROR_OK;
203    cache_->UnpinOnUIThread(
204        resource_id,
205        google_apis::test_util::CreateCopyResultCallback(&error));
206    google_apis::test_util::RunBlockingPoolTask();
207    VerifyCacheFileState(error, resource_id, std::string());
208  }
209
210  void TestMarkDirty(const std::string& resource_id,
211                     const std::string& md5,
212                     FileError expected_error,
213                     int expected_cache_state) {
214    expected_error_ = expected_error;
215    expected_cache_state_ = expected_cache_state;
216
217    FileError error = FILE_ERROR_OK;
218    cache_->MarkDirtyOnUIThread(
219        resource_id, md5,
220        google_apis::test_util::CreateCopyResultCallback(&error));
221    google_apis::test_util::RunBlockingPoolTask();
222
223    VerifyCacheFileState(error, resource_id, md5);
224
225    // Verify filename.
226    if (error == FILE_ERROR_OK) {
227      base::FilePath cache_file_path;
228      cache_->GetFileOnUIThread(
229          resource_id, md5,
230          google_apis::test_util::CreateCopyResultCallback(
231              &error, &cache_file_path));
232      google_apis::test_util::RunBlockingPoolTask();
233
234      EXPECT_EQ(FILE_ERROR_OK, error);
235      base::FilePath base_name = cache_file_path.BaseName();
236      EXPECT_EQ(util::EscapeCacheFileName(resource_id) +
237                base::FilePath::kExtensionSeparator +
238                "local",
239                base_name.value());
240    }
241  }
242
243  void TestClearDirty(const std::string& resource_id,
244                      const std::string& md5,
245                      FileError expected_error,
246                      int expected_cache_state) {
247    expected_error_ = expected_error;
248    expected_cache_state_ = expected_cache_state;
249
250    FileError error = FILE_ERROR_OK;
251    PostTaskAndReplyWithResult(
252        blocking_task_runner_.get(),
253        FROM_HERE,
254        base::Bind(&FileCache::ClearDirty,
255                   base::Unretained(cache_.get()),
256                   resource_id,
257                   md5),
258        google_apis::test_util::CreateCopyResultCallback(&error));
259    google_apis::test_util::RunBlockingPoolTask();
260    VerifyCacheFileState(error, resource_id, md5);
261  }
262
263  void TestMarkAsMounted(const std::string& resource_id,
264                         FileError expected_error,
265                         int expected_cache_state) {
266    expected_error_ = expected_error;
267    expected_cache_state_ = expected_cache_state;
268
269    FileCacheEntry entry;
270    EXPECT_TRUE(GetCacheEntryFromOriginThread(resource_id, std::string(),
271                                              &entry));
272
273    FileError error = FILE_ERROR_OK;
274    base::FilePath cache_file_path;
275    cache_->MarkAsMountedOnUIThread(
276        resource_id,
277        google_apis::test_util::CreateCopyResultCallback(
278            &error, &cache_file_path));
279    google_apis::test_util::RunBlockingPoolTask();
280
281    EXPECT_TRUE(file_util::PathExists(cache_file_path));
282    EXPECT_EQ(cache_file_path,
283              cache_->GetCacheFilePath(resource_id, entry.md5(),
284                                       FileCache::CACHED_FILE_FROM_SERVER));
285  }
286
287  void TestMarkAsUnmounted(const std::string& resource_id,
288                           const std::string& md5,
289                           const base::FilePath& file_path,
290                           FileError expected_error,
291                           int expected_cache_state) {
292    expected_error_ = expected_error;
293    expected_cache_state_ = expected_cache_state;
294
295    FileError error = FILE_ERROR_OK;
296    cache_->MarkAsUnmountedOnUIThread(
297        file_path,
298        google_apis::test_util::CreateCopyResultCallback(&error));
299    google_apis::test_util::RunBlockingPoolTask();
300
301    base::FilePath cache_file_path;
302    cache_->GetFileOnUIThread(
303        resource_id, md5,
304        google_apis::test_util::CreateCopyResultCallback(
305            &error, &cache_file_path));
306    google_apis::test_util::RunBlockingPoolTask();
307    EXPECT_EQ(FILE_ERROR_OK, error);
308
309    EXPECT_TRUE(file_util::PathExists(cache_file_path));
310    EXPECT_EQ(cache_file_path,
311              cache_->GetCacheFilePath(resource_id, md5,
312                                       FileCache::CACHED_FILE_FROM_SERVER));
313  }
314
315  void VerifyCacheFileState(FileError error,
316                            const std::string& resource_id,
317                            const std::string& md5) {
318    EXPECT_EQ(expected_error_, error);
319
320    // Verify cache map.
321    FileCacheEntry cache_entry;
322    const bool cache_entry_found =
323        GetCacheEntryFromOriginThread(resource_id, md5, &cache_entry);
324    if ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) ||
325        (expected_cache_state_ & TEST_CACHE_STATE_PINNED)) {
326      ASSERT_TRUE(cache_entry_found);
327      EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PINNED) != 0,
328                cache_entry.is_pinned());
329      EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) != 0,
330                cache_entry.is_present());
331      EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_DIRTY) != 0,
332                cache_entry.is_dirty());
333    } else {
334      EXPECT_FALSE(cache_entry_found);
335    }
336
337    // Verify actual cache file.
338    base::FilePath dest_path = cache_->GetCacheFilePath(
339        resource_id,
340        cache_entry.md5(),
341        (expected_cache_state_ & TEST_CACHE_STATE_DIRTY) ?
342            FileCache::CACHED_FILE_LOCALLY_MODIFIED :
343        FileCache::CACHED_FILE_FROM_SERVER);
344    EXPECT_EQ((expected_cache_state_ & TEST_CACHE_STATE_PRESENT) != 0,
345              file_util::PathExists(dest_path));
346  }
347
348  base::FilePath GetCacheFilePath(const std::string& resource_id,
349                                  const std::string& md5,
350                                  FileCache::CachedFileOrigin file_origin) {
351    return cache_->GetCacheFilePath(resource_id, md5, file_origin);
352  }
353
354  // Helper function to call GetCacheEntry from origin thread.
355  bool GetCacheEntryFromOriginThread(const std::string& resource_id,
356                                     const std::string& md5,
357                                     FileCacheEntry* cache_entry) {
358    bool result = false;
359    cache_->GetCacheEntryOnUIThread(
360        resource_id, md5,
361        google_apis::test_util::CreateCopyResultCallback(&result, cache_entry));
362    google_apis::test_util::RunBlockingPoolTask();
363    return result;
364  }
365
366  // Returns true if the cache entry exists for the given resource ID and MD5.
367  bool CacheEntryExists(const std::string& resource_id,
368                        const std::string& md5) {
369    FileCacheEntry cache_entry;
370    return GetCacheEntryFromOriginThread(resource_id, md5, &cache_entry);
371  }
372
373  void TestGetCacheFilePath(const std::string& resource_id,
374                            const std::string& md5,
375                            const std::string& expected_filename) {
376    base::FilePath actual_path = cache_->GetCacheFilePath(
377        resource_id, md5, FileCache::CACHED_FILE_FROM_SERVER);
378    base::FilePath expected_path =
379        temp_dir_.path().Append(util::kCacheFileDirectory);
380    expected_path = expected_path.Append(
381        base::FilePath::FromUTF8Unsafe(expected_filename));
382    EXPECT_EQ(expected_path, actual_path);
383
384    base::FilePath base_name = actual_path.BaseName();
385
386    // base::FilePath::Extension returns ".", so strip it.
387    std::string unescaped_md5 = util::UnescapeCacheFileName(
388        base_name.Extension().substr(1));
389    EXPECT_EQ(md5, unescaped_md5);
390    std::string unescaped_resource_id = util::UnescapeCacheFileName(
391        base_name.RemoveExtension().value());
392    EXPECT_EQ(resource_id, unescaped_resource_id);
393  }
394
395  // Returns the number of the cache files with name <resource_id>, and Confirm
396  // that they have the <md5>. This should return 1 or 0.
397  size_t CountCacheFiles(const std::string& resource_id,
398                         const std::string& md5) {
399    base::FilePath path = GetCacheFilePath(
400        resource_id, util::kWildCard, FileCache::CACHED_FILE_FROM_SERVER);
401    base::FileEnumerator enumerator(path.DirName(), false,
402                                    base::FileEnumerator::FILES,
403                                    path.BaseName().value());
404    size_t num_files_found = 0;
405    for (base::FilePath current = enumerator.Next(); !current.empty();
406         current = enumerator.Next()) {
407      ++num_files_found;
408      EXPECT_EQ(util::EscapeCacheFileName(resource_id) +
409                base::FilePath::kExtensionSeparator +
410                util::EscapeCacheFileName(md5),
411                current.BaseName().value());
412    }
413    return num_files_found;
414  }
415
416  content::TestBrowserThreadBundle thread_bundle_;
417  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
418  base::ScopedTempDir temp_dir_;
419  base::FilePath dummy_file_path_;
420
421  scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
422  scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
423
424  FileError expected_error_;
425  int expected_cache_state_;
426  std::string expected_file_extension_;
427};
428
429TEST_F(FileCacheTestOnUIThread, GetCacheFilePath) {
430  // Use alphanumeric characters for resource id.
431  std::string resource_id("pdf:1a2b");
432  std::string md5("abcdef0123456789");
433  TestGetCacheFilePath(resource_id, md5,
434                       resource_id + base::FilePath::kExtensionSeparator + md5);
435
436  // Use non-alphanumeric characters for resource id, including '.' which is an
437  // extension separator, to test that the characters are escaped and unescaped
438  // correctly, and '.' doesn't mess up the filename format and operations.
439  resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?";
440  std::string escaped_resource_id = util::EscapeCacheFileName(resource_id);
441  std::string escaped_md5 = util::EscapeCacheFileName(md5);
442  TestGetCacheFilePath(resource_id, md5, escaped_resource_id +
443                       base::FilePath::kExtensionSeparator + escaped_md5);
444}
445
446TEST_F(FileCacheTestOnUIThread, StoreToCacheSimple) {
447  std::string resource_id("pdf:1a2b");
448  std::string md5("abcdef0123456789");
449
450  // Store an existing file.
451  TestStoreToCache(resource_id, md5, dummy_file_path_,
452                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
453
454  // Store a non-existent file to the same |resource_id| and |md5|.
455  TestStoreToCache(resource_id, md5,
456                   base::FilePath::FromUTF8Unsafe("non_existent_file"),
457                   FILE_ERROR_FAILED,
458                   TEST_CACHE_STATE_PRESENT);
459
460  // Store a different existing file to the same |resource_id| but different
461  // |md5|.
462  md5 = "new_md5";
463  TestStoreToCache(resource_id, md5, dummy_file_path_,
464                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
465
466  // Verify that there's only one file with name <resource_id>, i.e. previously
467  // cached file with the different md5 should be deleted.
468  EXPECT_EQ(1U, CountCacheFiles(resource_id, md5));
469}
470
471
472TEST_F(FileCacheTestOnUIThread, GetFromCacheSimple) {
473  std::string resource_id("pdf:1a2b");
474  std::string md5("abcdef0123456789");
475  // First store a file to cache.
476  TestStoreToCache(resource_id, md5, dummy_file_path_,
477                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
478
479  // Then try to get the existing file from cache.
480  TestGetFileFromCacheByResourceIdAndMd5(
481      resource_id, md5, FILE_ERROR_OK, md5);
482
483  // Get file from cache with same resource id as existing file but different
484  // md5.
485  TestGetFileFromCacheByResourceIdAndMd5(
486      resource_id, "9999", FILE_ERROR_NOT_FOUND, md5);
487
488  // Get file from cache with different resource id from existing file but same
489  // md5.
490  resource_id = "document:1a2b";
491  TestGetFileFromCacheByResourceIdAndMd5(
492      resource_id, md5, FILE_ERROR_NOT_FOUND, md5);
493}
494
495TEST_F(FileCacheTestOnUIThread, RemoveFromCacheSimple) {
496  // Use alphanumeric characters for resource id.
497  std::string resource_id("pdf:1a2b");
498  std::string md5("abcdef0123456789");
499  // First store a file to cache.
500  TestStoreToCache(resource_id, md5, dummy_file_path_,
501                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
502
503  // Then try to remove existing file from cache.
504  TestRemoveFromCache(resource_id, FILE_ERROR_OK);
505
506  // Repeat using non-alphanumeric characters for resource id, including '.'
507  // which is an extension separator.
508  resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?";
509  TestStoreToCache(resource_id, md5, dummy_file_path_,
510                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
511
512  TestRemoveFromCache(resource_id, FILE_ERROR_OK);
513}
514
515TEST_F(FileCacheTestOnUIThread, PinAndUnpin) {
516  std::string resource_id("pdf:1a2b");
517  std::string md5("abcdef0123456789");
518
519  // First store a file to cache.
520  TestStoreToCache(resource_id, md5, dummy_file_path_,
521                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
522
523  // Pin the existing file in cache.
524  TestPin(resource_id, FILE_ERROR_OK,
525          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
526
527  // Unpin the existing file in cache.
528  TestUnpin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
529
530  // Pin back the same existing file in cache.
531  TestPin(resource_id, FILE_ERROR_OK,
532          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
533
534  // Pin a non-existent file in cache.
535  resource_id = "document:1a2b";
536
537  TestPin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED);
538
539  // Unpin the previously pinned non-existent file in cache.
540  TestUnpin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_NONE);
541
542  // Unpin a file that doesn't exist in cache and is not pinned, i.e. cache
543  // has zero knowledge of the file.
544  resource_id = "not-in-cache:1a2b";
545
546  TestUnpin(resource_id, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE);
547}
548
549TEST_F(FileCacheTestOnUIThread, StoreToCachePinned) {
550  std::string resource_id("pdf:1a2b");
551  std::string md5("abcdef0123456789");
552
553  // Pin a non-existent file.
554  TestPin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED);
555
556  // Store an existing file to a previously pinned file.
557  TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_OK,
558                   TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
559
560  // Store a non-existent file to a previously pinned and stored file.
561  TestStoreToCache(resource_id, md5,
562                   base::FilePath::FromUTF8Unsafe("non_existent_file"),
563                   FILE_ERROR_FAILED,
564                   TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
565}
566
567TEST_F(FileCacheTestOnUIThread, GetFromCachePinned) {
568  std::string resource_id("pdf:1a2b");
569  std::string md5("abcdef0123456789");
570
571  // Pin a non-existent file.
572  TestPin(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PINNED);
573
574  // Get the non-existent pinned file from cache.
575  TestGetFileFromCacheByResourceIdAndMd5(
576      resource_id, md5, FILE_ERROR_NOT_FOUND, md5);
577
578  // Store an existing file to the previously pinned non-existent file.
579  TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_OK,
580                   TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
581
582  // Get the previously pinned and stored file from cache.
583  TestGetFileFromCacheByResourceIdAndMd5(
584      resource_id, md5, FILE_ERROR_OK, md5);
585}
586
587TEST_F(FileCacheTestOnUIThread, RemoveFromCachePinned) {
588  // Use alphanumeric characters for resource_id.
589  std::string resource_id("pdf:1a2b");
590  std::string md5("abcdef0123456789");
591
592  // Store a file to cache, and pin it.
593  TestStoreToCache(resource_id, md5, dummy_file_path_,
594                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
595  TestPin(resource_id, FILE_ERROR_OK,
596          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
597
598  // Remove |resource_id| from cache.
599  TestRemoveFromCache(resource_id, FILE_ERROR_OK);
600
601  // Repeat using non-alphanumeric characters for resource id, including '.'
602  // which is an extension separator.
603  resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?";
604
605  TestStoreToCache(resource_id, md5, dummy_file_path_,
606                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
607  TestPin(resource_id, FILE_ERROR_OK,
608          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
609
610  TestRemoveFromCache(resource_id, FILE_ERROR_OK);
611}
612
613TEST_F(FileCacheTestOnUIThread, DirtyCacheSimple) {
614  std::string resource_id("pdf:1a2b");
615  std::string md5("abcdef0123456789");
616
617  // First store a file to cache.
618  TestStoreToCache(resource_id, md5, dummy_file_path_,
619                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
620
621  // Mark the file dirty.
622  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
623                TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
624
625  // Clear dirty state of the file.
626  TestClearDirty(resource_id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
627}
628
629TEST_F(FileCacheTestOnUIThread, DirtyCachePinned) {
630  std::string resource_id("pdf:1a2b");
631  std::string md5("abcdef0123456789");
632
633  // First store a file to cache and pin it.
634  TestStoreToCache(resource_id, md5, dummy_file_path_,
635                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
636  TestPin(resource_id, FILE_ERROR_OK,
637          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
638
639  // Mark the file dirty.
640  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
641                TEST_CACHE_STATE_PRESENT |
642                TEST_CACHE_STATE_DIRTY |
643                TEST_CACHE_STATE_PINNED);
644
645  // Clear dirty state of the file.
646  TestClearDirty(resource_id, md5, FILE_ERROR_OK,
647                 TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
648}
649
650TEST_F(FileCacheTestOnUIThread, PinAndUnpinDirtyCache) {
651  std::string resource_id("pdf:1a2b");
652  std::string md5("abcdef0123456789");
653
654  // First store a file to cache and mark it as dirty.
655  TestStoreToCache(resource_id, md5, dummy_file_path_,
656                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
657  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
658                TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
659
660  // Verifies dirty file exists.
661  base::FilePath dirty_path;
662  FileError error = FILE_ERROR_FAILED;
663  cache_->GetFileOnUIThread(
664      resource_id, md5,
665      google_apis::test_util::CreateCopyResultCallback(&error, &dirty_path));
666  google_apis::test_util::RunBlockingPoolTask();
667  EXPECT_EQ(FILE_ERROR_OK, error);
668  EXPECT_TRUE(file_util::PathExists(dirty_path));
669
670  // Pin the dirty file.
671  TestPin(resource_id, FILE_ERROR_OK,
672          TEST_CACHE_STATE_PRESENT |
673          TEST_CACHE_STATE_DIRTY |
674          TEST_CACHE_STATE_PINNED);
675
676  // Verify dirty file still exist at the same pathname.
677  EXPECT_TRUE(file_util::PathExists(dirty_path));
678
679  // Unpin the dirty file.
680  TestUnpin(resource_id, FILE_ERROR_OK,
681            TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
682
683  // Verify dirty file still exist at the same pathname.
684  EXPECT_TRUE(file_util::PathExists(dirty_path));
685}
686
687TEST_F(FileCacheTestOnUIThread, DirtyCacheRepetitive) {
688  std::string resource_id("pdf:1a2b");
689  std::string md5("abcdef0123456789");
690
691  // First store a file to cache.
692  TestStoreToCache(resource_id, md5, dummy_file_path_,
693                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
694
695  // Mark the file dirty.
696  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
697                TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
698
699  // Again, mark the file dirty.  Nothing should change.
700  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
701                TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
702
703  // Clear dirty state of the file.
704  TestClearDirty(resource_id, md5, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
705
706  // Again, clear dirty state of the file, which is no longer dirty.
707  TestClearDirty(resource_id, md5, FILE_ERROR_INVALID_OPERATION,
708                 TEST_CACHE_STATE_PRESENT);
709}
710
711TEST_F(FileCacheTestOnUIThread, DirtyCacheInvalid) {
712  std::string resource_id("pdf:1a2b");
713  std::string md5("abcdef0123456789");
714
715  // Mark a non-existent file dirty.
716  TestMarkDirty(resource_id, md5, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE);
717
718  // Clear dirty state of a non-existent file.
719  TestClearDirty(resource_id, md5, FILE_ERROR_NOT_FOUND, TEST_CACHE_STATE_NONE);
720
721  // Store a file to cache.
722  TestStoreToCache(resource_id, md5, dummy_file_path_,
723                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
724
725  // Clear dirty state of a non-dirty existing file.
726  TestClearDirty(resource_id, md5, FILE_ERROR_INVALID_OPERATION,
727                 TEST_CACHE_STATE_PRESENT);
728
729  // Mark an existing file dirty, then store a new file to the same resource id
730  // but different md5, which should fail.
731  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
732                TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
733  md5 = "new_md5";
734  TestStoreToCache(resource_id, md5, dummy_file_path_,
735                   FILE_ERROR_IN_USE,
736                   TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_DIRTY);
737}
738
739TEST_F(FileCacheTestOnUIThread, RemoveFromDirtyCache) {
740  std::string resource_id("pdf:1a2b");
741  std::string md5("abcdef0123456789");
742
743  // Store a file to cache, pin it, mark it dirty and commit it.
744  TestStoreToCache(resource_id, md5, dummy_file_path_,
745                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
746  TestPin(resource_id, FILE_ERROR_OK,
747          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
748  TestMarkDirty(resource_id, md5, FILE_ERROR_OK,
749                TEST_CACHE_STATE_PRESENT |
750                TEST_CACHE_STATE_PINNED |
751                TEST_CACHE_STATE_DIRTY);
752
753  // Try to remove the file.  Since file is dirty, it should not be removed.
754  TestRemoveFromCache(resource_id, FILE_ERROR_IN_USE);
755}
756
757TEST_F(FileCacheTestOnUIThread, MountUnmount) {
758  std::string resource_id("pdf:1a2b");
759  std::string md5("abcdef0123456789");
760
761  // First store a file to cache.
762  TestStoreToCache(resource_id, md5, dummy_file_path_,
763                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
764
765  // Mark the file mounted.
766  TestMarkAsMounted(resource_id, FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
767  EXPECT_TRUE(CacheEntryExists(resource_id, md5));
768
769  // Try to remove the file.
770  TestRemoveFromCache(resource_id, FILE_ERROR_IN_USE);
771
772  // Clear mounted state of the file.
773  base::FilePath file_path;
774  FileError error = FILE_ERROR_FAILED;
775  cache_->GetFileOnUIThread(
776      resource_id, md5,
777      google_apis::test_util::CreateCopyResultCallback(&error, &file_path));
778  google_apis::test_util::RunBlockingPoolTask();
779  EXPECT_EQ(FILE_ERROR_OK, error);
780
781  TestMarkAsUnmounted(resource_id, md5, file_path, FILE_ERROR_OK,
782                      TEST_CACHE_STATE_PRESENT);
783  EXPECT_TRUE(CacheEntryExists(resource_id, md5));
784
785  // Try to remove the file.
786  TestRemoveFromCache(resource_id, FILE_ERROR_OK);
787}
788
789TEST_F(FileCacheTestOnUIThread, Iterate) {
790  const std::vector<test_util::TestCacheResource> cache_resources(
791      test_util::GetDefaultTestCacheResources());
792  ASSERT_TRUE(test_util::PrepareTestCacheResources(cache_.get(),
793                                                   cache_resources));
794
795  std::vector<std::string> resource_ids;
796  std::vector<FileCacheEntry> cache_entries;
797  bool completed = false;
798  cache_->IterateOnUIThread(
799      base::Bind(&OnIterate, &resource_ids, &cache_entries),
800      base::Bind(&OnIterateCompleted, &completed));
801  google_apis::test_util::RunBlockingPoolTask();
802
803  ASSERT_TRUE(completed);
804
805  sort(resource_ids.begin(), resource_ids.end());
806  ASSERT_EQ(6U, resource_ids.size());
807  EXPECT_EQ("dirty:existing", resource_ids[0]);
808  EXPECT_EQ("dirty_and_pinned:existing", resource_ids[1]);
809  EXPECT_EQ("pinned:existing", resource_ids[2]);
810  EXPECT_EQ("pinned:non-existent", resource_ids[3]);
811  EXPECT_EQ("tmp:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?", resource_ids[4]);
812  EXPECT_EQ("tmp:resource_id", resource_ids[5]);
813
814  ASSERT_EQ(6U, cache_entries.size());
815}
816
817TEST_F(FileCacheTestOnUIThread, ClearAll) {
818  std::string resource_id("pdf:1a2b");
819  std::string md5("abcdef0123456789");
820
821  // Store an existing file.
822  TestStoreToCache(resource_id, md5, dummy_file_path_,
823                   FILE_ERROR_OK, TEST_CACHE_STATE_PRESENT);
824
825  // Verify that there's only one cached file.
826  EXPECT_EQ(1U, CountCacheFiles(resource_id, md5));
827
828  // Clear cache.
829  bool success = false;
830  cache_->ClearAllOnUIThread(
831      google_apis::test_util::CreateCopyResultCallback(&success));
832  google_apis::test_util::RunBlockingPoolTask();
833  EXPECT_TRUE(success);
834
835  // Verify that all the cache is removed.
836  expected_error_ = FILE_ERROR_OK;
837  VerifyRemoveFromCache(FILE_ERROR_OK, resource_id, md5);
838  EXPECT_EQ(0U, CountCacheFiles(resource_id, md5));
839}
840
841TEST_F(FileCacheTestOnUIThread, StoreToCacheNoSpace) {
842  fake_free_disk_space_getter_->set_default_value(0);
843
844  std::string resource_id("pdf:1a2b");
845  std::string md5("abcdef0123456789");
846
847  // Try to store an existing file.
848  TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_NO_SPACE,
849                   TEST_CACHE_STATE_NONE);
850
851  // Verify that there's no files added.
852  EXPECT_EQ(0U, CountCacheFiles(resource_id, md5));
853}
854
855// Don't use TEST_F, as we don't want SetUp() and TearDown() for this test.
856TEST(FileCacheExtraTest, InitializationFailure) {
857  content::TestBrowserThreadBundle thread_bundle;
858
859  // Set the cache root to a non existent path, so the initialization fails.
860  scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache(new FileCache(
861      base::FilePath::FromUTF8Unsafe("/somewhere/nonexistent/blah/meta"),
862      base::FilePath::FromUTF8Unsafe("/somewhere/nonexistent/blah/files"),
863      base::MessageLoopProxy::current(),
864      NULL /* free_disk_space_getter */));
865
866  EXPECT_FALSE(cache->Initialize());
867}
868
869TEST_F(FileCacheTestOnUIThread, UpdatePinnedCache) {
870  std::string resource_id("pdf:1a2b");
871  std::string md5("abcdef0123456789");
872  std::string md5_modified("aaaaaa0000000000");
873
874  // Store an existing file.
875  TestStoreToCache(resource_id, md5, dummy_file_path_, FILE_ERROR_OK,
876                   TEST_CACHE_STATE_PRESENT);
877
878  // Pin the file.
879  TestPin(resource_id, FILE_ERROR_OK,
880          TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
881
882  // Store the file with a modified content and md5. It should stay pinned.
883  TestStoreToCache(resource_id, md5_modified, dummy_file_path_, FILE_ERROR_OK,
884                   TEST_CACHE_STATE_PRESENT | TEST_CACHE_STATE_PINNED);
885}
886
887// Tests FileCache methods working with the blocking task runner.
888class FileCacheTest : public testing::Test {
889 protected:
890  virtual void SetUp() OVERRIDE {
891    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
892    ASSERT_TRUE(file_util::CreateDirectory(
893        temp_dir_.path().Append(util::kMetadataDirectory)));
894    ASSERT_TRUE(file_util::CreateDirectory(
895        temp_dir_.path().Append(util::kCacheFileDirectory)));
896
897    fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
898
899    cache_.reset(new FileCache(
900        temp_dir_.path().Append(util::kMetadataDirectory),
901        temp_dir_.path().Append(util::kCacheFileDirectory),
902        base::MessageLoopProxy::current(),
903        fake_free_disk_space_getter_.get()));
904
905    ASSERT_TRUE(cache_->Initialize());
906  }
907
908  virtual void TearDown() OVERRIDE {
909    cache_.reset();
910  }
911
912  content::TestBrowserThreadBundle thread_bundle_;
913  base::ScopedTempDir temp_dir_;
914
915  scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
916  scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
917};
918
919TEST_F(FileCacheTest, ScanCacheFile) {
920  // Set up files in the cache directory.
921  const base::FilePath directory =
922      temp_dir_.path().Append(util::kCacheFileDirectory);
923  ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
924      directory.AppendASCII("id_foo.md5foo"), "foo"));
925  ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
926      directory.AppendASCII("id_bar.local"), "bar"));
927
928  // Remove the existing DB.
929  ASSERT_TRUE(file_util::Delete(
930      temp_dir_.path().Append(util::kMetadataDirectory), true /* recursive */));
931
932  // Create a new cache and initialize it.
933  cache_.reset(new FileCache(temp_dir_.path().Append(util::kMetadataDirectory),
934                             temp_dir_.path().Append(util::kCacheFileDirectory),
935                             base::MessageLoopProxy::current(),
936                             fake_free_disk_space_getter_.get()));
937  ASSERT_TRUE(cache_->Initialize());
938
939  // Check contents of the cache.
940  FileCacheEntry cache_entry;
941  EXPECT_TRUE(cache_->GetCacheEntry("id_foo", std::string(), &cache_entry));
942  EXPECT_TRUE(cache_entry.is_present());
943  EXPECT_EQ("md5foo", cache_entry.md5());
944
945  EXPECT_TRUE(cache_->GetCacheEntry("id_bar", std::string(), &cache_entry));
946  EXPECT_TRUE(cache_entry.is_present());
947  EXPECT_TRUE(cache_entry.is_dirty());
948}
949
950TEST_F(FileCacheTest, FreeDiskSpaceIfNeededFor) {
951  base::FilePath src_file;
952  ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
953
954  // Store a file as a 'temporary' file and remember the path.
955  const std::string resource_id_tmp = "id_tmp", md5_tmp = "md5_tmp";
956  ASSERT_EQ(FILE_ERROR_OK,
957            cache_->Store(resource_id_tmp, md5_tmp, src_file,
958                          FileCache::FILE_OPERATION_COPY));
959  base::FilePath tmp_path;
960  ASSERT_EQ(FILE_ERROR_OK,
961            cache_->GetFile(resource_id_tmp, md5_tmp, &tmp_path));
962
963  // Store a file as a pinned file and remember the path.
964  const std::string resource_id_pinned = "id_pinned", md5_pinned = "md5_pinned";
965  ASSERT_EQ(FILE_ERROR_OK,
966            cache_->Store(resource_id_pinned, md5_pinned, src_file,
967                          FileCache::FILE_OPERATION_COPY));
968  ASSERT_EQ(FILE_ERROR_OK, cache_->Pin(resource_id_pinned));
969  base::FilePath pinned_path;
970  ASSERT_EQ(FILE_ERROR_OK,
971            cache_->GetFile(resource_id_pinned, md5_pinned, &pinned_path));
972
973  // Call FreeDiskSpaceIfNeededFor().
974  fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace);
975  fake_free_disk_space_getter_->PushFakeValue(0);
976  const int64 kNeededBytes = 1;
977  EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
978
979  // Only 'temporary' file gets removed.
980  FileCacheEntry entry;
981  EXPECT_FALSE(cache_->GetCacheEntry(resource_id_tmp, md5_tmp, &entry));
982  EXPECT_FALSE(file_util::PathExists(tmp_path));
983
984  EXPECT_TRUE(cache_->GetCacheEntry(resource_id_pinned, md5_pinned, &entry));
985  EXPECT_TRUE(file_util::PathExists(pinned_path));
986
987  // Returns false when disk space cannot be freed.
988  fake_free_disk_space_getter_->set_default_value(0);
989  EXPECT_FALSE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
990}
991
992}  // namespace internal
993}  // namespace drive
994