Home | History | Annotate | Download | only in crash_cache
      1 // Copyright (c) 2011 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 // This command-line program generates the set of files needed for the crash-
      6 // cache unit tests (DiskCacheTest,CacheBackend_Recover*). This program only
      7 // works properly on debug mode, because the crash functionality is not compiled
      8 // on release builds of the cache.
      9 
     10 #include <string>
     11 
     12 #include "base/at_exit.h"
     13 #include "base/command_line.h"
     14 #include "base/files/file_util.h"
     15 #include "base/logging.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/path_service.h"
     18 #include "base/process/kill.h"
     19 #include "base/process/launch.h"
     20 #include "base/process/process_handle.h"
     21 #include "base/strings/string_number_conversions.h"
     22 #include "base/strings/string_util.h"
     23 #include "base/strings/utf_string_conversions.h"
     24 #include "base/threading/thread.h"
     25 #include "net/base/net_errors.h"
     26 #include "net/base/net_export.h"
     27 #include "net/base/test_completion_callback.h"
     28 #include "net/disk_cache/blockfile/backend_impl.h"
     29 #include "net/disk_cache/blockfile/rankings.h"
     30 #include "net/disk_cache/disk_cache.h"
     31 #include "net/disk_cache/disk_cache_test_util.h"
     32 
     33 using base::Time;
     34 
     35 enum Errors {
     36   GENERIC = -1,
     37   ALL_GOOD = 0,
     38   INVALID_ARGUMENT = 1,
     39   CRASH_OVERWRITE,
     40   NOT_REACHED
     41 };
     42 
     43 using disk_cache::RankCrashes;
     44 
     45 // Starts a new process, to generate the files.
     46 int RunSlave(RankCrashes action) {
     47   base::FilePath exe;
     48   PathService::Get(base::FILE_EXE, &exe);
     49 
     50   base::CommandLine cmdline(exe);
     51   cmdline.AppendArg(base::IntToString(action));
     52 
     53   base::ProcessHandle handle;
     54   if (!base::LaunchProcess(cmdline, base::LaunchOptions(), &handle)) {
     55     printf("Unable to run test %d\n", action);
     56     return GENERIC;
     57   }
     58 
     59   int exit_code;
     60 
     61   if (!base::WaitForExitCode(handle, &exit_code)) {
     62     printf("Unable to get return code, test %d\n", action);
     63     return GENERIC;
     64   }
     65   if (ALL_GOOD != exit_code)
     66     printf("Test %d failed, code %d\n", action, exit_code);
     67 
     68   return exit_code;
     69 }
     70 
     71 // Main loop for the master process.
     72 int MasterCode() {
     73   for (int i = disk_cache::NO_CRASH + 1; i < disk_cache::MAX_CRASH; i++) {
     74     int ret = RunSlave(static_cast<RankCrashes>(i));
     75     if (ALL_GOOD != ret)
     76       return ret;
     77   }
     78 
     79   return ALL_GOOD;
     80 }
     81 
     82 // -----------------------------------------------------------------------
     83 
     84 namespace disk_cache {
     85 NET_EXPORT_PRIVATE extern RankCrashes g_rankings_crash;
     86 }
     87 
     88 const char* kCrashEntryName = "the first key";
     89 
     90 // Creates the destinaton folder for this run, and returns it on full_path.
     91 bool CreateTargetFolder(const base::FilePath& path, RankCrashes action,
     92                         base::FilePath* full_path) {
     93   const char* folders[] = {
     94     "",
     95     "insert_empty1",
     96     "insert_empty2",
     97     "insert_empty3",
     98     "insert_one1",
     99     "insert_one2",
    100     "insert_one3",
    101     "insert_load1",
    102     "insert_load2",
    103     "remove_one1",
    104     "remove_one2",
    105     "remove_one3",
    106     "remove_one4",
    107     "remove_head1",
    108     "remove_head2",
    109     "remove_head3",
    110     "remove_head4",
    111     "remove_tail1",
    112     "remove_tail2",
    113     "remove_tail3",
    114     "remove_load1",
    115     "remove_load2",
    116     "remove_load3"
    117   };
    118   COMPILE_ASSERT(arraysize(folders) == disk_cache::MAX_CRASH, sync_folders);
    119   DCHECK(action > disk_cache::NO_CRASH && action < disk_cache::MAX_CRASH);
    120 
    121   *full_path = path.AppendASCII(folders[action]);
    122 
    123   if (base::PathExists(*full_path))
    124     return false;
    125 
    126   return base::CreateDirectory(*full_path);
    127 }
    128 
    129 // Makes sure that any pending task is processed.
    130 void FlushQueue(disk_cache::Backend* cache) {
    131   net::TestCompletionCallback cb;
    132   int rv =
    133       reinterpret_cast<disk_cache::BackendImpl*>(cache)->FlushQueueForTest(
    134           cb.callback());
    135   cb.GetResult(rv);  // Ignore the result;
    136 }
    137 
    138 bool CreateCache(const base::FilePath& path,
    139                  base::Thread* thread,
    140                  disk_cache::Backend** cache,
    141                  net::TestCompletionCallback* cb) {
    142   int size = 1024 * 1024;
    143   disk_cache::BackendImpl* backend = new disk_cache::BackendImpl(
    144       path, thread->message_loop_proxy().get(), NULL);
    145   backend->SetMaxSize(size);
    146   backend->SetType(net::DISK_CACHE);
    147   backend->SetFlags(disk_cache::kNoRandom);
    148   int rv = backend->Init(cb->callback());
    149   *cache = backend;
    150   return (cb->GetResult(rv) == net::OK && !(*cache)->GetEntryCount());
    151 }
    152 
    153 // Generates the files for an empty and one item cache.
    154 int SimpleInsert(const base::FilePath& path, RankCrashes action,
    155                  base::Thread* cache_thread) {
    156   net::TestCompletionCallback cb;
    157   disk_cache::Backend* cache;
    158   if (!CreateCache(path, cache_thread, &cache, &cb))
    159     return GENERIC;
    160 
    161   const char* test_name = "some other key";
    162 
    163   if (action <= disk_cache::INSERT_EMPTY_3) {
    164     test_name = kCrashEntryName;
    165     disk_cache::g_rankings_crash = action;
    166   }
    167 
    168   disk_cache::Entry* entry;
    169   int rv = cache->CreateEntry(test_name, &entry, cb.callback());
    170   if (cb.GetResult(rv) != net::OK)
    171     return GENERIC;
    172 
    173   entry->Close();
    174   FlushQueue(cache);
    175 
    176   DCHECK(action <= disk_cache::INSERT_ONE_3);
    177   disk_cache::g_rankings_crash = action;
    178   test_name = kCrashEntryName;
    179 
    180   rv = cache->CreateEntry(test_name, &entry, cb.callback());
    181   if (cb.GetResult(rv) != net::OK)
    182     return GENERIC;
    183 
    184   return NOT_REACHED;
    185 }
    186 
    187 // Generates the files for a one item cache, and removing the head.
    188 int SimpleRemove(const base::FilePath& path, RankCrashes action,
    189                  base::Thread* cache_thread) {
    190   DCHECK(action >= disk_cache::REMOVE_ONE_1);
    191   DCHECK(action <= disk_cache::REMOVE_TAIL_3);
    192 
    193   net::TestCompletionCallback cb;
    194   disk_cache::Backend* cache;
    195   if (!CreateCache(path, cache_thread, &cache, &cb))
    196     return GENERIC;
    197 
    198   disk_cache::Entry* entry;
    199   int rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
    200   if (cb.GetResult(rv) != net::OK)
    201     return GENERIC;
    202 
    203   entry->Close();
    204   FlushQueue(cache);
    205 
    206   if (action >= disk_cache::REMOVE_TAIL_1) {
    207     rv = cache->CreateEntry("some other key", &entry, cb.callback());
    208     if (cb.GetResult(rv) != net::OK)
    209       return GENERIC;
    210 
    211     entry->Close();
    212     FlushQueue(cache);
    213   }
    214 
    215   rv = cache->OpenEntry(kCrashEntryName, &entry, cb.callback());
    216   if (cb.GetResult(rv) != net::OK)
    217     return GENERIC;
    218 
    219   disk_cache::g_rankings_crash = action;
    220   entry->Doom();
    221   entry->Close();
    222   FlushQueue(cache);
    223 
    224   return NOT_REACHED;
    225 }
    226 
    227 int HeadRemove(const base::FilePath& path, RankCrashes action,
    228                base::Thread* cache_thread) {
    229   DCHECK(action >= disk_cache::REMOVE_HEAD_1);
    230   DCHECK(action <= disk_cache::REMOVE_HEAD_4);
    231 
    232   net::TestCompletionCallback cb;
    233   disk_cache::Backend* cache;
    234   if (!CreateCache(path, cache_thread, &cache, &cb))
    235     return GENERIC;
    236 
    237   disk_cache::Entry* entry;
    238   int rv = cache->CreateEntry("some other key", &entry, cb.callback());
    239   if (cb.GetResult(rv) != net::OK)
    240     return GENERIC;
    241 
    242   entry->Close();
    243   FlushQueue(cache);
    244   rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
    245   if (cb.GetResult(rv) != net::OK)
    246     return GENERIC;
    247 
    248   entry->Close();
    249   FlushQueue(cache);
    250 
    251   rv = cache->OpenEntry(kCrashEntryName, &entry, cb.callback());
    252   if (cb.GetResult(rv) != net::OK)
    253     return GENERIC;
    254 
    255   disk_cache::g_rankings_crash = action;
    256   entry->Doom();
    257   entry->Close();
    258   FlushQueue(cache);
    259 
    260   return NOT_REACHED;
    261 }
    262 
    263 // Generates the files for insertion and removals on heavy loaded caches.
    264 int LoadOperations(const base::FilePath& path, RankCrashes action,
    265                    base::Thread* cache_thread) {
    266   DCHECK(action >= disk_cache::INSERT_LOAD_1);
    267 
    268   // Work with a tiny index table (16 entries).
    269   disk_cache::BackendImpl* cache = new disk_cache::BackendImpl(
    270       path, 0xf, cache_thread->message_loop_proxy().get(), NULL);
    271   if (!cache->SetMaxSize(0x100000))
    272     return GENERIC;
    273 
    274   // No experiments and use a simple LRU.
    275   cache->SetFlags(disk_cache::kNoRandom);
    276   net::TestCompletionCallback cb;
    277   int rv = cache->Init(cb.callback());
    278   if (cb.GetResult(rv) != net::OK || cache->GetEntryCount())
    279     return GENERIC;
    280 
    281   int seed = static_cast<int>(Time::Now().ToInternalValue());
    282   srand(seed);
    283 
    284   disk_cache::Entry* entry;
    285   for (int i = 0; i < 100; i++) {
    286     std::string key = GenerateKey(true);
    287     rv = cache->CreateEntry(key, &entry, cb.callback());
    288     if (cb.GetResult(rv) != net::OK)
    289       return GENERIC;
    290     entry->Close();
    291     FlushQueue(cache);
    292     if (50 == i && action >= disk_cache::REMOVE_LOAD_1) {
    293       rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
    294       if (cb.GetResult(rv) != net::OK)
    295         return GENERIC;
    296       entry->Close();
    297       FlushQueue(cache);
    298     }
    299   }
    300 
    301   if (action <= disk_cache::INSERT_LOAD_2) {
    302     disk_cache::g_rankings_crash = action;
    303 
    304     rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
    305     if (cb.GetResult(rv) != net::OK)
    306       return GENERIC;
    307   }
    308 
    309   rv = cache->OpenEntry(kCrashEntryName, &entry, cb.callback());
    310   if (cb.GetResult(rv) != net::OK)
    311     return GENERIC;
    312 
    313   disk_cache::g_rankings_crash = action;
    314 
    315   entry->Doom();
    316   entry->Close();
    317   FlushQueue(cache);
    318 
    319   return NOT_REACHED;
    320 }
    321 
    322 // Main function on the child process.
    323 int SlaveCode(const base::FilePath& path, RankCrashes action) {
    324   base::MessageLoopForIO message_loop;
    325 
    326   base::FilePath full_path;
    327   if (!CreateTargetFolder(path, action, &full_path)) {
    328     printf("Destination folder found, please remove it.\n");
    329     return CRASH_OVERWRITE;
    330   }
    331 
    332   base::Thread cache_thread("CacheThread");
    333   if (!cache_thread.StartWithOptions(
    334           base::Thread::Options(base::MessageLoop::TYPE_IO, 0)))
    335     return GENERIC;
    336 
    337   if (action <= disk_cache::INSERT_ONE_3)
    338     return SimpleInsert(full_path, action, &cache_thread);
    339 
    340   if (action <= disk_cache::INSERT_LOAD_2)
    341     return LoadOperations(full_path, action, &cache_thread);
    342 
    343   if (action <= disk_cache::REMOVE_ONE_4)
    344     return SimpleRemove(full_path, action, &cache_thread);
    345 
    346   if (action <= disk_cache::REMOVE_HEAD_4)
    347     return HeadRemove(full_path, action, &cache_thread);
    348 
    349   if (action <= disk_cache::REMOVE_TAIL_3)
    350     return SimpleRemove(full_path, action, &cache_thread);
    351 
    352   if (action <= disk_cache::REMOVE_LOAD_3)
    353     return LoadOperations(full_path, action, &cache_thread);
    354 
    355   return NOT_REACHED;
    356 }
    357 
    358 // -----------------------------------------------------------------------
    359 
    360 int main(int argc, const char* argv[]) {
    361   // Setup an AtExitManager so Singleton objects will be destructed.
    362   base::AtExitManager at_exit_manager;
    363 
    364   if (argc < 2)
    365     return MasterCode();
    366 
    367   char* end;
    368   RankCrashes action = static_cast<RankCrashes>(strtol(argv[1], &end, 0));
    369   if (action <= disk_cache::NO_CRASH || action >= disk_cache::MAX_CRASH) {
    370     printf("Invalid action\n");
    371     return INVALID_ARGUMENT;
    372   }
    373 
    374   base::FilePath path;
    375   PathService::Get(base::DIR_SOURCE_ROOT, &path);
    376   path = path.AppendASCII("net");
    377   path = path.AppendASCII("data");
    378   path = path.AppendASCII("cache_tests");
    379   path = path.AppendASCII("new_crashes");
    380 
    381   return SlaveCode(path, action);
    382 }
    383