summaryrefslogtreecommitdiffstats
path: root/xpcom/tests/gtest
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /xpcom/tests/gtest
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpcom/tests/gtest')
-rw-r--r--xpcom/tests/gtest/Helpers.cpp133
-rw-r--r--xpcom/tests/gtest/Helpers.h73
-rw-r--r--xpcom/tests/gtest/TestAllocReplacement.cpp175
-rw-r--r--xpcom/tests/gtest/TestAtoms.cpp153
-rw-r--r--xpcom/tests/gtest/TestAutoPtr.cpp220
-rw-r--r--xpcom/tests/gtest/TestAutoRef.cpp56
-rw-r--r--xpcom/tests/gtest/TestBase64.cpp291
-rw-r--r--xpcom/tests/gtest/TestCOMArray.cpp286
-rw-r--r--xpcom/tests/gtest/TestCOMPtr.cpp466
-rw-r--r--xpcom/tests/gtest/TestCOMPtrEq.cpp79
-rw-r--r--xpcom/tests/gtest/TestCRT.cpp86
-rw-r--r--xpcom/tests/gtest/TestCallTemplates.cpp104
-rw-r--r--xpcom/tests/gtest/TestCloneInputStream.cpp200
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetector.cpp322
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp170
-rw-r--r--xpcom/tests/gtest/TestEncoding.cpp109
-rw-r--r--xpcom/tests/gtest/TestEscapeURL.cpp69
-rw-r--r--xpcom/tests/gtest/TestExpirationTracker.cpp185
-rw-r--r--xpcom/tests/gtest/TestFile.cpp477
-rw-r--r--xpcom/tests/gtest/TestHashtables.cpp435
-rw-r--r--xpcom/tests/gtest/TestID.cpp36
-rw-r--r--xpcom/tests/gtest/TestNSPRLogModulesParser.cpp111
-rw-r--r--xpcom/tests/gtest/TestNsRefPtr.cpp479
-rw-r--r--xpcom/tests/gtest/TestObserverArray.cpp167
-rw-r--r--xpcom/tests/gtest/TestObserverService.cpp288
-rw-r--r--xpcom/tests/gtest/TestPLDHash.cpp368
-rw-r--r--xpcom/tests/gtest/TestPipes.cpp1097
-rw-r--r--xpcom/tests/gtest/TestPriorityQueue.cpp76
-rw-r--r--xpcom/tests/gtest/TestRacingServiceManager.cpp300
-rw-r--r--xpcom/tests/gtest/TestSTLWrappers.cpp78
-rw-r--r--xpcom/tests/gtest/TestSlicedInputStream.cpp266
-rw-r--r--xpcom/tests/gtest/TestSnappyStreams.cpp191
-rw-r--r--xpcom/tests/gtest/TestStateWatching.cpp46
-rw-r--r--xpcom/tests/gtest/TestStorageStream.cpp131
-rw-r--r--xpcom/tests/gtest/TestStringStream.cpp65
-rw-r--r--xpcom/tests/gtest/TestStrings.cpp982
-rw-r--r--xpcom/tests/gtest/TestSynchronization.cpp313
-rw-r--r--xpcom/tests/gtest/TestTArray.cpp206
-rw-r--r--xpcom/tests/gtest/TestTArray2.cpp1033
-rw-r--r--xpcom/tests/gtest/TestTextFormatter.cpp34
-rw-r--r--xpcom/tests/gtest/TestThreadPool.cpp124
-rw-r--r--xpcom/tests/gtest/TestThreadPoolListener.cpp209
-rw-r--r--xpcom/tests/gtest/TestThreadUtils.cpp378
-rw-r--r--xpcom/tests/gtest/TestThreads.cpp275
-rw-r--r--xpcom/tests/gtest/TestTimeStamp.cpp70
-rw-r--r--xpcom/tests/gtest/TestTimers.cpp437
-rw-r--r--xpcom/tests/gtest/TestTokenizer.cpp1134
-rw-r--r--xpcom/tests/gtest/TestUTF.cpp191
-rw-r--r--xpcom/tests/gtest/TestXPIDLString.cpp24
-rw-r--r--xpcom/tests/gtest/UTFStrings.h112
-rw-r--r--xpcom/tests/gtest/moz.build77
51 files changed, 13387 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/Helpers.cpp b/xpcom/tests/gtest/Helpers.cpp
new file mode 100644
index 000000000..e06ef901b
--- /dev/null
+++ b/xpcom/tests/gtest/Helpers.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* Helper routines for xpcom gtests. */
+
+#include "Helpers.h"
+
+#include <algorithm>
+#include "gtest/gtest.h"
+#include "nsIOutputStream.h"
+#include "nsStreamUtils.h"
+#include "nsTArray.h"
+
+namespace testing {
+
+// Populate an array with the given number of bytes. Data is lorem ipsum
+// random text, but deterministic across multiple calls.
+void
+CreateData(uint32_t aNumBytes, nsTArray<char>& aDataOut)
+{
+ static const char data[] =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec egestas "
+ "purus eu condimentum iaculis. In accumsan leo eget odio porttitor, non "
+ "rhoncus nulla vestibulum. Etiam lacinia consectetur nisl nec "
+ "sollicitudin. Sed fringilla accumsan diam, pulvinar varius massa. Duis "
+ "mollis dignissim felis, eget tempus nisi tristique ut. Fusce euismod, "
+ "lectus non lacinia tempor, tellus diam suscipit quam, eget hendrerit "
+ "lacus nunc fringilla ante. Sed ultrices massa vitae risus molestie, ut "
+ "finibus quam laoreet nullam.";
+ static const uint32_t dataLength = sizeof(data) - 1;
+
+ aDataOut.SetCapacity(aNumBytes);
+
+ while (aNumBytes > 0) {
+ uint32_t amount = std::min(dataLength, aNumBytes);
+ aDataOut.AppendElements(data, amount);
+ aNumBytes -= amount;
+ }
+}
+
+// Write the given number of bytes out to the stream. Loop until expected
+// bytes count is reached or an error occurs.
+void
+Write(nsIOutputStream* aStream, const nsTArray<char>& aData, uint32_t aOffset,
+ uint32_t aNumBytes)
+{
+ uint32_t remaining =
+ std::min(aNumBytes, static_cast<uint32_t>(aData.Length() - aOffset));
+
+ while (remaining > 0) {
+ uint32_t numWritten;
+ nsresult rv = aStream->Write(aData.Elements() + aOffset, remaining,
+ &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ if (numWritten < 1) {
+ break;
+ }
+ aOffset += numWritten;
+ remaining -= numWritten;
+ }
+}
+
+// Write the given number of bytes and then close the stream.
+void
+WriteAllAndClose(nsIOutputStream* aStream, const nsTArray<char>& aData)
+{
+ Write(aStream, aData, 0, aData.Length());
+ aStream->Close();
+}
+
+// Synchronously consume the given input stream and validate the resulting data
+// against the given array of expected values.
+void
+ConsumeAndValidateStream(nsIInputStream* aStream,
+ const nsTArray<char>& aExpectedData)
+{
+ nsDependentCSubstring data(aExpectedData.Elements(), aExpectedData.Length());
+ ConsumeAndValidateStream(aStream, data);
+}
+
+// Synchronously consume the given input stream and validate the resulting data
+// against the given string of expected values.
+void
+ConsumeAndValidateStream(nsIInputStream* aStream,
+ const nsACString& aExpectedData)
+{
+ nsAutoCString outputData;
+ nsresult rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_EQ(aExpectedData.Length(), outputData.Length());
+ ASSERT_TRUE(aExpectedData.Equals(outputData));
+}
+
+NS_IMPL_ISUPPORTS(OutputStreamCallback, nsIOutputStreamCallback);
+
+OutputStreamCallback::OutputStreamCallback()
+ : mCalled(false)
+{
+}
+
+OutputStreamCallback::~OutputStreamCallback()
+{
+}
+
+NS_IMETHODIMP
+OutputStreamCallback::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
+{
+ mCalled = true;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback);
+
+InputStreamCallback::InputStreamCallback()
+ : mCalled(false)
+{
+}
+
+InputStreamCallback::~InputStreamCallback()
+{
+}
+
+NS_IMETHODIMP
+InputStreamCallback::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+ mCalled = true;
+ return NS_OK;
+}
+
+} // namespace testing
diff --git a/xpcom/tests/gtest/Helpers.h b/xpcom/tests/gtest/Helpers.h
new file mode 100644
index 000000000..9cee1b825
--- /dev/null
+++ b/xpcom/tests/gtest/Helpers.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef __Helpers_h
+#define __Helpers_h
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsString.h"
+#include "nsTArrayForwardDeclare.h"
+#include <stdint.h>
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace testing {
+
+void
+CreateData(uint32_t aNumBytes, nsTArray<char>& aDataOut);
+
+void
+Write(nsIOutputStream* aStream, const nsTArray<char>& aData, uint32_t aOffset,
+ uint32_t aNumBytes);
+
+void
+WriteAllAndClose(nsIOutputStream* aStream, const nsTArray<char>& aData);
+
+void
+ConsumeAndValidateStream(nsIInputStream* aStream,
+ const nsTArray<char>& aExpectedData);
+
+void
+ConsumeAndValidateStream(nsIInputStream* aStream,
+ const nsACString& aExpectedData);
+
+class OutputStreamCallback final : public nsIOutputStreamCallback
+{
+public:
+ OutputStreamCallback();
+
+ bool Called() const { return mCalled; }
+
+private:
+ ~OutputStreamCallback();
+
+ bool mCalled;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+};
+
+class InputStreamCallback final : public nsIInputStreamCallback
+{
+public:
+ InputStreamCallback();
+
+ bool Called() const { return mCalled; }
+
+private:
+ ~InputStreamCallback();
+
+ bool mCalled;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+};
+
+} // namespace testing
+
+#endif // __Helpers_h
diff --git a/xpcom/tests/gtest/TestAllocReplacement.cpp b/xpcom/tests/gtest/TestAllocReplacement.cpp
new file mode 100644
index 000000000..7bcf3d4ad
--- /dev/null
+++ b/xpcom/tests/gtest/TestAllocReplacement.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/Attributes.h"
+#include "mozilla/Function.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/ScopeExit.h"
+#include "nsCOMPtr.h"
+#include "nsIMemoryReporter.h"
+#include "nsServiceManagerUtils.h"
+#include "gtest/gtest.h"
+
+// We want to ensure that various functions are hooked properly and that
+// allocations are getting routed through jemalloc. The strategy
+// pursued below relies on jemalloc's statistics tracking: we measure
+// the size of the jemalloc heap using nsIMemoryReporterManager,
+// allocate a chunk of memory with whatever function is supposed to be
+// hooked, and then ask for the size of the jemalloc heap again. If the
+// function has been hooked correctly, then the heap size should be
+// different between the two measurements. We can also check the
+// hooking of |free| and similar functions: once we free() the returned
+// pointer, we can measure the jemalloc heap size again, expecting it to
+// be identical to the size prior to the allocation.
+//
+// If we're not using jemalloc, then nsIMemoryReporterManager will
+// simply report an error, and we will ignore the entire test.
+//
+// This strategy is not perfect: it relies on GTests being
+// single-threaded, which they are, and no other threads doing
+// allocation during the test, which is uncertain, as XPCOM has started
+// up during gtests, and who knows what might be going on behind the
+// scenes. This latter assumption, however, does not seem to be a
+// problem in practice.
+#if defined(MOZ_MEMORY)
+#define ALLOCATION_ASSERT(b) ASSERT_TRUE((b))
+#else
+#define ALLOCATION_ASSERT(b) (void)(b)
+#endif
+
+#define ASSERT_ALLOCATION_HAPPENED(lambda) \
+ ALLOCATION_ASSERT(ValidateHookedAllocation(lambda, free));
+
+// We do run the risk of OOM'ing when we allocate something...all we can
+// do is try to allocate something so small that OOM'ing is unlikely.
+const size_t kAllocAmount = 16;
+
+// We declare this function MOZ_NEVER_INLINE to work around optimizing
+// compilers. If we permitted inlining here, then the compiler might
+// inline both this function and the calls to the function pointers we
+// pass in, giving something like:
+//
+// void* p = malloc(...);
+// ...do nothing with p except check nullptr-ness...
+// free(p);
+//
+// and the optimizer can delete the calls to malloc and free entirely,
+// which would make checking that the jemalloc heap had never changed
+// difficult.
+static MOZ_NEVER_INLINE bool
+ValidateHookedAllocation(void* (*aAllocator)(void),
+ void (*aFreeFunction)(void*))
+{
+ nsCOMPtr<nsIMemoryReporterManager> manager =
+ do_GetService("@mozilla.org/memory-reporter-manager;1");
+
+ int64_t before = 0;
+ nsresult rv = manager->GetHeapAllocated(&before);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ {
+ void* p = aAllocator();
+
+ if (!p) {
+ return false;
+ }
+
+ int64_t after = 0;
+ rv = manager->GetHeapAllocated(&after);
+
+ // Regardless of whether that call succeeded or failed, we are done with
+ // the allocated buffer now.
+ aFreeFunction(p);
+
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // Verify that our heap stats have changed.
+ if ((before + int64_t(kAllocAmount)) != after) {
+ return false;
+ }
+ }
+
+ // Verify that freeing the allocated pointer resets our heap to what it
+ // was before.
+ int64_t after = 0;
+ rv = manager->GetHeapAllocated(&after);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return before == after;
+}
+
+TEST(AllocReplacement, malloc_check)
+{
+ ASSERT_ALLOCATION_HAPPENED([] {
+ return malloc(kAllocAmount);
+ });
+}
+
+TEST(AllocReplacement, calloc_check)
+{
+ ASSERT_ALLOCATION_HAPPENED([] {
+ return calloc(1, kAllocAmount);
+ });
+}
+
+TEST(AllocReplacement, realloc_check)
+{
+ ASSERT_ALLOCATION_HAPPENED([] {
+ return realloc(nullptr, kAllocAmount);
+ });
+}
+
+#if defined(HAVE_POSIX_MEMALIGN)
+TEST(AllocReplacement, posix_memalign_check)
+{
+ ASSERT_ALLOCATION_HAPPENED([] {
+ void* p = nullptr;
+ int result = posix_memalign(&p, sizeof(void*), kAllocAmount);
+ if (result != 0) {
+ return static_cast<void*>(nullptr);
+ }
+ return p;
+ });
+}
+#endif
+
+#if defined(XP_WIN)
+#include <windows.h>
+
+#undef ASSERT_ALLOCATION_HAPPENED
+#define ASSERT_ALLOCATION_HAPPENED(lambda) \
+ ALLOCATION_ASSERT(ValidateHookedAllocation(lambda, [](void* p) { \
+ HeapFree(GetProcessHeap(), 0, p); \
+ }));
+
+TEST(AllocReplacement, HeapAlloc_check)
+{
+ ASSERT_ALLOCATION_HAPPENED([] {
+ HANDLE h = GetProcessHeap();
+ return HeapAlloc(h, 0, kAllocAmount);
+ });
+}
+
+TEST(AllocReplacement, HeapReAlloc_check)
+{
+ ASSERT_ALLOCATION_HAPPENED([] {
+ HANDLE h = GetProcessHeap();
+ void *p = HeapAlloc(h, 0, kAllocAmount / 2);
+
+ if (!p) {
+ return static_cast<void*>(nullptr);
+ }
+
+ return HeapReAlloc(h, 0, p, kAllocAmount);
+ });
+}
+#endif
diff --git a/xpcom/tests/gtest/TestAtoms.cpp b/xpcom/tests/gtest/TestAtoms.cpp
new file mode 100644
index 000000000..da3cc5ea2
--- /dev/null
+++ b/xpcom/tests/gtest/TestAtoms.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/ArrayUtils.h"
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "UTFStrings.h"
+#include "nsIServiceManager.h"
+#include "nsStaticAtom.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+namespace TestAtoms {
+
+TEST(Atoms, Basic)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsDependentString str16(ValidStrings[i].m16);
+ nsDependentCString str8(ValidStrings[i].m8);
+
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(str16);
+
+ EXPECT_TRUE(atom->Equals(str16));
+
+ nsString tmp16;
+ nsCString tmp8;
+ atom->ToString(tmp16);
+ atom->ToUTF8String(tmp8);
+ EXPECT_TRUE(str16.Equals(tmp16));
+ EXPECT_TRUE(str8.Equals(tmp8));
+
+ EXPECT_TRUE(nsDependentString(atom->GetUTF16String()).Equals(str16));
+
+ EXPECT_TRUE(nsAtomString(atom).Equals(str16));
+ EXPECT_TRUE(nsDependentAtomString(atom).Equals(str16));
+ EXPECT_TRUE(nsAtomCString(atom).Equals(str8));
+ }
+}
+
+TEST(Atoms, 16vs8)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsCOMPtr<nsIAtom> atom16 = NS_Atomize(ValidStrings[i].m16);
+ nsCOMPtr<nsIAtom> atom8 = NS_Atomize(ValidStrings[i].m8);
+ EXPECT_EQ(atom16, atom8);
+ }
+}
+
+TEST(Atoms, BufferSharing)
+{
+ nsString unique;
+ unique.AssignLiteral("this is a unique string !@#$");
+
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(unique);
+
+ EXPECT_EQ(unique.get(), atom->GetUTF16String());
+}
+
+TEST(Atoms, Null)
+{
+ nsAutoString str(NS_LITERAL_STRING("string with a \0 char"));
+ nsDependentString strCut(str.get());
+
+ EXPECT_FALSE(str.Equals(strCut));
+
+ nsCOMPtr<nsIAtom> atomCut = NS_Atomize(strCut);
+ nsCOMPtr<nsIAtom> atom = NS_Atomize(str);
+
+ EXPECT_EQ(atom->GetLength(), str.Length());
+ EXPECT_TRUE(atom->Equals(str));
+ EXPECT_NE(atom, atomCut);
+ EXPECT_TRUE(atomCut->Equals(strCut));
+}
+
+TEST(Atoms, Invalid)
+{
+ for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) {
+ nsrefcnt count = NS_GetNumberOfAtoms();
+
+ {
+ nsCOMPtr<nsIAtom> atom16 = NS_Atomize(Invalid16Strings[i].m16);
+ EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid16Strings[i].m16)));
+ }
+
+ EXPECT_EQ(count, NS_GetNumberOfAtoms());
+ }
+
+ for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) {
+ nsrefcnt count = NS_GetNumberOfAtoms();
+
+ {
+ nsCOMPtr<nsIAtom> atom8 = NS_Atomize(Invalid8Strings[i].m8);
+ nsCOMPtr<nsIAtom> atom16 = NS_Atomize(Invalid8Strings[i].m16);
+ EXPECT_EQ(atom16, atom8);
+ EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid8Strings[i].m16)));
+ }
+
+ EXPECT_EQ(count, NS_GetNumberOfAtoms());
+ }
+
+// Don't run this test in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ nsCOMPtr<nsIAtom> emptyAtom = NS_Atomize("");
+
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsrefcnt count = NS_GetNumberOfAtoms();
+
+ nsCOMPtr<nsIAtom> atom8 = NS_Atomize(Malformed8Strings[i]);
+ EXPECT_EQ(atom8, emptyAtom);
+ EXPECT_EQ(count, NS_GetNumberOfAtoms());
+ }
+#endif
+}
+
+#define FIRST_ATOM_STR "first static atom. Hello!"
+#define SECOND_ATOM_STR "second static atom. @World!"
+#define THIRD_ATOM_STR "third static atom?!"
+
+bool
+isStaticAtom(nsIAtom* atom)
+{
+ // Don't use logic && in order to ensure that all addrefs/releases are always
+ // run, even if one of the tests fail. This allows us to run this code on a
+ // non-static atom without affecting its refcount.
+ bool rv = (atom->AddRef() == 2);
+ rv &= (atom->AddRef() == 2);
+ rv &= (atom->AddRef() == 2);
+
+ rv &= (atom->Release() == 1);
+ rv &= (atom->Release() == 1);
+ rv &= (atom->Release() == 1);
+ return rv;
+}
+
+TEST(Atoms, Table)
+{
+ nsrefcnt count = NS_GetNumberOfAtoms();
+
+ nsCOMPtr<nsIAtom> thirdDynamic = NS_Atomize(THIRD_ATOM_STR);
+
+ EXPECT_FALSE(isStaticAtom(thirdDynamic));
+
+ EXPECT_TRUE(thirdDynamic);
+ EXPECT_EQ(NS_GetNumberOfAtoms(), count + 1);
+}
+
+}
diff --git a/xpcom/tests/gtest/TestAutoPtr.cpp b/xpcom/tests/gtest/TestAutoPtr.cpp
new file mode 100644
index 000000000..362ba55c5
--- /dev/null
+++ b/xpcom/tests/gtest/TestAutoPtr.cpp
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* 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 "nsAutoPtr.h"
+#include "gtest/gtest.h"
+
+class TestObjectBaseA {
+ public:
+ // Virtual dtor for deleting through base class pointer
+ virtual ~TestObjectBaseA() { }
+ void MemberFunction(int, int*, int&)
+ {
+ }
+ virtual void VirtualMemberFunction(int, int*, int&) { };
+ virtual void VirtualConstMemberFunction(int, int*, int&) const { };
+ int fooA;
+};
+
+class TestObjectBaseB {
+ public:
+ // Virtual dtor for deleting through base class pointer
+ virtual ~TestObjectBaseB() { }
+ int fooB;
+};
+
+class TestObject : public TestObjectBaseA, public TestObjectBaseB {
+ public:
+ TestObject()
+ {
+ }
+
+ // Virtual dtor for deleting through base class pointer
+ virtual ~TestObject()
+ {
+ destructed++;
+ }
+
+ virtual void VirtualMemberFunction(int, int*, int&) override
+ {
+ }
+ virtual void VirtualConstMemberFunction(int, int*, int&) const override
+ {
+ }
+
+ static int destructed;
+ static const void* last_ptr;
+};
+
+int TestObject::destructed = 0;
+const void* TestObject::last_ptr = nullptr;
+
+static void CreateTestObject(TestObject **aResult)
+{
+ *aResult = new TestObject();
+}
+
+static void DoSomethingWithTestObject(TestObject *aIn)
+{
+ TestObject::last_ptr = static_cast<void*>(aIn);
+}
+
+static void DoSomethingWithConstTestObject(const TestObject *aIn)
+{
+ TestObject::last_ptr = static_cast<const void*>(aIn);
+}
+
+static void DoSomethingWithTestObjectBaseB(TestObjectBaseB *aIn)
+{
+ TestObject::last_ptr = static_cast<void*>(aIn);
+}
+
+static void DoSomethingWithConstTestObjectBaseB(const TestObjectBaseB *aIn)
+{
+ TestObject::last_ptr = static_cast<const void*>(aIn);
+}
+
+TEST(AutoPtr, Assignment)
+{
+ TestObject::destructed = 0;
+
+ {
+ nsAutoPtr<TestObject> pobj( new TestObject() );
+ }
+
+ ASSERT_EQ(1, TestObject::destructed);
+
+ {
+ nsAutoPtr<TestObject> pobj( new TestObject() );
+ pobj = new TestObject();
+ ASSERT_EQ(2, TestObject::destructed);
+ }
+
+ ASSERT_EQ(3, TestObject::destructed);
+}
+
+TEST(AutoPtr, getter_Transfers)
+{
+ TestObject::destructed = 0;
+
+ {
+ nsAutoPtr<TestObject> ptr;
+ CreateTestObject(getter_Transfers(ptr));
+ }
+
+ ASSERT_EQ(1, TestObject::destructed);
+}
+
+TEST(AutoPtr, Casting)
+{
+ // This comparison is always false, as it should be. The extra parens
+ // suppress a -Wunreachable-code warning about printf being unreachable.
+ ASSERT_NE(((void*)(TestObject*)0x1000),
+ ((void*)(TestObjectBaseB*)(TestObject*)0x1000));
+
+ {
+ nsAutoPtr<TestObject> p1(new TestObject());
+ TestObjectBaseB *p2 = p1;
+ ASSERT_NE(static_cast<void*>(p1),
+ static_cast<void*>(p2));
+ ASSERT_EQ(p1, p2);
+ ASSERT_FALSE(p1 != p2);
+ ASSERT_EQ(p2, p1);
+ ASSERT_FALSE(p2 != p1);
+ }
+
+ {
+ TestObject *p1 = new TestObject();
+ nsAutoPtr<TestObjectBaseB> p2(p1);
+ ASSERT_EQ(p1, p2);
+ ASSERT_FALSE(p1 != p2);
+ ASSERT_EQ(p2, p1);
+ ASSERT_FALSE(p2 != p1);
+ }
+}
+
+TEST(AutoPtr, Forget)
+{
+ TestObject::destructed = 0;
+
+ {
+ nsAutoPtr<TestObject> pobj( new TestObject() );
+ nsAutoPtr<TestObject> pobj2( pobj.forget() );
+ ASSERT_EQ(0, TestObject::destructed);
+ }
+
+ ASSERT_EQ(1, TestObject::destructed);
+}
+
+TEST(AutoPtr, Construction)
+{
+ TestObject::destructed = 0;
+
+ {
+ nsAutoPtr<TestObject> pobj(new TestObject());
+ }
+
+ ASSERT_EQ(1, TestObject::destructed);
+}
+
+TEST(AutoPtr, ImplicitConversion)
+{
+ // This test is basically successful if it builds. We add a few assertions
+ // to make gtest happy.
+ TestObject::destructed = 0;
+
+ {
+ nsAutoPtr<TestObject> pobj(new TestObject());
+ DoSomethingWithTestObject(pobj);
+ DoSomethingWithConstTestObject(pobj);
+ }
+
+ ASSERT_EQ(1, TestObject::destructed);
+
+ {
+ nsAutoPtr<TestObject> pobj(new TestObject());
+ DoSomethingWithTestObjectBaseB(pobj);
+ DoSomethingWithConstTestObjectBaseB(pobj);
+ }
+
+ ASSERT_EQ(2, TestObject::destructed);
+
+ {
+ const nsAutoPtr<TestObject> pobj(new TestObject());
+ DoSomethingWithTestObject(pobj);
+ DoSomethingWithConstTestObject(pobj);
+ }
+
+ ASSERT_EQ(3, TestObject::destructed);
+
+ {
+ const nsAutoPtr<TestObject> pobj(new TestObject());
+ DoSomethingWithTestObjectBaseB(pobj);
+ DoSomethingWithConstTestObjectBaseB(pobj);
+ }
+
+ ASSERT_EQ(4, TestObject::destructed);
+}
+
+TEST(AutoPtr, ArrowOperator)
+{
+ // This test is basically successful if it builds. We add a few assertions
+ // to make gtest happy.
+ TestObject::destructed = 0;
+
+ {
+ int test = 1;
+ void (TestObjectBaseA::*fPtr)( int, int*, int& ) = &TestObjectBaseA::MemberFunction;
+ void (TestObjectBaseA::*fVPtr)( int, int*, int& ) = &TestObjectBaseA::VirtualMemberFunction;
+ void (TestObjectBaseA::*fVCPtr)( int, int*, int& ) const = &TestObjectBaseA::VirtualConstMemberFunction;
+ nsAutoPtr<TestObjectBaseA> pobj(new TestObject());
+ (pobj->*fPtr)(test, &test, test);
+ (pobj->*fVPtr)(test, &test, test);
+ (pobj->*fVCPtr)(test, &test, test);
+ }
+
+ ASSERT_EQ(1, TestObject::destructed);
+}
diff --git a/xpcom/tests/gtest/TestAutoRef.cpp b/xpcom/tests/gtest/TestAutoRef.cpp
new file mode 100644
index 000000000..49042e8fb
--- /dev/null
+++ b/xpcom/tests/gtest/TestAutoRef.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* 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 "nsAutoRef.h"
+#include "gtest/gtest.h"
+
+struct TestObjectA {
+public:
+ TestObjectA() : mRefCnt(0) {
+ }
+
+ ~TestObjectA() {
+ EXPECT_EQ(mRefCnt, 0);
+ }
+
+public:
+ int mRefCnt;
+};
+
+template <>
+class nsAutoRefTraits<TestObjectA> : public nsPointerRefTraits<TestObjectA>
+{
+public:
+ static int mTotalRefsCnt;
+
+ static void Release(TestObjectA *ptr) {
+ ptr->mRefCnt--;
+ if (ptr->mRefCnt == 0) {
+ delete ptr;
+ }
+ }
+
+ static void AddRef(TestObjectA *ptr) {
+ ptr->mRefCnt++;
+ }
+};
+
+int nsAutoRefTraits<TestObjectA>::mTotalRefsCnt = 0;
+
+TEST(AutoRef, Assignment)
+{
+ {
+ nsCountedRef<TestObjectA> a(new TestObjectA());
+ ASSERT_EQ(a->mRefCnt, 1);
+
+ nsCountedRef<TestObjectA> b;
+ ASSERT_EQ(b.get(), nullptr);
+
+ a.swap(b);
+ ASSERT_EQ(b->mRefCnt, 1);
+ ASSERT_EQ(a.get(), nullptr);
+ }
+}
diff --git a/xpcom/tests/gtest/TestBase64.cpp b/xpcom/tests/gtest/TestBase64.cpp
new file mode 100644
index 000000000..d8105619b
--- /dev/null
+++ b/xpcom/tests/gtest/TestBase64.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "mozilla/Attributes.h"
+#include "nsIScriptableBase64Encoder.h"
+#include "nsIInputStream.h"
+#include "nsString.h"
+
+#include "gtest/gtest.h"
+
+struct Chunk {
+ Chunk(uint32_t l, const char* c)
+ : mLength(l), mData(c)
+ {}
+
+ uint32_t mLength;
+ const char* mData;
+};
+
+struct Test {
+ Test(Chunk* c, const char* r)
+ : mChunks(c), mResult(r)
+ {}
+
+ Chunk* mChunks;
+ const char* mResult;
+};
+
+static Chunk kTest1Chunks[] =
+{
+ Chunk(9, "Hello sir"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest2Chunks[] =
+{
+ Chunk(3, "Hel"),
+ Chunk(3, "lo "),
+ Chunk(3, "sir"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest3Chunks[] =
+{
+ Chunk(1, "I"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest4Chunks[] =
+{
+ Chunk(2, "Hi"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest5Chunks[] =
+{
+ Chunk(1, "B"),
+ Chunk(2, "ob"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest6Chunks[] =
+{
+ Chunk(2, "Bo"),
+ Chunk(1, "b"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest7Chunks[] =
+{
+ Chunk(1, "F"), // Carry over 1
+ Chunk(4, "iref"), // Carry over 2
+ Chunk(2, "ox"), // 1
+ Chunk(4, " is "), // 2
+ Chunk(2, "aw"), // 1
+ Chunk(4, "esom"), // 2
+ Chunk(2, "e!"),
+ Chunk(0, nullptr)
+};
+
+static Chunk kTest8Chunks[] =
+{
+ Chunk(5, "ALL T"),
+ Chunk(1, "H"),
+ Chunk(4, "ESE "),
+ Chunk(2, "WO"),
+ Chunk(21, "RLDS ARE YOURS EXCEPT"),
+ Chunk(9, " EUROPA. "),
+ Chunk(25, "ATTEMPT NO LANDING THERE."),
+ Chunk(0, nullptr)
+};
+
+static Test kTests[] =
+ {
+ // Test 1, test a simple round string in one chunk
+ Test(
+ kTest1Chunks,
+ "SGVsbG8gc2ly"
+ ),
+ // Test 2, test a simple round string split into round chunks
+ Test(
+ kTest2Chunks,
+ "SGVsbG8gc2ly"
+ ),
+ // Test 3, test a single chunk that's 2 short
+ Test(
+ kTest3Chunks,
+ "SQ=="
+ ),
+ // Test 4, test a single chunk that's 1 short
+ Test(
+ kTest4Chunks,
+ "SGk="
+ ),
+ // Test 5, test a single chunk that's 2 short, followed by a chunk of 2
+ Test(
+ kTest5Chunks,
+ "Qm9i"
+ ),
+ // Test 6, test a single chunk that's 1 short, followed by a chunk of 1
+ Test(
+ kTest6Chunks,
+ "Qm9i"
+ ),
+ // Test 7, test alternating carryovers
+ Test(
+ kTest7Chunks,
+ "RmlyZWZveCBpcyBhd2Vzb21lIQ=="
+ ),
+ // Test 8, test a longish string
+ Test(
+ kTest8Chunks,
+ "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOTyBMQU5ESU5HIFRIRVJFLg=="
+ ),
+ // Terminator
+ Test(
+ nullptr,
+ nullptr
+ )
+ };
+
+class FakeInputStream final : public nsIInputStream
+{
+ ~FakeInputStream() {}
+
+public:
+
+ FakeInputStream()
+ : mTestNumber(0),
+ mTest(&kTests[0]),
+ mChunk(&mTest->mChunks[0]),
+ mClosed(false)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ void Reset();
+ bool NextTest();
+ void CheckTest(nsACString& aResult);
+ void CheckTest(nsAString& aResult);
+private:
+ uint32_t mTestNumber;
+ const Test* mTest;
+ const Chunk* mChunk;
+ bool mClosed;
+};
+
+NS_IMPL_ISUPPORTS(FakeInputStream, nsIInputStream)
+
+NS_IMETHODIMP
+FakeInputStream::Close()
+{
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FakeInputStream::Available(uint64_t* aAvailable)
+{
+ *aAvailable = 0;
+
+ if (mClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ const Chunk* chunk = mChunk;
+ while (chunk->mLength) {
+ *aAvailable += chunk->mLength;
+ chunk++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FakeInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aOut)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FakeInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* aRead)
+{
+ *aRead = 0;
+
+ if (mClosed)
+ return NS_BASE_STREAM_CLOSED;
+
+ while (mChunk->mLength) {
+ uint32_t written = 0;
+
+ nsresult rv = (*aWriter)(this, aClosure, mChunk->mData,
+ *aRead, mChunk->mLength, &written);
+
+ *aRead += written;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mChunk++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FakeInputStream::IsNonBlocking(bool* aIsBlocking)
+{
+ *aIsBlocking = false;
+ return NS_OK;
+}
+
+void
+FakeInputStream::Reset()
+{
+ mClosed = false;
+ mChunk = &mTest->mChunks[0];
+}
+
+bool
+FakeInputStream::NextTest()
+{
+ mTestNumber++;
+ mTest = &kTests[mTestNumber];
+ mChunk = &mTest->mChunks[0];
+ mClosed = false;
+
+ return mTest->mChunks ? true : false;
+}
+
+void
+FakeInputStream::CheckTest(nsACString& aResult)
+{
+ ASSERT_STREQ(aResult.BeginReading(), mTest->mResult);
+}
+
+void
+FakeInputStream::CheckTest(nsAString& aResult)
+{
+ ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult)) <<
+ "Actual: " << aResult.BeginReading() << std::endl <<
+ "Expected: " << mTest->mResult;
+}
+
+TEST(Base64, Test)
+{
+ nsCOMPtr<nsIScriptableBase64Encoder> encoder =
+ do_CreateInstance("@mozilla.org/scriptablebase64encoder;1");
+ ASSERT_TRUE(encoder);
+
+ RefPtr<FakeInputStream> stream = new FakeInputStream();
+ do {
+ nsString wideString;
+ nsCString string;
+
+ nsresult rv;
+ rv = encoder->EncodeToString(stream, 0, wideString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ stream->Reset();
+
+ rv = encoder->EncodeToCString(stream, 0, string);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ stream->CheckTest(wideString);
+ stream->CheckTest(string);
+ } while (stream->NextTest());
+}
diff --git a/xpcom/tests/gtest/TestCOMArray.cpp b/xpcom/tests/gtest/TestCOMArray.cpp
new file mode 100644
index 000000000..6703bf9d1
--- /dev/null
+++ b/xpcom/tests/gtest/TestCOMArray.cpp
@@ -0,0 +1,286 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* 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 "nsCOMArray.h"
+#include "gtest/gtest.h"
+
+// {9e70a320-be02-11d1-8031-006008159b5a}
+#define NS_IFOO_IID \
+ {0x9e70a320, 0xbe02, 0x11d1, \
+ {0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a}}
+
+class IFoo : public nsISupports {
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID)
+
+ NS_IMETHOD_(MozExternalRefCountType) RefCnt() = 0;
+ NS_IMETHOD_(int32_t) ID() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID)
+
+class Foo final : public IFoo {
+ ~Foo();
+
+public:
+
+ explicit Foo(int32_t aID);
+
+ // nsISupports implementation
+ NS_DECL_ISUPPORTS
+
+ // IFoo implementation
+ NS_IMETHOD_(MozExternalRefCountType) RefCnt() override { return mRefCnt; }
+ NS_IMETHOD_(int32_t) ID() override { return mID; }
+
+ static int32_t gCount;
+
+ int32_t mID;
+};
+
+int32_t Foo::gCount = 0;
+
+Foo::Foo(int32_t aID)
+{
+ mID = aID;
+ ++gCount;
+}
+
+Foo::~Foo()
+{
+ --gCount;
+}
+
+NS_IMPL_ISUPPORTS(Foo, IFoo)
+
+
+// {0e70a320-be02-11d1-8031-006008159b5a}
+#define NS_IBAR_IID \
+ {0x0e70a320, 0xbe02, 0x11d1, \
+ {0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a}}
+
+class IBar : public nsISupports {
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBAR_IID)
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IBar, NS_IBAR_IID)
+
+class Bar final : public IBar {
+public:
+
+ explicit Bar(nsCOMArray<IBar>& aArray);
+
+ // nsISupports implementation
+ NS_DECL_ISUPPORTS
+
+ static int32_t sReleaseCalled;
+
+private:
+ ~Bar();
+
+ nsCOMArray<IBar>& mArray;
+};
+
+int32_t Bar::sReleaseCalled = 0;
+
+typedef nsCOMArray<IBar> Array2;
+
+Bar::Bar(Array2& aArray)
+ : mArray(aArray)
+{
+}
+
+Bar::~Bar()
+{
+ EXPECT_FALSE(mArray.RemoveObject(this));
+}
+
+NS_IMPL_ADDREF(Bar)
+NS_IMPL_QUERY_INTERFACE(Bar, IBar)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+Bar::Release(void)
+{
+ ++Bar::sReleaseCalled;
+ EXPECT_GT(int(mRefCnt), 0);
+ NS_ASSERT_OWNINGTHREAD(_class);
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "Bar");
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
+
+TEST(COMArray, Sizing)
+{
+ nsCOMArray<IFoo> arr;
+
+ for (int32_t i = 0; i < 20; ++i) {
+ nsCOMPtr<IFoo> foo = new Foo(i);
+ arr.AppendObject(foo);
+ }
+
+ ASSERT_EQ(arr.Count(), int32_t(20));
+ ASSERT_EQ(Foo::gCount, int32_t(20));
+
+ arr.TruncateLength(10);
+
+ ASSERT_EQ(arr.Count(), int32_t(10));
+ ASSERT_EQ(Foo::gCount, int32_t(10));
+
+ arr.SetCount(30);
+
+ ASSERT_EQ(arr.Count(), int32_t(30));
+ ASSERT_EQ(Foo::gCount, int32_t(10));
+
+ for (int32_t i = 0; i < 10; ++i) {
+ ASSERT_NE(arr[i], nullptr);
+ }
+
+ for (int32_t i = 10; i < 30; ++i) {
+ ASSERT_EQ(arr[i], nullptr);
+ }
+}
+
+TEST(COMArray, ObjectFunctions)
+{
+ int32_t base;
+ {
+ nsCOMArray<IBar> arr2;
+
+ IBar *thirdObject = nullptr,
+ *fourthObject = nullptr,
+ *fifthObject = nullptr,
+ *ninthObject = nullptr;
+ for (int32_t i = 0; i < 20; ++i) {
+ nsCOMPtr<IBar> bar = new Bar(arr2);
+ switch (i) {
+ case 2:
+ thirdObject = bar; break;
+ case 3:
+ fourthObject = bar; break;
+ case 4:
+ fifthObject = bar; break;
+ case 8:
+ ninthObject = bar; break;
+ }
+ arr2.AppendObject(bar);
+ }
+
+ base = Bar::sReleaseCalled;
+
+ arr2.SetCount(10);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 10);
+ ASSERT_EQ(arr2.Count(), int32_t(10));
+
+ arr2.RemoveObjectAt(9);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 11);
+ ASSERT_EQ(arr2.Count(), int32_t(9));
+
+ arr2.RemoveObject(ninthObject);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 12);
+ ASSERT_EQ(arr2.Count(), int32_t(8));
+
+ arr2.RemoveObjectsAt(2, 3);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 15);
+ ASSERT_EQ(arr2.Count(), int32_t(5));
+ for (int32_t j = 0; j < arr2.Count(); ++j) {
+ ASSERT_NE(arr2.ObjectAt(j), thirdObject);
+ ASSERT_NE(arr2.ObjectAt(j), fourthObject);
+ ASSERT_NE(arr2.ObjectAt(j), fifthObject);
+ }
+
+ arr2.RemoveObjectsAt(4, 1);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 16);
+ ASSERT_EQ(arr2.Count(), int32_t(4));
+
+ arr2.Clear();
+ ASSERT_EQ(Bar::sReleaseCalled, base + 20);
+ }
+}
+
+TEST(COMArray, ElementFunctions)
+{
+ int32_t base;
+ {
+ nsCOMArray<IBar> arr2;
+
+ IBar *thirdElement = nullptr,
+ *fourthElement = nullptr,
+ *fifthElement = nullptr,
+ *ninthElement = nullptr;
+ for (int32_t i = 0; i < 20; ++i) {
+ nsCOMPtr<IBar> bar = new Bar(arr2);
+ switch (i) {
+ case 2:
+ thirdElement = bar; break;
+ case 3:
+ fourthElement = bar; break;
+ case 4:
+ fifthElement = bar; break;
+ case 8:
+ ninthElement = bar; break;
+ }
+ arr2.AppendElement(bar);
+ }
+
+ base = Bar::sReleaseCalled;
+
+ arr2.TruncateLength(10);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 10);
+ ASSERT_EQ(arr2.Length(), uint32_t(10));
+
+ arr2.RemoveElementAt(9);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 11);
+ ASSERT_EQ(arr2.Length(), uint32_t(9));
+
+ arr2.RemoveElement(ninthElement);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 12);
+ ASSERT_EQ(arr2.Length(), uint32_t(8));
+
+ arr2.RemoveElementsAt(2, 3);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 15);
+ ASSERT_EQ(arr2.Length(), uint32_t(5));
+ for (uint32_t j = 0; j < arr2.Length(); ++j) {
+ ASSERT_NE(arr2.ElementAt(j), thirdElement);
+ ASSERT_NE(arr2.ElementAt(j), fourthElement);
+ ASSERT_NE(arr2.ElementAt(j), fifthElement);
+ }
+
+ arr2.RemoveElementsAt(4, 1);
+ ASSERT_EQ(Bar::sReleaseCalled, base + 16);
+ ASSERT_EQ(arr2.Length(), uint32_t(4));
+
+ arr2.Clear();
+ ASSERT_EQ(Bar::sReleaseCalled, base + 20);
+ }
+}
+
+TEST(COMArray, Destructor)
+{
+ int32_t base;
+ Bar::sReleaseCalled = 0;
+
+ {
+ nsCOMArray<IBar> arr2;
+
+ for (int32_t i = 0; i < 20; ++i) {
+ nsCOMPtr<IBar> bar = new Bar(arr2);
+ arr2.AppendObject(bar);
+ }
+
+ base = Bar::sReleaseCalled;
+
+ // Let arr2 be destroyed
+ }
+ ASSERT_EQ(Bar::sReleaseCalled, base + 20);
+}
diff --git a/xpcom/tests/gtest/TestCOMPtr.cpp b/xpcom/tests/gtest/TestCOMPtr.cpp
new file mode 100644
index 000000000..e17331c65
--- /dev/null
+++ b/xpcom/tests/gtest/TestCOMPtr.cpp
@@ -0,0 +1,466 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "gtest/gtest.h"
+
+#include "mozilla/Unused.h"
+
+#define NS_IFOO_IID \
+{ 0x6f7652e0, 0xee43, 0x11d1, \
+ { 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 } }
+
+namespace TestCOMPtr
+{
+
+class IFoo : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID)
+
+public:
+ IFoo();
+ // virtual dtor because IBar uses our Release()
+ virtual ~IFoo();
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+ NS_IMETHOD QueryInterface( const nsIID&, void** );
+
+ unsigned int refcount_;
+
+ static int total_constructions_;
+ static int total_destructions_;
+ static int total_queries_;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID)
+
+int IFoo::total_constructions_;
+int IFoo::total_destructions_;
+int IFoo::total_queries_;
+
+IFoo::IFoo()
+ : refcount_(0)
+{
+ ++total_constructions_;
+}
+
+IFoo::~IFoo()
+{
+ ++total_destructions_;
+}
+
+MozExternalRefCountType
+IFoo::AddRef()
+{
+ ++refcount_;
+ return refcount_;
+}
+
+MozExternalRefCountType
+IFoo::Release()
+{
+ int newcount = --refcount_;
+
+ if ( newcount == 0 )
+ {
+ delete this;
+ }
+
+ return newcount;
+}
+
+nsresult
+IFoo::QueryInterface( const nsIID& aIID, void** aResult )
+{
+ total_queries_++;
+
+ nsISupports* rawPtr = 0;
+ nsresult status = NS_OK;
+
+ if ( aIID.Equals(NS_GET_IID(IFoo)) )
+ rawPtr = this;
+ else
+ {
+ nsID iid_of_ISupports = NS_ISUPPORTS_IID;
+ if ( aIID.Equals(iid_of_ISupports) )
+ rawPtr = static_cast<nsISupports*>(this);
+ else
+ status = NS_ERROR_NO_INTERFACE;
+ }
+
+ NS_IF_ADDREF(rawPtr);
+ *aResult = rawPtr;
+
+ return status;
+}
+
+nsresult
+CreateIFoo( void** result )
+// a typical factory function (that calls AddRef)
+{
+ IFoo* foop = new IFoo;
+
+ foop->AddRef();
+ *result = foop;
+
+ return NS_OK;
+}
+
+void
+set_a_IFoo( nsCOMPtr<IFoo>* result )
+{
+ nsCOMPtr<IFoo> foop( do_QueryInterface(new IFoo) );
+ *result = foop;
+}
+
+nsCOMPtr<IFoo>
+return_a_IFoo()
+{
+ nsCOMPtr<IFoo> foop( do_QueryInterface(new IFoo) );
+ return foop;
+}
+
+#define NS_IBAR_IID \
+{ 0x6f7652e1, 0xee43, 0x11d1, \
+ { 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 } }
+
+class IBar : public IFoo
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBAR_IID)
+
+public:
+ IBar();
+ virtual ~IBar();
+
+ NS_IMETHOD QueryInterface( const nsIID&, void** );
+
+ static int total_destructions_;
+ static int total_queries_;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IBar, NS_IBAR_IID)
+
+int IBar::total_destructions_;
+int IBar::total_queries_;
+
+IBar::IBar()
+{
+}
+
+IBar::~IBar()
+{
+ total_destructions_++;
+}
+
+nsresult
+IBar::QueryInterface( const nsID& aIID, void** aResult )
+{
+ total_queries_++;
+
+ nsISupports* rawPtr = 0;
+ nsresult status = NS_OK;
+
+ if ( aIID.Equals(NS_GET_IID(IBar)) )
+ rawPtr = this;
+ else if ( aIID.Equals(NS_GET_IID(IFoo)) )
+ rawPtr = static_cast<IFoo*>(this);
+ else
+ {
+ nsID iid_of_ISupports = NS_ISUPPORTS_IID;
+ if ( aIID.Equals(iid_of_ISupports) )
+ rawPtr = static_cast<nsISupports*>(this);
+ else
+ status = NS_ERROR_NO_INTERFACE;
+ }
+
+ NS_IF_ADDREF(rawPtr);
+ *aResult = rawPtr;
+
+ return status;
+}
+
+
+
+nsresult
+CreateIBar( void** result )
+ // a typical factory function (that calls AddRef)
+{
+ IBar* barp = new IBar;
+
+ barp->AddRef();
+ *result = barp;
+
+ return NS_OK;
+}
+
+void
+AnIFooPtrPtrContext( IFoo** )
+{
+}
+
+void
+AVoidPtrPtrContext( void** )
+{
+}
+
+void
+AnISupportsPtrPtrContext( nsISupports** )
+{
+}
+
+} // namespace TestCOMPtr
+
+using namespace TestCOMPtr;
+
+TEST(COMPtr, Bloat_Raw_Unsafe)
+{
+ // ER: I'm not sure what this is testing...
+ IBar* barP = 0;
+ nsresult rv = CreateIBar(reinterpret_cast<void**>(&barP));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(barP);
+
+ IFoo* fooP = 0;
+ rv = barP->QueryInterface(NS_GET_IID(IFoo), reinterpret_cast<void**>(&fooP));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(fooP);
+
+ NS_RELEASE(fooP);
+ NS_RELEASE(barP);
+}
+
+TEST(COMPtr, Bloat_Smart)
+{
+ // ER: I'm not sure what this is testing...
+ nsCOMPtr<IBar> barP;
+ nsresult rv = CreateIBar( getter_AddRefs(barP) );
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(barP);
+
+ nsCOMPtr<IFoo> fooP( do_QueryInterface(barP, &rv) );
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(fooP);
+}
+
+TEST(COMPtr, AddRefAndRelease)
+{
+ IFoo::total_constructions_ = 0;
+ IFoo::total_destructions_ = 0;
+ IBar::total_destructions_ = 0;
+
+ {
+ nsCOMPtr<IFoo> foop( do_QueryInterface(new IFoo) );
+ ASSERT_EQ(foop->refcount_, (unsigned int)1);
+ ASSERT_EQ(IFoo::total_constructions_, 1);
+ ASSERT_EQ(IFoo::total_destructions_, 0);
+
+ foop = do_QueryInterface(new IFoo);
+ ASSERT_EQ(foop->refcount_, (unsigned int)1);
+ ASSERT_EQ(IFoo::total_constructions_, 2);
+ ASSERT_EQ(IFoo::total_destructions_, 1);
+
+ // [Shouldn't compile] Is it a compile time error to try to |AddRef| by hand?
+ //foop->AddRef();
+
+ // [Shouldn't compile] Is it a compile time error to try to |Release| be hand?
+ //foop->Release();
+
+ // [Shouldn't compile] Is it a compile time error to try to |delete| an |nsCOMPtr|?
+ //delete foop;
+
+ static_cast<IFoo*>(foop)->AddRef();
+ ASSERT_EQ(foop->refcount_, (unsigned int)2);
+ ASSERT_EQ(IFoo::total_constructions_, 2);
+ ASSERT_EQ(IFoo::total_destructions_, 1);
+
+ static_cast<IFoo*>(foop)->Release();
+ ASSERT_EQ(foop->refcount_, (unsigned int)1);
+ ASSERT_EQ(IFoo::total_constructions_, 2);
+ ASSERT_EQ(IFoo::total_destructions_, 1);
+ }
+
+ ASSERT_EQ(IFoo::total_constructions_, 2);
+ ASSERT_EQ(IFoo::total_destructions_, 2);
+
+ {
+ nsCOMPtr<IFoo> foop( do_QueryInterface(new IBar) );
+ mozilla::Unused << foop;
+ }
+
+ ASSERT_EQ(IBar::total_destructions_, 1);
+}
+
+void Comparison()
+{
+ IFoo::total_constructions_ = 0;
+ IFoo::total_destructions_ = 0;
+
+ {
+ nsCOMPtr<IFoo> foo1p( do_QueryInterface(new IFoo) );
+ nsCOMPtr<IFoo> foo2p( do_QueryInterface(new IFoo) );
+
+ ASSERT_EQ(IFoo::total_constructions_, 2);
+
+ // Test != operator
+ ASSERT_NE(foo1p, foo2p);
+ ASSERT_NE(foo1p, foo2p.get());
+
+ // Test == operator
+ foo1p = foo2p;
+
+ ASSERT_EQ(IFoo::total_destructions_, 1);
+
+ ASSERT_EQ(foo1p, foo2p);
+ ASSERT_EQ(foo2p, foo2p.get());
+ ASSERT_EQ(foo2p.get(), foo2p);
+
+ // Test () operator
+ ASSERT_TRUE(foo1p);
+
+ ASSERT_EQ(foo1p->refcount_, (unsigned int)2);
+ ASSERT_EQ(foo2p->refcount_, (unsigned int)2);
+ }
+
+ ASSERT_EQ(IFoo::total_destructions_, 2);
+}
+
+void DontAddRef()
+{
+ {
+ IFoo* raw_foo1p = new IFoo;
+ raw_foo1p->AddRef();
+
+ IFoo* raw_foo2p = new IFoo;
+ raw_foo2p->AddRef();
+
+ nsCOMPtr<IFoo> foo1p( dont_AddRef(raw_foo1p) );
+ ASSERT_EQ(raw_foo1p, foo1p);
+ ASSERT_EQ(foo1p->refcount_, (unsigned int)1);
+
+ nsCOMPtr<IFoo> foo2p;
+ foo2p = dont_AddRef(raw_foo2p);
+ ASSERT_EQ(raw_foo2p, foo2p);
+ ASSERT_EQ(foo2p->refcount_, (unsigned int)1);
+ }
+}
+
+TEST(COMPtr, AssignmentHelpers)
+{
+ IFoo::total_constructions_ = 0;
+ IFoo::total_destructions_ = 0;
+
+ {
+ nsCOMPtr<IFoo> foop;
+ ASSERT_FALSE(foop);
+ CreateIFoo( nsGetterAddRefs<IFoo>(foop) );
+ ASSERT_TRUE(foop);
+ }
+
+ ASSERT_EQ(IFoo::total_constructions_, 1);
+ ASSERT_EQ(IFoo::total_destructions_, 1);
+
+ {
+ nsCOMPtr<IFoo> foop;
+ ASSERT_FALSE(foop);
+ CreateIFoo( getter_AddRefs(foop) );
+ ASSERT_TRUE(foop);
+ }
+
+ ASSERT_EQ(IFoo::total_constructions_, 2);
+ ASSERT_EQ(IFoo::total_destructions_, 2);
+
+ {
+ nsCOMPtr<IFoo> foop;
+ ASSERT_FALSE(foop);
+ set_a_IFoo(address_of(foop));
+ ASSERT_TRUE(foop);
+
+ ASSERT_EQ(IFoo::total_constructions_, 3);
+ ASSERT_EQ(IFoo::total_destructions_, 2);
+
+ foop = return_a_IFoo();
+ ASSERT_TRUE(foop);
+
+ ASSERT_EQ(IFoo::total_constructions_, 4);
+ ASSERT_EQ(IFoo::total_destructions_, 3);
+ }
+
+ ASSERT_EQ(IFoo::total_constructions_, 4);
+ ASSERT_EQ(IFoo::total_destructions_, 4);
+
+ {
+ nsCOMPtr<IFoo> fooP( do_QueryInterface(new IFoo) );
+ ASSERT_TRUE(fooP);
+
+ ASSERT_EQ(IFoo::total_constructions_, 5);
+ ASSERT_EQ(IFoo::total_destructions_, 4);
+
+ nsCOMPtr<IFoo> fooP2( fooP.forget() );
+ ASSERT_TRUE(fooP2);
+
+ ASSERT_EQ(IFoo::total_constructions_, 5);
+ ASSERT_EQ(IFoo::total_destructions_, 4);
+ }
+
+ ASSERT_EQ(IFoo::total_constructions_, 5);
+ ASSERT_EQ(IFoo::total_destructions_, 5);
+}
+
+TEST(COMPtr, QueryInterface)
+{
+ IFoo::total_queries_ = 0;
+ IBar::total_queries_ = 0;
+
+ {
+ nsCOMPtr<IFoo> fooP;
+ ASSERT_FALSE(fooP);
+ fooP = do_QueryInterface(new IFoo);
+ ASSERT_TRUE(fooP);
+ ASSERT_EQ(IFoo::total_queries_, 1);
+
+ nsCOMPtr<IFoo> foo2P;
+
+ // Test that |QueryInterface| _not_ called when assigning a smart-pointer
+ // of the same type.);
+ foo2P = fooP;
+ ASSERT_EQ(IFoo::total_queries_, 1);
+ }
+
+ {
+ nsCOMPtr<IBar> barP( do_QueryInterface(new IBar) );
+ ASSERT_EQ(IBar::total_queries_, 1);
+
+ // Test that |QueryInterface| is called when assigning a smart-pointer of
+ // a different type.
+ nsCOMPtr<IFoo> fooP( do_QueryInterface(barP) );
+ ASSERT_EQ(IBar::total_queries_, 2);
+ ASSERT_EQ(IFoo::total_queries_, 1);
+ ASSERT_TRUE(fooP);
+ }
+}
+
+TEST(COMPtr, GetterConversions)
+{
+ // This is just a compilation test. We add a few asserts to keep gtest happy.
+ {
+ nsCOMPtr<IFoo> fooP;
+ ASSERT_FALSE(fooP);
+
+ AnIFooPtrPtrContext( getter_AddRefs(fooP) );
+ AVoidPtrPtrContext( getter_AddRefs(fooP) );
+ }
+
+
+ {
+ nsCOMPtr<nsISupports> supportsP;
+ ASSERT_FALSE(supportsP);
+
+ AVoidPtrPtrContext( getter_AddRefs(supportsP) );
+ AnISupportsPtrPtrContext( getter_AddRefs(supportsP) );
+ }
+}
diff --git a/xpcom/tests/gtest/TestCOMPtrEq.cpp b/xpcom/tests/gtest/TestCOMPtrEq.cpp
new file mode 100644
index 000000000..b7513e2ae
--- /dev/null
+++ b/xpcom/tests/gtest/TestCOMPtrEq.cpp
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+/**
+ * This attempts to test all the possible variations of |operator==|
+ * used with |nsCOMPtr|s.
+ */
+
+#include "nsCOMPtr.h"
+#include "gtest/gtest.h"
+
+#define NS_ICOMPTREQTESTFOO_IID \
+{0x8eb5bbef, 0xd1a3, 0x4659, \
+ {0x9c, 0xf6, 0xfd, 0xf3, 0xe4, 0xd2, 0x00, 0x0e}}
+
+class nsICOMPtrEqTestFoo : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICOMPTREQTESTFOO_IID)
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsICOMPtrEqTestFoo, NS_ICOMPTREQTESTFOO_IID)
+
+TEST(COMPtrEq, NullEquality)
+{
+ nsCOMPtr<nsICOMPtrEqTestFoo> s;
+ nsICOMPtrEqTestFoo* r = nullptr;
+ const nsCOMPtr<nsICOMPtrEqTestFoo> sc;
+ const nsICOMPtrEqTestFoo* rc = nullptr;
+ nsICOMPtrEqTestFoo* const rk = nullptr;
+ const nsICOMPtrEqTestFoo* const rkc = nullptr;
+ nsICOMPtrEqTestFoo* d = s;
+
+ ASSERT_EQ(s, s);
+ ASSERT_EQ(s, r);
+ ASSERT_EQ(s, sc);
+ ASSERT_EQ(s, rc);
+ ASSERT_EQ(s, rk);
+ ASSERT_EQ(s, rkc);
+ ASSERT_EQ(s, d);
+ ASSERT_EQ(r, s);
+ ASSERT_EQ(r, sc);
+ ASSERT_EQ(r, rc);
+ ASSERT_EQ(r, rk);
+ ASSERT_EQ(r, rkc);
+ ASSERT_EQ(r, d);
+ ASSERT_EQ(sc, s);
+ ASSERT_EQ(sc, r);
+ ASSERT_EQ(sc, sc);
+ ASSERT_EQ(sc, rc);
+ ASSERT_EQ(sc, rk);
+ ASSERT_EQ(sc, rkc);
+ ASSERT_EQ(sc, d);
+ ASSERT_EQ(rc, s);
+ ASSERT_EQ(rc, r);
+ ASSERT_EQ(rc, sc);
+ ASSERT_EQ(rc, rk);
+ ASSERT_EQ(rc, rkc);
+ ASSERT_EQ(rc, d);
+ ASSERT_EQ(rk, s);
+ ASSERT_EQ(rk, r);
+ ASSERT_EQ(rk, sc);
+ ASSERT_EQ(rk, rc);
+ ASSERT_EQ(rk, rkc);
+ ASSERT_EQ(rk, d);
+ ASSERT_EQ(rkc, s);
+ ASSERT_EQ(rkc, r);
+ ASSERT_EQ(rkc, sc);
+ ASSERT_EQ(rkc, rc);
+ ASSERT_EQ(rkc, rk);
+ ASSERT_EQ(rkc, d);
+ ASSERT_EQ(d, s);
+ ASSERT_EQ(d, r);
+ ASSERT_EQ(d, sc);
+ ASSERT_EQ(d, rc);
+ ASSERT_EQ(d, rk);
+ ASSERT_EQ(d, rkc);
+}
diff --git a/xpcom/tests/gtest/TestCRT.cpp b/xpcom/tests/gtest/TestCRT.cpp
new file mode 100644
index 000000000..9fa731404
--- /dev/null
+++ b/xpcom/tests/gtest/TestCRT.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsCRT.h"
+#include "nsString.h"
+#include "plstr.h"
+#include <stdlib.h>
+#include "gtest/gtest.h"
+
+namespace TestCRT {
+
+// The return from strcmp etc is only defined to be postive, zero or
+// negative. The magnitude of a non-zero return is irrelevant.
+int sign(int val) {
+ if (val == 0) {
+ return 0;
+ } else {
+ if (val > 0) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+}
+
+
+// Verify that nsCRT versions of string comparison routines get the
+// same answers as the native non-unicode versions. We only pass in
+// iso-latin-1 strings, so the comparison must be valid.
+static void Check(const char* s1, const char* s2, int n)
+{
+ int clib = PL_strcmp(s1, s2);
+
+ int clib_n = PL_strncmp(s1, s2, n);
+
+ nsAutoString t1,t2;
+ t1.AssignWithConversion(s1);
+ t2.AssignWithConversion(s2);
+ const char16_t* us1 = t1.get();
+ const char16_t* us2 = t2.get();
+
+ int u2 = nsCRT::strcmp(us1, us2);
+
+ int u2_n = nsCRT::strncmp(us1, us2, n);
+
+ EXPECT_EQ(sign(clib), sign(u2));
+ EXPECT_EQ(sign(clib), sign(u2_n));
+ EXPECT_EQ(sign(clib), sign(clib_n));
+}
+
+struct Test {
+ const char* s1;
+ const char* s2;
+ int n;
+};
+
+static Test tests[] = {
+ { "foo", "foo", 3 },
+ { "foo", "fo", 3 },
+
+ { "foo", "bar", 3 },
+ { "foo", "ba", 3 },
+
+ { "foo", "zap", 3 },
+ { "foo", "za", 3 },
+
+ { "bar", "foo", 3 },
+ { "bar", "fo", 3 },
+
+ { "bar", "foo", 3 },
+ { "bar", "fo", 3 },
+};
+#define NUM_TESTS int((sizeof(tests) / sizeof(tests[0])))
+
+TEST(CRT, main)
+{
+ TestCRT::Test* tp = tests;
+ for (int i = 0; i < NUM_TESTS; i++, tp++) {
+ Check(tp->s1, tp->s2, tp->n);
+ }
+}
+
+} // namespace TestCRT
diff --git a/xpcom/tests/gtest/TestCallTemplates.cpp b/xpcom/tests/gtest/TestCallTemplates.cpp
new file mode 100644
index 000000000..b8087e82b
--- /dev/null
+++ b/xpcom/tests/gtest/TestCallTemplates.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim:cindent:ts=8:et:sw=4:
+ *
+ * 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/. */
+
+/*
+ * This test is NOT intended to be run. It's a test to make sure
+ * a group of functions BUILD correctly.
+ */
+
+#include "nsISupportsUtils.h"
+#include "nsIWeakReference.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsWeakReference.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+#define NS_ITESTSERVICE_IID \
+ {0x127b5253, 0x37b1, 0x43c7, \
+ { 0x96, 0x2b, 0xab, 0xf1, 0x2d, 0x22, 0x56, 0xae }}
+
+class NS_NO_VTABLE nsITestService : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITESTSERVICE_IID)
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITestService, NS_ITESTSERVICE_IID)
+
+class nsTestService final : public nsITestService,
+ public nsSupportsWeakReference
+{
+ ~nsTestService() {}
+ public:
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(nsTestService, nsITestService, nsISupportsWeakReference)
+
+#define NS_TEST_SERVICE_CONTRACTID "@mozilla.org/test/testservice;1"
+#define NS_TEST_SERVICE_CID \
+ {0xa00c1406, 0x283a, 0x45c9, \
+ {0xae, 0xd2, 0x1a, 0xb6, 0xdd, 0xba, 0xfe, 0x53}}
+static NS_DEFINE_CID(kTestServiceCID, NS_TEST_SERVICE_CID);
+
+void JustTestingCompilation()
+{
+ /*
+ * NOTE: This does NOT demonstrate how these functions are
+ * intended to be used. They are intended for filling in out
+ * parameters that need to be |AddRef|ed. I'm just too lazy
+ * to write lots of little getter functions for a test program
+ * when I don't need to.
+ */
+
+ NS_NOTREACHED("This test is not intended to run, only to compile!");
+
+ /* Test CallQueryInterface */
+
+ nsISupports *mySupportsPtr = reinterpret_cast<nsISupports*>(0x1000);
+
+ nsITestService *myITestService = nullptr;
+ CallQueryInterface(mySupportsPtr, &myITestService);
+
+ nsTestService *myTestService =
+ reinterpret_cast<nsTestService*>(mySupportsPtr);
+ nsISupportsWeakReference *mySupportsWeakRef;
+ CallQueryInterface(myTestService, &mySupportsWeakRef);
+
+ nsCOMPtr<nsISupports> mySupportsCOMPtr = mySupportsPtr;
+ CallQueryInterface(mySupportsCOMPtr, &myITestService);
+
+ RefPtr<nsTestService> myTestServiceRefPtr = myTestService;
+ CallQueryInterface(myTestServiceRefPtr, &mySupportsWeakRef);
+
+ /* Test CallQueryReferent */
+
+ nsIWeakReference *myWeakRef =
+ static_cast<nsIWeakReference*>(mySupportsPtr);
+ CallQueryReferent(myWeakRef, &myITestService);
+
+ /* Test CallCreateInstance */
+
+ CallCreateInstance(kTestServiceCID, mySupportsPtr, &myITestService);
+ CallCreateInstance(kTestServiceCID, &myITestService);
+ CallCreateInstance(NS_TEST_SERVICE_CONTRACTID, mySupportsPtr,
+ &myITestService);
+ CallCreateInstance(NS_TEST_SERVICE_CONTRACTID, &myITestService);
+
+ /* Test CallGetService */
+ CallGetService(kTestServiceCID, &myITestService);
+ CallGetService(NS_TEST_SERVICE_CONTRACTID, &myITestService);
+
+ /* Test CallGetInterface */
+ nsIInterfaceRequestor *myInterfaceRequestor =
+ static_cast<nsIInterfaceRequestor*>(mySupportsPtr);
+ CallGetInterface(myInterfaceRequestor, &myITestService);
+}
diff --git a/xpcom/tests/gtest/TestCloneInputStream.cpp b/xpcom/tests/gtest/TestCloneInputStream.cpp
new file mode 100644
index 000000000..de4dd5ea3
--- /dev/null
+++ b/xpcom/tests/gtest/TestCloneInputStream.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+#include "Helpers.h"
+#include "mozilla/Unused.h"
+#include "nsICloneableInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsComponentManagerUtils.h"
+
+TEST(CloneInputStream, InvalidInput)
+{
+ nsCOMPtr<nsIInputStream> clone;
+ nsresult rv = NS_CloneInputStream(nullptr, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_FAILED(rv));
+ ASSERT_FALSE(clone);
+}
+
+TEST(CloneInputStream, CloneableInput)
+{
+ nsTArray<char> inputData;
+ testing::CreateData(4 * 1024, inputData);
+ nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+ testing::ConsumeAndValidateStream(clone, inputString);
+}
+
+TEST(CloneInputStream, NonCloneableInput_NoFallback)
+{
+ nsTArray<char> inputData;
+ testing::CreateData(4 * 1024, inputData);
+ nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> base;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Take advantage of nsBufferedInputStream being non-cloneable right
+ // now. If this changes in the future, then we need a different stream
+ // type in this test.
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(stream), base, 4096);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream);
+ ASSERT_TRUE(cloneable == nullptr);
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_FAILED(rv));
+ ASSERT_TRUE(clone == nullptr);
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+}
+
+TEST(CloneInputStream, NonCloneableInput_Fallback)
+{
+ nsTArray<char> inputData;
+ testing::CreateData(4 * 1024, inputData);
+ nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> base;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Take advantage of nsBufferedInputStream being non-cloneable right
+ // now. If this changes in the future, then we need a different stream
+ // type in this test.
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(stream), base, 4096);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream);
+ ASSERT_TRUE(cloneable == nullptr);
+
+ nsCOMPtr<nsIInputStream> clone;
+ nsCOMPtr<nsIInputStream> replacement;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone),
+ getter_AddRefs(replacement));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(clone != nullptr);
+ ASSERT_TRUE(replacement != nullptr);
+ ASSERT_TRUE(stream.get() != replacement.get());
+ ASSERT_TRUE(clone.get() != replacement.get());
+
+ stream = replacement.forget();
+
+ // The stream is being copied asynchronously on the STS event target. Spin
+ // a yield loop here until the data is available. Yes, this is a bit hacky,
+ // but AFAICT, gtest does not support async test completion.
+ uint64_t available;
+ do {
+ mozilla::Unused << PR_Sleep(PR_INTERVAL_NO_WAIT);
+ rv = stream->Available(&available);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ } while(available < inputString.Length());
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+ testing::ConsumeAndValidateStream(clone, inputString);
+}
+
+TEST(CloneInputStream, CloneMultiplexStream)
+{
+ nsCOMPtr<nsIMultiplexInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(stream);
+
+ nsTArray<char> inputData;
+ testing::CreateData(1024, inputData);
+ for (uint32_t i = 0; i < 2; ++i) {
+ nsCString inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> base;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = stream->AppendStream(base);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ // Unread stream should clone successfully.
+ nsTArray<char> doubled;
+ doubled.AppendElements(inputData);
+ doubled.AppendElements(inputData);
+
+ nsCOMPtr<nsIInputStream> clone;
+ nsresult rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ testing::ConsumeAndValidateStream(clone, doubled);
+
+ // Stream that has been read should fail.
+ char buffer[512];
+ uint32_t read;
+ rv = stream->Read(buffer, 512, &read);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> clone2;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone2));
+ ASSERT_TRUE(NS_FAILED(rv));
+}
+
+TEST(CloneInputStream, CloneMultiplexStreamPartial)
+{
+ nsCOMPtr<nsIMultiplexInputStream> stream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(stream);
+
+ nsTArray<char> inputData;
+ testing::CreateData(1024, inputData);
+ for (uint32_t i = 0; i < 2; ++i) {
+ nsCString inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> base;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = stream->AppendStream(base);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ // Fail when first stream read, but second hasn't been started.
+ char buffer[1024];
+ uint32_t read;
+ nsresult rv = stream->Read(buffer, 1024, &read);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ // Fail after beginning read of second stream.
+ rv = stream->Read(buffer, 512, &read);
+ ASSERT_TRUE(NS_SUCCEEDED(rv) && read == 512);
+
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ // Fail at the end.
+ nsAutoCString consumed;
+ rv = NS_ConsumeStream(stream, UINT32_MAX, consumed);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_FAILED(rv));
+}
diff --git a/xpcom/tests/gtest/TestDeadlockDetector.cpp b/xpcom/tests/gtest/TestDeadlockDetector.cpp
new file mode 100644
index 000000000..646ee3e1d
--- /dev/null
+++ b/xpcom/tests/gtest/TestDeadlockDetector.cpp
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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 "mozilla/ArrayUtils.h"
+
+#include "prthread.h"
+
+#include "nsTArray.h"
+#include "nsMemory.h"
+
+#include "mozilla/CondVar.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Mutex.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsCOMPtr.h"
+#include "nsICrashReporter.h"
+#include "nsServiceManagerUtils.h"
+#endif
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+static PRThread*
+spawn(void (*run)(void*), void* arg)
+{
+ return PR_CreateThread(PR_SYSTEM_THREAD,
+ run,
+ arg,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD,
+ 0);
+}
+
+// This global variable is defined in toolkit/xre/nsSigHandlers.cpp.
+extern unsigned int _gdb_sleep_duration;
+
+/**
+ * Simple test fixture that makes sure the gdb sleep setup in the
+ * ah crap handler is bypassed during the death tests.
+ */
+class DeadlockDetectorTest : public ::testing::Test
+{
+protected:
+ void SetUp() final {
+ mOldSleepDuration = _gdb_sleep_duration;
+ _gdb_sleep_duration = 0;
+ }
+
+ void TearDown() final {
+ _gdb_sleep_duration = mOldSleepDuration;
+ }
+
+private:
+ unsigned int mOldSleepDuration;
+};
+
+void DisableCrashReporter()
+{
+#ifdef MOZ_CRASHREPORTER
+ nsCOMPtr<nsICrashReporter> crashreporter =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (crashreporter) {
+ crashreporter->SetEnabled(false);
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Single-threaded sanity tests
+
+// Stupidest possible deadlock.
+int
+Sanity_Child()
+{
+ DisableCrashReporter();
+
+ mozilla::Mutex m1("dd.sanity.m1");
+ m1.Lock();
+ m1.Lock();
+ return 0; // not reached
+}
+
+TEST_F(DeadlockDetectorTest, SanityDeathTest)
+{
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.sanity.m1.*"
+ "=== Cycle completed at.*--- Mutex : dd.sanity.m1.*"
+ "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(Sanity_Child(), regex);
+}
+
+// Slightly less stupid deadlock.
+int
+Sanity2_Child()
+{
+ DisableCrashReporter();
+
+ mozilla::Mutex m1("dd.sanity2.m1");
+ mozilla::Mutex m2("dd.sanity2.m2");
+ m1.Lock();
+ m2.Lock();
+ m1.Lock();
+ return 0; // not reached
+}
+
+TEST_F(DeadlockDetectorTest, Sanity2DeathTest)
+{
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.sanity2.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity2.m2.*"
+ "=== Cycle completed at.*--- Mutex : dd.sanity2.m1.*"
+ "###!!! Deadlock may happen NOW!.*" // better catch these easy cases...
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(Sanity2_Child(), regex);
+}
+
+int
+Sanity3_Child()
+{
+ DisableCrashReporter();
+
+ mozilla::Mutex m1("dd.sanity3.m1");
+ mozilla::Mutex m2("dd.sanity3.m2");
+ mozilla::Mutex m3("dd.sanity3.m3");
+ mozilla::Mutex m4("dd.sanity3.m4");
+
+ m1.Lock();
+ m2.Lock();
+ m3.Lock();
+ m4.Lock();
+ m4.Unlock();
+ m3.Unlock();
+ m2.Unlock();
+ m1.Unlock();
+
+ m4.Lock();
+ m1.Lock();
+ return 0;
+}
+
+TEST_F(DeadlockDetectorTest, Sanity3DeathTest)
+{
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.sanity3.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity3.m2.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity3.m3.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity3.m4.*"
+ "=== Cycle completed at.*--- Mutex : dd.sanity3.m1.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(Sanity3_Child(), regex);
+}
+
+int
+Sanity4_Child()
+{
+ DisableCrashReporter();
+
+ mozilla::ReentrantMonitor m1("dd.sanity4.m1");
+ mozilla::Mutex m2("dd.sanity4.m2");
+ m1.Enter();
+ m2.Lock();
+ m1.Enter();
+ return 0;
+}
+
+TEST_F(DeadlockDetectorTest, Sanity4DeathTest)
+{
+ const char* const regex =
+ "Re-entering ReentrantMonitor after acquiring other resources.*"
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- ReentrantMonitor : dd.sanity4.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*"
+ "=== Cycle completed at.*--- ReentrantMonitor : dd.sanity4.m1.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+ ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex);
+}
+
+//-----------------------------------------------------------------------------
+// Multithreaded tests
+
+/**
+ * Helper for passing state to threads in the multithread tests.
+ */
+struct ThreadState
+{
+ /**
+ * Locks to use during the test. This is just a reference and is owned by
+ * the main test thread.
+ */
+ const nsTArray<mozilla::Mutex*>& locks;
+
+ /**
+ * Integer argument used to identify each thread.
+ */
+ int id;
+};
+
+static void
+TwoThreads_thread(void* arg)
+{
+ ThreadState* state = static_cast<ThreadState*>(arg);
+
+ mozilla::Mutex* ttM1 = state->locks[0];
+ mozilla::Mutex* ttM2 = state->locks[1];
+
+ if (state->id) {
+ ttM1->Lock();
+ ttM2->Lock();
+ ttM2->Unlock();
+ ttM1->Unlock();
+ }
+ else {
+ ttM2->Lock();
+ ttM1->Lock();
+ ttM1->Unlock();
+ ttM2->Unlock();
+ }
+}
+
+int
+TwoThreads_Child()
+{
+ DisableCrashReporter();
+
+ nsTArray<mozilla::Mutex*> locks = {
+ new mozilla::Mutex("dd.twothreads.m1"),
+ new mozilla::Mutex("dd.twothreads.m2")
+ };
+
+ ThreadState state_1 {locks, 0};
+ PRThread* t1 = spawn(TwoThreads_thread, &state_1);
+ PR_JoinThread(t1);
+
+ ThreadState state_2 {locks, 1};
+ PRThread* t2 = spawn(TwoThreads_thread, &state_2);
+ PR_JoinThread(t2);
+
+ for (auto& lock : locks) {
+ delete lock;
+ }
+
+ return 0;
+}
+
+TEST_F(DeadlockDetectorTest, TwoThreadsDeathTest)
+{
+ const char* const regex =
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- Mutex : dd.twothreads.m2.*"
+ "--- Next dependency:.*--- Mutex : dd.twothreads.m1.*"
+ "=== Cycle completed at.*--- Mutex : dd.twothreads.m2.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+
+ ASSERT_DEATH_IF_SUPPORTED(TwoThreads_Child(), regex);
+}
+
+static void
+ContentionNoDeadlock_thread(void* arg)
+{
+ const uint32_t K = 100000;
+
+ ThreadState* state = static_cast<ThreadState*>(arg);
+ int32_t starti = static_cast<int32_t>(state->id);
+ auto& cndMs = state->locks;
+
+ for (uint32_t k = 0; k < K; ++k) {
+ for (int32_t i = starti; i < (int32_t)cndMs.Length(); ++i)
+ cndMs[i]->Lock();
+ // comment out the next two lines for deadlocking fun!
+ for (int32_t i = cndMs.Length() - 1; i >= starti; --i)
+ cndMs[i]->Unlock();
+
+ starti = (starti + 1) % 3;
+ }
+}
+
+int
+ContentionNoDeadlock_Child()
+{
+ const size_t kMutexCount = 4;
+
+ PRThread* threads[3];
+ nsTArray<mozilla::Mutex*> locks;
+ ThreadState states[] = {
+ { locks, 0 },
+ { locks, 1 },
+ { locks, 2 }
+ };
+
+ for (uint32_t i = 0; i < kMutexCount; ++i)
+ locks.AppendElement(new mozilla::Mutex("dd.cnd.ms"));
+
+ for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i)
+ threads[i] = spawn(ContentionNoDeadlock_thread, states + i);
+
+ for (uint32_t i = 0; i < ArrayLength(threads); ++i)
+ PR_JoinThread(threads[i]);
+
+ for (uint32_t i = 0; i < locks.Length(); ++i)
+ delete locks[i];
+
+ return 0;
+}
+
+TEST_F(DeadlockDetectorTest, ContentionNoDeadlock)
+{
+ // Just check that this test runs to completion.
+ ASSERT_EQ(ContentionNoDeadlock_Child(), 0);
+}
diff --git a/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp
new file mode 100644
index 000000000..e25217223
--- /dev/null
+++ b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ * 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/. */
+
+// Avoid DMD-specific parts of MOZ_DEFINE_MALLOC_SIZE_OF
+#undef MOZ_DMD
+
+#include "nsIMemoryReporter.h"
+#include "mozilla/Mutex.h"
+
+#include "gtest/gtest.h"
+
+//-----------------------------------------------------------------------------
+
+static void
+AllocLockRecurseUnlockFree(int i)
+{
+ if (0 == i)
+ return;
+
+ mozilla::Mutex* lock = new mozilla::Mutex("deadlockDetector.scalability.t1");
+ {
+ mozilla::MutexAutoLock _(*lock);
+ AllocLockRecurseUnlockFree(i - 1);
+ }
+ delete lock;
+}
+
+// This test creates a resource dependency chain N elements long, then
+// frees all the resources in the chain.
+TEST(DeadlockDetectorScalability, LengthNDepChain)
+{
+ const int N = 1 << 14; // 16K
+ AllocLockRecurseUnlockFree(N);
+ ASSERT_TRUE(true);
+}
+
+//-----------------------------------------------------------------------------
+
+// This test creates a single lock that is ordered < N resources, then
+// repeatedly exercises this order k times.
+//
+// NB: It takes a minute or two to run so it is disabled by default.
+TEST(DeadlockDetectorScalability, DISABLED_OneLockNDeps)
+{
+ // NB: Using a larger test size to stress our traversal logic.
+ const int N = 1 << 17; // 131k
+ const int K = 100;
+
+ mozilla::Mutex* lock = new mozilla::Mutex("deadlockDetector.scalability.t2.master");
+ mozilla::Mutex** locks = new mozilla::Mutex*[N];
+ if (!locks)
+ NS_RUNTIMEABORT("couldn't allocate lock array");
+
+ for (int i = 0; i < N; ++i)
+ locks[i] =
+ new mozilla::Mutex("deadlockDetector.scalability.t2.dep");
+
+ // establish orders
+ {mozilla::MutexAutoLock m(*lock);
+ for (int i = 0; i < N; ++i)
+ mozilla::MutexAutoLock s(*locks[i]);
+ }
+
+ // exercise order check
+ {mozilla::MutexAutoLock m(*lock);
+ for (int i = 0; i < K; ++i)
+ for (int j = 0; j < N; ++j)
+ mozilla::MutexAutoLock s(*locks[i]);
+ }
+
+ for (int i = 0; i < N; ++i)
+ delete locks[i];
+ delete[] locks;
+
+ ASSERT_TRUE(true);
+}
+
+//-----------------------------------------------------------------------------
+
+// This test creates N resources and adds the theoretical maximum number
+// of dependencies, O(N^2). It then repeats that sequence of
+// acquisitions k times. Finally, all resources are freed.
+//
+// It's very difficult to perform well on this test. It's put forth as a
+// challenge problem.
+
+TEST(DeadlockDetectorScalability, MaxDepsNsq)
+{
+ const int N = 1 << 10; // 1k
+ const int K = 10;
+
+ mozilla::Mutex** locks = new mozilla::Mutex*[N];
+ if (!locks)
+ NS_RUNTIMEABORT("couldn't allocate lock array");
+
+ for (int i = 0; i < N; ++i)
+ locks[i] = new mozilla::Mutex("deadlockDetector.scalability.t3");
+
+ for (int i = 0; i < N; ++i) {
+ mozilla::MutexAutoLock al1(*locks[i]);
+ for (int j = i+1; j < N; ++j)
+ mozilla::MutexAutoLock al2(*locks[j]);
+ }
+
+ for (int i = 0; i < K; ++i) {
+ for (int j = 0; j < N; ++j) {
+ mozilla::MutexAutoLock al1(*locks[j]);
+ for (int k = j+1; k < N; ++k)
+ mozilla::MutexAutoLock al2(*locks[k]);
+ }
+ }
+
+ for (int i = 0; i < N; ++i)
+ delete locks[i];
+ delete[] locks;
+
+ ASSERT_TRUE(true);
+}
+
+//-----------------------------------------------------------------------------
+
+// This test creates a single lock that is ordered < N resources. The
+// resources are allocated, exercised K times, and deallocated one at
+// a time.
+
+TEST(DeadlockDetectorScalability, OneLockNDepsUsedSeveralTimes)
+{
+ const size_t N = 1 << 17; // 131k
+ const size_t K = 3;
+
+ // Create master lock.
+ mozilla::Mutex* lock_1 = new mozilla::Mutex("deadlockDetector.scalability.t4.master");
+ for (size_t n = 0; n < N; n++) {
+ // Create child lock.
+ mozilla::Mutex* lock_2 = new mozilla::Mutex("deadlockDetector.scalability.t4.child");
+
+ // First lock the master.
+ mozilla::MutexAutoLock m(*lock_1);
+
+ // Now lock and unlock the child a few times.
+ for (size_t k = 0; k < K; k++) {
+ mozilla::MutexAutoLock c(*lock_2);
+ }
+
+ // Destroy the child lock.
+ delete lock_2;
+ }
+
+ // Cleanup the master lock.
+ delete lock_1;
+
+ ASSERT_TRUE(true);
+}
+
+//-----------------------------------------------------------------------------
+
+MOZ_DEFINE_MALLOC_SIZE_OF(DeadlockDetectorMallocSizeOf)
+
+// This is a simple test that exercises the deadlock detector memory reporting
+// functionality.
+TEST(DeadlockDetectorScalability, SizeOf)
+{
+ size_t memory_used = mozilla::BlockingResourceBase::SizeOfDeadlockDetector(
+ DeadlockDetectorMallocSizeOf);
+
+ ASSERT_GT(memory_used, size_t(0));
+}
diff --git a/xpcom/tests/gtest/TestEncoding.cpp b/xpcom/tests/gtest/TestEncoding.cpp
new file mode 100644
index 000000000..0671284ee
--- /dev/null
+++ b/xpcom/tests/gtest/TestEncoding.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <stdlib.h>
+#include "nsString.h"
+#include "gtest/gtest.h"
+
+TEST(Encoding, GoodSurrogatePair)
+{
+ // When this string is decoded, the surrogate pair is U+10302 and the rest of
+ // the string is specified by indexes 2 onward.
+ const char16_t goodPairData[] = { 0xD800, 0xDF02, 0x65, 0x78, 0x0 };
+ nsDependentString goodPair16(goodPairData);
+
+ uint32_t byteCount = 0;
+ char* goodPair8 = ToNewUTF8String(goodPair16, &byteCount);
+ EXPECT_TRUE(!!goodPair8);
+
+ EXPECT_EQ(byteCount, 6u);
+
+ const unsigned char expected8[] =
+ { 0xF0, 0x90, 0x8C, 0x82, 0x65, 0x78, 0x0 };
+ EXPECT_EQ(0, memcmp(expected8, goodPair8, sizeof(expected8)));
+
+ // This takes a different code path from the above, so test it to make sure
+ // the UTF-16 enumeration remains in sync with the UTF-8 enumeration.
+ nsDependentCString expected((const char*)expected8);
+ EXPECT_EQ(0, CompareUTF8toUTF16(expected, goodPair16));
+
+ free(goodPair8);
+}
+
+TEST(Encoding, BackwardsSurrogatePair)
+{
+ // When this string is decoded, the two surrogates are wrongly ordered and
+ // must each be interpreted as U+FFFD.
+ const char16_t backwardsPairData[] = { 0xDDDD, 0xD863, 0x65, 0x78, 0x0 };
+ nsDependentString backwardsPair16(backwardsPairData);
+
+ uint32_t byteCount = 0;
+ char* backwardsPair8 = ToNewUTF8String(backwardsPair16, &byteCount);
+ EXPECT_TRUE(!!backwardsPair8);
+
+ EXPECT_EQ(byteCount, 8u);
+
+ const unsigned char expected8[] =
+ { 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0x65, 0x78, 0x0 };
+ EXPECT_EQ(0, memcmp(expected8, backwardsPair8, sizeof(expected8)));
+
+ // This takes a different code path from the above, so test it to make sure
+ // the UTF-16 enumeration remains in sync with the UTF-8 enumeration.
+ nsDependentCString expected((const char*)expected8);
+ EXPECT_EQ(0, CompareUTF8toUTF16(expected, backwardsPair16));
+
+ free(backwardsPair8);
+}
+
+TEST(Encoding, MalformedUTF16OrphanHighSurrogate)
+{
+ // When this string is decoded, the high surrogate should be replaced and the
+ // rest of the string is specified by indexes 1 onward.
+ const char16_t highSurrogateData[] = { 0xD863, 0x74, 0x65, 0x78, 0x74, 0x0 };
+ nsDependentString highSurrogate16(highSurrogateData);
+
+ uint32_t byteCount = 0;
+ char* highSurrogate8 = ToNewUTF8String(highSurrogate16, &byteCount);
+ EXPECT_TRUE(!!highSurrogate8);
+
+ EXPECT_EQ(byteCount, 7u);
+
+ const unsigned char expected8[] =
+ { 0xEF, 0xBF, 0xBD, 0x74, 0x65, 0x78, 0x74, 0x0 };
+ EXPECT_EQ(0, memcmp(expected8, highSurrogate8, sizeof(expected8)));
+
+ // This takes a different code path from the above, so test it to make sure
+ // the UTF-16 enumeration remains in sync with the UTF-8 enumeration.
+ nsDependentCString expected((const char*)expected8);
+ EXPECT_EQ(0, CompareUTF8toUTF16(expected, highSurrogate16));
+
+ free(highSurrogate8);
+}
+
+TEST(Encoding, MalformedUTF16OrphanLowSurrogate)
+{
+ // When this string is decoded, the low surrogate should be replaced and the
+ // rest of the string is specified by indexes 1 onward.
+ const char16_t lowSurrogateData[] = { 0xDDDD, 0x74, 0x65, 0x78, 0x74, 0x0 };
+ nsDependentString lowSurrogate16(lowSurrogateData);
+
+ uint32_t byteCount = 0;
+ char* lowSurrogate8 = ToNewUTF8String(lowSurrogate16, &byteCount);
+ EXPECT_TRUE(!!lowSurrogate8);
+
+ EXPECT_EQ(byteCount, 7u);
+
+ const unsigned char expected8[] =
+ { 0xEF, 0xBF, 0xBD, 0x74, 0x65, 0x78, 0x74, 0x0 };
+ EXPECT_EQ(0, memcmp(expected8, lowSurrogate8, sizeof(expected8)));
+
+ // This takes a different code path from the above, so test it to make sure
+ // the UTF-16 enumeration remains in sync with the UTF-8 enumeration.
+ nsDependentCString expected((const char*)expected8);
+ EXPECT_EQ(0, CompareUTF8toUTF16(expected, lowSurrogate16));
+
+ free(lowSurrogate8);
+}
diff --git a/xpcom/tests/gtest/TestEscapeURL.cpp b/xpcom/tests/gtest/TestEscapeURL.cpp
new file mode 100644
index 000000000..2a8faf5f0
--- /dev/null
+++ b/xpcom/tests/gtest/TestEscapeURL.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsEscape.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+// Testing for failure here would be somewhat hard in automation. Locally you
+// could use something like ulimit to create a failure.
+
+TEST(EscapeURL, FallibleNoEscape)
+{
+ // Tests the fallible version of NS_EscapeURL works as expected when no
+ // escaping is necessary.
+ nsCString toEscape("data:,Hello%2C%20World!");
+ nsCString escaped;
+ nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible);
+ EXPECT_EQ(rv, NS_OK);
+ // Nothing should have been escaped, they should be the same string.
+ EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading());
+ // We expect them to point at the same buffer.
+ EXPECT_EQ(toEscape.BeginReading(), escaped.BeginReading());
+}
+
+TEST(EscapeURL, FallibleEscape)
+{
+ // Tests the fallible version of NS_EscapeURL works as expected when
+ // escaping is necessary.
+ nsCString toEscape("data:,Hello%2C%20World!\xC4\x9F");
+ nsCString escaped;
+ nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible);
+ EXPECT_EQ(rv, NS_OK);
+ EXPECT_STRNE(toEscape.BeginReading(), escaped.BeginReading());
+ const char* const kExpected = "data:,Hello%2C%20World!%C4%9F";
+ EXPECT_STREQ(escaped.BeginReading(), kExpected);
+}
+
+TEST(EscapeURL, BadEscapeSequences)
+{
+ {
+ char bad[] = "%s\0fa";
+
+ int32_t count = nsUnescapeCount(bad);
+ EXPECT_EQ(count, 2);
+ EXPECT_STREQ(bad, "%s");
+ }
+ {
+ char bad[] = "%a";
+ int32_t count = nsUnescapeCount(bad);
+ EXPECT_EQ(count, 2);
+ EXPECT_STREQ(bad, "%a");
+ }
+ {
+ char bad[] = "%";
+ int32_t count = nsUnescapeCount(bad);
+ EXPECT_EQ(count, 1);
+ EXPECT_STREQ(bad, "%");
+ }
+ {
+ char bad[] = "%s/%s";
+ int32_t count = nsUnescapeCount(bad);
+ EXPECT_EQ(count, 5);
+ EXPECT_STREQ(bad, "%s/%s");
+ }
+}
diff --git a/xpcom/tests/gtest/TestExpirationTracker.cpp b/xpcom/tests/gtest/TestExpirationTracker.cpp
new file mode 100644
index 000000000..a6a0e146c
--- /dev/null
+++ b/xpcom/tests/gtest/TestExpirationTracker.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <stdlib.h>
+#include <stdio.h>
+#include <prthread.h>
+#include "nsExpirationTracker.h"
+#include "nsMemory.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOM.h"
+#include "nsIFile.h"
+#include "prinrval.h"
+#include "nsThreadUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "gtest/gtest.h"
+
+namespace TestExpirationTracker {
+
+struct Object {
+ Object() : mExpired(false) { Touch(); }
+ void Touch() { mLastUsed = PR_IntervalNow(); mExpired = false; }
+
+ nsExpirationState mExpiration;
+ nsExpirationState* GetExpirationState() { return &mExpiration; }
+
+ PRIntervalTime mLastUsed;
+ bool mExpired;
+};
+
+static bool error;
+static uint32_t periodMS = 100;
+static uint32_t ops = 1000;
+static uint32_t iterations = 2;
+static bool logging = 0;
+static uint32_t sleepPeriodMS = 50;
+static uint32_t slackMS = 30; // allow this much error
+
+template <uint32_t K> class Tracker : public nsExpirationTracker<Object,K> {
+public:
+ Tracker() : nsExpirationTracker<Object,K>(periodMS, "Tracker") {
+ Object* obj = new Object();
+ mUniverse.AppendElement(obj);
+ LogAction(obj, "Created");
+ }
+
+ nsTArray<mozilla::UniquePtr<Object>> mUniverse;
+
+ void LogAction(Object* aObj, const char* aAction) {
+ if (logging) {
+ printf("%d %p(%d): %s\n", PR_IntervalNow(),
+ static_cast<void*>(aObj), aObj->mLastUsed, aAction);
+ }
+ }
+
+ void DoRandomOperation() {
+ Object* obj;
+ switch (rand() & 0x7) {
+ case 0: {
+ if (mUniverse.Length() < 50) {
+ obj = new Object();
+ mUniverse.AppendElement(obj);
+ nsExpirationTracker<Object,K>::AddObject(obj);
+ LogAction(obj, "Created and added");
+ }
+ break;
+ }
+ case 4: {
+ if (mUniverse.Length() < 50) {
+ obj = new Object();
+ mUniverse.AppendElement(obj);
+ LogAction(obj, "Created");
+ }
+ break;
+ }
+ case 1: {
+ UniquePtr<Object>& objref = mUniverse[uint32_t(rand())%mUniverse.Length()];
+ if (objref->mExpiration.IsTracked()) {
+ nsExpirationTracker<Object,K>::RemoveObject(objref.get());
+ LogAction(objref.get(), "Removed");
+ }
+ break;
+ }
+ case 2: {
+ UniquePtr<Object>& objref = mUniverse[uint32_t(rand())%mUniverse.Length()];
+ if (!objref->mExpiration.IsTracked()) {
+ objref->Touch();
+ nsExpirationTracker<Object,K>::AddObject(objref.get());
+ LogAction(objref.get(), "Added");
+ }
+ break;
+ }
+ case 3: {
+ UniquePtr<Object>& objref = mUniverse[uint32_t(rand())%mUniverse.Length()];
+ if (objref->mExpiration.IsTracked()) {
+ objref->Touch();
+ nsExpirationTracker<Object,K>::MarkUsed(objref.get());
+ LogAction(objref.get(), "Marked used");
+ }
+ break;
+ }
+ }
+ }
+
+protected:
+ void NotifyExpired(Object* aObj) {
+ LogAction(aObj, "Expired");
+ PRIntervalTime now = PR_IntervalNow();
+ uint32_t timeDiffMS = (now - aObj->mLastUsed)*1000/PR_TicksPerSecond();
+ // See the comment for NotifyExpired in nsExpirationTracker.h for these
+ // bounds
+ uint32_t lowerBoundMS = (K-1)*periodMS - slackMS;
+ uint32_t upperBoundMS = K*(periodMS + sleepPeriodMS) + slackMS;
+ if (logging) {
+ printf("Checking: %d-%d = %d [%d,%d]\n",
+ now, aObj->mLastUsed, timeDiffMS, lowerBoundMS, upperBoundMS);
+ }
+ if (timeDiffMS < lowerBoundMS || timeDiffMS > upperBoundMS) {
+ EXPECT_TRUE(timeDiffMS < periodMS && aObj->mExpired);
+ }
+ aObj->Touch();
+ aObj->mExpired = true;
+ DoRandomOperation();
+ DoRandomOperation();
+ DoRandomOperation();
+ }
+};
+
+template <uint32_t K> static bool test_random() {
+ srand(K);
+ error = false;
+
+ for (uint32_t j = 0; j < iterations; ++j) {
+ Tracker<K> tracker;
+
+ uint32_t i = 0;
+ for (i = 0; i < ops; ++i) {
+ if ((rand() & 0xF) == 0) {
+ // Simulate work that takes time
+ if (logging) {
+ printf("SLEEPING for %dms (%d)\n", sleepPeriodMS, PR_IntervalNow());
+ }
+ PR_Sleep(PR_MillisecondsToInterval(sleepPeriodMS));
+ // Process pending timer events
+ NS_ProcessPendingEvents(nullptr);
+ }
+ tracker.DoRandomOperation();
+ }
+ }
+
+ return !error;
+}
+
+static bool test_random3() { return test_random<3>(); }
+static bool test_random4() { return test_random<4>(); }
+static bool test_random8() { return test_random<8>(); }
+
+typedef bool (*TestFunc)();
+#define DECL_TEST(name) { #name, name }
+
+static const struct Test {
+ const char* name;
+ TestFunc func;
+} tests[] = {
+ DECL_TEST(test_random3),
+ DECL_TEST(test_random4),
+ DECL_TEST(test_random8),
+ { nullptr, nullptr }
+};
+
+TEST(ExpirationTracker, main)
+{
+ for (const TestExpirationTracker::Test* t = tests;
+ t->name != nullptr; ++t) {
+ EXPECT_TRUE(t->func());
+ }
+}
+
+} // namespace TestExpirationTracker
diff --git a/xpcom/tests/gtest/TestFile.cpp b/xpcom/tests/gtest/TestFile.cpp
new file mode 100644
index 000000000..7e7b4f4a1
--- /dev/null
+++ b/xpcom/tests/gtest/TestFile.cpp
@@ -0,0 +1,477 @@
+/* -*- 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 "prio.h"
+#include "prsystem.h"
+
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+
+#include "gtest/gtest.h"
+
+static bool VerifyResult(nsresult aRV, const char* aMsg)
+{
+ bool failed = NS_FAILED(aRV);
+ EXPECT_FALSE(failed) << aMsg << " rv=" << std::hex << (unsigned int)aRV;
+ return !failed;
+}
+
+static already_AddRefed<nsIFile> NewFile(nsIFile* aBase)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> file =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ VerifyResult(rv, "Creating nsIFile");
+ rv = file->InitWithFile(aBase);
+ VerifyResult(rv, "InitWithFile");
+ return file.forget();
+}
+
+static nsCString FixName(const char* aName)
+{
+ nsCString name;
+ for (uint32_t i = 0; aName[i]; ++i) {
+ char ch = aName[i];
+ // PR_GetPathSeparator returns the wrong value on Mac so don't use it
+#if defined(XP_WIN)
+ if (ch == '/') {
+ ch = '\\';
+ }
+#endif
+ name.Append(ch);
+ }
+ return name;
+}
+
+// Test nsIFile::AppendNative, verifying that aName is not a valid file name
+static bool TestInvalidFileName(nsIFile* aBase, const char* aName)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (NS_SUCCEEDED(rv)) {
+ EXPECT_FALSE(NS_SUCCEEDED(rv)) << "AppendNative with invalid filename " << name.get();
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::Create, verifying that the file exists and did not exist before,
+// and leaving it there for future tests
+static bool TestCreate(nsIFile* aBase, const char* aName, int32_t aType, int32_t aPerm)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (before)"))
+ return false;
+ EXPECT_FALSE(exists) << "File "<< name.get() << " already exists";
+ if (exists) {
+ return false;
+ }
+
+ rv = file->Create(aType, aPerm);
+ if (!VerifyResult(rv, "Create"))
+ return false;
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (after)"))
+ return false;
+ EXPECT_TRUE(exists) << "File " << name.get() << " was not created";
+ if (!exists) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::CreateUnique, verifying that the new file exists and if it existed before,
+// the new file has a different name.
+// The new file is left in place.
+static bool TestCreateUnique(nsIFile* aBase, const char* aName, int32_t aType, int32_t aPerm)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ bool existsBefore;
+ rv = file->Exists(&existsBefore);
+ if (!VerifyResult(rv, "Exists (before)"))
+ return false;
+
+ rv = file->CreateUnique(aType, aPerm);
+ if (!VerifyResult(rv, "Create"))
+ return false;
+
+ bool existsAfter;
+ rv = file->Exists(&existsAfter);
+ if (!VerifyResult(rv, "Exists (after)"))
+ return false;
+ EXPECT_TRUE(existsAfter) << "File " << name.get() << " was not created";
+ if (!existsAfter) {
+ return false;
+ }
+
+ if (existsBefore) {
+ nsAutoCString leafName;
+ rv = file->GetNativeLeafName(leafName);
+ if (!VerifyResult(rv, "GetNativeLeafName"))
+ return false;
+ EXPECT_FALSE(leafName.Equals(name)) << "File " << name.get() << " was not given a new name by CreateUnique";
+ if (leafName.Equals(name)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Test nsIFile::OpenNSPRFileDesc with DELETE_ON_CLOSE, verifying that the file exists
+// and did not exist before, and leaving it there for future tests
+static bool TestDeleteOnClose(nsIFile* aBase, const char* aName, int32_t aFlags, int32_t aPerm)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (before)"))
+ return false;
+ EXPECT_FALSE(exists) << "File " << name.get() << " already exists";
+ if (exists) {
+ return false;
+ }
+
+ PRFileDesc* fileDesc;
+ rv = file->OpenNSPRFileDesc(aFlags | nsIFile::DELETE_ON_CLOSE, aPerm, &fileDesc);
+ if (!VerifyResult(rv, "OpenNSPRFileDesc"))
+ return false;
+ PRStatus status = PR_Close(fileDesc);
+ EXPECT_EQ(status, PR_SUCCESS) << "File " << name.get() << " could not be closed";
+ if (status != PR_SUCCESS) {
+ return false;
+ }
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (after)"))
+ return false;
+ EXPECT_FALSE(exists) << "File " << name.get() << " was not removed on close";
+ if (exists) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::Remove, verifying that the file does not exist and did before
+static bool TestRemove(nsIFile* aBase, const char* aName, bool aRecursive)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (before)"))
+ return false;
+ EXPECT_TRUE(exists);
+ if (!exists) {
+ return false;
+ }
+
+ rv = file->Remove(aRecursive);
+ if (!VerifyResult(rv, "Remove"))
+ return false;
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (after)"))
+ return false;
+ EXPECT_FALSE(exists) << "File " << name.get() << " was not removed";
+ if (exists) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::MoveToNative, verifying that the file did not exist at the new location
+// before and does afterward, and that it does not exist at the old location anymore
+static bool TestMove(nsIFile* aBase, nsIFile* aDestDir, const char* aName, const char* aNewName)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (before)"))
+ return false;
+ EXPECT_TRUE(exists);
+ if (!exists) {
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> newFile = NewFile(file);
+ nsCString newName = FixName(aNewName);
+ rv = newFile->MoveToNative(aDestDir, newName);
+ if (!VerifyResult(rv, "MoveToNative"))
+ return false;
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (after)"))
+ return false;
+ EXPECT_FALSE(exists) << "File " << name.get() << " was not moved";
+ if (exists) {
+ return false;
+ }
+
+ file = NewFile(aDestDir);
+ if (!file)
+ return false;
+ rv = file->AppendNative(newName);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+ bool equal;
+ rv = file->Equals(newFile, &equal);
+ if (!VerifyResult(rv, "Equals"))
+ return false;
+ EXPECT_TRUE(equal) << "File object was not updated to destination";
+ if (!equal) {
+ return false;
+ }
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (new after)"))
+ return false;
+ EXPECT_TRUE(exists) << "Destination file " << newName.get() << " was not created";
+ if (!exists) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::CopyToNative, verifying that the file did not exist at the new location
+// before and does afterward, and that it does exist at the old location too
+static bool TestCopy(nsIFile* aBase, nsIFile* aDestDir, const char* aName, const char* aNewName)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file)
+ return false;
+
+ nsCString name = FixName(aName);
+ nsresult rv = file->AppendNative(name);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (before)"))
+ return false;
+ EXPECT_TRUE(exists);
+ if (!exists) {
+ return false;
+ }
+
+ nsCOMPtr<nsIFile> newFile = NewFile(file);
+ nsCString newName = FixName(aNewName);
+ rv = newFile->CopyToNative(aDestDir, newName);
+ if (!VerifyResult(rv, "MoveToNative"))
+ return false;
+ bool equal;
+ rv = file->Equals(newFile, &equal);
+ if (!VerifyResult(rv, "Equals"))
+ return false;
+ EXPECT_TRUE(equal) << "File object updated unexpectedly";
+ if (!equal) {
+ return false;
+ }
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (after)"))
+ return false;
+ EXPECT_TRUE(exists) << "File " << name.get() << " was removed";
+ if (!exists) {
+ return false;
+ }
+
+ file = NewFile(aDestDir);
+ if (!file)
+ return false;
+ rv = file->AppendNative(newName);
+ if (!VerifyResult(rv, "AppendNative"))
+ return false;
+
+ rv = file->Exists(&exists);
+ if (!VerifyResult(rv, "Exists (new after)"))
+ return false;
+ EXPECT_TRUE(exists) << "Destination file " << newName.get() << " was not created";
+ if (!exists) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::GetParent
+static bool TestParent(nsIFile* aBase, nsIFile* aStart)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aStart);
+ if (!file)
+ return false;
+
+ nsCOMPtr<nsIFile> parent;
+ nsresult rv = file->GetParent(getter_AddRefs(parent));
+ VerifyResult(rv, "GetParent");
+
+ bool equal;
+ rv = parent->Equals(aBase, &equal);
+ VerifyResult(rv, "Equals");
+ EXPECT_TRUE(equal) << "Incorrect parent";
+ if (!equal) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::Normalize and native path setting/getting
+static bool TestNormalizeNativePath(nsIFile* aBase, nsIFile* aStart)
+{
+ nsCOMPtr<nsIFile> file = NewFile(aStart);
+ if (!file)
+ return false;
+
+ nsAutoCString path;
+ nsresult rv = file->GetNativePath(path);
+ VerifyResult(rv, "GetNativePath");
+ path.Append(FixName("/./.."));
+ rv = file->InitWithNativePath(path);
+ VerifyResult(rv, "InitWithNativePath");
+ rv = file->Normalize();
+ VerifyResult(rv, "Normalize");
+ rv = file->GetNativePath(path);
+ VerifyResult(rv, "GetNativePath (after normalization)");
+
+ nsAutoCString basePath;
+ rv = aBase->GetNativePath(basePath);
+ VerifyResult(rv, "GetNativePath (base)");
+
+ EXPECT_TRUE(path.Equals(basePath)) << "Incorrect normalization: " << path.get() << " - " << basePath.get();
+ if (!path.Equals(basePath)) {
+ return false;
+ }
+
+ return true;
+}
+
+TEST(TestFile, Tests)
+{
+ nsCOMPtr<nsIFile> base;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ ASSERT_TRUE(VerifyResult(rv, "Getting temp directory"));
+
+ rv = base->AppendNative(nsDependentCString("mozfiletests"));
+ ASSERT_TRUE(VerifyResult(rv, "Appending mozfiletests to temp directory name"));
+
+ // Remove the directory in case tests failed and left it behind.
+ // don't check result since it might not be there
+ base->Remove(true);
+
+ // Now create the working directory we're going to use
+ rv = base->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ ASSERT_TRUE(VerifyResult(rv, "Creating temp directory"));
+
+ // Now we can safely normalize the path
+ rv = base->Normalize();
+ ASSERT_TRUE(VerifyResult(rv, "Normalizing temp directory name"));
+
+ // Initialize subdir object for later use
+ nsCOMPtr<nsIFile> subdir = NewFile(base);
+ ASSERT_TRUE(subdir);
+
+ rv = subdir->AppendNative(nsDependentCString("subdir"));
+ ASSERT_TRUE(VerifyResult(rv, "Appending 'subdir' to test dir name"));
+
+ // ---------------
+ // End setup code.
+ // ---------------
+
+ // Test path parsing
+ ASSERT_TRUE(TestInvalidFileName(base, "a/b"));
+ ASSERT_TRUE(TestParent(base, subdir));
+
+ // Test file creation
+ ASSERT_TRUE(TestCreate(base, "file.txt", nsIFile::NORMAL_FILE_TYPE, 0600));
+ ASSERT_TRUE(TestRemove(base, "file.txt", false));
+
+ // Test directory creation
+ ASSERT_TRUE(TestCreate(base, "subdir", nsIFile::DIRECTORY_TYPE, 0700));
+
+ // Test move and copy in the base directory
+ ASSERT_TRUE(TestCreate(base, "file.txt", nsIFile::NORMAL_FILE_TYPE, 0600));
+ ASSERT_TRUE(TestMove(base, base, "file.txt", "file2.txt"));
+ ASSERT_TRUE(TestCopy(base, base, "file2.txt", "file3.txt"));
+
+ // Test moving across directories
+ ASSERT_TRUE(TestMove(base, subdir, "file2.txt", "file2.txt"));
+
+ // Test moving across directories and renaming at the same time
+ ASSERT_TRUE(TestMove(subdir, base, "file2.txt", "file4.txt"));
+
+ // Test copying across directoreis
+ ASSERT_TRUE(TestCopy(base, subdir, "file4.txt", "file5.txt"));
+
+ // Run normalization tests while the directory exists
+ ASSERT_TRUE(TestNormalizeNativePath(base, subdir));
+
+ // Test recursive directory removal
+ ASSERT_TRUE(TestRemove(base, "subdir", true));
+
+ ASSERT_TRUE(TestCreateUnique(base, "foo", nsIFile::NORMAL_FILE_TYPE, 0600));
+ ASSERT_TRUE(TestCreateUnique(base, "foo", nsIFile::NORMAL_FILE_TYPE, 0600));
+ ASSERT_TRUE(TestCreateUnique(base, "bar.xx", nsIFile::DIRECTORY_TYPE, 0700));
+ ASSERT_TRUE(TestCreateUnique(base, "bar.xx", nsIFile::DIRECTORY_TYPE, 0700));
+
+ ASSERT_TRUE(TestDeleteOnClose(base, "file7.txt", PR_RDWR | PR_CREATE_FILE, 0600));
+
+ // Clean up temporary stuff
+ rv = base->Remove(true);
+ VerifyResult(rv, "Cleaning up temp directory");
+}
diff --git a/xpcom/tests/gtest/TestHashtables.cpp b/xpcom/tests/gtest/TestHashtables.cpp
new file mode 100644
index 000000000..394812631
--- /dev/null
+++ b/xpcom/tests/gtest/TestHashtables.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsTHashtable.h"
+#include "nsBaseHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsInterfaceHashtable.h"
+#include "nsClassHashtable.h"
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsCOMArray.h"
+#include "mozilla/Attributes.h"
+
+#include "gtest/gtest.h"
+
+namespace TestHashtables {
+
+class TestUniChar // for nsClassHashtable
+{
+public:
+ explicit TestUniChar(uint32_t aWord)
+ {
+ mWord = aWord;
+ }
+
+ ~TestUniChar()
+ {
+ }
+
+ uint32_t GetChar() const { return mWord; }
+
+private:
+ uint32_t mWord;
+};
+
+struct EntityNode {
+ const char* mStr; // never owns buffer
+ uint32_t mUnicode;
+};
+
+EntityNode gEntities[] = {
+ {"nbsp",160},
+ {"iexcl",161},
+ {"cent",162},
+ {"pound",163},
+ {"curren",164},
+ {"yen",165},
+ {"brvbar",166},
+ {"sect",167},
+ {"uml",168},
+ {"copy",169},
+ {"ordf",170},
+ {"laquo",171},
+ {"not",172},
+ {"shy",173},
+ {"reg",174},
+ {"macr",175}
+};
+
+#define ENTITY_COUNT (unsigned(sizeof(gEntities)/sizeof(EntityNode)))
+
+class EntityToUnicodeEntry : public PLDHashEntryHdr
+{
+public:
+ typedef const char* KeyType;
+ typedef const char* KeyTypePointer;
+
+ explicit EntityToUnicodeEntry(const char* aKey) { mNode = nullptr; }
+ EntityToUnicodeEntry(const EntityToUnicodeEntry& aEntry) { mNode = aEntry.mNode; }
+ ~EntityToUnicodeEntry() { }
+
+ bool KeyEquals(const char* aEntity) const { return !strcmp(mNode->mStr, aEntity); }
+ static const char* KeyToPointer(const char* aEntity) { return aEntity; }
+ static PLDHashNumber HashKey(const char* aEntity) { return mozilla::HashString(aEntity); }
+ enum { ALLOW_MEMMOVE = true };
+
+ const EntityNode* mNode;
+};
+
+static uint32_t
+nsTIterPrint(nsTHashtable<EntityToUnicodeEntry>& hash)
+{
+ uint32_t n = 0;
+ for (auto iter = hash.Iter(); !iter.Done(); iter.Next()) {
+ n++;
+ }
+ return n;
+}
+
+static uint32_t
+nsTIterPrintRemove(nsTHashtable<EntityToUnicodeEntry>& hash)
+{
+ uint32_t n = 0;
+ for (auto iter = hash.Iter(); !iter.Done(); iter.Next()) {
+ iter.Remove();
+ n++;
+ }
+ return n;
+}
+
+void
+testTHashtable(nsTHashtable<EntityToUnicodeEntry>& hash, uint32_t numEntries) {
+ uint32_t i;
+ for (i = 0; i < numEntries; ++i) {
+ EntityToUnicodeEntry* entry =
+ hash.PutEntry(gEntities[i].mStr);
+
+ EXPECT_TRUE(entry);
+
+ EXPECT_FALSE(entry->mNode);
+ entry->mNode = &gEntities[i];
+ }
+
+ for (i = 0; i < numEntries; ++i) {
+ EntityToUnicodeEntry* entry =
+ hash.GetEntry(gEntities[i].mStr);
+
+ EXPECT_TRUE(entry);
+ }
+
+ EntityToUnicodeEntry* entry =
+ hash.GetEntry("xxxy");
+
+ EXPECT_FALSE(entry);
+
+ uint32_t count = nsTIterPrint(hash);
+ EXPECT_EQ(count, numEntries);
+}
+
+//
+// all this nsIFoo stuff was copied wholesale from TestCOMPtr.cpp
+//
+
+#define NS_IFOO_IID \
+{ 0x6f7652e0, 0xee43, 0x11d1, \
+ { 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 } }
+
+class IFoo final : public nsISupports
+ {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID)
+
+ IFoo();
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+ NS_IMETHOD QueryInterface( const nsIID&, void** );
+
+ NS_IMETHOD SetString(const nsACString& /*in*/ aString);
+ NS_IMETHOD GetString(nsACString& /*out*/ aString);
+
+ static void print_totals();
+
+ private:
+ ~IFoo();
+
+ unsigned int refcount_;
+
+ static unsigned int total_constructions_;
+ static unsigned int total_destructions_;
+ nsCString mString;
+ };
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID)
+
+unsigned int IFoo::total_constructions_;
+unsigned int IFoo::total_destructions_;
+
+void
+IFoo::print_totals()
+ {
+ }
+
+IFoo::IFoo()
+ : refcount_(0)
+ {
+ ++total_constructions_;
+ }
+
+IFoo::~IFoo()
+ {
+ ++total_destructions_;
+ }
+
+MozExternalRefCountType
+IFoo::AddRef()
+ {
+ ++refcount_;
+ return refcount_;
+ }
+
+MozExternalRefCountType
+IFoo::Release()
+ {
+ int newcount = --refcount_;
+ if ( newcount == 0 )
+ {
+ delete this;
+ }
+
+ return newcount;
+ }
+
+nsresult
+IFoo::QueryInterface( const nsIID& aIID, void** aResult )
+ {
+ nsISupports* rawPtr = 0;
+ nsresult status = NS_OK;
+
+ if ( aIID.Equals(NS_GET_IID(IFoo)) )
+ rawPtr = this;
+ else
+ {
+ nsID iid_of_ISupports = NS_ISUPPORTS_IID;
+ if ( aIID.Equals(iid_of_ISupports) )
+ rawPtr = static_cast<nsISupports*>(this);
+ else
+ status = NS_ERROR_NO_INTERFACE;
+ }
+
+ NS_IF_ADDREF(rawPtr);
+ *aResult = rawPtr;
+
+ return status;
+ }
+
+nsresult
+IFoo::SetString(const nsACString& aString)
+{
+ mString = aString;
+ return NS_OK;
+}
+
+nsresult
+IFoo::GetString(nsACString& aString)
+{
+ aString = mString;
+ return NS_OK;
+}
+
+nsresult
+CreateIFoo( IFoo** result )
+ // a typical factory function (that calls AddRef)
+ {
+ IFoo* foop = new IFoo();
+
+ foop->AddRef();
+ *result = foop;
+
+ return NS_OK;
+ }
+
+} // namespace TestHashtables
+
+using namespace TestHashtables;
+
+TEST(Hashtable, THashtable)
+{
+ // check an nsTHashtable
+ nsTHashtable<EntityToUnicodeEntry> EntityToUnicode(ENTITY_COUNT);
+
+ testTHashtable(EntityToUnicode, 5);
+
+ uint32_t count = nsTIterPrintRemove(EntityToUnicode);
+ ASSERT_EQ(count, uint32_t(5));
+
+ count = nsTIterPrint(EntityToUnicode);
+ ASSERT_EQ(count, uint32_t(0));
+
+ testTHashtable(EntityToUnicode, ENTITY_COUNT);
+
+ EntityToUnicode.Clear();
+
+ count = nsTIterPrint(EntityToUnicode);
+ ASSERT_EQ(count, uint32_t(0));
+}
+
+TEST(Hashtables, DataHashtable)
+{
+ // check a data-hashtable
+ nsDataHashtable<nsUint32HashKey,const char*> UniToEntity(ENTITY_COUNT);
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ UniToEntity.Put(gEntities[i].mUnicode, gEntities[i].mStr);
+ }
+
+ const char* str;
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ ASSERT_TRUE(UniToEntity.Get(gEntities[i].mUnicode, &str));
+ }
+
+ ASSERT_FALSE(UniToEntity.Get(99446, &str));
+
+ uint32_t count = 0;
+ for (auto iter = UniToEntity.Iter(); !iter.Done(); iter.Next()) {
+ count++;
+ }
+ ASSERT_EQ(count, ENTITY_COUNT);
+
+ UniToEntity.Clear();
+
+ count = 0;
+ for (auto iter = UniToEntity.Iter(); !iter.Done(); iter.Next()) {
+ printf(" enumerated %u = \"%s\"\n", iter.Key(), iter.Data());
+ count++;
+ }
+ ASSERT_EQ(count, uint32_t(0));
+}
+
+TEST(Hashtables, ClassHashtable)
+{
+ // check a class-hashtable
+ nsClassHashtable<nsCStringHashKey,TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ TestUniChar* temp = new TestUniChar(gEntities[i].mUnicode);
+ EntToUniClass.Put(nsDependentCString(gEntities[i].mStr), temp);
+ }
+
+ TestUniChar* myChar;
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(gEntities[i].mStr), &myChar));
+ }
+
+ ASSERT_FALSE(EntToUniClass.Get(NS_LITERAL_CSTRING("xxxx"), &myChar));
+
+ uint32_t count = 0;
+ for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) {
+ count++;
+ }
+ ASSERT_EQ(count, ENTITY_COUNT);
+
+ EntToUniClass.Clear();
+
+ count = 0;
+ for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) {
+ count++;
+ }
+ ASSERT_EQ(count, uint32_t(0));
+}
+
+TEST(Hashtables, DataHashtableWithInterfaceKey)
+{
+ // check a data-hashtable with an interface key
+ nsDataHashtable<nsISupportsHashKey,uint32_t> EntToUniClass2(ENTITY_COUNT);
+
+ nsCOMArray<IFoo> fooArray;
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ nsCOMPtr<IFoo> foo;
+ CreateIFoo(getter_AddRefs(foo));
+ foo->SetString(nsDependentCString(gEntities[i].mStr));
+
+ fooArray.InsertObjectAt(foo, i);
+
+ EntToUniClass2.Put(foo, gEntities[i].mUnicode);
+ }
+
+ uint32_t myChar2;
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ ASSERT_TRUE(EntToUniClass2.Get(fooArray[i], &myChar2));
+ }
+
+ ASSERT_FALSE(EntToUniClass2.Get((nsISupports*) 0x55443316, &myChar2));
+
+ uint32_t count = 0;
+ for (auto iter = EntToUniClass2.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString s;
+ nsCOMPtr<IFoo> foo = do_QueryInterface(iter.Key());
+ foo->GetString(s);
+ count++;
+ }
+ ASSERT_EQ(count, ENTITY_COUNT);
+
+ EntToUniClass2.Clear();
+
+ count = 0;
+ for (auto iter = EntToUniClass2.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString s;
+ nsCOMPtr<IFoo> foo = do_QueryInterface(iter.Key());
+ foo->GetString(s);
+ count++;
+ }
+ ASSERT_EQ(count, uint32_t(0));
+}
+
+TEST(Hashtables, InterfaceHashtable)
+{
+ // check an interface-hashtable with an uint32_t key
+ nsInterfaceHashtable<nsUint32HashKey,IFoo> UniToEntClass2(ENTITY_COUNT);
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ nsCOMPtr<IFoo> foo;
+ CreateIFoo(getter_AddRefs(foo));
+ foo->SetString(nsDependentCString(gEntities[i].mStr));
+
+ UniToEntClass2.Put(gEntities[i].mUnicode, foo);
+ }
+
+ for (uint32_t i = 0; i < ENTITY_COUNT; ++i) {
+ nsCOMPtr<IFoo> myEnt;
+ ASSERT_TRUE(UniToEntClass2.Get(gEntities[i].mUnicode, getter_AddRefs(myEnt)));
+
+ nsAutoCString myEntStr;
+ myEnt->GetString(myEntStr);
+ }
+
+ nsCOMPtr<IFoo> myEnt;
+ ASSERT_FALSE(UniToEntClass2.Get(9462, getter_AddRefs(myEnt)));
+
+ uint32_t count = 0;
+ for (auto iter = UniToEntClass2.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString s;
+ iter.UserData()->GetString(s);
+ count++;
+ }
+ ASSERT_EQ(count, ENTITY_COUNT);
+
+ UniToEntClass2.Clear();
+
+ count = 0;
+ for (auto iter = UniToEntClass2.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoCString s;
+ iter.Data()->GetString(s);
+ count++;
+ }
+ ASSERT_EQ(count, uint32_t(0));
+}
diff --git a/xpcom/tests/gtest/TestID.cpp b/xpcom/tests/gtest/TestID.cpp
new file mode 100644
index 000000000..cb56554dc
--- /dev/null
+++ b/xpcom/tests/gtest/TestID.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsID.h"
+
+#include "gtest/gtest.h"
+
+static const char* const ids[] = {
+ "5C347B10-D55C-11D1-89B7-006008911B81",
+ "{5C347B10-D55C-11D1-89B7-006008911B81}",
+ "5c347b10-d55c-11d1-89b7-006008911b81",
+ "{5c347b10-d55c-11d1-89b7-006008911b81}",
+
+ "FC347B10-D55C-F1D1-F9B7-006008911B81",
+ "{FC347B10-D55C-F1D1-F9B7-006008911B81}",
+ "fc347b10-d55c-f1d1-f9b7-006008911b81",
+ "{fc347b10-d55c-f1d1-f9b7-006008911b81}",
+};
+#define NUM_IDS ((int) (sizeof(ids) / sizeof(ids[0])))
+
+TEST(nsID, StringConversion)
+{
+ nsID id;
+ for (int i = 0; i < NUM_IDS; i++) {
+ const char* idstr = ids[i];
+ ASSERT_TRUE(id.Parse(idstr));
+
+ char* cp = id.ToString();
+ ASSERT_NE(cp, nullptr);
+ ASSERT_STREQ(cp, ids[4*(i/4) + 3]);
+
+ free(cp);
+ }
+}
diff --git a/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp
new file mode 100644
index 000000000..4fd924175
--- /dev/null
+++ b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "NSPRLogModulesParser.h"
+#include "mozilla/ArrayUtils.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+TEST(NSPRLogModulesParser, Empty)
+{
+ bool callbackInvoked = false;
+ auto callback = [&](const char*, mozilla::LogLevel, int32_t) mutable { callbackInvoked = true; };
+
+ mozilla::NSPRLogModulesParser(nullptr, callback);
+ EXPECT_FALSE(callbackInvoked);
+
+ mozilla::NSPRLogModulesParser("", callback);
+ EXPECT_FALSE(callbackInvoked);
+}
+
+TEST(NSPRLogModulesParser, DefaultLevel)
+{
+ bool callbackInvoked = false;
+ auto callback =
+ [&](const char* aName, mozilla::LogLevel aLevel, int32_t) {
+ EXPECT_STREQ("Foo", aName);
+ EXPECT_EQ(mozilla::LogLevel::Error, aLevel);
+ callbackInvoked = true;
+ };
+
+ mozilla::NSPRLogModulesParser("Foo", callback);
+ EXPECT_TRUE(callbackInvoked);
+
+ callbackInvoked = false;
+ mozilla::NSPRLogModulesParser("Foo:", callback);
+ EXPECT_TRUE(callbackInvoked);
+}
+
+TEST(NSPRLogModulesParser, LevelSpecified)
+{
+ std::pair<const char*, mozilla::LogLevel> expected[] = {
+ { "Foo:0", mozilla::LogLevel::Disabled },
+ { "Foo:1", mozilla::LogLevel::Error },
+ { "Foo:2", mozilla::LogLevel::Warning },
+ { "Foo:3", mozilla::LogLevel::Info },
+ { "Foo:4", mozilla::LogLevel::Debug },
+ { "Foo:5", mozilla::LogLevel::Verbose },
+ { "Foo:25", mozilla::LogLevel::Verbose }, // too high
+ { "Foo:-12", mozilla::LogLevel::Disabled } // too low
+ };
+
+ auto* currTest = expected;
+
+ for (size_t i = 0; i < MOZ_ARRAY_LENGTH(expected); i++) {
+ bool callbackInvoked = false;
+ mozilla::NSPRLogModulesParser(currTest->first,
+ [&](const char* aName, mozilla::LogLevel aLevel, int32_t) {
+ EXPECT_STREQ("Foo", aName);
+ EXPECT_EQ(currTest->second, aLevel);
+ callbackInvoked = true;
+ });
+ EXPECT_TRUE(callbackInvoked);
+ currTest++;
+ }
+}
+
+TEST(NSPRLogModulesParser, Multiple)
+{
+ std::pair<const char*, mozilla::LogLevel> expected[] = {
+ { "timestamp", mozilla::LogLevel::Error },
+ { "Foo", mozilla::LogLevel::Info },
+ { "Bar", mozilla::LogLevel::Error },
+ { "Baz", mozilla::LogLevel::Warning },
+ { "Qux", mozilla::LogLevel::Verbose },
+ };
+
+ const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected);
+
+ auto* currTest = expected;
+
+ size_t count = 0;
+ mozilla::NSPRLogModulesParser("timestamp,Foo:3, Bar,Baz:2, Qux:5",
+ [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable {
+ ASSERT_LT(count, kExpectedCount);
+ EXPECT_STREQ(currTest->first, aName);
+ EXPECT_EQ(currTest->second, aLevel);
+ currTest++;
+ count++;
+ });
+
+ EXPECT_EQ(kExpectedCount, count);
+}
+
+TEST(NSPRLogModulesParser, RawArg)
+{
+ bool callbackInvoked = false;
+ auto callback =
+ [&](const char* aName, mozilla::LogLevel aLevel, int32_t aRawValue) {
+ EXPECT_STREQ("Foo", aName);
+ EXPECT_EQ(mozilla::LogLevel::Verbose, aLevel);
+ EXPECT_EQ(1000, aRawValue);
+ callbackInvoked = true;
+ };
+
+ mozilla::NSPRLogModulesParser("Foo:1000", callback);
+ EXPECT_TRUE(callbackInvoked);
+}
diff --git a/xpcom/tests/gtest/TestNsRefPtr.cpp b/xpcom/tests/gtest/TestNsRefPtr.cpp
new file mode 100644
index 000000000..a085c2966
--- /dev/null
+++ b/xpcom/tests/gtest/TestNsRefPtr.cpp
@@ -0,0 +1,479 @@
+/* -*- 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 "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsQueryObject.h"
+#include "mozilla/Unused.h"
+
+#include "gtest/gtest.h"
+
+namespace TestNsRefPtr
+{
+
+#define NS_FOO_IID \
+{ 0x6f7652e0, 0xee43, 0x11d1, \
+ { 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 } }
+
+class Foo : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_FOO_IID)
+
+public:
+ Foo();
+ // virtual dtor because Bar uses our Release()
+ virtual ~Foo();
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+ NS_IMETHOD QueryInterface( const nsIID&, void** );
+ void MemberFunction( int, int*, int& );
+ virtual void VirtualMemberFunction( int, int*, int& );
+ virtual void VirtualConstMemberFunction( int, int*, int& ) const;
+
+ void NonconstMethod() {}
+ void ConstMethod() const {}
+
+ int refcount_;
+
+ static int total_constructions_;
+ static int total_destructions_;
+ static int total_addrefs_;
+ static int total_queries_;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Foo, NS_FOO_IID)
+
+int Foo::total_constructions_;
+int Foo::total_destructions_;
+int Foo::total_addrefs_;
+int Foo::total_queries_;
+
+Foo::Foo()
+ : refcount_(0)
+{
+ ++total_constructions_;
+}
+
+Foo::~Foo()
+{
+ ++total_destructions_;
+}
+
+MozExternalRefCountType
+Foo::AddRef()
+{
+ ++refcount_;
+ ++total_addrefs_;
+ return refcount_;
+}
+
+MozExternalRefCountType
+Foo::Release()
+{
+ int newcount = --refcount_;
+ if ( newcount == 0 )
+ {
+ delete this;
+ }
+
+ return newcount;
+}
+
+nsresult
+Foo::QueryInterface( const nsIID& aIID, void** aResult )
+{
+ ++total_queries_;
+
+ nsISupports* rawPtr = 0;
+ nsresult status = NS_OK;
+
+ if ( aIID.Equals(NS_GET_IID(Foo)) )
+ rawPtr = this;
+ else
+ {
+ nsID iid_of_ISupports = NS_ISUPPORTS_IID;
+ if ( aIID.Equals(iid_of_ISupports) )
+ rawPtr = static_cast<nsISupports*>(this);
+ else
+ status = NS_ERROR_NO_INTERFACE;
+ }
+
+ NS_IF_ADDREF(rawPtr);
+ *aResult = rawPtr;
+
+ return status;
+}
+
+void
+Foo::MemberFunction( int aArg1, int* aArgPtr, int& aArgRef )
+{
+}
+
+void
+Foo::VirtualMemberFunction( int aArg1, int* aArgPtr, int& aArgRef )
+{
+}
+
+void
+Foo::VirtualConstMemberFunction( int aArg1, int* aArgPtr, int& aArgRef ) const
+{
+}
+
+nsresult
+CreateFoo( void** result )
+ // a typical factory function (that calls AddRef)
+{
+ Foo* foop = new Foo;
+
+ foop->AddRef();
+ *result = foop;
+
+ return NS_OK;
+}
+
+void
+set_a_Foo( RefPtr<Foo>* result )
+{
+ assert(result);
+
+ RefPtr<Foo> foop( do_QueryObject(new Foo) );
+ *result = foop;
+}
+
+RefPtr<Foo>
+return_a_Foo()
+{
+ RefPtr<Foo> foop( do_QueryObject(new Foo) );
+ return foop;
+}
+
+#define NS_BAR_IID \
+{ 0x6f7652e1, 0xee43, 0x11d1, \
+ { 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 } }
+
+class Bar : public Foo
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_BAR_IID)
+
+public:
+ Bar();
+ virtual ~Bar();
+
+ NS_IMETHOD QueryInterface( const nsIID&, void** ) override;
+
+ virtual void VirtualMemberFunction( int, int*, int& ) override;
+ virtual void VirtualConstMemberFunction( int, int*, int& ) const override;
+
+ static int total_constructions_;
+ static int total_destructions_;
+ static int total_queries_;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Bar, NS_BAR_IID)
+
+int Bar::total_constructions_;
+int Bar::total_destructions_;
+int Bar::total_queries_;
+
+Bar::Bar()
+{
+ ++total_constructions_;
+}
+
+Bar::~Bar()
+{
+ ++total_destructions_;
+}
+
+nsresult
+Bar::QueryInterface( const nsID& aIID, void** aResult )
+{
+ ++total_queries_;
+
+ nsISupports* rawPtr = 0;
+ nsresult status = NS_OK;
+
+ if ( aIID.Equals(NS_GET_IID(Bar)) )
+ rawPtr = this;
+ else if ( aIID.Equals(NS_GET_IID(Foo)) )
+ rawPtr = static_cast<Foo*>(this);
+ else
+ {
+ nsID iid_of_ISupports = NS_ISUPPORTS_IID;
+ if ( aIID.Equals(iid_of_ISupports) )
+ rawPtr = static_cast<nsISupports*>(this);
+ else
+ status = NS_ERROR_NO_INTERFACE;
+ }
+
+ NS_IF_ADDREF(rawPtr);
+ *aResult = rawPtr;
+
+ return status;
+}
+
+void
+Bar::VirtualMemberFunction( int aArg1, int* aArgPtr, int& aArgRef )
+{
+}
+void
+Bar::VirtualConstMemberFunction( int aArg1, int* aArgPtr, int& aArgRef ) const
+{
+}
+
+} // namespace TestNsRefPtr
+
+using namespace TestNsRefPtr;
+
+TEST(nsRefPtr, AddRefAndRelease)
+{
+ Foo::total_constructions_ = 0;
+ Foo::total_destructions_ = 0;
+
+ {
+ RefPtr<Foo> foop( do_QueryObject(new Foo) );
+ ASSERT_EQ(Foo::total_constructions_, 1);
+ ASSERT_EQ(Foo::total_destructions_, 0);
+ ASSERT_EQ(foop->refcount_, 1);
+
+ foop = do_QueryObject(new Foo);
+ ASSERT_EQ(Foo::total_constructions_, 2);
+ ASSERT_EQ(Foo::total_destructions_, 1);
+
+ // [Shouldn't compile] Is it a compile time error to try to |AddRef| by hand?
+ //foop->AddRef();
+
+ // [Shouldn't compile] Is it a compile time error to try to |Release| be hand?
+ //foop->Release();
+
+ // [Shouldn't compile] Is it a compile time error to try to |delete| an |nsCOMPtr|?
+ //delete foop;
+
+ static_cast<Foo*>(foop)->AddRef();
+ ASSERT_EQ(foop->refcount_, 2);
+
+ static_cast<Foo*>(foop)->Release();
+ ASSERT_EQ(foop->refcount_, 1);
+ }
+
+ ASSERT_EQ(Foo::total_destructions_, 2);
+
+ {
+ RefPtr<Foo> fooP( do_QueryObject(new Foo) );
+ ASSERT_EQ(Foo::total_constructions_, 3);
+ ASSERT_EQ(Foo::total_destructions_, 2);
+ ASSERT_EQ(fooP->refcount_, 1);
+
+ Foo::total_addrefs_ = 0;
+ RefPtr<Foo> fooP2( fooP.forget() );
+ ASSERT_EQ(Foo::total_addrefs_, 0);
+ }
+}
+
+TEST(nsRefPtr, VirtualDestructor)
+{
+ Bar::total_destructions_ = 0;
+
+ {
+ RefPtr<Foo> foop( do_QueryObject(new Bar) );
+ mozilla::Unused << foop;
+ }
+
+ ASSERT_EQ(Bar::total_destructions_, 1);
+}
+
+TEST(nsRefPtr, Equality)
+{
+ Foo::total_constructions_ = 0;
+ Foo::total_destructions_ = 0;
+
+ {
+ RefPtr<Foo> foo1p( do_QueryObject(new Foo) );
+ RefPtr<Foo> foo2p( do_QueryObject(new Foo) );
+
+ ASSERT_EQ(Foo::total_constructions_, 2);
+ ASSERT_EQ(Foo::total_destructions_, 0);
+
+ ASSERT_NE(foo1p, foo2p);
+
+ ASSERT_NE(foo1p, nullptr);
+ ASSERT_NE(nullptr, foo1p);
+ ASSERT_FALSE(foo1p == nullptr);
+ ASSERT_FALSE(nullptr == foo1p);
+
+ ASSERT_NE(foo1p, foo2p.get());
+
+ foo1p = foo2p;
+
+ ASSERT_EQ(Foo::total_constructions_, 2);
+ ASSERT_EQ(Foo::total_destructions_, 1);
+ ASSERT_EQ(foo1p, foo2p);
+
+ ASSERT_EQ(foo2p, foo2p.get());
+
+ ASSERT_EQ(RefPtr<Foo>(foo2p.get()), foo2p);
+
+ ASSERT_TRUE(foo1p);
+ }
+
+ ASSERT_EQ(Foo::total_constructions_, 2);
+ ASSERT_EQ(Foo::total_destructions_, 2);
+}
+
+TEST(nsRefPtr, AddRefHelpers)
+{
+ Foo::total_addrefs_ = 0;
+
+ {
+ Foo* raw_foo1p = new Foo;
+ raw_foo1p->AddRef();
+
+ Foo* raw_foo2p = new Foo;
+ raw_foo2p->AddRef();
+
+ ASSERT_EQ(Foo::total_addrefs_, 2);
+
+ RefPtr<Foo> foo1p( dont_AddRef(raw_foo1p) );
+
+ ASSERT_EQ(Foo::total_addrefs_, 2);
+
+ RefPtr<Foo> foo2p;
+ foo2p = dont_AddRef(raw_foo2p);
+
+ ASSERT_EQ(Foo::total_addrefs_, 2);
+ }
+
+ {
+ // Test that various assignment helpers compile.
+ RefPtr<Foo> foop;
+ CreateFoo( RefPtrGetterAddRefs<Foo>(foop) );
+ CreateFoo( getter_AddRefs(foop) );
+ set_a_Foo(address_of(foop));
+ foop = return_a_Foo();
+ }
+}
+
+TEST(nsRefPtr, QueryInterface)
+{
+ Foo::total_queries_ = 0;
+ Bar::total_queries_ = 0;
+
+ {
+ RefPtr<Foo> fooP;
+ fooP = do_QueryObject(new Foo);
+ ASSERT_EQ(Foo::total_queries_, 1);
+ }
+
+ {
+ RefPtr<Foo> fooP;
+ fooP = do_QueryObject(new Foo);
+ ASSERT_EQ(Foo::total_queries_, 2);
+
+ RefPtr<Foo> foo2P;
+ foo2P = fooP;
+ ASSERT_EQ(Foo::total_queries_, 2);
+ }
+
+ {
+ RefPtr<Bar> barP( do_QueryObject(new Bar) );
+ ASSERT_EQ(Bar::total_queries_, 1);
+
+ RefPtr<Foo> fooP( do_QueryObject(barP) );
+ ASSERT_TRUE(fooP);
+ ASSERT_EQ(Foo::total_queries_, 2);
+ ASSERT_EQ(Bar::total_queries_, 2);
+ }
+}
+
+// -------------------------------------------------------------------------
+// TODO(ER): The following tests should be moved to MFBT.
+
+#define NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(_class) \
+public: \
+NS_METHOD_(MozExternalRefCountType) AddRef(void) const { \
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \
+ MOZ_RELEASE_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ nsrefcnt count = ++mRefCnt; \
+ return (nsrefcnt) count; \
+} \
+NS_METHOD_(MozExternalRefCountType) Release(void) const { \
+ MOZ_RELEASE_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ nsrefcnt count = --mRefCnt; \
+ if (count == 0) { \
+ delete (this); \
+ return 0; \
+ } \
+ return count; \
+} \
+protected: \
+mutable ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \
+public:
+
+class ObjectForConstPtr
+{
+ private:
+ // Reference-counted classes cannot have public destructors.
+ ~ObjectForConstPtr()
+ {
+ }
+ public:
+ NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(ObjectForConstPtr)
+ void ConstMemberFunction( int aArg1, int* aArgPtr, int& aArgRef ) const
+ {
+ }
+};
+#undef NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING
+
+namespace TestNsRefPtr
+{
+void AnFooPtrPtrContext(Foo**) { }
+void AVoidPtrPtrContext(void**) { }
+} // namespace TestNsRefPtr
+
+TEST(nsRefPtr, RefPtrCompilationTests)
+{
+
+ {
+ RefPtr<Foo> fooP;
+
+ AnFooPtrPtrContext( getter_AddRefs(fooP) );
+ AVoidPtrPtrContext( getter_AddRefs(fooP) );
+ }
+
+ {
+ RefPtr<Foo> fooP(new Foo);
+ RefPtr<const Foo> constFooP = fooP;
+ constFooP->ConstMethod();
+
+ // [Shouldn't compile] Is it a compile time error to call a non-const method on an |RefPtr<const T>|?
+ //constFooP->NonconstMethod();
+
+ // [Shouldn't compile] Is it a compile time error to construct an |RefPtr<T> from an |RefPtr<const T>|?
+ //RefPtr<Foo> otherFooP(constFooP);
+ }
+
+ {
+ RefPtr<Foo> foop = new Foo;
+ RefPtr<Foo> foop2 = new Bar;
+ RefPtr<const ObjectForConstPtr> foop3 = new ObjectForConstPtr;
+ int test = 1;
+ void (Foo::*fPtr)( int, int*, int& ) = &Foo::MemberFunction;
+ void (Foo::*fVPtr)( int, int*, int& ) = &Foo::VirtualMemberFunction;
+ void (Foo::*fVCPtr)( int, int*, int& ) const = &Foo::VirtualConstMemberFunction;
+ void (ObjectForConstPtr::*fCPtr2)( int, int*, int& ) const = &ObjectForConstPtr::ConstMemberFunction;
+
+ (foop->*fPtr)(test, &test, test);
+ (foop2->*fVPtr)(test, &test, test);
+ (foop2->*fVCPtr)(test, &test, test);
+ (foop3->*fCPtr2)(test, &test, test);
+ }
+
+ // Looks like everything ran.
+ ASSERT_TRUE(true);
+}
diff --git a/xpcom/tests/gtest/TestObserverArray.cpp b/xpcom/tests/gtest/TestObserverArray.cpp
new file mode 100644
index 000000000..d907619be
--- /dev/null
+++ b/xpcom/tests/gtest/TestObserverArray.cpp
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=4:et:sw=4:
+/* 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 "nsTObserverArray.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+typedef nsTObserverArray<int> IntArray;
+
+#define DO_TEST(_type, _exp, _code) \
+ do { \
+ ++testNum; \
+ count = 0; \
+ IntArray::_type iter(arr); \
+ while (iter.HasMore() && count != ArrayLength(_exp)) { \
+ _code \
+ int next = iter.GetNext(); \
+ int expected = _exp[count++]; \
+ ASSERT_EQ(next, expected) << "During test " << testNum << " at position " << count - 1; \
+ } \
+ ASSERT_FALSE(iter.HasMore()) << "During test " << testNum << ", iterator ran over"; \
+ ASSERT_EQ(count, ArrayLength(_exp)) << "During test " << testNum << ", iterator finished too early"; \
+ } while (0)
+
+TEST(ObserverArray, Tests)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t count;
+ int testNum = 0;
+
+ // Basic sanity
+ static int test1Expected[] = { 3, 4 };
+ DO_TEST(ForwardIterator, test1Expected, { /* nothing */ });
+
+ // Appends
+ static int test2Expected[] = { 3, 4, 2 };
+ DO_TEST(ForwardIterator, test2Expected,
+ if (count == 1) arr.AppendElement(2);
+ );
+ DO_TEST(ForwardIterator, test2Expected, { /* nothing */ });
+
+ DO_TEST(EndLimitedIterator, test2Expected,
+ if (count == 1) arr.AppendElement(5);
+ );
+
+ static int test5Expected[] = { 3, 4, 2, 5 };
+ DO_TEST(ForwardIterator, test5Expected, { /* nothing */ });
+
+ // Removals
+ DO_TEST(ForwardIterator, test5Expected,
+ if (count == 1) arr.RemoveElementAt(0);
+ );
+
+ static int test7Expected[] = { 4, 2, 5 };
+ DO_TEST(ForwardIterator, test7Expected, { /* nothing */ });
+
+ static int test8Expected[] = { 4, 5 };
+ DO_TEST(ForwardIterator, test8Expected,
+ if (count == 1) arr.RemoveElementAt(1);
+ );
+ DO_TEST(ForwardIterator, test8Expected, { /* nothing */ });
+
+ arr.AppendElement(2);
+ arr.AppendElementUnlessExists(6);
+ static int test10Expected[] = { 4, 5, 2, 6 };
+ DO_TEST(ForwardIterator, test10Expected, { /* nothing */ });
+
+ arr.AppendElementUnlessExists(5);
+ DO_TEST(ForwardIterator, test10Expected, { /* nothing */ });
+
+ static int test12Expected[] = { 4, 5, 6 };
+ DO_TEST(ForwardIterator, test12Expected,
+ if (count == 1) arr.RemoveElementAt(2);
+ );
+ DO_TEST(ForwardIterator, test12Expected, { /* nothing */ });
+
+ // Removals + Appends
+ static int test14Expected[] = { 4, 6, 7 };
+ DO_TEST(ForwardIterator, test14Expected,
+ if (count == 1) {
+ arr.RemoveElementAt(1);
+ arr.AppendElement(7);
+ }
+ );
+ DO_TEST(ForwardIterator, test14Expected, { /* nothing */ });
+
+ arr.AppendElement(2);
+ static int test16Expected[] = { 4, 6, 7, 2 };
+ DO_TEST(ForwardIterator, test16Expected, { /* nothing */ });
+
+ static int test17Expected[] = { 4, 7, 2 };
+ DO_TEST(EndLimitedIterator, test17Expected,
+ if (count == 1) {
+ arr.RemoveElementAt(1);
+ arr.AppendElement(8);
+ }
+ );
+
+ static int test18Expected[] = { 4, 7, 2, 8 };
+ DO_TEST(ForwardIterator, test18Expected, { /* nothing */ });
+
+ // Prepends
+ arr.PrependElementUnlessExists(3);
+ static int test19Expected[] = { 3, 4, 7, 2, 8 };
+ DO_TEST(ForwardIterator, test19Expected, { /* nothing */ });
+
+ arr.PrependElementUnlessExists(7);
+ DO_TEST(ForwardIterator, test19Expected, { /* nothing */ });
+
+ DO_TEST(ForwardIterator, test19Expected,
+ if (count == 1) {
+ arr.PrependElementUnlessExists(9);
+ }
+ );
+
+ static int test22Expected[] = { 9, 3, 4, 7, 2, 8 };
+ DO_TEST(ForwardIterator, test22Expected, { });
+
+ // BackwardIterator
+ static int test23Expected[] = { 8, 2, 7, 4, 3, 9 };
+ DO_TEST(BackwardIterator, test23Expected, );
+
+ // Removals
+ static int test24Expected[] = { 8, 2, 7, 4, 9 };
+ DO_TEST(BackwardIterator, test24Expected,
+ if (count == 1) arr.RemoveElementAt(1);
+ );
+
+ // Appends
+ DO_TEST(BackwardIterator, test24Expected,
+ if (count == 1) arr.AppendElement(1);
+ );
+
+ static int test26Expected[] = { 1, 8, 2, 7, 4, 9 };
+ DO_TEST(BackwardIterator, test26Expected, );
+
+ // Prepends
+ static int test27Expected[] = { 1, 8, 2, 7, 4, 9, 3 };
+ DO_TEST(BackwardIterator, test27Expected,
+ if (count == 1) arr.PrependElementUnlessExists(3);
+ );
+
+ // Removal using Iterator
+ DO_TEST(BackwardIterator, test27Expected,
+ // when this code runs, |GetNext()| has only been called once, so
+ // this actually removes the very first element
+ if (count == 1) iter.Remove();
+ );
+
+ static int test28Expected[] = { 8, 2, 7, 4, 9, 3 };
+ DO_TEST(BackwardIterator, test28Expected, );
+
+ /**
+ * Note: _code is executed before the call to GetNext(), it can therefore not
+ * test the case of prepending when the BackwardIterator already returned the
+ * first element.
+ * In that case BackwardIterator does not traverse the newly prepended Element
+ */
+
+}
diff --git a/xpcom/tests/gtest/TestObserverService.cpp b/xpcom/tests/gtest/TestObserverService.cpp
new file mode 100644
index 000000000..d4eb51a7e
--- /dev/null
+++ b/xpcom/tests/gtest/TestObserverService.cpp
@@ -0,0 +1,288 @@
+/* -*- 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 "nsISupports.h"
+#include "nsIComponentManager.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsISimpleEnumerator.h"
+#include "nsComponentManagerUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+#include "mozilla/RefPtr.h"
+
+#include "gtest/gtest.h"
+
+static void testResult( nsresult rv ) {
+ EXPECT_TRUE(NS_SUCCEEDED(rv)) << "0x" << std::hex << (int)rv;
+}
+
+class TestObserver final : public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ explicit TestObserver( const nsAString &name )
+ : mName( name )
+ , mObservations( 0 ) {
+ }
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsString mName;
+ int mObservations;
+ static int sTotalObservations;
+
+ nsString mExpectedData;
+
+private:
+ ~TestObserver() {}
+};
+
+NS_IMPL_ISUPPORTS( TestObserver, nsIObserver, nsISupportsWeakReference )
+
+int TestObserver::sTotalObservations;
+
+NS_IMETHODIMP
+TestObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *someData ) {
+ mObservations++;
+ sTotalObservations++;
+
+ if (!mExpectedData.IsEmpty()) {
+ EXPECT_TRUE(mExpectedData.Equals(someData));
+ }
+
+ return NS_OK;
+}
+
+static nsISupports* ToSupports(TestObserver* aObs)
+{
+ return static_cast<nsIObserver*>(aObs);
+}
+
+static void TestExpectedCount(
+ nsIObserverService* svc,
+ const char* topic,
+ size_t expected)
+{
+ nsCOMPtr<nsISimpleEnumerator> e;
+ nsresult rv = svc->EnumerateObservers(topic, getter_AddRefs(e));
+ testResult(rv);
+ EXPECT_TRUE(e);
+
+ bool hasMore = false;
+ rv = e->HasMoreElements(&hasMore);
+ testResult(rv);
+
+ if (expected == 0) {
+ EXPECT_FALSE(hasMore);
+ return;
+ }
+
+ size_t count = 0;
+ while (hasMore) {
+ count++;
+
+ // Grab the element.
+ nsCOMPtr<nsISupports> supports;
+ e->GetNext(getter_AddRefs(supports));
+ ASSERT_TRUE(supports);
+
+ // Move on.
+ rv = e->HasMoreElements(&hasMore);
+ testResult(rv);
+ }
+
+ EXPECT_EQ(count, expected);
+}
+
+TEST(ObserverService, Creation)
+{
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1", &rv);
+
+ ASSERT_EQ(rv, NS_OK);
+ ASSERT_TRUE(svc);
+}
+
+TEST(ObserverService, AddObserver)
+{
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1");
+
+ // Add a strong ref.
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ nsresult rv = svc->AddObserver(a, "Foo", false);
+ testResult(rv);
+
+ // Add a few weak ref.
+ RefPtr<TestObserver> b = new TestObserver(NS_LITERAL_STRING("B"));
+ rv = svc->AddObserver(b, "Bar", true);
+ testResult(rv);
+}
+
+TEST(ObserverService, RemoveObserver)
+{
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1");
+
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ RefPtr<TestObserver> b = new TestObserver(NS_LITERAL_STRING("B"));
+ RefPtr<TestObserver> c = new TestObserver(NS_LITERAL_STRING("C"));
+
+ svc->AddObserver(a, "Foo", false);
+ svc->AddObserver(b, "Foo", true);
+
+ // Remove from non-existent topic.
+ nsresult rv = svc->RemoveObserver(a, "Bar");
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ // Remove a.
+ testResult(svc->RemoveObserver(a, "Foo"));
+
+ // Remove b.
+ testResult(svc->RemoveObserver(b, "Foo"));
+
+ // Attempt to remove c.
+ rv = svc->RemoveObserver(c, "Foo");
+ ASSERT_TRUE(NS_FAILED(rv));
+}
+
+TEST(ObserverService, EnumerateEmpty)
+{
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1");
+
+ // Try with no observers.
+ TestExpectedCount(svc, "A", 0);
+
+ // Now add an observer and enumerate an unobserved topic.
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ testResult(svc->AddObserver(a, "Foo", false));
+
+ TestExpectedCount(svc, "A", 0);
+}
+
+TEST(ObserverService, Enumerate)
+{
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1");
+
+ const size_t kFooCount = 10;
+ for (size_t i = 0; i < kFooCount; i++) {
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ testResult(svc->AddObserver(a, "Foo", false));
+ }
+
+ const size_t kBarCount = kFooCount / 2;
+ for (size_t i = 0; i < kBarCount; i++) {
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ testResult(svc->AddObserver(a, "Bar", false));
+ }
+
+ // Enumerate "Foo".
+ TestExpectedCount(svc, "Foo", kFooCount);
+
+ // Enumerate "Bar".
+ TestExpectedCount(svc, "Bar", kBarCount);
+}
+
+TEST(ObserverService, EnumerateWeakRefs)
+{
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1");
+
+ const size_t kFooCount = 10;
+ for (size_t i = 0; i < kFooCount; i++) {
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ testResult(svc->AddObserver(a, "Foo", true));
+ }
+
+ // All refs are out of scope, expect enumeration to be empty.
+ TestExpectedCount(svc, "Foo", 0);
+
+ // Now test a mixture.
+ for (size_t i = 0; i < kFooCount; i++) {
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ RefPtr<TestObserver> b = new TestObserver(NS_LITERAL_STRING("B"));
+
+ // Register a as weak for "Foo".
+ testResult(svc->AddObserver(a, "Foo", true));
+
+ // Register b as strong for "Foo".
+ testResult(svc->AddObserver(b, "Foo", false));
+ }
+
+ // Expect the b instances to stick around.
+ TestExpectedCount(svc, "Foo", kFooCount);
+
+ // Now add a couple weak refs, but don't go out of scope.
+ RefPtr<TestObserver> a = new TestObserver(NS_LITERAL_STRING("A"));
+ testResult(svc->AddObserver(a, "Foo", true));
+ RefPtr<TestObserver> b = new TestObserver(NS_LITERAL_STRING("B"));
+ testResult(svc->AddObserver(b, "Foo", true));
+
+ // Expect all the observers from before and the two new ones.
+ TestExpectedCount(svc, "Foo", kFooCount + 2);
+}
+
+TEST(ObserverService, TestNotify)
+{
+ nsCString topicA; topicA.Assign( "topic-A" );
+ nsCString topicB; topicB.Assign( "topic-B" );
+
+ nsCOMPtr<nsIObserverService> svc =
+ do_CreateInstance("@mozilla.org/observer-service;1");
+
+ RefPtr<TestObserver> aObserver = new TestObserver(NS_LITERAL_STRING("Observer-A"));
+ RefPtr<TestObserver> bObserver = new TestObserver(NS_LITERAL_STRING("Observer-B"));
+
+ // Add two observers for topicA.
+ testResult(svc->AddObserver(aObserver, topicA.get(), false));
+ testResult(svc->AddObserver(bObserver, topicA.get(), false));
+
+ // Add one observer for topicB.
+ testResult(svc->AddObserver(bObserver, topicB.get(), false));
+
+ // Notify topicA.
+ NS_NAMED_LITERAL_STRING(dataA, "Testing Notify(observer-A, topic-A)");
+ aObserver->mExpectedData = dataA;
+ bObserver->mExpectedData = dataA;
+ nsresult rv =
+ svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA.get());
+ testResult(rv);
+ ASSERT_EQ(aObserver->mObservations, 1);
+ ASSERT_EQ(bObserver->mObservations, 1);
+
+ // Notify topicB.
+ NS_NAMED_LITERAL_STRING(dataB, "Testing Notify(observer-B, topic-B)");
+ bObserver->mExpectedData = dataB;
+ rv = svc->NotifyObservers(ToSupports(bObserver), topicB.get(), dataB.get());
+ testResult(rv);
+ ASSERT_EQ(aObserver->mObservations, 1);
+ ASSERT_EQ(bObserver->mObservations, 2);
+
+ // Remove one of the topicA observers, make sure it's not notified.
+ testResult(svc->RemoveObserver(aObserver, topicA.get()));
+
+ // Notify topicA, only bObserver is expected to be notified.
+ bObserver->mExpectedData = dataA;
+ rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA.get());
+ testResult(rv);
+ ASSERT_EQ(aObserver->mObservations, 1);
+ ASSERT_EQ(bObserver->mObservations, 3);
+
+ // Remove the other topicA observer, make sure none are notified.
+ testResult(svc->RemoveObserver(bObserver, topicA.get()));
+ rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA.get());
+ testResult(rv);
+ ASSERT_EQ(aObserver->mObservations, 1);
+ ASSERT_EQ(bObserver->mObservations, 3);
+}
diff --git a/xpcom/tests/gtest/TestPLDHash.cpp b/xpcom/tests/gtest/TestPLDHash.cpp
new file mode 100644
index 000000000..e7a73ae1b
--- /dev/null
+++ b/xpcom/tests/gtest/TestPLDHash.cpp
@@ -0,0 +1,368 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "PLDHashTable.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "gtest/gtest.h"
+
+// This test mostly focuses on edge cases. But more coverage of normal
+// operations wouldn't be a bad thing.
+
+#ifdef XP_UNIX
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+// This global variable is defined in toolkit/xre/nsSigHandlers.cpp.
+extern unsigned int _gdb_sleep_duration;
+#endif
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsICrashReporter.h"
+#endif
+
+// We can test that certain operations cause expected aborts by forking
+// and then checking that the child aborted in the expected way (i.e. via
+// MOZ_CRASH). We skip this for the following configurations.
+// - On Windows, because it doesn't have fork().
+// - On non-DEBUG builds, because the crashes cause the crash reporter to pop
+// up when running this test locally, which is surprising and annoying.
+// - On ASAN builds, because ASAN alters the way a MOZ_CRASHing process
+// terminates, which makes it harder to test if the right thing has occurred.
+void
+TestCrashyOperation(void (*aCrashyOperation)())
+{
+#if defined(XP_UNIX) && defined(DEBUG) && !defined(MOZ_ASAN)
+ // We're about to trigger a crash. When it happens don't pause to allow GDB
+ // to be attached.
+ unsigned int old_gdb_sleep_duration = _gdb_sleep_duration;
+ _gdb_sleep_duration = 0;
+
+ int pid = fork();
+ ASSERT_NE(pid, -1);
+
+ if (pid == 0) {
+ // Disable the crashreporter -- writing a crash dump in the child will
+ // prevent the parent from writing a subsequent dump. Crashes here are
+ // expected, so we don't want their stacks to show up in the log anyway.
+#ifdef MOZ_CRASHREPORTER
+ nsCOMPtr<nsICrashReporter> crashreporter =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (crashreporter) {
+ crashreporter->SetEnabled(false);
+ }
+#endif
+
+ // Child: perform the crashy operation.
+ fprintf(stderr, "TestCrashyOperation: The following crash is expected. Do not panic.\n");
+ aCrashyOperation();
+ fprintf(stderr, "TestCrashyOperation: didn't crash?!\n");
+ ASSERT_TRUE(false); // shouldn't reach here
+ }
+
+ // Parent: check that child crashed as expected.
+ int status;
+ ASSERT_NE(waitpid(pid, &status, 0), -1);
+
+ // The path taken here depends on the platform and configuration.
+ ASSERT_TRUE(WIFEXITED(status) || WTERMSIG(status));
+ if (WIFEXITED(status)) {
+ // This occurs if the ah_crap_handler() is run, i.e. we caught the crash.
+ // It returns the number of the caught signal.
+ int signum = WEXITSTATUS(status);
+ if (signum != SIGSEGV && signum != SIGBUS) {
+ fprintf(stderr, "TestCrashyOperation 'exited' failure: %d\n", signum);
+ ASSERT_TRUE(false);
+ }
+ } else if (WIFSIGNALED(status)) {
+ // This one occurs if we didn't catch the crash. The exit code is the
+ // number of the terminating signal.
+ int signum = WTERMSIG(status);
+ if (signum != SIGSEGV && signum != SIGBUS) {
+ fprintf(stderr, "TestCrashyOperation 'signaled' failure: %d\n", signum);
+ ASSERT_TRUE(false);
+ }
+ }
+
+ _gdb_sleep_duration = old_gdb_sleep_duration;
+#endif
+}
+
+void
+InitCapacityOk_InitialLengthTooBig()
+{
+ PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub),
+ PLDHashTable::kMaxInitialLength + 1);
+}
+
+void
+InitCapacityOk_InitialEntryStoreTooBig()
+{
+ // Try the smallest disallowed power-of-two entry store size, which is 2^32
+ // bytes (which overflows to 0). (Note that the 2^23 *length* gets converted
+ // to a 2^24 *capacity*.)
+ PLDHashTable t(PLDHashTable::StubOps(), (uint32_t)1 << 23, (uint32_t)1 << 8);
+}
+
+TEST(PLDHashTableTest, InitCapacityOk)
+{
+ // Try the largest allowed capacity. With kMaxCapacity==1<<26, this
+ // would allocate (if we added an element) 0.5GB of entry store on 32-bit
+ // platforms and 1GB on 64-bit platforms.
+ PLDHashTable t1(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub),
+ PLDHashTable::kMaxInitialLength);
+
+ // Try the largest allowed power-of-two entry store size, which is 2^31 bytes
+ // (Note that the 2^23 *length* gets converted to a 2^24 *capacity*.)
+ PLDHashTable t2(PLDHashTable::StubOps(), (uint32_t)1 << 23, (uint32_t)1 << 7);
+
+ // Try a too-large capacity (which aborts).
+ TestCrashyOperation(InitCapacityOk_InitialLengthTooBig);
+
+ // Try a large capacity combined with a large entry size that when multiplied
+ // overflow (causing abort).
+ TestCrashyOperation(InitCapacityOk_InitialEntryStoreTooBig);
+
+ // Ideally we'd also try a large-but-ok capacity that almost but doesn't
+ // quite overflow, but that would result in allocating slightly less than 4
+ // GiB of entry storage. That would be very likely to fail on 32-bit
+ // platforms, so such a test wouldn't be reliable.
+}
+
+TEST(PLDHashTableTest, LazyStorage)
+{
+ PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub));
+
+ // PLDHashTable allocates entry storage lazily. Check that all the non-add
+ // operations work appropriately when the table is empty and the storage
+ // hasn't yet been allocated.
+
+ ASSERT_EQ(t.Capacity(), 0u);
+ ASSERT_EQ(t.EntrySize(), sizeof(PLDHashEntryStub));
+ ASSERT_EQ(t.EntryCount(), 0u);
+ ASSERT_EQ(t.Generation(), 0u);
+
+ ASSERT_TRUE(!t.Search((const void*)1));
+
+ // No result to check here, but call it to make sure it doesn't crash.
+ t.Remove((const void*)2);
+
+ for (auto iter = t.Iter(); !iter.Done(); iter.Next()) {
+ ASSERT_TRUE(false); // shouldn't hit this on an empty table
+ }
+
+ ASSERT_EQ(t.ShallowSizeOfExcludingThis(moz_malloc_size_of), 0u);
+}
+
+// A trivial hash function is good enough here. It's also super-fast for the
+// GrowToMaxCapacity test because we insert the integers 0.., which means it's
+// collision-free.
+static PLDHashNumber
+TrivialHash(const void *key)
+{
+ return (PLDHashNumber)(size_t)key;
+}
+
+static void
+TrivialInitEntry(PLDHashEntryHdr* aEntry, const void* aKey)
+{
+ auto entry = static_cast<PLDHashEntryStub*>(aEntry);
+ entry->key = aKey;
+}
+
+static const PLDHashTableOps trivialOps = {
+ TrivialHash,
+ PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ TrivialInitEntry
+};
+
+TEST(PLDHashTableTest, MoveSemantics)
+{
+ PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub));
+ t1.Add((const void*)88);
+ PLDHashTable t2(&trivialOps, sizeof(PLDHashEntryStub));
+ t2.Add((const void*)99);
+
+ t1 = mozilla::Move(t1); // self-move
+
+ t1 = mozilla::Move(t2); // empty overwritten with empty
+
+ PLDHashTable t3(&trivialOps, sizeof(PLDHashEntryStub));
+ PLDHashTable t4(&trivialOps, sizeof(PLDHashEntryStub));
+ t3.Add((const void*)88);
+
+ t3 = mozilla::Move(t4); // non-empty overwritten with empty
+
+ PLDHashTable t5(&trivialOps, sizeof(PLDHashEntryStub));
+ PLDHashTable t6(&trivialOps, sizeof(PLDHashEntryStub));
+ t6.Add((const void*)88);
+
+ t5 = mozilla::Move(t6); // empty overwritten with non-empty
+
+ PLDHashTable t7(&trivialOps, sizeof(PLDHashEntryStub));
+ PLDHashTable t8(mozilla::Move(t7)); // new table constructed with uninited
+
+ PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub));
+ t9.Add((const void*)88);
+ PLDHashTable t10(mozilla::Move(t9)); // new table constructed with inited
+}
+
+TEST(PLDHashTableTest, Clear)
+{
+ PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub));
+
+ t1.Clear();
+ ASSERT_EQ(t1.EntryCount(), 0u);
+
+ t1.ClearAndPrepareForLength(100);
+ ASSERT_EQ(t1.EntryCount(), 0u);
+
+ t1.Add((const void*)77);
+ t1.Add((const void*)88);
+ t1.Add((const void*)99);
+ ASSERT_EQ(t1.EntryCount(), 3u);
+
+ t1.Clear();
+ ASSERT_EQ(t1.EntryCount(), 0u);
+
+ t1.Add((const void*)55);
+ t1.Add((const void*)66);
+ t1.Add((const void*)77);
+ t1.Add((const void*)88);
+ t1.Add((const void*)99);
+ ASSERT_EQ(t1.EntryCount(), 5u);
+
+ t1.ClearAndPrepareForLength(8192);
+ ASSERT_EQ(t1.EntryCount(), 0u);
+}
+
+TEST(PLDHashTableTest, Iterator)
+{
+ PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub));
+
+ // Explicitly test the move constructor. We do this because, due to copy
+ // elision, compilers might optimize away move constructor calls for normal
+ // iterator use.
+ {
+ PLDHashTable::Iterator iter1(&t);
+ PLDHashTable::Iterator iter2(mozilla::Move(iter1));
+ }
+
+ // Iterate through the empty table.
+ for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) {
+ (void) iter.Get();
+ ASSERT_TRUE(false); // shouldn't hit this
+ }
+
+ // Add three entries.
+ t.Add((const void*)77);
+ t.Add((const void*)88);
+ t.Add((const void*)99);
+
+ // Check the iterator goes through each entry once.
+ bool saw77 = false, saw88 = false, saw99 = false;
+ int n = 0;
+ for (auto iter(t.Iter()); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PLDHashEntryStub*>(iter.Get());
+ if (entry->key == (const void*)77) {
+ saw77 = true;
+ }
+ if (entry->key == (const void*)88) {
+ saw88 = true;
+ }
+ if (entry->key == (const void*)99) {
+ saw99 = true;
+ }
+ n++;
+ }
+ ASSERT_TRUE(saw77 && saw88 && saw99 && n == 3);
+
+ t.Clear();
+
+ // First, we insert 64 items, which results in a capacity of 128, and a load
+ // factor of 50%.
+ for (intptr_t i = 0; i < 64; i++) {
+ t.Add((const void*)i);
+ }
+ ASSERT_EQ(t.EntryCount(), 64u);
+ ASSERT_EQ(t.Capacity(), 128u);
+
+ // The first removing iterator does no removing; capacity and entry count are
+ // unchanged.
+ for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) {
+ (void) iter.Get();
+ }
+ ASSERT_EQ(t.EntryCount(), 64u);
+ ASSERT_EQ(t.Capacity(), 128u);
+
+ // The second removing iterator removes 16 items. This reduces the load
+ // factor to 37.5% (48 / 128), which isn't low enough to shrink the table.
+ for (auto iter = t.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PLDHashEntryStub*>(iter.Get());
+ if ((intptr_t)(entry->key) % 4 == 0) {
+ iter.Remove();
+ }
+ }
+ ASSERT_EQ(t.EntryCount(), 48u);
+ ASSERT_EQ(t.Capacity(), 128u);
+
+ // The third removing iterator removes another 16 items. This reduces
+ // the load factor to 25% (32 / 128), so the table is shrunk.
+ for (auto iter = t.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PLDHashEntryStub*>(iter.Get());
+ if ((intptr_t)(entry->key) % 2 == 0) {
+ iter.Remove();
+ }
+ }
+ ASSERT_EQ(t.EntryCount(), 32u);
+ ASSERT_EQ(t.Capacity(), 64u);
+
+ // The fourth removing iterator removes all remaining items. This reduces
+ // the capacity to the minimum.
+ for (auto iter = t.Iter(); !iter.Done(); iter.Next()) {
+ iter.Remove();
+ }
+ ASSERT_EQ(t.EntryCount(), 0u);
+ ASSERT_EQ(t.Capacity(), unsigned(PLDHashTable::kMinCapacity));
+}
+
+// This test involves resizing a table repeatedly up to 512 MiB in size. On
+// 32-bit platforms (Win32, Android) it sometimes OOMs, causing the test to
+// fail. (See bug 931062 and bug 1267227.) Therefore, we only run it on 64-bit
+// platforms where OOM is much less likely.
+//
+// Also, it's slow, and so should always be last.
+#ifdef HAVE_64BIT_BUILD
+TEST(PLDHashTableTest, GrowToMaxCapacity)
+{
+ // This is infallible.
+ PLDHashTable* t =
+ new PLDHashTable(&trivialOps, sizeof(PLDHashEntryStub), 128);
+
+ // Keep inserting elements until failure occurs because the table is full.
+ size_t numInserted = 0;
+ while (true) {
+ if (!t->Add((const void*)numInserted, mozilla::fallible)) {
+ break;
+ }
+ numInserted++;
+ }
+
+ // We stop when the element count is 96.875% of PLDHashTable::kMaxCapacity
+ // (see MaxLoadOnGrowthFailure()).
+ if (numInserted !=
+ PLDHashTable::kMaxCapacity - (PLDHashTable::kMaxCapacity >> 5)) {
+ delete t;
+ ASSERT_TRUE(false);
+ }
+
+ delete t;
+}
+#endif
+
diff --git a/xpcom/tests/gtest/TestPipes.cpp b/xpcom/tests/gtest/TestPipes.cpp
new file mode 100644
index 000000000..87b923008
--- /dev/null
+++ b/xpcom/tests/gtest/TestPipes.cpp
@@ -0,0 +1,1097 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <algorithm>
+#include "gtest/gtest.h"
+#include "Helpers.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBufferedStreams.h"
+#include "nsIClassInfo.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIPipe.h"
+#include "nsISeekableStream.h"
+#include "nsIThread.h"
+#include "nsIRunnable.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "prprf.h"
+#include "prinrval.h"
+
+using namespace mozilla;
+
+#define ITERATIONS 33333
+char kTestPattern[] = "My hovercraft is full of eels.\n";
+
+bool gTrace = false;
+
+static nsresult
+WriteAll(nsIOutputStream *os, const char *buf, uint32_t bufLen, uint32_t *lenWritten)
+{
+ const char *p = buf;
+ *lenWritten = 0;
+ while (bufLen) {
+ uint32_t n;
+ nsresult rv = os->Write(p, bufLen, &n);
+ if (NS_FAILED(rv)) return rv;
+ p += n;
+ bufLen -= n;
+ *lenWritten += n;
+ }
+ return NS_OK;
+}
+
+class nsReceiver final : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+ char buf[101];
+ uint32_t count;
+ PRIntervalTime start = PR_IntervalNow();
+ while (true) {
+ rv = mIn->Read(buf, 100, &count);
+ if (NS_FAILED(rv)) {
+ printf("read failed\n");
+ break;
+ }
+ if (count == 0) {
+// printf("EOF count = %d\n", mCount);
+ break;
+ }
+
+ if (gTrace) {
+ buf[count] = '\0';
+ printf("read: %s\n", buf);
+ }
+ mCount += count;
+ }
+ PRIntervalTime end = PR_IntervalNow();
+ printf("read %d bytes, time = %dms\n", mCount,
+ PR_IntervalToMilliseconds(end - start));
+ return rv;
+ }
+
+ explicit nsReceiver(nsIInputStream* in) : mIn(in), mCount(0) {
+ }
+
+ uint32_t GetBytesRead() { return mCount; }
+
+private:
+ ~nsReceiver() {}
+
+protected:
+ nsCOMPtr<nsIInputStream> mIn;
+ uint32_t mCount;
+};
+
+NS_IMPL_ISUPPORTS(nsReceiver, nsIRunnable)
+
+nsresult
+TestPipe(nsIInputStream* in, nsIOutputStream* out)
+{
+ RefPtr<nsReceiver> receiver = new nsReceiver(in);
+ if (!receiver)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewThread(getter_AddRefs(thread), receiver);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t total = 0;
+ PRIntervalTime start = PR_IntervalNow();
+ for (uint32_t i = 0; i < ITERATIONS; i++) {
+ uint32_t writeCount;
+ char *buf = PR_smprintf("%d %s", i, kTestPattern);
+ uint32_t len = strlen(buf);
+ rv = WriteAll(out, buf, len, &writeCount);
+ if (gTrace) {
+ printf("wrote: ");
+ for (uint32_t j = 0; j < writeCount; j++) {
+ putc(buf[j], stdout);
+ }
+ printf("\n");
+ }
+ PR_smprintf_free(buf);
+ if (NS_FAILED(rv)) return rv;
+ total += writeCount;
+ }
+ rv = out->Close();
+ if (NS_FAILED(rv)) return rv;
+
+ PRIntervalTime end = PR_IntervalNow();
+
+ thread->Shutdown();
+
+ printf("wrote %d bytes, time = %dms\n", total,
+ PR_IntervalToMilliseconds(end - start));
+ EXPECT_EQ(receiver->GetBytesRead(), total);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsShortReader final : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+ char buf[101];
+ uint32_t count;
+ uint32_t total = 0;
+ while (true) {
+ //if (gTrace)
+ // printf("calling Read\n");
+ rv = mIn->Read(buf, 100, &count);
+ if (NS_FAILED(rv)) {
+ printf("read failed\n");
+ break;
+ }
+ if (count == 0) {
+ break;
+ }
+
+ if (gTrace) {
+ // For next |printf()| call and possible others elsewhere.
+ buf[count] = '\0';
+
+ printf("read %d bytes: %s\n", count, buf);
+ }
+
+ Received(count);
+ total += count;
+ }
+ printf("read %d bytes\n", total);
+ return rv;
+ }
+
+ explicit nsShortReader(nsIInputStream* in) : mIn(in), mReceived(0) {
+ mMon = new ReentrantMonitor("nsShortReader");
+ }
+
+ void Received(uint32_t count) {
+ ReentrantMonitorAutoEnter mon(*mMon);
+ mReceived += count;
+ mon.Notify();
+ }
+
+ uint32_t WaitForReceipt(const uint32_t aWriteCount) {
+ ReentrantMonitorAutoEnter mon(*mMon);
+ uint32_t result = mReceived;
+
+ while (result < aWriteCount) {
+ mon.Wait();
+
+ EXPECT_TRUE(mReceived > result);
+ result = mReceived;
+ }
+
+ mReceived = 0;
+ return result;
+ }
+
+private:
+ ~nsShortReader() {}
+
+protected:
+ nsCOMPtr<nsIInputStream> mIn;
+ uint32_t mReceived;
+ ReentrantMonitor* mMon;
+};
+
+NS_IMPL_ISUPPORTS(nsShortReader, nsIRunnable)
+
+nsresult
+TestShortWrites(nsIInputStream* in, nsIOutputStream* out)
+{
+ RefPtr<nsShortReader> receiver = new nsShortReader(in);
+ if (!receiver)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewThread(getter_AddRefs(thread), receiver);
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t total = 0;
+ for (uint32_t i = 0; i < ITERATIONS; i++) {
+ uint32_t writeCount;
+ char* buf = PR_smprintf("%d %s", i, kTestPattern);
+ uint32_t len = strlen(buf);
+ len = len * rand() / RAND_MAX;
+ len = std::min(1u, len);
+ rv = WriteAll(out, buf, len, &writeCount);
+ if (NS_FAILED(rv)) return rv;
+ EXPECT_EQ(writeCount, len);
+ total += writeCount;
+
+ if (gTrace)
+ printf("wrote %d bytes: %s\n", writeCount, buf);
+ PR_smprintf_free(buf);
+ //printf("calling Flush\n");
+ out->Flush();
+ //printf("calling WaitForReceipt\n");
+
+#ifdef DEBUG
+ const uint32_t received =
+ receiver->WaitForReceipt(writeCount);
+ EXPECT_EQ(received, writeCount);
+#endif
+ }
+ rv = out->Close();
+ if (NS_FAILED(rv)) return rv;
+
+ thread->Shutdown();
+
+ printf("wrote %d bytes\n", total);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPump final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+ uint32_t count;
+ while (true) {
+ rv = mOut->WriteFrom(mIn, ~0U, &count);
+ if (NS_FAILED(rv)) {
+ printf("Write failed\n");
+ break;
+ }
+ if (count == 0) {
+ printf("EOF count = %d\n", mCount);
+ break;
+ }
+
+ if (gTrace) {
+ printf("Wrote: %d\n", count);
+ }
+ mCount += count;
+ }
+ mOut->Close();
+ return rv;
+ }
+
+ nsPump(nsIInputStream* in,
+ nsIOutputStream* out)
+ : mIn(in), mOut(out), mCount(0) {
+ }
+
+private:
+ ~nsPump() {}
+
+protected:
+ nsCOMPtr<nsIInputStream> mIn;
+ nsCOMPtr<nsIOutputStream> mOut;
+ uint32_t mCount;
+};
+
+NS_IMPL_ISUPPORTS(nsPump, nsIRunnable)
+
+TEST(Pipes, ChainedPipes)
+{
+ nsresult rv;
+ if (gTrace) {
+ printf("TestChainedPipes\n");
+ }
+
+ nsCOMPtr<nsIInputStream> in1;
+ nsCOMPtr<nsIOutputStream> out1;
+ rv = NS_NewPipe(getter_AddRefs(in1), getter_AddRefs(out1), 20, 1999);
+ if (NS_FAILED(rv)) return;
+
+ nsCOMPtr<nsIInputStream> in2;
+ nsCOMPtr<nsIOutputStream> out2;
+ rv = NS_NewPipe(getter_AddRefs(in2), getter_AddRefs(out2), 200, 401);
+ if (NS_FAILED(rv)) return;
+
+ RefPtr<nsPump> pump = new nsPump(in1, out2);
+ if (pump == nullptr) return;
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewThread(getter_AddRefs(thread), pump);
+ if (NS_FAILED(rv)) return;
+
+ RefPtr<nsReceiver> receiver = new nsReceiver(in2);
+ if (receiver == nullptr) return;
+
+ nsCOMPtr<nsIThread> receiverThread;
+ rv = NS_NewThread(getter_AddRefs(receiverThread), receiver);
+ if (NS_FAILED(rv)) return;
+
+ uint32_t total = 0;
+ for (uint32_t i = 0; i < ITERATIONS; i++) {
+ uint32_t writeCount;
+ char* buf = PR_smprintf("%d %s", i, kTestPattern);
+ uint32_t len = strlen(buf);
+ len = len * rand() / RAND_MAX;
+ len = std::max(1u, len);
+ rv = WriteAll(out1, buf, len, &writeCount);
+ if (NS_FAILED(rv)) return;
+ EXPECT_EQ(writeCount, len);
+ total += writeCount;
+
+ if (gTrace)
+ printf("wrote %d bytes: %s\n", writeCount, buf);
+
+ PR_smprintf_free(buf);
+ }
+ if (gTrace) {
+ printf("wrote total of %d bytes\n", total);
+ }
+ rv = out1->Close();
+ if (NS_FAILED(rv)) return;
+
+ thread->Shutdown();
+ receiverThread->Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+RunTests(uint32_t segSize, uint32_t segCount)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> in;
+ nsCOMPtr<nsIOutputStream> out;
+ uint32_t bufSize = segSize * segCount;
+ if (gTrace) {
+ printf("Testing New Pipes: segment size %d buffer size %d\n", segSize, bufSize);
+ printf("Testing long writes...\n");
+ }
+ rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ rv = TestPipe(in, out);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ if (gTrace) {
+ printf("Testing short writes...\n");
+ }
+ rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ rv = TestShortWrites(in, out);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+}
+
+TEST(Pipes, Main)
+{
+ RunTests(16, 1);
+ RunTests(4096, 16);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+static const uint32_t DEFAULT_SEGMENT_SIZE = 4 * 1024;
+
+// An alternate pipe testing routing that uses NS_ConsumeStream() instead of
+// manual read loop.
+static void TestPipe2(uint32_t aNumBytes,
+ uint32_t aSegmentSize = DEFAULT_SEGMENT_SIZE)
+{
+ nsCOMPtr<nsIInputStream> reader;
+ nsCOMPtr<nsIOutputStream> writer;
+
+ uint32_t maxSize = std::max(aNumBytes, aSegmentSize);
+
+ nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer),
+ aSegmentSize, maxSize);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> inputData;
+ testing::CreateData(aNumBytes, inputData);
+ testing::WriteAllAndClose(writer, inputData);
+ testing::ConsumeAndValidateStream(reader, inputData);
+}
+
+} // namespace
+
+TEST(Pipes, Blocking_32k)
+{
+ TestPipe2(32 * 1024);
+}
+
+TEST(Pipes, Blocking_64k)
+{
+ TestPipe2(64 * 1024);
+}
+
+TEST(Pipes, Blocking_128k)
+{
+ TestPipe2(128 * 1024);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// Utility routine to validate pipe clone before. There are many knobs.
+//
+// aTotalBytes Total number of bytes to write to the pipe.
+// aNumWrites How many separate write calls should be made. Bytes
+// are evenly distributed over these write calls.
+// aNumInitialClones How many clones of the pipe input stream should be
+// made before writing begins.
+// aNumToCloseAfterWrite How many streams should be closed after each write.
+// One stream is always kept open. This verifies that
+// closing one stream does not effect other open
+// streams.
+// aNumToCloneAfterWrite How many clones to create after each write. Occurs
+// after closing any streams. This tests cloning
+// active streams on a pipe that is being written to.
+// aNumStreamToReadPerWrite How many streams to read fully after each write.
+// This tests reading cloned streams at different rates
+// while the pipe is being written to.
+static void TestPipeClone(uint32_t aTotalBytes,
+ uint32_t aNumWrites,
+ uint32_t aNumInitialClones,
+ uint32_t aNumToCloseAfterWrite,
+ uint32_t aNumToCloneAfterWrite,
+ uint32_t aNumStreamsToReadPerWrite,
+ uint32_t aSegmentSize = DEFAULT_SEGMENT_SIZE)
+{
+ nsCOMPtr<nsIInputStream> reader;
+ nsCOMPtr<nsIOutputStream> writer;
+
+ uint32_t maxSize = std::max(aTotalBytes, aSegmentSize);
+
+ // Use async input streams so we can NS_ConsumeStream() the current data
+ // while the pipe is still being written to.
+ nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer),
+ aSegmentSize, maxSize,
+ true, false); // non-blocking - reader, writer
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(reader);
+ ASSERT_TRUE(cloneable);
+ ASSERT_TRUE(cloneable->GetCloneable());
+
+ nsTArray<nsCString> outputDataList;
+
+ nsTArray<nsCOMPtr<nsIInputStream>> streamList;
+
+ // first stream is our original reader from the pipe
+ streamList.AppendElement(reader);
+ outputDataList.AppendElement();
+
+ // Clone the initial input stream the specified number of times
+ // before performing any writes.
+ for (uint32_t i = 0; i < aNumInitialClones; ++i) {
+ nsCOMPtr<nsIInputStream>* clone = streamList.AppendElement();
+ rv = cloneable->Clone(getter_AddRefs(*clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(*clone);
+
+ outputDataList.AppendElement();
+ }
+
+ nsTArray<char> inputData;
+ testing::CreateData(aTotalBytes, inputData);
+
+ const uint32_t bytesPerWrite = ((aTotalBytes - 1)/ aNumWrites) + 1;
+ uint32_t offset = 0;
+ uint32_t remaining = aTotalBytes;
+ uint32_t nextStreamToRead = 0;
+
+ while (remaining) {
+ uint32_t numToWrite = std::min(bytesPerWrite, remaining);
+ testing::Write(writer, inputData, offset, numToWrite);
+ offset += numToWrite;
+ remaining -= numToWrite;
+
+ // Close the specified number of streams. This allows us to
+ // test that one closed clone does not break other open clones.
+ for (uint32_t i = 0; i < aNumToCloseAfterWrite &&
+ streamList.Length() > 1; ++i) {
+
+ uint32_t lastIndex = streamList.Length() - 1;
+ streamList[lastIndex]->Close();
+ streamList.RemoveElementAt(lastIndex);
+ outputDataList.RemoveElementAt(lastIndex);
+
+ if (nextStreamToRead >= streamList.Length()) {
+ nextStreamToRead = 0;
+ }
+ }
+
+ // Create the specified number of clones. This lets us verify
+ // that we can create clones in the middle of pipe reading and
+ // writing.
+ for (uint32_t i = 0; i < aNumToCloneAfterWrite; ++i) {
+ nsCOMPtr<nsIInputStream>* clone = streamList.AppendElement();
+ rv = cloneable->Clone(getter_AddRefs(*clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE(*clone);
+
+ // Initialize the new output data to make whats been read to data for
+ // the original stream. First stream is always the original stream.
+ nsCString* outputData = outputDataList.AppendElement();
+ *outputData = outputDataList[0];
+ }
+
+ // Read the specified number of streams. This lets us verify that we
+ // can read from the clones at different rates while the pipe is being
+ // written to.
+ for (uint32_t i = 0; i < aNumStreamsToReadPerWrite; ++i) {
+ nsCOMPtr<nsIInputStream>& stream = streamList[nextStreamToRead];
+ nsCString& outputData = outputDataList[nextStreamToRead];
+
+ // Can't use ConsumeAndValidateStream() here because we're not
+ // guaranteed the exact amount read. It should just be at least
+ // as many as numToWrite.
+ nsAutoCString tmpOutputData;
+ rv = NS_ConsumeStream(stream, UINT32_MAX, tmpOutputData);
+ ASSERT_TRUE(rv == NS_BASE_STREAM_WOULD_BLOCK || NS_SUCCEEDED(rv));
+ ASSERT_GE(tmpOutputData.Length(), numToWrite);
+
+ outputData += tmpOutputData;
+
+ nextStreamToRead += 1;
+ if (nextStreamToRead >= streamList.Length()) {
+ // Note: When we wrap around on the streams being read, its possible
+ // we will trigger a segment to be deleted from the pipe. It
+ // would be nice to validate this here, but we don't have any
+ // QI'able interface that would let us check easily.
+
+ nextStreamToRead = 0;
+ }
+ }
+ }
+
+ rv = writer->Close();
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+ // Finally, read the remaining bytes from each stream. This may be
+ // different amounts of data depending on how much reading we did while
+ // writing. Verify that the end result matches the input data.
+ for (uint32_t i = 0; i < streamList.Length(); ++i) {
+ nsCOMPtr<nsIInputStream>& stream = streamList[i];
+ nsCString& outputData = outputDataList[i];
+
+ nsAutoCString tmpOutputData;
+ rv = NS_ConsumeStream(stream, UINT32_MAX, tmpOutputData);
+ ASSERT_TRUE(rv == NS_BASE_STREAM_WOULD_BLOCK || NS_SUCCEEDED(rv));
+ stream->Close();
+
+ // Append to total amount read from the stream
+ outputData += tmpOutputData;
+
+ ASSERT_EQ(inputString.Length(), outputData.Length());
+ ASSERT_TRUE(inputString.Equals(outputData));
+ }
+}
+
+} // namespace
+
+TEST(Pipes, Clone_BeforeWrite_ReadAtEnd)
+{
+ TestPipeClone(32 * 1024, // total bytes
+ 16, // num writes
+ 3, // num initial clones
+ 0, // num streams to close after each write
+ 0, // num clones to add after each write
+ 0); // num streams to read after each write
+}
+
+TEST(Pipes, Clone_BeforeWrite_ReadDuringWrite)
+{
+ // Since this reads all streams on every write, it should trigger the
+ // pipe cursor roll back optimization. Currently we can only verify
+ // this with logging.
+
+ TestPipeClone(32 * 1024, // total bytes
+ 16, // num writes
+ 3, // num initial clones
+ 0, // num streams to close after each write
+ 0, // num clones to add after each write
+ 4); // num streams to read after each write
+}
+
+TEST(Pipes, Clone_DuringWrite_ReadAtEnd)
+{
+ TestPipeClone(32 * 1024, // total bytes
+ 16, // num writes
+ 0, // num initial clones
+ 0, // num streams to close after each write
+ 1, // num clones to add after each write
+ 0); // num streams to read after each write
+}
+
+TEST(Pipes, Clone_DuringWrite_ReadDuringWrite)
+{
+ TestPipeClone(32 * 1024, // total bytes
+ 16, // num writes
+ 0, // num initial clones
+ 0, // num streams to close after each write
+ 1, // num clones to add after each write
+ 1); // num streams to read after each write
+}
+
+TEST(Pipes, Clone_DuringWrite_ReadDuringWrite_CloseDuringWrite)
+{
+ // Since this reads streams faster than we clone new ones, it should
+ // trigger pipe segment deletion periodically. Currently we can
+ // only verify this with logging.
+
+ TestPipeClone(32 * 1024, // total bytes
+ 16, // num writes
+ 1, // num initial clones
+ 1, // num streams to close after each write
+ 2, // num clones to add after each write
+ 3); // num streams to read after each write
+}
+
+TEST(Pipes, Write_AsyncWait)
+{
+ nsCOMPtr<nsIAsyncInputStream> reader;
+ nsCOMPtr<nsIAsyncOutputStream> writer;
+
+ const uint32_t segmentSize = 1024;
+ const uint32_t numSegments = 1;
+
+ nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer),
+ true, true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv);
+
+ RefPtr<testing::OutputStreamCallback> cb =
+ new testing::OutputStreamCallback();
+
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_FALSE(cb->Called());
+
+ testing::ConsumeAndValidateStream(reader, inputData);
+
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(Pipes, Write_AsyncWait_Clone)
+{
+ nsCOMPtr<nsIAsyncInputStream> reader;
+ nsCOMPtr<nsIAsyncOutputStream> writer;
+
+ const uint32_t segmentSize = 1024;
+ const uint32_t numSegments = 1;
+
+ nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer),
+ true, true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(reader, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // This attempts to write data beyond the original pipe size limit. It
+ // should fail since neither side of the clone has been read yet.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv);
+
+ RefPtr<testing::OutputStreamCallback> cb =
+ new testing::OutputStreamCallback();
+
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_FALSE(cb->Called());
+
+ // Consume data on the original stream, but the clone still has not been read.
+ testing::ConsumeAndValidateStream(reader, inputData);
+
+ // A clone that is not being read should not stall the other input stream
+ // reader. Therefore the writer callback should trigger when the fastest
+ // reader drains the other input stream.
+ ASSERT_TRUE(cb->Called());
+
+ // Attempt to write data. This will buffer data beyond the pipe size limit in
+ // order for the clone stream to still work. This is allowed because the
+ // other input stream has drained its buffered segments and is ready for more
+ // data.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Again, this should fail since the origin stream has not been read again.
+ // The pipe size should still restrict how far ahead we can buffer even
+ // when there is a cloned stream not being read.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ cb = new testing::OutputStreamCallback();
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // The write should again be blocked since we have written data and the
+ // main reader is at its maximum advance buffer.
+ ASSERT_FALSE(cb->Called());
+
+ nsTArray<char> expectedCloneData;
+ expectedCloneData.AppendElements(inputData);
+ expectedCloneData.AppendElements(inputData);
+
+ // We should now be able to consume the entire backlog of buffered data on
+ // the cloned stream.
+ testing::ConsumeAndValidateStream(clone, expectedCloneData);
+
+ // Draining the clone side should also trigger the AsyncWait() writer
+ // callback
+ ASSERT_TRUE(cb->Called());
+
+ // Finally, we should be able to consume the remaining data on the original
+ // reader.
+ testing::ConsumeAndValidateStream(reader, inputData);
+}
+
+TEST(Pipes, Write_AsyncWait_Clone_CloseOriginal)
+{
+ nsCOMPtr<nsIAsyncInputStream> reader;
+ nsCOMPtr<nsIAsyncOutputStream> writer;
+
+ const uint32_t segmentSize = 1024;
+ const uint32_t numSegments = 1;
+
+ nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer),
+ true, true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(reader, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // This attempts to write data beyond the original pipe size limit. It
+ // should fail since neither side of the clone has been read yet.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv);
+
+ RefPtr<testing::OutputStreamCallback> cb =
+ new testing::OutputStreamCallback();
+
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_FALSE(cb->Called());
+
+ // Consume data on the original stream, but the clone still has not been read.
+ testing::ConsumeAndValidateStream(reader, inputData);
+
+ // A clone that is not being read should not stall the other input stream
+ // reader. Therefore the writer callback should trigger when the fastest
+ // reader drains the other input stream.
+ ASSERT_TRUE(cb->Called());
+
+ // Attempt to write data. This will buffer data beyond the pipe size limit in
+ // order for the clone stream to still work. This is allowed because the
+ // other input stream has drained its buffered segments and is ready for more
+ // data.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Again, this should fail since the origin stream has not been read again.
+ // The pipe size should still restrict how far ahead we can buffer even
+ // when there is a cloned stream not being read.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ cb = new testing::OutputStreamCallback();
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // The write should again be blocked since we have written data and the
+ // main reader is at its maximum advance buffer.
+ ASSERT_FALSE(cb->Called());
+
+ // Close the original reader input stream. This was the fastest reader,
+ // so we should have a single stream that is buffered beyond our nominal
+ // limit.
+ reader->Close();
+
+ // Because the clone stream is still buffered the writable callback should
+ // not be fired.
+ ASSERT_FALSE(cb->Called());
+
+ // And we should not be able to perform a write.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ // Create another clone stream. Now we have two streams that exceed our
+ // maximum size limit
+ nsCOMPtr<nsIInputStream> clone2;
+ rv = NS_CloneInputStream(clone, getter_AddRefs(clone2));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> expectedCloneData;
+ expectedCloneData.AppendElements(inputData);
+ expectedCloneData.AppendElements(inputData);
+
+ // We should now be able to consume the entire backlog of buffered data on
+ // the cloned stream.
+ testing::ConsumeAndValidateStream(clone, expectedCloneData);
+
+ // The pipe should now be writable because we have two open streams, one of which
+ // is completely drained.
+ ASSERT_TRUE(cb->Called());
+
+ // Write again to reach our limit again.
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // The stream is again non-writeable.
+ cb = new testing::OutputStreamCallback();
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_FALSE(cb->Called());
+
+ // Close the empty stream. This is different from our previous close since
+ // before we were closing a stream with some data still buffered.
+ clone->Close();
+
+ // The pipe should not be writable. The second clone is still fully buffered
+ // over our limit.
+ ASSERT_FALSE(cb->Called());
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_FAILED(rv));
+
+ // Finally consume all of the buffered data on the second clone.
+ expectedCloneData.AppendElements(inputData);
+ testing::ConsumeAndValidateStream(clone2, expectedCloneData);
+
+ // Draining the final clone should make the pipe writable again.
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(Pipes, Read_AsyncWait)
+{
+ nsCOMPtr<nsIAsyncInputStream> reader;
+ nsCOMPtr<nsIAsyncOutputStream> writer;
+
+ const uint32_t segmentSize = 1024;
+ const uint32_t numSegments = 1;
+
+ nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer),
+ true, true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ RefPtr<testing::InputStreamCallback> cb =
+ new testing::InputStreamCallback();
+
+ rv = reader->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_FALSE(cb->Called());
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_TRUE(cb->Called());
+
+ testing::ConsumeAndValidateStream(reader, inputData);
+}
+
+TEST(Pipes, Read_AsyncWait_Clone)
+{
+ nsCOMPtr<nsIAsyncInputStream> reader;
+ nsCOMPtr<nsIAsyncOutputStream> writer;
+
+ const uint32_t segmentSize = 1024;
+ const uint32_t numSegments = 1;
+
+ nsresult rv = NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer),
+ true, true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(reader, getter_AddRefs(clone));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIAsyncInputStream> asyncClone = do_QueryInterface(clone);
+ ASSERT_TRUE(asyncClone);
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ RefPtr<testing::InputStreamCallback> cb =
+ new testing::InputStreamCallback();
+
+ RefPtr<testing::InputStreamCallback> cb2 =
+ new testing::InputStreamCallback();
+
+ rv = reader->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_FALSE(cb->Called());
+
+ rv = asyncClone->AsyncWait(cb2, 0, 0, nullptr);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_FALSE(cb2->Called());
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_TRUE(cb->Called());
+ ASSERT_TRUE(cb2->Called());
+
+ testing::ConsumeAndValidateStream(reader, inputData);
+}
+
+namespace {
+
+nsresult
+CloseDuringReadFunc(nsIInputStream *aReader,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCountOut)
+{
+ MOZ_RELEASE_ASSERT(aReader);
+ MOZ_RELEASE_ASSERT(aClosure);
+ MOZ_RELEASE_ASSERT(aFromSegment);
+ MOZ_RELEASE_ASSERT(aWriteCountOut);
+ MOZ_RELEASE_ASSERT(aToOffset == 0);
+
+ // This is insanity and you probably should not do this under normal
+ // conditions. We want to simulate the case where the pipe is closed
+ // (possibly from other end on another thread) simultaneously with the
+ // read. This is the easiest way to do trigger this case in a synchronous
+ // gtest.
+ MOZ_ALWAYS_SUCCEEDS(aReader->Close());
+
+ nsTArray<char>* buffer = static_cast<nsTArray<char>*>(aClosure);
+ buffer->AppendElements(aFromSegment, aCount);
+
+ *aWriteCountOut = aCount;
+
+ return NS_OK;
+}
+
+void
+TestCloseDuringRead(uint32_t aSegmentSize, uint32_t aDataSize)
+{
+ nsCOMPtr<nsIInputStream> reader;
+ nsCOMPtr<nsIOutputStream> writer;
+
+ const uint32_t maxSize = aSegmentSize;
+
+ nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer),
+ aSegmentSize, maxSize);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> inputData;
+
+ testing::CreateData(aDataSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsTArray<char> outputData;
+
+ uint32_t numRead = 0;
+ rv = reader->ReadSegments(CloseDuringReadFunc, &outputData,
+ inputData.Length(), &numRead);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_EQ(inputData.Length(), numRead);
+
+ ASSERT_EQ(inputData, outputData);
+
+ uint64_t available;
+ rv = reader->Available(&available);
+ ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv);
+}
+
+} // namespace
+
+TEST(Pipes, Close_During_Read_Partial_Segment)
+{
+ TestCloseDuringRead(1024, 512);
+}
+
+TEST(Pipes, Close_During_Read_Full_Segment)
+{
+ TestCloseDuringRead(1024, 1024);
+}
+
+TEST(Pipes, Interfaces)
+{
+ nsCOMPtr<nsIInputStream> reader;
+ nsCOMPtr<nsIOutputStream> writer;
+
+ nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIAsyncInputStream> readerType1 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType1);
+
+ nsCOMPtr<nsISeekableStream> readerType2 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType2);
+
+ nsCOMPtr<nsISearchableInputStream> readerType3 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType3);
+
+ nsCOMPtr<nsICloneableInputStream> readerType4 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType4);
+
+ nsCOMPtr<nsIClassInfo> readerType5 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType5);
+
+ nsCOMPtr<nsIBufferedInputStream> readerType6 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType6);
+}
diff --git a/xpcom/tests/gtest/TestPriorityQueue.cpp b/xpcom/tests/gtest/TestPriorityQueue.cpp
new file mode 100644
index 000000000..eeb2f1e09
--- /dev/null
+++ b/xpcom/tests/gtest/TestPriorityQueue.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsTPriorityQueue.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "gtest/gtest.h"
+
+template<class T, class Compare>
+void
+CheckPopSequence(const nsTPriorityQueue<T, Compare>& aQueue,
+ const T* aExpectedSequence, const uint32_t aSequenceLength)
+{
+ nsTPriorityQueue<T, Compare> copy(aQueue);
+
+ for (uint32_t i = 0; i < aSequenceLength; i++) {
+ EXPECT_FALSE(copy.IsEmpty());
+
+ T pop = copy.Pop();
+ EXPECT_EQ(pop, aExpectedSequence[i]);
+ }
+
+ EXPECT_TRUE(copy.IsEmpty());
+}
+
+template<class A>
+class MaxCompare {
+public:
+ bool LessThan(const A& a, const A& b) {
+ return a > b;
+ }
+};
+
+TEST(PriorityQueue, Main)
+{
+ nsTPriorityQueue<int> queue;
+
+ EXPECT_TRUE(queue.IsEmpty());
+
+ queue.Push(8);
+ queue.Push(6);
+ queue.Push(4);
+ queue.Push(2);
+ queue.Push(10);
+ queue.Push(6);
+ EXPECT_EQ(queue.Top(), 2);
+ EXPECT_EQ(queue.Length(), 6u);
+ EXPECT_FALSE(queue.IsEmpty());
+ int expected[] = { 2, 4, 6, 6, 8, 10 };
+ CheckPopSequence(queue, expected, sizeof(expected) / sizeof(expected[0]));
+
+ // copy ctor is tested by using CheckPopSequence, but check default assignment
+ // operator
+ nsTPriorityQueue<int> queue2;
+ queue2 = queue;
+ CheckPopSequence(queue2, expected, sizeof(expected) / sizeof(expected[0]));
+
+ queue.Clear();
+ EXPECT_TRUE(queue.IsEmpty());
+
+ // try same sequence with a max heap
+ nsTPriorityQueue<int, MaxCompare<int> > max_queue;
+ max_queue.Push(8);
+ max_queue.Push(6);
+ max_queue.Push(4);
+ max_queue.Push(2);
+ max_queue.Push(10);
+ max_queue.Push(6);
+ EXPECT_EQ(max_queue.Top(), 10);
+ int expected_max[] = { 10, 8, 6, 6, 4, 2 };
+ CheckPopSequence(max_queue, expected_max,
+ sizeof(expected_max) / sizeof(expected_max[0]));
+}
diff --git a/xpcom/tests/gtest/TestRacingServiceManager.cpp b/xpcom/tests/gtest/TestRacingServiceManager.cpp
new file mode 100644
index 000000000..b0638db02
--- /dev/null
+++ b/xpcom/tests/gtest/TestRacingServiceManager.cpp
@@ -0,0 +1,300 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsIFactory.h"
+#include "mozilla/Module.h"
+#include "nsXULAppAPI.h"
+#include "nsIThread.h"
+#include "nsIComponentRegistrar.h"
+
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pratom.h"
+#include "prmon.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/ReentrantMonitor.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+/* f93f6bdc-88af-42d7-9d64-1b43c649a3e5 */
+#define FACTORY_CID1 \
+{ \
+ 0xf93f6bdc, \
+ 0x88af, \
+ 0x42d7, \
+ { 0x9d, 0x64, 0x1b, 0x43, 0xc6, 0x49, 0xa3, 0xe5 } \
+}
+NS_DEFINE_CID(kFactoryCID1, FACTORY_CID1);
+
+/* ef38ad65-6595-49f0-8048-e819f81d15e2 */
+#define FACTORY_CID2 \
+{ \
+ 0xef38ad65, \
+ 0x6595, \
+ 0x49f0, \
+ { 0x80, 0x48, 0xe8, 0x19, 0xf8, 0x1d, 0x15, 0xe2 } \
+}
+NS_DEFINE_CID(kFactoryCID2, FACTORY_CID2);
+
+#define FACTORY_CONTRACTID \
+ "TestRacingThreadManager/factory;1"
+
+namespace TestRacingServiceManager
+{
+int32_t gComponent1Count = 0;
+int32_t gComponent2Count = 0;
+
+ReentrantMonitor* gReentrantMonitor = nullptr;
+
+bool gCreateInstanceCalled = false;
+bool gMainThreadWaiting = false;
+
+class AutoCreateAndDestroyReentrantMonitor
+{
+public:
+ explicit AutoCreateAndDestroyReentrantMonitor(ReentrantMonitor** aReentrantMonitorPtr)
+ : mReentrantMonitorPtr(aReentrantMonitorPtr) {
+ *aReentrantMonitorPtr =
+ new ReentrantMonitor("TestRacingServiceManager::AutoMon");
+ MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!");
+ }
+
+ ~AutoCreateAndDestroyReentrantMonitor() {
+ if (*mReentrantMonitorPtr) {
+ delete *mReentrantMonitorPtr;
+ *mReentrantMonitorPtr = nullptr;
+ }
+ }
+
+private:
+ ReentrantMonitor** mReentrantMonitorPtr;
+};
+
+class Factory final : public nsIFactory
+{
+ ~Factory() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Factory() : mFirstComponentCreated(false) { }
+
+ NS_IMETHOD CreateInstance(nsISupports* aDelegate,
+ const nsIID& aIID,
+ void** aResult) override;
+
+ NS_IMETHOD LockFactory(bool aLock) override {
+ return NS_OK;
+ }
+
+ bool mFirstComponentCreated;
+};
+
+NS_IMPL_ISUPPORTS(Factory, nsIFactory)
+
+class Component1 final : public nsISupports
+{
+ ~Component1() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Component1() {
+ // This is the real test - make sure that only one instance is ever created.
+ int32_t count = PR_AtomicIncrement(&gComponent1Count);
+ MOZ_RELEASE_ASSERT(count == 1, "Too many components created!");
+ }
+};
+
+NS_IMPL_ADDREF(Component1)
+NS_IMPL_RELEASE(Component1)
+
+NS_INTERFACE_MAP_BEGIN(Component1)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+class Component2 final : public nsISupports
+{
+ ~Component2() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Component2() {
+ // This is the real test - make sure that only one instance is ever created.
+ int32_t count = PR_AtomicIncrement(&gComponent2Count);
+ EXPECT_EQ(count, int32_t(1)) << "Too many components created!";
+ }
+};
+
+NS_IMPL_ADDREF(Component2)
+NS_IMPL_RELEASE(Component2)
+
+NS_INTERFACE_MAP_BEGIN(Component2)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+Factory::CreateInstance(nsISupports* aDelegate,
+ const nsIID& aIID,
+ void** aResult)
+{
+ // Make sure that the second thread beat the main thread to the getService
+ // call.
+ MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Wrong thread!");
+
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+
+ gCreateInstanceCalled = true;
+ mon.Notify();
+
+ mon.Wait(PR_MillisecondsToInterval(3000));
+ }
+
+ NS_ENSURE_FALSE(aDelegate, NS_ERROR_NO_AGGREGATION);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsISupports> instance;
+
+ if (!mFirstComponentCreated) {
+ instance = new Component1();
+ }
+ else {
+ instance = new Component2();
+ }
+ NS_ENSURE_TRUE(instance, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = instance->QueryInterface(aIID, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+class TestRunnable : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ TestRunnable() : mFirstRunnableDone(false) { }
+
+ bool mFirstRunnableDone;
+};
+
+NS_IMETHODIMP
+TestRunnable::Run()
+{
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+
+ while (!gMainThreadWaiting) {
+ mon.Wait();
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> component;
+
+ if (!mFirstRunnableDone) {
+ component = do_GetService(kFactoryCID1, &rv);
+ }
+ else {
+ component = do_GetService(FACTORY_CONTRACTID, &rv);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(rv)) << "GetService failed!";
+
+ return NS_OK;
+}
+
+static Factory* gFactory;
+
+static already_AddRefed<nsIFactory>
+CreateFactory(const mozilla::Module& module, const mozilla::Module::CIDEntry& entry)
+{
+ if (!gFactory) {
+ gFactory = new Factory();
+ NS_ADDREF(gFactory);
+ }
+ nsCOMPtr<nsIFactory> ret = gFactory;
+ return ret.forget();
+}
+
+static const mozilla::Module::CIDEntry kLocalCIDs[] = {
+ { &kFactoryCID1, false, CreateFactory, nullptr },
+ { &kFactoryCID2, false, CreateFactory, nullptr },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kLocalContracts[] = {
+ { FACTORY_CONTRACTID, &kFactoryCID2 },
+ { nullptr }
+};
+
+static const mozilla::Module kLocalModule = {
+ mozilla::Module::kVersion,
+ kLocalCIDs,
+ kLocalContracts
+};
+
+TEST(RacingServiceManager, Test)
+{
+ nsresult rv;
+ XRE_AddStaticComponent(&kLocalModule);
+
+ AutoCreateAndDestroyReentrantMonitor mon1(&gReentrantMonitor);
+
+ RefPtr<TestRunnable> runnable = new TestRunnable();
+ ASSERT_TRUE(runnable);
+
+ // Run the classID test
+ nsCOMPtr<nsIThread> newThread;
+ rv = NS_NewThread(getter_AddRefs(newThread), runnable);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ {
+ ReentrantMonitorAutoEnter mon2(*gReentrantMonitor);
+
+ gMainThreadWaiting = true;
+ mon2.Notify();
+
+ while (!gCreateInstanceCalled) {
+ mon2.Wait();
+ }
+ }
+
+ nsCOMPtr<nsISupports> component(do_GetService(kFactoryCID1, &rv));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Reset for the contractID test
+ gMainThreadWaiting = gCreateInstanceCalled = false;
+ gFactory->mFirstComponentCreated = runnable->mFirstRunnableDone = true;
+ component = nullptr;
+
+ rv = newThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ {
+ ReentrantMonitorAutoEnter mon3(*gReentrantMonitor);
+
+ gMainThreadWaiting = true;
+ mon3.Notify();
+
+ while (!gCreateInstanceCalled) {
+ mon3.Wait();
+ }
+ }
+
+ component = do_GetService(FACTORY_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ NS_RELEASE(gFactory);
+}
+
+} // namespace TestRacingServiceManager
diff --git a/xpcom/tests/gtest/TestSTLWrappers.cpp b/xpcom/tests/gtest/TestSTLWrappers.cpp
new file mode 100644
index 000000000..9559548a3
--- /dev/null
+++ b/xpcom/tests/gtest/TestSTLWrappers.cpp
@@ -0,0 +1,78 @@
+#include <stdio.h>
+
+#include <algorithm>
+#ifndef mozilla_algorithm_h
+# error "failed to wrap <algorithm>"
+#endif
+
+#include <vector>
+#ifndef mozilla_vector_h
+# error "failed to wrap <vector>"
+#endif
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsCOMPtr.h"
+#include "nsICrashReporter.h"
+#include "nsServiceManagerUtils.h"
+#endif
+
+// gcc errors out if we |try ... catch| with -fno-exceptions, but we
+// can still test on windows
+#ifdef _MSC_VER
+ // C4530 will be generated whenever try...catch is used without
+ // enabling exceptions. We know we don't enbale exceptions.
+# pragma warning( disable : 4530 )
+# define TRY try
+# define CATCH(e) catch (e)
+#else
+# define TRY
+# define CATCH(e) if (0)
+#endif
+
+
+#if defined(XP_UNIX)
+extern unsigned int _gdb_sleep_duration;
+#endif
+
+void ShouldAbort()
+{
+#if defined(XP_UNIX)
+ _gdb_sleep_duration = 0;
+#endif
+
+#ifdef MOZ_CRASHREPORTER
+ nsCOMPtr<nsICrashReporter> crashreporter =
+ do_GetService("@mozilla.org/toolkit/crash-reporter;1");
+ if (crashreporter) {
+ crashreporter->SetEnabled(false);
+ }
+#endif
+
+ std::vector<int> v;
+ int rv = 1;
+
+ TRY {
+ // v.at(1) on empty v should abort; NOT throw an exception
+
+ // (Do some arithmetic with result of v.at() to avoid
+ // compiler warnings for unused variable/result.)
+ rv += v.at(1) ? 1 : 2;
+ } CATCH(const std::out_of_range&) {
+ fputs("TEST-FAIL | TestSTLWrappers.cpp | caught an exception?\n",
+ stderr);
+ return;
+ }
+
+ fputs("TEST-FAIL | TestSTLWrappers.cpp | didn't abort()?\n",
+ stderr);
+ return;
+}
+
+#ifdef XP_WIN
+TEST(STLWrapper, DISABLED_ShouldAbortDeathTest)
+#else
+TEST(STLWrapper, ShouldAbortDeathTest)
+#endif
+{
+ ASSERT_DEATH_IF_SUPPORTED(ShouldAbort(), "terminate called after throwing an instance of 'std::out_of_range'|vector::_M_range_check");
+}
diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp
new file mode 100644
index 000000000..ccad0a6a8
--- /dev/null
+++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp
@@ -0,0 +1,266 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "SlicedInputStream.h"
+
+/* We want to ensure that sliced streams work with both seekable and
+ * non-seekable input streams. As our string streams are seekable, we need to
+ * provide a string stream that doesn't permit seeking, so we can test the
+ * logic that emulates seeking in sliced input streams.
+ */
+class NonSeekableStringStream final : public nsIInputStream
+{
+ nsCOMPtr<nsIInputStream> mStream;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonSeekableStringStream(const nsACString& aBuffer)
+ {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override
+ {
+ return mStream->Available(aLength);
+ }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override
+ {
+ return mStream->Read(aBuffer, aCount, aReadCount);
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t *aResult) override
+ {
+ return mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ }
+
+ NS_IMETHOD
+ Close() override
+ {
+ return mStream->Close();
+ }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override
+ {
+ return mStream->IsNonBlocking(aNonBlocking);
+ }
+
+private:
+ ~NonSeekableStringStream() {}
+};
+
+NS_IMPL_ISUPPORTS(NonSeekableStringStream, nsIInputStream)
+
+// Helper function for creating a seekable nsIInputStream + a SlicedInputStream.
+SlicedInputStream*
+CreateSeekableStreams(uint32_t aSize, uint64_t aStart, uint64_t aLength,
+ nsCString& aBuffer)
+{
+ aBuffer.SetLength(aSize);
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aBuffer.BeginWriting()[i] = i % 10;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), aBuffer);
+ return new SlicedInputStream(stream, aStart, aLength);
+}
+
+// Helper function for creating a non-seekable nsIInputStream + a
+// SlicedInputStream.
+SlicedInputStream*
+CreateNonSeekableStreams(uint32_t aSize, uint64_t aStart, uint64_t aLength,
+ nsCString& aBuffer)
+{
+ aBuffer.SetLength(aSize);
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aBuffer.BeginWriting()[i] = i % 10;
+ }
+
+ RefPtr<NonSeekableStringStream> stream = new NonSeekableStringStream(aBuffer);
+ return new SlicedInputStream(stream, aStart, aLength);
+}
+
+// Same start, same length.
+TEST(TestSlicedInputStream, Simple) {
+ const size_t kBufSize = 4096;
+
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateSeekableStreams(kBufSize, 0, kBufSize, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ char buf2[kBufSize];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get()).Equals(nsCString(buf2)));
+}
+
+// Simple sliced stream - seekable
+TEST(TestSlicedInputStream, Sliced) {
+ const size_t kBufSize = 4096;
+
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateSeekableStreams(kBufSize, 10, 100, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)100, length);
+
+ char buf2[kBufSize / 2];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)100, count);
+ ASSERT_TRUE(nsCString(buf.get() + 10, count).Equals(nsCString(buf2, count)));
+}
+
+// Simple sliced stream - non seekable
+TEST(TestSlicedInputStream, SlicedNoSeek) {
+ const size_t kBufSize = 4096;
+
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(kBufSize, 10, 100, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)100, length);
+
+ char buf2[kBufSize / 2];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)100, count);
+ ASSERT_TRUE(nsCString(buf.get() + 10, count).Equals(nsCString(buf2, count)));
+}
+
+// Big inputStream - seekable
+TEST(TestSlicedInputStream, BigSliced) {
+ const size_t kBufSize = 4096 * 40;
+
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateSeekableStreams(kBufSize, 4096 * 5, 4096 * 10, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)4096 * 10, length);
+
+ char buf2[kBufSize / 2];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)4096 * 10, count);
+ ASSERT_TRUE(nsCString(buf.get() + 4096 * 5, count).Equals(nsCString(buf2, count)));
+}
+
+// Big inputStream - non seekable
+TEST(TestSlicedInputStream, BigSlicedNoSeek) {
+ const size_t kBufSize = 4096 * 40;
+
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(kBufSize, 4096 * 5, 4096 * 10, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)4096 * 10, length);
+
+ char buf2[kBufSize / 2];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)4096 * 10, count);
+ ASSERT_TRUE(nsCString(buf.get() + 4096 * 5, count).Equals(nsCString(buf2, count)));
+}
+
+// Available size.
+TEST(TestSlicedInputStream, Available) {
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500000, 4, 400000, buf);
+
+ uint64_t toRead = 400000;
+ for (uint32_t i = 0; i < 400; ++i) {
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ(toRead, length);
+
+ char buf2[1000];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)1000, count);
+ ASSERT_TRUE(nsCString(buf.get() + 4 + (1000 * i), count).Equals(nsCString(buf2, count)));
+
+ toRead -= count;
+ }
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)0, count);
+}
+
+// What if start is > then the size of the buffer?
+TEST(TestSlicedInputStream, StartBiggerThan) {
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, 4000, 1, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)0, count);
+}
+
+// What if the length is > than the size of the buffer?
+TEST(TestSlicedInputStream, LengthBiggerThan) {
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, 0, 500000, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)500, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)500, count);
+}
+
+// What if the length is 0?
+TEST(TestSlicedInputStream, Length0) {
+ nsCString buf;
+ RefPtr<SlicedInputStream> sis =
+ CreateNonSeekableStreams(500, 0, 0, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)0, count);
+}
diff --git a/xpcom/tests/gtest/TestSnappyStreams.cpp b/xpcom/tests/gtest/TestSnappyStreams.cpp
new file mode 100644
index 000000000..99f41120b
--- /dev/null
+++ b/xpcom/tests/gtest/TestSnappyStreams.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <algorithm>
+#include "gtest/gtest.h"
+#include "Helpers.h"
+#include "mozilla/SnappyCompressOutputStream.h"
+#include "mozilla/SnappyUncompressInputStream.h"
+#include "nsIPipe.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+
+namespace {
+
+using mozilla::SnappyCompressOutputStream;
+using mozilla::SnappyUncompressInputStream;
+
+static already_AddRefed<nsIOutputStream>
+CompressPipe(nsIInputStream** aReaderOut)
+{
+ nsCOMPtr<nsIOutputStream> pipeWriter;
+
+ nsresult rv = NS_NewPipe(aReaderOut, getter_AddRefs(pipeWriter));
+ if (NS_FAILED(rv)) { return nullptr; }
+
+ nsCOMPtr<nsIOutputStream> compress =
+ new SnappyCompressOutputStream(pipeWriter);
+ return compress.forget();
+}
+
+// Verify the given number of bytes compresses to a smaller number of bytes.
+static void TestCompress(uint32_t aNumBytes)
+{
+ // Don't permit this test on small data sizes as snappy can slightly
+ // bloat very small content.
+ ASSERT_GT(aNumBytes, 1024u);
+
+ nsCOMPtr<nsIInputStream> pipeReader;
+ nsCOMPtr<nsIOutputStream> compress = CompressPipe(getter_AddRefs(pipeReader));
+ ASSERT_TRUE(compress);
+
+ nsTArray<char> inputData;
+ testing::CreateData(aNumBytes, inputData);
+
+ testing::WriteAllAndClose(compress, inputData);
+
+ nsAutoCString outputData;
+ nsresult rv = NS_ConsumeStream(pipeReader, UINT32_MAX, outputData);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_LT(outputData.Length(), inputData.Length());
+}
+
+// Verify that the given number of bytes can be compressed and uncompressed
+// successfully.
+static void TestCompressUncompress(uint32_t aNumBytes)
+{
+ nsCOMPtr<nsIInputStream> pipeReader;
+ nsCOMPtr<nsIOutputStream> compress = CompressPipe(getter_AddRefs(pipeReader));
+ ASSERT_TRUE(compress);
+
+ nsCOMPtr<nsIInputStream> uncompress =
+ new SnappyUncompressInputStream(pipeReader);
+
+ nsTArray<char> inputData;
+ testing::CreateData(aNumBytes, inputData);
+
+ testing::WriteAllAndClose(compress, inputData);
+
+ nsAutoCString outputData;
+ nsresult rv = NS_ConsumeStream(uncompress, UINT32_MAX, outputData);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ASSERT_EQ(inputData.Length(), outputData.Length());
+ for (uint32_t i = 0; i < inputData.Length(); ++i) {
+ EXPECT_EQ(inputData[i], outputData.get()[i]) << "Byte " << i;
+ }
+}
+
+static void TestUncompressCorrupt(const char* aCorruptData,
+ uint32_t aCorruptLength)
+{
+ nsCOMPtr<nsIInputStream> source;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(source), aCorruptData,
+ aCorruptLength);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> uncompress =
+ new SnappyUncompressInputStream(source);
+
+ nsAutoCString outputData;
+ rv = NS_ConsumeStream(uncompress, UINT32_MAX, outputData);
+ ASSERT_EQ(NS_ERROR_CORRUPTED_CONTENT, rv);
+}
+
+} // namespace
+
+TEST(SnappyStream, Compress_32k)
+{
+ TestCompress(32 * 1024);
+}
+
+TEST(SnappyStream, Compress_64k)
+{
+ TestCompress(64 * 1024);
+}
+
+TEST(SnappyStream, Compress_128k)
+{
+ TestCompress(128 * 1024);
+}
+
+TEST(SnappyStream, CompressUncompress_0)
+{
+ TestCompressUncompress(0);
+}
+
+TEST(SnappyStream, CompressUncompress_1)
+{
+ TestCompressUncompress(1);
+}
+
+TEST(SnappyStream, CompressUncompress_32)
+{
+ TestCompressUncompress(32);
+}
+
+TEST(SnappyStream, CompressUncompress_1k)
+{
+ TestCompressUncompress(1024);
+}
+
+TEST(SnappyStream, CompressUncompress_32k)
+{
+ TestCompressUncompress(32 * 1024);
+}
+
+TEST(SnappyStream, CompressUncompress_64k)
+{
+ TestCompressUncompress(64 * 1024);
+}
+
+TEST(SnappyStream, CompressUncompress_128k)
+{
+ TestCompressUncompress(128 * 1024);
+}
+
+// Test buffers that are not exactly power-of-2 in length to try to
+// exercise more edge cases. The number 13 is arbitrary.
+
+TEST(SnappyStream, CompressUncompress_256k_less_13)
+{
+ TestCompressUncompress((256 * 1024) - 13);
+}
+
+TEST(SnappyStream, CompressUncompress_256k)
+{
+ TestCompressUncompress(256 * 1024);
+}
+
+TEST(SnappyStream, CompressUncompress_256k_plus_13)
+{
+ TestCompressUncompress((256 * 1024) + 13);
+}
+
+TEST(SnappyStream, UncompressCorruptStreamIdentifier)
+{
+ static const char data[] = "This is not a valid compressed stream";
+ TestUncompressCorrupt(data, strlen(data));
+}
+
+TEST(SnappyStream, UncompressCorruptCompressedDataLength)
+{
+ static const char data[] = "\xff\x06\x00\x00sNaPpY" // stream identifier
+ "\x00\x99\x00\x00This is not a valid compressed stream";
+ static const uint32_t dataLength = (sizeof(data) / sizeof(const char)) - 1;
+ TestUncompressCorrupt(data, dataLength);
+}
+
+TEST(SnappyStream, UncompressCorruptCompressedDataContent)
+{
+ static const char data[] = "\xff\x06\x00\x00sNaPpY" // stream identifier
+ "\x00\x25\x00\x00This is not a valid compressed stream";
+ static const uint32_t dataLength = (sizeof(data) / sizeof(const char)) - 1;
+ TestUncompressCorrupt(data, dataLength);
+}
diff --git a/xpcom/tests/gtest/TestStateWatching.cpp b/xpcom/tests/gtest/TestStateWatching.cpp
new file mode 100644
index 000000000..16d06a5ff
--- /dev/null
+++ b/xpcom/tests/gtest/TestStateWatching.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/StateWatching.h"
+#include "mozilla/TaskQueue.h"
+#include "nsISupportsImpl.h"
+#include "VideoUtils.h"
+
+namespace TestStateWatching {
+
+using namespace mozilla;
+
+struct Foo {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Foo)
+ void Notify() { mNotified = true; }
+ bool mNotified = false;
+private:
+ ~Foo() {}
+};
+
+TEST(WatchManager, Shutdown)
+{
+ RefPtr<TaskQueue> queue = new TaskQueue(
+ GetMediaThreadPool(MediaThreadType::PLAYBACK));
+
+ RefPtr<Foo> p = new Foo;
+ WatchManager<Foo> manager(p, queue);
+ Watchable<bool> notifier(false, "notifier");
+
+ queue->Dispatch(NS_NewRunnableFunction([&] () {
+ manager.Watch(notifier, &Foo::Notify);
+ notifier = true; // Trigger the call to Foo::Notify().
+ manager.Shutdown(); // Shutdown() should cancel the call.
+ }));
+
+ queue->BeginShutdown();
+ queue->AwaitShutdownAndIdle();
+ EXPECT_FALSE(p->mNotified);
+}
+
+} // namespace TestStateWatching
diff --git a/xpcom/tests/gtest/TestStorageStream.cpp b/xpcom/tests/gtest/TestStorageStream.cpp
new file mode 100644
index 000000000..a49d6f6bc
--- /dev/null
+++ b/xpcom/tests/gtest/TestStorageStream.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <stdlib.h>
+#include "gtest/gtest.h"
+#include "Helpers.h"
+#include "nsCOMPtr.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIStorageStream.h"
+#include "nsTArray.h"
+
+namespace {
+
+void
+WriteData(nsIOutputStream* aOut, nsTArray<char>& aData, uint32_t aNumBytes,
+ nsACString& aDataWritten)
+{
+ uint32_t n;
+ nsresult rv = aOut->Write(aData.Elements(), aNumBytes, &n);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ aDataWritten.Append(aData.Elements(), aNumBytes);
+}
+
+} // namespace
+
+TEST(StorageStreams, Main)
+{
+ // generate some test data we will write in 4k chunks to the stream
+ nsTArray<char> kData;
+ testing::CreateData(4096, kData);
+
+ // track how much data was written so we can compare at the end
+ nsAutoCString dataWritten;
+
+ nsresult rv;
+ nsCOMPtr<nsIStorageStream> stor;
+
+ rv = NS_NewStorageStream(kData.Length(), UINT32_MAX, getter_AddRefs(stor));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = stor->GetOutputStream(0, getter_AddRefs(out));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, kData.Length(), dataWritten);
+
+ rv = out->Close();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ out = nullptr;
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = stor->NewInputStream(0, getter_AddRefs(in));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(in);
+ ASSERT_TRUE(cloneable != nullptr);
+ ASSERT_TRUE(cloneable->GetCloneable());
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = cloneable->Clone(getter_AddRefs(clone));
+
+ testing::ConsumeAndValidateStream(in, dataWritten);
+ testing::ConsumeAndValidateStream(clone, dataWritten);
+ in = nullptr;
+ clone = nullptr;
+
+ // now, write 3 more full 4k segments + 11 bytes, starting at 8192
+ // total written equals 20491 bytes
+
+ rv = stor->GetOutputStream(dataWritten.Length(), getter_AddRefs(out));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, 11, dataWritten);
+
+ rv = out->Close();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ out = nullptr;
+
+ // now, read all
+ rv = stor->NewInputStream(0, getter_AddRefs(in));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ testing::ConsumeAndValidateStream(in, dataWritten);
+ in = nullptr;
+}
+
+TEST(StorageStreams, EarlyInputStream)
+{
+ // generate some test data we will write in 4k chunks to the stream
+ nsTArray<char> kData;
+ testing::CreateData(4096, kData);
+
+ // track how much data was written so we can compare at the end
+ nsAutoCString dataWritten;
+
+ nsresult rv;
+ nsCOMPtr<nsIStorageStream> stor;
+
+ rv = NS_NewStorageStream(kData.Length(), UINT32_MAX, getter_AddRefs(stor));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ // Get input stream before writing data into the output stream
+ nsCOMPtr<nsIInputStream> in;
+ rv = stor->NewInputStream(0, getter_AddRefs(in));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ // Write data to output stream
+ nsCOMPtr<nsIOutputStream> out;
+ rv = stor->GetOutputStream(0, getter_AddRefs(out));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, kData.Length(), dataWritten);
+
+ rv = out->Close();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ out = nullptr;
+
+ // Should be able to consume input stream
+ testing::ConsumeAndValidateStream(in, dataWritten);
+ in = nullptr;
+}
diff --git a/xpcom/tests/gtest/TestStringStream.cpp b/xpcom/tests/gtest/TestStringStream.cpp
new file mode 100644
index 000000000..5591ed588
--- /dev/null
+++ b/xpcom/tests/gtest/TestStringStream.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "gtest/gtest.h"
+#include "Helpers.h"
+#include "nsICloneableInputStream.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsIInputStream.h"
+#include "nsCOMPtr.h"
+
+namespace {
+
+static void TestStringStream(uint32_t aNumBytes)
+{
+ nsTArray<char> inputData;
+ testing::CreateData(aNumBytes, inputData);
+ nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+}
+
+static void TestStringStreamClone(uint32_t aNumBytes)
+{
+ nsTArray<char> inputData;
+ testing::CreateData(aNumBytes, inputData);
+ nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream);
+ ASSERT_TRUE(cloneable != nullptr);
+ ASSERT_TRUE(cloneable->GetCloneable());
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = cloneable->Clone(getter_AddRefs(clone));
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+
+ // Release the stream to verify that the clone's string survives correctly.
+ stream = nullptr;
+
+ testing::ConsumeAndValidateStream(clone, inputString);
+}
+
+} // namespace
+
+TEST(StringStream, Simple_4k)
+{
+ TestStringStream(1024 * 4);
+}
+
+TEST(StringStream, Clone_4k)
+{
+ TestStringStreamClone(1024 * 4);
+}
diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp
new file mode 100644
index 000000000..285021b8e
--- /dev/null
+++ b/xpcom/tests/gtest/TestStrings.cpp
@@ -0,0 +1,982 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsReadableUtils.h"
+#include "nsCRTGlue.h"
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+#include "gtest/gtest.h"
+
+namespace TestStrings {
+
+using mozilla::fallible;
+
+void test_assign_helper(const nsACString& in, nsACString &_retval)
+{
+ _retval = in;
+}
+
+TEST(Strings, assign)
+{
+ nsCString result;
+ test_assign_helper(NS_LITERAL_CSTRING("a") + NS_LITERAL_CSTRING("b"), result);
+ EXPECT_STREQ(result.get(), "ab");
+}
+
+TEST(Strings, assign_c)
+{
+ nsCString c; c.Assign('c');
+ EXPECT_STREQ(c.get(), "c");
+}
+
+TEST(Strings, test1)
+{
+ NS_NAMED_LITERAL_STRING(empty, "");
+ const nsAString& aStr = empty;
+
+ nsAutoString buf(aStr);
+
+ int32_t n = buf.FindChar(',');
+ EXPECT_EQ(n, kNotFound);
+
+ n = buf.Length();
+
+ buf.Cut(0, n + 1);
+ n = buf.FindChar(',');
+
+ EXPECT_EQ(n, kNotFound);
+}
+
+TEST(Strings, test2)
+{
+ nsCString data("hello world");
+ const nsACString& aStr = data;
+
+ nsCString temp(aStr);
+ temp.Cut(0, 6);
+
+ EXPECT_STREQ(temp.get(), "world");
+}
+
+TEST(Strings, find)
+{
+ nsCString src("<!DOCTYPE blah blah blah>");
+
+ int32_t i = src.Find("DOCTYPE", true, 2, 1);
+ EXPECT_EQ(i, 2);
+}
+
+TEST(Strings, rfind)
+{
+ const char text[] = "<!DOCTYPE blah blah blah>";
+ const char term[] = "bLaH";
+ nsCString src(text);
+ int32_t i;
+
+ i = src.RFind(term, true, 3, -1);
+ EXPECT_EQ(i, kNotFound);
+
+ i = src.RFind(term, true, -1, -1);
+ EXPECT_EQ(i, 20);
+
+ i = src.RFind(term, true, 13, -1);
+ EXPECT_EQ(i, 10);
+
+ i = src.RFind(term, true, 22, 3);
+ EXPECT_EQ(i, 20);
+}
+
+TEST(Strings, rfind_2)
+{
+ const char text[] = "<!DOCTYPE blah blah blah>";
+ nsCString src(text);
+ int32_t i = src.RFind("TYPE", false, 5, -1);
+ EXPECT_EQ(i, 5);
+}
+
+TEST(Strings, rfind_3)
+{
+ const char text[] = "urn:mozilla:locale:en-US:necko";
+ nsAutoCString value(text);
+ int32_t i = value.RFind(":");
+ EXPECT_EQ(i, 24);
+}
+
+TEST(Strings, rfind_4)
+{
+ nsCString value("a.msf");
+ int32_t i = value.RFind(".msf");
+ EXPECT_EQ(i, 1);
+}
+
+TEST(Strings, findinreadable)
+{
+ const char text[] = "jar:jar:file:///c:/software/mozilla/mozilla_2006_02_21.jar!/browser/chrome/classic.jar!/";
+ nsAutoCString value(text);
+
+ nsACString::const_iterator begin, end;
+ value.BeginReading(begin);
+ value.EndReading(end);
+ nsACString::const_iterator delim_begin (begin),
+ delim_end (end);
+
+ // Search for last !/ at the end of the string
+ EXPECT_TRUE(FindInReadable(NS_LITERAL_CSTRING("!/"), delim_begin, delim_end));
+ char *r = ToNewCString(Substring(delim_begin, delim_end));
+ // Should match the first "!/" but not the last
+ EXPECT_NE(delim_end, end);
+ EXPECT_STREQ(r, "!/");
+ free(r);
+
+ delim_begin = begin;
+ delim_end = end;
+
+ // Search for first jar:
+ EXPECT_TRUE(FindInReadable(NS_LITERAL_CSTRING("jar:"), delim_begin, delim_end));
+
+ r = ToNewCString(Substring(delim_begin, delim_end));
+ // Should not match the first jar:, but the second one
+ EXPECT_EQ(delim_begin, begin);
+ EXPECT_STREQ(r, "jar:");
+ free(r);
+
+ // Search for jar: in a Substring
+ delim_begin = begin; delim_begin++;
+ delim_end = end;
+ EXPECT_TRUE(FindInReadable(NS_LITERAL_CSTRING("jar:"), delim_begin, delim_end));
+
+ r = ToNewCString(Substring(delim_begin, delim_end));
+ // Should not match the first jar:, but the second one
+ EXPECT_NE(delim_begin, begin);
+ EXPECT_STREQ(r, "jar:");
+ free(r);
+
+ // Should not find a match
+ EXPECT_FALSE(FindInReadable(NS_LITERAL_CSTRING("gecko"), delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+
+ // Should not find a match (search not beyond Substring)
+ delim_begin = begin; for (int i=0;i<6;i++) delim_begin++;
+ delim_end = end;
+ EXPECT_FALSE(FindInReadable(NS_LITERAL_CSTRING("jar:"), delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+
+ // Should not find a match (search not beyond Substring)
+ delim_begin = begin;
+ delim_end = end; for (int i=0;i<7;i++) delim_end--;
+ EXPECT_FALSE(FindInReadable(NS_LITERAL_CSTRING("classic"), delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+}
+
+TEST(Strings, rfindinreadable)
+{
+ const char text[] = "jar:jar:file:///c:/software/mozilla/mozilla_2006_02_21.jar!/browser/chrome/classic.jar!/";
+ nsAutoCString value(text);
+
+ nsACString::const_iterator begin, end;
+ value.BeginReading(begin);
+ value.EndReading(end);
+ nsACString::const_iterator delim_begin (begin),
+ delim_end (end);
+
+ // Search for last !/ at the end of the string
+ EXPECT_TRUE(RFindInReadable(NS_LITERAL_CSTRING("!/"), delim_begin, delim_end));
+ char *r = ToNewCString(Substring(delim_begin, delim_end));
+ // Should match the last "!/"
+ EXPECT_EQ(delim_end, end);
+ EXPECT_STREQ(r, "!/");
+ free(r);
+
+ delim_begin = begin;
+ delim_end = end;
+
+ // Search for last jar: but not the first one...
+ EXPECT_TRUE(RFindInReadable(NS_LITERAL_CSTRING("jar:"), delim_begin, delim_end));
+
+ r = ToNewCString(Substring(delim_begin, delim_end));
+ // Should not match the first jar:, but the second one
+ EXPECT_NE(delim_begin, begin);
+ EXPECT_STREQ(r, "jar:");
+ free(r);
+
+ // Search for jar: in a Substring
+ delim_begin = begin;
+ delim_end = begin; for (int i=0;i<6;i++) delim_end++;
+ EXPECT_TRUE(RFindInReadable(NS_LITERAL_CSTRING("jar:"), delim_begin, delim_end));
+
+ r = ToNewCString(Substring(delim_begin, delim_end));
+ // Should not match the first jar:, but the second one
+ EXPECT_EQ(delim_begin, begin);
+ EXPECT_STREQ(r, "jar:");
+ free(r);
+
+ // Should not find a match
+ delim_begin = begin;
+ delim_end = end;
+ EXPECT_FALSE(RFindInReadable(NS_LITERAL_CSTRING("gecko"), delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+
+ // Should not find a match (search not before Substring)
+ delim_begin = begin; for (int i=0;i<6;i++) delim_begin++;
+ delim_end = end;
+ EXPECT_FALSE(RFindInReadable(NS_LITERAL_CSTRING("jar:"), delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+
+ // Should not find a match (search not beyond Substring)
+ delim_begin = begin;
+ delim_end = end; for (int i=0;i<7;i++) delim_end--;
+ EXPECT_FALSE(RFindInReadable(NS_LITERAL_CSTRING("classic"), delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+}
+
+TEST(Strings, distance)
+{
+ const char text[] = "abc-xyz";
+ nsCString s(text);
+ nsCString::const_iterator begin, end;
+ s.BeginReading(begin);
+ s.EndReading(end);
+ size_t d = Distance(begin, end);
+ EXPECT_EQ(d, sizeof(text) - 1);
+}
+
+TEST(Strings, length)
+{
+ const char text[] = "abc-xyz";
+ nsCString s(text);
+ size_t d = s.Length();
+ EXPECT_EQ(d, sizeof(text) - 1);
+}
+
+TEST(Strings, trim)
+{
+ const char text[] = " a\t $ ";
+ const char set[] = " \t$";
+
+ nsCString s(text);
+ s.Trim(set);
+ EXPECT_STREQ(s.get(), "a");
+}
+
+TEST(Strings, replace_substr)
+{
+ const char text[] = "abc-ppp-qqq-ppp-xyz";
+ nsCString s(text);
+ s.ReplaceSubstring("ppp", "www");
+ EXPECT_STREQ(s.get(), "abc-www-qqq-www-xyz");
+
+ s.Assign("foobar");
+ s.ReplaceSubstring("foo", "bar");
+ s.ReplaceSubstring("bar", "");
+ EXPECT_STREQ(s.get(), "");
+
+ s.Assign("foofoofoo");
+ s.ReplaceSubstring("foo", "foo");
+ EXPECT_STREQ(s.get(), "foofoofoo");
+
+ s.Assign("foofoofoo");
+ s.ReplaceSubstring("of", "fo");
+ EXPECT_STREQ(s.get(), "fofoofooo");
+}
+
+TEST(Strings, replace_substr_2)
+{
+ const char *oldName = nullptr;
+ const char *newName = "user";
+ nsString acctName; acctName.AssignLiteral("forums.foo.com");
+ nsAutoString newAcctName, oldVal, newVal;
+ oldVal.AssignWithConversion(oldName);
+ newVal.AssignWithConversion(newName);
+ newAcctName.Assign(acctName);
+
+ // here, oldVal is empty. we are testing that this function
+ // does not hang. see bug 235355.
+ newAcctName.ReplaceSubstring(oldVal, newVal);
+
+ // we expect that newAcctName will be unchanged.
+ EXPECT_TRUE(newAcctName.Equals(acctName));
+}
+
+TEST(Strings, replace_substr_3)
+{
+ nsCString s;
+ s.Assign("abcabcabc");
+ s.ReplaceSubstring("ca", "X");
+ EXPECT_STREQ(s.get(), "abXbXbc");
+
+ s.Assign("abcabcabc");
+ s.ReplaceSubstring("ca", "XYZ");
+ EXPECT_STREQ(s.get(), "abXYZbXYZbc");
+
+ s.Assign("abcabcabc");
+ s.ReplaceSubstring("ca", "XY");
+ EXPECT_STREQ(s.get(), "abXYbXYbc");
+
+ s.Assign("abcabcabc");
+ s.ReplaceSubstring("ca", "XYZ!");
+ EXPECT_STREQ(s.get(), "abXYZ!bXYZ!bc");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "X");
+ EXPECT_STREQ(s.get(), "aXaXaX");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZ!");
+ EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XY");
+ EXPECT_STREQ(s.get(), "aXYaXYaXY");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZABC");
+ EXPECT_STREQ(s.get(), "aXYZABCaXYZABCaXYZABC");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZ");
+ EXPECT_STREQ(s.get(), "aXYZaXYZaXYZ");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZ!");
+ EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "X");
+ EXPECT_STREQ(s.get(), "XcdXcdXcd");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "XYZABC");
+ EXPECT_STREQ(s.get(), "XYZABCcdXYZABCcdXYZABCcd");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "XY");
+ EXPECT_STREQ(s.get(), "XYcdXYcdXYcd");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "XYZ!");
+ EXPECT_STREQ(s.get(), "XYZ!cdXYZ!cdXYZ!cd");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("notfound", "X");
+ EXPECT_STREQ(s.get(), "abcdabcdabcd");
+
+ s.Assign("abcdabcdabcd");
+ s.ReplaceSubstring("notfound", "longlongstring");
+ EXPECT_STREQ(s.get(), "abcdabcdabcd");
+}
+
+TEST(Strings, strip_ws)
+{
+ const char text[] = " a $ ";
+ nsCString s(text);
+ s.StripWhitespace();
+ EXPECT_STREQ(s.get(), "a$");
+}
+
+TEST(Strings, equals_ic)
+{
+ nsCString s;
+ EXPECT_FALSE(s.LowerCaseEqualsLiteral("view-source"));
+}
+
+TEST(Strings, fixed_string)
+{
+ char buf[256] = "hello world";
+
+ nsFixedCString s(buf, sizeof(buf));
+
+ EXPECT_EQ(s.Length(), strlen(buf));
+
+ EXPECT_STREQ(s.get(), buf);
+
+ s.Assign("foopy doopy doo");
+ EXPECT_EQ(s.get(), buf);
+}
+
+TEST(Strings, concat)
+{
+ nsCString bar("bar");
+ const nsACString& barRef = bar;
+
+ const nsPromiseFlatCString& result =
+ PromiseFlatCString(NS_LITERAL_CSTRING("foo") +
+ NS_LITERAL_CSTRING(",") +
+ barRef);
+ EXPECT_STREQ(result.get(), "foo,bar");
+}
+
+TEST(Strings, concat_2)
+{
+ nsCString fieldTextStr("xyz");
+ nsCString text("text");
+ const nsACString& aText = text;
+
+ nsAutoCString result( fieldTextStr + aText );
+
+ EXPECT_STREQ(result.get(), "xyztext");
+}
+
+TEST(Strings, concat_3)
+{
+ nsCString result;
+ nsCString ab("ab"), c("c");
+
+ result = ab + result + c;
+ EXPECT_STREQ(result.get(), "abc");
+}
+
+TEST(Strings, xpidl_string)
+{
+ nsXPIDLCString a, b;
+ a = b;
+ EXPECT_TRUE(a == b);
+
+ a.Adopt(0);
+ EXPECT_TRUE(a == b);
+
+ a.Append("foopy");
+ a.Assign(b);
+ EXPECT_TRUE(a == b);
+
+ a.Insert("", 0);
+ a.Assign(b);
+ EXPECT_TRUE(a == b);
+
+ const char text[] = "hello world";
+ *getter_Copies(a) = NS_strdup(text);
+ EXPECT_STREQ(a, text);
+
+ b = a;
+ EXPECT_STREQ(a, b);
+
+ a.Adopt(0);
+ nsACString::const_iterator begin, end;
+ a.BeginReading(begin);
+ a.EndReading(end);
+ char *r = ToNewCString(Substring(begin, end));
+ EXPECT_STREQ(r, "");
+ free(r);
+
+ a.Adopt(0);
+ EXPECT_TRUE(a.IsVoid());
+
+ int32_t index = a.FindCharInSet("xyz");
+ EXPECT_EQ(index, kNotFound);
+}
+
+TEST(Strings, empty_assign)
+{
+ nsCString a;
+ a.AssignLiteral("");
+
+ a.AppendLiteral("");
+
+ nsCString b;
+ b.SetCapacity(0);
+}
+
+TEST(Strings, set_length)
+{
+ const char kText[] = "Default Plugin";
+ nsCString buf;
+ buf.SetCapacity(sizeof(kText)-1);
+ buf.Assign(kText);
+ buf.SetLength(sizeof(kText)-1);
+ EXPECT_STREQ(buf.get(), kText);
+}
+
+TEST(Strings, substring)
+{
+ nsCString super("hello world"), sub("hello");
+
+ // this tests that |super| starts with |sub|,
+
+ EXPECT_TRUE(sub.Equals(StringHead(super, sub.Length())));
+
+ // and verifies that |sub| does not start with |super|.
+
+ EXPECT_FALSE(super.Equals(StringHead(sub, super.Length())));
+}
+
+#define test_append_expect(str, int, suffix, expect) \
+ str.Truncate(); \
+ str.AppendInt(suffix = int); \
+ EXPECT_TRUE(str.EqualsLiteral(expect));
+
+#define test_appends_expect(int, suffix, expect) \
+ test_append_expect(str, int, suffix, expect) \
+ test_append_expect(cstr, int, suffix, expect)
+
+#define test_appendbase(str, prefix, int, suffix, base) \
+ str.Truncate(); \
+ str.AppendInt(suffix = prefix ## int ## suffix, base); \
+ EXPECT_TRUE(str.EqualsLiteral(#int));
+
+#define test_appendbases(prefix, int, suffix, base) \
+ test_appendbase(str, prefix, int, suffix, base) \
+ test_appendbase(cstr, prefix, int, suffix, base)
+
+TEST(Strings, appendint)
+{
+ nsString str;
+ nsCString cstr;
+ int32_t L;
+ uint32_t UL;
+ int64_t LL;
+ uint64_t ULL;
+ test_appends_expect(INT32_MAX, L, "2147483647")
+ test_appends_expect(INT32_MIN, L, "-2147483648")
+ test_appends_expect(UINT32_MAX, UL, "4294967295")
+ test_appends_expect(INT64_MAX, LL, "9223372036854775807")
+ test_appends_expect(INT64_MIN, LL, "-9223372036854775808")
+ test_appends_expect(UINT64_MAX, ULL, "18446744073709551615")
+ test_appendbases(0, 17777777777, L, 8)
+ test_appendbases(0, 20000000000, L, 8)
+ test_appendbases(0, 37777777777, UL, 8)
+ test_appendbases(0, 777777777777777777777, LL, 8)
+ test_appendbases(0, 1000000000000000000000, LL, 8)
+ test_appendbases(0, 1777777777777777777777, ULL, 8)
+ test_appendbases(0x, 7fffffff, L, 16)
+ test_appendbases(0x, 80000000, L, 16)
+ test_appendbases(0x, ffffffff, UL, 16)
+ test_appendbases(0x, 7fffffffffffffff, LL, 16)
+ test_appendbases(0x, 8000000000000000, LL, 16)
+ test_appendbases(0x, ffffffffffffffff, ULL, 16)
+}
+
+TEST(Strings, appendint64)
+{
+ nsCString str;
+
+ int64_t max = INT64_MAX;
+ static const char max_expected[] = "9223372036854775807";
+ int64_t min = INT64_MIN;
+ static const char min_expected[] = "-9223372036854775808";
+ static const char min_expected_oct[] = "1000000000000000000000";
+ int64_t maxint_plus1 = 1LL << 32;
+ static const char maxint_plus1_expected[] = "4294967296";
+ static const char maxint_plus1_expected_x[] = "100000000";
+
+ str.AppendInt(max);
+
+ EXPECT_TRUE(str.Equals(max_expected));
+
+ str.Truncate();
+ str.AppendInt(min);
+ EXPECT_TRUE(str.Equals(min_expected));
+ str.Truncate();
+ str.AppendInt(min, 8);
+ EXPECT_TRUE(str.Equals(min_expected_oct));
+
+
+ str.Truncate();
+ str.AppendInt(maxint_plus1);
+ EXPECT_TRUE(str.Equals(maxint_plus1_expected));
+ str.Truncate();
+ str.AppendInt(maxint_plus1, 16);
+ EXPECT_TRUE(str.Equals(maxint_plus1_expected_x));
+}
+
+TEST(Strings, appendfloat)
+{
+ nsCString str;
+ double bigdouble = 11223344556.66;
+ static const char double_expected[] = "11223344556.66";
+ static const char float_expected[] = "0.01";
+
+ // AppendFloat is used to append doubles, therefore the precision must be
+ // large enough (see bug 327719)
+ str.AppendFloat( bigdouble );
+ EXPECT_TRUE(str.Equals(double_expected));
+
+ str.Truncate();
+ // AppendFloat is used to append floats (bug 327719 #27)
+ str.AppendFloat( 0.1f * 0.1f );
+ EXPECT_TRUE(str.Equals(float_expected));
+}
+
+TEST(Strings, findcharinset)
+{
+ nsCString buf("hello, how are you?");
+
+ int32_t index = buf.FindCharInSet(",?", 5);
+ EXPECT_EQ(index, 5);
+
+ index = buf.FindCharInSet("helo", 0);
+ EXPECT_EQ(index, 0);
+
+ index = buf.FindCharInSet("z?", 6);
+ EXPECT_EQ(index, (int32_t) buf.Length() - 1);
+}
+
+TEST(Strings, rfindcharinset)
+{
+ nsCString buf("hello, how are you?");
+
+ int32_t index = buf.RFindCharInSet(",?", 5);
+ EXPECT_EQ(index, 5);
+
+ index = buf.RFindCharInSet("helo", 0);
+ EXPECT_EQ(index, 0);
+
+ index = buf.RFindCharInSet("z?", 6);
+ EXPECT_EQ(index, kNotFound);
+
+ index = buf.RFindCharInSet("l", 5);
+ EXPECT_EQ(index, 3);
+
+ buf.Assign("abcdefghijkabc");
+
+ index = buf.RFindCharInSet("ab");
+ EXPECT_EQ(index, 12);
+
+ index = buf.RFindCharInSet("ab", 11);
+ EXPECT_EQ(index, 11);
+
+ index = buf.RFindCharInSet("ab", 10);
+ EXPECT_EQ(index, 1);
+
+ index = buf.RFindCharInSet("ab", 0);
+ EXPECT_EQ(index, 0);
+
+ index = buf.RFindCharInSet("cd", 1);
+ EXPECT_EQ(index, kNotFound);
+
+ index = buf.RFindCharInSet("h");
+ EXPECT_EQ(index, 7);
+}
+
+TEST(Strings, stringbuffer)
+{
+ const char kData[] = "hello world";
+
+ RefPtr<nsStringBuffer> buf;
+
+ buf = nsStringBuffer::Alloc(sizeof(kData));
+ EXPECT_TRUE(!!buf);
+
+ buf = nsStringBuffer::Alloc(sizeof(kData));
+ EXPECT_TRUE(!!buf);
+ char *data = (char *) buf->Data();
+ memcpy(data, kData, sizeof(kData));
+
+ nsCString str;
+ buf->ToString(sizeof(kData)-1, str);
+
+ nsStringBuffer *buf2;
+ buf2 = nsStringBuffer::FromString(str);
+
+ EXPECT_EQ(buf, buf2);
+}
+
+TEST(Strings, voided)
+{
+ const char kData[] = "hello world";
+
+ nsXPIDLCString str;
+ EXPECT_FALSE(str);
+ EXPECT_TRUE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+
+ str.Assign(kData);
+ EXPECT_STREQ(str, kData);
+
+ str.SetIsVoid(true);
+ EXPECT_FALSE(str);
+ EXPECT_TRUE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+
+ str.SetIsVoid(false);
+ EXPECT_STREQ(str, "");
+}
+
+TEST(Strings, voided_autostr)
+{
+ const char kData[] = "hello world";
+
+ nsAutoCString str;
+ EXPECT_FALSE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+
+ str.Assign(kData);
+ EXPECT_STREQ(str.get(), kData);
+
+ str.SetIsVoid(true);
+ EXPECT_TRUE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+
+ str.Assign(kData);
+ EXPECT_FALSE(str.IsVoid());
+ EXPECT_FALSE(str.IsEmpty());
+ EXPECT_STREQ(str.get(), kData);
+}
+
+TEST(Strings, voided_assignment)
+{
+ nsCString a, b;
+ b.SetIsVoid(true);
+ a = b;
+ EXPECT_TRUE(a.IsVoid());
+ EXPECT_EQ(a.get(), b.get());
+}
+
+TEST(Strings, empty_assignment)
+{
+ nsCString a, b;
+ a = b;
+ EXPECT_EQ(a.get(), b.get());
+}
+
+struct ToIntegerTest
+{
+ const char *str;
+ uint32_t radix;
+ int32_t result;
+ nsresult rv;
+};
+
+static const ToIntegerTest kToIntegerTests[] = {
+ { "123", 10, 123, NS_OK },
+ { "7b", 16, 123, NS_OK },
+ { "90194313659", 10, 0, NS_ERROR_ILLEGAL_VALUE },
+ { nullptr, 0, 0, NS_OK }
+};
+
+TEST(Strings, string_tointeger)
+{
+ nsresult rv;
+ for (const ToIntegerTest* t = kToIntegerTests; t->str; ++t) {
+ int32_t result = nsAutoCString(t->str).ToInteger(&rv, t->radix);
+ EXPECT_EQ(rv, t->rv);
+ EXPECT_EQ(result, t->result);
+ result = nsAutoCString(t->str).ToInteger(&rv, t->radix);
+ EXPECT_EQ(rv, t->rv);
+ EXPECT_EQ(result, t->result);
+ }
+}
+
+static void test_parse_string_helper(const char* str, char separator, int len,
+ const char* s1, const char* s2)
+{
+ nsCString data(str);
+ nsTArray<nsCString> results;
+ EXPECT_TRUE(ParseString(data, separator, results));
+ EXPECT_EQ(int(results.Length()), len);
+ const char* strings[] = { s1, s2 };
+ for (int i = 0; i < len; ++i) {
+ EXPECT_TRUE(results[i].Equals(strings[i]));
+ }
+}
+
+static void test_parse_string_helper0(const char* str, char separator)
+{
+ test_parse_string_helper(str, separator, 0, nullptr, nullptr);
+}
+
+static void test_parse_string_helper1(const char* str, char separator, const char* s1)
+{
+ test_parse_string_helper(str, separator, 1, s1, nullptr);
+}
+
+static void test_parse_string_helper2(const char* str, char separator, const char* s1, const char* s2)
+{
+ test_parse_string_helper(str, separator, 2, s1, s2);
+}
+
+TEST(String, parse_string)
+{
+ test_parse_string_helper1("foo, bar", '_', "foo, bar");
+ test_parse_string_helper2("foo, bar", ',', "foo", " bar");
+ test_parse_string_helper2("foo, bar ", ' ', "foo,", "bar");
+ test_parse_string_helper2("foo,bar", 'o', "f", ",bar");
+ test_parse_string_helper0("", '_');
+ test_parse_string_helper0(" ", ' ');
+ test_parse_string_helper1(" foo", ' ', "foo");
+ test_parse_string_helper1(" foo", ' ', "foo");
+}
+
+static void test_strip_chars_helper(const char16_t* str, const char16_t* strip, const nsAString& result, uint32_t offset=0)
+{
+ nsAutoString tmp(str);
+ nsAString& data = tmp;
+ data.StripChars(strip, offset);
+ EXPECT_TRUE(data.Equals(result));
+}
+
+TEST(String, strip_chars)
+{
+ test_strip_chars_helper(u"foo \r \nbar",
+ u" \n\r",
+ NS_LITERAL_STRING("foobar"));
+ test_strip_chars_helper(u"\r\nfoo\r\n",
+ u" \n\r",
+ NS_LITERAL_STRING("foo"));
+ test_strip_chars_helper(u"foo",
+ u" \n\r",
+ NS_LITERAL_STRING("foo"));
+ test_strip_chars_helper(u"foo",
+ u"fo",
+ NS_LITERAL_STRING(""));
+ test_strip_chars_helper(u"foo",
+ u"foo",
+ NS_LITERAL_STRING(""));
+ test_strip_chars_helper(u" foo",
+ u" ",
+ NS_LITERAL_STRING(" foo"), 1);
+}
+
+TEST(Strings, huge_capacity)
+{
+ nsString a, b, c, d, e, f, g, h, i, j, k, l, m, n;
+ nsCString n1;
+
+ // Ignore the result if the address space is less than 64-bit because
+ // some of the allocations above will exhaust the address space.
+ if (sizeof(void*) >= 8) {
+ EXPECT_TRUE(a.SetCapacity(1, fallible));
+ EXPECT_FALSE(a.SetCapacity(nsString::size_type(-1)/2, fallible));
+ EXPECT_TRUE(a.SetCapacity(0, fallible)); // free the allocated memory
+
+ EXPECT_TRUE(b.SetCapacity(1, fallible));
+ EXPECT_FALSE(b.SetCapacity(nsString::size_type(-1)/2 - 1, fallible));
+ EXPECT_TRUE(b.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(c.SetCapacity(1, fallible));
+ EXPECT_FALSE(c.SetCapacity(nsString::size_type(-1)/2, fallible));
+ EXPECT_TRUE(c.SetCapacity(0, fallible));
+
+ EXPECT_FALSE(d.SetCapacity(nsString::size_type(-1)/2 - 1, fallible));
+ EXPECT_FALSE(d.SetCapacity(nsString::size_type(-1)/2, fallible));
+ EXPECT_TRUE(d.SetCapacity(0, fallible));
+
+ EXPECT_FALSE(e.SetCapacity(nsString::size_type(-1)/4, fallible));
+ EXPECT_FALSE(e.SetCapacity(nsString::size_type(-1)/4 + 1, fallible));
+ EXPECT_TRUE(e.SetCapacity(0, fallible));
+
+ EXPECT_FALSE(f.SetCapacity(nsString::size_type(-1)/2, fallible));
+ EXPECT_TRUE(f.SetCapacity(0, fallible));
+
+ EXPECT_FALSE(g.SetCapacity(nsString::size_type(-1)/4 + 1000, fallible));
+ EXPECT_FALSE(g.SetCapacity(nsString::size_type(-1)/4 + 1001, fallible));
+ EXPECT_TRUE(g.SetCapacity(0, fallible));
+
+ EXPECT_FALSE(h.SetCapacity(nsString::size_type(-1)/4+1, fallible));
+ EXPECT_FALSE(h.SetCapacity(nsString::size_type(-1)/2, fallible));
+ EXPECT_TRUE(h.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(i.SetCapacity(1, fallible));
+ EXPECT_TRUE(i.SetCapacity(nsString::size_type(-1)/4 - 1000, fallible));
+ EXPECT_FALSE(i.SetCapacity(nsString::size_type(-1)/4 + 1, fallible));
+ EXPECT_TRUE(i.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(j.SetCapacity(nsString::size_type(-1)/4 - 1000, fallible));
+ EXPECT_FALSE(j.SetCapacity(nsString::size_type(-1)/4 + 1, fallible));
+ EXPECT_TRUE(j.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(k.SetCapacity(nsString::size_type(-1)/8 - 1000, fallible));
+ EXPECT_TRUE(k.SetCapacity(nsString::size_type(-1)/4 - 1001, fallible));
+ EXPECT_TRUE(k.SetCapacity(nsString::size_type(-1)/4 - 998, fallible));
+ EXPECT_FALSE(k.SetCapacity(nsString::size_type(-1)/4 + 1, fallible));
+ EXPECT_TRUE(k.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(l.SetCapacity(nsString::size_type(-1)/8, fallible));
+ EXPECT_TRUE(l.SetCapacity(nsString::size_type(-1)/8 + 1, fallible));
+ EXPECT_TRUE(l.SetCapacity(nsString::size_type(-1)/8 + 2, fallible));
+ EXPECT_TRUE(l.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(m.SetCapacity(nsString::size_type(-1)/8 + 1000, fallible));
+ EXPECT_TRUE(m.SetCapacity(nsString::size_type(-1)/8 + 1001, fallible));
+ EXPECT_TRUE(m.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(n.SetCapacity(nsString::size_type(-1)/8+1, fallible));
+ EXPECT_FALSE(n.SetCapacity(nsString::size_type(-1)/4, fallible));
+ EXPECT_TRUE(n.SetCapacity(0, fallible));
+
+ EXPECT_TRUE(n.SetCapacity(0, fallible));
+ EXPECT_TRUE(n.SetCapacity((nsString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 2 - 2, fallible));
+ EXPECT_TRUE(n.SetCapacity(0, fallible));
+ EXPECT_FALSE(n.SetCapacity((nsString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 2 - 1, fallible));
+ EXPECT_TRUE(n.SetCapacity(0, fallible));
+ EXPECT_TRUE(n1.SetCapacity(0, fallible));
+ EXPECT_TRUE(n1.SetCapacity((nsCString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 1 - 2, fallible));
+ EXPECT_TRUE(n1.SetCapacity(0, fallible));
+ EXPECT_FALSE(n1.SetCapacity((nsCString::size_type(-1)/2 - sizeof(nsStringBuffer)) / 1 - 1, fallible));
+ EXPECT_TRUE(n1.SetCapacity(0, fallible));
+ }
+}
+
+static void test_tofloat_helper(const nsString& aStr, float aExpected, bool aSuccess)
+{
+ nsresult result;
+ EXPECT_EQ(aStr.ToFloat(&result), aExpected);
+ if (aSuccess) {
+ EXPECT_EQ(result, NS_OK);
+ } else {
+ EXPECT_NE(result, NS_OK);
+ }
+}
+
+TEST(Strings, tofloat)
+{
+ test_tofloat_helper(NS_LITERAL_STRING("42"), 42.f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("42.0"), 42.f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("-42"), -42.f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("+42"), 42, true);
+ test_tofloat_helper(NS_LITERAL_STRING("13.37"), 13.37f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("1.23456789"), 1.23456789f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("1.98765432123456"), 1.98765432123456f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("0"), 0.f, true);
+ test_tofloat_helper(NS_LITERAL_STRING("1.e5"), 100000, true);
+ test_tofloat_helper(NS_LITERAL_STRING(""), 0.f, false);
+ test_tofloat_helper(NS_LITERAL_STRING("42foo"), 42.f, false);
+ test_tofloat_helper(NS_LITERAL_STRING("foo"), 0.f, false);
+}
+
+static void test_todouble_helper(const nsString& aStr, double aExpected, bool aSuccess)
+{
+ nsresult result;
+ EXPECT_EQ(aStr.ToDouble(&result), aExpected);
+ if (aSuccess) {
+ EXPECT_EQ(result, NS_OK);
+ } else {
+ EXPECT_NE(result, NS_OK);
+ }
+}
+
+TEST(Strings, todouble)
+{
+ test_todouble_helper(NS_LITERAL_STRING("42"), 42, true);
+ test_todouble_helper(NS_LITERAL_STRING("42.0"), 42, true);
+ test_todouble_helper(NS_LITERAL_STRING("-42"), -42, true);
+ test_todouble_helper(NS_LITERAL_STRING("+42"), 42, true);
+ test_todouble_helper(NS_LITERAL_STRING("13.37"), 13.37, true);
+ test_todouble_helper(NS_LITERAL_STRING("1.23456789"), 1.23456789, true);
+ test_todouble_helper(NS_LITERAL_STRING("1.98765432123456"), 1.98765432123456, true);
+ test_todouble_helper(NS_LITERAL_STRING("123456789.98765432123456"), 123456789.98765432123456, true);
+ test_todouble_helper(NS_LITERAL_STRING("0"), 0, true);
+ test_todouble_helper(NS_LITERAL_STRING("1.e5"), 100000, true);
+ test_todouble_helper(NS_LITERAL_STRING(""), 0, false);
+ test_todouble_helper(NS_LITERAL_STRING("42foo"), 42, false);
+ test_todouble_helper(NS_LITERAL_STRING("foo"), 0, false);
+}
+
+} // namespace TestStrings
diff --git a/xpcom/tests/gtest/TestSynchronization.cpp b/xpcom/tests/gtest/TestSynchronization.cpp
new file mode 100644
index 000000000..7ec55545a
--- /dev/null
+++ b/xpcom/tests/gtest/TestSynchronization.cpp
@@ -0,0 +1,313 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/CondVar.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Mutex.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+static PRThread*
+spawn(void (*run)(void*), void* arg)
+{
+ return PR_CreateThread(PR_SYSTEM_THREAD,
+ run,
+ arg,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD,
+ 0);
+}
+
+//-----------------------------------------------------------------------------
+// Sanity check: tests that can be done on a single thread
+//
+TEST(Synchronization, Sanity)
+{
+ Mutex lock("sanity::lock");
+ lock.Lock();
+ lock.AssertCurrentThreadOwns();
+ lock.Unlock();
+
+ {
+ MutexAutoLock autolock(lock);
+ lock.AssertCurrentThreadOwns();
+ }
+
+ lock.Lock();
+ lock.AssertCurrentThreadOwns();
+ {
+ MutexAutoUnlock autounlock(lock);
+ }
+ lock.AssertCurrentThreadOwns();
+ lock.Unlock();
+
+ ReentrantMonitor mon("sanity::monitor");
+ mon.Enter();
+ mon.AssertCurrentThreadIn();
+ mon.Enter();
+ mon.AssertCurrentThreadIn();
+ mon.Exit();
+ mon.AssertCurrentThreadIn();
+ mon.Exit();
+
+ {
+ ReentrantMonitorAutoEnter automon(mon);
+ mon.AssertCurrentThreadIn();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Mutex contention tests
+//
+static Mutex* gLock1;
+
+static void
+MutexContention_thread(void* /*arg*/)
+{
+ for (int i = 0; i < 100000; ++i) {
+ gLock1->Lock();
+ gLock1->AssertCurrentThreadOwns();
+ gLock1->Unlock();
+ }
+}
+
+TEST(Synchronization, MutexContention)
+{
+ gLock1 = new Mutex("lock1");
+ // PURPOSELY not checking for OOM. YAY!
+
+ PRThread* t1 = spawn(MutexContention_thread, nullptr);
+ PRThread* t2 = spawn(MutexContention_thread, nullptr);
+ PRThread* t3 = spawn(MutexContention_thread, nullptr);
+
+ PR_JoinThread(t1);
+ PR_JoinThread(t2);
+ PR_JoinThread(t3);
+
+ delete gLock1;
+}
+
+//-----------------------------------------------------------------------------
+// Monitor tests
+//
+static Monitor* gMon1;
+
+static void
+MonitorContention_thread(void* /*arg*/)
+{
+ for (int i = 0; i < 100000; ++i) {
+ gMon1->Lock();
+ gMon1->AssertCurrentThreadOwns();
+ gMon1->Unlock();
+ }
+}
+
+TEST(Synchronization, MonitorContention)
+{
+ gMon1 = new Monitor("mon1");
+
+ PRThread* t1 = spawn(MonitorContention_thread, nullptr);
+ PRThread* t2 = spawn(MonitorContention_thread, nullptr);
+ PRThread* t3 = spawn(MonitorContention_thread, nullptr);
+
+ PR_JoinThread(t1);
+ PR_JoinThread(t2);
+ PR_JoinThread(t3);
+
+ delete gMon1;
+}
+
+
+static ReentrantMonitor* gMon2;
+
+static void
+MonitorContention2_thread(void* /*arg*/)
+{
+ for (int i = 0; i < 100000; ++i) {
+ gMon2->Enter();
+ gMon2->AssertCurrentThreadIn();
+ {
+ gMon2->Enter();
+ gMon2->AssertCurrentThreadIn();
+ gMon2->Exit();
+ }
+ gMon2->AssertCurrentThreadIn();
+ gMon2->Exit();
+ }
+}
+
+TEST(Synchronization, MonitorContention2)
+{
+ gMon2 = new ReentrantMonitor("mon1");
+
+ PRThread* t1 = spawn(MonitorContention2_thread, nullptr);
+ PRThread* t2 = spawn(MonitorContention2_thread, nullptr);
+ PRThread* t3 = spawn(MonitorContention2_thread, nullptr);
+
+ PR_JoinThread(t1);
+ PR_JoinThread(t2);
+ PR_JoinThread(t3);
+
+ delete gMon2;
+}
+
+
+static ReentrantMonitor* gMon3;
+static int32_t gMonFirst;
+
+static void
+MonitorSyncSanity_thread(void* /*arg*/)
+{
+ gMon3->Enter();
+ gMon3->AssertCurrentThreadIn();
+ if (gMonFirst) {
+ gMonFirst = 0;
+ gMon3->Wait();
+ gMon3->Enter();
+ } else {
+ gMon3->Notify();
+ gMon3->Enter();
+ }
+ gMon3->AssertCurrentThreadIn();
+ gMon3->Exit();
+ gMon3->AssertCurrentThreadIn();
+ gMon3->Exit();
+}
+
+TEST(Synchronization, MonitorSyncSanity)
+{
+ gMon3 = new ReentrantMonitor("monitor::syncsanity");
+
+ for (int32_t i = 0; i < 10000; ++i) {
+ gMonFirst = 1;
+ PRThread* ping = spawn(MonitorSyncSanity_thread, nullptr);
+ PRThread* pong = spawn(MonitorSyncSanity_thread, nullptr);
+ PR_JoinThread(ping);
+ PR_JoinThread(pong);
+ }
+
+ delete gMon3;
+}
+
+//-----------------------------------------------------------------------------
+// Condvar tests
+//
+static Mutex* gCvlock1;
+static CondVar* gCv1;
+static int32_t gCvFirst;
+
+static void
+CondVarSanity_thread(void* /*arg*/)
+{
+ gCvlock1->Lock();
+ gCvlock1->AssertCurrentThreadOwns();
+ if (gCvFirst) {
+ gCvFirst = 0;
+ gCv1->Wait();
+ } else {
+ gCv1->Notify();
+ }
+ gCvlock1->AssertCurrentThreadOwns();
+ gCvlock1->Unlock();
+}
+
+TEST(Synchronization, CondVarSanity)
+{
+ gCvlock1 = new Mutex("cvlock1");
+ gCv1 = new CondVar(*gCvlock1, "cvlock1");
+
+ for (int32_t i = 0; i < 10000; ++i) {
+ gCvFirst = 1;
+ PRThread* ping = spawn(CondVarSanity_thread, nullptr);
+ PRThread* pong = spawn(CondVarSanity_thread, nullptr);
+ PR_JoinThread(ping);
+ PR_JoinThread(pong);
+ }
+
+ delete gCv1;
+ delete gCvlock1;
+}
+
+//-----------------------------------------------------------------------------
+// AutoLock tests
+//
+TEST(Synchronization, AutoLock)
+{
+ Mutex l1("autolock");
+ MutexAutoLock autol1(l1);
+
+ l1.AssertCurrentThreadOwns();
+
+ {
+ Mutex l2("autolock2");
+ MutexAutoLock autol2(l2);
+
+ l1.AssertCurrentThreadOwns();
+ l2.AssertCurrentThreadOwns();
+ }
+
+ l1.AssertCurrentThreadOwns();
+}
+
+//-----------------------------------------------------------------------------
+// AutoUnlock tests
+//
+TEST(Synchronization, AutoUnlock)
+{
+ Mutex l1("autounlock");
+ Mutex l2("autounlock2");
+
+ l1.Lock();
+ l1.AssertCurrentThreadOwns();
+
+ {
+ MutexAutoUnlock autol1(l1);
+ {
+ l2.Lock();
+ l2.AssertCurrentThreadOwns();
+
+ MutexAutoUnlock autol2(l2);
+ }
+ l2.AssertCurrentThreadOwns();
+ l2.Unlock();
+ }
+ l1.AssertCurrentThreadOwns();
+
+ l1.Unlock();
+}
+
+//-----------------------------------------------------------------------------
+// AutoMonitor tests
+//
+TEST(Synchronization, AutoMonitor)
+{
+ ReentrantMonitor m1("automonitor");
+ ReentrantMonitor m2("automonitor2");
+
+ m1.Enter();
+ m1.AssertCurrentThreadIn();
+ {
+ ReentrantMonitorAutoEnter autom1(m1);
+ m1.AssertCurrentThreadIn();
+
+ m2.Enter();
+ m2.AssertCurrentThreadIn();
+ {
+ ReentrantMonitorAutoEnter autom2(m2);
+ m1.AssertCurrentThreadIn();
+ m2.AssertCurrentThreadIn();
+ }
+ m2.AssertCurrentThreadIn();
+ m2.Exit();
+
+ m1.AssertCurrentThreadIn();
+ }
+ m1.AssertCurrentThreadIn();
+ m1.Exit();
+}
diff --git a/xpcom/tests/gtest/TestTArray.cpp b/xpcom/tests/gtest/TestTArray.cpp
new file mode 100644
index 000000000..10e33664f
--- /dev/null
+++ b/xpcom/tests/gtest/TestTArray.cpp
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsTArray.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+namespace TestTArray {
+
+struct Copyable
+{
+ Copyable()
+ : mDestructionCounter(nullptr)
+ {
+ }
+
+ ~Copyable()
+ {
+ if (mDestructionCounter) {
+ (*mDestructionCounter)++;
+ }
+ }
+
+ Copyable(const Copyable&) = default;
+ Copyable& operator=(const Copyable&) = default;
+
+ uint32_t* mDestructionCounter;
+};
+
+struct Movable
+{
+ Movable()
+ : mDestructionCounter(nullptr)
+ {
+ }
+
+ ~Movable()
+ {
+ if (mDestructionCounter) {
+ (*mDestructionCounter)++;
+ }
+ }
+
+ Movable(Movable&& aOther)
+ : mDestructionCounter(aOther.mDestructionCounter)
+ {
+ aOther.mDestructionCounter = nullptr;
+ }
+
+ uint32_t* mDestructionCounter;
+};
+
+} // namespace TestTArray
+
+template<>
+struct nsTArray_CopyChooser<TestTArray::Copyable>
+{
+ typedef nsTArray_CopyWithConstructors<TestTArray::Copyable> Type;
+};
+
+template<>
+struct nsTArray_CopyChooser<TestTArray::Movable>
+{
+ typedef nsTArray_CopyWithConstructors<TestTArray::Movable> Type;
+};
+
+namespace TestTArray {
+
+const nsTArray<int>& DummyArray()
+{
+ static nsTArray<int> sArray;
+ if (sArray.IsEmpty()) {
+ const int data[] = {4, 1, 2, 8};
+ sArray.AppendElements(data, ArrayLength(data));
+ }
+ return sArray;
+}
+
+// This returns an invalid nsTArray with a huge length in order to test that
+// fallible operations actually fail.
+#ifdef DEBUG
+const nsTArray<int>& FakeHugeArray()
+{
+ static nsTArray<int> sArray;
+ if (sArray.IsEmpty()) {
+ sArray.AppendElement();
+ ((nsTArrayHeader*)sArray.DebugGetHeader())->mLength = UINT32_MAX;
+ }
+ return sArray;
+}
+#endif
+
+TEST(TArray, AppendElementsRvalue)
+{
+ nsTArray<int> array;
+
+ nsTArray<int> temp(DummyArray());
+ array.AppendElements(Move(temp));
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyArray();
+ array.AppendElements(Move(temp));
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_TRUE(temp.IsEmpty());
+}
+
+TEST(TArray, Assign)
+{
+ nsTArray<int> array;
+ array.Assign(DummyArray());
+ ASSERT_EQ(DummyArray(), array);
+
+ ASSERT_TRUE(array.Assign(DummyArray(), fallible));
+ ASSERT_EQ(DummyArray(), array);
+
+#ifdef DEBUG
+ ASSERT_FALSE(array.Assign(FakeHugeArray(), fallible));
+#endif
+
+ nsTArray<int> array2;
+ array2.Assign(Move(array));
+ ASSERT_TRUE(array.IsEmpty());
+ ASSERT_EQ(DummyArray(), array2);
+}
+
+TEST(TArray, AssignmentOperatorSelfAssignment)
+{
+ nsTArray<int> array;
+ array = DummyArray();
+
+ array = array;
+ ASSERT_EQ(DummyArray(), array);
+ array = Move(array);
+ ASSERT_EQ(DummyArray(), array);
+}
+
+TEST(TArray, CopyOverlappingForwards)
+{
+ nsTArray<Movable> array;
+ const size_t rangeLength = 8;
+ const size_t initialLength = 2 * rangeLength;
+ array.AppendElements(initialLength);
+
+ uint32_t destructionCounters[initialLength];
+ for (uint32_t i = 0; i < initialLength; ++i) {
+ destructionCounters[i] = 0;
+ }
+ for (uint32_t i = 0; i < initialLength; ++i) {
+ array[i].mDestructionCounter = &destructionCounters[i];
+ }
+
+ const size_t removedLength = rangeLength / 2;
+ array.RemoveElementsAt(0, removedLength);
+
+ for (uint32_t i = 0; i < removedLength; ++i) {
+ ASSERT_EQ(destructionCounters[i], 1u);
+ }
+ for (uint32_t i = removedLength; i < initialLength; ++i) {
+ ASSERT_EQ(destructionCounters[i], 0u);
+ }
+}
+
+// The code to copy overlapping regions had a bug in that it wouldn't correctly
+// destroy all over the source elements being copied.
+TEST(TArray, CopyOverlappingBackwards)
+{
+ nsTArray<Copyable> array;
+ const size_t rangeLength = 8;
+ const size_t initialLength = 2 * rangeLength;
+ array.SetCapacity(3 * rangeLength);
+ array.AppendElements(initialLength);
+ // To tickle the bug, we need to copy a source region:
+ //
+ // ..XXXXX..
+ //
+ // such that it overlaps the destination region:
+ //
+ // ....XXXXX
+ //
+ // so we are forced to copy back-to-front to ensure correct behavior.
+ // The easiest way to do that is to call InsertElementsAt, which will force
+ // the desired kind of shift.
+ uint32_t destructionCounters[initialLength];
+ for (uint32_t i = 0; i < initialLength; ++i) {
+ destructionCounters[i] = 0;
+ }
+ for (uint32_t i = 0; i < initialLength; ++i) {
+ array[i].mDestructionCounter = &destructionCounters[i];
+ }
+
+ array.InsertElementsAt(0, rangeLength);
+
+ for (uint32_t i = 0; i < initialLength; ++i) {
+ ASSERT_EQ(destructionCounters[i], 1u);
+ }
+}
+
+} // namespace TestTArray
diff --git a/xpcom/tests/gtest/TestTArray2.cpp b/xpcom/tests/gtest/TestTArray2.cpp
new file mode 100644
index 000000000..421927104
--- /dev/null
+++ b/xpcom/tests/gtest/TestTArray2.cpp
@@ -0,0 +1,1033 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/ArrayUtils.h"
+#include "mozilla/Unused.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <iostream>
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOM.h"
+#include "nsIFile.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+namespace TestTArray {
+
+// Define this so we can use test_basic_array in test_comptr_array
+template <class T>
+inline bool operator<(const nsCOMPtr<T>& lhs, const nsCOMPtr<T>& rhs) {
+ return lhs.get() < rhs.get();
+}
+
+//----
+
+template <class ElementType>
+static bool test_basic_array(ElementType *data,
+ size_t dataLen,
+ const ElementType& extra) {
+ nsTArray<ElementType> ary;
+ ary.AppendElements(data, dataLen);
+ if (ary.Length() != dataLen) {
+ return false;
+ }
+ if (!(ary == ary)) {
+ return false;
+ }
+ size_t i;
+ for (i = 0; i < ary.Length(); ++i) {
+ if (ary[i] != data[i])
+ return false;
+ }
+ for (i = 0; i < ary.Length(); ++i) {
+ if (ary.SafeElementAt(i, extra) != data[i])
+ return false;
+ }
+ if (ary.SafeElementAt(ary.Length(), extra) != extra ||
+ ary.SafeElementAt(ary.Length() * 10, extra) != extra)
+ return false;
+ // ensure sort results in ascending order
+ ary.Sort();
+ size_t j = 0, k = ary.IndexOfFirstElementGt(extra);
+ if (k != 0 && ary[k-1] == extra)
+ return false;
+ for (i = 0; i < ary.Length(); ++i) {
+ k = ary.IndexOfFirstElementGt(ary[i]);
+ if (k == 0 || ary[k-1] != ary[i])
+ return false;
+ if (k < j)
+ return false;
+ j = k;
+ }
+ for (i = ary.Length(); --i; ) {
+ if (ary[i] < ary[i - 1])
+ return false;
+ if (ary[i] == ary[i - 1])
+ ary.RemoveElementAt(i);
+ }
+ if (!(ary == ary)) {
+ return false;
+ }
+ for (i = 0; i < ary.Length(); ++i) {
+ if (ary.BinaryIndexOf(ary[i]) != i)
+ return false;
+ }
+ if (ary.BinaryIndexOf(extra) != ary.NoIndex)
+ return false;
+ size_t oldLen = ary.Length();
+ ary.RemoveElement(data[dataLen / 2]);
+ if (ary.Length() != (oldLen - 1))
+ return false;
+ if (!(ary == ary))
+ return false;
+
+ size_t index = ary.Length() / 2;
+ if (!ary.InsertElementAt(index, extra))
+ return false;
+ if (!(ary == ary))
+ return false;
+ if (ary[index] != extra)
+ return false;
+ if (ary.IndexOf(extra) == ary.NoIndex)
+ return false;
+ if (ary.LastIndexOf(extra) == ary.NoIndex)
+ return false;
+ // ensure proper searching
+ if (ary.IndexOf(extra) > ary.LastIndexOf(extra))
+ return false;
+ if (ary.IndexOf(extra, index) != ary.LastIndexOf(extra, index))
+ return false;
+
+ nsTArray<ElementType> copy(ary);
+ if (!(ary == copy))
+ return false;
+ for (i = 0; i < copy.Length(); ++i) {
+ if (ary[i] != copy[i])
+ return false;
+ }
+ if (!ary.AppendElements(copy))
+ return false;
+ size_t cap = ary.Capacity();
+ ary.RemoveElementsAt(copy.Length(), copy.Length());
+ ary.Compact();
+ if (ary.Capacity() == cap)
+ return false;
+
+ ary.Clear();
+ if (ary.IndexOf(extra) != ary.NoIndex)
+ return false;
+ if (ary.LastIndexOf(extra) != ary.NoIndex)
+ return false;
+
+ ary.Clear();
+ if (!ary.IsEmpty() || ary.Elements() == nullptr)
+ return false;
+ if (!(ary == nsTArray<ElementType>()))
+ return false;
+ if (ary == copy)
+ return false;
+ if (ary.SafeElementAt(0, extra) != extra ||
+ ary.SafeElementAt(10, extra) != extra)
+ return false;
+
+ ary = copy;
+ if (!(ary == copy))
+ return false;
+ for (i = 0; i < copy.Length(); ++i) {
+ if (ary[i] != copy[i])
+ return false;
+ }
+
+ if (!ary.InsertElementsAt(0, copy))
+ return false;
+ if (ary == copy)
+ return false;
+ ary.RemoveElementsAt(0, copy.Length());
+ for (i = 0; i < copy.Length(); ++i) {
+ if (ary[i] != copy[i])
+ return false;
+ }
+
+ // These shouldn't crash!
+ nsTArray<ElementType> empty;
+ ary.AppendElements(reinterpret_cast<ElementType *>(0), 0);
+ ary.AppendElements(empty);
+
+ // See bug 324981
+ ary.RemoveElement(extra);
+ ary.RemoveElement(extra);
+
+ return true;
+}
+
+TEST(TArray, test_int_array) {
+ int data[] = {4,6,8,2,4,1,5,7,3};
+ ASSERT_TRUE(test_basic_array(data, ArrayLength(data), int(14)));
+}
+
+TEST(TArray, test_int64_array) {
+ int64_t data[] = {4,6,8,2,4,1,5,7,3};
+ ASSERT_TRUE(test_basic_array(data, ArrayLength(data), int64_t(14)));
+}
+
+TEST(TArray, test_char_array) {
+ char data[] = {4,6,8,2,4,1,5,7,3};
+ ASSERT_TRUE(test_basic_array(data, ArrayLength(data), char(14)));
+}
+
+TEST(TArray, test_uint32_array) {
+ uint32_t data[] = {4,6,8,2,4,1,5,7,3};
+ ASSERT_TRUE(test_basic_array(data, ArrayLength(data), uint32_t(14)));
+}
+
+//----
+
+class Object {
+ public:
+ Object() : mNum(0) {
+ }
+ Object(const char *str, uint32_t num) : mStr(str), mNum(num) {
+ }
+ Object(const Object& other) : mStr(other.mStr), mNum(other.mNum) {
+ }
+ ~Object() {}
+
+ Object& operator=(const Object& other) {
+ mStr = other.mStr;
+ mNum = other.mNum;
+ return *this;
+ }
+
+ bool operator==(const Object& other) const {
+ return mStr == other.mStr && mNum == other.mNum;
+ }
+
+ bool operator<(const Object& other) const {
+ // sort based on mStr only
+ return mStr.Compare(other.mStr.get()) < 0;
+ }
+
+ const char *Str() const { return mStr.get(); }
+ uint32_t Num() const { return mNum; }
+
+ private:
+ nsCString mStr;
+ uint32_t mNum;
+};
+
+TEST(TArray, test_object_array) {
+ nsTArray<Object> objArray;
+ const char kdata[] = "hello world";
+ size_t i;
+ for (i = 0; i < ArrayLength(kdata); ++i) {
+ char x[] = {kdata[i],'\0'};
+ ASSERT_TRUE(objArray.AppendElement(Object(x, i)));
+ }
+ for (i = 0; i < ArrayLength(kdata); ++i) {
+ ASSERT_EQ(objArray[i].Str()[0], kdata[i]);
+ ASSERT_EQ(objArray[i].Num(), i);
+ }
+ objArray.Sort();
+ const char ksorted[] = "\0 dehllloorw";
+ for (i = 0; i < ArrayLength(kdata)-1; ++i) {
+ ASSERT_EQ(objArray[i].Str()[0], ksorted[i]);
+ }
+}
+
+class Countable {
+ static int sCount;
+
+ public:
+ Countable()
+ {
+ sCount++;
+ }
+
+ Countable(const Countable& aOther)
+ {
+ sCount++;
+ }
+
+ static int Count() { return sCount; }
+};
+
+class Moveable {
+ static int sCount;
+
+ public:
+ Moveable()
+ {
+ sCount++;
+ }
+
+ Moveable(const Moveable& aOther)
+ {
+ sCount++;
+ }
+
+ Moveable(Moveable&& aOther)
+ {
+ // Do not increment sCount
+ }
+
+ static int Count() { return sCount; }
+};
+
+/* static */ int Countable::sCount = 0;
+/* static */ int Moveable::sCount = 0;
+
+static nsTArray<int> returns_by_value() {
+ nsTArray<int> result;
+ return result;
+}
+
+TEST(TArray, test_return_by_value) {
+ nsTArray<int> result = returns_by_value();
+ ASSERT_TRUE(true); // This is just a compilation test.
+}
+
+TEST(TArray, test_move_array) {
+ nsTArray<Countable> countableArray;
+ uint32_t i;
+ for (i = 0; i < 4; ++i) {
+ ASSERT_TRUE(countableArray.AppendElement(Countable()));
+ }
+
+ ASSERT_EQ(Countable::Count(), 8);
+
+ const nsTArray<Countable>& constRefCountableArray = countableArray;
+
+ ASSERT_EQ(Countable::Count(), 8);
+
+ nsTArray<Countable> copyCountableArray(constRefCountableArray);
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ nsTArray<Countable>&& moveRefCountableArray = Move(countableArray);
+ moveRefCountableArray.Length(); // Make compilers happy.
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ nsTArray<Countable> movedCountableArray(Move(countableArray));
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ // Test ctor
+ FallibleTArray<Countable> differentAllocatorCountableArray(Move(copyCountableArray));
+ // operator=
+ copyCountableArray = Move(differentAllocatorCountableArray);
+ differentAllocatorCountableArray = Move(copyCountableArray);
+ // And the other ctor
+ nsTArray<Countable> copyCountableArray2(Move(differentAllocatorCountableArray));
+ // with auto
+ AutoTArray<Countable, 3> autoCountableArray(Move(copyCountableArray2));
+ // operator=
+ copyCountableArray2 = Move(autoCountableArray);
+ // Mix with FallibleTArray
+ FallibleTArray<Countable> differentAllocatorCountableArray2(Move(copyCountableArray2));
+ AutoTArray<Countable, 4> autoCountableArray2(Move(differentAllocatorCountableArray2));
+ differentAllocatorCountableArray2 = Move(autoCountableArray2);
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ nsTArray<Moveable> moveableArray;
+ for (i = 0; i < 4; ++i) {
+ ASSERT_TRUE(moveableArray.AppendElement(Moveable()));
+ }
+
+ ASSERT_EQ(Moveable::Count(), 4);
+
+ const nsTArray<Moveable>& constRefMoveableArray = moveableArray;
+
+ ASSERT_EQ(Moveable::Count(), 4);
+
+ nsTArray<Moveable> copyMoveableArray(constRefMoveableArray);
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ nsTArray<Moveable>&& moveRefMoveableArray = Move(moveableArray);
+ moveRefMoveableArray.Length(); // Make compilers happy.
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ nsTArray<Moveable> movedMoveableArray(Move(moveableArray));
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ // Test ctor
+ FallibleTArray<Moveable> differentAllocatorMoveableArray(Move(copyMoveableArray));
+ // operator=
+ copyMoveableArray = Move(differentAllocatorMoveableArray);
+ differentAllocatorMoveableArray = Move(copyMoveableArray);
+ // And the other ctor
+ nsTArray<Moveable> copyMoveableArray2(Move(differentAllocatorMoveableArray));
+ // with auto
+ AutoTArray<Moveable, 3> autoMoveableArray(Move(copyMoveableArray2));
+ // operator=
+ copyMoveableArray2 = Move(autoMoveableArray);
+ // Mix with FallibleTArray
+ FallibleTArray<Moveable> differentAllocatorMoveableArray2(Move(copyMoveableArray2));
+ AutoTArray<Moveable, 4> autoMoveableArray2(Move(differentAllocatorMoveableArray2));
+ differentAllocatorMoveableArray2 = Move(autoMoveableArray2);
+
+ ASSERT_EQ(Moveable::Count(), 8);
+}
+
+//----
+
+TEST(TArray, test_string_array) {
+ nsTArray<nsCString> strArray;
+ const char kdata[] = "hello world";
+ size_t i;
+ for (i = 0; i < ArrayLength(kdata); ++i) {
+ nsCString str;
+ str.Assign(kdata[i]);
+ ASSERT_TRUE(strArray.AppendElement(str));
+ }
+ for (i = 0; i < ArrayLength(kdata); ++i) {
+ ASSERT_EQ(strArray[i].CharAt(0), kdata[i]);
+ }
+
+ const char kextra[] = "foo bar";
+ size_t oldLen = strArray.Length();
+ ASSERT_TRUE(strArray.AppendElement(kextra));
+ strArray.RemoveElement(kextra);
+ ASSERT_EQ(oldLen, strArray.Length());
+
+ ASSERT_EQ(strArray.IndexOf("e"), size_t(1));
+
+ strArray.Sort();
+ const char ksorted[] = "\0 dehllloorw";
+ for (i = ArrayLength(kdata); i--; ) {
+ ASSERT_EQ(strArray[i].CharAt(0), ksorted[i]);
+ if (i > 0 && strArray[i] == strArray[i - 1])
+ strArray.RemoveElementAt(i);
+ }
+ for (i = 0; i < strArray.Length(); ++i) {
+ ASSERT_EQ(strArray.BinaryIndexOf(strArray[i]), i);
+ }
+ auto no_index = strArray.NoIndex; // Fixes gtest compilation error
+ ASSERT_EQ(strArray.BinaryIndexOf(EmptyCString()), no_index);
+
+ nsCString rawArray[MOZ_ARRAY_LENGTH(kdata) - 1];
+ for (i = 0; i < ArrayLength(rawArray); ++i)
+ rawArray[i].Assign(kdata + i); // substrings of kdata
+
+ ASSERT_TRUE(test_basic_array(rawArray, ArrayLength(rawArray),
+ nsCString("foopy")));
+}
+
+//----
+
+typedef nsCOMPtr<nsIFile> FilePointer;
+
+class nsFileNameComparator {
+ public:
+ bool Equals(const FilePointer &a, const char *b) const {
+ nsAutoCString name;
+ a->GetNativeLeafName(name);
+ return name.Equals(b);
+ }
+};
+
+TEST(TArray, test_comptr_array) {
+ FilePointer tmpDir;
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir));
+ ASSERT_TRUE(tmpDir);
+ const char *kNames[] = {
+ "foo.txt", "bar.html", "baz.gif"
+ };
+ nsTArray<FilePointer> fileArray;
+ size_t i;
+ for (i = 0; i < ArrayLength(kNames); ++i) {
+ FilePointer f;
+ tmpDir->Clone(getter_AddRefs(f));
+ ASSERT_TRUE(f);
+ ASSERT_FALSE(NS_FAILED(f->AppendNative(nsDependentCString(kNames[i]))));
+ fileArray.AppendElement(f);
+ }
+
+ ASSERT_EQ(fileArray.IndexOf(kNames[1], 0, nsFileNameComparator()), size_t(1));
+
+ // It's unclear what 'operator<' means for nsCOMPtr, but whatever...
+ ASSERT_TRUE(test_basic_array(fileArray.Elements(), fileArray.Length(),
+ tmpDir));
+}
+
+//----
+
+class RefcountedObject {
+ public:
+ RefcountedObject() : rc(0) {}
+ void AddRef() {
+ ++rc;
+ }
+ void Release() {
+ if (--rc == 0)
+ delete this;
+ }
+ ~RefcountedObject() {}
+ private:
+ int32_t rc;
+};
+
+TEST(TArray, test_refptr_array) {
+ nsTArray< RefPtr<RefcountedObject> > objArray;
+
+ RefcountedObject *a = new RefcountedObject(); a->AddRef();
+ RefcountedObject *b = new RefcountedObject(); b->AddRef();
+ RefcountedObject *c = new RefcountedObject(); c->AddRef();
+
+ objArray.AppendElement(a);
+ objArray.AppendElement(b);
+ objArray.AppendElement(c);
+
+ ASSERT_EQ(objArray.IndexOf(b), size_t(1));
+
+ a->Release();
+ b->Release();
+ c->Release();
+}
+
+//----
+
+TEST(TArray, test_ptrarray) {
+ nsTArray<uint32_t*> ary;
+ ASSERT_EQ(ary.SafeElementAt(0), nullptr);
+ ASSERT_EQ(ary.SafeElementAt(1000), nullptr);
+
+ uint32_t a = 10;
+ ary.AppendElement(&a);
+ ASSERT_EQ(*ary[0], a);
+ ASSERT_EQ(*ary.SafeElementAt(0), a);
+
+ nsTArray<const uint32_t*> cary;
+ ASSERT_EQ(cary.SafeElementAt(0), nullptr);
+ ASSERT_EQ(cary.SafeElementAt(1000), nullptr);
+
+ const uint32_t b = 14;
+ cary.AppendElement(&a);
+ cary.AppendElement(&b);
+ ASSERT_EQ(*cary[0], a);
+ ASSERT_EQ(*cary[1], b);
+ ASSERT_EQ(*cary.SafeElementAt(0), a);
+ ASSERT_EQ(*cary.SafeElementAt(1), b);
+}
+
+//----
+
+// This test relies too heavily on the existence of DebugGetHeader to be
+// useful in non-debug builds.
+#ifdef DEBUG
+TEST(TArray, test_autoarray) {
+ uint32_t data[] = {4,6,8,2,4,1,5,7,3};
+ AutoTArray<uint32_t, MOZ_ARRAY_LENGTH(data)> array;
+
+ void* hdr = array.DebugGetHeader();
+ ASSERT_NE(hdr, nsTArray<uint32_t>().DebugGetHeader());
+ ASSERT_NE(hdr, (AutoTArray<uint32_t, MOZ_ARRAY_LENGTH(data)>().DebugGetHeader()));
+
+ array.AppendElement(1u);
+ ASSERT_EQ(hdr, array.DebugGetHeader());
+
+ array.RemoveElement(1u);
+ array.AppendElements(data, ArrayLength(data));
+ ASSERT_EQ(hdr, array.DebugGetHeader());
+
+ array.AppendElement(2u);
+ ASSERT_NE(hdr, array.DebugGetHeader());
+
+ array.Clear();
+ array.Compact();
+ ASSERT_EQ(hdr, array.DebugGetHeader());
+ array.AppendElements(data, ArrayLength(data));
+ ASSERT_EQ(hdr, array.DebugGetHeader());
+
+ nsTArray<uint32_t> array2;
+ void* emptyHdr = array2.DebugGetHeader();
+ array.SwapElements(array2);
+ ASSERT_NE(emptyHdr, array.DebugGetHeader());
+ ASSERT_NE(hdr, array2.DebugGetHeader());
+ size_t i;
+ for (i = 0; i < ArrayLength(data); ++i) {
+ ASSERT_EQ(array2[i], data[i]);
+ }
+ ASSERT_TRUE(array.IsEmpty());
+
+ array.Compact();
+ array.AppendElements(data, ArrayLength(data));
+ uint32_t data3[] = {5, 7, 11};
+ AutoTArray<uint32_t, MOZ_ARRAY_LENGTH(data3)> array3;
+ array3.AppendElements(data3, ArrayLength(data3));
+ array.SwapElements(array3);
+ for (i = 0; i < ArrayLength(data); ++i) {
+ ASSERT_EQ(array3[i], data[i]);
+ }
+ for (i = 0; i < ArrayLength(data3); ++i) {
+ ASSERT_EQ(array[i], data3[i]);
+ }
+}
+#endif
+
+//----
+
+// IndexOf used to potentially scan beyond the end of the array. Test for
+// this incorrect behavior by adding a value (5), removing it, then seeing
+// if IndexOf finds it.
+TEST(TArray, test_indexof) {
+ nsTArray<int> array;
+ array.AppendElement(0);
+ // add and remove the 5
+ array.AppendElement(5);
+ array.RemoveElementAt(1);
+ // we should not find the 5!
+ auto no_index = array.NoIndex; // Fixes gtest compilation error.
+ ASSERT_EQ(array.IndexOf(5, 1), no_index);
+}
+
+//----
+
+template <class Array>
+static bool is_heap(const Array& ary, size_t len) {
+ size_t index = 1;
+ while (index < len) {
+ if (ary[index] > ary[(index - 1) >> 1])
+ return false;
+ index++;
+ }
+ return true;
+}
+
+//----
+
+// An array |arr| is using its auto buffer if |&arr < arr.Elements()| and
+// |arr.Elements() - &arr| is small.
+
+#define IS_USING_AUTO(arr) \
+ ((uintptr_t) &(arr) < (uintptr_t) arr.Elements() && \
+ ((ptrdiff_t)arr.Elements() - (ptrdiff_t)&arr) <= 16)
+
+#define CHECK_IS_USING_AUTO(arr) \
+ do { \
+ ASSERT_TRUE(IS_USING_AUTO(arr)); \
+ } while(0)
+
+#define CHECK_NOT_USING_AUTO(arr) \
+ do { \
+ ASSERT_FALSE(IS_USING_AUTO(arr)); \
+ } while(0)
+
+#define CHECK_USES_SHARED_EMPTY_HDR(arr) \
+ do { \
+ nsTArray<int> _empty; \
+ ASSERT_EQ(_empty.Elements(), arr.Elements()); \
+ } while(0)
+
+#define CHECK_EQ_INT(actual, expected) \
+ do { \
+ ASSERT_EQ((actual), (expected)); \
+ } while(0)
+
+#define CHECK_ARRAY(arr, data) \
+ do { \
+ CHECK_EQ_INT((arr).Length(), (size_t)ArrayLength(data)); \
+ for (size_t _i = 0; _i < ArrayLength(data); _i++) { \
+ CHECK_EQ_INT((arr)[_i], (data)[_i]); \
+ } \
+ } while(0)
+
+TEST(TArray, test_swap) {
+ // Test nsTArray::SwapElements. Unfortunately there are many cases.
+ int data1[] = {8, 6, 7, 5};
+ int data2[] = {3, 0, 9};
+
+ // Swap two auto arrays.
+ {
+ AutoTArray<int, 8> a;
+ AutoTArray<int, 6> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+ b.AppendElements(data2, ArrayLength(data2));
+ CHECK_IS_USING_AUTO(a);
+ CHECK_IS_USING_AUTO(b);
+
+ a.SwapElements(b);
+
+ CHECK_IS_USING_AUTO(a);
+ CHECK_IS_USING_AUTO(b);
+ CHECK_ARRAY(a, data2);
+ CHECK_ARRAY(b, data1);
+ }
+
+ // Swap two auto arrays -- one whose data lives on the heap, the other whose
+ // data lives on the stack -- which each fits into the other's auto storage.
+ {
+ AutoTArray<int, 3> a;
+ AutoTArray<int, 3> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+ a.RemoveElementAt(3);
+ b.AppendElements(data2, ArrayLength(data2));
+
+ // Here and elsewhere, we assert that if we start with an auto array
+ // capable of storing N elements, we store N+1 elements into the array, and
+ // then we remove one element, that array is still not using its auto
+ // buffer.
+ //
+ // This isn't at all required by the TArray API. It would be fine if, when
+ // we shrink back to N elements, the TArray frees its heap storage and goes
+ // back to using its stack storage. But we assert here as a check that the
+ // test does what we expect. If the TArray implementation changes, just
+ // change the failing assertions.
+ CHECK_NOT_USING_AUTO(a);
+
+ // This check had better not change, though.
+ CHECK_IS_USING_AUTO(b);
+
+ a.SwapElements(b);
+
+ CHECK_IS_USING_AUTO(b);
+ CHECK_ARRAY(a, data2);
+ int expectedB[] = {8, 6, 7};
+ CHECK_ARRAY(b, expectedB);
+ }
+
+ // Swap two auto arrays which are using heap storage such that one fits into
+ // the other's auto storage, but the other needs to stay on the heap.
+ {
+ AutoTArray<int, 3> a;
+ AutoTArray<int, 2> b;
+ a.AppendElements(data1, ArrayLength(data1));
+ a.RemoveElementAt(3);
+
+ b.AppendElements(data2, ArrayLength(data2));
+ b.RemoveElementAt(2);
+
+ CHECK_NOT_USING_AUTO(a);
+ CHECK_NOT_USING_AUTO(b);
+
+ a.SwapElements(b);
+
+ CHECK_NOT_USING_AUTO(b);
+
+ int expected1[] = {3, 0};
+ int expected2[] = {8, 6, 7};
+
+ CHECK_ARRAY(a, expected1);
+ CHECK_ARRAY(b, expected2);
+ }
+
+ // Swap two arrays, neither of which fits into the other's auto-storage.
+ {
+ AutoTArray<int, 1> a;
+ AutoTArray<int, 3> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+ b.AppendElements(data2, ArrayLength(data2));
+
+ a.SwapElements(b);
+
+ CHECK_ARRAY(a, data2);
+ CHECK_ARRAY(b, data1);
+ }
+
+ // Swap an empty nsTArray with a non-empty AutoTArray.
+ {
+ nsTArray<int> a;
+ AutoTArray<int, 3> b;
+
+ b.AppendElements(data2, ArrayLength(data2));
+ CHECK_IS_USING_AUTO(b);
+
+ a.SwapElements(b);
+
+ CHECK_ARRAY(a, data2);
+ CHECK_EQ_INT(b.Length(), size_t(0));
+ CHECK_IS_USING_AUTO(b);
+ }
+
+ // Swap two big auto arrays.
+ {
+ const unsigned size = 8192;
+ AutoTArray<unsigned, size> a;
+ AutoTArray<unsigned, size> b;
+
+ for (unsigned i = 0; i < size; i++) {
+ a.AppendElement(i);
+ b.AppendElement(i + 1);
+ }
+
+ CHECK_IS_USING_AUTO(a);
+ CHECK_IS_USING_AUTO(b);
+
+ a.SwapElements(b);
+
+ CHECK_IS_USING_AUTO(a);
+ CHECK_IS_USING_AUTO(b);
+
+ CHECK_EQ_INT(a.Length(), size_t(size));
+ CHECK_EQ_INT(b.Length(), size_t(size));
+
+ for (unsigned i = 0; i < size; i++) {
+ CHECK_EQ_INT(a[i], i + 1);
+ CHECK_EQ_INT(b[i], i);
+ }
+ }
+
+ // Swap two arrays and make sure that their capacities don't increase
+ // unnecessarily.
+ {
+ nsTArray<int> a;
+ nsTArray<int> b;
+ b.AppendElements(data2, ArrayLength(data2));
+
+ CHECK_EQ_INT(a.Capacity(), size_t(0));
+ size_t bCapacity = b.Capacity();
+
+ a.SwapElements(b);
+
+ // Make sure that we didn't increase the capacity of either array.
+ CHECK_ARRAY(a, data2);
+ CHECK_EQ_INT(b.Length(), size_t(0));
+ CHECK_EQ_INT(b.Capacity(), size_t(0));
+ CHECK_EQ_INT(a.Capacity(), bCapacity);
+ }
+
+ // Swap an auto array with a TArray, then clear the auto array and make sure
+ // it doesn't forget the fact that it has an auto buffer.
+ {
+ nsTArray<int> a;
+ AutoTArray<int, 3> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+
+ a.SwapElements(b);
+
+ CHECK_EQ_INT(a.Length(), size_t(0));
+ CHECK_ARRAY(b, data1);
+
+ b.Clear();
+
+ CHECK_USES_SHARED_EMPTY_HDR(a);
+ CHECK_IS_USING_AUTO(b);
+ }
+
+ // Same thing as the previous test, but with more auto arrays.
+ {
+ AutoTArray<int, 16> a;
+ AutoTArray<int, 3> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+
+ a.SwapElements(b);
+
+ CHECK_EQ_INT(a.Length(), size_t(0));
+ CHECK_ARRAY(b, data1);
+
+ b.Clear();
+
+ CHECK_IS_USING_AUTO(a);
+ CHECK_IS_USING_AUTO(b);
+ }
+
+ // Swap an empty nsTArray and an empty AutoTArray.
+ {
+ AutoTArray<int, 8> a;
+ nsTArray<int> b;
+
+ a.SwapElements(b);
+
+ CHECK_IS_USING_AUTO(a);
+ CHECK_NOT_USING_AUTO(b);
+ CHECK_EQ_INT(a.Length(), size_t(0));
+ CHECK_EQ_INT(b.Length(), size_t(0));
+ }
+
+ // Swap empty auto array with non-empty AutoTArray using malloc'ed storage.
+ // I promise, all these tests have a point.
+ {
+ AutoTArray<int, 2> a;
+ AutoTArray<int, 1> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+
+ a.SwapElements(b);
+
+ CHECK_IS_USING_AUTO(a);
+ CHECK_NOT_USING_AUTO(b);
+ CHECK_ARRAY(b, data1);
+ CHECK_EQ_INT(a.Length(), size_t(0));
+ }
+}
+
+// Bug 1171296: Disabled on andoid due to crashes.
+#if !defined(ANDROID)
+TEST(TArray, test_fallible)
+{
+ // Test that FallibleTArray works properly; that is, it never OOMs, but
+ // instead eventually returns false.
+ //
+ // This test is only meaningful on 32-bit systems. On a 64-bit system, we
+ // might never OOM.
+ if (sizeof(void*) > 4) {
+ ASSERT_TRUE(true);
+ return;
+ }
+
+ // Allocate a bunch of 128MB arrays. Larger allocations will fail on some
+ // platforms without actually hitting OOM.
+ //
+ // 36 * 128MB > 4GB, so we should definitely OOM by the 36th array.
+ const unsigned numArrays = 36;
+ FallibleTArray<char> arrays[numArrays];
+ bool oomed = false;
+ for (size_t i = 0; i < numArrays; i++) {
+ // SetCapacity allocates the requested capacity + a header, and we want to
+ // avoid allocating more than 128MB overall because of the size padding it
+ // will cause, which depends on allocator behavior, so use 128MB - an
+ // arbitrary size larger than the array header, so that chances are good
+ // that allocations will always be 128MB.
+ bool success = arrays[i].SetCapacity(128 * 1024 * 1024 - 1024, fallible);
+ if (!success) {
+ // We got our OOM. Check that it didn't come too early.
+ oomed = true;
+ ASSERT_GE(i, size_t(8)) << "Got OOM on iteration " << i << ". Too early!";
+ }
+ }
+
+ ASSERT_TRUE(oomed) << "Didn't OOM or crash? nsTArray::SetCapacity"
+ "must be lying.";
+}
+#endif
+
+TEST(TArray, test_conversion_operator) {
+ FallibleTArray<int> f;
+ const FallibleTArray<int> fconst;
+
+ InfallibleTArray<int> i;
+ const InfallibleTArray<int> iconst;
+
+ nsTArray<int> t;
+ const nsTArray<int> tconst;
+ AutoTArray<int, 8> tauto;
+ const AutoTArray<int, 8> tautoconst;
+
+#define CHECK_ARRAY_CAST(type) \
+ do { \
+ const type<int>& z1 = f; \
+ ASSERT_EQ((void*)&z1, (void*)&f); \
+ const type<int>& z2 = fconst; \
+ ASSERT_EQ((void*)&z2, (void*)&fconst); \
+ const type<int>& z5 = i; \
+ ASSERT_EQ((void*)&z5, (void*)&i); \
+ const type<int>& z6 = iconst; \
+ ASSERT_EQ((void*)&z6, (void*)&iconst); \
+ const type<int>& z9 = t; \
+ ASSERT_EQ((void*)&z9, (void*)&t); \
+ const type<int>& z10 = tconst; \
+ ASSERT_EQ((void*)&z10, (void*)&tconst); \
+ const type<int>& z11 = tauto; \
+ ASSERT_EQ((void*)&z11, (void*)&tauto); \
+ const type<int>& z12 = tautoconst; \
+ ASSERT_EQ((void*)&z12, (void*)&tautoconst); \
+ } while (0)
+
+ CHECK_ARRAY_CAST(FallibleTArray);
+ CHECK_ARRAY_CAST(InfallibleTArray);
+ CHECK_ARRAY_CAST(nsTArray);
+
+#undef CHECK_ARRAY_CAST
+}
+
+template<class T>
+struct BufAccessor : public T
+{
+ void* GetHdr() { return T::mHdr; }
+};
+
+TEST(TArray, test_SetLengthAndRetainStorage_no_ctor) {
+ // 1050 because sizeof(int)*1050 is more than a page typically.
+ const int N = 1050;
+ FallibleTArray<int> f;
+
+ InfallibleTArray<int> i;
+
+ nsTArray<int> t;
+ AutoTArray<int, N> tauto;
+
+#define LPAREN (
+#define RPAREN )
+#define FOR_EACH(pre, post) \
+ do { \
+ pre f post; \
+ pre i post; \
+ pre t post; \
+ pre tauto post; \
+ } while (0)
+
+ // Setup test arrays.
+ FOR_EACH(; Unused << , .SetLength(N, fallible));
+ for (int n = 0; n < N; ++n) {
+ FOR_EACH(;, [n] = n);
+ }
+
+ void* initial_Hdrs[] = {
+ static_cast<BufAccessor<FallibleTArray<int> >&>(f).GetHdr(),
+ static_cast<BufAccessor<InfallibleTArray<int> >&>(i).GetHdr(),
+ static_cast<BufAccessor<nsTArray<int> >&>(t).GetHdr(),
+ static_cast<BufAccessor<AutoTArray<int, N> >&>(tauto).GetHdr(),
+ nullptr
+ };
+
+ // SetLengthAndRetainStorage(n), should NOT overwrite memory when T hasn't
+ // a default constructor.
+ FOR_EACH(;, .SetLengthAndRetainStorage(8));
+ FOR_EACH(;, .SetLengthAndRetainStorage(12));
+ for (int n = 0; n < 12; ++n) {
+ ASSERT_EQ(f[n], n);
+ ASSERT_EQ(i[n], n);
+ ASSERT_EQ(t[n], n);
+ ASSERT_EQ(tauto[n], n);
+ }
+ FOR_EACH(;, .SetLengthAndRetainStorage(0));
+ FOR_EACH(;, .SetLengthAndRetainStorage(N));
+ for (int n = 0; n < N; ++n) {
+ ASSERT_EQ(f[n], n);
+ ASSERT_EQ(i[n], n);
+ ASSERT_EQ(t[n], n);
+ ASSERT_EQ(tauto[n], n);
+ }
+
+ void* current_Hdrs[] = {
+ static_cast<BufAccessor<FallibleTArray<int> >&>(f).GetHdr(),
+ static_cast<BufAccessor<InfallibleTArray<int> >&>(i).GetHdr(),
+ static_cast<BufAccessor<nsTArray<int> >&>(t).GetHdr(),
+ static_cast<BufAccessor<AutoTArray<int, N> >&>(tauto).GetHdr(),
+ nullptr
+ };
+
+ // SetLengthAndRetainStorage(n) should NOT have reallocated the internal
+ // memory.
+ ASSERT_EQ(sizeof(initial_Hdrs), sizeof(current_Hdrs));
+ for (size_t n = 0; n < sizeof(current_Hdrs) / sizeof(current_Hdrs[0]); ++n) {
+ ASSERT_EQ(current_Hdrs[n], initial_Hdrs[n]);
+ }
+
+
+#undef FOR_EACH
+#undef LPAREN
+#undef RPAREN
+}
+
+} // namespace TestTArray
diff --git a/xpcom/tests/gtest/TestTextFormatter.cpp b/xpcom/tests/gtest/TestTextFormatter.cpp
new file mode 100644
index 000000000..c98b766cc
--- /dev/null
+++ b/xpcom/tests/gtest/TestTextFormatter.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsTextFormatter.h"
+#include "nsString.h"
+#include "gtest/gtest.h"
+
+TEST(TextFormatter, Tests)
+{
+ nsAutoString fmt(NS_LITERAL_STRING("%3$s %4$S %1$d %2$d %2$d %3$s"));
+ char utf8[] = "Hello";
+ char16_t ucs2[]={'W', 'o', 'r', 'l', 'd', 0x4e00, 0xAc00, 0xFF45, 0x0103, 0x00};
+ int d=3;
+
+ char16_t buf[256];
+ nsTextFormatter::snprintf(buf, 256, fmt.get(), d, 333, utf8, ucs2);
+ nsAutoString out(buf);
+ ASSERT_STREQ("Hello World", NS_LossyConvertUTF16toASCII(out).get());
+
+ const char16_t *uout = out.get();
+ const char16_t expected[] = {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20,
+ 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x4E00,
+ 0xAC00, 0xFF45, 0x0103, 0x20, 0x33,
+ 0x20, 0x33, 0x33, 0x33, 0x20, 0x33,
+ 0x33, 0x33, 0x20, 0x48, 0x65, 0x6C,
+ 0x6C, 0x6F};
+
+ for (uint32_t i=0; i<out.Length(); i++) {
+ ASSERT_EQ(uout[i], expected[i]);
+ }
+}
+
diff --git a/xpcom/tests/gtest/TestThreadPool.cpp b/xpcom/tests/gtest/TestThreadPool.cpp
new file mode 100644
index 000000000..56abf7608
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadPool.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include "nsXPCOM.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIThreadPool.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+class Task final : public nsIRunnable
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit Task(int i) : mIndex(i) {}
+
+ NS_IMETHOD Run() override
+ {
+ printf("###(%d) running from thread: %p\n", mIndex, (void *) PR_GetCurrentThread());
+ int r = (int) ((float) rand() * 200 / RAND_MAX);
+ PR_Sleep(PR_MillisecondsToInterval(r));
+ printf("###(%d) exiting from thread: %p\n", mIndex, (void *) PR_GetCurrentThread());
+ ++sCount;
+ return NS_OK;
+ }
+
+ static mozilla::Atomic<int> sCount;
+
+private:
+ ~Task() {}
+
+ int mIndex;
+};
+NS_IMPL_ISUPPORTS(Task, nsIRunnable)
+
+mozilla::Atomic<int> Task::sCount;
+
+TEST(ThreadPool, Main)
+{
+ nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+ EXPECT_TRUE(pool);
+
+ for (int i = 0; i < 100; ++i) {
+ nsCOMPtr<nsIRunnable> task = new Task(i);
+ EXPECT_TRUE(task);
+
+ pool->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+
+ pool->Shutdown();
+ EXPECT_EQ(Task::sCount, 100);
+}
+
+TEST(ThreadPool, Parallelism)
+{
+ nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+ EXPECT_TRUE(pool);
+
+ // Dispatch and sleep to ensure we have an idle thread
+ nsCOMPtr<nsIRunnable> r0 = new Runnable();
+ pool->Dispatch(r0, NS_DISPATCH_SYNC);
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ class Runnable1 : public Runnable {
+ public:
+ Runnable1(Monitor& aMonitor, bool& aDone)
+ : mMonitor(aMonitor), mDone(aDone) {}
+
+ NS_IMETHOD Run() override {
+ MonitorAutoLock mon(mMonitor);
+ if (!mDone) {
+ // Wait for a reasonable timeout since we don't want to block gtests
+ // forever should any regression happen.
+ mon.Wait(PR_SecondsToInterval(300));
+ }
+ EXPECT_TRUE(mDone);
+ return NS_OK;
+ }
+ private:
+ Monitor& mMonitor;
+ bool& mDone;
+ };
+
+ class Runnable2 : public Runnable {
+ public:
+ Runnable2(Monitor& aMonitor, bool& aDone)
+ : mMonitor(aMonitor), mDone(aDone) {}
+
+ NS_IMETHOD Run() override {
+ MonitorAutoLock mon(mMonitor);
+ mDone = true;
+ mon.NotifyAll();
+ return NS_OK;
+ }
+ private:
+ Monitor& mMonitor;
+ bool& mDone;
+ };
+
+ // Dispatch 2 events in a row. Since we are still within the thread limit,
+ // We should wake up the idle thread and spawn a new thread so these 2 events
+ // can run in parallel. We will time out if r1 and r2 run in sequence for r1
+ // won't finish until r2 finishes.
+ Monitor mon("ThreadPool::Parallelism");
+ bool done = false;
+ nsCOMPtr<nsIRunnable> r1 = new Runnable1(mon, done);
+ nsCOMPtr<nsIRunnable> r2 = new Runnable2(mon, done);
+ pool->Dispatch(r1, NS_DISPATCH_NORMAL);
+ pool->Dispatch(r2, NS_DISPATCH_NORMAL);
+
+ pool->Shutdown();
+}
diff --git a/xpcom/tests/gtest/TestThreadPoolListener.cpp b/xpcom/tests/gtest/TestThreadPoolListener.cpp
new file mode 100644
index 000000000..f95106fa8
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadPoolListener.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsIThread.h"
+#include "nsIThreadPool.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pratom.h"
+#include "prinrval.h"
+#include "prmon.h"
+#include "prthread.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/ReentrantMonitor.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+#define NUMBER_OF_THREADS 4
+
+// One hour... because test boxes can be slow!
+#define IDLE_THREAD_TIMEOUT 3600000
+
+namespace TestThreadPoolListener
+{
+static nsIThread** gCreatedThreadList = nullptr;
+static nsIThread** gShutDownThreadList = nullptr;
+
+static ReentrantMonitor* gReentrantMonitor = nullptr;
+
+static bool gAllRunnablesPosted = false;
+static bool gAllThreadsCreated = false;
+static bool gAllThreadsShutDown = false;
+
+class Listener final : public nsIThreadPoolListener
+{
+ ~Listener() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADPOOLLISTENER
+};
+
+NS_IMPL_ISUPPORTS(Listener, nsIThreadPoolListener)
+
+NS_IMETHODIMP
+Listener::OnThreadCreated()
+{
+ nsCOMPtr<nsIThread> current(do_GetCurrentThread());
+ EXPECT_TRUE(current) << "Couldn't get current thread!";
+
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+
+ while (!gAllRunnablesPosted) {
+ mon.Wait();
+ }
+
+ for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
+ nsIThread* thread = gCreatedThreadList[i];
+ EXPECT_NE(thread, current) << "Saw the same thread twice!";
+
+ if (!thread) {
+ gCreatedThreadList[i] = current;
+ if (i == (NUMBER_OF_THREADS - 1)) {
+ gAllThreadsCreated = true;
+ mon.NotifyAll();
+ }
+ return NS_OK;
+ }
+ }
+
+ EXPECT_TRUE(false) << "Too many threads!";
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+Listener::OnThreadShuttingDown()
+{
+ nsCOMPtr<nsIThread> current(do_GetCurrentThread());
+ EXPECT_TRUE(current) << "Couldn't get current thread!";
+
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+
+ for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
+ nsIThread* thread = gShutDownThreadList[i];
+ EXPECT_NE(thread, current) << "Saw the same thread twice!";
+
+ if (!thread) {
+ gShutDownThreadList[i] = current;
+ if (i == (NUMBER_OF_THREADS - 1)) {
+ gAllThreadsShutDown = true;
+ mon.NotifyAll();
+ }
+ return NS_OK;
+ }
+ }
+
+ EXPECT_TRUE(false) << "Too many threads!";
+ return NS_ERROR_FAILURE;
+}
+
+class AutoCreateAndDestroyReentrantMonitor
+{
+public:
+ explicit AutoCreateAndDestroyReentrantMonitor(ReentrantMonitor** aReentrantMonitorPtr)
+ : mReentrantMonitorPtr(aReentrantMonitorPtr) {
+ *aReentrantMonitorPtr = new ReentrantMonitor("TestThreadPoolListener::AutoMon");
+ MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!");
+ }
+
+ ~AutoCreateAndDestroyReentrantMonitor() {
+ delete *mReentrantMonitorPtr;
+ *mReentrantMonitorPtr = nullptr;
+ }
+
+private:
+ ReentrantMonitor** mReentrantMonitorPtr;
+};
+
+TEST(ThreadPoolListener, Test)
+{
+ nsIThread* createdThreadList[NUMBER_OF_THREADS] = { nullptr };
+ gCreatedThreadList = createdThreadList;
+
+ nsIThread* shutDownThreadList[NUMBER_OF_THREADS] = { nullptr };
+ gShutDownThreadList = shutDownThreadList;
+
+ AutoCreateAndDestroyReentrantMonitor newMon(&gReentrantMonitor);
+ ASSERT_TRUE(gReentrantMonitor);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIThreadPool> pool =
+ do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = pool->SetThreadLimit(NUMBER_OF_THREADS);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIThreadPoolListener> listener = new Listener();
+ ASSERT_TRUE(listener);
+
+ rv = pool->SetListener(listener);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+
+ for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
+ nsCOMPtr<nsIRunnable> runnable = new Runnable();
+ ASSERT_TRUE(runnable);
+
+ rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ gAllRunnablesPosted = true;
+ mon.NotifyAll();
+ }
+
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+ while (!gAllThreadsCreated) {
+ mon.Wait();
+ }
+ }
+
+ rv = pool->Shutdown();
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+ while (!gAllThreadsShutDown) {
+ mon.Wait();
+ }
+ }
+
+ for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
+ nsIThread* created = gCreatedThreadList[i];
+ ASSERT_TRUE(created);
+
+ bool match = false;
+ for (uint32_t j = 0; j < NUMBER_OF_THREADS; j++) {
+ nsIThread* destroyed = gShutDownThreadList[j];
+ ASSERT_TRUE(destroyed);
+
+ if (destroyed == created) {
+ match = true;
+ break;
+ }
+ }
+
+ ASSERT_TRUE(match);
+ }
+}
+
+} // namespace TestThreadPoolListener
diff --git a/xpcom/tests/gtest/TestThreadUtils.cpp b/xpcom/tests/gtest/TestThreadUtils.cpp
new file mode 100644
index 000000000..0d5d2f234
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadUtils.cpp
@@ -0,0 +1,378 @@
+/* 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 "nsThreadUtils.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+enum {
+ TEST_CALL_VOID_ARG_VOID_RETURN,
+ TEST_CALL_VOID_ARG_VOID_RETURN_CONST,
+ TEST_CALL_VOID_ARG_NONVOID_RETURN,
+ TEST_CALL_NONVOID_ARG_VOID_RETURN,
+ TEST_CALL_NONVOID_ARG_NONVOID_RETURN,
+ TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT,
+ TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT,
+#ifdef HAVE_STDCALL
+ TEST_STDCALL_VOID_ARG_VOID_RETURN,
+ TEST_STDCALL_VOID_ARG_NONVOID_RETURN,
+ TEST_STDCALL_NONVOID_ARG_VOID_RETURN,
+ TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN,
+ TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT,
+#endif
+ TEST_CALL_NEWTHREAD_SUICIDAL,
+ MAX_TESTS
+};
+
+bool gRunnableExecuted[MAX_TESTS];
+
+class nsFoo : public nsISupports {
+ NS_DECL_ISUPPORTS
+ nsresult DoFoo(bool* aBool) {
+ *aBool = true;
+ return NS_OK;
+ }
+
+private:
+ virtual ~nsFoo() {}
+};
+
+NS_IMPL_ISUPPORTS0(nsFoo)
+
+class TestSuicide : public mozilla::Runnable {
+ NS_IMETHOD Run() override {
+ // Runs first time on thread "Suicide", then dies on MainThread
+ if (!NS_IsMainThread()) {
+ mThread = do_GetCurrentThread();
+ NS_DispatchToMainThread(this);
+ return NS_OK;
+ }
+ MOZ_RELEASE_ASSERT(mThread);
+ mThread->Shutdown();
+ gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL] = true;
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+class nsBar : public nsISupports {
+ virtual ~nsBar() {}
+public:
+ NS_DECL_ISUPPORTS
+ void DoBar1(void) {
+ gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN] = true;
+ }
+ void DoBar1Const(void) const {
+ gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN_CONST] = true;
+ }
+ nsresult DoBar2(void) {
+ gRunnableExecuted[TEST_CALL_VOID_ARG_NONVOID_RETURN] = true;
+ return NS_OK;
+ }
+ void DoBar3(nsFoo* aFoo) {
+ aFoo->DoFoo(&gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN]);
+ }
+ nsresult DoBar4(nsFoo* aFoo) {
+ return aFoo->DoFoo(&gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN]);
+ }
+ void DoBar5(nsFoo* aFoo) {
+ if (aFoo)
+ gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true;
+ }
+ nsresult DoBar6(char* aFoo) {
+ if (strlen(aFoo))
+ gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT] = true;
+ return NS_OK;
+ }
+#ifdef HAVE_STDCALL
+ void __stdcall DoBar1std(void) {
+ gRunnableExecuted[TEST_STDCALL_VOID_ARG_VOID_RETURN] = true;
+ }
+ nsresult __stdcall DoBar2std(void) {
+ gRunnableExecuted[TEST_STDCALL_VOID_ARG_NONVOID_RETURN] = true;
+ return NS_OK;
+ }
+ void __stdcall DoBar3std(nsFoo* aFoo) {
+ aFoo->DoFoo(&gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN]);
+ }
+ nsresult __stdcall DoBar4std(nsFoo* aFoo) {
+ return aFoo->DoFoo(&gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN]);
+ }
+ void __stdcall DoBar5std(nsFoo* aFoo) {
+ if (aFoo)
+ gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true;
+ }
+ nsresult __stdcall DoBar6std(char* aFoo) {
+ if (strlen(aFoo))
+ gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true;
+ return NS_OK;
+ }
+#endif
+};
+
+NS_IMPL_ISUPPORTS0(nsBar)
+
+struct TestCopyWithNoMove
+{
+ explicit TestCopyWithNoMove(int* aCopyCounter) : mCopyCounter(aCopyCounter) {}
+ TestCopyWithNoMove(const TestCopyWithNoMove& a) : mCopyCounter(a.mCopyCounter) { ++mCopyCounter; };
+ // No 'move' declaration, allows passing object by rvalue copy.
+ // Destructor nulls member variable...
+ ~TestCopyWithNoMove() { mCopyCounter = nullptr; }
+ // ... so we can check that the object is called when still alive.
+ void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); }
+ int* mCopyCounter;
+};
+struct TestCopyWithDeletedMove
+{
+ explicit TestCopyWithDeletedMove(int* aCopyCounter) : mCopyCounter(aCopyCounter) {}
+ TestCopyWithDeletedMove(const TestCopyWithDeletedMove& a) : mCopyCounter(a.mCopyCounter) { ++mCopyCounter; };
+ // Deleted move prevents passing by rvalue (even if copy would work)
+ TestCopyWithDeletedMove(TestCopyWithDeletedMove&&) = delete;
+ ~TestCopyWithDeletedMove() { mCopyCounter = nullptr; }
+ void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); }
+ int* mCopyCounter;
+};
+struct TestMove
+{
+ explicit TestMove(int* aMoveCounter) : mMoveCounter(aMoveCounter) {}
+ TestMove(const TestMove&) = delete;
+ TestMove(TestMove&& a) : mMoveCounter(a.mMoveCounter) { a.mMoveCounter = nullptr; ++mMoveCounter; }
+ ~TestMove() { mMoveCounter = nullptr; }
+ void operator()() { MOZ_RELEASE_ASSERT(mMoveCounter); }
+ int* mMoveCounter;
+};
+struct TestCopyMove
+{
+ TestCopyMove(int* aCopyCounter, int* aMoveCounter) : mCopyCounter(aCopyCounter), mMoveCounter(aMoveCounter) {}
+ TestCopyMove(const TestCopyMove& a) : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { ++mCopyCounter; };
+ TestCopyMove(TestCopyMove&& a) : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { a.mMoveCounter = nullptr; ++mMoveCounter; }
+ ~TestCopyMove() { mCopyCounter = nullptr; mMoveCounter = nullptr; }
+ void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); MOZ_RELEASE_ASSERT(mMoveCounter); }
+ int* mCopyCounter;
+ int* mMoveCounter;
+};
+
+static void Expect(const char* aContext, int aCounter, int aMaxExpected)
+{
+ EXPECT_LE(aCounter, aMaxExpected) << aContext;
+}
+
+TEST(ThreadUtils, NewRunnableFunction)
+{
+ // Test NS_NewRunnableFunction with copyable-only function object.
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ trackedRunnable = NS_NewRunnableFunction(tracker);
+ // Original 'tracker' is destroyed here.
+ }
+ // Verify that the runnable contains a non-destroyed function object.
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and no move) function, copies",
+ copyCounter, 1);
+ }
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ // Passing as rvalue, but using copy.
+ // (TestCopyWithDeletedMove wouldn't allow this.)
+ trackedRunnable = NS_NewRunnableFunction(TestCopyWithNoMove(&copyCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and no move) function rvalue, copies",
+ copyCounter, 1);
+ }
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ trackedRunnable = NS_NewRunnableFunction(tracker);
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and deleted move) function, copies",
+ copyCounter, 1);
+ }
+
+ // Test NS_NewRunnableFunction with movable-only function object.
+ {
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestMove tracker(&moveCounter);
+ trackedRunnable = NS_NewRunnableFunction(Move(tracker));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with movable-only function, moves",
+ moveCounter, 1);
+ }
+ {
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable = NS_NewRunnableFunction(TestMove(&moveCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with movable-only function rvalue, moves",
+ moveCounter, 1);
+ }
+
+ // Test NS_NewRunnableFunction with copyable&movable function object.
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &moveCounter);
+ trackedRunnable = NS_NewRunnableFunction(Move(tracker));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable&movable function, copies",
+ copyCounter, 0);
+ Expect("NS_NewRunnableFunction with copyable&movable function, moves",
+ moveCounter, 1);
+ }
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable =
+ NS_NewRunnableFunction(TestCopyMove(&copyCounter, &moveCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable&movable function rvalue, copies",
+ copyCounter, 0);
+ Expect("NS_NewRunnableFunction with copyable&movable function rvalue, moves",
+ moveCounter, 1);
+ }
+
+ // Test NS_NewRunnableFunction with copyable-only lambda capture.
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ // Expect 2 copies (here -> local lambda -> runnable lambda).
+ trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); });
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and no move) capture, copies",
+ copyCounter, 2);
+ }
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ // Expect 2 copies (here -> local lambda -> runnable lambda).
+ trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); });
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and deleted move) capture, copies",
+ copyCounter, 2);
+ }
+
+ // Note: Not possible to use move-only captures.
+ // (Until we can use C++14 generalized lambda captures)
+
+ // Test NS_NewRunnableFunction with copyable&movable lambda capture.
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &moveCounter);
+ trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); });
+ // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable lambda).
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable&movable capture, copies",
+ copyCounter, 1);
+ Expect("NS_NewRunnableFunction with copyable&movable capture, moves",
+ moveCounter, 1);
+ }
+}
+
+TEST(ThreadUtils, RunnableMethod)
+{
+ memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool));
+ // Scope the smart ptrs so that the runnables need to hold on to whatever they need
+ {
+ RefPtr<nsFoo> foo = new nsFoo();
+ RefPtr<nsBar> bar = new nsBar();
+ RefPtr<const nsBar> constBar = bar;
+
+ // This pointer will be freed at the end of the block
+ // Do not dereference this pointer in the runnable method!
+ RefPtr<nsFoo> rawFoo = new nsFoo();
+
+ // Read only string. Dereferencing in runnable method to check this works.
+ char* message = (char*)"Test message";
+
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1));
+ NS_DispatchToMainThread(NewRunnableMethod(constBar, &nsBar::DoBar1Const));
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar3, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar4, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5, rawFoo));
+ NS_DispatchToMainThread(NewRunnableMethod<char*>(bar, &nsBar::DoBar6, message));
+#ifdef HAVE_STDCALL
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1std));
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2std));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar3std, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar4std, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5std, rawFoo));
+ NS_DispatchToMainThread(NewRunnableMethod<char*>(bar, &nsBar::DoBar6std, message));
+#endif
+ }
+
+ // Spin the event loop
+ NS_ProcessPendingEvents(nullptr);
+
+ // Now test a suicidal event in NS_New(Named)Thread
+ nsCOMPtr<nsIThread> thread;
+ NS_NewNamedThread("SuicideThread", getter_AddRefs(thread), new TestSuicide());
+ ASSERT_TRUE(thread);
+
+ while (!gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL]) {
+ NS_ProcessPendingEvents(nullptr);
+ }
+
+ for (uint32_t i = 0; i < MAX_TESTS; i++) {
+ EXPECT_TRUE(gRunnableExecuted[i]) << "Error in test " << i;
+ }
+}
diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp
new file mode 100644
index 000000000..4f6055dce
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreads.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsThreadUtils.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "nspr.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsXPCOM.h"
+#include "mozilla/Monitor.h"
+#include "gtest/gtest.h"
+
+class nsRunner final : public nsIRunnable {
+ ~nsRunner() {}
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ printf("running %d on thread %p\n", mNum, (void *)thread.get());
+
+ // if we don't do something slow, we'll never see the other
+ // worker threads run
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ return rv;
+ }
+
+ explicit nsRunner(int num) : mNum(num) {
+ }
+
+protected:
+ int mNum;
+};
+
+NS_IMPL_ISUPPORTS(nsRunner, nsIRunnable)
+
+TEST(Threads, Main)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRunnable> event = new nsRunner(0);
+ EXPECT_TRUE(event);
+
+ nsCOMPtr<nsIThread> runner;
+ rv = NS_NewThread(getter_AddRefs(runner), event);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = runner->Shutdown(); // wait for the runner to die before quitting
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ PR_Sleep(PR_MillisecondsToInterval(100)); // hopefully the runner will quit here
+}
+
+class nsStressRunner final : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+ PR_Sleep(1);
+ if (!PR_AtomicDecrement(&gNum)) {
+ printf(" last thread was %d\n", mNum);
+ }
+ return NS_OK;
+ }
+
+ explicit nsStressRunner(int num) : mNum(num), mWasRun(false) {
+ PR_AtomicIncrement(&gNum);
+ }
+
+ static int32_t GetGlobalCount() {return gNum;}
+
+private:
+ ~nsStressRunner() {
+ EXPECT_TRUE(mWasRun);
+ }
+
+protected:
+ static int32_t gNum;
+ int32_t mNum;
+ bool mWasRun;
+};
+
+int32_t nsStressRunner::gNum = 0;
+
+NS_IMPL_ISUPPORTS(nsStressRunner, nsIRunnable)
+
+TEST(Threads, Stress)
+{
+ const int loops = 1000;
+ const int threads = 50;
+
+ for (int i = 0; i < loops; i++) {
+ printf("Loop %d of %d\n", i+1, loops);
+
+ int k;
+ nsIThread** array = new nsIThread*[threads];
+
+ EXPECT_EQ(nsStressRunner::GetGlobalCount(), 0);
+
+ for (k = 0; k < threads; k++) {
+ nsCOMPtr<nsIThread> t;
+ nsresult rv = NS_NewThread(getter_AddRefs(t), new nsStressRunner(k));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ NS_ADDREF(array[k] = t);
+ }
+
+ for (k = threads-1; k >= 0; k--) {
+ array[k]->Shutdown();
+ NS_RELEASE(array[k]);
+ }
+ delete [] array;
+ }
+}
+
+mozilla::Monitor* gAsyncShutdownReadyMonitor;
+mozilla::Monitor* gBeginAsyncShutdownMonitor;
+
+class AsyncShutdownPreparer : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+
+ mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor);
+ lock.Notify();
+
+ return NS_OK;
+ }
+
+ explicit AsyncShutdownPreparer() : mWasRun(false) {}
+
+private:
+ virtual ~AsyncShutdownPreparer() {
+ EXPECT_TRUE(mWasRun);
+ }
+
+protected:
+ bool mWasRun;
+};
+
+NS_IMPL_ISUPPORTS(AsyncShutdownPreparer, nsIRunnable)
+
+class AsyncShutdownWaiter : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+
+ nsCOMPtr<nsIThread> t;
+ nsresult rv;
+
+ {
+ mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor);
+
+ rv = NS_NewThread(getter_AddRefs(t), new AsyncShutdownPreparer());
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ lock.Wait();
+ }
+
+ rv = t->AsyncShutdown();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+ }
+
+ explicit AsyncShutdownWaiter() : mWasRun(false) {}
+
+private:
+ virtual ~AsyncShutdownWaiter() {
+ EXPECT_TRUE(mWasRun);
+ }
+
+protected:
+ bool mWasRun;
+};
+
+NS_IMPL_ISUPPORTS(AsyncShutdownWaiter, nsIRunnable)
+
+class SameThreadSentinel : public nsIRunnable {
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor);
+ lock.Notify();
+ return NS_OK;
+ }
+
+private:
+ virtual ~SameThreadSentinel() {}
+};
+
+NS_IMPL_ISUPPORTS(SameThreadSentinel, nsIRunnable)
+
+TEST(Threads, AsyncShutdown)
+{
+ gAsyncShutdownReadyMonitor = new mozilla::Monitor("gAsyncShutdownReady");
+ gBeginAsyncShutdownMonitor = new mozilla::Monitor("gBeginAsyncShutdown");
+
+ nsCOMPtr<nsIThread> t;
+ nsresult rv;
+
+ {
+ mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor);
+
+ rv = NS_NewThread(getter_AddRefs(t), new AsyncShutdownWaiter());
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ lock.Wait();
+ }
+
+ NS_DispatchToCurrentThread(new SameThreadSentinel());
+ rv = t->Shutdown();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ delete gAsyncShutdownReadyMonitor;
+ delete gBeginAsyncShutdownMonitor;
+}
+
+static void threadProc(void *arg)
+{
+ // printf(" running thread %d\n", (int) arg);
+ PR_Sleep(1);
+ EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(PR_GetCurrentThread()));
+}
+
+TEST(Threads, StressNSPR)
+{
+ const int loops = 1000;
+ const int threads = 50;
+
+ for (int i = 0; i < loops; i++) {
+ printf("Loop %d of %d\n", i+1, loops);
+
+ intptr_t k;
+ PRThread** array = new PRThread*[threads];
+
+ for (k = 0; k < threads; k++) {
+ array[k] = PR_CreateThread(PR_USER_THREAD,
+ threadProc, (void*) k,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD,
+ 0);
+ EXPECT_TRUE(array[k]);
+ }
+
+ for (k = 0; k < threads; k++) {
+ EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(array[k]));
+ }
+
+ for (k = threads-1; k >= 0; k--) {
+ PR_JoinThread(array[k]);
+ }
+ delete [] array;
+ }
+}
diff --git a/xpcom/tests/gtest/TestTimeStamp.cpp b/xpcom/tests/gtest/TestTimeStamp.cpp
new file mode 100644
index 000000000..4e85b7e24
--- /dev/null
+++ b/xpcom/tests/gtest/TestTimeStamp.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/TimeStamp.h"
+
+#include "prinrval.h"
+#include "prthread.h"
+
+#include "gtest/gtest.h"
+
+using mozilla::TimeStamp;
+using mozilla::TimeDuration;
+
+TEST(TimeStamp, Main)
+{
+ TimeDuration td;
+ EXPECT_TRUE(td.ToSeconds() == 0.0);
+ EXPECT_TRUE(TimeDuration::FromSeconds(5).ToSeconds() == 5.0);
+ EXPECT_TRUE(TimeDuration::FromMilliseconds(5000).ToSeconds() == 5.0);
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(2));
+ EXPECT_FALSE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(2) > TimeDuration::FromSeconds(1));
+ EXPECT_FALSE(TimeDuration::FromSeconds(1) > TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(2));
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(1));
+ EXPECT_FALSE(TimeDuration::FromSeconds(2) <= TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(2) >= TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(1));
+ EXPECT_FALSE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(2));
+
+ TimeStamp ts;
+ EXPECT_TRUE(ts.IsNull());
+
+ ts = TimeStamp::Now();
+ EXPECT_TRUE(!ts.IsNull());
+ EXPECT_TRUE((ts - ts).ToSeconds() == 0.0);
+
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ TimeStamp ts2(TimeStamp::Now());
+ EXPECT_TRUE(ts2 > ts);
+ EXPECT_FALSE(ts > ts);
+ EXPECT_TRUE(ts < ts2);
+ EXPECT_FALSE(ts < ts);
+ EXPECT_TRUE(ts <= ts2);
+ EXPECT_TRUE(ts <= ts);
+ EXPECT_FALSE(ts2 <= ts);
+ EXPECT_TRUE(ts2 >= ts);
+ EXPECT_TRUE(ts2 >= ts);
+ EXPECT_FALSE(ts >= ts2);
+
+ // We can't be sure exactly how long PR_Sleep slept for. It should have
+ // slept for at least one second. We might have slept a lot longer due
+ // to process scheduling, but hopefully not more than 10 seconds.
+ td = ts2 - ts;
+ EXPECT_TRUE(td.ToSeconds() > 1.0);
+ EXPECT_TRUE(td.ToSeconds() < 20.0);
+ td = ts - ts2;
+ EXPECT_TRUE(td.ToSeconds() < -1.0);
+ EXPECT_TRUE(td.ToSeconds() > -20.0);
+
+ double resolution = TimeDuration::Resolution().ToSecondsSigDigits();
+ printf(" (platform timer resolution is ~%g s)\n", resolution);
+ EXPECT_TRUE(1e-10 < resolution);
+ // Don't upper-bound sanity check ... although NSPR reports 1ms
+ // resolution, it might be lying, so we shouldn't compare with it
+}
diff --git a/xpcom/tests/gtest/TestTimers.cpp b/xpcom/tests/gtest/TestTimers.cpp
new file mode 100644
index 000000000..fe7520ab1
--- /dev/null
+++ b/xpcom/tests/gtest/TestTimers.cpp
@@ -0,0 +1,437 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsIThread.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prinrval.h"
+#include "prmon.h"
+#include "prthread.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/ReentrantMonitor.h"
+
+#include <list>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+typedef nsresult(*TestFuncPtr)();
+
+class AutoTestThread
+{
+public:
+ AutoTestThread() {
+ nsCOMPtr<nsIThread> newThread;
+ nsresult rv = NS_NewThread(getter_AddRefs(newThread));
+ if (NS_FAILED(rv))
+ return;
+
+ newThread.swap(mThread);
+ }
+
+ ~AutoTestThread() {
+ mThread->Shutdown();
+ }
+
+ operator nsIThread*() const {
+ return mThread;
+ }
+
+ nsIThread* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ return mThread;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+class AutoCreateAndDestroyReentrantMonitor
+{
+public:
+ AutoCreateAndDestroyReentrantMonitor() {
+ mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon");
+ MOZ_RELEASE_ASSERT(mReentrantMonitor, "Out of memory!");
+ }
+
+ ~AutoCreateAndDestroyReentrantMonitor() {
+ delete mReentrantMonitor;
+ }
+
+ operator ReentrantMonitor* () {
+ return mReentrantMonitor;
+ }
+
+private:
+ ReentrantMonitor* mReentrantMonitor;
+};
+
+class TimerCallback final : public nsITimerCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ TimerCallback(nsIThread** aThreadPtr, ReentrantMonitor* aReentrantMonitor)
+ : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) { }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ MOZ_RELEASE_ASSERT(mThreadPtr, "Callback was not supposed to be called!");
+ nsCOMPtr<nsIThread> current(do_GetCurrentThread());
+
+ ReentrantMonitorAutoEnter mon(*mReentrantMonitor);
+
+ MOZ_RELEASE_ASSERT(!*mThreadPtr, "Timer called back more than once!");
+ *mThreadPtr = current;
+
+ mon.Notify();
+
+ return NS_OK;
+ }
+private:
+ ~TimerCallback() {}
+
+ nsIThread** mThreadPtr;
+ ReentrantMonitor* mReentrantMonitor;
+};
+
+NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback)
+
+TEST(Timers, TargetedTimers)
+{
+ AutoCreateAndDestroyReentrantMonitor newMon;
+ ASSERT_TRUE(newMon);
+
+ AutoTestThread testThread;
+ ASSERT_TRUE(testThread);
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread);
+
+ rv = timer->SetTarget(target);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsIThread* notifiedThread = nullptr;
+
+ nsCOMPtr<nsITimerCallback> callback =
+ new TimerCallback(&notifiedThread, newMon);
+ ASSERT_TRUE(callback);
+
+ rv = timer->InitWithCallback(callback, 2000, nsITimer::TYPE_ONE_SHOT);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ReentrantMonitorAutoEnter mon(*newMon);
+ while (!notifiedThread) {
+ mon.Wait();
+ }
+ ASSERT_EQ(notifiedThread, testThread);
+}
+
+TEST(Timers, TimerWithStoppedTarget)
+{
+ AutoTestThread testThread;
+ ASSERT_TRUE(testThread);
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread);
+
+ rv = timer->SetTarget(target);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // If this is called, we'll assert
+ nsCOMPtr<nsITimerCallback> callback =
+ new TimerCallback(nullptr, nullptr);
+ ASSERT_TRUE(callback);
+
+ rv = timer->InitWithCallback(callback, 100, nsITimer::TYPE_ONE_SHOT);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ testThread->Shutdown();
+
+ PR_Sleep(400);
+}
+
+#define FUZZ_MAX_TIMEOUT 9
+class FuzzTestThreadState final : public nsITimerCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit FuzzTestThreadState(nsIThread* thread) :
+ mThread(thread),
+ mStopped(false)
+ {}
+
+ class StartRunnable final : public mozilla::Runnable {
+ public:
+ explicit StartRunnable(FuzzTestThreadState* threadState) :
+ mThreadState(threadState)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mThreadState->ScheduleOrCancelTimers();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<FuzzTestThreadState> mThreadState;
+ };
+
+ void Start()
+ {
+ nsCOMPtr<nsIRunnable> runnable = new StartRunnable(this);
+ nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch StartRunnable.");
+ }
+
+ void Stop()
+ {
+ mStopped = true;
+ }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override
+ {
+ bool onCorrectThread;
+ nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check.");
+ MOZ_RELEASE_ASSERT(onCorrectThread, "Notify invoked on wrong thread.");
+
+ uint32_t delay;
+ rv = aTimer->GetDelay(&delay);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "GetDelay failed.");
+
+ MOZ_RELEASE_ASSERT(delay <= FUZZ_MAX_TIMEOUT,
+ "Delay was an invalid value for this test.");
+
+ uint32_t type;
+ rv = aTimer->GetType(&type);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type.");
+ MOZ_RELEASE_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+
+ if (type == nsITimer::TYPE_ONE_SHOT) {
+ MOZ_RELEASE_ASSERT(!mOneShotTimersByDelay[delay].empty(),
+ "Unexpected one-shot timer.");
+
+ MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[delay].front().get() == aTimer,
+ "One-shot timers have been reordered.");
+
+ mOneShotTimersByDelay[delay].pop_front();
+ --mTimersOutstanding;
+ } else if (mStopped) {
+ CancelRepeatingTimer(aTimer);
+ }
+
+ ScheduleOrCancelTimers();
+ RescheduleSomeTimers();
+ return NS_OK;
+ }
+
+ bool HasTimersOutstanding() const
+ {
+ return !!mTimersOutstanding;
+ }
+
+ private:
+ ~FuzzTestThreadState()
+ {
+ for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
+ MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[i].empty(),
+ "Timers remain at end of test.");
+ }
+ }
+
+ uint32_t GetRandomType() const
+ {
+ return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1);
+ }
+
+ size_t CountOneShotTimers() const
+ {
+ size_t count = 0;
+ for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
+ count += mOneShotTimersByDelay[i].size();
+ }
+ return count;
+ }
+
+ void ScheduleOrCancelTimers()
+ {
+ if (mStopped) {
+ return;
+ }
+
+ const size_t numTimersDesired = (rand() % 100) + 100;
+ MOZ_RELEASE_ASSERT(numTimersDesired >= 100);
+ MOZ_RELEASE_ASSERT(numTimersDesired < 200);
+ int adjustment = numTimersDesired - mTimersOutstanding;
+
+ while (adjustment > 0) {
+ CreateRandomTimer();
+ --adjustment;
+ }
+
+ while (adjustment < 0) {
+ CancelRandomTimer();
+ ++adjustment;
+ }
+
+ MOZ_RELEASE_ASSERT(numTimersDesired == mTimersOutstanding);
+ }
+
+ void RescheduleSomeTimers()
+ {
+ if (mStopped) {
+ return;
+ }
+
+ static const size_t kNumRescheduled = 40;
+
+ // Reschedule some timers with a Cancel first.
+ for (size_t i = 0; i < kNumRescheduled; ++i) {
+ InitRandomTimer(CancelRandomTimer().get());
+ }
+ // Reschedule some timers without a Cancel first.
+ for (size_t i = 0; i < kNumRescheduled; ++i) {
+ InitRandomTimer(RemoveRandomTimer().get());
+ }
+ }
+
+ void CreateRandomTimer()
+ {
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to create timer.");
+
+ rv = timer->SetTarget(static_cast<nsIEventTarget*>(mThread.get()));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set target.");
+
+ InitRandomTimer(timer.get());
+ }
+
+ nsCOMPtr<nsITimer> CancelRandomTimer()
+ {
+ nsCOMPtr<nsITimer> timer(RemoveRandomTimer());
+ timer->Cancel();
+ return timer;
+ }
+
+ nsCOMPtr<nsITimer> RemoveRandomTimer()
+ {
+ MOZ_RELEASE_ASSERT(mTimersOutstanding);
+
+ if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers())
+ || mRepeatingTimers.empty()) {
+ uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1);
+ while (mOneShotTimersByDelay[delayToRemove].empty()) {
+ // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1
+ delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1);
+ }
+
+ uint32_t indexToRemove =
+ rand() % mOneShotTimersByDelay[delayToRemove].size();
+
+ for (auto it = mOneShotTimersByDelay[delayToRemove].begin();
+ it != mOneShotTimersByDelay[delayToRemove].end();
+ ++it) {
+ if (indexToRemove) {
+ --indexToRemove;
+ continue;
+ }
+
+ nsCOMPtr<nsITimer> removed = *it;
+ mOneShotTimersByDelay[delayToRemove].erase(it);
+ --mTimersOutstanding;
+ return removed;
+ }
+ } else {
+ size_t indexToRemove = rand() % mRepeatingTimers.size();
+ nsCOMPtr<nsITimer> removed(mRepeatingTimers[indexToRemove]);
+ mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove);
+ --mTimersOutstanding;
+ return removed;
+ }
+
+ MOZ_CRASH("Unable to remove a timer");
+ }
+
+ void InitRandomTimer(nsITimer* aTimer)
+ {
+ // Between 0 and FUZZ_MAX_TIMEOUT
+ uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1);
+ uint32_t type = GetRandomType();
+ nsresult rv = aTimer->InitWithCallback(this, delay, type);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set timer.");
+
+ if (type == nsITimer::TYPE_ONE_SHOT) {
+ mOneShotTimersByDelay[delay].push_back(aTimer);
+ } else {
+ mRepeatingTimers.push_back(aTimer);
+ }
+ ++mTimersOutstanding;
+ }
+
+ void CancelRepeatingTimer(nsITimer* aTimer)
+ {
+ for (auto it = mRepeatingTimers.begin();
+ it != mRepeatingTimers.end();
+ ++it) {
+ if (it->get() == aTimer) {
+ mRepeatingTimers.erase(it);
+ aTimer->Cancel();
+ --mTimersOutstanding;
+ return;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIThread> mThread;
+ // Scheduled timers, indexed by delay between 0-9 ms, in lists
+ // with most recently scheduled last.
+ std::list<nsCOMPtr<nsITimer>> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1];
+ std::vector<nsCOMPtr<nsITimer>> mRepeatingTimers;
+ Atomic<bool> mStopped;
+ Atomic<size_t> mTimersOutstanding;
+};
+
+NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback)
+
+TEST(Timers, FuzzTestTimers)
+{
+ static const size_t kNumThreads(10);
+ AutoTestThread threads[kNumThreads];
+ RefPtr<FuzzTestThreadState> threadStates[kNumThreads];
+
+ for (size_t i = 0; i < kNumThreads; ++i) {
+ threadStates[i] = new FuzzTestThreadState(&*threads[i]);
+ threadStates[i]->Start();
+ }
+
+ PR_Sleep(PR_MillisecondsToInterval(20000));
+
+ for (size_t i = 0; i < kNumThreads; ++i) {
+ threadStates[i]->Stop();
+ }
+
+ // Wait at most 10 seconds for all outstanding timers to pop
+ PRIntervalTime start = PR_IntervalNow();
+ for (auto& threadState : threadStates) {
+ while (threadState->HasTimersOutstanding()) {
+ uint32_t elapsedMs = PR_IntervalToMilliseconds(PR_IntervalNow() - start);
+ ASSERT_LE(elapsedMs, uint32_t(10000)) << "Timed out waiting for all timers to pop";
+ PR_Sleep(PR_MillisecondsToInterval(10));
+ }
+ }
+}
diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp
new file mode 100644
index 000000000..283bbd3b8
--- /dev/null
+++ b/xpcom/tests/gtest/TestTokenizer.cpp
@@ -0,0 +1,1134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/Tokenizer.h"
+#include "mozilla/IncrementalTokenizer.h"
+#include "mozilla/Unused.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+static bool IsOperator(char const c)
+{
+ return c == '+' || c == '*';
+}
+
+static bool HttpHeaderCharacter(char const c)
+{
+ return (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ (c == '_') ||
+ (c == '-');
+}
+
+TEST(Tokenizer, HTTPResponse)
+{
+ Tokenizer::Token t;
+
+ // Real life test, HTTP response
+
+ Tokenizer p(NS_LITERAL_CSTRING(
+ "HTTP/1.0 304 Not modified\r\n"
+ "ETag: hallo\r\n"
+ "Content-Length: 16\r\n"
+ "\r\n"
+ "This is the body"));
+
+ EXPECT_TRUE(p.CheckWord("HTTP"));
+ EXPECT_TRUE(p.CheckChar('/'));
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 1);
+ EXPECT_TRUE(p.CheckChar('.'));
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 0);
+ p.SkipWhites();
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 304);
+ p.SkipWhites();
+
+ p.Record();
+ while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL);
+ EXPECT_FALSE(p.HasFailed());
+ nsAutoCString h;
+ p.Claim(h);
+ EXPECT_TRUE(h == "Not modified");
+
+ p.Record();
+ while (p.CheckChar(HttpHeaderCharacter));
+ p.Claim(h, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(h == "ETag");
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckChar(':'));
+ p.SkipWhites();
+ p.Record();
+ while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL);
+ EXPECT_FALSE(p.HasFailed());
+ p.Claim(h);
+ EXPECT_TRUE(h == "hallo");
+
+ p.Record();
+ while (p.CheckChar(HttpHeaderCharacter));
+ p.Claim(h, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(h == "Content-Length");
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckChar(':'));
+ p.SkipWhites();
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.AsInteger() == 16);
+ EXPECT_TRUE(p.CheckEOL());
+
+ EXPECT_TRUE(p.CheckEOL());
+
+ p.Record();
+ while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOF);
+ nsAutoCString b;
+ p.Claim(b);
+ EXPECT_TRUE(b == "This is the body");
+}
+
+TEST(Tokenizer, Main)
+{
+ Tokenizer::Token t;
+
+ // Synthetic code-specific test
+
+ Tokenizer p(NS_LITERAL_CSTRING("test123 ,15 \t*\r\n%xx,-15\r\r"));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_WORD);
+ EXPECT_TRUE(t.AsString() == "test123");
+
+ Tokenizer::Token u;
+ EXPECT_FALSE(p.Check(u));
+
+ EXPECT_FALSE(p.CheckChar('!'));
+
+ EXPECT_FALSE(p.Check(Tokenizer::Token::Number(123)));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15)));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15)));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 15);
+
+ EXPECT_FALSE(p.CheckChar(IsOperator));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ p.SkipWhites();
+
+ EXPECT_FALSE(p.CheckWhite());
+
+ p.Rollback();
+
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+
+ p.Record(Tokenizer::EXCLUDE_LAST);
+
+ EXPECT_TRUE(p.CheckChar(IsOperator));
+
+ p.Rollback();
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == '*');
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == '%');
+
+ nsAutoCString claim;
+ p.Claim(claim, Tokenizer::EXCLUDE_LAST);
+ EXPECT_TRUE(claim == "*\r\n");
+ p.Claim(claim, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(claim == "*\r\n%");
+
+ p.Rollback();
+ EXPECT_TRUE(p.CheckChar('%'));
+
+ p.Record(Tokenizer::INCLUDE_LAST);
+
+ EXPECT_FALSE(p.CheckWord("xy"));
+
+ EXPECT_TRUE(p.CheckWord("xx"));
+
+
+ p.Claim(claim, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(claim == "%xx");
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == ',');
+
+ EXPECT_TRUE(p.CheckChar('-'));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 15);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF);
+
+ EXPECT_FALSE(p.Next(t));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF);
+
+ EXPECT_FALSE(p.Next(t));
+
+ p.Rollback();
+ EXPECT_TRUE(p.CheckEOF());
+
+ EXPECT_FALSE(p.CheckEOF());
+}
+
+TEST(Tokenizer, SingleWord)
+{
+ // Single word with numbers in it test
+
+ Tokenizer p(NS_LITERAL_CSTRING("test123"));
+
+ EXPECT_TRUE(p.CheckWord("test123"));
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, EndingAfterNumber)
+{
+ // An end handling after a number
+
+ Tokenizer p(NS_LITERAL_CSTRING("123"));
+
+ EXPECT_FALSE(p.CheckWord("123"));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123)));
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, BadInteger)
+{
+ Tokenizer::Token t;
+
+ // A bad integer test
+
+ Tokenizer p(NS_LITERAL_CSTRING("189234891274981758617846178651647620587135"));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_ERROR);
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, CheckExpectedTokenValue)
+{
+ Tokenizer::Token t;
+
+ // Check expected token value test
+
+ Tokenizer p(NS_LITERAL_CSTRING("blue velvet"));
+
+ EXPECT_FALSE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t));
+ EXPECT_TRUE(t.AsString() == "blue");
+
+ EXPECT_FALSE(p.Check(Tokenizer::TOKEN_WORD, t));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t));
+ EXPECT_TRUE(t.AsString() == "velvet");
+
+ EXPECT_TRUE(p.CheckEOF());
+
+ EXPECT_FALSE(p.Next(t));
+}
+
+TEST(Tokenizer, HasFailed)
+{
+ Tokenizer::Token t;
+
+ // HasFailed test
+
+ Tokenizer p1(NS_LITERAL_CSTRING("a b"));
+
+ while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(p1.HasFailed());
+
+
+ Tokenizer p2(NS_LITERAL_CSTRING("a b ?!c"));
+
+ EXPECT_FALSE(p2.CheckChar('c'));
+ EXPECT_TRUE(p2.HasFailed());
+ EXPECT_TRUE(p2.CheckChar(HttpHeaderCharacter));
+ EXPECT_FALSE(p2.HasFailed());
+ p2.SkipWhites();
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_FALSE(p2.CheckChar('c'));
+ EXPECT_TRUE(p2.HasFailed());
+ EXPECT_TRUE(p2.Next(t));
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_TRUE(p2.Next(t));
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_FALSE(p2.CheckChar('c'));
+ EXPECT_TRUE(p2.HasFailed());
+ EXPECT_TRUE(p2.Check(Tokenizer::TOKEN_CHAR, t));
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_FALSE(p2.CheckChar('#'));
+ EXPECT_TRUE(p2.HasFailed());
+ t = Tokenizer::Token::Char('!');
+ EXPECT_TRUE(p2.Check(t));
+ EXPECT_FALSE(p2.HasFailed());
+
+ while (p2.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(p2.HasFailed());
+}
+
+TEST(Tokenizer, Construction)
+{
+ {
+ nsCString a("test");
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ nsAutoCString a("test");
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ static const char _a[] = "test";
+ nsDependentCString a(_a);
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ static const char* _a = "test";
+ nsDependentCString a(_a);
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ Tokenizer p1(nsDependentCString("test"));
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ Tokenizer p1(NS_LITERAL_CSTRING("test"));
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ Tokenizer p1("test");
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+}
+
+TEST(Tokenizer, Customization)
+{
+ Tokenizer p1(NS_LITERAL_CSTRING("test-custom*words and\tdefault-whites"), nullptr, "-*");
+ EXPECT_TRUE(p1.CheckWord("test-custom*words"));
+ EXPECT_TRUE(p1.CheckWhite());
+ EXPECT_TRUE(p1.CheckWord("and"));
+ EXPECT_TRUE(p1.CheckWhite());
+ EXPECT_TRUE(p1.CheckWord("default-whites"));
+
+ Tokenizer p2(NS_LITERAL_CSTRING("test, custom,whites"), ", ");
+ EXPECT_TRUE(p2.CheckWord("test"));
+ EXPECT_TRUE(p2.CheckWhite());
+ EXPECT_TRUE(p2.CheckWhite());
+ EXPECT_TRUE(p2.CheckWord("custom"));
+ EXPECT_TRUE(p2.CheckWhite());
+ EXPECT_TRUE(p2.CheckWord("whites"));
+
+ Tokenizer p3(NS_LITERAL_CSTRING("test, custom, whites-and#word-chars"), ",", "-#");
+ EXPECT_TRUE(p3.CheckWord("test"));
+ EXPECT_TRUE(p3.CheckWhite());
+ EXPECT_FALSE(p3.CheckWhite());
+ EXPECT_TRUE(p3.CheckChar(' '));
+ EXPECT_TRUE(p3.CheckWord("custom"));
+ EXPECT_TRUE(p3.CheckWhite());
+ EXPECT_FALSE(p3.CheckWhite());
+ EXPECT_TRUE(p3.CheckChar(' '));
+ EXPECT_TRUE(p3.CheckWord("whites-and#word-chars"));
+}
+
+TEST(Tokenizer, ShortcutChecks)
+{
+ Tokenizer p("test1 test2,123");
+
+ nsAutoCString test1;
+ nsDependentCSubstring test2;
+ char comma;
+ uint32_t integer;
+
+ EXPECT_TRUE(p.ReadWord(test1));
+ EXPECT_TRUE(test1 == "test1");
+ p.SkipWhites();
+ EXPECT_TRUE(p.ReadWord(test2));
+ EXPECT_TRUE(test2 == "test2");
+ EXPECT_TRUE(p.ReadChar(&comma));
+ EXPECT_TRUE(comma == ',');
+ EXPECT_TRUE(p.ReadInteger(&integer));
+ EXPECT_TRUE(integer == 123);
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+static bool ABChar(const char aChar)
+{
+ return aChar == 'a' || aChar == 'b';
+}
+
+TEST(Tokenizer, ReadCharClassified)
+{
+ Tokenizer p("abc");
+
+ char c;
+ EXPECT_TRUE(p.ReadChar(ABChar, &c));
+ EXPECT_TRUE(c == 'a');
+ EXPECT_TRUE(p.ReadChar(ABChar, &c));
+ EXPECT_TRUE(c == 'b');
+ EXPECT_FALSE(p.ReadChar(ABChar, &c));
+ nsDependentCSubstring w;
+ EXPECT_TRUE(p.ReadWord(w));
+ EXPECT_TRUE(w == "c");
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, ClaimSubstring)
+{
+ Tokenizer p(" abc ");
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ p.Record();
+ EXPECT_TRUE(p.CheckWord("abc"));
+ nsDependentCSubstring v;
+ p.Claim(v, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(v == "abc");
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, Fragment)
+{
+ const char str[] = "ab;cd:10 ";
+ Tokenizer p(str);
+ nsDependentCSubstring f;
+
+ Tokenizer::Token t1, t2;
+
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD);
+ EXPECT_TRUE(t1.Fragment() == "ab");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[0]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2));
+ EXPECT_TRUE(t2.Fragment() == "ab");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[0]);
+
+
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t1.Fragment() == ";");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[2]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2));
+ EXPECT_TRUE(t2.Fragment() == ";");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[2]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2));
+ EXPECT_TRUE(t2.Fragment() == "cd");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[3]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD);
+ EXPECT_TRUE(t1.Fragment() == "cd");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[3]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2));
+ EXPECT_TRUE(t2.Fragment() == ":");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[5]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t1.Fragment() == ":");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[5]);
+
+
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t1.Fragment() == "10");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[6]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WS, t2));
+ EXPECT_TRUE(t2.Fragment() == " ");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[8]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_EOF, t1));
+ EXPECT_TRUE(t1.Fragment() == "");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[9]);
+}
+
+TEST(Tokenizer, SkipWhites)
+{
+ Tokenizer p("Text1 \nText2 \nText3\n Text4\n ");
+
+ EXPECT_TRUE(p.CheckWord("Text1"));
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckEOL());
+
+ EXPECT_TRUE(p.CheckWord("Text2"));
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+
+ EXPECT_TRUE(p.CheckWord("Text3"));
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckEOL());
+ p.SkipWhites();
+
+ EXPECT_TRUE(p.CheckWord("Text4"));
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, SkipCustomWhites)
+{
+ Tokenizer p("Text1 \n\r\t.Text2 \n\r\t.", " \n\r\t.");
+
+ EXPECT_TRUE(p.CheckWord("Text1"));
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckWord("Text2"));
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, IntegerReading)
+{
+#define INT_6_BITS 64U
+#define INT_30_BITS 1073741824UL
+#define INT_32_BITS 4294967295UL
+#define INT_50_BITS 1125899906842624ULL
+#define STR_INT_MORE_THAN_64_BITS "922337203685477580899"
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_6_BITS));
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ EXPECT_TRUE(p.ReadInteger(&u8));
+ EXPECT_TRUE(u8 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u16));
+ EXPECT_TRUE(u16 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u32));
+ EXPECT_TRUE(u32 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u64));
+ EXPECT_TRUE(u64 == INT_6_BITS);
+
+ p.Rollback();
+
+ int8_t s8;
+ int16_t s16;
+ int32_t s32;
+ int64_t s64;
+ EXPECT_TRUE(p.ReadInteger(&s8));
+ EXPECT_TRUE(s8 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s16));
+ EXPECT_TRUE(s16 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s32));
+ EXPECT_TRUE(s32 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s64));
+ EXPECT_TRUE(s64 == INT_6_BITS);
+
+ EXPECT_TRUE(p.CheckWord("U"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_30_BITS));
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ EXPECT_FALSE(p.ReadInteger(&u8));
+ EXPECT_FALSE(p.ReadInteger(&u16));
+ EXPECT_TRUE(p.ReadInteger(&u32));
+ EXPECT_TRUE(u32 == INT_30_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u64));
+ EXPECT_TRUE(u64 == INT_30_BITS);
+
+ p.Rollback();
+
+ int8_t s8;
+ int16_t s16;
+ int32_t s32;
+ int64_t s64;
+ EXPECT_FALSE(p.ReadInteger(&s8));
+ EXPECT_FALSE(p.ReadInteger(&s16));
+ EXPECT_TRUE(p.ReadInteger(&s32));
+ EXPECT_TRUE(s32 == INT_30_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s64));
+ EXPECT_TRUE(s64 == INT_30_BITS);
+ EXPECT_TRUE(p.CheckWord("UL"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_32_BITS));
+ uint32_t u32;
+ int32_t s32;
+ EXPECT_FALSE(p.ReadInteger(&s32));
+ EXPECT_TRUE(p.ReadInteger(&u32));
+ EXPECT_TRUE(u32 == INT_32_BITS);
+ EXPECT_TRUE(p.CheckWord("UL"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_50_BITS));
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ EXPECT_FALSE(p.ReadInteger(&u8));
+ EXPECT_FALSE(p.ReadInteger(&u16));
+ EXPECT_FALSE(p.ReadInteger(&u32));
+ EXPECT_TRUE(p.ReadInteger(&u64));
+ EXPECT_TRUE(u64 == INT_50_BITS);
+ EXPECT_TRUE(p.CheckWord("ULL"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(STR_INT_MORE_THAN_64_BITS);
+ int64_t i;
+ EXPECT_FALSE(p.ReadInteger(&i));
+ uint64_t u;
+ EXPECT_FALSE(p.ReadInteger(&u));
+ EXPECT_FALSE(p.CheckEOF());
+ }
+}
+
+TEST(Tokenizer, ReadUntil)
+{
+ Tokenizer p("Hello;test 4,");
+ nsDependentCSubstring f;
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(';'), f));
+ EXPECT_TRUE(f == "Hello");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(';'), f, Tokenizer::INCLUDE_LAST));
+ EXPECT_TRUE(f == "Hello;");
+ p.Rollback();
+
+ EXPECT_FALSE(p.ReadUntil(Tokenizer::Token::Char('!'), f));
+ EXPECT_TRUE(f == "Hello;test 4,");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word(NS_LITERAL_CSTRING("test")), f));
+ EXPECT_TRUE(f == "Hello;");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word(NS_LITERAL_CSTRING("test")), f, Tokenizer::INCLUDE_LAST));
+ EXPECT_TRUE(f == "Hello;test");
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(','), f));
+ EXPECT_TRUE(f == " 4");
+}
+
+TEST(Tokenizer, SkipUntil)
+{
+ {
+ Tokenizer p("test1,test2,,,test3");
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckWord("test2"));
+
+ p.SkipUntil(Tokenizer::Token::Char(',')); // must not move
+ EXPECT_TRUE(p.CheckChar(',')); // check the first comma of the ',,,' string
+
+ p.Rollback(); // moves cursor back to the first comma of the ',,,' string
+
+ p.SkipUntil(Tokenizer::Token::Char(',')); // must not move, we are on the ',' char
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckWord("test3"));
+ p.Rollback();
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p("test0,test1,test2");
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ p.Rollback();
+
+ EXPECT_TRUE(p.CheckWord("test1"));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ p.Rollback();
+
+ EXPECT_TRUE(p.CheckWord("test2"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+}
+
+TEST(Tokenizer, Custom)
+{
+ Tokenizer p("aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2");
+
+ Tokenizer::Token c1 = p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE);
+ Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE);
+
+ // It's expected to NOT FIND the custom token if it's not on an edge
+ // between other recognizable tokens.
+ EXPECT_TRUE(p.CheckWord("aaaaaacustom"));
+ EXPECT_TRUE(p.CheckChar('-'));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1)));
+ EXPECT_TRUE(p.CheckEOL());
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(c1));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(c1));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ p.EnableCustomToken(c1, false);
+ EXPECT_TRUE(p.CheckWord("Custom"));
+ EXPECT_TRUE(p.CheckChar('-'));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1)));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(0)));
+ EXPECT_TRUE(p.Check(c2));
+ EXPECT_TRUE(p.CheckWord("xxxx"));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.CheckWord("CUSTOM"));
+ EXPECT_TRUE(p.CheckChar('-'));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(2)));
+
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, CustomRaw)
+{
+ Tokenizer p("aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2");
+
+ Tokenizer::Token c1 = p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE);
+ Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE);
+
+ // In this mode it's expected to find all custom tokens among any kind of input.
+ p.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ Tokenizer::Token t;
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral("aaaaaa"));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral("\r,"));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral(","));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral(","));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral(",00"));
+
+ EXPECT_TRUE(p.Check(c2));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral("xxxx,CUSTOM-2"));
+
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, Incremental)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break;
+ case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); break;
+ case 4: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 5: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 7: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test3")))); break;
+ case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+ for (; cur < end; ++cur) {
+ i.FeedInput(nsDependentCSubstring(cur, 1));
+ }
+
+ EXPECT_TRUE(test == 6);
+ i.FinishInput();
+ EXPECT_TRUE(test == 8);
+}
+
+TEST(Tokenizer, IncrementalRollback)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break;
+ case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2"))));
+ i.Rollback(); // so that we get the token again
+ break;
+ case 4: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); break;
+ case 5: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 7: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 8: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test3")))); break;
+ case 9: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+ for (; cur < end; ++cur) {
+ i.FeedInput(nsDependentCSubstring(cur, 1));
+ }
+
+ EXPECT_TRUE(test == 7);
+ i.FinishInput();
+ EXPECT_TRUE(test == 9);
+}
+
+TEST(Tokenizer, IncrementalNeedMoreInput)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ Token t2;
+ switch (++test) {
+ case 1:
+ EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("a"))));
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ EXPECT_TRUE(t.Equals(Token::Whitespace()));
+ if (i.Next(t2)) {
+ EXPECT_TRUE(test == 5);
+ EXPECT_TRUE(t2.Equals(Token::Word(NS_LITERAL_CSTRING("bb"))));
+ } else {
+ EXPECT_TRUE(test < 5);
+ i.NeedMoreInput();
+ }
+ break;
+ case 6:
+ EXPECT_TRUE(t.Equals(Token::Char(',')));
+ break;
+ case 7:
+ EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("c"))));
+ return NS_ERROR_FAILURE;
+ default:
+ EXPECT_TRUE(false);
+ break;
+ }
+
+ return NS_OK;
+ });
+
+ NS_NAMED_LITERAL_CSTRING(input, "a bb,c");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+
+ nsresult rv;
+ for (; cur < end; ++cur) {
+ rv = i.FeedInput(nsDependentCSubstring(cur, 1));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ EXPECT_TRUE(rv == NS_OK);
+ EXPECT_TRUE(test == 6);
+
+ rv = i.FinishInput();
+ EXPECT_TRUE(rv == NS_ERROR_FAILURE);
+ EXPECT_TRUE(test == 7);
+}
+
+TEST(Tokenizer, IncrementalCustom)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(custom)); break;
+ case 2: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("bla")))); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ }, nullptr, "-");
+
+ custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE);
+ i.FeedInput(NS_LITERAL_CSTRING("some-"));
+ EXPECT_TRUE(test == 0);
+ i.FeedInput(NS_LITERAL_CSTRING("tes"));
+ EXPECT_TRUE(test == 0);
+ i.FeedInput(NS_LITERAL_CSTRING("tbla"));
+ EXPECT_TRUE(test == 1);
+ i.FinishInput();
+ EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalCustomRaw)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("test1,")); break;
+ case 2: EXPECT_TRUE(t.Equals(custom)); break;
+ case 3: EXPECT_TRUE(t.Fragment().EqualsLiteral("!,,test3"));
+ i.Rollback();
+ i.SetTokenizingMode(Tokenizer::Mode::FULL);
+ break;
+ case 4: EXPECT_TRUE(t.Equals(Token::Char('!')));
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+ break;
+ case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral(",,test3")); break;
+ case 6: EXPECT_TRUE(t.Equals(custom)); break;
+ case 7: EXPECT_TRUE(t.Fragment().EqualsLiteral("tes")); break;
+ case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ custom = i.AddCustomToken("test2", Tokenizer::CASE_SENSITIVE);
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ NS_NAMED_LITERAL_CSTRING(input, "test1,test2!,,test3test2tes");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+ for (; cur < end; ++cur) {
+ i.FeedInput(nsDependentCSubstring(cur, 1));
+ }
+
+ EXPECT_TRUE(test == 6);
+ i.FinishInput();
+ EXPECT_TRUE(test == 8);
+}
+
+TEST(Tokenizer, IncrementalCustomRemove)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(custom));
+ i.RemoveCustomToken(custom);
+ break;
+ case 2: EXPECT_FALSE(t.Equals(custom)); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ custom = i.AddCustomToken("custom1", Tokenizer::CASE_SENSITIVE);
+
+ NS_NAMED_LITERAL_CSTRING(input, "custom1custom1");
+ i.FeedInput(input);
+ EXPECT_TRUE(test == 1);
+ i.FinishInput();
+ EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalBuffering1)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ nsDependentCSubstring observedFragment;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("012")); break;
+ case 2: EXPECT_TRUE(t.Fragment().EqualsLiteral("3456789")); break;
+ case 3: EXPECT_TRUE(t.Equals(custom)); break;
+ case 4: EXPECT_TRUE(t.Fragment().EqualsLiteral("qwe")); break;
+ case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral("rt")); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ observedFragment.Rebind(t.Fragment().BeginReading(),
+ t.Fragment().Length());
+ return NS_OK;
+ }, nullptr, nullptr, 3);
+
+ custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE);
+ // This externally unused token is added only to check the internal algorithm
+ // does work correctly as expected when there are two different length tokens.
+ Unused << i.AddCustomToken("bb", Tokenizer::CASE_SENSITIVE);
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ i.FeedInput(NS_LITERAL_CSTRING("01234"));
+ EXPECT_TRUE(test == 1);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("012"));
+
+ i.FeedInput(NS_LITERAL_CSTRING("5"));
+ EXPECT_TRUE(test == 1);
+ i.FeedInput(NS_LITERAL_CSTRING("6789aa"));
+ EXPECT_TRUE(test == 2);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("3456789"));
+
+ i.FeedInput(NS_LITERAL_CSTRING("aqwert"));
+ EXPECT_TRUE(test == 4);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("qwe"));
+
+ i.FinishInput();
+ EXPECT_TRUE(test == 6);
+}
+
+TEST(Tokenizer, IncrementalBuffering2)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("01")); break;
+ case 2: EXPECT_TRUE(t.Fragment().EqualsLiteral("234567")); break;
+ case 3: EXPECT_TRUE(t.Fragment().EqualsLiteral("89")); break;
+ case 4: EXPECT_TRUE(t.Equals(custom)); break;
+ case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral("qwert")); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+ return NS_OK;
+ }, nullptr, nullptr, 3);
+
+ custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE);
+ // This externally unused token is added only to check the internal algorithm
+ // does work correctly as expected when there are two different length tokens.
+ Unused << i.AddCustomToken("bbbbb", Tokenizer::CASE_SENSITIVE);
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ i.FeedInput(NS_LITERAL_CSTRING("01234"));
+ EXPECT_TRUE(test == 0);
+ i.FeedInput(NS_LITERAL_CSTRING("5"));
+ EXPECT_TRUE(test == 1);
+ i.FeedInput(NS_LITERAL_CSTRING("6789aa"));
+ EXPECT_TRUE(test == 2);
+ i.FeedInput(NS_LITERAL_CSTRING("aqwert"));
+ EXPECT_TRUE(test == 4);
+ i.FinishInput();
+ EXPECT_TRUE(test == 6);
+}
diff --git a/xpcom/tests/gtest/TestUTF.cpp b/xpcom/tests/gtest/TestUTF.cpp
new file mode 100644
index 000000000..14dc03abe
--- /dev/null
+++ b/xpcom/tests/gtest/TestUTF.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/ArrayUtils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsReadableUtils.h"
+#include "UTFStrings.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/HashFunctions.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+namespace TestUTF {
+
+TEST(UTF, Valid)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsDependentCString str8(ValidStrings[i].m8);
+ nsDependentString str16(ValidStrings[i].m16);
+
+ EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8));
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16));
+
+ nsCString tmp8("string ");
+ AppendUTF16toUTF8(str16, tmp8);
+ EXPECT_TRUE(tmp8.Equals(NS_LITERAL_CSTRING("string ") + str8));
+
+ nsString tmp16(NS_LITERAL_STRING("string "));
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(NS_LITERAL_STRING("string ") + str16));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Invalid16)
+{
+ for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) {
+ nsDependentString str16(Invalid16Strings[i].m16);
+ nsDependentCString str8(Invalid16Strings[i].m8);
+
+ EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8));
+
+ nsCString tmp8("string ");
+ AppendUTF16toUTF8(str16, tmp8);
+ EXPECT_TRUE(tmp8.Equals(NS_LITERAL_CSTRING("string ") + str8));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Invalid8)
+{
+ for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) {
+ nsDependentString str16(Invalid8Strings[i].m16);
+ nsDependentCString str8(Invalid8Strings[i].m8);
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16));
+
+ nsString tmp16(NS_LITERAL_STRING("string "));
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(NS_LITERAL_STRING("string ") + str16));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Malformed8)
+{
+// Don't run this test in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsDependentCString str8(Malformed8Strings[i]);
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).IsEmpty());
+
+ nsString tmp16(NS_LITERAL_STRING("string"));
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.EqualsLiteral("string"));
+
+ EXPECT_NE(CompareUTF8toUTF16(str8, EmptyString()), 0);
+ }
+#endif
+}
+
+TEST(UTF, Hash16)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsDependentCString str8(ValidStrings[i].m8);
+ bool err;
+ EXPECT_EQ(HashString(ValidStrings[i].m16),
+ HashUTF8AsUTF16(str8.get(), str8.Length(), &err));
+ EXPECT_FALSE(err);
+ }
+
+ for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) {
+ nsDependentCString str8(Invalid8Strings[i].m8);
+ bool err;
+ EXPECT_EQ(HashString(Invalid8Strings[i].m16),
+ HashUTF8AsUTF16(str8.get(), str8.Length(), &err));
+ EXPECT_FALSE(err);
+ }
+
+// Don't run this test in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsDependentCString str8(Malformed8Strings[i]);
+ bool err;
+ EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u);
+ EXPECT_TRUE(err);
+ }
+#endif
+}
+
+/**
+ * This tests the handling of a non-ascii character at various locations in a
+ * UTF-16 string that is being converted to UTF-8.
+ */
+void NonASCII16_helper(const size_t aStrSize)
+{
+ const size_t kTestSize = aStrSize;
+ const size_t kMaxASCII = 0x80;
+ const char16_t kUTF16Char = 0xC9;
+ const char kUTF8Surrogates[] = { char(0xC3), char(0x89) };
+
+ // Generate a string containing only ASCII characters.
+ nsString asciiString;
+ asciiString.SetLength(kTestSize);
+ nsCString asciiCString;
+ asciiCString.SetLength(kTestSize);
+
+ auto str_buff = asciiString.BeginWriting();
+ auto cstr_buff = asciiCString.BeginWriting();
+ for (size_t i = 0; i < kTestSize; i++) {
+ str_buff[i] = i % kMaxASCII;
+ cstr_buff[i] = i % kMaxASCII;
+ }
+
+ // Now go through and test conversion when exactly one character will
+ // result in a multibyte sequence.
+ for (size_t i = 0; i < kTestSize; i++) {
+ // Setup the UTF-16 string.
+ nsString unicodeString(asciiString);
+ auto buff = unicodeString.BeginWriting();
+ buff[i] = kUTF16Char;
+
+ // Do the conversion, make sure the length increased by 1.
+ nsCString dest;
+ AppendUTF16toUTF8(unicodeString, dest);
+ EXPECT_EQ(dest.Length(), unicodeString.Length() + 1);
+
+ // Build up the expected UTF-8 string.
+ nsCString expected;
+
+ // First add the leading ASCII chars.
+ expected.Append(asciiCString.BeginReading(), i);
+
+ // Now append the UTF-8 surrogate pair we expect the UTF-16 unicode char to
+ // be converted to.
+ for (auto& c : kUTF8Surrogates) {
+ expected.Append(c);
+ }
+
+ // And finish with the trailing ASCII chars.
+ expected.Append(asciiCString.BeginReading() + i + 1, kTestSize - i - 1);
+
+ EXPECT_STREQ(dest.BeginReading(), expected.BeginReading());
+ }
+}
+
+TEST(UTF, NonASCII16)
+{
+ // Test with various string sizes to catch any special casing.
+ NonASCII16_helper(1);
+ NonASCII16_helper(8);
+ NonASCII16_helper(16);
+ NonASCII16_helper(32);
+ NonASCII16_helper(512);
+}
+
+} // namespace TestUTF
diff --git a/xpcom/tests/gtest/TestXPIDLString.cpp b/xpcom/tests/gtest/TestXPIDLString.cpp
new file mode 100644
index 000000000..b26cc9aff
--- /dev/null
+++ b/xpcom/tests/gtest/TestXPIDLString.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "gtest/gtest.h"
+
+static void
+nsXPIDLStringTest_Value(char16_t** aResult)
+{
+ *aResult = ToNewUnicode(NS_LITERAL_STRING("Hello, World"));
+}
+
+TEST(XPIDLString, Main)
+{
+ nsXPIDLString s1;
+ nsXPIDLStringTest_Value(getter_Copies(s1));
+ EXPECT_TRUE(s1.EqualsLiteral("Hello, World"));
+}
+
diff --git a/xpcom/tests/gtest/UTFStrings.h b/xpcom/tests/gtest/UTFStrings.h
new file mode 100644
index 000000000..ec6bf15d3
--- /dev/null
+++ b/xpcom/tests/gtest/UTFStrings.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef utfstrings_h__
+#define utfstrings_h__
+
+struct UTFStringsStringPair
+ {
+ char16_t m16[16];
+ char m8[16];
+ };
+
+static const UTFStringsStringPair ValidStrings[] =
+ {
+ { { 'a', 'b', 'c', 'd' },
+ { 'a', 'b', 'c', 'd' } },
+ { { '1', '2', '3', '4' },
+ { '1', '2', '3', '4' } },
+ { { 0x7F, 'A', 0x80, 'B', 0x101, 0x200 },
+ { 0x7F, 'A', char(0xC2), char(0x80), 'B', char(0xC4), char(0x81), char(0xC8), char(0x80) } },
+ { { 0x7FF, 0x800, 0x1000 },
+ { char(0xDF), char(0xBF), char(0xE0), char(0xA0), char(0x80), char(0xE1), char(0x80), char(0x80) } },
+ { { 0xD7FF, 0xE000, 0xF00F, 'A', 0xFFF0 },
+ { char(0xED), char(0x9F), char(0xBF), char(0xEE), char(0x80), char(0x80), char(0xEF), char(0x80), char(0x8F), 'A', char(0xEF), char(0xBF), char(0xB0) } },
+ { { 0xFFF7, 0xFFFC, 0xFFFD, 0xFFFD },
+ { char(0xEF), char(0xBF), char(0xB7), char(0xEF), char(0xBF), char(0xBC), char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xD800, 0xDC00, 0xD800, 0xDCFF },
+ { char(0xF0), char(0x90), char(0x80), char(0x80), char(0xF0), char(0x90), char(0x83), char(0xBF) } },
+ { { 0xDBFF, 0xDFFF, 0xDBB7, 0xDCBA },
+ { char(0xF4), char(0x8F), char(0xBF), char(0xBF), char(0xF3), char(0xBD), char(0xB2), char(0xBA) } },
+ { { 0xFFFD, 0xFFFF },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBF) } },
+ { { 0xFFFD, 0xFFFE, 0xFFFF },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBE), char(0xEF), char(0xBF), char(0xBF) } },
+ };
+
+static const UTFStringsStringPair Invalid16Strings[] =
+ {
+ { { 'a', 'b', 0xD800 },
+ { 'a', 'b', char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xD8FF, 'b' },
+ { char(0xEF), char(0xBF), char(0xBD), 'b' } },
+ { { 0xD821 },
+ { char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC21 },
+ { char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800, 'b' },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), 'b' } },
+ { { 'b', 0xDC00, 0xD800 },
+ { 'b', char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800 },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800, 0xDC00, 0xD800 },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xF0), char(0x90), char(0x80), char(0x80), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800, 0xD800, 0xDC00 },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), char(0xF0), char(0x90), char(0x80), char(0x80) } },
+ };
+
+static const UTFStringsStringPair Invalid8Strings[] =
+ {
+ { { 'a', 0xFFFD, 'b' },
+ { 'a', char(0xC0), char(0x80), 'b' } },
+ { { 0xFFFD, 0x80 },
+ { char(0xC1), char(0xBF), char(0xC2), char(0x80) } },
+ { { 0xFFFD },
+ { char(0xC1), char(0xBF) } },
+ { { 0xFFFD, 'x', 0x0800 },
+ { char(0xE0), char(0x80), char(0x80), 'x', char(0xE0), char(0xA0), char(0x80) } },
+ { { 0xFFFD, 'x', 0xFFFD },
+ { char(0xF0), char(0x80), char(0x80), char(0x80), 'x', char(0xF0), char(0x80), char(0x8F), char(0x80) } },
+ { { 0xFFFD, 0xFFFD },
+ { char(0xF4), char(0x90), char(0x80), char(0x80), char(0xF7), char(0xBF), char(0xBF), char(0xBF) } },
+ { { 0xFFFD, 'x', 0xD800, 0xDC00, 0xFFFD },
+ { char(0xF0), char(0x8F), char(0xBF), char(0xBF), 'x', char(0xF0), char(0x90), char(0x80), char(0x80), char(0xF0), char(0x8F), char(0xBF), char(0xBF) } },
+ { { 0xFFFD, 'x', 0xFFFD },
+ { char(0xF8), char(0x80), char(0x80), char(0x80), char(0x80), 'x', char(0xF8), char(0x88), char(0x80), char(0x80), char(0x80) } },
+ { { 0xFFFD, 0xFFFD },
+ { char(0xFB), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xFC), char(0xA0), char(0x80), char(0x80), char(0x80), char(0x80) } },
+ { { 0xFFFD, 0xFFFD },
+ { char(0xFC), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), char(0xFD), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xBF) } },
+ };
+
+// Don't use this array in debug builds as that intentionally asserts.
+#ifndef DEBUG
+static const char Malformed8Strings[][16] =
+ {
+ { char(0x80) },
+ { 'a', char(0xC8), 'c' },
+ { 'a', char(0xC0) },
+ { 'a', char(0xE8), 'c' },
+ { 'a', char(0xE8), char(0x80), 'c' },
+ { 'a', char(0xE8), char(0x80) },
+ { char(0xE8), 0x7F, char(0x80) },
+ { 'a', char(0xE8), char(0xE8), char(0x80) },
+ { 'a', char(0xF4) },
+ { 'a', char(0xF4), char(0x80), char(0x80), 'c', 'c' },
+ { 'a', char(0xF4), char(0x80), 'x', char(0x80) },
+ { char(0xF4), char(0x80), char(0x80), char(0x80), char(0x80) },
+ { 'a', char(0xFA), 'c' },
+ { 'a', char(0xFA), char(0x80), char(0x80), 0x7F, char(0x80), 'c' },
+ { 'a', char(0xFA), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), 'c' },
+ { 'a', char(0xFD) },
+ { 'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), 'c' },
+ { 'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80) },
+ { 'a', char(0xFC), char(0x80), char(0x80), 0x40, char(0x80), char(0x80), 'c' },
+ };
+#endif
+
+#endif
diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build
new file mode 100644
index 000000000..53836eaef
--- /dev/null
+++ b/xpcom/tests/gtest/moz.build
@@ -0,0 +1,77 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'Helpers.cpp',
+ 'TestAtoms.cpp',
+ 'TestAutoPtr.cpp',
+ 'TestAutoRef.cpp',
+ 'TestBase64.cpp',
+ 'TestCallTemplates.cpp',
+ 'TestCloneInputStream.cpp',
+ 'TestCOMArray.cpp',
+ 'TestCOMPtrEq.cpp',
+ 'TestCRT.cpp',
+ 'TestEncoding.cpp',
+ 'TestEscapeURL.cpp',
+ 'TestExpirationTracker.cpp',
+ 'TestFile.cpp',
+ 'TestID.cpp',
+ 'TestNSPRLogModulesParser.cpp',
+ 'TestObserverArray.cpp',
+ 'TestObserverService.cpp',
+ 'TestPipes.cpp',
+ 'TestPLDHash.cpp',
+ 'TestPriorityQueue.cpp',
+ 'TestRacingServiceManager.cpp',
+ 'TestSlicedInputStream.cpp',
+ 'TestSnappyStreams.cpp',
+ 'TestStateWatching.cpp',
+ 'TestStorageStream.cpp',
+ 'TestStrings.cpp',
+ 'TestStringStream.cpp',
+ 'TestSynchronization.cpp',
+ 'TestTArray.cpp',
+ 'TestTArray2.cpp',
+ 'TestTextFormatter.cpp',
+ 'TestThreadPool.cpp',
+ 'TestThreadPoolListener.cpp',
+ 'TestThreads.cpp',
+ 'TestThreadUtils.cpp',
+ 'TestTimers.cpp',
+ 'TestTimeStamp.cpp',
+ 'TestTokenizer.cpp',
+ 'TestUTF.cpp',
+ 'TestXPIDLString.cpp',
+]
+
+if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT') and CONFIG['OS_TARGET'] != 'Android':
+ # FIXME bug 523392: TestDeadlockDetector doesn't like Windows
+ # Bug 1054249: Doesn't work on Android
+ UNIFIED_SOURCES += [
+ 'TestDeadlockDetector.cpp',
+ 'TestDeadlockDetectorScalability.cpp',
+ ]
+
+if CONFIG['WRAP_STL_INCLUDES'] and not CONFIG['CLANG_CL']:
+ UNIFIED_SOURCES += [
+ 'TestSTLWrappers.cpp',
+ ]
+
+# Compile TestAllocReplacement separately so Windows headers don't pollute
+# the global namespace for other files.
+SOURCES += [
+ 'TestAllocReplacement.cpp',
+ 'TestCOMPtr.cpp', # Redefines IFoo and IBar
+ 'TestHashtables.cpp', # Redefines IFoo
+ 'TestNsRefPtr.cpp', # Redefines Foo
+]
+
+LOCAL_INCLUDES += [
+ '../../base',
+]
+
+FINAL_LIBRARY = 'xul-gtest'