|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "TestHarness.h" |
|
7 |
|
8 #include "nsThreadUtils.h" |
|
9 #include "nsIClassInfo.h" |
|
10 #include "nsIOutputStream.h" |
|
11 #include "nsIObserver.h" |
|
12 #include "nsISerializable.h" |
|
13 #include "nsISupports.h" |
|
14 #include "nsIStartupCache.h" |
|
15 #include "nsIStringStream.h" |
|
16 #include "nsIStorageStream.h" |
|
17 #include "nsIObjectInputStream.h" |
|
18 #include "nsIObjectOutputStream.h" |
|
19 #include "nsIURI.h" |
|
20 #include "nsStringAPI.h" |
|
21 #include "nsIPrefBranch.h" |
|
22 #include "nsIPrefService.h" |
|
23 #include "nsIXPConnect.h" |
|
24 #include "prio.h" |
|
25 #include "mozilla/Maybe.h" |
|
26 |
|
27 using namespace JS; |
|
28 |
|
29 namespace mozilla { |
|
30 namespace scache { |
|
31 |
|
32 NS_IMPORT nsresult |
|
33 NewObjectInputStreamFromBuffer(char* buffer, uint32_t len, |
|
34 nsIObjectInputStream** stream); |
|
35 |
|
36 // We can't retrieve the wrapped stream from the objectOutputStream later, |
|
37 // so we return it here. |
|
38 NS_IMPORT nsresult |
|
39 NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream, |
|
40 nsIStorageStream** stream); |
|
41 |
|
42 NS_IMPORT nsresult |
|
43 NewBufferFromStorageStream(nsIStorageStream *storageStream, |
|
44 char** buffer, uint32_t* len); |
|
45 } |
|
46 } |
|
47 |
|
48 using namespace mozilla::scache; |
|
49 |
|
50 #define NS_ENSURE_STR_MATCH(str1, str2, testname) \ |
|
51 PR_BEGIN_MACRO \ |
|
52 if (0 != strcmp(str1, str2)) { \ |
|
53 fail("failed " testname); \ |
|
54 return NS_ERROR_FAILURE; \ |
|
55 } \ |
|
56 passed("passed " testname); \ |
|
57 PR_END_MACRO |
|
58 |
|
59 nsresult |
|
60 WaitForStartupTimer() { |
|
61 nsresult rv; |
|
62 nsCOMPtr<nsIStartupCache> sc |
|
63 = do_GetService("@mozilla.org/startupcache/cache;1"); |
|
64 PR_Sleep(10 * PR_TicksPerSecond()); |
|
65 |
|
66 bool complete; |
|
67 while (true) { |
|
68 |
|
69 NS_ProcessPendingEvents(nullptr); |
|
70 rv = sc->StartupWriteComplete(&complete); |
|
71 if (NS_FAILED(rv) || complete) |
|
72 break; |
|
73 PR_Sleep(1 * PR_TicksPerSecond()); |
|
74 } |
|
75 return rv; |
|
76 } |
|
77 |
|
78 nsresult |
|
79 TestStartupWriteRead() { |
|
80 nsresult rv; |
|
81 nsCOMPtr<nsIStartupCache> sc |
|
82 = do_GetService("@mozilla.org/startupcache/cache;1", &rv); |
|
83 if (!sc) { |
|
84 fail("didn't get a pointer..."); |
|
85 return NS_ERROR_FAILURE; |
|
86 } else { |
|
87 passed("got a pointer?"); |
|
88 } |
|
89 sc->InvalidateCache(); |
|
90 |
|
91 const char* buf = "Market opportunities for BeardBook"; |
|
92 const char* id = "id"; |
|
93 char* outbufPtr = nullptr; |
|
94 nsAutoArrayPtr<char> outbuf; |
|
95 uint32_t len; |
|
96 |
|
97 rv = sc->PutBuffer(id, buf, strlen(buf) + 1); |
|
98 NS_ENSURE_SUCCESS(rv, rv); |
|
99 |
|
100 rv = sc->GetBuffer(id, &outbufPtr, &len); |
|
101 NS_ENSURE_SUCCESS(rv, rv); |
|
102 outbuf = outbufPtr; |
|
103 NS_ENSURE_STR_MATCH(buf, outbuf, "pre-write read"); |
|
104 |
|
105 rv = sc->ResetStartupWriteTimer(); |
|
106 rv = WaitForStartupTimer(); |
|
107 NS_ENSURE_SUCCESS(rv, rv); |
|
108 |
|
109 rv = sc->GetBuffer(id, &outbufPtr, &len); |
|
110 NS_ENSURE_SUCCESS(rv, rv); |
|
111 outbuf = outbufPtr; |
|
112 NS_ENSURE_STR_MATCH(buf, outbuf, "simple write/read"); |
|
113 |
|
114 return NS_OK; |
|
115 } |
|
116 |
|
117 nsresult |
|
118 TestWriteInvalidateRead() { |
|
119 nsresult rv; |
|
120 const char* buf = "BeardBook competitive analysis"; |
|
121 const char* id = "id"; |
|
122 char* outbuf = nullptr; |
|
123 uint32_t len; |
|
124 nsCOMPtr<nsIStartupCache> sc |
|
125 = do_GetService("@mozilla.org/startupcache/cache;1", &rv); |
|
126 sc->InvalidateCache(); |
|
127 |
|
128 rv = sc->PutBuffer(id, buf, strlen(buf) + 1); |
|
129 NS_ENSURE_SUCCESS(rv, rv); |
|
130 |
|
131 sc->InvalidateCache(); |
|
132 |
|
133 rv = sc->GetBuffer(id, &outbuf, &len); |
|
134 delete[] outbuf; |
|
135 if (rv == NS_ERROR_NOT_AVAILABLE) { |
|
136 passed("buffer not available after invalidate"); |
|
137 } else if (NS_SUCCEEDED(rv)) { |
|
138 fail("GetBuffer succeeded unexpectedly after invalidate"); |
|
139 return NS_ERROR_UNEXPECTED; |
|
140 } else { |
|
141 fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE"); |
|
142 return rv; |
|
143 } |
|
144 |
|
145 sc->InvalidateCache(); |
|
146 return NS_OK; |
|
147 } |
|
148 |
|
149 nsresult |
|
150 TestWriteObject() { |
|
151 nsresult rv; |
|
152 |
|
153 nsCOMPtr<nsIURI> obj |
|
154 = do_CreateInstance("@mozilla.org/network/simple-uri;1"); |
|
155 if (!obj) { |
|
156 fail("did not create object in test write object"); |
|
157 return NS_ERROR_UNEXPECTED; |
|
158 } |
|
159 NS_NAMED_LITERAL_CSTRING(spec, "http://www.mozilla.org"); |
|
160 obj->SetSpec(spec); |
|
161 nsCOMPtr<nsIStartupCache> sc = do_GetService("@mozilla.org/startupcache/cache;1", &rv); |
|
162 |
|
163 sc->InvalidateCache(); |
|
164 |
|
165 // Create an object stream. Usually this is done with |
|
166 // NewObjectOutputWrappedStorageStream, but that uses |
|
167 // StartupCache::GetSingleton in debug builds, and we |
|
168 // don't have access to that here. Obviously. |
|
169 const char* id = "id"; |
|
170 nsCOMPtr<nsIStorageStream> storageStream |
|
171 = do_CreateInstance("@mozilla.org/storagestream;1"); |
|
172 NS_ENSURE_ARG_POINTER(storageStream); |
|
173 |
|
174 rv = storageStream->Init(256, (uint32_t) -1); |
|
175 NS_ENSURE_SUCCESS(rv, rv); |
|
176 |
|
177 nsCOMPtr<nsIObjectOutputStream> objectOutput |
|
178 = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); |
|
179 if (!objectOutput) |
|
180 return NS_ERROR_OUT_OF_MEMORY; |
|
181 |
|
182 nsCOMPtr<nsIOutputStream> outputStream |
|
183 = do_QueryInterface(storageStream); |
|
184 |
|
185 rv = objectOutput->SetOutputStream(outputStream); |
|
186 |
|
187 if (NS_FAILED(rv)) { |
|
188 fail("failed to create output stream"); |
|
189 return rv; |
|
190 } |
|
191 nsCOMPtr<nsISupports> objQI(do_QueryInterface(obj)); |
|
192 rv = objectOutput->WriteObject(objQI, true); |
|
193 if (NS_FAILED(rv)) { |
|
194 fail("failed to write object"); |
|
195 return rv; |
|
196 } |
|
197 |
|
198 char* bufPtr = nullptr; |
|
199 nsAutoArrayPtr<char> buf; |
|
200 uint32_t len; |
|
201 NewBufferFromStorageStream(storageStream, &bufPtr, &len); |
|
202 buf = bufPtr; |
|
203 |
|
204 // Since this is a post-startup write, it should be written and |
|
205 // available. |
|
206 rv = sc->PutBuffer(id, buf, len); |
|
207 if (NS_FAILED(rv)) { |
|
208 fail("failed to insert input stream"); |
|
209 return rv; |
|
210 } |
|
211 |
|
212 char* buf2Ptr = nullptr; |
|
213 nsAutoArrayPtr<char> buf2; |
|
214 uint32_t len2; |
|
215 nsCOMPtr<nsIObjectInputStream> objectInput; |
|
216 rv = sc->GetBuffer(id, &buf2Ptr, &len2); |
|
217 if (NS_FAILED(rv)) { |
|
218 fail("failed to retrieve buffer"); |
|
219 return rv; |
|
220 } |
|
221 buf2 = buf2Ptr; |
|
222 |
|
223 rv = NewObjectInputStreamFromBuffer(buf2, len2, getter_AddRefs(objectInput)); |
|
224 if (NS_FAILED(rv)) { |
|
225 fail("failed to created input stream"); |
|
226 return rv; |
|
227 } |
|
228 buf2.forget(); |
|
229 |
|
230 nsCOMPtr<nsISupports> deserialized; |
|
231 rv = objectInput->ReadObject(true, getter_AddRefs(deserialized)); |
|
232 if (NS_FAILED(rv)) { |
|
233 fail("failed to read object"); |
|
234 return rv; |
|
235 } |
|
236 |
|
237 bool match = false; |
|
238 nsCOMPtr<nsIURI> uri(do_QueryInterface(deserialized)); |
|
239 if (uri) { |
|
240 nsCString outSpec; |
|
241 uri->GetSpec(outSpec); |
|
242 match = outSpec.Equals(spec); |
|
243 } |
|
244 if (!match) { |
|
245 fail("deserialized object has incorrect information"); |
|
246 return rv; |
|
247 } |
|
248 |
|
249 passed("write object"); |
|
250 return NS_OK; |
|
251 } |
|
252 |
|
253 nsresult |
|
254 LockCacheFile(bool protect, nsIFile* profileDir) { |
|
255 NS_ENSURE_ARG(profileDir); |
|
256 |
|
257 nsCOMPtr<nsIFile> startupCache; |
|
258 profileDir->Clone(getter_AddRefs(startupCache)); |
|
259 NS_ENSURE_STATE(startupCache); |
|
260 startupCache->AppendNative(NS_LITERAL_CSTRING("startupCache")); |
|
261 |
|
262 nsresult rv; |
|
263 #ifndef XP_WIN |
|
264 static uint32_t oldPermissions; |
|
265 #else |
|
266 static PRFileDesc* fd = nullptr; |
|
267 #endif |
|
268 |
|
269 // To prevent deletion of the startupcache file, we change the containing |
|
270 // directory's permissions on Linux/Mac, and hold the file open on Windows |
|
271 if (protect) { |
|
272 #ifndef XP_WIN |
|
273 rv = startupCache->GetPermissions(&oldPermissions); |
|
274 NS_ENSURE_SUCCESS(rv, rv); |
|
275 rv = startupCache->SetPermissions(0555); |
|
276 NS_ENSURE_SUCCESS(rv, rv); |
|
277 #else |
|
278 // Filename logic from StartupCache.cpp |
|
279 #ifdef IS_BIG_ENDIAN |
|
280 #define SC_ENDIAN "big" |
|
281 #else |
|
282 #define SC_ENDIAN "little" |
|
283 #endif |
|
284 |
|
285 #if PR_BYTES_PER_WORD == 4 |
|
286 #define SC_WORDSIZE "4" |
|
287 #else |
|
288 #define SC_WORDSIZE "8" |
|
289 #endif |
|
290 char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN; |
|
291 startupCache->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName)); |
|
292 |
|
293 rv = startupCache->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); |
|
294 NS_ENSURE_SUCCESS(rv, rv); |
|
295 #endif |
|
296 } else { |
|
297 #ifndef XP_WIN |
|
298 rv = startupCache->SetPermissions(oldPermissions); |
|
299 NS_ENSURE_SUCCESS(rv, rv); |
|
300 #else |
|
301 PR_Close(fd); |
|
302 #endif |
|
303 } |
|
304 |
|
305 return NS_OK; |
|
306 } |
|
307 |
|
308 nsresult |
|
309 TestIgnoreDiskCache(nsIFile* profileDir) { |
|
310 nsresult rv; |
|
311 nsCOMPtr<nsIStartupCache> sc |
|
312 = do_GetService("@mozilla.org/startupcache/cache;1", &rv); |
|
313 sc->InvalidateCache(); |
|
314 |
|
315 const char* buf = "Get a Beardbook app for your smartphone"; |
|
316 const char* id = "id"; |
|
317 char* outbuf = nullptr; |
|
318 uint32_t len; |
|
319 |
|
320 rv = sc->PutBuffer(id, buf, strlen(buf) + 1); |
|
321 NS_ENSURE_SUCCESS(rv, rv); |
|
322 rv = sc->ResetStartupWriteTimer(); |
|
323 rv = WaitForStartupTimer(); |
|
324 NS_ENSURE_SUCCESS(rv, rv); |
|
325 |
|
326 // Prevent StartupCache::InvalidateCache from deleting the disk file |
|
327 rv = LockCacheFile(true, profileDir); |
|
328 NS_ENSURE_SUCCESS(rv, rv); |
|
329 |
|
330 sc->IgnoreDiskCache(); |
|
331 |
|
332 rv = sc->GetBuffer(id, &outbuf, &len); |
|
333 |
|
334 nsresult r = LockCacheFile(false, profileDir); |
|
335 NS_ENSURE_SUCCESS(r, r); |
|
336 |
|
337 delete[] outbuf; |
|
338 |
|
339 if (rv == NS_ERROR_NOT_AVAILABLE) { |
|
340 passed("buffer not available after ignoring disk cache"); |
|
341 } else if (NS_SUCCEEDED(rv)) { |
|
342 fail("GetBuffer succeeded unexpectedly after ignoring disk cache"); |
|
343 return NS_ERROR_UNEXPECTED; |
|
344 } else { |
|
345 fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE"); |
|
346 return rv; |
|
347 } |
|
348 |
|
349 sc->InvalidateCache(); |
|
350 return NS_OK; |
|
351 } |
|
352 |
|
353 nsresult |
|
354 TestEarlyShutdown() { |
|
355 nsresult rv; |
|
356 nsCOMPtr<nsIStartupCache> sc |
|
357 = do_GetService("@mozilla.org/startupcache/cache;1", &rv); |
|
358 sc->InvalidateCache(); |
|
359 |
|
360 const char* buf = "Find your soul beardmate on BeardBook"; |
|
361 const char* id = "id"; |
|
362 uint32_t len; |
|
363 char* outbuf = nullptr; |
|
364 |
|
365 sc->ResetStartupWriteTimer(); |
|
366 rv = sc->PutBuffer(id, buf, strlen(buf) + 1); |
|
367 NS_ENSURE_SUCCESS(rv, rv); |
|
368 |
|
369 nsCOMPtr<nsIObserver> obs; |
|
370 sc->GetObserver(getter_AddRefs(obs)); |
|
371 obs->Observe(nullptr, "xpcom-shutdown", nullptr); |
|
372 rv = WaitForStartupTimer(); |
|
373 NS_ENSURE_SUCCESS(rv, rv); |
|
374 |
|
375 rv = sc->GetBuffer(id, &outbuf, &len); |
|
376 delete[] outbuf; |
|
377 |
|
378 if (NS_SUCCEEDED(rv)) { |
|
379 passed("GetBuffer succeeded after early shutdown"); |
|
380 } else { |
|
381 fail("GetBuffer failed after early shutdown"); |
|
382 return rv; |
|
383 } |
|
384 |
|
385 const char* other_id = "other_id"; |
|
386 rv = sc->PutBuffer(other_id, buf, strlen(buf) + 1); |
|
387 |
|
388 if (rv == NS_ERROR_NOT_AVAILABLE) { |
|
389 passed("PutBuffer not available after early shutdown"); |
|
390 } else if (NS_SUCCEEDED(rv)) { |
|
391 fail("PutBuffer succeeded unexpectedly after early shutdown"); |
|
392 return NS_ERROR_UNEXPECTED; |
|
393 } else { |
|
394 fail("PutBuffer gave an unexpected failure, expected NOT_AVAILABLE"); |
|
395 return rv; |
|
396 } |
|
397 |
|
398 return NS_OK; |
|
399 } |
|
400 |
|
401 int main(int argc, char** argv) |
|
402 { |
|
403 ScopedXPCOM xpcom("Startup Cache"); |
|
404 if (xpcom.failed()) |
|
405 return 1; |
|
406 |
|
407 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
408 prefs->SetIntPref("hangmonitor.timeout", 0); |
|
409 |
|
410 int rv = 0; |
|
411 nsresult scrv; |
|
412 |
|
413 // Register TestStartupCacheTelemetry |
|
414 nsCOMPtr<nsIFile> manifest; |
|
415 scrv = NS_GetSpecialDirectory(NS_GRE_DIR, |
|
416 getter_AddRefs(manifest)); |
|
417 if (NS_FAILED(scrv)) { |
|
418 fail("NS_XPCOM_CURRENT_PROCESS_DIR"); |
|
419 return 1; |
|
420 } |
|
421 |
|
422 manifest->AppendNative(NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest")); |
|
423 XRE_AddManifestLocation(NS_COMPONENT_LOCATION, manifest); |
|
424 |
|
425 nsCOMPtr<nsIObserver> telemetryThing = |
|
426 do_GetService("@mozilla.org/testing/startup-cache-telemetry.js"); |
|
427 if (!telemetryThing) { |
|
428 fail("telemetryThing"); |
|
429 return 1; |
|
430 } |
|
431 scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr); |
|
432 if (NS_FAILED(scrv)) { |
|
433 fail("save-initial"); |
|
434 rv = 1; |
|
435 } |
|
436 |
|
437 nsCOMPtr<nsIStartupCache> sc |
|
438 = do_GetService("@mozilla.org/startupcache/cache;1", &scrv); |
|
439 if (NS_FAILED(scrv)) |
|
440 rv = 1; |
|
441 else |
|
442 sc->RecordAgesAlways(); |
|
443 if (NS_FAILED(TestStartupWriteRead())) |
|
444 rv = 1; |
|
445 if (NS_FAILED(TestWriteInvalidateRead())) |
|
446 rv = 1; |
|
447 if (NS_FAILED(TestWriteObject())) |
|
448 rv = 1; |
|
449 nsCOMPtr<nsIFile> profileDir = xpcom.GetProfileDirectory(); |
|
450 if (NS_FAILED(TestIgnoreDiskCache(profileDir))) |
|
451 rv = 1; |
|
452 if (NS_FAILED(TestEarlyShutdown())) |
|
453 rv = 1; |
|
454 |
|
455 scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr); |
|
456 if (NS_FAILED(scrv)) { |
|
457 fail("check-final"); |
|
458 rv = 1; |
|
459 } |
|
460 |
|
461 return rv; |
|
462 } |