diff options
Diffstat (limited to 'xpcom/tests')
200 files changed, 20775 insertions, 0 deletions
diff --git a/xpcom/tests/Makefile.in b/xpcom/tests/Makefile.in new file mode 100644 index 000000000..75796b93b --- /dev/null +++ b/xpcom/tests/Makefile.in @@ -0,0 +1,13 @@ +# 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/. + +# Make sure we have symbols in case we need to debug these. +MOZ_DEBUG_SYMBOLS = 1 + +include $(topsrcdir)/config/rules.mk + +ifneq (,$(SIMPLE_PROGRAMS)) +libs:: + $(INSTALL) $(SIMPLE_PROGRAMS) $(DEPTH)/_tests/xpcshell/$(relativesrcdir)/unit +endif diff --git a/xpcom/tests/NotXPCOMTest.idl b/xpcom/tests/NotXPCOMTest.idl new file mode 100644 index 000000000..75d1e73da --- /dev/null +++ b/xpcom/tests/NotXPCOMTest.idl @@ -0,0 +1,23 @@ +/* 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.idl" + +[scriptable, uuid(93142a4f-e4cf-424a-b833-e638f87d2607)] +interface ScriptableOK : nsISupports +{ + void method1(); +}; + +[scriptable, uuid(237d01a3-771e-4c6e-adf9-c97f9aab2950)] +interface ScriptableWithNotXPCOM : nsISupports +{ + [notxpcom] void method2(); +}; + +[scriptable, uuid(4f06ec60-3bb3-4712-ab18-b2b595285558)] +interface ScriptableWithNotXPCOMBase : ScriptableWithNotXPCOM +{ + void method3(); +}; diff --git a/xpcom/tests/RegFactory.cpp b/xpcom/tests/RegFactory.cpp new file mode 100644 index 000000000..0abb7a4e8 --- /dev/null +++ b/xpcom/tests/RegFactory.cpp @@ -0,0 +1,130 @@ +/* -*- 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 <iostream.h> +#include "plstr.h" +#include "prlink.h" +#include "nsIComponentRegistrar.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +static bool gUnreg = false; + +void print_err(nsresult err) +{ + switch (err) { + case NS_ERROR_FACTORY_NOT_LOADED: + cerr << "Factory not loaded"; + break; + case NS_NOINTERFACE: + cerr << "No Interface"; + break; + case NS_ERROR_NULL_POINTER: + cerr << "Null pointer"; + break; + case NS_ERROR_OUT_OF_MEMORY: + cerr << "Out of memory"; + break; + default: + cerr << hex << err << dec; + } +} + +nsresult Register(nsIComponentRegistrar* registrar, const char *path) +{ + nsCOMPtr<nsIFile> file; + nsresult rv = + NS_NewLocalFile( + NS_ConvertUTF8toUTF16(path), + true, + getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + rv = registrar->AutoRegister(file); + return rv; +} + +nsresult Unregister(const char *path) +{ + /* NEEDS IMPLEMENTATION */ +#if 0 + nsresult res = nsComponentManager::AutoUnregisterComponent(path); + return res; +#else + return NS_ERROR_FAILURE; +#endif +} + +int ProcessArgs(nsIComponentRegistrar* registrar, int argc, char *argv[]) +{ + int i = 1; + nsresult res; + + while (i < argc) { + if (argv[i][0] == '-') { + int j; + for (j = 1; argv[i][j] != '\0'; j++) { + switch (argv[i][j]) { + case 'u': + gUnreg = true; + break; + default: + cerr << "Unknown option '" << argv[i][j] << "'\n"; + } + } + i++; + } else { + if (gUnreg) { + res = Unregister(argv[i]); + if (NS_SUCCEEDED(res)) { + cout << "Successfully unregistered: " << argv[i] << "\n"; + } else { + cerr << "Unregister failed ("; + print_err(res); + cerr << "): " << argv[i] << "\n"; + } + } else { + res = Register(registrar, argv[i]); + if (NS_SUCCEEDED(res)) { + cout << "Successfully registered: " << argv[i] << "\n"; + } else { + cerr << "Register failed ("; + print_err(res); + cerr << "): " << argv[i] << "\n"; + } + } + i++; + } + } + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + nsresult rv; + { + nsCOMPtr<nsIServiceManager> servMan; + rv = NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr); + if (NS_FAILED(rv)) return -1; + nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan); + NS_ASSERTION(registrar, "Null nsIComponentRegistrar"); + + /* With no arguments, RegFactory will autoregister */ + if (argc <= 1) + { + rv = registrar->AutoRegister(nullptr); + ret = (NS_FAILED(rv)) ? -1 : 0; + } + else + ret = ProcessArgs(registrar, argc, argv); + } // this scopes the nsCOMPtrs + // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM + rv = NS_ShutdownXPCOM(nullptr); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); + return ret; +} diff --git a/xpcom/tests/SizeTest01.cpp b/xpcom/tests/SizeTest01.cpp new file mode 100644 index 000000000..cdb7f00c0 --- /dev/null +++ b/xpcom/tests/SizeTest01.cpp @@ -0,0 +1,107 @@ +// Test01.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + This test file compares the generated code size of similar functions between raw + COM interface pointers (|AddRef|ing and |Release|ing by hand) and |nsCOMPtr|s. + + Function size results were determined by examining dissassembly of the generated code. + mXXX is the size of the generated code on the Macintosh. wXXX is the size on Windows. + For these tests, all reasonable optimizations were enabled and exceptions were + disabled (just as we build for release). + + The tests in this file explore only the simplest functionality: assigning a pointer + to be reference counted into a [raw, nsCOMPtr] object; ensuring that it is + |AddRef|ed and |Release|d appropriately; calling through the pointer to a function + supplied by the underlying COM interface. + + Windows: + raw_optimized 31 bytes + raw, nsCOMPtr* 34 + nsCOMPtr_optimized* 38 + nsCOMPtr_optimized 42 + nsCOMPtr 46 + + Macintosh: + raw_optimized, nsCOMPtr_optimized 112 bytes (1.0000) + nsCOMPtr 120 (1.0714) i.e., 7.14% bigger than raw_optimized et al + raw 140 (1.2500) + + The overall difference in size between Windows and Macintosh is caused by the + the PowerPC RISC architecture where every instruction is 4 bytes. + + On Macintosh, nsCOMPtr generates out-of-line destructors which are + not referenced, and which can be stripped by the linker. + */ + +void +Test01_raw( nsIDOMNode* aDOMNode, nsString* aResult ) + // m140, w34 + { + /* + This test is designed to be more like a typical large function where, + because you are working with several resources, you don't just return when + one of them is |nullptr|. Similarly: |Test01_nsCOMPtr00|, and |Test01_nsIPtr00|. + */ + + nsIDOMNode* node = aDOMNode; + NS_IF_ADDREF(node); + + if ( node ) + node->GetNodeName(*aResult); + + NS_IF_RELEASE(node); + } + +void +Test01_raw_optimized( nsIDOMNode* aDOMNode, nsString* aResult ) + // m112, w31 + { + /* + This test simulates smaller functions where you _do_ just return + |nullptr| at the first sign of trouble. Similarly: |Test01_nsCOMPtr01|, + and |Test01_nsIPtr01|. + */ + + /* + This test produces smaller code that |Test01_raw| because it avoids + the three tests: |NS_IF_...|, and |if ( node )|. + */ + +// -- the following code is assumed, but is commented out so we compare only +// the relevent generated code + +// if ( !aDOMNode ) +// return; + + nsIDOMNode* node = aDOMNode; + NS_ADDREF(node); + node->GetNodeName(*aResult); + NS_RELEASE(node); + } + +void +Test01_nsCOMPtr( nsIDOMNode* aDOMNode, nsString* aResult ) + // m120, w46/34 + { + nsCOMPtr<nsIDOMNode> node = aDOMNode; + + if ( node ) + node->GetNodeName(*aResult); + } + +void +Test01_nsCOMPtr_optimized( nsIDOMNode* aDOMNode, nsString* aResult ) + // m112, w42/38 + { +// if ( !aDOMNode ) +// return; + + nsCOMPtr<nsIDOMNode> node = aDOMNode; + node->GetNodeName(*aResult); + } diff --git a/xpcom/tests/SizeTest02.cpp b/xpcom/tests/SizeTest02.cpp new file mode 100644 index 000000000..9fcd1a0ca --- /dev/null +++ b/xpcom/tests/SizeTest02.cpp @@ -0,0 +1,89 @@ +// Test02.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + This test file compares the generated code size of similar functions between raw + COM interface pointers (|AddRef|ing and |Release|ing by hand) and |nsCOMPtr|s. + + Function size results were determined by examining dissassembly of the generated code. + mXXX is the size of the generated code on the Macintosh. wXXX is the size on Windows. + For these tests, all reasonable optimizations were enabled and exceptions were + disabled (just as we build for release). + + The tests in this file explore more complicated functionality: assigning a pointer + to be reference counted into a [raw, nsCOMPtr] object using |QueryInterface|; + ensuring that it is |AddRef|ed and |Release|d appropriately; calling through the pointer + to a function supplied by the underlying COM interface. The tests in this file expand + on the tests in "Test01.cpp" by adding |QueryInterface|. + + Windows: + raw01 52 + nsCOMPtr 63 + raw 66 + nsCOMPtr* 68 + + Macintosh: + nsCOMPtr 120 (1.0000) + Raw01 128 (1.1429) i.e., 14.29% bigger than nsCOMPtr + Raw00 144 (1.2000) + */ + + +void // nsresult +Test02_Raw00( nsISupports* aDOMNode, nsString* aResult ) + // m144, w66 + { +// -- the following code is assumed, but is commented out so we compare only +// the relevent generated code + +// if ( !aDOMNode ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* node = 0; + nsresult status = aDOMNode->QueryInterface(NS_GET_IID(nsIDOMNode), (void**)&node); + if ( NS_SUCCEEDED(status) ) + { + node->GetNodeName(*aResult); + } + + NS_IF_RELEASE(node); + +// return status; + } + +void // nsresult +Test02_Raw01( nsISupports* aDOMNode, nsString* aResult ) + // m128, w52 + { +// if ( !aDOMNode ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* node; + nsresult status = aDOMNode->QueryInterface(NS_GET_IID(nsIDOMNode), (void**)&node); + if ( NS_SUCCEEDED(status) ) + { + node->GetNodeName(*aResult); + NS_RELEASE(node); + } + +// return status; + } + +void // nsresult +Test02_nsCOMPtr( nsISupports* aDOMNode, nsString* aResult ) + // m120, w63/68 + { + nsresult status; + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aDOMNode, &status); + + if ( node ) + node->GetNodeName(*aResult); + +// return status; + } + diff --git a/xpcom/tests/SizeTest03.cpp b/xpcom/tests/SizeTest03.cpp new file mode 100644 index 000000000..e1593171e --- /dev/null +++ b/xpcom/tests/SizeTest03.cpp @@ -0,0 +1,97 @@ +// Test03.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + Windows: + nsCOMPtr_optimized* 45 + raw_optimized 48 + nsCOMPtr_optimized 50 + nsCOMPtr 54 + nsCOMPtr* 59 + raw 62 + + Macintosh: + nsCOMPtr_optimized 112 (1.0000) + raw_optimized 124 bytes (1.1071) i.e., 10.71% bigger than nsCOMPtr_optimized + nsCOMPtr 144 (1.2857) + */ + +void // nsresult +Test03_raw( nsIDOMNode* aDOMNode, nsString* aResult ) + // m140, w62 + { +// -- the following code is assumed, but is commented out so we compare only +// the relevent generated code + +// if ( !aDOMNode || !aResult ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* parent = 0; + nsresult status = aDOMNode->GetParentNode(&parent); + + if ( NS_SUCCEEDED(status) ) + { + parent->GetNodeName(*aResult); + } + + NS_IF_RELEASE(parent); + +// return status; + } + + +void // nsresult +Test03_raw_optimized( nsIDOMNode* aDOMNode, nsString* aResult ) + // m124, w48 + { +// if ( !aDOMNode || !aResult ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* parent; + nsresult status = aDOMNode->GetParentNode(&parent); + + if ( NS_SUCCEEDED(status) ) + { + parent->GetNodeName(*aResult); + NS_RELEASE(parent); + } + +// return status; + } + + +void // nsresult +Test03_nsCOMPtr( nsIDOMNode* aDOMNode, nsString* aResult ) + // m144, w54/59 + { +// if ( !aDOMNode || !aResult ) +// return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIDOMNode> parent; + nsresult status = aDOMNode->GetParentNode( getter_AddRefs(parent) ); + if ( parent ) + parent->GetNodeName(*aResult); + +// return status; + } + +void // nsresult +Test03_nsCOMPtr_optimized( nsIDOMNode* aDOMNode, nsString* aResult ) + // m112, w50/45 + { +// if ( !aDOMNode || !aResult ) +// return NS_ERROR_NULL_POINTER; + + nsIDOMNode* temp; + nsresult status = aDOMNode->GetParentNode(&temp); + nsCOMPtr<nsIDOMNode> parent( dont_AddRef(temp) ); + if ( parent ) + parent->GetNodeName(*aResult); + +// return status; + } diff --git a/xpcom/tests/SizeTest04.cpp b/xpcom/tests/SizeTest04.cpp new file mode 100644 index 000000000..e045f29f6 --- /dev/null +++ b/xpcom/tests/SizeTest04.cpp @@ -0,0 +1,68 @@ +// Test04.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + Windows: + nsCOMPtr 13 + raw 36 + + Macintosh: + nsCOMPtr 36 bytes (1.0000) + raw 120 (3.3333) i.e., 333.33% bigger than nsCOMPtr + */ + +class Test04_Raw + { + public: + Test04_Raw(); + ~Test04_Raw(); + + void /*nsresult*/ SetNode( nsIDOMNode* newNode ); + + private: + nsIDOMNode* mNode; + }; + +Test04_Raw::Test04_Raw() + : mNode(0) + { + // nothing else to do here + } + +Test04_Raw::~Test04_Raw() + { + NS_IF_RELEASE(mNode); + } + +void // nsresult +Test04_Raw::SetNode( nsIDOMNode* newNode ) + // m120, w36 + { + NS_IF_ADDREF(newNode); + NS_IF_RELEASE(mNode); + mNode = newNode; + +// return NS_OK; + } + + + +class Test04_nsCOMPtr + { + public: + void /*nsresult*/ SetNode( nsIDOMNode* newNode ); + + private: + nsCOMPtr<nsIDOMNode> mNode; + }; + +void // nsresult +Test04_nsCOMPtr::SetNode( nsIDOMNode* newNode ) + // m36, w13/13 + { + mNode = newNode; + } diff --git a/xpcom/tests/SizeTest05.cpp b/xpcom/tests/SizeTest05.cpp new file mode 100644 index 000000000..1307b1c26 --- /dev/null +++ b/xpcom/tests/SizeTest05.cpp @@ -0,0 +1,74 @@ +// Test05.cpp + +#include "nsIDOMNode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsIDOMNode); + + /* + Windows: + raw, nsCOMPtr 21 bytes + + Macintosh: + Raw, nsCOMPtr 64 bytes + */ + +class Test05_Raw + { + public: + Test05_Raw(); + ~Test05_Raw(); + + void /*nsresult*/ GetNode( nsIDOMNode** aNode ); + + private: + nsIDOMNode* mNode; + }; + +Test05_Raw::Test05_Raw() + : mNode(0) + { + // nothing else to do here + } + +Test05_Raw::~Test05_Raw() + { + NS_IF_RELEASE(mNode); + } + +void // nsresult +Test05_Raw::GetNode( nsIDOMNode** aNode ) + // m64, w21 + { +// if ( !aNode ) +// return NS_ERROR_NULL_POINTER; + + *aNode = mNode; + NS_IF_ADDREF(*aNode); + +// return NS_OK; + } + + + +class Test05_nsCOMPtr + { + public: + void /*nsresult*/ GetNode( nsIDOMNode** aNode ); + + private: + nsCOMPtr<nsIDOMNode> mNode; + }; + +void // nsresult +Test05_nsCOMPtr::GetNode( nsIDOMNode** aNode ) + // m64, w21 + { +// if ( !aNode ) +// return NS_ERROR_NULL_POINTER; + + *aNode = mNode; + NS_IF_ADDREF(*aNode); + +// return NS_OK; + } diff --git a/xpcom/tests/SizeTest06.cpp b/xpcom/tests/SizeTest06.cpp new file mode 100644 index 000000000..67b57277d --- /dev/null +++ b/xpcom/tests/SizeTest06.cpp @@ -0,0 +1,150 @@ +// Test06.cpp + +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsPIDOMWindow); +NS_DEF_PTR(nsIBaseWindow); + + /* + Windows: + nsCOMPtr_optimized 176 + nsCOMPtr_as_found 181 + nsCOMPtr_optimized* 182 + nsCOMPtr02* 184 + nsCOMPtr02 187 + nsCOMPtr02* 188 + nsCOMPtr03 189 + raw_optimized, nsCOMPtr00 191 + nsCOMPtr00* 199 + nsCOMPtr_as_found* 201 + raw 214 + + Macintosh: + nsCOMPtr_optimized 300 (1.0000) + nsCOMPtr02 320 (1.0667) i.e., 6.67% bigger than nsCOMPtr_optimized + nsCOMPtr00 328 (1.0933) + raw_optimized, nsCOMPtr03 332 (1.1067) + nsCOMPtr_as_found 344 (1.1467) + raw 388 (1.2933) + + */ + + +void // nsresult +Test06_raw(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) + // m388, w214 +{ +// if (!aDOMWindow) +// return NS_ERROR_NULL_POINTER; + nsPIDOMWindow* window = 0; + nsresult status = aDOMWindow->QueryInterface(NS_GET_IID(nsPIDOMWindow), (void**)&window); + nsIDocShell* docShell = 0; + if (window) + window->GetDocShell(&docShell); + nsIWebShell* rootWebShell = 0; + NS_IF_RELEASE(rootWebShell); + NS_IF_RELEASE(docShell); + NS_IF_RELEASE(window); +// return status; +} + +void // nsresult +Test06_raw_optimized(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) + // m332, w191 +{ +// if (!aDOMWindow) +// return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsPIDOMWindow* window; + nsresult status = aDOMWindow->QueryInterface(NS_GET_IID(nsPIDOMWindow), (void**)&window); + if (NS_SUCCEEDED(status)) { + nsIDocShell* docShell = 0; + window->GetDocShell(&docShell); + if (docShell) { + NS_RELEASE(docShell); + } + NS_RELEASE(window); + } +// return status; +} + +void +Test06_nsCOMPtr_as_found(nsIDOMWindow* aDOMWindow, nsCOMPtr<nsIBaseWindow>* aBaseWindow) + // m344, w181/201 +{ +// if (!aDOMWindow) +// return; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow); + nsCOMPtr<nsIDocShell> docShell; + if (window) + window->GetDocShell(getter_AddRefs(docShell)); +} + +void // nsresult +Test06_nsCOMPtr00(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) + // m328, w191/199 +{ +// if (!aDOMWindow) +// return NS_ERROR_NULL_POINTER; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + nsIDocShell* temp0 = 0; + if (window) + window->GetDocShell(&temp0); + nsCOMPtr<nsIDocShell> docShell = dont_AddRef(temp0); + (*aBaseWindow) = 0; +// return status; +} + +void // nsresult +Test06_nsCOMPtr_optimized(nsIDOMWindow* aDOMWindow, nsCOMPtr<nsIBaseWindow>* aBaseWindow) + // m300, w176/182 +{ +// if (!aDOMWindow) +// return NS_ERROR_NULL_POINTER; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + nsIDocShell* temp0 = 0; + if (window) + window->GetDocShell(&temp0); + (*aBaseWindow) = do_QueryInterface(nullptr, &status); +// return status; +} + +void // nsresult +Test06_nsCOMPtr02(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) + // m320, w187/184 +{ +// if (!aDOMWindow) +// return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + if (window) { + nsIDocShell* temp0; + window->GetDocShell(&temp0); + } +// return status; +} + +void // nsresult +Test06_nsCOMPtr03(nsIDOMWindow* aDOMWindow, nsCOMPtr<nsIBaseWindow>* aBaseWindow) + // m332, w189/188 +{ +// if (!aDOMWindow) +// return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsresult status; + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aDOMWindow, &status); + if (window) { + nsIDocShell* temp0; + window->GetDocShell(&temp0); + nsCOMPtr<nsIDocShell> docShell = dont_AddRef(temp0); + if (docShell) { + } + } +// return status; +} diff --git a/xpcom/tests/TestArguments.cpp b/xpcom/tests/TestArguments.cpp new file mode 100644 index 000000000..a9a9a258b --- /dev/null +++ b/xpcom/tests/TestArguments.cpp @@ -0,0 +1,25 @@ +#include <string.h> + +int main(int argc, char* argv[]) { + if (argc != 9) + return -1; + + if (strcmp("mozilla", argv[1]) != 0) + return 1; + if (strcmp("firefox", argv[2]) != 0) + return 2; + if (strcmp("thunderbird", argv[3]) != 0) + return 3; + if (strcmp("seamonkey", argv[4]) != 0) + return 4; + if (strcmp("foo", argv[5]) != 0) + return 5; + if (strcmp("bar", argv[6]) != 0) + return 6; + if (strcmp("argument with spaces", argv[7]) != 0) + return 7; + if (strcmp("\"argument with quotes\"", argv[8]) != 0) + return 8; + + return 0; +} diff --git a/xpcom/tests/TestBlockingProcess.cpp b/xpcom/tests/TestBlockingProcess.cpp new file mode 100644 index 000000000..9526934c1 --- /dev/null +++ b/xpcom/tests/TestBlockingProcess.cpp @@ -0,0 +1,8 @@ +#include <stdio.h> + +int main() +{ + char tmp; + fread(&tmp, sizeof(tmp), 1, stdin); + return 0; +} diff --git a/xpcom/tests/TestHarness.h b/xpcom/tests/TestHarness.h new file mode 100644 index 000000000..753e0233a --- /dev/null +++ b/xpcom/tests/TestHarness.h @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +/* + * Test harness for XPCOM objects, providing a scoped XPCOM initializer, + * nsCOMPtr, nsRefPtr, do_CreateInstance, do_GetService, ns(Auto|C|)String, + * and stdio.h/stdlib.h. + */ + +#ifndef TestHarness_h__ +#define TestHarness_h__ + +#include "mozilla/ArrayUtils.h" + +#include "prenv.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsStringGlue.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +static uint32_t gFailCount = 0; + +/** + * Prints the given failure message and arguments using printf, prepending + * "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and + * appending "\n" to eliminate having to type it at each call site. + */ +void fail(const char* msg, ...) +{ + va_list ap; + + printf("TEST-UNEXPECTED-FAIL | "); + + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + + putchar('\n'); + ++gFailCount; +} + +/** + * Prints the given success message and arguments using printf, prepending + * "TEST-PASS " for the benefit of the test harness and + * appending "\n" to eliminate having to type it at each call site. + */ +void passed(const char* msg, ...) +{ + va_list ap; + + printf("TEST-PASS | "); + + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + + putchar('\n'); +} + +//----------------------------------------------------------------------------- + +class ScopedXPCOM : public nsIDirectoryServiceProvider2 +{ + public: + NS_DECL_ISUPPORTS + + explicit ScopedXPCOM(const char* testName, + nsIDirectoryServiceProvider *dirSvcProvider = nullptr) + : mDirSvcProvider(dirSvcProvider) + { + mTestName = testName; + printf("Running %s tests...\n", mTestName); + + nsresult rv = NS_InitXPCOM2(&mServMgr, nullptr, this); + if (NS_FAILED(rv)) + { + fail("NS_InitXPCOM2 returned failure code 0x%x", rv); + mServMgr = nullptr; + return; + } + } + + ~ScopedXPCOM() + { + // If we created a profile directory, we need to remove it. + if (mProfD) { + nsCOMPtr<nsIObserverService> os = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + MOZ_RELEASE_ASSERT(os); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-change-net-teardown", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-change-teardown", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change-qm", nullptr)); + MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change-telemetry", nullptr)); + + if (NS_FAILED(mProfD->Remove(true))) { + NS_WARNING("Problem removing profile directory"); + } + + mProfD = nullptr; + } + + if (mServMgr) + { + NS_RELEASE(mServMgr); + nsresult rv = NS_ShutdownXPCOM(nullptr); + if (NS_FAILED(rv)) + { + fail("XPCOM shutdown failed with code 0x%x", rv); + exit(1); + } + } + + printf("Finished running %s tests.\n", mTestName); + } + + bool failed() + { + return mServMgr == nullptr; + } + + already_AddRefed<nsIFile> GetProfileDirectory() + { + if (mProfD) { + nsCOMPtr<nsIFile> copy = mProfD; + return copy.forget(); + } + + // Create a unique temporary folder to use for this test. + // Note that runcppunittests.py will run tests with a temp + // directory as the cwd, so just put something under that. + nsCOMPtr<nsIFile> profD; + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_PROCESS_DIR, + getter_AddRefs(profD)); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = profD->Append(NS_LITERAL_STRING("cpp-unit-profd")); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = profD->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, nullptr); + + mProfD = profD; + return profD.forget(); + } + + already_AddRefed<nsIFile> GetGREDirectory() + { + if (mGRED) { + nsCOMPtr<nsIFile> copy = mGRED; + return copy.forget(); + } + + char* env = PR_GetEnv("MOZ_XRE_DIR"); + nsCOMPtr<nsIFile> greD; + if (env) { + NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, + getter_AddRefs(greD)); + } + + mGRED = greD; + return greD.forget(); + } + + already_AddRefed<nsIFile> GetGREBinDirectory() + { + if (mGREBinD) { + nsCOMPtr<nsIFile> copy = mGREBinD; + return copy.forget(); + } + + nsCOMPtr<nsIFile> greD = GetGREDirectory(); + if (!greD) { + return greD.forget(); + } + greD->Clone(getter_AddRefs(mGREBinD)); + +#ifdef XP_MACOSX + nsAutoCString leafName; + mGREBinD->GetNativeLeafName(leafName); + if (leafName.Equals("Resources")) { + mGREBinD->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS")); + } +#endif + + nsCOMPtr<nsIFile> copy = mGREBinD; + return copy.forget(); + } + + //////////////////////////////////////////////////////////////////////////// + //// nsIDirectoryServiceProvider + + NS_IMETHOD GetFile(const char *aProperty, bool *_persistent, + nsIFile **_result) override + { + // If we were supplied a directory service provider, ask it first. + if (mDirSvcProvider && + NS_SUCCEEDED(mDirSvcProvider->GetFile(aProperty, _persistent, + _result))) { + return NS_OK; + } + + // Otherwise, the test harness provides some directories automatically. + if (0 == strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) || + 0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR) || + 0 == strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) { + nsCOMPtr<nsIFile> profD = GetProfileDirectory(); + NS_ENSURE_TRUE(profD, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFile> clone; + nsresult rv = profD->Clone(getter_AddRefs(clone)); + NS_ENSURE_SUCCESS(rv, rv); + + *_persistent = true; + clone.forget(_result); + return NS_OK; + } else if (0 == strcmp(aProperty, NS_GRE_DIR)) { + nsCOMPtr<nsIFile> greD = GetGREDirectory(); + NS_ENSURE_TRUE(greD, NS_ERROR_FAILURE); + + *_persistent = true; + greD.forget(_result); + return NS_OK; + } else if (0 == strcmp(aProperty, NS_GRE_BIN_DIR)) { + nsCOMPtr<nsIFile> greBinD = GetGREBinDirectory(); + NS_ENSURE_TRUE(greBinD, NS_ERROR_FAILURE); + + *_persistent = true; + greBinD.forget(_result); + return NS_OK; + } + + return NS_ERROR_FAILURE; + } + + //////////////////////////////////////////////////////////////////////////// + //// nsIDirectoryServiceProvider2 + + NS_IMETHOD GetFiles(const char *aProperty, nsISimpleEnumerator **_enum) override + { + // If we were supplied a directory service provider, ask it first. + nsCOMPtr<nsIDirectoryServiceProvider2> provider = + do_QueryInterface(mDirSvcProvider); + if (provider && NS_SUCCEEDED(provider->GetFiles(aProperty, _enum))) { + return NS_OK; + } + + return NS_ERROR_FAILURE; + } + + private: + const char* mTestName; + nsIServiceManager* mServMgr; + nsCOMPtr<nsIDirectoryServiceProvider> mDirSvcProvider; + nsCOMPtr<nsIFile> mProfD; + nsCOMPtr<nsIFile> mGRED; + nsCOMPtr<nsIFile> mGREBinD; +}; + +NS_IMPL_QUERY_INTERFACE( + ScopedXPCOM, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2 +) + +NS_IMETHODIMP_(MozExternalRefCountType) +ScopedXPCOM::AddRef() +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +ScopedXPCOM::Release() +{ + return 1; +} + +#endif // TestHarness_h__ diff --git a/xpcom/tests/TestPRIntN.cpp b/xpcom/tests/TestPRIntN.cpp new file mode 100644 index 000000000..afc155d70 --- /dev/null +++ b/xpcom/tests/TestPRIntN.cpp @@ -0,0 +1,33 @@ +/* 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 <stdint.h> +#include "prtypes.h" + +// This test is NOT intended to be run. It's a test to make sure +// PRInt{N} matches int{N}_t. If they don't match, we should get a +// compiler warning or error in main(). + +static void +ClearNSPRIntTypes(PRInt8 *a, PRInt16 *b, PRInt32 *c, PRInt64 *d) +{ + *a = 0; *b = 0; *c = 0; *d = 0; +} + +static void +ClearStdIntTypes(int8_t *w, int16_t *x, int32_t *y, int64_t *z) +{ + *w = 0; *x = 0; *y = 0; *z = 0; +} + +int +main() +{ + PRInt8 a; PRInt16 b; PRInt32 c; PRInt64 d; + int8_t w; int16_t x; int32_t y; int64_t z; + + ClearNSPRIntTypes(&w, &x, &y, &z); + ClearStdIntTypes(&a, &b, &c, &d); + return 0; +} diff --git a/xpcom/tests/TestQuickReturn.cpp b/xpcom/tests/TestQuickReturn.cpp new file mode 100644 index 000000000..d00771ba7 --- /dev/null +++ b/xpcom/tests/TestQuickReturn.cpp @@ -0,0 +1,8 @@ +#include <stdio.h> + +int main (int argc, char* argv[]) { + if (argc != 1) + return -1; + + return 42; +} diff --git a/xpcom/tests/TestShutdown.cpp b/xpcom/tests/TestShutdown.cpp new file mode 100644 index 000000000..177c83dbd --- /dev/null +++ b/xpcom/tests/TestShutdown.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsIServiceManager.h" + +// Gee this seems simple! It's for testing for memory leaks with Purify. + +void main(int argc, char* argv[]) +{ + nsresult rv; + nsIServiceManager* servMgr; + rv = NS_InitXPCOM2(&servMgr, nullptr, nullptr); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_InitXPCOM failed"); + + // try loading a component and releasing it to see if it leaks + if (argc > 1 && argv[1] != nullptr) { + char* cidStr = argv[1]; + nsISupports* obj = nullptr; + if (cidStr[0] == '{') { + nsCID cid; + cid.Parse(cidStr); + rv = CallCreateInstance(cid, &obj); + } + else { + // contractID case: + rv = CallCreateInstance(cidStr, &obj); + } + if (NS_SUCCEEDED(rv)) { + printf("Successfully created %s\n", cidStr); + NS_RELEASE(obj); + } + else { + printf("Failed to create %s (%x)\n", cidStr, rv); + } + } + + rv = NS_ShutdownXPCOM(servMgr); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); +} diff --git a/xpcom/tests/TestStackCrawl.cpp b/xpcom/tests/TestStackCrawl.cpp new file mode 100644 index 000000000..e1fe37db7 --- /dev/null +++ b/xpcom/tests/TestStackCrawl.cpp @@ -0,0 +1,11 @@ +#include <stdio.h> + +#include "nsISupportsUtils.h" +#include "nsTraceRefcnt.h" + +int main(int argc, char* argv[]) +{ + nsTraceRefcnt::WalkTheStack(stdout); + return 0; +} + diff --git a/xpcom/tests/TestStreamUtils.cpp b/xpcom/tests/TestStreamUtils.cpp new file mode 100644 index 000000000..72640474f --- /dev/null +++ b/xpcom/tests/TestStreamUtils.cpp @@ -0,0 +1,74 @@ +/* -*- 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 <stdlib.h> +#include <stdio.h> +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +//---- + +static bool test_consume_stream() { + const char kData[] = + "Get your facts first, and then you can distort them as much as you " + "please."; + + nsCOMPtr<nsIInputStream> input; + nsCOMPtr<nsIOutputStream> output; + NS_NewPipe(getter_AddRefs(input), + getter_AddRefs(output), + 10, UINT32_MAX); + if (!input || !output) + return false; + + uint32_t n = 0; + output->Write(kData, sizeof(kData) - 1, &n); + if (n != (sizeof(kData) - 1)) + return false; + output = nullptr; // close output + + nsCString buf; + if (NS_FAILED(NS_ConsumeStream(input, UINT32_MAX, buf))) + return false; + + if (!buf.Equals(kData)) + return false; + + return true; +} + +//---- + +typedef bool (*TestFunc)(); +#define DECL_TEST(name) { #name, name } + +static const struct Test { + const char* name; + TestFunc func; +} tests[] = { + DECL_TEST(test_consume_stream), + { nullptr, nullptr } +}; + +int main(int argc, char **argv) { + int count = 1; + if (argc > 1) + count = atoi(argv[1]); + + if (NS_FAILED(NS_InitXPCOM2(nullptr, nullptr, nullptr))) + return -1; + + while (count--) { + for (const Test* t = tests; t->name != nullptr; ++t) { + printf("%25s : %s\n", t->name, t->func() ? "SUCCESS" : "FAILURE"); + } + } + + NS_ShutdownXPCOM(nullptr); + return 0; +} diff --git a/xpcom/tests/TestUnicodeArguments.cpp b/xpcom/tests/TestUnicodeArguments.cpp new file mode 100644 index 000000000..35d80a7f4 --- /dev/null +++ b/xpcom/tests/TestUnicodeArguments.cpp @@ -0,0 +1,77 @@ +/** + * On Windows, a Unicode argument is passed as UTF-16 using ShellExecuteExW. + * On other platforms, it is passed as UTF-8 + */ + +static const int args_length = 4; +#if defined(XP_WIN) && defined(_MSC_VER) +#define _UNICODE +#include <tchar.h> +#include <stdio.h> + +static const _TCHAR* expected_utf16[args_length] = { + // Latin-1 + L"M\xF8z\xEEll\xE5", + // Cyrillic + L"\x41C\x43E\x437\x438\x43B\x43B\x430", + // Bengali + L"\x9AE\x9CB\x99C\x9BF\x9B2\x9BE", + // Cuneiform + L"\xD808\xDE2C\xD808\xDF63\xD808\xDDB7" +}; + +int wmain(int argc, _TCHAR* argv[]) { + printf("argc = %d\n", argc); + + if (argc != args_length + 1) + return -1; + + for (int i = 1; i < argc; ++i) { + printf("expected[%d]: ", i - 1); + for (size_t j = 0; j < _tcslen(expected_utf16[i - 1]); ++j) { + printf("%x ", *(expected_utf16[i - 1] + j)); + } + printf("\n"); + + printf("argv[%d]: ", i); + for (size_t j = 0; j < _tcslen(argv[i]); ++j) { + printf("%x ", *(argv[i] + j)); + } + printf("\n"); + + if (_tcscmp(expected_utf16[i - 1], argv[i])) { + return i; + } + } + + return 0; +} +#else +#include <string.h> +#include <stdio.h> + +static const char* expected_utf8[args_length] = { + // Latin-1 + "M\xC3\xB8z\xC3\xAEll\xC3\xA5", + // Cyrillic + "\xD0\x9C\xD0\xBE\xD0\xB7\xD0\xB8\xD0\xBB\xD0\xBB\xD0\xB0", + // Bengali + "\xE0\xA6\xAE\xE0\xA7\x8B\xE0\xA6\x9C\xE0\xA6\xBF\xE0\xA6\xB2\xE0\xA6\xBE", + // Cuneiform + "\xF0\x92\x88\xAC\xF0\x92\x8D\xA3\xF0\x92\x86\xB7" +}; + +int main(int argc, char* argv[]) { + if (argc != args_length + 1) + return -1; + + for (int i = 1; i < argc; ++i) { + printf("argv[%d] = %s; expected = %s\n", i, argv[i], expected_utf8[i - 1]); + if (strcmp(expected_utf8[i - 1], argv[i])) { + return i; + } + } + + return 0; +} +#endif diff --git a/xpcom/tests/TestWinReg.js b/xpcom/tests/TestWinReg.js new file mode 100644 index 000000000..5bde37900 --- /dev/null +++ b/xpcom/tests/TestWinReg.js @@ -0,0 +1,57 @@ +/* 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 script is intended to be run using xpcshell + */ + +const nsIWindowsRegKey = Components.interfaces.nsIWindowsRegKey; +const BASE_PATH = "SOFTWARE\\Mozilla\\Firefox"; + +function idump(indent, str) +{ + for (var j = 0; j < indent; ++j) + dump(" "); + dump(str); +} + +function list_values(indent, key) { + idump(indent, "{\n"); + var count = key.valueCount; + for (var i = 0; i < count; ++i) { + var vn = key.getValueName(i); + var val = ""; + if (key.getValueType(vn) == nsIWindowsRegKey.TYPE_STRING) { + val = key.readStringValue(vn); + } + if (vn == "") + idump(indent + 1, "(Default): \"" + val + "\"\n"); + else + idump(indent + 1, vn + ": \"" + val + "\"\n"); + } + idump(indent, "}\n"); +} + +function list_children(indent, key) { + list_values(indent, key); + + var count = key.childCount; + for (var i = 0; i < count; ++i) { + var cn = key.getChildName(i); + idump(indent, "[" + cn + "]\n"); + list_children(indent + 1, key.openChild(cn, nsIWindowsRegKey.ACCESS_READ)); + } +} + +// enumerate everything under BASE_PATH +var key = Components.classes["@mozilla.org/windows-registry-key;1"]. + createInstance(nsIWindowsRegKey); +key.open(nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, BASE_PATH, + nsIWindowsRegKey.ACCESS_READ); +list_children(1, key); + +key.close(); +key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, BASE_PATH, + nsIWindowsRegKey.ACCESS_READ); +list_children(1, key); diff --git a/xpcom/tests/TestingAtomList.h b/xpcom/tests/TestingAtomList.h new file mode 100644 index 000000000..ffeada605 --- /dev/null +++ b/xpcom/tests/TestingAtomList.h @@ -0,0 +1,6 @@ +/* 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/. */ + +TESTING_ATOM(foo, "foo") +TESTING_ATOM(bar, "bar") diff --git a/xpcom/tests/bug656331_component/TestComponent.cpp b/xpcom/tests/bug656331_component/TestComponent.cpp new file mode 100644 index 000000000..987d21451 --- /dev/null +++ b/xpcom/tests/bug656331_component/TestComponent.cpp @@ -0,0 +1,32 @@ +/* -*- 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/ModuleUtils.h" + +// f18fb09b-28b4-4435-bc5b-8027f18df743 +#define NS_TESTING_CID \ +{ 0xf18fb09b, 0x28b4, 0x4435, \ + { 0xbc, 0x5b, 0x80, 0x27, 0xf1, 0x8d, 0xf7, 0x43 } } + +NS_DEFINE_NAMED_CID(NS_TESTING_CID); + +static nsresult +DummyConstructorFunc(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +static const mozilla::Module::CIDEntry kTestCIDs[] = { + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { nullptr } +}; + +static const mozilla::Module kTestModule = { + 3, /* faking mozilla::Module::kVersion with a value that will never be used */ + kTestCIDs +}; + +NSMODULE_DEFN(dummy) = &kTestModule; diff --git a/xpcom/tests/bug656331_component/bug656331.manifest b/xpcom/tests/bug656331_component/bug656331.manifest new file mode 100644 index 000000000..fb1991a56 --- /dev/null +++ b/xpcom/tests/bug656331_component/bug656331.manifest @@ -0,0 +1,2 @@ +#filter substitution +binary-component @LIBRARY_FILENAME@ diff --git a/xpcom/tests/bug656331_component/moz.build b/xpcom/tests/bug656331_component/moz.build new file mode 100644 index 000000000..e986f3de0 --- /dev/null +++ b/xpcom/tests/bug656331_component/moz.build @@ -0,0 +1,26 @@ +# -*- 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/. + +FINAL_TARGET = '_tests/xpcshell/xpcom/tests/unit' +EXTRA_PP_COMPONENTS += [ + 'bug656331.manifest', +] + +SOURCES += [ + 'TestComponent.cpp', +] + +XPCOMBinaryComponent('test656331') + +DEFINES['LIBRARY_FILENAME'] = '%s%s%s' % ( + CONFIG['DLL_PREFIX'], + LIBRARY_NAME, + CONFIG['DLL_SUFFIX'] +) + +# Need to link with CoreFoundation on Mac +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/xpcom/tests/component/TestComponent.cpp b/xpcom/tests/component/TestComponent.cpp new file mode 100644 index 000000000..85b8860ac --- /dev/null +++ b/xpcom/tests/component/TestComponent.cpp @@ -0,0 +1,44 @@ +/* -*- 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/ModuleUtils.h" + +#define NS_TESTING_CID \ +{ 0x335fb596, 0xe52d, 0x418f, \ + { 0xb0, 0x1c, 0x1b, 0xf1, 0x6c, 0xe5, 0xe7, 0xe4 } } +#define NS_NONEXISTENT_CID \ +{ 0x1e61fb15, 0xead4, 0x45cd, \ + { 0x80, 0x13, 0x40, 0x99, 0xa7, 0x10, 0xa2, 0xfa } } + +NS_DEFINE_NAMED_CID(NS_TESTING_CID); +NS_DEFINE_NAMED_CID(NS_NONEXISTENT_CID); + +static nsresult +DummyConstructorFunc(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +static const mozilla::Module::CIDEntry kTestCIDs[] = { + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kTestContractIDs[] = { + { "@testing/foo", &kNS_NONEXISTENT_CID }, + { nullptr } +}; + +static const mozilla::Module kTestModule = { + mozilla::Module::kVersion, + kTestCIDs, + kTestContractIDs +}; + +NSMODULE_DEFN(dummy) = &kTestModule; + + diff --git a/xpcom/tests/component/moz.build b/xpcom/tests/component/moz.build new file mode 100644 index 000000000..62e88502c --- /dev/null +++ b/xpcom/tests/component/moz.build @@ -0,0 +1,26 @@ +# -*- 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/. + +FINAL_TARGET = '_tests/xpcshell/xpcom/tests/unit' +EXTRA_PP_COMPONENTS += [ + 'testcomponent.manifest', +] + +SOURCES += [ + 'TestComponent.cpp', +] + +XPCOMBinaryComponent('testcomponent') + +DEFINES['LIBRARY_FILENAME'] = '%s%s%s' % ( + CONFIG['DLL_PREFIX'], + LIBRARY_NAME, + CONFIG['DLL_SUFFIX'] +) + +# Need to link with CoreFoundation on Mac +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/xpcom/tests/component/testcomponent.manifest b/xpcom/tests/component/testcomponent.manifest new file mode 100644 index 000000000..a570e4c18 --- /dev/null +++ b/xpcom/tests/component/testcomponent.manifest @@ -0,0 +1,4 @@ +#filter substitution +binary-component @LIBRARY_FILENAME@ +binary-component @LIBRARY_FILENAME@ +binary-component @LIBRARY_FILENAME@ diff --git a/xpcom/tests/component_no_aslr/Makefile.in b/xpcom/tests/component_no_aslr/Makefile.in new file mode 100644 index 000000000..f08d6ad8a --- /dev/null +++ b/xpcom/tests/component_no_aslr/Makefile.in @@ -0,0 +1,8 @@ +# +# 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 $(topsrcdir)/config/rules.mk + +LDFLAGS := $(filter-out -DYNAMICBASE,$(LDFLAGS)) -DYNAMICBASE:NO diff --git a/xpcom/tests/component_no_aslr/TestComponent.cpp b/xpcom/tests/component_no_aslr/TestComponent.cpp new file mode 100644 index 000000000..6fbfd316a --- /dev/null +++ b/xpcom/tests/component_no_aslr/TestComponent.cpp @@ -0,0 +1,33 @@ +/* -*- 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/ModuleUtils.h" + +#define NS_TESTING_CID \ +{ 0x335fb596, 0xe52d, 0x418f, \ + { 0xb0, 0x1c, 0x1b, 0xf1, 0x6c, 0xe5, 0xe7, 0xe4 } } + +NS_DEFINE_NAMED_CID(NS_TESTING_CID); + +static nsresult +DummyConstructorFunc(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +static const mozilla::Module::CIDEntry kTestCIDs[] = { + { &kNS_TESTING_CID, false, nullptr, DummyConstructorFunc }, + { nullptr } +}; + +static const mozilla::Module kTestModule = { + mozilla::Module::kVersion, + kTestCIDs +}; + +NSMODULE_DEFN(dummy) = &kTestModule; + + diff --git a/xpcom/tests/component_no_aslr/moz.build b/xpcom/tests/component_no_aslr/moz.build new file mode 100644 index 000000000..45e69c105 --- /dev/null +++ b/xpcom/tests/component_no_aslr/moz.build @@ -0,0 +1,26 @@ +# -*- 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/. + +FINAL_TARGET = '_tests/xpcshell/xpcom/tests/unit' +EXTRA_PP_COMPONENTS += [ + 'testcompnoaslr.manifest', +] + +SOURCES += [ + 'TestComponent.cpp', +] + +XPCOMBinaryComponent('testcompnoaslr') + +DEFINES['LIBRARY_FILENAME'] = '%s%s%s' % ( + CONFIG['DLL_PREFIX'], + LIBRARY_NAME, + CONFIG['DLL_SUFFIX'] +) + +# Need to link with CoreFoundation on Mac +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + OS_LIBS += CONFIG['TK_LIBS'] diff --git a/xpcom/tests/component_no_aslr/testcompnoaslr.manifest b/xpcom/tests/component_no_aslr/testcompnoaslr.manifest new file mode 100644 index 000000000..fb1991a56 --- /dev/null +++ b/xpcom/tests/component_no_aslr/testcompnoaslr.manifest @@ -0,0 +1,2 @@ +#filter substitution +binary-component @LIBRARY_FILENAME@ diff --git a/xpcom/tests/external/TestMinStringAPI.cpp b/xpcom/tests/external/TestMinStringAPI.cpp new file mode 100644 index 000000000..28bc0b1c1 --- /dev/null +++ b/xpcom/tests/external/TestMinStringAPI.cpp @@ -0,0 +1,1009 @@ +/* -*- 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 "nsStringAPI.h" +#include "nsXPCOM.h" +#include "nsMemory.h" + +static const char kAsciiData[] = "Hello World"; + +static const char16_t kUnicodeData[] = + {'H','e','l','l','o',' ','W','o','r','l','d','\0'}; + +static bool test_basic_1() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + + const char *ptr; + uint32_t len; + char *clone; + + NS_CStringGetData(s, &ptr); + if (ptr == nullptr || *ptr != '\0') + { + NS_ERROR("unexpected result"); + return false; + } + + NS_CStringSetData(s, kAsciiData, UINT32_MAX); + len = NS_CStringGetData(s, &ptr); + if (ptr == nullptr || strcmp(ptr, kAsciiData) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + if (len != sizeof(kAsciiData)-1) + { + NS_ERROR("unexpected result"); + return false; + } + + clone = NS_CStringCloneData(s); + if (ptr == nullptr || strcmp(ptr, kAsciiData) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + free(clone); + + nsCStringContainer temp; + NS_CStringContainerInit(temp); + NS_CStringCopy(temp, s); + + len = NS_CStringGetData(temp, &ptr); + if (ptr == nullptr || strcmp(ptr, kAsciiData) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + if (len != sizeof(kAsciiData)-1) + { + NS_ERROR("unexpected result"); + return false; + } + + NS_CStringContainerFinish(temp); + + NS_CStringContainerFinish(s); + return true; + } + +static bool test_basic_2() + { + nsStringContainer s; + NS_StringContainerInit(s); + + const char16_t *ptr; + uint32_t len; + char16_t *clone; + + NS_StringGetData(s, &ptr); + if (ptr == nullptr || *ptr != '\0') + { + NS_ERROR("unexpected result"); + return false; + } + + NS_StringSetData(s, kUnicodeData, UINT32_MAX); + len = NS_StringGetData(s, &ptr); + if (len != sizeof(kUnicodeData)/2 - 1) + { + NS_ERROR("unexpected result"); + return false; + } + if (ptr == nullptr || memcmp(ptr, kUnicodeData, sizeof(kUnicodeData)) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + + clone = NS_StringCloneData(s); + if (ptr == nullptr || memcmp(ptr, kUnicodeData, sizeof(kUnicodeData)) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + free(clone); + + nsStringContainer temp; + NS_StringContainerInit(temp); + NS_StringCopy(temp, s); + + len = NS_StringGetData(temp, &ptr); + if (len != sizeof(kUnicodeData)/2 - 1) + { + NS_ERROR("unexpected result"); + return false; + } + if (ptr == nullptr || memcmp(ptr, kUnicodeData, sizeof(kUnicodeData)) != 0) + { + NS_ERROR("unexpected result"); + return false; + } + + NS_StringContainerFinish(temp); + + NS_StringContainerFinish(s); + + return true; + } + +static bool test_convert() + { + nsStringContainer s; + NS_StringContainerInit(s); + NS_StringSetData(s, kUnicodeData, sizeof(kUnicodeData)/2 - 1); + + nsCStringContainer temp; + NS_CStringContainerInit(temp); + + const char *data; + + NS_UTF16ToCString(s, NS_CSTRING_ENCODING_ASCII, temp); + NS_CStringGetData(temp, &data); + if (strcmp(data, kAsciiData) != 0) + return false; + + NS_UTF16ToCString(s, NS_CSTRING_ENCODING_UTF8, temp); + NS_CStringGetData(temp, &data); + if (strcmp(data, kAsciiData) != 0) + return false; + + NS_CStringContainerFinish(temp); + + NS_StringContainerFinish(s); + return true; + } + +static bool test_append() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + + NS_CStringSetData(s, "foo"); + NS_CStringAppendData(s, "bar"); + + NS_CStringContainerFinish(s); + return true; + } + +// Replace all occurrences of |matchVal| with |newVal| +static void ReplaceSubstring( nsACString& str, + const nsACString& matchVal, + const nsACString& newVal ) + { + const char* sp; + const char* mp; + const char* np; + uint32_t sl = NS_CStringGetData(str, &sp); + uint32_t ml = NS_CStringGetData(matchVal, &mp); + uint32_t nl = NS_CStringGetData(newVal, &np); + + for (const char* iter = sp; iter <= sp + sl - ml; ++iter) + { + if (memcmp(iter, mp, ml) == 0) + { + uint32_t offset = iter - sp; + + NS_CStringSetDataRange(str, offset, ml, np, nl); + + sl = NS_CStringGetData(str, &sp); + + iter = sp + offset + nl - 1; + } + } + } + +static bool test_replace_driver(const char *strVal, + const char *matchVal, + const char *newVal, + const char *finalVal) + { + nsCStringContainer a; + NS_CStringContainerInit(a); + NS_CStringSetData(a, strVal); + + nsCStringContainer b; + NS_CStringContainerInit(b); + NS_CStringSetData(b, matchVal); + + nsCStringContainer c; + NS_CStringContainerInit(c); + NS_CStringSetData(c, newVal); + + ReplaceSubstring(a, b, c); + + const char *data; + NS_CStringGetData(a, &data); + if (strcmp(data, finalVal) != 0) + return false; + + NS_CStringContainerFinish(c); + NS_CStringContainerFinish(b); + NS_CStringContainerFinish(a); + return true; + } + +static bool test_replace() + { + bool rv; + + rv = test_replace_driver("hello world, hello again!", + "hello", + "goodbye", + "goodbye world, goodbye again!"); + if (!rv) + return rv; + + rv = test_replace_driver("foofoofoofoo!", + "foo", + "bar", + "barbarbarbar!"); + if (!rv) + return rv; + + rv = test_replace_driver("foo bar systems", + "xyz", + "crazy", + "foo bar systems"); + if (!rv) + return rv; + + rv = test_replace_driver("oh", + "xyz", + "crazy", + "oh"); + if (!rv) + return rv; + + return true; + } + +static const char* kWhitespace="\f\t\r\n "; + +static void +CompressWhitespace(nsACString &str) + { + const char *p; + int32_t i, len = (int32_t) NS_CStringGetData(str, &p); + + // trim leading whitespace + + for (i=0; i<len; ++i) + { + if (!strchr(kWhitespace, (char) p[i])) + break; + } + + if (i>0) + { + NS_CStringCutData(str, 0, i); + len = (int32_t) NS_CStringGetData(str, &p); + } + + // trim trailing whitespace + + for (i=len-1; i>=0; --i) + { + if (!strchr(kWhitespace, (char) p[i])) + break; + } + + if (++i < len) + NS_CStringCutData(str, i, len - i); + } + +static bool test_compress_ws() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + NS_CStringSetData(s, " \thello world\r \n"); + CompressWhitespace(s); + const char *d; + NS_CStringGetData(s, &d); + bool rv = !strcmp(d, "hello world"); + if (!rv) + printf("=> \"%s\"\n", d); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_depend() + { + static const char kData[] = "hello world"; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, kData, sizeof(kData)-1, + NS_CSTRING_CONTAINER_INIT_DEPEND), + false); + + const char *sd; + NS_CStringGetData(s, &sd); + + bool rv = (sd == kData); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_depend_sub() + { + static const char kData[] = "hello world"; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, kData, sizeof(kData)-1, + NS_CSTRING_CONTAINER_INIT_DEPEND | + NS_CSTRING_CONTAINER_INIT_SUBSTRING), + false); + + bool terminated; + const char *sd; + uint32_t len = NS_CStringGetData(s, &sd, &terminated); + + bool rv = (sd == kData && len == sizeof(kData)-1 && !terminated); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_adopt() + { + static const char kData[] = "hello world"; + + char *data = (char *) nsMemory::Clone(kData, sizeof(kData)); + if (!data) + return false; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, data, UINT32_MAX, + NS_CSTRING_CONTAINER_INIT_ADOPT), + false); // leaks data on failure *shrug* + + const char *sd; + NS_CStringGetData(s, &sd); + + bool rv = (sd == data); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_adopt_sub() + { + static const char kData[] = "hello world"; + + char *data = (char *) nsMemory::Clone(kData, sizeof(kData)-1); + if (!data) + return false; + + nsCStringContainer s; + NS_ENSURE_SUCCESS( + NS_CStringContainerInit2(s, data, sizeof(kData)-1, + NS_CSTRING_CONTAINER_INIT_ADOPT | + NS_CSTRING_CONTAINER_INIT_SUBSTRING), + false); // leaks data on failure *shrug* + + bool terminated; + const char *sd; + uint32_t len = NS_CStringGetData(s, &sd, &terminated); + + bool rv = (sd == data && len == sizeof(kData)-1 && !terminated); + NS_CStringContainerFinish(s); + return rv; + } + +static bool test_mutation() + { + nsCStringContainer s; + NS_CStringContainerInit(s); + + const char kText[] = "Every good boy does fine."; + + char *buf; + uint32_t len = NS_CStringGetMutableData(s, sizeof(kText) - 1, &buf); + if (!buf || len != sizeof(kText) - 1) + return false; + memcpy(buf, kText, sizeof(kText)); + + const char *data; + NS_CStringGetData(s, &data); + if (strcmp(data, kText) != 0) + return false; + + uint32_t newLen = len + 1; + len = NS_CStringGetMutableData(s, newLen, &buf); + if (!buf || len != newLen) + return false; + + buf[len - 1] = '.'; + + NS_CStringGetData(s, &data); + if (strncmp(data, kText, len - 1) != 0 || data[len - 1] != '.') + return false; + + NS_CStringContainerFinish(s); + return true; + } + +static bool test_ascii() +{ + nsCString testCString; + testCString.AppendASCII(kAsciiData); + if (!testCString.EqualsLiteral(kAsciiData)) + return false; + + testCString.AssignASCII(kAsciiData); + if (!testCString.LowerCaseEqualsLiteral("hello world")) + return false; + + nsString testString; + testString.AppendASCII(kAsciiData); + if (!testString.EqualsLiteral(kAsciiData)) + return false; + + testString.AssignASCII(kAsciiData); + if (!testString.LowerCaseEqualsLiteral("hello world")) + return false; + + return true; +} + +static bool test_chars() +{ + nsCString testCString(kAsciiData); + if (testCString.First() != 'H') + return false; + if (testCString.Last() != 'd') + return false; + testCString.SetCharAt('u', 8); + if (!testCString.EqualsASCII("Hello Would")) + return false; + + nsString testString(kUnicodeData); + if (testString.First() != 'H') + return false; + if (testString.Last() != 'd') + return false; + testString.SetCharAt('u', 8); + if (!testString.EqualsASCII("Hello Would")) + return false; + + return true; +} + +static bool test_stripchars() +{ + nsCString test(kAsciiData); + test.StripChars("ld"); + if (!test.EqualsLiteral("Heo Wor")) + return false; + + test.Assign(kAsciiData); + test.StripWhitespace(); + if (!test.EqualsLiteral("HelloWorld")) + return false; + + return true; +} + +static bool test_trim() +{ + static const char kWS[] = "\n\t\r "; + static const char kTestString[] = " \n\tTesting...\n\r"; + + nsCString test1(kTestString); + nsCString test2(kTestString); + nsCString test3(kTestString); + + test1.Trim(kWS); + test2.Trim(kWS, true, false); + test3.Trim(kWS, false, true); + + if (!test1.EqualsLiteral("Testing...")) + return false; + + if (!test2.EqualsLiteral("Testing...\n\r")) + return false; + + if (!test3.EqualsLiteral(" \n\tTesting...")) + return false; + + return true; +} + +static bool test_find() +{ + nsString uni(kUnicodeData); + + static const char kHello[] = "Hello"; + static const char khello[] = "hello"; + static const char kBye[] = "Bye!"; + + int32_t found; + + found = uni.Find(kHello); + if (found != 0) + return false; + + found = uni.Find(khello, false); + if (found != -1) + return false; + + found = uni.Find(khello, true); + if (found != 0) + return false; + + found = uni.Find(kBye); + if (found != -1) + return false; + + found = uni.Find(NS_LITERAL_STRING("World")); + if (found != 6) + return false; + + found = uni.Find(uni); + if (found != 0) + return false; + + return true; +} + +static bool test_compressws() +{ + nsString check(NS_LITERAL_STRING(" \tTesting \n\t1\n 2 3\n ")); + CompressWhitespace(check); + return check.EqualsLiteral("Testing 1 2 3"); +} + +static bool test_comparisons() +{ + bool result; + + // nsString + + NS_NAMED_LITERAL_STRING(shortString1, "Foo"); + NS_NAMED_LITERAL_STRING(shortString2, "Bar"); + NS_NAMED_LITERAL_STRING(shortString3, "Bar"); + NS_NAMED_LITERAL_STRING(shortString4, "bar"); + NS_NAMED_LITERAL_STRING(longString, "FooBar"); + + // == + + result = (shortString1 == shortString2); + if (result) + return false; + + result = (shortString2 == shortString3); + if (!result) + return false; + + result = (shortString3 == shortString4); + if (result) + return false; + + result = (shortString1 == longString); + if (result) + return false; + + result = (longString == shortString1); + if (result) + return false; + + // != + + result = (shortString1 != shortString2); + if (!result) + return false; + + result = (shortString2 != shortString3); + if (result) + return false; + + result = (shortString3 != shortString4); + if (!result) + return false; + + result = (shortString1 != longString); + if (!result) + return false; + + result = (longString != shortString1); + if (!result) + return false; + + // < + + result = (shortString1 < shortString2); + if (result) + return false; + + result = (shortString2 < shortString1); + if (!result) + return false; + + result = (shortString1 < longString); + if (!result) + return false; + + result = (longString < shortString1); + if (result) + return false; + + result = (shortString2 < shortString3); + if (result) + return false; + + result = (shortString3 < shortString4); + if (!result) + return false; + + result = (shortString4 < shortString3); + if (result) + return false; + + // <= + + result = (shortString1 <= shortString2); + if (result) + return false; + + result = (shortString2 <= shortString1); + if (!result) + return false; + + result = (shortString1 <= longString); + if (!result) + return false; + + result = (longString <= shortString1); + if (result) + return false; + + result = (shortString2 <= shortString3); + if (!result) + return false; + + result = (shortString3 <= shortString4); + if (!result) + return false; + + result = (shortString4 <= shortString3); + if (result) + return false; + + // > + + result = (shortString1 > shortString2); + if (!result) + return false; + + result = (shortString2 > shortString1); + if (result) + return false; + + result = (shortString1 > longString); + if (result) + return false; + + result = (longString > shortString1); + if (!result) + return false; + + result = (shortString2 > shortString3); + if (result) + return false; + + result = (shortString3 > shortString4); + if (result) + return false; + + result = (shortString4 > shortString3); + if (!result) + return false; + + // >= + + result = (shortString1 >= shortString2); + if (!result) + return false; + + result = (shortString2 >= shortString1); + if (result) + return false; + + result = (shortString1 >= longString); + if (result) + return false; + + result = (longString >= shortString1); + if (!result) + return false; + + result = (shortString2 >= shortString3); + if (!result) + return false; + + result = (shortString3 >= shortString4); + if (result) + return false; + + result = (shortString4 >= shortString3); + if (!result) + return false; + + // nsCString + + NS_NAMED_LITERAL_CSTRING(shortCString1, "Foo"); + NS_NAMED_LITERAL_CSTRING(shortCString2, "Bar"); + NS_NAMED_LITERAL_CSTRING(shortCString3, "Bar"); + NS_NAMED_LITERAL_CSTRING(shortCString4, "bar"); + NS_NAMED_LITERAL_CSTRING(longCString, "FooBar"); + + // == + + result = (shortCString1 == shortCString2); + if (result) + return false; + + result = (shortCString2 == shortCString3); + if (!result) + return false; + + result = (shortCString3 == shortCString4); + if (result) + return false; + + result = (shortCString1 == longCString); + if (result) + return false; + + result = (longCString == shortCString1); + if (result) + return false; + + // != + + result = (shortCString1 != shortCString2); + if (!result) + return false; + + result = (shortCString2 != shortCString3); + if (result) + return false; + + result = (shortCString3 != shortCString4); + if (!result) + return false; + + result = (shortCString1 != longCString); + if (!result) + return false; + + result = (longCString != shortCString1); + if (!result) + return false; + + // < + + result = (shortCString1 < shortCString2); + if (result) + return false; + + result = (shortCString2 < shortCString1); + if (!result) + return false; + + result = (shortCString1 < longCString); + if (!result) + return false; + + result = (longCString < shortCString1); + if (result) + return false; + + result = (shortCString2 < shortCString3); + if (result) + return false; + + result = (shortCString3 < shortCString4); + if (!result) + return false; + + result = (shortCString4 < shortCString3); + if (result) + return false; + + // <= + + result = (shortCString1 <= shortCString2); + if (result) + return false; + + result = (shortCString2 <= shortCString1); + if (!result) + return false; + + result = (shortCString1 <= longCString); + if (!result) + return false; + + result = (longCString <= shortCString1); + if (result) + return false; + + result = (shortCString2 <= shortCString3); + if (!result) + return false; + + result = (shortCString3 <= shortCString4); + if (!result) + return false; + + result = (shortCString4 <= shortCString3); + if (result) + return false; + + // > + + result = (shortCString1 > shortCString2); + if (!result) + return false; + + result = (shortCString2 > shortCString1); + if (result) + return false; + + result = (shortCString1 > longCString); + if (result) + return false; + + result = (longCString > shortCString1); + if (!result) + return false; + + result = (shortCString2 > shortCString3); + if (result) + return false; + + result = (shortCString3 > shortCString4); + if (result) + return false; + + result = (shortCString4 > shortCString3); + if (!result) + return false; + + // >= + + result = (shortCString1 >= shortCString2); + if (!result) + return false; + + result = (shortCString2 >= shortCString1); + if (result) + return false; + + result = (shortCString1 >= longCString); + if (result) + return false; + + result = (longCString >= shortCString1); + if (!result) + return false; + + result = (shortCString2 >= shortCString3); + if (!result) + return false; + + result = (shortCString3 >= shortCString4); + if (result) + return false; + + result = (shortCString4 >= shortCString3); + if (!result) + return false; + + return true; +} + +static bool test_parse_string_helper(const char* str, char separator, int len, + const char* s1, const char* s2) +{ + nsCString data(str); + nsTArray<nsCString> results; + if (!ParseString(data, separator, results)) + return false; + if (int(results.Length()) != len) + return false; + const char* strings[] = { s1, s2 }; + for (int i = 0; i < len; ++i) { + if (!results[i].Equals(strings[i])) + return false; + } + return true; +} + +static bool test_parse_string_helper0(const char* str, char separator) +{ + return test_parse_string_helper(str, separator, 0, nullptr, nullptr); +} + +static bool test_parse_string_helper1(const char* str, char separator, const char* s1) +{ + return test_parse_string_helper(str, separator, 1, s1, nullptr); +} + +static bool test_parse_string_helper2(const char* str, char separator, const char* s1, const char* s2) +{ + return test_parse_string_helper(str, separator, 2, s1, s2); +} + +static bool test_parse_string() +{ + return 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"); +} + +//---- + +typedef bool (*TestFunc)(); + +static const struct Test + { + const char* name; + TestFunc func; + } +tests[] = + { + { "test_basic_1", test_basic_1 }, + { "test_basic_2", test_basic_2 }, + { "test_convert", test_convert }, + { "test_append", test_append }, + { "test_replace", test_replace }, + { "test_compress_ws", test_compress_ws }, + { "test_depend", test_depend }, + { "test_depend_sub", test_depend_sub }, + { "test_adopt", test_adopt }, + { "test_adopt_sub", test_adopt_sub }, + { "test_mutation", test_mutation }, + { "test_ascii", test_ascii }, + { "test_chars", test_chars }, + { "test_stripchars", test_stripchars }, + { "test_trim", test_trim }, + { "test_find", test_find }, + { "test_compressws", test_compressws }, + { "test_comparisons", test_comparisons }, + { "test_parse_string", test_parse_string }, + { nullptr, nullptr } + }; + +//---- + +int main(int argc, char **argv) + { + int count = 1; + if (argc > 1) + count = atoi(argv[1]); + + while (count--) + { + for (const Test* t = tests; t->name != nullptr; ++t) + { + printf("%25s : %s\n", t->name, t->func() ? "SUCCESS" : "FAILURE"); + } + } + + return 0; + } diff --git a/xpcom/tests/external/moz.build b/xpcom/tests/external/moz.build new file mode 100644 index 000000000..d64560b58 --- /dev/null +++ b/xpcom/tests/external/moz.build @@ -0,0 +1,9 @@ +# -*- 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/. + +GeckoSimplePrograms([ + 'TestMinStringAPI', +]) 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' diff --git a/xpcom/tests/moz.build b/xpcom/tests/moz.build new file mode 100644 index 000000000..b5fc138e7 --- /dev/null +++ b/xpcom/tests/moz.build @@ -0,0 +1,51 @@ +# -*- 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/. + +TEST_DIRS += [ + 'external', + 'component', + 'bug656331_component', + 'component_no_aslr', + 'gtest', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + TEST_DIRS += ['windows'] + +EXPORTS.testing += [ + 'TestHarness.h', +] + +SimplePrograms([ + 'TestArguments', + 'TestBlockingProcess', + 'TestPRIntN', + 'TestQuickReturn', + 'TestUnicodeArguments', +]) + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] + +if CONFIG['COMPILE_ENVIRONMENT']: + TEST_HARNESS_FILES.xpcshell.xpcom.tests.unit += [ + '!/dist/bin/components/xpcomtest.xpt', + ] + +XPIDL_MODULE = 'xpcomtest' +XPIDL_SOURCES += [ + 'NotXPCOMTest.idl', +] + +# Don't add our test-only .xpt files to the normal manifests +XPIDL_NO_MANIFEST = True + +LOCAL_INCLUDES += [ + '../ds', +] + +RESOURCE_FILES += [ + 'test.properties', +] diff --git a/xpcom/tests/resources.h b/xpcom/tests/resources.h new file mode 100644 index 000000000..4deea759f --- /dev/null +++ b/xpcom/tests/resources.h @@ -0,0 +1,19 @@ +/* -*- 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/. */ +#ifndef resources_h___ +#define resources_h___ + +#define TIMER_1SECOND 40000 +#define TIMER_5SECOND 40001 +#define TIMER_10SECOND 40002 + +#define TIMER_1REPEAT 40003 +#define TIMER_5REPEAT 40004 +#define TIMER_10REPEAT 40005 + +#define TIMER_CANCEL 40006 +#define TIMER_EXIT 40010 + +#endif /* resources_h___ */ diff --git a/xpcom/tests/test.properties b/xpcom/tests/test.properties new file mode 100644 index 000000000..19cae9702 --- /dev/null +++ b/xpcom/tests/test.properties @@ -0,0 +1,14 @@ +# 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/. +1=1 + 2=2 +3 =3 + 4 =4 +5=5 +6= 6 +7=7 +8= 8 +# this is a comment +9=this is the first part of a continued line \ + and here is the 2nd part diff --git a/xpcom/tests/unit/bug725015.manifest b/xpcom/tests/unit/bug725015.manifest new file mode 100644 index 000000000..2f432f0b2 --- /dev/null +++ b/xpcom/tests/unit/bug725015.manifest @@ -0,0 +1,3 @@ +category bug725015-test-category bug725015-category-entry @bug725015.test.contract
+component {05070380-6e6e-42ba-aaa5-3289fc55ca5a} dummyfile.js
+contract @bug725015.test.contract {05070380-6e6e-42ba-aaa5-3289fc55ca5a}
diff --git a/xpcom/tests/unit/compmgr_warnings.manifest b/xpcom/tests/unit/compmgr_warnings.manifest new file mode 100644 index 000000000..6b60990db --- /dev/null +++ b/xpcom/tests/unit/compmgr_warnings.manifest @@ -0,0 +1,9 @@ +# The following line is malformed (mismatched braces) +component {94b346d7-0cde-4e6e-b819-95d6f200bbf6 MyComponent.js + +component 94b346d7-0cde-4e6e-b819-95d6f200bbf7 MyComponent.js +# The following line re-registers an existing CID +component {94b346d7-0cde-4e6e-b819-95d6f200bbf7} MyOtherComponent.js + +# The following line maps a contractID to a non-existent CID +contract @testing/foo {0c07730f-f875-436b-8deb-90c4251920ec} diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist new file mode 100644 index 000000000..8388fa2a5 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>SmallApp</string> + <key>CFBundleIdentifier</key> + <string>com.yourcompany.SmallApp</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>SmallApp</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> +</dict> +</plist> diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp Binary files differnew file mode 100755 index 000000000..c821003d3 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo new file mode 100644 index 000000000..bd04210fb --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL????
\ No newline at end of file diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..5e45963c3 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib new file mode 100644 index 000000000..59f8803c5 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib @@ -0,0 +1,343 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.02"> + <data> + <int key="IBDocument.SystemTarget">0</int> + <string key="IBDocument.SystemVersion">9E17</string> + <string key="IBDocument.InterfaceBuilderVersion">644</string> + <string key="IBDocument.AppKitVersion">949.33</string> + <string key="IBDocument.HIToolboxVersion">352.00</string> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="29"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="1048"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="1021"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSCustomObject" id="1014"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1050"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSMenu" id="649796088"> + <string key="NSTitle">AMainMenu</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="694149608"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">NewApplication</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <object class="NSCustomResource" key="NSOnImage" id="35465992"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuCheckmark</string> + </object> + <object class="NSCustomResource" key="NSMixedImage" id="591987212"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuMixedState</string> + </object> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="110575045"> + <string key="NSTitle">NewApplication</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="632727374"> + <reference key="NSMenu" ref="110575045"/> + <string key="NSTitle">Quit NewApplication</string> + <string key="NSKeyEquiv">q</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + </object> + <string key="NSName">_NSAppleMenu</string> + </object> + </object> + <object class="NSMenuItem" id="379814623"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">File</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="952259628"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Edit</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="626404410"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Format</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="586577488"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">View</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="713487014"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Window</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + <object class="NSMenuItem" id="391199113"> + <reference key="NSMenu" ref="649796088"/> + <string key="NSTitle">Help</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="35465992"/> + <reference key="NSMixedImage" ref="591987212"/> + </object> + </object> + <string key="NSName">_NSMainMenu</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">terminate:</string> + <reference key="source" ref="1014"/> + <reference key="destination" ref="632727374"/> + </object> + <int key="connectionID">369</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <object class="NSArray" key="object" id="1049"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="children" ref="1048"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1021"/> + <reference key="parent" ref="1049"/> + <string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1014"/> + <reference key="parent" ref="1049"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1050"/> + <reference key="parent" ref="1049"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">29</int> + <reference key="object" ref="649796088"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="713487014"/> + <reference ref="694149608"/> + <reference ref="391199113"/> + <reference ref="952259628"/> + <reference ref="379814623"/> + <reference ref="586577488"/> + <reference ref="626404410"/> + </object> + <reference key="parent" ref="1049"/> + <string key="objectName">MainMenu</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">19</int> + <reference key="object" ref="713487014"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">56</int> + <reference key="object" ref="694149608"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="110575045"/> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">103</int> + <reference key="object" ref="391199113"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + <string key="objectName">1</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">217</int> + <reference key="object" ref="952259628"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">83</int> + <reference key="object" ref="379814623"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">57</int> + <reference key="object" ref="110575045"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="632727374"/> + </object> + <reference key="parent" ref="694149608"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">136</int> + <reference key="object" ref="632727374"/> + <reference key="parent" ref="110575045"/> + <string key="objectName">1111</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">295</int> + <reference key="object" ref="586577488"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">299</int> + <reference key="object" ref="626404410"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="parent" ref="649796088"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMutableArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-1.IBPluginDependency</string> + <string>-2.IBPluginDependency</string> + <string>-3.IBPluginDependency</string> + <string>103.IBPluginDependency</string> + <string>103.ImportedFromIB2</string> + <string>136.IBPluginDependency</string> + <string>136.ImportedFromIB2</string> + <string>19.IBPluginDependency</string> + <string>19.ImportedFromIB2</string> + <string>217.IBPluginDependency</string> + <string>217.ImportedFromIB2</string> + <string>29.IBEditorWindowLastContentRect</string> + <string>29.IBPluginDependency</string> + <string>29.ImportedFromIB2</string> + <string>29.WindowOrigin</string> + <string>29.editorWindowContentRectSynchronizationRect</string> + <string>295.IBPluginDependency</string> + <string>299.IBPluginDependency</string> + <string>56.IBPluginDependency</string> + <string>56.ImportedFromIB2</string> + <string>57.IBEditorWindowLastContentRect</string> + <string>57.IBPluginDependency</string> + <string>57.ImportedFromIB2</string> + <string>57.editorWindowContentRectSynchronizationRect</string> + <string>83.IBPluginDependency</string> + <string>83.ImportedFromIB2</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilderKit</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <integer value="1" id="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{0, 975}, {478, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{74, 862}</string> + <string>{{6, 978}, {478, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{12, 952}, {218, 23}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + <string>{{23, 794}, {245, 183}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <reference ref="9"/> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">374</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"/> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.LastKnownRelativeProjectPath">../SmallApp.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + </data> +</archive> diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 000000000..bb27d4a5d --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/xpcom/tests/unit/data/bug121341-2.properties b/xpcom/tests/unit/data/bug121341-2.properties new file mode 100644 index 000000000..f7885e4fc --- /dev/null +++ b/xpcom/tests/unit/data/bug121341-2.properties @@ -0,0 +1,9 @@ +# this file contains invalid UTF-8 sequence +# no property should be loaded + +1 = test + +# property with invalid UTF-8 sequence (0xa0) +2 = ab + +3 = test2 diff --git a/xpcom/tests/unit/data/bug121341.properties b/xpcom/tests/unit/data/bug121341.properties new file mode 100644 index 000000000..b45fc9698 --- /dev/null +++ b/xpcom/tests/unit/data/bug121341.properties @@ -0,0 +1,68 @@ +# simple check +1=abc +# test whitespace trimming in key and value + 2 = xy +# test parsing of escaped values +3 = \u1234\t\r\n\uAB\ +\u1\n +# test multiline properties +4 = this is \ +multiline property +5 = this is \ + another multiline property +# property with DOS EOL
+6 = test\u0036
+# test multiline property with with DOS EOL +7 = yet another multi\
+ line propery
+# trimming should not trim escaped whitespaces +8 = \ttest5\u0020 +# another variant of #8 +9 = \ test6\t +# test UTF-8 encoded property/value +10aሴb = c췯d +# next property should test unicode escaping at the boundary of parsing buffer +# buffer size is expected to be 4096 so add comments to get to this offset +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +############################################################################### +11 = \uABCD diff --git a/xpcom/tests/unit/data/child_process_directive_service.js b/xpcom/tests/unit/data/child_process_directive_service.js new file mode 100644 index 000000000..9bc1c3206 --- /dev/null +++ b/xpcom/tests/unit/data/child_process_directive_service.js @@ -0,0 +1,21 @@ +/* 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/. */ +Components.utils.import("resource:///modules/XPCOMUtils.jsm"); + +function TestProcessDirective() {} +TestProcessDirective.prototype = { + + /* Boilerplate */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupportsString]), + contractID: "@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1", + classID: Components.ID("{4bd1ba60-45c4-11e4-916c-0800200c9a66}"), + + type: Components.interfaces.nsISupportsString.TYPE_STRING, + data: "child process", + toString: function() { + return this.data; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestProcessDirective]); diff --git a/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini new file mode 100644 index 000000000..46b134b19 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/xpcom/tests/unit/data/iniparser01-utf8BOM.ini b/xpcom/tests/unit/data/iniparser01-utf8BOM.ini new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01-utf8BOM.ini @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/xpcom/tests/unit/data/iniparser01.ini b/xpcom/tests/unit/data/iniparser01.ini new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01.ini diff --git a/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..49cc8ef0e --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser02-utf8BOM.ini b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini new file mode 100644 index 000000000..e02abfc9b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini @@ -0,0 +1 @@ + diff --git a/xpcom/tests/unit/data/iniparser02.ini b/xpcom/tests/unit/data/iniparser02.ini new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02.ini @@ -0,0 +1 @@ +
diff --git a/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..05255100a --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser03-utf8BOM.ini b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini new file mode 100644 index 000000000..b76e44e19 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini @@ -0,0 +1 @@ +[] diff --git a/xpcom/tests/unit/data/iniparser03.ini b/xpcom/tests/unit/data/iniparser03.ini new file mode 100644 index 000000000..60b074253 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03.ini @@ -0,0 +1 @@ +[]
diff --git a/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..e95d97113 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser04-utf8BOM.ini b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini new file mode 100644 index 000000000..47ef32c0a --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini @@ -0,0 +1 @@ +[section1] diff --git a/xpcom/tests/unit/data/iniparser04.ini b/xpcom/tests/unit/data/iniparser04.ini new file mode 100644 index 000000000..23a50d155 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04.ini @@ -0,0 +1 @@ +[section1]
diff --git a/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..a49491816 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser05-utf8BOM.ini b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini new file mode 100644 index 000000000..eb33b5ccf --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini @@ -0,0 +1 @@ +[section1]junk diff --git a/xpcom/tests/unit/data/iniparser05.ini b/xpcom/tests/unit/data/iniparser05.ini new file mode 100644 index 000000000..ade137337 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05.ini @@ -0,0 +1 @@ +[section1]junk
diff --git a/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..e9023ac7c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser06-utf8BOM.ini b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini new file mode 100644 index 000000000..073d841cf --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] + diff --git a/xpcom/tests/unit/data/iniparser06.ini b/xpcom/tests/unit/data/iniparser06.ini new file mode 100644 index 000000000..c24821e6e --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06.ini @@ -0,0 +1,2 @@ +[section1]
+
diff --git a/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..d1c167e6e --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser07-utf8BOM.ini b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini new file mode 100644 index 000000000..38176d944 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1 diff --git a/xpcom/tests/unit/data/iniparser07.ini b/xpcom/tests/unit/data/iniparser07.ini new file mode 100644 index 000000000..49816873b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07.ini @@ -0,0 +1,2 @@ +[section1]
+name1
diff --git a/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..e450566a0 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser08-utf8BOM.ini b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini new file mode 100644 index 000000000..5fa7d2495 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1= diff --git a/xpcom/tests/unit/data/iniparser08.ini b/xpcom/tests/unit/data/iniparser08.ini new file mode 100644 index 000000000..cfa15c9ff --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08.ini @@ -0,0 +1,2 @@ +[section1]
+name1=
diff --git a/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..ef1da39e2 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser09-utf8BOM.ini b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini new file mode 100644 index 000000000..e3edce4d4 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser09.ini b/xpcom/tests/unit/data/iniparser09.ini new file mode 100644 index 000000000..1c87762ba --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09.ini @@ -0,0 +1,2 @@ +[section1]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..e5e70b661 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser10-utf8BOM.ini b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini new file mode 100644 index 000000000..bda15fcc7 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini @@ -0,0 +1,3 @@ + +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser10.ini b/xpcom/tests/unit/data/iniparser10.ini new file mode 100644 index 000000000..037fd7930 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10.ini @@ -0,0 +1,3 @@ +
+[section1]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..932d4004b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser11-utf8BOM.ini b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini new file mode 100644 index 000000000..78caafaba --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini @@ -0,0 +1,3 @@ +# comment +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser11.ini b/xpcom/tests/unit/data/iniparser11.ini new file mode 100644 index 000000000..f8d573a28 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11.ini @@ -0,0 +1,3 @@ +# comment
+[section1]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..8a2912722 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser12-utf8BOM.ini b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini new file mode 100644 index 000000000..09ca62779 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini @@ -0,0 +1,3 @@ +[section1] +# [sectionBAD] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser12.ini b/xpcom/tests/unit/data/iniparser12.ini new file mode 100644 index 000000000..2727940c0 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12.ini @@ -0,0 +1,3 @@ +[section1]
+# [sectionBAD]
+name1=value1
diff --git a/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..ebd9a51d3 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser13-utf8BOM.ini b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini new file mode 100644 index 000000000..8c9499b66 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini @@ -0,0 +1,3 @@ +[section1] +name1=value1 +# nameBAD=valueBAD diff --git a/xpcom/tests/unit/data/iniparser13.ini b/xpcom/tests/unit/data/iniparser13.ini new file mode 100644 index 000000000..21d40b140 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13.ini @@ -0,0 +1,3 @@ +[section1]
+name1=value1
+# nameBAD=valueBAD
diff --git a/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..bbc3413aa --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser14-utf8BOM.ini b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini new file mode 100644 index 000000000..d109052c8 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +name2=value2 +[section2] +name1=value1 +name2=foopy diff --git a/xpcom/tests/unit/data/iniparser14.ini b/xpcom/tests/unit/data/iniparser14.ini new file mode 100644 index 000000000..744af4cb6 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14.ini @@ -0,0 +1,6 @@ +[section1]
+name1=value1
+name2=value2
+[section2]
+name1=value1
+name2=foopy
diff --git a/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..e60525dec --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser15-utf8BOM.ini b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini new file mode 100644 index 000000000..172803f90 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +[section2] +name1=foopy +[section1] +name1=newValue1 diff --git a/xpcom/tests/unit/data/iniparser15.ini b/xpcom/tests/unit/data/iniparser15.ini new file mode 100644 index 000000000..608a27d8f --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15.ini @@ -0,0 +1,6 @@ +[section1]
+name1=value1
+[section2]
+name1=foopy
+[section1]
+name1=newValue1
diff --git a/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini Binary files differnew file mode 100644 index 000000000..142b17590 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser16-utf8BOM.ini b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini new file mode 100644 index 000000000..bba1018da --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini @@ -0,0 +1,13 @@ +#Ώṍҳϖ·̐˄ȡǨŅ©& +[☺♫] +#ѼΏṍҳϖ +♫=☻ +#·̐˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏṍҳ +#ϖ·̐˄ȡǨŅ©& +[☼] +♣=♠ +♦=♥ +#‽ἧᵿΏṍҳ +#·̐˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/iniparser16.ini b/xpcom/tests/unit/data/iniparser16.ini new file mode 100644 index 000000000..b94607d15 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16.ini @@ -0,0 +1,13 @@ +#Ώṍҳϖ·̐˄ȡǨŅ©& +[☺♫] +#ѼΏṍҳϖ +♫=☻ +#·̐˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏṍҳ +#ϖ·̐˄ȡǨŅ©& +[☼] +♣=♠ +♦=♥ +#‽ἧᵿΏṍҳ +#·̐˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/main_process_directive_service.js b/xpcom/tests/unit/data/main_process_directive_service.js new file mode 100644 index 000000000..f4eed11c9 --- /dev/null +++ b/xpcom/tests/unit/data/main_process_directive_service.js @@ -0,0 +1,21 @@ +/* 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/. */ +Components.utils.import("resource:///modules/XPCOMUtils.jsm"); + +function TestProcessDirective() {} +TestProcessDirective.prototype = { + + /* Boilerplate */ + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupportsString]), + contractID: "@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1", + classID: Components.ID("{9b6f4160-45be-11e4-916c-0800200c9a66}"), + + type: Components.interfaces.nsISupportsString.TYPE_STRING, + data: "main process", + toString: function() { + return this.data; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestProcessDirective]); diff --git a/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict b/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict diff --git a/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo b/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo new file mode 100644 index 000000000..b0bc8e076 --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo @@ -0,0 +1 @@ +????????
\ No newline at end of file diff --git a/xpcom/tests/unit/data/presentation.key/index.apxl.gz b/xpcom/tests/unit/data/presentation.key/index.apxl.gz Binary files differnew file mode 100644 index 000000000..26178d809 --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/index.apxl.gz diff --git a/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff Binary files differnew file mode 100644 index 000000000..8b49316b4 --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff diff --git a/xpcom/tests/unit/data/process_directive.manifest b/xpcom/tests/unit/data/process_directive.manifest new file mode 100644 index 000000000..9cbf7f241 --- /dev/null +++ b/xpcom/tests/unit/data/process_directive.manifest @@ -0,0 +1,5 @@ +component {9b6f4160-45be-11e4-916c-0800200c9a66} main_process_directive_service.js process=main +contract @mozilla.org/xpcom/tests/MainProcessDirectiveTest;1 {9b6f4160-45be-11e4-916c-0800200c9a66} process=main + +component {4bd1ba60-45c4-11e4-916c-0800200c9a66} child_process_directive_service.js process=content +contract @mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1 {4bd1ba60-45c4-11e4-916c-0800200c9a66} process=content diff --git a/xpcom/tests/unit/head_xpcom.js b/xpcom/tests/unit/head_xpcom.js new file mode 100644 index 000000000..eacd5b4e6 --- /dev/null +++ b/xpcom/tests/unit/head_xpcom.js @@ -0,0 +1,21 @@ +function get_test_program(prog) +{ + var progPath = do_get_cwd(); + progPath.append(prog); + progPath.leafName = progPath.leafName + mozinfo.bin_suffix; + return progPath; +} + +function set_process_running_environment() +{ + var envSvc = Components.classes["@mozilla.org/process/environment;1"]. + getService(Components.interfaces.nsIEnvironment); + var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties); + var greBinDir = dirSvc.get("GreBinD", Components.interfaces.nsIFile); + envSvc.set("DYLD_LIBRARY_PATH", greBinDir.path); + // For Linux + envSvc.set("LD_LIBRARY_PATH", greBinDir.path); + //XXX: handle windows +} + diff --git a/xpcom/tests/unit/test_bug121341.js b/xpcom/tests/unit/test_bug121341.js new file mode 100644 index 000000000..3aa04186e --- /dev/null +++ b/xpcom/tests/unit/test_bug121341.js @@ -0,0 +1,71 @@ +var Ci = Components.interfaces; +var Cu = Components.utils; +Cu.import("resource://gre/modules/NetUtil.jsm"); + +function run_test() { + var ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + + var dataFile = do_get_file("data/bug121341.properties"); + var channel = NetUtil.newChannel({ + uri: ios.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true + }); + var inp = channel.open2(); + + var properties = Components.classes["@mozilla.org/persistent-properties;1"]. + createInstance(Components.interfaces.nsIPersistentProperties); + properties.load(inp); + + var value; + + value = properties.getStringProperty("1"); + do_check_eq(value, "abc"); + + value = properties.getStringProperty("2"); + do_check_eq(value, "xy"); + + value = properties.getStringProperty("3"); + do_check_eq(value, "\u1234\t\r\n\u00AB\u0001\n"); + + value = properties.getStringProperty("4"); + do_check_eq(value, "this is multiline property"); + + value = properties.getStringProperty("5"); + do_check_eq(value, "this is another multiline property"); + + value = properties.getStringProperty("6"); + do_check_eq(value, "test\u0036"); + + value = properties.getStringProperty("7"); + do_check_eq(value, "yet another multiline propery"); + + value = properties.getStringProperty("8"); + do_check_eq(value, "\ttest5\u0020"); + + value = properties.getStringProperty("9"); + do_check_eq(value, " test6\t"); + + value = properties.getStringProperty("10a\u1234b"); + do_check_eq(value, "c\uCDEFd"); + + value = properties.getStringProperty("11"); + do_check_eq(value, "\uABCD"); + + dataFile = do_get_file("data/bug121341-2.properties"); + + var channel = NetUtil.newChannel({ + uri: ios.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true + }); + inp = channel.open2(); + + var properties2 = Components.classes["@mozilla.org/persistent-properties;1"]. + createInstance(Components.interfaces.nsIPersistentProperties); + try { + properties2.load(inp); + do_throw("load() didn't fail"); + } + catch (e) { + } +} diff --git a/xpcom/tests/unit/test_bug325418.js b/xpcom/tests/unit/test_bug325418.js new file mode 100644 index 000000000..b18866d7e --- /dev/null +++ b/xpcom/tests/unit/test_bug325418.js @@ -0,0 +1,63 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +// 5 seconds. +const kExpectedDelay1 = 5; +// 1 second. +const kExpectedDelay2 = 1; + +var gStartTime1; +var gStartTime2; +var timer; + +var observer1 = { + observe: function observeTC1(subject, topic, data) { + if (topic == "timer-callback") { + // Stop timer, so it doesn't repeat (if test runs slowly). + timer.cancel(); + + // Actual delay may not be exact, so convert to seconds and round. + do_check_eq(Math.round((Date.now() - gStartTime1) / 1000), + kExpectedDelay1); + + timer = null; + + do_print("1st timer triggered (before being cancelled). Should not have happened!"); + do_check_true(false); + } + } +}; + +var observer2 = { + observe: function observeTC2(subject, topic, data) { + if (topic == "timer-callback") { + // Stop timer, so it doesn't repeat (if test runs slowly). + timer.cancel(); + + // Actual delay may not be exact, so convert to seconds and round. + do_check_eq(Math.round((Date.now() - gStartTime2) / 1000), + kExpectedDelay2); + + timer = null; + + do_test_finished(); + } + } +}; + +function run_test() { + do_test_pending(); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + // Initialize the timer (with some delay), then cancel it. + gStartTime1 = Date.now(); + timer.init(observer1, kExpectedDelay1 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP); + timer.cancel(); + + // Re-initialize the timer (with a different delay). + gStartTime2 = Date.now(); + timer.init(observer2, kExpectedDelay2 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP); +} diff --git a/xpcom/tests/unit/test_bug332389.js b/xpcom/tests/unit/test_bug332389.js new file mode 100644 index 000000000..91f8943e8 --- /dev/null +++ b/xpcom/tests/unit/test_bug332389.js @@ -0,0 +1,19 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + var f = + Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties).get("CurProcD", Ci.nsIFile); + + var terminated = false; + for (var i = 0; i < 100; i++) { + if (f == null) { + terminated = true; + break; + } + f = f.parent; + } + + do_check_true(terminated); +} diff --git a/xpcom/tests/unit/test_bug333505.js b/xpcom/tests/unit/test_bug333505.js new file mode 100644 index 000000000..856468605 --- /dev/null +++ b/xpcom/tests/unit/test_bug333505.js @@ -0,0 +1,10 @@ +function run_test() +{ + var dirEntries = do_get_cwd().directoryEntries; + + while (dirEntries.hasMoreElements()) + dirEntries.getNext(); + + // We ensure there is no crash + dirEntries.hasMoreElements(); +} diff --git a/xpcom/tests/unit/test_bug364285-1.js b/xpcom/tests/unit/test_bug364285-1.js new file mode 100644 index 000000000..f8bd6ee15 --- /dev/null +++ b/xpcom/tests/unit/test_bug364285-1.js @@ -0,0 +1,51 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; + +var nameArray = [ + "ascii", // ASCII + "fran\u00E7ais", // Latin-1 + "\u0420\u0443\u0441\u0441\u043A\u0438\u0439", // Cyrillic + "\u65E5\u672C\u8A9E", // Japanese + "\u4E2D\u6587", // Chinese + "\uD55C\uAD6D\uC5B4", // Korean + "\uD801\uDC0F\uD801\uDC2D\uD801\uDC3B\uD801\uDC2B" // Deseret +]; + +function getTempDir() +{ + var dirService = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties); + return dirService.get("TmpD", Ci.nsILocalFile); +} + +function create_file(fileName) +{ + var outFile = getTempDir(); + outFile.append(fileName); + outFile.createUnique(outFile.NORMAL_FILE_TYPE, 0o600); + + var stream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + stream.init(outFile, 0x02 | 0x08 | 0x20, 0o600, 0); + stream.write("foo", 3); + stream.close(); + + do_check_eq(outFile.leafName.substr(0, fileName.length), fileName); + + return outFile; +} + +function test_create(fileName) +{ + var file1 = create_file(fileName); + var file2 = create_file(fileName); + file1.remove(false); + file2.remove(false); +} + +function run_test() +{ + for (var i = 0; i < nameArray.length; ++i) { + test_create(nameArray[i]); + } +} diff --git a/xpcom/tests/unit/test_bug374754.js b/xpcom/tests/unit/test_bug374754.js new file mode 100644 index 000000000..9b0ccf26e --- /dev/null +++ b/xpcom/tests/unit/test_bug374754.js @@ -0,0 +1,59 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +var addedTopic = "xpcom-category-entry-added"; +var removedTopic = "xpcom-category-entry-removed"; +var testCategory = "bug-test-category"; +var testEntry = "@mozilla.org/bug-test-entry;1"; + +var testValue= "check validity"; +var result = ""; +var expected = "add remove add remove "; +var timer; + +var observer = { + QueryInterface: function(iid) { + if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + observe: function(subject, topic, data) { + if (topic == "timer-callback") { + do_check_eq(result, expected); + + var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + observerService.removeObserver(this, addedTopic); + observerService.removeObserver(this, removedTopic); + + do_test_finished(); + + timer = null; + } + + if (subject.QueryInterface(Ci.nsISupportsCString).data != testEntry || data != testCategory) + return; + + if (topic == addedTopic) + result += "add "; + else if (topic == removedTopic) + result += "remove "; + } +}; + +function run_test() { + do_test_pending(); + + var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + observerService.addObserver(observer, addedTopic, false); + observerService.addObserver(observer, removedTopic, false); + + var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); + categoryManager.addCategoryEntry(testCategory, testEntry, testValue, false, true); + categoryManager.addCategoryEntry(testCategory, testEntry, testValue, false, true); + categoryManager.deleteCategoryEntry(testCategory, testEntry, false); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(observer, 0, timer.TYPE_ONE_SHOT); +} diff --git a/xpcom/tests/unit/test_bug476919.js b/xpcom/tests/unit/test_bug476919.js new file mode 100644 index 000000000..21cd9253e --- /dev/null +++ b/xpcom/tests/unit/test_bug476919.js @@ -0,0 +1,27 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + // skip this test on Windows + if (mozinfo.os != "win") { + var testDir = __LOCATION__.parent; + // create a test file, then symlink it, then check that we think it's a symlink + var targetFile = testDir.clone(); + targetFile.append("target.txt"); + if (!targetFile.exists()) + targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + var link = testDir.clone(); + link.append("link"); + if (link.exists()) + link.remove(false); + + var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + ln.initWithPath("/bin/ln"); + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(ln); + var args = ["-s", targetFile.path, link.path]; + process.run(true, args, args.length); + do_check_true(link.isSymlink()); + } +} diff --git a/xpcom/tests/unit/test_bug478086.js b/xpcom/tests/unit/test_bug478086.js new file mode 100644 index 000000000..bd6ea0d08 --- /dev/null +++ b/xpcom/tests/unit/test_bug478086.js @@ -0,0 +1,24 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + var nsILocalFile = Components.interfaces.nsILocalFile; + var root = Components.classes["@mozilla.org/file/local;1"]. + createInstance(nsILocalFile); + + // copied from http://mxr.mozilla.org/mozilla-central/source/image/test/unit/test_imgtools.js#135 + // nsIXULRuntime.OS doesn't seem to be available in xpcshell, so we'll use + // this as a kludgy way to figure out if we're running on Windows. + if (mozinfo.os == "win") { + root.initWithPath("\\\\."); + } else { + return; // XXX disabled, since this causes intermittent failures on Mac (bug 481369). + root.initWithPath("/"); + } + var drives = root.directoryEntries; + do_check_true(drives.hasMoreElements()); + while (drives.hasMoreElements()) { + var newPath = drives.getNext().QueryInterface(nsILocalFile).path; + do_check_eq(newPath.indexOf("\0"), -1); + } +} diff --git a/xpcom/tests/unit/test_bug656331.js b/xpcom/tests/unit/test_bug656331.js new file mode 100644 index 000000000..3bc1f82c0 --- /dev/null +++ b/xpcom/tests/unit/test_bug656331.js @@ -0,0 +1,39 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function info(s) { + dump("TEST-INFO | test_bug656331.js | " + s + "\n"); +} + +var gMessageExpected = /Native module.*has version 3.*expected/; +var gFound = false; + +const kConsoleListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), + + observe: function listener_observe(message) { + if (gMessageExpected.test(message.message)) + gFound = true; + } +}; + +function run_test() { + let cs = Components.classes["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + cs.registerListener(kConsoleListener); + + let manifest = do_get_file('components/bug656331.manifest'); + registerAppManifest(manifest); + + do_check_false("{f18fb09b-28b4-4435-bc5b-8027f18df743}" in Components.classesByID); + + do_test_pending(); + Components.classes["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager).mainThread.dispatch(function() { + cs.unregisterListener(kConsoleListener); + do_check_true(gFound); + do_test_finished(); + }, 0); +} diff --git a/xpcom/tests/unit/test_bug725015.js b/xpcom/tests/unit/test_bug725015.js new file mode 100644 index 000000000..d6f62c509 --- /dev/null +++ b/xpcom/tests/unit/test_bug725015.js @@ -0,0 +1,39 @@ +/* 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +const manifest = do_get_file('bug725015.manifest'); +const contract = "@bug725015.test.contract"; +const observerTopic = "xpcom-category-entry-added"; +const category = "bug725015-test-category"; +const entry = "bug725015-category-entry"; +const cid = Components.ID("{05070380-6e6e-42ba-aaa5-3289fc55ca5a}"); + +function observe_category(subj, topic, data) { + try { + do_check_eq(topic, observerTopic); + if (data != category) + return; + + var thisentry = subj.QueryInterface(Ci.nsISupportsCString).data; + do_check_eq(thisentry, entry); + + do_check_eq(Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager).getCategoryEntry(category, entry), contract); + do_check_true(Cc[contract].equals(cid)); + } + catch (e) { + do_throw(e); + } + do_test_finished(); +} + +function run_test() { + do_test_pending(); + Services.obs.addObserver(observe_category, observerTopic, false); + Components.manager.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(manifest); +} diff --git a/xpcom/tests/unit/test_bug745466.js b/xpcom/tests/unit/test_bug745466.js new file mode 100644 index 000000000..22a911ac5 --- /dev/null +++ b/xpcom/tests/unit/test_bug745466.js @@ -0,0 +1,6 @@ +Components.utils.import("resource://gre/modules/FileUtils.jsm"); + +function run_test() +{ + do_check_true(FileUtils.File("~").equals(FileUtils.getDir("Home", []))); +} diff --git a/xpcom/tests/unit/test_comp_no_aslr.js b/xpcom/tests/unit/test_comp_no_aslr.js new file mode 100644 index 000000000..7e15731c6 --- /dev/null +++ b/xpcom/tests/unit/test_comp_no_aslr.js @@ -0,0 +1,18 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + let manifest = do_get_file('components/testcompnoaslr.manifest'); + registerAppManifest(manifest); + var sysInfo = Cc["@mozilla.org/system-info;1"]. + getService(Ci.nsIPropertyBag2); + var ver = parseFloat(sysInfo.getProperty("version")); + if (ver < 6.0) { + // This is disabled on pre-Vista OSs. + do_check_true("{335fb596-e52d-418f-b01c-1bf16ce5e7e4}" in Components.classesByID); + } else { + do_check_false("{335fb596-e52d-418f-b01c-1bf16ce5e7e4}" in Components.classesByID); + } +} diff --git a/xpcom/tests/unit/test_compmgr_warnings.js b/xpcom/tests/unit/test_compmgr_warnings.js new file mode 100644 index 000000000..be77b0d1b --- /dev/null +++ b/xpcom/tests/unit/test_compmgr_warnings.js @@ -0,0 +1,71 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function info(s) { + dump("TEST-INFO | test_compmgr_warnings.js | " + s + "\n"); +} + +var gMessagesExpected = [ + { line: 2, message: /Malformed CID/, found: false }, + { line: 6, message: /re-register/, found: false }, + { line: 9, message: /Could not/, found: false }, + { line: 2, message: /binary component twice/, found: false }, + { line: 3, message: /binary component twice/, found: false }, +]; + +const kConsoleListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), + + observe: function listener_observe(message) { + if (!(message instanceof Ci.nsIScriptError)) { + info("Not a script error: " + message.message); + return; + } + + info("Script error... " + message.sourceName + ":" + message.lineNumber + ": " + message.errorMessage); + for (let expected of gMessagesExpected) { + if (message.lineNumber != expected.line) + continue; + + if (!expected.message.test(message.errorMessage)) + continue; + + info("Found expected message: " + expected.message); + do_check_false(expected.found); + + expected.found = true; + } + } +}; + +function run_deferred_event(fn) { + do_test_pending(); + Components.classes["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager).mainThread.dispatch(function() { + fn(); + do_test_finished(); + }, 0); +} + +function run_test() +{ + let cs = Components.classes["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + cs.registerListener(kConsoleListener); + + var manifest = do_get_file('compmgr_warnings.manifest'); + registerAppManifest(manifest); + manifest = do_get_file('components/testcomponent.manifest'); + registerAppManifest(manifest); + + run_deferred_event(function() { + cs.unregisterListener(kConsoleListener); + + for (let expected of gMessagesExpected) { + info("checking " + expected.message); + do_check_true(expected.found); + } + }); +} diff --git a/xpcom/tests/unit/test_debugger_malloc_size_of.js b/xpcom/tests/unit/test_debugger_malloc_size_of.js new file mode 100644 index 000000000..450d62793 --- /dev/null +++ b/xpcom/tests/unit/test_debugger_malloc_size_of.js @@ -0,0 +1,34 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +// This is just a sanity test that Gecko is giving SpiderMonkey a MallocSizeOf +// function for new JSRuntimes. There is more extensive testing around the +// expected byte sizes within SpiderMonkey's jit-tests, we just want to make +// sure that Gecko is providing SpiderMonkey with the callback it needs. + +var Cu = Components.utils; +const { byteSize } = Cu.getJSTestingFunctions(); + +function run_test() +{ + const objects = [ + {}, + { w: 1, x: 2, y: 3, z:4, a: 5 }, + [], + Array(10).fill(null), + new RegExp("(2|two) problems", "g"), + new Date(), + new Uint8Array(64), + Promise.resolve(1), + function f() {}, + Object + ]; + + for (let obj of objects) { + do_print(uneval(obj)); + ok(byteSize(obj), "We should get some (non-zero) byte size"); + } +} diff --git a/xpcom/tests/unit/test_file_createUnique.js b/xpcom/tests/unit/test_file_createUnique.js new file mode 100644 index 000000000..1ab204bab --- /dev/null +++ b/xpcom/tests/unit/test_file_createUnique.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +function run_test() +{ + // Generate a leaf name that is 255 characters long. + var longLeafName = new Array(256).join("T"); + + // Generate the path for a file located in a directory with a long name. + var tempFile = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile); + tempFile.append(longLeafName); + tempFile.append("test.txt"); + + try { + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + do_throw("Creating an item in a folder with a very long name should throw"); + } + catch (e if (e instanceof Ci.nsIException && + e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH)) { + // We expect the function not to crash but to raise this exception. + } +} diff --git a/xpcom/tests/unit/test_file_equality.js b/xpcom/tests/unit/test_file_equality.js new file mode 100644 index 000000000..235792560 --- /dev/null +++ b/xpcom/tests/unit/test_file_equality.js @@ -0,0 +1,43 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +var Cr = Components.results; +var Ci = Components.interfaces; + +var CC = Components.Constructor; +var LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); + +function run_test() +{ + test_normalized_vs_non_normalized(); +} + +function test_normalized_vs_non_normalized() +{ + // get a directory that exists on all platforms + var dirProvider = Components.classes["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var tmp1 = dirProvider.get("TmpD", Ci.nsILocalFile); + var exists = tmp1.exists(); + do_check_true(exists); + if (!exists) + return; + + // the test logic below assumes we're starting with a normalized path, but the + // default location on macos is a symbolic link, so resolve it before starting + tmp1.normalize(); + + // this has the same exact path as tmp1, it should equal tmp1 + var tmp2 = new LocalFile(tmp1.path); + do_check_true(tmp1.equals(tmp2)); + + // this is a non-normalized version of tmp1, it should not equal tmp1 + tmp2.appendRelativePath("."); + do_check_false(tmp1.equals(tmp2)); + + // normalize and make sure they are equivalent again + tmp2.normalize(); + do_check_true(tmp1.equals(tmp2)); +} diff --git a/xpcom/tests/unit/test_file_renameTo.js b/xpcom/tests/unit/test_file_renameTo.js new file mode 100644 index 000000000..0a7196fe2 --- /dev/null +++ b/xpcom/tests/unit/test_file_renameTo.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() +{ + // Create the base directory. + let base = Cc['@mozilla.org/file/directory_service;1'] + .getService(Ci.nsIProperties) + .get('TmpD', Ci.nsILocalFile); + base.append('renameTesting'); + if (base.exists()) { + base.remove(true); + } + base.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0777', 8)); + + // Create a sub directory under the base. + let subdir = base.clone(); + subdir.append('subdir'); + subdir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0777', 8)); + + // Create a file under the sub directory. + let tempFile = subdir.clone(); + tempFile.append('file0.txt'); + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt('0777', 8)); + + // Test renameTo in the base directory + tempFile.renameTo(null, 'file1.txt'); + do_check_true(exists(subdir, 'file1.txt')); + + // Test moving across directories + tempFile = subdir.clone(); + tempFile.append('file1.txt'); + tempFile.renameTo(base, ''); + do_check_true(exists(base, 'file1.txt')); + + // Test moving across directories and renaming at the same time + tempFile = base.clone(); + tempFile.append('file1.txt'); + tempFile.renameTo(subdir, 'file2.txt'); + do_check_true(exists(subdir, 'file2.txt')); + + // Test moving a directory + subdir.renameTo(base, 'renamed'); + do_check_true(exists(base, 'renamed')); + let renamed = base.clone(); + renamed.append('renamed'); + do_check_true(exists(renamed, 'file2.txt')); + + base.remove(true); +} + +function exists(parent, filename) { + let file = parent.clone(); + file.append(filename); + return file.exists(); +} diff --git a/xpcom/tests/unit/test_hidden_files.js b/xpcom/tests/unit/test_hidden_files.js new file mode 100644 index 000000000..3383ba11b --- /dev/null +++ b/xpcom/tests/unit/test_hidden_files.js @@ -0,0 +1,28 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; +const NS_OS_TEMP_DIR = "TmpD"; + +const CWD = do_get_cwd(); + +var hiddenUnixFile; +function createUNIXHiddenFile() { + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var tmpDir = dirSvc.get(NS_OS_TEMP_DIR, Ci.nsIFile); + hiddenUnixFile = tmpDir.clone(); + hiddenUnixFile.append(".foo"); + // we don't care if this already exists because we don't care + // about the file's contents (just the name) + if (!hiddenUnixFile.exists()) + hiddenUnixFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + return hiddenUnixFile.exists(); +} + +function run_test() { + // Skip this test on Windows + if (mozinfo.os == "win") + return; + + do_check_true(createUNIXHiddenFile()); + do_check_true(hiddenUnixFile.isHidden()); +} + diff --git a/xpcom/tests/unit/test_home.js b/xpcom/tests/unit/test_home.js new file mode 100644 index 000000000..61fa5b344 --- /dev/null +++ b/xpcom/tests/unit/test_home.js @@ -0,0 +1,24 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; + +const CWD = do_get_cwd(); +function checkOS(os) { + const nsILocalFile_ = "nsILocalFile" + os; + return nsILocalFile_ in Components.interfaces && + CWD instanceof Components.interfaces[nsILocalFile_]; +} + +const isWin = checkOS("Win"); + +function run_test() { + var envVar = isWin ? "USERPROFILE" : "HOME"; + + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); + var homeDir = dirSvc.get("Home", Ci.nsIFile); + + var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + var expected = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + expected.initWithPath(env.get(envVar)); + + do_check_eq(homeDir.path, expected.path); +} diff --git a/xpcom/tests/unit/test_iniProcessor.js b/xpcom/tests/unit/test_iniProcessor.js new file mode 100644 index 000000000..3d3886d35 --- /dev/null +++ b/xpcom/tests/unit/test_iniProcessor.js @@ -0,0 +1,288 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cr = Components.results; + +var testnum = 0; +var factory; + +function parserForFile(filename) { + let parser = null; + try { + let file = do_get_file(filename); + do_check_true(!!file); + parser = factory.createINIParser(file); + do_check_true(!!parser); + } catch(e) { + dump("INFO | caught error: " + e); + // checkParserOutput will handle a null parser when it's expected. + } + return parser; + +} + +function checkParserOutput(parser, expected) { + // If the expected output is null, we expect the parser to have + // failed (and vice-versa). + if (!parser || !expected) { + do_check_eq(parser, null); + do_check_eq(expected, null); + return; + } + + let output = getParserOutput(parser); + for (let section in expected) { + do_check_true(section in output); + for (let key in expected[section]) { + do_check_true(key in output[section]); + do_check_eq(output[section][key], expected[section][key]); + delete output[section][key]; + } + for (let key in output[section]) + do_check_eq(key, "wasn't expecting this key!"); + delete output[section]; + } + for (let section in output) + do_check_eq(section, "wasn't expecting this section!"); +} + +function getParserOutput(parser) { + let output = {}; + + let sections = parser.getSections(); + do_check_true(!!sections); + while (sections.hasMore()) { + let section = sections.getNext(); + do_check_false(section in output); // catch dupes + output[section] = {}; + + let keys = parser.getKeys(section); + do_check_true(!!keys); + while (keys.hasMore()) { + let key = keys.getNext(); + do_check_false(key in output[section]); // catch dupes + let value = parser.getString(section, key); + output[section][key] = value; + } + } + return output; +} + +function run_test() { +try { + +var testdata = [ + { filename: "data/iniparser01.ini", reference: {} }, + { filename: "data/iniparser02.ini", reference: {} }, + { filename: "data/iniparser03.ini", reference: {} }, + { filename: "data/iniparser04.ini", reference: {} }, + { filename: "data/iniparser05.ini", reference: {} }, + { filename: "data/iniparser06.ini", reference: {} }, + { filename: "data/iniparser07.ini", reference: {} }, + { filename: "data/iniparser08.ini", reference: { section1: { name1: "" }} }, + { filename: "data/iniparser09.ini", reference: { section1: { name1: "value1" } } }, + { filename: "data/iniparser10.ini", reference: { section1: { name1: "value1" } } }, + { filename: "data/iniparser11.ini", reference: { section1: { name1: "value1" } } }, + { filename: "data/iniparser12.ini", reference: { section1: { name1: "value1" } } }, + { filename: "data/iniparser13.ini", reference: { section1: { name1: "value1" } } }, + { filename: "data/iniparser14.ini", reference: + { section1: { name1: "value1", name2: "value2" }, + section2: { name1: "value1", name2: "foopy" }} }, + { filename: "data/iniparser15.ini", reference: + { section1: { name1: "newValue1" }, + section2: { name1: "foopy" }} }, + { filename: "data/iniparser16.ini", reference: + { "☺♫": { "♫": "☻", "♪": "♥" }, + "☼": { "♣": "♠", "♦": "♥" }} }, + + ]; + + testdata.push( { filename: "data/iniparser01-utf8BOM.ini", + reference: testdata[0].reference } ); + testdata.push( { filename: "data/iniparser02-utf8BOM.ini", + reference: testdata[1].reference } ); + testdata.push( { filename: "data/iniparser03-utf8BOM.ini", + reference: testdata[2].reference } ); + testdata.push( { filename: "data/iniparser04-utf8BOM.ini", + reference: testdata[3].reference } ); + testdata.push( { filename: "data/iniparser05-utf8BOM.ini", + reference: testdata[4].reference } ); + testdata.push( { filename: "data/iniparser06-utf8BOM.ini", + reference: testdata[5].reference } ); + testdata.push( { filename: "data/iniparser07-utf8BOM.ini", + reference: testdata[6].reference } ); + testdata.push( { filename: "data/iniparser08-utf8BOM.ini", + reference: testdata[7].reference } ); + testdata.push( { filename: "data/iniparser09-utf8BOM.ini", + reference: testdata[8].reference } ); + testdata.push( { filename: "data/iniparser10-utf8BOM.ini", + reference: testdata[9].reference } ); + testdata.push( { filename: "data/iniparser11-utf8BOM.ini", + reference: testdata[10].reference } ); + testdata.push( { filename: "data/iniparser12-utf8BOM.ini", + reference: testdata[11].reference } ); + testdata.push( { filename: "data/iniparser13-utf8BOM.ini", + reference: testdata[12].reference } ); + testdata.push( { filename: "data/iniparser14-utf8BOM.ini", + reference: testdata[13].reference } ); + testdata.push( { filename: "data/iniparser15-utf8BOM.ini", + reference: testdata[14].reference } ); + testdata.push( { filename: "data/iniparser16-utf8BOM.ini", + reference: testdata[15].reference } ); + + let os = Cc["@mozilla.org/xre/app-info;1"] + .getService(Ci.nsIXULRuntime).OS; + if("WINNT" === os) { + testdata.push( { filename: "data/iniparser01-utf16leBOM.ini", + reference: testdata[0].reference } ); + testdata.push( { filename: "data/iniparser02-utf16leBOM.ini", + reference: testdata[1].reference } ); + testdata.push( { filename: "data/iniparser03-utf16leBOM.ini", + reference: testdata[2].reference } ); + testdata.push( { filename: "data/iniparser04-utf16leBOM.ini", + reference: testdata[3].reference } ); + testdata.push( { filename: "data/iniparser05-utf16leBOM.ini", + reference: testdata[4].reference } ); + testdata.push( { filename: "data/iniparser06-utf16leBOM.ini", + reference: testdata[5].reference } ); + testdata.push( { filename: "data/iniparser07-utf16leBOM.ini", + reference: testdata[6].reference } ); + testdata.push( { filename: "data/iniparser08-utf16leBOM.ini", + reference: testdata[7].reference } ); + testdata.push( { filename: "data/iniparser09-utf16leBOM.ini", + reference: testdata[8].reference } ); + testdata.push( { filename: "data/iniparser10-utf16leBOM.ini", + reference: testdata[9].reference } ); + testdata.push( { filename: "data/iniparser11-utf16leBOM.ini", + reference: testdata[10].reference } ); + testdata.push( { filename: "data/iniparser12-utf16leBOM.ini", + reference: testdata[11].reference } ); + testdata.push( { filename: "data/iniparser13-utf16leBOM.ini", + reference: testdata[12].reference } ); + testdata.push( { filename: "data/iniparser14-utf16leBOM.ini", + reference: testdata[13].reference } ); + testdata.push( { filename: "data/iniparser15-utf16leBOM.ini", + reference: testdata[14].reference } ); + testdata.push( { filename: "data/iniparser16-utf16leBOM.ini", + reference: testdata[15].reference } ); + } + +/* ========== 0 ========== */ +factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]. + getService(Ci.nsIINIParserFactory); +do_check_true(!!factory); + +// Test reading from a variety of files. While we're at it, write out each one +// and read it back to ensure that nothing changed. +while (testnum < testdata.length) { + dump("\nINFO | test #" + ++testnum); + let filename = testdata[testnum -1].filename; + dump(", filename " + filename + "\n"); + let parser = parserForFile(filename); + checkParserOutput(parser, testdata[testnum - 1].reference); + if (!parser) + continue; + do_check_true(parser instanceof Ci.nsIINIParserWriter); + // write contents out to a new file + let newfilename = filename + ".new"; + let newfile = do_get_file(filename); + newfile.leafName += ".new"; + parser.writeFile(newfile); + // read new file and make sure the contents are the same. + parser = parserForFile(newfilename); + checkParserOutput(parser, testdata[testnum - 1].reference); + // cleanup after the test + newfile.remove(false); +} + +dump("INFO | test #" + ++testnum + "\n"); + +// test writing to a new file. +var newfile = do_get_file("data/"); +newfile.append("nonexistent-file.ini"); +if (newfile.exists()) + newfile.remove(false); +do_check_false(newfile.exists()); + +var parser = factory.createINIParser(newfile); +do_check_true(!!parser); +do_check_true(parser instanceof Ci.nsIINIParserWriter); +checkParserOutput(parser, {}); +parser.writeFile(); +do_check_true(newfile.exists()); + +// test adding a new section and new key +parser.setString("section", "key", "value"); +parser.writeFile(); +do_check_true(newfile.exists()); +checkParserOutput(parser, {section: {key: "value"} }); +// read it in again, check for same data. +parser = parserForFile("data/nonexistent-file.ini"); +checkParserOutput(parser, {section: {key: "value"} }); +// cleanup after the test +newfile.remove(false); + +dump("INFO | test #" + ++testnum + "\n"); + +// test modifying a existing key's value (in an existing section) +parser = parserForFile("data/iniparser09.ini"); +checkParserOutput(parser, {section1: {name1: "value1"} }); + +do_check_true(parser instanceof Ci.nsIINIParserWriter); +parser.setString("section1", "name1", "value2"); +checkParserOutput(parser, {section1: {name1: "value2"} }); + +dump("INFO | test #" + ++testnum + "\n"); + +// test trying to set illegal characters +var caughtError; +caughtError = false; +checkParserOutput(parser, {section1: {name1: "value2"} }); + +// Bad characters in section name +try { parser.SetString("bad\0", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad\r", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad\n", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad[", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("bad]", "ok", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); + +// Bad characters in key name +caughtError = false; +try { parser.SetString("ok", "bad\0", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "bad\r", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "bad\n", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "bad=", "ok"); } catch (e) { caughtError = true; } +do_check_true(caughtError); + +// Bad characters in value +caughtError = false; +try { parser.SetString("ok", "ok", "bad\0"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "ok", "bad\r"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "ok", "bad\n"); } catch (e) { caughtError = true; } +do_check_true(caughtError); +caughtError = false; +try { parser.SetString("ok", "ok", "bad="); } catch (e) { caughtError = true; } +do_check_true(caughtError); + +} catch(e) { + throw "FAILED in test #" + testnum + " -- " + e; +} +} diff --git a/xpcom/tests/unit/test_ioutil.js b/xpcom/tests/unit/test_ioutil.js new file mode 100644 index 000000000..ef27f584f --- /dev/null +++ b/xpcom/tests/unit/test_ioutil.js @@ -0,0 +1,33 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +const util = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil); + +function run_test() +{ + try { + util.inputStreamIsBuffered(null); + do_throw("inputStreamIsBuffered should have thrown"); + } catch (e) { + do_check_eq(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + try { + util.outputStreamIsBuffered(null); + do_throw("outputStreamIsBuffered should have thrown"); + } catch (e) { + do_check_eq(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + var s = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + var body = "This is a test"; + s.setData(body, body.length); + do_check_eq(util.inputStreamIsBuffered(s), true); +} diff --git a/xpcom/tests/unit/test_localfile.js b/xpcom/tests/unit/test_localfile.js new file mode 100644 index 000000000..25f4bf34b --- /dev/null +++ b/xpcom/tests/unit/test_localfile.js @@ -0,0 +1,151 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +var Cr = Components.results; +var CC = Components.Constructor; +var Ci = Components.interfaces; + +const MAX_TIME_DIFFERENCE = 2500; +const MILLIS_PER_DAY = 1000 * 60 * 60 * 24; + +var LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); + +function run_test() +{ + test_toplevel_parent_is_null(); + test_normalize_crash_if_media_missing(); + test_file_modification_time(); + test_directory_modification_time(); + test_diskSpaceAvailable(); +} + +function test_toplevel_parent_is_null() +{ + try + { + var lf = new LocalFile("C:\\"); + + // not required by API, but a property on which the implementation of + // parent == null relies for correctness + do_check_true(lf.path.length == 2); + + do_check_true(lf.parent === null); + } + catch (e) + { + // not Windows + do_check_eq(e.result, Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH); + } +} + +function test_normalize_crash_if_media_missing() +{ + const a="a".charCodeAt(0); + const z="z".charCodeAt(0); + for (var i = a; i <= z; ++i) + { + try + { + LocalFile(String.fromCharCode(i)+":.\\test").normalize(); + } + catch (e) + { + } + } +} + +// Tests that changing a file's modification time is possible +function test_file_modification_time() +{ + var file = do_get_profile(); + file.append("testfile"); + + // Should never happen but get rid of it anyway + if (file.exists()) + file.remove(true); + + var now = Date.now(); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + do_check_true(file.exists()); + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + var diff = Math.abs(file.lastModifiedTime - now); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var yesterday = now - MILLIS_PER_DAY; + file.lastModifiedTime = yesterday; + + diff = Math.abs(file.lastModifiedTime - yesterday); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var tomorrow = now - MILLIS_PER_DAY; + file.lastModifiedTime = tomorrow; + + diff = Math.abs(file.lastModifiedTime - tomorrow); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var bug377307 = 1172950238000; + file.lastModifiedTime = bug377307; + + diff = Math.abs(file.lastModifiedTime - bug377307); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + file.remove(true); +} + +// Tests that changing a directory's modification time is possible +function test_directory_modification_time() +{ + var dir = do_get_profile(); + dir.append("testdir"); + + // Should never happen but get rid of it anyway + if (dir.exists()) + dir.remove(true); + + var now = Date.now(); + dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + do_check_true(dir.exists()); + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + var diff = Math.abs(dir.lastModifiedTime - now); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var yesterday = now - MILLIS_PER_DAY; + dir.lastModifiedTime = yesterday; + + diff = Math.abs(dir.lastModifiedTime - yesterday); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + var tomorrow = now - MILLIS_PER_DAY; + dir.lastModifiedTime = tomorrow; + + diff = Math.abs(dir.lastModifiedTime - tomorrow); + do_check_true(diff < MAX_TIME_DIFFERENCE); + + dir.remove(true); +} + +function test_diskSpaceAvailable() +{ + let file = do_get_profile(); + file.QueryInterface(Ci.nsILocalFile); + + let bytes = file.diskSpaceAvailable; + do_check_true(bytes > 0); + + file.append("testfile"); + if (file.exists()) + file.remove(true); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + bytes = file.diskSpaceAvailable; + do_check_true(bytes > 0); + + file.remove(true); +} diff --git a/xpcom/tests/unit/test_mac_bundle.js b/xpcom/tests/unit/test_mac_bundle.js new file mode 100644 index 000000000..550a4abd6 --- /dev/null +++ b/xpcom/tests/unit/test_mac_bundle.js @@ -0,0 +1,18 @@ +function run_test() { + // this is a hack to skip the rest of the code on non-Mac platforms, + // since #ifdef is not available to xpcshell tests... + if (mozinfo.os != "mac") { + return; + } + + // OK, here's the real part of the test: + // make sure these two test bundles are recognized as bundles (or "packages") + var keynoteBundle = do_get_file("data/presentation.key"); + var appBundle = do_get_file("data/SmallApp.app"); + + do_check_true(keynoteBundle instanceof Components.interfaces.nsILocalFileMac); + do_check_true(appBundle instanceof Components.interfaces.nsILocalFileMac); + + do_check_true(keynoteBundle.isPackage()); + do_check_true(appBundle.isPackage()); +} diff --git a/xpcom/tests/unit/test_notxpcom_scriptable.js b/xpcom/tests/unit/test_notxpcom_scriptable.js new file mode 100644 index 000000000..5d5e24bd9 --- /dev/null +++ b/xpcom/tests/unit/test_notxpcom_scriptable.js @@ -0,0 +1,86 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const kCID = Components.ID("{1f9f7181-e6c5-4f4c-8f71-08005cec8468}"); +const kContract = "@testing/notxpcomtest"; + +function run_test() +{ + let manifest = do_get_file("xpcomtest.manifest"); + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.autoRegister(manifest); + + ok(Ci.ScriptableWithNotXPCOM); + + let method1Called = false; + + let testObject = { + QueryInterface: XPCOMUtils.generateQI([Ci.ScriptableOK, + Ci.ScriptableWithNotXPCOM, + Ci.ScriptableWithNotXPCOMBase]), + + method1: function() { + method1Called = true; + }, + + method2: function() { + ok(false, "method2 should not have been called!"); + }, + + method3: function() { + ok(false, "mehod3 should not have been called!"); + }, + + jsonly: true, + }; + + let factory = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]), + + createInstance: function(outer, iid) { + if (outer) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + return testObject.QueryInterface(iid); + }, + }; + + registrar.registerFactory(kCID, null, kContract, factory); + + let xpcomObject = Cc[kContract].createInstance(); + ok(xpcomObject); + strictEqual(xpcomObject.jsonly, undefined); + + xpcomObject.QueryInterface(Ci.ScriptableOK); + + xpcomObject.method1(); + ok(method1Called); + + try { + xpcomObject.QueryInterface(Ci.ScriptableWithNotXPCOM); + ok(false, "Should not have implemented ScriptableWithNotXPCOM"); + } + catch(e) { + ok(true, "Should not have implemented ScriptableWithNotXPCOM. Correctly threw error: " + e); + } + strictEqual(xpcomObject.method2, undefined); + + try { + xpcomObject.QueryInterface(Ci.ScriptableWithNotXPCOMBase); + ok(false, "Should not have implemented ScriptableWithNotXPCOMBase"); + } + catch (e) { + ok(true, "Should not have implemented ScriptableWithNotXPCOMBase. Correctly threw error: " + e); + } + strictEqual(xpcomObject.method3, undefined); +} + diff --git a/xpcom/tests/unit/test_nsIMutableArray.js b/xpcom/tests/unit/test_nsIMutableArray.js new file mode 100644 index 000000000..b491aee96 --- /dev/null +++ b/xpcom/tests/unit/test_nsIMutableArray.js @@ -0,0 +1,184 @@ +/* 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/. */ + +var Ci = Components.interfaces; +var Cr = Components.results; +var Cc = Components.classes; +var CC = Components.Constructor; + +var MutableArray = CC("@mozilla.org/array;1", "nsIMutableArray"); +var SupportsString = CC("@mozilla.org/supports-string;1", "nsISupportsString"); + +function create_n_element_array(n) +{ + var arr = new MutableArray(); + for (let i=0; i<n; i++) { + let str = new SupportsString(); + str.data = "element " + i; + arr.appendElement(str, false); + } + return arr; +} + +function test_appending_null_actually_inserts() +{ + var arr = new MutableArray(); + do_check_eq(0, arr.length); + arr.appendElement(null, false); + do_check_eq(1, arr.length); +} + +function test_object_gets_appended() +{ + var arr = new MutableArray(); + var str = new SupportsString(); + str.data = "hello"; + arr.appendElement(str, false); + do_check_eq(1, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + do_check_eq(str, obj); +} + +function test_insert_at_beginning() +{ + var arr = create_n_element_array(5); + // just a sanity check + do_check_eq(5, arr.length); + var str = new SupportsString(); + str.data = "hello"; + arr.insertElementAt(str, 0, false); + do_check_eq(6, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + do_check_eq(str, obj); + // check the data of all the other objects + for (let i=1; i<arr.length; i++) { + let obj = arr.queryElementAt(i, Ci.nsISupportsString); + do_check_eq("element " + (i-1), obj.data); + } +} + +function test_replace_element() +{ + var arr = create_n_element_array(5); + // just a sanity check + do_check_eq(5, arr.length); + var str = new SupportsString(); + str.data = "hello"; + // replace first element + arr.replaceElementAt(str, 0, false); + do_check_eq(5, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + do_check_eq(str, obj); + // replace last element + arr.replaceElementAt(str, arr.length - 1, false); + do_check_eq(5, arr.length); + obj = arr.queryElementAt(arr.length - 1, Ci.nsISupportsString); + do_check_eq(str, obj); + // replace after last element, should insert empty elements + arr.replaceElementAt(str, 9, false); + do_check_eq(10, arr.length); + obj = arr.queryElementAt(9, Ci.nsISupportsString); + do_check_eq(str, obj); + // AFAIK there's no way to check the empty elements, since you can't QI them. +} + +function test_clear() +{ + var arr = create_n_element_array(5); + // just a sanity check + do_check_eq(5, arr.length); + arr.clear(); + do_check_eq(0, arr.length); +} + +function test_enumerate() +{ + var arr = create_n_element_array(5); + do_check_eq(5, arr.length); + var en = arr.enumerate(); + var i = 0; + while (en.hasMoreElements()) { + let str = en.getNext(); + do_check_true(str instanceof Ci.nsISupportsString); + do_check_eq(str.data, "element " + i); + i++; + } + do_check_eq(arr.length, i); +} + +function test_nssupportsarray_interop() { + // Tests to check that an nsSupportsArray instance can behave like an + // nsIArray. + let test = Components.classes["@mozilla.org/supports-array;1"] + .createInstance(Ci.nsISupportsArray); + + let str = new SupportsString(); + str.data = "element"; + test.AppendElement(str); + + // Now query to an nsIArray. + let iarray = test.QueryInterface(Ci.nsIArray); + do_check_neq(iarray, null); + + // Make sure |nsIArray.length| works. + do_check_eq(iarray.length, 1); + + // Make sure |nsIArray.queryElementAt| works. + let elm = iarray.queryElementAt(0, Ci.nsISupportsString); + do_check_eq(elm.data, "element"); + + // Make sure |nsIArray.indexOf| works. + let idx = iarray.indexOf(0, str); + do_check_eq(idx, 0); + + // Make sure |nsIArray.enumerate| works. + let en = iarray.enumerate(); + do_check_neq(en, null); + let i = 0; + while (en.hasMoreElements()) { + let str = en.getNext(); + do_check_true(str instanceof Ci.nsISupportsString); + do_check_eq(str.data, "element"); + i++; + } + + do_check_eq(iarray.length, i); +} + +function test_nsiarrayextensions() { + // Tests to check that the extensions that make an nsArray act like an + // nsISupportsArray for iteration purposes works. + // Note: we do not want to QI here, just want to make sure the magic glue + // works as a drop-in replacement. + + let fake_nsisupports_array = create_n_element_array(5); + + // Check that |Count| works. + do_check_eq(5, fake_nsisupports_array.Count()); + + for (let i = 0; i < fake_nsisupports_array.Count(); i++) { + // Check that the generic |GetElementAt| works. + let elm = fake_nsisupports_array.GetElementAt(i); + do_check_neq(elm, null); + let str = elm.QueryInterface(Ci.nsISupportsString); + do_check_neq(str, null); + do_check_eq(str.data, "element " + i); + } +} + +var tests = [ + test_appending_null_actually_inserts, + test_object_gets_appended, + test_insert_at_beginning, + test_replace_element, + test_clear, + test_enumerate, + test_nssupportsarray_interop, + test_nsiarrayextensions, +]; + +function run_test() { + for (var i = 0; i < tests.length; i++) + tests[i](); +} diff --git a/xpcom/tests/unit/test_nsIProcess.js b/xpcom/tests/unit/test_nsIProcess.js new file mode 100644 index 000000000..6c6a5a683 --- /dev/null +++ b/xpcom/tests/unit/test_nsIProcess.js @@ -0,0 +1,185 @@ +/* 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/. */ +// nsIProcess unit test +const TEST_ARGS = ["mozilla", "firefox", "thunderbird", "seamonkey", "foo", + "bar", "argument with spaces", "\"argument with quotes\""]; + +const TEST_UNICODE_ARGS = ["M\u00F8z\u00EEll\u00E5", + "\u041C\u043E\u0437\u0438\u043B\u043B\u0430", + "\u09AE\u09CB\u099C\u09BF\u09B2\u09BE", + "\uD808\uDE2C\uD808\uDF63\uD808\uDDB7"]; + +// test if a process can be started, polled for its running status +// and then killed +function test_kill() +{ + var file = get_test_program("TestBlockingProcess"); + + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + do_check_false(process.isRunning); + + try { + process.kill(); + do_throw("Attempting to kill a not-running process should throw"); + } + catch (e) { } + + process.run(false, [], 0); + + do_check_true(process.isRunning); + + process.kill(); + + do_check_false(process.isRunning); + + try { + process.kill(); + do_throw("Attempting to kill a not-running process should throw"); + } + catch (e) { } +} + +// test if we can get an exit value from an application that is +// guaranteed to return an exit value of 42 +function test_quick() +{ + var file = get_test_program("TestQuickReturn"); + + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + // to get an exit value it must be a blocking process + process.run(true, [], 0); + + do_check_eq(process.exitValue, 42); +} + +function test_args(file, args, argsAreASCII) +{ + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + if (argsAreASCII) + process.run(true, args, args.length); + else + process.runw(true, args, args.length); + + do_check_eq(process.exitValue, 0); +} + +// test if an argument can be successfully passed to an application +// that will return 0 if "mozilla" is the only argument +function test_arguments() +{ + test_args(get_test_program("TestArguments"), TEST_ARGS, true); +} + +// test if Unicode arguments can be successfully passed to an application +function test_unicode_arguments() +{ + test_args(get_test_program("TestUnicodeArguments"), TEST_UNICODE_ARGS, false); +} + +function rename_and_test(asciiName, unicodeName, args, argsAreASCII) +{ + var asciiFile = get_test_program(asciiName); + var asciiLeaf = asciiFile.leafName; + var unicodeLeaf = asciiLeaf.replace(asciiName, unicodeName); + + asciiFile.moveTo(null, unicodeLeaf); + + var unicodeFile = get_test_program(unicodeName); + + test_args(unicodeFile, args, argsAreASCII); + + unicodeFile.moveTo(null, asciiLeaf); +} + +// test passing ASCII and Unicode arguments to an application with a Unicode name +function test_unicode_app() +{ + rename_and_test("TestArguments", + // "Unicode" in Tamil + "\u0BAF\u0BC1\u0BA9\u0BBF\u0B95\u0BCB\u0B9F\u0BCD", + TEST_ARGS, true); + + rename_and_test("TestUnicodeArguments", + // "Unicode" in Thai + "\u0E22\u0E39\u0E19\u0E34\u0E42\u0E04\u0E14", + TEST_UNICODE_ARGS, false); +} + +// test if we get notified about a blocking process +function test_notify_blocking() +{ + var file = get_test_program("TestQuickReturn"); + + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + process.runAsync([], 0, { + observe: function(subject, topic, data) { + process = subject.QueryInterface(Components.interfaces.nsIProcess); + do_check_eq(topic, "process-finished"); + do_check_eq(process.exitValue, 42); + test_notify_nonblocking(); + } + }); +} + +// test if we get notified about a non-blocking process +function test_notify_nonblocking() +{ + var file = get_test_program("TestArguments"); + + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + process.runAsync(TEST_ARGS, TEST_ARGS.length, { + observe: function(subject, topic, data) { + process = subject.QueryInterface(Components.interfaces.nsIProcess); + do_check_eq(topic, "process-finished"); + do_check_eq(process.exitValue, 0); + test_notify_killed(); + } + }); +} + +// test if we get notified about a killed process +function test_notify_killed() +{ + var file = get_test_program("TestBlockingProcess"); + + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + process.runAsync([], 0, { + observe: function(subject, topic, data) { + process = subject.QueryInterface(Components.interfaces.nsIProcess); + do_check_eq(topic, "process-finished"); + do_test_finished(); + } + }); + + process.kill(); +} + +function run_test() { + set_process_running_environment(); + test_kill(); + test_quick(); + test_arguments(); + test_unicode_arguments(); + test_unicode_app(); + do_test_pending(); + test_notify_blocking(); +} diff --git a/xpcom/tests/unit/test_nsIProcess_stress.js b/xpcom/tests/unit/test_nsIProcess_stress.js new file mode 100644 index 000000000..63799141d --- /dev/null +++ b/xpcom/tests/unit/test_nsIProcess_stress.js @@ -0,0 +1,27 @@ +function run_test() { + set_process_running_environment(); + + var file = get_test_program("TestQuickReturn"); + var thread = Components.classes["@mozilla.org/thread-manager;1"] + .getService().currentThread; + + for (var i = 0; i < 1000; i++) { + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(file); + + process.run(false, [], 0); + + try { + process.kill(); + } + catch (e) { } + + // We need to ensure that we process any events on the main thread - + // this allow threads to clean up properly and avoid out of memory + // errors during the test. + while (thread.hasPendingEvents()) + thread.processNextEvent(false); + } + +}
\ No newline at end of file diff --git a/xpcom/tests/unit/test_pipe.js b/xpcom/tests/unit/test_pipe.js new file mode 100644 index 000000000..c16b33ab0 --- /dev/null +++ b/xpcom/tests/unit/test_pipe.js @@ -0,0 +1,63 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var CC = Components.Constructor; + +var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); + +function run_test() +{ + test_not_initialized(); + test_ends_are_threadsafe(); +} + +function test_not_initialized() +{ + var p = Cc["@mozilla.org/pipe;1"] + .createInstance(Ci.nsIPipe); + try + { + var dummy = p.outputStream; + throw Cr.NS_ERROR_FAILURE; + } + catch (e) + { + if (e.result != Cr.NS_ERROR_NOT_INITIALIZED) + do_throw("using a pipe before initializing it should throw NS_ERROR_NOT_INITIALIZED"); + } +} + +function test_ends_are_threadsafe() +{ + var p, is, os; + + p = new Pipe(true, true, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + do_check_true(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + do_check_true(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(true, false, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + do_check_true(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + do_check_true(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(false, true, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + do_check_true(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + do_check_true(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(false, false, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + do_check_true(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + do_check_true(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); +} diff --git a/xpcom/tests/unit/test_process_directives.js b/xpcom/tests/unit/test_process_directives.js new file mode 100644 index 000000000..807246f46 --- /dev/null +++ b/xpcom/tests/unit/test_process_directives.js @@ -0,0 +1,25 @@ +var Ci = Components.interfaces; +var Cc = Components.classes; + +Components.utils.import("resource:///modules/Services.jsm"); + +function run_test() +{ + Components.manager.autoRegister(do_get_file("data/process_directive.manifest")); + + let isChild = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + + if (isChild) { + do_check_false("@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1" in Cc); + } else { + let svc = Cc["@mozilla.org/xpcom/tests/MainProcessDirectiveTest;1"].createInstance(Ci.nsISupportsString); + do_check_eq(svc.data, "main process"); + } + + if (!isChild) { + do_check_false("@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1" in Cc); + } else { + let svc = Cc["@mozilla.org/xpcom/tests/ChildProcessDirectiveTest;1"].createInstance(Ci.nsISupportsString); + do_check_eq(svc.data, "child process"); + } +} diff --git a/xpcom/tests/unit/test_process_directives_child.js b/xpcom/tests/unit/test_process_directives_child.js new file mode 100644 index 000000000..dca63b356 --- /dev/null +++ b/xpcom/tests/unit/test_process_directives_child.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("test_process_directives.js"); +} diff --git a/xpcom/tests/unit/test_seek_multiplex.js b/xpcom/tests/unit/test_seek_multiplex.js new file mode 100644 index 000000000..31232d134 --- /dev/null +++ b/xpcom/tests/unit/test_seek_multiplex.js @@ -0,0 +1,173 @@ +/* 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/. */ + +var Ci = Components.interfaces; +var Cr = Components.results; +var CC = Components.Constructor; +var Cc = Components.classes; + +// The string we use as data. +const data = "0123456789"; +// Number of streams in the multiplex stream. +const count = 10; + +function test_multiplex_streams() { + var MultiplexStream = CC("@mozilla.org/io/multiplex-input-stream;1", + "nsIMultiplexInputStream"); + do_check_eq(1, 1); + + var BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream"); + var BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); + var multiplex = new MultiplexStream(); + for (var i = 0; i < count; ++i) { + let s = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + s.setData(data, data.length); + + multiplex.appendStream(s); + } + var seekable = multiplex.QueryInterface(Ci.nsISeekableStream); + var sis = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sis.init(seekable); + // Read some data. + var readData = sis.read(20); + do_check_eq(readData, data + data); + // -- Tests for NS_SEEK_SET + // Seek to a non-zero, non-stream-boundary offset. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 2); + do_check_eq(seekable.tell(), 2); + do_check_eq(seekable.available(), 98); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 9); + do_check_eq(seekable.tell(), 9); + do_check_eq(seekable.available(), 91); + // Seek across stream boundary. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 35); + do_check_eq(seekable.tell(), 35); + do_check_eq(seekable.available(), 65); + readData = sis.read(5); + do_check_eq(readData, data.slice(5)); + do_check_eq(seekable.available(), 60); + // Seek at stream boundaries. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 40); + do_check_eq(seekable.tell(), 40); + do_check_eq(seekable.available(), 60); + readData = sis.read(10); + do_check_eq(readData, data); + do_check_eq(seekable.tell(), 50); + do_check_eq(seekable.available(), 50); + // Rewind and read across streams. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 39); + do_check_eq(seekable.tell(), 39); + do_check_eq(seekable.available(), 61); + readData = sis.read(11); + do_check_eq(readData, data.slice(9) + data); + do_check_eq(seekable.tell(), 50); + do_check_eq(seekable.available(), 50); + // Rewind to the beginning. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + do_check_eq(seekable.tell(), 0); + do_check_eq(seekable.available(), 100); + // Seek to some random location + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 50); + // -- Tests for NS_SEEK_CUR + // Positive seek. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 15); + do_check_eq(seekable.tell(), 65); + do_check_eq(seekable.available(), 35); + readData = sis.read(10); + do_check_eq(readData, data.slice(5) + data.slice(0, 5)); + do_check_eq(seekable.tell(), 75); + do_check_eq(seekable.available(), 25); + // Negative seek. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15); + do_check_eq(seekable.tell(), 60); + do_check_eq(seekable.available(), 40); + readData = sis.read(10); + do_check_eq(readData, data); + do_check_eq(seekable.tell(), 70); + do_check_eq(seekable.available(), 30); + + // -- Tests for NS_SEEK_END + // Normal read. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -5); + do_check_eq(seekable.tell(), data.length * count - 5); + readData = sis.read(5); + do_check_eq(readData, data.slice(5)); + do_check_eq(seekable.tell(), data.length * count); + // Read across streams. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -15); + do_check_eq(seekable.tell(), data.length * count - 15); + readData = sis.read(15); + do_check_eq(readData, data.slice(5) + data); + do_check_eq(seekable.tell(), data.length * count); + + // -- Try to do various edge cases + // Forward seek from the end, should throw. + var caught = false; + try { + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, 15); + } catch(e) { + caught = true; + } + do_check_eq(caught, true); + do_check_eq(seekable.tell(), data.length * count); + // Backward seek from the beginning, should be clamped. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + do_check_eq(seekable.tell(), 0); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15); + do_check_eq(seekable.tell(), 0); + // Seek too far: should be clamped. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + do_check_eq(seekable.tell(), 0); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 3 * data.length * count); + do_check_eq(seekable.tell(), 100); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, data.length * count); + do_check_eq(seekable.tell(), 100); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -2 * data.length * count); + do_check_eq(seekable.tell(), 0); +} + +function test_multiplex_bug797871() { + + var data = "1234567890123456789012345678901234567890"; + + var MultiplexStream = CC("@mozilla.org/io/multiplex-input-stream;1", + "nsIMultiplexInputStream"); + do_check_eq(1, 1); + + var BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream"); + var BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); + var multiplex = new MultiplexStream(); + let s = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + s.setData(data, data.length); + + multiplex.appendStream(s); + + var seekable = multiplex.QueryInterface(Ci.nsISeekableStream); + var sis = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sis.init(seekable); + + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 8); + do_check_eq(seekable.tell(), 8); + readData = sis.read(2); + do_check_eq(seekable.tell(), 10); + + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 20); + do_check_eq(seekable.tell(), 20); +} + +function run_test() { + test_multiplex_streams(); + test_multiplex_bug797871(); +} + diff --git a/xpcom/tests/unit/test_storagestream.js b/xpcom/tests/unit/test_storagestream.js new file mode 100644 index 000000000..33fdc2202 --- /dev/null +++ b/xpcom/tests/unit/test_storagestream.js @@ -0,0 +1,162 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +function run_test() +{ + test1(); + test2(); + test3(); + test4(); +} + +/** + * Checks that getting an input stream from a storage stream which has never had + * anything written to it throws a not-initialized exception. + */ +function test1() +{ + var ss = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + var inp2 = ss.newInputStream(0); + do_check_eq(inp2.available(), 0); + do_check_true(inp2.isNonBlocking()); + + var sis = + Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sis.init(inp2); + + var threw = false; + try { + sis.read(1); + } catch (ex if ex.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) { + threw = true; + } + do_check_true(threw); +} + +/** + * Checks that getting an input stream from a storage stream to which 0 bytes of + * data have been explicitly written doesn't throw an exception. + */ +function test2() +{ + var ss = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + out.write("", 0); + try + { + var inp2 = ss.newInputStream(0); + } + catch (e) + { + do_throw("shouldn't throw exception when new input stream created"); + } +} + +/** + * Checks that reading any non-zero amount of data from a storage stream + * which has had 0 bytes written to it explicitly works correctly. + */ +function test3() +{ + var ss = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + out.write("", 0); + try + { + var inp = ss.newInputStream(0); + } + catch (e) + { + do_throw("newInputStream(0) shouldn't throw if write() is called: " + e); + } + + do_check_true(inp.isNonBlocking(), "next test expects a non-blocking stream"); + + try + { + var threw = false; + var bis = BIS(inp); + var dummy = bis.readByteArray(5); + } + catch (e) + { + if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) + do_throw("wrong error thrown: " + e); + threw = true; + } + do_check_true(threw, + "should have thrown (nsStorageInputStream is nonblocking)"); +} + +/** + * Basic functionality test for storagestream: write data to it, get an input + * stream, and read the data back to see that it matches. + */ +function test4() +{ + var bytes = [65, 66, 67, 68, 69, 70, 71, 72, 73, 74]; + + var ss = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + ss.init(1024, 1024, null); + + var outStream = ss.getOutputStream(0); + + var bos = Cc["@mozilla.org/binaryoutputstream;1"] + .createInstance(Ci.nsIBinaryOutputStream); + bos.setOutputStream(outStream); + + bos.writeByteArray(bytes, bytes.length); + bos.close(); + + var inp = ss.newInputStream(0); + var bis = BIS(inp); + + var count = 0; + while (count < bytes.length) + { + var data = bis.read8(1); + do_check_eq(data, bytes[count++]); + } + + var threw = false; + try + { + data = bis.read8(1); + } + catch (e) + { + if (e.result != Cr.NS_ERROR_FAILURE) + do_throw("wrong error thrown: " + e); + threw = true; + } + if (!threw) + do_throw("should have thrown but instead returned: '" + data + "'"); +} + + +function BIS(input) +{ + var bis = Cc["@mozilla.org/binaryinputstream;1"] + .createInstance(Ci.nsIBinaryInputStream); + bis.setInputStream(input); + return bis; +} diff --git a/xpcom/tests/unit/test_streams.js b/xpcom/tests/unit/test_streams.js new file mode 100644 index 000000000..e668cb8d5 --- /dev/null +++ b/xpcom/tests/unit/test_streams.js @@ -0,0 +1,157 @@ +/* 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/. */ + +var Ci = Components.interfaces; +var Cr = Components.results; +var CC = Components.Constructor; + +var Pipe = CC("@mozilla.org/pipe;1", + "nsIPipe", + "init"); +var BinaryOutput = CC("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream"); +var BinaryInput = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +/** + * Binary stream tests. + */ +function test_binary_streams() { + var p, is, os; + + p = new Pipe(false, false, 1024, 1, null); + is = new BinaryInput(p.inputStream); + os = new BinaryOutput(p.outputStream); + + const LargeNum = Math.pow(2, 18) + Math.pow(2, 12) + 1; + const HugeNum = Math.pow(2, 62); + const HelloStr = "Hello World"; + const HelloArray = Array.map(HelloStr, function(c) {return c.charCodeAt(0)}); + var countObj = {}; + var msg = {}; + var buffer = new ArrayBuffer(HelloArray.length); + + // Test reading immediately after writing. + os.writeBoolean(true); + do_check_eq(is.readBoolean(), true); + os.write8(4); + do_check_eq(is.read8(), 4); + os.write16(4); + do_check_eq(is.read16(), 4); + os.write16(1024); + do_check_eq(is.read16(), 1024); + os.write32(7); + do_check_eq(is.read32(), 7); + os.write32(LargeNum); + do_check_eq(is.read32(), LargeNum); + os.write64(LargeNum); + do_check_eq(is.read64(), LargeNum); + os.write64(1024); + do_check_eq(is.read64(), 1024); + os.write64(HugeNum); + do_check_eq(is.read64(), HugeNum); + os.writeFloat(2.5); + do_check_eq(is.readFloat(), 2.5); +// os.writeDouble(Math.SQRT2); +// do_check_eq(is.readDouble(), Math.SQRT2); + os.writeStringZ("Mozilla"); + do_check_eq(is.readCString(), "Mozilla"); + os.writeWStringZ("Gecko"); + do_check_eq(is.readString(), "Gecko"); + os.writeBytes(HelloStr, HelloStr.length); + do_check_eq(is.available(), HelloStr.length); + msg = is.readBytes(HelloStr.length); + do_check_eq(msg, HelloStr); + msg = null; + countObj.value = -1; + os.writeByteArray(HelloArray, HelloArray.length); + do_check_eq(is.available(), HelloStr.length); + msg = is.readByteArray(HelloStr.length) + do_check_eq(typeof msg, typeof HelloArray); + do_check_eq(msg.toSource(), HelloArray.toSource()); + do_check_eq(is.available(), 0); + os.writeByteArray(HelloArray, HelloArray.length); + do_check_eq(is.readArrayBuffer(buffer.byteLength, buffer), HelloArray.length); + do_check_eq([...(new Uint8Array(buffer))].toSource(), HelloArray.toSource()); + do_check_eq(is.available(), 0); + + // Test writing in one big chunk. + os.writeBoolean(true); + os.write8(4); + os.write16(4); + os.write16(1024); + os.write32(7); + os.write32(LargeNum); + os.write64(LargeNum); + os.write64(1024); + os.write64(HugeNum); + os.writeFloat(2.5); +// os.writeDouble(Math.SQRT2); + os.writeStringZ("Mozilla"); + os.writeWStringZ("Gecko"); + os.writeBytes(HelloStr, HelloStr.length); + os.writeByteArray(HelloArray, HelloArray.length); + // Available should not be zero after a long write like this. + do_check_neq(is.available(), 0); + + // Test reading in one big chunk. + do_check_eq(is.readBoolean(), true); + do_check_eq(is.read8(), 4); + do_check_eq(is.read16(), 4); + do_check_eq(is.read16(), 1024); + do_check_eq(is.read32(), 7); + do_check_eq(is.read32(), LargeNum); + do_check_eq(is.read64(), LargeNum); + do_check_eq(is.read64(), 1024); + do_check_eq(is.read64(), HugeNum); + do_check_eq(is.readFloat(), 2.5); +// do_check_eq(is.readDouble(), Math.SQRT2); + do_check_eq(is.readCString(), "Mozilla"); + do_check_eq(is.readString(), "Gecko"); + // Remember, we wrote HelloStr twice - once as a string, and then as an array. + do_check_eq(is.available(), HelloStr.length * 2); + msg = is.readBytes(HelloStr.length); + do_check_eq(msg, HelloStr); + msg = null; + countObj.value = -1; + do_check_eq(is.available(), HelloStr.length); + msg = is.readByteArray(HelloStr.length) + do_check_eq(typeof msg, typeof HelloArray); + do_check_eq(msg.toSource(), HelloArray.toSource()); + do_check_eq(is.available(), 0); + + // Test for invalid actions. + os.close(); + is.close(); + + try { + os.writeBoolean(false); + do_throw("Not reached!"); + } catch (e if (e instanceof Ci.nsIException && + e.result == Cr.NS_BASE_STREAM_CLOSED)) { + // do nothing + } + + try { + is.available(); + do_throw("Not reached!"); + } catch (e if (e instanceof Ci.nsIException && + e.result == Cr.NS_BASE_STREAM_CLOSED)) { + // do nothing + } + + try { + is.readBoolean(); + do_throw("Not reached!"); + } catch (e if (e instanceof Ci.nsIException && + e.result == Cr.NS_ERROR_FAILURE)) { + // do nothing + } +} + +function run_test() { + test_binary_streams(); +} diff --git a/xpcom/tests/unit/test_stringstream.js b/xpcom/tests/unit/test_stringstream.js new file mode 100644 index 000000000..60503a58f --- /dev/null +++ b/xpcom/tests/unit/test_stringstream.js @@ -0,0 +1,23 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +function run_test() +{ + var s = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + var body = "This is a test"; + s.setData(body, body.length); + do_check_eq(s.available(), body.length); + + var sis = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + sis.init(s); + + do_check_eq(sis.read(body.length), body); +} diff --git a/xpcom/tests/unit/test_symlinks.js b/xpcom/tests/unit/test_symlinks.js new file mode 100644 index 000000000..a615af8d5 --- /dev/null +++ b/xpcom/tests/unit/test_symlinks.js @@ -0,0 +1,144 @@ +const CWD = do_get_cwd(); + +const DIR_TARGET = "target"; +const DIR_LINK = "link"; +const DIR_LINK_LINK = "link_link"; +const FILE_TARGET = "target.txt"; +const FILE_LINK = "link.txt"; +const FILE_LINK_LINK = "link_link.txt"; + +const DOES_NOT_EXIST = "doesnotexist"; +const DANGLING_LINK = "dangling_link"; +const LOOP_LINK = "loop_link"; + +const nsIFile = Components.interfaces.nsIFile; + +var process; +function createSymLink(from, to) { + if (!process) { + var ln = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + ln.initWithPath("/bin/ln"); + + process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(ln); + } + + const args = ["-s", from, to]; + process.run(true, args, args.length); + do_check_eq(process.exitValue, 0); +} + +function makeSymLink(from, toName, relative) { + var to = from.parent; + to.append(toName); + + if (relative) { + createSymLink(from.leafName, to.path); + } + else { + createSymLink(from.path, to.path); + } + + do_check_true(to.isSymlink()); + + print("---"); + print(from.path); + print(to.path); + print(to.target); + + if (from.leafName != DOES_NOT_EXIST && from.isSymlink()) { + // XXXjag wish I could set followLinks to false so we'd just get + // the symlink's direct target instead of the final target. + do_check_eq(from.target, to.target); + } + else { + do_check_eq(from.path, to.target); + } + + return to; +} + +function setupTestDir(testDir, relative) { + var targetDir = testDir.clone(); + targetDir.append(DIR_TARGET); + + if (testDir.exists()) { + testDir.remove(true); + } + do_check_true(!testDir.exists()); + + testDir.create(nsIFile.DIRECTORY_TYPE, 0o777); + + targetDir.create(nsIFile.DIRECTORY_TYPE, 0o777); + + var targetFile = testDir.clone(); + targetFile.append(FILE_TARGET); + targetFile.create(nsIFile.NORMAL_FILE_TYPE, 0o666); + + var imaginary = testDir.clone(); + imaginary.append(DOES_NOT_EXIST); + + var loop = testDir.clone(); + loop.append(LOOP_LINK); + + var dirLink = makeSymLink(targetDir, DIR_LINK, relative); + var fileLink = makeSymLink(targetFile, FILE_LINK, relative); + + makeSymLink(dirLink, DIR_LINK_LINK, relative); + makeSymLink(fileLink, FILE_LINK_LINK, relative); + + makeSymLink(imaginary, DANGLING_LINK, relative); + + try { + makeSymLink(loop, LOOP_LINK, relative); + do_check_true(false); + } + catch (e) { + } +} + +function createSpaces(dirs, files, links) { + function longest(a, b) { + return a.length > b.length ? a : b; + } + return dirs.concat(files, links).reduce(longest, "").replace(/./g, " "); +} + +function testSymLinks(testDir, relative) { + setupTestDir(testDir, relative); + + const dirLinks = [DIR_LINK, DIR_LINK_LINK]; + const fileLinks = [FILE_LINK, FILE_LINK_LINK]; + const otherLinks = [DANGLING_LINK, LOOP_LINK]; + const dirs = [DIR_TARGET].concat(dirLinks); + const files = [FILE_TARGET].concat(fileLinks); + const links = otherLinks.concat(dirLinks, fileLinks); + + const spaces = createSpaces(dirs, files, links); + const bools = {false: " false", true: " true "}; + print(spaces + " dir file symlink"); + var dirEntries = testDir.directoryEntries; + while (dirEntries.hasMoreElements()) { + const file = dirEntries.getNext().QueryInterface(nsIFile); + const name = file.leafName; + print(name + spaces.substring(name.length) + bools[file.isDirectory()] + + bools[file.isFile()] + bools[file.isSymlink()]); + do_check_eq(file.isDirectory(), dirs.indexOf(name) != -1); + do_check_eq(file.isFile(), files.indexOf(name) != -1); + do_check_eq(file.isSymlink(), links.indexOf(name) != -1); + } +} + +function run_test() { + // Skip this test on Windows + if (mozinfo.os == "win") + return; + + var testDir = CWD; + testDir.append("test_symlinks"); + + testSymLinks(testDir, false); + testSymLinks(testDir, true); +} diff --git a/xpcom/tests/unit/test_systemInfo.js b/xpcom/tests/unit/test_systemInfo.js new file mode 100644 index 000000000..58fa8042c --- /dev/null +++ b/xpcom/tests/unit/test_systemInfo.js @@ -0,0 +1,20 @@ +/* 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/. */ + +function run_test() { + const PROPERTIES = ["name", "host", "arch", "version", "pagesize", + "pageshift", "memmapalign", "cpucount", "memsize"]; + let sysInfo = Components.classes["@mozilla.org/system-info;1"]. + getService(Components.interfaces.nsIPropertyBag2); + + PROPERTIES.forEach(function(aPropertyName) { + print("Testing property: " + aPropertyName); + let value = sysInfo.getProperty(aPropertyName); + do_check_true(!!value); + }); + + // This property must exist, but its value might be zero. + print("Testing property: umask") + do_check_eq(typeof sysInfo.getProperty("umask"), "number"); +} diff --git a/xpcom/tests/unit/test_versioncomparator.js b/xpcom/tests/unit/test_versioncomparator.js new file mode 100644 index 000000000..35f8f6eee --- /dev/null +++ b/xpcom/tests/unit/test_versioncomparator.js @@ -0,0 +1,59 @@ +// Versions to test listed in ascending order, none can be equal +var comparisons = [ + "0.9", + "0.9.1", + "1.0pre1", + "1.0pre2", + "1.0", + "1.1pre", + "1.1pre1a", + "1.1pre1", + "1.1pre10a", + "1.1pre10", + "1.1", + "1.1.0.1", + "1.1.1", + "1.1.*", + "1.*", + "2.0", + "2.1", + "3.0.-1", + "3.0" +]; + +// Every version in this list means the same version number +var equality = [ + "1.1pre", + "1.1pre0", + "1.0+" +]; + +function run_test() +{ + var vc = Components.classes["@mozilla.org/xpcom/version-comparator;1"] + .getService(Components.interfaces.nsIVersionComparator); + + for (var i = 0; i < comparisons.length; i++) { + for (var j = 0; j < comparisons.length; j++) { + var result = vc.compare(comparisons[i], comparisons[j]); + if (i == j) { + if (result != 0) + do_throw(comparisons[i] + " should be the same as itself"); + } + else if (i < j) { + if (!(result < 0)) + do_throw(comparisons[i] + " should be less than " + comparisons[j]); + } + else if (!(result > 0)) { + do_throw(comparisons[i] + " should be greater than " + comparisons[j]); + } + } + } + + for (i = 0; i < equality.length; i++) { + for (j = 0; j < equality.length; j++) { + if (vc.compare(equality[i], equality[j]) != 0) + do_throw(equality[i] + " should equal " + equality[j]); + } + } +} diff --git a/xpcom/tests/unit/test_windows_cmdline_file.js b/xpcom/tests/unit/test_windows_cmdline_file.js new file mode 100644 index 000000000..4a3a6cc5f --- /dev/null +++ b/xpcom/tests/unit/test_windows_cmdline_file.js @@ -0,0 +1,21 @@ +let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; +Cu.import("resource://gre/modules/Services.jsm"); + +let executableFile = Services.dirsvc.get("CurProcD", Ci.nsIFile); +executableFile.append("xpcshell.exe"); +function run_test() { + let quote = '"'; // Windows' cmd processor doesn't actually use single quotes. + for (let suffix of ["", " -osint", ` --blah "%PROGRAMFILES%"`]) { + let cmdline = quote + executableFile.path + quote + suffix; + do_print(`Testing with ${cmdline}`); + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin); + f.initWithCommandLine(cmdline); + Assert.equal(f.path, executableFile.path, "Should be able to recover executable path"); + } + + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin); + f.initWithCommandLine("%ComSpec% -c echo 'hi'"); + let cmd = Services.dirsvc.get("SysD", Ci.nsIFile); + cmd.append("cmd.exe"); + Assert.equal(f.path, cmd.path, "Should be able to replace env vars."); +} diff --git a/xpcom/tests/unit/test_windows_registry.js b/xpcom/tests/unit/test_windows_registry.js new file mode 100644 index 000000000..9a17678f8 --- /dev/null +++ b/xpcom/tests/unit/test_windows_registry.js @@ -0,0 +1,205 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +const Cr = Components.results; +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; +const CC = Components.Constructor; + +const nsIWindowsRegKey = Ci.nsIWindowsRegKey; +let regKeyComponent = Cc["@mozilla.org/windows-registry-key;1"]; + +function run_test() +{ + //* create a key structure in a spot that's normally writable (somewhere under HKCU). + let testKey = regKeyComponent.createInstance(nsIWindowsRegKey); + + // If it's already present because a previous test crashed or didn't clean up properly, clean it up first. + let keyName = BASE_PATH + "\\" + TESTDATA_KEYNAME; + setup_test_run(testKey, keyName); + + //* test that the write* functions write stuff + test_writing_functions(testKey); + + //* check that the valueCount/getValueName functions work for the values we just wrote + test_value_functions(testKey); + + //* check that the get* functions work for the values we just wrote. + test_reading_functions(testKey); + + //* check that the get* functions fail with the right exception codes if we ask for the wrong type or if the value name doesn't exist at all + test_invalidread_functions(testKey); + + //* check that creating/enumerating/deleting child keys works + test_childkey_functions(testKey); + + test_watching_functions(testKey); + + //* clean up + cleanup_test_run(testKey, keyName); +} + +function setup_test_run(testKey, keyName) +{ + do_print("Setup test run"); + try { + testKey.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, keyName, nsIWindowsRegKey.ACCESS_READ); + do_print("Test key exists. Needs cleanup."); + cleanup_test_run(testKey, keyName); + } + catch (e if (e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) + { + } + + testKey.create(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, keyName, nsIWindowsRegKey.ACCESS_ALL); +} + +function test_writing_functions(testKey) +{ + strictEqual(testKey.valueCount, 0); + + strictEqual(testKey.hasValue(TESTDATA_STRNAME), false); + testKey.writeStringValue(TESTDATA_STRNAME, TESTDATA_STRVALUE); + strictEqual(testKey.hasValue(TESTDATA_STRNAME), true); + + strictEqual(testKey.hasValue(TESTDATA_INTNAME), false); + testKey.writeIntValue(TESTDATA_INTNAME, TESTDATA_INTVALUE); + + strictEqual(testKey.hasValue(TESTDATA_INT64NAME), false); + testKey.writeInt64Value(TESTDATA_INT64NAME, TESTDATA_INT64VALUE); + + strictEqual(testKey.hasValue(TESTDATA_BINARYNAME), false); + testKey.writeBinaryValue(TESTDATA_BINARYNAME, TESTDATA_BINARYVALUE); +} + +function test_value_functions(testKey) +{ + strictEqual(testKey.valueCount, 4); + strictEqual(testKey.getValueName(0), TESTDATA_STRNAME); + strictEqual(testKey.getValueName(1), TESTDATA_INTNAME); + strictEqual(testKey.getValueName(2), TESTDATA_INT64NAME); + strictEqual(testKey.getValueName(3), TESTDATA_BINARYNAME); +} + +function test_reading_functions(testKey) +{ + strictEqual(testKey.getValueType(TESTDATA_STRNAME), nsIWindowsRegKey.TYPE_STRING); + strictEqual(testKey.readStringValue(TESTDATA_STRNAME), TESTDATA_STRVALUE); + + strictEqual(testKey.getValueType(TESTDATA_INTNAME), nsIWindowsRegKey.TYPE_INT); + strictEqual(testKey.readIntValue(TESTDATA_INTNAME), TESTDATA_INTVALUE); + + strictEqual(testKey.getValueType(TESTDATA_INT64NAME), nsIWindowsRegKey.TYPE_INT64); + strictEqual( testKey.readInt64Value(TESTDATA_INT64NAME), TESTDATA_INT64VALUE); + + strictEqual(testKey.getValueType(TESTDATA_BINARYNAME), nsIWindowsRegKey.TYPE_BINARY); + strictEqual( testKey.readBinaryValue(TESTDATA_BINARYNAME), TESTDATA_BINARYVALUE); +} + +function test_invalidread_functions(testKey) +{ + try { + testKey.readIntValue(TESTDATA_STRNAME); + do_throw("Reading an integer from a string registry value should throw."); + } catch (e if (e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + } + + try { + let val = testKey.readStringValue(TESTDATA_INTNAME); + do_throw("Reading an string from an Int registry value should throw." + val); + } catch (e if (e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + } + + try { + testKey.readStringValue(TESTDATA_INT64NAME); + do_throw("Reading an string from an Int64 registry value should throw."); + } catch (e if (e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + } + + try { + testKey.readStringValue(TESTDATA_BINARYNAME); + do_throw("Reading a string from an Binary registry value should throw."); + } catch (e if (e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + } + +} + +function test_childkey_functions(testKey) +{ + strictEqual(testKey.childCount, 0); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); + + let childKey = testKey.createChild(TESTDATA_CHILD_KEY, nsIWindowsRegKey.ACCESS_ALL); + childKey.close(); + + strictEqual(testKey.childCount, 1); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), true); + strictEqual(testKey.getChildName(0), TESTDATA_CHILD_KEY); + + childKey = testKey.openChild(TESTDATA_CHILD_KEY, nsIWindowsRegKey.ACCESS_ALL); + testKey.removeChild(TESTDATA_CHILD_KEY); + strictEqual(testKey.childCount, 0); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); +} + +function test_watching_functions(testKey) +{ + strictEqual(testKey.isWatching(), false); + strictEqual(testKey.hasChanged(), false); + + testKey.startWatching(true); + strictEqual(testKey.isWatching(), true); + + testKey.stopWatching(); + strictEqual(testKey.isWatching(), false); + + // Create a child key, and update a value + let childKey = testKey.createChild(TESTDATA_CHILD_KEY, nsIWindowsRegKey.ACCESS_ALL); + childKey.writeIntValue(TESTDATA_INTNAME, TESTDATA_INTVALUE); + + // Start a recursive watch, and update the child's value + testKey.startWatching(true); + strictEqual(testKey.isWatching(), true); + + childKey.writeIntValue(TESTDATA_INTNAME, 0); + strictEqual(testKey.hasChanged(), true); + testKey.stopWatching(); + strictEqual(testKey.isWatching(), false); + + childKey.removeValue(TESTDATA_INTNAME); + childKey.close(); + testKey.removeChild(TESTDATA_CHILD_KEY); +} + +function cleanup_test_run(testKey, keyName) +{ + do_print("Cleaning up test."); + + for (var i = 0; i < testKey.childCount; i++) { + testKey.removeChild(testKey.getChildName(i)); + } + testKey.close(); + + let baseKey = regKeyComponent.createInstance(nsIWindowsRegKey); + baseKey.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, BASE_PATH, nsIWindowsRegKey.ACCESS_ALL); + baseKey.removeChild(TESTDATA_KEYNAME); + baseKey.close(); +} + +// Test data used above. +const BASE_PATH = "SOFTWARE"; +const TESTDATA_KEYNAME = "TestRegXPC"; +const TESTDATA_STRNAME = "AString"; +const TESTDATA_STRVALUE = "The quick brown fox jumps over the lazy dog."; +const TESTDATA_INTNAME = "AnInteger"; +const TESTDATA_INTVALUE = 65536; +const TESTDATA_INT64NAME = "AnInt64"; +const TESTDATA_INT64VALUE = 9223372036854775807; +const TESTDATA_BINARYNAME = "ABinary"; +const TESTDATA_BINARYVALUE = "She sells seashells by the seashore"; +const TESTDATA_CHILD_KEY = "TestChildKey"; diff --git a/xpcom/tests/unit/test_windows_shortcut.js b/xpcom/tests/unit/test_windows_shortcut.js new file mode 100644 index 000000000..42cb023ff --- /dev/null +++ b/xpcom/tests/unit/test_windows_shortcut.js @@ -0,0 +1,279 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 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/. */ + +var Cr = Components.results; +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cu = Components.utils; +var CC = Components.Constructor; + +const LocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", "initWithPath"); + +Cu.import("resource://gre/modules/Services.jsm"); + +function run_test() +{ + // This test makes sense only on Windows, so skip it on other platforms + if ("nsILocalFileWin" in Ci + && do_get_cwd() instanceof Ci.nsILocalFileWin) { + + let tempDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile); + tempDir.append("shortcutTesting"); + tempDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o666); + + test_create_noargs(tempDir); + test_create_notarget(tempDir); + test_create_targetonly(tempDir); + test_create_normal(tempDir); + test_create_unicode(tempDir); + + test_update_noargs(tempDir); + test_update_notarget(tempDir); + test_update_targetonly(tempDir); + test_update_normal(tempDir); + test_update_unicode(tempDir); + } +} + +function test_create_noargs(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("shouldNeverExist.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + try + { + win.setShortcut(); + do_throw("Creating a shortcut with no args (no target) should throw"); + } + catch(e if (e instanceof Ci.nsIException + && e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) + { + + } +} + +function test_create_notarget(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("shouldNeverExist2.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + try + { + win.setShortcut(null, + do_get_cwd(), + "arg1 arg2", + "Shortcut with no target"); + do_throw("Creating a shortcut with no target should throw"); + } + catch(e if (e instanceof Ci.nsIException + && e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) + { + + } +} + +function test_create_targetonly(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)); +} + +function test_create_normal(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "Ordinary shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_create_unicode(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("ṩhогТϾừ†Target.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), // XXX: This should probably be a unicode dir + "ᾶṟǵ1 ᾶṟǵ2", + "ῧṋіḉѻₑ"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_update_noargs(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + win.setShortcut(); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_update_notarget(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + win.setShortcut(null, + do_get_profile(), + "arg3 arg4", + "An UPDATED shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(targetFile)) +} + +function test_update_targetonly(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + let newTargetFile = tempDir.clone(); + newTargetFile.append("shortcutTarget.exe"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + win.setShortcut(newTargetFile); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(newTargetFile)) +} + +function test_update_normal(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + let newTargetFile = tempDir.clone(); + newTargetFile.append("shortcutTarget.exe"); + newTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + win.setShortcut(newTargetFile, + do_get_profile(), + "arg3 arg4", + "An UPDATED shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(newTargetFile)) +} + +function test_update_unicode(tempDir) +{ + let shortcutFile = tempDir.clone(); + shortcutFile.append("createdShortcut.lnk"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let targetFile = tempDir.clone(); + targetFile.append("shortcutTarget.exe"); + targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + let win = shortcutFile.QueryInterface(Ci.nsILocalFileWin); + + win.setShortcut(targetFile, + do_get_cwd(), + "arg1 arg2", + "A sample shortcut"); + + let newTargetFile = tempDir.clone(); + newTargetFile.append("ṩhогТϾừ†Target.exe"); + shortcutFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + win.setShortcut(newTargetFile, + do_get_profile(), // XXX: This should probably be unicode + "ᾶṟǵ3 ᾶṟǵ4", + "A ῧṋіḉѻₑ shortcut"); + + let shortcutTarget = LocalFile(shortcutFile.target); + do_check_true(shortcutTarget.equals(newTargetFile)) +} diff --git a/xpcom/tests/unit/xpcomtest.manifest b/xpcom/tests/unit/xpcomtest.manifest new file mode 100644 index 000000000..43a4931c6 --- /dev/null +++ b/xpcom/tests/unit/xpcomtest.manifest @@ -0,0 +1 @@ +interfaces xpcomtest.xpt diff --git a/xpcom/tests/unit/xpcshell.ini b/xpcom/tests/unit/xpcshell.ini new file mode 100644 index 000000000..cf8d93627 --- /dev/null +++ b/xpcom/tests/unit/xpcshell.ini @@ -0,0 +1,79 @@ +[DEFAULT] +head = head_xpcom.js +tail = +support-files = + bug725015.manifest + compmgr_warnings.manifest + data/** + xpcomtest.xpt + xpcomtest.manifest +generated-files = + xpcomtest.xpt + +[test_bug121341.js] +[test_bug325418.js] +[test_bug332389.js] +[test_bug333505.js] +[test_bug364285-1.js] +# Bug 902073: test fails consistently on Android x86 +skip-if = os == "android" +[test_bug374754.js] +[test_bug476919.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_bug478086.js] +[test_bug656331.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_bug725015.js] +[test_debugger_malloc_size_of.js] +[test_compmgr_warnings.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_file_createUnique.js] +[test_file_equality.js] +[test_hidden_files.js] +[test_home.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_iniProcessor.js] +[test_ioutil.js] +[test_localfile.js] +[test_mac_bundle.js] +[test_nsIMutableArray.js] +[test_nsIProcess.js] +skip-if = os == "win" || os == "linux" # bug 582821, bug 1325609 +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_nsIProcess_stress.js] +skip-if = os == "win" # bug 676412, test isn't needed on windows and runs really slowly +[test_pipe.js] +[test_process_directives.js] +skip-if = os == "android" +[test_process_directives_child.js] +skip-if = os == "android" +[test_storagestream.js] +[test_streams.js] +[test_seek_multiplex.js] +[test_stringstream.js] +[test_symlinks.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_systemInfo.js] +# Bug 902081: test fails consistently on Android 2.2, passes on 4.0 +skip-if = os == "android" +[test_versioncomparator.js] +[test_comp_no_aslr.js] +skip-if = os != "win" +[test_windows_shortcut.js] +skip-if = os != "win" +[test_windows_cmdline_file.js] +skip-if = os != "win" +[test_bug745466.js] +skip-if = os == "win" +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_file_renameTo.js] +[test_notxpcom_scriptable.js] +[test_windows_registry.js] +skip-if = os != "win" diff --git a/xpcom/tests/windows/TestCOM.cpp b/xpcom/tests/windows/TestCOM.cpp new file mode 100644 index 000000000..9f0b9854c --- /dev/null +++ b/xpcom/tests/windows/TestCOM.cpp @@ -0,0 +1,158 @@ +/* -*- 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 <windows.h> +#include <unknwn.h> +#include <stdio.h> +#include "nsISupports.h" +#include "nsIFactory.h" + +// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN +#include <unknwn.h> + +#include "gtest/gtest.h" + +// {5846BA30-B856-11d1-A98A-00805F8A7AC4} +#define NS_ITEST_COM_IID \ +{ 0x5846ba30, 0xb856, 0x11d1, \ + { 0xa9, 0x8a, 0x0, 0x80, 0x5f, 0x8a, 0x7a, 0xc4 } } + +class nsITestCom: public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEST_COM_IID) + NS_IMETHOD Test() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestCom, NS_ITEST_COM_IID) + +/* + * nsTestCom + */ + +class nsTestCom final : public nsITestCom { + NS_DECL_ISUPPORTS + +public: + nsTestCom() { + } + + NS_IMETHOD Test() { + return NS_OK; + } + + static int sDestructions; + +private: + ~nsTestCom() { + sDestructions++; + } +}; + +int nsTestCom::sDestructions; + +NS_IMPL_QUERY_INTERFACE(nsTestCom, nsITestCom) + +MozExternalRefCountType nsTestCom::AddRef() +{ + nsrefcnt res = ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "nsTestCom", sizeof(*this)); + return res; +} + +MozExternalRefCountType nsTestCom::Release() +{ + nsrefcnt res = --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "nsTestCom"); + if (res == 0) { + delete this; + } + return res; +} + +class nsTestComFactory final : public nsIFactory { + ~nsTestComFactory() { sDestructions++; } + NS_DECL_ISUPPORTS +public: + nsTestComFactory() { + } + + NS_IMETHOD CreateInstance(nsISupports *aOuter, + const nsIID &aIID, + void **aResult); + + NS_IMETHOD LockFactory(bool aLock) { + return NS_OK; + } + + static int sDestructions; +}; + +int nsTestComFactory::sDestructions; + +NS_IMPL_ISUPPORTS(nsTestComFactory, nsIFactory) + +nsresult nsTestComFactory::CreateInstance(nsISupports *aOuter, + const nsIID &aIID, + void **aResult) +{ + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + + nsTestCom *t = new nsTestCom(); + + if (t == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(t); + nsresult res = t->QueryInterface(aIID, aResult); + NS_RELEASE(t); + + return res; +} + +TEST(TestCOM, WindowsInterop) +{ + nsTestComFactory *inst = new nsTestComFactory(); + + // Test we can QI nsIFactory to an IClassFactory. + IClassFactory *iFactory = nullptr; + nsresult rv = inst->QueryInterface(NS_GET_IID(nsIFactory), + (void **) &iFactory); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + ASSERT_TRUE(iFactory); + + // Test we can CreateInstance with an IUnknown. + IUnknown *iUnknown = nullptr; + + HRESULT hr = iFactory->LockServer(TRUE); + ASSERT_TRUE(SUCCEEDED(hr)); + hr = iFactory->CreateInstance(nullptr, IID_IUnknown, (void **) &iUnknown); + ASSERT_TRUE(SUCCEEDED(hr)); + ASSERT_TRUE(iUnknown); + hr = iFactory->LockServer(FALSE); + ASSERT_TRUE(SUCCEEDED(hr)); + + // Test we can QI an IUnknown to nsITestCom. + nsITestCom *iTestCom = nullptr; + GUID testGUID = NS_ITEST_COM_IID; + hr = iUnknown->QueryInterface(testGUID, + (void **) &iTestCom); + ASSERT_TRUE(SUCCEEDED(hr)); + ASSERT_TRUE(iTestCom); + + // Make sure we can call our test function (and the pointer is valid). + rv = iTestCom->Test(); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + iUnknown->Release(); + iTestCom->Release(); + iFactory->Release(); + + ASSERT_EQ(nsTestComFactory::sDestructions, 1); + ASSERT_EQ(nsTestCom::sDestructions, 1); +} diff --git a/xpcom/tests/windows/TestHelloXPLoop.cpp b/xpcom/tests/windows/TestHelloXPLoop.cpp new file mode 100644 index 000000000..ffb4442e0 --- /dev/null +++ b/xpcom/tests/windows/TestHelloXPLoop.cpp @@ -0,0 +1,144 @@ +/* -*- 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 "nsIServiceManager.h" +#include "nsCOMPtr.h" +#include "nsCNativeApp.h" +#include "nsIEventLoop.h" +#include <windows.h> + +static NS_DEFINE_CID(kNativeAppCID, NS_NATIVE_APP_CID); + +LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam); + +void ErrorBox(LPSTR text) +{ + MessageBox(nullptr, text, "XP Event Loop", MB_OK | MB_ICONSTOP); +} + +void InfoBox(LPSTR text) +{ + MessageBox(nullptr, text, "XP Event Loop", MB_OK | MB_ICONINFORMATION); +} + +int WINAPI WinMain(HINSTANCE inst, + HINSTANCE prevInstance, + LPSTR lpszCmdLine, + int nShowCmd) +{ + char* lpszAppName = "HelloWorld"; + HWND wnd; + WNDCLASSEX wndclass; + int retCode; + + { // Needed to scope all nsCOMPtr within XPCOM Init and Shutdown + nsresult rv; + nsCOMPtr<nsIServiceManager> servMan; + rv = NS_InitXPCOM2(getter_AddRefs(servMan), nullptr, nullptr); + if(NS_FAILED(rv)) + { + ErrorBox("Failed to initialize xpcom."); + return -1; + } + + nsCOMPtr<nsIComponentRegistrar> registrar = do_QueryInterface(servMan); + NS_ASSERTION(registrar, "Null nsIComponentRegistrar"); + registrar->AutoRegister(nullptr); + + nsCOMPtr<nsINativeApp> nativeAppService(do_GetService(kNativeAppCID, &rv)); + + if(NS_FAILED(rv)) + { + ErrorBox("Failed to get nativeAppService"); + return -1; + } + wndclass.cbSize = sizeof(wndclass); + wndclass.style = CS_HREDRAW | CS_VREDRAW; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = inst; + wndclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION); + wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW); + wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); + wndclass.lpszMenuName = nullptr; + wndclass.lpszClassName = lpszAppName; + wndclass.hIconSm = LoadIcon(nullptr, IDI_APPLICATION); + + RegisterClassEx(&wndclass) ; + + wnd = CreateWindow(lpszAppName, "The Hello World", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, inst, nullptr); + + ShowWindow(wnd, nShowCmd); + UpdateWindow(wnd); + + nsCOMPtr<nsIEventLoop> eventLoop; + + if(NS_FAILED(nativeAppService->CreateEventLoop(L"_MainLoop", + nsEventLoopTypes::MainAppLoop, getter_AddRefs(eventLoop)))) + { + ErrorBox("Failed to create event Loop"); + return 0; + } + + eventLoop->Run(nullptr, nullptr, nullptr, &retCode); + eventLoop = nullptr; // Clear out before Shutting down XPCOM + + InfoBox("Hello World app is out of loop"); + } + NS_ShutdownXPCOM(nullptr); + InfoBox("Hello World app is exiting"); + return retCode; +} + +LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HDC hdc; + PAINTSTRUCT ps; + RECT rect; + + switch(msg) + { + case WM_PAINT: + hdc = BeginPaint(wnd, &ps); + + GetClientRect(wnd, &rect); + + DrawText(hdc, "Hello, XP Event Loop!", -1, &rect, + DT_SINGLELINE | DT_CENTER | DT_VCENTER); + + EndPaint(wnd, &ps); + return 0; + + case WM_DESTROY: + { + nsresult rv; + nsCOMPtr<nsINativeApp> nativeAppService = + do_GetService(kNativeAppCID, &rv); + if(NS_FAILED(rv)) + { + ErrorBox("Could not get NativeAppService"); + return 0; + } + nsCOMPtr<nsIEventLoop> eventLoop; + + if(NS_FAILED(nativeAppService->FindEventLoop(L"_MainLoop", + getter_AddRefs(eventLoop)))) + { + ErrorBox("Failed to find event Loop"); + return 0; + } + eventLoop->Exit(0); + } + return 0; + } + + return DefWindowProc(wnd, msg, wParam, lParam); +} diff --git a/xpcom/tests/windows/TestNTFSPermissions.cpp b/xpcom/tests/windows/TestNTFSPermissions.cpp new file mode 100644 index 000000000..062a9e650 --- /dev/null +++ b/xpcom/tests/windows/TestNTFSPermissions.cpp @@ -0,0 +1,286 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +/* + * Test for NTFS File Permissions being correctly changed to match the new + * directory upon moving a file. (Bug 224692.) + */ + +#include "../TestHarness.h" +#include "nsEmbedString.h" +#include "nsIFile.h" +#include <windows.h> +#include <aclapi.h> + +#define BUFFSIZE 512 + + + +nsresult TestPermissions() +{ + + nsresult rv; // Return value + + // File variables + HANDLE tempFileHandle; + nsCOMPtr<nsIFile> tempFile; + nsCOMPtr<nsIFile> tempDirectory1; + nsCOMPtr<nsIFile> tempDirectory2; + WCHAR filePath[MAX_PATH]; + WCHAR dir1Path[MAX_PATH]; + WCHAR dir2Path[MAX_PATH]; + + // Security variables + DWORD result; + PSID everyoneSID = nullptr, adminSID = nullptr; + PACL dirACL = nullptr, fileACL = nullptr; + PSECURITY_DESCRIPTOR dirSD = nullptr, fileSD = nullptr; + EXPLICIT_ACCESS ea[2]; + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = + SECURITY_WORLD_SID_AUTHORITY; + SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; + SECURITY_ATTRIBUTES sa; + TRUSTEE everyoneTrustee; + ACCESS_MASK everyoneRights; + + // Create a well-known SID for the Everyone group. + if(!AllocateAndInitializeSid(&SIDAuthWorld, 1, + SECURITY_WORLD_RID, + 0, 0, 0, 0, 0, 0, 0, + &everyoneSID)) + { + fail("NTFS Permissions: AllocateAndInitializeSid Error"); + return NS_ERROR_FAILURE; + } + + // Create a SID for the Administrators group. + if(! AllocateAndInitializeSid(&SIDAuthNT, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &adminSID)) + { + fail("NTFS Permissions: AllocateAndInitializeSid Error"); + return NS_ERROR_FAILURE; + } + + // Initialize an EXPLICIT_ACCESS structure for an ACE. + // The ACE will allow Everyone read access to the directory. + ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS)); + ea[0].grfAccessPermissions = GENERIC_READ; + ea[0].grfAccessMode = SET_ACCESS; + ea[0].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea[0].Trustee.ptstrName = (LPTSTR) everyoneSID; + + // Initialize an EXPLICIT_ACCESS structure for an ACE. + // The ACE will allow the Administrators group full access + ea[1].grfAccessPermissions = GENERIC_ALL | STANDARD_RIGHTS_ALL; + ea[1].grfAccessMode = SET_ACCESS; + ea[1].grfInheritance= SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; + ea[1].Trustee.ptstrName = (LPTSTR) adminSID; + + // Create a new ACL that contains the new ACEs. + result = SetEntriesInAcl(2, ea, nullptr, &dirACL); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: SetEntriesInAcl Error"); + return NS_ERROR_FAILURE; + } + + // Initialize a security descriptor. + dirSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, + SECURITY_DESCRIPTOR_MIN_LENGTH); + if (nullptr == dirSD) + { + fail("NTFS Permissions: LocalAlloc Error"); + return NS_ERROR_FAILURE; + } + + if (!InitializeSecurityDescriptor(dirSD, + SECURITY_DESCRIPTOR_REVISION)) + { + fail("NTFS Permissions: InitializeSecurityDescriptor Error"); + return NS_ERROR_FAILURE; + } + + // Add the ACL to the security descriptor. + if (!SetSecurityDescriptorDacl(dirSD, true, dirACL, false)) + { + fail("NTFS Permissions: SetSecurityDescriptorDacl Error"); + return NS_ERROR_FAILURE; + } + + // Initialize a security attributes structure. + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = dirSD; + sa.bInheritHandle = false; + + // Create and open first temporary directory + if(!CreateDirectoryW(L".\\NTFSPERMTEMP1", &sa)) + { + fail("NTFS Permissions: Creating Temporary Directory"); + return NS_ERROR_FAILURE; + } + + GetFullPathNameW((LPCWSTR)L".\\NTFSPERMTEMP1", MAX_PATH, dir1Path, + nullptr); + + rv = NS_NewLocalFile(nsEmbedString(dir1Path), false, + getter_AddRefs(tempDirectory1)); + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Opening Temporary Directory 1"); + return rv; + } + + + // Create and open temporary file + tempFileHandle = CreateFileW(L".\\NTFSPERMTEMP1\\NTFSPerm.tmp", + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, //default security + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if(tempFileHandle == INVALID_HANDLE_VALUE) + { + fail("NTFS Permissions: Creating Temporary File"); + return NS_ERROR_FAILURE; + } + + CloseHandle(tempFileHandle); + + GetFullPathNameW((LPCWSTR)L".\\NTFSPERMTEMP1\\NTFSPerm.tmp", + MAX_PATH, filePath, nullptr); + + rv = NS_NewLocalFile(nsEmbedString(filePath), false, + getter_AddRefs(tempFile)); + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Opening Temporary File"); + return rv; + } + + // Update Everyone Explict_Acess to full access. + ea[0].grfAccessPermissions = GENERIC_ALL | STANDARD_RIGHTS_ALL; + + // Update the ACL to contain the new ACEs. + result = SetEntriesInAcl(2, ea, nullptr, &dirACL); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: SetEntriesInAcl 2 Error"); + return NS_ERROR_FAILURE; + } + + // Add the new ACL to the security descriptor. + if (!SetSecurityDescriptorDacl(dirSD, true, dirACL, false)) + { + fail("NTFS Permissions: SetSecurityDescriptorDacl 2 Error"); + return NS_ERROR_FAILURE; + } + + // Create and open second temporary directory + if(!CreateDirectoryW(L".\\NTFSPERMTEMP2", &sa)) + { + fail("NTFS Permissions: Creating Temporary Directory 2"); + return NS_ERROR_FAILURE; + } + + GetFullPathNameW((LPCWSTR)L".\\NTFSPERMTEMP2", MAX_PATH, dir2Path, + nullptr); + + rv = NS_NewLocalFile(nsEmbedString(dir2Path), false, + getter_AddRefs(tempDirectory2)); + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Opening Temporary Directory 2"); + return rv; + } + + // Move the file. + rv = tempFile->MoveTo(tempDirectory2, EmptyString()); + + if (NS_FAILED(rv)) + { + fail("NTFS Permissions: Moving"); + return rv; + } + + // Access the ACL of the file + result = GetNamedSecurityInfoW(L".\\NTFSPERMTEMP2\\NTFSPerm.tmp", + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + UNPROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, &fileACL, nullptr, + &fileSD); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: GetNamedSecurityDescriptor Error"); + return NS_ERROR_FAILURE; + } + + // Build a trustee representing "Everyone" + BuildTrusteeWithSid(&everyoneTrustee, everyoneSID); + + // Get Everyone's effective rights. + result = GetEffectiveRightsFromAcl(fileACL, &everyoneTrustee, + &everyoneRights); + if (ERROR_SUCCESS != result) + { + fail("NTFS Permissions: GetEffectiveRightsFromAcl Error"); + return NS_ERROR_FAILURE; + } + + // Check for delete access, which we won't have unless permissions have + // updated + if((everyoneRights & DELETE) == (DELETE)) + { + passed("NTFS Permissions Test"); + rv = NS_OK; + } + else + { + fail("NTFS Permissions: Access check."); + rv = NS_ERROR_FAILURE; + } + + // Cleanup + if (everyoneSID) + FreeSid(everyoneSID); + if (adminSID) + FreeSid(adminSID); + if (dirACL) + LocalFree(dirACL); + if (dirSD) + LocalFree(dirSD); + if(fileACL) + LocalFree(fileACL); + + tempDirectory1->Remove(true); + tempDirectory2->Remove(true); + + return rv; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("NTFSPermissionsTests"); // name for tests being run + if (xpcom.failed()) + return 1; + + int rv = 0; + + if(NS_FAILED(TestPermissions())) + rv = 1; + + return rv; + +} + diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp new file mode 100644 index 000000000..b826d4f20 --- /dev/null +++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp @@ -0,0 +1,193 @@ +/* -*- 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 <windows.h> +#include <winnetwk.h> + +#include "mozilla/FileUtilsWin.h" +#include "mozilla/DebugOnly.h" +#include "nsCRTGlue.h" + +#include "gtest/gtest.h" + +class DriveMapping +{ +public: + DriveMapping(const nsAString& aRemoteUNCPath); + ~DriveMapping(); + + bool + Init(); + bool + ChangeDriveLetter(); + wchar_t + GetDriveLetter() { return mDriveLetter; } + +private: + bool + DoMapping(); + void + Disconnect(wchar_t aDriveLetter); + + wchar_t mDriveLetter; + nsString mRemoteUNCPath; +}; + +DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath) + : mDriveLetter(0) + , mRemoteUNCPath(aRemoteUNCPath) +{ +} + +bool +DriveMapping::Init() +{ + if (mDriveLetter) { + return false; + } + return DoMapping(); +} + +bool +DriveMapping::DoMapping() +{ + wchar_t drvTemplate[] = L" :"; + NETRESOURCEW netRes = {0}; + netRes.dwType = RESOURCETYPE_DISK; + netRes.lpLocalName = drvTemplate; + netRes.lpRemoteName = reinterpret_cast<wchar_t*>(mRemoteUNCPath.BeginWriting()); + wchar_t driveLetter = L'D'; + DWORD result = NO_ERROR; + do { + drvTemplate[0] = driveLetter; + result = WNetAddConnection2W(&netRes, nullptr, nullptr, CONNECT_TEMPORARY); + } while (result == ERROR_ALREADY_ASSIGNED && ++driveLetter <= L'Z'); + if (result != NO_ERROR) { + return false; + } + mDriveLetter = driveLetter; + return true; +} + +bool +DriveMapping::ChangeDriveLetter() +{ + wchar_t prevDriveLetter = mDriveLetter; + bool result = DoMapping(); + MOZ_RELEASE_ASSERT(mDriveLetter != prevDriveLetter); + if (result && prevDriveLetter) { + Disconnect(prevDriveLetter); + } + return result; +} + +void +DriveMapping::Disconnect(wchar_t aDriveLetter) +{ + wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'}; + DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE); + MOZ_RELEASE_ASSERT(result == NO_ERROR); +} + +DriveMapping::~DriveMapping() +{ + if (mDriveLetter) { + Disconnect(mDriveLetter); + } +} + +bool +DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath) +{ + const wchar_t drvTpl[] = {aDriveLetter, L':', L'\0'}; + aNtPath.SetLength(MAX_PATH); + DWORD pathLen; + while (true) { + pathLen = QueryDosDeviceW(drvTpl, reinterpret_cast<wchar_t*>(aNtPath.BeginWriting()), aNtPath.Length()); + if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + aNtPath.SetLength(aNtPath.Length() * 2); + } + if (!pathLen) { + return false; + } + // aNtPath contains embedded NULLs, so we need to figure out the real length + // via wcslen. + aNtPath.SetLength(NS_strlen(aNtPath.BeginReading())); + return true; +} + +bool +TestNtPathToDosPath(const wchar_t* aNtPath, + const wchar_t* aExpectedDosPath) +{ + nsAutoString output; + bool result = mozilla::NtPathToDosPath(nsDependentString(aNtPath), output); + return result && output == reinterpret_cast<const nsAString::char_type *>(aExpectedDosPath); +} + +TEST(NtPathToDosPath, Tests) +{ + nsAutoString cDrive; + ASSERT_TRUE(DriveToNtPath(L'C', cDrive)); + + // empty string + EXPECT_TRUE(TestNtPathToDosPath(L"", L"")); + + // non-existent device, must fail + EXPECT_FALSE(TestNtPathToDosPath(L"\\Device\\ThisDeviceDoesNotExist\\Foo", nullptr)); + + // base case + nsAutoString testPath(cDrive); + testPath.Append(L"\\Program Files"); + EXPECT_TRUE(TestNtPathToDosPath(testPath.get(), L"C:\\Program Files")); + + // short filename + nsAutoString ntShortName(cDrive); + ntShortName.Append(L"\\progra~1"); + EXPECT_TRUE(TestNtPathToDosPath(ntShortName.get(), L"C:\\Program Files")); + + // drive letters as symbolic links (NtCreateFile uses these) + EXPECT_TRUE(TestNtPathToDosPath(L"\\??\\C:\\Foo", L"C:\\Foo")); + + // other symbolic links (should fail) + EXPECT_FALSE(TestNtPathToDosPath(L"\\??\\MountPointManager", nullptr)); + + // socket (should fail) + EXPECT_FALSE(TestNtPathToDosPath(L"\\Device\\Afd\\Endpoint", nullptr)); + + // UNC path (using MUP) + EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\Mup\\127.0.0.1\\C$", + L"\\\\127.0.0.1\\C$")); + + // UNC path (using LanmanRedirector) + EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\LanmanRedirector\\127.0.0.1\\C$", + L"\\\\127.0.0.1\\C$")); + + DriveMapping drvMapping(NS_LITERAL_STRING("\\\\127.0.0.1\\C$")); + // Only run these tests if we were able to map; some machines don't have perms + if (drvMapping.Init()) { + wchar_t expected[] = L" :\\"; + expected[0] = drvMapping.GetDriveLetter(); + nsAutoString networkPath; + ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)); + + networkPath += u"\\"; + EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected)); + + // NtPathToDosPath must correctly handle paths whose drive letter mapping has + // changed. We need to test this because the APIs called by NtPathToDosPath + // return different info if this has happened. + ASSERT_TRUE(drvMapping.ChangeDriveLetter()); + + expected[0] = drvMapping.GetDriveLetter(); + ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)); + + networkPath += u"\\"; + EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected)); + } +} diff --git a/xpcom/tests/windows/TestWinFileAttribs.cpp b/xpcom/tests/windows/TestWinFileAttribs.cpp new file mode 100644 index 000000000..56fbcbdea --- /dev/null +++ b/xpcom/tests/windows/TestWinFileAttribs.cpp @@ -0,0 +1,173 @@ +/* -*- 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/. */ + +/* + * Test: + */ + +#include "../TestHarness.h" +#include "nsEmbedString.h" +#include "nsILocalFileWin.h" +#include <windows.h> + +#define BUFFSIZE 512 + +nsresult TestWinAttribs() +{ + + nsresult rv; + + // File variables + HANDLE hIndexed; + nsCOMPtr<nsIFile> localFile; + WCHAR filePath[MAX_PATH]; + + // Create and open temporary file + hIndexed = CreateFileW(L".\\indexbit.txt", + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, //FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, not supported by cf + nullptr); + + if(hIndexed == INVALID_HANDLE_VALUE) + { + fail("Test Win Attribs: Creating Test File"); + return NS_ERROR_FAILURE; + } + + CloseHandle(hIndexed); + + GetFullPathNameW((LPCWSTR)L".\\indexbit.txt", + MAX_PATH, filePath, nullptr); + + //wprintf(filePath); + //wprintf(L"\n"); + + rv = NS_NewLocalFile(nsEmbedString(filePath), false, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: Opening Test File"); + DeleteFileW(filePath); + return rv; + } + + nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(localFile)); + + DWORD dwAttrs = GetFileAttributesW(filePath); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) + { + fail("Test Win Attribs: GetFileAttributesW - couldn't find our temp file."); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + uint32_t attribs = 0; + rv = localFileWin->GetFileAttributesWin(&attribs); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to GET attributes. (1)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (attribs & nsILocalFileWin::WFA_SEARCH_INDEXED) + { + fail("Test Win Attribs: GetFileAttributesWin attributed did not match. (2)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + rv = localFileWin->GetFileAttributesWin(&attribs); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to GET attributes. (3)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (!(attribs & nsILocalFileWin::WFA_SEARCH_INDEXED)) + { + fail("Test Win Attribs: GetFileAttributesWin attributed did not match. (4)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + attribs = nsILocalFileWin::WFA_SEARCH_INDEXED; + rv = localFileWin->SetFileAttributesWin(attribs); + + dwAttrs = GetFileAttributesW(filePath); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to SET attributes. (5)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) + { + fail("Test Win Attribs: SetFileAttributesWin attributed did not match. (6)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + SetFileAttributesW(filePath, dwAttrs); + + attribs = 0; + rv = localFileWin->SetFileAttributesWin(attribs); + + dwAttrs = GetFileAttributesW(filePath); + + if (NS_FAILED(rv)) + { + fail("Test Win Attribs: GetFileAttributesWin failed to SET attributes. (7)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) + { + fail("Test Win Attribs: SetFileAttributesWin attributed did not match. (8)"); + DeleteFileW(filePath); + return NS_ERROR_FAILURE; + } + + DeleteFileW(filePath); + + passed("Test Win Attribs: passed tests."); + + return NS_OK; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("WinFileAttributes"); + if (xpcom.failed()) + return 1; + + int rv = 0; + + if(NS_FAILED(TestWinAttribs())) + rv = 1; + + return rv; + +} + diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build new file mode 100644 index 000000000..21b5eb2f7 --- /dev/null +++ b/xpcom/tests/windows/moz.build @@ -0,0 +1,16 @@ +# -*- 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 += [ + 'TestCOM.cpp', + 'TestNtPathToDosPath.cpp', +] + +OS_LIBS += [ + 'mpr', +] + +FINAL_LIBRARY = 'xul-gtest' |