summaryrefslogtreecommitdiffstats
path: root/xpcom/tests
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /xpcom/tests
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpcom/tests')
-rw-r--r--xpcom/tests/Makefile.in13
-rw-r--r--xpcom/tests/NotXPCOMTest.idl23
-rw-r--r--xpcom/tests/RegFactory.cpp130
-rw-r--r--xpcom/tests/SizeTest01.cpp107
-rw-r--r--xpcom/tests/SizeTest02.cpp89
-rw-r--r--xpcom/tests/SizeTest03.cpp97
-rw-r--r--xpcom/tests/SizeTest04.cpp68
-rw-r--r--xpcom/tests/SizeTest05.cpp74
-rw-r--r--xpcom/tests/SizeTest06.cpp150
-rw-r--r--xpcom/tests/TestArguments.cpp25
-rw-r--r--xpcom/tests/TestBlockingProcess.cpp8
-rw-r--r--xpcom/tests/TestHarness.h292
-rw-r--r--xpcom/tests/TestPRIntN.cpp33
-rw-r--r--xpcom/tests/TestQuickReturn.cpp8
-rw-r--r--xpcom/tests/TestShutdown.cpp41
-rw-r--r--xpcom/tests/TestStackCrawl.cpp11
-rw-r--r--xpcom/tests/TestStreamUtils.cpp74
-rw-r--r--xpcom/tests/TestUnicodeArguments.cpp77
-rw-r--r--xpcom/tests/TestWinReg.js57
-rw-r--r--xpcom/tests/TestingAtomList.h6
-rw-r--r--xpcom/tests/bug656331_component/TestComponent.cpp32
-rw-r--r--xpcom/tests/bug656331_component/bug656331.manifest2
-rw-r--r--xpcom/tests/bug656331_component/moz.build26
-rw-r--r--xpcom/tests/component/TestComponent.cpp44
-rw-r--r--xpcom/tests/component/moz.build26
-rw-r--r--xpcom/tests/component/testcomponent.manifest4
-rw-r--r--xpcom/tests/component_no_aslr/Makefile.in8
-rw-r--r--xpcom/tests/component_no_aslr/TestComponent.cpp33
-rw-r--r--xpcom/tests/component_no_aslr/moz.build26
-rw-r--r--xpcom/tests/component_no_aslr/testcompnoaslr.manifest2
-rw-r--r--xpcom/tests/external/TestMinStringAPI.cpp1009
-rw-r--r--xpcom/tests/external/moz.build9
-rw-r--r--xpcom/tests/gtest/Helpers.cpp133
-rw-r--r--xpcom/tests/gtest/Helpers.h73
-rw-r--r--xpcom/tests/gtest/TestAllocReplacement.cpp175
-rw-r--r--xpcom/tests/gtest/TestAtoms.cpp153
-rw-r--r--xpcom/tests/gtest/TestAutoPtr.cpp220
-rw-r--r--xpcom/tests/gtest/TestAutoRef.cpp56
-rw-r--r--xpcom/tests/gtest/TestBase64.cpp291
-rw-r--r--xpcom/tests/gtest/TestCOMArray.cpp286
-rw-r--r--xpcom/tests/gtest/TestCOMPtr.cpp466
-rw-r--r--xpcom/tests/gtest/TestCOMPtrEq.cpp79
-rw-r--r--xpcom/tests/gtest/TestCRT.cpp86
-rw-r--r--xpcom/tests/gtest/TestCallTemplates.cpp104
-rw-r--r--xpcom/tests/gtest/TestCloneInputStream.cpp200
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetector.cpp322
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp170
-rw-r--r--xpcom/tests/gtest/TestEncoding.cpp109
-rw-r--r--xpcom/tests/gtest/TestEscapeURL.cpp69
-rw-r--r--xpcom/tests/gtest/TestExpirationTracker.cpp185
-rw-r--r--xpcom/tests/gtest/TestFile.cpp477
-rw-r--r--xpcom/tests/gtest/TestHashtables.cpp435
-rw-r--r--xpcom/tests/gtest/TestID.cpp36
-rw-r--r--xpcom/tests/gtest/TestNSPRLogModulesParser.cpp111
-rw-r--r--xpcom/tests/gtest/TestNsRefPtr.cpp479
-rw-r--r--xpcom/tests/gtest/TestObserverArray.cpp167
-rw-r--r--xpcom/tests/gtest/TestObserverService.cpp288
-rw-r--r--xpcom/tests/gtest/TestPLDHash.cpp368
-rw-r--r--xpcom/tests/gtest/TestPipes.cpp1097
-rw-r--r--xpcom/tests/gtest/TestPriorityQueue.cpp76
-rw-r--r--xpcom/tests/gtest/TestRacingServiceManager.cpp300
-rw-r--r--xpcom/tests/gtest/TestSTLWrappers.cpp78
-rw-r--r--xpcom/tests/gtest/TestSlicedInputStream.cpp266
-rw-r--r--xpcom/tests/gtest/TestSnappyStreams.cpp191
-rw-r--r--xpcom/tests/gtest/TestStateWatching.cpp46
-rw-r--r--xpcom/tests/gtest/TestStorageStream.cpp131
-rw-r--r--xpcom/tests/gtest/TestStringStream.cpp65
-rw-r--r--xpcom/tests/gtest/TestStrings.cpp982
-rw-r--r--xpcom/tests/gtest/TestSynchronization.cpp313
-rw-r--r--xpcom/tests/gtest/TestTArray.cpp206
-rw-r--r--xpcom/tests/gtest/TestTArray2.cpp1033
-rw-r--r--xpcom/tests/gtest/TestTextFormatter.cpp34
-rw-r--r--xpcom/tests/gtest/TestThreadPool.cpp124
-rw-r--r--xpcom/tests/gtest/TestThreadPoolListener.cpp209
-rw-r--r--xpcom/tests/gtest/TestThreadUtils.cpp378
-rw-r--r--xpcom/tests/gtest/TestThreads.cpp275
-rw-r--r--xpcom/tests/gtest/TestTimeStamp.cpp70
-rw-r--r--xpcom/tests/gtest/TestTimers.cpp437
-rw-r--r--xpcom/tests/gtest/TestTokenizer.cpp1134
-rw-r--r--xpcom/tests/gtest/TestUTF.cpp191
-rw-r--r--xpcom/tests/gtest/TestXPIDLString.cpp24
-rw-r--r--xpcom/tests/gtest/UTFStrings.h112
-rw-r--r--xpcom/tests/gtest/moz.build77
-rw-r--r--xpcom/tests/moz.build51
-rw-r--r--xpcom/tests/resources.h19
-rw-r--r--xpcom/tests/test.properties14
-rw-r--r--xpcom/tests/unit/bug725015.manifest3
-rw-r--r--xpcom/tests/unit/compmgr_warnings.manifest9
-rw-r--r--xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist26
-rwxr-xr-xxpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallAppbin0 -> 37988 bytes
-rw-r--r--xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo1
-rw-r--r--xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.stringsbin0 -> 92 bytes
-rw-r--r--xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib343
-rw-r--r--xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 3356 bytes
-rw-r--r--xpcom/tests/unit/data/bug121341-2.properties9
-rw-r--r--xpcom/tests/unit/data/bug121341.properties68
-rw-r--r--xpcom/tests/unit/data/child_process_directive_service.js21
-rw-r--r--xpcom/tests/unit/data/iniparser01-utf16leBOM.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser01-utf8BOM.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser01.ini0
-rw-r--r--xpcom/tests/unit/data/iniparser02-utf16leBOM.inibin0 -> 6 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser02-utf8BOM.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser02.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser03-utf16leBOM.inibin0 -> 10 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser03-utf8BOM.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser03.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser04-utf16leBOM.inibin0 -> 26 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser04-utf8BOM.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser04.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser05-utf16leBOM.inibin0 -> 34 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser05-utf8BOM.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser05.ini1
-rw-r--r--xpcom/tests/unit/data/iniparser06-utf16leBOM.inibin0 -> 30 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser06-utf8BOM.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser06.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser07-utf16leBOM.inibin0 -> 40 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser07-utf8BOM.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser07.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser08-utf16leBOM.inibin0 -> 42 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser08-utf8BOM.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser08.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser09-utf16leBOM.inibin0 -> 54 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser09-utf8BOM.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser09.ini2
-rw-r--r--xpcom/tests/unit/data/iniparser10-utf16leBOM.inibin0 -> 58 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser10-utf8BOM.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser10.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser11-utf16leBOM.inibin0 -> 76 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser11-utf8BOM.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser11.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser12-utf16leBOM.inibin0 -> 86 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser12-utf8BOM.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser12.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser13-utf16leBOM.inibin0 -> 94 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser13-utf8BOM.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser13.ini3
-rw-r--r--xpcom/tests/unit/data/iniparser14-utf16leBOM.inibin0 -> 160 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser14-utf8BOM.ini6
-rw-r--r--xpcom/tests/unit/data/iniparser14.ini6
-rw-r--r--xpcom/tests/unit/data/iniparser15-utf16leBOM.inibin0 -> 162 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser15-utf8BOM.ini6
-rw-r--r--xpcom/tests/unit/data/iniparser15.ini6
-rw-r--r--xpcom/tests/unit/data/iniparser16-utf16leBOM.inibin0 -> 210 bytes
-rw-r--r--xpcom/tests/unit/data/iniparser16-utf8BOM.ini13
-rw-r--r--xpcom/tests/unit/data/iniparser16.ini13
-rw-r--r--xpcom/tests/unit/data/main_process_directive_service.js21
-rw-r--r--xpcom/tests/unit/data/presentation.key/.typeAttributes.dict0
-rw-r--r--xpcom/tests/unit/data/presentation.key/Contents/PkgInfo1
-rw-r--r--xpcom/tests/unit/data/presentation.key/index.apxl.gzbin0 -> 83487 bytes
-rw-r--r--xpcom/tests/unit/data/presentation.key/thumbs/st0.tiffbin0 -> 16654 bytes
-rw-r--r--xpcom/tests/unit/data/process_directive.manifest5
-rw-r--r--xpcom/tests/unit/head_xpcom.js21
-rw-r--r--xpcom/tests/unit/test_bug121341.js71
-rw-r--r--xpcom/tests/unit/test_bug325418.js63
-rw-r--r--xpcom/tests/unit/test_bug332389.js19
-rw-r--r--xpcom/tests/unit/test_bug333505.js10
-rw-r--r--xpcom/tests/unit/test_bug364285-1.js51
-rw-r--r--xpcom/tests/unit/test_bug374754.js59
-rw-r--r--xpcom/tests/unit/test_bug476919.js27
-rw-r--r--xpcom/tests/unit/test_bug478086.js24
-rw-r--r--xpcom/tests/unit/test_bug656331.js39
-rw-r--r--xpcom/tests/unit/test_bug725015.js39
-rw-r--r--xpcom/tests/unit/test_bug745466.js6
-rw-r--r--xpcom/tests/unit/test_comp_no_aslr.js18
-rw-r--r--xpcom/tests/unit/test_compmgr_warnings.js71
-rw-r--r--xpcom/tests/unit/test_debugger_malloc_size_of.js34
-rw-r--r--xpcom/tests/unit/test_file_createUnique.js29
-rw-r--r--xpcom/tests/unit/test_file_equality.js43
-rw-r--r--xpcom/tests/unit/test_file_renameTo.js61
-rw-r--r--xpcom/tests/unit/test_hidden_files.js28
-rw-r--r--xpcom/tests/unit/test_home.js24
-rw-r--r--xpcom/tests/unit/test_iniProcessor.js288
-rw-r--r--xpcom/tests/unit/test_ioutil.js33
-rw-r--r--xpcom/tests/unit/test_localfile.js151
-rw-r--r--xpcom/tests/unit/test_mac_bundle.js18
-rw-r--r--xpcom/tests/unit/test_notxpcom_scriptable.js86
-rw-r--r--xpcom/tests/unit/test_nsIMutableArray.js184
-rw-r--r--xpcom/tests/unit/test_nsIProcess.js185
-rw-r--r--xpcom/tests/unit/test_nsIProcess_stress.js27
-rw-r--r--xpcom/tests/unit/test_pipe.js63
-rw-r--r--xpcom/tests/unit/test_process_directives.js25
-rw-r--r--xpcom/tests/unit/test_process_directives_child.js3
-rw-r--r--xpcom/tests/unit/test_seek_multiplex.js173
-rw-r--r--xpcom/tests/unit/test_storagestream.js162
-rw-r--r--xpcom/tests/unit/test_streams.js157
-rw-r--r--xpcom/tests/unit/test_stringstream.js23
-rw-r--r--xpcom/tests/unit/test_symlinks.js144
-rw-r--r--xpcom/tests/unit/test_systemInfo.js20
-rw-r--r--xpcom/tests/unit/test_versioncomparator.js59
-rw-r--r--xpcom/tests/unit/test_windows_cmdline_file.js21
-rw-r--r--xpcom/tests/unit/test_windows_registry.js205
-rw-r--r--xpcom/tests/unit/test_windows_shortcut.js279
-rw-r--r--xpcom/tests/unit/xpcomtest.manifest1
-rw-r--r--xpcom/tests/unit/xpcshell.ini79
-rw-r--r--xpcom/tests/windows/TestCOM.cpp158
-rw-r--r--xpcom/tests/windows/TestHelloXPLoop.cpp144
-rw-r--r--xpcom/tests/windows/TestNTFSPermissions.cpp286
-rw-r--r--xpcom/tests/windows/TestNtPathToDosPath.cpp193
-rw-r--r--xpcom/tests/windows/TestWinFileAttribs.cpp173
-rw-r--r--xpcom/tests/windows/moz.build16
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(&copyCounter);
+ trackedRunnable = NS_NewRunnableFunction(tracker);
+ // Original 'tracker' is destroyed here.
+ }
+ // Verify that the runnable contains a non-destroyed function object.
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and no move) function, copies",
+ copyCounter, 1);
+ }
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ // Passing as rvalue, but using copy.
+ // (TestCopyWithDeletedMove wouldn't allow this.)
+ trackedRunnable = NS_NewRunnableFunction(TestCopyWithNoMove(&copyCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and no move) function rvalue, copies",
+ copyCounter, 1);
+ }
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ trackedRunnable = NS_NewRunnableFunction(tracker);
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and deleted move) function, copies",
+ copyCounter, 1);
+ }
+
+ // Test NS_NewRunnableFunction with movable-only function object.
+ {
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestMove tracker(&moveCounter);
+ trackedRunnable = NS_NewRunnableFunction(Move(tracker));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with movable-only function, moves",
+ moveCounter, 1);
+ }
+ {
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable = NS_NewRunnableFunction(TestMove(&moveCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with movable-only function rvalue, moves",
+ moveCounter, 1);
+ }
+
+ // Test NS_NewRunnableFunction with copyable&movable function object.
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &moveCounter);
+ trackedRunnable = NS_NewRunnableFunction(Move(tracker));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable&movable function, copies",
+ copyCounter, 0);
+ Expect("NS_NewRunnableFunction with copyable&movable function, moves",
+ moveCounter, 1);
+ }
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable =
+ NS_NewRunnableFunction(TestCopyMove(&copyCounter, &moveCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable&movable function rvalue, copies",
+ copyCounter, 0);
+ Expect("NS_NewRunnableFunction with copyable&movable function rvalue, moves",
+ moveCounter, 1);
+ }
+
+ // Test NS_NewRunnableFunction with copyable-only lambda capture.
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ // Expect 2 copies (here -> local lambda -> runnable lambda).
+ trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); });
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and no move) capture, copies",
+ copyCounter, 2);
+ }
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ // Expect 2 copies (here -> local lambda -> runnable lambda).
+ trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); });
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable-only (and deleted move) capture, copies",
+ copyCounter, 2);
+ }
+
+ // Note: Not possible to use move-only captures.
+ // (Until we can use C++14 generalized lambda captures)
+
+ // Test NS_NewRunnableFunction with copyable&movable lambda capture.
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &moveCounter);
+ trackedRunnable = NS_NewRunnableFunction([tracker]() mutable { tracker(); });
+ // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable lambda).
+ }
+ trackedRunnable->Run();
+ }
+ Expect("NS_NewRunnableFunction with copyable&movable capture, copies",
+ copyCounter, 1);
+ Expect("NS_NewRunnableFunction with copyable&movable capture, moves",
+ moveCounter, 1);
+ }
+}
+
+TEST(ThreadUtils, RunnableMethod)
+{
+ memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool));
+ // Scope the smart ptrs so that the runnables need to hold on to whatever they need
+ {
+ RefPtr<nsFoo> foo = new nsFoo();
+ RefPtr<nsBar> bar = new nsBar();
+ RefPtr<const nsBar> constBar = bar;
+
+ // This pointer will be freed at the end of the block
+ // Do not dereference this pointer in the runnable method!
+ RefPtr<nsFoo> rawFoo = new nsFoo();
+
+ // Read only string. Dereferencing in runnable method to check this works.
+ char* message = (char*)"Test message";
+
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1));
+ NS_DispatchToMainThread(NewRunnableMethod(constBar, &nsBar::DoBar1Const));
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar3, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar4, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5, rawFoo));
+ NS_DispatchToMainThread(NewRunnableMethod<char*>(bar, &nsBar::DoBar6, message));
+#ifdef HAVE_STDCALL
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar1std));
+ NS_DispatchToMainThread(NewRunnableMethod(bar, &nsBar::DoBar2std));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar3std, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<RefPtr<nsFoo>>
+ (bar, &nsBar::DoBar4std, foo));
+ NS_DispatchToMainThread(NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5std, rawFoo));
+ NS_DispatchToMainThread(NewRunnableMethod<char*>(bar, &nsBar::DoBar6std, message));
+#endif
+ }
+
+ // Spin the event loop
+ NS_ProcessPendingEvents(nullptr);
+
+ // Now test a suicidal event in NS_New(Named)Thread
+ nsCOMPtr<nsIThread> thread;
+ NS_NewNamedThread("SuicideThread", getter_AddRefs(thread), new TestSuicide());
+ ASSERT_TRUE(thread);
+
+ while (!gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL]) {
+ NS_ProcessPendingEvents(nullptr);
+ }
+
+ for (uint32_t i = 0; i < MAX_TESTS; i++) {
+ EXPECT_TRUE(gRunnableExecuted[i]) << "Error in test " << i;
+ }
+}
diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp
new file mode 100644
index 000000000..4f6055dce
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreads.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsThreadUtils.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include "nspr.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsXPCOM.h"
+#include "mozilla/Monitor.h"
+#include "gtest/gtest.h"
+
+class nsRunner final : public nsIRunnable {
+ ~nsRunner() {}
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ printf("running %d on thread %p\n", mNum, (void *)thread.get());
+
+ // if we don't do something slow, we'll never see the other
+ // worker threads run
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ return rv;
+ }
+
+ explicit nsRunner(int num) : mNum(num) {
+ }
+
+protected:
+ int mNum;
+};
+
+NS_IMPL_ISUPPORTS(nsRunner, nsIRunnable)
+
+TEST(Threads, Main)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRunnable> event = new nsRunner(0);
+ EXPECT_TRUE(event);
+
+ nsCOMPtr<nsIThread> runner;
+ rv = NS_NewThread(getter_AddRefs(runner), event);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ rv = runner->Shutdown(); // wait for the runner to die before quitting
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ PR_Sleep(PR_MillisecondsToInterval(100)); // hopefully the runner will quit here
+}
+
+class nsStressRunner final : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+ PR_Sleep(1);
+ if (!PR_AtomicDecrement(&gNum)) {
+ printf(" last thread was %d\n", mNum);
+ }
+ return NS_OK;
+ }
+
+ explicit nsStressRunner(int num) : mNum(num), mWasRun(false) {
+ PR_AtomicIncrement(&gNum);
+ }
+
+ static int32_t GetGlobalCount() {return gNum;}
+
+private:
+ ~nsStressRunner() {
+ EXPECT_TRUE(mWasRun);
+ }
+
+protected:
+ static int32_t gNum;
+ int32_t mNum;
+ bool mWasRun;
+};
+
+int32_t nsStressRunner::gNum = 0;
+
+NS_IMPL_ISUPPORTS(nsStressRunner, nsIRunnable)
+
+TEST(Threads, Stress)
+{
+ const int loops = 1000;
+ const int threads = 50;
+
+ for (int i = 0; i < loops; i++) {
+ printf("Loop %d of %d\n", i+1, loops);
+
+ int k;
+ nsIThread** array = new nsIThread*[threads];
+
+ EXPECT_EQ(nsStressRunner::GetGlobalCount(), 0);
+
+ for (k = 0; k < threads; k++) {
+ nsCOMPtr<nsIThread> t;
+ nsresult rv = NS_NewThread(getter_AddRefs(t), new nsStressRunner(k));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+ NS_ADDREF(array[k] = t);
+ }
+
+ for (k = threads-1; k >= 0; k--) {
+ array[k]->Shutdown();
+ NS_RELEASE(array[k]);
+ }
+ delete [] array;
+ }
+}
+
+mozilla::Monitor* gAsyncShutdownReadyMonitor;
+mozilla::Monitor* gBeginAsyncShutdownMonitor;
+
+class AsyncShutdownPreparer : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+
+ mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor);
+ lock.Notify();
+
+ return NS_OK;
+ }
+
+ explicit AsyncShutdownPreparer() : mWasRun(false) {}
+
+private:
+ virtual ~AsyncShutdownPreparer() {
+ EXPECT_TRUE(mWasRun);
+ }
+
+protected:
+ bool mWasRun;
+};
+
+NS_IMPL_ISUPPORTS(AsyncShutdownPreparer, nsIRunnable)
+
+class AsyncShutdownWaiter : public nsIRunnable {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+
+ nsCOMPtr<nsIThread> t;
+ nsresult rv;
+
+ {
+ mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor);
+
+ rv = NS_NewThread(getter_AddRefs(t), new AsyncShutdownPreparer());
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ lock.Wait();
+ }
+
+ rv = t->AsyncShutdown();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+ }
+
+ explicit AsyncShutdownWaiter() : mWasRun(false) {}
+
+private:
+ virtual ~AsyncShutdownWaiter() {
+ EXPECT_TRUE(mWasRun);
+ }
+
+protected:
+ bool mWasRun;
+};
+
+NS_IMPL_ISUPPORTS(AsyncShutdownWaiter, nsIRunnable)
+
+class SameThreadSentinel : public nsIRunnable {
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Run() override {
+ mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor);
+ lock.Notify();
+ return NS_OK;
+ }
+
+private:
+ virtual ~SameThreadSentinel() {}
+};
+
+NS_IMPL_ISUPPORTS(SameThreadSentinel, nsIRunnable)
+
+TEST(Threads, AsyncShutdown)
+{
+ gAsyncShutdownReadyMonitor = new mozilla::Monitor("gAsyncShutdownReady");
+ gBeginAsyncShutdownMonitor = new mozilla::Monitor("gBeginAsyncShutdown");
+
+ nsCOMPtr<nsIThread> t;
+ nsresult rv;
+
+ {
+ mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor);
+
+ rv = NS_NewThread(getter_AddRefs(t), new AsyncShutdownWaiter());
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ lock.Wait();
+ }
+
+ NS_DispatchToCurrentThread(new SameThreadSentinel());
+ rv = t->Shutdown();
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ delete gAsyncShutdownReadyMonitor;
+ delete gBeginAsyncShutdownMonitor;
+}
+
+static void threadProc(void *arg)
+{
+ // printf(" running thread %d\n", (int) arg);
+ PR_Sleep(1);
+ EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(PR_GetCurrentThread()));
+}
+
+TEST(Threads, StressNSPR)
+{
+ const int loops = 1000;
+ const int threads = 50;
+
+ for (int i = 0; i < loops; i++) {
+ printf("Loop %d of %d\n", i+1, loops);
+
+ intptr_t k;
+ PRThread** array = new PRThread*[threads];
+
+ for (k = 0; k < threads; k++) {
+ array[k] = PR_CreateThread(PR_USER_THREAD,
+ threadProc, (void*) k,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD,
+ 0);
+ EXPECT_TRUE(array[k]);
+ }
+
+ for (k = 0; k < threads; k++) {
+ EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(array[k]));
+ }
+
+ for (k = threads-1; k >= 0; k--) {
+ PR_JoinThread(array[k]);
+ }
+ delete [] array;
+ }
+}
diff --git a/xpcom/tests/gtest/TestTimeStamp.cpp b/xpcom/tests/gtest/TestTimeStamp.cpp
new file mode 100644
index 000000000..4e85b7e24
--- /dev/null
+++ b/xpcom/tests/gtest/TestTimeStamp.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/TimeStamp.h"
+
+#include "prinrval.h"
+#include "prthread.h"
+
+#include "gtest/gtest.h"
+
+using mozilla::TimeStamp;
+using mozilla::TimeDuration;
+
+TEST(TimeStamp, Main)
+{
+ TimeDuration td;
+ EXPECT_TRUE(td.ToSeconds() == 0.0);
+ EXPECT_TRUE(TimeDuration::FromSeconds(5).ToSeconds() == 5.0);
+ EXPECT_TRUE(TimeDuration::FromMilliseconds(5000).ToSeconds() == 5.0);
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(2));
+ EXPECT_FALSE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(2) > TimeDuration::FromSeconds(1));
+ EXPECT_FALSE(TimeDuration::FromSeconds(1) > TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(2));
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(1));
+ EXPECT_FALSE(TimeDuration::FromSeconds(2) <= TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(2) >= TimeDuration::FromSeconds(1));
+ EXPECT_TRUE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(1));
+ EXPECT_FALSE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(2));
+
+ TimeStamp ts;
+ EXPECT_TRUE(ts.IsNull());
+
+ ts = TimeStamp::Now();
+ EXPECT_TRUE(!ts.IsNull());
+ EXPECT_TRUE((ts - ts).ToSeconds() == 0.0);
+
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ TimeStamp ts2(TimeStamp::Now());
+ EXPECT_TRUE(ts2 > ts);
+ EXPECT_FALSE(ts > ts);
+ EXPECT_TRUE(ts < ts2);
+ EXPECT_FALSE(ts < ts);
+ EXPECT_TRUE(ts <= ts2);
+ EXPECT_TRUE(ts <= ts);
+ EXPECT_FALSE(ts2 <= ts);
+ EXPECT_TRUE(ts2 >= ts);
+ EXPECT_TRUE(ts2 >= ts);
+ EXPECT_FALSE(ts >= ts2);
+
+ // We can't be sure exactly how long PR_Sleep slept for. It should have
+ // slept for at least one second. We might have slept a lot longer due
+ // to process scheduling, but hopefully not more than 10 seconds.
+ td = ts2 - ts;
+ EXPECT_TRUE(td.ToSeconds() > 1.0);
+ EXPECT_TRUE(td.ToSeconds() < 20.0);
+ td = ts - ts2;
+ EXPECT_TRUE(td.ToSeconds() < -1.0);
+ EXPECT_TRUE(td.ToSeconds() > -20.0);
+
+ double resolution = TimeDuration::Resolution().ToSecondsSigDigits();
+ printf(" (platform timer resolution is ~%g s)\n", resolution);
+ EXPECT_TRUE(1e-10 < resolution);
+ // Don't upper-bound sanity check ... although NSPR reports 1ms
+ // resolution, it might be lying, so we shouldn't compare with it
+}
diff --git a/xpcom/tests/gtest/TestTimers.cpp b/xpcom/tests/gtest/TestTimers.cpp
new file mode 100644
index 000000000..fe7520ab1
--- /dev/null
+++ b/xpcom/tests/gtest/TestTimers.cpp
@@ -0,0 +1,437 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThread.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "prinrval.h"
+#include "prmon.h"
+#include "prthread.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/ReentrantMonitor.h"
+
+#include <list>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+typedef nsresult(*TestFuncPtr)();
+
+class AutoTestThread
+{
+public:
+ AutoTestThread() {
+ nsCOMPtr<nsIThread> newThread;
+ nsresult rv = NS_NewThread(getter_AddRefs(newThread));
+ if (NS_FAILED(rv))
+ return;
+
+ newThread.swap(mThread);
+ }
+
+ ~AutoTestThread() {
+ mThread->Shutdown();
+ }
+
+ operator nsIThread*() const {
+ return mThread;
+ }
+
+ nsIThread* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ return mThread;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+class AutoCreateAndDestroyReentrantMonitor
+{
+public:
+ AutoCreateAndDestroyReentrantMonitor() {
+ mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon");
+ MOZ_RELEASE_ASSERT(mReentrantMonitor, "Out of memory!");
+ }
+
+ ~AutoCreateAndDestroyReentrantMonitor() {
+ delete mReentrantMonitor;
+ }
+
+ operator ReentrantMonitor* () {
+ return mReentrantMonitor;
+ }
+
+private:
+ ReentrantMonitor* mReentrantMonitor;
+};
+
+class TimerCallback final : public nsITimerCallback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ TimerCallback(nsIThread** aThreadPtr, ReentrantMonitor* aReentrantMonitor)
+ : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) { }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ MOZ_RELEASE_ASSERT(mThreadPtr, "Callback was not supposed to be called!");
+ nsCOMPtr<nsIThread> current(do_GetCurrentThread());
+
+ ReentrantMonitorAutoEnter mon(*mReentrantMonitor);
+
+ MOZ_RELEASE_ASSERT(!*mThreadPtr, "Timer called back more than once!");
+ *mThreadPtr = current;
+
+ mon.Notify();
+
+ return NS_OK;
+ }
+private:
+ ~TimerCallback() {}
+
+ nsIThread** mThreadPtr;
+ ReentrantMonitor* mReentrantMonitor;
+};
+
+NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback)
+
+TEST(Timers, TargetedTimers)
+{
+ AutoCreateAndDestroyReentrantMonitor newMon;
+ ASSERT_TRUE(newMon);
+
+ AutoTestThread testThread;
+ ASSERT_TRUE(testThread);
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread);
+
+ rv = timer->SetTarget(target);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsIThread* notifiedThread = nullptr;
+
+ nsCOMPtr<nsITimerCallback> callback =
+ new TimerCallback(&notifiedThread, newMon);
+ ASSERT_TRUE(callback);
+
+ rv = timer->InitWithCallback(callback, 2000, nsITimer::TYPE_ONE_SHOT);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ ReentrantMonitorAutoEnter mon(*newMon);
+ while (!notifiedThread) {
+ mon.Wait();
+ }
+ ASSERT_EQ(notifiedThread, testThread);
+}
+
+TEST(Timers, TimerWithStoppedTarget)
+{
+ AutoTestThread testThread;
+ ASSERT_TRUE(testThread);
+
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsIEventTarget* target = static_cast<nsIEventTarget*>(testThread);
+
+ rv = timer->SetTarget(target);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // If this is called, we'll assert
+ nsCOMPtr<nsITimerCallback> callback =
+ new TimerCallback(nullptr, nullptr);
+ ASSERT_TRUE(callback);
+
+ rv = timer->InitWithCallback(callback, 100, nsITimer::TYPE_ONE_SHOT);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ testThread->Shutdown();
+
+ PR_Sleep(400);
+}
+
+#define FUZZ_MAX_TIMEOUT 9
+class FuzzTestThreadState final : public nsITimerCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit FuzzTestThreadState(nsIThread* thread) :
+ mThread(thread),
+ mStopped(false)
+ {}
+
+ class StartRunnable final : public mozilla::Runnable {
+ public:
+ explicit StartRunnable(FuzzTestThreadState* threadState) :
+ mThreadState(threadState)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mThreadState->ScheduleOrCancelTimers();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<FuzzTestThreadState> mThreadState;
+ };
+
+ void Start()
+ {
+ nsCOMPtr<nsIRunnable> runnable = new StartRunnable(this);
+ nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch StartRunnable.");
+ }
+
+ void Stop()
+ {
+ mStopped = true;
+ }
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override
+ {
+ bool onCorrectThread;
+ nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check.");
+ MOZ_RELEASE_ASSERT(onCorrectThread, "Notify invoked on wrong thread.");
+
+ uint32_t delay;
+ rv = aTimer->GetDelay(&delay);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "GetDelay failed.");
+
+ MOZ_RELEASE_ASSERT(delay <= FUZZ_MAX_TIMEOUT,
+ "Delay was an invalid value for this test.");
+
+ uint32_t type;
+ rv = aTimer->GetType(&type);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type.");
+ MOZ_RELEASE_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+
+ if (type == nsITimer::TYPE_ONE_SHOT) {
+ MOZ_RELEASE_ASSERT(!mOneShotTimersByDelay[delay].empty(),
+ "Unexpected one-shot timer.");
+
+ MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[delay].front().get() == aTimer,
+ "One-shot timers have been reordered.");
+
+ mOneShotTimersByDelay[delay].pop_front();
+ --mTimersOutstanding;
+ } else if (mStopped) {
+ CancelRepeatingTimer(aTimer);
+ }
+
+ ScheduleOrCancelTimers();
+ RescheduleSomeTimers();
+ return NS_OK;
+ }
+
+ bool HasTimersOutstanding() const
+ {
+ return !!mTimersOutstanding;
+ }
+
+ private:
+ ~FuzzTestThreadState()
+ {
+ for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
+ MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[i].empty(),
+ "Timers remain at end of test.");
+ }
+ }
+
+ uint32_t GetRandomType() const
+ {
+ return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1);
+ }
+
+ size_t CountOneShotTimers() const
+ {
+ size_t count = 0;
+ for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) {
+ count += mOneShotTimersByDelay[i].size();
+ }
+ return count;
+ }
+
+ void ScheduleOrCancelTimers()
+ {
+ if (mStopped) {
+ return;
+ }
+
+ const size_t numTimersDesired = (rand() % 100) + 100;
+ MOZ_RELEASE_ASSERT(numTimersDesired >= 100);
+ MOZ_RELEASE_ASSERT(numTimersDesired < 200);
+ int adjustment = numTimersDesired - mTimersOutstanding;
+
+ while (adjustment > 0) {
+ CreateRandomTimer();
+ --adjustment;
+ }
+
+ while (adjustment < 0) {
+ CancelRandomTimer();
+ ++adjustment;
+ }
+
+ MOZ_RELEASE_ASSERT(numTimersDesired == mTimersOutstanding);
+ }
+
+ void RescheduleSomeTimers()
+ {
+ if (mStopped) {
+ return;
+ }
+
+ static const size_t kNumRescheduled = 40;
+
+ // Reschedule some timers with a Cancel first.
+ for (size_t i = 0; i < kNumRescheduled; ++i) {
+ InitRandomTimer(CancelRandomTimer().get());
+ }
+ // Reschedule some timers without a Cancel first.
+ for (size_t i = 0; i < kNumRescheduled; ++i) {
+ InitRandomTimer(RemoveRandomTimer().get());
+ }
+ }
+
+ void CreateRandomTimer()
+ {
+ nsresult rv;
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to create timer.");
+
+ rv = timer->SetTarget(static_cast<nsIEventTarget*>(mThread.get()));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set target.");
+
+ InitRandomTimer(timer.get());
+ }
+
+ nsCOMPtr<nsITimer> CancelRandomTimer()
+ {
+ nsCOMPtr<nsITimer> timer(RemoveRandomTimer());
+ timer->Cancel();
+ return timer;
+ }
+
+ nsCOMPtr<nsITimer> RemoveRandomTimer()
+ {
+ MOZ_RELEASE_ASSERT(mTimersOutstanding);
+
+ if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers())
+ || mRepeatingTimers.empty()) {
+ uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1);
+ while (mOneShotTimersByDelay[delayToRemove].empty()) {
+ // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1
+ delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1);
+ }
+
+ uint32_t indexToRemove =
+ rand() % mOneShotTimersByDelay[delayToRemove].size();
+
+ for (auto it = mOneShotTimersByDelay[delayToRemove].begin();
+ it != mOneShotTimersByDelay[delayToRemove].end();
+ ++it) {
+ if (indexToRemove) {
+ --indexToRemove;
+ continue;
+ }
+
+ nsCOMPtr<nsITimer> removed = *it;
+ mOneShotTimersByDelay[delayToRemove].erase(it);
+ --mTimersOutstanding;
+ return removed;
+ }
+ } else {
+ size_t indexToRemove = rand() % mRepeatingTimers.size();
+ nsCOMPtr<nsITimer> removed(mRepeatingTimers[indexToRemove]);
+ mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove);
+ --mTimersOutstanding;
+ return removed;
+ }
+
+ MOZ_CRASH("Unable to remove a timer");
+ }
+
+ void InitRandomTimer(nsITimer* aTimer)
+ {
+ // Between 0 and FUZZ_MAX_TIMEOUT
+ uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1);
+ uint32_t type = GetRandomType();
+ nsresult rv = aTimer->InitWithCallback(this, delay, type);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set timer.");
+
+ if (type == nsITimer::TYPE_ONE_SHOT) {
+ mOneShotTimersByDelay[delay].push_back(aTimer);
+ } else {
+ mRepeatingTimers.push_back(aTimer);
+ }
+ ++mTimersOutstanding;
+ }
+
+ void CancelRepeatingTimer(nsITimer* aTimer)
+ {
+ for (auto it = mRepeatingTimers.begin();
+ it != mRepeatingTimers.end();
+ ++it) {
+ if (it->get() == aTimer) {
+ mRepeatingTimers.erase(it);
+ aTimer->Cancel();
+ --mTimersOutstanding;
+ return;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIThread> mThread;
+ // Scheduled timers, indexed by delay between 0-9 ms, in lists
+ // with most recently scheduled last.
+ std::list<nsCOMPtr<nsITimer>> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1];
+ std::vector<nsCOMPtr<nsITimer>> mRepeatingTimers;
+ Atomic<bool> mStopped;
+ Atomic<size_t> mTimersOutstanding;
+};
+
+NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback)
+
+TEST(Timers, FuzzTestTimers)
+{
+ static const size_t kNumThreads(10);
+ AutoTestThread threads[kNumThreads];
+ RefPtr<FuzzTestThreadState> threadStates[kNumThreads];
+
+ for (size_t i = 0; i < kNumThreads; ++i) {
+ threadStates[i] = new FuzzTestThreadState(&*threads[i]);
+ threadStates[i]->Start();
+ }
+
+ PR_Sleep(PR_MillisecondsToInterval(20000));
+
+ for (size_t i = 0; i < kNumThreads; ++i) {
+ threadStates[i]->Stop();
+ }
+
+ // Wait at most 10 seconds for all outstanding timers to pop
+ PRIntervalTime start = PR_IntervalNow();
+ for (auto& threadState : threadStates) {
+ while (threadState->HasTimersOutstanding()) {
+ uint32_t elapsedMs = PR_IntervalToMilliseconds(PR_IntervalNow() - start);
+ ASSERT_LE(elapsedMs, uint32_t(10000)) << "Timed out waiting for all timers to pop";
+ PR_Sleep(PR_MillisecondsToInterval(10));
+ }
+ }
+}
diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp
new file mode 100644
index 000000000..283bbd3b8
--- /dev/null
+++ b/xpcom/tests/gtest/TestTokenizer.cpp
@@ -0,0 +1,1134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Tokenizer.h"
+#include "mozilla/IncrementalTokenizer.h"
+#include "mozilla/Unused.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+static bool IsOperator(char const c)
+{
+ return c == '+' || c == '*';
+}
+
+static bool HttpHeaderCharacter(char const c)
+{
+ return (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ (c == '_') ||
+ (c == '-');
+}
+
+TEST(Tokenizer, HTTPResponse)
+{
+ Tokenizer::Token t;
+
+ // Real life test, HTTP response
+
+ Tokenizer p(NS_LITERAL_CSTRING(
+ "HTTP/1.0 304 Not modified\r\n"
+ "ETag: hallo\r\n"
+ "Content-Length: 16\r\n"
+ "\r\n"
+ "This is the body"));
+
+ EXPECT_TRUE(p.CheckWord("HTTP"));
+ EXPECT_TRUE(p.CheckChar('/'));
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 1);
+ EXPECT_TRUE(p.CheckChar('.'));
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 0);
+ p.SkipWhites();
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 304);
+ p.SkipWhites();
+
+ p.Record();
+ while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL);
+ EXPECT_FALSE(p.HasFailed());
+ nsAutoCString h;
+ p.Claim(h);
+ EXPECT_TRUE(h == "Not modified");
+
+ p.Record();
+ while (p.CheckChar(HttpHeaderCharacter));
+ p.Claim(h, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(h == "ETag");
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckChar(':'));
+ p.SkipWhites();
+ p.Record();
+ while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL);
+ EXPECT_FALSE(p.HasFailed());
+ p.Claim(h);
+ EXPECT_TRUE(h == "hallo");
+
+ p.Record();
+ while (p.CheckChar(HttpHeaderCharacter));
+ p.Claim(h, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(h == "Content-Length");
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckChar(':'));
+ p.SkipWhites();
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+ EXPECT_TRUE(t.AsInteger() == 16);
+ EXPECT_TRUE(p.CheckEOL());
+
+ EXPECT_TRUE(p.CheckEOL());
+
+ p.Record();
+ while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOF);
+ nsAutoCString b;
+ p.Claim(b);
+ EXPECT_TRUE(b == "This is the body");
+}
+
+TEST(Tokenizer, Main)
+{
+ Tokenizer::Token t;
+
+ // Synthetic code-specific test
+
+ Tokenizer p(NS_LITERAL_CSTRING("test123 ,15 \t*\r\n%xx,-15\r\r"));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_WORD);
+ EXPECT_TRUE(t.AsString() == "test123");
+
+ Tokenizer::Token u;
+ EXPECT_FALSE(p.Check(u));
+
+ EXPECT_FALSE(p.CheckChar('!'));
+
+ EXPECT_FALSE(p.Check(Tokenizer::Token::Number(123)));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15)));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15)));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 15);
+
+ EXPECT_FALSE(p.CheckChar(IsOperator));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ p.SkipWhites();
+
+ EXPECT_FALSE(p.CheckWhite());
+
+ p.Rollback();
+
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+
+ p.Record(Tokenizer::EXCLUDE_LAST);
+
+ EXPECT_TRUE(p.CheckChar(IsOperator));
+
+ p.Rollback();
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == '*');
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == '%');
+
+ nsAutoCString claim;
+ p.Claim(claim, Tokenizer::EXCLUDE_LAST);
+ EXPECT_TRUE(claim == "*\r\n");
+ p.Claim(claim, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(claim == "*\r\n%");
+
+ p.Rollback();
+ EXPECT_TRUE(p.CheckChar('%'));
+
+ p.Record(Tokenizer::INCLUDE_LAST);
+
+ EXPECT_FALSE(p.CheckWord("xy"));
+
+ EXPECT_TRUE(p.CheckWord("xx"));
+
+
+ p.Claim(claim, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(claim == "%xx");
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == ',');
+
+ EXPECT_TRUE(p.CheckChar('-'));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 15);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF);
+
+ EXPECT_FALSE(p.Next(t));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF);
+
+ EXPECT_FALSE(p.Next(t));
+
+ p.Rollback();
+ EXPECT_TRUE(p.CheckEOF());
+
+ EXPECT_FALSE(p.CheckEOF());
+}
+
+TEST(Tokenizer, SingleWord)
+{
+ // Single word with numbers in it test
+
+ Tokenizer p(NS_LITERAL_CSTRING("test123"));
+
+ EXPECT_TRUE(p.CheckWord("test123"));
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, EndingAfterNumber)
+{
+ // An end handling after a number
+
+ Tokenizer p(NS_LITERAL_CSTRING("123"));
+
+ EXPECT_FALSE(p.CheckWord("123"));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123)));
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, BadInteger)
+{
+ Tokenizer::Token t;
+
+ // A bad integer test
+
+ Tokenizer p(NS_LITERAL_CSTRING("189234891274981758617846178651647620587135"));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_ERROR);
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, CheckExpectedTokenValue)
+{
+ Tokenizer::Token t;
+
+ // Check expected token value test
+
+ Tokenizer p(NS_LITERAL_CSTRING("blue velvet"));
+
+ EXPECT_FALSE(p.Check(Tokenizer::TOKEN_INTEGER, t));
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t));
+ EXPECT_TRUE(t.AsString() == "blue");
+
+ EXPECT_FALSE(p.Check(Tokenizer::TOKEN_WORD, t));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t));
+ EXPECT_TRUE(t.AsString() == "velvet");
+
+ EXPECT_TRUE(p.CheckEOF());
+
+ EXPECT_FALSE(p.Next(t));
+}
+
+TEST(Tokenizer, HasFailed)
+{
+ Tokenizer::Token t;
+
+ // HasFailed test
+
+ Tokenizer p1(NS_LITERAL_CSTRING("a b"));
+
+ while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(p1.HasFailed());
+
+
+ Tokenizer p2(NS_LITERAL_CSTRING("a b ?!c"));
+
+ EXPECT_FALSE(p2.CheckChar('c'));
+ EXPECT_TRUE(p2.HasFailed());
+ EXPECT_TRUE(p2.CheckChar(HttpHeaderCharacter));
+ EXPECT_FALSE(p2.HasFailed());
+ p2.SkipWhites();
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_FALSE(p2.CheckChar('c'));
+ EXPECT_TRUE(p2.HasFailed());
+ EXPECT_TRUE(p2.Next(t));
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_TRUE(p2.Next(t));
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_FALSE(p2.CheckChar('c'));
+ EXPECT_TRUE(p2.HasFailed());
+ EXPECT_TRUE(p2.Check(Tokenizer::TOKEN_CHAR, t));
+ EXPECT_FALSE(p2.HasFailed());
+ EXPECT_FALSE(p2.CheckChar('#'));
+ EXPECT_TRUE(p2.HasFailed());
+ t = Tokenizer::Token::Char('!');
+ EXPECT_TRUE(p2.Check(t));
+ EXPECT_FALSE(p2.HasFailed());
+
+ while (p2.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(p2.HasFailed());
+}
+
+TEST(Tokenizer, Construction)
+{
+ {
+ nsCString a("test");
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ nsAutoCString a("test");
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ static const char _a[] = "test";
+ nsDependentCString a(_a);
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ static const char* _a = "test";
+ nsDependentCString a(_a);
+ Tokenizer p1(a);
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ Tokenizer p1(nsDependentCString("test"));
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ Tokenizer p1(NS_LITERAL_CSTRING("test"));
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+
+ {
+ Tokenizer p1("test");
+ EXPECT_TRUE(p1.CheckWord("test"));
+ EXPECT_TRUE(p1.CheckEOF());
+ }
+}
+
+TEST(Tokenizer, Customization)
+{
+ Tokenizer p1(NS_LITERAL_CSTRING("test-custom*words and\tdefault-whites"), nullptr, "-*");
+ EXPECT_TRUE(p1.CheckWord("test-custom*words"));
+ EXPECT_TRUE(p1.CheckWhite());
+ EXPECT_TRUE(p1.CheckWord("and"));
+ EXPECT_TRUE(p1.CheckWhite());
+ EXPECT_TRUE(p1.CheckWord("default-whites"));
+
+ Tokenizer p2(NS_LITERAL_CSTRING("test, custom,whites"), ", ");
+ EXPECT_TRUE(p2.CheckWord("test"));
+ EXPECT_TRUE(p2.CheckWhite());
+ EXPECT_TRUE(p2.CheckWhite());
+ EXPECT_TRUE(p2.CheckWord("custom"));
+ EXPECT_TRUE(p2.CheckWhite());
+ EXPECT_TRUE(p2.CheckWord("whites"));
+
+ Tokenizer p3(NS_LITERAL_CSTRING("test, custom, whites-and#word-chars"), ",", "-#");
+ EXPECT_TRUE(p3.CheckWord("test"));
+ EXPECT_TRUE(p3.CheckWhite());
+ EXPECT_FALSE(p3.CheckWhite());
+ EXPECT_TRUE(p3.CheckChar(' '));
+ EXPECT_TRUE(p3.CheckWord("custom"));
+ EXPECT_TRUE(p3.CheckWhite());
+ EXPECT_FALSE(p3.CheckWhite());
+ EXPECT_TRUE(p3.CheckChar(' '));
+ EXPECT_TRUE(p3.CheckWord("whites-and#word-chars"));
+}
+
+TEST(Tokenizer, ShortcutChecks)
+{
+ Tokenizer p("test1 test2,123");
+
+ nsAutoCString test1;
+ nsDependentCSubstring test2;
+ char comma;
+ uint32_t integer;
+
+ EXPECT_TRUE(p.ReadWord(test1));
+ EXPECT_TRUE(test1 == "test1");
+ p.SkipWhites();
+ EXPECT_TRUE(p.ReadWord(test2));
+ EXPECT_TRUE(test2 == "test2");
+ EXPECT_TRUE(p.ReadChar(&comma));
+ EXPECT_TRUE(comma == ',');
+ EXPECT_TRUE(p.ReadInteger(&integer));
+ EXPECT_TRUE(integer == 123);
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+static bool ABChar(const char aChar)
+{
+ return aChar == 'a' || aChar == 'b';
+}
+
+TEST(Tokenizer, ReadCharClassified)
+{
+ Tokenizer p("abc");
+
+ char c;
+ EXPECT_TRUE(p.ReadChar(ABChar, &c));
+ EXPECT_TRUE(c == 'a');
+ EXPECT_TRUE(p.ReadChar(ABChar, &c));
+ EXPECT_TRUE(c == 'b');
+ EXPECT_FALSE(p.ReadChar(ABChar, &c));
+ nsDependentCSubstring w;
+ EXPECT_TRUE(p.ReadWord(w));
+ EXPECT_TRUE(w == "c");
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, ClaimSubstring)
+{
+ Tokenizer p(" abc ");
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ p.Record();
+ EXPECT_TRUE(p.CheckWord("abc"));
+ nsDependentCSubstring v;
+ p.Claim(v, Tokenizer::INCLUDE_LAST);
+ EXPECT_TRUE(v == "abc");
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, Fragment)
+{
+ const char str[] = "ab;cd:10 ";
+ Tokenizer p(str);
+ nsDependentCSubstring f;
+
+ Tokenizer::Token t1, t2;
+
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD);
+ EXPECT_TRUE(t1.Fragment() == "ab");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[0]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2));
+ EXPECT_TRUE(t2.Fragment() == "ab");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[0]);
+
+
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t1.Fragment() == ";");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[2]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2));
+ EXPECT_TRUE(t2.Fragment() == ";");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[2]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2));
+ EXPECT_TRUE(t2.Fragment() == "cd");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[3]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD);
+ EXPECT_TRUE(t1.Fragment() == "cd");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[3]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2));
+ EXPECT_TRUE(t2.Fragment() == ":");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[5]);
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR);
+ EXPECT_TRUE(t1.Fragment() == ":");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[5]);
+
+
+ EXPECT_TRUE(p.Next(t1));
+ EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_INTEGER);
+ EXPECT_TRUE(t1.Fragment() == "10");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[6]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WS, t2));
+ EXPECT_TRUE(t2.Fragment() == " ");
+ EXPECT_TRUE(t2.Fragment().BeginReading() == &str[8]);
+
+
+ EXPECT_TRUE(p.Check(Tokenizer::TOKEN_EOF, t1));
+ EXPECT_TRUE(t1.Fragment() == "");
+ EXPECT_TRUE(t1.Fragment().BeginReading() == &str[9]);
+}
+
+TEST(Tokenizer, SkipWhites)
+{
+ Tokenizer p("Text1 \nText2 \nText3\n Text4\n ");
+
+ EXPECT_TRUE(p.CheckWord("Text1"));
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckEOL());
+
+ EXPECT_TRUE(p.CheckWord("Text2"));
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+
+ EXPECT_TRUE(p.CheckWord("Text3"));
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckEOL());
+ p.SkipWhites();
+
+ EXPECT_TRUE(p.CheckWord("Text4"));
+ p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, SkipCustomWhites)
+{
+ Tokenizer p("Text1 \n\r\t.Text2 \n\r\t.", " \n\r\t.");
+
+ EXPECT_TRUE(p.CheckWord("Text1"));
+ p.SkipWhites();
+ EXPECT_TRUE(p.CheckWord("Text2"));
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckWhite());
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, IntegerReading)
+{
+#define INT_6_BITS 64U
+#define INT_30_BITS 1073741824UL
+#define INT_32_BITS 4294967295UL
+#define INT_50_BITS 1125899906842624ULL
+#define STR_INT_MORE_THAN_64_BITS "922337203685477580899"
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_6_BITS));
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ EXPECT_TRUE(p.ReadInteger(&u8));
+ EXPECT_TRUE(u8 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u16));
+ EXPECT_TRUE(u16 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u32));
+ EXPECT_TRUE(u32 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u64));
+ EXPECT_TRUE(u64 == INT_6_BITS);
+
+ p.Rollback();
+
+ int8_t s8;
+ int16_t s16;
+ int32_t s32;
+ int64_t s64;
+ EXPECT_TRUE(p.ReadInteger(&s8));
+ EXPECT_TRUE(s8 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s16));
+ EXPECT_TRUE(s16 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s32));
+ EXPECT_TRUE(s32 == INT_6_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s64));
+ EXPECT_TRUE(s64 == INT_6_BITS);
+
+ EXPECT_TRUE(p.CheckWord("U"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_30_BITS));
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ EXPECT_FALSE(p.ReadInteger(&u8));
+ EXPECT_FALSE(p.ReadInteger(&u16));
+ EXPECT_TRUE(p.ReadInteger(&u32));
+ EXPECT_TRUE(u32 == INT_30_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&u64));
+ EXPECT_TRUE(u64 == INT_30_BITS);
+
+ p.Rollback();
+
+ int8_t s8;
+ int16_t s16;
+ int32_t s32;
+ int64_t s64;
+ EXPECT_FALSE(p.ReadInteger(&s8));
+ EXPECT_FALSE(p.ReadInteger(&s16));
+ EXPECT_TRUE(p.ReadInteger(&s32));
+ EXPECT_TRUE(s32 == INT_30_BITS);
+ p.Rollback();
+ EXPECT_TRUE(p.ReadInteger(&s64));
+ EXPECT_TRUE(s64 == INT_30_BITS);
+ EXPECT_TRUE(p.CheckWord("UL"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_32_BITS));
+ uint32_t u32;
+ int32_t s32;
+ EXPECT_FALSE(p.ReadInteger(&s32));
+ EXPECT_TRUE(p.ReadInteger(&u32));
+ EXPECT_TRUE(u32 == INT_32_BITS);
+ EXPECT_TRUE(p.CheckWord("UL"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(NS_STRINGIFY(INT_50_BITS));
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ EXPECT_FALSE(p.ReadInteger(&u8));
+ EXPECT_FALSE(p.ReadInteger(&u16));
+ EXPECT_FALSE(p.ReadInteger(&u32));
+ EXPECT_TRUE(p.ReadInteger(&u64));
+ EXPECT_TRUE(u64 == INT_50_BITS);
+ EXPECT_TRUE(p.CheckWord("ULL"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p(STR_INT_MORE_THAN_64_BITS);
+ int64_t i;
+ EXPECT_FALSE(p.ReadInteger(&i));
+ uint64_t u;
+ EXPECT_FALSE(p.ReadInteger(&u));
+ EXPECT_FALSE(p.CheckEOF());
+ }
+}
+
+TEST(Tokenizer, ReadUntil)
+{
+ Tokenizer p("Hello;test 4,");
+ nsDependentCSubstring f;
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(';'), f));
+ EXPECT_TRUE(f == "Hello");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(';'), f, Tokenizer::INCLUDE_LAST));
+ EXPECT_TRUE(f == "Hello;");
+ p.Rollback();
+
+ EXPECT_FALSE(p.ReadUntil(Tokenizer::Token::Char('!'), f));
+ EXPECT_TRUE(f == "Hello;test 4,");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word(NS_LITERAL_CSTRING("test")), f));
+ EXPECT_TRUE(f == "Hello;");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word(NS_LITERAL_CSTRING("test")), f, Tokenizer::INCLUDE_LAST));
+ EXPECT_TRUE(f == "Hello;test");
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(','), f));
+ EXPECT_TRUE(f == " 4");
+}
+
+TEST(Tokenizer, SkipUntil)
+{
+ {
+ Tokenizer p("test1,test2,,,test3");
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckWord("test2"));
+
+ p.SkipUntil(Tokenizer::Token::Char(',')); // must not move
+ EXPECT_TRUE(p.CheckChar(',')); // check the first comma of the ',,,' string
+
+ p.Rollback(); // moves cursor back to the first comma of the ',,,' string
+
+ p.SkipUntil(Tokenizer::Token::Char(',')); // must not move, we are on the ',' char
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckChar(','));
+ EXPECT_TRUE(p.CheckWord("test3"));
+ p.Rollback();
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+
+ {
+ Tokenizer p("test0,test1,test2");
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ p.Rollback();
+
+ EXPECT_TRUE(p.CheckWord("test1"));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ p.SkipUntil(Tokenizer::Token::Char(','));
+ p.Rollback();
+
+ EXPECT_TRUE(p.CheckWord("test2"));
+ EXPECT_TRUE(p.CheckEOF());
+ }
+}
+
+TEST(Tokenizer, Custom)
+{
+ Tokenizer p("aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2");
+
+ Tokenizer::Token c1 = p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE);
+ Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE);
+
+ // It's expected to NOT FIND the custom token if it's not on an edge
+ // between other recognizable tokens.
+ EXPECT_TRUE(p.CheckWord("aaaaaacustom"));
+ EXPECT_TRUE(p.CheckChar('-'));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1)));
+ EXPECT_TRUE(p.CheckEOL());
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(c1));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(c1));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ p.EnableCustomToken(c1, false);
+ EXPECT_TRUE(p.CheckWord("Custom"));
+ EXPECT_TRUE(p.CheckChar('-'));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1)));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(0)));
+ EXPECT_TRUE(p.Check(c2));
+ EXPECT_TRUE(p.CheckWord("xxxx"));
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.CheckWord("CUSTOM"));
+ EXPECT_TRUE(p.CheckChar('-'));
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(2)));
+
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, CustomRaw)
+{
+ Tokenizer p("aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2");
+
+ Tokenizer::Token c1 = p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE);
+ Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE);
+
+ // In this mode it's expected to find all custom tokens among any kind of input.
+ p.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ Tokenizer::Token t;
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral("aaaaaa"));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral("\r,"));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral(","));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral(","));
+
+ EXPECT_TRUE(p.Check(c1));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral(",00"));
+
+ EXPECT_TRUE(p.Check(c2));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW);
+ EXPECT_TRUE(t.Fragment().EqualsLiteral("xxxx,CUSTOM-2"));
+
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, Incremental)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break;
+ case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); break;
+ case 4: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 5: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 7: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test3")))); break;
+ case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+ for (; cur < end; ++cur) {
+ i.FeedInput(nsDependentCSubstring(cur, 1));
+ }
+
+ EXPECT_TRUE(test == 6);
+ i.FinishInput();
+ EXPECT_TRUE(test == 8);
+}
+
+TEST(Tokenizer, IncrementalRollback)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test1")))); break;
+ case 2: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2"))));
+ i.Rollback(); // so that we get the token again
+ break;
+ case 4: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test2")))); break;
+ case 5: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 7: EXPECT_TRUE(t.Equals(Token::Char(','))); break;
+ case 8: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("test3")))); break;
+ case 9: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ NS_NAMED_LITERAL_CSTRING(input, "test1,test2,,,test3");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+ for (; cur < end; ++cur) {
+ i.FeedInput(nsDependentCSubstring(cur, 1));
+ }
+
+ EXPECT_TRUE(test == 7);
+ i.FinishInput();
+ EXPECT_TRUE(test == 9);
+}
+
+TEST(Tokenizer, IncrementalNeedMoreInput)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ Token t2;
+ switch (++test) {
+ case 1:
+ EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("a"))));
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ EXPECT_TRUE(t.Equals(Token::Whitespace()));
+ if (i.Next(t2)) {
+ EXPECT_TRUE(test == 5);
+ EXPECT_TRUE(t2.Equals(Token::Word(NS_LITERAL_CSTRING("bb"))));
+ } else {
+ EXPECT_TRUE(test < 5);
+ i.NeedMoreInput();
+ }
+ break;
+ case 6:
+ EXPECT_TRUE(t.Equals(Token::Char(',')));
+ break;
+ case 7:
+ EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("c"))));
+ return NS_ERROR_FAILURE;
+ default:
+ EXPECT_TRUE(false);
+ break;
+ }
+
+ return NS_OK;
+ });
+
+ NS_NAMED_LITERAL_CSTRING(input, "a bb,c");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+
+ nsresult rv;
+ for (; cur < end; ++cur) {
+ rv = i.FeedInput(nsDependentCSubstring(cur, 1));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ EXPECT_TRUE(rv == NS_OK);
+ EXPECT_TRUE(test == 6);
+
+ rv = i.FinishInput();
+ EXPECT_TRUE(rv == NS_ERROR_FAILURE);
+ EXPECT_TRUE(test == 7);
+}
+
+TEST(Tokenizer, IncrementalCustom)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(custom)); break;
+ case 2: EXPECT_TRUE(t.Equals(Token::Word(NS_LITERAL_CSTRING("bla")))); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ }, nullptr, "-");
+
+ custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE);
+ i.FeedInput(NS_LITERAL_CSTRING("some-"));
+ EXPECT_TRUE(test == 0);
+ i.FeedInput(NS_LITERAL_CSTRING("tes"));
+ EXPECT_TRUE(test == 0);
+ i.FeedInput(NS_LITERAL_CSTRING("tbla"));
+ EXPECT_TRUE(test == 1);
+ i.FinishInput();
+ EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalCustomRaw)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("test1,")); break;
+ case 2: EXPECT_TRUE(t.Equals(custom)); break;
+ case 3: EXPECT_TRUE(t.Fragment().EqualsLiteral("!,,test3"));
+ i.Rollback();
+ i.SetTokenizingMode(Tokenizer::Mode::FULL);
+ break;
+ case 4: EXPECT_TRUE(t.Equals(Token::Char('!')));
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+ break;
+ case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral(",,test3")); break;
+ case 6: EXPECT_TRUE(t.Equals(custom)); break;
+ case 7: EXPECT_TRUE(t.Fragment().EqualsLiteral("tes")); break;
+ case 8: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ custom = i.AddCustomToken("test2", Tokenizer::CASE_SENSITIVE);
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ NS_NAMED_LITERAL_CSTRING(input, "test1,test2!,,test3test2tes");
+ auto cur = input.BeginReading();
+ auto end = input.EndReading();
+ for (; cur < end; ++cur) {
+ i.FeedInput(nsDependentCSubstring(cur, 1));
+ }
+
+ EXPECT_TRUE(test == 6);
+ i.FinishInput();
+ EXPECT_TRUE(test == 8);
+}
+
+TEST(Tokenizer, IncrementalCustomRemove)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Equals(custom));
+ i.RemoveCustomToken(custom);
+ break;
+ case 2: EXPECT_FALSE(t.Equals(custom)); break;
+ case 3: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ return NS_OK;
+ });
+
+ custom = i.AddCustomToken("custom1", Tokenizer::CASE_SENSITIVE);
+
+ NS_NAMED_LITERAL_CSTRING(input, "custom1custom1");
+ i.FeedInput(input);
+ EXPECT_TRUE(test == 1);
+ i.FinishInput();
+ EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalBuffering1)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ nsDependentCSubstring observedFragment;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("012")); break;
+ case 2: EXPECT_TRUE(t.Fragment().EqualsLiteral("3456789")); break;
+ case 3: EXPECT_TRUE(t.Equals(custom)); break;
+ case 4: EXPECT_TRUE(t.Fragment().EqualsLiteral("qwe")); break;
+ case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral("rt")); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+
+ observedFragment.Rebind(t.Fragment().BeginReading(),
+ t.Fragment().Length());
+ return NS_OK;
+ }, nullptr, nullptr, 3);
+
+ custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE);
+ // This externally unused token is added only to check the internal algorithm
+ // does work correctly as expected when there are two different length tokens.
+ Unused << i.AddCustomToken("bb", Tokenizer::CASE_SENSITIVE);
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ i.FeedInput(NS_LITERAL_CSTRING("01234"));
+ EXPECT_TRUE(test == 1);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("012"));
+
+ i.FeedInput(NS_LITERAL_CSTRING("5"));
+ EXPECT_TRUE(test == 1);
+ i.FeedInput(NS_LITERAL_CSTRING("6789aa"));
+ EXPECT_TRUE(test == 2);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("3456789"));
+
+ i.FeedInput(NS_LITERAL_CSTRING("aqwert"));
+ EXPECT_TRUE(test == 4);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("qwe"));
+
+ i.FinishInput();
+ EXPECT_TRUE(test == 6);
+}
+
+TEST(Tokenizer, IncrementalBuffering2)
+{
+ typedef TokenizerBase::Token Token;
+
+ int test = 0;
+ Token custom;
+ IncrementalTokenizer i([&](Token const& t, IncrementalTokenizer& i) -> nsresult
+ {
+ switch (++test) {
+ case 1: EXPECT_TRUE(t.Fragment().EqualsLiteral("01")); break;
+ case 2: EXPECT_TRUE(t.Fragment().EqualsLiteral("234567")); break;
+ case 3: EXPECT_TRUE(t.Fragment().EqualsLiteral("89")); break;
+ case 4: EXPECT_TRUE(t.Equals(custom)); break;
+ case 5: EXPECT_TRUE(t.Fragment().EqualsLiteral("qwert")); break;
+ case 6: EXPECT_TRUE(t.Equals(Token::EndOfFile())); break;
+ }
+ return NS_OK;
+ }, nullptr, nullptr, 3);
+
+ custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE);
+ // This externally unused token is added only to check the internal algorithm
+ // does work correctly as expected when there are two different length tokens.
+ Unused << i.AddCustomToken("bbbbb", Tokenizer::CASE_SENSITIVE);
+ i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
+
+ i.FeedInput(NS_LITERAL_CSTRING("01234"));
+ EXPECT_TRUE(test == 0);
+ i.FeedInput(NS_LITERAL_CSTRING("5"));
+ EXPECT_TRUE(test == 1);
+ i.FeedInput(NS_LITERAL_CSTRING("6789aa"));
+ EXPECT_TRUE(test == 2);
+ i.FeedInput(NS_LITERAL_CSTRING("aqwert"));
+ EXPECT_TRUE(test == 4);
+ i.FinishInput();
+ EXPECT_TRUE(test == 6);
+}
diff --git a/xpcom/tests/gtest/TestUTF.cpp b/xpcom/tests/gtest/TestUTF.cpp
new file mode 100644
index 000000000..14dc03abe
--- /dev/null
+++ b/xpcom/tests/gtest/TestUTF.cpp
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsReadableUtils.h"
+#include "UTFStrings.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/HashFunctions.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+namespace TestUTF {
+
+TEST(UTF, Valid)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsDependentCString str8(ValidStrings[i].m8);
+ nsDependentString str16(ValidStrings[i].m16);
+
+ EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8));
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16));
+
+ nsCString tmp8("string ");
+ AppendUTF16toUTF8(str16, tmp8);
+ EXPECT_TRUE(tmp8.Equals(NS_LITERAL_CSTRING("string ") + str8));
+
+ nsString tmp16(NS_LITERAL_STRING("string "));
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(NS_LITERAL_STRING("string ") + str16));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Invalid16)
+{
+ for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) {
+ nsDependentString str16(Invalid16Strings[i].m16);
+ nsDependentCString str8(Invalid16Strings[i].m8);
+
+ EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8));
+
+ nsCString tmp8("string ");
+ AppendUTF16toUTF8(str16, tmp8);
+ EXPECT_TRUE(tmp8.Equals(NS_LITERAL_CSTRING("string ") + str8));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Invalid8)
+{
+ for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) {
+ nsDependentString str16(Invalid8Strings[i].m16);
+ nsDependentCString str8(Invalid8Strings[i].m8);
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16));
+
+ nsString tmp16(NS_LITERAL_STRING("string "));
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(NS_LITERAL_STRING("string ") + str16));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Malformed8)
+{
+// Don't run this test in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsDependentCString str8(Malformed8Strings[i]);
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).IsEmpty());
+
+ nsString tmp16(NS_LITERAL_STRING("string"));
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.EqualsLiteral("string"));
+
+ EXPECT_NE(CompareUTF8toUTF16(str8, EmptyString()), 0);
+ }
+#endif
+}
+
+TEST(UTF, Hash16)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsDependentCString str8(ValidStrings[i].m8);
+ bool err;
+ EXPECT_EQ(HashString(ValidStrings[i].m16),
+ HashUTF8AsUTF16(str8.get(), str8.Length(), &err));
+ EXPECT_FALSE(err);
+ }
+
+ for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) {
+ nsDependentCString str8(Invalid8Strings[i].m8);
+ bool err;
+ EXPECT_EQ(HashString(Invalid8Strings[i].m16),
+ HashUTF8AsUTF16(str8.get(), str8.Length(), &err));
+ EXPECT_FALSE(err);
+ }
+
+// Don't run this test in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsDependentCString str8(Malformed8Strings[i]);
+ bool err;
+ EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u);
+ EXPECT_TRUE(err);
+ }
+#endif
+}
+
+/**
+ * This tests the handling of a non-ascii character at various locations in a
+ * UTF-16 string that is being converted to UTF-8.
+ */
+void NonASCII16_helper(const size_t aStrSize)
+{
+ const size_t kTestSize = aStrSize;
+ const size_t kMaxASCII = 0x80;
+ const char16_t kUTF16Char = 0xC9;
+ const char kUTF8Surrogates[] = { char(0xC3), char(0x89) };
+
+ // Generate a string containing only ASCII characters.
+ nsString asciiString;
+ asciiString.SetLength(kTestSize);
+ nsCString asciiCString;
+ asciiCString.SetLength(kTestSize);
+
+ auto str_buff = asciiString.BeginWriting();
+ auto cstr_buff = asciiCString.BeginWriting();
+ for (size_t i = 0; i < kTestSize; i++) {
+ str_buff[i] = i % kMaxASCII;
+ cstr_buff[i] = i % kMaxASCII;
+ }
+
+ // Now go through and test conversion when exactly one character will
+ // result in a multibyte sequence.
+ for (size_t i = 0; i < kTestSize; i++) {
+ // Setup the UTF-16 string.
+ nsString unicodeString(asciiString);
+ auto buff = unicodeString.BeginWriting();
+ buff[i] = kUTF16Char;
+
+ // Do the conversion, make sure the length increased by 1.
+ nsCString dest;
+ AppendUTF16toUTF8(unicodeString, dest);
+ EXPECT_EQ(dest.Length(), unicodeString.Length() + 1);
+
+ // Build up the expected UTF-8 string.
+ nsCString expected;
+
+ // First add the leading ASCII chars.
+ expected.Append(asciiCString.BeginReading(), i);
+
+ // Now append the UTF-8 surrogate pair we expect the UTF-16 unicode char to
+ // be converted to.
+ for (auto& c : kUTF8Surrogates) {
+ expected.Append(c);
+ }
+
+ // And finish with the trailing ASCII chars.
+ expected.Append(asciiCString.BeginReading() + i + 1, kTestSize - i - 1);
+
+ EXPECT_STREQ(dest.BeginReading(), expected.BeginReading());
+ }
+}
+
+TEST(UTF, NonASCII16)
+{
+ // Test with various string sizes to catch any special casing.
+ NonASCII16_helper(1);
+ NonASCII16_helper(8);
+ NonASCII16_helper(16);
+ NonASCII16_helper(32);
+ NonASCII16_helper(512);
+}
+
+} // namespace TestUTF
diff --git a/xpcom/tests/gtest/TestXPIDLString.cpp b/xpcom/tests/gtest/TestXPIDLString.cpp
new file mode 100644
index 000000000..b26cc9aff
--- /dev/null
+++ b/xpcom/tests/gtest/TestXPIDLString.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "gtest/gtest.h"
+
+static void
+nsXPIDLStringTest_Value(char16_t** aResult)
+{
+ *aResult = ToNewUnicode(NS_LITERAL_STRING("Hello, World"));
+}
+
+TEST(XPIDLString, Main)
+{
+ nsXPIDLString s1;
+ nsXPIDLStringTest_Value(getter_Copies(s1));
+ EXPECT_TRUE(s1.EqualsLiteral("Hello, World"));
+}
+
diff --git a/xpcom/tests/gtest/UTFStrings.h b/xpcom/tests/gtest/UTFStrings.h
new file mode 100644
index 000000000..ec6bf15d3
--- /dev/null
+++ b/xpcom/tests/gtest/UTFStrings.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef utfstrings_h__
+#define utfstrings_h__
+
+struct UTFStringsStringPair
+ {
+ char16_t m16[16];
+ char m8[16];
+ };
+
+static const UTFStringsStringPair ValidStrings[] =
+ {
+ { { 'a', 'b', 'c', 'd' },
+ { 'a', 'b', 'c', 'd' } },
+ { { '1', '2', '3', '4' },
+ { '1', '2', '3', '4' } },
+ { { 0x7F, 'A', 0x80, 'B', 0x101, 0x200 },
+ { 0x7F, 'A', char(0xC2), char(0x80), 'B', char(0xC4), char(0x81), char(0xC8), char(0x80) } },
+ { { 0x7FF, 0x800, 0x1000 },
+ { char(0xDF), char(0xBF), char(0xE0), char(0xA0), char(0x80), char(0xE1), char(0x80), char(0x80) } },
+ { { 0xD7FF, 0xE000, 0xF00F, 'A', 0xFFF0 },
+ { char(0xED), char(0x9F), char(0xBF), char(0xEE), char(0x80), char(0x80), char(0xEF), char(0x80), char(0x8F), 'A', char(0xEF), char(0xBF), char(0xB0) } },
+ { { 0xFFF7, 0xFFFC, 0xFFFD, 0xFFFD },
+ { char(0xEF), char(0xBF), char(0xB7), char(0xEF), char(0xBF), char(0xBC), char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xD800, 0xDC00, 0xD800, 0xDCFF },
+ { char(0xF0), char(0x90), char(0x80), char(0x80), char(0xF0), char(0x90), char(0x83), char(0xBF) } },
+ { { 0xDBFF, 0xDFFF, 0xDBB7, 0xDCBA },
+ { char(0xF4), char(0x8F), char(0xBF), char(0xBF), char(0xF3), char(0xBD), char(0xB2), char(0xBA) } },
+ { { 0xFFFD, 0xFFFF },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBF) } },
+ { { 0xFFFD, 0xFFFE, 0xFFFF },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBE), char(0xEF), char(0xBF), char(0xBF) } },
+ };
+
+static const UTFStringsStringPair Invalid16Strings[] =
+ {
+ { { 'a', 'b', 0xD800 },
+ { 'a', 'b', char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xD8FF, 'b' },
+ { char(0xEF), char(0xBF), char(0xBD), 'b' } },
+ { { 0xD821 },
+ { char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC21 },
+ { char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800, 'b' },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), 'b' } },
+ { { 'b', 0xDC00, 0xD800 },
+ { 'b', char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800 },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800, 0xDC00, 0xD800 },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xF0), char(0x90), char(0x80), char(0x80), char(0xEF), char(0xBF), char(0xBD) } },
+ { { 0xDC00, 0xD800, 0xD800, 0xDC00 },
+ { char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), char(0xF0), char(0x90), char(0x80), char(0x80) } },
+ };
+
+static const UTFStringsStringPair Invalid8Strings[] =
+ {
+ { { 'a', 0xFFFD, 'b' },
+ { 'a', char(0xC0), char(0x80), 'b' } },
+ { { 0xFFFD, 0x80 },
+ { char(0xC1), char(0xBF), char(0xC2), char(0x80) } },
+ { { 0xFFFD },
+ { char(0xC1), char(0xBF) } },
+ { { 0xFFFD, 'x', 0x0800 },
+ { char(0xE0), char(0x80), char(0x80), 'x', char(0xE0), char(0xA0), char(0x80) } },
+ { { 0xFFFD, 'x', 0xFFFD },
+ { char(0xF0), char(0x80), char(0x80), char(0x80), 'x', char(0xF0), char(0x80), char(0x8F), char(0x80) } },
+ { { 0xFFFD, 0xFFFD },
+ { char(0xF4), char(0x90), char(0x80), char(0x80), char(0xF7), char(0xBF), char(0xBF), char(0xBF) } },
+ { { 0xFFFD, 'x', 0xD800, 0xDC00, 0xFFFD },
+ { char(0xF0), char(0x8F), char(0xBF), char(0xBF), 'x', char(0xF0), char(0x90), char(0x80), char(0x80), char(0xF0), char(0x8F), char(0xBF), char(0xBF) } },
+ { { 0xFFFD, 'x', 0xFFFD },
+ { char(0xF8), char(0x80), char(0x80), char(0x80), char(0x80), 'x', char(0xF8), char(0x88), char(0x80), char(0x80), char(0x80) } },
+ { { 0xFFFD, 0xFFFD },
+ { char(0xFB), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xFC), char(0xA0), char(0x80), char(0x80), char(0x80), char(0x80) } },
+ { { 0xFFFD, 0xFFFD },
+ { char(0xFC), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), char(0xFD), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xBF) } },
+ };
+
+// Don't use this array in debug builds as that intentionally asserts.
+#ifndef DEBUG
+static const char Malformed8Strings[][16] =
+ {
+ { char(0x80) },
+ { 'a', char(0xC8), 'c' },
+ { 'a', char(0xC0) },
+ { 'a', char(0xE8), 'c' },
+ { 'a', char(0xE8), char(0x80), 'c' },
+ { 'a', char(0xE8), char(0x80) },
+ { char(0xE8), 0x7F, char(0x80) },
+ { 'a', char(0xE8), char(0xE8), char(0x80) },
+ { 'a', char(0xF4) },
+ { 'a', char(0xF4), char(0x80), char(0x80), 'c', 'c' },
+ { 'a', char(0xF4), char(0x80), 'x', char(0x80) },
+ { char(0xF4), char(0x80), char(0x80), char(0x80), char(0x80) },
+ { 'a', char(0xFA), 'c' },
+ { 'a', char(0xFA), char(0x80), char(0x80), 0x7F, char(0x80), 'c' },
+ { 'a', char(0xFA), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), 'c' },
+ { 'a', char(0xFD) },
+ { 'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), 'c' },
+ { 'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80) },
+ { 'a', char(0xFC), char(0x80), char(0x80), 0x40, char(0x80), char(0x80), 'c' },
+ };
+#endif
+
+#endif
diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build
new file mode 100644
index 000000000..53836eaef
--- /dev/null
+++ b/xpcom/tests/gtest/moz.build
@@ -0,0 +1,77 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ 'Helpers.cpp',
+ 'TestAtoms.cpp',
+ 'TestAutoPtr.cpp',
+ 'TestAutoRef.cpp',
+ 'TestBase64.cpp',
+ 'TestCallTemplates.cpp',
+ 'TestCloneInputStream.cpp',
+ 'TestCOMArray.cpp',
+ 'TestCOMPtrEq.cpp',
+ 'TestCRT.cpp',
+ 'TestEncoding.cpp',
+ 'TestEscapeURL.cpp',
+ 'TestExpirationTracker.cpp',
+ 'TestFile.cpp',
+ 'TestID.cpp',
+ 'TestNSPRLogModulesParser.cpp',
+ 'TestObserverArray.cpp',
+ 'TestObserverService.cpp',
+ 'TestPipes.cpp',
+ 'TestPLDHash.cpp',
+ 'TestPriorityQueue.cpp',
+ 'TestRacingServiceManager.cpp',
+ 'TestSlicedInputStream.cpp',
+ 'TestSnappyStreams.cpp',
+ 'TestStateWatching.cpp',
+ 'TestStorageStream.cpp',
+ 'TestStrings.cpp',
+ 'TestStringStream.cpp',
+ 'TestSynchronization.cpp',
+ 'TestTArray.cpp',
+ 'TestTArray2.cpp',
+ 'TestTextFormatter.cpp',
+ 'TestThreadPool.cpp',
+ 'TestThreadPoolListener.cpp',
+ 'TestThreads.cpp',
+ 'TestThreadUtils.cpp',
+ 'TestTimers.cpp',
+ 'TestTimeStamp.cpp',
+ 'TestTokenizer.cpp',
+ 'TestUTF.cpp',
+ 'TestXPIDLString.cpp',
+]
+
+if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT') and CONFIG['OS_TARGET'] != 'Android':
+ # FIXME bug 523392: TestDeadlockDetector doesn't like Windows
+ # Bug 1054249: Doesn't work on Android
+ UNIFIED_SOURCES += [
+ 'TestDeadlockDetector.cpp',
+ 'TestDeadlockDetectorScalability.cpp',
+ ]
+
+if CONFIG['WRAP_STL_INCLUDES'] and not CONFIG['CLANG_CL']:
+ UNIFIED_SOURCES += [
+ 'TestSTLWrappers.cpp',
+ ]
+
+# Compile TestAllocReplacement separately so Windows headers don't pollute
+# the global namespace for other files.
+SOURCES += [
+ 'TestAllocReplacement.cpp',
+ 'TestCOMPtr.cpp', # Redefines IFoo and IBar
+ 'TestHashtables.cpp', # Redefines IFoo
+ 'TestNsRefPtr.cpp', # Redefines Foo
+]
+
+LOCAL_INCLUDES += [
+ '../../base',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
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
new file mode 100755
index 000000000..c821003d3
--- /dev/null
+++ b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp
Binary files differ
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
new file mode 100644
index 000000000..5e45963c3
--- /dev/null
+++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings
Binary files differ
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
new file mode 100644
index 000000000..bb27d4a5d
--- /dev/null
+++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
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
new file mode 100644
index 000000000..49cc8ef0e
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..05255100a
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..e95d97113
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..a49491816
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..e9023ac7c
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..d1c167e6e
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..e450566a0
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..ef1da39e2
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..e5e70b661
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..932d4004b
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..8a2912722
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..ebd9a51d3
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..bbc3413aa
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..e60525dec
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..142b17590
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini
Binary files differ
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
new file mode 100644
index 000000000..26178d809
--- /dev/null
+++ b/xpcom/tests/unit/data/presentation.key/index.apxl.gz
Binary files differ
diff --git a/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff
new file mode 100644
index 000000000..8b49316b4
--- /dev/null
+++ b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff
Binary files differ
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'