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