michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "TestHarness.h" michael@0: michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIClassInfo.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsISerializable.h" michael@0: #include "nsISupports.h" michael@0: #include "nsIStartupCache.h" michael@0: #include "nsIStringStream.h" michael@0: #include "nsIStorageStream.h" michael@0: #include "nsIObjectInputStream.h" michael@0: #include "nsIObjectOutputStream.h" michael@0: #include "nsIURI.h" michael@0: #include "nsStringAPI.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "prio.h" michael@0: #include "mozilla/Maybe.h" michael@0: michael@0: using namespace JS; michael@0: michael@0: namespace mozilla { michael@0: namespace scache { michael@0: michael@0: NS_IMPORT nsresult michael@0: NewObjectInputStreamFromBuffer(char* buffer, uint32_t len, michael@0: nsIObjectInputStream** stream); michael@0: michael@0: // We can't retrieve the wrapped stream from the objectOutputStream later, michael@0: // so we return it here. michael@0: NS_IMPORT nsresult michael@0: NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream, michael@0: nsIStorageStream** stream); michael@0: michael@0: NS_IMPORT nsresult michael@0: NewBufferFromStorageStream(nsIStorageStream *storageStream, michael@0: char** buffer, uint32_t* len); michael@0: } michael@0: } michael@0: michael@0: using namespace mozilla::scache; michael@0: michael@0: #define NS_ENSURE_STR_MATCH(str1, str2, testname) \ michael@0: PR_BEGIN_MACRO \ michael@0: if (0 != strcmp(str1, str2)) { \ michael@0: fail("failed " testname); \ michael@0: return NS_ERROR_FAILURE; \ michael@0: } \ michael@0: passed("passed " testname); \ michael@0: PR_END_MACRO michael@0: michael@0: nsresult michael@0: WaitForStartupTimer() { michael@0: nsresult rv; michael@0: nsCOMPtr sc michael@0: = do_GetService("@mozilla.org/startupcache/cache;1"); michael@0: PR_Sleep(10 * PR_TicksPerSecond()); michael@0: michael@0: bool complete; michael@0: while (true) { michael@0: michael@0: NS_ProcessPendingEvents(nullptr); michael@0: rv = sc->StartupWriteComplete(&complete); michael@0: if (NS_FAILED(rv) || complete) michael@0: break; michael@0: PR_Sleep(1 * PR_TicksPerSecond()); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: TestStartupWriteRead() { michael@0: nsresult rv; michael@0: nsCOMPtr sc michael@0: = do_GetService("@mozilla.org/startupcache/cache;1", &rv); michael@0: if (!sc) { michael@0: fail("didn't get a pointer..."); michael@0: return NS_ERROR_FAILURE; michael@0: } else { michael@0: passed("got a pointer?"); michael@0: } michael@0: sc->InvalidateCache(); michael@0: michael@0: const char* buf = "Market opportunities for BeardBook"; michael@0: const char* id = "id"; michael@0: char* outbufPtr = nullptr; michael@0: nsAutoArrayPtr outbuf; michael@0: uint32_t len; michael@0: michael@0: rv = sc->PutBuffer(id, buf, strlen(buf) + 1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sc->GetBuffer(id, &outbufPtr, &len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: outbuf = outbufPtr; michael@0: NS_ENSURE_STR_MATCH(buf, outbuf, "pre-write read"); michael@0: michael@0: rv = sc->ResetStartupWriteTimer(); michael@0: rv = WaitForStartupTimer(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sc->GetBuffer(id, &outbufPtr, &len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: outbuf = outbufPtr; michael@0: NS_ENSURE_STR_MATCH(buf, outbuf, "simple write/read"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TestWriteInvalidateRead() { michael@0: nsresult rv; michael@0: const char* buf = "BeardBook competitive analysis"; michael@0: const char* id = "id"; michael@0: char* outbuf = nullptr; michael@0: uint32_t len; michael@0: nsCOMPtr sc michael@0: = do_GetService("@mozilla.org/startupcache/cache;1", &rv); michael@0: sc->InvalidateCache(); michael@0: michael@0: rv = sc->PutBuffer(id, buf, strlen(buf) + 1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: sc->InvalidateCache(); michael@0: michael@0: rv = sc->GetBuffer(id, &outbuf, &len); michael@0: delete[] outbuf; michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: passed("buffer not available after invalidate"); michael@0: } else if (NS_SUCCEEDED(rv)) { michael@0: fail("GetBuffer succeeded unexpectedly after invalidate"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } else { michael@0: fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE"); michael@0: return rv; michael@0: } michael@0: michael@0: sc->InvalidateCache(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TestWriteObject() { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr obj michael@0: = do_CreateInstance("@mozilla.org/network/simple-uri;1"); michael@0: if (!obj) { michael@0: fail("did not create object in test write object"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: NS_NAMED_LITERAL_CSTRING(spec, "http://www.mozilla.org"); michael@0: obj->SetSpec(spec); michael@0: nsCOMPtr sc = do_GetService("@mozilla.org/startupcache/cache;1", &rv); michael@0: michael@0: sc->InvalidateCache(); michael@0: michael@0: // Create an object stream. Usually this is done with michael@0: // NewObjectOutputWrappedStorageStream, but that uses michael@0: // StartupCache::GetSingleton in debug builds, and we michael@0: // don't have access to that here. Obviously. michael@0: const char* id = "id"; michael@0: nsCOMPtr storageStream michael@0: = do_CreateInstance("@mozilla.org/storagestream;1"); michael@0: NS_ENSURE_ARG_POINTER(storageStream); michael@0: michael@0: rv = storageStream->Init(256, (uint32_t) -1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr objectOutput michael@0: = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); michael@0: if (!objectOutput) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsCOMPtr outputStream michael@0: = do_QueryInterface(storageStream); michael@0: michael@0: rv = objectOutput->SetOutputStream(outputStream); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: fail("failed to create output stream"); michael@0: return rv; michael@0: } michael@0: nsCOMPtr objQI(do_QueryInterface(obj)); michael@0: rv = objectOutput->WriteObject(objQI, true); michael@0: if (NS_FAILED(rv)) { michael@0: fail("failed to write object"); michael@0: return rv; michael@0: } michael@0: michael@0: char* bufPtr = nullptr; michael@0: nsAutoArrayPtr buf; michael@0: uint32_t len; michael@0: NewBufferFromStorageStream(storageStream, &bufPtr, &len); michael@0: buf = bufPtr; michael@0: michael@0: // Since this is a post-startup write, it should be written and michael@0: // available. michael@0: rv = sc->PutBuffer(id, buf, len); michael@0: if (NS_FAILED(rv)) { michael@0: fail("failed to insert input stream"); michael@0: return rv; michael@0: } michael@0: michael@0: char* buf2Ptr = nullptr; michael@0: nsAutoArrayPtr buf2; michael@0: uint32_t len2; michael@0: nsCOMPtr objectInput; michael@0: rv = sc->GetBuffer(id, &buf2Ptr, &len2); michael@0: if (NS_FAILED(rv)) { michael@0: fail("failed to retrieve buffer"); michael@0: return rv; michael@0: } michael@0: buf2 = buf2Ptr; michael@0: michael@0: rv = NewObjectInputStreamFromBuffer(buf2, len2, getter_AddRefs(objectInput)); michael@0: if (NS_FAILED(rv)) { michael@0: fail("failed to created input stream"); michael@0: return rv; michael@0: } michael@0: buf2.forget(); michael@0: michael@0: nsCOMPtr deserialized; michael@0: rv = objectInput->ReadObject(true, getter_AddRefs(deserialized)); michael@0: if (NS_FAILED(rv)) { michael@0: fail("failed to read object"); michael@0: return rv; michael@0: } michael@0: michael@0: bool match = false; michael@0: nsCOMPtr uri(do_QueryInterface(deserialized)); michael@0: if (uri) { michael@0: nsCString outSpec; michael@0: uri->GetSpec(outSpec); michael@0: match = outSpec.Equals(spec); michael@0: } michael@0: if (!match) { michael@0: fail("deserialized object has incorrect information"); michael@0: return rv; michael@0: } michael@0: michael@0: passed("write object"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: LockCacheFile(bool protect, nsIFile* profileDir) { michael@0: NS_ENSURE_ARG(profileDir); michael@0: michael@0: nsCOMPtr startupCache; michael@0: profileDir->Clone(getter_AddRefs(startupCache)); michael@0: NS_ENSURE_STATE(startupCache); michael@0: startupCache->AppendNative(NS_LITERAL_CSTRING("startupCache")); michael@0: michael@0: nsresult rv; michael@0: #ifndef XP_WIN michael@0: static uint32_t oldPermissions; michael@0: #else michael@0: static PRFileDesc* fd = nullptr; michael@0: #endif michael@0: michael@0: // To prevent deletion of the startupcache file, we change the containing michael@0: // directory's permissions on Linux/Mac, and hold the file open on Windows michael@0: if (protect) { michael@0: #ifndef XP_WIN michael@0: rv = startupCache->GetPermissions(&oldPermissions); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = startupCache->SetPermissions(0555); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: #else michael@0: // Filename logic from StartupCache.cpp michael@0: #ifdef IS_BIG_ENDIAN michael@0: #define SC_ENDIAN "big" michael@0: #else michael@0: #define SC_ENDIAN "little" michael@0: #endif michael@0: michael@0: #if PR_BYTES_PER_WORD == 4 michael@0: #define SC_WORDSIZE "4" michael@0: #else michael@0: #define SC_WORDSIZE "8" michael@0: #endif michael@0: char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN; michael@0: startupCache->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName)); michael@0: michael@0: rv = startupCache->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: #endif michael@0: } else { michael@0: #ifndef XP_WIN michael@0: rv = startupCache->SetPermissions(oldPermissions); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: #else michael@0: PR_Close(fd); michael@0: #endif michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TestIgnoreDiskCache(nsIFile* profileDir) { michael@0: nsresult rv; michael@0: nsCOMPtr sc michael@0: = do_GetService("@mozilla.org/startupcache/cache;1", &rv); michael@0: sc->InvalidateCache(); michael@0: michael@0: const char* buf = "Get a Beardbook app for your smartphone"; michael@0: const char* id = "id"; michael@0: char* outbuf = nullptr; michael@0: uint32_t len; michael@0: michael@0: rv = sc->PutBuffer(id, buf, strlen(buf) + 1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = sc->ResetStartupWriteTimer(); michael@0: rv = WaitForStartupTimer(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Prevent StartupCache::InvalidateCache from deleting the disk file michael@0: rv = LockCacheFile(true, profileDir); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: sc->IgnoreDiskCache(); michael@0: michael@0: rv = sc->GetBuffer(id, &outbuf, &len); michael@0: michael@0: nsresult r = LockCacheFile(false, profileDir); michael@0: NS_ENSURE_SUCCESS(r, r); michael@0: michael@0: delete[] outbuf; michael@0: michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: passed("buffer not available after ignoring disk cache"); michael@0: } else if (NS_SUCCEEDED(rv)) { michael@0: fail("GetBuffer succeeded unexpectedly after ignoring disk cache"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } else { michael@0: fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE"); michael@0: return rv; michael@0: } michael@0: michael@0: sc->InvalidateCache(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TestEarlyShutdown() { michael@0: nsresult rv; michael@0: nsCOMPtr sc michael@0: = do_GetService("@mozilla.org/startupcache/cache;1", &rv); michael@0: sc->InvalidateCache(); michael@0: michael@0: const char* buf = "Find your soul beardmate on BeardBook"; michael@0: const char* id = "id"; michael@0: uint32_t len; michael@0: char* outbuf = nullptr; michael@0: michael@0: sc->ResetStartupWriteTimer(); michael@0: rv = sc->PutBuffer(id, buf, strlen(buf) + 1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr obs; michael@0: sc->GetObserver(getter_AddRefs(obs)); michael@0: obs->Observe(nullptr, "xpcom-shutdown", nullptr); michael@0: rv = WaitForStartupTimer(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sc->GetBuffer(id, &outbuf, &len); michael@0: delete[] outbuf; michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: passed("GetBuffer succeeded after early shutdown"); michael@0: } else { michael@0: fail("GetBuffer failed after early shutdown"); michael@0: return rv; michael@0: } michael@0: michael@0: const char* other_id = "other_id"; michael@0: rv = sc->PutBuffer(other_id, buf, strlen(buf) + 1); michael@0: michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: passed("PutBuffer not available after early shutdown"); michael@0: } else if (NS_SUCCEEDED(rv)) { michael@0: fail("PutBuffer succeeded unexpectedly after early shutdown"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } else { michael@0: fail("PutBuffer gave an unexpected failure, expected NOT_AVAILABLE"); michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int main(int argc, char** argv) michael@0: { michael@0: ScopedXPCOM xpcom("Startup Cache"); michael@0: if (xpcom.failed()) michael@0: return 1; michael@0: michael@0: nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: prefs->SetIntPref("hangmonitor.timeout", 0); michael@0: michael@0: int rv = 0; michael@0: nsresult scrv; michael@0: michael@0: // Register TestStartupCacheTelemetry michael@0: nsCOMPtr manifest; michael@0: scrv = NS_GetSpecialDirectory(NS_GRE_DIR, michael@0: getter_AddRefs(manifest)); michael@0: if (NS_FAILED(scrv)) { michael@0: fail("NS_XPCOM_CURRENT_PROCESS_DIR"); michael@0: return 1; michael@0: } michael@0: michael@0: manifest->AppendNative(NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest")); michael@0: XRE_AddManifestLocation(NS_COMPONENT_LOCATION, manifest); michael@0: michael@0: nsCOMPtr telemetryThing = michael@0: do_GetService("@mozilla.org/testing/startup-cache-telemetry.js"); michael@0: if (!telemetryThing) { michael@0: fail("telemetryThing"); michael@0: return 1; michael@0: } michael@0: scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr); michael@0: if (NS_FAILED(scrv)) { michael@0: fail("save-initial"); michael@0: rv = 1; michael@0: } michael@0: michael@0: nsCOMPtr sc michael@0: = do_GetService("@mozilla.org/startupcache/cache;1", &scrv); michael@0: if (NS_FAILED(scrv)) michael@0: rv = 1; michael@0: else michael@0: sc->RecordAgesAlways(); michael@0: if (NS_FAILED(TestStartupWriteRead())) michael@0: rv = 1; michael@0: if (NS_FAILED(TestWriteInvalidateRead())) michael@0: rv = 1; michael@0: if (NS_FAILED(TestWriteObject())) michael@0: rv = 1; michael@0: nsCOMPtr profileDir = xpcom.GetProfileDirectory(); michael@0: if (NS_FAILED(TestIgnoreDiskCache(profileDir))) michael@0: rv = 1; michael@0: if (NS_FAILED(TestEarlyShutdown())) michael@0: rv = 1; michael@0: michael@0: scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr); michael@0: if (NS_FAILED(scrv)) { michael@0: fail("check-final"); michael@0: rv = 1; michael@0: } michael@0: michael@0: return rv; michael@0: }