summaryrefslogtreecommitdiffstats
path: root/startupcache/test
diff options
context:
space:
mode:
Diffstat (limited to 'startupcache/test')
-rw-r--r--startupcache/test/TestStartupCache.cpp480
-rw-r--r--startupcache/test/TestStartupCacheTelemetry.js60
-rw-r--r--startupcache/test/TestStartupCacheTelemetry.manifest2
-rw-r--r--startupcache/test/moz.build14
4 files changed, 556 insertions, 0 deletions
diff --git a/startupcache/test/TestStartupCache.cpp b/startupcache/test/TestStartupCache.cpp
new file mode 100644
index 000000000..a16c2de72
--- /dev/null
+++ b/startupcache/test/TestStartupCache.cpp
@@ -0,0 +1,480 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TestHarness.h"
+
+#include "nsThreadUtils.h"
+#include "nsIClassInfo.h"
+#include "nsIOutputStream.h"
+#include "nsIObserver.h"
+#include "nsISerializable.h"
+#include "nsISupports.h"
+#include "nsIStartupCache.h"
+#include "nsIStringStream.h"
+#include "nsIStorageStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIURI.h"
+#include "nsStringAPI.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIXPConnect.h"
+#include "prio.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+using namespace JS;
+
+namespace mozilla {
+namespace scache {
+
+NS_IMPORT nsresult
+NewObjectInputStreamFromBuffer(UniquePtr<char[]> buffer, uint32_t len,
+ nsIObjectInputStream** stream);
+
+// We can't retrieve the wrapped stream from the objectOutputStream later,
+// so we return it here.
+NS_IMPORT nsresult
+NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+ nsIStorageStream** stream);
+
+NS_IMPORT nsresult
+NewBufferFromStorageStream(nsIStorageStream *storageStream,
+ UniquePtr<char[]>* buffer, uint32_t* len);
+} // namespace scache
+} // namespace mozilla
+
+using namespace mozilla::scache;
+using mozilla::UniquePtr;
+
+#define NS_ENSURE_STR_MATCH(str1, str2, testname) \
+PR_BEGIN_MACRO \
+if (0 != strcmp(str1, str2)) { \
+ fail("failed " testname); \
+ return NS_ERROR_FAILURE; \
+} \
+passed("passed " testname); \
+PR_END_MACRO
+
+nsresult
+WaitForStartupTimer() {
+ nsresult rv;
+ nsCOMPtr<nsIStartupCache> sc
+ = do_GetService("@mozilla.org/startupcache/cache;1");
+ PR_Sleep(10 * PR_TicksPerSecond());
+
+ bool complete;
+ while (true) {
+
+ NS_ProcessPendingEvents(nullptr);
+ rv = sc->StartupWriteComplete(&complete);
+ if (NS_FAILED(rv) || complete)
+ break;
+ PR_Sleep(1 * PR_TicksPerSecond());
+ }
+ return rv;
+}
+
+nsresult
+TestStartupWriteRead() {
+ nsresult rv;
+ nsCOMPtr<nsIStartupCache> sc
+ = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+ if (!sc) {
+ fail("didn't get a pointer...");
+ return NS_ERROR_FAILURE;
+ } else {
+ passed("got a pointer?");
+ }
+ sc->InvalidateCache();
+
+ const char* buf = "Market opportunities for BeardBook";
+ const char* id = "id";
+ UniquePtr<char[]> outbuf;
+ uint32_t len;
+
+ rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = sc->GetBuffer(id, &outbuf, &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STR_MATCH(buf, outbuf.get(), "pre-write read");
+
+ rv = sc->ResetStartupWriteTimer();
+ rv = WaitForStartupTimer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = sc->GetBuffer(id, &outbuf, &len);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STR_MATCH(buf, outbuf.get(), "simple write/read");
+
+ return NS_OK;
+}
+
+nsresult
+TestWriteInvalidateRead() {
+ nsresult rv;
+ const char* buf = "BeardBook competitive analysis";
+ const char* id = "id";
+ UniquePtr<char[]> outbuf;
+ uint32_t len;
+ nsCOMPtr<nsIStartupCache> sc
+ = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+ sc->InvalidateCache();
+
+ rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sc->InvalidateCache();
+
+ rv = sc->GetBuffer(id, &outbuf, &len);
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ passed("buffer not available after invalidate");
+ } else if (NS_SUCCEEDED(rv)) {
+ fail("GetBuffer succeeded unexpectedly after invalidate");
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
+ return rv;
+ }
+
+ sc->InvalidateCache();
+ return NS_OK;
+}
+
+nsresult
+TestWriteObject() {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> obj
+ = do_CreateInstance("@mozilla.org/network/simple-uri;1");
+ if (!obj) {
+ fail("did not create object in test write object");
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_NAMED_LITERAL_CSTRING(spec, "http://www.mozilla.org");
+ rv = obj->SetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStartupCache> sc = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+
+ sc->InvalidateCache();
+
+ // Create an object stream. Usually this is done with
+ // NewObjectOutputWrappedStorageStream, but that uses
+ // StartupCache::GetSingleton in debug builds, and we
+ // don't have access to that here. Obviously.
+ const char* id = "id";
+ nsCOMPtr<nsIStorageStream> storageStream
+ = do_CreateInstance("@mozilla.org/storagestream;1");
+ NS_ENSURE_ARG_POINTER(storageStream);
+
+ rv = storageStream->Init(256, (uint32_t) -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObjectOutputStream> objectOutput
+ = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+ if (!objectOutput)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIOutputStream> outputStream
+ = do_QueryInterface(storageStream);
+
+ rv = objectOutput->SetOutputStream(outputStream);
+
+ if (NS_FAILED(rv)) {
+ fail("failed to create output stream");
+ return rv;
+ }
+ nsCOMPtr<nsISupports> objQI(do_QueryInterface(obj));
+ rv = objectOutput->WriteObject(objQI, true);
+ if (NS_FAILED(rv)) {
+ fail("failed to write object");
+ return rv;
+ }
+
+ UniquePtr<char[]> buf;
+ uint32_t len;
+ NewBufferFromStorageStream(storageStream, &buf, &len);
+
+ // Since this is a post-startup write, it should be written and
+ // available.
+ rv = sc->PutBuffer(id, buf.get(), len);
+ if (NS_FAILED(rv)) {
+ fail("failed to insert input stream");
+ return rv;
+ }
+
+ UniquePtr<char[]> buf2;
+ uint32_t len2;
+ nsCOMPtr<nsIObjectInputStream> objectInput;
+ rv = sc->GetBuffer(id, &buf2, &len2);
+ if (NS_FAILED(rv)) {
+ fail("failed to retrieve buffer");
+ return rv;
+ }
+
+ rv = NewObjectInputStreamFromBuffer(Move(buf2), len2,
+ getter_AddRefs(objectInput));
+ if (NS_FAILED(rv)) {
+ fail("failed to created input stream");
+ return rv;
+ }
+
+ nsCOMPtr<nsISupports> deserialized;
+ rv = objectInput->ReadObject(true, getter_AddRefs(deserialized));
+ if (NS_FAILED(rv)) {
+ fail("failed to read object");
+ return rv;
+ }
+
+ bool match = false;
+ nsCOMPtr<nsIURI> uri(do_QueryInterface(deserialized));
+ if (uri) {
+ nsCString outSpec;
+ rv = uri->GetSpec(outSpec);
+ if (NS_FAILED(rv)) {
+ fail("failed to get spec");
+ return rv;
+ }
+ match = outSpec.Equals(spec);
+ }
+ if (!match) {
+ fail("deserialized object has incorrect information");
+ return rv;
+ }
+
+ passed("write object");
+ return NS_OK;
+}
+
+nsresult
+LockCacheFile(bool protect, nsIFile* profileDir) {
+ NS_ENSURE_ARG(profileDir);
+
+ nsCOMPtr<nsIFile> startupCache;
+ profileDir->Clone(getter_AddRefs(startupCache));
+ NS_ENSURE_STATE(startupCache);
+ startupCache->AppendNative(NS_LITERAL_CSTRING("startupCache"));
+
+ nsresult rv;
+#ifndef XP_WIN
+ static uint32_t oldPermissions;
+#else
+ static PRFileDesc* fd = nullptr;
+#endif
+
+ // To prevent deletion of the startupcache file, we change the containing
+ // directory's permissions on Linux/Mac, and hold the file open on Windows
+ if (protect) {
+#ifndef XP_WIN
+ rv = startupCache->GetPermissions(&oldPermissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = startupCache->SetPermissions(0555);
+ NS_ENSURE_SUCCESS(rv, rv);
+#else
+ // Filename logic from StartupCache.cpp
+ #ifdef IS_BIG_ENDIAN
+ #define SC_ENDIAN "big"
+ #else
+ #define SC_ENDIAN "little"
+ #endif
+
+ #if PR_BYTES_PER_WORD == 4
+ #define SC_WORDSIZE "4"
+ #else
+ #define SC_WORDSIZE "8"
+ #endif
+ char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
+ startupCache->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
+
+ rv = startupCache->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+ } else {
+#ifndef XP_WIN
+ rv = startupCache->SetPermissions(oldPermissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+#else
+ PR_Close(fd);
+#endif
+ }
+
+ return NS_OK;
+}
+
+nsresult
+TestIgnoreDiskCache(nsIFile* profileDir) {
+ nsresult rv;
+ nsCOMPtr<nsIStartupCache> sc
+ = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+ sc->InvalidateCache();
+
+ const char* buf = "Get a Beardbook app for your smartphone";
+ const char* id = "id";
+ UniquePtr<char[]> outbuf;
+ uint32_t len;
+
+ rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sc->ResetStartupWriteTimer();
+ rv = WaitForStartupTimer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Prevent StartupCache::InvalidateCache from deleting the disk file
+ rv = LockCacheFile(true, profileDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sc->IgnoreDiskCache();
+
+ rv = sc->GetBuffer(id, &outbuf, &len);
+
+ nsresult r = LockCacheFile(false, profileDir);
+ NS_ENSURE_SUCCESS(r, r);
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ passed("buffer not available after ignoring disk cache");
+ } else if (NS_SUCCEEDED(rv)) {
+ fail("GetBuffer succeeded unexpectedly after ignoring disk cache");
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
+ return rv;
+ }
+
+ sc->InvalidateCache();
+ return NS_OK;
+}
+
+nsresult
+TestEarlyShutdown() {
+ nsresult rv;
+ nsCOMPtr<nsIStartupCache> sc
+ = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+ sc->InvalidateCache();
+
+ const char* buf = "Find your soul beardmate on BeardBook";
+ const char* id = "id";
+ uint32_t len;
+ UniquePtr<char[]> outbuf;
+
+ sc->ResetStartupWriteTimer();
+ rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserver> obs;
+ sc->GetObserver(getter_AddRefs(obs));
+ obs->Observe(nullptr, "xpcom-shutdown", nullptr);
+ rv = WaitForStartupTimer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = sc->GetBuffer(id, &outbuf, &len);
+
+ if (NS_SUCCEEDED(rv)) {
+ passed("GetBuffer succeeded after early shutdown");
+ } else {
+ fail("GetBuffer failed after early shutdown");
+ return rv;
+ }
+
+ const char* other_id = "other_id";
+ rv = sc->PutBuffer(other_id, buf, strlen(buf) + 1);
+
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ passed("PutBuffer not available after early shutdown");
+ } else if (NS_SUCCEEDED(rv)) {
+ fail("PutBuffer succeeded unexpectedly after early shutdown");
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ fail("PutBuffer gave an unexpected failure, expected NOT_AVAILABLE");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+int main(int argc, char** argv)
+{
+ ScopedXPCOM xpcom("Startup Cache");
+ if (xpcom.failed())
+ return 1;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs) {
+ fail("prefs");
+ return 1;
+ }
+ prefs->SetIntPref("hangmonitor.timeout", 0);
+
+ int rv = 0;
+ nsresult scrv;
+
+ // Register TestStartupCacheTelemetry
+ nsCOMPtr<nsIFile> manifest;
+ scrv = NS_GetSpecialDirectory(NS_GRE_DIR,
+ getter_AddRefs(manifest));
+ if (NS_FAILED(scrv)) {
+ fail("NS_XPCOM_CURRENT_PROCESS_DIR");
+ return 1;
+ }
+
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIFile> tempManifest;
+ manifest->Clone(getter_AddRefs(tempManifest));
+ manifest->AppendNative(
+ NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest"));
+ bool exists;
+ manifest->Exists(&exists);
+ if (!exists) {
+ // Workaround for bug 1080338 in mozharness.
+ manifest = tempManifest.forget();
+ manifest->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
+ manifest->AppendNative(
+ NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest"));
+ }
+#else
+ manifest->AppendNative(
+ NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest"));
+#endif
+
+ XRE_AddManifestLocation(NS_APP_LOCATION, manifest);
+
+ nsCOMPtr<nsIObserver> telemetryThing =
+ do_GetService("@mozilla.org/testing/startup-cache-telemetry.js");
+ if (!telemetryThing) {
+ fail("telemetryThing");
+ return 1;
+ }
+ scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr);
+ if (NS_FAILED(scrv)) {
+ fail("save-initial");
+ rv = 1;
+ }
+
+ nsCOMPtr<nsIStartupCache> sc
+ = do_GetService("@mozilla.org/startupcache/cache;1", &scrv);
+ if (NS_FAILED(scrv))
+ rv = 1;
+ else
+ sc->RecordAgesAlways();
+ if (NS_FAILED(TestStartupWriteRead()))
+ rv = 1;
+ if (NS_FAILED(TestWriteInvalidateRead()))
+ rv = 1;
+ if (NS_FAILED(TestWriteObject()))
+ rv = 1;
+ nsCOMPtr<nsIFile> profileDir = xpcom.GetProfileDirectory();
+ if (NS_FAILED(TestIgnoreDiskCache(profileDir)))
+ rv = 1;
+ if (NS_FAILED(TestEarlyShutdown()))
+ rv = 1;
+
+ scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr);
+ if (NS_FAILED(scrv)) {
+ fail("check-final");
+ rv = 1;
+ }
+
+ return rv;
+}
diff --git a/startupcache/test/TestStartupCacheTelemetry.js b/startupcache/test/TestStartupCacheTelemetry.js
new file mode 100644
index 000000000..7a570187f
--- /dev/null
+++ b/startupcache/test/TestStartupCacheTelemetry.js
@@ -0,0 +1,60 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function shouldHaveChanged(a, b)
+{
+ if (a.length != b.length) {
+ throw Error("TEST-UNEXPECTED-FAIL: telemetry count array size changed");
+ }
+
+ for (let i = 0; i < a.length; ++i) {
+ if (a[i] == b[i]) {
+ continue;
+ }
+ return; // something was different, that's all that matters
+ }
+ throw Error("TEST-UNEXPECTED-FAIL: telemetry data didn't change");
+}
+
+function TestStartupCacheTelemetry() { }
+
+TestStartupCacheTelemetry.prototype = {
+ classID: Components.ID("{73cbeffd-d6c7-42f0-aaf3-f176430dcfc8}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ saveInitial: function() {
+ let t = Services.telemetry;
+ this._age = t.getHistogramById("STARTUP_CACHE_AGE_HOURS").snapshot.counts;
+ this._invalid = t.getHistogramById("STARTUP_CACHE_INVALID").snapshot.counts;
+ },
+
+ checkFinal: function() {
+ let t = Services.telemetry;
+ let newAge = t.getHistogramById("STARTUP_CACHE_AGE_HOURS").snapshot.counts;
+ shouldHaveChanged(this._age, newAge);
+
+ let newInvalid = t.getHistogramById("STARTUP_CACHE_INVALID").snapshot.counts;
+ shouldHaveChanged(this._invalid, newInvalid);
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "save-initial":
+ this.saveInitial();
+ break;
+
+ case "check-final":
+ this.checkFinal();
+ break;
+
+ default:
+ throw Error("BADDOG, NO MILKBONE FOR YOU");
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestStartupCacheTelemetry]);
diff --git a/startupcache/test/TestStartupCacheTelemetry.manifest b/startupcache/test/TestStartupCacheTelemetry.manifest
new file mode 100644
index 000000000..b288a7292
--- /dev/null
+++ b/startupcache/test/TestStartupCacheTelemetry.manifest
@@ -0,0 +1,2 @@
+component {73cbeffd-d6c7-42f0-aaf3-f176430dcfc8} TestStartupCacheTelemetry.js
+contract @mozilla.org/testing/startup-cache-telemetry.js {73cbeffd-d6c7-42f0-aaf3-f176430dcfc8}
diff --git a/startupcache/test/moz.build b/startupcache/test/moz.build
new file mode 100644
index 000000000..5a9e6a61e
--- /dev/null
+++ b/startupcache/test/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+GeckoCppUnitTests([
+ 'TestStartupCache',
+])
+
+EXTRA_COMPONENTS += [
+ 'TestStartupCacheTelemetry.js',
+ 'TestStartupCacheTelemetry.manifest',
+]