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