1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Startup Cache.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * The Mozilla Foundation <http://www.mozilla.org/>.
19 : * Portions created by the Initial Developer are Copyright (C) 2009
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Benedict Hsieh <bhsieh@mozilla.com>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "TestHarness.h"
40 :
41 : #include "nsThreadUtils.h"
42 : #include "nsIClassInfo.h"
43 : #include "nsIOutputStream.h"
44 : #include "nsIObserver.h"
45 : #include "nsISerializable.h"
46 : #include "nsISupports.h"
47 : #include "nsIStartupCache.h"
48 : #include "nsIStringStream.h"
49 : #include "nsIStorageStream.h"
50 : #include "nsIObjectInputStream.h"
51 : #include "nsIObjectOutputStream.h"
52 : #include "nsIURI.h"
53 : #include "nsStringAPI.h"
54 : #include "nsIPrefBranch.h"
55 : #include "nsIPrefService.h"
56 : #include "nsITelemetry.h"
57 : #include "jsapi.h"
58 :
59 : namespace mozilla {
60 : namespace scache {
61 :
62 : NS_IMPORT nsresult
63 : NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len,
64 : nsIObjectInputStream** stream);
65 :
66 : // We can't retrieve the wrapped stream from the objectOutputStream later,
67 : // so we return it here.
68 : NS_IMPORT nsresult
69 : NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
70 : nsIStorageStream** stream);
71 :
72 : NS_IMPORT nsresult
73 : NewBufferFromStorageStream(nsIStorageStream *storageStream,
74 : char** buffer, PRUint32* len);
75 : }
76 : }
77 :
78 : using namespace mozilla::scache;
79 :
80 : #define NS_ENSURE_STR_MATCH(str1, str2, testname) \
81 : PR_BEGIN_MACRO \
82 : if (0 != strcmp(str1, str2)) { \
83 : fail("failed " testname); \
84 : return NS_ERROR_FAILURE; \
85 : } \
86 : passed("passed " testname); \
87 : PR_END_MACRO
88 :
89 : nsresult
90 2 : WaitForStartupTimer() {
91 : nsresult rv;
92 : nsCOMPtr<nsIStartupCache> sc
93 4 : = do_GetService("@mozilla.org/startupcache/cache;1");
94 2 : PR_Sleep(10 * PR_TicksPerSecond());
95 :
96 : bool complete;
97 100 : while (true) {
98 :
99 102 : NS_ProcessPendingEvents(nsnull);
100 102 : rv = sc->StartupWriteComplete(&complete);
101 102 : if (NS_FAILED(rv) || complete)
102 : break;
103 100 : PR_Sleep(1 * PR_TicksPerSecond());
104 : }
105 2 : return rv;
106 : }
107 :
108 : nsresult
109 1 : TestStartupWriteRead() {
110 : nsresult rv;
111 : nsCOMPtr<nsIStartupCache> sc
112 2 : = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
113 1 : if (!sc) {
114 0 : fail("didn't get a pointer...");
115 0 : return NS_ERROR_FAILURE;
116 : } else {
117 1 : passed("got a pointer?");
118 : }
119 1 : sc->InvalidateCache();
120 :
121 1 : const char* buf = "Market opportunities for BeardBook";
122 1 : const char* id = "id";
123 1 : char* outbufPtr = NULL;
124 2 : nsAutoArrayPtr<char> outbuf;
125 : PRUint32 len;
126 :
127 1 : rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
128 1 : NS_ENSURE_SUCCESS(rv, rv);
129 :
130 1 : rv = sc->GetBuffer(id, &outbufPtr, &len);
131 1 : NS_ENSURE_SUCCESS(rv, rv);
132 1 : outbuf = outbufPtr;
133 1 : NS_ENSURE_STR_MATCH(buf, outbuf, "pre-write read");
134 :
135 1 : rv = sc->ResetStartupWriteTimer();
136 1 : rv = WaitForStartupTimer();
137 1 : NS_ENSURE_SUCCESS(rv, rv);
138 :
139 1 : rv = sc->GetBuffer(id, &outbufPtr, &len);
140 1 : NS_ENSURE_SUCCESS(rv, rv);
141 1 : outbuf = outbufPtr;
142 1 : NS_ENSURE_STR_MATCH(buf, outbuf, "simple write/read");
143 :
144 1 : return NS_OK;
145 : }
146 :
147 : nsresult
148 1 : TestWriteInvalidateRead() {
149 : nsresult rv;
150 1 : const char* buf = "BeardBook competitive analysis";
151 1 : const char* id = "id";
152 1 : char* outbuf = NULL;
153 : PRUint32 len;
154 : nsCOMPtr<nsIStartupCache> sc
155 2 : = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
156 1 : sc->InvalidateCache();
157 :
158 1 : rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
159 1 : NS_ENSURE_SUCCESS(rv, rv);
160 :
161 1 : sc->InvalidateCache();
162 :
163 1 : rv = sc->GetBuffer(id, &outbuf, &len);
164 1 : delete[] outbuf;
165 1 : if (rv == NS_ERROR_NOT_AVAILABLE) {
166 1 : passed("buffer not available after invalidate");
167 0 : } else if (NS_SUCCEEDED(rv)) {
168 0 : fail("GetBuffer succeeded unexpectedly after invalidate");
169 0 : return NS_ERROR_UNEXPECTED;
170 : } else {
171 0 : fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
172 0 : return rv;
173 : }
174 :
175 1 : sc->InvalidateCache();
176 1 : return NS_OK;
177 : }
178 :
179 : nsresult
180 1 : TestWriteObject() {
181 : nsresult rv;
182 :
183 : nsCOMPtr<nsIURI> obj
184 2 : = do_CreateInstance("@mozilla.org/network/simple-uri;1");
185 1 : if (!obj) {
186 0 : fail("did not create object in test write object");
187 0 : return NS_ERROR_UNEXPECTED;
188 : }
189 2 : NS_NAMED_LITERAL_CSTRING(spec, "http://www.mozilla.org");
190 1 : obj->SetSpec(spec);
191 2 : nsCOMPtr<nsIStartupCache> sc = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
192 :
193 1 : sc->InvalidateCache();
194 :
195 : // Create an object stream. Usually this is done with
196 : // NewObjectOutputWrappedStorageStream, but that uses
197 : // StartupCache::GetSingleton in debug builds, and we
198 : // don't have access to that here. Obviously.
199 1 : const char* id = "id";
200 : nsCOMPtr<nsIStorageStream> storageStream
201 2 : = do_CreateInstance("@mozilla.org/storagestream;1");
202 1 : NS_ENSURE_ARG_POINTER(storageStream);
203 :
204 1 : rv = storageStream->Init(256, (PRUint32) -1, nsnull);
205 1 : NS_ENSURE_SUCCESS(rv, rv);
206 :
207 : nsCOMPtr<nsIObjectOutputStream> objectOutput
208 2 : = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
209 1 : if (!objectOutput)
210 0 : return NS_ERROR_OUT_OF_MEMORY;
211 :
212 : nsCOMPtr<nsIOutputStream> outputStream
213 2 : = do_QueryInterface(storageStream);
214 :
215 1 : rv = objectOutput->SetOutputStream(outputStream);
216 :
217 1 : if (NS_FAILED(rv)) {
218 0 : fail("failed to create output stream");
219 0 : return rv;
220 : }
221 2 : nsCOMPtr<nsISupports> objQI(do_QueryInterface(obj));
222 1 : rv = objectOutput->WriteObject(objQI, true);
223 1 : if (NS_FAILED(rv)) {
224 0 : fail("failed to write object");
225 0 : return rv;
226 : }
227 :
228 1 : char* bufPtr = NULL;
229 2 : nsAutoArrayPtr<char> buf;
230 : PRUint32 len;
231 1 : NewBufferFromStorageStream(storageStream, &bufPtr, &len);
232 1 : buf = bufPtr;
233 :
234 : // Since this is a post-startup write, it should be written and
235 : // available.
236 1 : rv = sc->PutBuffer(id, buf, len);
237 1 : if (NS_FAILED(rv)) {
238 0 : fail("failed to insert input stream");
239 0 : return rv;
240 : }
241 :
242 1 : char* buf2Ptr = NULL;
243 2 : nsAutoArrayPtr<char> buf2;
244 : PRUint32 len2;
245 2 : nsCOMPtr<nsIObjectInputStream> objectInput;
246 1 : rv = sc->GetBuffer(id, &buf2Ptr, &len2);
247 1 : if (NS_FAILED(rv)) {
248 0 : fail("failed to retrieve buffer");
249 0 : return rv;
250 : }
251 1 : buf2 = buf2Ptr;
252 :
253 1 : rv = NewObjectInputStreamFromBuffer(buf2, len2, getter_AddRefs(objectInput));
254 1 : if (NS_FAILED(rv)) {
255 0 : fail("failed to created input stream");
256 0 : return rv;
257 : }
258 1 : buf2.forget();
259 :
260 2 : nsCOMPtr<nsISupports> deserialized;
261 1 : rv = objectInput->ReadObject(true, getter_AddRefs(deserialized));
262 1 : if (NS_FAILED(rv)) {
263 0 : fail("failed to read object");
264 0 : return rv;
265 : }
266 :
267 1 : bool match = false;
268 2 : nsCOMPtr<nsIURI> uri(do_QueryInterface(deserialized));
269 1 : if (uri) {
270 2 : nsCString outSpec;
271 1 : uri->GetSpec(outSpec);
272 1 : match = outSpec.Equals(spec);
273 : }
274 1 : if (!match) {
275 0 : fail("deserialized object has incorrect information");
276 0 : return rv;
277 : }
278 :
279 1 : passed("write object");
280 1 : return NS_OK;
281 : }
282 :
283 : nsresult
284 1 : TestEarlyShutdown() {
285 : nsresult rv;
286 : nsCOMPtr<nsIStartupCache> sc
287 2 : = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
288 1 : sc->InvalidateCache();
289 :
290 1 : const char* buf = "Find your soul beardmate on BeardBook";
291 1 : const char* id = "id";
292 : PRUint32 len;
293 1 : char* outbuf = NULL;
294 :
295 1 : sc->ResetStartupWriteTimer();
296 1 : rv = sc->PutBuffer(buf, id, strlen(buf) + 1);
297 1 : NS_ENSURE_SUCCESS(rv, rv);
298 :
299 2 : nsCOMPtr<nsIObserver> obs;
300 1 : sc->GetObserver(getter_AddRefs(obs));
301 1 : obs->Observe(nsnull, "xpcom-shutdown", nsnull);
302 1 : rv = WaitForStartupTimer();
303 1 : NS_ENSURE_SUCCESS(rv, rv);
304 :
305 1 : rv = sc->GetBuffer(id, &outbuf, &len);
306 1 : delete[] outbuf;
307 :
308 1 : if (rv == NS_ERROR_NOT_AVAILABLE) {
309 1 : passed("buffer not available after early shutdown");
310 0 : } else if (NS_SUCCEEDED(rv)) {
311 0 : fail("GetBuffer succeeded unexpectedly after early shutdown");
312 0 : return NS_ERROR_UNEXPECTED;
313 : } else {
314 0 : fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
315 0 : return rv;
316 : }
317 :
318 1 : return NS_OK;
319 : }
320 :
321 : bool
322 1 : SetupJS(JSContext **cxp)
323 : {
324 1 : JSRuntime *rt = JS_NewRuntime(32 * 1024 * 1024);
325 1 : if (!rt)
326 0 : return false;
327 1 : JSContext *cx = JS_NewContext(rt, 8192);
328 1 : if (!cx)
329 0 : return false;
330 1 : *cxp = cx;
331 1 : return true;
332 : }
333 :
334 : bool
335 2 : GetHistogramCounts(const char *testmsg, JSContext *cx, jsval *counts)
336 : {
337 4 : nsCOMPtr<nsITelemetry> telemetry = do_GetService("@mozilla.org/base/telemetry;1");
338 4 : NS_NAMED_LITERAL_CSTRING(histogram_id, "STARTUP_CACHE_AGE_HOURS");
339 4 : JS::AutoValueRooter h(cx);
340 2 : nsresult trv = telemetry->GetHistogramById(histogram_id, cx, h.addr());
341 2 : if (NS_FAILED(trv)) {
342 0 : fail("%s: couldn't get histogram", testmsg);
343 0 : return false;
344 : }
345 2 : passed(testmsg);
346 :
347 4 : JS::AutoValueRooter snapshot_val(cx);
348 2 : JSFunction *snapshot_fn = NULL;
349 4 : JS::AutoValueRooter ss(cx);
350 2 : return (JS_GetProperty(cx, JSVAL_TO_OBJECT(h.value()), "snapshot",
351 4 : snapshot_val.addr())
352 2 : && (snapshot_fn = JS_ValueToFunction(cx, snapshot_val.value()))
353 2 : && JS::Call(cx, JSVAL_TO_OBJECT(h.value()),
354 4 : snapshot_fn, 0, NULL, ss.addr())
355 2 : && JS_GetProperty(cx, JSVAL_TO_OBJECT(ss.value()),
356 8 : "counts", counts));
357 : }
358 :
359 : nsresult
360 1 : CompareCountArrays(JSContext *cx, JSObject *before, JSObject *after)
361 : {
362 : uint32_t before_size, after_size;
363 1 : if (!(JS_GetArrayLength(cx, before, &before_size)
364 1 : && JS_GetArrayLength(cx, after, &after_size))) {
365 0 : return NS_ERROR_UNEXPECTED;
366 : }
367 :
368 1 : if (before_size != after_size) {
369 0 : return NS_ERROR_UNEXPECTED;
370 : }
371 :
372 2 : for (uint32_t i = 0; i < before_size; ++i) {
373 : jsval before_num, after_num;
374 :
375 1 : if (!(JS_GetElement(cx, before, i, &before_num)
376 1 : && JS_GetElement(cx, after, i, &after_num))) {
377 0 : return NS_ERROR_UNEXPECTED;
378 : }
379 :
380 1 : JSBool same = JS_TRUE;
381 1 : if (!JS_LooselyEqual(cx, before_num, after_num, &same)) {
382 0 : return NS_ERROR_UNEXPECTED;
383 : } else {
384 1 : if (same) {
385 0 : continue;
386 : } else {
387 : // Some element of the histograms's count arrays differed.
388 : // That's a good thing!
389 1 : return NS_OK;
390 : }
391 : }
392 : }
393 :
394 : // All of the elements of the histograms's count arrays differed.
395 : // Not good, we should have recorded something.
396 0 : return NS_ERROR_FAILURE;
397 : }
398 :
399 1 : int main(int argc, char** argv)
400 : {
401 2 : ScopedXPCOM xpcom("Startup Cache");
402 1 : if (xpcom.failed())
403 0 : return 1;
404 :
405 2 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
406 1 : prefs->SetIntPref("hangmonitor.timeout", 0);
407 :
408 1 : int rv = 0;
409 : // nsITelemetry doesn't have a nice C++ interface.
410 : JSContext *cx;
411 1 : bool use_js = true;
412 1 : if (!SetupJS(&cx))
413 0 : use_js = false;
414 :
415 2 : JSAutoRequest req(cx);
416 : static JSClass global_class = {
417 : "global", JSCLASS_NEW_RESOLVE | JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE,
418 : JS_PropertyStub, JS_PropertyStub,
419 : JS_PropertyStub, JS_StrictPropertyStub,
420 : JS_EnumerateStub, JS_ResolveStub,
421 : JS_ConvertStub, JS_FinalizeStub,
422 : JSCLASS_NO_OPTIONAL_MEMBERS
423 : };
424 1 : JSObject *glob = nsnull;
425 1 : if (use_js)
426 1 : glob = JS_NewCompartmentAndGlobalObject(cx, &global_class, NULL);
427 1 : if (!glob)
428 0 : use_js = false;
429 1 : JSCrossCompartmentCall *compartment = nsnull;
430 1 : if (use_js)
431 1 : compartment = JS_EnterCrossCompartmentCall(cx, glob);
432 1 : if (!compartment)
433 0 : use_js = false;
434 1 : if (use_js && !JS_InitStandardClasses(cx, glob))
435 0 : use_js = false;
436 :
437 2 : JS::AutoValueRooter before_counts(cx);
438 2 : if (use_js && !GetHistogramCounts("STARTUP_CACHE_AGE_HOURS histogram before test",
439 1 : cx, before_counts.addr()))
440 0 : use_js = false;
441 :
442 : nsresult scrv;
443 : nsCOMPtr<nsIStartupCache> sc
444 2 : = do_GetService("@mozilla.org/startupcache/cache;1", &scrv);
445 1 : if (NS_FAILED(scrv))
446 0 : rv = 1;
447 : else
448 1 : sc->RecordAgesAlways();
449 1 : if (NS_FAILED(TestStartupWriteRead()))
450 0 : rv = 1;
451 1 : if (NS_FAILED(TestWriteInvalidateRead()))
452 0 : rv = 1;
453 1 : if (NS_FAILED(TestWriteObject()))
454 0 : rv = 1;
455 1 : if (NS_FAILED(TestEarlyShutdown()))
456 0 : rv = 1;
457 :
458 2 : JS::AutoValueRooter after_counts(cx);
459 2 : if (use_js && !GetHistogramCounts("STARTUP_CACHE_AGE_HOURS histogram after test",
460 1 : cx, after_counts.addr()))
461 0 : use_js = false;
462 :
463 1 : if (!use_js) {
464 0 : fail("couldn't check histogram recording");
465 0 : rv = 1;
466 : } else {
467 : nsresult compare = CompareCountArrays(cx,
468 1 : JSVAL_TO_OBJECT(before_counts.value()),
469 2 : JSVAL_TO_OBJECT(after_counts.value()));
470 1 : if (compare == NS_ERROR_UNEXPECTED) {
471 0 : fail("count comparison error");
472 0 : rv = 1;
473 1 : } else if (compare == NS_ERROR_FAILURE) {
474 0 : fail("histogram didn't record samples");
475 0 : rv = 1;
476 : } else {
477 1 : passed("histogram records samples");
478 : }
479 : }
480 :
481 1 : if (use_js)
482 1 : JS_LeaveCrossCompartmentCall(compartment);
483 :
484 1 : return rv;
485 : }
|