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