diff options
Diffstat (limited to 'xpcom/tests/gtest')
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(©Counter); + 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(©Counter)); + } + trackedRunnable->Run(); + } + Expect("NS_NewRunnableFunction with copyable-only (and no move) function rvalue, copies", + copyCounter, 1); + } + { + int copyCounter = 0; + { + nsCOMPtr<nsIRunnable> trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + 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(©Counter, &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(©Counter, &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(©Counter); + // 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(©Counter); + // 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(©Counter, &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(¬ifiedThread, 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' |