summaryrefslogtreecommitdiffstats
path: root/xpcom/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /xpcom/tests
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/tests')
-rw-r--r--xpcom/tests/NotXPCOMTest.idl17
-rw-r--r--xpcom/tests/RegFactory.cpp118
-rw-r--r--xpcom/tests/SizeTest01.cpp107
-rw-r--r--xpcom/tests/SizeTest02.cpp87
-rw-r--r--xpcom/tests/SizeTest03.cpp94
-rw-r--r--xpcom/tests/SizeTest04.cpp61
-rw-r--r--xpcom/tests/SizeTest05.cpp65
-rw-r--r--xpcom/tests/SizeTest06.cpp148
-rw-r--r--xpcom/tests/TestArguments.cpp16
-rw-r--r--xpcom/tests/TestBlockingProcess.cpp8
-rw-r--r--xpcom/tests/TestHarness.h268
-rw-r--r--xpcom/tests/TestMemoryPressureWatcherLinux.cpp65
-rw-r--r--xpcom/tests/TestPRIntN.cpp39
-rw-r--r--xpcom/tests/TestQuickReturn.cpp5
-rw-r--r--xpcom/tests/TestShutdown.cpp37
-rw-r--r--xpcom/tests/TestStreamUtils.cpp63
-rw-r--r--xpcom/tests/TestUnicodeArguments.cpp73
-rw-r--r--xpcom/tests/TestWinReg.js64
-rw-r--r--xpcom/tests/TestingAtomList.h6
-rw-r--r--xpcom/tests/crashtests/bug-1714685.html13
-rw-r--r--xpcom/tests/crashtests/crashtests.list1
-rw-r--r--xpcom/tests/gtest/Helpers.cpp201
-rw-r--r--xpcom/tests/gtest/Helpers.h195
-rw-r--r--xpcom/tests/gtest/TestAllocReplacement.cpp106
-rw-r--r--xpcom/tests/gtest/TestArenaAllocator.cpp310
-rw-r--r--xpcom/tests/gtest/TestArrayAlgorithm.cpp108
-rw-r--r--xpcom/tests/gtest/TestAtoms.cpp177
-rw-r--r--xpcom/tests/gtest/TestAutoRefCnt.cpp66
-rw-r--r--xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp227
-rw-r--r--xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp226
-rw-r--r--xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp663
-rw-r--r--xpcom/tests/gtest/TestBase64.cpp454
-rw-r--r--xpcom/tests/gtest/TestCOMArray.cpp295
-rw-r--r--xpcom/tests/gtest/TestCOMPtr.cpp436
-rw-r--r--xpcom/tests/gtest/TestCOMPtrEq.cpp82
-rw-r--r--xpcom/tests/gtest/TestCRT.cpp90
-rw-r--r--xpcom/tests/gtest/TestCallTemplates.cpp115
-rw-r--r--xpcom/tests/gtest/TestCloneInputStream.cpp236
-rw-r--r--xpcom/tests/gtest/TestDafsa.cpp82
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetector.cpp314
-rw-r--r--xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp163
-rw-r--r--xpcom/tests/gtest/TestDelayedRunnable.cpp168
-rw-r--r--xpcom/tests/gtest/TestEncoding.cpp108
-rw-r--r--xpcom/tests/gtest/TestEscape.cpp238
-rw-r--r--xpcom/tests/gtest/TestEventPriorities.cpp91
-rw-r--r--xpcom/tests/gtest/TestEventTargetQI.cpp83
-rw-r--r--xpcom/tests/gtest/TestExpirationTracker.cpp192
-rw-r--r--xpcom/tests/gtest/TestFile.cpp576
-rw-r--r--xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp289
-rw-r--r--xpcom/tests/gtest/TestFilePreferencesUnix.cpp233
-rw-r--r--xpcom/tests/gtest/TestFilePreferencesWin.cpp196
-rw-r--r--xpcom/tests/gtest/TestGCPostBarriers.cpp165
-rw-r--r--xpcom/tests/gtest/TestHandleWatcher.cpp580
-rw-r--r--xpcom/tests/gtest/TestHashtables.cpp1617
-rw-r--r--xpcom/tests/gtest/TestID.cpp33
-rw-r--r--xpcom/tests/gtest/TestIDUtils.cpp36
-rw-r--r--xpcom/tests/gtest/TestInputStreamLengthHelper.cpp161
-rw-r--r--xpcom/tests/gtest/TestJSHolderMap.cpp348
-rw-r--r--xpcom/tests/gtest/TestLogCommandLineHandler.cpp183
-rw-r--r--xpcom/tests/gtest/TestLogging.cpp182
-rw-r--r--xpcom/tests/gtest/TestMacNSURLEscaping.mm140
-rw-r--r--xpcom/tests/gtest/TestMemoryPressure.cpp199
-rw-r--r--xpcom/tests/gtest/TestMoveString.cpp266
-rw-r--r--xpcom/tests/gtest/TestMozPromise.cpp756
-rw-r--r--xpcom/tests/gtest/TestMruCache.cpp395
-rw-r--r--xpcom/tests/gtest/TestMultiplexInputStream.cpp958
-rw-r--r--xpcom/tests/gtest/TestNSPRLogModulesParser.cpp167
-rw-r--r--xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp379
-rw-r--r--xpcom/tests/gtest/TestNsDeque.cpp594
-rw-r--r--xpcom/tests/gtest/TestNsRefPtr.cpp444
-rw-r--r--xpcom/tests/gtest/TestObserverArray.cpp573
-rw-r--r--xpcom/tests/gtest/TestObserverService.cpp281
-rw-r--r--xpcom/tests/gtest/TestOwningNonNull.cpp24
-rw-r--r--xpcom/tests/gtest/TestPLDHash.cpp407
-rw-r--r--xpcom/tests/gtest/TestPipes.cpp1031
-rw-r--r--xpcom/tests/gtest/TestPriorityQueue.cpp73
-rw-r--r--xpcom/tests/gtest/TestQueue.cpp186
-rw-r--r--xpcom/tests/gtest/TestRWLock.cpp214
-rw-r--r--xpcom/tests/gtest/TestRacingServiceManager.cpp260
-rw-r--r--xpcom/tests/gtest/TestRecursiveMutex.cpp25
-rw-r--r--xpcom/tests/gtest/TestRustRegex.cpp181
-rw-r--r--xpcom/tests/gtest/TestSTLWrappers.cpp65
-rw-r--r--xpcom/tests/gtest/TestSegmentedBuffer.cpp41
-rw-r--r--xpcom/tests/gtest/TestSlicedInputStream.cpp665
-rw-r--r--xpcom/tests/gtest/TestSmallArrayLRUCache.cpp368
-rw-r--r--xpcom/tests/gtest/TestSnappyStreams.cpp162
-rw-r--r--xpcom/tests/gtest/TestStateMirroring.cpp121
-rw-r--r--xpcom/tests/gtest/TestStateWatching.cpp50
-rw-r--r--xpcom/tests/gtest/TestStorageStream.cpp130
-rw-r--r--xpcom/tests/gtest/TestStringStream.cpp100
-rw-r--r--xpcom/tests/gtest/TestStrings.cpp2801
-rw-r--r--xpcom/tests/gtest/TestSubstringTuple.cpp55
-rw-r--r--xpcom/tests/gtest/TestSynchronization.cpp324
-rw-r--r--xpcom/tests/gtest/TestTArray.cpp1042
-rw-r--r--xpcom/tests/gtest/TestTArray2.cpp1524
-rw-r--r--xpcom/tests/gtest/TestTaskQueue.cpp215
-rw-r--r--xpcom/tests/gtest/TestTextFormatter.cpp237
-rw-r--r--xpcom/tests/gtest/TestThreadManager.cpp147
-rw-r--r--xpcom/tests/gtest/TestThreadPool.cpp211
-rw-r--r--xpcom/tests/gtest/TestThreadPoolListener.cpp205
-rw-r--r--xpcom/tests/gtest/TestThreadUtils.cpp2226
-rw-r--r--xpcom/tests/gtest/TestThreads.cpp415
-rw-r--r--xpcom/tests/gtest/TestThreads_mac.mm55
-rw-r--r--xpcom/tests/gtest/TestThrottledEventQueue.cpp613
-rw-r--r--xpcom/tests/gtest/TestTimeStamp.cpp70
-rw-r--r--xpcom/tests/gtest/TestTimers.cpp928
-rw-r--r--xpcom/tests/gtest/TestTokenizer.cpp1447
-rw-r--r--xpcom/tests/gtest/TestUTF.cpp264
-rw-r--r--xpcom/tests/gtest/TestVariant.cpp156
-rw-r--r--xpcom/tests/gtest/UTFStrings.h130
-rw-r--r--xpcom/tests/gtest/dafsa_test_1.dat6
-rw-r--r--xpcom/tests/gtest/moz.build181
-rw-r--r--xpcom/tests/gtest/wikipedia/README.txt13
-rw-r--r--xpcom/tests/gtest/wikipedia/ar.txt70
-rw-r--r--xpcom/tests/gtest/wikipedia/de-edit.txt487
-rw-r--r--xpcom/tests/gtest/wikipedia/de.txt487
-rw-r--r--xpcom/tests/gtest/wikipedia/ja.txt151
-rw-r--r--xpcom/tests/gtest/wikipedia/ko.txt110
-rw-r--r--xpcom/tests/gtest/wikipedia/ru.txt410
-rw-r--r--xpcom/tests/gtest/wikipedia/th.txt412
-rw-r--r--xpcom/tests/gtest/wikipedia/tr.txt245
-rw-r--r--xpcom/tests/gtest/wikipedia/vi.txt333
-rw-r--r--xpcom/tests/moz.build57
-rw-r--r--xpcom/tests/resources.h19
-rw-r--r--xpcom/tests/test.properties14
-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/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/iniparser17.ini7
-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.manifest2
-rw-r--r--xpcom/tests/unit/head_xpcom.js21
-rw-r--r--xpcom/tests/unit/test_bug121341.js64
-rw-r--r--xpcom/tests/unit/test_bug1434856.js27
-rw-r--r--xpcom/tests/unit/test_bug325418.js72
-rw-r--r--xpcom/tests/unit/test_bug332389.js14
-rw-r--r--xpcom/tests/unit/test_bug333505.js10
-rw-r--r--xpcom/tests/unit/test_bug364285-1.js43
-rw-r--r--xpcom/tests/unit/test_bug374754.js65
-rw-r--r--xpcom/tests/unit/test_bug476919.js25
-rw-r--r--xpcom/tests/unit/test_bug478086.js23
-rw-r--r--xpcom/tests/unit/test_bug745466.js7
-rw-r--r--xpcom/tests/unit/test_console_service_callFunctionAndLogException.js265
-rw-r--r--xpcom/tests/unit/test_debugger_malloc_size_of.js32
-rw-r--r--xpcom/tests/unit/test_file_createUnique.js29
-rw-r--r--xpcom/tests/unit/test_file_equality.js37
-rw-r--r--xpcom/tests/unit/test_file_renameTo.js55
-rw-r--r--xpcom/tests/unit/test_getTimers.js90
-rw-r--r--xpcom/tests/unit/test_hidden_files.js24
-rw-r--r--xpcom/tests/unit/test_home.js18
-rw-r--r--xpcom/tests/unit/test_iniParser.js476
-rw-r--r--xpcom/tests/unit/test_ioutil.js29
-rw-r--r--xpcom/tests/unit/test_localfile.js288
-rw-r--r--xpcom/tests/unit/test_mac_bundle.js18
-rw-r--r--xpcom/tests/unit/test_mac_xattrs.js98
-rw-r--r--xpcom/tests/unit/test_notxpcom_scriptable.js67
-rw-r--r--xpcom/tests/unit/test_nsIMutableArray.js131
-rw-r--r--xpcom/tests/unit/test_nsIProcess.js184
-rw-r--r--xpcom/tests/unit/test_nsIProcess_stress.js24
-rw-r--r--xpcom/tests/unit/test_pipe.js55
-rw-r--r--xpcom/tests/unit/test_process_directives.js20
-rw-r--r--xpcom/tests/unit/test_process_directives_child.js3
-rw-r--r--xpcom/tests/unit/test_seek_multiplex.js164
-rw-r--r--xpcom/tests/unit/test_storagestream.js152
-rw-r--r--xpcom/tests/unit/test_streams.js170
-rw-r--r--xpcom/tests/unit/test_stringstream.js20
-rw-r--r--xpcom/tests/unit/test_symlinks.js139
-rw-r--r--xpcom/tests/unit/test_systemInfo.js26
-rw-r--r--xpcom/tests/unit/test_versioncomparator.js55
-rw-r--r--xpcom/tests/unit/test_windows_cmdline_file.js22
-rw-r--r--xpcom/tests/unit/test_windows_registry.js204
-rw-r--r--xpcom/tests/unit/xpcshell.toml113
-rw-r--r--xpcom/tests/windows/TestCOM.cpp97
-rw-r--r--xpcom/tests/windows/TestNtPathToDosPath.cpp176
-rw-r--r--xpcom/tests/windows/TestPoisonIOInterposer.cpp155
-rw-r--r--xpcom/tests/windows/moz.build17
232 files changed, 41076 insertions, 0 deletions
diff --git a/xpcom/tests/NotXPCOMTest.idl b/xpcom/tests/NotXPCOMTest.idl
new file mode 100644
index 0000000000..acc1574e79
--- /dev/null
+++ b/xpcom/tests/NotXPCOMTest.idl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIScriptableOK : nsISupports
+{
+ void method1();
+};
+
+[scriptable, builtinclass, uuid(237d01a3-771e-4c6e-adf9-c97f9aab2950)]
+interface nsIScriptableWithNotXPCOM : nsISupports
+{
+ [notxpcom] void method2();
+};
diff --git a/xpcom/tests/RegFactory.cpp b/xpcom/tests/RegFactory.cpp
new file mode 100644
index 0000000000..7130ee5e1c
--- /dev/null
+++ b/xpcom/tests/RegFactory.cpp
@@ -0,0 +1,118 @@
+/* -*- 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 "prlink.h"
+#include "nsIComponentRegistrar.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_InitXPCOM(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 0000000000..790b0fa032
--- /dev/null
+++ b/xpcom/tests/SizeTest01.cpp
@@ -0,0 +1,107 @@
+// Test01.cpp
+
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+NS_DEF_PTR(nsINode);
+
+/*
+ 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(nsINode* 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|.
+ */
+
+ nsINode* node = aDOMNode;
+ NS_IF_ADDREF(node);
+
+ if (node) node->GetNodeName(*aResult);
+
+ NS_IF_RELEASE(node);
+}
+
+void Test01_raw_optimized(nsINode* 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;
+
+ nsINode* node = aDOMNode;
+ NS_ADDREF(node);
+ node->GetNodeName(*aResult);
+ NS_RELEASE(node);
+}
+
+void Test01_nsCOMPtr(nsINode* aDOMNode, nsString* aResult)
+// m120, w46/34
+{
+ nsCOMPtr<nsINode> node = aDOMNode;
+
+ if (node) node->GetNodeName(*aResult);
+}
+
+void Test01_nsCOMPtr_optimized(nsINode* aDOMNode, nsString* aResult)
+// m112, w42/38
+{
+ // if ( !aDOMNode )
+ // return;
+
+ nsCOMPtr<nsINode> node = aDOMNode;
+ node->GetNodeName(*aResult);
+}
diff --git a/xpcom/tests/SizeTest02.cpp b/xpcom/tests/SizeTest02.cpp
new file mode 100644
index 0000000000..2673bbe70d
--- /dev/null
+++ b/xpcom/tests/SizeTest02.cpp
@@ -0,0 +1,87 @@
+// Test02.cpp
+
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+NS_DEF_PTR(nsINode);
+
+/*
+ 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;
+
+ nsINode* node = 0;
+ nsresult status =
+ aDOMNode->QueryInterface(NS_GET_IID(nsINode), (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;
+
+ nsINode* node;
+ nsresult status =
+ aDOMNode->QueryInterface(NS_GET_IID(nsINode), (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<nsINode> 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 0000000000..055db264cd
--- /dev/null
+++ b/xpcom/tests/SizeTest03.cpp
@@ -0,0 +1,94 @@
+// Test03.cpp
+
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+NS_DEF_PTR(nsINode);
+
+/*
+ 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(nsINode* 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;
+
+ nsINode* 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(nsINode* aDOMNode, nsString* aResult)
+// m124, w48
+{
+ // if ( !aDOMNode || !aResult )
+ // return NS_ERROR_NULL_POINTER;
+
+ nsINode* parent;
+ nsresult status = aDOMNode->GetParentNode(&parent);
+
+ if (NS_SUCCEEDED(status)) {
+ parent->GetNodeName(*aResult);
+ NS_RELEASE(parent);
+ }
+
+ // return status;
+}
+
+void // nsresult
+Test03_nsCOMPtr(nsINode* aDOMNode, nsString* aResult)
+// m144, w54/59
+{
+ // if ( !aDOMNode || !aResult )
+ // return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsINode> parent;
+ nsresult status = aDOMNode->GetParentNode(getter_AddRefs(parent));
+ if (parent) parent->GetNodeName(*aResult);
+
+ // return status;
+}
+
+void // nsresult
+Test03_nsCOMPtr_optimized(nsINode* aDOMNode, nsString* aResult)
+// m112, w50/45
+{
+ // if ( !aDOMNode || !aResult )
+ // return NS_ERROR_NULL_POINTER;
+
+ nsINode* temp;
+ nsresult status = aDOMNode->GetParentNode(&temp);
+ nsCOMPtr<nsINode> 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 0000000000..c026a1cabf
--- /dev/null
+++ b/xpcom/tests/SizeTest04.cpp
@@ -0,0 +1,61 @@
+// Test04.cpp
+
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+
+NS_DEF_PTR(nsINode);
+
+/*
+ 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(nsINode* newNode);
+
+ private:
+ nsINode* 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(nsINode* newNode)
+// m120, w36
+{
+ NS_IF_ADDREF(newNode);
+ NS_IF_RELEASE(mNode);
+ mNode = newNode;
+
+ // return NS_OK;
+}
+
+class Test04_nsCOMPtr {
+ public:
+ void /*nsresult*/ SetNode(nsINode* newNode);
+
+ private:
+ nsCOMPtr<nsINode> mNode;
+};
+
+void // nsresult
+Test04_nsCOMPtr::SetNode(nsINode* newNode)
+// m36, w13/13
+{
+ mNode = newNode;
+}
diff --git a/xpcom/tests/SizeTest05.cpp b/xpcom/tests/SizeTest05.cpp
new file mode 100644
index 0000000000..4c813a0dc3
--- /dev/null
+++ b/xpcom/tests/SizeTest05.cpp
@@ -0,0 +1,65 @@
+// Test05.cpp
+
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+
+NS_DEF_PTR(nsINode);
+
+/*
+ Windows:
+ raw, nsCOMPtr 21 bytes
+
+ Macintosh:
+ Raw, nsCOMPtr 64 bytes
+*/
+
+class Test05_Raw {
+ public:
+ Test05_Raw();
+ ~Test05_Raw();
+
+ void /*nsresult*/ GetNode(nsINode** aNode);
+
+ private:
+ nsINode* 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(nsINode** 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(nsINode** aNode);
+
+ private:
+ nsCOMPtr<nsINode> mNode;
+};
+
+void // nsresult
+Test05_nsCOMPtr::GetNode(nsINode** 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 0000000000..11fb352320
--- /dev/null
+++ b/xpcom/tests/SizeTest06.cpp
@@ -0,0 +1,148 @@
+// 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 0000000000..ab162d31fb
--- /dev/null
+++ b/xpcom/tests/TestArguments.cpp
@@ -0,0 +1,16 @@
+#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(R"("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 0000000000..a1996aefdb
--- /dev/null
+++ b/xpcom/tests/TestBlockingProcess.cpp
@@ -0,0 +1,8 @@
+#include <stdio.h>
+#include "mozilla/Unused.h"
+
+int main() {
+ char tmp;
+ mozilla::Unused << 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 0000000000..e575497746
--- /dev/null
+++ b/xpcom/tests/TestHarness.h
@@ -0,0 +1,268 @@
+/* -*- 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 "mozilla/Attributes.h"
+
+#include "prenv.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIDirectoryService.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "mozilla/AppShutdown.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.
+ */
+MOZ_FORMAT_PRINTF(1, 2) 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.
+ */
+MOZ_FORMAT_PRINTF(1, 2) 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);
+
+ mInitRv = NS_InitXPCOM(nullptr, nullptr, this);
+ if (NS_FAILED(mInitRv)) {
+ fail("NS_InitXPCOM returned failure code 0x%" PRIx32,
+ static_cast<uint32_t>(mInitRv));
+ return;
+ }
+ }
+
+ ~ScopedXPCOM() {
+ // If we created a profile directory, we need to remove it.
+ if (mProfD) {
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownNetTeardown);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownTeardown);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdown);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownQM);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownTelemetry);
+
+ if (NS_FAILED(mProfD->Remove(true))) {
+ NS_WARNING("Problem removing profile directory");
+ }
+
+ mProfD = nullptr;
+ }
+
+ if (NS_SUCCEEDED(mInitRv)) {
+ nsresult rv = NS_ShutdownXPCOM(nullptr);
+ if (NS_FAILED(rv)) {
+ fail("XPCOM shutdown failed with code 0x%" PRIx32,
+ static_cast<uint32_t>(rv));
+ exit(1);
+ }
+ }
+
+ printf("Finished running %s tests.\n", mTestName);
+ }
+
+ bool failed() { return NS_FAILED(mInitRv); }
+
+ 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(u"cpp-unit-profd"_ns);
+ 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.EqualsLiteral("Resources")) {
+ mGREBinD->SetNativeLeafName("MacOS"_ns);
+ }
+#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;
+ nsresult mInitRv = NS_ERROR_NOT_INITIALIZED;
+ 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/TestMemoryPressureWatcherLinux.cpp b/xpcom/tests/TestMemoryPressureWatcherLinux.cpp
new file mode 100644
index 0000000000..8adc9f1092
--- /dev/null
+++ b/xpcom/tests/TestMemoryPressureWatcherLinux.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 "mozilla/AvailableMemoryWatcherUtils.h"
+
+#include <fstream>
+
+using namespace mozilla;
+
+const char* kMemInfoPath = "/proc/meminfo";
+const char* kTestfilePath = "testdata";
+
+// Test that we are reading some value from /proc/meminfo.
+// If the values are nonzero, the test is a success.
+void TestFromProc() {
+ MemoryInfo memInfo{0, 0};
+ ReadMemoryFile(kMemInfoPath, memInfo);
+ MOZ_RELEASE_ASSERT(memInfo.memTotal != 0);
+ MOZ_RELEASE_ASSERT(memInfo.memAvailable != 0);
+}
+
+// Test a file using expected syntax.
+void TestFromFile() {
+ MemoryInfo memInfo{0, 0};
+ std::ofstream aFile(kTestfilePath);
+ aFile << "MemTotal: 12345 kB\n";
+ aFile << "MemFree: 99999 kB\n";
+ aFile << "MemAvailable: 54321 kB\n";
+ aFile.close();
+
+ ReadMemoryFile(kTestfilePath, memInfo);
+
+ MOZ_RELEASE_ASSERT(memInfo.memTotal == 12345);
+ MOZ_RELEASE_ASSERT(memInfo.memAvailable == 54321);
+
+ // remove our dummy file
+ remove(kTestfilePath);
+}
+
+// Test a file with useless data. Results should be
+// the starting struct with {0,0}.
+void TestInvalidFile() {
+ MemoryInfo memInfo{0, 0};
+ std::ofstream aFile(kTestfilePath);
+ aFile << "foo: 12345 kB\n";
+ aFile << "bar";
+ aFile.close();
+
+ ReadMemoryFile(kTestfilePath, memInfo);
+
+ MOZ_RELEASE_ASSERT(memInfo.memTotal == 0);
+ MOZ_RELEASE_ASSERT(memInfo.memAvailable == 0);
+
+ // remove our dummy file
+ remove(kTestfilePath);
+}
+
+int main() {
+ TestFromProc();
+ TestFromFile();
+ TestInvalidFile();
+ return 0;
+}
diff --git a/xpcom/tests/TestPRIntN.cpp b/xpcom/tests/TestPRIntN.cpp
new file mode 100644
index 0000000000..0873d16ef8
--- /dev/null
+++ b/xpcom/tests/TestPRIntN.cpp
@@ -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/. */
+
+#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 0000000000..07500bde9c
--- /dev/null
+++ b/xpcom/tests/TestQuickReturn.cpp
@@ -0,0 +1,5 @@
+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 0000000000..4b277d423c
--- /dev/null
+++ b/xpcom/tests/TestShutdown.cpp
@@ -0,0 +1,37 @@
+/* -*- 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[]) {
+ nsIServiceManager* servMgr;
+ nsresult rv = NS_InitXPCOM(&servMgr, nullptr, nullptr);
+ MOZ_RELEASE_ASSERT(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);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed");
+}
diff --git a/xpcom/tests/TestStreamUtils.cpp b/xpcom/tests/TestStreamUtils.cpp
new file mode 100644
index 0000000000..1a24656617
--- /dev/null
+++ b/xpcom/tests/TestStreamUtils.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "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_InitXPCOM(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 0000000000..a44f8a2f2e
--- /dev/null
+++ b/xpcom/tests/TestUnicodeArguments.cpp
@@ -0,0 +1,73 @@
+/**
+ * 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 0000000000..8462f3ff04
--- /dev/null
+++ b/xpcom/tests/TestWinReg.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = Ci.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 =
+ Cc["@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 0000000000..ffeada6057
--- /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/crashtests/bug-1714685.html b/xpcom/tests/crashtests/bug-1714685.html
new file mode 100644
index 0000000000..a06e9d44cf
--- /dev/null
+++ b/xpcom/tests/crashtests/bug-1714685.html
@@ -0,0 +1,13 @@
+<script>
+document.addEventListener('DOMContentLoaded', async () => {
+ let a = new PromiseRejectionEvent('rejectionhandled', {
+ 'promise': new Promise(async (resolve, reject) => {
+ self.addEventListener('load', () => {
+ a.visible = false
+ });
+ }),
+ 'reason':''
+ });
+ SpecialPowers.forceGC();
+})
+</script>
diff --git a/xpcom/tests/crashtests/crashtests.list b/xpcom/tests/crashtests/crashtests.list
new file mode 100644
index 0000000000..7c1435dedd
--- /dev/null
+++ b/xpcom/tests/crashtests/crashtests.list
@@ -0,0 +1 @@
+load bug-1714685.html
diff --git a/xpcom/tests/gtest/Helpers.cpp b/xpcom/tests/gtest/Helpers.cpp
new file mode 100644
index 0000000000..84053cbeb3
--- /dev/null
+++ b/xpcom/tests/gtest/Helpers.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "mozilla/gtest/MozAssertions.h"
+#include "nsIOutputStream.h"
+#include "nsStreamUtils.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.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_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_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() = default;
+
+NS_IMETHODIMP
+OutputStreamCallback::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ mCalled = true;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback);
+
+InputStreamCallback::InputStreamCallback() : mCalled(false) {}
+
+InputStreamCallback::~InputStreamCallback() = default;
+
+NS_IMETHODIMP
+InputStreamCallback::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ mCalled = true;
+ return NS_OK;
+}
+
+AsyncStringStream::AsyncStringStream(const nsACString& aBuffer) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+}
+
+NS_IMETHODIMP
+AsyncStringStream::Available(uint64_t* aLength) {
+ return mStream->Available(aLength);
+}
+
+NS_IMETHODIMP
+AsyncStringStream::StreamStatus() { return mStream->StreamStatus(); }
+
+NS_IMETHODIMP
+AsyncStringStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) {
+ return mStream->Read(aBuffer, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+AsyncStringStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AsyncStringStream::Close() {
+ nsresult rv = mStream->Close();
+ if (NS_SUCCEEDED(rv)) {
+ MaybeExecCallback(mCallback, mCallbackEventTarget);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+AsyncStringStream::IsNonBlocking(bool* aNonBlocking) {
+ return mStream->IsNonBlocking(aNonBlocking);
+}
+
+NS_IMETHODIMP
+AsyncStringStream::CloseWithStatus(nsresult aStatus) { return Close(); }
+
+NS_IMETHODIMP
+AsyncStringStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ if (aFlags & nsIAsyncInputStream::WAIT_CLOSURE_ONLY) {
+ mCallback = aCallback;
+ mCallbackEventTarget = aEventTarget;
+ return NS_OK;
+ }
+
+ MaybeExecCallback(aCallback, aEventTarget);
+ return NS_OK;
+}
+
+void AsyncStringStream::MaybeExecCallback(nsIInputStreamCallback* aCallback,
+ nsIEventTarget* aEventTarget) {
+ if (!aCallback) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback;
+ nsCOMPtr<nsIAsyncInputStream> self = this;
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "AsyncWait", [callback, self]() { callback->OnInputStreamReady(self); });
+
+ if (aEventTarget) {
+ aEventTarget->Dispatch(r.forget());
+ } else {
+ r->Run();
+ }
+}
+
+NS_IMPL_ISUPPORTS(AsyncStringStream, nsIAsyncInputStream, nsIInputStream)
+
+NS_IMPL_ADDREF(LengthInputStream);
+NS_IMPL_RELEASE(LengthInputStream);
+
+NS_INTERFACE_MAP_BEGIN(LengthInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
+ mIsAsyncInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ISUPPORTS(LengthCallback, nsIInputStreamLengthCallback)
+
+} // namespace testing
diff --git a/xpcom/tests/gtest/Helpers.h b/xpcom/tests/gtest/Helpers.h
new file mode 100644
index 0000000000..cfd7d6fc2e
--- /dev/null
+++ b/xpcom/tests/gtest/Helpers.h
@@ -0,0 +1,195 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInputStreamLength.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsThreadUtils.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
+};
+
+class AsyncStringStream final : public nsIAsyncInputStream {
+ nsCOMPtr<nsIInputStream> mStream;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit AsyncStringStream(const nsACString& aBuffer);
+
+ private:
+ ~AsyncStringStream() = default;
+
+ void MaybeExecCallback(nsIInputStreamCallback* aCallback,
+ nsIEventTarget* aEventTarget);
+
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+};
+
+// This class implements a simple nsIInputStreamLength stream.
+class LengthInputStream : public nsIInputStream,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength {
+ nsCOMPtr<nsIInputStream> mStream;
+ bool mIsInputStreamLength;
+ bool mIsAsyncInputStreamLength;
+ nsresult mLengthRv;
+ bool mNegativeValue;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ LengthInputStream(const nsACString& aBuffer, bool aIsInputStreamLength,
+ bool aIsAsyncInputStreamLength, nsresult aLengthRv = NS_OK,
+ bool aNegativeValue = false)
+ : mIsInputStreamLength(aIsInputStreamLength),
+ mIsAsyncInputStreamLength(aIsAsyncInputStreamLength),
+ mLengthRv(aLengthRv),
+ mNegativeValue(aNegativeValue) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return mStream->Available(aLength); }
+
+ NS_IMETHOD
+ StreamStatus() override { return mStream->StreamStatus(); }
+
+ 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);
+ }
+
+ NS_IMETHOD
+ Length(int64_t* aLength) override {
+ if (mNegativeValue) {
+ *aLength = -1;
+ } else {
+ mStream->Available((uint64_t*)aLength);
+ }
+ return mLengthRv;
+ }
+
+ NS_IMETHOD
+ AsyncLengthWait(nsIInputStreamLengthCallback* aCallback,
+ nsIEventTarget* aEventTarget) override {
+ RefPtr<LengthInputStream> self = this;
+ nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback;
+
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("AsyncLengthWait", [self, callback]() {
+ int64_t length;
+ self->Length(&length);
+ callback->OnInputStreamLengthReady(self, length);
+ });
+
+ return aEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ protected:
+ virtual ~LengthInputStream() = default;
+};
+
+class LengthCallback final : public nsIInputStreamLengthCallback {
+ bool mCalled;
+ int64_t mSize;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ LengthCallback() : mCalled(false), mSize(0) {}
+
+ NS_IMETHOD
+ OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream,
+ int64_t aLength) override {
+ mCalled = true;
+ mSize = aLength;
+ return NS_OK;
+ }
+
+ bool Called() const { return mCalled; }
+
+ int64_t Size() const { return mSize; }
+
+ private:
+ ~LengthCallback() = default;
+};
+
+} // namespace testing
+
+#endif // __Helpers_h
diff --git a/xpcom/tests/gtest/TestAllocReplacement.cpp b/xpcom/tests/gtest/TestAllocReplacement.cpp
new file mode 100644
index 0000000000..4b2c41b0f3
--- /dev/null
+++ b/xpcom/tests/gtest/TestAllocReplacement.cpp
@@ -0,0 +1,106 @@
+/* -*- 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 "mozmemory.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_info_ptr knowing about the pointers
+// returned by the allocator. If the function has been hooked correctly,
+// then jemalloc_info_ptr returns a TagLiveAlloc tag, or TagUnknown
+// otherwise.
+// We could also check the hooking of |free| and similar functions: once
+// we free() the returned pointer, jemalloc_info_ptr would return a tag
+// that is not TagLiveAlloc. However, in the GTests environment, with
+// other threads running in the background, it is possible for some of
+// them to get a new allocation at the same location we just freed, and
+// jemalloc_info_ptr would return a TagLiveAlloc tag.
+
+#define ASSERT_ALLOCATION_HAPPENED(lambda) \
+ ASSERT_TRUE(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;
+
+static bool ValidateHookedAllocation(void* (*aAllocator)(void),
+ void (*aFreeFunction)(void*)) {
+ void* p = aAllocator();
+
+ if (!p) {
+ return false;
+ }
+
+ jemalloc_ptr_info_t info;
+ jemalloc_ptr_info(p, &info);
+
+ // Regardless of whether that call succeeded or failed, we are done with
+ // the allocated buffer now.
+ aFreeFunction(p);
+
+ return (info.tag == PtrInfoTag::TagLiveAlloc);
+}
+
+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) \
+ ASSERT_TRUE(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/TestArenaAllocator.cpp b/xpcom/tests/gtest/TestArenaAllocator.cpp
new file mode 100644
index 0000000000..fb11952927
--- /dev/null
+++ b/xpcom/tests/gtest/TestArenaAllocator.cpp
@@ -0,0 +1,310 @@
+/* -*- 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/ArenaAllocator.h"
+#include "mozilla/ArenaAllocatorExtensions.h"
+#include "nsIMemoryReporter.h" // MOZ_MALLOC_SIZE_OF
+
+#include "gtest/gtest.h"
+
+using mozilla::ArenaAllocator;
+
+TEST(ArenaAllocator, Constructor)
+{ ArenaAllocator<4096, 4> a; }
+
+TEST(ArenaAllocator, DefaultAllocate)
+{
+ // Test default 1-byte alignment.
+ ArenaAllocator<1024> a;
+ void* x = a.Allocate(101);
+ void* y = a.Allocate(101);
+
+ // Given 1-byte aligment, we expect the allocations to follow
+ // each other exactly.
+ EXPECT_EQ(uintptr_t(x) + 101, uintptr_t(y));
+}
+
+TEST(ArenaAllocator, AllocateAlignment)
+{
+ // Test non-default 8-byte alignment.
+ static const size_t kAlignment = 8;
+ ArenaAllocator<1024, kAlignment> a;
+
+ // Make sure aligment is correct for 1-8.
+ for (size_t i = 1; i <= kAlignment; i++) {
+ // All of these should be 8 bytes
+ void* x = a.Allocate(i);
+ void* y = a.Allocate(i);
+ EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y));
+ }
+
+ // Test with slightly larger than specified alignment.
+ void* x = a.Allocate(kAlignment + 1);
+ void* y = a.Allocate(kAlignment + 1);
+
+ // Given 8-byte aligment, and a non-8-byte aligned request we expect the
+ // allocations to be padded.
+ EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y));
+
+ // We expect 7 bytes of padding to have been added.
+ EXPECT_EQ(uintptr_t(x) + kAlignment * 2, uintptr_t(y));
+}
+
+#if 0
+TEST(ArenaAllocator, AllocateZeroBytes)
+{
+ // This would have to be a death test. Since we chose to provide an
+ // infallible allocator we can't just return nullptr in the 0 case as
+ // there's no way to differentiate that from the OOM case.
+ ArenaAllocator<1024> a;
+ void* x = a.Allocate(0);
+ EXPECT_FALSE(x);
+}
+
+TEST(ArenaAllocator, BadAlignment)
+{
+ // This test causes build failures by triggering the static assert enforcing
+ // a power-of-two alignment.
+ ArenaAllocator<256, 3> a;
+ ArenaAllocator<256, 7> b;
+ ArenaAllocator<256, 17> c;
+}
+#endif
+
+TEST(ArenaAllocator, AllocateMultipleSizes)
+{
+ // Test non-default 4-byte alignment.
+ ArenaAllocator<4096, 4> a;
+
+ for (int i = 1; i < 50; i++) {
+ void* x = a.Allocate(i);
+ // All the allocations should be aligned properly.
+ EXPECT_EQ(uintptr_t(x) % 4, uintptr_t(0));
+ }
+
+ // Test a large 64-byte alignment
+ ArenaAllocator<8192, 64> b;
+ for (int i = 1; i < 100; i++) {
+ void* x = b.Allocate(i);
+ // All the allocations should be aligned properly.
+ EXPECT_EQ(uintptr_t(x) % 64, uintptr_t(0));
+ }
+}
+
+TEST(ArenaAllocator, AllocateInDifferentChunks)
+{
+ // Test default 1-byte alignment.
+ ArenaAllocator<4096> a;
+ void* x = a.Allocate(4000);
+ void* y = a.Allocate(4000);
+ EXPECT_NE(uintptr_t(x) + 4000, uintptr_t(y));
+}
+
+TEST(ArenaAllocator, AllocateLargerThanArenaSize)
+{
+ // Test default 1-byte alignment.
+ ArenaAllocator<256> a;
+ void* x = a.Allocate(4000);
+ void* y = a.Allocate(4000);
+ EXPECT_TRUE(x);
+ EXPECT_TRUE(y);
+
+ // Now try a normal allocation, it should behave as expected.
+ x = a.Allocate(8);
+ y = a.Allocate(8);
+ EXPECT_EQ(uintptr_t(x) + 8, uintptr_t(y));
+}
+
+TEST(ArenaAllocator, AllocationsPerChunk)
+{
+ // Test that expected number of allocations fit in one chunk.
+ // We use an alignment of 64-bytes to avoid worrying about differences in
+ // the header size on 32 and 64-bit platforms.
+ const size_t kArenaSize = 1024;
+ const size_t kAlignment = 64;
+ ArenaAllocator<kArenaSize, kAlignment> a;
+
+ // With an alignment of 64 bytes we expect the header to take up the first
+ // alignment sized slot leaving bytes leaving the rest available for
+ // allocation.
+ const size_t kAllocationsPerChunk = (kArenaSize / kAlignment) - 1;
+ void* x = nullptr;
+ void* y = a.Allocate(kAlignment);
+ EXPECT_TRUE(y);
+ for (size_t i = 1; i < kAllocationsPerChunk; i++) {
+ x = y;
+ y = a.Allocate(kAlignment);
+ EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y));
+ }
+
+ // The next allocation should be in a different chunk.
+ x = y;
+ y = a.Allocate(kAlignment);
+ EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y));
+}
+
+TEST(ArenaAllocator, MemoryIsValid)
+{
+ // Make multiple allocations and actually access the memory. This is
+ // expected to trip up ASAN or valgrind if out of bounds memory is
+ // accessed.
+ static const size_t kArenaSize = 1024;
+ static const size_t kAlignment = 64;
+ static const char kMark = char(0xBC);
+ ArenaAllocator<kArenaSize, kAlignment> a;
+
+ // Single allocation that should fill the arena.
+ size_t sz = kArenaSize - kAlignment;
+ char* x = (char*)a.Allocate(sz);
+ EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0));
+ memset(x, kMark, sz);
+ for (size_t i = 0; i < sz; i++) {
+ EXPECT_EQ(x[i], kMark);
+ }
+
+ // Allocation over arena size.
+ sz = kArenaSize * 2;
+ x = (char*)a.Allocate(sz);
+ EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0));
+ memset(x, kMark, sz);
+ for (size_t i = 0; i < sz; i++) {
+ EXPECT_EQ(x[i], kMark);
+ }
+
+ // Allocation half the arena size.
+ sz = kArenaSize / 2;
+ x = (char*)a.Allocate(sz);
+ EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0));
+ memset(x, kMark, sz);
+ for (size_t i = 0; i < sz; i++) {
+ EXPECT_EQ(x[i], kMark);
+ }
+
+ // Repeat, this should actually end up in a new chunk.
+ x = (char*)a.Allocate(sz);
+ EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0));
+ memset(x, kMark, sz);
+ for (size_t i = 0; i < sz; i++) {
+ EXPECT_EQ(x[i], kMark);
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(TestSizeOf);
+
+TEST(ArenaAllocator, SizeOf)
+{
+ // This tests the sizeof functionality. We can't test for equality as we
+ // can't reliably guarantee what sizes the underlying allocator is going to
+ // choose, so we just test that things grow (or not) as expected.
+ static const size_t kArenaSize = 4096;
+ ArenaAllocator<kArenaSize> a;
+
+ // Excluding *this we expect an empty arena allocator to have no overhead.
+ size_t sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_EQ(sz, size_t(0));
+
+ // Cause one chunk to be allocated.
+ (void)a.Allocate(kArenaSize / 2);
+ size_t prev_sz = sz;
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_GT(sz, prev_sz);
+
+ // Allocate within the current chunk.
+ (void)a.Allocate(kArenaSize / 4);
+ prev_sz = sz;
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_EQ(sz, prev_sz);
+
+ // Overflow to a new chunk.
+ (void)a.Allocate(kArenaSize / 2);
+ prev_sz = sz;
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_GT(sz, prev_sz);
+
+ // Allocate an oversized chunk with enough room for a header to fit in page
+ // size. We expect the underlying allocator to round up to page alignment.
+ (void)a.Allocate((kArenaSize * 2) - 64);
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_GT(sz, prev_sz);
+}
+
+TEST(ArenaAllocator, Clear)
+{
+ // Tests that the Clear function works as expected. The best proxy for
+ // checking if a clear is successful is to measure the size. If it's empty we
+ // expect the size to be 0.
+ static const size_t kArenaSize = 128;
+ ArenaAllocator<kArenaSize> a;
+
+ // Clearing an empty arena should work.
+ a.Clear();
+
+ size_t sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_EQ(sz, size_t(0));
+
+ // Allocating should work after clearing an empty arena.
+ void* x = a.Allocate(10);
+ EXPECT_TRUE(x);
+
+ size_t prev_sz = sz;
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_GT(sz, prev_sz);
+
+ // Allocate enough for a few arena chunks to be necessary.
+ for (size_t i = 0; i < kArenaSize * 2; i++) {
+ x = a.Allocate(1);
+ EXPECT_TRUE(x);
+ }
+
+ prev_sz = sz;
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_GT(sz, prev_sz);
+
+ // Clearing should reduce the size back to zero.
+ a.Clear();
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_EQ(sz, size_t(0));
+
+ // Allocating should work after clearing an arena with allocations.
+ x = a.Allocate(10);
+ EXPECT_TRUE(x);
+
+ prev_sz = sz;
+ sz = a.SizeOfExcludingThis(TestSizeOf);
+ EXPECT_GT(sz, prev_sz);
+}
+
+TEST(ArenaAllocator, Extensions)
+{
+ ArenaAllocator<4096, 8> a;
+
+ // Test with raw strings.
+ const char* const kTestCStr = "This is a test string.";
+ char* c_dup = mozilla::ArenaStrdup(kTestCStr, a);
+ EXPECT_STREQ(c_dup, kTestCStr);
+
+ const char16_t* const kTestStr = u"This is a wide test string.";
+ char16_t* dup = mozilla::ArenaStrdup(kTestStr, a);
+ EXPECT_TRUE(nsString(dup).Equals(kTestStr));
+
+ // Make sure it works with literal strings.
+ constexpr auto wideStr = u"A wide string."_ns;
+ nsLiteralString::char_type* wide = mozilla::ArenaStrdup(wideStr, a);
+ EXPECT_TRUE(wideStr.Equals(wide));
+
+ constexpr auto cStr = "A c-string."_ns;
+ nsLiteralCString::char_type* cstr = mozilla::ArenaStrdup(cStr, a);
+ EXPECT_TRUE(cStr.Equals(cstr));
+
+ // Make sure it works with normal strings.
+ nsAutoString x(u"testing wide");
+ nsAutoString::char_type* x_copy = mozilla::ArenaStrdup(x, a);
+ EXPECT_TRUE(x.Equals(x_copy));
+
+ nsAutoCString y("testing c-string");
+ nsAutoCString::char_type* y_copy = mozilla::ArenaStrdup(y, a);
+ EXPECT_TRUE(y.Equals(y_copy));
+}
diff --git a/xpcom/tests/gtest/TestArrayAlgorithm.cpp b/xpcom/tests/gtest/TestArrayAlgorithm.cpp
new file mode 100644
index 0000000000..fbaf9a6a24
--- /dev/null
+++ b/xpcom/tests/gtest/TestArrayAlgorithm.cpp
@@ -0,0 +1,108 @@
+/* -*- 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/ArrayAlgorithm.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+using std::begin;
+using std::end;
+
+namespace {
+constexpr static int32_t arr1[3] = {1, 2, 3};
+}
+
+TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, NoError_Fallible)
+{
+ auto res = TransformIntoNewArrayAbortOnErr(
+ begin(arr1), end(arr1),
+ [](const int32_t value) -> Result<int64_t, nsresult> {
+ return value * 10;
+ },
+ fallible);
+ ASSERT_TRUE(res.isOk());
+ const nsTArray<int64_t>& out = res.inspect();
+
+ const nsTArray<int64_t> expected = {10, 20, 30};
+ ASSERT_EQ(expected, out);
+}
+
+TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, NoError_Fallible_Range)
+{
+ auto res = TransformIntoNewArrayAbortOnErr(
+ arr1,
+ [](const int32_t value) -> Result<int64_t, nsresult> {
+ return value * 10;
+ },
+ fallible);
+ ASSERT_TRUE(res.isOk());
+ const nsTArray<int64_t>& out = res.inspect();
+
+ const nsTArray<int64_t> expected = {10, 20, 30};
+ ASSERT_EQ(expected, out);
+}
+
+TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, ErrorOnOther_Fallible)
+{
+ auto res = TransformIntoNewArrayAbortOnErr(
+ begin(arr1), end(arr1),
+ [](const int32_t value) -> Result<int64_t, nsresult> {
+ if (value > 1) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ return value * 10;
+ },
+ fallible);
+ ASSERT_TRUE(res.isErr());
+ ASSERT_EQ(NS_ERROR_FAILURE, res.inspectErr());
+}
+
+TEST(nsAlgorithm_TransformIntoNewArray, NoError)
+{
+ auto res = TransformIntoNewArray(
+ begin(arr1), end(arr1),
+ [](const int32_t value) -> int64_t { return value * 10; });
+
+ const nsTArray<int64_t> expected = {10, 20, 30};
+ ASSERT_EQ(expected, res);
+}
+
+TEST(nsAlgorithm_TransformIntoNewArray, NoError_Range)
+{
+ auto res = TransformIntoNewArray(
+ arr1, [](const int32_t value) -> int64_t { return value * 10; });
+
+ const nsTArray<int64_t> expected = {10, 20, 30};
+ ASSERT_EQ(expected, res);
+}
+
+TEST(nsAlgorithm_TransformIntoNewArray, NoError_Fallible)
+{
+ auto res = TransformIntoNewArray(
+ begin(arr1), end(arr1),
+ [](const int32_t value) -> int64_t { return value * 10; }, fallible);
+ ASSERT_TRUE(res.isOk());
+ const nsTArray<int64_t>& out = res.inspect();
+
+ const nsTArray<int64_t> expected = {10, 20, 30};
+ ASSERT_EQ(expected, out);
+}
+
+TEST(nsAlgorithm_TransformIntoNewArray, NoError_Fallible_Range)
+{
+ auto res = TransformIntoNewArray(
+ arr1, [](const int32_t value) -> int64_t { return value * 10; },
+ fallible);
+ ASSERT_TRUE(res.isOk());
+ const nsTArray<int64_t>& out = res.inspect();
+
+ const nsTArray<int64_t> expected = {10, 20, 30};
+ ASSERT_EQ(expected, out);
+}
diff --git a/xpcom/tests/gtest/TestAtoms.cpp b/xpcom/tests/gtest/TestAtoms.cpp
new file mode 100644
index 0000000000..53148895d2
--- /dev/null
+++ b/xpcom/tests/gtest/TestAtoms.cpp
@@ -0,0 +1,177 @@
+/* -*- 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 "nsAtom.h"
+#include "nsString.h"
+#include "UTFStrings.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+
+using namespace mozilla;
+
+int32_t NS_GetUnusedAtomCount(void);
+
+namespace TestAtoms {
+
+TEST(Atoms, Basic)
+{
+ for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) {
+ nsDependentString str16(ValidStrings[i].m16);
+ nsDependentCString str8(ValidStrings[i].m8);
+
+ RefPtr<nsAtom> 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) {
+ RefPtr<nsAtom> atom16 = NS_Atomize(ValidStrings[i].m16);
+ RefPtr<nsAtom> atom8 = NS_Atomize(ValidStrings[i].m8);
+ EXPECT_EQ(atom16, atom8);
+ }
+}
+
+TEST(Atoms, Null)
+{
+ nsAutoString str(u"string with a \0 char"_ns);
+ nsDependentString strCut(str.get());
+
+ EXPECT_FALSE(str.Equals(strCut));
+
+ RefPtr<nsAtom> atomCut = NS_Atomize(strCut);
+ RefPtr<nsAtom> 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();
+
+ {
+ RefPtr<nsAtom> atom16 = NS_Atomize(Invalid16Strings[i].m16);
+ EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid16Strings[i].m16)));
+ }
+
+ EXPECT_EQ(count, NS_GetNumberOfAtoms());
+ }
+#ifndef DEBUG
+ // Don't run this test in debug builds as that intentionally asserts.
+ for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) {
+ nsrefcnt count = NS_GetNumberOfAtoms();
+
+ {
+ RefPtr<nsAtom> atom8 = NS_Atomize(Invalid8Strings[i].m8);
+ RefPtr<nsAtom> atom16 = NS_Atomize(Invalid8Strings[i].m16);
+ EXPECT_EQ(atom16, atom8);
+ EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid8Strings[i].m16)));
+ }
+
+ EXPECT_EQ(count, NS_GetNumberOfAtoms());
+ }
+
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsrefcnt count = NS_GetNumberOfAtoms();
+
+ {
+ RefPtr<nsAtom> atom8 = NS_Atomize(Malformed8Strings[i].m8);
+ RefPtr<nsAtom> atom16 = NS_Atomize(Malformed8Strings[i].m16);
+ EXPECT_EQ(atom8, atom16);
+ }
+
+ 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?!"
+
+static bool isStaticAtom(nsAtom* 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();
+
+ RefPtr<nsAtom> thirdDynamic = NS_Atomize(THIRD_ATOM_STR);
+
+ EXPECT_FALSE(isStaticAtom(thirdDynamic));
+
+ EXPECT_TRUE(thirdDynamic);
+ EXPECT_EQ(NS_GetNumberOfAtoms(), count + 1);
+}
+
+static void AccessAtoms(void*) {
+ for (int i = 0; i < 10000; i++) {
+ RefPtr<nsAtom> atom = NS_Atomize(u"A Testing Atom");
+ }
+}
+
+TEST(Atoms, ConcurrentAccessing)
+{
+ static const size_t kThreadCount = 4;
+ // Force a GC before so that we don't have any unused atom.
+ NS_GetNumberOfAtoms();
+ EXPECT_EQ(NS_GetUnusedAtomCount(), int32_t(0));
+
+ // Spawn PRThreads to do the concurrent atom access, to make sure we don't
+ // spin the main thread event loop. Spinning the event loop may run a task
+ // that uses an atom, leading to a false positive test failure.
+ PRThread* threads[kThreadCount];
+ for (size_t i = 0; i < kThreadCount; i++) {
+ threads[i] = PR_CreateThread(PR_USER_THREAD, AccessAtoms, nullptr,
+ PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD, 0);
+ EXPECT_TRUE(threads[i]);
+ }
+
+ for (size_t i = 0; i < kThreadCount; i++) {
+ EXPECT_EQ(PR_SUCCESS, PR_JoinThread(threads[i]));
+ }
+
+ // We should have one unused atom from this test.
+ EXPECT_EQ(NS_GetUnusedAtomCount(), int32_t(1));
+}
+
+} // namespace TestAtoms
diff --git a/xpcom/tests/gtest/TestAutoRefCnt.cpp b/xpcom/tests/gtest/TestAutoRefCnt.cpp
new file mode 100644
index 0000000000..92cc0d2538
--- /dev/null
+++ b/xpcom/tests/gtest/TestAutoRefCnt.cpp
@@ -0,0 +1,66 @@
+/* -*- 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 "nsISupportsImpl.h"
+
+#include "mozilla/Atomics.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+
+using namespace mozilla;
+
+class nsThreadSafeAutoRefCntRunner final : public Runnable {
+ public:
+ NS_IMETHOD Run() final {
+ for (int i = 0; i < 10000; i++) {
+ if (++sRefCnt == 1) {
+ sIncToOne++;
+ }
+ if (--sRefCnt == 0) {
+ sDecToZero++;
+ }
+ }
+ return NS_OK;
+ }
+
+ static ThreadSafeAutoRefCnt sRefCnt;
+ static Atomic<uint32_t, Relaxed> sIncToOne;
+ static Atomic<uint32_t, Relaxed> sDecToZero;
+
+ nsThreadSafeAutoRefCntRunner() : Runnable("nsThreadSafeAutoRefCntRunner") {}
+
+ private:
+ ~nsThreadSafeAutoRefCntRunner() = default;
+};
+
+ThreadSafeAutoRefCnt nsThreadSafeAutoRefCntRunner::sRefCnt;
+Atomic<uint32_t, Relaxed> nsThreadSafeAutoRefCntRunner::sIncToOne(0);
+Atomic<uint32_t, Relaxed> nsThreadSafeAutoRefCntRunner::sDecToZero(0);
+
+// When a refcounted object is actually owned by a cache, we may not
+// want to release the object after last reference gets released. In
+// this pattern, the cache may rely on the balance of increment to one
+// and decrement to zero, so that it can maintain a counter for GC.
+TEST(AutoRefCnt, ThreadSafeAutoRefCntBalance)
+{
+ static const size_t kThreadCount = 4;
+ nsCOMPtr<nsIThread> threads[kThreadCount];
+ for (size_t i = 0; i < kThreadCount; i++) {
+ nsresult rv =
+ NS_NewNamedThread("AutoRefCnt Test", getter_AddRefs(threads[i]),
+ new nsThreadSafeAutoRefCntRunner);
+ EXPECT_NS_SUCCEEDED(rv);
+ }
+ for (size_t i = 0; i < kThreadCount; i++) {
+ threads[i]->Shutdown();
+ }
+ EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sRefCnt, nsrefcnt(0));
+ EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sIncToOne,
+ nsThreadSafeAutoRefCntRunner::sDecToZero);
+}
diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp
new file mode 100644
index 0000000000..56d6f03ff8
--- /dev/null
+++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp
@@ -0,0 +1,227 @@
+/* -*- 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 <sys/mman.h> // For memory-locking.
+
+#include "gtest/gtest.h"
+
+#include "AvailableMemoryWatcher.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "nsIObserverService.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsMemoryPressure.h"
+
+using namespace mozilla;
+
+namespace {
+
+// Dummy tab unloader whose one job is to dispatch a low memory event.
+class MockTabUnloader final : public nsITabUnloader {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ public:
+ MockTabUnloader() = default;
+
+ NS_IMETHOD UnloadTabAsync() override {
+ // We want to issue a memory pressure event for
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ return NS_OK;
+ }
+
+ private:
+ ~MockTabUnloader() = default;
+};
+
+NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader)
+
+// Class that gradually increases the percent memory threshold
+// until it reaches 100%, which should guarantee a memory pressure
+// notification.
+class AvailableMemoryChecker final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ AvailableMemoryChecker();
+ void Init();
+ void Shutdown();
+
+ private:
+ ~AvailableMemoryChecker() = default;
+
+ bool mResolved;
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<nsAvailableMemoryWatcherBase> mWatcher;
+ RefPtr<MockTabUnloader> mTabUnloader;
+
+ const uint32_t kPollingInterval = 50;
+ const uint32_t kPrefIncrement = 5;
+};
+
+AvailableMemoryChecker::AvailableMemoryChecker() : mResolved(false) {}
+
+NS_IMPL_ISUPPORTS(AvailableMemoryChecker, nsITimerCallback, nsINamed);
+
+void AvailableMemoryChecker::Init() {
+ mTabUnloader = new MockTabUnloader;
+
+ mWatcher = nsAvailableMemoryWatcherBase::GetSingleton();
+ mWatcher->RegisterTabUnloader(mTabUnloader);
+
+ mTimer = NS_NewTimer();
+ mTimer->InitWithCallback(this, kPollingInterval,
+ nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void AvailableMemoryChecker::Shutdown() {
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+ Preferences::ClearUser("browser.low_commit_space_threshold_percent");
+}
+
+// Timer callback to increase the pref threshold.
+NS_IMETHODIMP
+AvailableMemoryChecker::Notify(nsITimer* aTimer) {
+ uint32_t threshold =
+ StaticPrefs::browser_low_commit_space_threshold_percent();
+ if (threshold >= 100) {
+ mResolved = true;
+ return NS_OK;
+ }
+ threshold += kPrefIncrement;
+ Preferences::SetUint("browser.low_commit_space_threshold_percent", threshold);
+ return NS_OK;
+}
+
+NS_IMETHODIMP AvailableMemoryChecker::GetName(nsACString& aName) {
+ aName.AssignLiteral("AvailableMemoryChecker");
+ return NS_OK;
+}
+
+// Class that listens for a given notification, then records
+// if it was received.
+class Spinner final : public nsIObserver {
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+ nsDependentCString mTopic;
+ bool mTopicObserved;
+
+ ~Spinner() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ Spinner(nsIObserverService* aObserverSvc, const char* aTopic)
+ : mObserverSvc(aObserverSvc), mTopic(aTopic), mTopicObserved(false) {}
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (mTopic == aTopic) {
+ mTopicObserved = true;
+ mObserverSvc->RemoveObserver(this, aTopic);
+
+ // Force the loop to move in case there is no event in the queue.
+ nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__);
+ NS_DispatchToMainThread(dummyEvent);
+ }
+ return NS_OK;
+ }
+ void StartListening() {
+ mObserverSvc->AddObserver(this, mTopic.get(), false);
+ }
+ bool TopicObserved() { return mTopicObserved; }
+ bool WaitForNotification();
+};
+NS_IMPL_ISUPPORTS(Spinner, nsIObserver);
+
+bool Spinner::WaitForNotification() {
+ bool isTimeout = false;
+
+ nsCOMPtr<nsITimer> timer;
+
+ // This timer should time us out if we never observe our notification.
+ // Set to 5000 since the memory checker should finish incrementing the
+ // pref by then, and if it hasn't then it is probably stuck somehow.
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer),
+ [](nsITimer*, void* isTimeout) {
+ *reinterpret_cast<bool*>(isTimeout) = true;
+ },
+ &isTimeout, 5000, nsITimer::TYPE_ONE_SHOT, __func__);
+
+ SpinEventLoopUntil("Spinner:WaitForNotification"_ns, [&]() -> bool {
+ if (isTimeout) {
+ return true;
+ }
+ return mTopicObserved;
+ });
+ return !isTimeout;
+}
+
+void StartUserInteraction(const nsCOMPtr<nsIObserverService>& aObserverSvc) {
+ aObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr);
+}
+
+TEST(AvailableMemoryWatcher, BasicTest)
+{
+ nsCOMPtr<nsIObserverService> observerSvc = services::GetObserverService();
+ RefPtr<Spinner> aSpinner = new Spinner(observerSvc, "memory-pressure");
+ aSpinner->StartListening();
+
+ // Start polling for low memory.
+ StartUserInteraction(observerSvc);
+
+ RefPtr<AvailableMemoryChecker> checker = new AvailableMemoryChecker();
+ checker->Init();
+
+ aSpinner->WaitForNotification();
+
+ // The checker should have dispatched a low memory event before reaching 100%
+ // memory pressure threshold, so the topic should be observed by the spinner.
+ EXPECT_TRUE(aSpinner->TopicObserved());
+ checker->Shutdown();
+}
+
+TEST(AvailableMemoryWatcher, MemoryLowToHigh)
+{
+ // Setting this pref to 100 ensures we start in a low memory scenario.
+ Preferences::SetUint("browser.low_commit_space_threshold_percent", 100);
+
+ nsCOMPtr<nsIObserverService> observerSvc = services::GetObserverService();
+ RefPtr<Spinner> lowMemorySpinner =
+ new Spinner(observerSvc, "memory-pressure");
+ lowMemorySpinner->StartListening();
+
+ StartUserInteraction(observerSvc);
+
+ // Start polling for low memory. We should start with low memory when we start
+ // the checker.
+ RefPtr<AvailableMemoryChecker> checker = new AvailableMemoryChecker();
+ checker->Init();
+
+ lowMemorySpinner->WaitForNotification();
+
+ EXPECT_TRUE(lowMemorySpinner->TopicObserved());
+
+ RefPtr<Spinner> highMemorySpinner =
+ new Spinner(observerSvc, "memory-pressure-stop");
+ highMemorySpinner->StartListening();
+
+ // Now that we are definitely low on memory, let's reset the pref to 0 to
+ // exit low memory.
+ Preferences::SetUint("browser.low_commit_space_threshold_percent", 0);
+
+ highMemorySpinner->WaitForNotification();
+
+ EXPECT_TRUE(highMemorySpinner->TopicObserved());
+
+ checker->Shutdown();
+}
+} // namespace
diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp
new file mode 100644
index 0000000000..cbd564b7db
--- /dev/null
+++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp
@@ -0,0 +1,226 @@
+/* -*- 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 "AvailableMemoryWatcher.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsMemoryPressure.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+
+namespace {
+
+template <typename ConditionT>
+bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) {
+ bool isTimeout = false;
+
+ // The message queue can be empty and the loop stops
+ // waiting for a new event before detecting timeout.
+ // Creating a timer to fire a timeout event.
+ nsCOMPtr<nsITimer> timer;
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer),
+ [](nsITimer*, void* isTimeout) {
+ *reinterpret_cast<bool*>(isTimeout) = true;
+ },
+ &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__);
+
+ SpinEventLoopUntil("TestAvailableMemoryWatcherMac"_ns, [&]() -> bool {
+ if (isTimeout) {
+ return true;
+ }
+ return aCondition();
+ });
+
+ return !isTimeout;
+}
+
+class Spinner final : public nsIObserver {
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+ nsDependentCString mTopicToWatch;
+ bool mTopicObserved;
+
+ ~Spinner() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ Spinner(nsIObserverService* aObserverSvc, const char* const aTopic,
+ const char16_t* const aSubTopic)
+ : mObserverSvc(aObserverSvc),
+ mTopicToWatch(aTopic),
+ mTopicObserved(false) {}
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (mTopicToWatch == aTopic) {
+ mTopicObserved = true;
+ mObserverSvc->RemoveObserver(this, aTopic);
+
+ // Force the loop to move in case that there is no event in the queue.
+ nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__);
+ NS_DispatchToMainThread(dummyEvent);
+ }
+ return NS_OK;
+ }
+
+ void StartListening() {
+ mTopicObserved = false;
+ mObserverSvc->AddObserver(this, mTopicToWatch.get(), false);
+ }
+
+ bool Wait(uint32_t aTimeoutMs) {
+ return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs);
+ }
+};
+
+NS_IMPL_ISUPPORTS(Spinner, nsIObserver)
+
+class MockTabUnloader final : public nsITabUnloader {
+ ~MockTabUnloader() = default;
+
+ uint32_t mCounter;
+
+ public:
+ MockTabUnloader() : mCounter(0) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ void ResetCounter() { mCounter = 0; }
+ uint32_t GetCounter() const { return mCounter; }
+
+ NS_IMETHOD UnloadTabAsync() override {
+ ++mCounter;
+ // Issue a memory-pressure to verify OnHighMemory issues
+ // a memory-pressure-stop event.
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader)
+
+} // namespace
+
+class AvailableMemoryWatcherFixture : public TelemetryTestFixture {
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+
+ protected:
+ RefPtr<nsAvailableMemoryWatcherBase> mWatcher;
+ RefPtr<Spinner> mHighMemoryObserver;
+ RefPtr<Spinner> mLowMemoryObserver;
+ RefPtr<MockTabUnloader> mTabUnloader;
+
+ static constexpr uint32_t kStateChangeTimeoutMs = 20000;
+ static constexpr uint32_t kNotificationTimeoutMs = 20000;
+
+ void SetUp() override {
+ TelemetryTestFixture::SetUp();
+
+ mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+ ASSERT_TRUE(mObserverSvc);
+
+ mHighMemoryObserver =
+ new Spinner(mObserverSvc, "memory-pressure-stop", nullptr);
+ mLowMemoryObserver = new Spinner(mObserverSvc, "memory-pressure", nullptr);
+
+ mTabUnloader = new MockTabUnloader;
+
+ mWatcher = nsAvailableMemoryWatcherBase::GetSingleton();
+ mWatcher->RegisterTabUnloader(mTabUnloader);
+ }
+};
+
+class MemoryWatcherTelemetryEvent {
+ static nsLiteralString sEventCategory;
+ static nsLiteralString sEventMethod;
+ static nsLiteralString sEventObject;
+ uint32_t mLastCountOfEvents;
+
+ public:
+ explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) {
+ JS::RootedValue snapshot(aCx);
+ TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot);
+ nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray(
+ aCx, snapshot, sEventCategory, sEventMethod, sEventObject);
+ mLastCountOfEvents = eventValues.Length();
+ }
+
+ void ValidateLastEvent(JSContext* aCx) {
+ JS::RootedValue snapshot(aCx);
+ TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot);
+ nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray(
+ aCx, snapshot, sEventCategory, sEventMethod, sEventObject);
+
+ // A new event was generated.
+ EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1);
+ if (eventValues.IsEmpty()) {
+ return;
+ }
+
+ // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent
+ ++mLastCountOfEvents;
+
+ nsTArray<nsString> tokens;
+ for (const nsAString& token : eventValues.LastElement().Split(',')) {
+ tokens.AppendElement(token);
+ }
+ EXPECT_EQ(tokens.Length(), 3U);
+
+ // The third token should be a valid floating number.
+ nsresult rv;
+ tokens[2].ToDouble(&rv);
+ EXPECT_NS_SUCCEEDED(rv);
+ }
+};
+
+nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory =
+ u"memory_watcher"_ns;
+nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod =
+ u"on_high_memory"_ns;
+nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns;
+
+/*
+ * Test the browser memory pressure reponse by artificially putting the system
+ * into the "critical" level and ensuring 1) a tab unload attempt occurs and
+ * 2) the Gecko memory-pressure notitificiation start and stop events occur.
+ */
+TEST_F(AvailableMemoryWatcherFixture, MemoryPressureResponse) {
+ // Set the memory pressure state to normal in case we are already
+ // running in a low memory pressure state.
+ mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eNormal);
+
+ // Reset state
+ mTabUnloader->ResetCounter();
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext());
+
+ // Simulate a low memory OS callback and make sure we observe
+ // a memory-pressure event and a tab unload.
+ mLowMemoryObserver->StartListening();
+ mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eWarning);
+ mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eCritical);
+ EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs));
+ EXPECT_TRUE(mLowMemoryObserver->Wait(kStateChangeTimeoutMs));
+
+ // Simulate the normal memory pressure OS callback and make
+ // sure we observe a memory-pressure-stop event.
+ mHighMemoryObserver->StartListening();
+ mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eWarning);
+ mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eNormal);
+ EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
+
+ telemetryEvent.ValidateLastEvent(cx.GetJSContext());
+}
diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp
new file mode 100644
index 0000000000..409d547aaa
--- /dev/null
+++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp
@@ -0,0 +1,663 @@
+/* -*- 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 <windows.h>
+#include <memoryapi.h>
+#include "gtest/gtest.h"
+
+#include "AvailableMemoryWatcher.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsITimer.h"
+#include "nsMemoryPressure.h"
+#include "nsWindowsHelpers.h"
+#include "nsIWindowsRegKey.h"
+#include "nsXULAppAPI.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+
+namespace {
+
+static constexpr size_t kBytesInMB = 1024 * 1024;
+
+template <typename ConditionT>
+bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) {
+ const uint64_t t0 = ::GetTickCount64();
+ bool isTimeout = false;
+
+ // The message queue can be empty and the loop stops
+ // waiting for a new event before detecting timeout.
+ // Creating a timer to fire a timeout event.
+ nsCOMPtr<nsITimer> timer;
+ NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer),
+ [](nsITimer*, void* isTimeout) {
+ *reinterpret_cast<bool*>(isTimeout) = true;
+ },
+ &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__);
+
+ SpinEventLoopUntil("xpcom-tests:WaitUntil"_ns, [&]() -> bool {
+ if (isTimeout) {
+ return true;
+ }
+
+ bool done = aCondition();
+ if (done) {
+ fprintf(stderr, "Done in %llu msec\n", ::GetTickCount64() - t0);
+ }
+ return done;
+ });
+
+ return !isTimeout;
+}
+
+class Spinner final : public nsIObserver {
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+ nsDependentCString mTopicToWatch;
+ Maybe<nsDependentString> mSubTopicToWatch;
+ bool mTopicObserved;
+
+ ~Spinner() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ Spinner(nsIObserverService* aObserverSvc, const char* const aTopic,
+ const char16_t* const aSubTopic)
+ : mObserverSvc(aObserverSvc),
+ mTopicToWatch(aTopic),
+ mSubTopicToWatch(aSubTopic ? Some(nsDependentString(aSubTopic))
+ : Nothing()),
+ mTopicObserved(false) {}
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (mTopicToWatch == aTopic) {
+ if ((mSubTopicToWatch.isNothing() && !aData) ||
+ mSubTopicToWatch.ref() == aData) {
+ mTopicObserved = true;
+ mObserverSvc->RemoveObserver(this, aTopic);
+
+ // Force the loop to move in case that there is no event in the queue.
+ nsCOMPtr<nsIRunnable> dummyEvent = new Runnable(__func__);
+ NS_DispatchToMainThread(dummyEvent);
+ }
+ } else {
+ fprintf(stderr, "Unexpected topic: %s\n", aTopic);
+ }
+
+ return NS_OK;
+ }
+
+ void StartListening() {
+ mTopicObserved = false;
+ mObserverSvc->AddObserver(this, mTopicToWatch.get(), false);
+ }
+
+ bool Wait(uint32_t aTimeoutMs) {
+ return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs);
+ }
+};
+
+NS_IMPL_ISUPPORTS(Spinner, nsIObserver)
+
+/**
+ * Starts a new thread with a message queue to process
+ * memory allocation/free requests
+ */
+class MemoryEater {
+ using PageT = UniquePtr<void, VirtualFreeDeleter>;
+
+ static DWORD WINAPI ThreadStart(LPVOID aParam) {
+ return reinterpret_cast<MemoryEater*>(aParam)->ThreadProc();
+ }
+
+ static void TouchMemory(void* aAddr, size_t aSize) {
+ constexpr uint32_t kPageSize = 4096;
+ volatile uint8_t x = 0;
+ auto base = reinterpret_cast<uint8_t*>(aAddr);
+ for (int64_t i = 0, pages = aSize / kPageSize; i < pages; ++i) {
+ // Pick a random place in every allocated page
+ // and dereference it.
+ x ^= *(base + i * kPageSize + rand() % kPageSize);
+ }
+ (void)x;
+ }
+
+ static uint32_t GetAvailablePhysicalMemoryInMb() {
+ MEMORYSTATUSEX statex = {sizeof(statex)};
+ if (!::GlobalMemoryStatusEx(&statex)) {
+ return 0;
+ }
+
+ return static_cast<uint32_t>(statex.ullAvailPhys / kBytesInMB);
+ }
+
+ static bool AddWorkingSet(size_t aSize, Vector<PageT>& aOutput) {
+ constexpr size_t kMinGranularity = 64 * 1024;
+
+ size_t currentSize = aSize;
+ while (aSize >= kMinGranularity) {
+ if (!GetAvailablePhysicalMemoryInMb()) {
+ // If the available physical memory is less than 1MB, we finish
+ // allocation though there may be still the available commit space.
+ fprintf(stderr, "No enough physical memory.\n");
+ return false;
+ }
+
+ PageT page(::VirtualAlloc(nullptr, currentSize, MEM_RESERVE | MEM_COMMIT,
+ PAGE_READWRITE));
+ if (!page) {
+ DWORD gle = ::GetLastError();
+ if (gle != ERROR_COMMITMENT_LIMIT) {
+ return false;
+ }
+
+ // Try again with a smaller allocation size.
+ currentSize /= 2;
+ continue;
+ }
+
+ aSize -= currentSize;
+
+ // VirtualAlloc consumes the commit space, but we need to *touch* memory
+ // to consume physical memory
+ TouchMemory(page.get(), currentSize);
+ Unused << aOutput.emplaceBack(std::move(page));
+ }
+ return true;
+ }
+
+ DWORD mThreadId;
+ nsAutoHandle mThread;
+ nsAutoHandle mMessageQueueReady;
+ Atomic<bool> mTaskStatus;
+
+ enum class TaskType : UINT {
+ Alloc = WM_USER, // WPARAM = Allocation size
+ Free,
+
+ Last,
+ };
+
+ DWORD ThreadProc() {
+ Vector<PageT> stock;
+ MSG msg;
+
+ // Force the system to create a message queue
+ ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
+
+ // Ready to get a message. Unblock the main thread.
+ ::SetEvent(mMessageQueueReady.get());
+
+ for (;;) {
+ BOOL result = ::GetMessage(&msg, reinterpret_cast<HWND>(-1), WM_QUIT,
+ static_cast<UINT>(TaskType::Last));
+ if (result == -1) {
+ return ::GetLastError();
+ }
+ if (!result) {
+ // Got WM_QUIT
+ break;
+ }
+
+ switch (static_cast<TaskType>(msg.message)) {
+ case TaskType::Alloc:
+ mTaskStatus = AddWorkingSet(msg.wParam, stock);
+ break;
+ case TaskType::Free:
+ stock = Vector<PageT>();
+ mTaskStatus = true;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected message in the queue");
+ break;
+ }
+ }
+
+ return static_cast<DWORD>(msg.wParam);
+ }
+
+ bool PostTask(TaskType aTask, WPARAM aW = 0, LPARAM aL = 0) const {
+ return !!::PostThreadMessageW(mThreadId, static_cast<UINT>(aTask), aW, aL);
+ }
+
+ public:
+ MemoryEater()
+ : mThread(::CreateThread(nullptr, 0, ThreadStart, this, 0, &mThreadId)),
+ mMessageQueueReady(::CreateEventW(nullptr, /*bManualReset*/ TRUE,
+ /*bInitialState*/ FALSE, nullptr)) {
+ ::WaitForSingleObject(mMessageQueueReady.get(), INFINITE);
+ }
+
+ ~MemoryEater() {
+ ::PostThreadMessageW(mThreadId, WM_QUIT, 0, 0);
+ if (::WaitForSingleObject(mThread.get(), 30000) != WAIT_OBJECT_0) {
+ ::TerminateThread(mThread.get(), 0);
+ }
+ }
+
+ bool GetTaskStatus() const { return mTaskStatus; }
+ void RequestAlloc(size_t aSize) { PostTask(TaskType::Alloc, aSize); }
+ void RequestFree() { PostTask(TaskType::Free); }
+};
+
+class MockTabUnloader final : public nsITabUnloader {
+ ~MockTabUnloader() = default;
+
+ uint32_t mCounter;
+
+ public:
+ MockTabUnloader() : mCounter(0) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ void ResetCounter() { mCounter = 0; }
+ uint32_t GetCounter() const { return mCounter; }
+
+ NS_IMETHOD UnloadTabAsync() override {
+ ++mCounter;
+ // Issue a memory-pressure to verify OnHighMemory issues
+ // a memory-pressure-stop event.
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader)
+
+} // namespace
+
+class AvailableMemoryWatcherFixture : public TelemetryTestFixture {
+ static const char kPrefLowCommitSpaceThreshold[];
+
+ RefPtr<nsAvailableMemoryWatcherBase> mWatcher;
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+
+ protected:
+ static bool IsPageFileExpandable() {
+ const auto kMemMgmtKey =
+ u"SYSTEM\\CurrentControlSet\\Control\\"
+ u"Session Manager\\Memory Management"_ns;
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kMemMgmtKey,
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoString pagingFiles;
+ rv = regKey->ReadStringValue(u"PagingFiles"_ns, pagingFiles);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // The value data is REG_MULTI_SZ and each element is "<path> <min> <max>".
+ // If the page file size is automatically managed for all drives, the <path>
+ // is set to "?:\pagefile.sys".
+ // If the page file size is configured per drive, for a drive whose page
+ // file is set to "system managed size", both <min> and <max> are set to 0.
+ return !pagingFiles.IsEmpty() &&
+ (pagingFiles[0] == u'?' || FindInReadable(u" 0 0"_ns, pagingFiles));
+ }
+
+ static size_t GetAllocationSizeToTriggerMemoryNotification() {
+ // The percentage of the used physical memory to the total physical memory
+ // size which is big enough to trigger a memory resource notification.
+ constexpr uint32_t kThresholdPercentage = 98;
+ // If the page file is not expandable, leave a little commit space.
+ const uint32_t kMinimumSafeCommitSpaceMb =
+ IsPageFileExpandable() ? 0 : 1024;
+
+ MEMORYSTATUSEX statex = {sizeof(statex)};
+ EXPECT_TRUE(::GlobalMemoryStatusEx(&statex));
+
+ // How much memory needs to be used to trigger the notification
+ const size_t targetUsedTotalMb =
+ (statex.ullTotalPhys / kBytesInMB) * kThresholdPercentage / 100;
+
+ // How much memory is currently consumed
+ const size_t currentConsumedMb =
+ (statex.ullTotalPhys - statex.ullAvailPhys) / kBytesInMB;
+
+ if (currentConsumedMb >= targetUsedTotalMb) {
+ fprintf(stderr, "The available physical memory is already low.\n");
+ return 0;
+ }
+
+ // How much memory we need to allocate to trigger the notification
+ const uint32_t allocMb = targetUsedTotalMb - currentConsumedMb;
+
+ // If we allocate the target amount, how much commit space will be
+ // left available.
+ const uint32_t estimtedAvailCommitSpace = std::max(
+ 0,
+ static_cast<int32_t>((statex.ullAvailPageFile / kBytesInMB) - allocMb));
+
+ // If the available commit space will be too low, we should not continue
+ if (estimtedAvailCommitSpace < kMinimumSafeCommitSpaceMb) {
+ fprintf(stderr, "The available commit space will be short - %d\n",
+ estimtedAvailCommitSpace);
+ return 0;
+ }
+
+ fprintf(stderr,
+ "Total physical memory = %ul\n"
+ "Available commit space = %ul\n"
+ "Amount to allocate = %ul\n"
+ "Future available commit space after allocation = %d\n",
+ static_cast<uint32_t>(statex.ullTotalPhys / kBytesInMB),
+ static_cast<uint32_t>(statex.ullAvailPageFile / kBytesInMB),
+ allocMb, estimtedAvailCommitSpace);
+ return allocMb * kBytesInMB;
+ }
+
+ static void SetThresholdAsPercentageOfCommitSpace(uint32_t aPercentage) {
+ aPercentage = std::min(100u, aPercentage);
+
+ MEMORYSTATUSEX statex = {sizeof(statex)};
+ EXPECT_TRUE(::GlobalMemoryStatusEx(&statex));
+
+ const uint32_t newVal = static_cast<uint32_t>(
+ (statex.ullAvailPageFile / kBytesInMB) * aPercentage / 100);
+ fprintf(stderr, "Setting %s to %u\n", kPrefLowCommitSpaceThreshold, newVal);
+
+ Preferences::SetUint(kPrefLowCommitSpaceThreshold, newVal);
+ }
+
+ static constexpr uint32_t kStateChangeTimeoutMs = 20000;
+ static constexpr uint32_t kNotificationTimeoutMs = 20000;
+
+ RefPtr<Spinner> mHighMemoryObserver;
+ RefPtr<MockTabUnloader> mTabUnloader;
+ MemoryEater mMemEater;
+ nsAutoHandle mLowMemoryHandle;
+
+ void SetUp() override {
+ TelemetryTestFixture::SetUp();
+
+ mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
+ ASSERT_TRUE(mObserverSvc);
+
+ mHighMemoryObserver =
+ new Spinner(mObserverSvc, "memory-pressure-stop", nullptr);
+ mTabUnloader = new MockTabUnloader;
+
+ mWatcher = nsAvailableMemoryWatcherBase::GetSingleton();
+ mWatcher->RegisterTabUnloader(mTabUnloader);
+
+ mLowMemoryHandle.own(
+ ::CreateMemoryResourceNotification(LowMemoryResourceNotification));
+ ASSERT_TRUE(mLowMemoryHandle);
+
+ // We set the threshold to 50% of the current available commit space.
+ // This means we declare low-memory when the available commit space
+ // gets lower than this threshold, otherwise we declare high-memory.
+ SetThresholdAsPercentageOfCommitSpace(50);
+ }
+
+ void TearDown() override {
+ StopUserInteraction();
+ Preferences::ClearUser(kPrefLowCommitSpaceThreshold);
+ }
+
+ bool WaitForMemoryResourceNotification() {
+ uint64_t t0 = ::GetTickCount64();
+ if (::WaitForSingleObject(mLowMemoryHandle, kNotificationTimeoutMs) !=
+ WAIT_OBJECT_0) {
+ fprintf(stderr, "The memory notification was not triggered.\n");
+ return false;
+ }
+ fprintf(stderr, "Notified in %llu msec\n", ::GetTickCount64() - t0);
+ return true;
+ }
+
+ void StartUserInteraction() {
+ mObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr);
+ }
+
+ void StopUserInteraction() {
+ mObserverSvc->NotifyObservers(nullptr, "user-interaction-inactive",
+ nullptr);
+ }
+};
+
+const char AvailableMemoryWatcherFixture::kPrefLowCommitSpaceThreshold[] =
+ "browser.low_commit_space_threshold_mb";
+
+class MemoryWatcherTelemetryEvent {
+ static nsLiteralString sEventCategory;
+ static nsLiteralString sEventMethod;
+ static nsLiteralString sEventObject;
+
+ uint32_t mLastCountOfEvents;
+
+ public:
+ explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) {
+ JS::RootedValue snapshot(aCx);
+ TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot);
+ nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray(
+ aCx, snapshot, sEventCategory, sEventMethod, sEventObject);
+ mLastCountOfEvents = eventValues.Length();
+ }
+
+ void ValidateLastEvent(JSContext* aCx) {
+ JS::RootedValue snapshot(aCx);
+ TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot);
+ nsTArray<nsString> eventValues = TelemetryTestHelpers::EventValuesToArray(
+ aCx, snapshot, sEventCategory, sEventMethod, sEventObject);
+
+ // A new event was generated.
+ EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1);
+ if (eventValues.IsEmpty()) {
+ return;
+ }
+
+ // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent
+ ++mLastCountOfEvents;
+
+ nsTArray<nsString> tokens;
+ for (const nsAString& token : eventValues.LastElement().Split(',')) {
+ tokens.AppendElement(token);
+ }
+ EXPECT_EQ(tokens.Length(), 3U);
+ if (tokens.Length() != 3U) {
+ const wchar_t* valueStr = eventValues.LastElement().get();
+ fprintf(stderr, "Unexpected event value: %S\n", valueStr);
+ return;
+ }
+
+ // Since this test does not involve TabUnloader, the first two numbers
+ // are always expected to be zero.
+ EXPECT_STREQ(tokens[0].get(), L"0");
+ EXPECT_STREQ(tokens[1].get(), L"0");
+
+ // The third token should be a valid floating number.
+ nsresult rv;
+ tokens[2].ToDouble(&rv);
+ EXPECT_NS_SUCCEEDED(rv);
+ }
+};
+
+nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory =
+ u"memory_watcher"_ns;
+nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod =
+ u"on_high_memory"_ns;
+nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns;
+
+TEST_F(AvailableMemoryWatcherFixture, AlwaysActive) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext());
+ StartUserInteraction();
+
+ const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
+ if (!allocSize) {
+ // Not enough memory to safely create a low-memory situation.
+ // Aborting the test without failure.
+ return;
+ }
+
+ mTabUnloader->ResetCounter();
+ mMemEater.RequestAlloc(allocSize);
+ if (!WaitForMemoryResourceNotification()) {
+ // If the notification was not triggered, abort the test without failure
+ // because it's not a fault in nsAvailableMemoryWatcher.
+ return;
+ }
+
+ EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs));
+
+ mHighMemoryObserver->StartListening();
+ mMemEater.RequestFree();
+ EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
+
+ telemetryEvent.ValidateLastEvent(cx.GetJSContext());
+}
+
+TEST_F(AvailableMemoryWatcherFixture, InactiveToActive) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext());
+ const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
+ if (!allocSize) {
+ // Not enough memory to safely create a low-memory situation.
+ // Aborting the test without failure.
+ return;
+ }
+
+ mTabUnloader->ResetCounter();
+ mMemEater.RequestAlloc(allocSize);
+ if (!WaitForMemoryResourceNotification()) {
+ // If the notification was not triggered, abort the test without failure
+ // because it's not a fault in nsAvailableMemoryWatcher.
+ return;
+ }
+
+ mHighMemoryObserver->StartListening();
+ EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs));
+
+ mMemEater.RequestFree();
+
+ // OnHighMemory should not be triggered during no user interaction
+ // eve after all memory was freed. Expecting false.
+ EXPECT_FALSE(mHighMemoryObserver->Wait(3000));
+
+ StartUserInteraction();
+
+ // After user is active, we expect true.
+ EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
+
+ telemetryEvent.ValidateLastEvent(cx.GetJSContext());
+}
+
+TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_AlwaysActive) {
+ // Setting a low threshold simulates a high commit space.
+ SetThresholdAsPercentageOfCommitSpace(1);
+ StartUserInteraction();
+
+ const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
+ if (!allocSize) {
+ // Not enough memory to safely create a low-memory situation.
+ // Aborting the test without failure.
+ return;
+ }
+
+ mTabUnloader->ResetCounter();
+ mMemEater.RequestAlloc(allocSize);
+ if (!WaitForMemoryResourceNotification()) {
+ // If the notification was not triggered, abort the test without failure
+ // because it's not a fault in nsAvailableMemoryWatcher.
+ return;
+ }
+
+ // Tab unload will not be triggered because the commit space is not low.
+ EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs / 2));
+
+ mMemEater.RequestFree();
+ ::Sleep(kStateChangeTimeoutMs / 2);
+
+ // Set a high threshold and make sure the watcher will trigger the tab
+ // unloader next time.
+ SetThresholdAsPercentageOfCommitSpace(50);
+
+ mMemEater.RequestAlloc(allocSize);
+ if (!WaitForMemoryResourceNotification()) {
+ return;
+ }
+
+ EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs));
+
+ mHighMemoryObserver->StartListening();
+ mMemEater.RequestFree();
+ EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
+}
+
+TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_InactiveToActive) {
+ // Setting a low threshold simulates a high commit space.
+ SetThresholdAsPercentageOfCommitSpace(1);
+
+ const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification();
+ if (!allocSize) {
+ // Not enough memory to safely create a low-memory situation.
+ // Aborting the test without failure.
+ return;
+ }
+
+ mTabUnloader->ResetCounter();
+ mMemEater.RequestAlloc(allocSize);
+ if (!WaitForMemoryResourceNotification()) {
+ // If the notification was not triggered, abort the test without failure
+ // because it's not a fault in nsAvailableMemoryWatcher.
+ return;
+ }
+
+ // Tab unload will not be triggered because the commit space is not low.
+ EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs / 2));
+
+ mMemEater.RequestFree();
+ ::Sleep(kStateChangeTimeoutMs / 2);
+
+ // Set a high threshold and make sure the watcher will trigger the tab
+ // unloader next time.
+ SetThresholdAsPercentageOfCommitSpace(50);
+
+ // When the user becomes active, the watcher will resume the timer.
+ StartUserInteraction();
+
+ mMemEater.RequestAlloc(allocSize);
+ if (!WaitForMemoryResourceNotification()) {
+ return;
+ }
+
+ EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; },
+ kStateChangeTimeoutMs));
+
+ mHighMemoryObserver->StartListening();
+ mMemEater.RequestFree();
+ EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs));
+}
diff --git a/xpcom/tests/gtest/TestBase64.cpp b/xpcom/tests/gtest/TestBase64.cpp
new file mode 100644
index 0000000000..55fbcefe38
--- /dev/null
+++ b/xpcom/tests/gtest/TestBase64.cpp
@@ -0,0 +1,454 @@
+/* -*- 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 "mozilla/Base64.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIScriptableBase64Encoder.h"
+#include "nsIInputStream.h"
+#include "nsString.h"
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.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,
+ "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOT"
+ "yBMQU5ESU5HIFRIRVJFLg=="),
+ // Terminator
+ Test(nullptr, nullptr)};
+
+class FakeInputStream final : public nsIInputStream {
+ ~FakeInputStream() = default;
+
+ 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::StreamStatus() {
+ return mClosed ? NS_BASE_STREAM_CLOSED : 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 != nullptr;
+}
+
+void FakeInputStream::CheckTest(nsACString& aResult) {
+ ASSERT_STREQ(aResult.BeginReading(), mTest->mResult);
+}
+
+void FakeInputStream::CheckTest(nsAString& aResult) {
+ ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult))
+ << "Actual: " << NS_ConvertUTF16toUTF8(aResult).get() << '\n'
+ << "Expected: " << mTest->mResult;
+}
+
+TEST(Base64, StreamEncoder)
+{
+ 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_NS_SUCCEEDED(rv);
+
+ stream->Reset();
+
+ rv = encoder->EncodeToCString(stream, 0, string);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ stream->CheckTest(wideString);
+ stream->CheckTest(string);
+ } while (stream->NextTest());
+}
+
+struct EncodeDecodeTestCase {
+ const char* mInput;
+ const char* mOutput;
+};
+
+static EncodeDecodeTestCase sRFC4648TestCases[] = {
+ {"", ""},
+ {"f", "Zg=="},
+ {"fo", "Zm8="},
+ {"foo", "Zm9v"},
+ {"foob", "Zm9vYg=="},
+ {"fooba", "Zm9vYmE="},
+ {"foobar", "Zm9vYmFy"},
+};
+
+TEST(Base64, RFC4648Encoding)
+{
+ for (auto& testcase : sRFC4648TestCases) {
+ nsDependentCString in(testcase.mInput);
+ nsAutoCString out;
+ nsresult rv = mozilla::Base64Encode(in, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
+ }
+
+ for (auto& testcase : sRFC4648TestCases) {
+ NS_ConvertUTF8toUTF16 in(testcase.mInput);
+ nsAutoString out;
+ nsresult rv = mozilla::Base64Encode(in, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
+ }
+}
+
+TEST(Base64, RFC4648Encoding_TransformAndAppend_EmptyPrefix)
+{
+ for (auto& testcase : sRFC4648TestCases) {
+ nsDependentCString in(testcase.mInput);
+ nsAutoString out;
+ nsresult rv =
+ mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
+ }
+}
+
+TEST(Base64, RFC4648Encoding_TransformAndAppend_NonEmptyPrefix)
+{
+ for (auto& testcase : sRFC4648TestCases) {
+ nsDependentCString in(testcase.mInput);
+ nsAutoString out{u"foo"_ns};
+ nsresult rv =
+ mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(StringBeginsWith(out, u"foo"_ns));
+ ASSERT_TRUE(Substring(out, 3).EqualsASCII(testcase.mOutput));
+ }
+}
+
+TEST(Base64, RFC4648Decoding)
+{
+ for (auto& testcase : sRFC4648TestCases) {
+ nsDependentCString out(testcase.mOutput);
+ nsAutoCString in;
+ nsresult rv = mozilla::Base64Decode(out, in);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(in.EqualsASCII(testcase.mInput));
+ }
+
+ for (auto& testcase : sRFC4648TestCases) {
+ NS_ConvertUTF8toUTF16 out(testcase.mOutput);
+ nsAutoString in;
+ nsresult rv = mozilla::Base64Decode(out, in);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(in.EqualsASCII(testcase.mInput));
+ }
+}
+
+TEST(Base64, RFC4648DecodingRawPointers)
+{
+ for (auto& testcase : sRFC4648TestCases) {
+ size_t outputLength = strlen(testcase.mOutput);
+ size_t inputLength = strlen(testcase.mInput);
+
+ // This will be allocated by Base64Decode.
+ char* buffer = nullptr;
+
+ uint32_t binaryLength = 0;
+ nsresult rv = mozilla::Base64Decode(testcase.mOutput, outputLength, &buffer,
+ &binaryLength);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(binaryLength, inputLength);
+ ASSERT_STREQ(testcase.mInput, buffer);
+ free(buffer);
+ }
+}
+
+static EncodeDecodeTestCase sNonASCIITestCases[] = {
+ {"\x80", "gA=="},
+ {"\xff", "/w=="},
+ {"\x80\x80", "gIA="},
+ {"\x80\x81", "gIE="},
+ {"\xff\xff", "//8="},
+ {"\x80\x80\x80", "gICA"},
+ {"\xff\xff\xff", "////"},
+ {"\x80\x80\x80\x80", "gICAgA=="},
+ {"\xff\xff\xff\xff", "/////w=="},
+};
+
+TEST(Base64, NonASCIIEncoding)
+{
+ for (auto& testcase : sNonASCIITestCases) {
+ nsDependentCString in(testcase.mInput);
+ nsAutoCString out;
+ nsresult rv = mozilla::Base64Encode(in, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
+ }
+}
+
+TEST(Base64, NonASCIIEncodingWideString)
+{
+ for (auto& testcase : sNonASCIITestCases) {
+ nsAutoString in, out;
+ // XXX Handles Latin1 despite the name
+ AppendASCIItoUTF16(nsDependentCString(testcase.mInput), in);
+ nsresult rv = mozilla::Base64Encode(in, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
+ }
+}
+
+TEST(Base64, NonASCIIDecoding)
+{
+ for (auto& testcase : sNonASCIITestCases) {
+ nsDependentCString out(testcase.mOutput);
+ nsAutoCString in;
+ nsresult rv = mozilla::Base64Decode(out, in);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(in.Equals(testcase.mInput));
+ }
+}
+
+TEST(Base64, NonASCIIDecodingWideString)
+{
+ for (auto& testcase : sNonASCIITestCases) {
+ nsAutoString in, out;
+ // XXX Handles Latin1 despite the name
+ AppendASCIItoUTF16(nsDependentCString(testcase.mOutput), out);
+ nsresult rv = mozilla::Base64Decode(out, in);
+ ASSERT_NS_SUCCEEDED(rv);
+ // Can't use EqualsASCII, because our comparison string isn't ASCII.
+ for (size_t i = 0; i < in.Length(); ++i) {
+ ASSERT_TRUE(((unsigned int)in[i] & 0xff00) == 0);
+ ASSERT_EQ((unsigned char)in[i], (unsigned char)testcase.mInput[i]);
+ }
+ ASSERT_TRUE(strlen(testcase.mInput) == in.Length());
+ }
+}
+
+// For historical reasons, our wide string base64 encode routines mask off
+// the high bits of non-latin1 wide strings.
+TEST(Base64, EncodeNon8BitWideString)
+{
+ {
+ const nsAutoString non8Bit(u"\x1ff");
+ nsAutoString out;
+ nsresult rv = mozilla::Base64Encode(non8Bit, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsLiteral("/w=="));
+ }
+ {
+ const nsAutoString non8Bit(u"\xfff");
+ nsAutoString out;
+ nsresult rv = mozilla::Base64Encode(non8Bit, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.EqualsLiteral("/w=="));
+ }
+}
+
+// For historical reasons, our wide string base64 decode routines mask off
+// the high bits of non-latin1 wide strings.
+TEST(Base64, DecodeNon8BitWideString)
+{
+ {
+ // This would be "/w==" in a nsCString
+ const nsAutoString non8Bit(u"\x12f\x177==");
+ const nsAutoString expectedOutput(u"\xff");
+ ASSERT_EQ(non8Bit.Length(), 4u);
+ nsAutoString out;
+ nsresult rv = mozilla::Base64Decode(non8Bit, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.Equals(expectedOutput));
+ }
+ {
+ const nsAutoString non8Bit(u"\xf2f\xf77==");
+ const nsAutoString expectedOutput(u"\xff");
+ nsAutoString out;
+ nsresult rv = mozilla::Base64Decode(non8Bit, out);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(out.Equals(expectedOutput));
+ }
+}
+
+TEST(Base64, DecodeWideTo8Bit)
+{
+ for (auto& testCase : sRFC4648TestCases) {
+ const nsAutoCString in8bit(testCase.mOutput);
+ const NS_ConvertUTF8toUTF16 inWide(testCase.mOutput);
+ nsAutoCString out2;
+ nsAutoCString out1;
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(inWide, out1)));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(in8bit, out2)));
+ ASSERT_EQ(out1, out2);
+ }
+}
+
+TEST(Base64, TruncateOnInvalidDecodeCString)
+{
+ constexpr auto invalid = "@@=="_ns;
+ nsAutoCString out("I should be truncated!");
+ nsresult rv = mozilla::Base64Decode(invalid, out);
+ ASSERT_NS_FAILED(rv);
+ ASSERT_EQ(out.Length(), 0u);
+}
+
+TEST(Base64, TruncateOnInvalidDecodeWideString)
+{
+ constexpr auto invalid = u"@@=="_ns;
+ nsAutoString out(u"I should be truncated!");
+ nsresult rv = mozilla::Base64Decode(invalid, out);
+ ASSERT_NS_FAILED(rv);
+ ASSERT_EQ(out.Length(), 0u);
+}
+
+// TODO: Add tests for OOM handling.
diff --git a/xpcom/tests/gtest/TestCOMArray.cpp b/xpcom/tests/gtest/TestCOMArray.cpp
new file mode 100644
index 0000000000..9e29e15230
--- /dev/null
+++ b/xpcom/tests/gtest/TestCOMArray.cpp
@@ -0,0 +1,295 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsISupports.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);
+}
+
+TEST(COMArray, ConvertIteratorToConstIterator)
+{
+ nsCOMArray<IFoo> array;
+
+ for (int32_t i = 0; i < 20; ++i) {
+ nsCOMPtr<IFoo> foo = new Foo(i);
+ array.AppendObject(foo);
+ }
+
+ nsCOMArray<IFoo>::const_iterator it = array.begin();
+ ASSERT_EQ(array.cbegin(), it);
+}
diff --git a/xpcom/tests/gtest/TestCOMPtr.cpp b/xpcom/tests/gtest/TestCOMPtr.cpp
new file mode 100644
index 0000000000..01fde5632a
--- /dev/null
+++ b/xpcom/tests/gtest/TestCOMPtr.cpp
@@ -0,0 +1,436 @@
+/* -*- 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/gtest/MozAssertions.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() override;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+ NS_IMETHOD QueryInterface(const nsIID&, void**) override;
+
+ 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;
+}
+
+static nsresult CreateIFoo(void** result)
+// a typical factory function (that calls AddRef)
+{
+ auto* foop = new IFoo;
+
+ foop->AddRef();
+ *result = foop;
+
+ return NS_OK;
+}
+
+static void set_a_IFoo(nsCOMPtr<IFoo>* result) {
+ // Various places in this file do a static_cast to nsISupports* in order to
+ // make the QI non-trivial, to avoid hitting a static assert.
+ nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
+ *result = foop;
+}
+
+static nsCOMPtr<IFoo> return_a_IFoo() {
+ nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(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();
+ ~IBar() override;
+
+ NS_IMETHOD QueryInterface(const nsIID&, void**) override;
+
+ 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() = default;
+
+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;
+}
+
+static nsresult CreateIBar(void** result)
+// a typical factory function (that calls AddRef)
+{
+ auto* barp = new IBar;
+
+ barp->AddRef();
+ *result = barp;
+
+ return NS_OK;
+}
+
+static void AnIFooPtrPtrContext(IFoo**) {}
+
+static void AVoidPtrPtrContext(void**) {}
+
+static 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_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(barP);
+
+ IFoo* fooP = 0;
+ rv = barP->QueryInterface(NS_GET_IID(IFoo), reinterpret_cast<void**>(&fooP));
+ ASSERT_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_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(barP);
+
+ nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(barP), &rv));
+ ASSERT_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(static_cast<nsISupports*>(new IFoo)));
+ ASSERT_EQ(foop->refcount_, (unsigned int)1);
+ ASSERT_EQ(IFoo::total_constructions_, 1);
+ ASSERT_EQ(IFoo::total_destructions_, 0);
+
+ foop = do_QueryInterface(static_cast<nsISupports*>(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(static_cast<nsISupports*>(new IBar)));
+ mozilla::Unused << foop;
+ }
+
+ ASSERT_EQ(IBar::total_destructions_, 1);
+}
+
+TEST(COMPtr, Comparison)
+{
+ IFoo::total_constructions_ = 0;
+ IFoo::total_destructions_ = 0;
+
+ {
+ nsCOMPtr<IFoo> foo1p(
+ do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
+ nsCOMPtr<IFoo> foo2p(
+ do_QueryInterface(static_cast<nsISupports*>(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);
+}
+
+TEST(COMPtr, DontAddRef)
+{
+ {
+ auto* raw_foo1p = new IFoo;
+ raw_foo1p->AddRef();
+
+ auto* 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(static_cast<nsISupports*>(new IFoo)));
+ ASSERT_TRUE(fooP);
+
+ ASSERT_EQ(IFoo::total_constructions_, 5);
+ ASSERT_EQ(IFoo::total_destructions_, 4);
+
+ nsCOMPtr<IFoo> fooP2(std::move(fooP));
+ 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(static_cast<nsISupports*>(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(static_cast<nsISupports*>(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(static_cast<nsISupports*>(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 0000000000..2056ce1368
--- /dev/null
+++ b/xpcom/tests/gtest/TestCOMPtrEq.cpp
@@ -0,0 +1,82 @@
+/* -*- 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 0000000000..22e161fcdd
--- /dev/null
+++ b/xpcom/tests/gtest/TestCRT.cpp
@@ -0,0 +1,90 @@
+/* -*- 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.
+static 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, size_t n) {
+ bool longerThanN = strlen(s1) > n || strlen(s2) > n;
+
+ int clib = PL_strcmp(s1, s2);
+ int clib_n = PL_strncmp(s1, s2, n);
+
+ if (!longerThanN) {
+ EXPECT_EQ(sign(clib), sign(clib_n));
+ }
+
+ nsAutoString t1, t2;
+ CopyASCIItoUTF16(mozilla::MakeStringSpan(s1), t1);
+ CopyASCIItoUTF16(mozilla::MakeStringSpan(s2), t2);
+ const char16_t* us1 = t1.get();
+ const char16_t* us2 = t2.get();
+
+ int u2, u2_n;
+ u2 = nsCRT::strcmp(us1, us2);
+
+ EXPECT_EQ(sign(clib), sign(u2));
+
+ u2 = NS_strcmp(us1, us2);
+ u2_n = NS_strncmp(us1, us2, n);
+
+ EXPECT_EQ(sign(clib), sign(u2));
+ EXPECT_EQ(sign(clib_n), sign(u2_n));
+}
+
+struct Test {
+ const char* s1;
+ const char* s2;
+ size_t 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},
+
+ {"foo", "foobar", 3}, {"foobar", "foo", 3},
+ {"foobar", "foozap", 3}, {"foozap", "foobar", 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 0000000000..90a03a8a8f
--- /dev/null
+++ b/xpcom/tests/gtest/TestCallTemplates.cpp
@@ -0,0 +1,115 @@
+/* -*- 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 "nsWeakReference.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.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)
+
+#define NS_ITESTSERVICE2_IID \
+ { \
+ 0x137b5253, 0x37b1, 0x43c7, { \
+ 0x96, 0x2b, 0xab, 0xf1, 0x2d, 0x22, 0x56, 0xaf \
+ } \
+ }
+
+class NS_NO_VTABLE nsITestService2 : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITESTSERVICE2_IID)
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITestService2, NS_ITESTSERVICE2_IID)
+
+class nsTestService final : public nsITestService,
+ public nsSupportsWeakReference {
+ ~nsTestService() = default;
+
+ 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);
+
+inline 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.
+ */
+
+ MOZ_ASSERT_UNREACHABLE("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);
+ nsITestService2* myTestService2;
+ CallQueryInterface(myTestService, &myTestService2);
+
+ nsCOMPtr<nsISupports> mySupportsCOMPtr = mySupportsPtr;
+ CallQueryInterface(mySupportsCOMPtr, &myITestService);
+
+ RefPtr<nsTestService> myTestServiceRefPtr = myTestService;
+ CallQueryInterface(myTestServiceRefPtr, &myTestService2);
+
+ /* Test CallQueryReferent */
+
+ nsIWeakReference* myWeakRef = static_cast<nsIWeakReference*>(mySupportsPtr);
+ CallQueryReferent(myWeakRef, &myITestService);
+
+ /* Test CallCreateInstance */
+ CallCreateInstance(kTestServiceCID, &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 0000000000..9a3b400854
--- /dev/null
+++ b/xpcom/tests/gtest/TestCloneInputStream.cpp
@@ -0,0 +1,236 @@
+/* -*- 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/gtest/MozAssertions.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_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_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+ testing::ConsumeAndValidateStream(clone, inputString);
+}
+
+class NonCloneableInputStream final : public nsIInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonCloneableInputStream(
+ already_AddRefed<nsIInputStream> aInputStream)
+ : mStream(aInputStream) {}
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return mStream->Available(aLength); }
+
+ NS_IMETHOD
+ StreamStatus() override { return mStream->StreamStatus(); }
+
+ 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:
+ ~NonCloneableInputStream() = default;
+
+ nsCOMPtr<nsIInputStream> mStream;
+};
+
+NS_IMPL_ISUPPORTS(NonCloneableInputStream, nsIInputStream)
+
+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_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> stream = new NonCloneableInputStream(base.forget());
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(stream);
+ ASSERT_TRUE(cloneable == nullptr);
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_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_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> stream = new NonCloneableInputStream(base.forget());
+
+ 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_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(clone != nullptr);
+ ASSERT_TRUE(replacement != nullptr);
+ ASSERT_TRUE(stream.get() != replacement.get());
+ ASSERT_TRUE(clone.get() != replacement.get());
+
+ stream = std::move(replacement);
+
+ // 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_NS_SUCCEEDED(rv);
+ } while (available < inputString.Length());
+
+ testing::ConsumeAndValidateStream(stream, inputString);
+ testing::ConsumeAndValidateStream(clone, inputString);
+}
+
+TEST(CloneInputStream, CloneMultiplexStream)
+{
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(multiplexStream);
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ 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_NS_SUCCEEDED(rv);
+
+ rv = multiplexStream->AppendStream(base);
+ ASSERT_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_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_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> clone2;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone2));
+ ASSERT_NS_FAILED(rv);
+}
+
+TEST(CloneInputStream, CloneMultiplexStreamPartial)
+{
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(multiplexStream);
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ 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_NS_SUCCEEDED(rv);
+
+ rv = multiplexStream->AppendStream(base);
+ ASSERT_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_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> clone;
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_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_NS_FAILED(rv);
+
+ // Fail at the end.
+ nsAutoCString consumed;
+ rv = NS_ConsumeStream(stream, UINT32_MAX, consumed);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = NS_CloneInputStream(stream, getter_AddRefs(clone));
+ ASSERT_NS_FAILED(rv);
+}
diff --git a/xpcom/tests/gtest/TestDafsa.cpp b/xpcom/tests/gtest/TestDafsa.cpp
new file mode 100644
index 0000000000..f52bf74256
--- /dev/null
+++ b/xpcom/tests/gtest/TestDafsa.cpp
@@ -0,0 +1,82 @@
+/* -*- 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/Dafsa.h"
+#include "gtest/gtest.h"
+
+#include "nsString.h"
+
+using mozilla::Dafsa;
+
+namespace dafsa_test_1 {
+#include "dafsa_test_1.inc" // kDafsa
+}
+
+TEST(Dafsa, Constructor)
+{ Dafsa d(dafsa_test_1::kDafsa); }
+
+TEST(Dafsa, StringsFound)
+{
+ Dafsa d(dafsa_test_1::kDafsa);
+
+ int tag = d.Lookup("foo.bar.baz"_ns);
+ EXPECT_EQ(tag, 1);
+
+ tag = d.Lookup("a.test.string"_ns);
+ EXPECT_EQ(tag, 0);
+
+ tag = d.Lookup("a.test.string2"_ns);
+ EXPECT_EQ(tag, 2);
+
+ tag = d.Lookup("aaaa"_ns);
+ EXPECT_EQ(tag, 4);
+}
+
+TEST(Dafsa, StringsNotFound)
+{
+ Dafsa d(dafsa_test_1::kDafsa);
+
+ // Matches all but last letter.
+ int tag = d.Lookup("foo.bar.ba"_ns);
+ EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+ // Matches prefix with extra letter.
+ tag = d.Lookup("a.test.strings"_ns);
+ EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+ // Matches small portion.
+ tag = d.Lookup("a.test"_ns);
+ EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+ // Matches repeating pattern with extra letters.
+ tag = d.Lookup("aaaaa"_ns);
+ EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+
+ // Empty string.
+ tag = d.Lookup(""_ns);
+ EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+}
+
+TEST(Dafsa, HugeString)
+{
+ Dafsa d(dafsa_test_1::kDafsa);
+
+ int tag = d.Lookup(nsLiteralCString(
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "
+ "This is a very long string that is larger than the dafsa itself. "));
+ EXPECT_EQ(tag, Dafsa::kKeyNotFound);
+}
diff --git a/xpcom/tests/gtest/TestDeadlockDetector.cpp b/xpcom/tests/gtest/TestDeadlockDetector.cpp
new file mode 100644
index 0000000000..c02ba13da2
--- /dev/null
+++ b/xpcom/tests/gtest/TestDeadlockDetector.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=2 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 "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#include "mozilla/CondVar.h"
+#include "mozilla/RecursiveMutex.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Mutex.h"
+
+#include "mozilla/gtest/MozHelpers.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+// The code in this file is also used by
+// storage/test/gtest/test_deadlock_detector.cpp. The following two macros are
+// used to provide the necessary differentiation between this file and that
+// file.
+#ifndef MUTEX
+# define MUTEX mozilla::Mutex
+#endif
+#ifndef TESTNAME
+# define TESTNAME(name) XPCOM##name
+#endif
+
+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);
+}
+
+/**
+ * Simple test fixture that makes sure the gdb sleep setup in the
+ * ah crap handler is bypassed during the death tests.
+ */
+class TESTNAME(DeadlockDetectorTest) : public ::testing::Test {
+ protected:
+ void SetUp() final { SAVE_GDB_SLEEP_GLOBAL(mOldSleepDuration); }
+
+ void TearDown() final { RESTORE_GDB_SLEEP_GLOBAL(mOldSleepDuration); }
+
+ private:
+#if defined(HAS_GDB_SLEEP_DURATION)
+ unsigned int mOldSleepDuration;
+#endif // defined(HAS_GDB_SLEEP_DURATION)
+};
+
+//-----------------------------------------------------------------------------
+// Single-threaded sanity tests
+
+// Stupidest possible deadlock.
+static int Sanity_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ MUTEX m1("dd.sanity.m1");
+ m1.Lock();
+ m1.Lock();
+ return 0; // not reached
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(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.
+static int Sanity2_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ MUTEX m1("dd.sanity2.m1");
+ MUTEX m2("dd.sanity2.m2");
+ m1.Lock();
+ m2.Lock();
+ m1.Lock();
+ return 0; // not reached
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(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);
+}
+
+#if 0
+// Temporarily disabled, see bug 1370644.
+int
+Sanity3_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS
+{
+ mozilla::gtest::DisableCrashReporter();
+
+ MUTEX m1("dd.sanity3.m1");
+ MUTEX m2("dd.sanity3.m2");
+ MUTEX m3("dd.sanity3.m3");
+ 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(TESTNAME(DeadlockDetectorTest), TESTNAME(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);
+}
+#endif
+
+static int Sanity4_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ mozilla::ReentrantMonitor m1 MOZ_UNANNOTATED("dd.sanity4.m1");
+ MUTEX m2("dd.sanity4.m2");
+ m1.Enter();
+ m2.Lock();
+ m1.Enter();
+ return 0;
+}
+
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(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);
+}
+
+static int Sanity5_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ mozilla::gtest::DisableCrashReporter();
+
+ mozilla::RecursiveMutex m1 MOZ_UNANNOTATED("dd.sanity4.m1");
+ MUTEX m2("dd.sanity4.m2");
+ m1.Lock();
+ m2.Lock();
+ m1.Lock();
+ return 0;
+}
+
+#if !defined(DISABLE_STORAGE_SANITY5_DEATH_TEST)
+TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity5DeathTest)) {
+ const char* const regex =
+ "Re-entering RecursiveMutex after acquiring other resources.*"
+ "###!!! ERROR: Potential deadlock detected.*"
+ "=== Cyclical dependency starts at.*--- RecursiveMutex : dd.sanity4.m1.*"
+ "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*"
+ "=== Cycle completed at.*--- RecursiveMutex : dd.sanity4.m1.*"
+ "###!!! ASSERTION: Potential deadlock detected.*";
+ ASSERT_DEATH_IF_SUPPORTED(Sanity5_Child(), regex);
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// 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<MUTEX*>& locks;
+
+ /**
+ * Integer argument used to identify each thread.
+ */
+ int id;
+};
+
+#if 0
+// Temporarily disabled, see bug 1370644.
+static void
+TwoThreads_thread(void* arg) MOZ_NO_THREAD_SAFETY_ANALYSIS
+{
+ ThreadState* state = static_cast<ThreadState*>(arg);
+
+ MUTEX* ttM1 = state->locks[0];
+ 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() MOZ_NO_THREAD_SAFETY_ANALYSIS
+{
+ mozilla::gtest::DisableCrashReporter();
+
+ nsTArray<MUTEX*> locks = {
+ new MUTEX("dd.twothreads.m1"),
+ new 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(TESTNAME(DeadlockDetectorTest), TESTNAME(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);
+}
+#endif
+
+static void ContentionNoDeadlock_thread(void* arg)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ 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;
+ }
+}
+
+static int ContentionNoDeadlock_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ const size_t kMutexCount = 4;
+
+ PRThread* threads[3];
+ nsTArray<MUTEX*> locks;
+ ThreadState states[] = {{locks, 0}, {locks, 1}, {locks, 2}};
+
+ for (uint32_t i = 0; i < kMutexCount; ++i)
+ locks.AppendElement(new 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(TESTNAME(DeadlockDetectorTest), TESTNAME(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 0000000000..40519f43ed
--- /dev/null
+++ b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=2 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) MOZ_CRASH("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) MOZ_CRASH("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/TestDelayedRunnable.cpp b/xpcom/tests/gtest/TestDelayedRunnable.cpp
new file mode 100644
index 0000000000..b612522b55
--- /dev/null
+++ b/xpcom/tests/gtest/TestDelayedRunnable.cpp
@@ -0,0 +1,168 @@
+/* -*- 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/DelayedRunnable.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskQueue.h"
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "MediaTimer.h"
+#include "mozilla/media/MediaUtils.h"
+#include "VideoUtils.h"
+
+using mozilla::Atomic;
+using mozilla::MakeRefPtr;
+using mozilla::Monitor;
+using mozilla::MonitorAutoLock;
+using mozilla::TaskQueue;
+
+namespace {
+struct ReleaseDetector {
+ explicit ReleaseDetector(Atomic<bool>* aActive) : mActive(aActive) {
+ *mActive = true;
+ }
+ ReleaseDetector(ReleaseDetector&& aOther) noexcept : mActive(aOther.mActive) {
+ aOther.mActive = nullptr;
+ }
+ ReleaseDetector(const ReleaseDetector&) = delete;
+ ~ReleaseDetector() {
+ if (mActive) {
+ *mActive = false;
+ }
+ }
+ Atomic<bool>* mActive;
+};
+} // namespace
+
+TEST(DelayedRunnable, TaskQueueShutdownLeak)
+{
+ Atomic<bool> active{false};
+ auto taskQueue = TaskQueue::Create(
+ GetMediaThreadPool(mozilla::MediaThreadType::SUPERVISOR),
+ "TestDelayedRunnable TaskQueueShutdownLeak");
+ taskQueue->DelayedDispatch(
+ NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}),
+ 60e3 /* 1 minute */);
+ EXPECT_TRUE(active);
+ taskQueue->BeginShutdown();
+ taskQueue->AwaitIdle();
+ // Leaks are often detected after process shutdown. This doesn't wait that
+ // long, but leaking past thread shutdown would be equally bad since the
+ // runnable can no longer be released on the target thread. This is also the
+ // reason why timers assert that they don't release the last reference to
+ // their callbacks when dispatch fails (like when the target has been
+ // shutdown).
+ EXPECT_FALSE(active);
+}
+
+TEST(DelayedRunnable, nsThreadShutdownLeak)
+{
+ Atomic<bool> active{false};
+ nsCOMPtr<nsIThread> thread;
+ ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK);
+ thread->DelayedDispatch(
+ NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}),
+ 60e3 /* 1 minute */);
+ EXPECT_TRUE(active);
+ ASSERT_EQ(thread->Shutdown(), NS_OK);
+ // Leaks are often detected after process shutdown. This doesn't wait that
+ // long, but leaking past thread shutdown would be equally bad since the
+ // runnable can no longer be released on the target thread. This is also the
+ // reason why timers assert that they don't release the last reference to
+ // their callbacks when dispatch fails (like when the target has been
+ // shutdown).
+ EXPECT_FALSE(active);
+}
+
+/*
+ * This tests a case where we create a background TaskQueue that lives until
+ * xpcom shutdown. This test will fail (by assertion failure) if the TaskQueue
+ * shutdown task is dispatched too late in the shutdown sequence, or:
+ * If the background thread pool is then empty, the TaskQueue shutdown task will
+ * when dispatched require creating a new nsThread, which is forbidden too late
+ * in the shutdown sequence.
+ */
+TEST(DelayedRunnable, BackgroundTaskQueueShutdownTask)
+{
+ nsCOMPtr<nsISerialEventTarget> taskQueue;
+ nsresult rv = NS_CreateBackgroundTaskQueue("TestDelayedRunnable",
+ getter_AddRefs(taskQueue));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // Leak the queue, so it gets cleaned up by xpcom-shutdown.
+ nsISerialEventTarget* tq = taskQueue.forget().take();
+ mozilla::Unused << tq;
+}
+
+/*
+ * Like BackgroundTaskQueueShutdownTask but for nsThread, since both background
+ * TaskQueues and nsThreads are managed by nsThreadManager. For nsThread things
+ * are different and the shutdown task doesn't use Dispatch, but timings are
+ * similar.
+ */
+TEST(DelayedRunnable, nsThreadShutdownTask)
+{
+ nsCOMPtr<nsIThread> thread;
+ ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK);
+
+ // Leak the thread, so it gets cleaned up by xpcom-shutdown.
+ nsIThread* t = thread.forget().take();
+ mozilla::Unused << t;
+}
+
+TEST(DelayedRunnable, TimerFiresBeforeRunnableRuns)
+{
+ RefPtr<mozilla::SharedThreadPool> pool =
+ mozilla::SharedThreadPool::Get("Test Pool"_ns);
+ auto tailTaskQueue1 =
+ TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue1",
+ /* aSupportsTailDispatch = */ true);
+ auto tailTaskQueue2 =
+ TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue2",
+ /* aSupportsTailDispatch = */ true);
+ auto noTailTaskQueue =
+ TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable noTailTaskQueue",
+ /* aSupportsTailDispatch = */ false);
+ enum class State : uint8_t {
+ Start,
+ TimerRan,
+ TasksFinished,
+ } state = State::Start;
+ Monitor monitor MOZ_UNANNOTATED(__func__);
+ MonitorAutoLock lock(monitor);
+ MOZ_ALWAYS_SUCCEEDS(
+ tailTaskQueue1->Dispatch(NS_NewRunnableFunction(__func__, [&] {
+ // This will tail dispatch the delayed runnable, making it prone to
+ // lose a race against the directly-initiated timer firing (and
+ // dispatching another non-tail-dispatched runnable).
+ EXPECT_TRUE(tailTaskQueue1->RequiresTailDispatch(tailTaskQueue2));
+ tailTaskQueue2->DelayedDispatch(
+ NS_NewRunnableFunction(__func__, [&] {}), 1);
+ MonitorAutoLock lock(monitor);
+ auto timer = MakeRefPtr<mozilla::MediaTimer>();
+ timer->WaitFor(mozilla::TimeDuration::FromMilliseconds(1), __func__)
+ ->Then(noTailTaskQueue, __func__, [&] {
+ MonitorAutoLock lock(monitor);
+ state = State::TimerRan;
+ monitor.NotifyAll();
+ });
+ // Wait until the timer has run. It should have dispatched the
+ // TimerEvent to tailTaskQueue2 by then. The tail dispatch happens when
+ // we leave scope.
+ while (state != State::TimerRan) {
+ monitor.Wait();
+ }
+ // Notify main thread that we've finished the async steps.
+ state = State::TasksFinished;
+ monitor.Notify();
+ })));
+ // Wait for async steps before wrapping up the test case.
+ while (state != State::TasksFinished) {
+ monitor.Wait();
+ }
+}
diff --git a/xpcom/tests/gtest/TestEncoding.cpp b/xpcom/tests/gtest/TestEncoding.cpp
new file mode 100644
index 0000000000..2abbc5d708
--- /dev/null
+++ b/xpcom/tests/gtest/TestEncoding.cpp
@@ -0,0 +1,108 @@
+/* -*- 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/TestEscape.cpp b/xpcom/tests/gtest/TestEscape.cpp
new file mode 100644
index 0000000000..6834d5fa13
--- /dev/null
+++ b/xpcom/tests/gtest/TestEscape.cpp
@@ -0,0 +1,238 @@
+/* -*- 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"
+#include "mozilla/ArrayUtils.h"
+#include "nsNetUtil.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(Escape, 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(Escape, 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(Escape, 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");
+ }
+}
+
+TEST(Escape, nsAppendEscapedHTML)
+{
+ const char* srcs[] = {
+ "a", "bcdefgh", "<", ">", "&", "\"",
+ "'", "'bad'", "Foo<T>& foo", "'\"&><abc", "",
+ };
+
+ const char* dsts1[] = {
+ "a",
+ "bcdefgh",
+ "&lt;",
+ "&gt;",
+ "&amp;",
+ "&quot;",
+ "&#39;",
+ "&#39;bad&#39;",
+ "Foo&lt;T&gt;&amp; foo",
+ "&#39;&quot;&amp;&gt;&lt;abc",
+ "",
+ };
+
+ const char* dsts2[] = {
+ "a",
+ "abcdefgh",
+ "abcdefgh&lt;",
+ "abcdefgh&lt;&gt;",
+ "abcdefgh&lt;&gt;&amp;",
+ "abcdefgh&lt;&gt;&amp;&quot;",
+ "abcdefgh&lt;&gt;&amp;&quot;&#39;",
+ "abcdefgh&lt;&gt;&amp;&quot;&#39;&#39;bad&#39;",
+ "abcdefgh&lt;&gt;&amp;&quot;&#39;&#39;bad&#39;Foo&lt;T&gt;&amp; foo",
+ "abcdefgh&lt;&gt;&amp;&quot;&#39;&#39;bad&#39;Foo&lt;T&gt;&amp; "
+ "foo&#39;&quot;&amp;&gt;&lt;abc",
+ "abcdefgh&lt;&gt;&amp;&quot;&#39;&#39;bad&#39;Foo&lt;T&gt;&amp; "
+ "foo&#39;&quot;&amp;&gt;&lt;abc",
+ };
+
+ ASSERT_EQ(ArrayLength(srcs), ArrayLength(dsts1));
+ ASSERT_EQ(ArrayLength(srcs), ArrayLength(dsts2));
+
+ // Test when the destination is empty.
+ for (size_t i = 0; i < ArrayLength(srcs); i++) {
+ nsCString src(srcs[i]);
+ nsCString dst;
+ nsAppendEscapedHTML(src, dst);
+ ASSERT_TRUE(dst.Equals(dsts1[i]));
+ }
+
+ // Test when the destination is non-empty.
+ nsCString dst;
+ for (size_t i = 0; i < ArrayLength(srcs); i++) {
+ nsCString src(srcs[i]);
+ nsAppendEscapedHTML(src, dst);
+ ASSERT_TRUE(dst.Equals(dsts2[i]));
+ }
+}
+
+TEST(Escape, EscapeSpaces)
+{
+ // Tests the fallible version of NS_EscapeURL works as expected when no
+ // escaping is necessary.
+ nsCString toEscape("data:\x0D\x0A spa ces\xC4\x9F");
+ nsCString escaped;
+ nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible);
+ EXPECT_EQ(rv, NS_OK);
+ // Only non-ASCII and C0
+ EXPECT_STREQ(escaped.BeginReading(), "data:%0D%0A spa ces%C4%9F");
+
+ escaped.Truncate();
+ rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII | esc_Spaces, escaped, fallible);
+ EXPECT_EQ(rv, NS_OK);
+ EXPECT_STREQ(escaped.BeginReading(), "data:%0D%0A%20spa%20ces%C4%9F");
+}
+
+TEST(Escape, AppleNSURLEscapeHash)
+{
+ nsCString toEscape("#");
+ nsCString escaped;
+ bool isEscapedOK = NS_Escape(toEscape, escaped, url_NSURLRef);
+ EXPECT_EQ(isEscapedOK, true);
+ EXPECT_STREQ(escaped.BeginReading(), "%23");
+}
+
+TEST(Escape, AppleNSURLEscapeNoDouble)
+{
+ // The '%' in "%23" shouldn't be encoded again.
+ nsCString toEscape("%23");
+ nsCString escaped;
+ bool isEscapedOK = NS_Escape(toEscape, escaped, url_NSURLRef);
+ EXPECT_EQ(isEscapedOK, true);
+ EXPECT_STREQ(escaped.BeginReading(), "%23");
+}
+
+// Test escaping of URLs that shouldn't be changed by escaping.
+TEST(Escape, AppleNSURLEscapeLists)
+{
+ // Pairs of URLs (un-encoded, encoded)
+ nsTArray<std::pair<nsCString, nsCString>> pairs{
+ {"https://chat.mozilla.org/#/room/#macdev:mozilla.org"_ns,
+ "https://chat.mozilla.org/#/room/%23macdev:mozilla.org"_ns},
+ };
+
+ for (std::pair<nsCString, nsCString>& pair : pairs) {
+ nsCString escaped;
+ nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, pair.first);
+ EXPECT_EQ(rv, NS_OK);
+ EXPECT_STREQ(pair.second.BeginReading(), escaped.BeginReading());
+ }
+
+ // A list of URLs that should not be changed by encoding.
+ nsTArray<nsCString> unchangedURLs{
+ // '=' In the query
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854"_ns,
+ // Escaped character in the fragment
+ "https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address"_ns,
+ // Misc query
+ "https://www.google.com/search?q=firefox+web+browser&client=firefox-b-1-d&ei=abc&ved=abc&abc=5&oq=firefox+web+browser&gs_lcp=abc&sclient=gws-wiz"_ns,
+ // Check for double encoding. % encoded octals should not be re-encoded.
+ "https://chat.mozilla.org/#/room/%23macdev%3Amozilla.org"_ns,
+ "https://searchfox.org/mozilla-central/search?q=symbol%3AE_%3CT_mozilla%3A%3AWebGLExtensionID%3E_EXT_color_buffer_half_float&path="_ns,
+ // Other
+ "https://site.com/script?foo=bar#this_ref"_ns,
+ };
+
+ for (nsCString& toEscape : unchangedURLs) {
+ nsCString escaped;
+ nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, toEscape);
+ EXPECT_EQ(rv, NS_OK);
+ EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading());
+ }
+}
+
+// Test external handler URLs are properly escaped.
+TEST(Escape, EscapeURLExternalHandlerURLs)
+{
+ const nsCString input[] = {
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/?:@&=+$,!'()*-._~#[]"_ns,
+ " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"_ns,
+ "custom_proto:Hello World"_ns,
+ "custom_proto:Hello%20World"_ns,
+ "myApp://\"foo\" 'bar' `foo`"_ns,
+ "translator://en-de?view=übersicht"_ns,
+ "foo:some\\path\\here"_ns,
+ "web+foo://user:1234@example.com:8080?foo=bar"_ns,
+ "ext+bar://id='myId'"_ns};
+
+ const nsCString expected[] = {
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/?:@&=+$,!'()*-._~#[]"_ns,
+ "%20!%22#$%&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~"_ns,
+ "custom_proto:Hello%20World"_ns,
+ "custom_proto:Hello%20World"_ns,
+ "myApp://%22foo%22%20'bar'%20%60foo%60"_ns,
+ "translator://en-de?view=%C3%BCbersicht"_ns,
+ "foo:some%5Cpath%5Chere"_ns,
+ "web+foo://user:1234@example.com:8080?foo=bar"_ns,
+ "ext+bar://id='myId'"_ns};
+
+ for (size_t i = 0; i < ArrayLength(input); i++) {
+ nsCString src(input[i]);
+ nsCString dst;
+ nsresult rv =
+ NS_EscapeURL(src, esc_ExtHandler | esc_AlwaysCopy, dst, fallible);
+ EXPECT_EQ(rv, NS_OK);
+ ASSERT_TRUE(dst.Equals(expected[i]));
+ }
+}
diff --git a/xpcom/tests/gtest/TestEventPriorities.cpp b/xpcom/tests/gtest/TestEventPriorities.cpp
new file mode 100644
index 0000000000..874fc81a33
--- /dev/null
+++ b/xpcom/tests/gtest/TestEventPriorities.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsIRunnable.h"
+#include "nsXPCOM.h"
+#include "nsThreadUtils.h"
+#include "gtest/gtest.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+#include <functional>
+
+using namespace mozilla;
+
+class TestEvent final : public Runnable, nsIRunnablePriority {
+ public:
+ explicit TestEvent(int* aCounter, std::function<void()>&& aCheck,
+ uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL)
+ : Runnable("TestEvent"),
+ mCounter(aCounter),
+ mCheck(std::move(aCheck)),
+ mPriority(aPriority) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD GetPriority(uint32_t* aPriority) override {
+ *aPriority = mPriority;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP Run() override {
+ (*mCounter)++;
+ mCheck();
+ return NS_OK;
+ }
+
+ private:
+ ~TestEvent() = default;
+
+ int* mCounter;
+ std::function<void()> mCheck;
+ uint32_t mPriority;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(TestEvent, Runnable, nsIRunnablePriority)
+
+TEST(EventPriorities, IdleAfterNormal)
+{
+ int normalRan = 0, idleRan = 0;
+
+ RefPtr<TestEvent> evNormal =
+ new TestEvent(&normalRan, [&] { ASSERT_EQ(idleRan, 0); });
+ RefPtr<TestEvent> evIdle =
+ new TestEvent(&idleRan, [&] { ASSERT_EQ(normalRan, 3); });
+
+ NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle);
+ NS_DispatchToMainThread(evNormal);
+ NS_DispatchToMainThread(evNormal);
+ NS_DispatchToMainThread(evNormal);
+
+ MOZ_ALWAYS_TRUE(
+ SpinEventLoopUntil("xpcom:TEST(EventPriorities, IdleAfterNormal)"_ns,
+ [&]() { return normalRan == 3 && idleRan == 3; }));
+}
+
+TEST(EventPriorities, HighNormal)
+{
+ int normalRan = 0, highRan = 0;
+
+ RefPtr<TestEvent> evNormal = new TestEvent(
+ &normalRan, [&] { ASSERT_TRUE((highRan - normalRan) >= 0); });
+ RefPtr<TestEvent> evHigh = new TestEvent(
+ &highRan, [&] { ASSERT_TRUE((highRan - normalRan) >= 0); },
+ nsIRunnablePriority::PRIORITY_VSYNC);
+
+ NS_DispatchToMainThread(evNormal);
+ NS_DispatchToMainThread(evNormal);
+ NS_DispatchToMainThread(evNormal);
+ NS_DispatchToMainThread(evHigh);
+ NS_DispatchToMainThread(evHigh);
+ NS_DispatchToMainThread(evHigh);
+
+ MOZ_ALWAYS_TRUE(
+ SpinEventLoopUntil("xpcom:TEST(EventPriorities, HighNormal)"_ns,
+ [&]() { return normalRan == 3 && highRan == 3; }));
+}
diff --git a/xpcom/tests/gtest/TestEventTargetQI.cpp b/xpcom/tests/gtest/TestEventTargetQI.cpp
new file mode 100644
index 0000000000..6131b5e63e
--- /dev/null
+++ b/xpcom/tests/gtest/TestEventTargetQI.cpp
@@ -0,0 +1,83 @@
+/* -*- 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/LazyIdleThread.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsThreadPool.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "nsXPCOMCIDInternal.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+// Cast the pointer to nsISupports* through nsIEventTarget* before doing the QI
+// in order to avoid a static assert intended to prevent trivial QIs, while also
+// avoiding ambiguous base errors.
+template <typename TargetInterface, typename SourcePtr>
+bool TestQITo(SourcePtr& aPtr1) {
+ nsCOMPtr<TargetInterface> aPtr2 = do_QueryInterface(
+ static_cast<nsISupports*>(static_cast<nsIEventTarget*>(aPtr1.get())));
+ return (bool)aPtr2;
+}
+
+TEST(TestEventTargetQI, ThreadPool)
+{
+ nsCOMPtr<nsIThreadPool> thing = new nsThreadPool();
+
+ EXPECT_FALSE(TestQITo<nsISerialEventTarget>(thing));
+
+ EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
+
+ thing->Shutdown();
+}
+
+TEST(TestEventTargetQI, SharedThreadPool)
+{
+ nsCOMPtr<nsIThreadPool> thing = SharedThreadPool::Get("TestPool"_ns, 1);
+ EXPECT_TRUE(thing);
+
+ EXPECT_FALSE(TestQITo<nsISerialEventTarget>(thing));
+
+ EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
+}
+
+TEST(TestEventTargetQI, Thread)
+{
+ nsCOMPtr<nsIThread> thing = do_GetCurrentThread();
+ EXPECT_TRUE(thing);
+
+ EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
+
+ EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
+}
+
+TEST(TestEventTargetQI, ThrottledEventQueue)
+{
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+ RefPtr<ThrottledEventQueue> thing =
+ ThrottledEventQueue::Create(thread, "test queue");
+ EXPECT_TRUE(thing);
+
+ EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
+
+ EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
+}
+
+TEST(TestEventTargetQI, LazyIdleThread)
+{
+ RefPtr<LazyIdleThread> thing = new LazyIdleThread(0, "TestThread");
+ EXPECT_TRUE(thing);
+
+ EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
+
+ EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
+
+ thing->Shutdown();
+}
diff --git a/xpcom/tests/gtest/TestExpirationTracker.cpp b/xpcom/tests/gtest/TestExpirationTracker.cpp
new file mode 100644
index 0000000000..de55d6f907
--- /dev/null
+++ b/xpcom/tests/gtest/TestExpirationTracker.cpp
@@ -0,0 +1,192 @@
+/* -*- 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 "nsString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOM.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 upperBoundSlackMS = 1200; // allow this much error
+static uint32_t lowerBoundSlackMS = 60;
+
+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() {
+ using mozilla::UniquePtr;
+
+ 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) override {
+ 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 - lowerBoundSlackMS;
+ uint32_t upperBoundMS = K * (periodMS + sleepPeriodMS) + upperBoundSlackMS;
+ if (logging) {
+ printf("Checking: %d-%d = %d [%d,%d]\n", now, aObj->mLastUsed, timeDiffMS,
+ lowerBoundMS, upperBoundMS);
+ }
+ if (timeDiffMS < lowerBoundMS || timeDiffMS > upperBoundMS) {
+ EXPECT_LT(timeDiffMS, periodMS);
+ EXPECT_TRUE(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 0000000000..6e95366584
--- /dev/null
+++ b/xpcom/tests/gtest/TestFile.cpp
@@ -0,0 +1,576 @@
+/* -*- 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"
+#ifdef XP_WIN
+# include "nsILocalFileWin.h"
+#endif
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsPrintfCString.h"
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+
+#ifdef XP_WIN
+bool gTestWithPrefix_Win = false;
+#endif
+
+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;
+}
+
+#ifdef XP_WIN
+static void SetUseDOSDevicePathSyntax(nsIFile* aFile) {
+ if (gTestWithPrefix_Win) {
+ nsresult rv;
+ nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(aFile, &rv);
+ VerifyResult(rv, "Querying nsILocalFileWin");
+
+ MOZ_ASSERT(winFile);
+ winFile->SetUseDOSDevicePathSyntax(true);
+ }
+}
+#endif
+
+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");
+
+#ifdef XP_WIN
+ SetUseDOSDevicePathSyntax(file);
+#endif
+
+ return file.forget();
+}
+
+template <typename char_type>
+static nsTString<char_type> FixName(const char_type* aName) {
+ nsTString<char_type> name;
+ for (uint32_t i = 0; aName[i]; ++i) {
+ char_type 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_NS_FAILED(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,
+ uint32_t aExpectedRemoveCount = 1) {
+ 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;
+ }
+
+ uint32_t removeCount = 0;
+ rv = file->Remove(aRecursive, &removeCount);
+ if (!VerifyResult(rv, "Remove")) return false;
+ EXPECT_EQ(removeCount, aExpectedRemoveCount) << "Removal count was wrong";
+
+ 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;
+
+ auto path = file->NativePath();
+#ifdef XP_WIN
+ path.Append(FixName(u"/./.."));
+ nsresult rv = file->InitWithPath(path);
+ VerifyResult(rv, "InitWithPath");
+#else
+ path.Append(FixName("/./.."));
+ nsresult rv = file->InitWithNativePath(path);
+ VerifyResult(rv, "InitWithNativePath");
+#endif
+ rv = file->Normalize();
+ VerifyResult(rv, "Normalize");
+ path = file->NativePath();
+
+ auto basePath = aBase->NativePath();
+ VerifyResult(rv, "GetNativePath (base)");
+
+ EXPECT_TRUE(path.Equals(basePath))
+ << "Incorrect normalization: " << file->HumanReadablePath().get() << " - "
+ << aBase->HumanReadablePath().get();
+ if (!path.Equals(basePath)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Test nsIFile::GetDiskSpaceAvailable
+static bool TestDiskSpaceAvailable(nsIFile* aBase) {
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file) return false;
+
+ int64_t diskSpaceAvailable = 0;
+ nsresult rv = file->GetDiskSpaceAvailable(&diskSpaceAvailable);
+ VerifyResult(rv, "GetDiskSpaceAvailable");
+
+ EXPECT_GE(diskSpaceAvailable, 0);
+
+ return true;
+}
+
+// Test nsIFile::GetDiskCapacity
+static bool TestDiskCapacity(nsIFile* aBase) {
+ nsCOMPtr<nsIFile> file = NewFile(aBase);
+ if (!file) return false;
+
+ int64_t diskCapacity = 0;
+ nsresult rv = file->GetDiskCapacity(&diskCapacity);
+ VerifyResult(rv, "GetDiskCapacity");
+
+ EXPECT_GE(diskCapacity, 0);
+
+ return true;
+}
+
+static void SetupAndTestFunctions(const nsAString& aDirName,
+ bool aTestCreateUnique, bool aTestNormalize) {
+ nsCOMPtr<nsIFile> base;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ ASSERT_TRUE(VerifyResult(rv, "Getting temp directory"));
+
+#ifdef XP_WIN
+ SetUseDOSDevicePathSyntax(base);
+#endif
+
+ rv = base->Append(aDirName);
+ ASSERT_TRUE(
+ VerifyResult(rv, nsPrintfCString("Appending %s to temp directory name",
+ NS_ConvertUTF16toUTF8(aDirName).get())
+ .get()));
+
+ // 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
+ if (aTestNormalize) {
+ 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 leafName
+ nsString leafName;
+ rv = base->GetLeafName(leafName);
+ ASSERT_TRUE(VerifyResult(rv, "Getting leafName"));
+ ASSERT_TRUE(leafName.Equals(aDirName));
+
+ // 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 directories
+ ASSERT_TRUE(TestCopy(base, subdir, "file4.txt", "file5.txt"));
+
+ if (aTestNormalize) {
+ // Run normalization tests while the directory exists
+ ASSERT_TRUE(TestNormalizeNativePath(base, subdir));
+ }
+
+ // Test recursive directory removal
+ ASSERT_TRUE(TestRemove(base, "subdir", true, 2));
+
+ if (aTestCreateUnique) {
+ 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));
+
+ ASSERT_TRUE(TestDiskSpaceAvailable(base));
+ ASSERT_TRUE(TestDiskCapacity(base));
+
+ // Clean up temporary stuff
+ rv = base->Remove(true);
+ VerifyResult(rv, "Cleaning up temp directory");
+}
+
+TEST(TestFile, Unprefixed)
+{
+#ifdef XP_WIN
+ gTestWithPrefix_Win = false;
+#endif
+
+ SetupAndTestFunctions(u"mozfiletests"_ns,
+ /* aTestCreateUnique */ true,
+ /* aTestNormalize */ true);
+
+#ifdef XP_WIN
+ gTestWithPrefix_Win = true;
+#endif
+}
+
+// This simulates what QM_NewLocalFile does (NS_NewLocalFiles and then
+// SetUseDOSDevicePathSyntax if it's on Windows for NewFile)
+TEST(TestFile, PrefixedOnWin)
+{
+ SetupAndTestFunctions(u"mozfiletests"_ns,
+ /* aTestCreateUnique */ true,
+ /* aTestNormalize */ true);
+}
+
+TEST(TestFile, PrefixedOnWin_PathExceedsMaxPath)
+{
+ // We want to verify if the prefix would allow as to create a file with over
+ // 260 char for its path. However, on Windows, the maximum length of filename
+ // is 255. Given the base file path and we are going append some other file
+ // to the current base file, let's assume the file path will exceed 260 so
+ // that we are able to verify if the prefix works or not.
+ nsString dirName;
+ dirName.AssignLiteral("mozfiletests");
+ for (uint32_t i = 255 - dirName.Length(); i > 0; --i) {
+ dirName.AppendLiteral("a");
+ }
+
+ // Bypass the test for CreateUnique because there is a check for the max
+ // length of the root on all platforms.
+ SetupAndTestFunctions(dirName, /* aTestCreateUnique */ false,
+ /* aTestNormalize */ true);
+}
+
+TEST(TestFile, PrefixedOnWin_ComponentEndsWithPeriod)
+{
+ // Bypass the normalization for this because it would strip the trailing
+ // period.
+ SetupAndTestFunctions(u"mozfiletests."_ns,
+ /* aTestCreateUnique */ true,
+ /* aTestNormalize */ false);
+}
diff --git a/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp b/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp
new file mode 100644
index 0000000000..39b73a7148
--- /dev/null
+++ b/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp
@@ -0,0 +1,289 @@
+/* -*- 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 "mozilla/gtest/MozAssertions.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIFile.h"
+#include "nsILocalFileWin.h"
+#include "nsString.h"
+
+#define MAX_PATH 260
+
+#include "gtest/gtest.h"
+
+static void CanInitWith(const char* aPath, bool aShouldWork) {
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ nsresult rv = file->InitWithNativePath(nsDependentCString(aPath));
+ bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv);
+ EXPECT_TRUE(success) << "'" << aPath << "' rv=" << std::hex
+ << (unsigned int)rv;
+}
+
+static void CanAppend(const char* aRoot, const char* aPath, bool aShouldWork) {
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ file->InitWithNativePath(nsDependentCString(aRoot));
+ nsAutoCString basePath;
+ file->GetNativeTarget(basePath);
+
+ nsresult rv = file->AppendNative(nsDependentCString(aPath));
+ bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv);
+ EXPECT_TRUE(success) << "'" << basePath.get() << "' + '" << aPath
+ << "' rv=" << std::hex << (unsigned int)rv;
+}
+
+static void CanSetLeafName(const char* aRoot, const char* aPath,
+ bool aShouldWork) {
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ file->InitWithNativePath(nsDependentCString(aRoot));
+ nsAutoCString basePath;
+ file->GetNativeTarget(basePath);
+
+ nsresult rv =
+ file->SetLeafName(NS_ConvertUTF8toUTF16(nsDependentCString(aPath)));
+ bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv);
+ EXPECT_TRUE(success) << "'" << basePath.get() << "' set leaf to '" << aPath
+ << "' rv=" << std::hex << (unsigned int)rv;
+}
+
+TEST(TestFileNTFSSpecialPaths, PlainPaths)
+{
+ CanInitWith("C:\\", true);
+ CanInitWith("C:\\foo", true);
+ CanInitWith("C:\\bar\\foo", true);
+ CanInitWith("C:\\bar\\foo\\", true);
+
+ CanAppend("C:\\", "foo", true);
+ CanAppend("C:\\", "bar", true);
+ CanAppend("C:\\bar", "foo", true);
+
+ CanSetLeafName("C:\\a", "foo", true);
+ CanSetLeafName("C:\\a", "bar", true);
+}
+
+TEST(TestFileNTFSSpecialPaths, AllowedSpecialChars)
+{
+ CanInitWith("C:\\$foo", true);
+ CanInitWith("C:\\bar\\$foo", true);
+ CanInitWith("C:\\foo:Zone.Identifier", true);
+ CanInitWith("C:\\$foo:Zone.Identifier", true);
+ CanInitWith("C:\\bar\\$foo:Zone.Identifier", true);
+
+ CanAppend("C:\\", "$foo", true);
+ CanAppend("C:\\bar\\", "$foo", true);
+ CanAppend("C:\\", "foo:Zone.Identifier", true);
+ CanAppend("C:\\", "$foo:Zone.Identifier", true);
+ CanAppend("C:\\bar\\", "$foo:Zone.Identifier", true);
+
+ CanSetLeafName("C:\\a", "$foo", true);
+ CanSetLeafName("C:\\a", "foo:Zone.Identifier", true);
+ CanSetLeafName("C:\\a", "$foo:Zone.Identifier", true);
+}
+
+TEST(TestFileNTFSSpecialPaths, ForbiddenAttributes)
+{
+ CanInitWith("C:\\:$MFT", false);
+ CanInitWith("C:\\:$mft", false);
+ CanInitWith("C:\\:$foo", false);
+ // nsLocalFileWin strips the trailing slash so this should also fail:
+ CanInitWith("C:\\:$MFT\\", false);
+ CanInitWith("C:\\:$mft\\", false);
+ CanInitWith("C:\\:$foo\\", false);
+
+ // We just block these everywhere, not just at the root:
+ CanInitWith("C:\\bar\\:$mft", false);
+ CanInitWith("C:\\bar\\:$mft\\", false);
+ CanInitWith("C:\\bar\\:$foo", false);
+ CanInitWith("C:\\bar\\:$foo\\", false);
+
+ // Now do the same for appending.
+ CanAppend("C:\\", ":$MFT", false);
+ CanAppend("C:\\", ":$mft", false);
+ CanAppend("C:\\", ":$foo", false);
+ // nsLocalFileWin strips the trailing slash so this should also fail:
+ CanAppend("C:\\", ":$MFT\\", false);
+ CanAppend("C:\\", ":$mft\\", false);
+ CanAppend("C:\\", ":$foo\\", false);
+
+ // We just block these everywhere, not just at the root:
+ CanAppend("C:\\bar\\", ":$mft", false);
+ CanAppend("C:\\bar\\", ":$mft\\", false);
+ CanAppend("C:\\bar\\", ":$foo", false);
+ CanAppend("C:\\bar\\", ":$foo\\", false);
+
+ // And the same thing for leaf names:
+ CanSetLeafName("C:\\a", ":$MFT", false);
+ CanSetLeafName("C:\\a", ":$mft", false);
+ CanSetLeafName("C:\\a", ":$foo", false);
+
+ CanSetLeafName("C:\\a", ":$MFT\\", false);
+ CanSetLeafName("C:\\a", ":$mft\\", false);
+ CanSetLeafName("C:\\a", ":$foo\\", false);
+
+ CanSetLeafName("C:\\bar\\foo", ":$mft", false);
+ CanSetLeafName("C:\\bar\\foo", ":$mft\\", false);
+ CanSetLeafName("C:\\bar\\foo", ":$foo", false);
+ CanSetLeafName("C:\\bar\\foo", ":$foo\\", false);
+}
+
+TEST(TestFileNTFSSpecialPaths, ForbiddenMetaFiles)
+{
+ CanInitWith("C:\\$MFT", false);
+ CanInitWith("C:\\$mft", false);
+ CanInitWith("C:\\$bitmap", false);
+
+ CanAppend("C:\\", "$MFT", false);
+ CanAppend("C:\\", "$mft", false);
+ CanAppend("C:\\", "$bitmap", false);
+
+ CanSetLeafName("C:\\a", "$MFT", false);
+ CanSetLeafName("C:\\a", "$mft", false);
+ CanSetLeafName("C:\\a", "$bitmap", false);
+
+ // nsLocalFileWin strips the trailing slash so this should also fail:
+ CanInitWith("C:\\$MFT\\", false);
+ CanInitWith("C:\\$mft\\", false);
+ CanInitWith("C:\\$bitmap\\", false);
+
+ CanAppend("C:\\", "$MFT\\", false);
+ CanAppend("C:\\", "$mft\\", false);
+ CanAppend("C:\\", "$bitmap\\", false);
+
+ CanSetLeafName("C:\\a", "$MFT\\", false);
+ CanSetLeafName("C:\\a", "$mft\\", false);
+ CanSetLeafName("C:\\a", "$bitmap\\", false);
+
+ // Shouldn't be able to bypass this by asking for ADS stuff:
+ CanInitWith("C:\\$MFT:Zone.Identifier", false);
+ CanInitWith("C:\\$mft:Zone.Identifier", false);
+ CanInitWith("C:\\$bitmap:Zone.Identifier", false);
+
+ CanAppend("C:\\", "$MFT:Zone.Identifier", false);
+ CanAppend("C:\\", "$mft:Zone.Identifier", false);
+ CanAppend("C:\\", "$bitmap:Zone.Identifier", false);
+
+ CanSetLeafName("C:\\a", "$MFT:Zone.Identifier", false);
+ CanSetLeafName("C:\\a", "$mft:Zone.Identifier", false);
+ CanSetLeafName("C:\\a", "$bitmap:Zone.Identifier", false);
+}
+
+TEST(TestFileNTFSSpecialPaths, ForbiddenMetaFilesOtherRoots)
+{
+ // Should still block them for UNC and volume roots
+ CanInitWith("\\\\LOCALHOST\\C$\\$MFT", false);
+ CanInitWith("\\\\?\\Volume{1234567}\\$MFT", false);
+
+ CanAppend("\\\\LOCALHOST\\", "C$\\$MFT", false);
+ CanAppend("\\\\LOCALHOST\\C$\\", "$MFT", false);
+ CanAppend("\\\\?\\Volume{1234567}\\", "$MFT", false);
+ CanAppend("\\\\Blah\\", "Volume{1234567}\\$MFT", false);
+
+ CanSetLeafName("\\\\LOCALHOST\\C$", "C$\\$MFT", false);
+ CanSetLeafName("\\\\LOCALHOST\\C$\\foo", "$MFT", false);
+ CanSetLeafName("\\\\?\\Volume{1234567}\\foo", "$MFT", false);
+ CanSetLeafName("\\\\Blah\\foo", "Volume{1234567}\\$MFT", false);
+
+ // Root detection should cope with un-normalized paths:
+ CanInitWith("C:\\foo\\..\\$MFT", false);
+ CanInitWith("C:\\foo\\..\\$mft\\", false);
+ CanInitWith("\\\\LOCALHOST\\C$\\blah\\..\\$MFT", false);
+ CanInitWith("\\\\?\\Volume{13455635}\\blah\\..\\$MFT", false);
+ // As well as different or duplicated separators:
+ CanInitWith("C:\\foo\\..\\\\$MFT\\", false);
+ CanInitWith("\\\\?\\Volume{1234567}/$MFT", false);
+ CanInitWith("\\\\LOCALHOST\\C$/blah//../$MFT", false);
+
+ // There are no "append" equivalents for the preceding set of tests,
+ // because append does not allow '..' to be used as a relative path
+ // component, nor does it allow forward slashes:
+ CanAppend("C:\\foo", "..\\", false);
+ CanAppend("C:\\foo", "bar/baz", false);
+
+ // But this is (strangely) allowed for SetLeafName. Yes, really.
+ CanSetLeafName("C:\\foo\\bar", "..\\$MFT", false);
+ CanSetLeafName("C:\\foo\\bar", "..\\$mft\\", false);
+ CanSetLeafName("\\\\LOCALHOST\\C$\\bl", "ah\\..\\$MFT", false);
+ CanSetLeafName("\\\\?\\Volume{13455635}\\bla", "ah\\..\\$MFT", false);
+
+ CanSetLeafName("C:\\foo\\bar", "..\\\\$MFT\\", false);
+ CanSetLeafName("\\\\?\\Volume{1234567}\\bar", "/$MFT", false);
+ CanSetLeafName("\\\\LOCALHOST\\C$/blah/", "\\../$MFT", false);
+}
+
+TEST(TestFileNTFSSpecialPaths, NotQuiteMetaFiles)
+{
+ // These files should not be blocked away from the root:
+ CanInitWith("C:\\bar\\$bitmap", true);
+ CanInitWith("C:\\bar\\$mft", true);
+
+ // Same for append:
+ CanAppend("C:\\bar\\", "$bitmap", true);
+ CanAppend("C:\\bar\\", "$mft", true);
+
+ // And SetLeafName:
+ CanSetLeafName("C:\\bar\\foo", "$bitmap", true);
+ CanSetLeafName("C:\\bar\\foo", "$mft", true);
+
+ // And we shouldn't block on substring matches:
+ CanInitWith("C:\\$MFT stocks", true);
+ CanAppend("C:\\", "$MFT stocks", true);
+ CanSetLeafName("C:\\", "$MFT stocks", true);
+}
+
+TEST(TestFileNTFSSpecialPaths, Normalization)
+{
+ // First determine the working directory:
+ wchar_t workingDir[MAX_PATH];
+ if (nullptr == _wgetcwd(workingDir, MAX_PATH - 1)) {
+ EXPECT_FALSE(true) << "Getting working directory failed.";
+ return;
+ }
+
+ nsString normalizedPath(workingDir);
+ // Need at least 3 chars for the root, at least 2 more to get another subdir
+ // in there. This test will fail if cwd is the root of a drive.
+ if (normalizedPath.Length() < 5 ||
+ !mozilla::IsAsciiAlpha(normalizedPath.First()) ||
+ normalizedPath.CharAt(1) != L':' || normalizedPath.CharAt(2) != L'\\') {
+ EXPECT_FALSE(true) << "Working directory not long enough?!";
+ return;
+ }
+
+ // Copy the drive and colon, but NOT the backslash.
+ nsAutoString startingFilePath(Substring(normalizedPath, 0, 2));
+ normalizedPath.Cut(0, 3);
+
+ // Then determine the number of path components in cwd:
+ nsAString::const_iterator begin, end;
+ normalizedPath.BeginReading(begin);
+ normalizedPath.EndReading(end);
+ if (!FindCharInReadable(L'\\', begin, end)) {
+ EXPECT_FALSE(true) << "Working directory was at a root";
+ return;
+ }
+ auto numberOfComponentsAboveRoot = 1;
+ while (FindCharInReadable(L'\\', begin, end)) {
+ begin++;
+ numberOfComponentsAboveRoot++;
+ }
+
+ // Then set up a file with that many `..\` components:
+ startingFilePath.SetCapacity(3 + numberOfComponentsAboveRoot * 3 + 9);
+ while (numberOfComponentsAboveRoot--) {
+ startingFilePath.AppendLiteral(u"..\\");
+ }
+ startingFilePath.AppendLiteral(u"$mft");
+
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ // This should fail immediately, rather than waiting for a call to
+ // nsIFile::Normalize, because normalization doesn't happen reliably,
+ // and where it does happen consumers often don't check for errors.
+ nsresult rv = file->InitWithPath(startingFilePath);
+ EXPECT_NS_FAILED(rv) << " from normalizing '"
+ << NS_ConvertUTF16toUTF8(startingFilePath).get()
+ << "' rv=" << std::hex << (unsigned int)rv;
+}
diff --git a/xpcom/tests/gtest/TestFilePreferencesUnix.cpp b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp
new file mode 100644
index 0000000000..93c6506b7d
--- /dev/null
+++ b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp
@@ -0,0 +1,233 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/FilePreferences.h"
+
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ScopeExit.h"
+#include "nsIDirectoryEnumerator.h"
+
+using namespace mozilla;
+
+const char kForbiddenPathsPref[] = "network.file.path_blacklist";
+
+TEST(TestFilePreferencesUnix, Parsing)
+{
+#define kForbidden "/tmp/forbidden"
+#define kForbiddenDir "/tmp/forbidden/"
+#define kForbiddenFile "/tmp/forbidden/file"
+#define kOther "/tmp/other"
+#define kOtherDir "/tmp/other/"
+#define kOtherFile "/tmp/other/file"
+#define kAllowed "/tmp/allowed"
+
+ // This is run on exit of this function to make sure we clear the pref
+ // and that behaviour with the pref cleared is correct.
+ auto cleanup = MakeScopeExit([&] {
+ nsresult rv = Preferences::ClearUser(kForbiddenPathsPref);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbidden)),
+ true);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)),
+ true);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenFile)),
+ true);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kAllowed)), true);
+ });
+
+ auto CheckPrefs = [](const nsACString& aPaths) {
+ nsresult rv;
+ rv = Preferences::SetCString(kForbiddenPathsPref, aPaths);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)),
+ false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)),
+ false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenFile)),
+ false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbidden)),
+ false);
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kAllowed)), true);
+ };
+
+ CheckPrefs(nsLiteralCString(kForbidden));
+ CheckPrefs(nsLiteralCString(kForbidden "," kOther));
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kOtherFile)),
+ false);
+ CheckPrefs(nsLiteralCString(kForbidden "," kOther ","));
+ ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kOtherFile)),
+ false);
+}
+
+TEST(TestFilePreferencesUnix, Simple)
+{
+ nsAutoCString tempPath;
+
+ // This is the directory we will forbid
+ nsCOMPtr<nsIFile> forbiddenDir;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(forbiddenDir));
+ ASSERT_EQ(rv, NS_OK);
+ rv = forbiddenDir->GetNativePath(tempPath);
+ ASSERT_EQ(rv, NS_OK);
+ rv = forbiddenDir->AppendNative("forbidden_dir"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ // This is executed at exit to clean up after ourselves.
+ auto cleanup = MakeScopeExit([&] {
+ nsresult rv = Preferences::ClearUser(kForbiddenPathsPref);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+
+ rv = forbiddenDir->Remove(true);
+ ASSERT_EQ(rv, NS_OK);
+ });
+
+ // Create the directory
+ rv = forbiddenDir->Create(nsIFile::DIRECTORY_TYPE, 0666);
+ ASSERT_EQ(rv, NS_OK);
+
+ // This is the file we will try to access
+ nsCOMPtr<nsIFile> forbiddenFile;
+ rv = forbiddenDir->Clone(getter_AddRefs(forbiddenFile));
+ ASSERT_EQ(rv, NS_OK);
+ rv = forbiddenFile->AppendNative("test_file"_ns);
+
+ // Create the file
+ ASSERT_EQ(rv, NS_OK);
+ rv = forbiddenFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+
+ // Get the forbidden path
+ nsAutoCString forbiddenPath;
+ rv = forbiddenDir->GetNativePath(forbiddenPath);
+ ASSERT_EQ(rv, NS_OK);
+
+ // Set the pref and make sure it is enforced
+ rv = Preferences::SetCString(kForbiddenPathsPref, forbiddenPath);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+
+ // Check that we can't access some of the file attributes
+ int64_t size;
+ rv = forbiddenFile->GetFileSize(&size);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ bool exists;
+ rv = forbiddenFile->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that we can't enumerate the directory
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator;
+ rv = forbiddenDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ nsCOMPtr<nsIFile> newPath;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative("."_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative("forbidden_dir"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ rv = newPath->AppendNative("test_file"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that ./ does not bypass the filter
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendRelativeNativePath("./forbidden_dir/file"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->Exists(&exists);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that .. does not bypass the filter
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendRelativeNativePath("allowed/../forbidden_dir/file"_ns);
+ ASSERT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH);
+
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath));
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative("allowed"_ns);
+ ASSERT_EQ(rv, NS_OK);
+ rv = newPath->AppendNative(".."_ns);
+ ASSERT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH);
+
+#if defined(XP_UNIX) && !defined(ANDROID)
+ nsAutoCString homePath;
+ NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(newPath));
+ newPath->GetNativePath(homePath);
+
+ newPath->InitWithNativePath("~"_ns);
+ ASSERT_TRUE(newPath->NativePath().Equals(homePath));
+
+ newPath->InitWithNativePath("~/foo"_ns);
+ ASSERT_TRUE(newPath->NativePath().Equals(homePath + "/foo"_ns));
+
+ nsLiteralCString homeBase =
+# ifdef XP_MACOSX
+ "/Users"_ns;
+# else
+ "/home"_ns;
+# endif
+
+ newPath->InitWithNativePath("~foo"_ns);
+ ASSERT_TRUE(newPath->NativePath().Equals(homeBase + "/foo"_ns));
+
+ newPath->InitWithNativePath("~foo/bar"_ns);
+ ASSERT_TRUE(newPath->NativePath().Equals(homeBase + "/foo/bar"_ns));
+#endif
+
+ nsAutoCString trickyPath(tempPath);
+ trickyPath.AppendLiteral("/allowed/../forbidden_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that we can't construct a path that is functionally the same
+ // as the forbidden one and bypasses the filter.
+ trickyPath = tempPath;
+ trickyPath.AppendLiteral("/./forbidden_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ trickyPath = tempPath;
+ trickyPath.AppendLiteral("//forbidden_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ trickyPath.Truncate();
+ trickyPath.AppendLiteral("//");
+ trickyPath.Append(tempPath);
+ trickyPath.AppendLiteral("/forbidden_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ trickyPath.Truncate();
+ trickyPath.AppendLiteral("//");
+ trickyPath.Append(tempPath);
+ trickyPath.AppendLiteral("//forbidden_dir/file");
+ rv = newPath->InitWithNativePath(trickyPath);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ // Check that if the forbidden string is a directory, we only block access
+ // to subresources, not the directory itself.
+ nsAutoCString forbiddenDirPath(forbiddenPath);
+ forbiddenDirPath.Append("/");
+ rv = Preferences::SetCString(kForbiddenPathsPref, forbiddenDirPath);
+ ASSERT_EQ(rv, NS_OK);
+ FilePreferences::InitPrefs();
+
+ // This should work, since we only block subresources
+ rv = forbiddenDir->Exists(&exists);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = forbiddenDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+}
diff --git a/xpcom/tests/gtest/TestFilePreferencesWin.cpp b/xpcom/tests/gtest/TestFilePreferencesWin.cpp
new file mode 100644
index 0000000000..dfee139970
--- /dev/null
+++ b/xpcom/tests/gtest/TestFilePreferencesWin.cpp
@@ -0,0 +1,196 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/FilePreferences.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsXPCOMCID.h"
+
+TEST(FilePreferencesWin, Normalization)
+{
+ nsAutoString normalized;
+
+ mozilla::FilePreferences::testing::NormalizePath(u"foo"_ns, normalized);
+ ASSERT_TRUE(normalized == u"foo"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\foo"_ns, normalized);
+ ASSERT_TRUE(normalized == u"\\foo"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo"_ns, normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"foo\\some"_ns, normalized);
+ ASSERT_TRUE(normalized == u"foo\\some"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\foo"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\."_ns, normalized);
+ ASSERT_TRUE(normalized == u"\\\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\"_ns, normalized);
+ ASSERT_TRUE(normalized == u"\\\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\."_ns, normalized);
+ ASSERT_TRUE(normalized == u"\\\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo\\bar"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\."_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\.\\"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\..\\"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\.."_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\foo\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\..\\bar\\..\\"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\..\\bar"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\bar"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\..\\..\\"_ns,
+ normalized);
+ ASSERT_TRUE(normalized == u"\\\\"_ns);
+
+ mozilla::FilePreferences::testing::NormalizePath(
+ u"\\\\foo\\bar\\.\\..\\.\\..\\"_ns, normalized);
+ ASSERT_TRUE(normalized == u"\\\\"_ns);
+
+ bool result;
+
+ result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.."_ns,
+ normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\..\\"_ns,
+ normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\..\\"_ns,
+ normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(
+ u"\\\\foo\\\\bar"_ns, normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(
+ u"\\\\foo\\bar\\..\\..\\..\\..\\"_ns, normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\\\"_ns,
+ normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\\\"_ns,
+ normalized);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\..\\\\"_ns,
+ normalized);
+ ASSERT_FALSE(result);
+}
+
+TEST(FilePreferencesWin, AccessUNC)
+{
+ nsCOMPtr<nsIFile> lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+
+ nsresult rv;
+
+ mozilla::FilePreferences::testing::SetBlockUNCPaths(false);
+
+ rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ mozilla::FilePreferences::testing::SetBlockUNCPaths(true);
+
+ rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ mozilla::FilePreferences::testing::AddDirectoryToAllowlist(u"\\\\nice"_ns);
+
+ rv = lf->InitWithPath(u"\\\\nice\\share"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+}
+
+TEST(FilePreferencesWin, AccessDOSDevicePath)
+{
+ const auto devicePathSpecifier = u"\\\\?\\"_ns;
+
+ nsCOMPtr<nsIFile> lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+
+ nsresult rv;
+
+ mozilla::FilePreferences::testing::SetBlockUNCPaths(true);
+
+ rv = lf->InitWithPath(devicePathSpecifier + u"evil\\z:\\share"_ns);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ rv = lf->InitWithPath(devicePathSpecifier + u"UNC\\evil\\share"_ns);
+ ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED);
+
+ rv = lf->InitWithPath(devicePathSpecifier + u"C:\\"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ nsCOMPtr<nsIFile> base;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ ASSERT_EQ(rv, NS_OK);
+
+ nsAutoString path;
+ rv = base->GetPath(path);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = lf->InitWithPath(devicePathSpecifier + path);
+ ASSERT_EQ(rv, NS_OK);
+}
+
+TEST(FilePreferencesWin, StartsWithDiskDesignatorAndBackslash)
+{
+ bool result;
+
+ result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash(
+ u"\\\\UNC\\path"_ns);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash(
+ u"\\single\\backslash"_ns);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash(
+ u"C:relative"_ns);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash(
+ u"\\\\?\\C:\\"_ns);
+ ASSERT_FALSE(result);
+
+ result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash(
+ u"C:\\"_ns);
+ ASSERT_TRUE(result);
+
+ result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash(
+ u"c:\\"_ns);
+ ASSERT_TRUE(result);
+}
diff --git a/xpcom/tests/gtest/TestGCPostBarriers.cpp b/xpcom/tests/gtest/TestGCPostBarriers.cpp
new file mode 100644
index 0000000000..c32cd48c86
--- /dev/null
+++ b/xpcom/tests/gtest/TestGCPostBarriers.cpp
@@ -0,0 +1,165 @@
+/* -*- 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/. */
+
+/*
+ * Tests that generational garbage collection post-barriers are correctly
+ * implemented for nsTArrays that contain JavaScript Values.
+ */
+
+#include "mozilla/UniquePtr.h"
+
+#include "jsapi.h"
+#include "nsTArray.h"
+
+#include "gtest/gtest.h"
+
+#include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty
+#include "js/TracingAPI.h"
+#include "js/HeapAPI.h"
+
+#include "mozilla/CycleCollectedJSContext.h"
+
+using namespace mozilla;
+
+template <class ArrayT>
+static void TraceArray(JSTracer* trc, void* data) {
+ ArrayT* array = static_cast<ArrayT*>(data);
+ for (unsigned i = 0; i < array->Length(); ++i) {
+ JS::TraceEdge(trc, &array->ElementAt(i), "array-element");
+ }
+}
+
+/*
+ * Use arrays with initial size much smaller than the final number of elements
+ * to test that moving Heap<T> elements works correctly.
+ */
+const size_t ElementCount = 100;
+const size_t InitialElements = ElementCount / 10;
+
+template <class ArrayT>
+static void TestGrow(JSContext* cx) {
+ JS_GC(cx);
+
+ auto array = MakeUnique<ArrayT>();
+ ASSERT_TRUE(array != nullptr);
+
+ JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+
+ /*
+ * Create the array and fill it with new JS objects. With GGC these will be
+ * allocated in the nursery.
+ */
+ JS::RootedValue value(cx);
+ const char* property = "foo";
+ for (size_t i = 0; i < ElementCount; ++i) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ ASSERT_FALSE(JS::ObjectIsTenured(obj));
+ value = JS::Int32Value(static_cast<int32_t>(i));
+ ASSERT_TRUE(JS_SetProperty(cx, obj, property, value));
+ ASSERT_TRUE(array->AppendElement(obj, fallible));
+ }
+
+ /*
+ * If postbarriers are not working, we will crash here when we try to mark
+ * objects that have been moved to the tenured heap.
+ */
+ JS_GC(cx);
+
+ /*
+ * Sanity check that our array contains what we expect.
+ */
+ ASSERT_EQ(array->Length(), ElementCount);
+ for (size_t i = 0; i < array->Length(); i++) {
+ JS::RootedObject obj(cx, array->ElementAt(i));
+ ASSERT_TRUE(JS::ObjectIsTenured(obj));
+ ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value));
+ ASSERT_TRUE(value.isInt32());
+ ASSERT_EQ(static_cast<int32_t>(i), value.toInt32());
+ }
+
+ JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+}
+
+template <class ArrayT>
+static void TestShrink(JSContext* cx) {
+ JS_GC(cx);
+
+ auto array = MakeUnique<ArrayT>();
+ ASSERT_TRUE(array != nullptr);
+
+ JS_AddExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+
+ /*
+ * Create the array and fill it with new JS objects. With GGC these will be
+ * allocated in the nursery.
+ */
+ JS::RootedValue value(cx);
+ const char* property = "foo";
+ for (size_t i = 0; i < ElementCount; ++i) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ ASSERT_FALSE(JS::ObjectIsTenured(obj));
+ value = JS::Int32Value(static_cast<int32_t>(i));
+ ASSERT_TRUE(JS_SetProperty(cx, obj, property, value));
+ ASSERT_TRUE(array->AppendElement(obj, fallible));
+ }
+
+ /* Shrink and compact the array */
+ array->RemoveElementsAt(InitialElements, ElementCount - InitialElements);
+ array->Compact();
+
+ JS_GC(cx);
+
+ ASSERT_EQ(array->Length(), InitialElements);
+ for (size_t i = 0; i < array->Length(); i++) {
+ JS::RootedObject obj(cx, array->ElementAt(i));
+ ASSERT_TRUE(JS::ObjectIsTenured(obj));
+ ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value));
+ ASSERT_TRUE(value.isInt32());
+ ASSERT_EQ(static_cast<int32_t>(i), value.toInt32());
+ }
+
+ JS_RemoveExtraGCRootsTracer(cx, TraceArray<ArrayT>, array.get());
+}
+
+template <class ArrayT>
+static void TestArrayType(JSContext* cx) {
+ TestGrow<ArrayT>(cx);
+ TestShrink<ArrayT>(cx);
+}
+
+static void CreateGlobalAndRunTest(JSContext* cx) {
+ static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+
+ JS::RealmOptions options;
+ // dummy
+ options.behaviors().setReduceTimerPrecisionCallerType(
+ JS::RTPCallerTypeToken{0});
+ JS::PersistentRootedObject global(cx);
+ global = JS_NewGlobalObject(cx, &GlobalClass, nullptr,
+ JS::FireOnNewGlobalHook, options);
+ ASSERT_TRUE(global != nullptr);
+
+ JS::Realm* oldRealm = JS::EnterRealm(cx, global);
+
+ using ElementT = JS::Heap<JSObject*>;
+
+ TestArrayType<nsTArray<ElementT>>(cx);
+ TestArrayType<FallibleTArray<ElementT>>(cx);
+ TestArrayType<AutoTArray<ElementT, 1>>(cx);
+
+ JS::LeaveRealm(cx, oldRealm);
+}
+
+TEST(GCPostBarriers, nsTArray)
+{
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+ ASSERT_TRUE(ccjscx != nullptr);
+ JSContext* cx = ccjscx->Context();
+ ASSERT_TRUE(cx != nullptr);
+
+ CreateGlobalAndRunTest(cx);
+}
diff --git a/xpcom/tests/gtest/TestHandleWatcher.cpp b/xpcom/tests/gtest/TestHandleWatcher.cpp
new file mode 100644
index 0000000000..c003a026a1
--- /dev/null
+++ b/xpcom/tests/gtest/TestHandleWatcher.cpp
@@ -0,0 +1,580 @@
+/* -*- 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 <minwindef.h>
+#include <handleapi.h>
+#include <synchapi.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Result.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/WinHandleWatcher.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsIEventTarget.h"
+#include "nsITargetShutdownTask.h"
+#include "nsIThread.h"
+#include "nsIThreadShutdown.h"
+#include "nsITimer.h"
+#include "nsTHashMap.h"
+#include "nsThreadUtils.h"
+// #include "nscore.h"
+
+namespace details {
+static nsCString MakeTargetName(const char* name) {
+ const char* testName =
+ ::testing::UnitTest::GetInstance()->current_test_info()->name();
+ nsCString ret;
+ ret.AppendPrintf("%s: %s", testName, name);
+ return ret;
+}
+} // namespace details
+
+using HandleWatcher = mozilla::HandleWatcher;
+
+///////////////////////////////////////////////////////////////////////
+// Error handling
+
+// nsresult_fatal_err_: auxiliary function for testing-macros.
+[[noreturn]] void nsresult_fatal_err_(const char* file, size_t line,
+ const char* expr, nsresult res) {
+ // implementation details from the MOZ_CRASH* family of macros
+ MOZ_Crash(file, static_cast<int>(line),
+ MOZ_CrashPrintf("%s gave nsresult %s(%" PRIX32 ")", expr,
+ mozilla::GetStaticErrorName(res), uint32_t(res)));
+}
+
+// UNWRAP: testing-oriented variant of Result::unwrap.
+//
+// We make no use of gtest's `ASSERT_*` family of macros, since they assume
+// that a `return;` statement is sufficient to abort the test.
+template <typename T>
+T unwrap_impl_(const char* file, size_t line, const char* expr,
+ mozilla::Result<T, nsresult> res) {
+ if (MOZ_LIKELY(res.isOk())) {
+ return res.unwrap();
+ }
+ nsresult_fatal_err_(file, line, expr, res.unwrapErr());
+}
+
+#define UNWRAP(expr) unwrap_impl_(__FILE__, __LINE__, #expr, expr)
+
+///////////////////////////////////////////////////////////////////////
+// Milliseconds()
+//
+// Convenience declaration for millisecond-based mozilla::TimeDurations.
+static mozilla::TimeDuration Milliseconds(double d) {
+ return mozilla::TimeDuration::FromMilliseconds(d);
+}
+
+///////////////////////////////////////////////////////////////////////
+// TestHandleWatcher
+//
+// GTest test fixture. Provides shared resources.
+class TestHandleWatcher : public testing::Test {
+ protected:
+ static void SetUpTestSuite() { sIsLive = true; }
+ static void TearDownTestSuite() {
+ sPool = nullptr;
+ sIsLive = false;
+ }
+
+ public:
+ static already_AddRefed<mozilla::SharedThreadPool> GetPool() {
+ AssertIsLive();
+ if (!sPool) {
+ sPool = mozilla::SharedThreadPool::Get("Test Pool"_ns);
+ }
+ return do_AddRef(sPool);
+ }
+
+ private:
+ static bool sIsLive; // just for confirmation
+ static void AssertIsLive() {
+ MOZ_ASSERT(sIsLive,
+ "attempted to use `class TestHandleWatcher` outside test group");
+ }
+
+ static RefPtr<mozilla::SharedThreadPool> sPool;
+};
+
+/* static */
+bool TestHandleWatcher::sIsLive = false;
+/* static */
+RefPtr<mozilla::SharedThreadPool> TestHandleWatcher::sPool = nullptr;
+
+///////////////////////////////////////////////////////////////////////
+// WindowsEventObject
+//
+// Convenient interface to a Windows `event` object. (This is a synchronization
+// object that's usually the wrong thing to use.)
+struct WindowsEventObject {
+ HANDLE const handle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
+ WindowsEventObject() = default;
+ ~WindowsEventObject() { ::CloseHandle(handle); }
+
+ WindowsEventObject(WindowsEventObject const&) = delete;
+ WindowsEventObject(WindowsEventObject&&) = delete;
+ WindowsEventObject& operator=(WindowsEventObject const&) = delete;
+ WindowsEventObject& operator=(WindowsEventObject&&) = delete;
+
+ void Set() { ::SetEvent(handle); }
+};
+
+///////////////////////////////////////////////////////////////////////
+// SpawnNewThread
+//
+nsCOMPtr<nsIThread> SpawnNewThread(const char* name) {
+ nsCOMPtr<nsIThread> thread;
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_NewNamedThread(details::MakeTargetName(name), getter_AddRefs(thread)));
+ return thread;
+}
+
+///////////////////////////////////////////////////////////////////////
+// SpawnNewBackgroundQueue
+//
+// (mozilla::TaskQueue expects the supplied name to outlive the queue, so we
+// just use a static string.)
+RefPtr<mozilla::TaskQueue> SpawnNewBackgroundQueue() {
+ return mozilla::TaskQueue::Create(TestHandleWatcher::GetPool(),
+ "task queue for TestHandleWatcher");
+}
+
+///////////////////////////////////////////////////////////////////////
+// SpinEventLoopUntil
+//
+// Local equivalent of `mozilla::SpinEventLoopUntil`, extended with a timeout.
+//
+// Spin the current thread's event loop until either a specified predicate is
+// satisfied or a specified time-interval has passed.
+//
+struct SpinEventLoopUntilRet {
+ enum Value { Ok, TimedOut, InternalError } value;
+ bool ok() const { return value == Value::Ok; }
+ bool timedOut() const { return value == Value::TimedOut; }
+
+ MOZ_IMPLICIT SpinEventLoopUntilRet(Value v) : value(v) {}
+};
+template <typename Predicate>
+SpinEventLoopUntilRet SpinEventLoopUntil(
+ Predicate const& aPredicate,
+ mozilla::TimeDuration aDuration = Milliseconds(500)) {
+ using Value = SpinEventLoopUntilRet::Value;
+ nsIThread* currentThread = NS_GetCurrentThread();
+
+ // Set up timer.
+ bool timedOut = false;
+ auto timer = UNWRAP(NS_NewTimerWithCallback(
+ [&](nsITimer*) { timedOut = true; }, aDuration, nsITimer::TYPE_ONE_SHOT,
+ "SpinEventLoop timer", currentThread));
+ auto onExitCancelTimer = mozilla::MakeScopeExit([&] { timer->Cancel(); });
+
+ bool const ret = mozilla::SpinEventLoopUntil(
+ "TestHandleWatcher"_ns, [&] { return timedOut || aPredicate(); });
+ if (!ret) return Value::InternalError;
+ if (timedOut) return Value::TimedOut;
+ return Value::Ok;
+}
+
+// metatest for `SpinEventLoopUntil`
+TEST_F(TestHandleWatcher, SpinEventLoopUntil) {
+ auto should_fail = SpinEventLoopUntil([] { return false; }, Milliseconds(1));
+ ASSERT_TRUE(should_fail.timedOut());
+ auto should_pass = SpinEventLoopUntil([] { return true; }, Milliseconds(50));
+ ASSERT_TRUE(should_pass.ok());
+}
+
+///////////////////////////////////////////////////////////////////////
+// PingMainThread
+//
+// Post a do-nothing message to the main thread's event queue. (This will signal
+// it to wake up and check its predicate, if it's waiting for that to happen.)
+void PingMainThread() {
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NS_NewRunnableFunction("Ping", [] {})));
+}
+
+///////////////////////////////////////////////////////////////////////
+// Individual tests
+
+// Test basic creation and destruction.
+TEST_F(TestHandleWatcher, Trivial) { HandleWatcher hw; }
+
+// Test interaction before a Watch is created.
+TEST_F(TestHandleWatcher, Empty) {
+ HandleWatcher hw;
+ ASSERT_TRUE(hw.IsStopped());
+ hw.Stop();
+}
+
+// Start and trigger an HandleWatcher directly from the main thread.
+TEST_F(TestHandleWatcher, Simple) {
+ WindowsEventObject event;
+ HandleWatcher hw;
+
+ std::atomic<bool> run = false;
+
+ hw.Watch(
+ event.handle, NS_GetCurrentThread(),
+ NS_NewRunnableFunction("TestHandleWatcher::Simple", [&] { run = true; }));
+
+ ASSERT_FALSE(run.load());
+ event.Set();
+ // Attempt to force a race below.
+ ::Sleep(0);
+ // This should not race. The HandleWatcher doesn't execute its delegate; it
+ // just queues a mozilla::Task to do that onto our thread's event queue,
+ // and that Task hasn't been permitted to run yet.
+ ASSERT_FALSE(run.load());
+
+ ASSERT_TRUE(SpinEventLoopUntil([&] { return run.load(); }).ok());
+}
+
+// Test that calling Stop() stops the watcher.
+TEST_F(TestHandleWatcher, Stop) {
+ WindowsEventObject event;
+ HandleWatcher hw;
+ std::atomic<bool> run = false;
+
+ hw.Watch(
+ event.handle, NS_GetCurrentThread(),
+ NS_NewRunnableFunction("TestHandleWatcher::Stop", [&] { run = true; }));
+
+ ASSERT_FALSE(hw.IsStopped());
+ hw.Stop();
+ ASSERT_TRUE(hw.IsStopped());
+
+ ASSERT_TRUE(SpinEventLoopUntil([&] { return run.load(); }, Milliseconds(25))
+ .timedOut());
+}
+
+// Test that the target's destruction stops the watch.
+TEST_F(TestHandleWatcher, TargetDestroyed) {
+ WindowsEventObject event;
+ HandleWatcher hw;
+ bool run = false;
+
+ auto queue = SpawnNewThread("target thread");
+ hw.Watch(event.handle, queue.get(),
+ NS_NewRunnableFunction("never called", [&] { run = true; }));
+
+ ASSERT_FALSE(hw.IsStopped());
+ // synchronous shutdown before destruction
+ queue->Shutdown();
+
+ ASSERT_TRUE(hw.IsStopped());
+ ASSERT_FALSE(run);
+}
+
+// Test that calling `Watch` again stops the current watch.
+TEST_F(TestHandleWatcher, Rewatch) {
+ WindowsEventObject event;
+ HandleWatcher hw;
+
+ bool b1 = false;
+ bool b2 = false;
+
+ {
+ auto queue = SpawnNewThread("target thread");
+
+ hw.Watch(event.handle, queue.get(), NS_NewRunnableFunction("b1", [&] {
+ b1 = true;
+ PingMainThread();
+ }));
+ hw.Watch(event.handle, queue.get(), NS_NewRunnableFunction("b2", [&] {
+ b2 = true;
+ PingMainThread();
+ }));
+
+ event.Set();
+
+ ASSERT_TRUE(SpinEventLoopUntil([&] { return b1 || b2; }).ok());
+ queue->Shutdown();
+ }
+ ASSERT_FALSE(b1);
+ ASSERT_TRUE(b2);
+}
+
+// Test that watching a HANDLE which is _already_ signaled still fires the
+// associated task.
+TEST_F(TestHandleWatcher, Presignalled) {
+ WindowsEventObject event;
+ HandleWatcher hw;
+
+ bool run = false;
+ event.Set();
+ hw.Watch(event.handle, NS_GetCurrentThread(),
+ NS_NewRunnableFunction("TestHandleWatcher::Presignalled",
+ [&] { run = true; }));
+
+ ASSERT_TRUE(SpinEventLoopUntil([&] { return run; }).ok());
+}
+
+///////////////////////////////////////////////////////////////////////
+// Systematic tests: normal activation
+//
+// Test that a handle becoming signalled on target A correctly enqueues a task
+// on target B, regardless of whether A == B.
+//
+struct ActivationTestSetup {
+ enum TargetType { Main, Side, Background };
+
+ WindowsEventObject event;
+ HandleWatcher watcher;
+
+ std::atomic<bool> run = false;
+ nsCOMPtr<nsIThread> sideThread;
+ nsCOMPtr<nsISerialEventTarget> backgroundQueue;
+
+ private:
+ nsIEventTarget* GetQueue(TargetType targetTyoe) {
+ MOZ_ASSERT(NS_IsMainThread());
+ switch (targetTyoe) {
+ case TargetType::Main:
+ return NS_GetCurrentThread();
+
+ case TargetType::Side: {
+ if (!sideThread) {
+ sideThread = SpawnNewThread("side thread");
+ }
+ return sideThread;
+ }
+
+ case TargetType::Background: {
+ if (!backgroundQueue) {
+ backgroundQueue = SpawnNewBackgroundQueue();
+ }
+ return backgroundQueue.get();
+ }
+ }
+ }
+
+ void OnSignaled() {
+ run = true;
+ // If we're not running on the main thread, it may be blocked waiting for
+ // events.
+ PingMainThread();
+ }
+
+ public:
+ void Setup(TargetType from, TargetType to) {
+ watcher.Watch(
+ event.handle, GetQueue(to),
+ NS_NewRunnableFunction("Reaction", [this] { this->OnSignaled(); }));
+
+ MOZ_ALWAYS_SUCCEEDS(GetQueue(from)->Dispatch(
+ NS_NewRunnableFunction("Action", [this] { event.Set(); })));
+ }
+
+ bool Execute() {
+ MOZ_ASSERT(NS_IsMainThread());
+ bool const spin = SpinEventLoopUntil([this] {
+ MOZ_ASSERT(NS_IsMainThread());
+ return run.load();
+ }).ok();
+ return spin && watcher.IsStopped();
+ }
+
+ ~ActivationTestSetup() { watcher.Stop(); }
+};
+
+#define MOZ_HANDLEWATCHER_GTEST_FROM_TO(FROM, TO) \
+ TEST_F(TestHandleWatcher, FROM##To##TO) { \
+ ActivationTestSetup s; \
+ s.Setup(ActivationTestSetup::TargetType::FROM, \
+ ActivationTestSetup::TargetType::TO); \
+ ASSERT_TRUE(s.Execute()); \
+ }
+
+// Note that `Main -> Main` is subtly different from `Simple`, above: `Simple`
+// sets the event before spinning, while `Main -> Main` merely enqueues a Task
+// that will set the event during the spin.
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Main);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Side);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Background);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Main);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Side);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Background);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Main);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Side);
+MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Background);
+
+///////////////////////////////////////////////////////////////////////
+// Ad-hoc tests: reentrancy
+//
+// Test that HandleWatcher neither deadlocks nor loses data if its release of a
+// referenced object causes the invocation of another method on HandleWatcher.
+
+// Reentrancy case 1/2: the event target.
+namespace {
+class MockEventTarget final : public nsIEventTarget {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ // Map from registered shutdown tasks to whether or not they have been (or are
+ // being) executed. (This should probably guarantee some deterministic order,
+ // and also be mutex-protected; but that doesn't matter here.)
+ nsTHashMap<RefPtr<nsITargetShutdownTask>, bool> mShutdownTasks;
+ // Out-of band task to be run last at destruction time, regardless of anything
+ // else.
+ std::function<void(void)> mDeathAction;
+
+ ~MockEventTarget() {
+ for (auto& task : mShutdownTasks) {
+ task.SetData(true);
+ task.GetKey()->TargetShutdown();
+ }
+ if (mDeathAction) {
+ mDeathAction();
+ }
+ }
+
+ public:
+ // shutdown task handling
+ NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* task) override {
+ mShutdownTasks.WithEntryHandle(task, [&](auto entry) {
+ if (entry.HasEntry()) {
+ MOZ_CRASH("attempted to double-register shutdown task");
+ }
+ entry.Insert(false);
+ });
+ return NS_OK;
+ }
+ NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* task) override {
+ mozilla::Maybe<bool> res = mShutdownTasks.Extract(task);
+ if (!res.isSome()) {
+ MOZ_CRASH("attempted to unregister non-registered task");
+ }
+ if (res.value()) {
+ MOZ_CRASH("attempted to unregister already-executed shutdown task");
+ }
+ return NS_OK;
+ }
+ void RegisterDeathAction(std::function<void(void)>&& f) {
+ mDeathAction = std::move(f);
+ }
+
+ // other nsIEventTarget methods (that we don't actually use)
+ NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) { return false; }
+ NS_IMETHOD IsOnCurrentThread(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+ }
+ NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD DispatchFromScript(nsIRunnable*, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+NS_IMPL_ISUPPORTS(MockEventTarget, nsIEventTarget)
+} // anonymous namespace
+
+// Test that a HandleWatcher neither deadlocks nor loses data if it's invoked
+// when it releases its target.
+TEST_F(TestHandleWatcher, TargetDestructionRecurrency) {
+ WindowsEventObject e1, e2;
+ bool b1 = false, b2 = false;
+ HandleWatcher hw;
+
+ {
+ RefPtr<MockEventTarget> p = mozilla::MakeRefPtr<MockEventTarget>();
+
+ hw.Watch(e1.handle, p.get(), NS_NewRunnableFunction("first callback", [&] {
+ b1 = true;
+ PingMainThread();
+ }));
+
+ p->RegisterDeathAction([&] {
+ hw.Watch(e2.handle, mozilla::GetMainThreadSerialEventTarget(),
+ NS_NewRunnableFunction("second callback", [&] { b2 = true; }));
+ });
+ }
+
+ ASSERT_FALSE(hw.IsStopped());
+ hw.Stop();
+ ASSERT_FALSE(hw.IsStopped()); // [sic]
+
+ e1.Set(); // should do nothing
+ e2.Set();
+ ASSERT_TRUE(SpinEventLoopUntil([&] { return b1 || b2; }).ok());
+ ASSERT_FALSE(b1);
+ ASSERT_TRUE(b2);
+}
+
+// Reentrancy case 2/2: the runnable.
+namespace {
+class MockRunnable final : public nsIRunnable {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_IMETHOD Run() override {
+ MOZ_CRASH("MockRunnable was invoked");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ std::function<void(void)> mDeathAction;
+
+ public:
+ void RegisterDeathAction(std::function<void(void)>&& f) {
+ mDeathAction = std::move(f);
+ }
+
+ private:
+ ~MockRunnable() {
+ if (mDeathAction) {
+ mDeathAction();
+ }
+ }
+};
+NS_IMPL_ISUPPORTS(MockRunnable, nsIRunnable)
+} // anonymous namespace
+
+// Test that a HandleWatcher neither deadlocks nor loses data if it's invoked
+// when it releases its task.
+TEST_F(TestHandleWatcher, TaskDestructionRecurrency) {
+ WindowsEventObject e1, e2;
+ bool run = false;
+ HandleWatcher hw;
+
+ auto thread = SpawnNewBackgroundQueue();
+
+ {
+ RefPtr<MockRunnable> runnable = mozilla::MakeRefPtr<MockRunnable>();
+
+ runnable->RegisterDeathAction([&] {
+ hw.Watch(e2.handle, thread, NS_NewRunnableFunction("callback", [&] {
+ run = true;
+ PingMainThread();
+ }));
+ });
+
+ hw.Watch(e1.handle, thread.get(), runnable.forget());
+ }
+
+ ASSERT_FALSE(hw.IsStopped());
+ hw.Stop();
+ ASSERT_FALSE(hw.IsStopped()); // [sic]
+
+ e1.Set();
+ // give MockRunnable a chance to run (and therefore crash) if it somehow
+ // hasn't been discmnnected
+ ASSERT_TRUE(
+ SpinEventLoopUntil([&] { return false; }, Milliseconds(10)).timedOut());
+
+ e2.Set();
+ ASSERT_TRUE(SpinEventLoopUntil([&] { return run; }).ok());
+ ASSERT_TRUE(run);
+}
diff --git a/xpcom/tests/gtest/TestHashtables.cpp b/xpcom/tests/gtest/TestHashtables.cpp
new file mode 100644
index 0000000000..fe1e6a3611
--- /dev/null
+++ b/xpcom/tests/gtest/TestHashtables.cpp
@@ -0,0 +1,1617 @@
+/* -*- 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 "nsTHashMap.h"
+#include "nsInterfaceHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsRefCountedHashtable.h"
+
+#include "nsCOMPtr.h"
+#include "nsIMemoryReporter.h"
+#include "nsISupports.h"
+#include "nsCOMArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Unused.h"
+
+#include "gtest/gtest.h"
+
+#include <numeric>
+
+using mozilla::MakeRefPtr;
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+
+namespace TestHashtables {
+
+class TestUniChar // for nsClassHashtable
+{
+ public:
+ explicit TestUniChar(uint32_t aWord) { mWord = aWord; }
+
+ ~TestUniChar() = default;
+
+ uint32_t GetChar() const { return mWord; }
+
+ private:
+ uint32_t mWord;
+};
+
+class TestUniCharDerived : public TestUniChar {
+ using TestUniChar::TestUniChar;
+};
+
+class TestUniCharRefCounted // for nsRefPtrHashtable
+{
+ public:
+ explicit TestUniCharRefCounted(uint32_t aWord,
+ uint32_t aExpectedAddRefCnt = 0)
+ : mExpectedAddRefCnt(aExpectedAddRefCnt),
+ mAddRefCnt(0),
+ mRefCnt(0),
+ mWord(aWord) {}
+
+ uint32_t AddRef() {
+ mRefCnt++;
+ mAddRefCnt++;
+ return mRefCnt;
+ }
+
+ uint32_t Release() {
+ EXPECT_TRUE(mRefCnt > 0);
+ mRefCnt--;
+ if (mRefCnt == 0) {
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+ uint32_t GetChar() const { return mWord; }
+
+ private:
+ ~TestUniCharRefCounted() {
+ if (mExpectedAddRefCnt > 0) {
+ EXPECT_EQ(mAddRefCnt, mExpectedAddRefCnt);
+ }
+ }
+
+ uint32_t mExpectedAddRefCnt;
+ uint32_t mAddRefCnt;
+ uint32_t mRefCnt;
+ uint32_t mWord;
+};
+
+struct EntityNode {
+ const char* mStr; // never owns buffer
+ uint32_t mUnicode;
+
+ bool operator<(const EntityNode& aOther) const {
+ return mUnicode < aOther.mUnicode ||
+ (mUnicode == aOther.mUnicode && strcmp(mStr, aOther.mStr) < 0);
+ }
+};
+
+static const 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() = default;
+
+ 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;
+}
+
+static 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);
+
+ for (const auto& entry :
+ const_cast<const nsTHashtable<EntityToUnicodeEntry>&>(hash)) {
+ static_assert(std::is_same_v<decltype(entry), const EntityToUnicodeEntry&>);
+ }
+ for (auto& entry : hash) {
+ static_assert(std::is_same_v<decltype(entry), EntityToUnicodeEntry&>);
+ }
+
+ EXPECT_EQ(numEntries == ENTITY_COUNT ? 6 : 0,
+ std::count_if(hash.cbegin(), hash.cend(), [](const auto& entry) {
+ return entry.mNode->mUnicode >= 170;
+ }));
+}
+
+//
+// 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() override;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+ NS_IMETHOD QueryInterface(const nsIID&, void**) override;
+
+ 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;
+}
+
+static nsresult CreateIFoo(IFoo** result)
+// a typical factory function (that calls AddRef)
+{
+ auto* foop = new IFoo();
+
+ foop->AddRef();
+ *result = foop;
+
+ return NS_OK;
+}
+
+class DefaultConstructible {
+ public:
+ // Allow default construction.
+ DefaultConstructible() = default;
+
+ // Construct/assign from a ref counted char.
+ explicit DefaultConstructible(RefPtr<TestUniCharRefCounted> aChar)
+ : mChar(std::move(aChar)) {}
+
+ const RefPtr<TestUniCharRefCounted>& CharRef() const { return mChar; }
+
+ // DefaultConstructible can be copied and moved.
+ DefaultConstructible(const DefaultConstructible&) = default;
+ DefaultConstructible& operator=(const DefaultConstructible&) = default;
+ DefaultConstructible(DefaultConstructible&&) = default;
+ DefaultConstructible& operator=(DefaultConstructible&&) = default;
+
+ private:
+ RefPtr<TestUniCharRefCounted> mChar;
+};
+
+class MovingNonDefaultConstructible;
+
+class NonDefaultConstructible {
+ public:
+ // Construct/assign from a ref counted char.
+ explicit NonDefaultConstructible(RefPtr<TestUniCharRefCounted> aChar)
+ : mChar(std::move(aChar)) {}
+
+ // Disallow default construction.
+ NonDefaultConstructible() = delete;
+
+ MOZ_IMPLICIT NonDefaultConstructible(MovingNonDefaultConstructible&& aOther);
+
+ const RefPtr<TestUniCharRefCounted>& CharRef() const { return mChar; }
+
+ // NonDefaultConstructible can be copied, but not trivially (efficiently)
+ // moved.
+ NonDefaultConstructible(const NonDefaultConstructible&) = default;
+ NonDefaultConstructible& operator=(const NonDefaultConstructible&) = default;
+
+ private:
+ RefPtr<TestUniCharRefCounted> mChar;
+};
+
+class MovingNonDefaultConstructible {
+ public:
+ // Construct/assign from a ref counted char.
+ explicit MovingNonDefaultConstructible(RefPtr<TestUniCharRefCounted> aChar)
+ : mChar(std::move(aChar)) {}
+
+ MovingNonDefaultConstructible() = delete;
+
+ MOZ_IMPLICIT MovingNonDefaultConstructible(
+ const NonDefaultConstructible& aSrc)
+ : mChar(aSrc.CharRef()) {}
+
+ RefPtr<TestUniCharRefCounted> unwrapChar() && { return std::move(mChar); }
+
+ // MovingNonDefaultConstructible can be moved, but not copied.
+ MovingNonDefaultConstructible(const MovingNonDefaultConstructible&) = delete;
+ MovingNonDefaultConstructible& operator=(
+ const MovingNonDefaultConstructible&) = delete;
+ MovingNonDefaultConstructible(MovingNonDefaultConstructible&&) = default;
+ MovingNonDefaultConstructible& operator=(MovingNonDefaultConstructible&&) =
+ default;
+
+ private:
+ RefPtr<TestUniCharRefCounted> mChar;
+};
+
+NonDefaultConstructible::NonDefaultConstructible(
+ MovingNonDefaultConstructible&& aOther)
+ : mChar(std::move(aOther).unwrapChar()) {}
+
+struct DefaultConstructible_DefaultConstructible {
+ using DataType = DefaultConstructible;
+ using UserDataType = DefaultConstructible;
+
+ static constexpr uint32_t kExpectedAddRefCnt_Contains = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Count = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 3;
+ static constexpr uint32_t kExpectedAddRefCnt_Get = 3;
+ static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 3;
+ static constexpr uint32_t kExpectedAddRefCnt_LookupOrInsert = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible =
+ 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Remove_OutputParam = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Remove = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Extract = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Lookup = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd_OrInsert = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd_OrRemove = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Iter = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_begin_end = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Clear = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 1;
+};
+
+struct NonDefaultConstructible_NonDefaultConstructible {
+ using DataType = NonDefaultConstructible;
+ using UserDataType = NonDefaultConstructible;
+
+ static constexpr uint32_t kExpectedAddRefCnt_Contains = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 3;
+ static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 3;
+ static constexpr uint32_t kExpectedAddRefCnt_Count = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 5;
+ static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 5;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible =
+ 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Remove = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Extract = 3;
+ static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Lookup = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Iter = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_begin_end = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_Clear = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 2;
+};
+
+struct NonDefaultConstructible_MovingNonDefaultConstructible {
+ using DataType = NonDefaultConstructible;
+ using UserDataType = MovingNonDefaultConstructible;
+
+ static constexpr uint32_t kExpectedAddRefCnt_Contains = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Count = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible =
+ 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Remove = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Extract = 2;
+ static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Lookup = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Iter = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_begin_end = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_Clear = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 1;
+ static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 1;
+};
+
+template <bool flag = false>
+void UnsupportedType() {
+ static_assert(flag, "Unsupported type!");
+}
+
+class TypeNames {
+ public:
+ template <typename T>
+ static std::string GetName(int) {
+ if constexpr (std::is_same<T,
+ DefaultConstructible_DefaultConstructible>()) {
+ return "DefaultConstructible_DefaultConstructible";
+ } else if constexpr (
+ std::is_same<T, NonDefaultConstructible_NonDefaultConstructible>()) {
+ return "NonDefaultConstructible_NonDefaultConstructible";
+ } else if constexpr (
+ std::is_same<T,
+ NonDefaultConstructible_MovingNonDefaultConstructible>()) {
+ return "NonDefaultConstructible_MovingNonDefaultConstructible";
+ } else {
+ UnsupportedType();
+ }
+ }
+};
+
+template <typename TypeParam>
+auto MakeEmptyBaseHashtable() {
+ nsBaseHashtable<nsUint64HashKey, typename TypeParam::DataType,
+ typename TypeParam::UserDataType>
+ table;
+
+ return table;
+}
+
+template <typename TypeParam>
+auto MakeBaseHashtable(const uint32_t aExpectedAddRefCnt) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ auto myChar = MakeRefPtr<TestUniCharRefCounted>(42, aExpectedAddRefCnt);
+
+ table.InsertOrUpdate(1, typename TypeParam::UserDataType(std::move(myChar)));
+
+ return table;
+}
+
+template <typename TypeParam>
+typename TypeParam::DataType GetDataFrom(
+ typename TypeParam::UserDataType& aUserData) {
+ if constexpr (std::is_same_v<TypeParam,
+ DefaultConstructible_DefaultConstructible> ||
+ std::is_same_v<
+ TypeParam,
+ NonDefaultConstructible_NonDefaultConstructible>) {
+ return aUserData;
+ } else if constexpr (
+ std::is_same_v<TypeParam,
+ NonDefaultConstructible_MovingNonDefaultConstructible>) {
+ return std::move(aUserData);
+ } else {
+ UnsupportedType();
+ }
+}
+
+template <typename TypeParam>
+typename TypeParam::DataType GetDataFrom(
+ mozilla::Maybe<typename TypeParam::UserDataType>& aMaybeUserData) {
+ return GetDataFrom<TypeParam>(*aMaybeUserData);
+}
+
+} // 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(Hashtable, PtrHashtable)
+{
+ nsTHashtable<nsPtrHashKey<int>> hash;
+
+ for (const auto& entry :
+ const_cast<const nsTHashtable<nsPtrHashKey<int>>&>(hash)) {
+ static_assert(std::is_same_v<decltype(entry), const nsPtrHashKey<int>&>);
+ }
+ for (auto& entry : hash) {
+ static_assert(std::is_same_v<decltype(entry), nsPtrHashKey<int>&>);
+ }
+}
+
+TEST(Hashtable, Move)
+{
+ const void* kPtr = reinterpret_cast<void*>(static_cast<uintptr_t>(0xbadc0de));
+
+ nsTHashtable<nsPtrHashKey<const void>> table;
+ table.PutEntry(kPtr);
+
+ nsTHashtable<nsPtrHashKey<const void>> moved = std::move(table);
+ ASSERT_EQ(table.Count(), 0u);
+ ASSERT_EQ(moved.Count(), 1u);
+
+ EXPECT_TRUE(moved.Contains(kPtr));
+ EXPECT_FALSE(table.Contains(kPtr));
+}
+
+TEST(Hashtable, Keys)
+{
+ static constexpr uint64_t count = 10;
+
+ nsTHashtable<nsUint64HashKey> table;
+ for (uint64_t i = 0; i < count; i++) {
+ table.PutEntry(i);
+ }
+
+ nsTArray<uint64_t> keys;
+ for (const uint64_t& key : table.Keys()) {
+ keys.AppendElement(key);
+ }
+ keys.Sort();
+
+ EXPECT_EQ((nsTArray<uint64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys);
+}
+
+template <typename TypeParam>
+class BaseHashtableTest : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(BaseHashtableTest);
+
+TYPED_TEST_P(BaseHashtableTest, Contains) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Contains);
+
+ auto res = table.Contains(1);
+ EXPECT_TRUE(res);
+}
+
+TYPED_TEST_P(BaseHashtableTest, GetGeneration) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_GetGeneration);
+
+ auto res = table.GetGeneration();
+ EXPECT_GT(res, 0u);
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
+
+TYPED_TEST_P(BaseHashtableTest, SizeOfExcludingThis) {
+ // This doesn't compile at the moment, since nsBaseHashtableET lacks
+ // SizeOfExcludingThis implementation. Bug 1689214.
+#if 0
+ auto table = MakeBaseHashtable<TypeParam>(
+ TypeParam::kExpectedAddRefCnt_SizeOfExcludingThis);
+
+ auto res = table.SizeOfExcludingThis(MallocSizeOf);
+ EXPECT_GT(res, 0u);
+#endif
+}
+
+TYPED_TEST_P(BaseHashtableTest, SizeOfIncludingThis) {
+ // This doesn't compile at the moment, since nsBaseHashtableET lacks
+ // SizeOfIncludingThis implementation. Bug 1689214.
+#if 0
+ auto table = MakeBaseHashtable<TypeParam>(
+ TypeParam::kExpectedAddRefCnt_SizeOfIncludingThis);
+
+ auto res = table.SizeOfIncludingThis(MallocSizeOf);
+ EXPECT_GT(res, 0u);
+#endif
+}
+
+TYPED_TEST_P(BaseHashtableTest, Count) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Count);
+
+ auto res = table.Count();
+ EXPECT_EQ(res, 1u);
+}
+
+TYPED_TEST_P(BaseHashtableTest, IsEmpty) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_IsEmpty);
+
+ auto res = table.IsEmpty();
+ EXPECT_EQ(res, false);
+}
+
+TYPED_TEST_P(BaseHashtableTest, Get_OutputParam) {
+ auto table = MakeBaseHashtable<TypeParam>(
+ TypeParam::kExpectedAddRefCnt_Get_OutputParam);
+
+ typename TypeParam::UserDataType userData(nullptr);
+ auto res = table.Get(1, &userData);
+ EXPECT_TRUE(res);
+
+ auto data = GetDataFrom<TypeParam>(userData);
+ EXPECT_EQ(data.CharRef()->GetChar(), 42u);
+}
+
+TYPED_TEST_P(BaseHashtableTest, Get) {
+ // The Get overload can't support non-default-constructible UserDataType.
+ if constexpr (std::is_default_constructible_v<
+ typename TypeParam::UserDataType>) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Get);
+
+ auto userData = table.Get(1);
+
+ auto data = GetDataFrom<TypeParam>(userData);
+ EXPECT_EQ(data.CharRef()->GetChar(), 42u);
+ }
+}
+
+TYPED_TEST_P(BaseHashtableTest, MaybeGet) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_MaybeGet);
+
+ auto maybeUserData = table.MaybeGet(1);
+ EXPECT_TRUE(maybeUserData);
+
+ auto data = GetDataFrom<TypeParam>(maybeUserData);
+ EXPECT_EQ(data.CharRef()->GetChar(), 42u);
+}
+
+TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_Default) {
+ if constexpr (std::is_default_constructible_v<typename TypeParam::DataType>) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ typename TypeParam::DataType& data = table.LookupOrInsert(1);
+ EXPECT_EQ(data.CharRef(), nullptr);
+
+ data = typename TypeParam::DataType(MakeRefPtr<TestUniCharRefCounted>(
+ 42, TypeParam::kExpectedAddRefCnt_LookupOrInsert));
+ }
+}
+
+TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_NonDefault) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ typename TypeParam::DataType& data = table.LookupOrInsert(
+ 1, typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)});
+ EXPECT_NE(data.CharRef(), nullptr);
+}
+
+TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_NonDefault_AlreadyPresent) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ typename TypeParam::DataType& data1 = table.LookupOrInsert(
+ 1, typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)});
+ TestUniCharRefCounted* const address = data1.CharRef();
+ typename TypeParam::DataType& data2 = table.LookupOrInsert(
+ 1,
+ typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42, 1)});
+ EXPECT_EQ(&data1, &data2);
+ EXPECT_EQ(address, data2.CharRef());
+}
+
+TYPED_TEST_P(BaseHashtableTest, LookupOrInsertWith) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ typename TypeParam::DataType& data = table.LookupOrInsertWith(1, [] {
+ return typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)};
+ });
+ EXPECT_NE(data.CharRef(), nullptr);
+}
+
+TYPED_TEST_P(BaseHashtableTest, LookupOrInsertWith_AlreadyPresent) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.LookupOrInsertWith(1, [] {
+ return typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)};
+ });
+ table.LookupOrInsertWith(1, [] {
+ ADD_FAILURE();
+ return typename TypeParam::DataType{MakeRefPtr<TestUniCharRefCounted>(42)};
+ });
+}
+
+TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ auto myChar = MakeRefPtr<TestUniCharRefCounted>(
+ 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate);
+
+ table.InsertOrUpdate(1, typename TypeParam::UserDataType(std::move(myChar)));
+}
+
+TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Fallible) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ auto myChar = MakeRefPtr<TestUniCharRefCounted>(
+ 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Fallible);
+
+ auto res = table.InsertOrUpdate(
+ 1, typename TypeParam::UserDataType(std::move(myChar)),
+ mozilla::fallible);
+ EXPECT_TRUE(res);
+}
+
+TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Rvalue) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ auto myChar = MakeRefPtr<TestUniCharRefCounted>(
+ 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Rvalue);
+
+ table.InsertOrUpdate(
+ 1, std::move(typename TypeParam::UserDataType(std::move(myChar))));
+}
+
+TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Rvalue_Fallible) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ auto myChar = MakeRefPtr<TestUniCharRefCounted>(
+ 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible);
+
+ auto res = table.InsertOrUpdate(
+ 1, std::move(typename TypeParam::UserDataType(std::move(myChar))),
+ mozilla::fallible);
+ EXPECT_TRUE(res);
+}
+
+TYPED_TEST_P(BaseHashtableTest, Remove_OutputParam) {
+ // The Remove overload can't support non-default-constructible DataType.
+ if constexpr (std::is_default_constructible_v<typename TypeParam::DataType>) {
+ auto table = MakeBaseHashtable<TypeParam>(
+ TypeParam::kExpectedAddRefCnt_Remove_OutputParam);
+
+ typename TypeParam::DataType data;
+ auto res = table.Remove(1, &data);
+ EXPECT_TRUE(res);
+ EXPECT_EQ(data.CharRef()->GetChar(), 42u);
+ }
+}
+
+TYPED_TEST_P(BaseHashtableTest, Remove) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Remove);
+
+ auto res = table.Remove(1);
+ EXPECT_TRUE(res);
+}
+
+TYPED_TEST_P(BaseHashtableTest, Extract) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Extract);
+
+ auto maybeData = table.Extract(1);
+ EXPECT_TRUE(maybeData);
+ EXPECT_EQ(maybeData->CharRef()->GetChar(), 42u);
+}
+
+TYPED_TEST_P(BaseHashtableTest, RemoveIf) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_RemoveIf);
+
+ table.RemoveIf([](const auto&) { return true; });
+}
+
+TYPED_TEST_P(BaseHashtableTest, Lookup) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Lookup);
+
+ auto res = table.Lookup(1);
+ EXPECT_TRUE(res);
+ EXPECT_EQ(res.Data().CharRef()->GetChar(), 42u);
+}
+
+TYPED_TEST_P(BaseHashtableTest, Lookup_Remove) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Lookup_Remove);
+
+ auto res = table.Lookup(1);
+ EXPECT_TRUE(res);
+ EXPECT_EQ(res.Data().CharRef()->GetChar(), 42u);
+
+ res.Remove();
+}
+
+TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NoOp) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.WithEntryHandle(1, [](auto&&) {});
+
+ EXPECT_FALSE(table.Contains(1));
+}
+
+TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsert) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.WithEntryHandle(1, [](auto&& entry) {
+ entry.OrInsert(typename TypeParam::UserDataType(
+ MakeRefPtr<TestUniCharRefCounted>(42)));
+ });
+
+ EXPECT_TRUE(table.Contains(1));
+}
+
+TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsertFrom) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.WithEntryHandle(1, [](auto&& entry) {
+ entry.OrInsertWith([] {
+ return typename TypeParam::UserDataType(
+ MakeRefPtr<TestUniCharRefCounted>(42));
+ });
+ });
+
+ EXPECT_TRUE(table.Contains(1));
+}
+
+TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsertFrom_Exists) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.WithEntryHandle(1, [](auto&& entry) {
+ entry.OrInsertWith([] {
+ return typename TypeParam::UserDataType(
+ MakeRefPtr<TestUniCharRefCounted>(42));
+ });
+ });
+ table.WithEntryHandle(1, [](auto&& entry) {
+ entry.OrInsertWith([]() -> typename TypeParam::UserDataType {
+ ADD_FAILURE();
+ return typename TypeParam::UserDataType(
+ MakeRefPtr<TestUniCharRefCounted>(42));
+ });
+ });
+
+ EXPECT_TRUE(table.Contains(1));
+}
+
+TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrRemove) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.WithEntryHandle(1, [](auto&& entry) { entry.OrRemove(); });
+
+ EXPECT_FALSE(table.Contains(1));
+}
+
+TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrRemove_Exists) {
+ auto table = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.WithEntryHandle(1, [](auto&& entry) {
+ entry.OrInsertWith([] {
+ return typename TypeParam::UserDataType(
+ MakeRefPtr<TestUniCharRefCounted>(42));
+ });
+ });
+ table.WithEntryHandle(1, [](auto&& entry) { entry.OrRemove(); });
+
+ EXPECT_FALSE(table.Contains(1));
+}
+
+TYPED_TEST_P(BaseHashtableTest, Iter) {
+ auto table = MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Iter);
+
+ for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
+ EXPECT_EQ(iter.Data().CharRef()->GetChar(), 42u);
+ }
+}
+
+TYPED_TEST_P(BaseHashtableTest, ConstIter) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_ConstIter);
+
+ for (auto iter = table.ConstIter(); !iter.Done(); iter.Next()) {
+ EXPECT_EQ(iter.Data().CharRef()->GetChar(), 42u);
+ }
+}
+
+TYPED_TEST_P(BaseHashtableTest, begin_end) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_begin_end);
+
+ auto res = std::count_if(table.begin(), table.end(), [](const auto& entry) {
+ return entry.GetData().CharRef()->GetChar() == 42;
+ });
+ EXPECT_EQ(res, 1);
+}
+
+TYPED_TEST_P(BaseHashtableTest, cbegin_cend) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_cbegin_cend);
+
+ auto res = std::count_if(table.cbegin(), table.cend(), [](const auto& entry) {
+ return entry.GetData().CharRef()->GetChar() == 42;
+ });
+ EXPECT_EQ(res, 1);
+}
+
+TYPED_TEST_P(BaseHashtableTest, Clear) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_Clear);
+
+ table.Clear();
+}
+
+TYPED_TEST_P(BaseHashtableTest, ShallowSizeOfExcludingThis) {
+ auto table = MakeBaseHashtable<TypeParam>(
+ TypeParam::kExpectedAddRefCnt_ShallowSizeOfExcludingThis);
+
+ auto res = table.ShallowSizeOfExcludingThis(MallocSizeOf);
+ EXPECT_GT(res, 0u);
+}
+
+TYPED_TEST_P(BaseHashtableTest, ShallowSizeOfIncludingThis) {
+ // Make this work with ASAN builds, bug 1689549.
+#if !defined(MOZ_ASAN)
+ auto table = MakeBaseHashtable<TypeParam>(
+ TypeParam::kExpectedAddRefCnt_ShallowSizeOfIncludingThis);
+
+ auto res = table.ShallowSizeOfIncludingThis(MallocSizeOf);
+ EXPECT_GT(res, 0u);
+#endif
+}
+
+TYPED_TEST_P(BaseHashtableTest, SwapElements) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_SwapElements);
+
+ auto table2 = MakeEmptyBaseHashtable<TypeParam>();
+
+ table.SwapElements(table2);
+}
+
+TYPED_TEST_P(BaseHashtableTest, MarkImmutable) {
+ auto table =
+ MakeBaseHashtable<TypeParam>(TypeParam::kExpectedAddRefCnt_MarkImmutable);
+
+ table.MarkImmutable();
+}
+
+REGISTER_TYPED_TEST_SUITE_P(
+ BaseHashtableTest, Contains, GetGeneration, SizeOfExcludingThis,
+ SizeOfIncludingThis, Count, IsEmpty, Get_OutputParam, Get, MaybeGet,
+ LookupOrInsert_Default, LookupOrInsert_NonDefault,
+ LookupOrInsert_NonDefault_AlreadyPresent, LookupOrInsertWith,
+ LookupOrInsertWith_AlreadyPresent, InsertOrUpdate, InsertOrUpdate_Fallible,
+ InsertOrUpdate_Rvalue, InsertOrUpdate_Rvalue_Fallible, Remove_OutputParam,
+ Remove, Extract, RemoveIf, Lookup, Lookup_Remove, WithEntryHandle_NoOp,
+ WithEntryHandle_NotFound_OrInsert, WithEntryHandle_NotFound_OrInsertFrom,
+ WithEntryHandle_NotFound_OrInsertFrom_Exists,
+ WithEntryHandle_NotFound_OrRemove, WithEntryHandle_NotFound_OrRemove_Exists,
+ Iter, ConstIter, begin_end, cbegin_cend, Clear, ShallowSizeOfExcludingThis,
+ ShallowSizeOfIncludingThis, SwapElements, MarkImmutable);
+
+using BaseHashtableTestTypes =
+ ::testing::Types<DefaultConstructible_DefaultConstructible,
+ NonDefaultConstructible_NonDefaultConstructible,
+ NonDefaultConstructible_MovingNonDefaultConstructible>;
+
+INSTANTIATE_TYPED_TEST_SUITE_P(Hashtables, BaseHashtableTest,
+ BaseHashtableTestTypes, TypeNames);
+
+TEST(Hashtables, DataHashtable)
+{
+ // check a data-hashtable
+ nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr);
+ }
+
+ const char* str;
+
+ for (auto& entity : gEntities) {
+ ASSERT_TRUE(UniToEntity.Get(entity.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, DataHashtable_STLIterators)
+{
+ using mozilla::Unused;
+
+ nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr);
+ }
+
+ // operators, including conversion from iterator to const_iterator
+ nsTHashMap<nsUint32HashKey, const char*>::const_iterator ci =
+ UniToEntity.begin();
+ ++ci;
+ ASSERT_EQ(1, std::distance(UniToEntity.cbegin(), ci++));
+ ASSERT_EQ(2, std::distance(UniToEntity.cbegin(), ci));
+ ASSERT_TRUE(ci == ci);
+ auto otherCi = ci;
+ ++otherCi;
+ ++ci;
+ ASSERT_TRUE(&*ci == &*otherCi);
+
+ // STL algorithms (just to check that the iterator sufficiently conforms
+ // with the actual syntactical requirements of those algorithms).
+ std::for_each(UniToEntity.cbegin(), UniToEntity.cend(),
+ [](const auto& entry) {});
+ Unused << std::find_if(
+ UniToEntity.cbegin(), UniToEntity.cend(),
+ [](const auto& entry) { return entry.GetKey() == 42; });
+ Unused << std::accumulate(
+ UniToEntity.cbegin(), UniToEntity.cend(), 0u,
+ [](size_t sum, const auto& entry) { return sum + entry.GetKey(); });
+ Unused << std::any_of(UniToEntity.cbegin(), UniToEntity.cend(),
+ [](const auto& entry) { return entry.GetKey() == 42; });
+ Unused << std::max_element(UniToEntity.cbegin(), UniToEntity.cend(),
+ [](const auto& lhs, const auto& rhs) {
+ return lhs.GetKey() > rhs.GetKey();
+ });
+
+ // const range-based for
+ {
+ std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT);
+ for (const auto& entity :
+ const_cast<const nsTHashMap<nsUint32HashKey, const char*>&>(
+ UniToEntity)) {
+ ASSERT_EQ(1u,
+ entities.erase(EntityNode{entity.GetData(), entity.GetKey()}));
+ }
+ ASSERT_TRUE(entities.empty());
+ }
+
+ // non-const range-based for
+ {
+ std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT);
+ for (auto& entity : UniToEntity) {
+ ASSERT_EQ(1u,
+ entities.erase(EntityNode{entity.GetData(), entity.GetKey()}));
+
+ entity.SetData(nullptr);
+ ASSERT_EQ(nullptr, entity.GetData());
+ }
+ ASSERT_TRUE(entities.empty());
+ }
+}
+
+TEST(Hashtables, DataHashtable_RemoveIf)
+{
+ // check a data-hashtable
+ nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr);
+ }
+
+ UniToEntity.RemoveIf([](const auto& iter) { return iter.Key() >= 170; });
+
+ ASSERT_EQ(10u, UniToEntity.Count());
+}
+
+TEST(Hashtables, ClassHashtable)
+{
+ // check a class-hashtable
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ // Insert a sub-class of TestUniChar to test if this is accepted by
+ // InsertOrUpdate.
+ EntToUniClass.InsertOrUpdate(
+ nsDependentCString(entity.mStr),
+ mozilla::MakeUnique<TestUniCharDerived>(entity.mUnicode));
+ }
+
+ TestUniChar* myChar;
+
+ for (auto& entity : gEntities) {
+ ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(entity.mStr), &myChar));
+ }
+
+ ASSERT_FALSE(EntToUniClass.Get("xxxx"_ns, &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, ClassHashtable_RangeBasedFor)
+{
+ // check a class-hashtable
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ EntToUniClass.InsertOrUpdate(nsDependentCString(entity.mStr),
+ MakeUnique<TestUniChar>(entity.mUnicode));
+ }
+
+ // const range-based for
+ {
+ std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT);
+ for (const auto& entity :
+ const_cast<const nsClassHashtable<nsCStringHashKey, TestUniChar>&>(
+ EntToUniClass)) {
+ const char* str;
+ entity.GetKey().GetData(&str);
+ ASSERT_EQ(1u,
+ entities.erase(EntityNode{str, entity.GetData()->GetChar()}));
+ }
+ ASSERT_TRUE(entities.empty());
+ }
+
+ // non-const range-based for
+ {
+ std::set<EntityNode> entities(gEntities, gEntities + ENTITY_COUNT);
+ for (auto& entity : EntToUniClass) {
+ const char* str;
+ entity.GetKey().GetData(&str);
+ ASSERT_EQ(1u,
+ entities.erase(EntityNode{str, entity.GetData()->GetChar()}));
+
+ entity.SetData(UniquePtr<TestUniChar>{});
+ ASSERT_EQ(nullptr, entity.GetData());
+ }
+ ASSERT_TRUE(entities.empty());
+ }
+}
+
+TEST(Hashtables, DataHashtableWithInterfaceKey)
+{
+ // check a data-hashtable with an interface key
+ nsTHashMap<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.InsertOrUpdate(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 (auto& entity : gEntities) {
+ nsCOMPtr<IFoo> foo;
+ CreateIFoo(getter_AddRefs(foo));
+ foo->SetString(nsDependentCString(entity.mStr));
+
+ UniToEntClass2.InsertOrUpdate(entity.mUnicode, foo);
+ }
+
+ for (auto& entity : gEntities) {
+ nsCOMPtr<IFoo> myEnt;
+ ASSERT_TRUE(UniToEntClass2.Get(entity.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));
+}
+
+TEST(Hashtables, DataHashtable_WithEntryHandle)
+{
+ // check WithEntryHandle/OrInsertWith
+ nsTHashMap<nsUint32HashKey, const char*> UniToEntity(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ UniToEntity.WithEntryHandle(entity.mUnicode, [&entity](auto&& entry) {
+ EXPECT_FALSE(entry);
+ const char* const val =
+ entry.OrInsertWith([&entity]() { return entity.mStr; });
+ EXPECT_TRUE(entry);
+ EXPECT_TRUE(val == entity.mStr);
+ EXPECT_TRUE(entry.Data() == entity.mStr);
+ });
+ }
+
+ for (auto& entity : gEntities) {
+ UniToEntity.WithEntryHandle(entity.mUnicode,
+ [](auto&& entry) { EXPECT_TRUE(entry); });
+ }
+
+ // 0 should not be found
+ size_t count = UniToEntity.Count();
+ UniToEntity.Lookup(0U).Remove();
+ ASSERT_TRUE(count == UniToEntity.Count());
+
+ // Lookup should find all entries
+ count = 0;
+ for (auto& entity : gEntities) {
+ if (UniToEntity.Lookup(entity.mUnicode)) {
+ count++;
+ }
+ }
+ ASSERT_TRUE(count == UniToEntity.Count());
+
+ for (auto& entity : gEntities) {
+ UniToEntity.WithEntryHandle(entity.mUnicode,
+ [](auto&& entry) { EXPECT_TRUE(entry); });
+ }
+
+ // Lookup().Remove() should remove all entries.
+ for (auto& entity : gEntities) {
+ if (auto entry = UniToEntity.Lookup(entity.mUnicode)) {
+ entry.Remove();
+ }
+ }
+ ASSERT_TRUE(0 == UniToEntity.Count());
+
+ // Remove newly added entries via OrRemove.
+ for (auto& entity : gEntities) {
+ UniToEntity.WithEntryHandle(entity.mUnicode, [](auto&& entry) {
+ EXPECT_FALSE(entry);
+ entry.OrRemove();
+ });
+ }
+ ASSERT_TRUE(0 == UniToEntity.Count());
+
+ // Remove existing entries via OrRemove.
+ for (auto& entity : gEntities) {
+ UniToEntity.WithEntryHandle(entity.mUnicode, [&entity](auto&& entry) {
+ EXPECT_FALSE(entry);
+ const char* const val = entry.OrInsert(entity.mStr);
+ EXPECT_TRUE(entry);
+ EXPECT_TRUE(val == entity.mStr);
+ EXPECT_TRUE(entry.Data() == entity.mStr);
+ });
+
+ UniToEntity.WithEntryHandle(entity.mUnicode, [](auto&& entry) {
+ EXPECT_TRUE(entry);
+ entry.OrRemove();
+ });
+ }
+ ASSERT_TRUE(0 == UniToEntity.Count());
+}
+
+TEST(Hashtables, ClassHashtable_WithEntryHandle)
+{
+ // check a class-hashtable WithEntryHandle with null values
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ EntToUniClass.WithEntryHandle(
+ nsDependentCString(entity.mStr), [](auto&& entry) {
+ EXPECT_FALSE(entry);
+ const TestUniChar* val = entry.OrInsert(nullptr).get();
+ EXPECT_TRUE(entry);
+ EXPECT_TRUE(val == nullptr);
+ EXPECT_TRUE(entry.Data() == nullptr);
+ });
+ }
+
+ for (auto& entity : gEntities) {
+ EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr),
+ [](auto&& entry) { EXPECT_TRUE(entry); });
+ EntToUniClass.WithEntryHandle(
+ nsDependentCString(entity.mStr),
+ [](auto&& entry) { EXPECT_TRUE(entry.Data() == nullptr); });
+ }
+
+ // "" should not be found
+ size_t count = EntToUniClass.Count();
+ EntToUniClass.Lookup(nsDependentCString("")).Remove();
+ ASSERT_TRUE(count == EntToUniClass.Count());
+
+ // Lookup should find all entries.
+ count = 0;
+ for (auto& entity : gEntities) {
+ if (EntToUniClass.Lookup(nsDependentCString(entity.mStr))) {
+ count++;
+ }
+ }
+ ASSERT_TRUE(count == EntToUniClass.Count());
+
+ for (auto& entity : gEntities) {
+ EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr),
+ [](auto&& entry) { EXPECT_TRUE(entry); });
+ }
+
+ // Lookup().Remove() should remove all entries.
+ for (auto& entity : gEntities) {
+ if (auto entry = EntToUniClass.Lookup(nsDependentCString(entity.mStr))) {
+ entry.Remove();
+ }
+ }
+ ASSERT_TRUE(0 == EntToUniClass.Count());
+
+ // Remove newly added entries via OrRemove.
+ for (auto& entity : gEntities) {
+ EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr),
+ [](auto&& entry) {
+ EXPECT_FALSE(entry);
+ entry.OrRemove();
+ });
+ }
+ ASSERT_TRUE(0 == EntToUniClass.Count());
+
+ // Remove existing entries via OrRemove.
+ for (auto& entity : gEntities) {
+ EntToUniClass.WithEntryHandle(
+ nsDependentCString(entity.mStr), [](auto&& entry) {
+ EXPECT_FALSE(entry);
+ const TestUniChar* val = entry.OrInsert(nullptr).get();
+ EXPECT_TRUE(entry);
+ EXPECT_TRUE(val == nullptr);
+ EXPECT_TRUE(entry.Data() == nullptr);
+ });
+
+ EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr),
+ [](auto&& entry) {
+ EXPECT_TRUE(entry);
+ entry.OrRemove();
+ });
+ }
+ ASSERT_TRUE(0 == EntToUniClass.Count());
+}
+
+TEST(Hashtables, ClassHashtable_GetOrInsertNew_Present)
+{
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ for (const auto& entity : gEntities) {
+ EntToUniClass.InsertOrUpdate(
+ nsDependentCString(entity.mStr),
+ mozilla::MakeUnique<TestUniCharDerived>(entity.mUnicode));
+ }
+
+ auto* entry = EntToUniClass.GetOrInsertNew("uml"_ns, 42);
+ EXPECT_EQ(168u, entry->GetChar());
+}
+
+TEST(Hashtables, ClassHashtable_GetOrInsertNew_NotPresent)
+{
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ // This is going to insert a TestUniChar.
+ auto* entry = EntToUniClass.GetOrInsertNew("uml"_ns, 42);
+ EXPECT_EQ(42u, entry->GetChar());
+}
+
+TEST(Hashtables, ClassHashtable_LookupOrInsertWith_Present)
+{
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ for (const auto& entity : gEntities) {
+ EntToUniClass.InsertOrUpdate(
+ nsDependentCString(entity.mStr),
+ mozilla::MakeUnique<TestUniCharDerived>(entity.mUnicode));
+ }
+
+ const auto& entry = EntToUniClass.LookupOrInsertWith(
+ "uml"_ns, [] { return mozilla::MakeUnique<TestUniCharDerived>(42); });
+ EXPECT_EQ(168u, entry->GetChar());
+}
+
+TEST(Hashtables, ClassHashtable_LookupOrInsertWith_NotPresent)
+{
+ nsClassHashtable<nsCStringHashKey, TestUniChar> EntToUniClass(ENTITY_COUNT);
+
+ // This is going to insert a TestUniCharDerived.
+ const auto& entry = EntToUniClass.LookupOrInsertWith(
+ "uml"_ns, [] { return mozilla::MakeUnique<TestUniCharDerived>(42); });
+ EXPECT_EQ(42u, entry->GetChar());
+}
+
+TEST(Hashtables, RefPtrHashtable)
+{
+ // check a RefPtr-hashtable
+ nsRefPtrHashtable<nsCStringHashKey, TestUniCharRefCounted> EntToUniClass(
+ ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ EntToUniClass.InsertOrUpdate(
+ nsDependentCString(entity.mStr),
+ MakeRefPtr<TestUniCharRefCounted>(entity.mUnicode));
+ }
+
+ TestUniCharRefCounted* myChar;
+
+ for (auto& entity : gEntities) {
+ ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(entity.mStr), &myChar));
+ }
+
+ ASSERT_FALSE(EntToUniClass.Get("xxxx"_ns, &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, RefPtrHashtable_Clone)
+{
+ // check a RefPtr-hashtable
+ nsRefPtrHashtable<nsCStringHashKey, TestUniCharRefCounted> EntToUniClass(
+ ENTITY_COUNT);
+
+ for (auto& entity : gEntities) {
+ EntToUniClass.InsertOrUpdate(
+ nsDependentCString(entity.mStr),
+ MakeRefPtr<TestUniCharRefCounted>(entity.mUnicode));
+ }
+
+ auto clone = EntToUniClass.Clone();
+ static_assert(std::is_same_v<decltype(clone), decltype(EntToUniClass)>);
+
+ EXPECT_EQ(clone.Count(), EntToUniClass.Count());
+
+ for (const auto& entry : EntToUniClass) {
+ auto cloneEntry = clone.Lookup(entry.GetKey());
+
+ EXPECT_TRUE(cloneEntry);
+ EXPECT_EQ(cloneEntry.Data(), entry.GetWeak());
+ }
+}
+
+TEST(Hashtables, Clone)
+{
+ static constexpr uint64_t count = 10;
+
+ nsTHashMap<nsUint64HashKey, uint64_t> table;
+ for (uint64_t i = 0; i < count; i++) {
+ table.InsertOrUpdate(42 + i, i);
+ }
+
+ auto clone = table.Clone();
+
+ static_assert(std::is_same_v<decltype(clone), decltype(table)>);
+
+ EXPECT_EQ(clone.Count(), table.Count());
+
+ for (const auto& entry : table) {
+ auto cloneEntry = clone.Lookup(entry.GetKey());
+
+ EXPECT_TRUE(cloneEntry);
+ EXPECT_EQ(cloneEntry.Data(), entry.GetData());
+ }
+}
+
+TEST(Hashtables, Values)
+{
+ static constexpr uint64_t count = 10;
+
+ nsTHashMap<nsUint64HashKey, uint64_t> table;
+ for (uint64_t i = 0; i < count; i++) {
+ table.InsertOrUpdate(42 + i, i);
+ }
+
+ nsTArray<uint64_t> values;
+ for (const uint64_t& value : table.Values()) {
+ values.AppendElement(value);
+ }
+ values.Sort();
+
+ EXPECT_EQ((nsTArray<uint64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), values);
+}
diff --git a/xpcom/tests/gtest/TestID.cpp b/xpcom/tests/gtest/TestID.cpp
new file mode 100644
index 0000000000..65b41a2b26
--- /dev/null
+++ b/xpcom/tests/gtest/TestID.cpp
@@ -0,0 +1,33 @@
+/* -*- 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));
+
+ auto cp = id.ToString();
+ ASSERT_STREQ(cp.get(), ids[4 * (i / 4) + 3]);
+ }
+}
diff --git a/xpcom/tests/gtest/TestIDUtils.cpp b/xpcom/tests/gtest/TestIDUtils.cpp
new file mode 100644
index 0000000000..adf6a96611
--- /dev/null
+++ b/xpcom/tests/gtest/TestIDUtils.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 "nsIDUtils.h"
+
+#include "gtest/gtest.h"
+
+static const char* const bare_ids[] = {
+ "5c347b10-d55c-11d1-89b7-006008911b81",
+ "fc347b10-d55c-f1d1-f9b7-006008911b81",
+};
+
+TEST(nsIDUtils, NSID_TrimBracketsUTF16)
+{
+ nsID id{};
+ for (const auto* idstr : bare_ids) {
+ ASSERT_TRUE(id.Parse(idstr));
+
+ NSID_TrimBracketsUTF16 trimmed(id);
+ ASSERT_TRUE(trimmed.EqualsASCII(idstr));
+ }
+}
+
+TEST(nsIDUtils, NSID_TrimBracketsASCII)
+{
+ nsID id{};
+ for (const auto* idstr : bare_ids) {
+ ASSERT_TRUE(id.Parse(idstr));
+
+ NSID_TrimBracketsASCII trimmed(id);
+ ASSERT_TRUE(trimmed.EqualsASCII(idstr));
+ }
+}
diff --git a/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp b/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp
new file mode 100644
index 0000000000..5bb3f2bbe4
--- /dev/null
+++ b/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp
@@ -0,0 +1,161 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+#include "Helpers.h"
+
+using namespace mozilla;
+
+TEST(TestInputStreamLengthHelper, NonLengthStream)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), buf);
+
+ bool called = false;
+ InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) {
+ ASSERT_EQ(int64_t(buf.Length()), aLength);
+ called = true;
+ });
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestInputStreamLengthHelper, NonLengthStream)"_ns,
+ [&]() { return called; }));
+}
+
+class LengthStream final : public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStream {
+ public:
+ NS_DECL_ISUPPORTS
+
+ LengthStream(int64_t aLength, nsresult aLengthRv, uint64_t aAvailable,
+ bool aIsAsyncLength)
+ : mLength(aLength),
+ mLengthRv(aLengthRv),
+ mAvailable(aAvailable),
+ mIsAsyncLength(aIsAsyncLength) {}
+
+ NS_IMETHOD Close(void) override { MOZ_CRASH("Invalid call!"); }
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override {
+ MOZ_CRASH("Invalid call!");
+ }
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ MOZ_CRASH("Invalid call!");
+ }
+ NS_IMETHOD IsNonBlocking(bool* _retval) override {
+ MOZ_CRASH("Invalid call!");
+ }
+
+ NS_IMETHOD Length(int64_t* aLength) override {
+ *aLength = mLength;
+ return mLengthRv;
+ }
+
+ NS_IMETHOD AsyncLengthWait(nsIInputStreamLengthCallback* aCallback,
+ nsIEventTarget* aEventTarget) override {
+ if (aCallback) {
+ aCallback->OnInputStreamLengthReady(this, mLength);
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD Available(uint64_t* aAvailable) override {
+ *aAvailable = mAvailable;
+ return NS_OK;
+ }
+
+ NS_IMETHOD StreamStatus() override { return NS_OK; }
+
+ private:
+ ~LengthStream() = default;
+
+ int64_t mLength;
+ nsresult mLengthRv;
+ uint64_t mAvailable;
+
+ bool mIsAsyncLength;
+};
+
+NS_IMPL_ADDREF(LengthStream);
+NS_IMPL_RELEASE(LengthStream);
+
+NS_INTERFACE_MAP_BEGIN(LengthStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, mIsAsyncLength)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+TEST(TestInputStreamLengthHelper, LengthStream)
+{
+ nsCOMPtr<nsIInputStream> stream = new LengthStream(42, NS_OK, 0, false);
+
+ bool called = false;
+ InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) {
+ ASSERT_EQ(42, aLength);
+ called = true;
+ });
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestInputStreamLengthHelper, LengthStream)"_ns,
+ [&]() { return called; }));
+}
+
+TEST(TestInputStreamLengthHelper, InvalidLengthStream)
+{
+ nsCOMPtr<nsIInputStream> stream =
+ new LengthStream(42, NS_ERROR_NOT_AVAILABLE, 0, false);
+
+ bool called = false;
+ InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) {
+ ASSERT_EQ(-1, aLength);
+ called = true;
+ });
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestInputStreamLengthHelper, InvalidLengthStream)"_ns,
+ [&]() { return called; }));
+}
+
+TEST(TestInputStreamLengthHelper, AsyncLengthStream)
+{
+ nsCOMPtr<nsIInputStream> stream =
+ new LengthStream(22, NS_BASE_STREAM_WOULD_BLOCK, 123, true);
+
+ bool called = false;
+ InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) {
+ ASSERT_EQ(22, aLength);
+ called = true;
+ });
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestInputStreamLengthHelper, AsyncLengthStream)"_ns,
+ [&]() { return called; }));
+}
+
+TEST(TestInputStreamLengthHelper, FallbackLengthStream)
+{
+ nsCOMPtr<nsIInputStream> stream =
+ new LengthStream(-1, NS_BASE_STREAM_WOULD_BLOCK, 123, false);
+
+ bool called = false;
+ InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) {
+ ASSERT_EQ(123, aLength);
+ called = true;
+ });
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestInputStreamLengthHelper, FallbackLengthStream)"_ns,
+ [&]() { return called; }));
+}
diff --git a/xpcom/tests/gtest/TestJSHolderMap.cpp b/xpcom/tests/gtest/TestJSHolderMap.cpp
new file mode 100644
index 0000000000..4b05459499
--- /dev/null
+++ b/xpcom/tests/gtest/TestJSHolderMap.cpp
@@ -0,0 +1,348 @@
+/* -*- 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 "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsCycleCollector.h"
+
+#include "js/GCAPI.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+enum HolderKind { SingleZone, MultiZone };
+
+class MyHolder final : public nsScriptObjectTracer {
+ public:
+ explicit MyHolder(HolderKind kind = SingleZone, size_t value = 0)
+ : nsScriptObjectTracer(FlagsForKind(kind)), value(value) {}
+
+ const size_t value;
+
+ NS_IMETHOD_(void) Root(void*) override { MOZ_CRASH(); }
+ NS_IMETHOD_(void) Unlink(void*) override { MOZ_CRASH(); }
+ NS_IMETHOD_(void) Unroot(void*) override { MOZ_CRASH(); }
+ NS_IMETHOD_(void) DeleteCycleCollectable(void*) override { MOZ_CRASH(); }
+ NS_IMETHOD_(void)
+ Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override {
+ MOZ_CRASH();
+ }
+ NS_IMETHOD TraverseNative(void* aPtr,
+ nsCycleCollectionTraversalCallback& aCb) override {
+ MOZ_CRASH();
+ }
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(MyHolder)
+
+ private:
+ Flags FlagsForKind(HolderKind kind) {
+ return kind == MultiZone ? FlagMultiZoneJSHolder
+ : FlagMaybeSingleZoneJSHolder;
+ }
+};
+
+static size_t CountEntries(JSHolderMap& map) {
+ size_t count = 0;
+ for (JSHolderMap::Iter i(map); !i.Done(); i.Next()) {
+ MOZ_RELEASE_ASSERT(i->mHolder);
+ MOZ_RELEASE_ASSERT(i->mTracer);
+ count++;
+ }
+ return count;
+}
+
+JS::Zone* DummyZone = reinterpret_cast<JS::Zone*>(1);
+
+JS::Zone* ZoneForKind(HolderKind kind) {
+ return kind == MultiZone ? nullptr : DummyZone;
+}
+
+TEST(JSHolderMap, Empty)
+{
+ JSHolderMap map;
+ ASSERT_EQ(CountEntries(map), 0u);
+}
+
+static void TestAddAndRemove(HolderKind kind) {
+ JSHolderMap map;
+
+ MyHolder holder(kind);
+ nsScriptObjectTracer* tracer = &holder;
+
+ ASSERT_FALSE(map.Has(&holder));
+ ASSERT_EQ(map.Extract(&holder), nullptr);
+
+ map.Put(&holder, tracer, ZoneForKind(kind));
+ ASSERT_TRUE(map.Has(&holder));
+ ASSERT_EQ(CountEntries(map), 1u);
+ ASSERT_EQ(map.Get(&holder), tracer);
+
+ ASSERT_EQ(map.Extract(&holder), tracer);
+ ASSERT_EQ(map.Extract(&holder), nullptr);
+ ASSERT_FALSE(map.Has(&holder));
+ ASSERT_EQ(CountEntries(map), 0u);
+}
+
+TEST(JSHolderMap, AddAndRemove)
+{
+ TestAddAndRemove(SingleZone);
+ TestAddAndRemove(MultiZone);
+}
+
+static void TestIterate(HolderKind kind) {
+ JSHolderMap map;
+
+ MyHolder holder(kind, 0);
+ nsScriptObjectTracer* tracer = &holder;
+
+ Maybe<JSHolderMap::Iter> iter;
+
+ // Iterate an empty map.
+ iter.emplace(map);
+ ASSERT_TRUE(iter->Done());
+ iter.reset();
+
+ // Iterate a map with one entry.
+ map.Put(&holder, tracer, ZoneForKind(kind));
+ iter.emplace(map);
+ ASSERT_FALSE(iter->Done());
+ ASSERT_EQ(iter->Get().mHolder, &holder);
+ iter->Next();
+ ASSERT_TRUE(iter->Done());
+ iter.reset();
+
+ // Iterate a map with 10 entries.
+ constexpr size_t count = 10;
+ Vector<UniquePtr<MyHolder>, 0, InfallibleAllocPolicy> holders;
+ bool seen[count] = {};
+ for (size_t i = 1; i < count; i++) {
+ MOZ_ALWAYS_TRUE(
+ holders.emplaceBack(mozilla::MakeUnique<MyHolder>(kind, i)));
+ map.Put(holders.back().get(), tracer, ZoneForKind(kind));
+ }
+ for (iter.emplace(map); !iter->Done(); iter->Next()) {
+ MyHolder* holder = static_cast<MyHolder*>(iter->Get().mHolder);
+ size_t value = holder->value;
+ ASSERT_TRUE(value < count);
+ ASSERT_FALSE(seen[value]);
+ seen[value] = true;
+ }
+ for (const auto& s : seen) {
+ ASSERT_TRUE(s);
+ }
+}
+
+TEST(JSHolderMap, Iterate)
+{
+ TestIterate(SingleZone);
+ TestIterate(MultiZone);
+}
+
+static void TestAddRemoveMany(HolderKind kind, size_t count) {
+ JSHolderMap map;
+
+ Vector<UniquePtr<MyHolder>, 0, InfallibleAllocPolicy> holders;
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(holders.emplaceBack(mozilla::MakeUnique<MyHolder>(kind)));
+ }
+
+ for (size_t i = 0; i < count; i++) {
+ MyHolder* holder = holders[i].get();
+ map.Put(holder, holder, ZoneForKind(kind));
+ }
+
+ ASSERT_EQ(CountEntries(map), count);
+
+ for (size_t i = 0; i < count; i++) {
+ MyHolder* holder = holders[i].get();
+ ASSERT_EQ(map.Extract(holder), holder);
+ }
+
+ ASSERT_EQ(CountEntries(map), 0u);
+}
+
+TEST(JSHolderMap, TestAddRemoveMany)
+{
+ TestAddRemoveMany(SingleZone, 10000);
+ TestAddRemoveMany(MultiZone, 10000);
+}
+
+static void TestRemoveWhileIterating(HolderKind kind, size_t count) {
+ JSHolderMap map;
+ Vector<UniquePtr<MyHolder>, 0, InfallibleAllocPolicy> holders;
+ Maybe<JSHolderMap::Iter> iter;
+
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(holders.emplaceBack(MakeUnique<MyHolder>(kind)));
+ }
+
+ // Iterate a map with one entry but remove it before we get to it.
+ MyHolder* holder = holders[0].get();
+ map.Put(holder, holder, ZoneForKind(kind));
+ iter.emplace(map);
+ ASSERT_FALSE(iter->Done());
+ ASSERT_EQ(map.Extract(holder), holder);
+ iter->UpdateForRemovals();
+ ASSERT_TRUE(iter->Done());
+
+ // Check UpdateForRemovals is safe to call on a done iterator.
+ iter->UpdateForRemovals();
+ ASSERT_TRUE(iter->Done());
+ iter.reset();
+
+ // Add many holders and remove them mid way through iteration.
+
+ for (size_t i = 0; i < count; i++) {
+ MyHolder* holder = holders[i].get();
+ map.Put(holder, holder, ZoneForKind(kind));
+ }
+
+ iter.emplace(map);
+ for (size_t i = 0; i < count / 2; i++) {
+ iter->Next();
+ ASSERT_FALSE(iter->Done());
+ }
+
+ for (size_t i = 0; i < count; i++) {
+ MyHolder* holder = holders[i].get();
+ ASSERT_EQ(map.Extract(holder), holder);
+ }
+
+ iter->UpdateForRemovals();
+
+ ASSERT_TRUE(iter->Done());
+ iter.reset();
+
+ ASSERT_EQ(CountEntries(map), 0u);
+}
+
+TEST(JSHolderMap, TestRemoveWhileIterating)
+{
+ TestRemoveWhileIterating(SingleZone, 10000);
+ TestRemoveWhileIterating(MultiZone, 10000);
+}
+
+class ObjectHolder final {
+ public:
+ ObjectHolder() { HoldJSObjects(this); }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ObjectHolder)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(ObjectHolder)
+
+ void SetObject(JSObject* aObject) { mObject = aObject; }
+
+ void ClearObject() { mObject = nullptr; }
+
+ JSObject* GetObject() const { return mObject; }
+ JSObject* GetObjectUnbarriered() const { return mObject.unbarrieredGet(); }
+
+ bool ObjectIsGray() const {
+ JSObject* obj = mObject.unbarrieredGet();
+ MOZ_RELEASE_ASSERT(obj);
+ return JS::GCThingIsMarkedGray(JS::GCCellPtr(obj));
+ }
+
+ private:
+ JS::Heap<JSObject*> mObject;
+
+ ~ObjectHolder() { DropJSObjects(this); }
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ObjectHolder)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ObjectHolder)
+ tmp->ClearObject();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ObjectHolder)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ObjectHolder)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mObject)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+// Test GC things stored in JS holders are marked as gray roots by the GC.
+static void TestHoldersAreMarkedGray(JSContext* cx) {
+ RefPtr holder(new ObjectHolder);
+
+ JSObject* obj = JS_NewPlainObject(cx);
+ ASSERT_TRUE(obj);
+ holder->SetObject(obj);
+ obj = nullptr;
+
+ JS_GC(cx);
+
+ ASSERT_TRUE(holder->ObjectIsGray());
+}
+
+// Test GC things stored in JS holders are updated by compacting GC.
+static void TestHoldersAreMoved(JSContext* cx, bool singleZone) {
+ JS::RootedObject obj(cx, JS_NewPlainObject(cx));
+ ASSERT_TRUE(obj);
+
+ // Set a property so we can check we have the same object at the end.
+ const char* PropertyName = "answer";
+ const int32_t PropertyValue = 42;
+ JS::RootedValue value(cx, JS::Int32Value(PropertyValue));
+ ASSERT_TRUE(JS_SetProperty(cx, obj, PropertyName, value));
+
+ // Ensure the object is tenured.
+ JS_GC(cx);
+
+ RefPtr<ObjectHolder> holder(new ObjectHolder);
+ holder->SetObject(obj);
+
+ uintptr_t original = uintptr_t(obj.get());
+
+ if (singleZone) {
+ JS::PrepareZoneForGC(cx, js::GetContextZone(cx));
+ } else {
+ JS::PrepareForFullGC(cx);
+ }
+
+ JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::DEBUG_GC);
+
+ // Shrinking DEBUG_GC should move all GC things.
+ ASSERT_NE(uintptr_t(holder->GetObject()), original);
+
+ // Both root and holder should have been updated.
+ ASSERT_EQ(obj, holder->GetObject());
+
+ // Check it's the object we expect.
+ value.setUndefined();
+ ASSERT_TRUE(JS_GetProperty(cx, obj, PropertyName, &value));
+ ASSERT_EQ(value, JS::Int32Value(PropertyValue));
+}
+
+TEST(JSHolderMap, GCIntegration)
+{
+ CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
+ ASSERT_NE(ccjscx, nullptr);
+ JSContext* cx = ccjscx->Context();
+ ASSERT_NE(cx, nullptr);
+
+ static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+
+ JS::RealmOptions options;
+ // dummy
+ options.behaviors().setReduceTimerPrecisionCallerType(
+ JS::RTPCallerTypeToken{0});
+ JS::RootedObject global(cx);
+ global = JS_NewGlobalObject(cx, &GlobalClass, nullptr,
+ JS::FireOnNewGlobalHook, options);
+ ASSERT_NE(global, nullptr);
+
+ JSAutoRealm ar(cx, global);
+
+ TestHoldersAreMarkedGray(cx);
+ TestHoldersAreMoved(cx, true);
+ TestHoldersAreMoved(cx, false);
+}
diff --git a/xpcom/tests/gtest/TestLogCommandLineHandler.cpp b/xpcom/tests/gtest/TestLogCommandLineHandler.cpp
new file mode 100644
index 0000000000..ebec4854dd
--- /dev/null
+++ b/xpcom/tests/gtest/TestLogCommandLineHandler.cpp
@@ -0,0 +1,183 @@
+/* -*- 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 "LogCommandLineHandler.h"
+
+#include <iterator>
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+template <class T, size_t N>
+constexpr size_t array_size(T (&)[N]) {
+ return N;
+}
+
+TEST(LogCommandLineHandler, Empty)
+{
+ bool callbackInvoked = false;
+ auto callback = [&](nsACString const& env) mutable {
+ callbackInvoked = true;
+ };
+
+ mozilla::LoggingHandleCommandLineArgs(0, nullptr, callback);
+ EXPECT_FALSE(callbackInvoked);
+
+ char const* argv1[] = {""};
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback);
+ EXPECT_FALSE(callbackInvoked);
+}
+
+TEST(LogCommandLineHandler, MOZ_LOG_regular)
+{
+ nsTArray<nsCString> results;
+
+ auto callback = [&](nsACString const& env) mutable {
+ results.AppendElement(env);
+ };
+
+ char const* argv1[] = {"", "-MOZ_LOG", "module1:5,module2:4,sync,timestamp"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback);
+ EXPECT_TRUE(results.Length() == 1);
+ EXPECT_TRUE(
+ "MOZ_LOG=module1:5,module2:4,sync,timestamp"_ns.Equals(results[0]));
+
+ char const* argv2[] = {"", "-MOZ_LOG=modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback);
+ EXPECT_TRUE(results.Length() == 1);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+
+ char const* argv3[] = {"", "--MOZ_LOG", "modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback);
+ EXPECT_TRUE(results.Length() == 1);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+
+ char const* argv4[] = {"", "--MOZ_LOG=modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback);
+ EXPECT_TRUE(results.Length() == 1);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+}
+
+TEST(LogCommandLineHandler, MOZ_LOG_and_FILE_regular)
+{
+ nsTArray<nsCString> results;
+
+ auto callback = [&](nsACString const& env) mutable {
+ results.AppendElement(env);
+ };
+
+ char const* argv1[] = {"", "-MOZ_LOG", "modules", "-MOZ_LOG_FILE",
+ "c:\\file/path"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback);
+ EXPECT_TRUE(results.Length() == 2);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+ EXPECT_TRUE("MOZ_LOG_FILE=c:\\file/path"_ns.Equals(results[1]));
+
+ char const* argv2[] = {"", "-MOZ_LOG=modules", "-MOZ_LOG_FILE=file"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback);
+ EXPECT_TRUE(results.Length() == 2);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+ EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1]));
+
+ char const* argv3[] = {"", "--MOZ_LOG", "modules", "--MOZ_LOG_FILE", "file"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback);
+ EXPECT_TRUE(results.Length() == 2);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+ EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1]));
+
+ char const* argv4[] = {"", "--MOZ_LOG=modules", "--MOZ_LOG_FILE=file"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback);
+ EXPECT_TRUE(results.Length() == 2);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+ EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1]));
+
+ char const* argv5[] = {"", "--MOZ_LOG", "modules", "-P",
+ "foo", "--MOZ_LOG_FILE", "file"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv5), argv5, callback);
+ EXPECT_TRUE(results.Length() == 2);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+ EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1]));
+}
+
+TEST(LogCommandLineHandler, MOZ_LOG_fuzzy)
+{
+ nsTArray<nsCString> results;
+
+ auto callback = [&](nsACString const& env) mutable {
+ results.AppendElement(env);
+ };
+
+ char const* argv1[] = {"", "-MOZ_LOG"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback);
+ EXPECT_TRUE(results.Length() == 0);
+
+ char const* argv2[] = {"", "modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback);
+ EXPECT_TRUE(results.Length() == 0);
+
+ char const* argv3[] = {"", "-MOZ_LOG,modules", "-MOZ_LOG"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback);
+ EXPECT_TRUE(results.Length() == 0);
+
+ char const* argv4[] = {"", "-MOZ_LOG", "-MOZ_LOG", "-MOZ_LOG"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback);
+ EXPECT_TRUE(results.Length() == 0);
+
+ char const* argv5[] = {"", "-MOZ_LOG", "-diffent_command", "modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv5), argv5, callback);
+ EXPECT_TRUE(results.Length() == 0);
+}
+
+TEST(LogCommandLineHandler, MOZ_LOG_overlapping)
+{
+ nsTArray<nsCString> results;
+
+ auto callback = [&](nsACString const& env) mutable {
+ results.AppendElement(env);
+ };
+
+ char const* argv1[] = {"", "-MOZ_LOG=modules1", "-MOZ_LOG=modules2"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback);
+ EXPECT_TRUE(results.Length() == 2);
+ EXPECT_TRUE("MOZ_LOG=modules1"_ns.Equals(results[0]));
+ EXPECT_TRUE("MOZ_LOG=modules2"_ns.Equals(results[1]));
+
+ char const* argv2[] = {"", "-MOZ_LOG", "--MOZ_LOG", "modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback);
+ EXPECT_TRUE(results.Length() == 1);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+
+ char const* argv3[] = {"", "-MOZ_LOG_FILE", "-MOZ_LOG", "modules"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback);
+ EXPECT_TRUE(results.Length() == 1);
+ EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0]));
+
+ char const* argv4[] = {"", "-MOZ_LOG", "-MOZ_LOG_FILE", "-MOZ_LOG"};
+ results.Clear();
+ mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback);
+ EXPECT_TRUE(results.Length() == 0);
+}
diff --git a/xpcom/tests/gtest/TestLogging.cpp b/xpcom/tests/gtest/TestLogging.cpp
new file mode 100644
index 0000000000..0eb2b7a152
--- /dev/null
+++ b/xpcom/tests/gtest/TestLogging.cpp
@@ -0,0 +1,182 @@
+/* -*- 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/Logging.h"
+#include "mozilla/Sprintf.h"
+#include "gtest/gtest.h"
+
+namespace mozilla::detail {
+bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize,
+ uint16_t aLongLineSize);
+}
+
+// These format strings result in 1024 byte lines on disk regardless
+// of OS, which makes various file sizes OS-agnostic.
+#ifdef XP_WIN
+# define WHOLE_LINE "%01022d\n"
+# define SHORT_LINE "%0510d\n"
+#else
+# define WHOLE_LINE "%01023d\n"
+# define SHORT_LINE "%0511d\n"
+#endif
+
+// Write the given number of 1k lines to the given file name.
+void WriteTestLogFile(const char* name, uint32_t numLines) {
+ FILE* f = fopen(name, "w");
+ ASSERT_NE(f, (FILE*)nullptr);
+
+ for (uint32_t i = 0; i < numLines; i++) {
+ char buf[1024 + 1];
+ SprintfLiteral(buf, WHOLE_LINE, i);
+ EXPECT_TRUE(fputs(buf, f) >= 0);
+ }
+
+ uint64_t size = static_cast<uint64_t>(ftell(f));
+
+ // Close before asserting.
+ EXPECT_FALSE(fclose(f));
+
+ ASSERT_EQ(numLines * 1024, size);
+}
+
+// Assert that the given file name has the expected size and that its
+// first line is the expected line.
+void AssertSizeAndFirstLine(const char* name, uint32_t expectedSize,
+ const char* expectedLine) {
+ FILE* f = fopen(name, "r");
+ ASSERT_NE(f, (FILE*)nullptr);
+
+ EXPECT_FALSE(fseek(f, 0, SEEK_END));
+ uint64_t size = static_cast<uint64_t>(ftell(f));
+
+ EXPECT_FALSE(fseek(f, 0, SEEK_SET));
+
+ char line[1024 + 1];
+ const char* result = fgets(line, sizeof(line), f);
+
+ // Close before asserting.
+ EXPECT_FALSE(fclose(f));
+
+ ASSERT_NE(result, nullptr);
+ ASSERT_EQ(expectedSize, size);
+ ASSERT_STREQ(expectedLine, line);
+}
+
+TEST(Logging, DoesNothingWhenNotNeededExact)
+{
+ char nameBuf[2048];
+ SprintfLiteral(
+ nameBuf, "%s_%s.moz_log",
+ testing::UnitTest::GetInstance()->current_test_info()->test_case_name(),
+ testing::UnitTest::GetInstance()->current_test_info()->name());
+
+ WriteTestLogFile(nameBuf, 256);
+
+ // Here the log file is exactly the allowed size. It shouldn't be limited.
+ ASSERT_TRUE(
+ mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024));
+
+ char expectedLine[1024 + 1];
+ SprintfLiteral(expectedLine, WHOLE_LINE, 0);
+
+ AssertSizeAndFirstLine(nameBuf, 256 * 1024, expectedLine);
+
+ EXPECT_FALSE(remove(nameBuf));
+}
+
+TEST(Logging, DoesNothingWhenNotNeededInexact)
+{
+ char nameBuf[2048];
+ SprintfLiteral(
+ nameBuf, "%s_%s.moz_log",
+ testing::UnitTest::GetInstance()->current_test_info()->test_case_name(),
+ testing::UnitTest::GetInstance()->current_test_info()->name());
+
+ WriteTestLogFile(nameBuf, 200);
+
+ // Here the log file is strictly less than the allowed size. It shouldn't be
+ // limited.
+ ASSERT_TRUE(
+ mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024));
+
+ char expectedLine[1024 + 1];
+ SprintfLiteral(expectedLine, WHOLE_LINE, 0);
+
+ AssertSizeAndFirstLine(nameBuf, 200 * 1024, expectedLine);
+
+ EXPECT_FALSE(remove(nameBuf));
+}
+
+TEST(Logging, LimitsToLessThanSize)
+{
+ char nameBuf[2048];
+ SprintfLiteral(
+ nameBuf, "%s_%s.moz_log",
+ testing::UnitTest::GetInstance()->current_test_info()->test_case_name(),
+ testing::UnitTest::GetInstance()->current_test_info()->name());
+
+ WriteTestLogFile(nameBuf, 300);
+
+ ASSERT_TRUE(
+ mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024));
+
+ char expectedLine[1024 + 1];
+ SprintfLiteral(expectedLine, WHOLE_LINE, 300 - 256);
+
+ AssertSizeAndFirstLine(nameBuf, 256 * 1024, expectedLine);
+
+ EXPECT_FALSE(remove(nameBuf));
+}
+
+TEST(Logging, MayCutLongLinesExact)
+{
+ char nameBuf[2048];
+ SprintfLiteral(
+ nameBuf, "%s_%s.moz_log",
+ testing::UnitTest::GetInstance()->current_test_info()->test_case_name(),
+ testing::UnitTest::GetInstance()->current_test_info()->name());
+
+ WriteTestLogFile(nameBuf, 300);
+
+ char expectedLine[1024 + 1];
+
+ ASSERT_TRUE(mozilla::detail::LimitFileToLessThanSize(
+ nameBuf, (256 * 1024) - 512, 512));
+
+ SprintfLiteral(expectedLine, SHORT_LINE, 300 - 256);
+
+ // The line to be cut ends "...044\n." We read 512 bytes (the
+ // buffer size), so we're left with 512 bytes, one of which is the
+ // newline.
+ AssertSizeAndFirstLine(nameBuf, 256 * 1024 - 512, expectedLine);
+
+ EXPECT_FALSE(remove(nameBuf));
+}
+
+TEST(Logging, MayCutLongLinesInexact)
+{
+ char nameBuf[2048];
+ SprintfLiteral(
+ nameBuf, "%s_%s.moz_log",
+ testing::UnitTest::GetInstance()->current_test_info()->test_case_name(),
+ testing::UnitTest::GetInstance()->current_test_info()->name());
+
+ WriteTestLogFile(nameBuf, 300);
+
+ char expectedLine[1024 + 1];
+
+ ASSERT_TRUE(mozilla::detail::LimitFileToLessThanSize(
+ nameBuf, (256 * 1024) - 512, 512));
+
+ SprintfLiteral(expectedLine, SHORT_LINE, 300 - 256);
+
+ // We read 512 bytes (the buffer size), so we're left with 512
+ // bytes, one of which is the newline. Notice that the limited size
+ // is smaller than the requested size.
+ AssertSizeAndFirstLine(nameBuf, 256 * 1024 - 512, expectedLine);
+
+ EXPECT_FALSE(remove(nameBuf));
+}
diff --git a/xpcom/tests/gtest/TestMacNSURLEscaping.mm b/xpcom/tests/gtest/TestMacNSURLEscaping.mm
new file mode 100644
index 0000000000..bbb0c8e2e9
--- /dev/null
+++ b/xpcom/tests/gtest/TestMacNSURLEscaping.mm
@@ -0,0 +1,140 @@
+#include "nsCocoaUtils.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "gtest/gtest.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+//
+// For the macOS File->Share menu, we must create an NSURL. However, NSURL is
+// more strict than the browser about the encoding of URLs it accepts.
+// Therefore additional encoding must be done on a URL before it is used to
+// create an NSURL object. These tests aim to exercise the code used to
+// perform additional encoding on a URL used to create NSURL objects.
+//
+
+// Ensure nsCocoaUtils::ToNSURL() didn't change the URL.
+// Create an NSURL with the provided string and then read the URL out of
+// the NSURL and test it matches the provided string.
+void ExpectUnchangedByNSURL(nsCString& aEncoded) {
+ NSURL* macURL = nsCocoaUtils::ToNSURL(NS_ConvertUTF8toUTF16(aEncoded));
+ NSString* macURLString = [macURL absoluteString];
+
+ nsString geckoURLString;
+ nsCocoaUtils::GetStringForNSString(macURLString, geckoURLString);
+ EXPECT_STREQ(aEncoded.BeginReading(),
+ NS_ConvertUTF16toUTF8(geckoURLString).get());
+}
+
+// Test escaping of URLs to ensure that
+// 1) We escape URLs in such a way that macOS's NSURL code accepts the URL as
+// valid.
+// 2) The encoding encoded the expected characters only.
+// 2) NSURL not modify the URL. Check this by reading the URL back out of the
+// NSURL object and comparing it. If the URL is changed by creating an
+// NSURL, it may indicate the encoding is incorrect.
+//
+// It is not a requirement that NSURL not change the URL, but we don't
+// expect that for these test cases.
+TEST(NSURLEscaping, NSURLEscapingTests)
+{
+ // Per RFC2396, URI "unreserved" characters. These are allowed in URIs and
+ // can be escaped without changing the semantics of the URI, but the escaping
+ // should only be done if the URI is used in a context that requires it.
+ //
+ // "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ //
+ // These are the URI general "reserved" characters. Their reserved purpose
+ // is as delimters so they don't need to be escaped unless used in a URI
+ // component in a way that conflicts with the reserved purpose. i.e.,
+ // whether or not they must be encoded depends on the component.
+ //
+ // ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
+ //
+ // Characters considered excluded from URI use and should be escaped.
+ // "#" when not used to delimit the start of the fragment identifier.
+ // "%" when not used to escape a character.
+ //
+ // "<" | ">" | "#" | "%" | <"> | "{" | "}" | "|" | "\" | "^" | "[" | "]" |
+ // "`"
+
+ // Pairs of URLs of the form (un-encoded, expected encoded result) to verify.
+ nsTArray<std::pair<nsCString, nsCString>> pairs{
+ {// '#' in ref
+ "https://chat.mozilla.org/#/room/#macdev:mozilla.org"_ns,
+ "https://chat.mozilla.org/#/room/%23macdev:mozilla.org"_ns},
+ {
+ // '"' in ref
+ "https://example.com/path#ref_with_#_and\""_ns,
+ "https://example.com/path#ref_with_%23_and%22"_ns,
+ },
+ {
+ // '[]{}|' in ref
+ "https://example.com/path#ref_with_[foo]_{and}_|"_ns,
+ "https://example.com/path#ref_with_%5Bfoo%5D_%7Band%7D_%7C"_ns,
+ },
+ {
+ // Unreserved characters in path, query, and ref
+ "https://example.com/path-_.!~&'(x)?x=y&x=z-_.!~&'(x)#ref-_.!~&'(x)"_ns,
+ "https://example.com/path-_.!~&'(x)?x=y&x=z-_.!~&%27(x)#ref-_.!~&'(x)"_ns,
+ },
+ {
+ // All excluded characters in the ref.
+ "https://example.com/path#ref \"#<>[]\\^`{|}ref"_ns,
+ "https://example.com/path#ref%20%22%23%3C%3E%5B%5D%5C%5E%60%7B%7C%7Dref"_ns,
+ },
+ /*
+ * Bug 1739533:
+ * This test fails because the '%' character needs to be escaped before
+ * the URI is passed to [NSURL URLWithString]. '<' brackets are
+ * already escaped by the browser to their "%xx" form so the encoding must
+ * be added in a way that ignores characters already % encoded "%xx".
+ {
+ // Unreserved characters in path, query, and ref
+ // https://example.com/path/with<more>/%/and"#frag_with_#_and"
+ "https://example.com/path/with<more>/%/and\"#frag_with_#_and\""_ns,
+ "https://example.com/path/with%3Cmore%3E/%25/and%22#frag_with_%23_and%22"_ns,
+ },
+ */
+ };
+
+ for (std::pair<nsCString, nsCString>& pair : pairs) {
+ nsCString escaped;
+ nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, pair.first);
+ EXPECT_EQ(rv, NS_OK);
+ EXPECT_STREQ(pair.second.BeginReading(), escaped.BeginReading());
+ ExpectUnchangedByNSURL(escaped);
+ }
+
+ // A list of URLs that should not be changed by encoding.
+ nsTArray<nsCString> unchangedURLs{
+ // '=' In the query
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854"_ns,
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854#ref"_ns,
+ "https://bugzilla.mozilla.org/allinref#show_bug.cgi?id=1737854ref"_ns,
+ "https://example.com/script?foo=bar#this_ref"_ns,
+ // Escaped character in the ref
+ "https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address"_ns,
+ // Misc query
+ "https://www.google.com/search?q=firefox+web+browser&client=firefox-b-1-d&ei=abc&ved=abc&abc=5&oq=firefox+web+browser&gs_lcp=abc&sclient=gws-wiz"_ns,
+ // Check for double encoding. % encoded octals should not be re-encoded.
+ "https://chat.mozilla.org/#/room/%23macdev%3Amozilla.org"_ns,
+ "https://searchfox.org/mozilla-central/search?q=symbol%3AE_%3CT_mozilla%3A%3AWebGLExtensionID%3E_EXT_color_buffer_half_float&path="_ns,
+ // Unreserved and reserved that don't need encoding in ref.
+ "https://example.com/path#ref!$&'(foo),:;=?@~"_ns,
+ // Unreserved and reserved that don't need encoding in path.
+ "https://example.com/path-_.!~&'(x)#ref"_ns,
+ // Unreserved and reserved that don't need encoding in path and ref.
+ "https://example.com/path-_.!~&'(x)#ref-_.!~&'(x)"_ns,
+ // Reserved in query.
+ "https://example.com/path?a=b&;=/&/=?&@=a+b,$"_ns,
+ };
+
+ for (nsCString& toEscape : unchangedURLs) {
+ nsCString escaped;
+ nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, toEscape);
+ EXPECT_EQ(rv, NS_OK);
+ EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading());
+ ExpectUnchangedByNSURL(escaped);
+ }
+}
diff --git a/xpcom/tests/gtest/TestMemoryPressure.cpp b/xpcom/tests/gtest/TestMemoryPressure.cpp
new file mode 100644
index 0000000000..8435848aa4
--- /dev/null
+++ b/xpcom/tests/gtest/TestMemoryPressure.cpp
@@ -0,0 +1,199 @@
+/* -*- 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 <thread>
+#include "gtest/gtest.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsMemoryPressure.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+namespace {
+
+enum class MemoryPressureEventType : int {
+ LowMemory,
+ LowMemoryOngoing,
+ Stop,
+};
+
+class MemoryPressureObserver final : public nsIObserver {
+ nsCOMPtr<nsIObserverService> mObserverSvc;
+ Vector<MemoryPressureEventType> mEvents;
+
+ ~MemoryPressureObserver() {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(mObserverSvc->RemoveObserver(this, kTopicMemoryPressure)));
+ EXPECT_TRUE(NS_SUCCEEDED(
+ mObserverSvc->RemoveObserver(this, kTopicMemoryPressureStop)));
+ }
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ MemoryPressureObserver()
+ : mObserverSvc(do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) {
+ EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver(
+ this, kTopicMemoryPressure, /* ownsWeak */ false)));
+ EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver(
+ this, kTopicMemoryPressureStop, /* ownsWeak */ false)));
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ Maybe<MemoryPressureEventType> event;
+ if (strcmp(aTopic, kTopicMemoryPressure) == 0) {
+ if (nsDependentString(aData) == kSubTopicLowMemoryNew) {
+ event = Some(MemoryPressureEventType::LowMemory);
+ } else if (nsDependentString(aData) == kSubTopicLowMemoryOngoing) {
+ event = Some(MemoryPressureEventType::LowMemoryOngoing);
+ } else {
+ fprintf(stderr, "Unexpected subtopic: %S\n",
+ reinterpret_cast<const wchar_t*>(aData));
+ EXPECT_TRUE(false);
+ }
+ } else if (strcmp(aTopic, kTopicMemoryPressureStop) == 0) {
+ event = Some(MemoryPressureEventType::Stop);
+ } else {
+ fprintf(stderr, "Unexpected topic: %s\n", aTopic);
+ EXPECT_TRUE(false);
+ }
+
+ if (event) {
+ Unused << mEvents.emplaceBack(event.value());
+ }
+ return NS_OK;
+ }
+
+ uint32_t GetCount() const { return mEvents.length(); }
+ void Reset() { mEvents.clear(); }
+ MemoryPressureEventType Top() const { return mEvents[0]; }
+
+ bool ValidateTransitions() const {
+ if (mEvents.length() == 0) {
+ return true;
+ }
+
+ for (size_t i = 1; i < mEvents.length(); ++i) {
+ MemoryPressureEventType eventFrom = mEvents[i - 1];
+ MemoryPressureEventType eventTo = mEvents[i];
+ if ((eventFrom == MemoryPressureEventType::LowMemory &&
+ eventTo == MemoryPressureEventType::LowMemoryOngoing) ||
+ (eventFrom == MemoryPressureEventType::LowMemoryOngoing &&
+ eventTo == MemoryPressureEventType::LowMemoryOngoing) ||
+ (eventFrom == MemoryPressureEventType::Stop &&
+ eventTo == MemoryPressureEventType::LowMemory) ||
+ (eventFrom == MemoryPressureEventType::LowMemoryOngoing &&
+ eventTo == MemoryPressureEventType::Stop) ||
+ (eventFrom == MemoryPressureEventType::LowMemory &&
+ eventTo == MemoryPressureEventType::Stop)) {
+ // Only these transitions are valid.
+ continue;
+ }
+
+ fprintf(stderr, "Invalid transition: %d -> %d\n",
+ static_cast<int>(eventFrom), static_cast<int>(eventTo));
+ return false;
+ }
+ return true;
+ }
+};
+
+NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver)
+
+template <MemoryPressureState State>
+void PressureSender(Atomic<bool>& aContinue) {
+ while (aContinue) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ NS_NotifyOfEventualMemoryPressure(State);
+ }
+}
+
+template <MemoryPressureState State>
+void PressureSenderQuick(Atomic<bool>& aContinue) {
+ while (aContinue) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ NS_NotifyOfMemoryPressure(State);
+ }
+}
+
+} // anonymous namespace
+
+TEST(MemoryPressure, Singlethread)
+{
+ RefPtr observer(new MemoryPressureObserver);
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 1"_ns,
+ [&observer]() { return observer->GetCount() == 1; });
+ EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemory);
+
+ observer->Reset();
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 2"_ns,
+ [&observer]() { return observer->GetCount() == 1; });
+ EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing);
+
+ observer->Reset();
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory);
+ SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 3"_ns,
+ [&observer]() { return observer->GetCount() == 1; });
+ EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing);
+
+ observer->Reset();
+ NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure);
+ SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 4"_ns,
+ [&observer]() { return observer->GetCount() == 1; });
+ EXPECT_EQ(observer->Top(), MemoryPressureEventType::Stop);
+}
+
+TEST(MemoryPressure, Multithread)
+{
+ // Start |kNumThreads| threads each for the following thread type:
+ // - LowMemory via NS_NotifyOfEventualMemoryPressure
+ // - LowMemory via NS_NotifyOfMemoryPressure
+ // - LowMemoryOngoing via NS_NotifyOfEventualMemoryPressure
+ // - LowMemoryOngoing via NS_NotifyOfMemoryPressure
+ // and keep them running until |kNumEventsToValidate| memory-pressure events
+ // are received.
+ constexpr int kNumThreads = 5;
+ constexpr int kNumEventsToValidate = 200;
+
+ Atomic<bool> shouldContinue(true);
+ Vector<std::thread> threads;
+ for (int i = 0; i < kNumThreads; ++i) {
+ Unused << threads.emplaceBack(
+ PressureSender<MemoryPressureState::LowMemory>,
+ std::ref(shouldContinue));
+ Unused << threads.emplaceBack(
+ PressureSender<MemoryPressureState::NoPressure>,
+ std::ref(shouldContinue));
+ Unused << threads.emplaceBack(
+ PressureSenderQuick<MemoryPressureState::LowMemory>,
+ std::ref(shouldContinue));
+ Unused << threads.emplaceBack(
+ PressureSenderQuick<MemoryPressureState::NoPressure>,
+ std::ref(shouldContinue));
+ }
+
+ RefPtr observer(new MemoryPressureObserver);
+
+ // We cannot sleep here because the main thread needs to keep running.
+ SpinEventLoopUntil(
+ "xpcom:TEST(MemoryPressure, Multithread)"_ns,
+ [&observer]() { return observer->GetCount() >= kNumEventsToValidate; });
+
+ shouldContinue = false;
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ EXPECT_TRUE(observer->ValidateTransitions());
+}
diff --git a/xpcom/tests/gtest/TestMoveString.cpp b/xpcom/tests/gtest/TestMoveString.cpp
new file mode 100644
index 0000000000..cdfacdbbac
--- /dev/null
+++ b/xpcom/tests/gtest/TestMoveString.cpp
@@ -0,0 +1,266 @@
+/* -*- 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 "nsASCIIMask.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsReadableUtils.h"
+#include "nsCRTGlue.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Unused.h"
+#include "gtest/gtest.h"
+
+namespace TestMoveString {
+
+#define NEW_VAL "**new value**"
+#define OLD_VAL "old value"
+
+typedef mozilla::detail::StringDataFlags Df;
+
+static void SetAsOwned(nsACString& aStr, const char* aValue) {
+ size_t len = strlen(aValue);
+ char* data = new char[len + 1];
+ memcpy(data, aValue, len + 1);
+ aStr.Adopt(data, len);
+ EXPECT_EQ(aStr.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+ EXPECT_STREQ(aStr.BeginReading(), aValue);
+}
+
+static void ExpectTruncated(const nsACString& aStr) {
+ EXPECT_EQ(aStr.Length(), uint32_t(0));
+ EXPECT_STREQ(aStr.BeginReading(), "");
+ EXPECT_EQ(aStr.GetDataFlags(), Df::TERMINATED);
+}
+
+static void ExpectNew(const nsACString& aStr) {
+ EXPECT_EQ(aStr.Length(), strlen(NEW_VAL));
+ EXPECT_TRUE(aStr.EqualsASCII(NEW_VAL));
+}
+
+TEST(MoveString, SharedIntoOwned)
+{
+ nsCString out;
+ SetAsOwned(out, OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+
+ nsCString in;
+ in.Assign(NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED);
+ EXPECT_EQ(out.get(), data);
+}
+
+TEST(MoveString, OwnedIntoOwned)
+{
+ nsCString out;
+ SetAsOwned(out, OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+
+ nsCString in;
+ SetAsOwned(in, NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+ EXPECT_EQ(out.get(), data);
+}
+
+TEST(MoveString, LiteralIntoOwned)
+{
+ nsCString out;
+ SetAsOwned(out, OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+
+ nsCString in;
+ in.AssignLiteral(NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::LITERAL | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::LITERAL | Df::TERMINATED);
+ EXPECT_EQ(out.get(), data);
+}
+
+TEST(MoveString, AutoIntoOwned)
+{
+ nsCString out;
+ SetAsOwned(out, OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+
+ nsAutoCString in;
+ in.Assign(NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED);
+ EXPECT_NE(out.get(), data);
+}
+
+TEST(MoveString, DepIntoOwned)
+{
+ nsCString out;
+ SetAsOwned(out, OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+
+ nsDependentCSubstring in(NEW_VAL "garbage after", strlen(NEW_VAL));
+ EXPECT_EQ(in.GetDataFlags(), Df(0));
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED);
+}
+
+TEST(MoveString, VoidIntoOwned)
+{
+ nsCString out;
+ SetAsOwned(out, OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+
+ nsCString in = VoidCString();
+ EXPECT_EQ(in.GetDataFlags(), Df::VOIDED | Df::TERMINATED);
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+
+ EXPECT_EQ(out.Length(), 0u);
+ EXPECT_STREQ(out.get(), "");
+ EXPECT_EQ(out.GetDataFlags(), Df::VOIDED | Df::TERMINATED);
+}
+
+TEST(MoveString, SharedIntoAuto)
+{
+ nsAutoCString out;
+ out.Assign(OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+
+ nsCString in;
+ in.Assign(NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED);
+ EXPECT_EQ(out.get(), data);
+}
+
+TEST(MoveString, OwnedIntoAuto)
+{
+ nsAutoCString out;
+ out.Assign(OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+
+ nsCString in;
+ SetAsOwned(in, NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED);
+ EXPECT_EQ(out.get(), data);
+}
+
+TEST(MoveString, LiteralIntoAuto)
+{
+ nsAutoCString out;
+ out.Assign(OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+
+ nsCString in;
+ in.AssignLiteral(NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::LITERAL | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::LITERAL | Df::TERMINATED);
+ EXPECT_EQ(out.get(), data);
+}
+
+TEST(MoveString, AutoIntoAuto)
+{
+ nsAutoCString out;
+ out.Assign(OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+
+ nsAutoCString in;
+ in.Assign(NEW_VAL);
+ EXPECT_EQ(in.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+ const char* data = in.get();
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+ EXPECT_NE(out.get(), data);
+}
+
+TEST(MoveString, DepIntoAuto)
+{
+ nsAutoCString out;
+ out.Assign(OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+
+ nsDependentCSubstring in(NEW_VAL "garbage after", strlen(NEW_VAL));
+ EXPECT_EQ(in.GetDataFlags(), Df(0));
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+ ExpectNew(out);
+
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+}
+
+TEST(MoveString, VoidIntoAuto)
+{
+ nsAutoCString out;
+ out.Assign(OLD_VAL);
+ EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED);
+
+ nsCString in = VoidCString();
+ EXPECT_EQ(in.GetDataFlags(), Df::VOIDED | Df::TERMINATED);
+
+ out.Assign(std::move(in));
+ ExpectTruncated(in);
+
+ EXPECT_EQ(out.Length(), 0u);
+ EXPECT_STREQ(out.get(), "");
+ EXPECT_EQ(out.GetDataFlags(), Df::VOIDED | Df::TERMINATED);
+}
+
+#undef NEW_VAL
+#undef OLD_VAL
+
+} // namespace TestMoveString
diff --git a/xpcom/tests/gtest/TestMozPromise.cpp b/xpcom/tests/gtest/TestMozPromise.cpp
new file mode 100644
index 0000000000..bb7273cc1f
--- /dev/null
+++ b/xpcom/tests/gtest/TestMozPromise.cpp
@@ -0,0 +1,756 @@
+/* -*- 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 "VideoUtils.h"
+#include "base/message_loop.h"
+#include "gtest/gtest.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+#include "nsISupportsImpl.h"
+
+using namespace mozilla;
+
+typedef MozPromise<int, double, false> TestPromise;
+typedef MozPromise<int, double, true /* exclusive */> TestPromiseExcl;
+typedef TestPromise::ResolveOrRejectValue RRValue;
+
+class MOZ_STACK_CLASS AutoTaskQueue {
+ public:
+ AutoTaskQueue()
+ : mTaskQueue(
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestMozPromise AutoTaskQueue")) {}
+
+ ~AutoTaskQueue() { mTaskQueue->AwaitShutdownAndIdle(); }
+
+ TaskQueue* Queue() { return mTaskQueue; }
+
+ private:
+ RefPtr<TaskQueue> mTaskQueue;
+};
+
+class DelayedResolveOrReject : public Runnable {
+ public:
+ DelayedResolveOrReject(TaskQueue* aTaskQueue, TestPromise::Private* aPromise,
+ const TestPromise::ResolveOrRejectValue& aValue,
+ int aIterations)
+ : mozilla::Runnable("DelayedResolveOrReject"),
+ mTaskQueue(aTaskQueue),
+ mPromise(aPromise),
+ mValue(aValue),
+ mIterations(aIterations) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (!mPromise) {
+ // Canceled.
+ return NS_OK;
+ }
+
+ if (--mIterations == 0) {
+ mPromise->ResolveOrReject(mValue, __func__);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> r = this;
+ return mTaskQueue->Dispatch(r.forget());
+ }
+
+ void Cancel() { mPromise = nullptr; }
+
+ protected:
+ ~DelayedResolveOrReject() = default;
+
+ private:
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<TestPromise::Private> mPromise;
+ TestPromise::ResolveOrRejectValue mValue;
+ int mIterations;
+};
+
+template <typename FunctionType>
+void RunOnTaskQueue(TaskQueue* aQueue, FunctionType aFun) {
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("RunOnTaskQueue", aFun);
+ Unused << aQueue->Dispatch(r.forget());
+}
+
+// std::function can't come soon enough. :-(
+#define DO_FAIL \
+ []() { \
+ EXPECT_TRUE(false); \
+ return TestPromise::CreateAndReject(0, __func__); \
+ }
+
+TEST(MozPromise, BasicResolve)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ TestPromise::CreateAndResolve(42, __func__)
+ ->Then(
+ queue, __func__,
+ [queue](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 42);
+ queue->BeginShutdown();
+ },
+ DO_FAIL);
+ });
+}
+
+TEST(MozPromise, BasicReject)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ TestPromise::CreateAndReject(42.0, __func__)
+ ->Then(queue, __func__, DO_FAIL, [queue](int aRejectValue) -> void {
+ EXPECT_EQ(aRejectValue, 42.0);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, BasicResolveOrRejectResolved)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ TestPromise::CreateAndResolve(42, __func__)
+ ->Then(
+ queue, __func__,
+ [queue](const TestPromise::ResolveOrRejectValue& aValue) -> void {
+ EXPECT_TRUE(aValue.IsResolve());
+ EXPECT_FALSE(aValue.IsReject());
+ EXPECT_FALSE(aValue.IsNothing());
+ EXPECT_EQ(aValue.ResolveValue(), 42);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, BasicResolveOrRejectRejected)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ TestPromise::CreateAndReject(42.0, __func__)
+ ->Then(
+ queue, __func__,
+ [queue](const TestPromise::ResolveOrRejectValue& aValue) -> void {
+ EXPECT_TRUE(aValue.IsReject());
+ EXPECT_FALSE(aValue.IsResolve());
+ EXPECT_FALSE(aValue.IsNothing());
+ EXPECT_EQ(aValue.RejectValue(), 42.0);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, AsyncResolve)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__);
+
+ // Kick off three racing tasks, and make sure we get the one that finishes
+ // earliest.
+ RefPtr<DelayedResolveOrReject> a =
+ new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(32), 10);
+ RefPtr<DelayedResolveOrReject> b =
+ new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(42), 5);
+ RefPtr<DelayedResolveOrReject> c =
+ new DelayedResolveOrReject(queue, p, RRValue::MakeReject(32.0), 7);
+
+ nsCOMPtr<nsIRunnable> ref = a.get();
+ Unused << queue->Dispatch(ref.forget());
+ ref = b.get();
+ Unused << queue->Dispatch(ref.forget());
+ ref = c.get();
+ Unused << queue->Dispatch(ref.forget());
+
+ p->Then(
+ queue, __func__,
+ [queue, a, b, c](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 42);
+ a->Cancel();
+ b->Cancel();
+ c->Cancel();
+ queue->BeginShutdown();
+ },
+ DO_FAIL);
+ });
+}
+
+TEST(MozPromise, CompletionPromises)
+{
+ bool invokedPass = false;
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue, &invokedPass]() -> void {
+ TestPromise::CreateAndResolve(40, __func__)
+ ->Then(
+ queue, __func__,
+ [](int aVal) -> RefPtr<TestPromise> {
+ return TestPromise::CreateAndResolve(aVal + 10, __func__);
+ },
+ DO_FAIL)
+ ->Then(
+ queue, __func__,
+ [&invokedPass](int aVal) {
+ invokedPass = true;
+ return TestPromise::CreateAndResolve(aVal, __func__);
+ },
+ DO_FAIL)
+ ->Then(
+ queue, __func__,
+ [queue](int aVal) -> RefPtr<TestPromise> {
+ RefPtr<TestPromise::Private> p =
+ new TestPromise::Private(__func__);
+ nsCOMPtr<nsIRunnable> resolver = new DelayedResolveOrReject(
+ queue, p, RRValue::MakeResolve(aVal - 8), 10);
+ Unused << queue->Dispatch(resolver.forget());
+ return RefPtr<TestPromise>(p);
+ },
+ DO_FAIL)
+ ->Then(
+ queue, __func__,
+ [](int aVal) -> RefPtr<TestPromise> {
+ return TestPromise::CreateAndReject(double(aVal - 42) + 42.0,
+ __func__);
+ },
+ DO_FAIL)
+ ->Then(queue, __func__, DO_FAIL,
+ [queue, &invokedPass](double aVal) -> void {
+ EXPECT_EQ(aVal, 42.0);
+ EXPECT_TRUE(invokedPass);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, PromiseAllResolve)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ nsTArray<RefPtr<TestPromise>> promises;
+ promises.AppendElement(TestPromise::CreateAndResolve(22, __func__));
+ promises.AppendElement(TestPromise::CreateAndResolve(32, __func__));
+ promises.AppendElement(TestPromise::CreateAndResolve(42, __func__));
+
+ TestPromise::All(queue, promises)
+ ->Then(
+ queue, __func__,
+ [queue](const CopyableTArray<int>& aResolveValues) -> void {
+ EXPECT_EQ(aResolveValues.Length(), 3UL);
+ EXPECT_EQ(aResolveValues[0], 22);
+ EXPECT_EQ(aResolveValues[1], 32);
+ EXPECT_EQ(aResolveValues[2], 42);
+ queue->BeginShutdown();
+ },
+ []() { EXPECT_TRUE(false); });
+ });
+}
+
+TEST(MozPromise, PromiseAllResolveAsync)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ nsTArray<RefPtr<TestPromise>> promises;
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(22, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(32, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(42, __func__);
+ }));
+
+ TestPromise::All(queue, promises)
+ ->Then(
+ queue, __func__,
+ [queue](const CopyableTArray<int>& aResolveValues) -> void {
+ EXPECT_EQ(aResolveValues.Length(), 3UL);
+ EXPECT_EQ(aResolveValues[0], 22);
+ EXPECT_EQ(aResolveValues[1], 32);
+ EXPECT_EQ(aResolveValues[2], 42);
+ queue->BeginShutdown();
+ },
+ []() { EXPECT_TRUE(false); });
+ });
+}
+
+TEST(MozPromise, PromiseAllReject)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ nsTArray<RefPtr<TestPromise>> promises;
+ promises.AppendElement(TestPromise::CreateAndResolve(22, __func__));
+ promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__));
+ promises.AppendElement(TestPromise::CreateAndResolve(42, __func__));
+ // Ensure that more than one rejection doesn't cause a crash (bug #1207312)
+ promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__));
+
+ TestPromise::All(queue, promises)
+ ->Then(
+ queue, __func__, []() { EXPECT_TRUE(false); },
+ [queue](float aRejectValue) -> void {
+ EXPECT_EQ(aRejectValue, 32.0);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, PromiseAllRejectAsync)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ nsTArray<RefPtr<TestPromise>> promises;
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(22, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndReject(32.0, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(42, __func__);
+ }));
+ // Ensure that more than one rejection doesn't cause a crash (bug #1207312)
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndReject(52.0, __func__);
+ }));
+
+ TestPromise::All(queue, promises)
+ ->Then(
+ queue, __func__, []() { EXPECT_TRUE(false); },
+ [queue](float aRejectValue) -> void {
+ EXPECT_EQ(aRejectValue, 32.0);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, PromiseAllSettled)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue]() -> void {
+ nsTArray<RefPtr<TestPromise>> promises;
+ promises.AppendElement(TestPromise::CreateAndResolve(22, __func__));
+ promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__));
+ promises.AppendElement(TestPromise::CreateAndResolve(42, __func__));
+ promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__));
+
+ TestPromise::AllSettled(queue, promises)
+ ->Then(
+ queue, __func__,
+ [queue](const TestPromise::AllSettledPromiseType::ResolveValueType&
+ aResolveValues) -> void {
+ EXPECT_EQ(aResolveValues.Length(), 4UL);
+ EXPECT_TRUE(aResolveValues[0].IsResolve());
+ EXPECT_EQ(aResolveValues[0].ResolveValue(), 22);
+ EXPECT_FALSE(aResolveValues[1].IsResolve());
+ EXPECT_EQ(aResolveValues[1].RejectValue(), 32.0);
+ EXPECT_TRUE(aResolveValues[2].IsResolve());
+ EXPECT_EQ(aResolveValues[2].ResolveValue(), 42);
+ EXPECT_FALSE(aResolveValues[3].IsResolve());
+ EXPECT_EQ(aResolveValues[3].RejectValue(), 52.0);
+ queue->BeginShutdown();
+ },
+ []() { EXPECT_TRUE(false); });
+ });
+}
+
+TEST(MozPromise, PromiseAllSettledAsync)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+
+ RunOnTaskQueue(queue, [queue]() -> void {
+ nsTArray<RefPtr<TestPromise>> promises;
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(22, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndReject(32.0, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndResolve(42, __func__);
+ }));
+ promises.AppendElement(InvokeAsync(queue, __func__, []() {
+ return TestPromise::CreateAndReject(52.0, __func__);
+ }));
+
+ TestPromise::AllSettled(queue, promises)
+ ->Then(
+ queue, __func__,
+ [queue](const TestPromise::AllSettledPromiseType::ResolveValueType&
+ aResolveValues) -> void {
+ EXPECT_EQ(aResolveValues.Length(), 4UL);
+ EXPECT_TRUE(aResolveValues[0].IsResolve());
+ EXPECT_EQ(aResolveValues[0].ResolveValue(), 22);
+ EXPECT_FALSE(aResolveValues[1].IsResolve());
+ EXPECT_EQ(aResolveValues[1].RejectValue(), 32.0);
+ EXPECT_TRUE(aResolveValues[2].IsResolve());
+ EXPECT_EQ(aResolveValues[2].ResolveValue(), 42);
+ EXPECT_FALSE(aResolveValues[3].IsResolve());
+ EXPECT_EQ(aResolveValues[3].RejectValue(), 52.0);
+ queue->BeginShutdown();
+ },
+ []() { EXPECT_TRUE(false); });
+ });
+}
+
+// Test we don't hit the assertions in MozPromise when exercising promise
+// chaining upon task queue shutdown.
+TEST(MozPromise, Chaining)
+{
+ // We declare this variable before |atq| to ensure
+ // the destructor is run after |holder.Disconnect()|.
+ MozPromiseRequestHolder<TestPromise> holder;
+
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+
+ RunOnTaskQueue(queue, [queue, &holder]() {
+ auto p = TestPromise::CreateAndResolve(42, __func__);
+ const size_t kIterations = 100;
+ for (size_t i = 0; i < kIterations; ++i) {
+ p = p->Then(
+ queue, __func__,
+ [](int aVal) {
+ EXPECT_EQ(aVal, 42);
+ return TestPromise::CreateAndResolve(aVal, __func__);
+ },
+ [](double aVal) {
+ return TestPromise::CreateAndReject(aVal, __func__);
+ });
+
+ if (i == kIterations / 2) {
+ p->Then(
+ queue, __func__,
+ [queue, &holder]() {
+ holder.Disconnect();
+ queue->BeginShutdown();
+ },
+ DO_FAIL);
+ }
+ }
+ // We will hit the assertion if we don't disconnect the leaf Request
+ // in the promise chain.
+ p->Then(
+ queue, __func__, []() {}, []() {})
+ ->Track(holder);
+ });
+}
+
+TEST(MozPromise, ResolveOrRejectValue)
+{
+ using MyPromise = MozPromise<UniquePtr<int>, bool, false>;
+ using RRValue = MyPromise::ResolveOrRejectValue;
+
+ RRValue val;
+ EXPECT_TRUE(val.IsNothing());
+ EXPECT_FALSE(val.IsResolve());
+ EXPECT_FALSE(val.IsReject());
+
+ val.SetResolve(MakeUnique<int>(87));
+ EXPECT_FALSE(val.IsNothing());
+ EXPECT_TRUE(val.IsResolve());
+ EXPECT_FALSE(val.IsReject());
+ EXPECT_EQ(87, *val.ResolveValue());
+
+ // IsResolve() should remain true after std::move().
+ UniquePtr<int> i = std::move(val.ResolveValue());
+ EXPECT_EQ(87, *i);
+ EXPECT_TRUE(val.IsResolve());
+ EXPECT_EQ(val.ResolveValue().get(), nullptr);
+}
+
+TEST(MozPromise, MoveOnlyType)
+{
+ using MyPromise = MozPromise<UniquePtr<int>, bool, true>;
+ using RRValue = MyPromise::ResolveOrRejectValue;
+
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+
+ MyPromise::CreateAndResolve(MakeUnique<int>(87), __func__)
+ ->Then(
+ queue, __func__, [](UniquePtr<int> aVal) { EXPECT_EQ(87, *aVal); },
+ []() { EXPECT_TRUE(false); });
+
+ MyPromise::CreateAndResolve(MakeUnique<int>(87), __func__)
+ ->Then(queue, __func__, [queue](RRValue&& aVal) {
+ EXPECT_FALSE(aVal.IsNothing());
+ EXPECT_TRUE(aVal.IsResolve());
+ EXPECT_FALSE(aVal.IsReject());
+ EXPECT_EQ(87, *aVal.ResolveValue());
+
+ // std::move() shouldn't change the resolve/reject state of aVal.
+ RRValue val = std::move(aVal);
+ EXPECT_TRUE(aVal.IsResolve());
+ EXPECT_EQ(nullptr, aVal.ResolveValue().get());
+ EXPECT_EQ(87, *val.ResolveValue());
+
+ queue->BeginShutdown();
+ });
+}
+
+TEST(MozPromise, HeterogeneousChaining)
+{
+ using Promise1 = MozPromise<UniquePtr<char>, bool, true>;
+ using Promise2 = MozPromise<UniquePtr<int>, bool, true>;
+ using RRValue1 = Promise1::ResolveOrRejectValue;
+ using RRValue2 = Promise2::ResolveOrRejectValue;
+
+ MozPromiseRequestHolder<Promise2> holder;
+
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+
+ RunOnTaskQueue(queue, [queue, &holder]() {
+ Promise1::CreateAndResolve(MakeUnique<char>(0), __func__)
+ ->Then(queue, __func__,
+ [&holder]() {
+ holder.Disconnect();
+ return Promise2::CreateAndResolve(MakeUnique<int>(0),
+ __func__);
+ })
+ ->Then(queue, __func__,
+ []() {
+ // Shouldn't be called for we've disconnected the request.
+ EXPECT_FALSE(true);
+ })
+ ->Track(holder);
+ });
+
+ Promise1::CreateAndResolve(MakeUnique<char>(87), __func__)
+ ->Then(
+ queue, __func__,
+ [](UniquePtr<char> aVal) {
+ EXPECT_EQ(87, *aVal);
+ return Promise2::CreateAndResolve(MakeUnique<int>(94), __func__);
+ },
+ []() {
+ return Promise2::CreateAndResolve(MakeUnique<int>(95), __func__);
+ })
+ ->Then(
+ queue, __func__, [](UniquePtr<int> aVal) { EXPECT_EQ(94, *aVal); },
+ []() { EXPECT_FALSE(true); });
+
+ Promise1::CreateAndResolve(MakeUnique<char>(87), __func__)
+ ->Then(queue, __func__,
+ [](RRValue1&& aVal) {
+ EXPECT_EQ(87, *aVal.ResolveValue());
+ return Promise2::CreateAndResolve(MakeUnique<int>(94), __func__);
+ })
+ ->Then(queue, __func__, [queue](RRValue2&& aVal) {
+ EXPECT_EQ(94, *aVal.ResolveValue());
+ queue->BeginShutdown();
+ });
+}
+
+TEST(MozPromise, XPCOMEventTarget)
+{
+ TestPromise::CreateAndResolve(42, __func__)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); },
+ DO_FAIL);
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+TEST(MozPromise, MessageLoopEventTarget)
+{
+ TestPromise::CreateAndResolve(42, __func__)
+ ->Then(
+ MessageLoop::current()->SerialEventTarget(), __func__,
+ [](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); },
+ DO_FAIL);
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+TEST(MozPromise, ChainTo)
+{
+ RefPtr<TestPromise> promise1 = TestPromise::CreateAndResolve(42, __func__);
+ RefPtr<TestPromise::Private> promise2 = new TestPromise::Private(__func__);
+ promise2->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); },
+ DO_FAIL);
+
+ promise1->ChainTo(promise2.forget(), __func__);
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+TEST(MozPromise, SynchronousTaskDispatch1)
+{
+ bool value = false;
+ RefPtr<TestPromiseExcl::Private> promise =
+ new TestPromiseExcl::Private(__func__);
+ promise->UseSynchronousTaskDispatch(__func__);
+ promise->Resolve(42, __func__);
+ EXPECT_EQ(value, false);
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 42);
+ value = true;
+ },
+ DO_FAIL);
+ EXPECT_EQ(value, true);
+}
+
+TEST(MozPromise, SynchronousTaskDispatch2)
+{
+ bool value = false;
+ RefPtr<TestPromiseExcl::Private> promise =
+ new TestPromiseExcl::Private(__func__);
+ promise->UseSynchronousTaskDispatch(__func__);
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 42);
+ value = true;
+ },
+ DO_FAIL);
+ EXPECT_EQ(value, false);
+ promise->Resolve(42, __func__);
+ EXPECT_EQ(value, true);
+}
+
+TEST(MozPromise, DirectTaskDispatch)
+{
+ bool value1 = false;
+ bool value2 = false;
+
+ // For direct task dispatch to be working, we must be within a
+ // nested event loop. So the test itself must be dispatched within
+ // a task.
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() {
+ GetCurrentSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction("test", [&]() {
+ EXPECT_EQ(value1, true);
+ value2 = true;
+ }));
+
+ RefPtr<TestPromise::Private> promise = new TestPromise::Private(__func__);
+ promise->UseDirectTaskDispatch(__func__);
+ promise->Resolve(42, __func__);
+ EXPECT_EQ(value1, false);
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 42);
+ EXPECT_EQ(value2, false);
+ value1 = true;
+ },
+ DO_FAIL);
+ EXPECT_EQ(value1, false);
+ }));
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+TEST(MozPromise, ChainedDirectTaskDispatch)
+{
+ bool value1 = false;
+ bool value2 = false;
+
+ // For direct task dispatch to be working, we must be within a
+ // nested event loop. So the test itself must be dispatched within
+ // a task.
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() {
+ GetCurrentSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction("test", [&]() {
+ EXPECT_EQ(value1, true);
+ value2 = true;
+ }));
+
+ RefPtr<TestPromise::Private> promise1 = new TestPromise::Private(__func__);
+ promise1->UseDirectTaskDispatch(__func__);
+ promise1->Resolve(42, __func__);
+ EXPECT_EQ(value1, false);
+ promise1
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> RefPtr<TestPromise> {
+ EXPECT_EQ(aResolveValue, 42);
+ EXPECT_EQ(value2, false);
+ RefPtr<TestPromise::Private> promise2 =
+ new TestPromise::Private(__func__);
+ promise2->UseDirectTaskDispatch(__func__);
+ promise2->Resolve(43, __func__);
+ return promise2;
+ },
+ DO_FAIL)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 43);
+ EXPECT_EQ(value2, false);
+ value1 = true;
+ },
+ DO_FAIL);
+ EXPECT_EQ(value1, false);
+ }));
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+TEST(MozPromise, ChainToDirectTaskDispatch)
+{
+ bool value1 = false;
+ bool value2 = false;
+
+ // For direct task dispatch to be working, we must be within a
+ // nested event loop. So the test itself must be dispatched within
+ // a task.
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() {
+ GetCurrentSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction("test", [&]() {
+ EXPECT_EQ(value1, true);
+ value2 = true;
+ }));
+
+ RefPtr<TestPromise::Private> promise1 = new TestPromise::Private(__func__);
+ promise1->UseDirectTaskDispatch(__func__);
+
+ RefPtr<TestPromise::Private> promise2 = new TestPromise::Private(__func__);
+ promise2->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](int aResolveValue) -> void {
+ EXPECT_EQ(aResolveValue, 42);
+ EXPECT_EQ(value2, false);
+ value1 = true;
+ },
+ DO_FAIL);
+
+ promise1->ChainTo(promise2.forget(), __func__);
+ EXPECT_EQ(value1, false);
+ promise1->Resolve(42, __func__);
+ }));
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+#undef DO_FAIL
diff --git a/xpcom/tests/gtest/TestMruCache.cpp b/xpcom/tests/gtest/TestMruCache.cpp
new file mode 100644
index 0000000000..8cf97408d1
--- /dev/null
+++ b/xpcom/tests/gtest/TestMruCache.cpp
@@ -0,0 +1,395 @@
+/* -*- 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/MruCache.h"
+#include "nsString.h"
+
+using namespace mozilla;
+
+// A few MruCache implementations to use during testing.
+struct IntMap : public MruCache<int, int, IntMap> {
+ static HashNumber Hash(const KeyType& aKey) { return aKey - 1; }
+ static bool Match(const KeyType& aKey, const ValueType& aVal) {
+ return aKey == aVal;
+ }
+};
+
+struct UintPtrMap : public MruCache<uintptr_t, int*, UintPtrMap> {
+ static HashNumber Hash(const KeyType& aKey) { return aKey - 1; }
+ static bool Match(const KeyType& aKey, const ValueType& aVal) {
+ return aKey == (KeyType)aVal;
+ }
+};
+
+struct StringStruct {
+ nsCString mKey;
+ nsCString mOther;
+};
+
+struct StringStructMap
+ : public MruCache<nsCString, StringStruct, StringStructMap> {
+ static HashNumber Hash(const KeyType& aKey) {
+ return *aKey.BeginReading() - 1;
+ }
+ static bool Match(const KeyType& aKey, const ValueType& aVal) {
+ return aKey == aVal.mKey;
+ }
+};
+
+// Helper for emulating convertable holders such as RefPtr.
+template <typename T>
+struct Convertable {
+ T mItem;
+ operator T() const { return mItem; }
+};
+
+// Helper to create a StringStructMap key.
+static nsCString MakeStringKey(char aKey) {
+ nsCString key;
+ key.Append(aKey);
+ return key;
+}
+
+TEST(MruCache, TestNullChecker)
+{
+ using mozilla::detail::EmptyChecker;
+
+ {
+ int test = 0;
+ EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
+
+ test = 42;
+ EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
+ }
+
+ {
+ const char* test = "abc";
+ EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
+
+ test = nullptr;
+ EXPECT_FALSE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
+ }
+
+ {
+ int foo = 42;
+ int* test = &foo;
+ EXPECT_TRUE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
+
+ test = nullptr;
+ EXPECT_FALSE(EmptyChecker<decltype(test)>::IsNotEmpty(test));
+ }
+}
+
+TEST(MruCache, TestEmptyCache)
+{
+ {
+ // Test a basic empty cache.
+ IntMap mru;
+
+ // Make sure the default values are set.
+ for (int i = 1; i < 32; i++) {
+ auto p = mru.Lookup(i);
+
+ // Shouldn't be found.
+ EXPECT_FALSE(p);
+ }
+ }
+
+ {
+ // Test an empty cache with pointer values.
+ UintPtrMap mru;
+
+ // Make sure the default values are set.
+ for (uintptr_t i = 1; i < 32; i++) {
+ auto p = mru.Lookup(i);
+
+ // Shouldn't be found.
+ EXPECT_FALSE(p);
+ }
+ }
+
+ {
+ // Test an empty cache with more complex structure.
+ StringStructMap mru;
+
+ // Make sure the default values are set.
+ for (char i = 1; i < 32; i++) {
+ const nsCString key = MakeStringKey(i);
+ auto p = mru.Lookup(key);
+
+ // Shouldn't be found.
+ EXPECT_FALSE(p);
+ }
+ }
+}
+
+TEST(MruCache, TestPut)
+{
+ IntMap mru;
+
+ // Fill it up.
+ for (int i = 1; i < 32; i++) {
+ mru.Put(i, i);
+ }
+
+ // Now check each value.
+ for (int i = 1; i < 32; i++) {
+ auto p = mru.Lookup(i);
+
+ // Should be found.
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), i);
+ }
+}
+
+TEST(MruCache, TestPutConvertable)
+{
+ UintPtrMap mru;
+
+ // Fill it up.
+ for (uintptr_t i = 1; i < 32; i++) {
+ Convertable<int*> val{(int*)i};
+ mru.Put(i, val);
+ }
+
+ // Now check each value.
+ for (uintptr_t i = 1; i < 32; i++) {
+ auto p = mru.Lookup(i);
+
+ // Should be found.
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), (int*)i);
+ }
+}
+
+TEST(MruCache, TestOverwriting)
+{
+ // Test overwrting
+ IntMap mru;
+
+ // 1-31 should be overwritten by 32-63
+ for (int i = 1; i < 63; i++) {
+ mru.Put(i, i);
+ }
+
+ // Look them up.
+ for (int i = 32; i < 63; i++) {
+ auto p = mru.Lookup(i);
+
+ // Should be found.
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), i);
+ }
+}
+
+TEST(MruCache, TestRemove)
+{
+ {
+ IntMap mru;
+
+ // Fill it up.
+ for (int i = 1; i < 32; i++) {
+ mru.Put(i, i);
+ }
+
+ // Now remove each value.
+ for (int i = 1; i < 32; i++) {
+ // Should be present.
+ auto p = mru.Lookup(i);
+ EXPECT_TRUE(p);
+
+ mru.Remove(i);
+
+ // Should no longer match.
+ p = mru.Lookup(i);
+ EXPECT_FALSE(p);
+ }
+ }
+
+ {
+ UintPtrMap mru;
+
+ // Fill it up.
+ for (uintptr_t i = 1; i < 32; i++) {
+ mru.Put(i, (int*)i);
+ }
+
+ // Now remove each value.
+ for (uintptr_t i = 1; i < 32; i++) {
+ // Should be present.
+ auto p = mru.Lookup(i);
+ EXPECT_TRUE(p);
+
+ mru.Remove(i);
+
+ // Should no longer match.
+ p = mru.Lookup(i);
+ EXPECT_FALSE(p);
+ }
+ }
+
+ {
+ StringStructMap mru;
+
+ // Fill it up.
+ for (char i = 1; i < 32; i++) {
+ const nsCString key = MakeStringKey(i);
+ mru.Put(key, StringStruct{key, "foo"_ns});
+ }
+
+ // Now remove each value.
+ for (char i = 1; i < 32; i++) {
+ const nsCString key = MakeStringKey(i);
+
+ // Should be present.
+ auto p = mru.Lookup(key);
+ EXPECT_TRUE(p);
+
+ mru.Remove(key);
+
+ // Should no longer match.
+ p = mru.Lookup(key);
+ EXPECT_FALSE(p);
+ }
+ }
+}
+
+TEST(MruCache, TestClear)
+{
+ IntMap mru;
+
+ // Fill it up.
+ for (int i = 1; i < 32; i++) {
+ mru.Put(i, i);
+ }
+
+ // Empty it.
+ mru.Clear();
+
+ // Now check each value.
+ for (int i = 1; i < 32; i++) {
+ auto p = mru.Lookup(i);
+
+ // Should not be found.
+ EXPECT_FALSE(p);
+ }
+}
+
+TEST(MruCache, TestLookupMissingAndSet)
+{
+ IntMap mru;
+
+ // Value not found.
+ auto p = mru.Lookup(1);
+ EXPECT_FALSE(p);
+
+ // Set it.
+ p.Set(1);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 1);
+
+ // Look it up again.
+ p = mru.Lookup(1);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 1);
+
+ // Test w/ a convertable value.
+ p = mru.Lookup(2);
+ EXPECT_FALSE(p);
+
+ // Set it.
+ Convertable<int> val{2};
+ p.Set(val);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 2);
+
+ // Look it up again.
+ p = mru.Lookup(2);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 2);
+}
+
+TEST(MruCache, TestLookupAndOverwrite)
+{
+ IntMap mru;
+
+ // Set 1.
+ mru.Put(1, 1);
+
+ // Lookup a key that maps the 1's entry.
+ auto p = mru.Lookup(32);
+ EXPECT_FALSE(p); // not a match
+
+ // Now overwrite the entry.
+ p.Set(32);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 32);
+
+ // 1 should be gone now.
+ p = mru.Lookup(1);
+ EXPECT_FALSE(p);
+
+ // 32 should be found.
+ p = mru.Lookup(32);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 32);
+}
+
+TEST(MruCache, TestLookupAndRemove)
+{
+ IntMap mru;
+
+ // Set 1.
+ mru.Put(1, 1);
+
+ auto p = mru.Lookup(1);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 1);
+
+ // Now remove it.
+ p.Remove();
+ EXPECT_FALSE(p);
+
+ p = mru.Lookup(1);
+ EXPECT_FALSE(p);
+}
+
+TEST(MruCache, TestLookupNotMatchedAndRemove)
+{
+ IntMap mru;
+
+ // Set 1.
+ mru.Put(1, 1);
+
+ // Lookup a key that matches 1's entry.
+ auto p = mru.Lookup(32);
+ EXPECT_FALSE(p);
+
+ // Now attempt to remove it.
+ p.Remove();
+
+ // Make sure 1 is still there.
+ p = mru.Lookup(1);
+ EXPECT_TRUE(p);
+ EXPECT_EQ(p.Data(), 1);
+}
+
+TEST(MruCache, TestLookupAndSetWithMove)
+{
+ StringStructMap mru;
+
+ const nsCString key = MakeStringKey((char)1);
+ StringStruct val{key, "foo"_ns};
+
+ auto p = mru.Lookup(key);
+ EXPECT_FALSE(p);
+ p.Set(std::move(val));
+
+ EXPECT_TRUE(p.Data().mKey == key);
+ EXPECT_TRUE(p.Data().mOther == "foo"_ns);
+}
diff --git a/xpcom/tests/gtest/TestMultiplexInputStream.cpp b/xpcom/tests/gtest/TestMultiplexInputStream.cpp
new file mode 100644
index 0000000000..7c422b068b
--- /dev/null
+++ b/xpcom/tests/gtest/TestMultiplexInputStream.cpp
@@ -0,0 +1,958 @@
+/* -*- 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/gtest/MozAssertions.h"
+#include "mozilla/ipc/DataPipe.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIAsyncInputStream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIPipe.h"
+#include "nsISeekableStream.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIThread.h"
+#include "Helpers.h"
+
+using mozilla::GetCurrentSerialEventTarget;
+using mozilla::SpinEventLoopUntil;
+
+TEST(MultiplexInputStream, Seek_SET)
+{
+ nsCString buf1;
+ nsCString buf2;
+ nsCString buf3;
+ buf1.AssignLiteral("Hello world");
+ buf2.AssignLiteral("The quick brown fox jumped over the lazy dog");
+ buf3.AssignLiteral("Foo bar");
+
+ nsCOMPtr<nsIInputStream> inputStream1;
+ nsCOMPtr<nsIInputStream> inputStream2;
+ nsCOMPtr<nsIInputStream> inputStream3;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(multiplexStream);
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ ASSERT_TRUE(stream);
+
+ rv = multiplexStream->AppendStream(inputStream1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = multiplexStream->AppendStream(inputStream2);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = multiplexStream->AppendStream(inputStream3);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ int64_t tell;
+ uint64_t length;
+ uint32_t count;
+ char readBuf[4096];
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(seekStream);
+
+ // Seek forward in first input stream
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1,
+ length);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 1);
+
+ // Check read is correct
+ rv = stream->Read(readBuf, 3, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)3, count);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4,
+ length);
+ ASSERT_EQ(0, strncmp(readBuf, "ell", count));
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 4);
+
+ // Seek to start of third input stream
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET,
+ buf1.Length() + buf2.Length());
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf3.Length(), length);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length()));
+
+ // Check read is correct
+ rv = stream->Read(readBuf, 5, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)5, count);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf3.Length() - 5, length);
+ ASSERT_EQ(0, strncmp(readBuf, "Foo b", count));
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + 5));
+
+ // Seek back to start of second stream (covers bug 1272371)
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, buf1.Length());
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf2.Length() + buf3.Length(), length);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, int64_t(buf1.Length()));
+
+ // Check read is correct
+ rv = stream->Read(readBuf, 6, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)6, count);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf2.Length() - 6 + buf3.Length(), length);
+ ASSERT_EQ(0, strncmp(readBuf, "The qu", count));
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, int64_t(buf1.Length() + 6));
+}
+
+TEST(MultiplexInputStream, Seek_CUR)
+{
+ nsCString buf1;
+ nsCString buf2;
+ nsCString buf3;
+ buf1.AssignLiteral("Hello world");
+ buf2.AssignLiteral("The quick brown fox jumped over the lazy dog");
+ buf3.AssignLiteral("Foo bar");
+
+ nsCOMPtr<nsIInputStream> inputStream1;
+ nsCOMPtr<nsIInputStream> inputStream2;
+ nsCOMPtr<nsIInputStream> inputStream3;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(multiplexStream);
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ ASSERT_TRUE(stream);
+
+ rv = multiplexStream->AppendStream(inputStream1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = multiplexStream->AppendStream(inputStream2);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = multiplexStream->AppendStream(inputStream3);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ int64_t tell;
+ uint64_t length;
+ uint32_t count;
+ char readBuf[4096];
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(seekStream);
+
+ // Seek forward in first input stream
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1,
+ length);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 1);
+
+ // Check read is correct
+ rv = stream->Read(readBuf, 3, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)3, count);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4,
+ length);
+ ASSERT_EQ(0, strncmp(readBuf, "ell", count));
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 4);
+
+ // Let's go to the second stream
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 11);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 15);
+ rv = stream->Read(readBuf, 3, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)3, count);
+ ASSERT_EQ(0, strncmp(readBuf, "qui", count));
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 18);
+
+ // Let's go back to the first stream
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, -9);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 9);
+ rv = stream->Read(readBuf, 3, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)3, count);
+ ASSERT_EQ(0, strncmp(readBuf, "ldT", count));
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 12);
+}
+
+TEST(MultiplexInputStream, Seek_END)
+{
+ nsCString buf1;
+ nsCString buf2;
+ nsCString buf3;
+ buf1.AssignLiteral("Hello world");
+ buf2.AssignLiteral("The quick brown fox jumped over the lazy dog");
+ buf3.AssignLiteral("Foo bar");
+
+ nsCOMPtr<nsIInputStream> inputStream1;
+ nsCOMPtr<nsIInputStream> inputStream2;
+ nsCOMPtr<nsIInputStream> inputStream3;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_TRUE(multiplexStream);
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ ASSERT_TRUE(stream);
+
+ rv = multiplexStream->AppendStream(inputStream1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = multiplexStream->AppendStream(inputStream2);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = multiplexStream->AppendStream(inputStream3);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ int64_t tell;
+ uint64_t length;
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(seekStream);
+
+ // SEEK_END wants <=0 values
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 1);
+ ASSERT_NS_FAILED(rv);
+
+ // Let's go to the end.
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)0, length);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + buf3.Length()));
+
+ // -1
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, -1);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ((uint64_t)1, length);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + buf3.Length() - 1));
+
+ // Almost at the beginning
+ tell = 1;
+ tell -= buf1.Length();
+ tell -= buf2.Length();
+ tell -= buf3.Length();
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->Available(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(length, buf1.Length() + buf2.Length() + buf3.Length() - 1);
+ rv = seekStream->Tell(&tell);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(tell, 1);
+}
+
+static already_AddRefed<nsIInputStream> CreateStreamHelper() {
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+ nsCString buf1;
+ buf1.AssignLiteral("Hello");
+
+ nsCOMPtr<nsIInputStream> inputStream1 = new testing::AsyncStringStream(buf1);
+ multiplexStream->AppendStream(inputStream1);
+
+ nsCString buf2;
+ buf2.AssignLiteral(" ");
+
+ nsCOMPtr<nsIInputStream> inputStream2 = new testing::AsyncStringStream(buf2);
+ multiplexStream->AppendStream(inputStream2);
+
+ nsCString buf3;
+ buf3.AssignLiteral("World!");
+
+ nsCOMPtr<nsIInputStream> inputStream3 = new testing::AsyncStringStream(buf3);
+ multiplexStream->AppendStream(inputStream3);
+
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ return stream.forget();
+}
+
+// AsyncWait - without EventTarget
+TEST(MultiplexInputStream, AsyncWait_withoutEventTarget)
+{
+ nsCOMPtr<nsIInputStream> is = CreateStreamHelper();
+
+ nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is);
+ ASSERT_TRUE(!!ais);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, nullptr));
+ ASSERT_TRUE(cb->Called());
+}
+
+// AsyncWait - with EventTarget
+TEST(MultiplexInputStream, AsyncWait_withEventTarget)
+{
+ nsCOMPtr<nsIInputStream> is = CreateStreamHelper();
+
+ nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is);
+ ASSERT_TRUE(!!ais);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, thread));
+
+ ASSERT_FALSE(cb->Called());
+
+ // Eventually it is called.
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "xpcom:TEST(MultiplexInputStream, AsyncWait_withEventTarget)"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+// AsyncWait - without EventTarget - closureOnly
+TEST(MultiplexInputStream, AsyncWait_withoutEventTarget_closureOnly)
+{
+ nsCOMPtr<nsIInputStream> is = CreateStreamHelper();
+
+ nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is);
+ ASSERT_TRUE(!!ais);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
+ nullptr));
+ ASSERT_FALSE(cb->Called());
+
+ ais->CloseWithStatus(NS_ERROR_FAILURE);
+ ASSERT_TRUE(cb->Called());
+}
+
+// AsyncWait - with EventTarget - closureOnly
+TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly)
+{
+ nsCOMPtr<nsIInputStream> is = CreateStreamHelper();
+
+ nsCOMPtr<nsIAsyncInputStream> ais = do_QueryInterface(is);
+ ASSERT_TRUE(!!ais);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
+ thread));
+
+ ASSERT_FALSE(cb->Called());
+ ais->CloseWithStatus(NS_ERROR_FAILURE);
+ ASSERT_FALSE(cb->Called());
+
+ // Eventually it is called.
+ MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil(
+ "xpcom:TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly)"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+class ClosedStream final : public nsIInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ ClosedStream() = default;
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; }
+
+ NS_IMETHOD
+ StreamStatus() override { return NS_BASE_STREAM_CLOSED; }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override {
+ MOZ_CRASH("This should not be called!");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD
+ Close() override { return NS_OK; }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = true;
+ return NS_OK;
+ }
+
+ private:
+ ~ClosedStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(ClosedStream, nsIInputStream)
+
+class AsyncStream final : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit AsyncStream(int64_t aSize) : mState(eBlocked), mSize(aSize) {}
+
+ void Unblock() { mState = eUnblocked; }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override {
+ *aLength = mState == eBlocked ? 0 : mSize;
+ return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+ }
+
+ NS_IMETHOD
+ StreamStatus() override {
+ return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+ }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override {
+ MOZ_CRASH("This should not be called!");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD
+ Close() override {
+ mState = eClosed;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = true;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override {
+ MOZ_CRASH("This should not be called!");
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ CloseWithStatus(nsresult aStatus) override { return NS_OK; }
+
+ private:
+ ~AsyncStream() = default;
+
+ enum { eBlocked, eUnblocked, eClosed } mState;
+
+ uint64_t mSize;
+};
+
+NS_IMPL_ISUPPORTS(AsyncStream, nsIInputStream, nsIAsyncInputStream)
+
+class BlockingStream final : public nsIInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ BlockingStream() = default;
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; }
+
+ NS_IMETHOD
+ StreamStatus() override { return NS_BASE_STREAM_CLOSED; }
+
+ NS_IMETHOD
+ Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override {
+ // We are actually empty.
+ *aReadCount = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD
+ Close() override { return NS_OK; }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = false;
+ return NS_OK;
+ }
+
+ private:
+ ~BlockingStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(BlockingStream, nsIInputStream)
+
+TEST(MultiplexInputStream, Available)
+{
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+ nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!s);
+
+ nsCOMPtr<nsIAsyncInputStream> as = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!as);
+
+ uint64_t length;
+
+ // The stream returns NS_BASE_STREAM_CLOSED if there are no substreams.
+ nsresult rv = s->Available(&length);
+ ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv);
+
+ rv = multiplexStream->AppendStream(new ClosedStream());
+ ASSERT_EQ(NS_OK, rv);
+
+ uint64_t asyncSize = 2;
+ RefPtr<AsyncStream> asyncStream = new AsyncStream(2);
+ rv = multiplexStream->AppendStream(asyncStream);
+ ASSERT_EQ(NS_OK, rv);
+
+ nsCString buffer;
+ buffer.Assign("World!!!");
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), buffer);
+ ASSERT_EQ(NS_OK, rv);
+
+ rv = multiplexStream->AppendStream(stringStream);
+ ASSERT_EQ(NS_OK, rv);
+
+ // Now we are async.
+ as = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!as);
+
+ // Available should skip the closed stream and return 0 because the
+ // asyncStream returns 0 and it's async.
+ rv = s->Available(&length);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ((uint64_t)0, length);
+
+ asyncStream->Unblock();
+
+ // Now we should return only the size of the async stream because we don't
+ // know when this is completed.
+ rv = s->Available(&length);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(asyncSize, length);
+
+ asyncStream->Close();
+
+ rv = s->Available(&length);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(buffer.Length(), length);
+}
+
+class NonBufferableStringStream final : public nsIInputStream {
+ nsCOMPtr<nsIInputStream> mStream;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonBufferableStringStream(const nsACString& aBuffer) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return mStream->Available(aLength); }
+
+ NS_IMETHOD
+ StreamStatus() override { return mStream->StreamStatus(); }
+
+ 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 NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD
+ Close() override { return mStream->Close(); }
+
+ NS_IMETHOD
+ IsNonBlocking(bool* aNonBlocking) override {
+ return mStream->IsNonBlocking(aNonBlocking);
+ }
+
+ private:
+ ~NonBufferableStringStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(NonBufferableStringStream, nsIInputStream)
+
+TEST(MultiplexInputStream, Bufferable)
+{
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+ nsCOMPtr<nsIInputStream> s = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!s);
+
+ nsCString buf1;
+ buf1.AssignLiteral("Hello ");
+ nsCOMPtr<nsIInputStream> inputStream1;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCString buf2;
+ buf2.AssignLiteral("world");
+ nsCOMPtr<nsIInputStream> inputStream2 = new NonBufferableStringStream(buf2);
+
+ rv = multiplexStream->AppendStream(inputStream1);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = multiplexStream->AppendStream(inputStream2);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multiplexStream));
+ ASSERT_TRUE(!!stream);
+
+ char buf3[1024];
+ uint32_t size = 0;
+ rv = stream->ReadSegments(NS_CopySegmentToBuffer, buf3, sizeof(buf3), &size);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_EQ(size, buf1.Length() + buf2.Length());
+ ASSERT_TRUE(!strncmp(buf3, "Hello world", size));
+}
+
+TEST(MultiplexInputStream, QILengthInputStream)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+ // nsMultiplexInputStream doesn't expose nsIInputStreamLength if there are
+ // no nsIInputStreamLength sub streams.
+ {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = multiplexStream->AppendStream(inputStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!fsis);
+
+ nsCOMPtr<nsIAsyncInputStreamLength> afsis =
+ do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!afsis);
+ }
+
+ // nsMultiplexInputStream exposes nsIInputStreamLength if there is one or
+ // more nsIInputStreamLength sub streams.
+ {
+ RefPtr<testing::LengthInputStream> inputStream =
+ new testing::LengthInputStream(buf, true, false);
+
+ nsresult rv = multiplexStream->AppendStream(inputStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!fsis);
+
+ nsCOMPtr<nsIAsyncInputStreamLength> afsis =
+ do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!afsis);
+ }
+
+ // nsMultiplexInputStream exposes nsIAsyncInputStreamLength if there is one
+ // or more nsIAsyncInputStreamLength sub streams.
+ {
+ RefPtr<testing::LengthInputStream> inputStream =
+ new testing::LengthInputStream(buf, true, true);
+
+ nsresult rv = multiplexStream->AppendStream(inputStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!fsis);
+
+ nsCOMPtr<nsIAsyncInputStreamLength> afsis =
+ do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!afsis);
+ }
+}
+
+TEST(MultiplexInputStream, LengthInputStream)
+{
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+ // First stream is a a simple one.
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = multiplexStream->AppendStream(inputStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // A LengthInputStream, non-async.
+ RefPtr<testing::LengthInputStream> lengthStream =
+ new testing::LengthInputStream(buf, true, false);
+
+ rv = multiplexStream->AppendStream(lengthStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIInputStreamLength> fsis = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!fsis);
+
+ // Size is the sum of the 2 streams.
+ int64_t length;
+ rv = fsis->Length(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(int64_t(buf.Length() * 2), length);
+
+ // An async LengthInputStream.
+ RefPtr<testing::LengthInputStream> asyncLengthStream =
+ new testing::LengthInputStream(buf, true, true,
+ NS_BASE_STREAM_WOULD_BLOCK);
+
+ rv = multiplexStream->AppendStream(asyncLengthStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIAsyncInputStreamLength> afsis =
+ do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!afsis);
+
+ // Now it would block.
+ rv = fsis->Length(&length);
+ ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv);
+
+ // Let's read the size async.
+ RefPtr<testing::LengthCallback> callback = new testing::LengthCallback();
+ rv = afsis->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(MultiplexInputStream, LengthInputStream) 1"_ns,
+ [&]() { return callback->Called(); }));
+ ASSERT_EQ(int64_t(buf.Length() * 3), callback->Size());
+
+ // Now a negative stream
+ lengthStream = new testing::LengthInputStream(buf, true, false, NS_OK, true);
+
+ rv = multiplexStream->AppendStream(lengthStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = fsis->Length(&length);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_EQ(-1, length);
+
+ // Another async LengthInputStream.
+ asyncLengthStream = new testing::LengthInputStream(
+ buf, true, true, NS_BASE_STREAM_WOULD_BLOCK);
+
+ rv = multiplexStream->AppendStream(asyncLengthStream);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ afsis = do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(!!afsis);
+
+ // Let's read the size async.
+ RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback();
+ rv = afsis->AsyncLengthWait(callback1, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback();
+ rv = afsis->AsyncLengthWait(callback2, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(MultiplexInputStream, LengthInputStream) 2"_ns,
+ [&]() { return callback2->Called(); }));
+ ASSERT_FALSE(callback1->Called());
+ ASSERT_TRUE(callback2->Called());
+}
+
+void TestMultiplexStreamReadWhileWaiting(nsIAsyncInputStream* pipeIn,
+ nsIAsyncOutputStream* pipeOut) {
+ // We had an issue where a stream which was read while a message was in-flight
+ // to report the stream was ready, meaning that the stream reported 0 bytes
+ // available when checked in the MultiplexInputStream's callback, and was
+ // skipped over.
+
+ nsCOMPtr<nsIThread> mainThread = NS_GetCurrentThread();
+
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+ ASSERT_NS_SUCCEEDED(multiplexStream->AppendStream(pipeIn));
+
+ nsCOMPtr<nsIInputStream> stringStream;
+ ASSERT_TRUE(NS_SUCCEEDED(
+ NS_NewCStringInputStream(getter_AddRefs(stringStream), "xxxx\0"_ns)));
+ ASSERT_NS_SUCCEEDED(multiplexStream->AppendStream(stringStream));
+
+ nsCOMPtr<nsIAsyncInputStream> asyncMultiplex =
+ do_QueryInterface(multiplexStream);
+ ASSERT_TRUE(asyncMultiplex);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->AsyncWait(cb, 0, 0, mainThread));
+ EXPECT_FALSE(cb->Called());
+
+ NS_ProcessPendingEvents(mainThread);
+ EXPECT_FALSE(cb->Called());
+
+ uint64_t available;
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, 0u);
+
+ // Write some data to the pipe, which should wake up the async wait message to
+ // be delivered.
+ char toWrite[] = "1234";
+ uint32_t written;
+ ASSERT_NS_SUCCEEDED(pipeOut->Write(toWrite, sizeof(toWrite), &written));
+ EXPECT_EQ(written, sizeof(toWrite));
+ EXPECT_FALSE(cb->Called());
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, sizeof(toWrite));
+
+ // Read that data from the stream
+ char toRead[sizeof(toWrite)];
+ uint32_t read;
+ ASSERT_TRUE(
+ NS_SUCCEEDED(asyncMultiplex->Read(toRead, sizeof(toRead), &read)));
+ EXPECT_EQ(read, sizeof(toRead));
+ EXPECT_STREQ(toRead, toWrite);
+ EXPECT_FALSE(cb->Called());
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, 0u);
+
+ // The multiplex stream will have detected the read and prevented the callback
+ // from having been called yet.
+ NS_ProcessPendingEvents(mainThread);
+ EXPECT_FALSE(cb->Called());
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, 0u);
+
+ // Write more data and close, then make sure we can read everything else in
+ // the stream.
+ char toWrite2[] = "56789";
+ ASSERT_TRUE(
+ NS_SUCCEEDED(pipeOut->Write(toWrite2, sizeof(toWrite2), &written)));
+ EXPECT_EQ(written, sizeof(toWrite2));
+ EXPECT_FALSE(cb->Called());
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, sizeof(toWrite2));
+
+ ASSERT_NS_SUCCEEDED(pipeOut->Close());
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ // XXX: Theoretically if the multiplex stream could detect it, we could report
+ // `sizeof(toWrite2) + 4` because the stream is complete, but there's no way
+ // for the multiplex stream to know.
+ EXPECT_EQ(available, sizeof(toWrite2));
+
+ NS_ProcessPendingEvents(mainThread);
+ EXPECT_TRUE(cb->Called());
+
+ // Read that final bit of data and make sure we read it.
+ char toRead2[sizeof(toWrite2)];
+ ASSERT_TRUE(
+ NS_SUCCEEDED(asyncMultiplex->Read(toRead2, sizeof(toRead2), &read)));
+ EXPECT_EQ(read, sizeof(toRead2));
+ EXPECT_STREQ(toRead2, toWrite2);
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, 5u);
+
+ // Read the extra data as well.
+ char extraRead[5];
+ ASSERT_TRUE(
+ NS_SUCCEEDED(asyncMultiplex->Read(extraRead, sizeof(extraRead), &read)));
+ EXPECT_EQ(read, sizeof(extraRead));
+ EXPECT_STREQ(extraRead, "xxxx");
+ ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available));
+ EXPECT_EQ(available, 0u);
+}
+
+TEST(MultiplexInputStream, ReadWhileWaiting_nsPipe)
+{
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true);
+ TestMultiplexStreamReadWhileWaiting(pipeIn, pipeOut);
+}
+
+TEST(MultiplexInputStream, ReadWhileWaiting_DataPipe)
+{
+ RefPtr<mozilla::ipc::DataPipeReceiver> pipeIn;
+ RefPtr<mozilla::ipc::DataPipeSender> pipeOut;
+ ASSERT_TRUE(NS_SUCCEEDED(mozilla::ipc::NewDataPipe(
+ mozilla::ipc::kDefaultDataPipeCapacity, getter_AddRefs(pipeOut),
+ getter_AddRefs(pipeIn))));
+ TestMultiplexStreamReadWhileWaiting(pipeIn, pipeOut);
+}
diff --git a/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp
new file mode 100644
index 0000000000..2294794ad2
--- /dev/null
+++ b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp
@@ -0,0 +1,167 @@
+/* -*- 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, Characters)
+{
+ std::pair<const char*, mozilla::LogLevel> expected[] = {
+ {"valid.name", mozilla::LogLevel::Verbose},
+ {"valid_name", mozilla::LogLevel::Debug},
+ {"invalid", mozilla::LogLevel::Error},
+ };
+
+ const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected);
+
+ auto* currTest = expected;
+
+ size_t count = 0;
+ mozilla::NSPRLogModulesParser(
+ "valid.name:5,valid_name:4,invalid/name:3,aborts-everything:2",
+ [&](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);
+}
+
+TEST(NSPRLogModulesParser, RustModules)
+{
+ std::pair<const char*, mozilla::LogLevel> expected[] = {
+ {"timestamp", mozilla::LogLevel::Error},
+ {"crate::mod::file1", mozilla::LogLevel::Error},
+ {"crate::mod::file2", mozilla::LogLevel::Verbose},
+ {"crate::mod::*", mozilla::LogLevel::Info},
+ };
+
+ const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected);
+
+ auto* currTest = expected;
+
+ size_t count = 0;
+ mozilla::NSPRLogModulesParser(
+ "timestamp,crate::mod::file1, crate::mod::file2:5, crate::mod::*:3",
+ [&](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);
+}
diff --git a/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp b/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp
new file mode 100644
index 0000000000..8301adf6c8
--- /dev/null
+++ b/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp
@@ -0,0 +1,379 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/NonBlockingAsyncInputStream.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIThread.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "Helpers.h"
+
+using mozilla::NonBlockingAsyncInputStream;
+using mozilla::SpinEventLoopUntil;
+
+TEST(TestNonBlockingAsyncInputStream, Simple)
+{
+ nsCString data;
+ data.Assign("Hello world!");
+
+ // It should not be async.
+ bool nonBlocking = false;
+ nsCOMPtr<nsIAsyncInputStream> async;
+
+ {
+ // Let's create a test string inputStream
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+
+ async = do_QueryInterface(stream);
+ ASSERT_EQ(nullptr, async);
+
+ // It must be non-blocking
+ ASSERT_EQ(NS_OK, stream->IsNonBlocking(&nonBlocking));
+ ASSERT_TRUE(nonBlocking);
+
+ // Here the non-blocking stream.
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+ ASSERT_TRUE(!!async);
+
+ // Still non-blocking
+ ASSERT_EQ(NS_OK, async->IsNonBlocking(&nonBlocking));
+ ASSERT_TRUE(nonBlocking);
+
+ // Testing ::Available()
+ uint64_t length;
+ ASSERT_EQ(NS_OK, async->Available(&length));
+ ASSERT_EQ(data.Length(), length);
+
+ // Read works fine.
+ char buffer[1024];
+ uint32_t read = 0;
+ ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read));
+ ASSERT_EQ(data.Length(), read);
+ ASSERT_TRUE(data.Equals(nsCString(buffer, read)));
+}
+
+class ReadSegmentsData {
+ public:
+ ReadSegmentsData(nsIInputStream* aStream, char* aBuffer)
+ : mStream(aStream), mBuffer(aBuffer) {}
+
+ nsIInputStream* mStream;
+ char* mBuffer;
+};
+
+static nsresult ReadSegmentsFunction(nsIInputStream* aInStr, void* aClosure,
+ const char* aBuffer, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aCountWritten) {
+ ReadSegmentsData* data = static_cast<ReadSegmentsData*>(aClosure);
+ if (aInStr != data->mStream) return NS_ERROR_FAILURE;
+ memcpy(&data->mBuffer[aOffset], aBuffer, aCount);
+ *aCountWritten = aCount;
+ return NS_OK;
+}
+
+TEST(TestNonBlockingAsyncInputStream, ReadSegments)
+{
+ nsCString data;
+ data.Assign("Hello world!");
+
+ nsCOMPtr<nsIAsyncInputStream> async;
+ {
+ // Let's create a test string inputStream
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+
+ // Here the non-blocking stream.
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+
+ // Read works fine.
+ char buffer[1024];
+ uint32_t read = 0;
+ ReadSegmentsData closure(async, buffer);
+ ASSERT_EQ(NS_OK, async->ReadSegments(ReadSegmentsFunction, &closure,
+ sizeof(buffer), &read));
+ ASSERT_EQ(data.Length(), read);
+ ASSERT_TRUE(data.Equals(nsCString(buffer, read)));
+}
+
+TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple)
+{
+ nsCString data;
+ data.Assign("Hello world!");
+
+ nsCOMPtr<nsIAsyncInputStream> async;
+ {
+ // Let's create a test string inputStream
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+
+ // Here the non-blocking stream.
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+ ASSERT_TRUE(!!async);
+
+ // Testing ::Available()
+ uint64_t length;
+ ASSERT_EQ(NS_OK, async->Available(&length));
+ ASSERT_EQ(data.Length(), length);
+
+ // Testing ::AsyncWait - without EventTarget
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, nullptr));
+ ASSERT_TRUE(cb->Called());
+
+ // Testing ::AsyncWait - with EventTarget
+ cb = new testing::InputStreamCallback();
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, thread));
+ ASSERT_FALSE(cb->Called());
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple)"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+
+ // Read works fine.
+ char buffer[1024];
+ uint32_t read = 0;
+ ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read));
+ ASSERT_EQ(data.Length(), read);
+ ASSERT_TRUE(data.Equals(nsCString(buffer, read)));
+}
+
+TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withoutEventTarget)
+{
+ nsCString data;
+ data.Assign("Hello world!");
+
+ nsCOMPtr<nsIAsyncInputStream> async;
+ {
+ // Let's create a test string inputStream
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+
+ // Here the non-blocking stream.
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+ ASSERT_TRUE(!!async);
+
+ // Testing ::AsyncWait - no eventTarget
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
+ 0, nullptr));
+
+ ASSERT_FALSE(cb->Called());
+ ASSERT_EQ(NS_OK, async->Close());
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget)
+{
+ nsCString data;
+ data.Assign("Hello world!");
+
+ nsCOMPtr<nsIAsyncInputStream> async;
+ {
+ // Let's create a test string inputStream
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+
+ // Here the non-blocking stream.
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+ ASSERT_TRUE(!!async);
+
+ // Testing ::AsyncWait - with EventTarget
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+
+ ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
+ 0, thread));
+
+ ASSERT_FALSE(cb->Called());
+ ASSERT_EQ(NS_OK, async->Close());
+ ASSERT_FALSE(cb->Called());
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget)"_ns,
+ [&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
+
+TEST(TestNonBlockingAsyncInputStream, Helper)
+{
+ nsCString data;
+ data.Assign("Hello world!");
+
+ nsCOMPtr<nsIAsyncInputStream> async;
+ {
+ // Let's create a test string inputStream
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+
+ // Here the non-blocking stream.
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+ ASSERT_TRUE(!!async);
+
+ // This should return the same object because async is already non-blocking
+ // and async.
+ nsCOMPtr<nsIAsyncInputStream> result;
+ nsCOMPtr<nsIAsyncInputStream> asyncTmp = async;
+ ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream(asyncTmp.forget(),
+ getter_AddRefs(result)));
+ ASSERT_EQ(async, result);
+
+ // This will use NonBlockingAsyncInputStream wrapper.
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data));
+ ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream(
+ stream.forget(), getter_AddRefs(result)));
+ }
+ ASSERT_TRUE(async != result);
+ ASSERT_TRUE(async);
+}
+
+class QIInputStream final : public nsIInputStream,
+ public nsICloneableInputStream,
+ public nsIIPCSerializableInputStream,
+ public nsISeekableStream {
+ public:
+ NS_DECL_ISUPPORTS
+
+ QIInputStream(bool aNonBlockingError, bool aCloneable, bool aIPCSerializable,
+ bool aSeekable)
+ : mNonBlockingError(aNonBlockingError),
+ mCloneable(aCloneable),
+ mIPCSerializable(aIPCSerializable),
+ mSeekable(aSeekable) {}
+
+ // nsIInputStream
+ NS_IMETHOD Close() override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD Available(uint64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD StreamStatus() override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD Read(char*, uint32_t, uint32_t*) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun, void*, uint32_t,
+ uint32_t*) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD IsNonBlocking(bool* aNonBlocking) override {
+ *aNonBlocking = true;
+ return mNonBlockingError ? NS_ERROR_FAILURE : NS_OK;
+ }
+
+ // nsICloneableInputStream
+ NS_IMETHOD GetCloneable(bool*) override { return NS_ERROR_NOT_IMPLEMENTED; }
+ NS_IMETHOD Clone(nsIInputStream**) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // nsIIPCSerializableInputStream
+ void SerializedComplexity(uint32_t, uint32_t*, uint32_t*,
+ uint32_t*) override {}
+ void Serialize(mozilla::ipc::InputStreamParams&, uint32_t,
+ uint32_t*) override {}
+ bool Deserialize(const mozilla::ipc::InputStreamParams&) override {
+ return false;
+ }
+
+ // nsISeekableStream
+ NS_IMETHOD Seek(int32_t, int64_t) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD SetEOF() override { return NS_ERROR_NOT_IMPLEMENTED; }
+
+ // nsITellableStream
+ NS_IMETHOD Tell(int64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; }
+
+ private:
+ ~QIInputStream() = default;
+
+ bool mNonBlockingError;
+ bool mCloneable;
+ bool mIPCSerializable;
+ bool mSeekable;
+};
+
+NS_IMPL_ADDREF(QIInputStream);
+NS_IMPL_RELEASE(QIInputStream);
+
+NS_INTERFACE_MAP_BEGIN(QIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, mCloneable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
+ mIPCSerializable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, mSeekable)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+TEST(TestNonBlockingAsyncInputStream, QI)
+{
+ // Let's test ::Create() returning error.
+
+ nsCOMPtr<nsIAsyncInputStream> async;
+ {
+ nsCOMPtr<nsIInputStream> stream = new QIInputStream(true, true, true, true);
+
+ ASSERT_EQ(NS_ERROR_FAILURE, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+
+ // Let's test the QIs
+ for (int i = 0; i < 8; ++i) {
+ bool shouldBeCloneable = !!(i & 0x01);
+ bool shouldBeSerializable = !!(i & 0x02);
+ bool shouldBeSeekable = !!(i & 0x04);
+
+ nsCOMPtr<nsICloneableInputStream> cloneable;
+ nsCOMPtr<nsIIPCSerializableInputStream> ipcSerializable;
+ nsCOMPtr<nsISeekableStream> seekable;
+
+ {
+ nsCOMPtr<nsIInputStream> stream = new QIInputStream(
+ false, shouldBeCloneable, shouldBeSerializable, shouldBeSeekable);
+
+ cloneable = do_QueryInterface(stream);
+ ASSERT_EQ(shouldBeCloneable, !!cloneable);
+
+ ipcSerializable = do_QueryInterface(stream);
+ ASSERT_EQ(shouldBeSerializable, !!ipcSerializable);
+
+ seekable = do_QueryInterface(stream);
+ ASSERT_EQ(shouldBeSeekable, !!seekable);
+
+ ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create(
+ stream.forget(), getter_AddRefs(async)));
+ }
+
+ // The returned async stream should be cloneable only if the underlying
+ // stream is.
+ cloneable = do_QueryInterface(async);
+ ASSERT_EQ(shouldBeCloneable, !!cloneable);
+
+ // The returned async stream should be serializable only if the underlying
+ // stream is.
+ ipcSerializable = do_QueryInterface(async);
+ ASSERT_EQ(shouldBeSerializable, !!ipcSerializable);
+
+ // The returned async stream should be seekable only if the underlying
+ // stream is.
+ seekable = do_QueryInterface(async);
+ ASSERT_EQ(shouldBeSeekable, !!seekable);
+ }
+}
diff --git a/xpcom/tests/gtest/TestNsDeque.cpp b/xpcom/tests/gtest/TestNsDeque.cpp
new file mode 100644
index 0000000000..af37246647
--- /dev/null
+++ b/xpcom/tests/gtest/TestNsDeque.cpp
@@ -0,0 +1,594 @@
+/* -*- 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 "nsDeque.h"
+#include "nsCRT.h"
+#include "mozilla/RefPtr.h"
+#include <stdio.h>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+/**************************************************************
+ Now define the token deallocator class...
+ **************************************************************/
+namespace TestNsDeque {
+
+template <typename T>
+class _Dealloc : public nsDequeFunctor<T> {
+ virtual void operator()(T* aObject) {}
+};
+
+static bool VerifyContents(const nsDeque<int>& aDeque, const int* aContents,
+ size_t aLength) {
+ for (size_t i = 0; i < aLength; ++i) {
+ if (*aDeque.ObjectAt(i) != aContents[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class Deallocator : public nsDequeFunctor<int> {
+ virtual void operator()(int* aObject) {
+ if (aObject) {
+ // Set value to -1, to use in test function.
+ *(aObject) = -1;
+ }
+ }
+};
+
+class ForEachAdder : public nsDequeFunctor<int> {
+ virtual void operator()(int* aObject) {
+ if (aObject) {
+ sum += *(int*)aObject;
+ }
+ }
+
+ private:
+ int sum = 0;
+
+ public:
+ int GetSum() { return sum; }
+};
+
+static uint32_t sFreeCount = 0;
+
+class RefCountedClass {
+ public:
+ RefCountedClass() : mRefCnt(0), mVal(0) {}
+
+ ~RefCountedClass() { ++sFreeCount; }
+
+ NS_METHOD_(MozExternalRefCountType) AddRef() {
+ mRefCnt++;
+ return mRefCnt;
+ }
+ NS_METHOD_(MozExternalRefCountType) Release() {
+ NS_ASSERTION(mRefCnt > 0, "");
+ mRefCnt--;
+ if (mRefCnt == 0) {
+ delete this;
+ }
+ return 0;
+ }
+
+ inline uint32_t GetRefCount() const { return mRefCnt; }
+
+ inline void SetVal(int aVal) { mVal = aVal; }
+
+ inline int GetVal() const { return mVal; }
+
+ private:
+ uint32_t mRefCnt;
+ int mVal;
+};
+
+class ForEachRefPtr : public nsDequeFunctor<RefCountedClass> {
+ virtual void operator()(RefCountedClass* aObject) {
+ if (aObject) {
+ aObject->SetVal(mVal);
+ }
+ }
+
+ private:
+ int mVal = 0;
+
+ public:
+ explicit ForEachRefPtr(int aVal) : mVal(aVal) {}
+};
+
+} // namespace TestNsDeque
+
+using namespace TestNsDeque;
+
+TEST(NsDeque, OriginalTest)
+{
+ const size_t size = 200;
+ int ints[size];
+ size_t i = 0;
+ int temp;
+ nsDeque<int> theDeque(new _Dealloc<int>); // construct a simple one...
+
+ // ints = [0...199]
+ for (i = 0; i < size; i++) { // initialize'em
+ ints[i] = static_cast<int>(i);
+ }
+ // queue = [0...69]
+ for (i = 0; i < 70; i++) {
+ theDeque.Push(&ints[i]);
+ temp = *theDeque.Peek();
+ EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push #1";
+ EXPECT_EQ(i + 1, theDeque.GetSize()) << "Verify size after push #1";
+ }
+
+ EXPECT_EQ(70u, theDeque.GetSize()) << "Verify overall size after pushes #1";
+
+ // queue = [0...14]
+ for (i = 1; i <= 55; i++) {
+ temp = *theDeque.Pop();
+ EXPECT_EQ(70 - static_cast<int>(i), temp) << "Verify end after pop # 1";
+ EXPECT_EQ(70u - i, theDeque.GetSize()) << "Verify size after pop # 1";
+ }
+ EXPECT_EQ(15u, theDeque.GetSize()) << "Verify overall size after pops";
+
+ // queue = [0...14,0...54]
+ for (i = 0; i < 55; i++) {
+ theDeque.Push(&ints[i]);
+ temp = *theDeque.Peek();
+ EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push #2";
+ EXPECT_EQ(i + 15u + 1, theDeque.GetSize()) << "Verify size after push # 2";
+ }
+ EXPECT_EQ(70u, theDeque.GetSize())
+ << "Verify size after end of all pushes #2";
+
+ // queue = [0...14,0...19]
+ for (i = 1; i <= 35; i++) {
+ temp = *theDeque.Pop();
+ EXPECT_EQ(55 - static_cast<int>(i), temp) << "Verify end after pop # 2";
+ EXPECT_EQ(70u - i, theDeque.GetSize()) << "Verify size after pop #2";
+ }
+ EXPECT_EQ(35u, theDeque.GetSize())
+ << "Verify overall size after end of all pops #2";
+
+ // queue = [0...14,0...19,0...34]
+ for (i = 0; i < 35; i++) {
+ theDeque.Push(&ints[i]);
+ temp = *theDeque.Peek();
+ EXPECT_EQ(static_cast<int>(i), temp) << "Verify end after push # 3";
+ EXPECT_EQ(35u + 1u + i, theDeque.GetSize()) << "Verify size after push #3";
+ }
+
+ // queue = [0...14,0...19]
+ for (i = 0; i < 35; i++) {
+ temp = *theDeque.Pop();
+ EXPECT_EQ(34 - static_cast<int>(i), temp) << "Verify end after pop # 3";
+ }
+
+ // queue = [0...14]
+ for (i = 0; i < 20; i++) {
+ temp = *theDeque.Pop();
+ EXPECT_EQ(19 - static_cast<int>(i), temp) << "Verify end after pop # 4";
+ }
+
+ // queue = []
+ for (i = 0; i < 15; i++) {
+ temp = *theDeque.Pop();
+ EXPECT_EQ(14 - static_cast<int>(i), temp) << "Verify end after pop # 5";
+ }
+
+ EXPECT_EQ(0u, theDeque.GetSize()) << "Deque should finish empty.";
+}
+
+TEST(NsDeque, OriginalFlaw)
+{
+ int ints[200];
+ int i = 0;
+ int temp;
+ nsDeque<int> d(new _Dealloc<int>);
+ /**
+ * Test 1. Origin near end, semi full, call Peek().
+ * you start, mCapacity is 8
+ */
+ for (i = 0; i < 30; i++) ints[i] = i;
+
+ for (i = 0; i < 6; i++) {
+ d.Push(&ints[i]);
+ temp = *d.Peek();
+ EXPECT_EQ(i, temp) << "OriginalFlaw push #1";
+ }
+ EXPECT_EQ(6u, d.GetSize()) << "OriginalFlaw size check #1";
+
+ for (i = 0; i < 4; i++) {
+ temp = *d.PopFront();
+ EXPECT_EQ(i, temp) << "PopFront test";
+ }
+ // d = [4,5]
+ EXPECT_EQ(2u, d.GetSize()) << "OriginalFlaw size check #2";
+
+ for (i = 0; i < 4; i++) {
+ d.Push(&ints[6 + i]);
+ }
+
+ // d = [4...9]
+ for (i = 4; i <= 9; i++) {
+ temp = *d.PopFront();
+ EXPECT_EQ(i, temp) << "OriginalFlaw empty check";
+ }
+}
+
+TEST(NsDeque, TestObjectAt)
+{
+ nsDeque<int> d;
+ const int count = 10;
+ int ints[count];
+ for (int i = 0; i < count; i++) {
+ ints[i] = i;
+ }
+
+ for (int i = 0; i < 6; i++) {
+ d.Push(&ints[i]);
+ }
+ // d = [0...5]
+ d.PopFront();
+ d.PopFront();
+
+ // d = [2..5]
+ for (size_t i = 2; i <= 5; i++) {
+ int t = *d.ObjectAt(i - 2);
+ EXPECT_EQ(static_cast<int>(i), t) << "Verify ObjectAt()";
+ }
+}
+
+TEST(NsDeque, TestPushFront)
+{
+ // PushFront has some interesting corner cases, primarily we're interested in
+ // whether:
+ // - wrapping around works properly
+ // - growing works properly
+
+ nsDeque<int> d;
+
+ const int kPoolSize = 10;
+ const size_t kMaxSizeBeforeGrowth = 8;
+
+ int pool[kPoolSize];
+ for (int i = 0; i < kPoolSize; i++) {
+ pool[i] = i;
+ }
+
+ for (size_t i = 0; i < kMaxSizeBeforeGrowth; i++) {
+ d.PushFront(pool + i);
+ }
+
+ EXPECT_EQ(kMaxSizeBeforeGrowth, d.GetSize()) << "verify size";
+
+ static const int t1[] = {7, 6, 5, 4, 3, 2, 1, 0};
+ EXPECT_TRUE(VerifyContents(d, t1, kMaxSizeBeforeGrowth))
+ << "verify pushfront 1";
+
+ // Now push one more so it grows
+ d.PushFront(pool + kMaxSizeBeforeGrowth);
+ EXPECT_EQ(kMaxSizeBeforeGrowth + 1, d.GetSize()) << "verify size";
+
+ static const int t2[] = {8, 7, 6, 5, 4, 3, 2, 1, 0};
+ EXPECT_TRUE(VerifyContents(d, t2, kMaxSizeBeforeGrowth + 1))
+ << "verify pushfront 2";
+
+ // And one more so that it wraps again
+ d.PushFront(pool + kMaxSizeBeforeGrowth + 1);
+ EXPECT_EQ(kMaxSizeBeforeGrowth + 2, d.GetSize()) << "verify size";
+
+ static const int t3[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+ EXPECT_TRUE(VerifyContents(d, t3, kMaxSizeBeforeGrowth + 2))
+ << "verify pushfront 3";
+}
+
+template <typename T>
+static void CheckIfQueueEmpty(nsDeque<T>& d) {
+ EXPECT_EQ(0u, d.GetSize()) << "Size should be 0";
+ EXPECT_EQ(nullptr, d.Pop()) << "Invalid operation should return nullptr";
+ EXPECT_EQ(nullptr, d.PopFront()) << "Invalid operation should return nullptr";
+ EXPECT_EQ(nullptr, d.Peek()) << "Invalid operation should return nullptr";
+ EXPECT_EQ(nullptr, d.PeekFront())
+ << "Invalid operation should return nullptr";
+ EXPECT_EQ(nullptr, d.ObjectAt(0u))
+ << "Invalid operation should return nullptr";
+}
+
+TEST(NsDeque, TestEmpty)
+{
+ // Make sure nsDeque gives sane results if it's empty.
+ nsDeque<void> d;
+ size_t numberOfEntries = 8;
+
+ CheckIfQueueEmpty(d);
+
+ // Fill it up and drain it.
+ for (size_t i = 0; i < numberOfEntries; i++) {
+ d.Push((void*)0xAA);
+ }
+
+ EXPECT_EQ(numberOfEntries, d.GetSize());
+
+ for (size_t i = 0; i < numberOfEntries; i++) {
+ (void)d.Pop();
+ }
+
+ // Now check it again.
+ CheckIfQueueEmpty(d);
+}
+
+TEST(NsDeque, TestEraseMethod)
+{
+ nsDeque<void> d;
+ const size_t numberOfEntries = 8;
+
+ // Fill it up before calling Erase
+ for (size_t i = 0; i < numberOfEntries; i++) {
+ d.Push((void*)0xAA);
+ }
+
+ // Call Erase
+ d.Erase();
+
+ // Now check it again.
+ CheckIfQueueEmpty(d);
+}
+
+TEST(NsDeque, TestEraseShouldCallDeallocator)
+{
+ nsDeque<int> d(new Deallocator());
+ const size_t NumTestValues = 8;
+
+ int* testArray[NumTestValues];
+ for (size_t i = 0; i < NumTestValues; i++) {
+ testArray[i] = new int();
+ *(testArray[i]) = i;
+ d.Push(testArray[i]);
+ }
+
+ d.Erase();
+
+ // Now check it again.
+ CheckIfQueueEmpty(d);
+
+ for (size_t i = 0; i < NumTestValues; i++) {
+ EXPECT_EQ(-1, *(testArray[i]))
+ << "Erase should call deallocator: " << *(testArray[i]);
+ }
+}
+
+TEST(NsDeque, TestForEach)
+{
+ nsDeque<int> d(new Deallocator());
+ const size_t NumTestValues = 8;
+ int sum = 0;
+
+ int* testArray[NumTestValues];
+ for (size_t i = 0; i < NumTestValues; i++) {
+ testArray[i] = new int();
+ *(testArray[i]) = i;
+ sum += i;
+ d.Push(testArray[i]);
+ }
+
+ ForEachAdder adder;
+ d.ForEach(adder);
+ EXPECT_EQ(sum, adder.GetSum()) << "For each should iterate over values";
+
+ d.Erase();
+}
+
+TEST(NsDeque, TestConstRangeFor)
+{
+ nsDeque<int> d(new Deallocator());
+
+ const size_t NumTestValues = 3;
+ for (size_t i = 0; i < NumTestValues; ++i) {
+ d.Push(new int(i + 1));
+ }
+
+ static_assert(
+ std::is_same_v<nsDeque<int>::ConstDequeIterator,
+ decltype(std::declval<const nsDeque<int>&>().begin())>,
+ "(const nsDeque).begin() should return ConstDequeIterator");
+ static_assert(
+ std::is_same_v<nsDeque<int>::ConstDequeIterator,
+ decltype(std::declval<const nsDeque<int>&>().end())>,
+ "(const nsDeque).end() should return ConstDequeIterator");
+
+ int sum = 0;
+ for (int* ob : const_cast<const nsDeque<int>&>(d)) {
+ sum += *ob;
+ }
+ EXPECT_EQ(1 + 2 + 3, sum) << "Const-range-for should iterate over values";
+}
+
+TEST(NsDeque, TestRangeFor)
+{
+ const size_t NumTestValues = 3;
+ struct Test {
+ size_t runAfterLoopCount;
+ std::function<void(nsDeque<int>&)> function;
+ int expectedSum;
+ const char* description;
+ };
+ // Note: All tests start with a deque containing 3 pointers to ints 1, 2, 3.
+ Test tests[] = {
+ {0, [](nsDeque<int>& d) {}, 1 + 2 + 3, "no changes"},
+
+ {1, [](nsDeque<int>& d) { d.Pop(); }, 1 + 2, "Pop after 1st loop"},
+ {2, [](nsDeque<int>& d) { d.Pop(); }, 1 + 2, "Pop after 2nd loop"},
+ {3, [](nsDeque<int>& d) { d.Pop(); }, 1 + 2 + 3, "Pop after 3rd loop"},
+
+ {1, [](nsDeque<int>& d) { d.PopFront(); }, 1 + 3,
+ "PopFront after 1st loop"},
+ {2, [](nsDeque<int>& d) { d.PopFront(); }, 1 + 2,
+ "PopFront after 2nd loop"},
+ {3, [](nsDeque<int>& d) { d.PopFront(); }, 1 + 2 + 3,
+ "PopFront after 3rd loop"},
+
+ {1, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4,
+ "Push after 1st loop"},
+ {2, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4,
+ "Push after 2nd loop"},
+ {3, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4,
+ "Push after 3rd loop"},
+ {4, [](nsDeque<int>& d) { d.Push(new int(4)); }, 1 + 2 + 3,
+ "Push after would-be-4th loop"},
+
+ {1, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 1 + 2 + 3,
+ "PushFront after 1st loop"},
+ {2, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 2 + 2 + 3,
+ "PushFront after 2nd loop"},
+ {3, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 2 + 3 + 3,
+ "PushFront after 3rd loop"},
+ {4, [](nsDeque<int>& d) { d.PushFront(new int(4)); }, 1 + 2 + 3,
+ "PushFront after would-be-4th loop"},
+
+ {1, [](nsDeque<int>& d) { d.Erase(); }, 1, "Erase after 1st loop"},
+ {2, [](nsDeque<int>& d) { d.Erase(); }, 1 + 2, "Erase after 2nd loop"},
+ {3, [](nsDeque<int>& d) { d.Erase(); }, 1 + 2 + 3,
+ "Erase after 3rd loop"},
+
+ {1,
+ [](nsDeque<int>& d) {
+ d.Erase();
+ d.Push(new int(4));
+ },
+ 1, "Erase after 1st loop, Push 4"},
+ {1,
+ [](nsDeque<int>& d) {
+ d.Erase();
+ d.Push(new int(4));
+ d.Push(new int(5));
+ },
+ 1 + 5, "Erase after 1st loop, Push 4,5"},
+ {2,
+ [](nsDeque<int>& d) {
+ d.Erase();
+ d.Push(new int(4));
+ },
+ 1 + 2, "Erase after 2nd loop, Push 4"},
+ {2,
+ [](nsDeque<int>& d) {
+ d.Erase();
+ d.Push(new int(4));
+ d.Push(new int(5));
+ },
+ 1 + 2, "Erase after 2nd loop, Push 4,5"},
+ {2,
+ [](nsDeque<int>& d) {
+ d.Erase();
+ d.Push(new int(4));
+ d.Push(new int(5));
+ d.Push(new int(6));
+ },
+ 1 + 2 + 6, "Erase after 2nd loop, Push 4,5,6"}};
+
+ for (const Test& test : tests) {
+ nsDeque<int> d(new Deallocator());
+
+ for (size_t i = 0; i < NumTestValues; ++i) {
+ d.Push(new int(i + 1));
+ }
+
+ static_assert(
+ std::is_same_v<nsDeque<int>::ConstIterator, decltype(d.begin())>,
+ "(non-const nsDeque).begin() should return ConstIterator");
+ static_assert(
+ std::is_same_v<nsDeque<int>::ConstIterator, decltype(d.end())>,
+ "(non-const nsDeque).end() should return ConstIterator");
+
+ int sum = 0;
+ size_t loopCount = 0;
+ for (int* ob : d) {
+ sum += *ob;
+ if (++loopCount == test.runAfterLoopCount) {
+ test.function(d);
+ }
+ }
+ EXPECT_EQ(test.expectedSum, sum)
+ << "Range-for should iterate over values in test '" << test.description
+ << "'";
+ }
+}
+
+TEST(NsDeque, RefPtrDeque)
+{
+ sFreeCount = 0;
+ nsRefPtrDeque<RefCountedClass> deque;
+ RefPtr<RefCountedClass> ptr1 = new RefCountedClass();
+ EXPECT_EQ(1u, ptr1->GetRefCount());
+
+ deque.Push(ptr1);
+ EXPECT_EQ(2u, ptr1->GetRefCount());
+
+ {
+ auto* peekPtr1 = deque.Peek();
+ EXPECT_TRUE(peekPtr1);
+ EXPECT_EQ(2u, ptr1->GetRefCount());
+ EXPECT_EQ(1u, deque.GetSize());
+ }
+
+ {
+ RefPtr<RefCountedClass> ptr2 = new RefCountedClass();
+ deque.PushFront(ptr2.forget());
+ EXPECT_TRUE(deque.PeekFront());
+ ptr2 = deque.PopFront();
+ EXPECT_EQ(ptr1, deque.PeekFront());
+ }
+ EXPECT_EQ(1u, sFreeCount);
+
+ {
+ RefPtr<RefCountedClass> popPtr1 = deque.Pop();
+ EXPECT_TRUE(popPtr1);
+ EXPECT_EQ(2u, ptr1->GetRefCount());
+ EXPECT_EQ(0u, deque.GetSize());
+ }
+
+ EXPECT_EQ(1u, ptr1->GetRefCount());
+ deque.Erase();
+ EXPECT_EQ(0u, deque.GetSize());
+ ptr1 = nullptr;
+ EXPECT_EQ(2u, sFreeCount);
+}
+
+TEST(NsDeque, RefPtrDequeTestIterator)
+{
+ sFreeCount = 0;
+ nsRefPtrDeque<RefCountedClass> deque;
+ const uint32_t cnt = 10;
+ for (uint32_t i = 0; i < cnt; ++i) {
+ RefPtr<RefCountedClass> ptr = new RefCountedClass();
+ deque.Push(ptr.forget());
+ EXPECT_TRUE(deque.Peek());
+ }
+ EXPECT_EQ(cnt, deque.GetSize());
+
+ int val = 100;
+ ForEachRefPtr functor(val);
+ deque.ForEach(functor);
+
+ uint32_t pos = 0;
+ for (nsRefPtrDeque<RefCountedClass>::ConstIterator it = deque.begin();
+ it != deque.end(); ++it) {
+ RefPtr<RefCountedClass> cur = *it;
+ EXPECT_TRUE(cur);
+ EXPECT_EQ(cur, deque.ObjectAt(pos++));
+ EXPECT_EQ(val, cur->GetVal());
+ }
+
+ EXPECT_EQ(deque.ObjectAt(0), deque.PeekFront());
+ EXPECT_EQ(deque.ObjectAt(cnt - 1), deque.Peek());
+
+ deque.Erase();
+
+ EXPECT_EQ(0u, deque.GetSize());
+ EXPECT_EQ(cnt, sFreeCount);
+}
diff --git a/xpcom/tests/gtest/TestNsRefPtr.cpp b/xpcom/tests/gtest/TestNsRefPtr.cpp
new file mode 100644
index 0000000000..cd89f2e86f
--- /dev/null
+++ b/xpcom/tests/gtest/TestNsRefPtr.cpp
@@ -0,0 +1,444 @@
+/* -*- 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() override;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+ NS_IMETHOD QueryInterface(const nsIID&, void**) override;
+ 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 {}
+
+static nsresult CreateFoo(void** result)
+// a typical factory function (that calls AddRef)
+{
+ auto* foop = new Foo;
+
+ foop->AddRef();
+ *result = foop;
+
+ return NS_OK;
+}
+
+static void set_a_Foo(RefPtr<Foo>* result) {
+ assert(result);
+
+ RefPtr<Foo> foop(do_QueryObject(new Foo));
+ *result = foop;
+}
+
+static 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();
+ ~Bar() override;
+
+ NS_IMETHOD QueryInterface(const nsIID&, void**) override;
+
+ void VirtualMemberFunction(int, int*, int&) override;
+ 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 = std::move(fooP);
+ mozilla::Unused << fooP2;
+ 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;
+
+ {
+ auto* raw_foo1p = new Foo;
+ raw_foo1p->AddRef();
+
+ auto* 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() = default;
+
+ 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 {
+static void AnFooPtrPtrContext(Foo**) {}
+static 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 0000000000..8a4744d06b
--- /dev/null
+++ b/xpcom/tests/gtest/TestObserverArray.cpp
@@ -0,0 +1,573 @@
+/* -*- 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"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/UniquePtr.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)
+
+// XXX Split this up into independent test cases
+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
+ */
+}
+
+TEST(ObserverArray, ForwardIterator_Remove)
+{
+ static const int expected[] = {3, 4};
+
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t count = 0;
+ for (auto iter = IntArray::ForwardIterator{arr}; iter.HasMore();) {
+ const int next = iter.GetNext();
+ iter.Remove();
+
+ ASSERT_EQ(expected[count++], next);
+ }
+ ASSERT_EQ(2u, count);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Forward_NonEmpty)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.ForwardRange()) {
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Forward_RemoveCurrent)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.ForwardRange()) {
+ sum += element;
+ ++iterations;
+ arr.RemoveElementAt(0);
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Forward_Append)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.ForwardRange()) {
+ if (!iterations) {
+ arr.AppendElement(5);
+ }
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(3u, iterations);
+ EXPECT_EQ(12, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Forward_Prepend)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.ForwardRange()) {
+ if (!iterations) {
+ arr.InsertElementAt(0, 5);
+ }
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Forward_Empty)
+{
+ IntArray arr;
+
+ size_t iterations = 0;
+ for (int element : arr.ForwardRange()) {
+ (void)element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(0u, iterations);
+}
+
+TEST(ObserverArray, RangeBasedFor_Reference_Forward_NonEmpty)
+{
+ const auto arr = [] {
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+ return arr;
+ }();
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (const UniquePtr<int>& element : arr.ForwardRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_NonConstReference_Forward_NonEmpty)
+{
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (UniquePtr<int>& element : arr.ForwardRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Backward_NonEmpty)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.BackwardRange()) {
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Backward_RemoveCurrent)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.BackwardRange()) {
+ sum += element;
+ ++iterations;
+ arr.RemoveElementAt(arr.Length() - 1);
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Backward_Append)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.BackwardRange()) {
+ if (!iterations) {
+ arr.AppendElement(5);
+ }
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Backward_Prepend)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.BackwardRange()) {
+ if (!iterations) {
+ arr.InsertElementAt(0, 5);
+ }
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(3u, iterations);
+ EXPECT_EQ(12, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_Backward_Empty)
+{
+ IntArray arr;
+
+ size_t iterations = 0;
+ for (int element : arr.BackwardRange()) {
+ (void)element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(0u, iterations);
+}
+
+TEST(ObserverArray, RangeBasedFor_Reference_Backward_NonEmpty)
+{
+ const auto arr = [] {
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+ return arr;
+ }();
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (const UniquePtr<int>& element : arr.BackwardRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_NonConstReference_Backward_NonEmpty)
+{
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (UniquePtr<int>& element : arr.BackwardRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_EndLimited_NonEmpty)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.EndLimitedRange()) {
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_EndLimited_RemoveCurrent)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.EndLimitedRange()) {
+ sum += element;
+ ++iterations;
+ arr.RemoveElementAt(0);
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Append)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.EndLimitedRange()) {
+ if (!iterations) {
+ arr.AppendElement(5);
+ }
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Prepend)
+{
+ IntArray arr;
+ arr.AppendElement(3);
+ arr.AppendElement(4);
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (int element : arr.EndLimitedRange()) {
+ if (!iterations) {
+ arr.InsertElementAt(0, 5);
+ }
+ sum += element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Empty)
+{
+ IntArray arr;
+
+ size_t iterations = 0;
+ for (int element : arr.EndLimitedRange()) {
+ (void)element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(0u, iterations);
+}
+
+TEST(ObserverArray, RangeBasedFor_Reference_EndLimited_NonEmpty)
+{
+ const auto arr = [] {
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+ return arr;
+ }();
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (const UniquePtr<int>& element : arr.EndLimitedRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_NonConstReference_EndLimited_NonEmpty)
+{
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (UniquePtr<int>& element : arr.EndLimitedRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+TEST(ObserverArray, RangeBasedFor_Reference_NonObserving_NonEmpty)
+{
+ const auto arr = [] {
+ nsTObserverArray<UniquePtr<int>> arr;
+ arr.AppendElement(MakeUnique<int>(3));
+ arr.AppendElement(MakeUnique<int>(4));
+ return arr;
+ }();
+
+ size_t iterations = 0;
+ int sum = 0;
+ for (const UniquePtr<int>& element : arr.NonObservingRange()) {
+ sum += *element;
+ ++iterations;
+ }
+
+ EXPECT_EQ(2u, iterations);
+ EXPECT_EQ(7, sum);
+}
+
+// TODO add tests for EndLimitedIterator
diff --git a/xpcom/tests/gtest/TestObserverService.cpp b/xpcom/tests/gtest/TestObserverService.cpp
new file mode 100644
index 0000000000..4126815f1f
--- /dev/null
+++ b/xpcom/tests/gtest/TestObserverService.cpp
@@ -0,0 +1,281 @@
+/* -*- 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 "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsISimpleEnumerator.h"
+#include "nsComponentManagerUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+
+#include "mozilla/gtest/MozAssertions.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() = default;
+};
+
+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(u"A"_ns);
+ nsresult rv = svc->AddObserver(a, "Foo", false);
+ testResult(rv);
+
+ // Add a few weak ref.
+ RefPtr<TestObserver> b = new TestObserver(u"B"_ns);
+ 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(u"A"_ns);
+ RefPtr<TestObserver> b = new TestObserver(u"B"_ns);
+ RefPtr<TestObserver> c = new TestObserver(u"C"_ns);
+
+ svc->AddObserver(a, "Foo", false);
+ svc->AddObserver(b, "Foo", true);
+
+ // Remove from non-existent topic.
+ nsresult rv = svc->RemoveObserver(a, "Bar");
+ ASSERT_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_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(u"A"_ns);
+ 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(u"A"_ns);
+ 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(u"A"_ns);
+ 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(u"A"_ns);
+ 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(u"A"_ns);
+ RefPtr<TestObserver> b = new TestObserver(u"B"_ns);
+
+ // 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(u"A"_ns);
+ testResult(svc->AddObserver(a, "Foo", true));
+ RefPtr<TestObserver> b = new TestObserver(u"B"_ns);
+ 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(u"Observer-A"_ns);
+ RefPtr<TestObserver> bObserver = new TestObserver(u"Observer-B"_ns);
+
+ // 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.
+ const char16_t* dataA = u"Testing Notify(observer-A, topic-A)";
+ aObserver->mExpectedData = dataA;
+ bObserver->mExpectedData = dataA;
+ nsresult rv =
+ svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA);
+ testResult(rv);
+ ASSERT_EQ(aObserver->mObservations, 1);
+ ASSERT_EQ(bObserver->mObservations, 1);
+
+ // Notify topicB.
+ const char16_t* dataB = u"Testing Notify(observer-B, topic-B)";
+ bObserver->mExpectedData = dataB;
+ rv = svc->NotifyObservers(ToSupports(bObserver), topicB.get(), dataB);
+ 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);
+ 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);
+ testResult(rv);
+ ASSERT_EQ(aObserver->mObservations, 1);
+ ASSERT_EQ(bObserver->mObservations, 3);
+}
diff --git a/xpcom/tests/gtest/TestOwningNonNull.cpp b/xpcom/tests/gtest/TestOwningNonNull.cpp
new file mode 100644
index 0000000000..5f82c7b37b
--- /dev/null
+++ b/xpcom/tests/gtest/TestOwningNonNull.cpp
@@ -0,0 +1,24 @@
+/* -*- 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/OwningNonNull.h"
+#include "mozilla/RefCounted.h"
+#include "mozilla/RefPtr.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+struct OwnedRefCounted : public RefCounted<OwnedRefCounted> {
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(OwnedRefCounted)
+
+ OwnedRefCounted() = default;
+};
+
+TEST(OwningNonNull, Move)
+{
+ auto refptr = MakeRefPtr<OwnedRefCounted>();
+ OwningNonNull<OwnedRefCounted> owning(std::move(refptr));
+ EXPECT_FALSE(!!refptr);
+}
diff --git a/xpcom/tests/gtest/TestPLDHash.cpp b/xpcom/tests/gtest/TestPLDHash.cpp
new file mode 100644
index 0000000000..d302e72595
--- /dev/null
+++ b/xpcom/tests/gtest/TestPLDHash.cpp
@@ -0,0 +1,407 @@
+/* -*- 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 "gtest/gtest.h"
+#include "mozilla/gtest/MozHelpers.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>
+#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.
+static void TestCrashyOperation(const char* label, 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.
+ SAVE_GDB_SLEEP_LOCAL();
+
+ 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.
+ mozilla::gtest::DisableCrashReporter();
+
+ // Child: perform the crashy operation.
+ FILE* stderr_dup = fdopen(dup(fileno(stderr)), "w");
+ // We don't want MOZ_CRASH from the crashy operation to print out its
+ // error message and stack-trace, which would be confusing and irrelevant.
+ fclose(stderr);
+ aCrashyOperation();
+ fprintf(stderr_dup, "TestCrashyOperation %s: didn't crash?!\n", label);
+ 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 %s: 'exited' failure: %d\n", label,
+ 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 %s: 'signaled' failure: %d\n", label,
+ signum);
+ ASSERT_TRUE(false);
+ }
+ }
+
+ RESTORE_GDB_SLEEP_LOCAL();
+#endif
+}
+
+static void InitCapacityOk_InitialLengthTooBig() {
+ PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub),
+ PLDHashTable::kMaxInitialLength + 1);
+}
+
+static 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 << 8, (uint32_t)1 << 23);
+}
+
+static void InitCapacityOk_EntrySizeTooBig() {
+ // Try the smallest disallowed entry size, which is 256 bytes.
+ PLDHashTable t(PLDHashTable::StubOps(), 256);
+}
+
+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 << 7, (uint32_t)1 << 23);
+
+ // Try a too-large capacity (which aborts).
+ TestCrashyOperation("length too big", InitCapacityOk_InitialLengthTooBig);
+
+ // Try a large capacity combined with a large entry size that when multiplied
+ // overflow (causing abort).
+ TestCrashyOperation("entry store too big",
+ InitCapacityOk_InitialEntryStoreTooBig);
+
+ // Try the largest allowed entry size.
+ PLDHashTable t3(PLDHashTable::StubOps(), 255);
+
+ // Try an overly large entry size.
+ TestCrashyOperation("entry size too big", InitCapacityOk_EntrySizeTooBig);
+
+ // 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);
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wself-move"
+#endif
+ t1 = std::move(t1); // self-move
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+
+ t1 = std::move(t2); // empty overwritten with empty
+
+ PLDHashTable t3(&trivialOps, sizeof(PLDHashEntryStub));
+ PLDHashTable t4(&trivialOps, sizeof(PLDHashEntryStub));
+ t3.Add((const void*)88);
+
+ t3 = std::move(t4); // non-empty overwritten with empty
+
+ PLDHashTable t5(&trivialOps, sizeof(PLDHashEntryStub));
+ PLDHashTable t6(&trivialOps, sizeof(PLDHashEntryStub));
+ t6.Add((const void*)88);
+
+ t5 = std::move(t6); // empty overwritten with non-empty
+
+ PLDHashTable t7(&trivialOps, sizeof(PLDHashEntryStub));
+ PLDHashTable t8(std::move(t7)); // new table constructed with uninited
+
+ PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub));
+ t9.Add((const void*)88);
+ PLDHashTable t10(std::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(std::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));
+}
+
+TEST(PLDHashTableTest, WithEntryHandle)
+{
+ PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub));
+
+ PLDHashEntryHdr* entry1 =
+ t.WithEntryHandle((const void*)88, [](auto entryHandle) {
+ EXPECT_FALSE(entryHandle);
+
+ bool initEntryCalled = false;
+ PLDHashEntryHdr* entry =
+ entryHandle.OrInsert([&initEntryCalled](PLDHashEntryHdr* entry) {
+ EXPECT_TRUE(entry);
+ TrivialInitEntry(entry, (const void*)88);
+ initEntryCalled = true;
+ });
+ EXPECT_TRUE(initEntryCalled);
+ EXPECT_EQ(entryHandle.Entry(), entry);
+
+ return entry;
+ });
+ ASSERT_TRUE(entry1);
+ ASSERT_EQ(t.EntryCount(), 1u);
+
+ PLDHashEntryHdr* entry2 =
+ t.WithEntryHandle((const void*)88, [](auto entryHandle) {
+ EXPECT_TRUE(entryHandle);
+
+ bool initEntryCalled = false;
+ PLDHashEntryHdr* entry =
+ entryHandle.OrInsert([&initEntryCalled](PLDHashEntryHdr* entry) {
+ EXPECT_TRUE(entry);
+ TrivialInitEntry(entry, (const void*)88);
+ initEntryCalled = true;
+ });
+ EXPECT_FALSE(initEntryCalled);
+ EXPECT_EQ(entryHandle.Entry(), entry);
+
+ return entry;
+ });
+ ASSERT_TRUE(entry2);
+ ASSERT_EQ(t.EntryCount(), 1u);
+
+ ASSERT_EQ(entry1, entry2);
+}
+
+// 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 0000000000..a4f0ebc7a5
--- /dev/null
+++ b/xpcom/tests/gtest/TestPipes.cpp
@@ -0,0 +1,1031 @@
+/* -*- 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/gtest/MozAssertions.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Printf.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 "nsITellableStream.h"
+#include "nsIThread.h"
+#include "nsIRunnable.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.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 Runnable {
+ public:
+ 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)
+ : Runnable("nsReceiver"), mIn(in), mCount(0) {}
+
+ uint32_t GetBytesRead() { return mCount; }
+
+ private:
+ ~nsReceiver() = default;
+
+ protected:
+ nsCOMPtr<nsIInputStream> mIn;
+ uint32_t mCount;
+};
+
+static nsresult TestPipe(nsIInputStream* in, nsIOutputStream* out) {
+ RefPtr<nsReceiver> receiver = new nsReceiver(in);
+ nsresult rv;
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewNamedThread("TestPipe", 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;
+ SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern);
+ uint32_t len = strlen(buf.get());
+ rv = WriteAll(out, buf.get(), len, &writeCount);
+ if (gTrace) {
+ printf("wrote: ");
+ for (uint32_t j = 0; j < writeCount; j++) {
+ putc(buf.get()[j], stdout);
+ }
+ printf("\n");
+ }
+ 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 Runnable {
+ public:
+ 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)
+ : Runnable("nsShortReader"), 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() = default;
+
+ protected:
+ nsCOMPtr<nsIInputStream> mIn;
+ uint32_t mReceived;
+ ReentrantMonitor* mMon;
+};
+
+static nsresult TestShortWrites(nsIInputStream* in, nsIOutputStream* out) {
+ RefPtr<nsShortReader> receiver = new nsShortReader(in);
+ nsresult rv;
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewNamedThread("TestShortWrites", 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;
+ SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern);
+ uint32_t len = strlen(buf.get());
+ len = len * rand() / RAND_MAX;
+ len = std::min(1u, len);
+ rv = WriteAll(out, buf.get(), len, &writeCount);
+ if (NS_FAILED(rv)) return rv;
+ EXPECT_EQ(writeCount, len);
+ total += writeCount;
+
+ if (gTrace) printf("wrote %d bytes: %s\n", writeCount, buf.get());
+ // 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 Runnable {
+ public:
+ 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)
+ : Runnable("nsPump"), mIn(in), mOut(out), mCount(0) {}
+
+ private:
+ ~nsPump() = default;
+
+ protected:
+ nsCOMPtr<nsIInputStream> mIn;
+ nsCOMPtr<nsIOutputStream> mOut;
+ uint32_t mCount;
+};
+
+TEST(Pipes, ChainedPipes)
+{
+ nsresult rv;
+ if (gTrace) {
+ printf("TestChainedPipes\n");
+ }
+
+ nsCOMPtr<nsIInputStream> in1;
+ nsCOMPtr<nsIOutputStream> out1;
+ NS_NewPipe(getter_AddRefs(in1), getter_AddRefs(out1), 20, 1999);
+
+ nsCOMPtr<nsIInputStream> in2;
+ nsCOMPtr<nsIOutputStream> out2;
+ NS_NewPipe(getter_AddRefs(in2), getter_AddRefs(out2), 200, 401);
+
+ RefPtr<nsPump> pump = new nsPump(in1, out2);
+ if (pump == nullptr) return;
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewNamedThread("ChainedPipePump", getter_AddRefs(thread), pump);
+ if (NS_FAILED(rv)) return;
+
+ RefPtr<nsReceiver> receiver = new nsReceiver(in2);
+ if (receiver == nullptr) return;
+
+ nsCOMPtr<nsIThread> receiverThread;
+ rv = NS_NewNamedThread("ChainedPipeRecv", getter_AddRefs(receiverThread),
+ receiver);
+ if (NS_FAILED(rv)) return;
+
+ uint32_t total = 0;
+ for (uint32_t i = 0; i < ITERATIONS; i++) {
+ uint32_t writeCount;
+ SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern);
+ uint32_t len = strlen(buf.get());
+ len = len * rand() / RAND_MAX;
+ len = std::max(1u, len);
+ rv = WriteAll(out1, buf.get(), len, &writeCount);
+ if (NS_FAILED(rv)) return;
+ EXPECT_EQ(writeCount, len);
+ total += writeCount;
+
+ if (gTrace) printf("wrote %d bytes: %s\n", writeCount, buf.get());
+ }
+ if (gTrace) {
+ printf("wrote total of %d bytes\n", total);
+ }
+ rv = out1->Close();
+ if (NS_FAILED(rv)) return;
+
+ thread->Shutdown();
+ receiverThread->Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static 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");
+ }
+ NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize);
+ rv = TestPipe(in, out);
+ EXPECT_NS_SUCCEEDED(rv);
+
+ if (gTrace) {
+ printf("Testing short writes...\n");
+ }
+ NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize);
+ rv = TestShortWrites(in, out);
+ EXPECT_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);
+
+ NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize,
+ maxSize);
+
+ 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.
+ NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize,
+ maxSize, true, false); // non-blocking - reader, writer
+
+ 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.
+ nsresult rv;
+ for (uint32_t i = 0; i < aNumInitialClones; ++i) {
+ nsCOMPtr<nsIInputStream>* clone = streamList.AppendElement();
+ rv = cloneable->Clone(getter_AddRefs(*clone));
+ ASSERT_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_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_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;
+
+ NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true,
+ true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ nsresult rv =
+ writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_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_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;
+
+ NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true,
+ true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+
+ nsCOMPtr<nsIInputStream> clone;
+ nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_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_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_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_NS_FAILED(rv);
+
+ cb = new testing::OutputStreamCallback();
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_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;
+
+ NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true,
+ true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+
+ nsCOMPtr<nsIInputStream> clone;
+ nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_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_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_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_NS_FAILED(rv);
+
+ cb = new testing::OutputStreamCallback();
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_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_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_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_NS_SUCCEEDED(rv);
+
+ // The stream is again non-writeable.
+ cb = new testing::OutputStreamCallback();
+ rv = writer->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_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_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;
+
+ NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true,
+ true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ nsresult rv = reader->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_FALSE(cb->Called());
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_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;
+
+ NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true,
+ true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+
+ nsCOMPtr<nsIInputStream> clone;
+ nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone));
+ ASSERT_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_NS_SUCCEEDED(rv);
+
+ ASSERT_FALSE(cb->Called());
+
+ rv = asyncClone->AsyncWait(cb2, 0, 0, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_FALSE(cb2->Called());
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_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;
+
+ NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize,
+ maxSize);
+
+ nsTArray<char> inputData;
+
+ testing::CreateData(aDataSize, inputData);
+
+ uint32_t numWritten = 0;
+ nsresult rv =
+ writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsTArray<char> outputData;
+
+ uint32_t numRead = 0;
+ rv = reader->ReadSegments(CloseDuringReadFunc, &outputData,
+ inputData.Length(), &numRead);
+ ASSERT_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;
+
+ NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer));
+
+ nsCOMPtr<nsIAsyncInputStream> readerType1 = do_QueryInterface(reader);
+ ASSERT_TRUE(readerType1);
+
+ nsCOMPtr<nsITellableStream> 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 0000000000..c5f59072da
--- /dev/null
+++ b/xpcom/tests/gtest/TestPriorityQueue.cpp
@@ -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/. */
+
+#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.Clone();
+
+ 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.Clone();
+ 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/TestQueue.cpp b/xpcom/tests/gtest/TestQueue.cpp
new file mode 100644
index 0000000000..e6d8c07dd2
--- /dev/null
+++ b/xpcom/tests/gtest/TestQueue.cpp
@@ -0,0 +1,186 @@
+/* -*- 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/Queue.h"
+#include "gtest/gtest.h"
+#include <array>
+
+using namespace mozilla;
+
+namespace TestQueue {
+
+struct Movable {
+ Movable() : mDestructionCounter(nullptr) {}
+ explicit Movable(uint32_t* aDestructionCounter)
+ : mDestructionCounter(aDestructionCounter) {}
+
+ ~Movable() {
+ if (mDestructionCounter) {
+ (*mDestructionCounter)++;
+ }
+ }
+
+ Movable(Movable&& aOther) : mDestructionCounter(aOther.mDestructionCounter) {
+ aOther.mDestructionCounter = nullptr;
+ }
+
+ uint32_t* mDestructionCounter;
+};
+
+template <size_t N, size_t ItemsPerPage>
+void PushMovables(Queue<Movable, ItemsPerPage>& aQueue,
+ std::array<uint32_t, N>& aDestructionCounters) {
+ auto oldDestructionCounters = aDestructionCounters;
+ auto oldCount = aQueue.Count();
+ for (uint32_t i = 0; i < N; ++i) {
+ aQueue.Push(Movable(&aDestructionCounters[i]));
+ }
+ for (uint32_t i = 0; i < N; ++i) {
+ EXPECT_EQ(aDestructionCounters[i], oldDestructionCounters[i]);
+ }
+ EXPECT_EQ(aQueue.Count(), oldCount + N);
+ EXPECT_FALSE(aQueue.IsEmpty());
+}
+
+template <size_t N>
+void ExpectCounts(const std::array<uint32_t, N>& aDestructionCounters,
+ uint32_t aExpected) {
+ for (const auto& counters : aDestructionCounters) {
+ EXPECT_EQ(counters, aExpected);
+ }
+}
+
+TEST(Queue, Clear)
+{
+ std::array<uint32_t, 32> counts{0};
+
+ Queue<Movable, 8> queue;
+ PushMovables(queue, counts);
+ queue.Clear();
+ ExpectCounts(counts, 1);
+ EXPECT_EQ(queue.Count(), 0u);
+ EXPECT_TRUE(queue.IsEmpty());
+}
+
+TEST(Queue, Destroy)
+{
+ std::array<uint32_t, 32> counts{0};
+
+ {
+ Queue<Movable, 8> queue;
+ PushMovables(queue, counts);
+ }
+ ExpectCounts(counts, 1u);
+}
+
+TEST(Queue, MoveConstruct)
+{
+ std::array<uint32_t, 32> counts{0};
+
+ {
+ Queue<Movable, 8> queue;
+ PushMovables(queue, counts);
+
+ Queue<Movable, 8> queue2(std::move(queue));
+ EXPECT_EQ(queue2.Count(), 32u);
+ EXPECT_FALSE(queue2.IsEmpty());
+ // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move)
+ EXPECT_EQ(queue.Count(), 0u);
+ // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move)
+ EXPECT_TRUE(queue.IsEmpty());
+ ExpectCounts(counts, 0u);
+ }
+ ExpectCounts(counts, 1u);
+}
+
+TEST(Queue, MoveAssign)
+{
+ std::array<uint32_t, 32> counts{0};
+ std::array<uint32_t, 32> counts2{0};
+
+ {
+ Queue<Movable, 8> queue;
+ PushMovables(queue, counts);
+
+ {
+ Queue<Movable, 8> queue2;
+ PushMovables(queue2, counts2);
+
+ queue = std::move(queue2);
+ ExpectCounts(counts, 1);
+ ExpectCounts(counts2, 0);
+ EXPECT_EQ(queue.Count(), 32u);
+ EXPECT_FALSE(queue.IsEmpty());
+ // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move)
+ EXPECT_EQ(queue2.Count(), 0u);
+ // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move)
+ EXPECT_TRUE(queue2.IsEmpty());
+ }
+ ExpectCounts(counts, 1);
+ ExpectCounts(counts2, 0);
+ EXPECT_EQ(queue.Count(), 32u);
+ EXPECT_FALSE(queue.IsEmpty());
+ }
+ ExpectCounts(counts, 1);
+ ExpectCounts(counts2, 1);
+}
+
+TEST(Queue, PopOrder)
+{
+ std::array<uint32_t, 32> counts{0};
+ Queue<Movable, 8> queue;
+ PushMovables(queue, counts);
+
+ for (auto& count : counts) {
+ EXPECT_EQ(count, 0u);
+ {
+ Movable popped = queue.Pop();
+ EXPECT_EQ(popped.mDestructionCounter, &count);
+ EXPECT_EQ(count, 0u);
+ }
+ EXPECT_EQ(count, 1u);
+ }
+ EXPECT_TRUE(queue.IsEmpty());
+ EXPECT_EQ(queue.Count(), 0u);
+}
+
+void DoPushPopSequence(Queue<uint32_t, 8>& aQueue, uint32_t& aInSerial,
+ uint32_t& aOutSerial, uint32_t aPush, uint32_t aPop) {
+ auto initialCount = aQueue.Count();
+ for (uint32_t i = 0; i < aPush; ++i) {
+ aQueue.Push(aInSerial++);
+ }
+ EXPECT_EQ(aQueue.Count(), initialCount + aPush);
+ for (uint32_t i = 0; i < aPop; ++i) {
+ uint32_t popped = aQueue.Pop();
+ EXPECT_EQ(popped, aOutSerial++);
+ }
+ EXPECT_EQ(aQueue.Count(), initialCount + aPush - aPop);
+}
+
+void PushPopPushPop(uint32_t aPush1, uint32_t aPop1, uint32_t aPush2,
+ uint32_t aPop2) {
+ Queue<uint32_t, 8> queue;
+ uint32_t inSerial = 0;
+ uint32_t outSerial = 0;
+ DoPushPopSequence(queue, inSerial, outSerial, aPush1, aPop1);
+ DoPushPopSequence(queue, inSerial, outSerial, aPush2, aPop2);
+}
+
+TEST(Queue, PushPopSequence)
+{
+ for (uint32_t push1 = 0; push1 < 16; ++push1) {
+ for (uint32_t pop1 = 0; pop1 < push1; ++pop1) {
+ for (uint32_t push2 = 0; push2 < 16; ++push2) {
+ for (uint32_t pop2 = 0; pop2 < push1 - pop1 + push2; ++pop2) {
+ PushPopPushPop(push1, pop1, push2, pop2);
+ }
+ }
+ }
+ }
+}
+
+} // namespace TestQueue
diff --git a/xpcom/tests/gtest/TestRWLock.cpp b/xpcom/tests/gtest/TestRWLock.cpp
new file mode 100644
index 0000000000..eee392f709
--- /dev/null
+++ b/xpcom/tests/gtest/TestRWLock.cpp
@@ -0,0 +1,214 @@
+/* -*- 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 "mozilla/Atomics.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsIThread.h"
+#include "gtest/gtest.h"
+
+using mozilla::AutoReadLock;
+using mozilla::AutoTryReadLock;
+using mozilla::AutoTryWriteLock;
+using mozilla::AutoWriteLock;
+using mozilla::RWLock;
+
+static const size_t sNumThreads = 4;
+static const size_t sOuterIterations = 100;
+static const size_t sInnerIterations = 100;
+static const size_t sWriteLockIteration = 10;
+
+// Based on example code from _Programming with POSIX Threads_. Not an actual
+// test of correctness, but more of a "does this work at all" sort of test.
+
+class RWLockRunnable : public mozilla::Runnable {
+ public:
+ RWLockRunnable(RWLock* aRWLock, mozilla::Atomic<size_t>* aSharedData)
+ : mozilla::Runnable("RWLockRunnable"),
+ mRWLock(aRWLock),
+ mSharedData(aSharedData) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ ~RWLockRunnable() = default;
+
+ RWLock* mRWLock;
+ mozilla::Atomic<size_t>* mSharedData;
+};
+
+NS_IMETHODIMP
+RWLockRunnable::Run() {
+ for (size_t i = 0; i < sOuterIterations; ++i) {
+ if (i % sWriteLockIteration == 0) {
+ mozilla::AutoWriteLock lock(*mRWLock);
+
+ ++(*mSharedData);
+ } else {
+ mozilla::AutoReadLock lock(*mRWLock);
+
+ // Loop and try to force other threads to run, but check that our
+ // shared data isn't being modified by them.
+ size_t initialValue = *mSharedData;
+ for (size_t j = 0; j < sInnerIterations; ++j) {
+ EXPECT_EQ(initialValue, *mSharedData);
+
+ // This is a magic yield call.
+ PR_Sleep(PR_INTERVAL_NO_WAIT);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+TEST(RWLock, SmokeTest)
+{
+ nsCOMPtr<nsIThread> threads[sNumThreads];
+ RWLock rwlock MOZ_UNANNOTATED("test lock");
+ mozilla::Atomic<size_t> data(0);
+
+ for (size_t i = 0; i < sNumThreads; ++i) {
+ nsCOMPtr<nsIRunnable> event = new RWLockRunnable(&rwlock, &data);
+ NS_NewNamedThread("RWLockTester", getter_AddRefs(threads[i]), event);
+ }
+
+ // Wait for all the threads to finish.
+ for (size_t i = 0; i < sNumThreads; ++i) {
+ nsresult rv = threads[i]->Shutdown();
+ EXPECT_NS_SUCCEEDED(rv);
+ }
+
+ EXPECT_EQ(data, (sOuterIterations / sWriteLockIteration) * sNumThreads);
+}
+
+template <typename Function>
+static std::invoke_result_t<Function> RunOnBackgroundThread(
+ Function&& aFunction) {
+ using Result = std::invoke_result_t<Function>;
+ nsCOMPtr<nsISerialEventTarget> thread;
+ MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
+ "TestRWLock Background Thread", getter_AddRefs(thread)));
+ mozilla::Maybe<Result> tryResult;
+ RefPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction(__func__, [&] { tryResult.emplace(aFunction()); });
+ MOZ_ALWAYS_SUCCEEDS(
+ mozilla::SyncRunnable::DispatchToThread(thread.get(), runnable));
+ return *tryResult;
+}
+
+TEST(RWLock, AutoTryReadLock)
+{
+ RWLock l1 MOZ_UNANNOTATED("autotryreadlock");
+ {
+ AutoTryReadLock autol1(l1);
+
+ EXPECT_TRUE(autol1);
+
+ AutoTryReadLock autol2(l1);
+ EXPECT_TRUE(autol2);
+
+ EXPECT_TRUE(RunOnBackgroundThread([&] {
+ AutoTryReadLock lock(l1);
+ return !!lock;
+ }));
+
+ EXPECT_TRUE(autol1);
+ EXPECT_TRUE(autol2);
+
+ {
+ RWLock l2 MOZ_UNANNOTATED("autotryreadlock2");
+ AutoTryReadLock autol3(l2);
+
+ EXPECT_TRUE(autol3);
+ }
+
+ EXPECT_TRUE(autol1);
+ EXPECT_TRUE(autol2);
+ }
+
+ {
+ AutoWriteLock autol4(l1);
+ MOZ_ASSERT(l1.LockedForWritingByCurrentThread());
+
+ AutoTryReadLock autol5(l1);
+ EXPECT_FALSE(autol5);
+
+ EXPECT_FALSE(RunOnBackgroundThread([&] {
+ AutoTryReadLock lock(l1);
+ return !!lock;
+ }));
+ }
+
+ AutoTryReadLock autol6(l1);
+ EXPECT_TRUE(autol6);
+
+ EXPECT_TRUE(RunOnBackgroundThread([&] {
+ AutoTryReadLock lock(l1);
+ return !!lock;
+ }));
+}
+
+TEST(RWLock, AutoTryWriteLock)
+{
+ RWLock l1 MOZ_UNANNOTATED("autotrywritelock");
+ {
+ AutoTryWriteLock autol1(l1);
+
+ EXPECT_TRUE(autol1);
+
+ AutoTryReadLock autol2(l1);
+ EXPECT_FALSE(autol2);
+
+ EXPECT_FALSE(RunOnBackgroundThread([&] {
+ AutoTryWriteLock lock(l1);
+ return !!lock;
+ }));
+
+ EXPECT_TRUE(autol1);
+ EXPECT_FALSE(autol2);
+
+ {
+ RWLock l2 MOZ_UNANNOTATED("autotrywritelock2");
+ AutoTryWriteLock autol3(l2);
+
+ EXPECT_TRUE(autol3);
+ }
+
+ EXPECT_TRUE(autol1);
+ EXPECT_FALSE(autol2);
+ }
+
+ {
+ AutoReadLock autol4(l1);
+
+ AutoTryWriteLock autol5(l1);
+ EXPECT_FALSE(autol5);
+
+ EXPECT_FALSE(RunOnBackgroundThread([&] {
+ AutoTryWriteLock lock(l1);
+ return !!lock;
+ }));
+ }
+
+ {
+ AutoWriteLock autol6(l1);
+ MOZ_ASSERT(l1.LockedForWritingByCurrentThread());
+
+ AutoTryWriteLock autol7(l1);
+ EXPECT_FALSE(autol7);
+
+ EXPECT_FALSE(RunOnBackgroundThread([&] {
+ AutoTryWriteLock lock(l1);
+ return !!lock;
+ }));
+ }
+
+ AutoTryWriteLock autol8(l1);
+ EXPECT_TRUE(autol8);
+}
diff --git a/xpcom/tests/gtest/TestRacingServiceManager.cpp b/xpcom/tests/gtest/TestRacingServiceManager.cpp
new file mode 100644
index 0000000000..ad6c08d97b
--- /dev/null
+++ b/xpcom/tests/gtest/TestRacingServiceManager.cpp
@@ -0,0 +1,260 @@
+/* -*- 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 "nsXULAppAPI.h"
+#include "nsIThread.h"
+
+#include "nsComponentManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pratom.h"
+#include "prmon.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/gtest/MozAssertions.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() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ Factory() : mFirstComponentCreated(false) {}
+
+ NS_IMETHOD CreateInstance(const nsIID& aIID, void** aResult) override;
+
+ bool mFirstComponentCreated;
+};
+
+NS_IMPL_ISUPPORTS(Factory, nsIFactory)
+
+class Component1 final : public nsISupports {
+ ~Component1() = default;
+
+ 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() = default;
+
+ 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(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_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()
+ : mozilla::Runnable("TestRacingServiceManager::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;
+
+TEST(RacingServiceManager, Test)
+{
+ nsresult rv;
+
+ gFactory = new Factory();
+ NS_ADDREF(gFactory);
+
+ nsComponentManagerImpl::gComponentManager->RegisterFactory(
+ kFactoryCID2, "factory1", FACTORY_CONTRACTID, gFactory);
+ nsComponentManagerImpl::gComponentManager->RegisterFactory(
+ kFactoryCID1, "factory2", nullptr, gFactory);
+
+ AutoCreateAndDestroyReentrantMonitor mon1(&gReentrantMonitor);
+
+ RefPtr<TestRunnable> runnable = new TestRunnable();
+ ASSERT_TRUE(runnable);
+
+ // Run the classID test
+ nsCOMPtr<nsIThread> newThread;
+ rv = NS_NewNamedThread("RacingServMan", getter_AddRefs(newThread), runnable);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ {
+ ReentrantMonitorAutoEnter mon2(*gReentrantMonitor);
+
+ gMainThreadWaiting = true;
+ mon2.Notify();
+
+ while (!gCreateInstanceCalled) {
+ mon2.Wait();
+ }
+ }
+
+ nsCOMPtr<nsISupports> component(do_GetService(kFactoryCID1, &rv));
+ ASSERT_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_NS_SUCCEEDED(rv);
+
+ {
+ ReentrantMonitorAutoEnter mon3(*gReentrantMonitor);
+
+ gMainThreadWaiting = true;
+ mon3.Notify();
+
+ while (!gCreateInstanceCalled) {
+ mon3.Wait();
+ }
+ }
+
+ component = do_GetService(FACTORY_CONTRACTID, &rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ NS_RELEASE(gFactory);
+}
+
+} // namespace TestRacingServiceManager
diff --git a/xpcom/tests/gtest/TestRecursiveMutex.cpp b/xpcom/tests/gtest/TestRecursiveMutex.cpp
new file mode 100644
index 0000000000..57fb6fc038
--- /dev/null
+++ b/xpcom/tests/gtest/TestRecursiveMutex.cpp
@@ -0,0 +1,25 @@
+/* -*- 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 "mozilla/RecursiveMutex.h"
+#include "gtest/gtest.h"
+
+using mozilla::RecursiveMutex;
+using mozilla::RecursiveMutexAutoLock;
+
+// Basic test to make sure the underlying implementation of RecursiveMutex is,
+// well, actually recursively acquirable.
+
+TEST(RecursiveMutex, SmokeTest)
+MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ RecursiveMutex mutex("testing mutex");
+
+ RecursiveMutexAutoLock lock1(mutex);
+ RecursiveMutexAutoLock lock2(mutex);
+
+ //...and done.
+}
diff --git a/xpcom/tests/gtest/TestRustRegex.cpp b/xpcom/tests/gtest/TestRustRegex.cpp
new file mode 100644
index 0000000000..38f7119b6b
--- /dev/null
+++ b/xpcom/tests/gtest/TestRustRegex.cpp
@@ -0,0 +1,181 @@
+/* -*- 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/RustRegex.h"
+
+// This file is adapted from the test.c file in the `rure` crate, but modified
+// to use gtest and the `RustRegex` wrapper.
+
+namespace mozilla {
+
+TEST(TestRustRegex, IsMatch)
+{
+ RustRegex re("\\p{So}$");
+ ASSERT_TRUE(re.IsValid());
+ ASSERT_TRUE(re.IsMatch("snowman: \xE2\x98\x83"));
+}
+
+TEST(TestRustRegex, ShortestMatch)
+{
+ RustRegex re("a+");
+ ASSERT_TRUE(re.IsValid());
+
+ Maybe<size_t> match = re.ShortestMatch("aaaaa");
+ ASSERT_TRUE(match);
+ EXPECT_EQ(*match, 1u);
+}
+
+TEST(TestRustRegex, Find)
+{
+ RustRegex re("\\p{So}$");
+ ASSERT_TRUE(re.IsValid());
+
+ auto match = re.Find("snowman: \xE2\x98\x83");
+ ASSERT_TRUE(match);
+ EXPECT_EQ(match->start, 9u);
+ EXPECT_EQ(match->end, 12u);
+}
+
+TEST(TestRustRegex, Captures)
+{
+ RustRegex re(".(.*(?P<snowman>\\p{So}))$");
+ ASSERT_TRUE(re);
+
+ auto captures = re.FindCaptures("snowman: \xE2\x98\x83");
+ ASSERT_TRUE(captures);
+ EXPECT_EQ(captures.Length(), 3u);
+ EXPECT_EQ(re.CaptureNameIndex("snowman"), 2);
+
+ auto match = captures[2];
+ ASSERT_TRUE(match);
+ EXPECT_EQ(match->start, 9u);
+ EXPECT_EQ(match->end, 12u);
+}
+
+TEST(TestRustRegex, Iter)
+{
+ RustRegex re("\\w+(\\w)");
+ ASSERT_TRUE(re);
+
+ auto it = re.IterMatches("abc xyz");
+ ASSERT_TRUE(it);
+
+ auto match = it.Next();
+ ASSERT_TRUE(match);
+ EXPECT_EQ(match->start, 0u);
+ EXPECT_EQ(match->end, 3u);
+
+ auto captures = it.NextCaptures();
+ ASSERT_TRUE(captures);
+
+ auto capture = captures[1];
+ ASSERT_TRUE(capture);
+ EXPECT_EQ(capture->start, 6u);
+ EXPECT_EQ(capture->end, 7u);
+}
+
+TEST(TestRustRegex, IterCaptureNames)
+{
+ RustRegex re("(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})");
+ ASSERT_TRUE(re);
+
+ auto it = re.IterCaptureNames();
+ Maybe<const char*> result = it.Next();
+ ASSERT_TRUE(result.isSome());
+ EXPECT_STREQ(*result, "");
+
+ result = it.Next();
+ ASSERT_TRUE(result.isSome());
+ EXPECT_STREQ(*result, "year");
+
+ result = it.Next();
+ ASSERT_TRUE(result.isSome());
+ EXPECT_STREQ(*result, "month");
+
+ result = it.Next();
+ ASSERT_TRUE(result.isSome());
+ EXPECT_STREQ(*result, "day");
+
+ result = it.Next();
+ ASSERT_TRUE(result.isNothing());
+}
+
+/*
+ * This tests whether we can set the flags correctly. In this case, we disable
+ * all flags, which includes disabling Unicode mode. When we disable Unicode
+ * mode, we can match arbitrary possibly invalid UTF-8 bytes, such as \xFF.
+ * (When Unicode mode is enabled, \xFF won't match .)
+ */
+TEST(TestRustRegex, Flags)
+{
+ {
+ RustRegex re(".");
+ ASSERT_TRUE(re);
+ ASSERT_FALSE(re.IsMatch("\xFF"));
+ }
+ {
+ RustRegex re(".", RustRegexOptions().Unicode(false));
+ ASSERT_TRUE(re);
+ ASSERT_TRUE(re.IsMatch("\xFF"));
+ }
+}
+
+TEST(TestRustRegex, CompileErrorSizeLimit)
+{
+ RustRegex re("\\w{100}", RustRegexOptions().SizeLimit(0));
+ EXPECT_FALSE(re);
+}
+
+TEST(TestRustRegex, SetMatches)
+{
+ RustRegexSet set(nsTArray<std::string_view>{"foo", "barfoo", "\\w+", "\\d+",
+ "foobar", "bar"});
+
+ ASSERT_TRUE(set);
+ EXPECT_EQ(set.Length(), 6u);
+ EXPECT_TRUE(set.IsMatch("foobar"));
+ EXPECT_FALSE(set.IsMatch(""));
+
+ auto matches = set.Matches("foobar");
+ EXPECT_TRUE(matches.matchedAny);
+ EXPECT_EQ(matches.matches.Length(), 6u);
+
+ nsTArray<bool> expectedMatches{true, false, true, false, true, true};
+ EXPECT_EQ(matches.matches, expectedMatches);
+}
+
+TEST(TestRustRegex, SetMatchStart)
+{
+ RustRegexSet re(nsTArray<std::string_view>{"foo", "bar", "fooo"});
+ EXPECT_TRUE(re);
+ EXPECT_EQ(re.Length(), 3u);
+
+ EXPECT_FALSE(re.IsMatch("foobiasdr", 2));
+
+ {
+ auto matches = re.Matches("fooobar");
+ EXPECT_TRUE(matches.matchedAny);
+ nsTArray<bool> expectedMatches{true, true, true};
+ EXPECT_EQ(matches.matches, expectedMatches);
+ }
+
+ {
+ auto matches = re.Matches("fooobar", 1);
+ EXPECT_TRUE(matches.matchedAny);
+ nsTArray<bool> expectedMatches{false, true, false};
+ EXPECT_EQ(matches.matches, expectedMatches);
+ }
+}
+
+TEST(TestRustRegex, RegexSetOptions)
+{
+ RustRegexSet re(nsTArray<std::string_view>{"\\w{100}"},
+ RustRegexOptions().SizeLimit(0));
+ EXPECT_FALSE(re);
+}
+
+} // namespace mozilla
diff --git a/xpcom/tests/gtest/TestSTLWrappers.cpp b/xpcom/tests/gtest/TestSTLWrappers.cpp
new file mode 100644
index 0000000000..31b658f764
--- /dev/null
+++ b/xpcom/tests/gtest/TestSTLWrappers.cpp
@@ -0,0 +1,65 @@
+#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
+
+// 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
+
+#include "gtest/gtest.h"
+
+#include "mozilla/gtest/MozHelpers.h"
+
+void ShouldAbort() {
+ ZERO_GDB_SLEEP();
+
+ mozilla::gtest::DisableCrashReporter();
+
+ std::vector<int> v;
+
+ TRY {
+ // v.at(1) on empty v should abort; NOT throw an exception
+
+ (void)v.at(1);
+ }
+ 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);
+}
+
+#if defined(XP_WIN) || (defined(XP_MACOSX) && !defined(MOZ_DEBUG))
+TEST(STLWrapper, DISABLED_ShouldAbortDeathTest)
+#else
+TEST(STLWrapper, ShouldAbortDeathTest)
+#endif
+{
+ ASSERT_DEATH_IF_SUPPORTED(ShouldAbort(),
+#ifdef __GLIBCXX__
+ // Only libstdc++ will print this message.
+ "terminate called after throwing an instance of "
+ "'std::out_of_range'|vector::_M_range_check"
+#else
+ ""
+#endif
+ );
+}
diff --git a/xpcom/tests/gtest/TestSegmentedBuffer.cpp b/xpcom/tests/gtest/TestSegmentedBuffer.cpp
new file mode 100644
index 0000000000..136e35d489
--- /dev/null
+++ b/xpcom/tests/gtest/TestSegmentedBuffer.cpp
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "../../io/nsSegmentedBuffer.h"
+#include "nsIEventTarget.h"
+
+using namespace mozilla;
+
+TEST(SegmentedBuffer, AppendAndDelete)
+{
+ auto buf = MakeUnique<nsSegmentedBuffer>();
+ buf->Init(4);
+ char* seg;
+ bool empty;
+ seg = buf->AppendNewSegment();
+ EXPECT_TRUE(seg) << "AppendNewSegment failed";
+ seg = buf->AppendNewSegment();
+ EXPECT_TRUE(seg) << "AppendNewSegment failed";
+ seg = buf->AppendNewSegment();
+ EXPECT_TRUE(seg) << "AppendNewSegment failed";
+ empty = buf->DeleteFirstSegment();
+ EXPECT_TRUE(!empty) << "DeleteFirstSegment failed";
+ empty = buf->DeleteFirstSegment();
+ EXPECT_TRUE(!empty) << "DeleteFirstSegment failed";
+ seg = buf->AppendNewSegment();
+ EXPECT_TRUE(seg) << "AppendNewSegment failed";
+ seg = buf->AppendNewSegment();
+ EXPECT_TRUE(seg) << "AppendNewSegment failed";
+ seg = buf->AppendNewSegment();
+ EXPECT_TRUE(seg) << "AppendNewSegment failed";
+ empty = buf->DeleteFirstSegment();
+ EXPECT_TRUE(!empty) << "DeleteFirstSegment failed";
+ empty = buf->DeleteFirstSegment();
+ EXPECT_TRUE(!empty) << "DeleteFirstSegment failed";
+ empty = buf->DeleteFirstSegment();
+ EXPECT_TRUE(!empty) << "DeleteFirstSegment failed";
+ empty = buf->DeleteFirstSegment();
+ EXPECT_TRUE(empty) << "DeleteFirstSegment failed";
+}
diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp
new file mode 100644
index 0000000000..a2c1a077e4
--- /dev/null
+++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp
@@ -0,0 +1,665 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsIPipe.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "Helpers.h"
+
+using namespace mozilla;
+
+// This helper class is used to call OnInputStreamReady with the right stream
+// as argument.
+class InputStreamCallback final : public nsIInputStreamCallback {
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ InputStreamCallback(nsIAsyncInputStream* aStream,
+ nsIInputStreamCallback* aCallback)
+ : mStream(aStream), mCallback(aCallback) {}
+
+ NS_IMETHOD
+ OnInputStreamReady(nsIAsyncInputStream* aStream) override {
+ return mCallback->OnInputStreamReady(mStream);
+ }
+
+ private:
+ ~InputStreamCallback() = default;
+};
+
+NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback)
+
+/* 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 nsIAsyncInputStream {
+ nsCOMPtr<nsIInputStream> mStream;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonSeekableStringStream(const nsACString& aBuffer) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ explicit NonSeekableStringStream(nsIInputStream* aStream)
+ : mStream(aStream) {}
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return mStream->Available(aLength); }
+
+ NS_IMETHOD
+ StreamStatus() override { return mStream->StreamStatus(); }
+
+ 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);
+ }
+
+ NS_IMETHOD
+ CloseWithStatus(nsresult aStatus) override {
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mStream);
+ if (!async) {
+ MOZ_CRASH("This should not happen.");
+ return NS_ERROR_FAILURE;
+ }
+
+ return async->CloseWithStatus(aStatus);
+ }
+
+ NS_IMETHOD
+ AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override {
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mStream);
+ if (!async) {
+ MOZ_CRASH("This should not happen.");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<InputStreamCallback> callback =
+ new InputStreamCallback(this, aCallback);
+
+ return async->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget);
+ }
+
+ private:
+ ~NonSeekableStringStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(NonSeekableStringStream, nsIInputStream, nsIAsyncInputStream)
+
+// Helper function for creating a seekable nsIInputStream + a SlicedInputStream.
+static 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.forget(), aStart, aLength);
+}
+
+// Helper function for creating a non-seekable nsIInputStream + a
+// SlicedInputStream.
+static 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.forget(), 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);
+}
+
+// Seek test NS_SEEK_SET
+TEST(TestSlicedInputStream, Seek_SET)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ RefPtr<SlicedInputStream> sis;
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), buf);
+ sis = new SlicedInputStream(stream.forget(), 1, buf.Length());
+ }
+
+ ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_SET, 1));
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)buf.Length() - 2, length);
+
+ char buf2[4096];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)buf.Length() - 2, count);
+ ASSERT_EQ(0, strncmp(buf2, "llo world", count));
+}
+
+// Seek test NS_SEEK_CUR
+TEST(TestSlicedInputStream, Seek_CUR)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ RefPtr<SlicedInputStream> sis;
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), buf);
+
+ sis = new SlicedInputStream(stream.forget(), 1, buf.Length());
+ }
+
+ ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_CUR, 1));
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)buf.Length() - 2, length);
+
+ char buf2[3];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)3, count);
+ ASSERT_EQ(0, strncmp(buf2, "llo", count));
+
+ ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_CUR, 1));
+
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)3, count);
+ ASSERT_EQ(0, strncmp(buf2, "wor", count));
+}
+
+// Seek test NS_SEEK_END - length > real one
+TEST(TestSlicedInputStream, Seek_END_Bigger)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ RefPtr<SlicedInputStream> sis;
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), buf);
+
+ sis = new SlicedInputStream(stream.forget(), 2, buf.Length());
+ }
+
+ ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_END, -5));
+
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), buf);
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(stream);
+ ASSERT_EQ(NS_OK, seekStream->Seek(nsISeekableStream::NS_SEEK_END, -5));
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)5, length);
+
+ ASSERT_EQ(NS_OK, stream->Available(&length));
+ ASSERT_EQ((uint64_t)5, length);
+
+ char buf2[5];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)5, count);
+ ASSERT_EQ(0, strncmp(buf2, "world", count));
+
+ ASSERT_EQ(NS_OK, stream->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)5, count);
+ ASSERT_EQ(0, strncmp(buf2, "world", count));
+}
+
+// Seek test NS_SEEK_END - length < real one
+TEST(TestSlicedInputStream, Seek_END_Lower)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ RefPtr<SlicedInputStream> sis;
+ {
+ nsCOMPtr<nsIInputStream> stream;
+ NS_NewCStringInputStream(getter_AddRefs(stream), buf);
+
+ sis = new SlicedInputStream(stream.forget(), 2, 6);
+ }
+
+ ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_END, -3));
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, sis->Available(&length));
+ ASSERT_EQ((uint64_t)3, length);
+
+ char buf2[5];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ((uint64_t)3, count);
+ ASSERT_EQ(0, strncmp(buf2, " wo", count));
+}
+
+// Check the nsIAsyncInputStream interface
+TEST(TestSlicedInputStream, NoAsyncInputStream)
+{
+ const size_t kBufSize = 4096;
+
+ nsCString buf;
+ nsCOMPtr<nsIInputStream> sis =
+ CreateSeekableStreams(kBufSize, 0, kBufSize, buf);
+
+ // If the stream is not asyncInputStream, also SIS is not.
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(sis);
+ ASSERT_TRUE(!async);
+}
+
+TEST(TestSlicedInputStream, AsyncInputStream)
+{
+ nsCOMPtr<nsIAsyncInputStream> reader;
+ nsCOMPtr<nsIAsyncOutputStream> writer;
+
+ const uint32_t segmentSize = 1024;
+ const uint32_t numSegments = 1;
+
+ NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true,
+ true, // non-blocking - reader, writer
+ segmentSize, numSegments);
+
+ nsTArray<char> inputData;
+ testing::CreateData(segmentSize, inputData);
+
+ // We have to wrap the reader because it implements only a partial
+ // nsISeekableStream interface. When ::Seek() is called, it does a MOZ_CRASH.
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<NonSeekableStringStream> wrapper =
+ new NonSeekableStringStream(reader);
+
+ sis = new SlicedInputStream(wrapper.forget(), 500, 500);
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(sis);
+ ASSERT_TRUE(!!async);
+
+ RefPtr<testing::InputStreamCallback> cb = new testing::InputStreamCallback();
+
+ nsresult rv = async->AsyncWait(cb, 0, 0, nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_FALSE(cb->Called());
+
+ uint32_t numWritten = 0;
+ rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_TRUE(cb->Called());
+
+ inputData.RemoveElementsAt(0, 500);
+ inputData.RemoveElementsAt(500, 24);
+
+ testing::ConsumeAndValidateStream(async, inputData);
+}
+
+TEST(TestSlicedInputStream, QIInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ for (int i = 0; i < 4; i++) {
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, i % 2, i > 1);
+
+ sis = new SlicedInputStream(stream.forget(), 0, 5);
+ }
+
+ {
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_EQ(!!(i % 2), !!qi);
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_EQ(i > 1, !!qi);
+ }
+ }
+}
+
+TEST(TestSlicedInputStream, InputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, true, false);
+
+ sis = new SlicedInputStream(stream.forget(), 0, 5);
+ }
+
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_TRUE(!!qi);
+
+ int64_t size;
+ nsresult rv = qi->Length(&size);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(5, size);
+}
+
+TEST(TestSlicedInputStream, NegativeInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, true, false, NS_OK, true);
+
+ sis = new SlicedInputStream(stream.forget(), 0, 5);
+ }
+
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_TRUE(!!qi);
+
+ int64_t size;
+ nsresult rv = qi->Length(&size);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(-1, size);
+}
+
+TEST(TestSlicedInputStream, AsyncInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, false, true);
+
+ sis = new SlicedInputStream(stream.forget(), 0, 5);
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_TRUE(!!qi);
+
+ RefPtr<testing::LengthCallback> callback = new testing::LengthCallback();
+
+ nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestSlicedInputStream, AsyncInputStreamLength)"_ns,
+ [&]() { return callback->Called(); }));
+ ASSERT_EQ(5, callback->Size());
+}
+
+TEST(TestSlicedInputStream, NegativeAsyncInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, false, true, NS_OK, true);
+
+ sis = new SlicedInputStream(stream.forget(), 0, 5);
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_TRUE(!!qi);
+
+ RefPtr<testing::LengthCallback> callback = new testing::LengthCallback();
+
+ nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestSlicedInputStream, NegativeAsyncInputStreamLength)"_ns,
+ [&]() { return callback->Called(); }));
+ ASSERT_EQ(-1, callback->Size());
+}
+
+TEST(TestSlicedInputStream, AbortLengthCallback)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> sis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, false, true, NS_OK, true);
+
+ sis = new SlicedInputStream(stream.forget(), 0, 5);
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(sis);
+ ASSERT_TRUE(!!qi);
+
+ RefPtr<testing::LengthCallback> callback1 = new testing::LengthCallback();
+ nsresult rv = qi->AsyncLengthWait(callback1, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ RefPtr<testing::LengthCallback> callback2 = new testing::LengthCallback();
+ rv = qi->AsyncLengthWait(callback2, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(TestSlicedInputStream, AbortLengthCallback)"_ns,
+ [&]() { return callback2->Called(); }));
+ ASSERT_TRUE(!callback1->Called());
+ ASSERT_EQ(-1, callback2->Size());
+}
diff --git a/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp b/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp
new file mode 100644
index 0000000000..10e2b71a69
--- /dev/null
+++ b/xpcom/tests/gtest/TestSmallArrayLRUCache.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 "gtest/gtest.h"
+
+#include "mozilla/SmallArrayLRUCache.h"
+
+#include <algorithm>
+#include <cstring>
+#include <utility>
+
+using Key = unsigned;
+
+struct Value {
+ Value() : m(unsigned(-1)) {}
+ explicit Value(unsigned a) : m(a) {}
+
+ bool operator==(const Value& aOther) const { return m == aOther.m; }
+ bool operator!=(const Value& aOther) const { return m != aOther.m; }
+
+ unsigned m;
+};
+
+constexpr static unsigned CacheSize = 8;
+
+using TestCache = mozilla::SmallArrayLRUCache<Key, Value, CacheSize>;
+
+// This struct embeds a given object type between two "guard" objects, to check
+// if anything is written out of bounds.
+template <typename T>
+struct Boxed {
+ constexpr static size_t GuardSize = std::max(sizeof(T), size_t(256));
+
+ // A Guard is a character array with a pre-set content that can be checked for
+ // unwanted changes.
+ struct Guard {
+ char mGuard[GuardSize];
+ explicit Guard(char aValue) { memset(&mGuard, aValue, GuardSize); }
+ void Check(char aValue) {
+ for (const char& c : mGuard) {
+ ASSERT_EQ(c, aValue);
+ }
+ }
+ };
+
+ Guard mGuardBefore;
+ T mObject;
+ Guard mGuardAfter;
+
+ template <typename... Ts>
+ explicit Boxed(Ts&&... aTs)
+ : mGuardBefore(0x5a),
+ mObject(std::forward<Ts>(aTs)...),
+ mGuardAfter(0xa5) {
+ Check();
+ }
+
+ ~Boxed() { Check(); }
+
+ T& Object() { return mObject; }
+ const T& Object() const { return mObject; }
+
+ void Check() {
+ mGuardBefore.Check(0x5a);
+ mGuardAfter.Check(0xa5);
+ }
+};
+
+TEST(SmallArrayLRUCache, FetchOrAdd_KeysFitInCache)
+{
+ // We're going to add-or-fetch between 1 and CacheSize keys, so they all fit
+ // in the cache.
+ for (Key keys = 1; keys <= CacheSize; ++keys) {
+ Boxed<TestCache> boxedCache;
+ TestCache& cache = boxedCache.Object();
+ for (Key i = 0; i < keys; ++i) {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i, [&]() {
+ valueFunctionCalled = true;
+ return Value{i};
+ });
+ ASSERT_EQ(v, Value{i});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ // Fetching any key should never call the value function.
+ for (Key i = 0; i < CacheSize * 3; ++i) {
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ // Fetching the same key again will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ }
+ }
+}
+
+TEST(SmallArrayLRUCache, Add_FetchOrAdd_KeysFitInCache)
+{
+ // We're going to add between 1 and CacheSize keys, so they all fit in the
+ // cache.
+ for (Key keys = 1; keys <= CacheSize; ++keys) {
+ Boxed<TestCache> boxedCache;
+ TestCache& cache = boxedCache.Object();
+ for (Key i = 0; i < keys; ++i) {
+ cache.Add(i, Value{i});
+ boxedCache.Check();
+ }
+
+ // Fetching any key should never call the value function.
+ for (Key i = 0; i < CacheSize * 3; ++i) {
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ // Fetching the same key again will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ }
+ }
+}
+
+TEST(SmallArrayLRUCache, FetchOrAdd_KeysDoNotFitInCache)
+{
+ // We're going to add-or-fetch strictly more than CacheSize keys, so they
+ // cannot fit in the cache, only the last `CacheSize` ones are kept.
+ for (Key keys = CacheSize + 1; keys <= CacheSize * 2; ++keys) {
+ Boxed<TestCache> boxedCache;
+ TestCache& cache = boxedCache.Object();
+ for (Key i = 0; i < keys; ++i) {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i, [&]() {
+ valueFunctionCalled = true;
+ return Value{i};
+ });
+ ASSERT_EQ(v, Value{i});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ // Fetching keys from 0 should always call the function value:
+ // - 0 is the oldest key, it must have been pushed out when `CacheSize`
+ // was added.
+ // - Once we've fetched 0, it's pushed out the old (smallest) key.
+ // Etc.
+ for (Key i = 0; i < CacheSize * 3; ++i) {
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ // Fetching the same key again will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ }
+ }
+}
+
+TEST(SmallArrayLRUCache, Add_FetchOrAdd_KeysDoNotFitInCache)
+{
+ // We're going to add strictly more than CacheSize keys, so they cannot fit in
+ // the cache, only the last `CacheSize` ones are kept.
+ for (Key keys = CacheSize + 1; keys <= CacheSize * 2; ++keys) {
+ Boxed<TestCache> boxedCache;
+ TestCache& cache = boxedCache.Object();
+ for (Key i = 0; i < keys; ++i) {
+ cache.Add(i, Value{i});
+ boxedCache.Check();
+ }
+
+ // Fetching keys from 0 should always call the function value:
+ // - 0 is the oldest key, it must have been pushed out when `CacheSize`
+ // was added.
+ // - Once we've fetched 0, it's pushed out the old (smallest) key.
+ // Etc.
+ for (Key i = 0; i < CacheSize * 3; ++i) {
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ // Fetching the same key again will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(i % keys, [&]() {
+ valueFunctionCalled = true;
+ return Value{i % keys};
+ });
+ ASSERT_EQ(v, Value{i % keys});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ }
+ }
+}
+
+TEST(SmallArrayLRUCache, Clear)
+{
+ Boxed<TestCache> boxedCache;
+ TestCache& cache = boxedCache.Object();
+
+ // First fetch will always call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ // Second fetch will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ cache.Clear();
+
+ // After Clear(), first fetch will always call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ // Next fetch will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+}
+
+TEST(SmallArrayLRUCache, Shutdown)
+{
+ Boxed<TestCache> boxedCache;
+ TestCache& cache = boxedCache.Object();
+
+ // First fetch will always call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ // Second fetch will never call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_FALSE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+
+ cache.Shutdown();
+
+ // After Shutdown(), any fetch will always call the function value.
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+ cache.Add(42, Value{4242});
+ boxedCache.Check();
+ {
+ bool valueFunctionCalled = false;
+ Value v = cache.FetchOrAdd(42, [&]() {
+ valueFunctionCalled = true;
+ return Value{4242};
+ });
+ ASSERT_EQ(v, Value{4242});
+ ASSERT_TRUE(valueFunctionCalled);
+ boxedCache.Check();
+ }
+}
diff --git a/xpcom/tests/gtest/TestSnappyStreams.cpp b/xpcom/tests/gtest/TestSnappyStreams.cpp
new file mode 100644
index 0000000000..b7e7e7cf73
--- /dev/null
+++ b/xpcom/tests/gtest/TestSnappyStreams.cpp
@@ -0,0 +1,162 @@
+/* -*- 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/gtest/MozAssertions.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;
+ NS_NewPipe(aReaderOut, getter_AddRefs(pipeWriter));
+
+ 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_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_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), mozilla::Span(aCorruptData, aCorruptLength),
+ NS_ASSIGNMENT_DEPEND);
+ ASSERT_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/TestStateMirroring.cpp b/xpcom/tests/gtest/TestStateMirroring.cpp
new file mode 100644
index 0000000000..e96ecb9a18
--- /dev/null
+++ b/xpcom/tests/gtest/TestStateMirroring.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/WaitFor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/SynchronizedEventQueue.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+#include "nsISupportsImpl.h"
+#include "nsThreadUtils.h"
+#include "VideoUtils.h"
+
+namespace TestStateMirroring {
+
+using namespace mozilla;
+
+class StateMirroringTest : public ::testing::Test {
+ public:
+ using ValueType = int;
+ using Promise = MozPromise<ValueType, bool, /*IsExclusive =*/true>;
+
+ StateMirroringTest()
+ : mTarget(
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestStateMirroring",
+ /*aSupportsTailDispatch =*/true)),
+ mCanonical(AbstractThread::GetCurrent(), 0, "TestCanonical"),
+ mMirror(mTarget, 0, "TestMirror") {}
+
+ void TearDown() override {
+ mTarget->BeginShutdown();
+ mTarget->AwaitShutdownAndIdle();
+
+ // Make sure to run any events just dispatched from mTarget to main thread
+ // before continuing on to the next test.
+ NS_ProcessPendingEvents(nullptr);
+ }
+
+ RefPtr<Promise> ReadMirrorAsync() {
+ return InvokeAsync(mTarget, __func__, [&] {
+ return Promise::CreateAndResolve(mMirror, "ReadMirrorAsync::Resolve");
+ });
+ }
+
+ protected:
+ const RefPtr<TaskQueue> mTarget;
+ Canonical<int> mCanonical;
+ Mirror<int> mMirror;
+};
+
+TEST_F(StateMirroringTest, MirrorInitiatedEventOrdering) {
+ // Note that this requires the tail dispatcher, which is not available until
+ // we are processing events.
+ ASSERT_FALSE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable());
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchAndSpinEventLoopUntilComplete(
+ "NeedTailDispatcher"_ns, GetCurrentSerialEventTarget(),
+ NS_NewRunnableFunction(__func__, [&] {
+ ASSERT_TRUE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable());
+
+ RefPtr<Promise> mirrorPromise;
+
+ // This will ensure the tail dispatcher fires to dispatch the group
+ // runnable holding the Mirror::Connect task, before we continue the
+ // test.
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchAndSpinEventLoopUntilComplete(
+ "NeedTailDispatcher"_ns, GetCurrentSerialEventTarget(),
+ NS_NewRunnableFunction(__func__, [&] {
+ // Show that mirroring does not take effect until async init is
+ // done.
+
+ MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(NS_NewRunnableFunction(
+ __func__, [&] { mMirror.Connect(&mCanonical); })));
+ mCanonical = 1;
+ mirrorPromise = ReadMirrorAsync();
+ })));
+
+ // Make sure Mirror::Connect has run on mTarget.
+ mTarget->AwaitIdle();
+
+ // Make sure Canonical::AddMirror has run on this thread.
+ NS_ProcessPendingEvents(nullptr);
+
+ // Prior to establishing the connection, a value has not been mirrored.
+ EXPECT_EQ(WaitFor(mirrorPromise).unwrap(), 0);
+
+ // Once a connection has been establised, event ordering is as expected.
+ mCanonical = 2;
+ EXPECT_EQ(WaitFor(ReadMirrorAsync()).unwrap(), 2);
+
+ MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(NS_NewRunnableFunction(
+ __func__, [&] { mMirror.DisconnectIfConnected(); })));
+ })));
+}
+
+TEST_F(StateMirroringTest, CanonicalInitiatedEventOrdering) {
+ // Note that this requires the tail dispatcher, which is not available until
+ // we are processing events.
+ ASSERT_FALSE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable());
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchAndSpinEventLoopUntilComplete(
+ "NeedTailDispatcher"_ns, GetCurrentSerialEventTarget(),
+ NS_NewRunnableFunction(__func__, [&] {
+ ASSERT_TRUE(AbstractThread::GetCurrent()->IsTailDispatcherAvailable());
+
+ mCanonical.ConnectMirror(&mMirror);
+
+ // Event ordering is as expected immediately.
+ mCanonical = 1;
+ EXPECT_EQ(WaitFor(ReadMirrorAsync()).unwrap(), 1);
+
+ mCanonical.DisconnectAll();
+ })));
+}
+
+} // namespace TestStateMirroring
diff --git a/xpcom/tests/gtest/TestStateWatching.cpp b/xpcom/tests/gtest/TestStateWatching.cpp
new file mode 100644
index 0000000000..e4b48fb2b3
--- /dev/null
+++ b/xpcom/tests/gtest/TestStateWatching.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "mozilla/Unused.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() = default;
+};
+
+TEST(WatchManager, Shutdown)
+{
+ RefPtr<TaskQueue> queue =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestWatchManager Shutdown");
+
+ RefPtr<Foo> p = new Foo;
+ WatchManager<Foo> manager(p, queue);
+ Watchable<bool> notifier(false, "notifier");
+
+ Unused << queue->Dispatch(NS_NewRunnableFunction(
+ "TestStateWatching::WatchManager_Shutdown_Test::TestBody", [&]() {
+ 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 0000000000..f92cb986ba
--- /dev/null
+++ b/xpcom/tests/gtest/TestStorageStream.cpp
@@ -0,0 +1,130 @@
+/* -*- 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 "mozilla/gtest/MozAssertions.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_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_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = stor->GetOutputStream(0, getter_AddRefs(out));
+ EXPECT_NS_SUCCEEDED(rv);
+
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, kData.Length(), dataWritten);
+
+ rv = out->Close();
+ EXPECT_NS_SUCCEEDED(rv);
+ out = nullptr;
+
+ nsCOMPtr<nsIInputStream> in;
+ rv = stor->NewInputStream(0, getter_AddRefs(in));
+ EXPECT_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_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_NS_SUCCEEDED(rv);
+ out = nullptr;
+
+ // now, read all
+ rv = stor->NewInputStream(0, getter_AddRefs(in));
+ EXPECT_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_NS_SUCCEEDED(rv);
+
+ // Get input stream before writing data into the output stream
+ nsCOMPtr<nsIInputStream> in;
+ rv = stor->NewInputStream(0, getter_AddRefs(in));
+ EXPECT_NS_SUCCEEDED(rv);
+
+ // Write data to output stream
+ nsCOMPtr<nsIOutputStream> out;
+ rv = stor->GetOutputStream(0, getter_AddRefs(out));
+ EXPECT_NS_SUCCEEDED(rv);
+
+ WriteData(out, kData, kData.Length(), dataWritten);
+ WriteData(out, kData, kData.Length(), dataWritten);
+
+ rv = out->Close();
+ EXPECT_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 0000000000..8314c3e74c
--- /dev/null
+++ b/xpcom/tests/gtest/TestStringStream.cpp
@@ -0,0 +1,100 @@
+/* -*- 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/gtest/MozAssertions.h"
+#include "nsICloneableInputStream.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsIInputStream.h"
+#include "nsCOMPtr.h"
+#include "nsStreamUtils.h"
+#include "mozilla/Span.h"
+#include "nsISeekableStream.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_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_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); }
+
+static nsresult CloseStreamThenRead(nsIInputStream* aInStr, void* aClosure,
+ const char* aBuffer, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aCountWritten) {
+ // Closing the stream will free the data
+ nsresult rv = aInStr->Close();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // This will likely be allocated in the same slot as what we have in aBuffer
+ char* newAlloc = moz_xstrdup("abcd");
+
+ char* toBuf = static_cast<char*>(aClosure);
+ memcpy(&toBuf[aOffset], aBuffer, aCount);
+ *aCountWritten = aCount;
+ free(newAlloc);
+ return NS_OK;
+}
+
+TEST(StringStream, CancelInReadSegments)
+{
+ char* buffer = moz_xstrdup("test");
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(
+ getter_AddRefs(stream), mozilla::Span(buffer, 5), NS_ASSIGNMENT_ADOPT);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ char buf[100];
+ uint32_t count = 0;
+ uint64_t available = 0;
+ rv = stream->Available(&available);
+ ASSERT_NS_SUCCEEDED(rv);
+ rv = stream->ReadSegments(CloseStreamThenRead, buf, available, &count);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(count == 5);
+ ASSERT_TRUE(!strcmp(buf, "test"));
+}
diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp
new file mode 100644
index 0000000000..7e0f986d29
--- /dev/null
+++ b/xpcom/tests/gtest/TestStrings.cpp
@@ -0,0 +1,2801 @@
+/* -*- 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 "nsASCIIMask.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsStringBuffer.h"
+#include "nsReadableUtils.h"
+#include "nsCRTGlue.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Utf8.h"
+#include "nsTArray.h"
+#include "gtest/gtest.h"
+#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+#include "gtest/BlackBox.h"
+#include "nsBidiUtils.h"
+#include "js/String.h"
+
+#define CONVERSION_ITERATIONS 50000
+
+#define CONVERSION_BENCH(name, func, src, dstType) \
+ MOZ_GTEST_BENCH_F(Strings, name, [this] { \
+ for (int i = 0; i < CONVERSION_ITERATIONS; i++) { \
+ dstType dst; \
+ func(*BlackBox(&src), *BlackBox(&dst)); \
+ } \
+ });
+
+// Disable the C++ 2a warning. See bug #1509926
+#if defined(__clang__) && (__clang_major__ >= 6)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wc++2a-compat"
+#endif
+
+namespace TestStrings {
+
+using mozilla::BlackBox;
+using mozilla::fallible;
+using mozilla::IsAscii;
+using mozilla::IsUtf8;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+using mozilla::Span;
+
+#define TestExample1 \
+ "Sed ut perspiciatis unde omnis iste natus error sit voluptatem " \
+ "accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae " \
+ "ab illo inventore veritatis et quasi\r architecto beatae vitae dicta sunt " \
+ "explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur\n aut " \
+ "odit aut fugit, sed quia consequuntur magni dolores eos qui ratione " \
+ "voluptatem sequi nesciunt. Neque porro quisquam est, qui\r\n\r dolorem " \
+ "ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non " \
+ "numquam eius modi tempora incidunt ut labore et dolore magnam aliquam " \
+ "quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem " \
+ "ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi " \
+ "consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate " \
+ "velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum " \
+ "fugiat quo voluptas nulla pariatur?"
+
+#define TestExample2 \
+ "At vero eos et accusamus et iusto odio dignissimos ducimus\n\n qui " \
+ "blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et " \
+ "quas molestias excepturi sint occaecati cupiditate non provident, " \
+ "similique sunt in culpa qui officia deserunt\r\r \n mollitia animi, id " \
+ "est laborum et dolorum fuga. Et harum quidem rerum facilis est et " \
+ "expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi " \
+ "optio cumque nihil impedit quo minus id quod maxime placeat facere " \
+ "possimus, omnis voluptas assumenda est, omnis dolor repellendus. " \
+ "Temporibus autem quibusdam et aut officiis debitis aut rerum " \
+ "necessitatibus saepe eveniet ut et voluptates repudiandae sint et " \
+ "molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente " \
+ "delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut " \
+ "perferendis doloribus asperiores repellat."
+
+#define TestExample3 \
+ " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac tellus " \
+ "eget velit viverra viverra id sit amet neque. Sed id consectetur mi, " \
+ "vestibulum aliquet arcu. Curabitur sagittis accumsan convallis. Sed eu " \
+ "condimentum ipsum, a laoreet tortor. Orci varius natoque penatibus et " \
+ "magnis dis \r\r\n\n parturient montes, nascetur ridiculus mus. Sed non " \
+ "tellus nec ante sodales placerat a nec risus. Cras vel bibendum sapien, " \
+ "nec ullamcorper felis. Pellentesque congue eget nisi sit amet vehicula. " \
+ "Morbi pulvinar turpis justo, in commodo dolor vulputate id. Curabitur in " \
+ "dui urna. Vestibulum placerat dui in sem congue, ut faucibus nibh rutrum. " \
+ "Duis mattis turpis facilisis ullamcorper tincidunt. Vestibulum pharetra " \
+ "tortor at enim sagittis, dapibus consectetur ex blandit. Curabitur ac " \
+ "fringilla quam. In ornare lectus ut ipsum mattis venenatis. Etiam in " \
+ "mollis lectus, sed luctus risus.\nCras dapibus\f\t \n finibus justo sit " \
+ "amet dictum. Aliquam non elit diam. Fusce magna nulla, bibendum in massa " \
+ "a, commodo finibus lectus. Sed rutrum a augue id imperdiet. Aliquam " \
+ "sagittis sodales felis, a tristique ligula. Aliquam erat volutpat. " \
+ "Pellentesque habitant morbi tristique senectus et netus et malesuada " \
+ "fames ac turpis egestas. Duis volutpat interdum lorem et congue. " \
+ "Phasellus porttitor posuere justo eget euismod. Nam a condimentum turpis, " \
+ "sit amet gravida lacus. Vestibulum dolor diam, lobortis ac metus et, " \
+ "convallis dapibus tellus. Ut nec metus in velit malesuada tincidunt et " \
+ "eget justo. Curabitur ut libero bibendum, porttitor diam vitae, aliquet " \
+ "justo. "
+
+#define TestExample4 \
+ " Donec feugiat volutpat massa. Cras ornare lacinia porta. Fusce in " \
+ "feugiat nunc. Praesent non felis varius diam feugiat ultrices ultricies a " \
+ "risus. Donec maximus nisi nisl, non consectetur nulla eleifend in. Nulla " \
+ "in massa interdum, eleifend orci a, vestibulum est. Mauris aliquet, massa " \
+ "et convallis mollis, felis augue vestibulum augue, in lobortis metus eros " \
+ "a quam. Nam ac diam ornare, vestibulum elit sit amet, " \
+ "consectetur ante. Praesent massa mauris, pulvinar sit amet sapien vel, " \
+ "tempus gravida neque. Praesent id quam sit amet est maximus molestie eget " \
+ "at turpis. Nunc sit amet orci id arcu dapibus fermentum non eu " \
+ "erat.\f\tSuspendisse commodo nunc sem, eu congue eros condimentum vel. " \
+ "Nullam sit amet posuere arcu. Nulla facilisi. Mauris dapibus iaculis " \
+ "massa sed gravida. Nullam vitae urna at tortor feugiat auctor ut sit amet " \
+ "dolor. Proin rutrum at nunc et faucibus. Quisque suscipit id nibh a " \
+ "aliquet. Pellentesque habitant morbi tristique senectus et netus et " \
+ "malesuada fames ac turpis egestas. Aliquam a dapibus erat, id imperdiet " \
+ "mauris. Nulla blandit libero non magna dapibus tristique. Integer " \
+ "hendrerit imperdiet lorem, quis facilisis lacus semper ut. Vestibulum " \
+ "ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia " \
+ "Curae Nullam dignissim elit in congue ultricies. Quisque erat odio, " \
+ "maximus mollis laoreet id, iaculis at turpis. "
+
+#define TestExample5 \
+ "Donec id risus urna. Nunc consequat lacinia urna id bibendum. Nulla " \
+ "faucibus faucibus enim. Cras ex risus, ultrices id semper vitae, luctus " \
+ "ut nulla. Sed vehicula tellus sed purus imperdiet efficitur. Suspendisse " \
+ "feugiat\n\n\n imperdiet odio, sed porta lorem feugiat nec. Curabitur " \
+ "laoreet massa venenatis\r\n risus ornare\r\n, vitae feugiat tortor " \
+ "accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " \
+ "Maecenas id scelerisque mauris, eget facilisis erat. Ut nec pulvinar " \
+ "risus, sed iaculis ante. Mauris tincidunt, risus et pretium elementum, " \
+ "leo nisi consectetur ligula, tincidunt suscipit erat velit eget libero. " \
+ "Sed ac est tempus, consequat dolor mattis, mattis mi. "
+
+// Originally ReadVPXFile in TestVPXDecoding.cpp
+static void ReadFile(const char* aPath, nsACString& aBuffer) {
+ FILE* f = fopen(aPath, "rb");
+ ASSERT_NE(f, (FILE*)nullptr);
+
+ int r = fseek(f, 0, SEEK_END);
+ ASSERT_EQ(r, 0);
+
+ long size = ftell(f);
+ ASSERT_NE(size, -1);
+ aBuffer.SetLength(size);
+
+ r = fseek(f, 0, SEEK_SET);
+ ASSERT_EQ(r, 0);
+
+ size_t got = fread(aBuffer.BeginWriting(), 1, size, f);
+ ASSERT_EQ(got, size_t(size));
+
+ r = fclose(f);
+ ASSERT_EQ(r, 0);
+}
+
+class Strings : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Intentionally AssignASCII and not AssignLiteral
+ // to simulate the usual heap case.
+ mExample1Utf8.AssignASCII(TestExample1);
+ mExample2Utf8.AssignASCII(TestExample2);
+ mExample3Utf8.AssignASCII(TestExample3);
+ mExample4Utf8.AssignASCII(TestExample4);
+ mExample5Utf8.AssignASCII(TestExample5);
+
+ // Use span to make the resulting string as ordinary as possible
+ mAsciiOneUtf8.Append(Span(mExample3Utf8).To(1));
+ mAsciiThreeUtf8.Append(Span(mExample3Utf8).To(3));
+ mAsciiFifteenUtf8.Append(Span(mExample3Utf8).To(15));
+ mAsciiHundredUtf8.Append(Span(mExample3Utf8).To(100));
+ mAsciiThousandUtf8.Append(Span(mExample3Utf8).To(1000));
+
+ ReadFile("ar.txt", mArUtf8);
+ ReadFile("de.txt", mDeUtf8);
+ ReadFile("de-edit.txt", mDeEditUtf8);
+ ReadFile("ru.txt", mRuUtf8);
+ ReadFile("th.txt", mThUtf8);
+ ReadFile("ko.txt", mKoUtf8);
+ ReadFile("ja.txt", mJaUtf8);
+ ReadFile("tr.txt", mTrUtf8);
+ ReadFile("vi.txt", mViUtf8);
+
+ CopyASCIItoUTF16(mExample1Utf8, mExample1Utf16);
+ CopyASCIItoUTF16(mExample2Utf8, mExample2Utf16);
+ CopyASCIItoUTF16(mExample3Utf8, mExample3Utf16);
+ CopyASCIItoUTF16(mExample4Utf8, mExample4Utf16);
+ CopyASCIItoUTF16(mExample5Utf8, mExample5Utf16);
+
+ CopyASCIItoUTF16(mAsciiOneUtf8, mAsciiOneUtf16);
+ CopyASCIItoUTF16(mAsciiFifteenUtf8, mAsciiFifteenUtf16);
+ CopyASCIItoUTF16(mAsciiHundredUtf8, mAsciiHundredUtf16);
+ CopyASCIItoUTF16(mAsciiThousandUtf8, mAsciiThousandUtf16);
+
+ CopyUTF8toUTF16(mArUtf8, mArUtf16);
+ CopyUTF8toUTF16(mDeUtf8, mDeUtf16);
+ CopyUTF8toUTF16(mDeEditUtf8, mDeEditUtf16);
+ CopyUTF8toUTF16(mRuUtf8, mRuUtf16);
+ CopyUTF8toUTF16(mThUtf8, mThUtf16);
+ CopyUTF8toUTF16(mJaUtf8, mJaUtf16);
+ CopyUTF8toUTF16(mKoUtf8, mKoUtf16);
+ CopyUTF8toUTF16(mTrUtf8, mTrUtf16);
+ CopyUTF8toUTF16(mViUtf8, mViUtf16);
+
+ LossyCopyUTF16toASCII(mDeEditUtf16, mDeEditLatin1);
+
+ // Use span to make the resulting string as ordinary as possible
+ mArOneUtf16.Append(Span(mArUtf16).To(1));
+ mDeOneUtf16.Append(Span(mDeUtf16).To(1));
+ mDeEditOneUtf16.Append(Span(mDeEditUtf16).To(1));
+ mRuOneUtf16.Append(Span(mRuUtf16).To(1));
+ mThOneUtf16.Append(Span(mThUtf16).To(1));
+ mJaOneUtf16.Append(Span(mJaUtf16).To(1));
+ mKoOneUtf16.Append(Span(mKoUtf16).To(1));
+ mTrOneUtf16.Append(Span(mTrUtf16).To(1));
+ mViOneUtf16.Append(Span(mViUtf16).To(1));
+
+ mDeEditOneLatin1.Append(Span(mDeEditLatin1).To(1));
+
+ mArThreeUtf16.Append(Span(mArUtf16).To(3));
+ mDeThreeUtf16.Append(Span(mDeUtf16).To(3));
+ mDeEditThreeUtf16.Append(Span(mDeEditUtf16).To(3));
+ mRuThreeUtf16.Append(Span(mRuUtf16).To(3));
+ mThThreeUtf16.Append(Span(mThUtf16).To(3));
+ mJaThreeUtf16.Append(Span(mJaUtf16).To(3));
+ mKoThreeUtf16.Append(Span(mKoUtf16).To(3));
+ mTrThreeUtf16.Append(Span(mTrUtf16).To(3));
+ mViThreeUtf16.Append(Span(mViUtf16).To(3));
+
+ mDeEditThreeLatin1.Append(Span(mDeEditLatin1).To(3));
+
+ mArFifteenUtf16.Append(Span(mArUtf16).To(15));
+ mDeFifteenUtf16.Append(Span(mDeUtf16).To(15));
+ mDeEditFifteenUtf16.Append(Span(mDeEditUtf16).To(15));
+ mRuFifteenUtf16.Append(Span(mRuUtf16).To(15));
+ mThFifteenUtf16.Append(Span(mThUtf16).To(15));
+ mJaFifteenUtf16.Append(Span(mJaUtf16).To(15));
+ mKoFifteenUtf16.Append(Span(mKoUtf16).To(15));
+ mTrFifteenUtf16.Append(Span(mTrUtf16).To(15));
+ mViFifteenUtf16.Append(Span(mViUtf16).To(15));
+
+ mDeEditFifteenLatin1.Append(Span(mDeEditLatin1).To(15));
+
+ mArHundredUtf16.Append(Span(mArUtf16).To(100));
+ mDeHundredUtf16.Append(Span(mDeUtf16).To(100));
+ mDeEditHundredUtf16.Append(Span(mDeEditUtf16).To(100));
+ mRuHundredUtf16.Append(Span(mRuUtf16).To(100));
+ mThHundredUtf16.Append(Span(mThUtf16).To(100));
+ mJaHundredUtf16.Append(Span(mJaUtf16).To(100));
+ mKoHundredUtf16.Append(Span(mKoUtf16).To(100));
+ mTrHundredUtf16.Append(Span(mTrUtf16).To(100));
+ mViHundredUtf16.Append(Span(mViUtf16).To(100));
+
+ mDeEditHundredLatin1.Append(Span(mDeEditLatin1).To(100));
+
+ mArThousandUtf16.Append(Span(mArUtf16).To(1000));
+ mDeThousandUtf16.Append(Span(mDeUtf16).To(1000));
+ mDeEditThousandUtf16.Append(Span(mDeEditUtf16).To(1000));
+ mRuThousandUtf16.Append(Span(mRuUtf16).To(1000));
+ mThThousandUtf16.Append(Span(mThUtf16).To(1000));
+ mJaThousandUtf16.Append(Span(mJaUtf16).To(1000));
+ mKoThousandUtf16.Append(Span(mKoUtf16).To(1000));
+ mTrThousandUtf16.Append(Span(mTrUtf16).To(1000));
+ mViThousandUtf16.Append(Span(mViUtf16).To(1000));
+
+ mDeEditThousandLatin1.Append(Span(mDeEditLatin1).To(1000));
+
+ CopyUTF16toUTF8(mArOneUtf16, mArOneUtf8);
+ CopyUTF16toUTF8(mDeOneUtf16, mDeOneUtf8);
+ CopyUTF16toUTF8(mDeEditOneUtf16, mDeEditOneUtf8);
+ CopyUTF16toUTF8(mRuOneUtf16, mRuOneUtf8);
+ CopyUTF16toUTF8(mThOneUtf16, mThOneUtf8);
+ CopyUTF16toUTF8(mJaOneUtf16, mJaOneUtf8);
+ CopyUTF16toUTF8(mKoOneUtf16, mKoOneUtf8);
+ CopyUTF16toUTF8(mTrOneUtf16, mTrOneUtf8);
+ CopyUTF16toUTF8(mViOneUtf16, mViOneUtf8);
+
+ CopyUTF16toUTF8(mArThreeUtf16, mArThreeUtf8);
+ CopyUTF16toUTF8(mDeThreeUtf16, mDeThreeUtf8);
+ CopyUTF16toUTF8(mDeEditThreeUtf16, mDeEditThreeUtf8);
+ CopyUTF16toUTF8(mRuThreeUtf16, mRuThreeUtf8);
+ CopyUTF16toUTF8(mThThreeUtf16, mThThreeUtf8);
+ CopyUTF16toUTF8(mJaThreeUtf16, mJaThreeUtf8);
+ CopyUTF16toUTF8(mKoThreeUtf16, mKoThreeUtf8);
+ CopyUTF16toUTF8(mTrThreeUtf16, mTrThreeUtf8);
+ CopyUTF16toUTF8(mViThreeUtf16, mViThreeUtf8);
+
+ CopyUTF16toUTF8(mArFifteenUtf16, mArFifteenUtf8);
+ CopyUTF16toUTF8(mDeFifteenUtf16, mDeFifteenUtf8);
+ CopyUTF16toUTF8(mDeEditFifteenUtf16, mDeEditFifteenUtf8);
+ CopyUTF16toUTF8(mRuFifteenUtf16, mRuFifteenUtf8);
+ CopyUTF16toUTF8(mThFifteenUtf16, mThFifteenUtf8);
+ CopyUTF16toUTF8(mJaFifteenUtf16, mJaFifteenUtf8);
+ CopyUTF16toUTF8(mKoFifteenUtf16, mKoFifteenUtf8);
+ CopyUTF16toUTF8(mTrFifteenUtf16, mTrFifteenUtf8);
+ CopyUTF16toUTF8(mViFifteenUtf16, mViFifteenUtf8);
+
+ CopyUTF16toUTF8(mArHundredUtf16, mArHundredUtf8);
+ CopyUTF16toUTF8(mDeHundredUtf16, mDeHundredUtf8);
+ CopyUTF16toUTF8(mDeEditHundredUtf16, mDeEditHundredUtf8);
+ CopyUTF16toUTF8(mRuHundredUtf16, mRuHundredUtf8);
+ CopyUTF16toUTF8(mThHundredUtf16, mThHundredUtf8);
+ CopyUTF16toUTF8(mJaHundredUtf16, mJaHundredUtf8);
+ CopyUTF16toUTF8(mKoHundredUtf16, mKoHundredUtf8);
+ CopyUTF16toUTF8(mTrHundredUtf16, mTrHundredUtf8);
+ CopyUTF16toUTF8(mViHundredUtf16, mViHundredUtf8);
+
+ CopyUTF16toUTF8(mArThousandUtf16, mArThousandUtf8);
+ CopyUTF16toUTF8(mDeThousandUtf16, mDeThousandUtf8);
+ CopyUTF16toUTF8(mDeEditThousandUtf16, mDeEditThousandUtf8);
+ CopyUTF16toUTF8(mRuThousandUtf16, mRuThousandUtf8);
+ CopyUTF16toUTF8(mThThousandUtf16, mThThousandUtf8);
+ CopyUTF16toUTF8(mJaThousandUtf16, mJaThousandUtf8);
+ CopyUTF16toUTF8(mKoThousandUtf16, mKoThousandUtf8);
+ CopyUTF16toUTF8(mTrThousandUtf16, mTrThousandUtf8);
+ CopyUTF16toUTF8(mViThousandUtf16, mViThousandUtf8);
+ }
+
+ public:
+ nsCString mAsciiOneUtf8;
+ nsCString mAsciiThreeUtf8;
+ nsCString mAsciiFifteenUtf8;
+ nsCString mAsciiHundredUtf8;
+ nsCString mAsciiThousandUtf8;
+ nsCString mExample1Utf8;
+ nsCString mExample2Utf8;
+ nsCString mExample3Utf8;
+ nsCString mExample4Utf8;
+ nsCString mExample5Utf8;
+ nsCString mArUtf8;
+ nsCString mDeUtf8;
+ nsCString mDeEditUtf8;
+ nsCString mRuUtf8;
+ nsCString mThUtf8;
+ nsCString mJaUtf8;
+ nsCString mKoUtf8;
+ nsCString mTrUtf8;
+ nsCString mViUtf8;
+
+ nsString mAsciiOneUtf16;
+ nsString mAsciiThreeUtf16;
+ nsString mAsciiFifteenUtf16;
+ nsString mAsciiHundredUtf16;
+ nsString mAsciiThousandUtf16;
+ nsString mExample1Utf16;
+ nsString mExample2Utf16;
+ nsString mExample3Utf16;
+ nsString mExample4Utf16;
+ nsString mExample5Utf16;
+ nsString mArUtf16;
+ nsString mDeUtf16;
+ nsString mDeEditUtf16;
+ nsString mRuUtf16;
+ nsString mThUtf16;
+ nsString mJaUtf16;
+ nsString mKoUtf16;
+ nsString mTrUtf16;
+ nsString mViUtf16;
+
+ nsCString mDeEditLatin1;
+
+ nsString mArOneUtf16;
+ nsString mDeOneUtf16;
+ nsString mDeEditOneUtf16;
+ nsString mRuOneUtf16;
+ nsString mThOneUtf16;
+ nsString mJaOneUtf16;
+ nsString mKoOneUtf16;
+ nsString mTrOneUtf16;
+ nsString mViOneUtf16;
+
+ nsCString mDeEditOneLatin1;
+
+ nsCString mArOneUtf8;
+ nsCString mDeOneUtf8;
+ nsCString mDeEditOneUtf8;
+ nsCString mRuOneUtf8;
+ nsCString mThOneUtf8;
+ nsCString mJaOneUtf8;
+ nsCString mKoOneUtf8;
+ nsCString mTrOneUtf8;
+ nsCString mViOneUtf8;
+
+ nsString mArThreeUtf16;
+ nsString mDeThreeUtf16;
+ nsString mDeEditThreeUtf16;
+ nsString mRuThreeUtf16;
+ nsString mThThreeUtf16;
+ nsString mJaThreeUtf16;
+ nsString mKoThreeUtf16;
+ nsString mTrThreeUtf16;
+ nsString mViThreeUtf16;
+
+ nsCString mDeEditThreeLatin1;
+
+ nsCString mArThreeUtf8;
+ nsCString mDeThreeUtf8;
+ nsCString mDeEditThreeUtf8;
+ nsCString mRuThreeUtf8;
+ nsCString mThThreeUtf8;
+ nsCString mJaThreeUtf8;
+ nsCString mKoThreeUtf8;
+ nsCString mTrThreeUtf8;
+ nsCString mViThreeUtf8;
+
+ nsString mArFifteenUtf16;
+ nsString mDeFifteenUtf16;
+ nsString mDeEditFifteenUtf16;
+ nsString mRuFifteenUtf16;
+ nsString mThFifteenUtf16;
+ nsString mJaFifteenUtf16;
+ nsString mKoFifteenUtf16;
+ nsString mTrFifteenUtf16;
+ nsString mViFifteenUtf16;
+
+ nsCString mDeEditFifteenLatin1;
+
+ nsCString mArFifteenUtf8;
+ nsCString mDeFifteenUtf8;
+ nsCString mDeEditFifteenUtf8;
+ nsCString mRuFifteenUtf8;
+ nsCString mThFifteenUtf8;
+ nsCString mJaFifteenUtf8;
+ nsCString mKoFifteenUtf8;
+ nsCString mTrFifteenUtf8;
+ nsCString mViFifteenUtf8;
+
+ nsString mArHundredUtf16;
+ nsString mDeHundredUtf16;
+ nsString mDeEditHundredUtf16;
+ nsString mRuHundredUtf16;
+ nsString mThHundredUtf16;
+ nsString mJaHundredUtf16;
+ nsString mKoHundredUtf16;
+ nsString mTrHundredUtf16;
+ nsString mViHundredUtf16;
+
+ nsCString mDeEditHundredLatin1;
+
+ nsCString mArHundredUtf8;
+ nsCString mDeHundredUtf8;
+ nsCString mDeEditHundredUtf8;
+ nsCString mRuHundredUtf8;
+ nsCString mThHundredUtf8;
+ nsCString mJaHundredUtf8;
+ nsCString mKoHundredUtf8;
+ nsCString mTrHundredUtf8;
+ nsCString mViHundredUtf8;
+
+ nsString mArThousandUtf16;
+ nsString mDeThousandUtf16;
+ nsString mDeEditThousandUtf16;
+ nsString mRuThousandUtf16;
+ nsString mThThousandUtf16;
+ nsString mJaThousandUtf16;
+ nsString mKoThousandUtf16;
+ nsString mTrThousandUtf16;
+ nsString mViThousandUtf16;
+
+ nsCString mDeEditThousandLatin1;
+
+ nsCString mArThousandUtf8;
+ nsCString mDeThousandUtf8;
+ nsCString mDeEditThousandUtf8;
+ nsCString mRuThousandUtf8;
+ nsCString mThThousandUtf8;
+ nsCString mJaThousandUtf8;
+ nsCString mKoThousandUtf8;
+ nsCString mTrThousandUtf8;
+ nsCString mViThousandUtf8;
+};
+
+static void test_assign_helper(const nsACString& in, nsACString& _retval) {
+ _retval = in;
+}
+
+// Simple helper struct to test if conditionally enabled string functions are
+// working.
+template <typename T>
+struct EnableTest {
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ bool IsChar16() {
+ return true;
+ }
+
+ template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>>
+ bool IsChar16(int dummy = 42) {
+ return false;
+ }
+
+ template <typename Q = T, typename EnableIfChar16 = mozilla::Char16OnlyT<Q>>
+ bool IsChar() {
+ return false;
+ }
+
+ template <typename Q = T, typename EnableIfChar = mozilla::CharOnlyT<Q>>
+ bool IsChar(int dummy = 42) {
+ return true;
+ }
+};
+
+TEST_F(Strings, IsChar) {
+ EnableTest<char> charTest;
+ EXPECT_TRUE(charTest.IsChar());
+ EXPECT_FALSE(charTest.IsChar16());
+
+ EnableTest<char16_t> char16Test;
+ EXPECT_TRUE(char16Test.IsChar16());
+ EXPECT_FALSE(char16Test.IsChar());
+
+#ifdef COMPILATION_FAILURE_TEST
+ nsAutoCString a_ctest;
+ nsAutoString a_test;
+
+ a_ctest.AssignLiteral("hello");
+ // This should cause a compilation failure.
+ a_ctest.AssignLiteral(u"hello");
+ a_test.AssignLiteral(u"hello");
+ a_test.AssignLiteral("hello");
+#endif
+}
+
+TEST_F(Strings, DependentStrings) {
+ // A few tests that make sure copying nsTDependentStrings behaves properly.
+ using DataFlags = mozilla::detail::StringDataFlags;
+
+ {
+ // Test copy ctor.
+ nsDependentCString tmp("foo");
+ auto data = tmp.Data();
+ nsDependentCString foo(tmp);
+ // Neither string should be using a shared buffer.
+ EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED);
+ EXPECT_FALSE(foo.GetDataFlags() & DataFlags::REFCOUNTED);
+ // Both strings should be pointing to the original buffer.
+ EXPECT_EQ(data, tmp.Data());
+ EXPECT_EQ(data, foo.Data());
+ }
+ {
+ // Test move ctor.
+ nsDependentCString tmp("foo");
+ auto data = tmp.Data();
+ nsDependentCString foo(std::move(tmp));
+ // Neither string should be using a shared buffer.
+ EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED);
+ EXPECT_FALSE(foo.GetDataFlags() & DataFlags::REFCOUNTED);
+ // First string should be reset, the second should be pointing to the
+ // original buffer.
+ EXPECT_NE(data, tmp.Data());
+ EXPECT_EQ(data, foo.Data());
+ EXPECT_TRUE(tmp.IsEmpty());
+ }
+ {
+ // Test copying to a nsCString.
+ nsDependentCString tmp("foo");
+ auto data = tmp.Data();
+ nsCString foo(tmp);
+ // Original string should not be shared, copy should be shared.
+ EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED);
+ EXPECT_TRUE(foo.GetDataFlags() & DataFlags::REFCOUNTED);
+ // First string should remain the same, the second should be pointing to
+ // a new buffer.
+ EXPECT_EQ(data, tmp.Data());
+ EXPECT_NE(data, foo.Data());
+ }
+}
+
+TEST_F(Strings, assign) {
+ nsCString result;
+ test_assign_helper("a"_ns + "b"_ns, result);
+ EXPECT_STREQ(result.get(), "ab");
+}
+
+TEST_F(Strings, assign_c) {
+ nsCString c;
+ c.Assign('c');
+ EXPECT_STREQ(c.get(), "c");
+}
+
+TEST_F(Strings, test1) {
+ constexpr auto empty = u""_ns;
+ 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_F(Strings, test2) {
+ nsCString data("hello world");
+ const nsACString& aStr = data;
+
+ nsCString temp(aStr);
+ temp.Cut(0, 6);
+
+ EXPECT_STREQ(temp.get(), "world");
+}
+
+TEST_F(Strings, find) {
+ nsCString src("<!DOCTYPE blah blah blah>");
+
+ int32_t i = src.Find("DOCTYPE", 2);
+ EXPECT_EQ(i, 2);
+
+ i = src.Find("DOCTYPE");
+ EXPECT_EQ(i, 2);
+}
+
+TEST_F(Strings, lower_case_find) {
+ nsCString src("<!DOCTYPE blah blah blah>");
+
+ int32_t i = src.LowerCaseFindASCII("doctype", 2);
+ EXPECT_EQ(i, 2);
+
+ i = src.LowerCaseFindASCII("doctype");
+ EXPECT_EQ(i, 2);
+}
+
+TEST_F(Strings, rfind) {
+ const char text[] = "<!DOCTYPE blah bLaH bLaH>";
+ nsCString src(text);
+ int32_t i;
+
+ i = src.RFind("bLaH");
+ EXPECT_EQ(i, 20);
+
+ i = src.RFind("blah");
+ EXPECT_EQ(i, 10);
+
+ i = src.RFind("BLAH");
+ EXPECT_EQ(i, kNotFound);
+}
+
+TEST_F(Strings, rfind_2) {
+ const char text[] = "<!DOCTYPE blah blah blah>";
+ nsCString src(text);
+ int32_t i = src.RFind("TYPE");
+ EXPECT_EQ(i, 5);
+}
+
+TEST_F(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_F(Strings, rfind_4) {
+ nsCString value("a.msf");
+ int32_t i = value.RFind(".msf");
+ EXPECT_EQ(i, 1);
+}
+
+TEST_F(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, 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("jar:"_ns, 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("jar:"_ns, 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("gecko"_ns, 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("jar:"_ns, 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("classic"_ns, delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+}
+
+TEST_F(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, 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("jar:"_ns, 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("jar:"_ns, 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("gecko"_ns, 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("jar:"_ns, 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("classic"_ns, delim_begin, delim_end));
+
+ // When no match is found, range should be empty
+ EXPECT_EQ(delim_begin, delim_end);
+}
+
+TEST_F(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_F(Strings, length) {
+ const char text[] = "abc-xyz";
+ nsCString s(text);
+ size_t d = s.Length();
+ EXPECT_EQ(d, sizeof(text) - 1);
+}
+
+TEST_F(Strings, trim) {
+ const char text[] = " a\t $ ";
+ const char set[] = " \t$";
+
+ nsCString s(text);
+ s.Trim(set);
+ EXPECT_STREQ(s.get(), "a");
+
+ s.AssignLiteral("\t \t\t \t");
+ s.Trim(set);
+ EXPECT_STREQ(s.get(), "");
+
+ s.AssignLiteral(" ");
+ s.Trim(set);
+ EXPECT_STREQ(s.get(), "");
+
+ s.AssignLiteral(" ");
+ s.Trim(set, false, true);
+ EXPECT_STREQ(s.get(), "");
+
+ s.AssignLiteral(" ");
+ s.Trim(set, true, false);
+ EXPECT_STREQ(s.get(), "");
+}
+
+TEST_F(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.AssignLiteral("foobar");
+ s.ReplaceSubstring("foo", "bar");
+ s.ReplaceSubstring("bar", "");
+ EXPECT_STREQ(s.get(), "");
+
+ s.AssignLiteral("foofoofoo");
+ s.ReplaceSubstring("foo", "foo");
+ EXPECT_STREQ(s.get(), "foofoofoo");
+
+ s.AssignLiteral("foofoofoo");
+ s.ReplaceSubstring("of", "fo");
+ EXPECT_STREQ(s.get(), "fofoofooo");
+}
+
+TEST_F(Strings, replace_substr_2) {
+ const char* newName = "user";
+ nsString acctName;
+ acctName.AssignLiteral("forums.foo.com");
+ nsAutoString newAcctName, oldVal, newVal;
+ CopyASCIItoUTF16(mozilla::MakeStringSpan(newName), newVal);
+ 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_F(Strings, replace_substr_3) {
+ nsCString s;
+ s.AssignLiteral("abcabcabc");
+ s.ReplaceSubstring("ca", "X");
+ EXPECT_STREQ(s.get(), "abXbXbc");
+
+ s.AssignLiteral("abcabcabc");
+ s.ReplaceSubstring("ca", "XYZ");
+ EXPECT_STREQ(s.get(), "abXYZbXYZbc");
+
+ s.AssignLiteral("abcabcabc");
+ s.ReplaceSubstring("ca", "XY");
+ EXPECT_STREQ(s.get(), "abXYbXYbc");
+
+ s.AssignLiteral("abcabcabc");
+ s.ReplaceSubstring("ca", "XYZ!");
+ EXPECT_STREQ(s.get(), "abXYZ!bXYZ!bc");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "X");
+ EXPECT_STREQ(s.get(), "aXaXaX");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZ!");
+ EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XY");
+ EXPECT_STREQ(s.get(), "aXYaXYaXY");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZABC");
+ EXPECT_STREQ(s.get(), "aXYZABCaXYZABCaXYZABC");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZ");
+ EXPECT_STREQ(s.get(), "aXYZaXYZaXYZ");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("bcd", "XYZ!");
+ EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "X");
+ EXPECT_STREQ(s.get(), "XcdXcdXcd");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "XYZABC");
+ EXPECT_STREQ(s.get(), "XYZABCcdXYZABCcdXYZABCcd");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "XY");
+ EXPECT_STREQ(s.get(), "XYcdXYcdXYcd");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("ab", "XYZ!");
+ EXPECT_STREQ(s.get(), "XYZ!cdXYZ!cdXYZ!cd");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("notfound", "X");
+ EXPECT_STREQ(s.get(), "abcdabcdabcd");
+
+ s.AssignLiteral("abcdabcdabcd");
+ s.ReplaceSubstring("notfound", "longlongstring");
+ EXPECT_STREQ(s.get(), "abcdabcdabcd");
+}
+
+TEST_F(Strings, strip_ws) {
+ const char* texts[] = {"", " a $ ", "Some\fother\t thing\r\n",
+ "And \f\t\r\n even\nmore\r \f"};
+ const char* results[] = {"", "a$", "Someotherthing", "Andevenmore"};
+ for (size_t i = 0; i < sizeof(texts) / sizeof(texts[0]); i++) {
+ nsCString s(texts[i]);
+ s.StripWhitespace();
+ EXPECT_STREQ(s.get(), results[i]);
+ }
+}
+
+TEST_F(Strings, equals_ic) {
+ nsCString s;
+ EXPECT_FALSE(s.LowerCaseEqualsLiteral("view-source"));
+}
+
+TEST_F(Strings, concat) {
+ nsCString bar("bar");
+ const nsACString& barRef = bar;
+
+ const nsPromiseFlatCString& result =
+ PromiseFlatCString("foo"_ns + ","_ns + barRef);
+ EXPECT_STREQ(result.get(), "foo,bar");
+}
+
+TEST_F(Strings, concat_2) {
+ nsCString fieldTextStr("xyz");
+ nsCString text("text");
+ const nsACString& aText = text;
+
+ nsAutoCString result(fieldTextStr + aText);
+
+ EXPECT_STREQ(result.get(), "xyztext");
+}
+
+TEST_F(Strings, concat_3) {
+ nsCString result;
+ nsCString ab("ab"), c("c");
+
+ result = ab + result + c;
+ EXPECT_STREQ(result.get(), "abc");
+}
+
+TEST_F(Strings, empty_assign) {
+ nsCString a;
+ a.AssignLiteral("");
+
+ a.AppendLiteral("");
+
+ nsCString b;
+ b.SetCapacity(0);
+}
+
+TEST_F(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_F(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_F(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_F(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_F(Strings, inttotstring) {
+ EXPECT_EQ("42"_ns, IntToCString(42));
+ EXPECT_EQ(u"42"_ns, IntToString(42));
+
+ EXPECT_EQ("2a"_ns, IntToCString(42, 16));
+ EXPECT_EQ(u"2a"_ns, IntToString(42, 16));
+}
+
+TEST_F(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_F(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_F(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.AssignLiteral("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_F(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_F(Strings, voided) {
+ const char kData[] = "hello world";
+
+ nsCString str;
+ EXPECT_TRUE(!str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+ EXPECT_STREQ(str.get(), "");
+
+ str.SetIsVoid(true);
+ EXPECT_TRUE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+ EXPECT_STREQ(str.get(), "");
+
+ str.Assign(kData);
+ EXPECT_TRUE(!str.IsVoid());
+ EXPECT_TRUE(!str.IsEmpty());
+ EXPECT_STREQ(str.get(), kData);
+
+ str.SetIsVoid(true);
+ EXPECT_TRUE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+ EXPECT_STREQ(str.get(), "");
+
+ str.SetIsVoid(false);
+ EXPECT_TRUE(!str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+ EXPECT_STREQ(str.get(), "");
+
+ str.Assign(kData);
+ EXPECT_TRUE(!str.IsVoid());
+ EXPECT_TRUE(!str.IsEmpty());
+ EXPECT_STREQ(str.get(), kData);
+
+ str.Adopt(nullptr);
+ EXPECT_TRUE(str.IsVoid());
+ EXPECT_TRUE(str.IsEmpty());
+ EXPECT_STREQ(str.get(), "");
+}
+
+TEST_F(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_F(Strings, voided_assignment) {
+ nsCString a, b;
+ b.SetIsVoid(true);
+ a = b;
+ EXPECT_TRUE(a.IsVoid());
+ EXPECT_EQ(a.get(), b.get());
+}
+
+TEST_F(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_F(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;
+ 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) {
+ nsAutoString data(str);
+ data.StripChars(strip);
+ EXPECT_TRUE(data.Equals(result));
+}
+
+TEST(String, strip_chars)
+{
+ test_strip_chars_helper(u"foo \r \nbar", u" \n\r", u"foobar"_ns);
+ test_strip_chars_helper(u"\r\nfoo\r\n", u" \n\r", u"foo"_ns);
+ test_strip_chars_helper(u"foo", u" \n\r", u"foo"_ns);
+ test_strip_chars_helper(u"foo", u"fo", u""_ns);
+ test_strip_chars_helper(u"foo", u"foo", u""_ns);
+ test_strip_chars_helper(u" foo", u" ", u"foo"_ns);
+}
+
+TEST_F(Strings, append_with_capacity) {
+ nsAutoString s;
+ const char16_t* origPtr = s.BeginReading();
+ s.SetCapacity(8000);
+ const char16_t* ptr = s.BeginReading();
+ EXPECT_NE(origPtr, ptr);
+ for (int i = 0; i < 100; i++) {
+ s.Append(u'a');
+ EXPECT_EQ(s.BeginReading(), ptr);
+ EXPECT_EQ(s.Length(), uint32_t(i + 1));
+ }
+}
+
+TEST_F(Strings, append_string_with_capacity) {
+ nsAutoString aa;
+ aa.Append(u'a');
+ aa.Append(u'a');
+ nsAutoString s;
+ const char16_t* origPtr = s.BeginReading();
+ s.SetCapacity(8000);
+ const char16_t* ptr = s.BeginReading();
+ EXPECT_NE(origPtr, ptr);
+ nsAutoString empty;
+ s.Append(empty);
+ EXPECT_EQ(s.BeginReading(), ptr);
+ for (int i = 0; i < 100; i++) {
+ s.Append(aa);
+ EXPECT_EQ(s.BeginReading(), ptr);
+ EXPECT_EQ(s.Length(), uint32_t(2 * (i + 1)));
+ }
+}
+
+TEST_F(Strings, append_literal_with_capacity) {
+ nsAutoString s;
+ const char16_t* origPtr = s.BeginReading();
+ s.SetCapacity(8000);
+ const char16_t* ptr = s.BeginReading();
+ EXPECT_NE(origPtr, ptr);
+ s.AppendLiteral(u"");
+ EXPECT_EQ(s.BeginReading(), ptr);
+ for (int i = 0; i < 100; i++) {
+ s.AppendLiteral(u"aa");
+ EXPECT_EQ(s.BeginReading(), ptr);
+ EXPECT_EQ(s.Length(), uint32_t(2 * (i + 1)));
+ }
+}
+
+// The following test is intentionally not memory
+// checking-clean.
+#if !(defined(MOZ_HAVE_MEM_CHECKS) || defined(MOZ_MSAN))
+TEST_F(Strings, legacy_set_length_semantics) {
+ const char* foobar = "foobar";
+ nsCString s;
+ s.SetCapacity(2048);
+ memcpy(s.BeginWriting(), foobar, strlen(foobar));
+ s.SetLength(strlen(foobar));
+ EXPECT_FALSE(s.EqualsASCII(foobar));
+}
+#endif
+
+TEST_F(Strings, bulk_write) {
+ nsCString s;
+ const char* ptrTwoThousand;
+ {
+ auto handleOrErr = s.BulkWrite(500, 0, true);
+ EXPECT_TRUE(handleOrErr.isOk());
+
+ auto handle = handleOrErr.unwrap();
+
+ auto span = handle.AsSpan();
+ for (auto&& c : span) {
+ c = 'a';
+ }
+ mozilla::Unused << handle.RestartBulkWrite(2000, 500, false);
+ span = handle.AsSpan().From(500);
+ for (auto&& c : span) {
+ c = 'b';
+ }
+ ptrTwoThousand = handle.Elements();
+ handle.Finish(1000, true);
+ }
+ EXPECT_EQ(s.Length(), 1000U);
+ EXPECT_NE(s.BeginReading(), ptrTwoThousand);
+ EXPECT_EQ(s.BeginReading()[1000], '\0');
+ for (uint32_t i = 0; i < 500; i++) {
+ EXPECT_EQ(s[i], 'a');
+ }
+ for (uint32_t i = 500; i < 1000; i++) {
+ EXPECT_EQ(s[i], 'b');
+ }
+}
+
+TEST_F(Strings, bulk_write_fail) {
+ nsCString s;
+ {
+ auto handleOrErr = s.BulkWrite(500, 0, true);
+ EXPECT_TRUE(handleOrErr.isOk());
+ }
+ EXPECT_EQ(s.Length(), 3U);
+ EXPECT_TRUE(s.Equals(u8"\uFFFD"));
+}
+
+TEST_F(Strings, huge_capacity) {
+ nsString a, b, c, d, e, f, g, h, i, j, k, l, m, n, o;
+ nsCString n1, o1;
+
+ // 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(uint32_t(-1) / 2, fallible));
+ a.Truncate(); // free the allocated memory
+
+ EXPECT_TRUE(b.SetCapacity(1, fallible));
+ EXPECT_FALSE(b.SetCapacity(uint32_t(-1) / 2 - 1, fallible));
+ b.Truncate();
+
+ EXPECT_TRUE(c.SetCapacity(1, fallible));
+ EXPECT_FALSE(c.SetCapacity(uint32_t(-1) / 2, fallible));
+ c.Truncate();
+
+ EXPECT_FALSE(d.SetCapacity(uint32_t(-1) / 2 - 1, fallible));
+ EXPECT_FALSE(d.SetCapacity(uint32_t(-1) / 2, fallible));
+ d.Truncate();
+
+ EXPECT_FALSE(e.SetCapacity(uint32_t(-1) / 4, fallible));
+ EXPECT_FALSE(e.SetCapacity(uint32_t(-1) / 4 + 1, fallible));
+ e.Truncate();
+
+ EXPECT_FALSE(f.SetCapacity(uint32_t(-1) / 2, fallible));
+ f.Truncate();
+
+ EXPECT_FALSE(g.SetCapacity(uint32_t(-1) / 4 + 1000, fallible));
+ EXPECT_FALSE(g.SetCapacity(uint32_t(-1) / 4 + 1001, fallible));
+ g.Truncate();
+
+ EXPECT_FALSE(h.SetCapacity(uint32_t(-1) / 4 + 1, fallible));
+ EXPECT_FALSE(h.SetCapacity(uint32_t(-1) / 2, fallible));
+ h.Truncate();
+
+ EXPECT_TRUE(i.SetCapacity(1, fallible));
+ EXPECT_TRUE(i.SetCapacity(uint32_t(-1) / 4 - 1000, fallible));
+ EXPECT_FALSE(i.SetCapacity(uint32_t(-1) / 4 + 1, fallible));
+ i.Truncate();
+
+ EXPECT_TRUE(j.SetCapacity(uint32_t(-1) / 4 - 1000, fallible));
+ EXPECT_FALSE(j.SetCapacity(uint32_t(-1) / 4 + 1, fallible));
+ j.Truncate();
+
+// Disabled due to intermittent failures.
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1493458
+#if 0
+ EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/8 - 1000, fallible));
+ EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/4 - 1001, fallible));
+ EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/4 - 998, fallible));
+ EXPECT_FALSE(k.SetCapacity(uint32_t(-1)/4 + 1, fallible));
+ k.Truncate();
+#endif
+
+ EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8, fallible));
+ EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8 + 1, fallible));
+ EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8 + 2, fallible));
+ l.Truncate();
+
+ EXPECT_TRUE(m.SetCapacity(uint32_t(-1) / 8 + 1000, fallible));
+ EXPECT_TRUE(m.SetCapacity(uint32_t(-1) / 8 + 1001, fallible));
+ m.Truncate();
+
+ EXPECT_TRUE(n.SetCapacity(uint32_t(-1) / 8 + 1, fallible));
+ EXPECT_FALSE(n.SetCapacity(uint32_t(-1) / 4, fallible));
+ n.Truncate();
+
+ n.Truncate();
+ EXPECT_TRUE(n.SetCapacity((uint32_t(-1) / 2) / 2 - 1, fallible));
+ n.Truncate();
+ EXPECT_FALSE(n.SetCapacity((uint32_t(-1) / 2) / 2, fallible));
+ n.Truncate();
+ n1.Truncate();
+ EXPECT_TRUE(n1.SetCapacity((uint32_t(-1) / 2) / 1 - 1, fallible));
+ n1.Truncate();
+ EXPECT_FALSE(n1.SetCapacity((uint32_t(-1) / 2) / 1, fallible));
+ n1.Truncate();
+
+ // The longest possible JS string should fit within both a `nsString` and
+ // nsCString.
+ EXPECT_TRUE(o.SetCapacity(JS::MaxStringLength, fallible));
+ o.Truncate();
+ EXPECT_TRUE(o1.SetCapacity(JS::MaxStringLength, fallible));
+ o1.Truncate();
+ }
+}
+
+static void test_tofloat_helper(const nsString& aStr,
+ mozilla::Maybe<float> aExpected) {
+ nsresult result;
+ float value = aStr.ToFloat(&result);
+ if (aExpected) {
+ EXPECT_TRUE(NS_SUCCEEDED(result));
+ EXPECT_EQ(value, *aExpected);
+ } else {
+ EXPECT_TRUE(NS_FAILED(result));
+ }
+}
+
+TEST_F(Strings, tofloat) {
+ test_tofloat_helper(u"42"_ns, Some(42.f));
+ test_tofloat_helper(u"42.0"_ns, Some(42.f));
+ test_tofloat_helper(u"-42"_ns, Some(-42.f));
+ test_tofloat_helper(u"+42"_ns, Some(42));
+ test_tofloat_helper(u"13.37"_ns, Some(13.37f));
+ test_tofloat_helper(u"1.23456789"_ns, Some(1.23456789f));
+ test_tofloat_helper(u"1.98765432123456"_ns, Some(1.98765432123456f));
+ test_tofloat_helper(u"0"_ns, Some(0.f));
+ test_tofloat_helper(u"1.e5"_ns, Some(100000));
+ test_tofloat_helper(u""_ns, Nothing());
+ test_tofloat_helper(u"42foo"_ns, Nothing());
+ test_tofloat_helper(u"foo"_ns, Nothing());
+ test_tofloat_helper(u"1.5e-"_ns, Nothing());
+
+ // Leading spaces are ignored
+ test_tofloat_helper(u" \t5"_ns, Some(5.f));
+
+ // Values which are too large generate an error
+ test_tofloat_helper(u"3.402823e38"_ns, Some(3.402823e+38));
+ test_tofloat_helper(u"1e39"_ns, Nothing());
+ test_tofloat_helper(u"-3.402823e38"_ns, Some(-3.402823e+38));
+ test_tofloat_helper(u"-1e39"_ns, Nothing());
+
+ // Values which are too small round to zero
+ test_tofloat_helper(u"1.4013e-45"_ns, Some(1.4013e-45f));
+ test_tofloat_helper(u"1e-46"_ns, Some(0.f));
+ test_tofloat_helper(u"-1.4013e-45"_ns, Some(-1.4013e-45f));
+ test_tofloat_helper(u"-1e-46"_ns, Some(-0.f));
+}
+
+static void test_tofloat_allow_trailing_chars_helper(const nsString& aStr,
+ Maybe<float> aExpected) {
+ nsresult result;
+ float value = aStr.ToFloatAllowTrailingChars(&result);
+ if (aExpected) {
+ EXPECT_TRUE(NS_SUCCEEDED(result));
+ EXPECT_EQ(value, *aExpected);
+ } else {
+ EXPECT_TRUE(NS_FAILED(result));
+ }
+}
+
+TEST_F(Strings, ToFloatAllowTrailingChars) {
+ test_tofloat_allow_trailing_chars_helper(u""_ns, Nothing());
+ test_tofloat_allow_trailing_chars_helper(u"foo"_ns, Nothing());
+ test_tofloat_allow_trailing_chars_helper(u"42foo"_ns, Some(42.f));
+ test_tofloat_allow_trailing_chars_helper(u"42-5"_ns, Some(42.f));
+ test_tofloat_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37f));
+ test_tofloat_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5f));
+}
+
+static void test_todouble_helper(const nsString& aStr,
+ Maybe<double> aExpected) {
+ nsresult result;
+ double value = aStr.ToDouble(&result);
+ if (aExpected) {
+ EXPECT_TRUE(NS_SUCCEEDED(result));
+ EXPECT_EQ(value, *aExpected);
+ } else {
+ EXPECT_TRUE(NS_FAILED(result));
+ }
+}
+
+TEST_F(Strings, todouble) {
+ test_todouble_helper(u"42"_ns, Some(42));
+ test_todouble_helper(u"42.0"_ns, Some(42));
+ test_todouble_helper(u"-42"_ns, Some(-42));
+ test_todouble_helper(u"+42"_ns, Some(42));
+ test_todouble_helper(u"13.37"_ns, Some(13.37));
+ test_todouble_helper(u"1.23456789"_ns, Some(1.23456789));
+ test_todouble_helper(u"1.98765432123456"_ns, Some(1.98765432123456));
+ test_todouble_helper(u"123456789.98765432123456"_ns,
+ Some(123456789.98765432123456));
+ test_todouble_helper(u"0"_ns, Some(0));
+ test_todouble_helper(u"1.e5"_ns, Some(100000));
+ test_todouble_helper(u""_ns, Nothing());
+ test_todouble_helper(u"42foo"_ns, Nothing());
+ test_todouble_helper(u"foo"_ns, Nothing());
+ test_todouble_helper(u"1.5e-"_ns, Nothing());
+
+ // Leading spaces are ignored
+ test_todouble_helper(u" \t5"_ns, Some(5.));
+
+ // Values which are too large generate an error
+ test_todouble_helper(u"1.797693e+308"_ns, Some(1.797693e+308));
+ test_todouble_helper(u"1e309"_ns, Nothing());
+ test_todouble_helper(u"-1.797693e+308"_ns, Some(-1.797693e+308));
+ test_todouble_helper(u"-1e309"_ns, Nothing());
+
+ // Values which are too small round to zero
+ test_todouble_helper(u"4.940656e-324"_ns, Some(4.940656e-324));
+ test_todouble_helper(u"1e-325"_ns, Some(0.));
+ test_todouble_helper(u"-4.940656e-324"_ns, Some(-4.940656e-324));
+ test_todouble_helper(u"-1e-325"_ns, Some(-0.));
+}
+
+static void test_todouble_allow_trailing_chars_helper(const nsString& aStr,
+ Maybe<double> aExpected) {
+ nsresult result;
+ double value = aStr.ToDoubleAllowTrailingChars(&result);
+ if (aExpected) {
+ EXPECT_TRUE(NS_SUCCEEDED(result));
+ EXPECT_EQ(value, *aExpected);
+ } else {
+ EXPECT_TRUE(NS_FAILED(result));
+ }
+}
+
+TEST_F(Strings, ToDoubleAllowTrailingChars) {
+ test_todouble_allow_trailing_chars_helper(u""_ns, Nothing());
+ test_todouble_allow_trailing_chars_helper(u"foo"_ns, Nothing());
+ test_todouble_allow_trailing_chars_helper(u"42foo"_ns, Some(42));
+ test_todouble_allow_trailing_chars_helper(u"42-5"_ns, Some(42));
+ test_todouble_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37));
+ test_todouble_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5));
+}
+
+TEST_F(Strings, Split) {
+ nsCString one("one"), two("one;two"), three("one--three"), empty(""),
+ delimStart("-two"), delimEnd("one-");
+
+ nsString wide(u"hello world");
+
+ size_t counter = 0;
+ for (const nsACString& token : one.Split(',')) {
+ EXPECT_TRUE(token.EqualsLiteral("one"));
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)1);
+
+ counter = 0;
+ for (const nsACString& token : two.Split(';')) {
+ if (counter == 0) {
+ EXPECT_TRUE(token.EqualsLiteral("one"));
+ } else if (counter == 1) {
+ EXPECT_TRUE(token.EqualsLiteral("two"));
+ }
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)2);
+
+ counter = 0;
+ for (const nsACString& token : three.Split('-')) {
+ if (counter == 0) {
+ EXPECT_TRUE(token.EqualsLiteral("one"));
+ } else if (counter == 1) {
+ EXPECT_TRUE(token.EqualsLiteral(""));
+ } else if (counter == 2) {
+ EXPECT_TRUE(token.EqualsLiteral("three"));
+ }
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)3);
+
+ counter = 0;
+ for (const nsACString& token : empty.Split(',')) {
+ mozilla::Unused << token;
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)0);
+
+ counter = 0;
+ for (const nsACString& token : delimStart.Split('-')) {
+ if (counter == 0) {
+ EXPECT_TRUE(token.EqualsLiteral(""));
+ } else if (counter == 1) {
+ EXPECT_TRUE(token.EqualsLiteral("two"));
+ }
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)2);
+
+ counter = 0;
+ for (const nsACString& token : delimEnd.Split('-')) {
+ if (counter == 0) {
+ EXPECT_TRUE(token.EqualsLiteral("one"));
+ } else if (counter == 1) {
+ EXPECT_TRUE(token.EqualsLiteral(""));
+ }
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)2);
+
+ counter = 0;
+ for (const nsAString& token : wide.Split(' ')) {
+ if (counter == 0) {
+ EXPECT_TRUE(token.Equals(u"hello"_ns));
+ } else if (counter == 1) {
+ EXPECT_TRUE(token.Equals(u"world"_ns));
+ }
+ counter++;
+ }
+ EXPECT_EQ(counter, (size_t)2);
+}
+
+TEST_F(Strings, Join) {
+ // Join a sequence of strings.
+ {
+ // 8-bit strings
+ EXPECT_EQ(""_ns, StringJoin(","_ns, std::array<nsCString, 0>{}));
+ EXPECT_EQ("foo"_ns, StringJoin(","_ns, std::array{"foo"_ns}));
+ EXPECT_EQ("foo,bar"_ns, StringJoin(","_ns, std::array{"foo"_ns, "bar"_ns}));
+
+ // 16-bit strings
+ EXPECT_EQ(u""_ns, StringJoin(u","_ns, std::array<nsString, 0>{}));
+ EXPECT_EQ(u"foo"_ns, StringJoin(u","_ns, std::array{u"foo"_ns}));
+ EXPECT_EQ(u"foo,bar"_ns,
+ StringJoin(u","_ns, std::array{u"foo"_ns, u"bar"_ns}));
+ }
+
+ // Join a sequence of strings, appending.
+ {
+ // 8-bit string
+ {
+ nsAutoCString dst{"prefix:"_ns};
+ StringJoinAppend(dst, ","_ns, std::array{"foo"_ns, "bar"_ns});
+ EXPECT_EQ("prefix:foo,bar"_ns, dst);
+ }
+
+ // 16-bit string
+ {
+ nsAutoString dst{u"prefix:"_ns};
+ StringJoinAppend(dst, u","_ns, std::array{u"foo"_ns, u"bar"_ns});
+ EXPECT_EQ(u"prefix:foo,bar"_ns, dst);
+ }
+ }
+}
+
+TEST_F(Strings, JoinWithAppendingTransformation) {
+ const auto toCString = [](nsACString& dst, int val) { dst.AppendInt(val); };
+ const auto toString = [](nsAString& dst, int val) { dst.AppendInt(val); };
+
+ // Join a sequence of elements transformed to a string.
+ {
+ // 8-bit strings
+ EXPECT_EQ(""_ns, StringJoin(","_ns, std::array<int, 0>{}, toCString));
+ EXPECT_EQ("7"_ns, StringJoin(","_ns, std::array{7}, toCString));
+ EXPECT_EQ("7,42"_ns, StringJoin(","_ns, std::array{7, 42}, toCString));
+
+ // 16-bit strings
+ EXPECT_EQ(u""_ns, StringJoin(u","_ns, std::array<int, 0>{}, toString));
+ EXPECT_EQ(u"7"_ns, StringJoin(u","_ns, std::array{7}, toString));
+ EXPECT_EQ(u"7,42"_ns, StringJoin(u","_ns, std::array{7, 42}, toString));
+ }
+
+ // Join a sequence of elements transformed to a string, appending.
+ {
+ // 8-bit string
+ {
+ nsAutoCString dst{"prefix:"_ns};
+ StringJoinAppend(dst, ","_ns, std::array{7, 42}, toCString);
+ EXPECT_EQ("prefix:7,42"_ns, dst);
+ }
+
+ // 16-bit string
+ {
+ nsAutoString dst{u"prefix:"_ns};
+ StringJoinAppend(dst, u","_ns, std::array{7, 42}, toString);
+ EXPECT_EQ(u"prefix:7,42"_ns, dst);
+ }
+ }
+}
+
+constexpr bool TestSomeChars(char c) {
+ return c == 'a' || c == 'c' || c == 'e' || c == '7' || c == 'G' || c == 'Z' ||
+ c == '\b' || c == '?';
+}
+TEST_F(Strings, ASCIIMask) {
+ const ASCIIMaskArray& maskCRLF = mozilla::ASCIIMask::MaskCRLF();
+ EXPECT_TRUE(maskCRLF['\n'] && mozilla::ASCIIMask::IsMasked(maskCRLF, '\n'));
+ EXPECT_TRUE(maskCRLF['\r'] && mozilla::ASCIIMask::IsMasked(maskCRLF, '\r'));
+ EXPECT_FALSE(maskCRLF['g'] || mozilla::ASCIIMask::IsMasked(maskCRLF, 'g'));
+ EXPECT_FALSE(maskCRLF[' '] || mozilla::ASCIIMask::IsMasked(maskCRLF, ' '));
+ EXPECT_FALSE(maskCRLF['\0'] || mozilla::ASCIIMask::IsMasked(maskCRLF, '\0'));
+ EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324));
+
+ const ASCIIMaskArray& mask0to9 = mozilla::ASCIIMask::Mask0to9();
+ EXPECT_TRUE(mask0to9['9'] && mozilla::ASCIIMask::IsMasked(mask0to9, '9'));
+ EXPECT_TRUE(mask0to9['0'] && mozilla::ASCIIMask::IsMasked(mask0to9, '0'));
+ EXPECT_TRUE(mask0to9['4'] && mozilla::ASCIIMask::IsMasked(mask0to9, '4'));
+ EXPECT_FALSE(mask0to9['g'] || mozilla::ASCIIMask::IsMasked(mask0to9, 'g'));
+ EXPECT_FALSE(mask0to9[' '] || mozilla::ASCIIMask::IsMasked(mask0to9, ' '));
+ EXPECT_FALSE(mask0to9['\n'] || mozilla::ASCIIMask::IsMasked(mask0to9, '\n'));
+ EXPECT_FALSE(mask0to9['\0'] || mozilla::ASCIIMask::IsMasked(mask0to9, '\0'));
+ EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324));
+
+ const ASCIIMaskArray& maskWS = mozilla::ASCIIMask::MaskWhitespace();
+ EXPECT_TRUE(maskWS[' '] && mozilla::ASCIIMask::IsMasked(maskWS, ' '));
+ EXPECT_TRUE(maskWS['\t'] && mozilla::ASCIIMask::IsMasked(maskWS, '\t'));
+ EXPECT_FALSE(maskWS['8'] || mozilla::ASCIIMask::IsMasked(maskWS, '8'));
+ EXPECT_FALSE(maskWS['\0'] || mozilla::ASCIIMask::IsMasked(maskWS, '\0'));
+ EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324));
+
+ constexpr ASCIIMaskArray maskSome = mozilla::CreateASCIIMask(TestSomeChars);
+ EXPECT_TRUE(maskSome['a'] && mozilla::ASCIIMask::IsMasked(maskSome, 'a'));
+ EXPECT_TRUE(maskSome['c'] && mozilla::ASCIIMask::IsMasked(maskSome, 'c'));
+ EXPECT_TRUE(maskSome['e'] && mozilla::ASCIIMask::IsMasked(maskSome, 'e'));
+ EXPECT_TRUE(maskSome['7'] && mozilla::ASCIIMask::IsMasked(maskSome, '7'));
+ EXPECT_TRUE(maskSome['G'] && mozilla::ASCIIMask::IsMasked(maskSome, 'G'));
+ EXPECT_TRUE(maskSome['Z'] && mozilla::ASCIIMask::IsMasked(maskSome, 'Z'));
+ EXPECT_TRUE(maskSome['\b'] && mozilla::ASCIIMask::IsMasked(maskSome, '\b'));
+ EXPECT_TRUE(maskSome['?'] && mozilla::ASCIIMask::IsMasked(maskSome, '?'));
+ EXPECT_FALSE(maskSome['8'] || mozilla::ASCIIMask::IsMasked(maskSome, '8'));
+ EXPECT_FALSE(maskSome['\0'] || mozilla::ASCIIMask::IsMasked(maskSome, '\0'));
+ EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324));
+}
+
+template <typename T>
+void CompressWhitespaceHelper() {
+ T s;
+ s.AssignLiteral("abcabcabc");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral("abcabcabc"));
+
+ s.AssignLiteral(" \n\rabcabcabc\r\n");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral("abcabcabc"));
+
+ s.AssignLiteral(" \n\rabc abc abc\r\n");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral("abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r abc\n abc\r\n");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral("abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r \nabc\n \rabc\r\n");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral("abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r abc\n abc\r\n");
+ s.CompressWhitespace(false, true);
+ EXPECT_TRUE(s.EqualsLiteral(" abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r abc\n abc\r\n");
+ s.CompressWhitespace(true, false);
+ EXPECT_TRUE(s.EqualsLiteral("abc abc abc "));
+
+ s.AssignLiteral(" \n\rabc\r abc\n abc\r\n");
+ s.CompressWhitespace(false, false);
+ EXPECT_TRUE(s.EqualsLiteral(" abc abc abc "));
+
+ s.AssignLiteral(" \r\n ");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral(" \r\n \t");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral("\n \r\n \t");
+ s.CompressWhitespace(false, false);
+ EXPECT_TRUE(s.EqualsLiteral(" "));
+
+ s.AssignLiteral("\n \r\n \t");
+ s.CompressWhitespace(false, true);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral("\n \r\n \t");
+ s.CompressWhitespace(true, false);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral("");
+ s.CompressWhitespace(false, false);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral("");
+ s.CompressWhitespace(false, true);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral("");
+ s.CompressWhitespace(true, false);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+
+ s.AssignLiteral("");
+ s.CompressWhitespace(true, true);
+ EXPECT_TRUE(s.EqualsLiteral(""));
+}
+
+TEST_F(Strings, CompressWhitespace) { CompressWhitespaceHelper<nsCString>(); }
+
+TEST_F(Strings, CompressWhitespaceW) {
+ CompressWhitespaceHelper<nsString>();
+
+ nsString str, result;
+ str.AssignLiteral(u"\u263A is\r\n ;-)");
+ result.AssignLiteral(u"\u263A is ;-)");
+ str.CompressWhitespace(true, true);
+ EXPECT_TRUE(str == result);
+}
+
+template <typename T>
+void StripCRLFHelper() {
+ T s;
+ s.AssignLiteral("abcabcabc");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral("abcabcabc"));
+
+ s.AssignLiteral(" \n\rabcabcabc\r\n");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" abcabcabc"));
+
+ s.AssignLiteral(" \n\rabc abc abc\r\n");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r abc\n abc\r\n");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r \nabc\n \rabc\r\n");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" abc abc abc"));
+
+ s.AssignLiteral(" \n\rabc\r abc\n abc\r\n");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" abc abc abc"));
+
+ s.AssignLiteral(" \r\n ");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" "));
+
+ s.AssignLiteral(" \r\n \t");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" \t"));
+
+ s.AssignLiteral("\n \r\n \t");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(" \t"));
+
+ s.AssignLiteral("");
+ s.StripCRLF();
+ EXPECT_TRUE(s.EqualsLiteral(""));
+}
+
+TEST_F(Strings, StripCRLF) { StripCRLFHelper<nsCString>(); }
+
+TEST_F(Strings, StripCRLFW) {
+ StripCRLFHelper<nsString>();
+
+ nsString str, result;
+ str.AssignLiteral(u"\u263A is\r\n ;-)");
+ result.AssignLiteral(u"\u263A is ;-)");
+ str.StripCRLF();
+ EXPECT_TRUE(str == result);
+}
+
+TEST_F(Strings, utf8_to_latin1_sharing) {
+ nsCString s;
+ s.Append('a');
+ s.Append('b');
+ s.Append('c');
+ nsCString t;
+ LossyAppendUTF8toLatin1(s, t);
+ EXPECT_TRUE(t.EqualsLiteral("abc"));
+ EXPECT_EQ(s.BeginReading(), t.BeginReading());
+ LossyCopyUTF8toLatin1(s, t);
+ EXPECT_TRUE(t.EqualsLiteral("abc"));
+ EXPECT_EQ(s.BeginReading(), t.BeginReading());
+}
+
+TEST_F(Strings, latin1_to_utf8_sharing) {
+ nsCString s;
+ s.Append('a');
+ s.Append('b');
+ s.Append('c');
+ nsCString t;
+ AppendLatin1toUTF8(s, t);
+ EXPECT_TRUE(t.EqualsLiteral("abc"));
+ EXPECT_EQ(s.BeginReading(), t.BeginReading());
+ CopyLatin1toUTF8(s, t);
+ EXPECT_TRUE(t.EqualsLiteral("abc"));
+ EXPECT_EQ(s.BeginReading(), t.BeginReading());
+}
+
+TEST_F(Strings, utf8_to_latin1) {
+ nsCString s;
+ s.AssignLiteral("\xC3\xA4");
+ nsCString t;
+ LossyCopyUTF8toLatin1(s, t);
+ // EqualsLiteral requires ASCII
+ EXPECT_TRUE(t.Equals("\xE4"));
+}
+
+TEST_F(Strings, latin1_to_utf8) {
+ nsCString s;
+ s.AssignLiteral("\xE4");
+ nsCString t;
+ CopyLatin1toUTF8(s, t);
+ // EqualsLiteral requires ASCII
+ EXPECT_TRUE(t.Equals("\xC3\xA4"));
+}
+
+TEST_F(Strings, ConvertToSpan) {
+ nsString string;
+
+ // from const string
+ {
+ const auto& constStringRef = string;
+
+ auto span = Span{constStringRef};
+ static_assert(std::is_same_v<decltype(span), Span<const char16_t>>);
+ }
+
+ // from non-const string
+ {
+ auto span = Span{string};
+ static_assert(std::is_same_v<decltype(span), Span<const char16_t>>);
+ }
+
+ // get mutable data
+ {
+ auto span = string.GetMutableData();
+ static_assert(std::is_same_v<decltype(span), Span<char16_t>>);
+ }
+
+ nsCString cstring;
+
+ // from const string
+ {
+ const auto& constCStringRef = cstring;
+
+ auto span = Span{constCStringRef};
+ static_assert(std::is_same_v<decltype(span), Span<const char>>);
+ }
+
+ // from non-const string
+ {
+ auto span = Span{cstring};
+ static_assert(std::is_same_v<decltype(span), Span<const char>>);
+ }
+
+ // get mutable data
+ {
+ auto span = cstring.GetMutableData();
+ static_assert(std::is_same_v<decltype(span), Span<char>>);
+ }
+}
+
+template <typename T>
+void InsertSpanHelper() {
+ T str1, str2;
+ str1.AssignLiteral("hello world");
+ str2.AssignLiteral("span ");
+
+ T expect;
+ expect.AssignLiteral("hello span world");
+
+ Span span(str2);
+ str1.Insert(span, 6);
+ EXPECT_TRUE(str1.Equals(expect));
+}
+
+TEST_F(Strings, InsertSpan) { InsertSpanHelper<nsCString>(); }
+TEST_F(Strings, InsertSpanW) { InsertSpanHelper<nsString>(); }
+
+TEST_F(Strings, TokenizedRangeEmpty) {
+ // 8-bit strings
+ {
+ for (const auto& token : nsCCharSeparatedTokenizer(""_ns, ',').ToRange()) {
+ (void)token;
+ ADD_FAILURE();
+ }
+ }
+
+ // 16-bit strings
+ {
+ for (const auto& token : nsCharSeparatedTokenizer(u""_ns, ',').ToRange()) {
+ (void)token;
+ ADD_FAILURE();
+ }
+ }
+}
+
+TEST_F(Strings, TokenizedRangeWhitespaceOnly) {
+ // 8-bit strings
+ {
+ for (const auto& token : nsCCharSeparatedTokenizer(" "_ns, ',').ToRange()) {
+ (void)token;
+ ADD_FAILURE();
+ }
+ }
+
+ // 16-bit strings
+ {
+ for (const auto& token : nsCharSeparatedTokenizer(u" "_ns, ',').ToRange()) {
+ (void)token;
+ ADD_FAILURE();
+ }
+ }
+}
+
+TEST_F(Strings, TokenizedRangeNonEmpty) {
+ // 8-bit strings
+ {
+ nsTArray<nsCString> res;
+ for (const auto& token :
+ nsCCharSeparatedTokenizer("foo,bar"_ns, ',').ToRange()) {
+ res.EmplaceBack(token);
+ }
+
+ EXPECT_EQ(res, (nsTArray<nsCString>{"foo"_ns, "bar"_ns}));
+ }
+
+ // 16-bit strings
+ {
+ nsTArray<nsString> res;
+ for (const auto& token :
+ nsCharSeparatedTokenizer(u"foo,bar"_ns, ',').ToRange()) {
+ res.EmplaceBack(token);
+ }
+
+ EXPECT_EQ(res, (nsTArray<nsString>{u"foo"_ns, u"bar"_ns}));
+ }
+}
+
+// Macros for reducing verbosity of printf tests.
+#define create_printf_strings(format, ...) \
+ nsCString appendPrintfString; \
+ appendPrintfString.AppendPrintf(format, __VA_ARGS__); \
+ const nsCString appendVprintfString( \
+ getAppendVprintfString(format, __VA_ARGS__)); \
+ const nsPrintfCString printfString(format, __VA_ARGS__); \
+ const nsVprintfCString vprintfString{getVprintfCString(format, __VA_ARGS__)};
+
+// We don't check every possible combination as we assume equality is
+// transitive.
+#define verify_printf_strings(expected) \
+ EXPECT_TRUE(appendPrintfString.EqualsASCII(expected)) \
+ << "appendPrintfString != expected:" << appendPrintfString.get() \
+ << " != " << (expected); \
+ EXPECT_TRUE(appendPrintfString.Equals(appendVprintfString)) \
+ << "appendPrintfString != appendVprintfString:" \
+ << appendPrintfString.get() << " != " << appendVprintfString; \
+ EXPECT_TRUE(appendPrintfString.Equals(printfString)) \
+ << "appendPrintfString != printfString:" << appendPrintfString.get() \
+ << " != " << printfString; \
+ EXPECT_TRUE(appendPrintfString.Equals(vprintfString)) \
+ << "appendPrintfString != vprintfString:" << appendPrintfString.get() \
+ << " != " << vprintfString;
+
+TEST_F(Strings, printf) {
+ auto getAppendVprintfString = [](const char* aFormat, ...) {
+ // Helper to get a string with contents set via AppendVprint.
+ nsCString cString;
+ va_list ap;
+ va_start(ap, aFormat);
+ cString.AppendVprintf(aFormat, ap);
+ va_end(ap);
+ return cString;
+ };
+
+ auto getVprintfCString = [](const char* aFormat, ...) {
+ // Helper to get a nsVprintfCString.
+ va_list ap;
+ va_start(ap, aFormat);
+ const nsVprintfCString vprintfString(aFormat, ap);
+ va_end(ap);
+ return vprintfString;
+ };
+
+ {
+ const char* format = "Characters %c %%";
+ const char* expectedOutput = "Characters B %";
+ create_printf_strings(format, 'B');
+ verify_printf_strings(expectedOutput);
+ }
+ {
+ const char* format = "Strings %s %s";
+ const char* expectedOutput = "Strings foo bar";
+ create_printf_strings(format, "foo", "bar");
+ verify_printf_strings(expectedOutput);
+ }
+ {
+ const int signedThree = 3;
+ const unsigned int unsignedTen = 10;
+ const char* format = "Integers %i %.3d %.2u %o %x %X";
+ const char* expectedOutput = "Integers 3 003 10 12 a A";
+ create_printf_strings(format, signedThree, signedThree, unsignedTen,
+ unsignedTen, unsignedTen, unsignedTen);
+ verify_printf_strings(expectedOutput);
+ }
+ {
+ const char* format = "Floats %f %.0f %e %.2E";
+ const char* expectedOutput = "Floats 1.500000 2 1.500000e+00 1.50E+00";
+ create_printf_strings(format, 1.5, 1.5, 1.5, 1.5);
+ verify_printf_strings(expectedOutput);
+ }
+ {
+ const char* expectedOutput = "Just a string";
+ const char* format = "%s";
+ create_printf_strings(format, "Just a string");
+ verify_printf_strings(expectedOutput);
+ }
+ {
+ const char* anotherString = "another string";
+ const char* format = "Just a string and %s";
+ const char* expectedOutput = "Just a string and another string";
+ create_printf_strings(format, anotherString);
+ verify_printf_strings(expectedOutput);
+ }
+ {
+ // This case tickles an unexpected overload resolution in MSVC where a
+ // va_list overload will be selected if available. See bug 1673670 and
+ // 1673917 for more detail.
+ char anotherString[] = "another string";
+ const char* format = "Just a string and %s";
+ const char* expectedOutput = "Just a string and another string";
+ // Calling with a non-const pointer triggers selection of va_list overload
+ // in MSVC at time of writing
+ create_printf_strings(format, (char*)anotherString);
+ verify_printf_strings(expectedOutput);
+ }
+}
+
+// We don't need these macros following the printf test.
+#undef verify_printf_strings
+#undef create_printf_strings
+
+// Note the five calls in the loop, so divide by 100k
+MOZ_GTEST_BENCH_F(Strings, PerfStripWhitespace, [this] {
+ nsCString test1(mExample1Utf8);
+ nsCString test2(mExample2Utf8);
+ nsCString test3(mExample3Utf8);
+ nsCString test4(mExample4Utf8);
+ nsCString test5(mExample5Utf8);
+ for (int i = 0; i < 20000; i++) {
+ test1.StripWhitespace();
+ test2.StripWhitespace();
+ test3.StripWhitespace();
+ test4.StripWhitespace();
+ test5.StripWhitespace();
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfStripCharsWhitespace, [this] {
+ // This is the unoptimized (original) version of
+ // StripWhitespace using StripChars.
+ nsCString test1(mExample1Utf8);
+ nsCString test2(mExample2Utf8);
+ nsCString test3(mExample3Utf8);
+ nsCString test4(mExample4Utf8);
+ nsCString test5(mExample5Utf8);
+ for (int i = 0; i < 20000; i++) {
+ test1.StripChars("\f\t\r\n ");
+ test2.StripChars("\f\t\r\n ");
+ test3.StripChars("\f\t\r\n ");
+ test4.StripChars("\f\t\r\n ");
+ test5.StripChars("\f\t\r\n ");
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfCompressWhitespace, [this] {
+ nsCString test1(mExample1Utf8);
+ nsCString test2(mExample2Utf8);
+ nsCString test3(mExample3Utf8);
+ nsCString test4(mExample4Utf8);
+ nsCString test5(mExample5Utf8);
+ for (int i = 0; i < 20000; i++) {
+ test1.CompressWhitespace();
+ test2.CompressWhitespace();
+ test3.CompressWhitespace();
+ test4.CompressWhitespace();
+ test5.CompressWhitespace();
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfStripCRLF, [this] {
+ nsCString test1(mExample1Utf8);
+ nsCString test2(mExample2Utf8);
+ nsCString test3(mExample3Utf8);
+ nsCString test4(mExample4Utf8);
+ nsCString test5(mExample5Utf8);
+ for (int i = 0; i < 20000; i++) {
+ test1.StripCRLF();
+ test2.StripCRLF();
+ test3.StripCRLF();
+ test4.StripCRLF();
+ test5.StripCRLF();
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfStripCharsCRLF, [this] {
+ // This is the unoptimized (original) version of
+ // stripping \r\n using StripChars.
+ nsCString test1(mExample1Utf8);
+ nsCString test2(mExample2Utf8);
+ nsCString test3(mExample3Utf8);
+ nsCString test4(mExample4Utf8);
+ nsCString test5(mExample5Utf8);
+ for (int i = 0; i < 20000; i++) {
+ test1.StripChars("\r\n");
+ test2.StripChars("\r\n");
+ test3.StripChars("\r\n");
+ test4.StripChars("\r\n");
+ test5.StripChars("\r\n");
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8One, [this] {
+ for (int i = 0; i < 200000; i++) {
+ bool b = IsUtf8(*BlackBox(&mAsciiOneUtf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Fifteen, [this] {
+ for (int i = 0; i < 200000; i++) {
+ bool b = IsUtf8(*BlackBox(&mAsciiFifteenUtf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Hundred, [this] {
+ for (int i = 0; i < 200000; i++) {
+ bool b = IsUtf8(*BlackBox(&mAsciiHundredUtf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Example3, [this] {
+ for (int i = 0; i < 100000; i++) {
+ bool b = IsUtf8(*BlackBox(&mExample3Utf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsASCII8One, [this] {
+ for (int i = 0; i < 200000; i++) {
+ bool b = IsAscii(*BlackBox(&mAsciiOneUtf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIFifteen, [this] {
+ for (int i = 0; i < 200000; i++) {
+ bool b = IsAscii(*BlackBox(&mAsciiFifteenUtf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIHundred, [this] {
+ for (int i = 0; i < 200000; i++) {
+ bool b = IsAscii(*BlackBox(&mAsciiHundredUtf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIExample3, [this] {
+ for (int i = 0; i < 100000; i++) {
+ bool b = IsAscii(*BlackBox(&mExample3Utf8));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsExample3, [this] {
+ for (int i = 0; i < 5000; i++) {
+ bool b = HasRTLChars(*BlackBox(&mExample3Utf16));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsDE, [this] {
+ for (int i = 0; i < 5000; i++) {
+ bool b = HasRTLChars(*BlackBox(&mDeUtf16));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsRU, [this] {
+ for (int i = 0; i < 5000; i++) {
+ bool b = HasRTLChars(*BlackBox(&mRuUtf16));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsTH, [this] {
+ for (int i = 0; i < 5000; i++) {
+ bool b = HasRTLChars(*BlackBox(&mThUtf16));
+ BlackBox(&b);
+ }
+});
+
+MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsJA, [this] {
+ for (int i = 0; i < 5000; i++) {
+ bool b = HasRTLChars(*BlackBox(&mJaUtf16));
+ BlackBox(&b);
+ }
+});
+
+CONVERSION_BENCH(PerfUTF16toLatin1ASCIIOne, LossyCopyUTF16toASCII,
+ mAsciiOneUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1ASCIIThree, LossyCopyUTF16toASCII,
+ mAsciiThreeUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1ASCIIFifteen, LossyCopyUTF16toASCII,
+ mAsciiFifteenUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1ASCIIHundred, LossyCopyUTF16toASCII,
+ mAsciiHundredUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1ASCIIThousand, LossyCopyUTF16toASCII,
+ mAsciiThousandUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1DEOne, LossyCopyUTF16toASCII, mDeEditOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1DEThree, LossyCopyUTF16toASCII,
+ mDeEditThreeUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1DEFifteen, LossyCopyUTF16toASCII,
+ mDeEditFifteenUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1DEHundred, LossyCopyUTF16toASCII,
+ mDeEditHundredUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toLatin1DEThousand, LossyCopyUTF16toASCII,
+ mDeEditThousandUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16AsciiOne, CopyASCIItoUTF16, mAsciiOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16AsciiThree, CopyASCIItoUTF16, mAsciiThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16AsciiFifteen, CopyASCIItoUTF16,
+ mAsciiFifteenUtf8, nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16AsciiHundred, CopyASCIItoUTF16,
+ mAsciiHundredUtf8, nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16AsciiThousand, CopyASCIItoUTF16,
+ mAsciiThousandUtf8, nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16DEOne, CopyASCIItoUTF16, mDeEditOneLatin1,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16DEThree, CopyASCIItoUTF16, mDeEditThreeLatin1,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16DEFifteen, CopyASCIItoUTF16,
+ mDeEditFifteenLatin1, nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16DEHundred, CopyASCIItoUTF16,
+ mDeEditHundredLatin1, nsAutoString);
+
+CONVERSION_BENCH(PerfLatin1toUTF16DEThousand, CopyASCIItoUTF16,
+ mDeEditThousandLatin1, nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8AsciiOne, CopyUTF16toUTF8, mAsciiOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8AsciiThree, CopyUTF16toUTF8, mAsciiThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8AsciiFifteen, CopyUTF16toUTF8,
+ mAsciiFifteenUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8AsciiHundred, CopyUTF16toUTF8,
+ mAsciiHundredUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8AsciiThousand, CopyUTF16toUTF8,
+ mAsciiThousandUtf16, nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16AsciiOne, CopyUTF8toUTF16, mAsciiOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16AsciiThree, CopyUTF8toUTF16, mAsciiThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16AsciiFifteen, CopyUTF8toUTF16,
+ mAsciiFifteenUtf8, nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16AsciiHundred, CopyUTF8toUTF16,
+ mAsciiHundredUtf8, nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16AsciiThousand, CopyUTF8toUTF16,
+ mAsciiThousandUtf8, nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8AROne, CopyUTF16toUTF8, mArOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8ARThree, CopyUTF16toUTF8, mArThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8ARFifteen, CopyUTF16toUTF8, mArFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8ARHundred, CopyUTF16toUTF8, mArHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8ARThousand, CopyUTF16toUTF8, mArThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16AROne, CopyUTF8toUTF16, mArOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16ARThree, CopyUTF8toUTF16, mArThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16ARFifteen, CopyUTF8toUTF16, mArFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16ARHundred, CopyUTF8toUTF16, mArHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16ARThousand, CopyUTF8toUTF16, mArThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8DEOne, CopyUTF16toUTF8, mDeOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8DEThree, CopyUTF16toUTF8, mDeThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8DEFifteen, CopyUTF16toUTF8, mDeFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8DEHundred, CopyUTF16toUTF8, mDeHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8DEThousand, CopyUTF16toUTF8, mDeThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16DEOne, CopyUTF8toUTF16, mDeOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16DEThree, CopyUTF8toUTF16, mDeThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16DEFifteen, CopyUTF8toUTF16, mDeFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16DEHundred, CopyUTF8toUTF16, mDeHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16DEThousand, CopyUTF8toUTF16, mDeThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8RUOne, CopyUTF16toUTF8, mRuOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8RUThree, CopyUTF16toUTF8, mRuThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8RUFifteen, CopyUTF16toUTF8, mRuFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8RUHundred, CopyUTF16toUTF8, mRuHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8RUThousand, CopyUTF16toUTF8, mRuThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16RUOne, CopyUTF8toUTF16, mRuOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16RUThree, CopyUTF8toUTF16, mRuThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16RUFifteen, CopyUTF8toUTF16, mRuFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16RUHundred, CopyUTF8toUTF16, mRuHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16RUThousand, CopyUTF8toUTF16, mRuThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8THOne, CopyUTF16toUTF8, mThOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8THThree, CopyUTF16toUTF8, mThThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8THFifteen, CopyUTF16toUTF8, mThFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8THHundred, CopyUTF16toUTF8, mThHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8THThousand, CopyUTF16toUTF8, mThThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16THOne, CopyUTF8toUTF16, mThOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16THThree, CopyUTF8toUTF16, mThThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16THFifteen, CopyUTF8toUTF16, mThFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16THHundred, CopyUTF8toUTF16, mThHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16THThousand, CopyUTF8toUTF16, mThThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8JAOne, CopyUTF16toUTF8, mJaOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8JAThree, CopyUTF16toUTF8, mJaThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8JAFifteen, CopyUTF16toUTF8, mJaFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8JAHundred, CopyUTF16toUTF8, mJaHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8JAThousand, CopyUTF16toUTF8, mJaThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16JAOne, CopyUTF8toUTF16, mJaOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16JAThree, CopyUTF8toUTF16, mJaThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16JAFifteen, CopyUTF8toUTF16, mJaFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16JAHundred, CopyUTF8toUTF16, mJaHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16JAThousand, CopyUTF8toUTF16, mJaThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8KOOne, CopyUTF16toUTF8, mKoOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8KOThree, CopyUTF16toUTF8, mKoThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8KOFifteen, CopyUTF16toUTF8, mKoFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8KOHundred, CopyUTF16toUTF8, mKoHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8KOThousand, CopyUTF16toUTF8, mKoThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16KOOne, CopyUTF8toUTF16, mKoOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16KOThree, CopyUTF8toUTF16, mKoThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16KOFifteen, CopyUTF8toUTF16, mKoFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16KOHundred, CopyUTF8toUTF16, mKoHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16KOThousand, CopyUTF8toUTF16, mKoThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8TROne, CopyUTF16toUTF8, mTrOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8TRThree, CopyUTF16toUTF8, mTrThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8TRFifteen, CopyUTF16toUTF8, mTrFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8TRHundred, CopyUTF16toUTF8, mTrHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8TRThousand, CopyUTF16toUTF8, mTrThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16TROne, CopyUTF8toUTF16, mTrOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16TRThree, CopyUTF8toUTF16, mTrThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16TRFifteen, CopyUTF8toUTF16, mTrFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16TRHundred, CopyUTF8toUTF16, mTrHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16TRThousand, CopyUTF8toUTF16, mTrThousandUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8VIOne, CopyUTF16toUTF8, mViOneUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8VIThree, CopyUTF16toUTF8, mViThreeUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8VIFifteen, CopyUTF16toUTF8, mViFifteenUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8VIHundred, CopyUTF16toUTF8, mViHundredUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF16toUTF8VIThousand, CopyUTF16toUTF8, mViThousandUtf16,
+ nsAutoCString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16VIOne, CopyUTF8toUTF16, mViOneUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16VIThree, CopyUTF8toUTF16, mViThreeUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16VIFifteen, CopyUTF8toUTF16, mViFifteenUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16VIHundred, CopyUTF8toUTF16, mViHundredUtf8,
+ nsAutoString);
+
+CONVERSION_BENCH(PerfUTF8toUTF16VIThousand, CopyUTF8toUTF16, mViThousandUtf8,
+ nsAutoString);
+
+// Tests for usability of nsTLiteralString in constant expressions.
+static_assert(u""_ns.IsEmpty());
+
+constexpr auto testStringA = u"a"_ns;
+static_assert(!testStringA.IsEmpty());
+static_assert(!testStringA.IsVoid());
+static_assert(testStringA.IsLiteral());
+static_assert(testStringA.IsTerminated());
+static_assert(testStringA.GetDataFlags() ==
+ (nsLiteralString::DataFlags::LITERAL |
+ nsLiteralString::DataFlags::TERMINATED));
+static_assert(*static_cast<const char16_t*>(testStringA.Data()) == 'a');
+static_assert(1 == testStringA.Length());
+static_assert(testStringA.CharAt(0) == 'a');
+static_assert(testStringA[0] == 'a');
+static_assert(*testStringA.BeginReading() == 'a');
+static_assert(*testStringA.EndReading() == 0);
+static_assert(testStringA.EndReading() - testStringA.BeginReading() == 1);
+
+} // namespace TestStrings
+
+#if defined(__clang__) && (__clang_major__ >= 6)
+# pragma clang diagnostic pop
+#endif
diff --git a/xpcom/tests/gtest/TestSubstringTuple.cpp b/xpcom/tests/gtest/TestSubstringTuple.cpp
new file mode 100644
index 0000000000..6dfd6bc154
--- /dev/null
+++ b/xpcom/tests/gtest/TestSubstringTuple.cpp
@@ -0,0 +1,55 @@
+/* -*- 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 "nsLiteralString.h"
+#include "nsTSubstringTuple.h"
+#include "gtest/gtest.h"
+
+namespace TestSubstringTuple {
+
+static const auto kFooLiteral = u"foo"_ns;
+
+static const auto kFoo = nsCString("foo");
+static const auto kBar = nsCString("bar");
+static const auto kBaz = nsCString("baz");
+
+// The test must be done in a macro to ensure that tuple is always a temporary.
+#define DO_SUBSTRING_TUPLE_TEST(tuple, dependentString, expectedLength, \
+ expectedDependency) \
+ const auto length = (tuple).Length(); \
+ const auto isDependentOn = (tuple).IsDependentOn( \
+ dependentString.BeginReading(), dependentString.EndReading()); \
+ \
+ EXPECT_EQ((expectedLength), length); \
+ EXPECT_EQ((expectedDependency), isDependentOn); \
+ \
+ const auto [combinedIsDependentOn, combinedLength] = \
+ (tuple).IsDependentOnWithLength(dependentString.BeginReading(), \
+ dependentString.EndReading()); \
+ \
+ EXPECT_EQ(length, combinedLength); \
+ EXPECT_EQ(isDependentOn, combinedIsDependentOn);
+
+TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_Literal_ZeroLength)
+{ DO_SUBSTRING_TUPLE_TEST(u""_ns + u""_ns, kFooLiteral, 0u, false); }
+
+TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_Literal_NonZeroLength)
+{ DO_SUBSTRING_TUPLE_TEST(u"bar"_ns + u"baz"_ns, kFooLiteral, 6u, false); }
+
+TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_NonZeroLength)
+{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz, kFoo, 6u, false); }
+
+TEST(SubstringTuple,
+ IsDependentOnAndLength_NonDependent_NonZeroLength_ThreeParts)
+{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz + kBar, kFoo, 9u, false); }
+
+TEST(SubstringTuple, IsDependentOnAndLength_Dependent_NonZeroLength)
+{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz, kBar, 6u, true); }
+
+TEST(SubstringTuple, IsDependentOnAndLength_Dependent_NonZeroLength_ThreeParts)
+{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz + kFoo, kBar, 9u, true); }
+
+} // namespace TestSubstringTuple
diff --git a/xpcom/tests/gtest/TestSynchronization.cpp b/xpcom/tests/gtest/TestSynchronization.cpp
new file mode 100644
index 0000000000..c4a1f5c99e
--- /dev/null
+++ b/xpcom/tests/gtest/TestSynchronization.cpp
@@ -0,0 +1,324 @@
+/* -*- 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)
+MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ 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*/)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ 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*/)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ 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 MOZ_UNANNOTATED("autolock");
+ MutexAutoLock autol1(l1);
+
+ l1.AssertCurrentThreadOwns();
+
+ {
+ Mutex l2 MOZ_UNANNOTATED("autolock2");
+ MutexAutoLock autol2(l2);
+
+ l1.AssertCurrentThreadOwns();
+ l2.AssertCurrentThreadOwns();
+ }
+
+ l1.AssertCurrentThreadOwns();
+}
+
+//-----------------------------------------------------------------------------
+// AutoTryLock tests
+//
+// The thread owns assertions make mutex analysis throw spurious warnings
+TEST(Synchronization, AutoTryLock)
+MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ Mutex l1 MOZ_UNANNOTATED("autotrylock");
+ MutexAutoTryLock autol1(l1);
+
+ EXPECT_TRUE(autol1);
+ l1.AssertCurrentThreadOwns();
+
+ MutexAutoTryLock autol2(l1);
+
+ EXPECT_TRUE(autol1);
+ EXPECT_FALSE(autol2);
+ l1.AssertCurrentThreadOwns();
+
+ {
+ Mutex l2 MOZ_UNANNOTATED("autotrylock2");
+ MutexAutoTryLock autol3(l2);
+
+ EXPECT_TRUE(autol3);
+ l1.AssertCurrentThreadOwns();
+ l2.AssertCurrentThreadOwns();
+ }
+
+ l1.AssertCurrentThreadOwns();
+}
+
+//-----------------------------------------------------------------------------
+// AutoUnlock tests
+//
+TEST(Synchronization, AutoUnlock)
+{
+ Mutex l1 MOZ_UNANNOTATED("autounlock");
+ Mutex l2 MOZ_UNANNOTATED("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)
+MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ 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 0000000000..54aea92dff
--- /dev/null
+++ b/xpcom/tests/gtest/TestTArray.cpp
@@ -0,0 +1,1042 @@
+/* -*- 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"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/RefPtr.h"
+#include "nsTHashMap.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_RelocationStrategy<TestTArray::Copyable> {
+ using Type = nsTArray_RelocateUsingMoveConstructor<TestTArray::Copyable>;
+};
+
+template <>
+struct nsTArray_RelocationStrategy<TestTArray::Movable> {
+ using Type = nsTArray_RelocateUsingMoveConstructor<TestTArray::Movable>;
+};
+
+namespace TestTArray {
+
+constexpr int dummyArrayData[] = {4, 1, 2, 8};
+
+static const nsTArray<int>& DummyArray() {
+ static nsTArray<int> sArray;
+ if (sArray.IsEmpty()) {
+ sArray.AppendElements(dummyArrayData, ArrayLength(dummyArrayData));
+ }
+ return sArray;
+}
+
+// This returns an invalid nsTArray with a huge length in order to test that
+// fallible operations actually fail.
+#ifdef DEBUG
+static const nsTArray<int>& FakeHugeArray() {
+ static nsTArray<int> sArray;
+ if (sArray.IsEmpty()) {
+ sArray.AppendElement();
+ ((nsTArrayHeader*)sArray.DebugGetHeader())->mLength = UINT32_MAX;
+ }
+ return sArray;
+}
+#endif
+
+TEST(TArray, int_AppendElements_PlainArray)
+{
+ nsTArray<int> array;
+
+ int* ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData));
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+
+ ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData));
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+}
+
+TEST(TArray, int_AppendElements_PlainArray_Fallible)
+{
+ nsTArray<int> array;
+
+ int* ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData),
+ fallible);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+
+ ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData),
+ fallible);
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+}
+
+TEST(TArray, int_AppendElements_TArray_Copy)
+{
+ nsTArray<int> array;
+
+ const nsTArray<int> temp(DummyArray().Clone());
+ int* ptr = array.AppendElements(temp);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_FALSE(temp.IsEmpty());
+
+ ptr = array.AppendElements(temp);
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_FALSE(temp.IsEmpty());
+}
+
+TEST(TArray, int_AppendElements_TArray_Copy_Fallible)
+{
+ nsTArray<int> array;
+
+ const nsTArray<int> temp(DummyArray().Clone());
+ int* ptr = array.AppendElements(temp, fallible);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_FALSE(temp.IsEmpty());
+
+ ptr = array.AppendElements(temp, fallible);
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_FALSE(temp.IsEmpty());
+}
+
+TEST(TArray, int_AppendElements_TArray_Rvalue)
+{
+ nsTArray<int> array;
+
+ nsTArray<int> temp(DummyArray().Clone());
+ int* ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyArray().Clone();
+ ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_TRUE(temp.IsEmpty());
+}
+
+TEST(TArray, int_AppendElements_TArray_Rvalue_Fallible)
+{
+ nsTArray<int> array;
+
+ nsTArray<int> temp(DummyArray().Clone());
+ int* ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyArray().Clone();
+ ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_TRUE(temp.IsEmpty());
+}
+
+TEST(TArray, int_AppendElements_FallibleArray_Rvalue)
+{
+ nsTArray<int> array;
+
+ FallibleTArray<int> temp;
+ ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible));
+ int* ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible));
+ ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_TRUE(temp.IsEmpty());
+}
+
+TEST(TArray, int_AppendElements_FallibleArray_Rvalue_Fallible)
+{
+ nsTArray<int> array;
+
+ FallibleTArray<int> temp;
+ ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible));
+ int* ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_EQ(DummyArray(), array);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible));
+ ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[DummyArray().Length()], ptr);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+ ASSERT_TRUE(temp.IsEmpty());
+}
+
+TEST(TArray, AppendElementsSpan)
+{
+ nsTArray<int> array;
+
+ nsTArray<int> temp(DummyArray().Clone());
+ Span<int> span = temp;
+ array.AppendElements(span);
+ ASSERT_EQ(DummyArray(), array);
+
+ Span<const int> constSpan = temp;
+ array.AppendElements(constSpan);
+ nsTArray<int> expected;
+ expected.AppendElements(DummyArray());
+ expected.AppendElements(DummyArray());
+ ASSERT_EQ(expected, array);
+}
+
+TEST(TArray, int_AppendElement_NoElementArg)
+{
+ nsTArray<int> array;
+ array.AppendElement();
+
+ ASSERT_EQ(1u, array.Length());
+}
+
+TEST(TArray, int_AppendElement_NoElementArg_Fallible)
+{
+ nsTArray<int> array;
+ ASSERT_NE(nullptr, array.AppendElement(fallible));
+
+ ASSERT_EQ(1u, array.Length());
+}
+
+TEST(TArray, int_AppendElement_NoElementArg_Address)
+{
+ nsTArray<int> array;
+ *array.AppendElement() = 42;
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(42, array[0]);
+}
+
+TEST(TArray, int_AppendElement_NoElementArg_Fallible_Address)
+{
+ nsTArray<int> array;
+ *array.AppendElement(fallible) = 42;
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(42, array[0]);
+}
+
+TEST(TArray, int_AppendElement_ElementArg)
+{
+ nsTArray<int> array;
+ array.AppendElement(42);
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(42, array[0]);
+}
+
+TEST(TArray, int_AppendElement_ElementArg_Fallible)
+{
+ nsTArray<int> array;
+ ASSERT_NE(nullptr, array.AppendElement(42, fallible));
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(42, array[0]);
+}
+
+constexpr size_t dummyMovableArrayLength = 4;
+uint32_t dummyMovableArrayDestructorCounter;
+
+static nsTArray<Movable> DummyMovableArray() {
+ nsTArray<Movable> res;
+ res.SetLength(dummyMovableArrayLength);
+ for (size_t i = 0; i < dummyMovableArrayLength; ++i) {
+ res[i].mDestructionCounter = &dummyMovableArrayDestructorCounter;
+ }
+ return res;
+}
+
+TEST(TArray, Movable_AppendElements_TArray_Rvalue)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ {
+ nsTArray<Movable> array;
+
+ nsTArray<Movable> temp(DummyMovableArray());
+ Movable* ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyMovableArray();
+ ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[dummyMovableArrayLength], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+ }
+ ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElements_TArray_Rvalue_Fallible)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ {
+ nsTArray<Movable> array;
+
+ nsTArray<Movable> temp(DummyMovableArray());
+ Movable* ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyMovableArray();
+ ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[dummyMovableArrayLength], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+ }
+ ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElements_FallibleArray_Rvalue)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ {
+ nsTArray<Movable> array;
+
+ FallibleTArray<Movable> temp(DummyMovableArray());
+ Movable* ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyMovableArray();
+ ptr = array.AppendElements(std::move(temp));
+ ASSERT_EQ(&array[dummyMovableArrayLength], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+ }
+ ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElements_FallibleArray_Rvalue_Fallible)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ {
+ nsTArray<Movable> array;
+
+ FallibleTArray<Movable> temp(DummyMovableArray());
+ Movable* ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[0], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+
+ temp = DummyMovableArray();
+ ptr = array.AppendElements(std::move(temp), fallible);
+ ASSERT_EQ(&array[dummyMovableArrayLength], ptr);
+ ASSERT_TRUE(temp.IsEmpty());
+ }
+ ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElement_NoElementArg)
+{
+ nsTArray<Movable> array;
+ array.AppendElement();
+
+ ASSERT_EQ(1u, array.Length());
+}
+
+TEST(TArray, Movable_AppendElement_NoElementArg_Fallible)
+{
+ nsTArray<Movable> array;
+ ASSERT_NE(nullptr, array.AppendElement(fallible));
+
+ ASSERT_EQ(1u, array.Length());
+}
+
+TEST(TArray, Movable_AppendElement_NoElementArg_Address)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ {
+ nsTArray<Movable> array;
+ array.AppendElement()->mDestructionCounter =
+ &dummyMovableArrayDestructorCounter;
+
+ ASSERT_EQ(1u, array.Length());
+ }
+ ASSERT_EQ(1u, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElement_NoElementArg_Fallible_Address)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ {
+ nsTArray<Movable> array;
+ array.AppendElement(fallible)->mDestructionCounter =
+ &dummyMovableArrayDestructorCounter;
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(&dummyMovableArrayDestructorCounter,
+ array[0].mDestructionCounter);
+ }
+ ASSERT_EQ(1u, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElement_ElementArg)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ Movable movable;
+ movable.mDestructionCounter = &dummyMovableArrayDestructorCounter;
+ {
+ nsTArray<Movable> array;
+ array.AppendElement(std::move(movable));
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(&dummyMovableArrayDestructorCounter,
+ array[0].mDestructionCounter);
+ }
+ ASSERT_EQ(1u, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, Movable_AppendElement_ElementArg_Fallible)
+{
+ dummyMovableArrayDestructorCounter = 0;
+ Movable movable;
+ movable.mDestructionCounter = &dummyMovableArrayDestructorCounter;
+ {
+ nsTArray<Movable> array;
+ ASSERT_NE(nullptr, array.AppendElement(std::move(movable), fallible));
+
+ ASSERT_EQ(1u, array.Length());
+ ASSERT_EQ(&dummyMovableArrayDestructorCounter,
+ array[0].mDestructionCounter);
+ }
+ ASSERT_EQ(1u, dummyMovableArrayDestructorCounter);
+}
+
+TEST(TArray, int_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(std::move(array));
+ ASSERT_TRUE(array.IsEmpty());
+ ASSERT_EQ(DummyArray(), array2);
+}
+
+TEST(TArray, int_Assign_FromEmpty_ToNonEmpty)
+{
+ nsTArray<int> array;
+ array.AppendElement(42);
+
+ const nsTArray<int> empty;
+ array.Assign(empty);
+
+ ASSERT_TRUE(array.IsEmpty());
+}
+
+TEST(TArray, int_Assign_FromEmpty_ToNonEmpty_Fallible)
+{
+ nsTArray<int> array;
+ array.AppendElement(42);
+
+ const nsTArray<int> empty;
+ ASSERT_TRUE(array.Assign(empty, fallible));
+
+ ASSERT_TRUE(array.IsEmpty());
+}
+
+TEST(TArray, int_AssignmentOperatorSelfAssignment)
+{
+ CopyableTArray<int> array;
+ array = DummyArray();
+
+ array = *&array;
+ ASSERT_EQ(DummyArray(), array);
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wself-move"
+#endif
+ array = std::move(array); // self-move
+ ASSERT_EQ(DummyArray(), array);
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+}
+
+TEST(TArray, Movable_CopyOverlappingForwards)
+{
+ const size_t rangeLength = 8;
+ const size_t initialLength = 2 * rangeLength;
+ uint32_t destructionCounters[initialLength];
+ nsTArray<Movable> array;
+ array.AppendElements(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, Copyable_CopyOverlappingBackwards)
+{
+ const size_t rangeLength = 8;
+ const size_t initialLength = 2 * rangeLength;
+ uint32_t destructionCounters[initialLength];
+ nsTArray<Copyable> array;
+ 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.
+ 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 {
+
+class E {
+ public:
+ E() : mA(-1), mB(-2) { constructCount++; }
+ E(int a, int b) : mA(a), mB(b) { constructCount++; }
+ E(E&& aRhs) : mA(aRhs.mA), mB(aRhs.mB) {
+ aRhs.mA = 0;
+ aRhs.mB = 0;
+ moveCount++;
+ }
+
+ E& operator=(E&& aRhs) {
+ mA = aRhs.mA;
+ aRhs.mA = 0;
+ mB = aRhs.mB;
+ aRhs.mB = 0;
+ moveCount++;
+ return *this;
+ }
+
+ int a() const { return mA; }
+ int b() const { return mB; }
+
+ E(const E&) = delete;
+ E& operator=(const E&) = delete;
+
+ static size_t constructCount;
+ static size_t moveCount;
+
+ private:
+ int mA;
+ int mB;
+};
+
+size_t E::constructCount = 0;
+size_t E::moveCount = 0;
+
+} // namespace
+
+TEST(TArray, Emplace)
+{
+ nsTArray<E> array;
+ array.SetCapacity(20);
+
+ ASSERT_EQ(array.Length(), 0u);
+
+ for (int i = 0; i < 10; i++) {
+ E s(i, i * i);
+ array.AppendElement(std::move(s));
+ }
+
+ ASSERT_EQ(array.Length(), 10u);
+ ASSERT_EQ(E::constructCount, 10u);
+ ASSERT_EQ(E::moveCount, 10u);
+
+ for (int i = 10; i < 20; i++) {
+ array.EmplaceBack(i, i * i);
+ }
+
+ ASSERT_EQ(array.Length(), 20u);
+ ASSERT_EQ(E::constructCount, 20u);
+ ASSERT_EQ(E::moveCount, 10u);
+
+ for (int i = 0; i < 20; i++) {
+ ASSERT_EQ(array[i].a(), i);
+ ASSERT_EQ(array[i].b(), i * i);
+ }
+
+ array.EmplaceBack();
+
+ ASSERT_EQ(array.Length(), 21u);
+ ASSERT_EQ(E::constructCount, 21u);
+ ASSERT_EQ(E::moveCount, 10u);
+
+ ASSERT_EQ(array[20].a(), -1);
+ ASSERT_EQ(array[20].b(), -2);
+}
+
+TEST(TArray, UnorderedRemoveElements)
+{
+ // When removing an element from the end of the array, it can be removed in
+ // place, by destroying it and decrementing the length.
+ //
+ // [ 1, 2, 3 ] => [ 1, 2 ]
+ // ^
+ {
+ nsTArray<int> array{1, 2, 3};
+ array.UnorderedRemoveElementAt(2);
+
+ nsTArray<int> goal{1, 2};
+ ASSERT_EQ(array, goal);
+ }
+
+ // When removing any other single element, it is removed by swapping it with
+ // the last element, and then decrementing the length as before.
+ //
+ // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ]
+ // ^
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5, 6};
+ array.UnorderedRemoveElementAt(1);
+
+ nsTArray<int> goal{1, 6, 3, 4, 5};
+ ASSERT_EQ(array, goal);
+ }
+
+ // This method also supports efficiently removing a range of elements. If they
+ // are at the end, then they can all be removed like in the one element case.
+ //
+ // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ]
+ // ^--------^
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5, 6};
+ array.UnorderedRemoveElementsAt(2, 4);
+
+ nsTArray<int> goal{1, 2};
+ ASSERT_EQ(array, goal);
+ }
+
+ // If more elements are removed than exist after the removed section, the
+ // remaining elements will be shifted down like in a normal removal.
+ //
+ // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ]
+ // ^--------^
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8};
+ array.UnorderedRemoveElementsAt(2, 4);
+
+ nsTArray<int> goal{1, 2, 7, 8};
+ ASSERT_EQ(array, goal);
+ }
+
+ // And if fewer elements are removed than exist after the removed section,
+ // elements will be moved from the end of the array to fill the vacated space.
+ //
+ // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ]
+ // ^--^
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5, 6, 7, 8};
+ array.UnorderedRemoveElementsAt(1, 2);
+
+ nsTArray<int> goal{1, 7, 8, 4, 5, 6};
+ ASSERT_EQ(array, goal);
+ }
+
+ // We should do the right thing if we drain the entire array.
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5};
+ array.UnorderedRemoveElementsAt(0, 5);
+
+ nsTArray<int> goal{};
+ ASSERT_EQ(array, goal);
+ }
+
+ {
+ nsTArray<int> array{1};
+ array.UnorderedRemoveElementAt(0);
+
+ nsTArray<int> goal{};
+ ASSERT_EQ(array, goal);
+ }
+
+ // We should do the right thing if we remove the same number of elements that
+ // we have remaining.
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5, 6};
+ array.UnorderedRemoveElementsAt(2, 2);
+
+ nsTArray<int> goal{1, 2, 5, 6};
+ ASSERT_EQ(array, goal);
+ }
+
+ {
+ nsTArray<int> array{1, 2, 3};
+ array.UnorderedRemoveElementAt(1);
+
+ nsTArray<int> goal{1, 3};
+ ASSERT_EQ(array, goal);
+ }
+
+ // We should be able to remove elements from the front without issue.
+ {
+ nsTArray<int> array{1, 2, 3, 4, 5, 6};
+ array.UnorderedRemoveElementsAt(0, 2);
+
+ nsTArray<int> goal{5, 6, 3, 4};
+ ASSERT_EQ(array, goal);
+ }
+
+ {
+ nsTArray<int> array{1, 2, 3, 4};
+ array.UnorderedRemoveElementAt(0);
+
+ nsTArray<int> goal{4, 2, 3};
+ ASSERT_EQ(array, goal);
+ }
+}
+
+TEST(TArray, RemoveFromEnd)
+{
+ {
+ nsTArray<int> array{1, 2, 3, 4};
+ ASSERT_EQ(array.PopLastElement(), 4);
+ array.RemoveLastElement();
+ ASSERT_EQ(array.PopLastElement(), 2);
+ array.RemoveLastElement();
+ ASSERT_TRUE(array.IsEmpty());
+ }
+}
+
+TEST(TArray, ConvertIteratorToConstIterator)
+{
+ nsTArray<int> array{1, 2, 3, 4};
+
+ nsTArray<int>::const_iterator it = array.begin();
+ ASSERT_EQ(array.cbegin(), it);
+}
+
+TEST(TArray, RemoveElementAt_ByIterator)
+{
+ nsTArray<int> array{1, 2, 3, 4};
+ const auto it = std::find(array.begin(), array.end(), 3);
+ const auto itAfter = array.RemoveElementAt(it);
+
+ // Based on the implementation of the iterator, we could compare it and
+ // itAfter, but we should not rely on such implementation details.
+
+ ASSERT_EQ(2, std::distance(array.cbegin(), itAfter));
+ const nsTArray<int> expected{1, 2, 4};
+ ASSERT_EQ(expected, array);
+}
+
+TEST(TArray, RemoveElementsRange_ByIterator)
+{
+ nsTArray<int> array{1, 2, 3, 4};
+ const auto it = std::find(array.begin(), array.end(), 3);
+ const auto itAfter = array.RemoveElementsRange(it, array.end());
+
+ // Based on the implementation of the iterator, we could compare it and
+ // itAfter, but we should not rely on such implementation details.
+
+ ASSERT_EQ(2, std::distance(array.cbegin(), itAfter));
+ const nsTArray<int> expected{1, 2};
+ ASSERT_EQ(expected, array);
+}
+
+TEST(TArray, RemoveLastElements_None)
+{
+ const nsTArray<int> original{1, 2, 3, 4};
+ nsTArray<int> array = original.Clone();
+ array.RemoveLastElements(0);
+
+ ASSERT_EQ(original, array);
+}
+
+TEST(TArray, RemoveLastElements_Empty_None)
+{
+ nsTArray<int> array;
+ array.RemoveLastElements(0);
+
+ ASSERT_EQ(0u, array.Length());
+}
+
+TEST(TArray, RemoveLastElements_All)
+{
+ nsTArray<int> array{1, 2, 3, 4};
+ array.RemoveLastElements(4);
+
+ ASSERT_EQ(0u, array.Length());
+}
+
+TEST(TArray, RemoveLastElements_One)
+{
+ nsTArray<int> array{1, 2, 3, 4};
+ array.RemoveLastElements(1);
+
+ ASSERT_EQ((nsTArray<int>{1, 2, 3}), array);
+}
+
+static_assert(std::is_copy_assignable<decltype(MakeBackInserter(
+ std::declval<nsTArray<int>&>()))>::value,
+ "output iteraror must be copy-assignable");
+static_assert(std::is_copy_constructible<decltype(MakeBackInserter(
+ std::declval<nsTArray<int>&>()))>::value,
+ "output iterator must be copy-constructible");
+
+TEST(TArray, MakeBackInserter)
+{
+ const std::vector<int> src{1, 2, 3, 4};
+ nsTArray<int> dst;
+
+ std::copy(src.begin(), src.end(), MakeBackInserter(dst));
+
+ const nsTArray<int> expected{1, 2, 3, 4};
+ ASSERT_EQ(expected, dst);
+}
+
+TEST(TArray, MakeBackInserter_Move)
+{
+ uint32_t destructionCounter = 0;
+
+ {
+ std::vector<Movable> src(1);
+ src[0].mDestructionCounter = &destructionCounter;
+
+ nsTArray<Movable> dst;
+
+ std::copy(std::make_move_iterator(src.begin()),
+ std::make_move_iterator(src.end()), MakeBackInserter(dst));
+
+ ASSERT_EQ(1u, dst.Length());
+ ASSERT_EQ(0u, destructionCounter);
+ }
+
+ ASSERT_EQ(1u, destructionCounter);
+}
+
+TEST(TArray, ConvertToSpan)
+{
+ nsTArray<int> arr = {1, 2, 3, 4, 5};
+
+ // from const
+ {
+ const auto& constArrRef = arr;
+
+ auto span = Span{constArrRef};
+ static_assert(std::is_same_v<decltype(span), Span<const int>>);
+ }
+
+ // from non-const
+ {
+ auto span = Span{arr};
+ static_assert(std::is_same_v<decltype(span), Span<int>>);
+ }
+}
+
+// This should compile:
+struct RefCounted;
+
+class Foo {
+ ~Foo(); // Intentionally out of line
+
+ nsTArray<RefPtr<RefCounted>> mArray;
+
+ const RefCounted* GetFirst() const { return mArray.SafeElementAt(0); }
+};
+
+TEST(TArray, StableSort)
+{
+ const nsTArray<std::pair<int, int>> expected = {
+ std::pair(1, 9), std::pair(1, 8), std::pair(1, 7), std::pair(2, 0),
+ std::pair(3, 0)};
+ nsTArray<std::pair<int, int>> array = {std::pair(1, 9), std::pair(2, 0),
+ std::pair(1, 8), std::pair(3, 0),
+ std::pair(1, 7)};
+
+ array.StableSort([](std::pair<int, int> left, std::pair<int, int> right) {
+ return left.first - right.first;
+ });
+
+ EXPECT_EQ(expected, array);
+}
+
+TEST(TArray, ToArray)
+{
+ const auto src = std::array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ nsTArray<int> keys = ToArray(src);
+ keys.Sort();
+
+ EXPECT_EQ((nsTArray<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys);
+}
+
+// Test this to make sure this properly uses ADL.
+TEST(TArray, ToArray_HashMap)
+{
+ nsTHashMap<uint32_t, uint64_t> src;
+
+ for (uint32_t i = 0; i < 10; ++i) {
+ src.InsertOrUpdate(i, i);
+ }
+
+ nsTArray<uint32_t> keys = ToArray(src.Keys());
+ keys.Sort();
+
+ EXPECT_EQ((nsTArray<uint32_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys);
+}
+
+TEST(TArray, ToTArray)
+{
+ const auto src = std::array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ auto keys = ToTArray<AutoTArray<uint64_t, 10>>(src);
+ keys.Sort();
+
+ static_assert(std::is_same_v<decltype(keys), AutoTArray<uint64_t, 10>>);
+
+ EXPECT_EQ((nsTArray<uint64_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys);
+}
+
+TEST(TArray, RemoveElementsBy)
+{
+ // Removing elements returns the correct number of removed elements.
+ {
+ nsTArray<int> array{8, 1, 1, 3, 3, 5, 2, 3};
+ auto removed = array.RemoveElementsBy([](int i) { return i == 3; });
+ EXPECT_EQ(removed, 3u);
+
+ nsTArray<int> goal{8, 1, 1, 5, 2};
+ EXPECT_EQ(array, goal);
+ }
+
+ // The check is called in order.
+ {
+ int index = 0;
+ nsTArray<int> array{0, 1, 2, 3, 4, 5};
+ auto removed = array.RemoveElementsBy([&](int i) {
+ EXPECT_EQ(index, i);
+ index++;
+ return i == 3;
+ });
+ EXPECT_EQ(removed, 1u);
+
+ nsTArray<int> goal{0, 1, 2, 4, 5};
+ EXPECT_EQ(array, goal);
+ }
+
+ // Removing nothing works
+ {
+ nsTArray<int> array{0, 1, 2, 3, 4};
+ auto removed = array.RemoveElementsBy([](int) { return false; });
+ EXPECT_EQ(removed, 0u);
+
+ nsTArray<int> goal{0, 1, 2, 3, 4};
+ EXPECT_EQ(array, goal);
+ }
+
+ // Removing everything works
+ {
+ nsTArray<int> array{0, 1, 2, 3, 4};
+ auto removed = array.RemoveElementsBy([](int) { return true; });
+ EXPECT_EQ(removed, 5u);
+
+ nsTArray<int> goal{};
+ EXPECT_EQ(array, goal);
+ }
+}
+
+} // namespace TestTArray
diff --git a/xpcom/tests/gtest/TestTArray2.cpp b/xpcom/tests/gtest/TestTArray2.cpp
new file mode 100644
index 0000000000..2ccf7df4fa
--- /dev/null
+++ b/xpcom/tests/gtest/TestTArray2.cpp
@@ -0,0 +1,1524 @@
+/* -*- 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 "mozilla/TimeStamp.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <iostream>
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOM.h"
+#include "nsIFile.h"
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.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) {
+ CopyableTArray<ElementType> ary;
+ const nsTArray<ElementType>& cary = 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;
+
+ if (ary.ApplyIf(
+ extra, []() { return true; }, []() { return false; }))
+ return false;
+ if (ary.ApplyIf(
+ extra, [](size_t) { return true; }, []() { return false; }))
+ return false;
+ // On a non-const array, ApplyIf's first lambda may use either const or non-
+ // const element types.
+ if (ary.ApplyIf(
+ extra, [](ElementType&) { return true; }, []() { return false; }))
+ return false;
+ if (ary.ApplyIf(
+ extra, [](const ElementType&) { return true; },
+ []() { return false; }))
+ return false;
+ if (ary.ApplyIf(
+ extra, [](size_t, ElementType&) { return true; },
+ []() { return false; }))
+ return false;
+ if (ary.ApplyIf(
+ extra, [](size_t, const ElementType&) { return true; },
+ []() { return false; }))
+ return false;
+
+ if (cary.ApplyIf(
+ extra, []() { return true; }, []() { return false; }))
+ if (cary.ApplyIf(
+ extra, [](size_t) { return true; }, []() { return false; }))
+ // On a const array, ApplyIf's first lambda must only use const element
+ // types.
+ if (cary.ApplyIf(
+ extra, [](const ElementType&) { return true; },
+ []() { return false; }))
+ if (cary.ApplyIf(
+ extra, [](size_t, const ElementType&) { return true; },
+ []() { return false; }))
+ return false;
+
+ size_t index = ary.Length() / 2;
+ ary.InsertElementAt(index, extra);
+ 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;
+ if (!ary.ApplyIf(
+ extra,
+ [&](size_t i, const ElementType& e) {
+ return i == index && e == extra;
+ },
+ []() { return false; }))
+ return false;
+ if (!cary.ApplyIf(
+ extra,
+ [&](size_t i, const ElementType& e) {
+ return i == index && e == extra;
+ },
+ []() { return false; }))
+ return false;
+
+ nsTArray<ElementType> copy(ary.Clone());
+ if (!(ary == copy)) return false;
+ for (i = 0; i < copy.Length(); ++i) {
+ if (ary[i] != copy[i]) return false;
+ }
+ ary.AppendElements(copy);
+ 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;
+ if (ary.ApplyIf(
+ extra, []() { return true; }, []() { return false; }))
+ return false;
+ if (cary.ApplyIf(
+ extra, []() { return true; }, []() { return false; }))
+ return false;
+
+ ary.Clear();
+ if (!ary.IsEmpty()) 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;
+ }
+
+ ary.InsertElementsAt(0, copy);
+ 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) = default;
+ ~Object() = default;
+
+ Object& operator=(const Object& other) = default;
+
+ 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 Compare(mStr, other.mStr) < 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'};
+ 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; }
+};
+
+class MoveOnly_RelocateUsingMemutils {
+ public:
+ MoveOnly_RelocateUsingMemutils() = default;
+
+ MoveOnly_RelocateUsingMemutils(const MoveOnly_RelocateUsingMemutils&) =
+ delete;
+ MoveOnly_RelocateUsingMemutils(MoveOnly_RelocateUsingMemutils&&) = default;
+
+ MoveOnly_RelocateUsingMemutils& operator=(
+ const MoveOnly_RelocateUsingMemutils&) = delete;
+ MoveOnly_RelocateUsingMemutils& operator=(MoveOnly_RelocateUsingMemutils&&) =
+ default;
+};
+
+static_assert(
+ std::is_move_constructible_v<nsTArray<MoveOnly_RelocateUsingMemutils>>);
+static_assert(
+ std::is_move_assignable_v<nsTArray<MoveOnly_RelocateUsingMemutils>>);
+static_assert(
+ !std::is_copy_constructible_v<nsTArray<MoveOnly_RelocateUsingMemutils>>);
+static_assert(
+ !std::is_copy_assignable_v<nsTArray<MoveOnly_RelocateUsingMemutils>>);
+
+class MoveOnly_RelocateUsingMoveConstructor {
+ public:
+ MoveOnly_RelocateUsingMoveConstructor() = default;
+
+ MoveOnly_RelocateUsingMoveConstructor(
+ const MoveOnly_RelocateUsingMoveConstructor&) = delete;
+ MoveOnly_RelocateUsingMoveConstructor(
+ MoveOnly_RelocateUsingMoveConstructor&&) = default;
+
+ MoveOnly_RelocateUsingMoveConstructor& operator=(
+ const MoveOnly_RelocateUsingMoveConstructor&) = delete;
+ MoveOnly_RelocateUsingMoveConstructor& operator=(
+ MoveOnly_RelocateUsingMoveConstructor&&) = default;
+};
+} // namespace TestTArray
+
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(
+ TestTArray::MoveOnly_RelocateUsingMoveConstructor)
+
+namespace TestTArray {
+static_assert(std::is_move_constructible_v<
+ nsTArray<MoveOnly_RelocateUsingMoveConstructor>>);
+static_assert(
+ std::is_move_assignable_v<nsTArray<MoveOnly_RelocateUsingMoveConstructor>>);
+static_assert(!std::is_copy_constructible_v<
+ nsTArray<MoveOnly_RelocateUsingMoveConstructor>>);
+static_assert(!std::is_copy_assignable_v<
+ nsTArray<MoveOnly_RelocateUsingMoveConstructor>>);
+} // namespace TestTArray
+
+namespace TestTArray {
+
+/* 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) {
+ countableArray.AppendElement(Countable());
+ }
+
+ ASSERT_EQ(Countable::Count(), 8);
+
+ const nsTArray<Countable>& constRefCountableArray = countableArray;
+
+ ASSERT_EQ(Countable::Count(), 8);
+
+ nsTArray<Countable> copyCountableArray(constRefCountableArray.Clone());
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ nsTArray<Countable>&& moveRefCountableArray = std::move(countableArray);
+ moveRefCountableArray.Length(); // Make compilers happy.
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ nsTArray<Countable> movedCountableArray(std::move(countableArray));
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ // Test ctor
+ FallibleTArray<Countable> differentAllocatorCountableArray(
+ std::move(copyCountableArray));
+ // operator=
+ copyCountableArray = std::move(differentAllocatorCountableArray);
+ differentAllocatorCountableArray = std::move(copyCountableArray);
+ // And the other ctor
+ nsTArray<Countable> copyCountableArray2(
+ std::move(differentAllocatorCountableArray));
+ // with auto
+ AutoTArray<Countable, 3> autoCountableArray(std::move(copyCountableArray2));
+ // operator=
+ copyCountableArray2 = std::move(autoCountableArray);
+ // Mix with FallibleTArray
+ FallibleTArray<Countable> differentAllocatorCountableArray2(
+ std::move(copyCountableArray2));
+ AutoTArray<Countable, 4> autoCountableArray2(
+ std::move(differentAllocatorCountableArray2));
+ differentAllocatorCountableArray2 = std::move(autoCountableArray2);
+
+ ASSERT_EQ(Countable::Count(), 12);
+
+ nsTArray<Moveable> moveableArray;
+ for (i = 0; i < 4; ++i) {
+ moveableArray.AppendElement(Moveable());
+ }
+
+ ASSERT_EQ(Moveable::Count(), 4);
+
+ const nsTArray<Moveable>& constRefMoveableArray = moveableArray;
+
+ ASSERT_EQ(Moveable::Count(), 4);
+
+ nsTArray<Moveable> copyMoveableArray(constRefMoveableArray.Clone());
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ nsTArray<Moveable>&& moveRefMoveableArray = std::move(moveableArray);
+ moveRefMoveableArray.Length(); // Make compilers happy.
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ nsTArray<Moveable> movedMoveableArray(std::move(moveableArray));
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ // Test ctor
+ FallibleTArray<Moveable> differentAllocatorMoveableArray(
+ std::move(copyMoveableArray));
+ // operator=
+ copyMoveableArray = std::move(differentAllocatorMoveableArray);
+ differentAllocatorMoveableArray = std::move(copyMoveableArray);
+ // And the other ctor
+ nsTArray<Moveable> copyMoveableArray2(
+ std::move(differentAllocatorMoveableArray));
+ // with auto
+ AutoTArray<Moveable, 3> autoMoveableArray(std::move(copyMoveableArray2));
+ // operator=
+ copyMoveableArray2 = std::move(autoMoveableArray);
+ // Mix with FallibleTArray
+ FallibleTArray<Moveable> differentAllocatorMoveableArray2(
+ std::move(copyMoveableArray2));
+ AutoTArray<Moveable, 4> autoMoveableArray2(
+ std::move(differentAllocatorMoveableArray2));
+ differentAllocatorMoveableArray2 = std::move(autoMoveableArray2);
+
+ ASSERT_EQ(Moveable::Count(), 8);
+
+ AutoTArray<Moveable, 8> moveableAutoArray;
+ for (uint32_t i = 0; i < 4; ++i) {
+ moveableAutoArray.AppendElement(Moveable());
+ }
+
+ ASSERT_EQ(Moveable::Count(), 12);
+
+ const AutoTArray<Moveable, 8>& constRefMoveableAutoArray = moveableAutoArray;
+
+ ASSERT_EQ(Moveable::Count(), 12);
+
+ CopyableAutoTArray<Moveable, 8> copyMoveableAutoArray(
+ constRefMoveableAutoArray);
+
+ ASSERT_EQ(Moveable::Count(), 16);
+
+ AutoTArray<Moveable, 8> movedMoveableAutoArray(std::move(moveableAutoArray));
+
+ ASSERT_EQ(Moveable::Count(), 16);
+}
+
+template <typename TypeParam>
+class TArray_MoveOnlyTest : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(TArray_MoveOnlyTest);
+
+static constexpr size_t kMoveOnlyTestArrayLength = 4;
+
+template <typename ArrayType>
+static auto MakeMoveOnlyArray() {
+ ArrayType moveOnlyArray;
+ for (size_t i = 0; i < kMoveOnlyTestArrayLength; ++i) {
+ EXPECT_TRUE(moveOnlyArray.AppendElement(typename ArrayType::value_type(),
+ fallible));
+ }
+ return moveOnlyArray;
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveConstruct) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ nsTArray<TypeParam> movedMoveOnlyArray(std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveAssign) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ nsTArray<TypeParam> movedMoveOnlyArray;
+ movedMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveReAssign) {
+ nsTArray<TypeParam> movedMoveOnlyArray;
+ movedMoveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ // Re-assign, to check that move-assign does not only work on an empty array.
+ movedMoveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+
+ ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_to_FallibleTArray_MoveConstruct) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ FallibleTArray<TypeParam> differentAllocatorMoveOnlyArray(
+ std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_to_FallibleTArray_MoveAssign) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ FallibleTArray<TypeParam> differentAllocatorMoveOnlyArray;
+ differentAllocatorMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, FallibleTArray_to_nsTArray_MoveConstruct) {
+ auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>();
+ nsTArray<TypeParam> differentAllocatorMoveOnlyArray(std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, FallibleTArray_to_nsTArray_MoveAssign) {
+ auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>();
+ nsTArray<TypeParam> differentAllocatorMoveOnlyArray;
+ differentAllocatorMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, AutoTArray_AutoStorage_MoveConstruct) {
+ auto moveOnlyArray =
+ MakeMoveOnlyArray<AutoTArray<TypeParam, kMoveOnlyTestArrayLength>>();
+ AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray(
+ std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest, AutoTArray_AutoStorage_MoveAssign) {
+ auto moveOnlyArray =
+ MakeMoveOnlyArray<AutoTArray<TypeParam, kMoveOnlyTestArrayLength>>();
+ AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray;
+ autoMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest,
+ nsTArray_to_AutoTArray_AutoStorage_MoveConstruct) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray(
+ std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest,
+ nsTArray_to_AutoTArray_AutoStorage_MoveAssign) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ AutoTArray<TypeParam, kMoveOnlyTestArrayLength> autoMoveOnlyArray;
+ autoMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest,
+ nsTArray_to_AutoTArray_HeapStorage_MoveConstruct) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ AutoTArray<TypeParam, kMoveOnlyTestArrayLength - 1> autoMoveOnlyArray(
+ std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest,
+ nsTArray_to_AutoTArray_HeapStorage_MoveAssign) {
+ auto moveOnlyArray = MakeMoveOnlyArray<nsTArray<TypeParam>>();
+ AutoTArray<TypeParam, kMoveOnlyTestArrayLength - 1> autoMoveOnlyArray;
+ autoMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest,
+ FallibleTArray_to_AutoTArray_HeapStorage_MoveConstruct) {
+ auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>();
+ AutoTArray<TypeParam, 4> autoMoveOnlyArray(std::move(moveOnlyArray));
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+TYPED_TEST_P(TArray_MoveOnlyTest,
+ FallibleTArray_to_AutoTArray_HeapStorage_MoveAssign) {
+ auto moveOnlyArray = MakeMoveOnlyArray<FallibleTArray<TypeParam>>();
+ AutoTArray<TypeParam, 4> autoMoveOnlyArray;
+ autoMoveOnlyArray = std::move(moveOnlyArray);
+
+ ASSERT_EQ(0u, moveOnlyArray.Length());
+ ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length());
+}
+
+REGISTER_TYPED_TEST_SUITE_P(
+ TArray_MoveOnlyTest, nsTArray_MoveConstruct, nsTArray_MoveAssign,
+ nsTArray_MoveReAssign, nsTArray_to_FallibleTArray_MoveConstruct,
+ nsTArray_to_FallibleTArray_MoveAssign,
+ FallibleTArray_to_nsTArray_MoveConstruct,
+ FallibleTArray_to_nsTArray_MoveAssign, AutoTArray_AutoStorage_MoveConstruct,
+ AutoTArray_AutoStorage_MoveAssign,
+ nsTArray_to_AutoTArray_AutoStorage_MoveConstruct,
+ nsTArray_to_AutoTArray_AutoStorage_MoveAssign,
+ nsTArray_to_AutoTArray_HeapStorage_MoveConstruct,
+ nsTArray_to_AutoTArray_HeapStorage_MoveAssign,
+ FallibleTArray_to_AutoTArray_HeapStorage_MoveConstruct,
+ FallibleTArray_to_AutoTArray_HeapStorage_MoveAssign);
+
+using BothMoveOnlyTypes =
+ ::testing::Types<MoveOnly_RelocateUsingMemutils,
+ MoveOnly_RelocateUsingMoveConstructor>;
+INSTANTIATE_TYPED_TEST_SUITE_P(InstantiationOf, TArray_MoveOnlyTest,
+ BothMoveOnlyTypes);
+
+//----
+
+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]);
+ 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();
+ strArray.AppendElement(kextra);
+ strArray.RemoveElement(kextra);
+ ASSERT_EQ(oldLen, strArray.Length());
+
+ ASSERT_EQ(strArray.IndexOf("e"), size_t(1));
+ ASSERT_TRUE(strArray.ApplyIf(
+ "e", [](size_t i, nsCString& s) { return i == 1 && s == "e"; },
+ []() { return false; }));
+
+ 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(""_ns), 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_NS_SUCCEEDED(f->AppendNative(nsDependentCString(kNames[i])));
+ fileArray.AppendElement(f);
+ }
+
+ ASSERT_EQ(fileArray.IndexOf(kNames[1], 0, nsFileNameComparator()), size_t(1));
+ ASSERT_TRUE(fileArray.ApplyIf(
+ kNames[1], 0, nsFileNameComparator(), [](size_t i) { return i == 1; },
+ []() { return false; }));
+
+ // 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) { val = std::rand(); }
+ void AddRef() {
+ MOZ_DIAGNOSTIC_ASSERT(rcchangeallowed);
+ ++rc;
+ }
+ void Release() {
+ MOZ_DIAGNOSTIC_ASSERT(rcchangeallowed);
+ if (--rc == 0) delete this;
+ }
+ ~RefcountedObject() = default;
+
+ int32_t GetVal() const { return val; }
+
+ static void AllowRCChange() { rcchangeallowed = true; }
+ static void ForbidRCChange() { rcchangeallowed = false; }
+
+ bool operator<(const RefcountedObject& b) const {
+ return this->GetVal() < b.GetVal();
+ };
+
+ bool operator==(const RefcountedObject& b) const {
+ return this->GetVal() == b.GetVal();
+ };
+
+ private:
+ int rc;
+ int32_t val;
+ static bool rcchangeallowed;
+};
+bool RefcountedObject::rcchangeallowed = true;
+
+class ObjectComparatorRaw {
+ public:
+ bool Equals(RefcountedObject* const& a, RefcountedObject* const& b) const {
+ return a->GetVal() == b->GetVal();
+ }
+
+ bool LessThan(RefcountedObject* const& a, RefcountedObject* const& b) const {
+ return a->GetVal() < b->GetVal();
+ }
+};
+
+class ObjectComparatorRefPtr {
+ public:
+ bool Equals(RefPtr<RefcountedObject> const& a,
+ RefPtr<RefcountedObject> const& b) const {
+ return a->GetVal() == b->GetVal();
+ }
+
+ bool LessThan(RefPtr<RefcountedObject> const& a,
+ RefPtr<RefcountedObject> const& b) const {
+ return a->GetVal() < b->GetVal();
+ }
+};
+
+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));
+ ASSERT_TRUE(objArray.ApplyIf(
+ b,
+ [&](size_t i, RefPtr<RefcountedObject>& r) { return i == 1 && r == b; },
+ []() { return false; }));
+
+ a->Release();
+ b->Release();
+ c->Release();
+}
+
+TEST(TArray, test_sort_refptr)
+{
+ int numobjects = 1111111;
+ std::vector<RefPtr<RefcountedObject>> myobjects;
+ for (int i = 0; i < numobjects; i++) {
+ auto* obj = new RefcountedObject();
+ myobjects.push_back(obj);
+ }
+
+ {
+ nsTArray<RefPtr<RefcountedObject>> objArray(numobjects);
+ std::vector<RefPtr<RefcountedObject>> plainRefPtrArray(numobjects, nullptr);
+
+ for (int i = 0; i < numobjects; i++) {
+ objArray.AppendElement(myobjects[i]);
+ plainRefPtrArray[i] = myobjects[i];
+ }
+
+ ASSERT_EQ(objArray.IndexOf(myobjects[1]), size_t(1));
+ ASSERT_TRUE(objArray.ApplyIf(
+ myobjects[1],
+ [&](size_t i, RefPtr<RefcountedObject>& r) {
+ return i == 1 && r == myobjects[1];
+ },
+ []() { return false; }));
+
+ // Do not expect that sorting affects the reference counters of elements.
+ RefcountedObject::ForbidRCChange();
+
+ // Sort objArray with explicit, pointee value based comparator
+ objArray.Sort(ObjectComparatorRefPtr());
+ for (int i = 0; i < numobjects - 1; i++) {
+ ASSERT_TRUE(objArray[i]->GetVal() <= objArray[i + 1]->GetVal());
+ }
+
+ // std::sort plainRefPtrArray
+ auto comp = ObjectComparatorRefPtr();
+ std::sort(plainRefPtrArray.begin(), plainRefPtrArray.end(),
+ [&comp](auto const& left, auto const& right) {
+ return comp.LessThan(left, right);
+ });
+
+ // We expect the order to be the same.
+ for (int i = 0; i < numobjects; i++) {
+ ASSERT_TRUE(objArray[i]->GetVal() == plainRefPtrArray[i]->GetVal());
+ }
+
+ RefcountedObject::AllowRCChange();
+ // Destroy the arrays
+ }
+
+ for (int i = 0; i < numobjects; i++) {
+ myobjects.pop_back();
+ }
+}
+
+//----
+
+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);
+ ASSERT_FALSE(array.ApplyIf(
+ 5, 1, []() { return true; }, []() { return false; }));
+}
+
+//----
+
+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));
+ }
+
+ // Test fallible SwapElements of nsTArray.
+ {
+ nsTArray<int> a;
+ nsTArray<int> b;
+
+ a.AppendElements(data1, ArrayLength(data1));
+
+ ASSERT_TRUE(a.SwapElements(b, fallible));
+
+ CHECK_ARRAY(b, data1);
+ CHECK_EQ_INT(a.Length(), size_t(0));
+ }
+
+ // Test fallible SwapElements of FallibleTArray.
+ {
+ FallibleTArray<int> a;
+ FallibleTArray<int> b;
+
+ ASSERT_TRUE(a.AppendElements(data1, ArrayLength(data1), fallible));
+
+ ASSERT_TRUE(a.SwapElements(b, fallible));
+
+ CHECK_ARRAY(b, data1);
+ CHECK_EQ_INT(a.Length(), size_t(0));
+ }
+
+ // Test fallible SwapElements of FallibleTArray with large AutoTArray.
+ {
+ FallibleTArray<int> a;
+ AutoTArray<int, 8192> b;
+
+ ASSERT_TRUE(a.AppendElements(data1, ArrayLength(data1), fallible));
+
+ ASSERT_TRUE(a.SwapElements(b, fallible));
+
+ CHECK_IS_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;
+# ifdef XP_WIN
+ // 32-bit Windows sometimes OOMs on the 6th, 7th, or 8th. To keep the
+ // test green, choose the lower of those: the important thing here is
+ // that some allocations fail and some succeed. We're not too
+ // concerned about how many iterations it takes.
+ const size_t kOOMIterations = 6;
+# else
+ const size_t kOOMIterations = 8;
+# endif
+ ASSERT_GE(i, kOOMIterations)
+ << "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;
+
+ 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>& 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(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;
+
+ nsTArray<int> t;
+ AutoTArray<int, N> tauto;
+
+#define LPAREN (
+#define RPAREN )
+#define FOR_EACH(pre, post) \
+ do { \
+ pre f 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<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(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(t[n], n);
+ ASSERT_EQ(tauto[n], n);
+ }
+
+ void* current_Hdrs[] = {
+ static_cast<BufAccessor<FallibleTArray<int>>&>(f).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
+}
+
+template <typename Comparator>
+bool TestCompareMethods(const Comparator& aComp) {
+ nsTArray<int> ary({57, 4, 16, 17, 3, 5, 96, 12});
+
+ ary.Sort(aComp);
+
+ const int sorted[] = {3, 4, 5, 12, 16, 17, 57, 96};
+ for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sorted); i++) {
+ if (sorted[i] != ary[i]) {
+ return false;
+ }
+ }
+
+ if (!ary.ContainsSorted(5, aComp)) {
+ return false;
+ }
+ if (ary.ContainsSorted(42, aComp)) {
+ return false;
+ }
+
+ if (ary.BinaryIndexOf(16, aComp) != 4) {
+ return false;
+ }
+
+ return true;
+}
+
+struct IntComparator {
+ bool Equals(int aLeft, int aRight) const { return aLeft == aRight; }
+
+ bool LessThan(int aLeft, int aRight) const { return aLeft < aRight; }
+};
+
+TEST(TArray, test_comparator_objects)
+{
+ ASSERT_TRUE(TestCompareMethods(IntComparator()));
+ ASSERT_TRUE(
+ TestCompareMethods([](int aLeft, int aRight) { return aLeft - aRight; }));
+}
+
+struct Big {
+ uint64_t size[40] = {};
+};
+
+TEST(TArray, test_AutoTArray_SwapElements)
+{
+ AutoTArray<Big, 40> oneArray;
+ AutoTArray<Big, 40> another;
+
+ for (size_t i = 0; i < 8; ++i) {
+ oneArray.AppendElement(Big());
+ }
+ oneArray[0].size[10] = 1;
+ for (size_t i = 0; i < 9; ++i) {
+ another.AppendElement(Big());
+ }
+ oneArray.SwapElements(another);
+
+ ASSERT_EQ(oneArray.Length(), 9u);
+ ASSERT_EQ(another.Length(), 8u);
+
+ ASSERT_EQ(oneArray[0].size[10], 0u);
+ ASSERT_EQ(another[0].size[10], 1u);
+}
+
+} // namespace TestTArray
diff --git a/xpcom/tests/gtest/TestTaskQueue.cpp b/xpcom/tests/gtest/TestTaskQueue.cpp
new file mode 100644
index 0000000000..bc0e78b608
--- /dev/null
+++ b/xpcom/tests/gtest/TestTaskQueue.cpp
@@ -0,0 +1,215 @@
+/* -*- 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 <memory>
+#include "gtest/gtest.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/Unused.h"
+#include "nsITargetShutdownTask.h"
+#include "VideoUtils.h"
+
+namespace TestTaskQueue {
+
+using namespace mozilla;
+
+TEST(TaskQueue, EventOrder)
+{
+ RefPtr<TaskQueue> tq1 =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestTaskQueue tq1", true);
+ RefPtr<TaskQueue> tq2 =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestTaskQueue tq2", true);
+ RefPtr<TaskQueue> tq3 =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestTaskQueue tq3", true);
+
+ bool errored = false;
+ int counter = 0;
+ int sync = 0;
+ Monitor monitor MOZ_UNANNOTATED("TaskQueue::EventOrder::monitor");
+
+ // We expect task1 happens before task3.
+ for (int i = 0; i < 10000; ++i) {
+ Unused << tq1->Dispatch(
+ NS_NewRunnableFunction(
+ "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
+ [&]() {
+ Unused << tq2->Dispatch(NS_NewRunnableFunction(
+ "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
+ []() { // task0
+ }));
+ Unused << tq3->Dispatch(NS_NewRunnableFunction(
+ "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
+ [&]() { // task1
+ EXPECT_EQ(1, ++counter);
+ errored = counter != 1;
+ MonitorAutoLock mon(monitor);
+ ++sync;
+ mon.Notify();
+ }));
+ Unused << tq2->Dispatch(NS_NewRunnableFunction(
+ "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
+ [&]() { // task2
+ Unused << tq3->Dispatch(NS_NewRunnableFunction(
+ "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody",
+ [&]() { // task3
+ EXPECT_EQ(0, --counter);
+ errored = counter != 0;
+ MonitorAutoLock mon(monitor);
+ ++sync;
+ mon.Notify();
+ }));
+ }));
+ }),
+ AbstractThread::TailDispatch);
+
+ // Ensure task1 and task3 are done before next loop.
+ MonitorAutoLock mon(monitor);
+ while (sync != 2) {
+ mon.Wait();
+ }
+ sync = 0;
+
+ if (errored) {
+ break;
+ }
+ }
+
+ tq1->BeginShutdown();
+ tq1->AwaitShutdownAndIdle();
+ tq2->BeginShutdown();
+ tq2->AwaitShutdownAndIdle();
+ tq3->BeginShutdown();
+ tq3->AwaitShutdownAndIdle();
+}
+
+TEST(TaskQueue, GetCurrentSerialEventTarget)
+{
+ RefPtr<TaskQueue> tq1 =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "TestTaskQueue GetCurrentSerialEventTarget", false);
+ Unused << tq1->Dispatch(NS_NewRunnableFunction(
+ "TestTaskQueue::TestCurrentSerialEventTarget::TestBody", [tq1]() {
+ nsCOMPtr<nsISerialEventTarget> thread = GetCurrentSerialEventTarget();
+ EXPECT_EQ(thread, tq1);
+ }));
+ tq1->BeginShutdown();
+ tq1->AwaitShutdownAndIdle();
+}
+
+namespace {
+
+class TestShutdownTask final : public nsITargetShutdownTask {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit TestShutdownTask(std::function<void()> aCallback)
+ : mCallback(std::move(aCallback)) {}
+
+ void TargetShutdown() override {
+ if (mCallback) {
+ mCallback();
+ }
+ }
+
+ private:
+ ~TestShutdownTask() = default;
+ std::function<void()> mCallback;
+};
+
+NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask)
+
+} // namespace
+
+TEST(TaskQueue, ShutdownTask)
+{
+ auto shutdownTaskRun = std::make_shared<bool>();
+ auto runnableFromShutdownRun = std::make_shared<bool>();
+
+ RefPtr<TaskQueue> tq = TaskQueue::Create(
+ GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue");
+
+ nsCOMPtr<nsITargetShutdownTask> shutdownTask = new TestShutdownTask([=] {
+ EXPECT_TRUE(tq->IsOnCurrentThread());
+
+ ASSERT_FALSE(*shutdownTaskRun);
+ *shutdownTaskRun = true;
+
+ nsCOMPtr<nsITargetShutdownTask> dummyTask = new TestShutdownTask([] {});
+ nsresult rv = tq->RegisterShutdownTask(dummyTask);
+ EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ tq->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] {
+ EXPECT_TRUE(tq->IsOnCurrentThread());
+
+ nsCOMPtr<nsITargetShutdownTask> dummyTask =
+ new TestShutdownTask([] {});
+ nsresult rv = tq->RegisterShutdownTask(dummyTask);
+ EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED);
+
+ ASSERT_FALSE(*runnableFromShutdownRun);
+ *runnableFromShutdownRun = true;
+ })));
+ });
+ MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask));
+
+ ASSERT_FALSE(*shutdownTaskRun);
+ ASSERT_FALSE(*runnableFromShutdownRun);
+
+ RefPtr<mozilla::SyncRunnable> syncWithThread =
+ new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {}));
+ MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq));
+
+ ASSERT_FALSE(*shutdownTaskRun);
+ ASSERT_FALSE(*runnableFromShutdownRun);
+
+ tq->BeginShutdown();
+ tq->AwaitShutdownAndIdle();
+
+ ASSERT_TRUE(*shutdownTaskRun);
+ ASSERT_TRUE(*runnableFromShutdownRun);
+}
+
+TEST(TaskQueue, UnregisteredShutdownTask)
+{
+ RefPtr<TaskQueue> tq = TaskQueue::Create(
+ GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue");
+
+ nsCOMPtr<nsITargetShutdownTask> shutdownTask =
+ new TestShutdownTask([=] { MOZ_CRASH("should not be run"); });
+
+ MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask));
+
+ RefPtr<mozilla::SyncRunnable> syncWithThread =
+ new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {}));
+ MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq));
+
+ MOZ_ALWAYS_SUCCEEDS(tq->UnregisterShutdownTask(shutdownTask));
+
+ tq->BeginShutdown();
+ tq->AwaitShutdownAndIdle();
+}
+
+TEST(AbstractThread, GetCurrentSerialEventTarget)
+{
+ RefPtr<AbstractThread> mainThread = AbstractThread::GetCurrent();
+ EXPECT_EQ(mainThread, AbstractThread::MainThread());
+ Unused << mainThread->Dispatch(NS_NewRunnableFunction(
+ "TestAbstractThread::TestCurrentSerialEventTarget::TestBody",
+ [mainThread]() {
+ nsCOMPtr<nsISerialEventTarget> thread = GetCurrentSerialEventTarget();
+ EXPECT_EQ(thread, mainThread);
+ }));
+
+ // Spin the event loop.
+ NS_ProcessPendingEvents(nullptr);
+}
+
+} // namespace TestTaskQueue
diff --git a/xpcom/tests/gtest/TestTextFormatter.cpp b/xpcom/tests/gtest/TestTextFormatter.cpp
new file mode 100644
index 0000000000..9e3d99a056
--- /dev/null
+++ b/xpcom/tests/gtest/TestTextFormatter.cpp
@@ -0,0 +1,237 @@
+/* -*- 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(u"%3$s %4$S %1$d %2$d %2$d %3$s"_ns);
+ 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);
+
+ 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]);
+ }
+
+ // Test that an unrecognized escape is passed through.
+ nsString out2;
+ nsTextFormatter::ssprintf(out2, u"%1m!", 23);
+ EXPECT_STREQ("%1m!", NS_ConvertUTF16toUTF8(out2).get());
+
+ // Treat NULL the same in both %s cases.
+ nsTextFormatter::ssprintf(out2, u"%s %S", (char*)nullptr, (char16_t*)nullptr);
+ EXPECT_STREQ("(null) (null)", NS_ConvertUTF16toUTF8(out2).get());
+
+ nsTextFormatter::ssprintf(out2, u"%lld", INT64_MIN);
+ EXPECT_STREQ("-9223372036854775808", NS_ConvertUTF16toUTF8(out2).get());
+
+ // Regression test for bug 1401821.
+ nsTextFormatter::ssprintf(out2, u"%*.f", 0, 23.2);
+ EXPECT_STREQ("23", NS_ConvertUTF16toUTF8(out2).get());
+}
+
+/*
+ * Check misordered parameters
+ */
+
+TEST(TextFormatterOrdering, orders)
+{
+ nsString out;
+
+ // plain list
+ nsTextFormatter::ssprintf(out, u"%S %S %S", u"1", u"2", u"3");
+ EXPECT_STREQ("1 2 3", NS_ConvertUTF16toUTF8(out).get());
+
+ // ordered list
+ nsTextFormatter::ssprintf(out, u"%2$S %3$S %1$S", u"1", u"2", u"3");
+ EXPECT_STREQ("2 3 1", NS_ConvertUTF16toUTF8(out).get());
+
+ // Mixed ordered list and non-ordered does not work. This shouldn't
+ // crash (hence the calls to ssprintf) but should fail for for
+ // snprintf.
+ nsTextFormatter::ssprintf(out, u"%2S %S %1$S", u"1", u"2", u"3");
+ nsTextFormatter::ssprintf(out, u"%S %2$S", u"1", u"2");
+ char16_t buffer[1024]; // plenty big
+ EXPECT_EQ(nsTextFormatter::snprintf(buffer, sizeof(buffer), u"%2S %S %1$S",
+ u"1", u"2", u"3"),
+ uint32_t(-1));
+ EXPECT_EQ(
+ nsTextFormatter::snprintf(buffer, sizeof(buffer), u"%S %2$S", u"1", u"2"),
+ uint32_t(-1));
+
+ // Referencing an extra param returns empty strings in release.
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u" %2$S ", u"1");
+ EXPECT_STREQ(" ", NS_ConvertUTF16toUTF8(out).get());
+#endif
+
+ // Double referencing existing argument works
+ nsTextFormatter::ssprintf(out, u"%1$S %1$S", u"1");
+ EXPECT_STREQ("1 1", NS_ConvertUTF16toUTF8(out).get());
+
+ // Dropping trailing argument works
+ nsTextFormatter::ssprintf(out, u" %1$S ", u"1", u"2");
+ EXPECT_STREQ(" 1 ", NS_ConvertUTF16toUTF8(out).get());
+
+ // Dropping leading arguments works
+ nsTextFormatter::ssprintf(out, u" %2$S ", u"1", u"2");
+ EXPECT_STREQ(" 2 ", NS_ConvertUTF16toUTF8(out).get());
+
+ // Dropping middle arguments works
+ nsTextFormatter::ssprintf(out, u" %3$S %1$S ", u"1", u"2", u"3");
+ EXPECT_STREQ(" 3 1 ", NS_ConvertUTF16toUTF8(out).get());
+}
+
+/*
+ * Tests to validate that horrible things don't happen if the passed-in
+ * variable and the formatter don't match.
+ */
+TEST(TextFormatterTestMismatch, format_d)
+{
+ nsString out;
+ // just for completeness, this is our format, and works
+ nsTextFormatter::ssprintf(out, u"%d", int(-1));
+ EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%d", uint32_t(-1));
+ EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get());
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%d", float(3.5));
+ EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%d", "foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%d", u"foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#endif
+}
+
+TEST(TextFormatterTestMismatch, format_u)
+{
+ nsString out;
+ nsTextFormatter::ssprintf(out, u"%u", int(-1));
+ EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get());
+ // just for completeness, this is our format, and works
+ nsTextFormatter::ssprintf(out, u"%u", uint32_t(-1));
+ EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get());
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%u", float(3.5));
+ EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%u", "foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%u", u"foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#endif
+}
+
+TEST(TextFormatterTestMismatch, format_x)
+{
+ nsString out;
+ nsTextFormatter::ssprintf(out, u"%x", int32_t(-1));
+ EXPECT_STREQ("ffffffff", NS_ConvertUTF16toUTF8(out).get());
+ // just for completeness, this is our format, and works
+ nsTextFormatter::ssprintf(out, u"%x", uint32_t(-1));
+ EXPECT_STREQ("ffffffff", NS_ConvertUTF16toUTF8(out).get());
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%x", float(3.5));
+ EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%x", "foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%x", u"foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#endif
+}
+
+TEST(TextFormatterTestMismatch, format_s)
+{
+ nsString out;
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%s", int(-1));
+ EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%s", uint32_t(-1));
+ EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%s", float(3.5));
+ EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get());
+#endif
+ // just for completeness, this is our format, and works
+ nsTextFormatter::ssprintf(out, u"%s", "foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%s", u"foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#endif
+}
+
+TEST(TextFormatterTestMismatch, format_S)
+{
+ nsString out;
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%S", int32_t(-1));
+ EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%S", uint32_t(-1));
+ EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%S", float(3.5));
+ EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%S", "foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#endif
+ // just for completeness, this is our format, and works
+ nsTextFormatter::ssprintf(out, u"%S", u"foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+}
+
+TEST(TextFormatterTestMismatch, format_c)
+{
+ nsString out;
+ nsTextFormatter::ssprintf(out, u"%c", int32_t(-1));
+ EXPECT_EQ(1u, out.Length());
+ EXPECT_EQ((uint16_t)-1, out.CharAt(0)); // not useful for humans :-/
+ nsTextFormatter::ssprintf(out, u"%c", uint32_t(-1));
+ EXPECT_EQ(1u, out.Length());
+ EXPECT_EQ((uint16_t)-1, out.CharAt(0)); // not useful for humans :-/
+#ifndef DEBUG
+ nsTextFormatter::ssprintf(out, u"%c", float(3.5));
+ EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%c", "foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+ nsTextFormatter::ssprintf(out, u"%c", u"foo");
+ EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get());
+#endif
+
+ // just for completeness, this is our format, and works
+ nsTextFormatter::ssprintf(out, u"%c", 'c');
+ EXPECT_EQ(1u, out.Length());
+ EXPECT_EQ(u'c', out.CharAt(0));
+ nsTextFormatter::ssprintf(out, u"%c", u'c');
+ EXPECT_EQ(1u, out.Length());
+ EXPECT_EQ(u'c', out.CharAt(0));
+}
+
+TEST(TextFormatterTestResults, Tests)
+{
+ char16_t buf[10];
+
+ EXPECT_EQ(
+ nsTextFormatter::snprintf(buf, 10, u"%s", "more than 10 characters"), 9u);
+ EXPECT_EQ(buf[9], '\0');
+ EXPECT_STREQ("more than", NS_ConvertUTF16toUTF8(&buf[0]).get());
+
+ nsString out;
+ nsTextFormatter::ssprintf(out, u"%s", "more than 10 characters");
+ // The \0 isn't written here.
+ EXPECT_EQ(out.Length(), 23u);
+}
diff --git a/xpcom/tests/gtest/TestThreadManager.cpp b/xpcom/tests/gtest/TestThreadManager.cpp
new file mode 100644
index 0000000000..41279e104c
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadManager.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "nsIThreadManager.h"
+#include "nsCOMPtr.h"
+#include "nsIThread.h"
+#include "nsXPCOM.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Atomics.h"
+#include "gtest/gtest.h"
+#include "mozilla/gtest/MozAssertions.h"
+
+using mozilla::Atomic;
+using mozilla::Runnable;
+
+class WaitCondition final : public nsINestedEventLoopCondition {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WaitCondition(Atomic<uint32_t>& aCounter, uint32_t aMaxCount)
+ : mCounter(aCounter), mMaxCount(aMaxCount) {}
+
+ NS_IMETHODIMP IsDone(bool* aDone) override {
+ *aDone = (mCounter == mMaxCount);
+ return NS_OK;
+ }
+
+ private:
+ ~WaitCondition() = default;
+
+ Atomic<uint32_t>& mCounter;
+ const uint32_t mMaxCount;
+};
+
+NS_IMPL_ISUPPORTS(WaitCondition, nsINestedEventLoopCondition)
+
+class SpinRunnable final : public Runnable {
+ public:
+ explicit SpinRunnable(nsINestedEventLoopCondition* aCondition)
+ : Runnable("SpinRunnable"), mCondition(aCondition), mResult(NS_OK) {}
+
+ NS_IMETHODIMP Run() {
+ nsCOMPtr<nsIThreadManager> threadMan =
+ do_GetService("@mozilla.org/thread-manager;1");
+
+ mResult = threadMan->SpinEventLoopUntil(
+ "xpcom:TestThreadManager.cpp:SpinRunnable->Run()"_ns, mCondition);
+ return NS_OK;
+ }
+
+ nsresult SpinLoopResult() { return mResult; }
+
+ private:
+ ~SpinRunnable() = default;
+
+ nsCOMPtr<nsINestedEventLoopCondition> mCondition;
+ Atomic<nsresult> mResult;
+};
+
+class CountRunnable final : public Runnable {
+ public:
+ explicit CountRunnable(Atomic<uint32_t>& aCounter)
+ : Runnable("CountRunnable"), mCounter(aCounter) {}
+
+ NS_IMETHODIMP Run() {
+ mCounter++;
+ return NS_OK;
+ }
+
+ private:
+ Atomic<uint32_t>& mCounter;
+};
+
+TEST(ThreadManager, SpinEventLoopUntilSuccess)
+{
+ const uint32_t kRunnablesToDispatch = 100;
+ nsresult rv;
+ mozilla::Atomic<uint32_t> count(0);
+
+ nsCOMPtr<nsINestedEventLoopCondition> condition =
+ new WaitCondition(count, kRunnablesToDispatch);
+ RefPtr<SpinRunnable> spinner = new SpinRunnable(condition);
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIRunnable> counter = new CountRunnable(count);
+ for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) {
+ rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL);
+ ASSERT_NS_SUCCEEDED(rv);
+ }
+
+ rv = thread->Shutdown();
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_NS_SUCCEEDED(spinner->SpinLoopResult());
+}
+
+class ErrorCondition final : public nsINestedEventLoopCondition {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ ErrorCondition(Atomic<uint32_t>& aCounter, uint32_t aMaxCount)
+ : mCounter(aCounter), mMaxCount(aMaxCount) {}
+
+ NS_IMETHODIMP IsDone(bool* aDone) override {
+ if (mCounter == mMaxCount) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~ErrorCondition() = default;
+
+ Atomic<uint32_t>& mCounter;
+ const uint32_t mMaxCount;
+};
+
+NS_IMPL_ISUPPORTS(ErrorCondition, nsINestedEventLoopCondition)
+
+TEST(ThreadManager, SpinEventLoopUntilError)
+{
+ const uint32_t kRunnablesToDispatch = 100;
+ nsresult rv;
+ mozilla::Atomic<uint32_t> count(0);
+
+ nsCOMPtr<nsINestedEventLoopCondition> condition =
+ new ErrorCondition(count, kRunnablesToDispatch);
+ RefPtr<SpinRunnable> spinner = new SpinRunnable(condition);
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIRunnable> counter = new CountRunnable(count);
+ for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) {
+ rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL);
+ ASSERT_NS_SUCCEEDED(rv);
+ }
+
+ rv = thread->Shutdown();
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_NS_FAILED(spinner->SpinLoopResult());
+}
diff --git a/xpcom/tests/gtest/TestThreadPool.cpp b/xpcom/tests/gtest/TestThreadPool.cpp
new file mode 100644
index 0000000000..0dd0f51537
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadPool.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "nsThreadPool.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 TestTask final : public Runnable {
+ public:
+ TestTask(int i, Atomic<int>& aCounter)
+ : Runnable("TestThreadPool::Task"), mIndex(i), mCounter(aCounter) {}
+
+ NS_IMETHOD Run() override {
+ printf("###(%d) running from thread: %p\n", mIndex,
+ (void*)PR_GetCurrentThread());
+ int r = (int)((float)rand() * 200 / float(RAND_MAX));
+ PR_Sleep(PR_MillisecondsToInterval(r));
+ printf("###(%d) exiting from thread: %p\n", mIndex,
+ (void*)PR_GetCurrentThread());
+ ++mCounter;
+ return NS_OK;
+ }
+
+ private:
+ ~TestTask() = default;
+
+ int mIndex;
+ Atomic<int>& mCounter;
+};
+
+TEST(ThreadPool, Main)
+{
+ nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+
+ Atomic<int> count(0);
+
+ for (int i = 0; i < 100; ++i) {
+ nsCOMPtr<nsIRunnable> task = new TestTask(i, count);
+ EXPECT_TRUE(task);
+
+ pool->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+
+ pool->Shutdown();
+ EXPECT_EQ(count, 100);
+}
+
+TEST(ThreadPool, Parallelism)
+{
+ nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+
+ // Dispatch and sleep to ensure we have an idle thread
+ nsCOMPtr<nsIRunnable> r0 = new Runnable("TestRunnable");
+ NS_DispatchAndSpinEventLoopUntilComplete("ThreadPool::Parallelism"_ns, pool,
+ do_AddRef(r0));
+ PR_Sleep(PR_SecondsToInterval(2));
+
+ class Runnable1 : public Runnable {
+ public:
+ Runnable1(Monitor& aMonitor, bool& aDone)
+ : mozilla::Runnable("Runnable1"), 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(TimeDuration::FromSeconds(300));
+ }
+ EXPECT_TRUE(mDone);
+ return NS_OK;
+ }
+
+ private:
+ Monitor& mMonitor;
+ bool& mDone;
+ };
+
+ class Runnable2 : public Runnable {
+ public:
+ Runnable2(Monitor& aMonitor, bool& aDone)
+ : mozilla::Runnable("Runnable2"), 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 MOZ_UNANNOTATED("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();
+}
+
+TEST(ThreadPool, ShutdownWithTimeout)
+{
+ nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+
+ Atomic<int> allThreadsCount(0);
+ for (int i = 0; i < 4; ++i) {
+ nsCOMPtr<nsIRunnable> task = new TestTask(i, allThreadsCount);
+ EXPECT_TRUE(task);
+
+ pool->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+
+ // Wait for a max of 350 ms. All threads should be done by then.
+ pool->ShutdownWithTimeout(350);
+ EXPECT_EQ(allThreadsCount, 4);
+
+ Atomic<int> infiniteLoopCount(0);
+ Atomic<bool> shutdownInfiniteLoop(false);
+ Atomic<bool> shutdownAck(false);
+ pool = new nsThreadPool();
+ for (int i = 0; i < 3; ++i) {
+ nsCOMPtr<nsIRunnable> task = new TestTask(i, infiniteLoopCount);
+ EXPECT_TRUE(task);
+
+ pool->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+
+ pool->Dispatch(NS_NewRunnableFunction(
+ "infinite-loop",
+ [&shutdownInfiniteLoop, &shutdownAck]() {
+ printf("### running from thread that never ends: %p\n",
+ (void*)PR_GetCurrentThread());
+ while (!shutdownInfiniteLoop) {
+ PR_Sleep(PR_MillisecondsToInterval(100));
+ }
+ shutdownAck = true;
+ }),
+ NS_DISPATCH_NORMAL);
+
+ pool->ShutdownWithTimeout(1000);
+ EXPECT_EQ(infiniteLoopCount, 3);
+
+ shutdownInfiniteLoop = true;
+ while (!shutdownAck) {
+ /* nothing */
+ }
+}
+
+TEST(ThreadPool, ShutdownWithTimeoutThenSleep)
+{
+ Atomic<int> count(0);
+ nsCOMPtr<nsIThreadPool> pool = new nsThreadPool();
+
+ for (int i = 0; i < 3; ++i) {
+ nsCOMPtr<nsIRunnable> task = new TestTask(i, count);
+ EXPECT_TRUE(task);
+
+ pool->Dispatch(task, NS_DISPATCH_NORMAL);
+ }
+
+ pool->Dispatch(
+ NS_NewRunnableFunction(
+ "sleep-for-400-ms",
+ [&count]() {
+ printf("### running from thread that sleeps for 400ms: %p\n",
+ (void*)PR_GetCurrentThread());
+ PR_Sleep(PR_MillisecondsToInterval(400));
+ ++count;
+ printf("### thread awoke from long sleep: %p\n",
+ (void*)PR_GetCurrentThread());
+ }),
+ NS_DISPATCH_NORMAL);
+
+ // Wait for a max of 350 ms. The thread should still be sleeping, and will
+ // be leaked.
+ pool->ShutdownWithTimeout(350);
+ // We can't be exact here; the thread we're running on might have gotten
+ // suspended and the sleeping thread, above, might have finished.
+ EXPECT_GE(count, 3);
+
+ // Sleep for a bit, and wait for the last thread to finish up.
+ PR_Sleep(PR_MillisecondsToInterval(200));
+
+ // Process events so the shutdown ack is received
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ EXPECT_EQ(count, 4);
+}
diff --git a/xpcom/tests/gtest/TestThreadPoolListener.cpp b/xpcom/tests/gtest/TestThreadPoolListener.cpp
new file mode 100644
index 0000000000..2ca4fa26f1
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadPoolListener.cpp
@@ -0,0 +1,205 @@
+/* -*- 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 "nsComponentManagerUtils.h"
+#include "nsThreadPool.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/gtest/MozAssertions.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() = default;
+
+ 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 = new nsThreadPool();
+
+ rv = pool->SetThreadLimit(NUMBER_OF_THREADS);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIThreadPoolListener> listener = new Listener();
+ ASSERT_TRUE(listener);
+
+ rv = pool->SetListener(listener);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+
+ for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) {
+ nsCOMPtr<nsIRunnable> runnable = new Runnable("TestRunnable");
+ ASSERT_TRUE(runnable);
+
+ rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ ASSERT_NS_SUCCEEDED(rv);
+ }
+
+ gAllRunnablesPosted = true;
+ mon.NotifyAll();
+ }
+
+ {
+ ReentrantMonitorAutoEnter mon(*gReentrantMonitor);
+ while (!gAllThreadsCreated) {
+ mon.Wait();
+ }
+ }
+
+ rv = pool->Shutdown();
+ ASSERT_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 0000000000..2b9ff97192
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreadUtils.cpp
@@ -0,0 +1,2226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <type_traits>
+
+#include "nsComponentManagerUtils.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "mozilla/IdleTaskRunner.h"
+#include "mozilla/RefCounted.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/UniquePtr.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() = default;
+};
+
+NS_IMPL_ISUPPORTS0(nsFoo)
+
+class TestSuicide : public mozilla::Runnable {
+ public:
+ TestSuicide() : mozilla::Runnable("TestSuicide") {}
+ 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() = default;
+
+ 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 += 1;
+ };
+ // 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 += 1;
+ };
+ // 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 += 1;
+ }
+ ~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 += 1;
+ };
+ TestCopyMove(TestCopyMove&& a)
+ : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) {
+ a.mMoveCounter = nullptr;
+ *mMoveCounter += 1;
+ }
+ ~TestCopyMove() {
+ mCopyCounter = nullptr;
+ mMoveCounter = nullptr;
+ }
+ void operator()() {
+ MOZ_RELEASE_ASSERT(mCopyCounter);
+ MOZ_RELEASE_ASSERT(mMoveCounter);
+ }
+ int* mCopyCounter;
+ int* mMoveCounter;
+};
+
+struct TestRefCounted : RefCounted<TestRefCounted> {
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(TestRefCounted);
+};
+
+static void Expect(const char* aContext, int aCounter, int aMaxExpected) {
+ EXPECT_LE(aCounter, aMaxExpected) << aContext;
+}
+
+static void ExpectRunnableName(Runnable* aRunnable, const char* aExpectedName) {
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ nsAutoCString name;
+ EXPECT_TRUE(NS_SUCCEEDED(aRunnable->GetName(name))) << "Runnable::GetName()";
+ EXPECT_TRUE(name.EqualsASCII(aExpectedName)) << "Verify Runnable name";
+#endif
+}
+
+struct BasicRunnableFactory {
+ static constexpr bool SupportsCopyWithDeletedMove = true;
+
+ template <typename Function>
+ static auto Create(const char* aName, Function&& aFunc) {
+ return NS_NewRunnableFunction(aName, std::forward<Function>(aFunc));
+ }
+};
+
+struct CancelableRunnableFactory {
+ static constexpr bool SupportsCopyWithDeletedMove = false;
+
+ template <typename Function>
+ static auto Create(const char* aName, Function&& aFunc) {
+ return NS_NewCancelableRunnableFunction(aName,
+ std::forward<Function>(aFunc));
+ }
+};
+
+template <typename RunnableFactory>
+static void TestRunnableFactory(bool aNamed) {
+ // Test RunnableFactory with copyable-only function object.
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker)
+ : RunnableFactory::Create(
+ "TestNewRunnableFunction", tracker);
+ // Original 'tracker' is destroyed here.
+ }
+ // Verify that the runnable contains a non-destroyed function object.
+ trackedRunnable->Run();
+ }
+ Expect(
+ "RunnableFactory 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 =
+ aNamed ? RunnableFactory::Create("unused",
+ TestCopyWithNoMove(&copyCounter))
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ TestCopyWithNoMove(&copyCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect(
+ "RunnableFactory with copyable-only (and no move) function "
+ "rvalue, copies",
+ copyCounter, 1);
+ }
+ if constexpr (RunnableFactory::SupportsCopyWithDeletedMove) {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithDeletedMove tracker(&copyCounter);
+ trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker)
+ : RunnableFactory::Create(
+ "TestNewRunnableFunction", tracker);
+ }
+ trackedRunnable->Run();
+ }
+ Expect(
+ "RunnableFactory with copyable-only (and deleted move) "
+ "function, copies",
+ copyCounter, 1);
+ }
+
+ // Test RunnableFactory with movable-only function object.
+ {
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestMove tracker(&moveCounter);
+ trackedRunnable =
+ aNamed ? RunnableFactory::Create("unused", std::move(tracker))
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ std::move(tracker));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("RunnableFactory with movable-only function, moves", moveCounter, 1);
+ }
+ {
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable =
+ aNamed ? RunnableFactory::Create("unused", TestMove(&moveCounter))
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ TestMove(&moveCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("RunnableFactory with movable-only function rvalue, moves",
+ moveCounter, 1);
+ }
+
+ // Test RunnableFactory with copyable&movable function object.
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &moveCounter);
+ trackedRunnable =
+ aNamed ? RunnableFactory::Create("unused", std::move(tracker))
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ std::move(tracker));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("RunnableFactory with copyable&movable function, copies",
+ copyCounter, 0);
+ Expect("RunnableFactory with copyable&movable function, moves", moveCounter,
+ 1);
+ }
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ trackedRunnable =
+ aNamed ? RunnableFactory::Create(
+ "unused", TestCopyMove(&copyCounter, &moveCounter))
+ : RunnableFactory::Create(
+ "TestNewRunnableFunction",
+ TestCopyMove(&copyCounter, &moveCounter));
+ }
+ trackedRunnable->Run();
+ }
+ Expect("RunnableFactory with copyable&movable function rvalue, copies",
+ copyCounter, 0);
+ Expect("RunnableFactory with copyable&movable function rvalue, moves",
+ moveCounter, 1);
+ }
+
+ // Test RunnableFactory with copyable-only lambda capture.
+ {
+ int copyCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyWithNoMove tracker(&copyCounter);
+ // Expect 2 copies (here -> local lambda -> runnable lambda).
+ trackedRunnable =
+ aNamed
+ ? RunnableFactory::Create("unused",
+ [tracker]() mutable { tracker(); })
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ [tracker]() mutable { tracker(); });
+ }
+ trackedRunnable->Run();
+ }
+ Expect(
+ "RunnableFactory 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 =
+ aNamed
+ ? RunnableFactory::Create("unused",
+ [tracker]() mutable { tracker(); })
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ [tracker]() mutable { tracker(); });
+ }
+ trackedRunnable->Run();
+ }
+ Expect(
+ "RunnableFactory 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 RunnableFactory with copyable&movable lambda capture.
+ {
+ int copyCounter = 0;
+ int moveCounter = 0;
+ {
+ nsCOMPtr<nsIRunnable> trackedRunnable;
+ {
+ TestCopyMove tracker(&copyCounter, &moveCounter);
+ trackedRunnable =
+ aNamed
+ ? RunnableFactory::Create("unused",
+ [tracker]() mutable { tracker(); })
+ : RunnableFactory::Create("TestNewRunnableFunction",
+ [tracker]() mutable { tracker(); });
+ // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable
+ // lambda).
+ }
+ trackedRunnable->Run();
+ }
+ Expect("RunnableFactory with copyable&movable capture, copies", copyCounter,
+ 1);
+ Expect("RunnableFactory with copyable&movable capture, moves", moveCounter,
+ 1);
+ }
+}
+
+TEST(ThreadUtils, NewRunnableFunction)
+{ TestRunnableFactory<BasicRunnableFactory>(/*aNamed*/ false); }
+
+TEST(ThreadUtils, NewNamedRunnableFunction)
+{
+ // The named overload shall behave identical to the non-named counterpart.
+ TestRunnableFactory<BasicRunnableFactory>(/*aNamed*/ true);
+
+ // Test naming.
+ {
+ const char* expectedName = "NamedRunnable";
+ RefPtr<Runnable> NamedRunnable =
+ NS_NewRunnableFunction(expectedName, [] {});
+ ExpectRunnableName(NamedRunnable, expectedName);
+ }
+}
+
+TEST(ThreadUtils, NewCancelableRunnableFunction)
+{ TestRunnableFactory<CancelableRunnableFactory>(/*aNamed*/ false); }
+
+TEST(ThreadUtils, NewNamedCancelableRunnableFunction)
+{
+ // The named overload shall behave identical to the non-named counterpart.
+ TestRunnableFactory<CancelableRunnableFactory>(/*aNamed*/ true);
+
+ // Test naming.
+ {
+ const char* expectedName = "NamedRunnable";
+ RefPtr<Runnable> NamedRunnable =
+ NS_NewCancelableRunnableFunction(expectedName, [] {});
+ ExpectRunnableName(NamedRunnable, expectedName);
+ }
+
+ // Test release on cancelation.
+ {
+ auto foo = MakeRefPtr<TestRefCounted>();
+ bool ran = false;
+
+ RefPtr<CancelableRunnable> func =
+ NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; });
+
+ EXPECT_EQ(foo->refCount(), 2u);
+ func->Cancel();
+
+ EXPECT_EQ(foo->refCount(), 1u);
+ EXPECT_FALSE(ran);
+ }
+
+ // Test no-op after cancelation.
+ {
+ auto foo = MakeRefPtr<TestRefCounted>();
+ bool ran = false;
+
+ RefPtr<CancelableRunnable> func =
+ NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; });
+
+ EXPECT_EQ(foo->refCount(), 2u);
+ func->Cancel();
+ func->Run();
+
+ EXPECT_FALSE(ran);
+ }
+}
+
+static void TestNewRunnableMethod(bool aNamed) {
+ 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";
+
+ {
+ auto bar = MakeRefPtr<nsBar>();
+
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod("unused", std::move(bar), &nsBar::DoBar1)
+ : NewRunnableMethod("nsBar::DoBar1", std::move(bar),
+ &nsBar::DoBar1));
+ }
+
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1)
+ : NewRunnableMethod("nsBar::DoBar1", bar, &nsBar::DoBar1));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod("unused", constBar, &nsBar::DoBar1Const)
+ : NewRunnableMethod("nsBar::DoBar1Const", constBar,
+ &nsBar::DoBar1Const));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2)
+ : NewRunnableMethod("nsBar::DoBar2", bar, &nsBar::DoBar2));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, &nsBar::DoBar3,
+ foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>("nsBar::DoBar3", bar,
+ &nsBar::DoBar3, foo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar, &nsBar::DoBar4,
+ foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>("nsBar::DoBar4", bar,
+ &nsBar::DoBar4, foo));
+ NS_DispatchToMainThread(
+ aNamed
+ ? NewRunnableMethod<nsFoo*>("unused", bar, &nsBar::DoBar5, rawFoo)
+ : NewRunnableMethod<nsFoo*>("nsBar::DoBar5", bar, &nsBar::DoBar5,
+ rawFoo));
+ NS_DispatchToMainThread(
+ aNamed
+ ? NewRunnableMethod<char*>("unused", bar, &nsBar::DoBar6, message)
+ : NewRunnableMethod<char*>("nsBar::DoBar6", bar, &nsBar::DoBar6,
+ message));
+#ifdef HAVE_STDCALL
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1std)
+ : NewRunnableMethod(bar, &nsBar::DoBar1std));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2std)
+ : NewRunnableMethod(bar, &nsBar::DoBar2std));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar,
+ &nsBar::DoBar3std, foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>(bar, &nsBar::DoBar3std, foo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<RefPtr<nsFoo>>("unused", bar,
+ &nsBar::DoBar4std, foo)
+ : NewRunnableMethod<RefPtr<nsFoo>>(bar, &nsBar::DoBar4std, foo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<nsFoo*>("unused", bar, &nsBar::DoBar5std,
+ rawFoo)
+ : NewRunnableMethod<nsFoo*>(bar, &nsBar::DoBar5std, rawFoo));
+ NS_DispatchToMainThread(
+ aNamed ? NewRunnableMethod<char*>("unused", bar, &nsBar::DoBar6std,
+ message)
+ : 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;
+ }
+}
+
+TEST(ThreadUtils, RunnableMethod)
+{ TestNewRunnableMethod(/* aNamed */ false); }
+
+TEST(ThreadUtils, NamedRunnableMethod)
+{
+ // The named overloads shall behave identical to the non-named counterparts.
+ TestNewRunnableMethod(/* aNamed */ true);
+
+ // Test naming.
+ {
+ RefPtr<nsFoo> foo = new nsFoo();
+ const char* expectedName = "NamedRunnable";
+ bool unused;
+ RefPtr<Runnable> NamedRunnable =
+ NewRunnableMethod<bool*>(expectedName, foo, &nsFoo::DoFoo, &unused);
+ ExpectRunnableName(NamedRunnable, expectedName);
+ }
+}
+
+class IdleObjectWithoutSetDeadline final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(IdleObjectWithoutSetDeadline)
+ IdleObjectWithoutSetDeadline() : mRunnableExecuted(false) {}
+ void Method() { mRunnableExecuted = true; }
+ bool mRunnableExecuted;
+
+ private:
+ ~IdleObjectWithoutSetDeadline() = default;
+};
+
+class IdleObjectParentWithSetDeadline {
+ public:
+ IdleObjectParentWithSetDeadline() : mSetDeadlineCalled(false) {}
+ void SetDeadline(TimeStamp aDeadline) { mSetDeadlineCalled = true; }
+ bool mSetDeadlineCalled;
+};
+
+class IdleObjectInheritedSetDeadline final
+ : public IdleObjectParentWithSetDeadline {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(IdleObjectInheritedSetDeadline)
+ IdleObjectInheritedSetDeadline() : mRunnableExecuted(false) {}
+ void Method() { mRunnableExecuted = true; }
+ bool mRunnableExecuted;
+
+ private:
+ ~IdleObjectInheritedSetDeadline() = default;
+};
+
+class IdleObject final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(IdleObject)
+ IdleObject() {
+ for (uint32_t index = 0; index < ArrayLength(mRunnableExecuted); ++index) {
+ mRunnableExecuted[index] = false;
+ mSetIdleDeadlineCalled = false;
+ }
+ }
+ void SetDeadline(TimeStamp aTimeStamp) { mSetIdleDeadlineCalled = true; }
+
+ void CheckExecutedMethods(const char* aKey, uint32_t aNumExecuted) {
+ uint32_t index;
+ for (index = 0; index < aNumExecuted; ++index) {
+ ASSERT_TRUE(mRunnableExecuted[index])
+ << aKey << ": Method" << index << " should've executed";
+ }
+
+ for (; index < ArrayLength(mRunnableExecuted); ++index) {
+ ASSERT_FALSE(mRunnableExecuted[index])
+ << aKey << ": Method" << index << " shouldn't have executed";
+ }
+ }
+
+ void Method0() {
+ CheckExecutedMethods("Method0", 0);
+ mRunnableExecuted[0] = true;
+ mSetIdleDeadlineCalled = false;
+ }
+
+ void Method1() {
+ CheckExecutedMethods("Method1", 1);
+ ASSERT_TRUE(mSetIdleDeadlineCalled);
+ mRunnableExecuted[1] = true;
+ mSetIdleDeadlineCalled = false;
+ }
+
+ void Method2() {
+ CheckExecutedMethods("Method2", 2);
+ ASSERT_TRUE(mSetIdleDeadlineCalled);
+ mRunnableExecuted[2] = true;
+ mSetIdleDeadlineCalled = false;
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("IdleObject::Method3", this, &IdleObject::Method3));
+ }
+
+ void Method3() {
+ CheckExecutedMethods("Method3", 3);
+
+ NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), Method4, this, 10,
+ nsITimer::TYPE_ONE_SHOT, "IdleObject::Method3");
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethodWithTimer("IdleObject::Method5", this,
+ &IdleObject::Method5),
+ 50, EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(
+ NewRunnableMethod("IdleObject::Method6", this, &IdleObject::Method6),
+ 100, EventQueuePriority::Idle);
+
+ PR_Sleep(PR_MillisecondsToInterval(200));
+ mRunnableExecuted[3] = true;
+ mSetIdleDeadlineCalled = false;
+ }
+
+ static void Method4(nsITimer* aTimer, void* aClosure) {
+ RefPtr<IdleObject> self = static_cast<IdleObject*>(aClosure);
+ self->CheckExecutedMethods("Method4", 4);
+ self->mRunnableExecuted[4] = true;
+ self->mSetIdleDeadlineCalled = false;
+ }
+
+ void Method5() {
+ CheckExecutedMethods("Method5", 5);
+ ASSERT_TRUE(mSetIdleDeadlineCalled);
+ mRunnableExecuted[5] = true;
+ mSetIdleDeadlineCalled = false;
+ }
+
+ void Method6() {
+ CheckExecutedMethods("Method6", 6);
+ mRunnableExecuted[6] = true;
+ mSetIdleDeadlineCalled = false;
+ }
+
+ void Method7() {
+ CheckExecutedMethods("Method7", 7);
+ ASSERT_TRUE(mSetIdleDeadlineCalled);
+ mRunnableExecuted[7] = true;
+ mSetIdleDeadlineCalled = false;
+ }
+
+ private:
+ nsCOMPtr<nsITimer> mTimer;
+ bool mRunnableExecuted[8];
+ bool mSetIdleDeadlineCalled;
+ ~IdleObject() = default;
+};
+
+// Disable test due to frequent failures
+#if 0
+// because test fails on multiple platforms
+TEST(ThreadUtils, IdleRunnableMethod)
+{
+ {
+ RefPtr<IdleObject> idle = new IdleObject();
+ RefPtr<IdleObjectWithoutSetDeadline> idleNoSetDeadline =
+ new IdleObjectWithoutSetDeadline();
+ RefPtr<IdleObjectInheritedSetDeadline> idleInheritedSetDeadline =
+ new IdleObjectInheritedSetDeadline();
+
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("IdleObject::Method0", idle, &IdleObject::Method0));
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethod("IdleObject::Method1", idle,
+ &IdleObject::Method1),
+ EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethodWithTimer("IdleObject::Method2", idle,
+ &IdleObject::Method2),
+ 60000, EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethod("IdleObject::Method7", idle,
+ &IdleObject::Method7),
+ EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethod<const char*, uint32_t>(
+ "IdleObject::CheckExecutedMethods", idle,
+ &IdleObject::CheckExecutedMethods, "final", 8),
+ EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethod("IdleObjectWithoutSetDeadline::Method",
+ idleNoSetDeadline,
+ &IdleObjectWithoutSetDeadline::Method),
+ EventQueuePriority::Idle);
+ NS_DispatchToCurrentThreadQueue(
+ NewIdleRunnableMethod("IdleObjectInheritedSetDeadline::Method",
+ idleInheritedSetDeadline,
+ &IdleObjectInheritedSetDeadline::Method),
+ EventQueuePriority::Idle);
+
+ NS_ProcessPendingEvents(nullptr);
+
+ ASSERT_TRUE(idleNoSetDeadline->mRunnableExecuted);
+ ASSERT_TRUE(idleInheritedSetDeadline->mRunnableExecuted);
+ ASSERT_TRUE(idleInheritedSetDeadline->mSetDeadlineCalled);
+ }
+}
+#endif
+
+TEST(ThreadUtils, IdleTaskRunner)
+{
+ using namespace mozilla;
+
+ // Repeating.
+ int cnt1 = 0;
+ RefPtr<IdleTaskRunner> runner1 = IdleTaskRunner::Create(
+ [&cnt1](TimeStamp) {
+ cnt1++;
+ return true;
+ },
+ "runner1", 0, TimeDuration::FromMilliseconds(10),
+ TimeDuration::FromMilliseconds(3), true, nullptr);
+
+ // Non-repeating but callback always return false so it's still repeating.
+ int cnt2 = 0;
+ RefPtr<IdleTaskRunner> runner2 = IdleTaskRunner::Create(
+ [&cnt2](TimeStamp) {
+ cnt2++;
+ return false;
+ },
+ "runner2", 0, TimeDuration::FromMilliseconds(10),
+ TimeDuration::FromMilliseconds(3), false, nullptr);
+
+ // Repeating until cnt3 >= 2 by returning 'true' in MayStopProcessing
+ // callback. The strategy is to stop repeating as early as possible so that we
+ // are more probable to catch the bug if it didn't stop as expected.
+ int cnt3 = 0;
+ RefPtr<IdleTaskRunner> runner3 = IdleTaskRunner::Create(
+ [&cnt3](TimeStamp) {
+ cnt3++;
+ return true;
+ },
+ "runner3", 0, TimeDuration::FromMilliseconds(10),
+ TimeDuration::FromMilliseconds(3), true, [&cnt3] { return cnt3 >= 2; });
+
+ // Non-repeating can callback return true so the callback will
+ // be only run once.
+ int cnt4 = 0;
+ RefPtr<IdleTaskRunner> runner4 = IdleTaskRunner::Create(
+ [&cnt4](TimeStamp) {
+ cnt4++;
+ return true;
+ },
+ "runner4", 0, TimeDuration::FromMilliseconds(10),
+ TimeDuration::FromMilliseconds(3), false, nullptr);
+
+ // Firstly we wait until the two repeating tasks reach their limits.
+ MOZ_ALWAYS_TRUE(
+ SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt1"_ns,
+ [&]() { return cnt1 >= 100; }));
+ MOZ_ALWAYS_TRUE(
+ SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt2"_ns,
+ [&]() { return cnt2 >= 100; }));
+
+ // At any point ==> 0 <= cnt3 <= 2 since MayStopProcessing() would return
+ // true when cnt3 >= 2.
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt3"_ns, [&]() {
+ if (cnt3 > 2) {
+ EXPECT_TRUE(false) << "MaybeContinueProcess() doesn't work.";
+ return true; // Stop on failure.
+ }
+ return cnt3 == 2; // Stop finish if we have reached its max value.
+ }));
+
+ // At any point ==> 0 <= cnt4 <= 1 since this is a non-repeating
+ // idle runner.
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt4"_ns, [&]() {
+ // At any point: 0 <= cnt4 <= 1
+ if (cnt4 > 1) {
+ EXPECT_TRUE(false) << "The 'mRepeating' flag doesn't work.";
+ return true; // Stop on failure.
+ }
+ return cnt4 == 1;
+ }));
+
+ // The repeating timers require an explicit Cancel() call.
+ runner1->Cancel();
+ runner2->Cancel();
+}
+
+// {9e70a320-be02-11d1-8031-006008159b5a}
+#define NS_IFOO_IID \
+ { \
+ 0x9e70a320, 0xbe02, 0x11d1, { \
+ 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \
+ } \
+ }
+
+TEST(ThreadUtils, TypeTraits)
+{
+ static_assert(!mozilla::IsRefcountedSmartPointer<int>::value,
+ "IsRefcountedSmartPointer<int> should be false");
+ static_assert(mozilla::IsRefcountedSmartPointer<RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<RefPtr<...>> should be true");
+ static_assert(mozilla::IsRefcountedSmartPointer<const RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<const RefPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<volatile RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<volatile RefPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<const volatile RefPtr<int>>::value,
+ "IsRefcountedSmartPointer<const volatile RefPtr<...>> should be true");
+ static_assert(mozilla::IsRefcountedSmartPointer<nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<nsCOMPtr<...>> should be true");
+ static_assert(mozilla::IsRefcountedSmartPointer<const nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<const nsCOMPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<volatile nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<volatile nsCOMPtr<...>> should be true");
+ static_assert(
+ mozilla::IsRefcountedSmartPointer<const volatile nsCOMPtr<int>>::value,
+ "IsRefcountedSmartPointer<const volatile nsCOMPtr<...>> should be true");
+
+ static_assert(std::is_same_v<int, mozilla::RemoveSmartPointer<int>::Type>,
+ "RemoveSmartPointer<int>::Type should be int");
+ static_assert(std::is_same_v<int*, mozilla::RemoveSmartPointer<int*>::Type>,
+ "RemoveSmartPointer<int*>::Type should be int*");
+ static_assert(
+ std::is_same_v<UniquePtr<int>,
+ mozilla::RemoveSmartPointer<UniquePtr<int>>::Type>,
+ "RemoveSmartPointer<UniquePtr<int>>::Type should be UniquePtr<int>");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveSmartPointer<RefPtr<int>>::Type>,
+ "RemoveSmartPointer<RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveSmartPointer<const RefPtr<int>>::Type>,
+ "RemoveSmartPointer<const RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveSmartPointer<volatile RefPtr<int>>::Type>,
+ "RemoveSmartPointer<volatile RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveSmartPointer<const volatile RefPtr<int>>::Type>,
+ "RemoveSmartPointer<const volatile RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveSmartPointer<nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveSmartPointer<const nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<const nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveSmartPointer<volatile nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<volatile nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveSmartPointer<const volatile nsCOMPtr<int>>::Type>,
+ "RemoveSmartPointer<const volatile nsCOMPtr<int>>::Type should be int");
+
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<int>::Type>,
+ "RemoveRawOrSmartPointer<int>::Type should be int");
+ static_assert(
+ std::is_same_v<UniquePtr<int>,
+ mozilla::RemoveRawOrSmartPointer<UniquePtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<UniquePtr<int>>::Type should be UniquePtr<int>");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<int*>::Type>,
+ "RemoveRawOrSmartPointer<int*>::Type should be int");
+ static_assert(
+ std::is_same_v<const int,
+ mozilla::RemoveRawOrSmartPointer<const int*>::Type>,
+ "RemoveRawOrSmartPointer<const int*>::Type should be const int");
+ static_assert(
+ std::is_same_v<volatile int,
+ mozilla::RemoveRawOrSmartPointer<volatile int*>::Type>,
+ "RemoveRawOrSmartPointer<volatile int*>::Type should be volatile int");
+ static_assert(
+ std::is_same_v<const volatile int, mozilla::RemoveRawOrSmartPointer<
+ const volatile int*>::Type>,
+ "RemoveRawOrSmartPointer<const volatile int*>::Type should be const "
+ "volatile int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveRawOrSmartPointer<const RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveRawOrSmartPointer<volatile RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<volatile RefPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<
+ const volatile RefPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const volatile RefPtr<int>>::Type should be "
+ "int");
+ static_assert(
+ std::is_same_v<int,
+ mozilla::RemoveRawOrSmartPointer<nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveRawOrSmartPointer<const nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<
+ int, mozilla::RemoveRawOrSmartPointer<volatile nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<volatile nsCOMPtr<int>>::Type should be int");
+ static_assert(
+ std::is_same_v<int, mozilla::RemoveRawOrSmartPointer<
+ const volatile nsCOMPtr<int>>::Type>,
+ "RemoveRawOrSmartPointer<const volatile nsCOMPtr<int>>::Type should be "
+ "int");
+}
+
+namespace TestThreadUtils {
+
+static bool gDebug = false;
+static int gAlive, gZombies;
+static int gAllConstructions, gConstructions, gCopyConstructions,
+ gMoveConstructions, gDestructions, gAssignments, gMoves;
+struct Spy {
+ static void ClearActions() {
+ gAllConstructions = gConstructions = gCopyConstructions =
+ gMoveConstructions = gDestructions = gAssignments = gMoves = 0;
+ }
+ static void ClearAll() {
+ ClearActions();
+ gAlive = 0;
+ }
+
+ explicit Spy(int aID) : mID(aID) {
+ ++gAlive;
+ ++gAllConstructions;
+ ++gConstructions;
+ if (gDebug) {
+ printf("Spy[%d@%p]()\n", mID, this);
+ }
+ }
+ Spy(const Spy& o) : mID(o.mID) {
+ ++gAlive;
+ ++gAllConstructions;
+ ++gCopyConstructions;
+ if (gDebug) {
+ printf("Spy[%d@%p](&[%d@%p])\n", mID, this, o.mID, &o);
+ }
+ }
+ Spy(Spy&& o) : mID(o.mID) {
+ o.mID = -o.mID;
+ ++gZombies;
+ ++gAllConstructions;
+ ++gMoveConstructions;
+ if (gDebug) {
+ printf("Spy[%d@%p](&&[%d->%d@%p])\n", mID, this, -o.mID, o.mID, &o);
+ }
+ }
+ ~Spy() {
+ if (mID >= 0) {
+ --gAlive;
+ } else {
+ --gZombies;
+ }
+ ++gDestructions;
+ if (gDebug) {
+ printf("~Spy[%d@%p]()\n", mID, this);
+ }
+ mID = 0;
+ }
+ Spy& operator=(const Spy& o) {
+ ++gAssignments;
+ if (gDebug) {
+ printf("Spy[%d->%d@%p] = &[%d@%p]\n", mID, o.mID, this, o.mID, &o);
+ }
+ mID = o.mID;
+ return *this;
+ };
+ Spy& operator=(Spy&& o) {
+ --gAlive;
+ ++gZombies;
+ ++gMoves;
+ if (gDebug) {
+ printf("Spy[%d->%d@%p] = &&[%d->%d@%p]\n", mID, o.mID, this, o.mID,
+ -o.mID, &o);
+ }
+ mID = o.mID;
+ o.mID = -o.mID;
+ return *this;
+ };
+
+ int mID; // ID given at construction, or negation if was moved from; 0 when
+ // destroyed.
+};
+
+struct ISpyWithISupports : public nsISupports {
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID)
+ NS_IMETHOD_(nsrefcnt) RefCnt() = 0;
+ NS_IMETHOD_(int32_t) ID() = 0;
+};
+NS_DEFINE_STATIC_IID_ACCESSOR(ISpyWithISupports, NS_IFOO_IID)
+struct SpyWithISupports : public ISpyWithISupports, public Spy {
+ private:
+ virtual ~SpyWithISupports() = default;
+
+ public:
+ explicit SpyWithISupports(int aID) : Spy(aID){};
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; }
+ NS_IMETHOD_(int32_t) ID() override { return mID; }
+};
+NS_IMPL_ISUPPORTS(SpyWithISupports, ISpyWithISupports)
+
+class IThreadUtilsObject : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID)
+
+ NS_IMETHOD_(nsrefcnt) RefCnt() = 0;
+ NS_IMETHOD_(int32_t) ID() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IThreadUtilsObject, NS_IFOO_IID)
+
+struct ThreadUtilsObjectNonRefCountedBase {
+ virtual void MethodFromNonRefCountedBase() {}
+};
+
+struct ThreadUtilsObject : public IThreadUtilsObject,
+ public ThreadUtilsObjectNonRefCountedBase {
+ // nsISupports implementation
+ NS_DECL_ISUPPORTS
+
+ // IThreadUtilsObject implementation
+ NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; }
+ NS_IMETHOD_(int32_t) ID() override { return 0; }
+
+ int mCount; // Number of calls + arguments processed.
+ int mA0, mA1, mA2, mA3;
+ Spy mSpy;
+ const Spy* mSpyPtr;
+ ThreadUtilsObject()
+ : mCount(0), mA0(0), mA1(0), mA2(0), mA3(0), mSpy(1), mSpyPtr(nullptr) {}
+
+ private:
+ virtual ~ThreadUtilsObject() = default;
+
+ public:
+ void Test0() { mCount += 1; }
+ void Test1i(int a0) {
+ mCount += 2;
+ mA0 = a0;
+ }
+ void Test2i(int a0, int a1) {
+ mCount += 3;
+ mA0 = a0;
+ mA1 = a1;
+ }
+ void Test3i(int a0, int a1, int a2) {
+ mCount += 4;
+ mA0 = a0;
+ mA1 = a1;
+ mA2 = a2;
+ }
+ void Test4i(int a0, int a1, int a2, int a3) {
+ mCount += 5;
+ mA0 = a0;
+ mA1 = a1;
+ mA2 = a2;
+ mA3 = a3;
+ }
+ void Test1pi(int* ap) {
+ mCount += 2;
+ mA0 = ap ? *ap : -1;
+ }
+ void Test1pci(const int* ap) {
+ mCount += 2;
+ mA0 = ap ? *ap : -1;
+ }
+ void Test1ri(int& ar) {
+ mCount += 2;
+ mA0 = ar;
+ }
+ void Test1rri(int&& arr) {
+ mCount += 2;
+ mA0 = arr;
+ }
+ void Test1upi(mozilla::UniquePtr<int> aup) {
+ mCount += 2;
+ mA0 = aup ? *aup : -1;
+ }
+ void Test1rupi(mozilla::UniquePtr<int>& aup) {
+ mCount += 2;
+ mA0 = aup ? *aup : -1;
+ }
+ void Test1rrupi(mozilla::UniquePtr<int>&& aup) {
+ mCount += 2;
+ mA0 = aup ? *aup : -1;
+ }
+
+ void Test1s(Spy) { mCount += 2; }
+ void Test1ps(Spy*) { mCount += 2; }
+ void Test1rs(Spy&) { mCount += 2; }
+ void Test1rrs(Spy&&) { mCount += 2; }
+ void Test1ups(mozilla::UniquePtr<Spy>) { mCount += 2; }
+ void Test1rups(mozilla::UniquePtr<Spy>&) { mCount += 2; }
+ void Test1rrups(mozilla::UniquePtr<Spy>&&) { mCount += 2; }
+
+ // Possible parameter passing styles:
+ void TestByValue(Spy s) {
+ if (gDebug) {
+ printf("TestByValue(Spy[%d@%p])\n", s.mID, &s);
+ }
+ mSpy = s;
+ };
+ void TestByConstLRef(const Spy& s) {
+ if (gDebug) {
+ printf("TestByConstLRef(Spy[%d@%p]&)\n", s.mID, &s);
+ }
+ mSpy = s;
+ };
+ void TestByRRef(Spy&& s) {
+ if (gDebug) {
+ printf("TestByRRef(Spy[%d@%p]&&)\n", s.mID, &s);
+ }
+ mSpy = std::move(s);
+ };
+ void TestByLRef(Spy& s) {
+ if (gDebug) {
+ printf("TestByLRef(Spy[%d@%p]&)\n", s.mID, &s);
+ }
+ mSpy = s;
+ mSpyPtr = &s;
+ };
+ void TestByPointer(Spy* p) {
+ if (p) {
+ if (gDebug) {
+ printf("TestByPointer(&Spy[%d@%p])\n", p->mID, p);
+ }
+ mSpy = *p;
+ } else {
+ if (gDebug) {
+ printf("TestByPointer(nullptr)\n");
+ }
+ }
+ mSpyPtr = p;
+ };
+ void TestByPointerToConst(const Spy* p) {
+ if (p) {
+ if (gDebug) {
+ printf("TestByPointerToConst(&Spy[%d@%p])\n", p->mID, p);
+ }
+ mSpy = *p;
+ } else {
+ if (gDebug) {
+ printf("TestByPointerToConst(nullptr)\n");
+ }
+ }
+ mSpyPtr = p;
+ };
+};
+
+NS_IMPL_ISUPPORTS(ThreadUtilsObject, IThreadUtilsObject)
+
+class ThreadUtilsRefCountedFinal final {
+ public:
+ ThreadUtilsRefCountedFinal() : m_refCount(0) {}
+ ~ThreadUtilsRefCountedFinal() = default;
+ // 'AddRef' and 'Release' methods with different return types, to verify
+ // that the return type doesn't influence storage selection.
+ long AddRef(void) { return ++m_refCount; }
+ void Release(void) { --m_refCount; }
+
+ private:
+ long m_refCount;
+};
+
+class ThreadUtilsRefCountedBase {
+ public:
+ ThreadUtilsRefCountedBase() : m_refCount(0) {}
+ virtual ~ThreadUtilsRefCountedBase() = default;
+ // 'AddRef' and 'Release' methods with different return types, to verify
+ // that the return type doesn't influence storage selection.
+ virtual void AddRef(void) { ++m_refCount; }
+ virtual MozExternalRefCountType Release(void) { return --m_refCount; }
+
+ private:
+ MozExternalRefCountType m_refCount;
+};
+
+class ThreadUtilsRefCountedDerived : public ThreadUtilsRefCountedBase {};
+
+class ThreadUtilsNonRefCounted {};
+
+} // namespace TestThreadUtils
+
+TEST(ThreadUtils, main)
+{
+ using namespace TestThreadUtils;
+
+ static_assert(!IsParameterStorageClass<int>::value,
+ "'int' should not be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByValue<int>>::value,
+ "StoreCopyPassByValue<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByConstLRef<int>>::value,
+ "StoreCopyPassByConstLRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByLRef<int>>::value,
+ "StoreCopyPassByLRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByRRef<int>>::value,
+ "StoreCopyPassByRRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreRefPassByLRef<int>>::value,
+ "StoreRefPassByLRef<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreConstRefPassByConstLRef<int>>::value,
+ "StoreConstRefPassByConstLRef<int> should be recognized as Storage "
+ "Class");
+ static_assert(
+ IsParameterStorageClass<StoreRefPtrPassByPtr<int>>::value,
+ "StoreRefPtrPassByPtr<int> should be recognized as Storage Class");
+ static_assert(IsParameterStorageClass<StorePtrPassByPtr<int>>::value,
+ "StorePtrPassByPtr<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreConstPtrPassByConstPtr<int>>::value,
+ "StoreConstPtrPassByConstPtr<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByConstPtr<int>>::value,
+ "StoreCopyPassByConstPtr<int> should be recognized as Storage Class");
+ static_assert(
+ IsParameterStorageClass<StoreCopyPassByPtr<int>>::value,
+ "StoreCopyPassByPtr<int> should be recognized as Storage Class");
+
+ RefPtr<ThreadUtilsObject> rpt(new ThreadUtilsObject);
+ int count = 0;
+
+ // Test legacy functions.
+
+ nsCOMPtr<nsIRunnable> r1 =
+ NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt,
+ &ThreadUtilsObject::Test0);
+ r1->Run();
+ EXPECT_EQ(count += 1, rpt->mCount);
+
+ r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt,
+ &ThreadUtilsObject::Test1i, 11);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(11, rpt->mA0);
+
+ // Test calling a method from a non-ref-counted base.
+
+ r1 = NewRunnableMethod(
+ "TestThreadUtils::ThreadUtilsObjectNonRefCountedBase::"
+ "MethodFromNonRefCountedBase",
+ rpt, &ThreadUtilsObject::MethodFromNonRefCountedBase);
+ r1->Run();
+ EXPECT_EQ(count, rpt->mCount);
+
+ // Test variadic function with simple POD arguments.
+
+ r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt,
+ &ThreadUtilsObject::Test0);
+ r1->Run();
+ EXPECT_EQ(count += 1, rpt->mCount);
+
+ static_assert(std::is_same_v<::detail::ParameterStorage<int>::Type,
+ StoreCopyPassByConstLRef<int>>,
+ "detail::ParameterStorage<int>::Type should be "
+ "StoreCopyPassByConstLRef<int>");
+ static_assert(std::is_same_v<
+ ::detail::ParameterStorage<StoreCopyPassByValue<int>>::Type,
+ StoreCopyPassByValue<int>>,
+ "detail::ParameterStorage<StoreCopyPassByValue<int>>::Type "
+ "should be StoreCopyPassByValue<int>");
+
+ r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt,
+ &ThreadUtilsObject::Test1i, 12);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(12, rpt->mA0);
+
+ r1 = NewRunnableMethod<int, int>("TestThreadUtils::ThreadUtilsObject::Test2i",
+ rpt, &ThreadUtilsObject::Test2i, 21, 22);
+ r1->Run();
+ EXPECT_EQ(count += 3, rpt->mCount);
+ EXPECT_EQ(21, rpt->mA0);
+ EXPECT_EQ(22, rpt->mA1);
+
+ r1 = NewRunnableMethod<int, int, int>(
+ "TestThreadUtils::ThreadUtilsObject::Test3i", rpt,
+ &ThreadUtilsObject::Test3i, 31, 32, 33);
+ r1->Run();
+ EXPECT_EQ(count += 4, rpt->mCount);
+ EXPECT_EQ(31, rpt->mA0);
+ EXPECT_EQ(32, rpt->mA1);
+ EXPECT_EQ(33, rpt->mA2);
+
+ r1 = NewRunnableMethod<int, int, int, int>(
+ "TestThreadUtils::ThreadUtilsObject::Test4i", rpt,
+ &ThreadUtilsObject::Test4i, 41, 42, 43, 44);
+ r1->Run();
+ EXPECT_EQ(count += 5, rpt->mCount);
+ EXPECT_EQ(41, rpt->mA0);
+ EXPECT_EQ(42, rpt->mA1);
+ EXPECT_EQ(43, rpt->mA2);
+ EXPECT_EQ(44, rpt->mA3);
+
+ // More interesting types of arguments.
+
+ // Passing a short to make sure forwarding works with an inexact type match.
+ short int si = 11;
+ r1 = NewRunnableMethod<int>("TestThreadUtils::ThreadUtilsObject::Test1i", rpt,
+ &ThreadUtilsObject::Test1i, si);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(si, rpt->mA0);
+
+ // Raw pointer, possible cv-qualified.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int*>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int*>::Type should be StorePtrPassByPtr<int>");
+ static_assert(std::is_same_v<::detail::ParameterStorage<int* const>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int* const>::Type should be "
+ "StorePtrPassByPtr<int>");
+ static_assert(std::is_same_v<::detail::ParameterStorage<int* volatile>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int* volatile>::Type should be "
+ "StorePtrPassByPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int* const volatile>::Type,
+ StorePtrPassByPtr<int>>,
+ "detail::ParameterStorage<int* const volatile>::Type should be "
+ "StorePtrPassByPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int*>::Type::stored_type, int*>,
+ "detail::ParameterStorage<int*>::Type::stored_type should be int*");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int*>::Type::passed_type, int*>,
+ "detail::ParameterStorage<int*>::Type::passed_type should be int*");
+ {
+ int i = 12;
+ r1 = NewRunnableMethod<int*>("TestThreadUtils::ThreadUtilsObject::Test1pi",
+ rpt, &ThreadUtilsObject::Test1pi, &i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // Raw pointer to const.
+ static_assert(std::is_same_v<::detail::ParameterStorage<const int*>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int*>::Type should be "
+ "StoreConstPtrPassByConstPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int* const>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int* const>::Type should be "
+ "StoreConstPtrPassByConstPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int* volatile>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int* volatile>::Type should be "
+ "StoreConstPtrPassByConstPtr<int>");
+ static_assert(std::is_same_v<
+ ::detail::ParameterStorage<const int* const volatile>::Type,
+ StoreConstPtrPassByConstPtr<int>>,
+ "detail::ParameterStorage<const int* const volatile>::Type "
+ "should be StoreConstPtrPassByConstPtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int*>::Type::stored_type,
+ const int*>,
+ "detail::ParameterStorage<const int*>::Type::stored_type should be const "
+ "int*");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<const int*>::Type::passed_type,
+ const int*>,
+ "detail::ParameterStorage<const int*>::Type::passed_type should be const "
+ "int*");
+ {
+ int i = 1201;
+ r1 = NewRunnableMethod<const int*>(
+ "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt,
+ &ThreadUtilsObject::Test1pci, &i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // Raw pointer to copy.
+ static_assert(std::is_same_v<StoreCopyPassByPtr<int>::stored_type, int>,
+ "StoreCopyPassByPtr<int>::stored_type should be int");
+ static_assert(std::is_same_v<StoreCopyPassByPtr<int>::passed_type, int*>,
+ "StoreCopyPassByPtr<int>::passed_type should be int*");
+ {
+ int i = 1202;
+ r1 = NewRunnableMethod<StoreCopyPassByPtr<int>>(
+ "TestThreadUtils::ThreadUtilsObject::Test1pi", rpt,
+ &ThreadUtilsObject::Test1pi, i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // Raw pointer to const copy.
+ static_assert(std::is_same_v<StoreCopyPassByConstPtr<int>::stored_type, int>,
+ "StoreCopyPassByConstPtr<int>::stored_type should be int");
+ static_assert(
+ std::is_same_v<StoreCopyPassByConstPtr<int>::passed_type, const int*>,
+ "StoreCopyPassByConstPtr<int>::passed_type should be const int*");
+ {
+ int i = 1203;
+ r1 = NewRunnableMethod<StoreCopyPassByConstPtr<int>>(
+ "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt,
+ &ThreadUtilsObject::Test1pci, i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // nsRefPtr to pointer.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ StoreRefPtrPassByPtr<SpyWithISupports>>::Type,
+ StoreRefPtrPassByPtr<SpyWithISupports>>,
+ "ParameterStorage<StoreRefPtrPassByPtr<SpyWithISupports>>::Type should "
+ "be StoreRefPtrPassByPtr<SpyWithISupports>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<SpyWithISupports*>::Type,
+ StoreRefPtrPassByPtr<SpyWithISupports>>,
+ "ParameterStorage<SpyWithISupports*>::Type should be "
+ "StoreRefPtrPassByPtr<SpyWithISupports>");
+ static_assert(
+ std::is_same_v<StoreRefPtrPassByPtr<SpyWithISupports>::stored_type,
+ RefPtr<SpyWithISupports>>,
+ "StoreRefPtrPassByPtr<SpyWithISupports>::stored_type should be "
+ "RefPtr<SpyWithISupports>");
+ static_assert(
+ std::is_same_v<StoreRefPtrPassByPtr<SpyWithISupports>::passed_type,
+ SpyWithISupports*>,
+ "StoreRefPtrPassByPtr<SpyWithISupports>::passed_type should be "
+ "SpyWithISupports*");
+ // (more nsRefPtr tests below)
+
+ // nsRefPtr for ref-countable classes that do not derive from ISupports.
+ static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedFinal>::value,
+ "ThreadUtilsRefCountedFinal has AddRef() and Release()");
+ static_assert(
+ std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsRefCountedFinal*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsRefCountedFinal>>,
+ "ParameterStorage<ThreadUtilsRefCountedFinal*>::Type should be "
+ "StoreRefPtrPassByPtr<ThreadUtilsRefCountedFinal>");
+ static_assert(::detail::HasRefCountMethods<ThreadUtilsRefCountedBase>::value,
+ "ThreadUtilsRefCountedBase has AddRef() and Release()");
+ static_assert(
+ std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsRefCountedBase*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsRefCountedBase>>,
+ "ParameterStorage<ThreadUtilsRefCountedBase*>::Type should be "
+ "StoreRefPtrPassByPtr<ThreadUtilsRefCountedBase>");
+ static_assert(
+ ::detail::HasRefCountMethods<ThreadUtilsRefCountedDerived>::value,
+ "ThreadUtilsRefCountedDerived has AddRef() and Release()");
+ static_assert(
+ std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsRefCountedDerived*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsRefCountedDerived>>,
+ "ParameterStorage<ThreadUtilsRefCountedDerived*>::Type should be "
+ "StoreRefPtrPassByPtr<ThreadUtilsRefCountedDerived>");
+
+ static_assert(!::detail::HasRefCountMethods<ThreadUtilsNonRefCounted>::value,
+ "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()");
+ static_assert(!std::is_same_v<
+ ::detail::ParameterStorage<ThreadUtilsNonRefCounted*>::Type,
+ StoreRefPtrPassByPtr<ThreadUtilsNonRefCounted>>,
+ "ParameterStorage<ThreadUtilsNonRefCounted*>::Type should NOT "
+ "be StoreRefPtrPassByPtr<ThreadUtilsNonRefCounted>");
+
+ // Lvalue reference.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type,
+ StoreRefPassByLRef<int>>,
+ "ParameterStorage<int&>::Type should be StoreRefPassByLRef<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type::stored_type,
+ StoreRefPassByLRef<int>::stored_type>,
+ "ParameterStorage<int&>::Type::stored_type should be "
+ "StoreRefPassByLRef<int>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type::stored_type, int&>,
+ "ParameterStorage<int&>::Type::stored_type should be int&");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&>::Type::passed_type, int&>,
+ "ParameterStorage<int&>::Type::passed_type should be int&");
+ {
+ int i = 13;
+ r1 = NewRunnableMethod<int&>("TestThreadUtils::ThreadUtilsObject::Test1ri",
+ rpt, &ThreadUtilsObject::Test1ri, i);
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(i, rpt->mA0);
+ }
+
+ // Rvalue reference -- Actually storing a copy and then moving it.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type,
+ StoreCopyPassByRRef<int>>,
+ "ParameterStorage<int&&>::Type should be StoreCopyPassByRRef<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type::stored_type,
+ StoreCopyPassByRRef<int>::stored_type>,
+ "ParameterStorage<int&&>::Type::stored_type should be "
+ "StoreCopyPassByRRef<int>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type::stored_type, int>,
+ "ParameterStorage<int&&>::Type::stored_type should be int");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<int&&>::Type::passed_type,
+ int&&>,
+ "ParameterStorage<int&&>::Type::passed_type should be int&&");
+ {
+ int i = 14;
+ r1 = NewRunnableMethod<int&&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1rri", rpt,
+ &ThreadUtilsObject::Test1rri, std::move(i));
+ }
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(14, rpt->mA0);
+
+ // Null unique pointer, by semi-implicit store&move with "T&&" syntax.
+ static_assert(std::is_same_v<
+ ::detail::ParameterStorage<mozilla::UniquePtr<int>&&>::Type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>>,
+ "ParameterStorage<UniquePtr<int>&&>::Type should be "
+ "StoreCopyPassByRRef<UniquePtr<int>>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ mozilla::UniquePtr<int>&&>::Type::stored_type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>,
+ "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be "
+ "StoreCopyPassByRRef<UniquePtr<int>>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ mozilla::UniquePtr<int>&&>::Type::stored_type,
+ mozilla::UniquePtr<int>>,
+ "ParameterStorage<UniquePtr<int>&&>::Type::stored_type should be "
+ "UniquePtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<
+ mozilla::UniquePtr<int>&&>::Type::passed_type,
+ mozilla::UniquePtr<int>&&>,
+ "ParameterStorage<UniquePtr<int>&&>::Type::passed_type should be "
+ "UniquePtr<int>&&");
+ {
+ mozilla::UniquePtr<int> upi;
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, std::move(upi));
+ }
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(-1, rpt->mA0);
+ rpt->mA0 = 0;
+
+ // Null unique pointer, by explicit store&move with "StoreCopyPassByRRef<T>"
+ // syntax.
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::stored_type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_"
+ "type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::stored_type,
+ StoreCopyPassByRRef<mozilla::UniquePtr<int>>::stored_type>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_"
+ "type should be StoreCopyPassByRRef<UniquePtr<int>>::stored_type");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::stored_type,
+ mozilla::UniquePtr<int>>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::stored_"
+ "type should be UniquePtr<int>");
+ static_assert(
+ std::is_same_v<::detail::ParameterStorage<StoreCopyPassByRRef<
+ mozilla::UniquePtr<int>>>::Type::passed_type,
+ mozilla::UniquePtr<int>&&>,
+ "ParameterStorage<StoreCopyPassByRRef<UniquePtr<int>>>::Type::passed_"
+ "type should be UniquePtr<int>&&");
+ {
+ mozilla::UniquePtr<int> upi;
+ r1 = NewRunnableMethod<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, std::move(upi));
+ }
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(-1, rpt->mA0);
+
+ // Unique pointer as xvalue.
+ {
+ mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1);
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, std::move(upi));
+ }
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(1, rpt->mA0);
+
+ {
+ mozilla::UniquePtr<int> upi = mozilla::MakeUnique<int>(1);
+ r1 = NewRunnableMethod<StoreCopyPassByRRef<mozilla::UniquePtr<int>>>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, std::move(upi));
+ }
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(1, rpt->mA0);
+
+ // Unique pointer as prvalue.
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt,
+ &ThreadUtilsObject::Test1upi, mozilla::MakeUnique<int>(2));
+ r1->Run();
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(2, rpt->mA0);
+
+ // Unique pointer as lvalue to lref.
+ {
+ mozilla::UniquePtr<int> upi;
+ r1 = NewRunnableMethod<mozilla::UniquePtr<int>&>(
+ "TestThreadUtils::ThreadUtilsObject::Test1rupi", rpt,
+ &ThreadUtilsObject::Test1rupi, upi);
+ // Passed as lref, so Run() must be called while local upi is still alive!
+ r1->Run();
+ }
+ EXPECT_EQ(count += 2, rpt->mCount);
+ EXPECT_EQ(-1, rpt->mA0);
+
+ // Verify copy/move assumptions.
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from lvalue, pass by value\n", __LINE__);
+ }
+ { // Block around nsCOMPtr lifetime.
+ nsCOMPtr<nsIRunnable> r2;
+ { // Block around Spy lifetime.
+ if (gDebug) {
+ printf("%d - Spy s(10)\n", __LINE__);
+ }
+ Spy s(10);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ if (gDebug) {
+ printf(
+ "%d - r2 = "
+ "NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, s)\n",
+ __LINE__);
+ }
+ r2 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt,
+ &ThreadUtilsObject::TestByValue, s);
+ EXPECT_EQ(2, gAlive);
+ EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with Spy s(10)\n", __LINE__);
+ }
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r2->Run();
+ EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call.
+ EXPECT_EQ(10, rpt->mSpy.mID);
+ EXPECT_LE(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from prvalue, pass by value\n", __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf(
+ "%d - r3 = "
+ "NewRunnableMethod<StoreCopyPassByValue<Spy>>(&TestByValue, "
+ "Spy(11))\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r3 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt,
+ &ThreadUtilsObject::TestByValue, Spy(11));
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_LE(1, gMoveConstructions);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r3->Run();
+ EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call.
+ EXPECT_EQ(11, rpt->mSpy.mID);
+ EXPECT_LE(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ { // Store copy from xvalue, pass by value.
+ nsCOMPtr<nsIRunnable> r4;
+ {
+ Spy s(12);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ r4 = NewRunnableMethod<StoreCopyPassByValue<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt,
+ &ThreadUtilsObject::TestByValue, std::move(s));
+ EXPECT_LE(1, gMoveConstructions);
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(1, gZombies);
+ Spy::ClearActions();
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(0, gZombies);
+ Spy::ClearActions();
+ r4->Run();
+ EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call.
+ EXPECT_EQ(12, rpt->mSpy.mID);
+ EXPECT_LE(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+ // Won't test xvalues anymore, prvalues are enough to verify all rvalues.
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from lvalue, pass by const lvalue ref\n",
+ __LINE__);
+ }
+ { // Block around nsCOMPtr lifetime.
+ nsCOMPtr<nsIRunnable> r5;
+ { // Block around Spy lifetime.
+ if (gDebug) {
+ printf("%d - Spy s(20)\n", __LINE__);
+ }
+ Spy s(20);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ if (gDebug) {
+ printf(
+ "%d - r5 = "
+ "NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef,"
+ " s)\n",
+ __LINE__);
+ }
+ r5 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt,
+ &ThreadUtilsObject::TestByConstLRef, s);
+ EXPECT_EQ(2, gAlive);
+ EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with Spy s(20)\n", __LINE__);
+ }
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r5->Run();
+ EXPECT_EQ(0, gCopyConstructions); // No copies in call.
+ EXPECT_EQ(20, rpt->mSpy.mID);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from prvalue, pass by const lvalue ref\n",
+ __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf(
+ "%d - r6 = "
+ "NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(&TestByConstLRef, "
+ "Spy(21))\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r6 = NewRunnableMethod<StoreCopyPassByConstLRef<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt,
+ &ThreadUtilsObject::TestByConstLRef, Spy(21));
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_LE(1, gMoveConstructions);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r6->Run();
+ EXPECT_EQ(0, gCopyConstructions); // No copies in call.
+ EXPECT_EQ(21, rpt->mSpy.mID);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from lvalue, pass by rvalue ref\n", __LINE__);
+ }
+ { // Block around nsCOMPtr lifetime.
+ nsCOMPtr<nsIRunnable> r7;
+ { // Block around Spy lifetime.
+ if (gDebug) {
+ printf("%d - Spy s(30)\n", __LINE__);
+ }
+ Spy s(30);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ if (gDebug) {
+ printf(
+ "%d - r7 = "
+ "NewRunnableMethod<StoreCopyPassByRRef<Spy>>(&TestByRRef, s)\n",
+ __LINE__);
+ }
+ r7 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt,
+ &ThreadUtilsObject::TestByRRef, s);
+ EXPECT_EQ(2, gAlive);
+ EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with Spy s(30)\n", __LINE__);
+ }
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r7->Run();
+ EXPECT_LE(1, gMoves); // Move in call.
+ EXPECT_EQ(30, rpt->mSpy.mID);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(0, gAlive); // Spy inside Test is not counted.
+ EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store copy from prvalue, pass by rvalue ref\n",
+ __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf(
+ "%d - r8 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(&TestByRRef, "
+ "Spy(31))\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r8 = NewRunnableMethod<StoreCopyPassByRRef<Spy>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt,
+ &ThreadUtilsObject::TestByRRef, Spy(31));
+ EXPECT_EQ(1, gAlive);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_LE(1, gMoveConstructions);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r8->Run();
+ EXPECT_LE(1, gMoves); // Move in call.
+ EXPECT_EQ(31, rpt->mSpy.mID);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(0, gAlive); // Spy inside Test is not counted.
+ EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store lvalue ref, pass lvalue ref\n", __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf("%d - Spy s(40)\n", __LINE__);
+ }
+ Spy s(40);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - r9 = NewRunnableMethod<Spy&>(&TestByLRef, s)\n", __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r9 = NewRunnableMethod<Spy&>(
+ "TestThreadUtils::ThreadUtilsObject::TestByLRef", rpt,
+ &ThreadUtilsObject::TestByLRef, s);
+ EXPECT_EQ(0, gAllConstructions);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r9->Run();
+ EXPECT_LE(1, gAssignments); // Assignment from reference in call.
+ EXPECT_EQ(40, rpt->mSpy.mID);
+ EXPECT_EQ(&s, rpt->mSpyPtr);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive); // Spy inside Test is not counted.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store nsRefPtr, pass by pointer\n", __LINE__);
+ }
+ { // Block around nsCOMPtr lifetime.
+ nsCOMPtr<nsIRunnable> r10;
+ SpyWithISupports* ptr = 0;
+ { // Block around RefPtr<Spy> lifetime.
+ if (gDebug) {
+ printf("%d - RefPtr<SpyWithISupports> s(new SpyWithISupports(45))\n",
+ __LINE__);
+ }
+ RefPtr<SpyWithISupports> s(new SpyWithISupports(45));
+ ptr = s.get();
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ if (gDebug) {
+ printf(
+ "%d - r10 = "
+ "NewRunnableMethod<StoreRefPtrPassByPtr<Spy>>(&TestByRRef, "
+ "s.get())\n",
+ __LINE__);
+ }
+ r10 = NewRunnableMethod<StoreRefPtrPassByPtr<SpyWithISupports>>(
+ "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt,
+ &ThreadUtilsObject::TestByPointer, s.get());
+ EXPECT_LE(0, gAllConstructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with RefPtr<Spy> s\n", __LINE__);
+ }
+ }
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r10->Run();
+ EXPECT_LE(1, gAssignments); // Assignment from pointee in call.
+ EXPECT_EQ(45, rpt->mSpy.mID);
+ EXPECT_EQ(ptr, rpt->mSpyPtr);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive); // Spy inside Test is not counted.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store pointer to lvalue, pass by pointer\n", __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf("%d - Spy s(55)\n", __LINE__);
+ }
+ Spy s(55);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - r11 = NewRunnableMethod<Spy*>(&TestByPointer, s)\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r11 = NewRunnableMethod<Spy*>(
+ "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt,
+ &ThreadUtilsObject::TestByPointer, &s);
+ EXPECT_EQ(0, gAllConstructions);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r11->Run();
+ EXPECT_LE(1, gAssignments); // Assignment from pointee in call.
+ EXPECT_EQ(55, rpt->mSpy.mID);
+ EXPECT_EQ(&s, rpt->mSpyPtr);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive); // Spy inside Test is not counted.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+
+ Spy::ClearAll();
+ if (gDebug) {
+ printf("%d - Test: Store pointer to const lvalue, pass by pointer\n",
+ __LINE__);
+ }
+ {
+ if (gDebug) {
+ printf("%d - Spy s(60)\n", __LINE__);
+ }
+ Spy s(60);
+ EXPECT_EQ(1, gConstructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - r12 = NewRunnableMethod<Spy*>(&TestByPointer, s)\n",
+ __LINE__);
+ }
+ nsCOMPtr<nsIRunnable> r12 = NewRunnableMethod<const Spy*>(
+ "TestThreadUtils::ThreadUtilsObject::TestByPointerToConst", rpt,
+ &ThreadUtilsObject::TestByPointerToConst, &s);
+ EXPECT_EQ(0, gAllConstructions);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive);
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - Run()\n", __LINE__);
+ }
+ r12->Run();
+ EXPECT_LE(1, gAssignments); // Assignment from pointee in call.
+ EXPECT_EQ(60, rpt->mSpy.mID);
+ EXPECT_EQ(&s, rpt->mSpyPtr);
+ EXPECT_EQ(0, gDestructions);
+ EXPECT_EQ(1, gAlive); // Spy inside Test is not counted.
+ Spy::ClearActions();
+ if (gDebug) {
+ printf("%d - End block with r\n", __LINE__);
+ }
+ }
+ if (gDebug) {
+ printf("%d - After end block with r\n", __LINE__);
+ }
+ EXPECT_EQ(1, gDestructions);
+ EXPECT_EQ(0, gAlive);
+}
diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp
new file mode 100644
index 0000000000..c4b1217031
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreads.cpp
@@ -0,0 +1,415 @@
+/* -*- 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 <memory>
+#include "nspr.h"
+#include "nsCOMPtr.h"
+#include "nsITargetShutdownTask.h"
+#include "nsIThread.h"
+#include "nsXPCOM.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/SyncRunnable.h"
+#include "gtest/gtest.h"
+
+#ifdef XP_WIN
+# include <windef.h>
+# include <winuser.h>
+#endif
+
+using namespace mozilla;
+
+class nsRunner final : public Runnable {
+ ~nsRunner() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ EXPECT_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) : Runnable("nsRunner"), mNum(num) {}
+
+ protected:
+ int mNum;
+};
+
+TEST(Threads, Main)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRunnable> event = new nsRunner(0);
+ EXPECT_TRUE(event);
+
+ nsCOMPtr<nsIThread> runner;
+ rv = NS_NewNamedThread("TestThreadsMain", getter_AddRefs(runner), event);
+ EXPECT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ EXPECT_NS_SUCCEEDED(rv);
+
+ rv = runner->Shutdown(); // wait for the runner to die before quitting
+ EXPECT_NS_SUCCEEDED(rv);
+
+ PR_Sleep(
+ PR_MillisecondsToInterval(100)); // hopefully the runner will quit here
+}
+
+class nsStressRunner final : public Runnable {
+ public:
+ 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)
+ : Runnable("nsStressRunner"), 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;
+
+TEST(Threads, Stress)
+{
+#if defined(XP_WIN) && defined(MOZ_ASAN) // Easily hits OOM
+ const int loops = 250;
+#else
+ const int loops = 1000;
+#endif
+
+ 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_NewNamedThread("StressRunner", getter_AddRefs(t),
+ new nsStressRunner(k));
+ EXPECT_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 Runnable {
+ public:
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+
+ mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor);
+ lock.Notify();
+
+ return NS_OK;
+ }
+
+ explicit AsyncShutdownPreparer()
+ : Runnable("AsyncShutdownPreparer"), mWasRun(false) {}
+
+ private:
+ virtual ~AsyncShutdownPreparer() { EXPECT_TRUE(mWasRun); }
+
+ protected:
+ bool mWasRun;
+};
+
+class AsyncShutdownWaiter : public Runnable {
+ public:
+ NS_IMETHOD Run() override {
+ EXPECT_FALSE(mWasRun);
+ mWasRun = true;
+
+ nsCOMPtr<nsIThread> t;
+ nsresult rv;
+
+ {
+ mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor);
+
+ rv = NS_NewNamedThread("AsyncShutdownPr", getter_AddRefs(t),
+ new AsyncShutdownPreparer());
+ EXPECT_NS_SUCCEEDED(rv);
+
+ lock.Wait();
+ }
+
+ rv = t->AsyncShutdown();
+ EXPECT_NS_SUCCEEDED(rv);
+
+ return NS_OK;
+ }
+
+ explicit AsyncShutdownWaiter()
+ : Runnable("AsyncShutdownWaiter"), mWasRun(false) {}
+
+ private:
+ virtual ~AsyncShutdownWaiter() { EXPECT_TRUE(mWasRun); }
+
+ protected:
+ bool mWasRun;
+};
+
+class SameThreadSentinel : public Runnable {
+ public:
+ NS_IMETHOD Run() override {
+ mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor);
+ lock.Notify();
+ return NS_OK;
+ }
+
+ SameThreadSentinel() : Runnable("SameThreadSentinel") {}
+
+ private:
+ virtual ~SameThreadSentinel() = default;
+};
+
+TEST(Threads, AsyncShutdown)
+{
+ gAsyncShutdownReadyMonitor = new mozilla::Monitor("gAsyncShutdownReady");
+ gBeginAsyncShutdownMonitor = new mozilla::Monitor("gBeginAsyncShutdown");
+
+ nsCOMPtr<nsIThread> t;
+ nsresult rv;
+
+ {
+ mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor);
+
+ rv = NS_NewNamedThread("AsyncShutdownWt", getter_AddRefs(t),
+ new AsyncShutdownWaiter());
+ EXPECT_NS_SUCCEEDED(rv);
+
+ lock.Wait();
+ }
+
+ NS_DispatchToCurrentThread(new SameThreadSentinel());
+ rv = t->Shutdown();
+ EXPECT_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)
+{
+#if defined(XP_WIN) && defined(MOZ_ASAN) // Easily hits OOM
+ const int loops = 250;
+#else
+ const int loops = 1000;
+#endif
+
+ 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;
+ }
+}
+
+TEST(Threads, GetCurrentSerialEventTarget)
+{
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv =
+ NS_NewNamedThread("Testing Thread", getter_AddRefs(thread),
+ NS_NewRunnableFunction("Testing Thread::check", []() {
+ nsCOMPtr<nsISerialEventTarget> serialEventTarget =
+ GetCurrentSerialEventTarget();
+ nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+ EXPECT_EQ(thread.get(), serialEventTarget.get());
+ }));
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ thread->Shutdown();
+}
+
+namespace {
+
+class TestShutdownTask final : public nsITargetShutdownTask {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit TestShutdownTask(std::function<void()> aCallback)
+ : mCallback(std::move(aCallback)) {}
+
+ void TargetShutdown() override {
+ if (mCallback) {
+ mCallback();
+ }
+ }
+
+ private:
+ ~TestShutdownTask() = default;
+ std::function<void()> mCallback;
+};
+
+NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask)
+
+} // namespace
+
+TEST(Threads, ShutdownTask)
+{
+ auto shutdownTaskRun = std::make_shared<bool>();
+ auto runnableFromShutdownRun = std::make_shared<bool>();
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread));
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ nsCOMPtr<nsITargetShutdownTask> shutdownTask = new TestShutdownTask([=] {
+ EXPECT_TRUE(thread->IsOnCurrentThread());
+
+ ASSERT_FALSE(*shutdownTaskRun);
+ *shutdownTaskRun = true;
+
+ nsCOMPtr<nsITargetShutdownTask> dummyTask = new TestShutdownTask([] {});
+ nsresult rv = thread->RegisterShutdownTask(dummyTask);
+ EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ thread->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] {
+ EXPECT_TRUE(thread->IsOnCurrentThread());
+
+ nsCOMPtr<nsITargetShutdownTask> dummyTask =
+ new TestShutdownTask([] {});
+ nsresult rv = thread->RegisterShutdownTask(dummyTask);
+ EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED);
+
+ ASSERT_FALSE(*runnableFromShutdownRun);
+ *runnableFromShutdownRun = true;
+ })));
+ });
+ MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask));
+
+ ASSERT_FALSE(*shutdownTaskRun);
+ ASSERT_FALSE(*runnableFromShutdownRun);
+
+ RefPtr<mozilla::SyncRunnable> syncWithThread =
+ new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {}));
+ MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread));
+
+ ASSERT_FALSE(*shutdownTaskRun);
+ ASSERT_FALSE(*runnableFromShutdownRun);
+
+ thread->Shutdown();
+
+ ASSERT_TRUE(*shutdownTaskRun);
+ ASSERT_TRUE(*runnableFromShutdownRun);
+}
+
+TEST(Threads, UnregisteredShutdownTask)
+{
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread));
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ nsCOMPtr<nsITargetShutdownTask> shutdownTask =
+ new TestShutdownTask([=] { MOZ_CRASH("should not be run"); });
+
+ MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask));
+
+ RefPtr<mozilla::SyncRunnable> syncWithThread =
+ new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {}));
+ MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread));
+
+ MOZ_ALWAYS_SUCCEEDS(thread->UnregisterShutdownTask(shutdownTask));
+
+ thread->Shutdown();
+}
+
+#if (defined(XP_WIN) || !defined(DEBUG)) && !defined(XP_MACOSX)
+TEST(Threads, OptionsIsUiThread)
+{
+ // On Windows, test that the isUiThread flag results in a GUI thread.
+ // In non-Windows non-debug builds, test that the isUiThread flag is ignored.
+
+ nsCOMPtr<nsIThread> thread;
+ nsIThreadManager::ThreadCreationOptions options;
+ options.isUiThread = true;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread(
+ "Testing Thread", getter_AddRefs(thread), nullptr, options));
+
+ bool isGuiThread = false;
+ auto syncRunnable =
+ MakeRefPtr<SyncRunnable>(NS_NewRunnableFunction(__func__, [&] {
+# ifdef XP_WIN
+ isGuiThread = ::IsGUIThread(false);
+# endif
+ }));
+ MOZ_ALWAYS_SUCCEEDS(syncRunnable->DispatchToThread(thread));
+
+ bool expectGuiThread = false;
+# ifdef XP_WIN
+ expectGuiThread = true;
+# endif
+ EXPECT_EQ(expectGuiThread, isGuiThread);
+
+ thread->Shutdown();
+}
+#endif
diff --git a/xpcom/tests/gtest/TestThreads_mac.mm b/xpcom/tests/gtest/TestThreads_mac.mm
new file mode 100644
index 0000000000..cb6e1c83cc
--- /dev/null
+++ b/xpcom/tests/gtest/TestThreads_mac.mm
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */
+
+#include <Foundation/Foundation.h>
+
+#include "gtest/gtest.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsIThread.h"
+#include "nsIThreadManager.h"
+
+using namespace mozilla;
+
+// Tests whether an nsThread ran a block in its NSRunLoop.
+// ThreadCreationOptions.isUiThread gets set to aIsUiThread.
+// Returns true if a block ran on the NSRunLoop.
+bool UiThreadRunsRunLoop(bool aIsUiThread) {
+ nsCOMPtr<nsIThread> thread;
+ nsIThreadManager::ThreadCreationOptions options;
+ options.isUiThread = aIsUiThread;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread(
+ "Testing Thread", getter_AddRefs(thread), nullptr, options));
+
+ __block bool blockRanInRunLoop = false;
+ {
+ // We scope this so `loop` doesn't live past `thread-Shutdown()` since this
+ // file is compiled without ARC.
+ NSRunLoop* loop = nil;
+ auto syncRunnable =
+ MakeRefPtr<SyncRunnable>(NS_NewRunnableFunction(__func__, [&] {
+ // If the thread doesn't already have an NSRunLoop, one will be
+ // created.
+ loop = NSRunLoop.currentRunLoop;
+ }));
+ MOZ_ALWAYS_SUCCEEDS(syncRunnable->DispatchToThread(thread));
+
+ [loop performBlock:^void() {
+ blockRanInRunLoop = true;
+ }];
+ }
+
+ thread->Shutdown();
+ return blockRanInRunLoop;
+}
+
+TEST(ThreadsMac, OptionsIsUiThread)
+{
+ const bool isUiThread = true;
+ const bool isNoUiThread = false;
+
+ EXPECT_TRUE(UiThreadRunsRunLoop(isUiThread));
+ EXPECT_FALSE(UiThreadRunsRunLoop(isNoUiThread));
+}
diff --git a/xpcom/tests/gtest/TestThrottledEventQueue.cpp b/xpcom/tests/gtest/TestThrottledEventQueue.cpp
new file mode 100644
index 0000000000..64f34ff14f
--- /dev/null
+++ b/xpcom/tests/gtest/TestThrottledEventQueue.cpp
@@ -0,0 +1,613 @@
+/* -*- 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 <functional>
+#include <queue>
+#include <string>
+#include <utility>
+
+#include "MainThreadUtils.h"
+#include "gtest/gtest.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsIRunnable.h"
+#include "nsISerialEventTarget.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "prinrval.h"
+
+using mozilla::CondVar;
+using mozilla::MakeRefPtr;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+using mozilla::ThrottledEventQueue;
+using std::function;
+using std::string;
+
+namespace TestThrottledEventQueue {
+
+// A simple queue of runnables, to serve as the base target of
+// ThrottledEventQueues in tests.
+//
+// This is much simpler than mozilla::TaskQueue, and so better for unit tests.
+// It's about the same as mozilla::EventQueue, but that doesn't implement
+// nsIEventTarget, so it can't be the base target of a ThrottledEventQueue.
+struct RunnableQueue : nsISerialEventTarget {
+ std::queue<nsCOMPtr<nsIRunnable>> runnables;
+
+ bool IsEmpty() { return runnables.empty(); }
+ size_t Length() { return runnables.size(); }
+
+ [[nodiscard]] nsresult Run() {
+ while (!runnables.empty()) {
+ auto runnable = std::move(runnables.front());
+ runnables.pop();
+ nsresult rv = runnable->Run();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // nsIEventTarget methods
+
+ [[nodiscard]] NS_IMETHODIMP Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aFlags) override {
+ MOZ_ALWAYS_TRUE(aFlags == nsIEventTarget::DISPATCH_NORMAL);
+ runnables.push(aRunnable);
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHODIMP DispatchFromScript(nsIRunnable* aRunnable,
+ uint32_t aFlags) override {
+ RefPtr<nsIRunnable> r = aRunnable;
+ return Dispatch(r.forget(), aFlags);
+ }
+
+ NS_IMETHOD_(bool)
+ IsOnCurrentThreadInfallible(void) override { return NS_IsMainThread(); }
+
+ [[nodiscard]] NS_IMETHOD IsOnCurrentThread(bool* retval) override {
+ *retval = IsOnCurrentThreadInfallible();
+ return NS_OK;
+ }
+
+ [[nodiscard]] NS_IMETHODIMP DelayedDispatch(
+ already_AddRefed<nsIRunnable> aEvent, uint32_t aDelay) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask*) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask*) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // nsISupports methods
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ virtual ~RunnableQueue() = default;
+};
+
+NS_IMPL_ISUPPORTS(RunnableQueue, nsIEventTarget, nsISerialEventTarget)
+
+static void Enqueue(nsIEventTarget* target, function<void()>&& aCallable) {
+ nsresult rv = target->Dispatch(
+ NS_NewRunnableFunction("TEQ GTest", std::move(aCallable)));
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+}
+
+} // namespace TestThrottledEventQueue
+
+using namespace TestThrottledEventQueue;
+
+TEST(ThrottledEventQueue, RunnableQueue)
+{
+ string log;
+
+ RefPtr<RunnableQueue> queue = MakeRefPtr<RunnableQueue>();
+ Enqueue(queue, [&]() { log += 'a'; });
+ Enqueue(queue, [&]() { log += 'b'; });
+ Enqueue(queue, [&]() { log += 'c'; });
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(queue->Run());
+ ASSERT_EQ(log, "abc");
+}
+
+TEST(ThrottledEventQueue, SimpleDispatch)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 1");
+
+ Enqueue(throttled, [&]() { log += 'a'; });
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "a");
+
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+}
+
+TEST(ThrottledEventQueue, MixedDispatch)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 2");
+
+ // A ThrottledEventQueue limits its impact on the base target by only queuing
+ // its next event on the base once the prior event has been run. What it
+ // actually queues on the base is a sort of proxy event called an
+ // "executor": the base running the executor draws an event from the
+ // ThrottledEventQueue and runs that. If the ThrottledEventQueue has further
+ // events, it re-queues the executor on the base, effectively "going to the
+ // back of the line".
+
+ // Queue an event on the ThrottledEventQueue. This also queues the "executor"
+ // event on the base.
+ Enqueue(throttled, [&]() { log += 'a'; });
+ ASSERT_EQ(throttled->Length(), 1U);
+ ASSERT_EQ(base->Length(), 1U);
+
+ // Add a second event to the throttled queue. The executor is already queued.
+ Enqueue(throttled, [&]() { log += 'b'; });
+ ASSERT_EQ(throttled->Length(), 2U);
+ ASSERT_EQ(base->Length(), 1U);
+
+ // Add an event directly to the base, after the executor.
+ Enqueue(base, [&]() { log += 'c'; });
+ ASSERT_EQ(throttled->Length(), 2U);
+ ASSERT_EQ(base->Length(), 2U);
+
+ // Run the base target. This runs:
+ // - the executor, which runs the first event from the ThrottledEventQueue,
+ // and re-enqueues itself
+ // - the event queued directly on the base
+ // - the executor again, which runs the second event from the
+ // ThrottledEventQueue.
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "acb");
+
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+}
+
+TEST(ThrottledEventQueue, EnqueueFromRun)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 3");
+
+ // When an event from the throttled queue dispatches a new event directly to
+ // the base target, it is queued after the executor, so the next event from
+ // the throttled queue will run before it.
+ Enqueue(base, [&]() { log += 'a'; });
+ Enqueue(throttled, [&]() {
+ log += 'b';
+ Enqueue(base, [&]() { log += 'c'; });
+ });
+ Enqueue(throttled, [&]() { log += 'd'; });
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "abdc");
+
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+}
+
+TEST(ThrottledEventQueue, RunFromRun)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 4");
+
+ // Running the event queue from within an event (i.e., a nested event loop)
+ // does not stall the ThrottledEventQueue.
+ Enqueue(throttled, [&]() {
+ log += '(';
+ // This should run subsequent events from throttled.
+ ASSERT_NS_SUCCEEDED(base->Run());
+ log += ')';
+ });
+
+ Enqueue(throttled, [&]() { log += 'a'; });
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "(a)");
+
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+}
+
+TEST(ThrottledEventQueue, DropWhileRunning)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+
+ // If we drop the event queue while it still has events, they still run.
+ {
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 5");
+ Enqueue(throttled, [&]() { log += 'a'; });
+ }
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "a");
+}
+
+TEST(ThrottledEventQueue, AwaitIdle)
+{
+ Mutex mutex MOZ_UNANNOTATED("TEQ AwaitIdle");
+ CondVar cond(mutex, "TEQ AwaitIdle");
+
+ string dequeue_await; // mutex
+ bool threadFinished = false; // mutex & cond
+ bool runnableFinished = false; // main thread only
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 6");
+
+ // Put an event in the queue so the AwaitIdle might block.
+ Enqueue(throttled, [&]() { runnableFinished = true; });
+
+ // Create a separate thread that waits for the queue to become idle, and
+ // then takes observable action.
+ nsCOMPtr<nsIRunnable> await = NS_NewRunnableFunction("TEQ AwaitIdle", [&]() {
+ throttled->AwaitIdle();
+ MutexAutoLock lock(mutex);
+ dequeue_await += " await";
+ threadFinished = true;
+ cond.Notify();
+ });
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv =
+ NS_NewNamedThread("TEQ AwaitIdle", getter_AddRefs(thread), await);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // We can't guarantee that the thread has reached the AwaitIdle call, but we
+ // can get pretty close. Either way, it shouldn't affect the behavior of the
+ // test.
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ // Drain the queue.
+ {
+ MutexAutoLock lock(mutex);
+ ASSERT_EQ(dequeue_await, "");
+ dequeue_await += "dequeue";
+ ASSERT_FALSE(threadFinished);
+ }
+ ASSERT_FALSE(runnableFinished);
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_TRUE(runnableFinished);
+
+ // Wait for the thread to finish.
+ {
+ MutexAutoLock lock(mutex);
+ while (!threadFinished) cond.Wait();
+ ASSERT_EQ(dequeue_await, "dequeue await");
+ }
+
+ ASSERT_NS_SUCCEEDED(thread->Shutdown());
+}
+
+TEST(ThrottledEventQueue, AwaitIdleMixed)
+{
+ // Create a separate thread that waits for the queue to become idle, and
+ // then takes observable action.
+ nsCOMPtr<nsIThread> thread;
+ ASSERT_TRUE(NS_SUCCEEDED(
+ NS_NewNamedThread("AwaitIdleMixed", getter_AddRefs(thread))));
+
+ Mutex mutex MOZ_UNANNOTATED("AwaitIdleMixed");
+ CondVar cond(mutex, "AwaitIdleMixed");
+
+ // The following are protected by mutex and cond, above.
+ string log;
+ bool threadStarted = false;
+ bool threadFinished = false;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 7");
+
+ Enqueue(throttled, [&]() {
+ MutexAutoLock lock(mutex);
+ log += 'a';
+ });
+
+ Enqueue(throttled, [&]() {
+ MutexAutoLock lock(mutex);
+ log += 'b';
+ });
+
+ nsCOMPtr<nsIRunnable> await = NS_NewRunnableFunction("AwaitIdleMixed", [&]() {
+ {
+ MutexAutoLock lock(mutex);
+
+ // Note that we are about to begin awaiting. When the main thread sees
+ // this notification, it will begin draining the queue.
+ log += '(';
+ threadStarted = true;
+ cond.Notify();
+ }
+
+ // Wait for the main thread to drain the TEQ.
+ throttled->AwaitIdle();
+
+ {
+ MutexAutoLock lock(mutex);
+
+ // Note that we have finished awaiting.
+ log += ')';
+ threadFinished = true;
+ cond.Notify();
+ }
+ });
+
+ {
+ MutexAutoLock lock(mutex);
+ ASSERT_EQ(log, "");
+ }
+
+ ASSERT_NS_SUCCEEDED(thread->Dispatch(await.forget()));
+
+ // Wait for the thread to be ready to await. We can't be sure it will actually
+ // be blocking before we get around to draining the event queue, but that's
+ // the nature of the API; this test should work even if we drain the queue
+ // before it awaits.
+ {
+ MutexAutoLock lock(mutex);
+ while (!threadStarted) cond.Wait();
+ ASSERT_EQ(log, "(");
+ }
+
+ // Let the queue drain.
+ ASSERT_NS_SUCCEEDED(base->Run());
+
+ {
+ MutexAutoLock lock(mutex);
+ // The first runnable must always finish before AwaitIdle returns. But the
+ // TEQ notifies the condition variable as soon as it dequeues the last
+ // runnable, without waiting for that runnable to complete. So the thread
+ // and the last runnable could run in either order. Or, we might beat the
+ // thread to the mutex.
+ //
+ // (The only combination excluded here is "(a)": the 'b' runnable should
+ // definitely have run.)
+ ASSERT_TRUE(log == "(ab" || log == "(a)b" || log == "(ab)");
+ while (!threadFinished) cond.Wait();
+ ASSERT_TRUE(log == "(a)b" || log == "(ab)");
+ }
+
+ ASSERT_NS_SUCCEEDED(thread->Shutdown());
+}
+
+TEST(ThrottledEventQueue, SimplePauseResume)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 8");
+
+ ASSERT_FALSE(throttled->IsPaused());
+
+ Enqueue(throttled, [&]() { log += 'a'; });
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "a");
+
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
+ ASSERT_TRUE(throttled->IsPaused());
+
+ Enqueue(throttled, [&]() { log += 'b'; });
+
+ ASSERT_EQ(log, "a");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "a");
+
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
+ ASSERT_FALSE(throttled->IsPaused());
+
+ ASSERT_EQ(log, "a");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "ab");
+
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+}
+
+TEST(ThrottledEventQueue, MixedPauseResume)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 9");
+
+ ASSERT_FALSE(throttled->IsPaused());
+
+ Enqueue(base, [&]() { log += 'A'; });
+ Enqueue(throttled, [&]() {
+ log += 'b';
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(throttled->SetIsPaused(true)));
+ });
+ Enqueue(throttled, [&]() { log += 'c'; });
+ Enqueue(base, [&]() { log += 'D'; });
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ // Since the 'b' event paused the throttled queue, 'c' should not have run.
+ // but 'D' was enqueued directly on the base, and should have run.
+ ASSERT_EQ(log, "AbD");
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_FALSE(throttled->IsEmpty());
+ ASSERT_TRUE(throttled->IsPaused());
+
+ Enqueue(base, [&]() { log += 'E'; });
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
+ Enqueue(base, [&]() { log += 'F'; });
+ ASSERT_FALSE(throttled->IsPaused());
+
+ ASSERT_NS_SUCCEEDED(base->Run());
+ // Since we've unpaused, 'c' should be able to run now. The executor should
+ // have been enqueued between 'E' and 'F'.
+ ASSERT_EQ(log, "AbDEcF");
+
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+}
+
+TEST(ThrottledEventQueue, AwaitIdlePaused)
+{
+ Mutex mutex MOZ_UNANNOTATED("AwaitIdlePaused");
+ CondVar cond(mutex, "AwaitIdlePaused");
+
+ string dequeue_await; // mutex
+ bool threadFinished = false; // mutex & cond
+ bool runnableFinished = false; // main thread only
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 10");
+
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
+
+ // Put an event in the queue so the AwaitIdle might block. Since throttled is
+ // paused, this should not enqueue an executor in the base target.
+ Enqueue(throttled, [&]() { runnableFinished = true; });
+ ASSERT_TRUE(base->IsEmpty());
+
+ // Create a separate thread that waits for the queue to become idle, and
+ // then takes observable action.
+ nsCOMPtr<nsIRunnable> await =
+ NS_NewRunnableFunction("AwaitIdlePaused", [&]() {
+ throttled->AwaitIdle();
+ MutexAutoLock lock(mutex);
+ dequeue_await += " await";
+ threadFinished = true;
+ cond.Notify();
+ });
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv =
+ NS_NewNamedThread("AwaitIdlePaused", getter_AddRefs(thread), await);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ // We can't guarantee that the thread has reached the AwaitIdle call, but we
+ // can get pretty close. Either way, it shouldn't affect the behavior of the
+ // test.
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ // The AwaitIdle call should be blocked, even though there is no executor,
+ // because throttled is paused.
+ {
+ MutexAutoLock lock(mutex);
+ ASSERT_EQ(dequeue_await, "");
+ dequeue_await += "dequeue";
+ ASSERT_FALSE(threadFinished);
+ }
+
+ // A paused TEQ contributes no events to its base target. (This is covered by
+ // other tests...)
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_FALSE(throttled->IsEmpty());
+
+ // Resume and drain the queue.
+ ASSERT_FALSE(runnableFinished);
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_TRUE(base->IsEmpty());
+ ASSERT_TRUE(throttled->IsEmpty());
+ ASSERT_TRUE(runnableFinished);
+
+ // Wait for the thread to finish.
+ {
+ MutexAutoLock lock(mutex);
+ while (!threadFinished) cond.Wait();
+ ASSERT_EQ(dequeue_await, "dequeue await");
+ }
+
+ ASSERT_NS_SUCCEEDED(thread->Shutdown());
+}
+
+TEST(ThrottledEventQueue, ExecutorTransitions)
+{
+ string log;
+
+ auto base = MakeRefPtr<RunnableQueue>();
+ RefPtr<ThrottledEventQueue> throttled =
+ ThrottledEventQueue::Create(base, "test queue 11");
+
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
+
+ // Since we're paused, queueing an event on throttled shouldn't queue the
+ // executor on the base target.
+ Enqueue(throttled, [&]() { log += 'a'; });
+ ASSERT_EQ(throttled->Length(), 1U);
+ ASSERT_EQ(base->Length(), 0U);
+
+ // Resuming throttled should create the executor, since throttled is not
+ // empty.
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
+ ASSERT_EQ(throttled->Length(), 1U);
+ ASSERT_EQ(base->Length(), 1U);
+
+ // Pausing can't remove the executor from the base target since we've already
+ // queued it there, but it can ensure that it doesn't do anything.
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "");
+ ASSERT_EQ(throttled->Length(), 1U);
+ ASSERT_EQ(base->Length(), 0U);
+
+ // As before, resuming must create the executor, since throttled is not empty.
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
+ ASSERT_EQ(throttled->Length(), 1U);
+ ASSERT_EQ(base->Length(), 1U);
+
+ ASSERT_EQ(log, "");
+ ASSERT_NS_SUCCEEDED(base->Run());
+ ASSERT_EQ(log, "a");
+ ASSERT_EQ(throttled->Length(), 0U);
+ ASSERT_EQ(base->Length(), 0U);
+
+ // Since throttled is empty, pausing and resuming now should not enqueue an
+ // executor.
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
+ ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
+ ASSERT_EQ(throttled->Length(), 0U);
+ ASSERT_EQ(base->Length(), 0U);
+}
diff --git a/xpcom/tests/gtest/TestTimeStamp.cpp b/xpcom/tests/gtest/TestTimeStamp.cpp
new file mode 100644
index 0000000000..acd8ff575c
--- /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::TimeDuration;
+using mozilla::TimeStamp;
+
+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 0000000000..ea8792e273
--- /dev/null
+++ b/xpcom/tests/gtest/TestTimers.cpp
@@ -0,0 +1,928 @@
+/* -*- 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 "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "prinrval.h"
+#include "prmon.h"
+#include "prthread.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Services.h"
+
+#include "mozilla/Monitor.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/StaticPrefs_timer.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_NewNamedThread("AutoTestThread", 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*() const { 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() = default;
+
+ nsIThread** mThreadPtr;
+ ReentrantMonitor* mReentrantMonitor;
+};
+
+NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback)
+
+class TimerHelper {
+ public:
+ explicit TimerHelper(nsIEventTarget* aTarget)
+ : mStart(TimeStamp::Now()),
+ mTimer(NS_NewTimer(aTarget)),
+ mMonitor(__func__),
+ mTarget(aTarget) {}
+
+ ~TimerHelper() { Cancel(); }
+
+ static void ClosureCallback(nsITimer*, void* aClosure) {
+ reinterpret_cast<TimerHelper*>(aClosure)->Notify();
+ }
+
+ // We do not use nsITimerCallback, because that results in a circular
+ // reference. One of the properties we want from TimerHelper is for the
+ // timer to be canceled when it goes out of scope.
+ void Notify() {
+ MonitorAutoLock lock(mMonitor);
+ EXPECT_TRUE(mTarget->IsOnCurrentThread());
+ TimeDuration elapsed = TimeStamp::Now() - mStart;
+ mStart = TimeStamp::Now();
+ mLastDelay = Some(elapsed.ToMilliseconds());
+ if (mBlockTime) {
+ PR_Sleep(mBlockTime);
+ }
+ mMonitor.Notify();
+ }
+
+ nsresult SetTimer(uint32_t aDelay, uint8_t aType) {
+ Cancel();
+ MonitorAutoLock lock(mMonitor);
+ mStart = TimeStamp::Now();
+ return mTimer->InitWithNamedFuncCallback(
+ ClosureCallback, this, aDelay, aType, "TimerHelper::ClosureCallback");
+ }
+
+ Maybe<uint32_t> Wait(uint32_t aLimitMs) {
+ return WaitAndBlockCallback(aLimitMs, 0);
+ }
+
+ // Waits for callback, and if it occurs within the limit, causes the callback
+ // to block for the specified time. Useful for testing cases where the
+ // callback takes a long time to return.
+ Maybe<uint32_t> WaitAndBlockCallback(uint32_t aLimitMs, uint32_t aBlockTime) {
+ MonitorAutoLock lock(mMonitor);
+ mBlockTime = aBlockTime;
+ TimeStamp start = TimeStamp::Now();
+ while (!mLastDelay.isSome()) {
+ mMonitor.Wait(TimeDuration::FromMilliseconds(aLimitMs));
+ TimeDuration elapsed = TimeStamp::Now() - start;
+ uint32_t elapsedMs = static_cast<uint32_t>(elapsed.ToMilliseconds());
+ if (elapsedMs >= aLimitMs) {
+ break;
+ }
+ aLimitMs -= elapsedMs;
+ start = TimeStamp::Now();
+ }
+ mBlockTime = 0;
+ return std::move(mLastDelay);
+ }
+
+ void Cancel() {
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "~TimerHelper timer cancel"_ns, mTarget,
+ NS_NewRunnableFunction("~TimerHelper timer cancel", [this] {
+ MonitorAutoLock lock(mMonitor);
+ mTimer->Cancel();
+ }));
+ }
+
+ private:
+ TimeStamp mStart;
+ RefPtr<nsITimer> mTimer;
+ mutable Monitor mMonitor MOZ_UNANNOTATED;
+ uint32_t mBlockTime = 0;
+ Maybe<uint32_t> mLastDelay;
+ RefPtr<nsIEventTarget> mTarget;
+};
+
+class SimpleTimerTest : public ::testing::Test {
+ public:
+ std::unique_ptr<TimerHelper> MakeTimer(uint32_t aDelay, uint8_t aType) {
+ std::unique_ptr<TimerHelper> timer(new TimerHelper(mThread));
+ timer->SetTimer(aDelay, aType);
+ return timer;
+ }
+
+ void PauseTimerThread() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(nullptr, "sleep_notification", nullptr);
+ }
+
+ void ResumeTimerThread() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(nullptr, "wake_notification", nullptr);
+ }
+
+ protected:
+ AutoTestThread mThread;
+};
+
+#ifdef XP_MACOSX
+// For some reason, our OS X testers fire timed condition waits _extremely_
+// late (as much as 200ms).
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1726915
+const unsigned kSlowdownFactor = 50;
+#elif XP_WIN
+// Windows also needs some extra leniency, but not nearly as much as our OS X
+// testers
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1729035
+const unsigned kSlowdownFactor = 5;
+#else
+const unsigned kSlowdownFactor = 1;
+#endif
+
+TEST_F(SimpleTimerTest, OneShot) {
+ auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT);
+ auto res = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(res.isSome());
+ ASSERT_LT(*res, 110U * kSlowdownFactor);
+ ASSERT_GT(*res, 95U * kSlowdownFactor);
+}
+
+TEST_F(SimpleTimerTest, TimerWithStoppedTarget) {
+ mThread->Shutdown();
+ auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT);
+ auto res = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_FALSE(res.isSome());
+}
+
+TEST_F(SimpleTimerTest, SlackRepeating) {
+ auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK);
+ auto delay =
+ timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+ // REPEATING_SLACK timers re-schedule with the full duration when the timer
+ // callback completes
+
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 160U * kSlowdownFactor);
+ ASSERT_GT(*delay, 145U * kSlowdownFactor);
+}
+
+TEST_F(SimpleTimerTest, RepeatingPrecise) {
+ auto timer = MakeTimer(100 * kSlowdownFactor,
+ nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+ auto delay =
+ timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+
+ // Delays smaller than the timer's period do not effect the period.
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+
+ // Delays larger than the timer's period should result in the skipping of
+ // firings, but the cadence should remain the same.
+ delay =
+ timer->WaitAndBlockCallback(110 * kSlowdownFactor, 150 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 210U * kSlowdownFactor);
+ ASSERT_GT(*delay, 195U * kSlowdownFactor);
+}
+
+// gtest on 32bit Win7 debug build is unstable and somehow this test
+// makes it even worse.
+#if !defined(XP_WIN) || !defined(DEBUG) || defined(HAVE_64BIT_BUILD)
+
+class FindExpirationTimeState final {
+ public:
+ // We'll offset the timers 10 seconds into the future to assure that they
+ // won't fire
+ const uint32_t kTimerOffset = 10 * 1000;
+ // And we'll set the timers spaced by 5 seconds.
+ const uint32_t kTimerInterval = 5 * 1000;
+ // We'll use 20 timers
+ const uint32_t kNumTimers = 20;
+
+ TimeStamp mBefore;
+ TimeStamp mMiddle;
+
+ std::list<nsCOMPtr<nsITimer>> mTimers;
+
+ ~FindExpirationTimeState() {
+ while (!mTimers.empty()) {
+ nsCOMPtr<nsITimer> t = mTimers.front().get();
+ mTimers.pop_front();
+ t->Cancel();
+ }
+ }
+
+ // Create timers, with aNumLowPriority low priority timers first in the queue
+ void InitTimers(uint32_t aNumLowPriority, uint32_t aType) {
+ // aType is just for readability.
+ MOZ_ASSERT(aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
+ InitTimers(aNumLowPriority, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, nullptr);
+ }
+
+ // Create timers, with aNumDifferentTarget timers with target aTarget first in
+ // the queue
+ void InitTimers(uint32_t aNumDifferentTarget, nsIEventTarget* aTarget) {
+ InitTimers(aNumDifferentTarget, nsITimer::TYPE_ONE_SHOT, aTarget);
+ }
+
+ void InitTimers(uint32_t aNumDifferingTimers, uint32_t aType,
+ nsIEventTarget* aTarget) {
+ do {
+ TimeStamp clearUntil =
+ TimeStamp::Now() + TimeDuration::FromMilliseconds(
+ kTimerOffset + kNumTimers * kTimerInterval);
+
+ // NS_GetTimerDeadlineHintOnCurrentThread returns clearUntil if there are
+ // no pending timers before clearUntil.
+ // Passing 0 ensures that we examine the next timer to fire, regardless
+ // of its thread target. This is important, because lots of the checks
+ // we perform in the test get confused by timers targeted at other
+ // threads.
+ TimeStamp t = NS_GetTimerDeadlineHintOnCurrentThread(clearUntil, 0);
+ if (t >= clearUntil) {
+ break;
+ }
+
+ // Clear whatever random timers there might be pending.
+ uint32_t waitTime = 10;
+ if (t > TimeStamp::Now()) {
+ waitTime = uint32_t((t - TimeStamp::Now()).ToMilliseconds());
+ }
+ PR_Sleep(PR_MillisecondsToInterval(waitTime));
+ } while (true);
+
+ mBefore = TimeStamp::Now();
+ // To avoid getting exactly the same time for a timer and mMiddle, subtract
+ // 50 ms.
+ mMiddle = mBefore +
+ TimeDuration::FromMilliseconds(kTimerOffset +
+ kTimerInterval * kNumTimers / 2) -
+ TimeDuration::FromMilliseconds(50);
+ for (uint32_t i = 0; i < kNumTimers; ++i) {
+ nsCOMPtr<nsITimer> timer = NS_NewTimer();
+ ASSERT_TRUE(timer);
+
+ if (i < aNumDifferingTimers) {
+ if (aTarget) {
+ timer->SetTarget(aTarget);
+ }
+
+ timer->InitWithNamedFuncCallback(
+ &UnusedCallbackFunc, nullptr, kTimerOffset + kTimerInterval * i,
+ aType, "FindExpirationTimeState::InitTimers");
+ } else {
+ timer->InitWithNamedFuncCallback(
+ &UnusedCallbackFunc, nullptr, kTimerOffset + kTimerInterval * i,
+ nsITimer::TYPE_ONE_SHOT, "FindExpirationTimeState::InitTimers");
+ }
+ mTimers.push_front(timer.get());
+ }
+ }
+
+ static void UnusedCallbackFunc(nsITimer* aTimer, void* aClosure) {
+ FAIL() << "Timer shouldn't fire.";
+ }
+};
+
+TEST_F(SimpleTimerTest, FindExpirationTime) {
+ {
+ FindExpirationTimeState state;
+ // 0 low priority timers
+ state.InitTimers(0, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
+ TimeStamp before = state.mBefore;
+ TimeStamp middle = state.mMiddle;
+
+ TimeStamp t;
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+ }
+
+ {
+ FindExpirationTimeState state;
+ // 5 low priority timers
+ state.InitTimers(5, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
+ TimeStamp before = state.mBefore;
+ TimeStamp middle = state.mMiddle;
+
+ TimeStamp t;
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+ }
+
+ {
+ FindExpirationTimeState state;
+ // 15 low priority timers
+ state.InitTimers(15, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY);
+ TimeStamp before = state.mBefore;
+ TimeStamp middle = state.mMiddle;
+
+ TimeStamp t;
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, middle) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, middle) << "Found time should be equal to default";
+ }
+
+ {
+ AutoTestThread testThread;
+ FindExpirationTimeState state;
+ // 5 other targets
+ state.InitTimers(5, static_cast<nsIEventTarget*>(testThread));
+ TimeStamp before = state.mBefore;
+ TimeStamp middle = state.mMiddle;
+
+ TimeStamp t;
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+ }
+
+ {
+ AutoTestThread testThread;
+ FindExpirationTimeState state;
+ // 15 other targets
+ state.InitTimers(15, static_cast<nsIEventTarget*>(testThread));
+ TimeStamp before = state.mBefore;
+ TimeStamp middle = state.mMiddle;
+
+ TimeStamp t;
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, before) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_LT(t, middle) << "Found time should be less than default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, middle) << "Found time should be equal to default";
+
+ t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
+ EXPECT_TRUE(t) << "We should find a time";
+ EXPECT_EQ(t, middle) << "Found time should be equal to default";
+ }
+}
+
+#endif
+
+// Do these _after_ FindExpirationTime; apparently pausing the timer thread
+// schedules minute-long timers, which FindExpirationTime waits out before
+// starting.
+TEST_F(SimpleTimerTest, SleepWakeOneShot) {
+ if (StaticPrefs::timer_ignore_sleep_wake_notifications()) {
+ return;
+ }
+ auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT);
+ PauseTimerThread();
+ auto delay = timer->Wait(200 * kSlowdownFactor);
+ ResumeTimerThread();
+ ASSERT_FALSE(delay.isSome());
+}
+
+TEST_F(SimpleTimerTest, SleepWakeRepeatingSlack) {
+ if (StaticPrefs::timer_ignore_sleep_wake_notifications()) {
+ return;
+ }
+ auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK);
+ PauseTimerThread();
+ auto delay = timer->Wait(200 * kSlowdownFactor);
+ ResumeTimerThread();
+ ASSERT_FALSE(delay.isSome());
+
+ // Timer thread slept for ~200ms, longer than the duration of the timer, so
+ // it should fire pretty much immediately.
+ delay = timer->Wait(10 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 210 * kSlowdownFactor);
+ ASSERT_GT(*delay, 199 * kSlowdownFactor);
+
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+
+ PauseTimerThread();
+ delay = timer->Wait(50 * kSlowdownFactor);
+ ResumeTimerThread();
+ ASSERT_FALSE(delay.isSome());
+
+ // Timer thread only slept for ~50 ms, shorter than the duration of the
+ // timer, so there should be no effect on the timing.
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+}
+
+TEST_F(SimpleTimerTest, SleepWakeRepeatingPrecise) {
+ if (StaticPrefs::timer_ignore_sleep_wake_notifications()) {
+ return;
+ }
+ auto timer = MakeTimer(100 * kSlowdownFactor,
+ nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
+ PauseTimerThread();
+ auto delay = timer->Wait(350 * kSlowdownFactor);
+ ResumeTimerThread();
+ ASSERT_FALSE(delay.isSome());
+
+ // Timer thread slept longer than the duration of the timer, so it should
+ // fire pretty much immediately.
+ delay = timer->Wait(10 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 360U * kSlowdownFactor);
+ ASSERT_GT(*delay, 349U * kSlowdownFactor);
+
+ // After that, we should get back on our original cadence
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 60U * kSlowdownFactor);
+ ASSERT_GT(*delay, 45U * kSlowdownFactor);
+
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+
+ PauseTimerThread();
+ delay = timer->Wait(50 * kSlowdownFactor);
+ ResumeTimerThread();
+ ASSERT_FALSE(delay.isSome());
+
+ // Timer thread only slept for ~50 ms, shorter than the duration of the
+ // timer, so there should be no effect on the timing.
+ delay = timer->Wait(110 * kSlowdownFactor);
+ ASSERT_TRUE(delay.isSome());
+ ASSERT_LT(*delay, 110U * kSlowdownFactor);
+ ASSERT_GT(*delay, 95U * kSlowdownFactor);
+}
+
+#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)
+ : mozilla::Runnable("FuzzTestThreadState::StartRunnable"),
+ 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() {
+ nsCOMPtr<nsITimer> timer =
+ NS_NewTimer(static_cast<nsIEventTarget*>(mThread.get()));
+ MOZ_RELEASE_ASSERT(timer, "Failed to create timer.");
+
+ 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));
+ }
+ }
+}
+
+TEST(Timers, ClosureCallback)
+{
+ AutoCreateAndDestroyReentrantMonitor newMon;
+ ASSERT_TRUE(newMon);
+
+ AutoTestThread testThread;
+ ASSERT_TRUE(testThread);
+
+ nsIThread* notifiedThread = nullptr;
+
+ nsCOMPtr<nsITimer> timer;
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(timer),
+ [&](nsITimer*) {
+ nsCOMPtr<nsIThread> current(do_GetCurrentThread());
+
+ ReentrantMonitorAutoEnter mon(*newMon);
+ ASSERT_FALSE(notifiedThread);
+ notifiedThread = current;
+ mon.Notify();
+ },
+ 50, nsITimer::TYPE_ONE_SHOT, "(test) Timers.ClosureCallback", testThread);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ReentrantMonitorAutoEnter mon(*newMon);
+ while (!notifiedThread) {
+ mon.Wait();
+ }
+ ASSERT_EQ(notifiedThread, testThread);
+}
+
+static void SetTime(nsITimer* aTimer, void* aClosure) {
+ *static_cast<TimeStamp*>(aClosure) = TimeStamp::Now();
+}
+
+TEST(Timers, HighResFuncCallback)
+{
+ TimeStamp first;
+ TimeStamp second;
+ TimeStamp third;
+ nsCOMPtr<nsITimer> t1 = NS_NewTimer(GetCurrentSerialEventTarget());
+ nsCOMPtr<nsITimer> t2 = NS_NewTimer(GetCurrentSerialEventTarget());
+ nsCOMPtr<nsITimer> t3 = NS_NewTimer(GetCurrentSerialEventTarget());
+
+ // Reverse order, since if the timers are not high-res we'd end up
+ // out-of-order.
+ MOZ_ALWAYS_SUCCEEDS(t3->InitHighResolutionWithNamedFuncCallback(
+ &SetTime, &third, TimeDuration::FromMicroseconds(300),
+ nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::third"));
+ MOZ_ALWAYS_SUCCEEDS(t2->InitHighResolutionWithNamedFuncCallback(
+ &SetTime, &second, TimeDuration::FromMicroseconds(200),
+ nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::second"));
+ MOZ_ALWAYS_SUCCEEDS(t1->InitHighResolutionWithNamedFuncCallback(
+ &SetTime, &first, TimeDuration::FromMicroseconds(100),
+ nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::first"));
+
+ SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
+ "TestTimers::HighResFuncCallback"_ns,
+ [&] { return !first.IsNull() && !second.IsNull() && !third.IsNull(); });
+}
diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp
new file mode 100644
index 0000000000..1457b82fff
--- /dev/null
+++ b/xpcom/tests/gtest/TestTokenizer.cpp
@@ -0,0 +1,1447 @@
+/* -*- 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;
+
+template <typename Char>
+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(
+ nsLiteralCString("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("test123 ,15 \t*\r\n%xx,-15\r\r"_ns);
+
+ 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, Main16)
+{
+ Tokenizer16::Token t;
+
+ // Synthetic code-specific test
+
+ Tokenizer16 p(u"test123 ,15 \t*\r\n%xx,-15\r\r"_ns);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_WORD);
+ EXPECT_TRUE(t.AsString() == u"test123"_ns);
+
+ Tokenizer16::Token u;
+ EXPECT_FALSE(p.Check(u));
+
+ EXPECT_FALSE(p.CheckChar('!'));
+
+ EXPECT_FALSE(p.Check(Tokenizer16::Token::Number(123)));
+
+ EXPECT_TRUE(p.CheckWhite());
+
+ EXPECT_TRUE(p.CheckChar(','));
+
+ EXPECT_TRUE(p.Check(Tokenizer16::Token::Number(15)));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Check(Tokenizer16::Token::Number(15)));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::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(Tokenizer16::EXCLUDE_LAST);
+
+ EXPECT_TRUE(p.CheckChar(IsOperator));
+
+ p.Rollback();
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == '*');
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == '%');
+
+ nsAutoString claim;
+ p.Claim(claim, Tokenizer16::EXCLUDE_LAST);
+ EXPECT_TRUE(claim == u"*\r\n"_ns);
+ p.Claim(claim, Tokenizer16::INCLUDE_LAST);
+ EXPECT_TRUE(claim == u"*\r\n%"_ns);
+
+ p.Rollback();
+ EXPECT_TRUE(p.CheckChar('%'));
+
+ p.Record(Tokenizer16::INCLUDE_LAST);
+
+ EXPECT_FALSE(p.CheckWord(u"xy"_ns));
+
+ EXPECT_TRUE(p.CheckWord(u"xx"_ns));
+
+ p.Claim(claim, Tokenizer16::INCLUDE_LAST);
+ EXPECT_TRUE(claim == u"%xx"_ns);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR);
+ EXPECT_TRUE(t.AsChar() == ',');
+
+ EXPECT_TRUE(p.CheckChar('-'));
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_INTEGER);
+ EXPECT_TRUE(t.AsInteger() == 15);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL);
+
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOF);
+
+ EXPECT_FALSE(p.Next(t));
+
+ p.Rollback();
+ EXPECT_TRUE(p.Next(t));
+ EXPECT_TRUE(t.Type() == Tokenizer16::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("test123"_ns);
+
+ EXPECT_TRUE(p.CheckWord("test123"));
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, EndingAfterNumber)
+{
+ // An end handling after a number
+
+ Tokenizer p("123"_ns);
+
+ EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123)));
+ EXPECT_TRUE(p.CheckEOF());
+}
+
+TEST(Tokenizer, BadInteger)
+{
+ Tokenizer::Token t;
+
+ // A bad integer test
+
+ Tokenizer p("189234891274981758617846178651647620587135"_ns);
+
+ 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("blue velvet"_ns);
+
+ 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("a b"_ns);
+
+ while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR) {
+ ;
+ }
+ EXPECT_TRUE(p1.HasFailed());
+
+ Tokenizer p2("a b ?!c"_ns);
+
+ 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("test"_ns);
+ 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("test-custom*words and\tdefault-whites"_ns, 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("test, custom,whites"_ns, ", ");
+ 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("test, custom, whites-and#word-chars"_ns, ",", "-#");
+ 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(MOZ_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(MOZ_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(MOZ_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(MOZ_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("test"_ns), f));
+ EXPECT_TRUE(f == "Hello;");
+ p.Rollback();
+
+ EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word("test"_ns), 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)
+{
+ using Token = IncrementalTokenizer::Token;
+
+ int test = 0;
+ IncrementalTokenizer i(
+ [&](Token const& t, IncrementalTokenizer& i) -> nsresult {
+ switch (++test) {
+ case 1:
+ EXPECT_TRUE(t.Equals(Token::Word("test1"_ns)));
+ break;
+ case 2:
+ EXPECT_TRUE(t.Equals(Token::Char(',')));
+ break;
+ case 3:
+ EXPECT_TRUE(t.Equals(Token::Word("test2"_ns)));
+ 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("test3"_ns)));
+ break;
+ case 8:
+ EXPECT_TRUE(t.Equals(Token::EndOfFile()));
+ break;
+ }
+
+ return NS_OK;
+ });
+
+ constexpr auto input = "test1,test2,,,test3"_ns;
+ const auto* cur = input.BeginReading();
+ const 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)
+{
+ using Token = IncrementalTokenizer::Token;
+
+ int test = 0;
+ IncrementalTokenizer i(
+ [&](Token const& t, IncrementalTokenizer& i) -> nsresult {
+ switch (++test) {
+ case 1:
+ EXPECT_TRUE(t.Equals(Token::Word("test1"_ns)));
+ break;
+ case 2:
+ EXPECT_TRUE(t.Equals(Token::Char(',')));
+ break;
+ case 3:
+ EXPECT_TRUE(t.Equals(Token::Word("test2"_ns)));
+ i.Rollback(); // so that we get the token again
+ break;
+ case 4:
+ EXPECT_TRUE(t.Equals(Token::Word("test2"_ns)));
+ 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("test3"_ns)));
+ break;
+ case 9:
+ EXPECT_TRUE(t.Equals(Token::EndOfFile()));
+ break;
+ }
+
+ return NS_OK;
+ });
+
+ constexpr auto input = "test1,test2,,,test3"_ns;
+ const auto* cur = input.BeginReading();
+ const 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)
+{
+ using Token = IncrementalTokenizer::Token;
+
+ int test = 0;
+ IncrementalTokenizer i(
+ [&](Token const& t, IncrementalTokenizer& i) -> nsresult {
+ Token t2;
+ switch (++test) {
+ case 1:
+ EXPECT_TRUE(t.Equals(Token::Word("a"_ns)));
+ 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("bb"_ns)));
+ } 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("c"_ns)));
+ return NS_ERROR_FAILURE;
+ default:
+ EXPECT_TRUE(false);
+ break;
+ }
+
+ return NS_OK;
+ });
+
+ constexpr auto input = "a bb,c"_ns;
+ const auto* cur = input.BeginReading();
+ const 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)
+{
+ using Token = IncrementalTokenizer::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("bla"_ns)));
+ break;
+ case 3:
+ EXPECT_TRUE(t.Equals(Token::EndOfFile()));
+ break;
+ }
+
+ return NS_OK;
+ },
+ nullptr, "-");
+
+ custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE);
+ i.FeedInput("some-"_ns);
+ EXPECT_TRUE(test == 0);
+ i.FeedInput("tes"_ns);
+ EXPECT_TRUE(test == 0);
+ i.FeedInput("tbla"_ns);
+ EXPECT_TRUE(test == 1);
+ i.FinishInput();
+ EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalCustomRaw)
+{
+ using Token = IncrementalTokenizer::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);
+
+ constexpr auto input = "test1,test2!,,test3test2tes"_ns;
+ const auto* cur = input.BeginReading();
+ const 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)
+{
+ using Token = IncrementalTokenizer::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);
+
+ constexpr auto input = "custom1custom1"_ns;
+ i.FeedInput(input);
+ EXPECT_TRUE(test == 1);
+ i.FinishInput();
+ EXPECT_TRUE(test == 3);
+}
+
+TEST(Tokenizer, IncrementalBuffering1)
+{
+ using Token = IncrementalTokenizer::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("01234"_ns);
+ EXPECT_TRUE(test == 1);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("012"));
+
+ i.FeedInput("5"_ns);
+ EXPECT_TRUE(test == 1);
+ i.FeedInput("6789aa"_ns);
+ EXPECT_TRUE(test == 2);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("3456789"));
+
+ i.FeedInput("aqwert"_ns);
+ EXPECT_TRUE(test == 4);
+ EXPECT_TRUE(observedFragment.EqualsLiteral("qwe"));
+
+ i.FinishInput();
+ EXPECT_TRUE(test == 6);
+}
+
+TEST(Tokenizer, IncrementalBuffering2)
+{
+ using Token = IncrementalTokenizer::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("01234"_ns);
+ EXPECT_TRUE(test == 0);
+ i.FeedInput("5"_ns);
+ EXPECT_TRUE(test == 1);
+ i.FeedInput("6789aa"_ns);
+ EXPECT_TRUE(test == 2);
+ i.FeedInput("aqwert"_ns);
+ EXPECT_TRUE(test == 4);
+ i.FinishInput();
+ EXPECT_TRUE(test == 6);
+}
+
+TEST(Tokenizer, RecordAndReadUntil)
+{
+ Tokenizer t("aaaa,bbbb");
+ t.SkipWhites();
+ nsDependentCSubstring subject;
+
+ EXPECT_TRUE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject));
+ EXPECT_FALSE(t.CheckChar(','));
+ EXPECT_TRUE(subject.Length() == 4);
+ EXPECT_TRUE(subject == "aaaa");
+
+ EXPECT_FALSE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject));
+ EXPECT_TRUE(subject.Length() == 4);
+ EXPECT_TRUE(subject == "bbbb");
+
+ EXPECT_FALSE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject));
+ EXPECT_TRUE(subject.Length() == 0);
+
+ EXPECT_TRUE(t.CheckEOF());
+}
+
+TEST(Tokenizer, ReadIntegers)
+{
+ // Make sure that adding dash (the 'minus' sign) as an additional char
+ // doesn't break reading negative numbers.
+ Tokenizer t("100,-100,200,-200,4294967295,-4294967295,-2147483647", nullptr,
+ "-");
+
+ uint32_t unsigned_value32;
+ int32_t signed_value32;
+ int64_t signed_value64;
+
+ // "100,"
+ EXPECT_TRUE(t.ReadInteger(&unsigned_value32));
+ EXPECT_TRUE(unsigned_value32 == 100);
+ EXPECT_TRUE(t.CheckChar(','));
+
+ // "-100,"
+ EXPECT_FALSE(t.ReadInteger(&unsigned_value32));
+ EXPECT_FALSE(t.CheckChar(','));
+
+ EXPECT_TRUE(t.ReadSignedInteger(&signed_value32));
+ EXPECT_TRUE(signed_value32 == -100);
+ EXPECT_TRUE(t.CheckChar(','));
+
+ // "200,"
+ EXPECT_TRUE(t.ReadSignedInteger(&signed_value32));
+ EXPECT_TRUE(signed_value32 == 200);
+ EXPECT_TRUE(t.CheckChar(','));
+
+ // "-200,"
+ EXPECT_TRUE(t.ReadSignedInteger(&signed_value32));
+ EXPECT_TRUE(signed_value32 == -200);
+ EXPECT_TRUE(t.CheckChar(','));
+
+ // "4294967295,"
+ EXPECT_FALSE(t.ReadSignedInteger(&signed_value32));
+ EXPECT_FALSE(t.CheckChar(','));
+
+ EXPECT_TRUE(t.ReadInteger(&unsigned_value32));
+ EXPECT_TRUE(unsigned_value32 == 4294967295UL);
+ EXPECT_TRUE(t.CheckChar(','));
+
+ // "-4294967295,"
+ EXPECT_FALSE(t.ReadSignedInteger(&signed_value32));
+ EXPECT_FALSE(t.CheckChar(','));
+
+ EXPECT_FALSE(t.ReadInteger(&unsigned_value32));
+ EXPECT_FALSE(t.CheckChar(','));
+
+ EXPECT_TRUE(t.ReadSignedInteger(&signed_value64));
+ EXPECT_TRUE(signed_value64 == -4294967295LL);
+ EXPECT_TRUE(t.CheckChar(','));
+
+ // "-2147483647"
+ EXPECT_FALSE(t.ReadInteger(&unsigned_value32));
+ EXPECT_FALSE(t.CheckChar(','));
+
+ EXPECT_TRUE(t.ReadSignedInteger(&signed_value32));
+ EXPECT_TRUE(signed_value32 == -2147483647L);
+ EXPECT_TRUE(t.CheckEOF());
+}
+
+TEST(Tokenizer, CheckPhrase)
+{
+ Tokenizer t("foo bar baz");
+
+ EXPECT_TRUE(t.CheckPhrase("foo "));
+
+ EXPECT_FALSE(t.CheckPhrase("barr"));
+ EXPECT_FALSE(t.CheckPhrase("BAR BAZ"));
+ EXPECT_FALSE(t.CheckPhrase("bar baz "));
+ EXPECT_FALSE(t.CheckPhrase("b"));
+ EXPECT_FALSE(t.CheckPhrase("ba"));
+ EXPECT_FALSE(t.CheckPhrase("??"));
+
+ EXPECT_TRUE(t.CheckPhrase("bar baz"));
+
+ t.Rollback();
+ EXPECT_TRUE(t.CheckPhrase("bar"));
+ EXPECT_TRUE(t.CheckPhrase(" baz"));
+
+ t.Rollback();
+ EXPECT_FALSE(t.CheckPhrase("\tbaz"));
+ EXPECT_TRUE(t.CheckPhrase(" baz"));
+ EXPECT_TRUE(t.CheckEOF());
+}
diff --git a/xpcom/tests/gtest/TestUTF.cpp b/xpcom/tests/gtest/TestUTF.cpp
new file mode 100644
index 0000000000..cb574aa855
--- /dev/null
+++ b/xpcom/tests/gtest/TestUTF.cpp
@@ -0,0 +1,264 @@
+/* -*- 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 "nsUTF8Utils.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("string "_ns + str8));
+
+ nsString tmp16(u"string "_ns);
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(u"string "_ns + 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("string "_ns + 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(u"string "_ns);
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+TEST(UTF, Malformed8)
+{
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsDependentString str16(Malformed8Strings[i].m16);
+ nsDependentCString str8(Malformed8Strings[i].m8);
+
+ EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16));
+
+ nsString tmp16(u"string "_ns);
+ AppendUTF8toUTF16(str8, tmp16);
+ EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16));
+
+ EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0);
+ }
+}
+
+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(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u);
+ EXPECT_TRUE(err);
+ }
+
+ for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) {
+ nsDependentCString str8(Malformed8Strings[i].m8);
+ bool err;
+ EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u);
+ EXPECT_TRUE(err);
+ }
+}
+
+/**
+ * This tests the handling of a non-ascii character at various locations in a
+ * UTF-16 string that is being converted to UTF-8.
+ */
+static 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 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);
+}
+
+TEST(UTF, UTF8CharEnumerator)
+{
+ const char* p =
+ "\x61\xC0\xC2\xC2\x80\xE0\x80\x80\xE0\xA0\x80\xE1\x80\x80\xED\xBF\xBF\xED"
+ "\x9F\xBF\xEE\x80\x80\xEE\x80\xFF\xF0\x90\x80\x80\xF0\x80\x80\x80\xF1\x80"
+ "\x80\x80\xF4\x8F\xBF\xF4\x8F\xBF\xBF\xF4\xBF\xBF\xBF";
+ const char* end = p + 49;
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0061U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0080U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0800U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x1000U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xD7FFU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xE000U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x10000U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x40000U);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x10FFFFU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+ p = "\xC2\xB6";
+ end = p + 1;
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+ p = "\xE2\x98\x83";
+ end = p + 2;
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+ p = "\xF0\x9F\x92\xA9";
+ end = p + 2;
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+ p = "\xF0\x9F\x92\xA9";
+ end = p + 3;
+ EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+}
+
+TEST(UTF, UTF16CharEnumerator)
+{
+ const char16_t* p = u"\u0061\U0001F4A9";
+ const char16_t* end = p + 3;
+ EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x0061U);
+ EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x1F4A9U);
+ EXPECT_EQ(p, end);
+ const char16_t loneHigh = 0xD83D;
+ p = &loneHigh;
+ end = p + 1;
+ EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+ const char16_t loneLow = 0xDCA9;
+ p = &loneLow;
+ end = p + 1;
+ EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(p, end);
+ const char16_t loneHighStr[] = {0xD83D, 0x0061};
+ p = loneHighStr;
+ end = p + 2;
+ EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU);
+ EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x0061U);
+ EXPECT_EQ(p, end);
+}
+
+} // namespace TestUTF
diff --git a/xpcom/tests/gtest/TestVariant.cpp b/xpcom/tests/gtest/TestVariant.cpp
new file mode 100644
index 0000000000..8aec8840c6
--- /dev/null
+++ b/xpcom/tests/gtest/TestVariant.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */
+
+#include <math.h>
+#include "nsVariant.h"
+#include "gtest/gtest.h"
+
+TEST(Variant, DoubleNaN)
+{
+ nsDiscriminatedUnion du;
+ du.SetFromDouble(NAN);
+
+ uint8_t ui8;
+ EXPECT_EQ(du.ConvertToInt8(&ui8), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ int16_t i16;
+ EXPECT_EQ(du.ConvertToInt16(&i16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ int32_t i32;
+ EXPECT_EQ(du.ConvertToInt32(&i32), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ int64_t i64;
+ EXPECT_EQ(du.ConvertToInt64(&i64), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ EXPECT_EQ(du.ConvertToUint8(&ui8), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ uint16_t ui16;
+ EXPECT_EQ(du.ConvertToUint16(&ui16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ uint32_t ui32;
+ EXPECT_EQ(du.ConvertToUint32(&ui32), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ uint64_t ui64;
+ EXPECT_EQ(du.ConvertToUint64(&ui64), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ float f = 0.0f;
+ EXPECT_EQ(du.ConvertToFloat(&f), NS_OK);
+ EXPECT_TRUE(isnan(f));
+
+ double d = 0.0;
+ EXPECT_EQ(du.ConvertToDouble(&d), NS_OK);
+ EXPECT_TRUE(isnan(d));
+
+ bool b = true;
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_FALSE(b);
+
+ char c;
+ EXPECT_EQ(du.ConvertToChar(&c), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ char16_t c16;
+ EXPECT_EQ(du.ConvertToWChar(&c16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+
+ nsID id = {};
+ EXPECT_EQ(du.ConvertToID(&id), NS_ERROR_CANNOT_CONVERT_DATA);
+
+ nsAutoString string;
+ EXPECT_EQ(du.ConvertToAString(string), NS_OK);
+ EXPECT_EQ(string, u"NaN"_ns);
+
+ nsAutoCString utf8string;
+ EXPECT_EQ(du.ConvertToAUTF8String(utf8string), NS_OK);
+ EXPECT_EQ(utf8string, "NaN"_ns);
+
+ nsAutoCString autocstring;
+ EXPECT_EQ(du.ConvertToACString(autocstring), NS_OK);
+ EXPECT_EQ(autocstring, "NaN"_ns);
+
+ char* chars = nullptr;
+ EXPECT_EQ(du.ConvertToString(&chars), NS_OK);
+ EXPECT_STREQ(chars, "NaN");
+ free(chars);
+
+ char16_t* wchars = nullptr;
+ EXPECT_EQ(du.ConvertToWString(&wchars), NS_OK);
+ {
+ // gtest doesn't seem to support EXPECT_STREQ on char16_t, so convert
+ // to Gecko strings to do the comparison.
+ nsDependentString wstring(wchars);
+ EXPECT_EQ(wstring, u"NaN"_ns);
+ }
+ free(wchars);
+
+ chars = nullptr;
+ uint32_t size = 0;
+ EXPECT_EQ(du.ConvertToStringWithSize(&size, &chars), NS_OK);
+ EXPECT_STREQ(chars, "NaN");
+ EXPECT_EQ(size, 3u);
+ free(chars);
+
+ wchars = nullptr;
+ size = 0;
+ EXPECT_EQ(du.ConvertToWStringWithSize(&size, &wchars), NS_OK);
+ {
+ // gtest doesn't seem to support EXPECT_STREQ on char16_t, so convert
+ // to Gecko strings to do the comparison.
+ nsDependentString wstring(wchars);
+ EXPECT_EQ(wstring, u"NaN"_ns);
+ EXPECT_EQ(size, 3u);
+ }
+ free(wchars);
+
+ nsISupports* isupports;
+ EXPECT_EQ(du.ConvertToISupports(&isupports), NS_ERROR_CANNOT_CONVERT_DATA);
+
+ nsIID* idp;
+ void* iface;
+ EXPECT_EQ(du.ConvertToInterface(&idp, &iface), NS_ERROR_CANNOT_CONVERT_DATA);
+
+ uint16_t type;
+ nsIID iid;
+ uint32_t count;
+ void* array;
+ EXPECT_EQ(du.ConvertToArray(&type, &iid, &count, &array),
+ NS_ERROR_CANNOT_CONVERT_DATA);
+}
+
+TEST(Variant, Bool)
+{
+ nsDiscriminatedUnion du;
+ bool b = false;
+
+ du.SetFromInt64(12);
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_TRUE(b);
+
+ b = true;
+ du.SetFromInt64(0);
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_FALSE(b);
+
+ b = true;
+ du.SetFromDouble(1.25);
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_TRUE(b);
+
+ b = true;
+ du.SetFromDouble(0.0);
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_FALSE(b);
+
+ b = true;
+ du.SetFromDouble(-0.0);
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_FALSE(b);
+
+ // This is also checked in the previous test, but I'm including it
+ // here for completeness.
+ b = true;
+ du.SetFromDouble(NAN);
+ EXPECT_EQ(du.ConvertToBool(&b), NS_OK);
+ EXPECT_FALSE(b);
+}
diff --git a/xpcom/tests/gtest/UTFStrings.h b/xpcom/tests/gtest/UTFStrings.h
new file mode 100644
index 0000000000..9613da32a1
--- /dev/null
+++ b/xpcom/tests/gtest/UTFStrings.h
@@ -0,0 +1,130 @@
+/* -*- 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, 0xFFFD, 'b'}, {'a', char(0xC0), char(0x80), 'b'}},
+ {{0xFFFD, 0xFFFD, 0x80}, {char(0xC1), char(0xBF), char(0xC2), char(0x80)}},
+ {{0xFFFD, 0xFFFD}, {char(0xC1), char(0xBF)}},
+ {{0xFFFD, 0xFFFD, 0xFFFD, 'x', 0x0800},
+ {char(0xE0), char(0x80), char(0x80), 'x', char(0xE0), char(0xA0),
+ char(0x80)}},
+ {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD},
+ {char(0xF0), char(0x80), char(0x80), char(0x80), 'x', char(0xF0),
+ char(0x80), char(0x8F), char(0x80)}},
+ {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD},
+ {char(0xF4), char(0x90), char(0x80), char(0x80), char(0xF7), char(0xBF),
+ char(0xBF), char(0xBF)}},
+ {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xD800, 0xDC00, 0xFFFD, 0xFFFD,
+ 0xFFFD, 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, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 0xFFFD},
+ {char(0xF8), char(0x80), char(0x80), char(0x80), char(0x80), 'x',
+ char(0xF8), char(0x88), char(0x80), char(0x80), char(0x80)}},
+ {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 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, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD,
+ 0xFFFD, 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)}},
+};
+
+static const UTFStringsStringPair Malformed8Strings[] = {
+ {{0xFFFD}, {char(0x80)}},
+ {{'a', 0xFFFD, 'c'}, {'a', char(0xC8), 'c'}},
+ {{'a', 0xFFFD}, {'a', char(0xC8)}},
+ {{'a', 0xFFFD, 'c'}, {'a', char(0xE8), 'c'}},
+ {{'a', 0xFFFD, 'c'}, {'a', char(0xE8), char(0x80), 'c'}},
+ {{'a', 0xFFFD}, {'a', char(0xE8), char(0x80)}},
+ {{0xFFFD, 0x7F, 0xFFFD}, {char(0xE8), 0x7F, char(0x80)}},
+ {{'a', 0xFFFD, 0xFFFD}, {'a', char(0xE8), char(0xE8), char(0x80)}},
+ {{'a', 0xFFFD}, {'a', char(0xF4)}},
+ {{'a', 0xFFFD, 'c', 'c'},
+ {'a', char(0xF4), char(0x80), char(0x80), 'c', 'c'}},
+ {{'a', 0xFFFD, 'x', 0xFFFD},
+ {'a', char(0xF4), char(0x80), 'x', char(0x80)}},
+ {{0xDBC0, 0xDC00, 0xFFFD},
+ {char(0xF4), char(0x80), char(0x80), char(0x80), char(0x80)}},
+ {{'a', 0xFFFD, 'c'}, {'a', char(0xFA), 'c'}},
+ {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0x7F, 0xFFFD, 'c'},
+ {'a', char(0xFA), char(0x80), char(0x80), 0x7F, char(0x80), 'c'}},
+ {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'c'},
+ {'a', char(0xFA), char(0x80), char(0x80), char(0x80), char(0x80),
+ char(0x80), 'c'}},
+ {{'a', 0xFFFD}, {'a', char(0xFD)}},
+ {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'c'},
+ {'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), 'c'}},
+ {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD},
+ {'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80),
+ char(0x80), char(0x80)}},
+ {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0x40, 0xFFFD, 0xFFFD, 'c'},
+ {'a', char(0xFD), char(0x80), char(0x80), 0x40, char(0x80), char(0x80),
+ 'c'}},
+};
+
+#endif
diff --git a/xpcom/tests/gtest/dafsa_test_1.dat b/xpcom/tests/gtest/dafsa_test_1.dat
new file mode 100644
index 0000000000..603813dbb4
--- /dev/null
+++ b/xpcom/tests/gtest/dafsa_test_1.dat
@@ -0,0 +1,6 @@
+%%
+foo.bar.baz, 1
+a.test.string, 0
+a.test.string2, 2
+aaaa, 4
+%%
diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build
new file mode 100644
index 0000000000..57ec43a371
--- /dev/null
+++ b/xpcom/tests/gtest/moz.build
@@ -0,0 +1,181 @@
+# -*- 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",
+ "TestArenaAllocator.cpp",
+ "TestArrayAlgorithm.cpp",
+ "TestAtoms.cpp",
+ "TestAutoRefCnt.cpp",
+ "TestBase64.cpp",
+ "TestCallTemplates.cpp",
+ "TestCloneInputStream.cpp",
+ "TestCOMPtrEq.cpp",
+ "TestCRT.cpp",
+ "TestDafsa.cpp",
+ "TestDelayedRunnable.cpp",
+ "TestEncoding.cpp",
+ "TestEscape.cpp",
+ "TestEventPriorities.cpp",
+ "TestEventTargetQI.cpp",
+ "TestFile.cpp",
+ "TestGCPostBarriers.cpp",
+ "TestID.cpp",
+ "TestIDUtils.cpp",
+ "TestInputStreamLengthHelper.cpp",
+ "TestJSHolderMap.cpp",
+ "TestLogCommandLineHandler.cpp",
+ "TestLogging.cpp",
+ "TestMemoryPressure.cpp",
+ "TestMoveString.cpp",
+ "TestMozPromise.cpp",
+ "TestMruCache.cpp",
+ "TestMultiplexInputStream.cpp",
+ "TestNonBlockingAsyncInputStream.cpp",
+ "TestNsDeque.cpp",
+ "TestNSPRLogModulesParser.cpp",
+ "TestObserverArray.cpp",
+ "TestObserverService.cpp",
+ "TestOwningNonNull.cpp",
+ "TestPLDHash.cpp",
+ "TestPriorityQueue.cpp",
+ "TestQueue.cpp",
+ "TestRacingServiceManager.cpp",
+ "TestRecursiveMutex.cpp",
+ "TestRustRegex.cpp",
+ "TestRWLock.cpp",
+ "TestSegmentedBuffer.cpp",
+ "TestSlicedInputStream.cpp",
+ "TestSmallArrayLRUCache.cpp",
+ "TestSnappyStreams.cpp",
+ "TestStateMirroring.cpp",
+ "TestStateWatching.cpp",
+ "TestStorageStream.cpp",
+ "TestStrings.cpp",
+ "TestStringStream.cpp",
+ "TestSubstringTuple.cpp",
+ "TestSynchronization.cpp",
+ "TestTArray.cpp",
+ "TestTArray2.cpp",
+ "TestTaskQueue.cpp",
+ "TestTextFormatter.cpp",
+ "TestThreadManager.cpp",
+ "TestThreadPool.cpp",
+ "TestThreadPoolListener.cpp",
+ "TestThrottledEventQueue.cpp",
+ "TestTimeStamp.cpp",
+ "TestTokenizer.cpp",
+ "TestUTF.cpp",
+ "TestVariant.cpp",
+]
+
+if CONFIG["OS_TARGET"] != "Android":
+ UNIFIED_SOURCES += [
+ "TestPipes.cpp",
+ "TestThreads.cpp",
+ ]
+
+# skip the test on windows10-aarch64 due to perma-fail, bug 1422219
+if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64"):
+ UNIFIED_SOURCES += ["TestThreadUtils.cpp"]
+
+# skip the test on OSX due to frequent failures (bug 1571186)
+if CONFIG["OS_TARGET"] != "Darwin":
+ UNIFIED_SOURCES += ["TestExpirationTracker.cpp"]
+
+# skip the test on windows10-aarch64 and Android, aarch64 due to bug 1545670
+if CONFIG["OS_TARGET"] != "Android" and not (
+ CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64"
+):
+ UNIFIED_SOURCES += ["TestTimers.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["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestAvailableMemoryWatcherWin.cpp",
+ "TestFileNTFSSpecialPaths.cpp",
+ "TestFilePreferencesWin.cpp",
+ "TestHandleWatcher.cpp",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "TestFilePreferencesUnix.cpp",
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+ UNIFIED_SOURCES += [
+ "TestAvailableMemoryWatcherMac.cpp",
+ "TestMacNSURLEscaping.mm",
+ "TestThreads_mac.mm",
+ ]
+
+if CONFIG["OS_TARGET"] == "Linux":
+ UNIFIED_SOURCES += [
+ "TestAvailableMemoryWatcherLinux.cpp",
+ ]
+
+if (
+ CONFIG["WRAP_STL_INCLUDES"]
+ and CONFIG["CC_TYPE"] != "clang-cl"
+ and CONFIG["OS_TARGET"] != "Android"
+):
+ UNIFIED_SOURCES += [
+ "TestSTLWrappers.cpp",
+ ]
+
+# Compile TestAllocReplacement separately so Windows headers don't pollute
+# the global namespace for other files.
+if CONFIG["MOZ_MEMORY"]:
+ SOURCES += [
+ "TestAllocReplacement.cpp",
+ ]
+
+SOURCES += [
+ "TestCOMArray.cpp",
+ "TestCOMPtr.cpp", # Redefines IFoo and IBar
+ "TestHashtables.cpp", # Redefines IFoo
+ "TestNsRefPtr.cpp", # Redefines Foo
+]
+
+LOCAL_INCLUDES += [
+ "../../base",
+ "/toolkit/components/telemetry/tests/gtest",
+ "/xpcom/components",
+]
+
+GeneratedFile(
+ "dafsa_test_1.inc",
+ script="../../ds/tools/make_dafsa.py",
+ inputs=["dafsa_test_1.dat"],
+)
+
+TEST_HARNESS_FILES.gtest += [
+ "wikipedia/ar.txt",
+ "wikipedia/de-edit.txt",
+ "wikipedia/de.txt",
+ "wikipedia/ja.txt",
+ "wikipedia/ko.txt",
+ "wikipedia/ru.txt",
+ "wikipedia/th.txt",
+ "wikipedia/tr.txt",
+ "wikipedia/vi.txt",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/xpcom/tests/gtest/wikipedia/README.txt b/xpcom/tests/gtest/wikipedia/README.txt
new file mode 100644
index 0000000000..bd17b17c85
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/README.txt
@@ -0,0 +1,13 @@
+The content of the .txt files in this directory originate from Wikipedia and
+is licensed the Creative Commons Attribution-ShareAlike 3.0 Unported license
+<https://creativecommons.org/licenses/by-sa/3.0/legalcode>.
+
+The content comes from the following revisions:
+ar: https://ar.wikipedia.org/w/index.php?title=%D8%A7%D9%84%D9%85%D8%B1%D9%8A%D8%AE&oldid=21144485
+de: https://de.wikipedia.org/w/index.php?title=Mars_(Planet)&oldid=158965843
+ja: https://ja.wikipedia.org/w/index.php?title=%E7%81%AB%E6%98%9F&oldid=61095795
+ko: https://ko.wikipedia.org/w/index.php?title=%ED%99%94%EC%84%B1&oldid=17394891
+ru: https://ru.wikipedia.org/w/index.php?title=%D0%9C%D0%B0%D1%80%D1%81&oldid=81533008
+th: https://th.wikipedia.org/w/index.php?title=%E0%B8%94%E0%B8%B2%E0%B8%A7%E0%B8%AD%E0%B8%B1%E0%B8%87%E0%B8%84%E0%B8%B2%E0%B8%A3&oldid=6511628
+tr: https://tr.wikipedia.org/w/index.php?title=Mars&oldid=17608551
+vi: https://vi.wikipedia.org/w/index.php?title=Sao_H%E1%BB%8Fa&oldid=24192627
diff --git a/xpcom/tests/gtest/wikipedia/ar.txt b/xpcom/tests/gtest/wikipedia/ar.txt
new file mode 100644
index 0000000000..0dba6a8a9a
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/ar.txt
@@ -0,0 +1,70 @@
+المÙرÙّيخ (Mars مارس) هو الكوكب الرابع ÙÙŠ البعد عن الشمس ÙÙŠ النظام الشمسي وهو الجار الخارجي للأرض ويصن٠كوكبا صخريا، من مجموعة الكواكب الأرضية (الشبيهة بالأرض).
+
+اطلق عليه بالعربية المريخ نسبةً إلى كلمة أمرخ أي ذو البقع الحمراء، Ùيقال ثور أَمرخ أي به بقع حمراء، وهو باللاتينية مارس الذي اتخذه الرومان آله للحرب، وهو يلقب ÙÙŠ الوقت الحالي بالكوكب الأحمر بسبب لونه المائل إلى الحمره، بÙعل نسبة غبار أكسيد الحديد الثلاثي العالية على سطحه ÙˆÙÙŠ جوه.
+
+يبلغ قطر المريخ حوالي 6800 كلم وهو بذلك مساو لنص٠قطر الأرض وثاني أصغر كواكب النظام الشمسي بعد عطارد. تقدّر مساحته بربع مساحة الأرض. يدور المريخ حول الشمس ÙÙŠ مدار يبعد عنها بمعدل 228 مليون كلم تقريبا، أي 1.5 مرات من المساÙØ© الÙاصلة بين مدار الأرض والشمس.
+
+له قمران، يسمّى الأول ديموس أي الرعب باللغة اليونانية والثاني Ùوبوس أي الخوÙ.
+
+يعتقد العلماء أن كوكب المريخ احتوى الماء قبل 3.8 مليار سنة، مما يجعل Ùرضية وجود حياة عليه متداولة نظريا على الأقل. به جبال أعلى من مثيلاتها الأرضية ووديان ممتدة. وبه أكبر بركان ÙÙŠ المجموعة الشمسية يطلق عليه اسم أوليمبس مونز تيمنا بجبل الأولمب.
+
+تبلغ درجة حرارته العليا 27 درجة مئوية ودرجة حرارته الصغرى -133 درجة مئوية. ويتكون غلاÙÙ‡ الجوي من ثاني أكسيد الكربون والنيتروجين والأرغون وبخار الماء وغازات أخرى. رمز المريخ الÙلكي هو ♂.يجذب الانتباه بلونه الأحمر
+
+قد يكون المريخ ÙˆÙقا لدراسة عالمين أمريكيين مجرد كوكب جنين لم يستطع أن يتم نموه، بعد أن نجا من الأصطدامات الكثيرة بين الأجرام السماوية التي شهدها النظام الشمسي ÙÙŠ بداية تكوينه والتي أدت لتضخم أغلب الكواكب الأخرى. وهذا ÙŠÙسر صغر حجم المريخ مقارنة بالأرض أو بالزهرة. خلص العالمان إلى هذه النتيجة بعد دراسة استقصائية لنواتج الاضمحلال المشعة ÙÙŠ النيازك.[1]
+
+يستضي٠المريخ حالياً 5 مركبات Ùضائية لا تزال تعمل، ثلاث ÙÙŠ مدار حول الكوكب وهم مارس أوديسي ومارس إكسبريس ومارس ريكونيسانس أوربيتر، واثنتان على سطح الكوكب وهما كيوريوسيتي روÙر وأبورتيونيتي، كما أن هناك مركبات Ùضائية لم تعد تعمل سواء كانت مهمتها ناجحة أم لا مثل مركبة Ùينيكس لاندر التي أنهت مهمتها عام 2008.[2]
+
+مقارنة بكوكب الأرض، للمريخ ربع مساحة سطح الأرض وبكتلة تعادل عÙشر كتلة الأرض. هواء المريخ لا يتمتع بنÙس كثاÙØ© هواء الأرض إذ يبلغ الضغط الجوي على سطح المريخ 0.75% من معدّل الضغط الجوي على الأرض، لذا نرى ان المجسّات الآلية التي قامت وكالة الÙضاء الأمريكية بإرسالها لكوكب المريخ، تÙغلّ٠بكÙرة٠هوائية لامتصاص الصدمة عند الارتطام بسطح كوكب المريخ. يتكون هواء المريخ من 95% ثنائي أكسيد الكربون، 3% نيتروجين، 1.6% ارجون، وجزء بسيط من الأكسجين والماء. ÙˆÙÙŠ العام 2000ØŒ توصّل الباحثون لنتائج توحي بوجود حياة على كوكب المريخ بعد معاينة قطع من نيزك عثر عليه ÙÙŠ القارة المتجمدة الجنوبية وتم تحديد أصله من كوكب المريخ نتيجة مقارنة تكوينه المعدني وتكوين الصخور التي تمت معاينتها من المركبات Ùيكينغ 1 Ùˆ2ØŒ حيث استدلّ الباحثون على وجود أحاÙير مجهرية ÙÙŠ النيزك. ولكن تبقى الÙرضية آنÙØ© الذكر مثاراً للجدل دون التوصل إلى نتيجة أكيدة بوجود حياة ÙÙŠ الماضي على كوكب المريخ.
+
+ويعتبر المريخ كوكب صخري ومعظم سطحه أحمر إلا بعض البقع الأغمق لوناً بسبب تربته وصخوره والغلا٠الجوي لكوكب المريخ قليل الكثاÙØ© ويتكون أساساً من ثاني أكسيد الكربون وكميات قليلة من بخار الماء والضغط الجوي على المريخ منخÙض جدًا ويصل إلى 0.01 من الضغط الجوي للأرض وجو المريخ ابرد من الأرض والسنة على المريخ 687 يوماً ارضياً.
+
+التركيب الداخلي[عدل]
+
+حدث للمريخ تماماً ما حدث للأرض من تمايز أو تباين والمقصود بالتمايز هنا العملية التي ينتج عنها اختلا٠ÙÙŠ كثاÙØ© ومكونات كل طبقة من طبقات الكوكب بحيث يكون قلب أو لب الكوكب عالي الكثاÙØ© وما Ùوقه أقل منه ÙÙŠ الكثاÙØ©. النموذج الحالي لكوكب المريخ ينطوي على التالي: القلب يمتد لمساÙØ© يبلغ نص٠قطرها 1794 ± 65 كيلومتر وهي تتكون أساساً من الحديد والنيكل والكبريت بنسبة 16-17%. هذا القلب المكون من كبريتات الحديد سائل جزئياً، وتركيزه ضع٠تركيز باقي المواد الأخ٠الموجودة ÙÙŠ القلب. يحاط هذا القلب بدثار من السليكات والتي تكون العديد من المظاهر التكتونية والبركانية على الكوكب إلا أنها الآن تبدو كامنة. بجانب السيليكون والأكسجين، Ùإن أكثر العناصر انتشاراً ÙÙŠ قشرة كوكب المريخ هي الحديد والألومنيوم والماغنسيوم والألومنيوم والكالسيوم والبوتاسيوم. يبلغ متوسط سماكة قشرة كوكب المريخ 50 كيلومتر وأقصى ارتÙاع 125 كيلومتر، ÙÙŠ حين أن قشرة الأرض تبلغ سماكتها 40 كم، وهذا السÙمك بالنسبة لحجم الأرض يعادل ثلث سماكة قشرة كوكب المريخ بالنسبة إلى حجمه. من المخطط له أن تقوم مركبة الÙضاء إن سايت بتحليل أكثر دقة لكوكب المريخ أثناء مهمتها عليه ÙÙŠ عام 2016 باستخدام جهاز مقياس الزلازل لتحدد نموذج للتركيب الداخلي للكوكب بصورة Ø£Ùضل.
+التربة[عدل]
+
+أظهرت البيانات التي وصلت من مسبار الÙضاء Ùينيكس أن تربة المريخ قلوية قليلاً وتحتوي على مواد مثل الماغنسيوم والصوديوم والبوتاسيوم والكلورين، هذه المغذيات موجودة ÙÙŠ الحدائق على الأرض، وهي ضرورية لنمو النباتات. وأظهرت التجارب التي أجراها مسبار الÙضاء أن تربة المريخ لها تركيز هيدروجيني 8.3 وربما تحتوي على آثار لملح البيركلوريك. قال سام كوناÙيس كبير الخبراء المختصين بمختبر كيمياء الموائع الموجود على Ùينيكس للصحÙيين "وجدنا أساسا ما تبدو أنها الخصائص أو العناصر المغذية التي تدعم إمكانية الحياة سواء ÙÙŠ الماضي أو الحاضر أو المستقبل.[3]
+المياه[عدل]
+
+ Crystal Clear app kdict.png مقالة Ù…Ùصلة: المياه علي كوكب المريخ
+
+توجد المياه علي سطح المريخ غالبا ÙÙŠ صورة جليد ويمثل الغطائين الجليديين ÙÙŠ القطب الشمالي والجنوبي للكوكب معظم الجليد الموجود علي السطح يوجد أيضا بعض الجليد ÙÙŠ صخور القشرة المريخية. كما توجد نسبة ضئيلة من بخار الماء ÙÙŠ الغلا٠الجوي للكوكب. لكن لاتوجد مياه سائلة علي سطح المريخ إطلاقا. يرجع وجود الماء ÙÙŠ صورة جليدية الي الظرو٠المناخية للمريخ حيث درجات الحرارة المنخÙضة جدا والتي تؤدي الي تجمد المياه الÙوري. مع ذلك Ùقد أكدت الدراسات ان الوضع علي سطح المريخ كان مختلÙا كثيرا عما هو عليه الآن ولربما كان يشبه كوكب الأرض حيث كانت توجد المياة السائلة[4] ÙÙŠ مساحات كبيرة من سطح الكوكب مشكله محيطات مثل الموجودة الآن علي سطح الأرض.
+
+توجد الكثير من الدلائل المباشرة وغير المباشرة علي هذه النظرية منها التحليلات الطيÙية لسطح تربة المريخ وأيضا الغطائين القطبيين الجليديين وأيضاً وجود الكثير من المعادن ÙÙŠ قشرة المريخ والتي ارتبط وجودها علي سطح الأرض بوجود المياه. منها أكسيد الحديد Hematite وأكسيد الكبريت Sulfate والجوثايت goethite ومركبات السيليكا phyllosilicate.[5] لقد ساعدت كثيرا مركبات ورحلات الÙضاء غير المأهولة الي المريخ ÙÙŠ دراسة سطح الكوكب وتحليل تربته وغلاÙÙ‡ الجوي. ومن أكثر المركبات التي ساعدت علي ذلك مركبة مارس ريكونيسانس أوربيتر علي تصوير سطح المريخ بدقة عالية وتحليل سطح الكوكب بÙضل وجود الكاميرا عالية الجودة HiRISE كما كشÙت عن Ùوهات البراكين المتآكلة ومجاري النهار الجاÙØ© والأنهار الجليدية.
+
+كما كشÙت الدراسات الطيÙية بأشعة غاما عن وجود الجليد تحت سطح تربة المريخ. أيضا، كشÙت الدراسات بالرادار عن وجود الجليد النقي ÙÙŠ التشكيلات التي يعتقد أنها كانت أنهار جليدية قديمة.المركبة الÙضائية Ùينيكس التي هبطت قرب القطب الشمالي ورأت الجليد وهو يذوب الجليد، وشهدت تساقط الثلوج، ورأت حتي قطرات من الماء السائل.
+Arabic-final.jpg
+الطبوغراÙيا[عدل]
+
+ Crystal Clear app kdict.png مقالة Ù…Ùصلة: نيازك المريخ
+
+طبوغراÙية كوكب المريخ جديرة بالاهتمام، ÙÙÙŠ حين يتكون الجزء الشمالي من الكوكب من سهول الحمم البركانية، وتقع البراكين العملاقة على هضبة تارسيس وأشهرها على الإطلاق أوليمبس مون وهو بدون شك أكبر بركان ÙÙŠ المجموعة الشمسية، نجد ان الجزء الجنوبي من كوكب المريخ يتمتّع بمرتÙعات شاهقة ويبدو على المرتÙعات آثار النيازك والشّهب التي ارتطمت على تلك المرتÙعات. يغطي سهول كوكب المريخ الغبار والرمل الغني بأكسيد الحديد ذو اللون الأحمر. تغطّي بعض مناطق المريخ أحيانا طبقة رقيقة من جليد الماء. ÙÙŠ حين تغطي القطبين طبقات سميكة من جليد مكون من ثاني أكسيد الكربون والماء المتجمّد. تجدرالإشارة أن أعلى قمّة جبلية ÙÙŠ النظام الشمسي هي قمّة جبل "اوليمبوس" والتي يصل ارتÙاعها إلى 25 كم. أمّا بالنسبة للأخاديد، Ùيمتاز الكوكب الأحمر بوجود أكبر أخدود ÙÙŠ النظام الشمسي، ويمتد الأخدود "وادي مارينر" إلى مساÙØ© 4000 كم، وبعمق يصل إلى 7 كم.
+الغلا٠الجوي[عدل]
+
+لقد كانت أول الأخبار عن جو المريخ من سلسلة رحلات مارينر، حيث تم التأكيد على أن للكوكب غلا٠الجوي رقيق جداً يصل إلى 0.01 بالنسبة لغلا٠الأرض الجوي. يتأل٠هذا الجو الرقيق من CO2 ÙÙŠ أغلبه حيث تصل نسبته إلى 95% من مكوناته. ثم تم تحليل مكونات الجو بواسطة المركبة Ùايكينغ 1 لنصل إلى خلاصته عن تركيب الجو وهي كما ÙÙŠ الجدول:
+المادة النسبة %
+
+والضغط الجوي على سطح هذا الكوكب يقارب 1/100 من الضغط الجوي على سطح الأرض عند مستوى سطح البحر. وقد تم تلمس كمية ضئيلة جداً من الأوزون يصل تركيزها إلى 0.03 جزئ /مليون جزيء.ولكن هذا التركيز لا يحمي من الأشعة Ùوق البنÙسجية الضارة. ونلاحظ من الجدول أن نسبة بخار الماء ÙÙŠ الجو ضئيلة جداً مما يجعل الجو جاÙاً. ولكن بسبب برودة سطح الكوكب Ùإن كمية بخار الماء الضئيلة هذه تكÙÙŠ لإشباعه. ومع استمرارية انخÙاض درجة الحرارة دون درجة الندى تبدأ الغازات وخاصة CO2 بالتكاث٠والتجمد والسقوط على سطح الكوكب. وتم رصد عواص٠محلية على السطح وهي عبارة عن هبوب رياح قوية تتحرك بسرعة وتكون غيوم غبارية وزوابع تدور على السطح وتنقل التربة من مكان إلى آخر. وهذه الرياح التي تعص٠على الكوكب لها كما على الأرض دورة رياح يومية ودورة موسمية. ولها تأثير كبير ÙÙŠ عمليات الحت والتجوية على سطح الكوكب. ولأن كثاÙØ© الجو 2% من كثاÙØ© جو الأرض يجب أن تكون قوة الرياح أكبر بحوالي 7 إلى 8 مرات من قوة الرياح الأرضية حتى تستطيع أن تثير وتحمل الغبار وتكون زوابع. Ùالرياح الأرضية بسرعة 24 كلم بالساعة تثير هذه العواص٠أما على المريخ Ùنحتاج إلى رياح بسرعة 180 كلم بالساعة لتقوم بمثل هذه العواصÙ. ودعيت هذه التأثير بتأثير عÙولس Eo`lian effect نسبة إلى آله الريح عÙولس E`olus. ومن الأدلة الواضحة على تأثر عÙولس لحركة الرياح هو الكثبان الرملية. حيث تحمل الرياح الرمال من مكان وتلقيها ÙÙŠ مكان آخر. Ùنجد لها امتداداً واضحاً على سطح الكوكب. وعندما تثور كمية من الغبار Ùإن العاصÙØ© تحاÙظ على بقائها بتحويل الطاقة الشمسية إلى طاقة حركية ريحية، حيث تمتص الطاقة من الإشعاع الشمسي وتسخن الجو وتزيد من سرعة الرياح. Ùيل٠الكوكب دثار مصÙر من الزوابع. ولعدم وجود ماء يغسل الغبار من الجو Ùإنه يبقى عالقاً لعدة أسابيع قبل أن يستقر على السطح ثانية. ومن الغريب أن هذه الرياح تعص٠بهدوء ومن دون أصوات Ùلا ينطبق عليها أصوات العواص٠الهادرة الأرضية.[6]
+مدار الكوكب ودورانه[عدل]
+
+المريخ هو رابع الكواكب بعداً عن الشمس. وأول كوكب له مدار خارج مدار الأرض ويبعد عن الشمس حوالي 228 مليون كلم بالمتوسط. شذوذية مركزيته e = 0.093 وهي كبيرة نسبياً، مما يدل على أن مداره إهليلجي بشكل واضح حيث يكون وهو ÙÙŠ الحضيض على بعد 206 مليون كلم عن الشمس وعند وصوله إلى الأوج يصبح على بعد 249 مليون كلم عن الشمس. Ùنرى Ùرقاً واضحاً ÙÙŠ البعدين وهذا يؤدي إلى تباين كمية أشعة الشمس الساقطة على سطحه بنسبة تصل إلى 45% بين الأوج والحضيض، أي بÙارق 30 Ù’ س وما يتبع ذلك من تغيرات ÙÙŠ مناخ الكوكب بين الموقعين. ودرجة الحرارة تتراوح على السطح بين الشتاء والصي٠-144 ْس إلى 27 ْس أما ÙÙŠ المتوسط Ùإن درجة الحرارة تقدر بحوالي –23 Ù’ إلى -55 Ù’ س.
+
+ويقطع الكوكب هذا المدار ÙÙŠ زمن يعادل 687 يوم أرضي، وأثناء دورانه ÙÙŠ مداره هذا تحدث له عدد من الظواهر منها الاقتران.[6]
+قمرا المريخ[عدل]
+كوكب المريخ
+
+تم اكتشا٠قمري المريخ ÙÙŠ العام 1877 على يد "آسا٠هول" وتمّت تسميتهم تيمّناً بمراÙقي الآله اليوناني "آريس". يدور كل من القمر "Ùوبوس" والقمر "ديموس" حول الكوكب الأحمر، وخلال Ùترة الدوران، تقابل Ù†Ùس الجهة من القمر الكوكب الأحمر تماما مثلما يعرض القمر Ù†Ùس الجانب لكوكب الأرض.
+القمر Ùوبوس[عدل]
+
+Ùوبوس قطعة صخرية صغيرة غير منتظمة الشكل لا يزيد طولها عن 21 كم (13 ميلا) ويتم دورته حول المريخ كل 7.7 ساعات. يبدو القمر هرم نوعا ما. وتغشاه Ùوهات صدم متÙاوتة القدم. ويلاحظ عليه وجود حزوز striations وسلاسل من Ùوهات صغيرة. يطلق أكبرها اسم ستيكني stickney الذي يقارب قطره 10 كم (6 أميال). يقوم القمر Ùوبوس بالدوران حول المريخ اسرع من دوران المريخ حول Ù†Ùسه، مما يؤدي بقطر دوران القمر Ùوبوس حول المريخ للتناقص يوماً بعد يوم إلى أن ينتهي به الأمر إلى التÙتت ومن ثم الارتطام بكوكب المريخ.
+القمر ديموس[عدل]
+
+ديموس هو أحد الأقمار التابعة لكوكب المريخ إلى جانب القمر Ùوبوس وهو عبارة عن قطعة صخرية صغيرة غير منتظمة الشكل لا يزيد طولها عن 12 كم (7 ميلا) ويتم دورته حول المريخ خلال 1.3 يوم. ولبعده عن الكوكب الأحمر، Ùإن قطر مدار القمر آخذ بالزيادة. ويبدو ديموس على شكل هرمي نوعاً ما. وتغشاه Ùوهات صدم متÙاوتة القدم.
+استكشا٠المريخ[عدل]
+سطح كوكب المريخ
+
+ مقال تÙصيلي: تاريخ رصد المريخ
+
+هناك ما يقرب من 44 محاولة[7] إرسال مركبات Ùضائية للكوكب الأحمر من Ù‚Ùبل الولايات المتحدة، الاتحاد السوÙيتي، أوروبا، واليابان. قرابة ثلثين المركبات الÙضائية Ùشلت ÙÙŠ مهمّتها أما على الأرض، أو خلال رحلتها أو خلال هبوطها على سطح الكوكب الأحمر. من أنجح المحاولات إلى كوكب المريخ تلك التي سمّيت بـ "مارينر"ØŒ "برنامج الÙيكنج"ØŒ "سورÙيور"ØŒ "باثÙيندر"ØŒ Ùˆ"أوديسي". قامت المركبة "سورÙيور" بالتقاط صور لسطح الكوكب، الأمر الذي أعطى العلماء تصوراً بوجود ماء، إمّا على السطح أو تحت سطح الكوكب بقليل. وبالنسبة للمركبة "أوديسي"ØŒ Ùقد قامت بإرسال معلومات إلى العلماء على الأرض والتي مكّنت العلماء من الاستنتاج من وجود ماء متجمّد تحت سطح الكوكب ÙÙŠ المنطقة الواقعة عند 60 درجة جنوب القطب الجنوبي للكوكب.
+
+ÙÙŠ العام 2003ØŒ قامت وكالة الÙضاء الأوروبية بإرسال مركبة مدارية وسيارة تعمل عن طريق التحكم عن بعد، وقامت الأولى بتأكيد المعلومة المتعلقة بوجود ماء جليد وغاز ثاني أكسيد الكربون المتجمد ÙÙŠ منطقة القطب الجنوبي لكوكب المريخ. تجدر الإشارة إلى أن أول من توصل إلى تلك المعلومة هي وكالة الÙضاء الأمريكية وان المركبة الأوروبية قامت بتأكيد المعلومة. باءت محاولات الوكالة الأوروبية بالÙشل ÙÙŠ محاولة الاتصال بالسيارة المصاحبة للمركبة الÙضائية وأعلنت الوكالة رسمياً Ùقدانها للسيارة الآلية ÙÙŠ Ùبراير من من Ù†Ùس العام. لحقت وكالة الÙضاء الأمريكية الرّكب بإرسالها مركبتين Ùضائيتين وكان Ùرق الوقت بين المركبة الأولى والثانية، 3 أسابيع، وتمكن السيارات الآلية الأمريكية من إرسال صور مذهلة لسطح الكوكب وقامت السيارات بإرسال معلومات إلى العلماء على الأرض تÙيد، بل تؤكّد على تواجد الماء على سطح الكوكب الأحمر ÙÙŠ الماضي. ÙÙŠ الشكل أدناه خريطة لسطح المريخ تظهر أماكن تواجد أهم المركبات الأمريكية على سطحه.
diff --git a/xpcom/tests/gtest/wikipedia/de-edit.txt b/xpcom/tests/gtest/wikipedia/de-edit.txt
new file mode 100644
index 0000000000..2fbcd2d74d
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/de-edit.txt
@@ -0,0 +1,487 @@
+gezählt, der vierte Planet im Sonnensystem und der äußere Nachbar der Erde. Er zählt zu den erdähnlichen (terrestrischen) Planeten.
+
+Sein Durchmesser ist mit knapp 6800 Kilometer etwa halb so groß wie der der Erde, sein Volumen beträgt gut ein Siebtel des Erdevolumens. Damit ist der Mars nach dem Merkur der zweitkleinste Planet des Sonnensystems, hat jedoch eine ausgeprägte Geologie und die höchsten Vulkane des Sonnensystems. Mit einer durchschnittlichen Entfernung von 228 Millionen Kilometern ist er rund 1,5-mal so weit von der Sonne entfernt wie die Erde.
+
+Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse. Die Fallbeschleunigung auf seiner Oberfläche beträgt 3,69 m/s², dies entspricht etwa 38 % der irdischen. Mit einer Dichte von 3,9 g/cm³ weist der Mars den geringsten Wert der terrestrischen Planeten auf. Deshalb ist die Schwerkraft auf ihm sogar geringfügig niedriger als auf dem kleineren, jedoch dichteren Merkur.
+
+Der Mars wird oft auch als der Rote Planet bezeichnet. Diese Färbung geht auf Eisenoxid-Staub (Rost) zurück, der sich auf der Oberfläche und in der dünnen CO2-Atmosphäre verteilt hat. Seine orange- bis blutrote Farbe und seine Helligkeitsschwankungen sind auch verantwortlich für seine Namensgebung nach dem römischen Kriegsgott Mars.[3]
+
+In größeren Fernrohren deutlich sichtbar sind die zwei Polkappen und mehrere dunkle Ebenen, die sich im Frühjahr etwas verfärben. Fotos von Raumsonden zeigen eine teilweise mit Kratern bedeckte Oberfläche und starke Spuren früherer Tektonik (tiefe Canyons und fünf über 20 km hohe Vulkane). Marsroboter haben schon mehrere Gebiete geologisch untersucht.
+
+Der Mars besitzt zwei kleine, unregelmäßig geformte Monde, die 1877 entdeckt wurden: Phobos und Deimos (griechisch für Furcht und Schrecken).
+
+Das astronomische Symbol des Mars ist .
+
+Umlauf und Rotation
+Umlaufbahn
+
+Der Mars bewegt sich in einem Abstand von 206,62 bis 249,23 Millionen Kilometern (1,38 AE bis 1,67 AE) in knapp 687 Tagen (etwa 1,9 Jahre) auf einer elliptischen Umlaufbahn um die Sonne. Die Bahnebene ist 1,85° gegen die Erdbahnebene geneigt.
+
+Seine Bahngeschwindigkeit schwankt mit dem Sonnenabstand zwischen 26,50 km/s und 21,97 km/s und beträgt im Mittel 24,13 km/s. Die Bahnexzentrizität beträgt 0,0935. Nach der Umlaufbahn des Merkurs ist das die zweitgrößte Abweichung von der Kreisform unter allen Planetenbahnen des Sonnensystems.
+
+Jedoch hatte der Mars in der Vergangenheit eine weniger exzentrische Umlaufbahn. Vor 1,35 Millionen Jahren betrug die Exzentrizität nur etwa 0,002, weniger als die der Erde heute.[4] Die Periode der Exzentrizität des Mars beträgt etwa 96.000 Jahre, die der Erde etwa 100.000 Jahre.[5] Mars hat jedoch noch einen längeren Zyklus der Exzentrizität mit einer Periode von 2,2 Millionen Jahren, der den mit der Periode von 96.000 Jahren überlagert. In den letzten 35.000 Jahren wurde die Umlaufbahn aufgrund der gravitativen Kräfte der anderen Planeten geringfügig exzentrischer. Der minimale Abstand zwischen Erde und Mars wird in den nächsten 25.000 Jahren noch ein wenig geringer werden.[6]
+
+Es gibt vier bekannte Asteroiden, die sich mit dem Mars die gleiche Umlaufbahn teilen (Mars-Trojaner). Sie befinden sich auf den Lagrangepunkten L4 und L5, das heißt, sie eilen dem Planeten um 60° voraus oder folgen ihm um 60° nach.
+Rotation
+
+Der Mars rotiert in 24 Stunden und 37,4 Minuten um die eigene Achse (Siderischer Tag). In Bezug auf die Sonne ergibt sich daraus ein Marstag (auch Sol genannt) von 24:39:35. Die Äquatorebene des Planeten ist um 25,19° gegen seine Bahnebene geneigt (Erde 23,44°), somit gibt es Jahreszeiten ähnlich wie auf der Erde. Sie dauern jedoch fast doppelt so lang, weil das siderisches Marsjahr 687 Erdtage hat. Da die Bahn des Mars aber eine deutlich größere Exzentrizität aufweist, als die der Erde, und Mars-Nord tendenziell in Richtung der großen Bahn-Ellipsenachse weist, sind die Jahreszeiten unterschiedlich lang. In den letzten 300.000 Jahren variierte die Rotationsachse zwischen 22° und 26°. Zuvor lag sie mehrmals auch über 40°, wodurch starke Klimaschwankungen auftraten, es Vereisungen auch in der Äquatorregion gab und so die starken Bodenerosionen zu erklären sind.
+
+Der Nordpol des Mars weist zum nördlichen Teil des Sternbilds Schwan, womit sich die Richtung um etwa 40° von jener der Erdachse unterscheidet. Der marsianische Polarstern ist Deneb (mit leichter Abweichung der Achse Richtung Alpha Cephei).[7]
+
+Die Rotationsachse führt eine Präzessionsbewegung aus, deren Periode 170.000 Jahre beträgt (7× langsamer als die Erde). Aus diesem Wert, der mit Hilfe der Pathfinder-Mission festgestellt wurde, können die Wissenschaftler auf die Massenkonzentration im Inneren des Planeten schließen.[8]
+Atmosphäre und Klima
+Über dem Marshorizont ist die Atmosphäre als dunstiger Schleier erkennbar. Links ist der einem Smiley ähnelnde Krater Galle zu sehen. Viking, 1976
+
+Der Mars besitzt eine sehr dünne Atmosphäre. Dadurch ist der Atmosphärendruck sehr niedrig, und Wasser kann nicht in flüssiger Form auf der Marsoberfläche existieren, ausgenommen kurzzeitig in den tiefstgelegenen Gebieten.
+
+Da die dünne Marsatmosphäre nur wenig Sonnenwärme speichern kann, sind die Temperaturunterschiede auf der Oberfläche sehr groß. Die Temperaturen erreichen in Äquatornähe etwa 20 °C am Tag und sinken bis auf 85 °C in der Nacht. Die mittlere Temperatur des Planeten liegt bei etwa 55 °C.
+Atmosphäre
+ Hauptartikel: Atmosphäre des Mars
+
+Die Marsatmosphäre besteht zu 95,3 % aus Kohlenstoffdioxid. Dazu kommen noch 2,7 % Stickstoff, 1,6 % Argon, geringe Anteile an Sauerstoff (1300 ppm) und Kohlenstoffmonoxid (800 ppm) sowie Spuren von Wasserdampf (210 ppm) und anderen Verbindungen oder Elementen.
+
+Die Atmosphäre ist ziemlich staubig. Sie enthält Teilchen mit etwa 1,5 µm im Durchmesser, die den Himmel über dem Mars in einem blassen gelb- bis orange-braunen Farbton erscheinen lassen.
+
+Der atmosphärische Druck beträgt auf der Oberfläche des Mars im Schnitt nur 6,36 hPa (Hektopascal). Im Vergleich zu durchschnittlich 1013 hPa auf der Erde sind dies nur 0,63 %, was dem Luftdruck der Erdatmosphäre in 35 Kilometern Höhe entspricht. Die Atmosphäre wurde wahrscheinlich im Laufe der Zeit vom Sonnenwind abgetragen und in den Weltraum mitgerissen. Dies wurde durch die geringe Schwerkraft des Planeten und sein schwaches Magnetfeld begünstigt, das kaum Schutz vor den hochenergetischen Teilchen der Sonne bietet.
+Klima und Wetter
+Eiswolken über Mars, aufgenommen von Mars Pathfinder
+
+Abhängig von den Jahreszeiten und der Intensität der Sonneneinstrahlung finden in der Atmosphäre dynamische Vorgänge statt. Die vereisten Polkappen sublimieren im Sommer teilweise, und kondensierter Wasserdampf bildet ausgedehnte Zirruswolken. Die Polkappen selbst bestehen aus Kohlendioxideis und Wassereis.
+
+2008 entdeckte man mit Hilfe der Raumsonde Mars Express Wolken aus gefrorenem Kohlendioxid. Sie befinden sich in bis zu 80 Kilometern Höhe und haben eine horizontale Ausdehnung von bis zu 100 km. Sie absorbieren bis zu 40 % des einstrahlenden Sonnenlichts und können damit die Temperatur der Oberfläche um bis zu 10 °C verringern.[9]
+
+Mit Hilfe des Lasers LIDAR der Raumsonde Phoenix wurde 2009 entdeckt, dass in der zweiten Nachthälfte fünfzig Tage nach der Sonnenwende winzige Eiskristalle aus dünnen Zirruswolken auf den Marsboden fielen.[10]
+Jahreszeiten
+Staubsturm in der Syria-Region, fotografiert von Mars Global Surveyor im Mai 2003
+
+Hätte Mars eine erdähnliche Umlaufbahn, würden die Jahreszeiten aufgrund der Achsenneigung ähnlich denen der Erde sein. Jedoch führt die vergleichsweise große Exzentrizität seines Orbits zu einer beträchtlichen Auswirkung auf die Jahreszeiten. Der Mars befindet sich während des Sommers in der Südhalbkugel und des Winters in der nördlichen Hemisphäre nahe dem Perihel seiner Bahn. Nahe dem Aphel ist in der südlichen Hemisphäre Winter und in der nördlichen Sommer.
+
+Das hat zur Folge, dass die Jahreszeiten in der südlichen Hemisphäre viel deutlicher ausgeprägt sind als in der nördlichen, wo das Klima ausgeglichener ist, als es sonst der Fall wäre. Die Sommertemperaturen im Süden können bis zu 30 °C höher sein als die vergleichbaren Temperaturen im Sommer des Nordens.[11] Die Jahreszeiten sind aufgrund der Exzentrizität der Umlaufbahn des Mars unterschiedlich lang. Auf der Nordhalbkugel dauert der Frühling 199,6, der Sommer 181,7, der Herbst 145,6 und der Winter 160,1 irdische Tage.[12]
+Wind und Stürme
+
+Aufgrund der starken Tag-Nacht-Temperaturschwankungen der Oberfläche gibt es tägliche Morgen- und Abendwinde.[13]
+
+Während des Marsfrühjahrs können in den ausgedehnten flachen Ebenen heftige Staubstürme auftreten, die mitunter große Teile der Marsoberfläche verhüllen. Die Aufnahmen von Marssonden zeigen auch Windhosen, die über die Marsebenen ziehen und auf dem Boden dunkle Spuren hinterlassen. Stürme auf dem Mars haben wegen der sehr dünnen Atmosphäre eine wesentlich geringere Kraft als Stürme auf der Erde. Selbst bei hohen Windgeschwindigkeiten werden nur kleine Partikel (Staub) aufgeweht.[14] Allerdings verbleibt aufgewehter Staub auf dem Mars wesentlich länger in der Atmosphäre als auf der Erde, da es keine Niederschläge gibt, die die Luft reinigen, und zudem die Gravitation geringer ist.
+
+Staubstürme treten gewöhnlich während des Perihels auf, da der Planet zu diesem Zeitpunkt 40 Prozent mehr Sonnenlicht empfängt als während des Aphels. Während des Aphels bilden sich in der Atmosphäre Wolken aus Wassereis, die ihrerseits mit den Staubpartikeln interagieren und so die Temperatur auf dem Planeten beeinflussen.[15] Die Windgeschwindigkeiten in der oberen Atmosphäre können bis zu 650 km/h erreichen, auf dem Boden immerhin fast 400 km/h.[16]
+Gewitter
+
+Bei heftigen Staubstürmen scheint es auch zu Gewittern zu kommen. Im Juni 2006 untersuchten Forscher mit einem Radioteleskop den Mars und stellten im Mikrowellenbereich Strahlungsausbrüche fest, wie sie bei Blitzen auftreten. In der Region, in der man die Strahlungsimpulse beobachtet hat, herrschte zu der Zeit ein heftiger Staubsturm mit hohen Staubwolken. Sowohl der beobachtete Staubsturm wie auch das Spektrum der Strahlungsimpulse deuten auf ein Staubgewitter mit Blitzen bzw. großen Entladungen hin.[17][18]
+Oberfläche
+Typisches Felsengestein auf der Marsoberfläche (aufgenommen von Mars Pathfinder)
+
+Die Oberfläche des Mars beträgt etwa ein Viertel der Erdoberfläche. Sie entspricht mit 144 Mio. km2 ungefähr der Gesamtoberfläche aller Kontinente der Erde (149 Mio. km2).
+
+Die rote Färbung seiner Oberfläche verdankt der Planet dem Eisenoxid-Staub, der sich auf der Oberfläche und in der Atmosphäre verteilt hat. Somit ist der Rote Planet ein rostiger Planet.
+
+Seine beiden Hemisphären sind sehr verschieden. Die Südhalbkugel stellt ein riesiges Hochland dar, das durchschnittlich 23 km über dem globalen Nullniveau liegt und ausgedehnte Schildvulkane aufweist. Die vielen Einschlagkrater belegen sein hohes Alter von fast 4 Milliarden Jahren. Dem steht die geologisch junge, fast kraterlose nördliche Tiefebene gegenüber. Sie liegt 35 km unter dem Nullniveau und hat ihre ursprüngliche Struktur durch noch ungeklärte geologische Prozesse verloren. Auslöser war möglicherweise eine gewaltige Kollision in der Frühzeit des Planeten.
+Gesteine
+ Hauptartikel: Marsgestein
+
+An den Landestellen der Marssonden sind Gesteinsbrocken, sandige Böden und Dünen sichtbar. Die Marsgesteine weisen an der Oberfläche eine blasenartige Struktur auf und ähneln in ihrer Zusammensetzung irdischen Basalten, was bereits vor Jahrzehnten aus den auf der Erde (Antarktis) gefundenen Marsmeteoriten erschlossen wurde. Die roten Böden sind offensichtlich durch die Verwitterung von eisenhaltigen, vulkanischen Basalten entstanden.
+
+Die Pathfinder-Sonde fand 1997 außer verschiedensten Basalten auch quarzreichere Tiefengesteine ähnlich dem südamerikanischen Andesit, ferner das aus der Tiefe stammende Olivin und runde Kiesel aus Konglomeraten. Weitverbreitet ist metamorpher Regolith (ähnlich wie am Mond) und äolische Sedimente. Vereinzelt verwehter Sand aus schwefelhaltigen Staubteilchen.
+Areografie
+
+Die kartografische Darstellung und Beschreibung der Marsoberfläche ist die Areografie, von Ares (, griechisch für Mars) und grafein (, griechisch für beschreiben). Die Geologie des Mars wird mitunter dementsprechend als Areologie bezeichnet.
+
+Zur Festlegung von Positionen auf der Marsoberfläche dienen areografische Koordinaten, die definiert sind wie geografische Breite und Länge.
+Topografische Hemisphären
+Topografische Karte des Mars. Die blauen Regionen befinden sich unterhalb des festgelegten Nullniveaus, die roten oberhalb
+
+Auffallend ist die Dichotomie, die Zweiteilung, des Mars. Die nördliche und die südliche Hemisphäre unterscheiden sich deutlich, wobei man von den Tiefebenen des Nordens und den Hochländern des Südens sprechen kann. Der mittlere Großkreis, der die topografischen Hemisphären voneinander trennt, ist rund 40° gegen den Äquator geneigt. Der Massenmittelpunkt des Mars ist gegenüber dem geometrischen Mittelpunkt um etwa drei Kilometer in Richtung der nördlichen Tiefebenen versetzt.
+
+Auf der nördlichen Halbkugel sind flache sand- und staubbedeckte Ebenen vorherrschend, die Namen wie Utopia Planitia oder Amazonis Planitia erhielten. Dunkle Oberflächenmerkmale, die in Teleskopen sichtbar sind, wurden einst für Meere gehalten und erhielten Namen wie Mare Erythraeum, Mare Sirenum oder Aurorae Sinus. Diese Namen werden heute nicht mehr verwendet. Die ausgedehnteste dunkle Struktur, die von der Erde aus gesehen werden kann, ist Syrtis Major, die große Syrte.
+
+Die südliche Halbkugel ist durchschnittlich sechs Kilometer höher als die nördliche und besteht aus geologisch älteren Formationen. Die Südhalbkugel ist zudem stärker verkratert, wie zum Beispiel in der Hochlandregion Arabia Terra. Unter den zahlreichen Einschlagkratern der Südhalbkugel befindet sich auch der größte Marskrater, Hellas Planitia, die Hellas-Tiefebene. Das Becken misst im Durchmesser bis zu 2100 km. In seinem Innern maß Mars Global Surveyor 8180 m unter Nullniveau unter dem Durchschnittsniveau des Mars den tiefsten Punkt auf dem Planeten. Der zweitgrößte Einschlagkrater des Mars, Chryse Planitia, liegt im Randbereich der nördlichen Tiefländer.
+Übersichtskarte des Mars mit den größten Regionen
+
+Die deutlichen Unterschiede der Topografie können durch innere Prozesse oder aber ein Impaktereignis verursacht worden sein. In letzterem Fall könnte in der Frühzeit der Marsentstehung ein größerer Himmelskörper, etwa ein Asteroid, auf der Nordhalbkugel eingeschlagen sein und die silikatische Kruste durchschlagen haben. Aus dem Innern könnte Lava ausgetreten sein und das Einschlagbecken ausgefüllt haben.
+
+Wie sich gezeigt hat, hat die Marskruste unter den nördlichen Tiefebenen eine Dicke von etwa 40 km, die im Gegensatz zum stufenartigen Übergang an der Oberfläche nur langsam auf 70 km bis zum Südpol hin zunimmt. Dies könnte ein Indiz für innere Ursachen der Zweiteilung sein.
+Oberflächenstrukturen
+Gräben
+In der Bildmitte liegt das System der Mariner-Täler. Ganz links die Tharsis-Vulkane (Bildmosaik von Viking 1 Orbiter, 1980)
+
+Südlich am Äquator und fast parallel zu ihm verlaufen die Valles Marineris (die Mariner-Täler), das größte bekannte Grabensystem des Sonnensystems. Es erstreckt sich über 4000 km und ist bis zu 700 km breit und bis zu 7 km tief. Es handelt sich um einen gewaltigen tektonischen Bruch. In seinem westlichen Teil, dem Noctis Labyrinthus, verästelt er sich zu einem chaotisch anmutenden Gewirr zahlreicher Schluchten und Täler, die bis zu 20 km breit und bis zu 5 km tief sind.
+
+Noctis Labyrinthus liegt auf der östlichen Flanke des Tharsis-Rückens, einer gewaltigen Wulst der Mars-Lithosphäre quer über dem Äquator mit einer Ausdehnung von etwa 4000 mal 3000 Kilometern und einer Höhe von bis zu rund 10 Kilometern über dem nördlichen Tiefland. Die Aufwölbung ist entlang einer offenbar zentralen Bruchlinie von drei sehr hohen, erloschenen Schildvulkanen besetzt: Ascraeus Mons, Pavonis Mons und Arsia Mons. Der Tharsis-Rücken und die Mariner-Täler dürften in ursächlichem Zusammenhang stehen. Wahrscheinlich drückten vulkanische Kräfte die Oberfläche des Planeten in dieser Region empor, wobei die Kruste im Bereich des Grabensystems aufgerissen wurde. Eine Vermutung besagt, dass diese vulkanische Tätigkeit durch ein Impaktereignis ausgelöst wurde, dessen Einschlagstelle das Hellas-Becken auf der gegenüberliegenden Seite des Mars sei. 2007 wurden im Nordosten von Arsia Mons sieben tiefere Schächte mit 100 bis 250 Metern Durchmesser entdeckt.
+Vulkane
+Olympus Mons, der mit 26 km höchste Berg im Sonnensystem
+Die komplexe Caldera des Olympus Mons
+
+Dem Hellas-Becken exakt gegenüber befindet sich der Vulkanriese Alba Patera. Er ragt unmittelbar am Nordrand des Tharsis-Rückens rund 6 km über das umgebende Tiefland und ist mit einem Basisdurchmesser von über 1200 km der flächengrößte Vulkan im Sonnensystem. Patera ist die Bezeichnung für unregelmäßig begrenzte Vulkane mit flachem Relief. Alba Patera ist anscheinend einmal durch einen Kollaps in sich zusammengefallen.
+
+Unmittelbar westlich neben dem Tharsis-Rücken und südwestlich von Alba Patera ragt der höchste Vulkan, Olympus Mons, 26,4 km über die Umgebung des nördlichen Tieflands. Mit einer Gipfelhöhe von etwa 21,3 km über dem mittleren Null-Niveau ist er die höchste bekannte Erhebung im Sonnensystem.
+
+Ein weiteres, wenn auch weniger ausgedehntes vulkanisches Gebiet ist die Elysium-Region nördlich des Äquators mit den Schildvulkanen Elysium Mons, Hecates Tholus und Albor Tholus.
+Stromtäler
+Kasei Vallis, das größte Stromtal des Mars
+
+Auf der Marsoberfläche verlaufen Stromtäler, die mehrere hundert Kilometer lang und mehrere Kilometer breit sein können. Die heutigen Trockentäler beginnen ziemlich abrupt und haben keine Zuflüsse. Die meisten entspringen an den Enden der Mariner-Täler und laufen nördlich im Chryse-Becken zusammen. In den Tälern erheben sich mitunter stromlinienförmige Inseln. Sie weisen auf eine vergangene Flutperiode hin, bei der über einen geologisch relativ kurzen Zeitraum große Mengen Wasser geflossen sein müssen. Es könnte sich um Wassereis gehandelt haben, das sich unter der Marsoberfläche befand, danach durch vulkanische Prozesse geschmolzen wurde und dann abgeflossen ist.
+
+Darüber hinaus finden sich an Abhängen und Kraterrändern Spuren von Erosionen, die möglicherweise ebenfalls durch flüssiges Wasser verursacht wurden.
+
+2006 proklamierte die NASA einen einzigartigen Fund: Auf einigen NASA-Fotografien, die im Abstand von sieben Jahren vom Mars gemacht wurden, lassen sich Veränderungen auf der Marsoberfläche erkennen, die eine gewisse Ähnlichkeit mit Veränderungen durch fließendes Wasser haben. Innerhalb der NASA wird nun diskutiert, ob es neben Wassereis auch flüssiges Wasser geben könnte.[19]
+Delta-Strukturen
+
+In alten Marslandschaften, z. B. im Eberswalde-Krater auf der Südhalbkugel oder in der äquatornahen Hochebene Xanthe Terra, finden sich typische Ablagerungen einstiger Flussdeltas.
+Tharsis-Tholus-Streifen, aufgenommen mit der Hirise-Kamera des Mars Reconnaissance Orbiters. Der Streifen ist links in der Mitte zu sehen. Rechts sind die Ausläufer von Tharsis Tholus.
+
+Seit längerem vermutet man, dass die tief eingeschnittenen Täler in Xanthe Terra einst durch Flüsse geformt wurden. Wenn ein solcher Fluss in ein größeres Becken, beispielsweise einen Krater, mündete, lagerte er erodiertes Gesteinsmaterial als Sedimente ab. Die Art der Ablagerung hängt dabei von der Natur dieses Beckens ab: Ist es mit dem Wasser eines Sees gefüllt, so bildet sich ein Delta. Ist das Becken jedoch trocken, so verliert der Fluss an Geschwindigkeit und versickert langsam. Es bildet sich ein sogenannter Schwemmkegel, der sich deutlich vom Delta unterscheidet.
+
+Jüngste Analysen von Sedimentkörpern auf Basis von Orbiter-Fotos weisen an zahlreichen Stellen in Xanthe Terra auf Deltas hin Flüsse und Seen waren in der Marsfrühzeit also recht verbreitet.[20]
+Dark Slope Streaks
+
+Dunkle Streifen an Hängen sind auf dem Mars häufig zu sehen. Sie treten an steilen Hängen von Kratern, Mulden und Tälern auf und werden mit zunehmendem Alter heller. Manchmal beginnen sie in einem kleinen punktförmigen Bereich und werden dann zunehmend breiter. Man beobachtete, dass sie sich um Hindernisse, wie Mulden, weiterbewegen.
+
+Es wird angenommen, dass die Farbe von dunklen darunterliegenden Schichten stammt, die durch Lawinen von hellem Staub freigelegt werden. Es wurden jedoch auch andere Hypothesen aufgestellt, wie Wasser oder sogar der Wuchs von Organismen. Das Interessanteste an diesen dunklen Streifen (engl. dark slope streaks) ist, dass sie sich auch heute noch bilden.[21]
+Chaotische Gebiete
+
+Auf dem Mars gibt es zahlreiche Regionen mit einer Häufung von unterschiedlich großen Gesteinsbrocken und tafelbergähnlichen Erhebungen. Sie werden auch chaotische Gebiete genannt. Ariadnes Colles ist mit einer Fläche von etwa 29.000 km² so ein Gebiet. Es liegt im Terra Sirenum, einem südlichen Hochland des Mars. Dabei haben die Blöcke Ausmaße von einem bis zu zehn Kilometern Ausdehnung. Die größeren Blöcke ähneln Tafelbergen mit Erhebungen von bis zu 300 Metern.
+
+Es treten hierbei riefenartige Strukturen und Runzelrücken (engl. wrinkle ridges) auf. Die Ursachen dafür sind vulkanisch-tektonische Bewegungen.[22]
+Gesteinsschichten und Ablagerungen
+Salzlager
+
+Mit Hilfe der Sonde Mars Odyssey wies die NASA ein umfangreiches Salzlager in den Hochebenen der Südhalbkugel des Mars nach. Vermutlich entstanden diese Ablagerungen durch Oberflächenwasser vor etwa 3,5 bis 3,9 Milliarden Jahren.[23]
+Carbonatvorkommen
+
+Mit Hilfe der Compact Reconnaissance Imaging Spectrometer for Mars (CRISM) an Bord der NASA-Sonde Mars Reconnaissance Orbiter konnten Wissenschaftler Carbonat-Verbindungen in Gesteinsschichten rund um das knapp 1500 Kilometer große Isidis-Einschlagbecken nachweisen. Demnach wäre das vor mehr als 3,6 Milliarden Jahren existierende Wasser hier nicht sauer, sondern eher alkalisch oder neutral gewesen.
+
+Carbonatgestein entsteht, wenn Wasser und Kohlendioxid mit Kalzium, Eisen oder Magnesium in vulkanischem Gestein reagiert. Bei diesem Vorgang wird Kohlendioxid aus der Atmosphäre in dem Gestein eingelagert. Dies könnte bedeuten, dass der Mars früher eine dichte kohlendioxidreiche Atmosphäre hatte, wodurch ein wärmeres Klima möglich wurde, in dem es auch flüssiges Wasser gab.[24]
+
+Mit Hilfe von Daten des MRO wurden 2010 Gesteine entdeckt, die durch kosmische Einschläge aus der Tiefe an die Oberfläche befördert worden waren. Anhand ihrer spezifischen spektroskopischen Fingerabdrücke konnte festgestellt werden, dass sie hydrothermal (unter Einwirkung von Wasser) verändert wurden. Neben diesen Karbonat-Mineralen wurden auch Silikate nachgewiesen, die vermutlich auf die gleiche Weise entstanden sind. Dieser neue Fund beweise, dass es sich dabei nicht um örtlich begrenzte Vorkommen handele, sondern dass Karbonate in einer sehr großen Region des frühen Mars entstanden seien.[25]
+Hämatitkügelchen
+Hämatitkügelchen auf dem Felsen Berry Bowl
+
+Die Marssonde Opportunity fand im Gebiet des Meridiani Planum millimetergroße Kügelchen des Eisenminerals Hämatit. Diese könnten sich vor Milliarden Jahren unter Einwirkung von Wasser abgelagert haben. Darüber hinaus wurden Minerale gefunden, die aus Schwefel-, Eisen- oder Bromverbindungen aufgebaut sind, wie zum Beispiel Jarosit. Auf der entgegengesetzten Hemisphäre[26] des Mars fand die Sonde Spirit in den Columbia Hills das Mineral Goethit, das ausschließlich unter dem Einfluss von Wasser gebildet werden kann.
+Kieselsäure
+
+Forscher entdeckten 2010 mit Hilfe von MRO Ablagerungen auf einem Vulkankegel, die von Wasser verursacht wurden. Sie konnten das Mineral als Kieselsäurehydrat identifizieren, das nur in Verbindung mit Wasser entstanden sein kann. Die Wissenschaftler nehmen an, dass, falls es auf dem Mars Leben gegeben hat, es sich dort in der hydrothermalen Umgebung am längsten hätte halten können.[27]
+Polkappen
+Die Nordpolregion, aufgenommen von Mars Global Surveyor
+ Hauptartikel: Polkappen des Mars
+
+Der Mars besitzt zwei auffällige Polkappen, die zum größten Teil aus gefrorenem Kohlendioxid (Trockeneis) sowie einem geringen Anteil an Wassereis zusammengesetzt sind. Die nördliche Polkappe hat während des nördlichen Marssommers einen Durchmesser von rund 1000 Kilometern. Ihre Dicke wird auf 5 km geschätzt. Die südliche Polkappe ist mit 350 km Durchmesser und einer Dicke von 1½ km weniger ausgedehnt. Die Polarkappen zeigen spiralförmige Einschnitte, deren Entstehung bislang nicht geklärt ist.
+
+Wenn im Sommer die jeweiligen Polkappen teilweise abschmelzen, werden darunter geschichtete Ablagerungen sichtbar, die möglicherweise abwechselnd aus Staub und Eis zusammengesetzt sind. Im Marswinter nimmt der Durchmesser der dann jeweils der Sonne abgewandten Polkappe durch ausfrierendes Kohlendioxid wieder zu.
+
+Da ein größerer, stabilisierender Mond fehlt, taumelt der Mars mit einer Periode von etwa 5 Millionen Jahren. Die Polarregionen werden daher immer wieder so stark erwärmt, dass das Wassereis schmilzt. Durch das abfließende Wasser entstehen die Riemen und Streifen an den Polkappen.
+Wasservorkommen
+
+Der Mars erscheint heute als trockener Wüstenplanet. Die bislang vorliegenden Ergebnisse der Marsmissionen lassen jedoch den Schluss zu, dass die Marsatmosphäre in der Vergangenheit (vor Milliarden Jahren) wesentlich dichter war und auf der Oberfläche des Planeten reichlich flüssiges Wasser vorhanden war.
+Eisvorkommen an den Polen
+Die Südpolregion, aufgenommen von Viking Orbiter
+
+Durch Radarmessungen mit der Sonde Mars Express wurden in der Südpolarregion, dem Planum Australe, Ablagerungsschichten mit eingelagertem Wassereis entdeckt, die weit größer und tiefreichender als die hauptsächlich aus Kohlendioxideis bestehende Südpolkappe sind. Die Wassereisschichten bedecken eine Fläche, die fast der Größe Europas entspricht, und reichen in eine Tiefe von bis zu 3,7 Kilometern. Das in ihnen gespeicherte Wasservolumen wird auf bis zu 1,6 Millionen Kubikkilometer geschätzt circa zwei Drittel des irdischen Grönlandeispanzers was laut der Europäischen Weltraumorganisation (ESA) ausreichen würde, die Marsoberfläche mit einer etwa 11 Meter dicken Wasserschicht zu bedecken.[28]
+Weitere Eisvorkommen
+Beobachtete Veränderungen könnten Anzeichen für fließendes Wasser innerhalb der letzten Jahre sein.[19]
+
+Die schon lange gehegte Vermutung, dass sich unter der Oberfläche des Mars Wassereis befinden könnte, erwies sich 2005 durch Entdeckungen der ESA-Sonde Mars Express als richtig.
+
+Geologen gehen von wiederkehrenden Vereisungsperioden auf dem Mars aus, ähnlich irdischen Eiszeiten. Dabei sollen Gletscher bis in subtropische Breiten vorgestoßen sein. Die Forscher schließen dies aus Orbiter-Fotos, die Spuren einstiger Gletscher in diesen äquatornahen Gebieten zeigen. Zusätzlich stützen auch Radarmessungen aus der Umlaufbahn die Existenz beträchtlicher Mengen an Bodeneis in ebendiesen Gebieten. Diese Bodeneisvorkommen werden als Reste solcher Mars-Eiszeiten gedeutet.[29]
+
+Auf der Europäischen Planetologenkonferenz EPSC im September 2008 in Münster wurden hochauflösende Bilder des Mars Reconnaissance Orbiters der NASA vorgestellt, die jüngste Einschlagkrater zeigen. Wegen der sehr dünnen Atmosphäre stürzen die Meteoriten praktisch ohne Verglühen auf die Marsoberfläche. Die fünf neuen Krater, die nur drei bis sechs Meter Durchmesser und eine Tiefe von 30 bis 60 cm aufweisen, wurden in mittleren nördlichen Breiten gefunden. Sie zeigen an ihrem Boden ein gleißend weißes Material. Wenige Monate später waren die weißen Flecken durch Sublimation verschwunden. Damit erhärten sich die Hinweise, dass auch weit außerhalb der Polgebiete Wassereis dicht unter der Marsoberfläche begraben ist.[30][31]
+Flüssiges Wasser
+
+Unter der Kryosphäre des Mars werden große Mengen flüssigen Wassers vermutet. Nahe oder an der Oberfläche ist es für flüssiges Wasser zu kalt, und Eis würde langsam verdunsten, da der Partialdruck von Wasser in der Marsatmosphäre zu gering ist.
+
+Es gibt jedoch Hinweise, dass die Raumsonde Phoenix Wassertropfen auf der Oberfläche entdeckt habe. Dabei könnten Perchlorate als Frostschutz wirken. Diese Salze haben die Eigenschaft, Wasser anzuziehen. Dies kann auch Wasserdampf aus der Atmosphäre sein. Bei ausreichender Konzentration der Salze könnte Wasser sogar bis 70 °C flüssig bleiben. Durch eine Durchmischung mit Perchloraten könnte Wasser auch unter der Oberfläche in flüssigem Zustand vorhanden sein.[32] 2010 fanden Forscher der Uni Münster Belege dafür, dass zumindest im Frühjahr und in Kratern wie dem Russell-Krater flüssiges Wasser auf der Marsoberfläche existiert. Auf Fotos, die vom Mars Reconnaissance Orbiter aufgenommen wurden, entdeckten sie an steilen Hängen Erosionsrinnen, die sich zwischen November 2006 und Mai 2009 verlängert hatten. Dass die Rinnen nach unten dünner werden, deuten die Forscher als Versickern,[33] andere als Verdunsten.[34]
+
+Eine alternative Erklärung für die Erosionsrinnen schlugen Wissenschaftler der NASA 2010 vor: Kohlendioxid, das sich im marsianischen Winter bei unter -100 °C aus der Atmosphäre an den Berghängen als Trockeneis ansammelt, bei Erwärmung des Planeten als sublimiertes Gas die Hänge hinab"fließt" und dabei Staub erodiert.[35][36]
+
+Mit dem abbildenden Spektrometer (CRISM) des Mars Reconnaissance Orbiters konnten Spektren von aktiven (jahreszeitlich dunkleren) Rinnen gewonnen werden, deren Auswertung, 2015 veröffentlicht,[37] Magnesiumperchlorat, Magnesiumchlorat und Natriumperchlorat ergaben.
+Siehe auch: Extraterrestrischer Ozean
+Innerer Aufbau
+Illustration des vermuteten Marsaufbaus
+
+Ãœber den inneren Aufbau des Mars ist nur wenig bekannt, da bislang nur begrenzt seismische Messungen vorgenommen werden konnten.
+
+Sein Inneres gliedert sich ähnlich dem Schalenaufbau der Erde in eine Kruste, einen Gesteinsmantel und einen Kern, der überwiegend aus Eisen und zu etwa 14 bis 17 Prozent aus Schwefel besteht. Der Kern beinhaltet etwa doppelt so viele leichte Elemente wie der Erdkern. Deshalb ist die Dichte des Kerns niedriger, als es bei einem reinen Eisenkern der Fall wäre.[38]
+
+Laut neueren experimentellen Simulationen der Bedingungen in der Übergangszone zwischen Mantel und Kern (Messungen des Mars Global Surveyor ergaben eine Temperatur von 1500 Grad Celsius und einen Druck von 23 Gigapascal) hat der Kern des Mars im Unterschied zu dem der Erde keinen inneren festen Bereich, sondern ist vollständig flüssig.[39] Dies belegt auch die Analyse der Bahndaten des Mars Global Surveyor. Dabei konnte nachgewiesen werden, dass der Mars einen flüssigen Kern mit einem Radius zwischen 1520 und 1840 km besitzt und damit eine höhere Temperatur hat, als zuvor angenommen wurde.
+
+Der Kern ist von einem Mantel aus Silicaten umgeben, der viele der tektonischen und vulkanischen Merkmale des Planeten formte, nun aber inaktiv zu sein scheint. Die durchschnittliche Dicke der Planetenkruste beträgt etwa 50 km, mit einem Maximum von 125 km.[38] Im Vergleich dazu ist die Erdkruste mit einer Dicke von durchschnittlich 40 km nur etwa ein Drittel so dick, wenn man die relative Größe der beiden Planeten berücksichtigt.
+Magnetfeld
+Magnetisierung des Mars Rot und Blau kennzeichnen entgegengesetzte Richtungen des Magnetfelds, ein Drittel der Südhalbkugel
+
+Anders als die Erde und der Merkur besitzt der Mars kein globales Magnetfeld mehr, seit er es ca. 500 Millionen Jahre nach seiner Entstehung verlor. Vermutlich erlosch es, als der Zerfall radioaktiver Elemente nicht mehr genügend Wärmeenergie produzierte, um im flüssigen Kern Konvektionsströmungen anzutreiben. Weil der Mars keinen festen inneren Kern besitzt, konnte er den Dynamo-Effekt nicht auf die gleiche Art aufbauen wie die Erde.
+
+Dennoch ergaben Messungen einzelne und sehr schwache lokale Magnetfelder. Die Messung des Magnetfeldes wird erschwert durch die Magnetisierung der Kruste mit Feldstärken von bis zu 220 Nanotesla und durch externe Magnetfelder mit Stärken zwischen wenigen Nanotesla und bis zu 100 Nanotesla, die durch die Wechselwirkung des Sonnenwindes mit der Marsatmosphäre entstehen und zeitlich sehr stark variieren. Nach den Analysen der Daten des Mars Global Surveyor konnte die Stärke des Magnetfeldes trotzdem sehr genau bestimmt werden sie liegt bei weniger als 0,5 Nanotesla gegenüber 30 bis 60 Mikrotesla des Erdmagnetfeldes.
+
+Messungen von Magnetfeldlinien durch Mars Global Surveyor ergaben, dass Teile der planetaren Kruste durch das einstige Magnetfeld stark magnetisiert sind, aber mit unterschiedlicher Orientierung, wobei gleichgerichtete Bänder von etwa 1000 km Länge und 150 km Breite auftreten. Ihre Größe und Verteilung erinnert an die streifenförmigen Magnetanomalien auf den Ozeanböden der Erde. Durch sie wurde die Theorie der Plattentektonik gestützt, weshalb 1991 auch eine ähnliche Theorie für den Mars entwickelt wurde. Magnetische Beobachtungen auf dem Mars sind jedoch noch nicht detailliert genug, um sichere Schlussfolgerungen zu erlauben oder gar die Theorie zu bestätigen.
+
+Möglicherweise werden bei der mit der Zeit zwangsläufigen Abkühlung des Marskerns durch die damit einsetzende Auskristallisation des Eisens und die freigesetzte Kristallisationswärme wieder Konvektionen einsetzen, die ausreichen, dass der Planet in ein paar Milliarden Jahren wieder über ein globales Magnetfeld in alter Stärke verfügt.[39]
+Monde
+Die Umlaufbahnen von Phobos und Deimos
+Phobos (oben) und Deimos (unten) im Größenvergleich
+
+Zwei kleine Monde, Phobos und Deimos (griech. Furcht und Schrecken), umkreisen den Mars. Sie wurden 1877 von dem US-amerikanischen Astronomen Asaph Hall entdeckt und nach den in der Ilias überlieferten beiden Begleitern, die den Wagen des Kriegsgottes Ares (lat. Mars) ziehen, benannt.
+
+Phobos (Durchmesser 26,8 × 22,4 × 18,4 km) und Deimos (Durchmesser 15,0 × 12,2 × 10,4 km) sind zwei unregelmäßig geformte Felsbrocken. Möglicherweise handelt es sich um Asteroiden, die vom Mars eingefangen wurden. Phobos große Halbachse beträgt 9.376 km, diejenige von Deimos 23.459 km. Phobos ist damit kaum mehr als 6.000 km von der Oberfläche des Mars entfernt, der Abstand ist geringer als der Durchmesser des Planeten.
+
+Die periodischen Umlaufbewegungen der beiden Monde befinden sich mit der Größe von 0,31891 (Phobos) und 1,262 Tagen (Deimos) zueinander in einer 1:4-Bahnresonanz.
+
+Die Umlaufzeit von Phobos ist kürzer als die Rotationszeit von Mars. Der Mond kommt dem Planeten durch die Gezeitenwechselwirkung auf einer Spiralbahn langsam immer näher und wird schließlich auf diesen stürzen oder durch die Gezeitenkräfte auseinandergerissen werden, so dass er für kurze Zeit zu einem Marsring wird. Für ihn berechneten DLR-Forscher, basierend auf neueren Daten der europäischen Raumsonde Mars Express, dass dies in ca. 50 Millionen Jahren geschehen wird. Deimos wird dagegen in einer noch ferneren Zukunft dem Mars entfliehen. Er driftet durch die Gezeitenwechselwirkung langsam nach außen, wie alle Monde, die langsamer (und nicht retrograd) um einen Planeten kreisen, als dieser rotiert.
+
+Ihre Existenz war schon lange vorher mehrmals literarisch beschrieben worden, zuletzt von Voltaire, der in seiner 1750 erschienenen Geschichte Micromégas über zwei Marsmonde schreibt. Es ist wahrscheinlich, dass Voltaire diese Idee von Jonathan Swift übernahm, dessen Buch Gullivers Reisen 1726 erschienen war. Darin wird im dritten Teil beschrieben, die Astronomen des Landes Laputa hätten ebenfalls zwei kleinere Sterne oder Satelliten entdeckt, die um den Mars kreisen, wovon der innere vom Zentrum des Hauptplaneten genau drei seiner Durchmesser entfernt ist und der äußere fünf. Es wird vermutet, dass Swift von einer Fehlinterpretation Johannes Keplers gehört hatte. Der hatte das Anagramm, das Galileo Galilei 1609 an ihn schickte, um ihm die Entdeckung der Phasen der Venus mitzuteilen, als die Entdeckung zweier Marsmonde aufgefasst.
+Entstehungsgeschichte
+Datei:Mars.ogvMediendatei abspielen
+Animation, welche die Topographie des Mars zeigt. Olympus Mons Mariner-Täler Mars Südpol Hellas-Becken Mars Nordpol
+
+Anhand der astrogeologischen Formationenvielfalt und der Verteilung von Einschlagskratern kann ein Großteil der Geschichte des Planeten abgeleitet werden. Der Mars entstand, wie die übrigen Planeten des Sonnensystems, vor etwa 4,5 Milliarden Jahren durch Zusammenballung kleinerer Körper, sogenannter Planetesimale, innerhalb der protoplanetaren Scheibe zu einem Protoplaneten. Vor 4 Milliarden Jahren bildete der im Innern noch glutflüssige planetare Körper eine feste Gesteinskruste aus, die einem heftigen Bombardement von Asteroiden und Kometen ausgesetzt war.
+Noachische Periode
+
+Die ältesten der heute noch vorhandenen Formationen, wie das Hellas-Becken, und die verkraterten Hochländer, wie Noachis Terra, wurden vor 3,8 bis 3,5 Milliarden Jahren, in der so genannten Noachischen Periode, gebildet. In dieser Periode setzte die Zweiteilung der Marsoberfläche ein, wobei die nördlichen Tiefländer gebildet wurden. Durch starke vulkanische Eruptionen wurden weite Teile des Planeten von Ablagerungen aus vulkanischer Lava und Asche bedeckt. Diese wurden an vielen Stellen durch Wind und Wasser wieder abgetragen und ließen ein Netzwerk von Tälern zurück.
+Hesperianische Periode
+
+Das geologische Mittelalter des Mars wird als Hesperianische Periode bezeichnet. Sie umfasst den Zeitraum von vor 3,5 bis 1,8 Milliarden Jahren. In dieser Periode ergossen sich riesige Lavamengen aus ausgedehnten Spalten in der Marskruste und bildeten weite Ebenen, wie Hesperia Planum. Es entstanden auch die ältesten Vulkane der Tharsis- und der Elysium-Region, wobei die Gesteinskruste stark verformt wurde und sich das Grabensystem der Mariner-Täler öffnete. Es bildeten sich die gewaltigen Stromtäler, in denen große Wassermengen flossen und sich stellenweise aufstauten.
+
+Es entwickelte sich auf dem Mars ein Wasserkreislauf. Im Unterschied zur Erde gab es jedoch keinen Wetterzyklus mit Verdunstung, Wolkenbildung und anschließendem Niederschlag. Das Wasser versickerte im Untergrund und wurde später durch hydrothermale Prozesse wieder an die Oberfläche getrieben. Da jedoch der Planet immer weiter abkühlte, endete dieser Prozess vor etwa 1,5 Milliarden Jahren, und es hielten sich nur noch Gletscher an der Oberfläche. Zeichen dieser Aktivität sind vor kurzem entdeckte Moränen am Olympus Mons.[40]
+Amazonische Periode
+
+Das jüngste geologische Zeitalter des Mars wird als Amazonische Periode bezeichnet und begann vor 1,8 Milliarden Jahren. In dieser Phase entstanden die jüngeren Vulkane der Tharsis- und der Elysium-Region, aus denen große Lavamassen flossen. So bildeten sich weite Ebenen aus wie zum Beispiel Amazonis Planitia.
+
+2008 fanden Forscher Hinweise auf Geysire auf dem Mars, die vor einigen Millionen Jahren aktiv gewesen sein dürften. Dabei hätten sie Fontänen von kohlensäurehaltigem Wasser einige Kilometer weit in die Höhe geschossen. Darauf deuten auch die Formen von Ablagerungen hin, die britische Forscher in der Nähe zweier ausgedehnter Grabensysteme entdeckten. Wahrscheinlich wurden diese Eruptionen durch Blasen aus Kohlendioxid ausgelöst. Dadurch wurde das Wasser aus einer Tiefe von bis zu vier Kilometern durch Spalten im Marsboden an die Oberfläche gedrückt. Die Fontänen müssen dabei mit einem so großen Druck herausgepresst worden sein, dass das schlammige Wasser erst in einer Entfernung von mehreren Kilometern von der Austrittsstelle wieder auf den Boden regnete oder, bedingt durch die tiefen Temperaturen, als Hagel niederging.[41]
+
+Gegenwärtig wird die Oberfläche des Mars hauptsächlich durch Winderosion und Hangrutschung geformt.
+Erforschung
+
+Aufgrund seiner großen Helligkeit war der Mars schon im frühen Altertum als Planet bekannt. Wegen seiner langen Planetenschleifen (die alle 2 Jahre in der Opposition auftreten) galten seine Bewegungen den Ägyptern als unvorhersehbar. Den Babyloniern gelang es zwar, sie näherungsweise vorauszusagen, sie schrieben die Bahnanomalien aber den Launen und der Gewalttätigkeit des Gottes Nergal zu.
+Vor dem Raumfahrtzeitalter
+Marsoberfläche nach Schiaparelli (1888)
+Mars auf einer astronomischen Zeichnung des 19. Jahrhunderts (Trouvelot, 1881)
+
+ Tycho Brahe (15461601) vermaß die Planetenpositionen des Mars mit bis dahin nicht gekannter Genauigkeit und ermöglichte es so Johannes Kepler (15711630), die elliptische Bahn des Planeten zu berechnen und die drei Keplerschen Gesetze abzuleiten.
+ Christiaan Huygens entdeckte 1659 eine dunkle, dreieckige Zone (Syrtis Major) auf der Marsoberfläche. Aus deren Positionsveränderungen errechnete er die Eigenrotation des Mars zu 24,5 Stunden (heutiger Wert: 24,623 Stunden).
+ Giovanni Domenico Cassini beschrieb 1666 die weißen Polkappen des Mars.
+ Wilhelm Herschel bestimmte 1784 die Neigung der Rotationsachse gegenüber der Umlaufbahn mit 25° (heutiger Wert 25,19°).
+ Wilhelm Beer fertigte 1830 die erste Marskarte an, Angelo Secchi 1863 schon in Farbe.
+ Richard Proctor veröffentlichte 1869 eine detaillierte Marskarte, die er aus Zeichnungen von William Rutter Dawes erstellte.
+ Giovanni Schiaparelli nahm 1877 auf der Marsoberfläche zarte Linienstrukturen wahr, die er Canali (italienisch für Rinnen oder Gräben) nannte und in eine detaillierte Karte eintrug. Er machte zunächst keine Angaben über den Ursprung der Canali (die er für breiter als 100 km schätzte), doch wurden sie in englischen Medien fälschlich als Channel (Kanäle) übersetzt und bald als Werk intelligenter Marsbewohner interpretiert. Auf älteren Marskarten erhielten viele dieser Linien auch Namen. Während einige Astronomen Schiaparellis Beobachtungen bestätigten, wurde die Existenz der Canali von anderen angezweifelt und als Ergebnis optischer Täuschungen bezeichnet. Erst der Vorbeiflug der amerikanischen Mariner-Sonden beendete die Spekulationen, denn Fotos der Marsoberfläche zeigten keine so breiten Rinnen. Drei Canali entsprechen aber den riesigen Canyons Valles Marineris, andere zeichnen Geländestufen und Schattenlinien nach, einige auch längere Kraterketten.
+
+ Hauptartikel: Marskanäle
+
+ Asaph Hall entdeckt bei der günstigen Opposition 1877 die beiden Marsmonde Phobos und Deimos.
+ Percival Lowell gründet 1894 das Lowell-Observatorium in Arizona, um die Marskanäle, ihre jahreszeitlichen Verfärbungen und allfällige Lebensspuren zu erforschen. Spektroskopisch findet man biologische Moleküle, die sich allerdings später als terrestrisch erweisen. In der Atmosphäre werden Spektrallinien von Sauerstoff entdeckt, dessen Volumsanteil aber überschätzt wird.
+ Eugène Antoniadi bestätigte zunächst die Marskanäle, kam aber 1909 am Riesenteleskop Meudon zum Schluss, sie würden nur in kleineren Fernrohren als solche erscheinen. In seinen detaillierten Marskarten die bis zu den ersten Marssonden kaum mehr übertroffen wurden zeichnete er sie als Folge diffuser Flecken ein.
+ Gerard Kuiper wies in den 1950ern Kohlendioxid in der Marsatmosphäre nach und glaubte bis zu den ersten Marssonden an die mögliche Existenz von Moosen oder Flechten.
+
+Im Raumfahrtzeitalter
+Die erste Nahaufnahme vom Mars, aufgenommen von Mariner 4
+
+Viele unbemannte Raumsonden wurden schon zum Mars entsandt, von denen einige sehr erfolgreich waren. Etwa die Hälfte der Missionen endete in einem Misserfolg, die meisten davon waren sowjetische Sonden. Im Unterschied zur Erkundung des Erdmondes gibt es bis heute keine Gesteinsproben, die vom Mars geholt wurden, so dass Marsmeteoriten die einzige Möglichkeit sind, Material vom Mars in irdischen Laboratorien zu erforschen. Bislang hat es auch noch keine bemannte Marsmission gegeben ein aktueller Ansatz dafür ist Mars One.
+ Hauptartikel: Chronologie der Marsmissionen
+1960er Jahre
+Darstellung auf einer ungarischen Sondermarke von 1964
+
+Die beiden sowjetischen Sonden Marsnik 1 und 2 wurden im Oktober 1960 gestartet, um am Mars vorbeizufliegen, erreichten aber noch nicht einmal die Erdumlaufbahn. 1962 versagten drei weitere sowjetische Sonden (Sputnik 22, Mars 1 und Sputnik 24), zwei von ihnen blieben im Erdorbit, die dritte verlor auf dem Weg zum Mars den Kontakt mit der Erde. Auch ein weiterer Versuch im Jahre 1964 schlug fehl.
+
+Zwischen 1962 und 1973 wurden zehn Mariner-Raumsonden vom Jet Propulsion Laboratory der NASA entwickelt und gebaut, um das innere Sonnensystem zu erforschen. Es waren relativ kleine Sonden, die meistens nicht einmal eine halbe Tonne wogen.
+
+Mariner 3 und Mariner 4 waren identische Raumsonden, die am Mars vorbeifliegen sollten. Mariner 3 wurde am 5. November 1964 gestartet, aber die Transportverkleidung löste sich nicht richtig, und die Sonde erreichte den Mars nicht.
+
+Drei Wochen später, am 28. November 1964, wurde Mariner 4 erfolgreich auf eine achtmonatige Reise zum Roten Planeten geschickt. Am 15. Juli 1965 flog die Sonde am Mars vorbei und lieferte die ersten Nahaufnahmen insgesamt 22 Fotos des Planeten. Die Bilder zeigten mondähnliche Krater, von denen einige mit Reif bedeckt zu sein scheinen.
+
+1969 folgten Mariner 6 und Mariner 7 und lieferten insgesamt 200 Fotos.
+1970er Jahre
+
+1971 missglückte der Start von Mariner 8, dafür erhielt die NASA im gleichen Jahr von Mariner 9 mehrere tausend Bilder.
+
+Ebenfalls 1971 landete mit der sowjetischen Mars 3 die erste Sonde weich auf dem Mars, nachdem Mars 2 wenige Tage zuvor gescheitert war. Der Funkkontakt brach jedoch 20 Sekunden nach der Landung ab. Mögliche Ursache war ein gerade tobender globaler Staubsturm, der den Lander umgeworfen haben könnte.
+Bild von Viking 1. Der große Felsen links von der Mitte ist etwa zwei Meter breit. Er wurde Big Joe getauft.
+
+In den 1970er-Jahren landeten die Viking-Sonden erfolgreich auf dem Mars und lieferten die ersten Farbbilder sowie Daten von Bodenproben: Viking 1 schaffte am 20. Juli 1976 als erste US-amerikanische Sonde eine weiche Landung. Die Sowjetunion versuchte noch weitere Landungen auf dem Mars, scheiterte jedoch.
+1980er Jahre
+
+Die einzigen Raumsonden, die in den 1980er Jahren zum Mars flogen, waren die beiden sowjetischen Fobos-Sonden. Sie wurden 1988 von Baikonur aus gestartet und sollten den Mars und seinen Mond Phobos untersuchen. Dafür waren sie im Rahmen einer internationalen Kooperation neben sowjetischen auch mit zahlreichen westlichen Instrumenten bestückt. Der Kontakt zu Fobos 1 brach jedoch schon auf dem Weg zum Mars wegen eines falschen Steuerbefehls ab. Fobos 2 erreichte eine Marsumlaufbahn und einige Daten und Bilder vom Mars wurden zur Erde übertragen. Danach wurde die Sonde zu Phobos gelenkt. Jedoch brach kurz vor dem Rendezvous auch der Kontakt zu Fobos 2 ab.
+1990er Jahre
+
+1992 wurde die US-Sonde Mars Observer gestartet. Sie ging 1993 kurz vor dem Einschwenken in die Umlaufbahn verloren.
+
+Am 16. November 1996 startete Mars 96, die erste russische Raumsonde seit dem Zusammenbruch der Sowjetunion. Doch versagte die Proton-Trägerrakete, so dass Mars 96 wieder in die Erdatmosphäre eintrat und verglühte.
+Der Marsrover Sojourner
+
+Besonderes Aufsehen erregte 1997 der Mars Pathfinder, bei dem zum ersten Mal ein kleines Marsmobil, der Rover Sojourner, eingesetzt wurde. Er landete publikumswirksam am 4. Juli, dem amerikanischen Unabhängigkeitstag, und lieferte viele Aufnahmen von der Umgebung der Landestelle, die von der NASA zum ersten Mal sofort im Internet veröffentlicht wurden.
+
+Eine weitere erfolgreiche Mission war 1997 die des Mars Global Surveyor, bei der die Marsoberfläche in einer hohen Auflösung kartografiert wurde. Am 2. November 2006 fünf Tage vor dem 10-jährigen Jubiläum seines Starts brach der Kontakt mit dem Satelliten ab.
+
+Das Scheitern der Marssonden Mars Climate Orbiter, der wegen eines Programmierfehlers in der Navigation verlorenging, und Mars Polar Lander, der wahrscheinlich wegen eines fehlerhaften Sensors bei der Landung aus größerer Höhe abstürzte, stellte 1999 einen herben Rückschlag für die Marsforschung dar.
+
+Auch die 1998 gestartete japanische Raumsonde Nozomi konnte den Mars nicht erreichen.
+2000er Jahre
+
+Seit dem 24. Oktober 2001 umkreist außer dem Global Surveyor noch 2001 Mars Odyssey den roten Planeten, der spezielle Instrumente zur Fernerkundung von Wasservorkommen an Bord hat.
+
+Von den bis 2002 insgesamt 33 Missionen zum Mars waren nur acht erfolgreich, allesamt US-amerikanisch.
+
+Am 2. Juni 2003 startete im Rahmen der ersten europäischen Marsmission die ESA-Raumsonde Mars Express mit dem Landegerät Beagle 2 erfolgreich zum Mars. Zwar landete Beagle 2 am 25. Dezember 2003 auf der Marsoberfläche, allerdings konnte der Funkkontakt niemals aufgebaut werden. 2014 wurde er auf Bildern des MRO entdeckt. Der Orbiter Mars Express arbeitet jedoch erfolgreich in der Marsumlaufbahn und konnte unter anderem viele Aufnahmen von Formationen machen, von denen man annimmt, dass sie ausgetrocknete oder ausgefrorene Flusstäler seien. Er kartiert den Planeten u. a. mittels Radar und einer Stereokamera im sichtbaren Licht, sowie spektroskopisch auch in Infrarot. Am 30. November 2005 fand die Sonde unter der Ebene Chryse Planitia ein Eisfeld mit 250 km Durchmesser.
+Marsrover Opportunity (MER-B)
+
+Am 10. Juni 2003 wurde die US-amerikanische Marssonde Spirit (MER-A) zum Mars gestartet. An Bord befand sich ein Rover, der nach der Landung drei Monate lang Gesteinsproben entnehmen und nach Spuren von früher vorhandenem Wasser suchen sollte. Die Landung erfolgte am 4. Januar 2004 im Krater Gusev, in den das Ma'adim Vallis mündet. Im April 2009 fuhr sich der Rover in einer Sandanhäufung fest und konnte seit dem 22. März 2010 auch nicht mehr kontaktiert werden (Stand: März 2011).
+
+Am 8. Juli 2003 wurde die baugleiche Sonde Opportunity (MER-B) mit einer Delta-II-Rakete gestartet. Sie landete am 25. Januar 2004 in der Tiefebene Meridiani Planum nahe dem Marsäquator, fast genau gegenüber von Spirit.[26] Die vom Rover gesammelten Beweise, dass der Mars einst warm und feucht war, wurden im Jahresrückblick der Fachzeitschrift Science mit der Wahl zum Durchbruch des Jahres 2004 gewürdigt. Opportunity ist noch immer aktiv (Stand: April 2015).
+vergrößern und Informationen zum Bild anzeigen
+Das Panoramabild, von dem hier nur ein Ausschnitt zu sehen ist, wurde aus hunderten Einzelbildern montiert, die Opportunity vom 6. Oktober bis 6. November 2006 aufgenommen hat. Es zeigt annähernd in Echtfarben den Victoria-Krater vom Cap Verde
+
+Am 12. August 2005 wurde die US-Sonde Mars Reconnaissance Orbiter mit einer Atlas-V-Rakete auf die Reise geschickt und erreichte am 10. März 2006 den Mars. Sie soll ihn mit hochauflösenden Kameras kartografieren und auch nach geeigneten Landestellen für spätere Rover-Missionen suchen. Außerdem soll sie zur Hochgeschwindigkeits-Kommunikation zwischen zukünftigen Raumsonden auf der Marsoberfläche und der Erde dienen.
+Sonnenuntergang auf dem Mars beim Krater Gusev (Spirit am 19. Mai 2005)
+
+2007 fotografierte Mars Reconnaissance sieben fast kreisrunde schwarze und strukturlosen Flecken, die im Nordosten des Marsvulkans Arsia Mons liegen.[42] Der größte, genannt Jeanne, hat einen Durchmesser von etwa 150 Meter. Eine Schrägaufnahme der sonnenbeschienenen Seitenwand im August 2007 zeigte, dass es sich um einen mindestens 78 Meter tiefen senkrechten Schacht handeln muss. Diese Strukturen sind sehr wahrscheinlich vulkanischer Natur und durch den Einbruch einer nicht mehr tragfähigen Deckschicht entstanden.[43]
+
+Am 26. Dezember 2007 machte die High Resolution Stereo Camera des Mars Express Aufnahmen von Eumenides Dorsum, einem Bergrücken westlich der Tharsis-Region. Die Aufnahmen zeigen kilometerlange lineare Strukturen, die von Kanälen unterbrochen sind. Es handelt sich um durch Winderosion entstandene Yardangs (Windhöcker bzw. Sandwälle).
+
+Mit der Sonde Mars Odyssey wies die NASA im März 2008 eine umfangreiche Salzlagerstätte in den Hochebenen der Südhalbkugel nach. Die Wissenschaftler des JPL in Pasadena meinen, sie habe sich vor 3,5 bis 3,9 Milliarden Jahren gebildet. Vermutlich entstanden die Salze durch mineralienreiches Grundwasser, das an die Oberfläche gelangte und dort verdunstete. Die Bilder von Mars Odyssey zeigen kanalähnliche Strukturen, die in den Salzbecken enden.[23] Insgesamt wurden über 200 Gebiete mit Salzvorkommen ausgemacht, die zwischen 1 und 25 km² groß sind. Die Entdeckung deutet darauf hin, dass der Mars vor langer Zeit ein wärmeres und deutlich feuchteres Klima hatte.[44]. Solche Klimaschwankungen dürften durch aperiodische Änderungen der Rotationsachse entstehen, deren Neigung (derzeit 25°) zwischen 14 und 50° variiert.[45]
+Die Orte der sieben erfolgreichen Marslandungen
+
+Am 26. Mai 2008 landete die Sonde Phoenix im nördlichen Polargebiet des Planeten. Sie suchte dort bis November 2008 im Boden nach Wassereis und habitablen Zonen, also für primitive Organismen bewohnbare Umgebungen. Ihr Roboterarm konnte Proben aus etwa 50 cm Tiefe holen, um sie dann in einem Minilabor zu analysieren. Phoenix entdeckte bei einer Grabung weiße Klümpchen, die nach einigen Tagen verschwanden. Man vermutete, dass es sich dabei um Wassereis handelt,[46] was am 31. Juli bestätigt wurde beim Erhitzen einer Gesteinsprobe trat Wasserdampf aus.[47] Mit dem nasschemischen Labor MECA, das die wasserlöslichen Ionen im Marsboden bestimmte, konnten erhebliche Mengen an Perchloraten detektiert werden. Auf der Erde kommen Perchlorate in den ariden Wüstengebieten vor. Natriumperchlorat wird durch Oxidation von Natriumchlorid in der Atmosphäre erzeugt und dann mit dem Staub abgelagert.
+
+
+2010er Jahre
+Curiosity auf dem Mars
+
+Am 26. November 2011 um 15:02 UTC startete die Rover-Mission Mars Science Laboratory (Curiosity) der NASA mit einer Atlas V(541) von Cape Canaveral und landete am 6. August 2012 auf dem Mars. Der Rover kann weite Strecken zurücklegen und umfassende Untersuchungen eines großen Umkreises durchführen. Wichtigstes Projektziel sind geologische Analysen des Marsbodens.
+
+Am 18. November 2013 startete eine weitere NASA-Sonde zum Mars. Die Mission mit dem Projektnamen Mars Atmosphere and Volatile Evolution (MAVEN) soll das Rätsel der verlorenen Atmosphäre aufklären.[48] Der Orbiter umkreist den Planeten seit dem 22. September 2014 und soll sich in fünf Tiefflügen annähern. Weiters wurde am 5. November 2013 eine indische Marsmission gestartet. Sie soll ebenfalls die Atmosphäre sowie verschiedene Oberflächenphänomene untersuchen.[49]
+
+ExoMars Rover ist ein europäischer Rover, dessen Start für 2020 geplant ist. Er soll speziell nach Spuren von Leben suchen. Die Finanzierung dieser Mission ist allerdings noch ungewiss.
+Geplante Missionen
+
+Weitere Pläne der NASA und ESA zur Marserforschung enthalten unter anderem das Aussetzen von kleineren Flugzeugen in der Atmosphäre und nach 2020 die Rückführung von Marsproben zur Erde (Mission Mars Sample Return).
+vergrößern und Informationen zum Bild anzeigen
+Panoramabild der Marsoberfläche, aufgenommen von der Sonde Pathfinder
+
+Im Januar 2004 kündigte der US-amerikanische Präsident George W. Bush Anstrengungen der USA für eine bemannte Marsmission an. Im Rahmen des Raumfahrtprogramms Constellation plante die NASA diese Flüge für die Zeit nach 2020. Der ehemalige NASA-Direktor Michael Griffin nannte die Zeit bis 2037. Constellation wurde aber durch die Nachfolgeregierung unter Barack Obama aus Kostengründen gestrichen.[50]
+
+Auch das langfristig angelegte europäische Raumfahrtprogramm Aurora strebt insbesondere die Landung eines Menschen auf dem Mars an und plant sie für das Jahr 2033.
+
+Darüber hinaus existieren im Rahmen von Visionen einer Marskolonisation Vorstellungen, den Mars durch Terraforming in weiter Zukunft in einen für den Menschen lebensfreundlicheren Planeten umzuwandeln. Robert Zubrin, Edwin Aldrin und andere namhafte Stimmen in den Vereinigten Staaten von Amerika treten mittlerweile dafür ein, auf dem Mars unter dem Motto Mars to Stay schon in naher Zukunft die erste menschliche Siedlung zu begründen: Das sei möglich und sinnvoll, weil es wesentlich weniger Aufwand erfordere, die ersten Marsfahrer dauerhaft auf dem Planeten siedeln zu lassen, als sie sogleich wieder zur Erde zurückzuholen.
+Möglichkeit von Leben
+ Hauptartikel: Leben auf dem Mars
+
+Die Ökosphäre (oder habitable Zone) des Sonnensystems reicht von 0,95 bis 1,37 AE Abstand zur Sonne. Im Sonnensystem befindet sich nur die Erde innerhalb dieses Gürtels um die Sonne, der Mars liegt knapp außerhalb.
+
+Höheres oder gar intelligentes Leben gibt es auf dem Mars nicht, Wissenschaftler halten jedoch primitive Lebensformen (Mikroben) tiefer im Boden, um vor UV-Strahlen geschützt zu sein, für denkbar.[51] Tatsächlich haben die in der Antarktis im Inneren von Gesteinen lebenden Pilzarten Cryomyces antarcticus und Cryomyces minteri simulierte Mars-Umweltbedingungen relativ gut überstanden: Nach 18 Monaten auf der Internationalen Raumstation[52] enthielten knapp 10 % der Proben noch fortpflanzungsfähige Zellen.[53] Auch die Flechte Xanthoria elegans hat die simulierten Marsbedingungen während des Experiments überlebt.
+Vermutungen vor dem Raumzeitalter
+Marsoberfläche nach Oswald Lohse (1888). Auf der Karte ist das Kanalsystem Schiaparellis nicht eingezeichnet. Die von Lohse gewählten Namen für die Seen und Ozeane sind heute nicht mehr gebräuchlich
+
+Der Gedanke an die Möglichkeit von Leben auf dem Mars beflügelte oft die Fantasie der Menschen. Im 18. Jahrhundert beobachtete man, dass die dunklen Flecken auf der Marsoberfläche ihre Farbe änderten und wuchsen oder schrumpften. Man hielt sie für ausgedehnte Vegetationszonen, deren Ausdehnung sich mit den Jahreszeiten änderte.
+
+Durch Schiaparellis Entdeckung der Marskanäle wurden die Spekulationen um intelligentes Leben auf dem Mars angefacht.
+
+So entstanden zahlreiche Legenden um vermeintliche Zivilisationen auf dem Mars. Die Diskussionen um die Marsmenschen hielten etwa ein Jahrhundert an. Der US-Amerikaner Percival Lowell, einer der heftigsten Verfechter der Marskanäle-Theorie, gründete sogar eine eigene Sternwarte, um die Marsbewohner zu erforschen. Für ihn waren die Kanäle das Produkt außerirdischer Ingenieure, die geschaffen wurden, um die Marszivilisation vor einer großen Trockenheit zu retten. Lowell beschrieb seine Vorstellungen der Marswelt in zahlreichen Publikationen, die weite Verbreitung fanden.
+
+Obwohl nicht alle Astronomen die Kanäle sehen konnten und keine Fotos existierten, hielt sich die Theorie, begleitet von einer heftigen Debatte. Die Vorstellung von außerirdischem Leben übt bis heute eine Faszination auf die Menschen aus, die mit wissenschaftlichem Interesse alleine oft nicht erklärt werden kann. Erst die Ergebnisse der unbemannten Marsmissionen beendeten den Streit um die Kanäle.
+
+Untersuchungen durch Viking
+
+Als im Juli 1976 der Orbiter 1 der Viking-Mission Bilder der Cydonia-Region machte und diese zur Erde schickte, wurde der Mars in der Öffentlichkeit wieder zum Gesprächsthema. Eine der Aufnahmen zeigte eine Formation auf der Marsoberfläche, die einem menschlichen Gesicht ähnelte, das gen Himmel blickt. In der unmittelbaren Nähe wurden außerdem Strukturen entdeckt, die Pyramiden auf der Erde ähneln, sowie rechteckige Strukturen (von den Wissenschaftlern Inka-Stadt getauft). Erst die Mission Mars Global Surveyor der NASA brachte im April 1998 für viele die Ernüchterung: Alle entdeckten Strukturen waren das Ergebnis natürlicher Erosion. Durch neue Bilder mit wesentlich höherer Auflösung wurde deutlich, dass auf dem Mars keine künstlichen Strukturen außerirdischer Intelligenz ersichtlich sind.
+Das Marsgesicht in der Cydonia-Region; Aufnahme des Orbiters von Viking 1, 1976
+
+Viking 1 und 2 hatten unter anderem die Aufgabe, der Frage nach dem Leben auf dem Mars nachzugehen. Dabei wurden ein chemisches und drei biologische Experimente durchgeführt. In dem chemischen Experiment wurde versucht, organische Substanzen im Marsboden nachzuweisen. Dazu wurde eine am MIT entwickelte GC/MS-Einheit (Kopplung eines Gaschromatographen mit einem Massenspektrometer) benutzt. Es konnten allerdings keine auf Kohlenstoff aufbauenden organischen Substanzen nachgewiesen werden.
+
+Das erste biologische Experiment beruhte auf Stoffwechselaktivitäten von Organismen. Eine Bodenprobe wurde mit einer Nährlösung benetzt und entstehende Gase registriert. Der Marsboden reagierte auf das Experiment mit Abgabe großer Mengen Sauerstoff. Im zweiten Experiment wurde eine Nährlösung mit radioaktiven Kohlenstoffatomen versehen und auf eine Probe gegeben. Als Ergebnis eines Stoffwechsels hätten sie unter den ausgeschiedenen Gasen nachgewiesen werden müssen. Tatsächlich wurden radioaktive Kohlenstoffatome nachgewiesen. Das dritte Experiment war ein Photosynthese-Experiment. Radioaktiv markiertes Kohlendioxid wurde dem Marsboden zugesetzt. Dieses Kohlendioxid hätte assimiliert werden und später nachgewiesen werden müssen. Auch dieses Ergebnis war positiv. Obwohl die Ergebnisse der biologischen Experimente positiv waren, gaben sie aufgrund des negativen Ergebnisses des GC/MS-Versuchs keinen schlüssigen Beweis für die Existenz oder Nichtexistenz von Leben auf dem Mars.
+1990er und 2000er Jahre
+Marsgesicht, Aufnahme von Mars Global Surveyor, 2001
+
+Im Jahr 1996 fanden David S. McKay und seine Mitarbeiter Strukturen im Marsmeteoriten ALH 84001, die sie als Spuren von fossilen Bakterien deuteten. Das in diesem Meteoriten gefundene, kettenartig angeordnete Magnetit ähnelt morphologisch dem bakteriellen Magnetit aus Magnetospirillum magnetotacticum. Allerdings wird die Beweiskraft der gefundenen Strukturen von vielen Wissenschaftlern angezweifelt, da diese auch auf rein chemischem Wege entstehen konnten.
+
+Am 23. Januar 2004 entdeckte die europäische Marssonde Mars Express am Südpol des Mars große Mengen gefrorenen Wassers, Ende Juli 2005 auch in einem nahe dem Nordpol gelegenen Krater.
+
+Ende März 2004 wurde bekannt, dass Forscher der NASA und der ESA unabhängig voneinander Methan in der Marsatmosphäre nachgewiesen haben. Ob das Methan geologischen Ursprungs ist oder etwa durch den Stoffwechsel von Mikroorganismen gebildet wurde, sollen weitere Untersuchungen zeigen.
+
+Ebenfalls Anfang 2004 entdeckte die Marssonde Opportunity Gesteine, die in offenstehendem Wasser abgelagert worden sein müssen und viele regelmäßig verteilte kugelige, bis 1 cm große Hämatit-Konkretionen enthalten. Solche Konkretionen kommen auch auf der Erde vor. Unter irdischen Bedingungen ist es wahrscheinlich, dass bei ihrer Entstehung Bakterien beteiligt sind. Ob dies auch für den Mars gilt, könnten nur Laboruntersuchungen auf der Erde zeigen.
+
+Weitere Mikrostrukturen, welche die Rover Spirit und Opportunity 2004 entdeckt hatten und in denen ein Teil der interessierten Öffentlichkeit Hinweise auf Leben hatte sehen wollen, erwiesen sich bei näherer Untersuchung als abiotisch oder künstlich, so zum Beispiel Schleifspuren auf durch die Instrumente bearbeiteten Gesteinsoberflächen oder Filamente, die sich als Textilfasern der Lande-Airbags herausstellten.
+
+Forschungsergebnisse auf der Erde bestätigen, dass es Leben auch in extremen Bedingungen geben kann. Bei Bohrungen im grönländischen Eis entdeckten Forscher der University of California, Berkeley im Jahre 2005 in drei Kilometern Tiefe eine auffallende Menge Methan. Dieses Gas produzierten methanogene Bakterien, die trotz unwirtlicher Lebensbedingungen wie Kälte, Dunkelheit und Nährstoffmangel im Eis überleben. Dabei erhalten sie sich nur mühsam am Leben sie reparieren Erbgutschäden, vermehren jedoch nicht nennenswert ihre Population. Methanogene Mikroben sind eine Untergruppe der Archaebakterien, die sich auf Extremstandorte spezialisiert haben. So fanden sich im Jahr 2002 Mikroben in einer 15.000 Jahre alten heißen Quelle in Idaho. Die Bakterien zählen, wie schon der Name besagt, zu den ältesten Mikroorganismen der Erde. Die Wissenschaftler schätzen das Alter der in Grönland entdeckten Bakterienkolonie auf 100.000 Jahre und vermuten, dass das in der Atmosphäre des Roten Planeten nachgewiesene Methan nicht nur von chemischen Prozessen, sondern auch von solchen Mikroben stammen könnte.
+Aktuelle Forschung
+
+Mit dem Mars Science Laboratory wird versucht, neue Aufschlüsse über mögliches Leben auf dem Mars zu liefern. Es ist fraglich, ob der Mars-Rover tief genug bohren kann, um Leben oder zumindest Lebensreste zu finden. Aber eine Isotopenanalyse des Methans kann bereits weitere Aufschlüsse geben. Leben, wie es auf der Erde bekannt ist, bevorzugt leichtere Wasserstoffisotope.
+Beobachtung
+Stellung zur Erde und Bahneigenschaften
+Planetenschleife des Mars im Sternbild Wassermann im Jahr 2003
+
+Aufgrund der Bahneigenschaften der Planeten überholt die Erde den Mars durchschnittlich alle 779 Tage auf ihrer inneren Bahn. Diesen Zeitraum, der zwischen 764 und 811 Tagen schwankt, nennt man synodische Periode. Befinden sich Sonne, Erde und Mars in dieser Anordnung auf einer Linie, so steht der Mars von der Erde aus gesehen in Opposition zur Sonne. Zu diesem Zeitpunkt ist Mars besonders gut zu beobachten, er steht dann als rötlicher Stern auffallend hell am Nachthimmel. Beobachtet man den Mars regelmäßig, kann man feststellen, dass er vor und nach einer Opposition am Himmel eine Schleifenbewegung vollführt. Diese Planetenschleife (Oppositionsschleife) ergibt sich aus den Sichtwinkeln, die Mars bietet, während er von der Erde überholt wird.
+Marsoppositionen von 2003 bis 2018, relative Bewegung des Mars zur Erde, mit der Erde im Zentrum; Ansicht auf die Ekliptikebene
+
+Da die Planeten sich nicht auf idealen Kreisbahnen, sondern auf mehr oder weniger stark ausgeprägten elliptischen Bahnen bewegen, haben Erde und Mars zum Zeitpunkt der Oppositionen unterschiedliche Entfernungen zueinander. Diese können zwischen 55,6 und 101,3 Millionen Kilometern bzw. 0,37 und 0,68 AE betragen. Bei einer geringen Oppositionsentfernung spricht man von einer Perihelopposition, bei einer großen von einer Aphelopposition.
+Schwankung des minimalen Abstands ErdeMars bei Oppositionen. Die tatsächlichen Werte sind an den einzelnen Punkten ablesbar. Die verbindende Kurve veranschaulicht die mathematische Gesetzmäßigkeit.
+
+Die alle 15 bis 17 Jahre stattfindenden Periheloppositionen bieten die besten Gelegenheiten, den Mars von der Erde aus mittels Teleskop zu beobachten. Der Planet hat dann einen scheinbaren Durchmesser von bis zu 25,8 Bogensekunden. Bei einer Aphelopposition ist er mit 14,1 Bogensekunden nur etwa halb so groß. Besonders erdnahe Oppositionen fanden im Abstand von jeweils 79 Jahren, zum Beispiel in den Jahren 1766, 1845, 1924 und 2003 statt. Am 28. August 2003 betrug der Abstand ErdeMars 55,76 Millionen Kilometer. Dies war die geringste Distanz seit etwa 60.000 Jahren.[54][55] Erst im Jahre 2287 wird der Mars der Erde noch näher kommen, der Abstand beträgt dann 55,69 Millionen Kilometer.
+
+Im Teleskop erscheint der Mars zunächst als rötliches Scheibchen. Bei stärkerer Vergrößerung können die Polkappen und dunkle Oberflächenmerkmale wie die Große Syrte ausgemacht werden. Treten auf dem Mars größere Staubstürme auf, verblassen die Merkmale, da die Oberfläche von einer rötlichen Dunstschicht eingehüllt wird, die sich mitunter über Wochen halten kann. Durch den Einsatz von CCD-Kameras sind mittlerweile auch Amateurastronomen in der Lage, detailreiche Aufnahmen der Marsoberfläche zu erzielen, wie sie vor etwa zehn Jahren nur von den leistungsfähigsten Großteleskopen erstellt werden konnten.
+
+Ereignisse (Jahreszeitenbeginn gilt für die Nordhalbkugel):[56][57][58]
+Ereignis Frühlingsbeginn Aphel Sommerbeginn Herbstbeginn Perihel Winterbeginn
+Datum 18. Juni 2015 20. November 2015 15. Februar 2014 17. August 2014 12. Dezember 2014 11. Januar 2015
+Nächste
+Termine 5. Mai 2017 7. Oktober 2017 3. Januar 2016 4. Juli 2016 29. Oktober 2016 28. November 2016
+Ereignis Konjunktion Opposition
+Datum 14. Juni 2015 8. April 2014
+Nächste
+Termine 27. Juli 2017 22. Mai 2016
+Sichtbarkeiten
+ Hauptartikel: Marspositionen
+
+Wegen der Exzentrizität der Marsbahn kann der erdnächste Punkt bis zu einer Woche vor oder nach der Opposition erreicht werden, und die scheinbare Helligkeit während der Opposition sowie der Erdabstand und der scheinbare Durchmesser während der Erdnähe können recht unterschiedlich ausfallen.
+
+Eine Opposition findet etwa alle zwei Jahre (779,94 Tage) statt. Dabei kann bei einer Perihelopposition die maximale scheinbare Helligkeit bis zu 2,91m erreichen. Zu diesem Zeitpunkt sind nur die Sonne, der Erdmond, die Venus und in seltenen Fällen Jupiter (bis zu 2,94m) noch heller. Bei Konjunktion hingegen erscheint Mars nur mehr mit einer Helligkeit von +1,8m.[1]
+Kulturgeschichte
+Beschäftigung mit dem Mars von der Antike bis in die Neuzeit
+Allegorische Darstellung des Mars als Herrscher der Tierkreiszeichen Widder und Skorpion, von Hans Sebald Beham, 16. Jahrhundert
+
+Der Mars bewegte die Menschheit von alters her besonders. Im alten Ägypten wurde Mars als Horus der Rote bezeichnet. Da der Planet sich während seiner Oppositionsschleife (Planetenschleife) zeitweise rückläufig bewegt, sprachen die Ägypter davon, dass Mars rückwärts wandere. Der Name der ägyptischen Hauptstadt Kairo leitet sich von Al Qahira ab, dem altarabischen Namen für den Planeten Mars.
+
+Im indischen Sanskrit wird der Mars als Mangal (verheißungsvoll), Angaraka (Glühende Kohle) und Kuja (der Blonde) bezeichnet. Er repräsentiert kraftvolle Aktion, Vertrauen und Zuversicht.
+
+Aufgrund seiner roten Färbung wurde der Mars in verschiedenen Kulturen mit den Gottheiten des Krieges in Verbindung gebracht. Die Babylonier sahen in ihm Nergal, den Gott der Unterwelt, des Todes und des Krieges. Für die Griechen und Römer der Antike repräsentierte er deren Kriegsgötter Ares beziehungsweise Mars. In der nordischen Mythologie steht er für Tyr, den Gott des Rechts und des Krieges. Die Azteken nannten ihn Huitzilopochtli, der Zerstörer von Menschen und Städten. Für die Chinesen war er Huoxing (chin. Huxng, ), Stern des Feuers.
+
+In der Astrologie ist Mars unter anderem das Symbol der Triebkraft. Es wird dem Element Feuer, dem Planetenmetall Eisen, den Tierkreiszeichen Widder und Skorpion sowie dem 1. Haus zugeordnet.
+Rezeption in Literatur, Film, Videospiele und Musik
+
+Der Mars und seine fiktiven Bewohner sind auch Thema zahlreicher Romane und Verfilmungen.
+
+Ein Beispiel des 18. Jahrhunderts ist Carl Ignaz Geigers Roman Reise eines Erdbewohners in den Mars von 1790.
+
+1880 veröffentlichte Percy Greg seinen Roman Across the Zodiac, in dem er eine Reise in einem Raumschiff namens Astronaut zum Mars beschrieb.
+
+Die klassische Figur des kleinen grünen Männchens mit Antennen auf dem Kopf erschien erstmals 1913 in einem Comic und ist seitdem Klischee.
+
+Als der Astronom Percival Lowell Ende des 19. Jahrhunderts die Vorstellung entwickelte, die mit dem Fernrohr wahrnehmbaren Marskanäle seien künstlich angelegte Wasserkanäle, wurde diese Idee in der Science-Fiction-Literatur aufgegriffen und weitergesponnen. Dort wurde der Mars häufig als eine sterbende Welt vorgestellt, in deren kalten Wüstenregionen alte und weit entwickelte Zivilisationen ums Überleben kämpften.
+
+Kurd Laßwitz brachte 1897 seinen sehr umfangreichen Roman Auf zwei Planeten über einen Besuch bei den Marsbewohnern heraus.
+Angriff der Marsianer in Krieg der Welten von H. G. Wells. Buchillustration der französischen Ausgabe von Alvim Corréa von 1906
+
+In H. G. Wells bekanntem Roman Krieg der Welten, der 1898 erschien, verlassen die Marsianer ihre Heimatwelt, um die lebensfreundlichere Erde zu erobern. Die Menschheit, die den hochtechnisierten kriegerischen Marsianern hoffnungslos unterlegen ist, entgeht ihrer Auslöschung nur dadurch, dass die Invasoren von für Menschen harmlosen, irdischen Mikroben dahingerafft werden. Orson Welles verwendete den Stoff im Jahre 1938 in einem Hörspiel, wobei er die Marsianer in New Jersey landen ließ. Das Hörspiel wurde im Stil einer realistischen Reportage ausgestrahlt. Hörer, die sich später einschalteten, hielten die Invasion der Marsianer für Realität.
+
+Wells Romanvorlage wurde 1952 verfilmt, wobei die Handlung wiederum in die USA der Gegenwart verlegt wurde. Der Film erhielt für die damals bahnbrechenden Spezialeffekte einen Oscar.
+
+1923 brachte Tolstoi seinen Roman Aelita heraus, der von der Liebe eines sowjetischen Ingenieurs zur Marsprinzessin und dem Untergang der Zivilisation auf dem Planeten handelt. Dieses Werk wurde 1924 verfilmt.
+
+Im Jahr 1978 entstand der Film Unternehmen Capricorn. Er griff das Thema der Verschwörungstheorien zur Mondlandung auf, indem er es in sehr zugespitzter Form auf eine im Filmstudio vorgetäuschte Marsexpedition übertrug.
+
+Der 1996 entstandene Film Mars Attacks! setzt sich ironisch mit dem Thema Marsinvasion auseinander, wobei den Marsianern amerikanische Schnulzenmusik aus den 1950er Jahren zum Verhängnis wird.
+
+Unter der Regie von Brian De Palma wurden im Jahr 2000 mit dem Film Mission to Mars die Spekulationen um das Marsgesicht der Cydonia-Region als hinterlassenes Bauwerk dramatisch weitgehend thematisiert.
+
+Steven Spielbergs 2005 entstandenes Remake von Krieg der Welten nahm noch einmal das Thema auf und zeigte die Invasion von Außerirdischen auf der Erde aus der Sicht eines Familienvaters aus den USA.
+
+Weitere bekannte Science-Fiction-Filme, die auf dem Mars handeln, sind Red Planet (2000) und Die totale Erinnerung Total Recall (1990).
+
+Edgar Rice Burroughs, der Autor von Tarzan, schrieb von 1917 bis 1943 die elfbändige Saga John Carter vom Mars, in der sich der irdische Held in marsianische Prinzessinnen verliebt und gegen Luftpiraten, grünhäutige Unholde, weiße Riesenaffen und andere Untiere kämpft.
+
+Die Mars-Chroniken (1950), eine stimmungsvolle Sammlung von Erzählungen des Schriftstellers Ray Bradbury, sind ebenfalls auf dem Mars angesiedelt.
+
+Große Beachtung erhielt die Marstrilogie, eine von Kim Stanley Robinson von 1993 bis 1996 verfasste Romanserie über die Besiedelung des Mars. Der besondere Ansatz dieser Geschichten liegt in der vorwiegend technischen Schilderung unter vollständigem Verzicht phantastischer Elemente.
+Die Route von Mark Watney in einer nachgestellten topographischen Kartierung des DLR-Instituts für Planetenforschung
+
+Der wohl prominenteste Auftritt des Mars in der Musik dürfte der erste Satz von Gustav Holsts Orchestersuite Die Planeten (19141916) sein, deren erster Satz Mars, the Bringer of War mit seinem drohend-martialischen Charakter die mythologische Gestalt Mars eindrucksvoll porträtiert.
+
+Bestsellerautor Andreas Eschbach verfasste von 2001 bis 2008 die Pentalogie Das Marsprojekt.
+
+2011 veröffentlichte Andy Weir den Science-Fiction-Roman Der Marsianer, in dem ein Astronaut nach einem Unfall auf dem Mars zurückgelassen wird und fortan um sein Überleben kämpfen muss. Mit Der Marsianer Rettet Mark Watney erschien 2015 eine Verfilmung dieses Bestsellers.
+
+Helga Abret und Lucian Boa geben in ihrem Buch Das Jahrhundert der Marsianer (1984) einen literarischen Überblick über Erzählungen und Romane über den Mars und seine fiktiven Bewohner. Von der Beschreibung einer ekstatischen Reise zum Mars (Itinerarium exstaticum coeleste, 1656) des Jesuitenpaters Athanasius Kircher bis hin zu Science-Fiction-Erzählungen des 20. Jahrhunderts reicht die Bandbreite der kommentierten Werke, mit denen die Autoren aufzuzeigen versuchen, dass sich aus dem Zusammenwirken von Naturwissenschaften, Astronomie und Literatur ein moderner Mythos[59] entwickelte.
+
diff --git a/xpcom/tests/gtest/wikipedia/de.txt b/xpcom/tests/gtest/wikipedia/de.txt
new file mode 100644
index 0000000000..486c676110
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/de.txt
@@ -0,0 +1,487 @@
+Der Mars ist, von der Sonne aus gezählt, der vierte Planet im Sonnensystem und der äußere Nachbar der Erde. Er zählt zu den erdähnlichen (terrestrischen) Planeten.
+
+Sein Durchmesser ist mit knapp 6800 Kilometer etwa halb so groß wie der der Erde, sein Volumen beträgt gut ein Siebtel des Erdevolumens. Damit ist der Mars nach dem Merkur der zweitkleinste Planet des Sonnensystems, hat jedoch eine ausgeprägte Geologie und die höchsten Vulkane des Sonnensystems. Mit einer durchschnittlichen Entfernung von 228 Millionen Kilometern ist er rund 1,5-mal so weit von der Sonne entfernt wie die Erde.
+
+Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse. Die Fallbeschleunigung auf seiner Oberfläche beträgt 3,69 m/s², dies entspricht etwa 38 % der irdischen. Mit einer Dichte von 3,9 g/cm³ weist der Mars den geringsten Wert der terrestrischen Planeten auf. Deshalb ist die Schwerkraft auf ihm sogar geringfügig niedriger als auf dem kleineren, jedoch dichteren Merkur.
+
+Der Mars wird oft auch als der Rote Planet bezeichnet. Diese Färbung geht auf Eisenoxid-Staub (Rost) zurück, der sich auf der Oberfläche und in der dünnen CO2-Atmosphäre verteilt hat. Seine orange- bis blutrote Farbe und seine Helligkeitsschwankungen sind auch verantwortlich für seine Namensgebung nach dem römischen Kriegsgott Mars.[3]
+
+In größeren Fernrohren deutlich sichtbar sind die zwei Polkappen und mehrere dunkle Ebenen, die sich im Frühjahr etwas verfärben. Fotos von Raumsonden zeigen eine teilweise mit Kratern bedeckte Oberfläche und starke Spuren früherer Tektonik (tiefe Canyons und fünf über 20 km hohe Vulkane). Marsroboter haben schon mehrere Gebiete geologisch untersucht.
+
+Der Mars besitzt zwei kleine, unregelmäßig geformte Monde, die 1877 entdeckt wurden: Phobos und Deimos (griechisch für Furcht und Schrecken).
+
+Das astronomische Symbol des Mars ist ♂.
+
+Umlauf und Rotation
+Umlaufbahn
+
+Der Mars bewegt sich in einem Abstand von 206,62 bis 249,23 Millionen Kilometern (1,38 AE bis 1,67 AE) in knapp 687 Tagen (etwa 1,9 Jahre) auf einer elliptischen Umlaufbahn um die Sonne. Die Bahnebene ist 1,85° gegen die Erdbahnebene geneigt.
+
+Seine Bahngeschwindigkeit schwankt mit dem Sonnenabstand zwischen 26,50 km/s und 21,97 km/s und beträgt im Mittel 24,13 km/s. Die Bahnexzentrizität beträgt 0,0935. Nach der Umlaufbahn des Merkurs ist das die zweitgrößte Abweichung von der Kreisform unter allen Planetenbahnen des Sonnensystems.
+
+Jedoch hatte der Mars in der Vergangenheit eine weniger exzentrische Umlaufbahn. Vor 1,35 Millionen Jahren betrug die Exzentrizität nur etwa 0,002, weniger als die der Erde heute.[4] Die Periode der Exzentrizität des Mars beträgt etwa 96.000 Jahre, die der Erde etwa 100.000 Jahre.[5] Mars hat jedoch noch einen längeren Zyklus der Exzentrizität mit einer Periode von 2,2 Millionen Jahren, der den mit der Periode von 96.000 Jahren überlagert. In den letzten 35.000 Jahren wurde die Umlaufbahn aufgrund der gravitativen Kräfte der anderen Planeten geringfügig exzentrischer. Der minimale Abstand zwischen Erde und Mars wird in den nächsten 25.000 Jahren noch ein wenig geringer werden.[6]
+
+Es gibt vier bekannte Asteroiden, die sich mit dem Mars die gleiche Umlaufbahn teilen (Mars-Trojaner). Sie befinden sich auf den Lagrangepunkten L4 und L5, das heißt, sie eilen dem Planeten um 60° voraus oder folgen ihm um 60° nach.
+Rotation
+
+Der Mars rotiert in 24 Stunden und 37,4 Minuten um die eigene Achse (Siderischer Tag). In Bezug auf die Sonne ergibt sich daraus ein Marstag (auch Sol genannt) von 24:39:35. Die Äquatorebene des Planeten ist um 25,19° gegen seine Bahnebene geneigt (Erde 23,44°), somit gibt es Jahreszeiten ähnlich wie auf der Erde. Sie dauern jedoch fast doppelt so lang, weil das siderisches Marsjahr 687 Erdtage hat. Da die Bahn des Mars aber eine deutlich größere Exzentrizität aufweist, als die der Erde, und Mars-Nord tendenziell in Richtung der großen Bahn-Ellipsenachse weist, sind die Jahreszeiten unterschiedlich lang. In den letzten 300.000 Jahren variierte die Rotationsachse zwischen 22° und 26°. Zuvor lag sie mehrmals auch über 40°, wodurch starke Klimaschwankungen auftraten, es Vereisungen auch in der Äquatorregion gab und so die starken Bodenerosionen zu erklären sind.
+
+Der Nordpol des Mars weist zum nördlichen Teil des Sternbilds Schwan, womit sich die Richtung um etwa 40° von jener der Erdachse unterscheidet. Der marsianische Polarstern ist Deneb (mit leichter Abweichung der Achse Richtung Alpha Cephei).[7]
+
+Die Rotationsachse führt eine Präzessionsbewegung aus, deren Periode 170.000 Jahre beträgt (7× langsamer als die Erde). Aus diesem Wert, der mit Hilfe der Pathfinder-Mission festgestellt wurde, können die Wissenschaftler auf die Massenkonzentration im Inneren des Planeten schließen.[8]
+Atmosphäre und Klima
+Über dem Marshorizont ist die Atmosphäre als dunstiger Schleier erkennbar. Links ist der einem Smiley ähnelnde Krater Galle zu sehen. Viking, 1976
+
+Der Mars besitzt eine sehr dünne Atmosphäre. Dadurch ist der Atmosphärendruck sehr niedrig, und Wasser kann nicht in flüssiger Form auf der Marsoberfläche existieren, ausgenommen kurzzeitig in den tiefstgelegenen Gebieten.
+
+Da die dünne Marsatmosphäre nur wenig Sonnenwärme speichern kann, sind die Temperaturunterschiede auf der Oberfläche sehr groß. Die Temperaturen erreichen in Äquatornähe etwa 20 °C am Tag und sinken bis auf −85 °C in der Nacht. Die mittlere Temperatur des Planeten liegt bei etwa −55 °C.
+Atmosphäre
+→ Hauptartikel: Atmosphäre des Mars
+
+Die Marsatmosphäre besteht zu 95,3 % aus Kohlenstoffdioxid. Dazu kommen noch 2,7 % Stickstoff, 1,6 % Argon, geringe Anteile an Sauerstoff (1300 ppm) und Kohlenstoffmonoxid (800 ppm) sowie Spuren von Wasserdampf (210 ppm) und anderen Verbindungen oder Elementen.
+
+Die Atmosphäre ist ziemlich staubig. Sie enthält Teilchen mit etwa 1,5 µm im Durchmesser, die den Himmel über dem Mars in einem blassen gelb- bis orange-braunen Farbton erscheinen lassen.
+
+Der atmosphärische Druck beträgt auf der Oberfläche des Mars im Schnitt nur 6,36 hPa (Hektopascal). Im Vergleich zu durchschnittlich 1013 hPa auf der Erde sind dies nur 0,63 %, was dem Luftdruck der Erdatmosphäre in 35 Kilometern Höhe entspricht. Die Atmosphäre wurde wahrscheinlich im Laufe der Zeit vom Sonnenwind abgetragen und in den Weltraum mitgerissen. Dies wurde durch die geringe Schwerkraft des Planeten und sein schwaches Magnetfeld begünstigt, das kaum Schutz vor den hochenergetischen Teilchen der Sonne bietet.
+Klima und Wetter
+Eiswolken über Mars, aufgenommen von Mars Pathfinder
+
+Abhängig von den Jahreszeiten und der Intensität der Sonneneinstrahlung finden in der Atmosphäre dynamische Vorgänge statt. Die vereisten Polkappen sublimieren im Sommer teilweise, und kondensierter Wasserdampf bildet ausgedehnte Zirruswolken. Die Polkappen selbst bestehen aus Kohlendioxideis und Wassereis.
+
+2008 entdeckte man mit Hilfe der Raumsonde Mars Express Wolken aus gefrorenem Kohlendioxid. Sie befinden sich in bis zu 80 Kilometern Höhe und haben eine horizontale Ausdehnung von bis zu 100 km. Sie absorbieren bis zu 40 % des einstrahlenden Sonnenlichts und können damit die Temperatur der Oberfläche um bis zu 10 °C verringern.[9]
+
+Mit Hilfe des Lasers LIDAR der Raumsonde Phoenix wurde 2009 entdeckt, dass in der zweiten Nachthälfte fünfzig Tage nach der Sonnenwende winzige Eiskristalle aus dünnen Zirruswolken auf den Marsboden fielen.[10]
+Jahreszeiten
+Staubsturm in der Syria-Region, fotografiert von Mars Global Surveyor im Mai 2003
+
+Hätte Mars eine erdähnliche Umlaufbahn, würden die Jahreszeiten aufgrund der Achsenneigung ähnlich denen der Erde sein. Jedoch führt die vergleichsweise große Exzentrizität seines Orbits zu einer beträchtlichen Auswirkung auf die Jahreszeiten. Der Mars befindet sich während des Sommers in der Südhalbkugel und des Winters in der nördlichen Hemisphäre nahe dem Perihel seiner Bahn. Nahe dem Aphel ist in der südlichen Hemisphäre Winter und in der nördlichen Sommer.
+
+Das hat zur Folge, dass die Jahreszeiten in der südlichen Hemisphäre viel deutlicher ausgeprägt sind als in der nördlichen, wo das Klima ausgeglichener ist, als es sonst der Fall wäre. Die Sommertemperaturen im Süden können bis zu 30 °C höher sein als die vergleichbaren Temperaturen im Sommer des Nordens.[11] Die Jahreszeiten sind aufgrund der Exzentrizität der Umlaufbahn des Mars unterschiedlich lang. Auf der Nordhalbkugel dauert der Frühling 199,6, der Sommer 181,7, der Herbst 145,6 und der Winter 160,1 irdische Tage.[12]
+Wind und Stürme
+
+Aufgrund der starken Tag-Nacht-Temperaturschwankungen der Oberfläche gibt es tägliche Morgen- und Abendwinde.[13]
+
+Während des Marsfrühjahrs können in den ausgedehnten flachen Ebenen heftige Staubstürme auftreten, die mitunter große Teile der Marsoberfläche verhüllen. Die Aufnahmen von Marssonden zeigen auch Windhosen, die über die Marsebenen ziehen und auf dem Boden dunkle Spuren hinterlassen. Stürme auf dem Mars haben wegen der sehr dünnen Atmosphäre eine wesentlich geringere Kraft als Stürme auf der Erde. Selbst bei hohen Windgeschwindigkeiten werden nur kleine Partikel (Staub) aufgeweht.[14] Allerdings verbleibt aufgewehter Staub auf dem Mars wesentlich länger in der Atmosphäre als auf der Erde, da es keine Niederschläge gibt, die die Luft reinigen, und zudem die Gravitation geringer ist.
+
+Staubstürme treten gewöhnlich während des Perihels auf, da der Planet zu diesem Zeitpunkt 40 Prozent mehr Sonnenlicht empfängt als während des Aphels. Während des Aphels bilden sich in der Atmosphäre Wolken aus Wassereis, die ihrerseits mit den Staubpartikeln interagieren und so die Temperatur auf dem Planeten beeinflussen.[15] Die Windgeschwindigkeiten in der oberen Atmosphäre können bis zu 650 km/h erreichen, auf dem Boden immerhin fast 400 km/h.[16]
+Gewitter
+
+Bei heftigen Staubstürmen scheint es auch zu Gewittern zu kommen. Im Juni 2006 untersuchten Forscher mit einem Radioteleskop den Mars und stellten im Mikrowellenbereich Strahlungsausbrüche fest, wie sie bei Blitzen auftreten. In der Region, in der man die Strahlungsimpulse beobachtet hat, herrschte zu der Zeit ein heftiger Staubsturm mit hohen Staubwolken. Sowohl der beobachtete Staubsturm wie auch das Spektrum der Strahlungsimpulse deuten auf ein Staubgewitter mit Blitzen bzw. großen Entladungen hin.[17][18]
+Oberfläche
+Typisches Felsengestein auf der Marsoberfläche (aufgenommen von Mars Pathfinder)
+
+Die Oberfläche des Mars beträgt etwa ein Viertel der Erdoberfläche. Sie entspricht mit 144 Mio. km2 ungefähr der Gesamtoberfläche aller Kontinente der Erde (149 Mio. km2).
+
+Die rote Färbung seiner Oberfläche verdankt der Planet dem Eisenoxid-Staub, der sich auf der Oberfläche und in der Atmosphäre verteilt hat. Somit ist der Rote Planet ein „rostiger Planet“.
+
+Seine beiden Hemisphären sind sehr verschieden. Die Südhalbkugel stellt ein riesiges Hochland dar, das durchschnittlich 2–3 km über dem globalen Nullniveau liegt und ausgedehnte Schildvulkane aufweist. Die vielen Einschlagkrater belegen sein hohes Alter von fast 4 Milliarden Jahren. Dem steht die geologisch junge, fast kraterlose nördliche Tiefebene gegenüber. Sie liegt 3–5 km unter dem Nullniveau und hat ihre ursprüngliche Struktur durch noch ungeklärte geologische Prozesse verloren. Auslöser war möglicherweise eine gewaltige Kollision in der Frühzeit des Planeten.
+Gesteine
+→ Hauptartikel: Marsgestein
+
+An den Landestellen der Marssonden sind Gesteinsbrocken, sandige Böden und Dünen sichtbar. Die Marsgesteine weisen an der Oberfläche eine blasenartige Struktur auf und ähneln in ihrer Zusammensetzung irdischen Basalten, was bereits vor Jahrzehnten aus den auf der Erde (Antarktis) gefundenen Marsmeteoriten erschlossen wurde. Die roten Böden sind offensichtlich durch die Verwitterung von eisenhaltigen, vulkanischen Basalten entstanden.
+
+Die Pathfinder-Sonde fand 1997 außer verschiedensten Basalten auch quarzreichere Tiefengesteine ähnlich dem südamerikanischen Andesit, ferner das aus der Tiefe stammende Olivin und runde Kiesel aus Konglomeraten. Weitverbreitet ist metamorpher Regolith (ähnlich wie am Mond) und äolische Sedimente. Vereinzelt verwehter Sand aus schwefelhaltigen Staubteilchen.
+Areografie
+
+Die kartografische Darstellung und Beschreibung der Marsoberfläche ist die Areografie, von Ares (ΆÏης, griechisch für Mars) und grafein (γÏάφειν, griechisch für beschreiben). Die „Geologie“ des Mars wird mitunter dementsprechend als Areologie bezeichnet.
+
+Zur Festlegung von Positionen auf der Marsoberfläche dienen areografische Koordinaten, die definiert sind wie geografische Breite und Länge.
+Topografische Hemisphären
+Topografische Karte des Mars. Die blauen Regionen befinden sich unterhalb des festgelegten Nullniveaus, die roten oberhalb
+
+Auffallend ist die Dichotomie, die „Zweiteilung“, des Mars. Die nördliche und die südliche Hemisphäre unterscheiden sich deutlich, wobei man von den Tiefebenen des Nordens und den Hochländern des Südens sprechen kann. Der mittlere Großkreis, der die topografischen Hemisphären voneinander trennt, ist rund 40° gegen den Äquator geneigt. Der Massenmittelpunkt des Mars ist gegenüber dem geometrischen Mittelpunkt um etwa drei Kilometer in Richtung der nördlichen Tiefebenen versetzt.
+
+Auf der nördlichen Halbkugel sind flache sand- und staubbedeckte Ebenen vorherrschend, die Namen wie Utopia Planitia oder Amazonis Planitia erhielten. Dunkle Oberflächenmerkmale, die in Teleskopen sichtbar sind, wurden einst für Meere gehalten und erhielten Namen wie Mare Erythraeum, Mare Sirenum oder Aurorae Sinus. Diese Namen werden heute nicht mehr verwendet. Die ausgedehnteste dunkle Struktur, die von der Erde aus gesehen werden kann, ist Syrtis Major, die „große Syrte“.
+
+Die südliche Halbkugel ist durchschnittlich sechs Kilometer höher als die nördliche und besteht aus geologisch älteren Formationen. Die Südhalbkugel ist zudem stärker verkratert, wie zum Beispiel in der Hochlandregion Arabia Terra. Unter den zahlreichen Einschlagkratern der Südhalbkugel befindet sich auch der größte Marskrater, Hellas Planitia, die Hellas-Tiefebene. Das Becken misst im Durchmesser bis zu 2100 km. In seinem Innern maß Mars Global Surveyor 8180 m unter Nullniveau – unter dem Durchschnittsniveau des Mars – den tiefsten Punkt auf dem Planeten. Der zweitgrößte Einschlagkrater des Mars, Chryse Planitia, liegt im Randbereich der nördlichen Tiefländer.
+Übersichtskarte des Mars mit den größten Regionen
+
+Die deutlichen Unterschiede der Topografie können durch innere Prozesse oder aber ein Impaktereignis verursacht worden sein. In letzterem Fall könnte in der Frühzeit der Marsentstehung ein größerer Himmelskörper, etwa ein Asteroid, auf der Nordhalbkugel eingeschlagen sein und die silikatische Kruste durchschlagen haben. Aus dem Innern könnte Lava ausgetreten sein und das Einschlagbecken ausgefüllt haben.
+
+Wie sich gezeigt hat, hat die Marskruste unter den nördlichen Tiefebenen eine Dicke von etwa 40 km, die im Gegensatz zum stufenartigen Übergang an der Oberfläche nur langsam auf 70 km bis zum Südpol hin zunimmt. Dies könnte ein Indiz für innere Ursachen der Zweiteilung sein.
+Oberflächenstrukturen
+Gräben
+In der Bildmitte liegt das System der Mariner-Täler. Ganz links die Tharsis-Vulkane (Bildmosaik von Viking 1 Orbiter, 1980)
+
+Südlich am Äquator und fast parallel zu ihm verlaufen die Valles Marineris (die Mariner-Täler), das größte bekannte Grabensystem des Sonnensystems. Es erstreckt sich über 4000 km und ist bis zu 700 km breit und bis zu 7 km tief. Es handelt sich um einen gewaltigen tektonischen Bruch. In seinem westlichen Teil, dem Noctis Labyrinthus, verästelt er sich zu einem chaotisch anmutenden Gewirr zahlreicher Schluchten und Täler, die bis zu 20 km breit und bis zu 5 km tief sind.
+
+Noctis Labyrinthus liegt auf der östlichen Flanke des Tharsis-Rückens, einer gewaltigen Wulst der Mars-Lithosphäre quer über dem Äquator mit einer Ausdehnung von etwa 4000 mal 3000 Kilometern und einer Höhe von bis zu rund 10 Kilometern über dem nördlichen Tiefland. Die Aufwölbung ist entlang einer offenbar zentralen Bruchlinie von drei sehr hohen, erloschenen Schildvulkanen besetzt: Ascraeus Mons, Pavonis Mons und Arsia Mons. Der Tharsis-Rücken und die Mariner-Täler dürften in ursächlichem Zusammenhang stehen. Wahrscheinlich drückten vulkanische Kräfte die Oberfläche des Planeten in dieser Region empor, wobei die Kruste im Bereich des Grabensystems aufgerissen wurde. Eine Vermutung besagt, dass diese vulkanische Tätigkeit durch ein Impaktereignis ausgelöst wurde, dessen Einschlagstelle das Hellas-Becken auf der gegenüberliegenden Seite des Mars sei. 2007 wurden im Nordosten von Arsia Mons sieben tiefere Schächte mit 100 bis 250 Metern Durchmesser entdeckt.
+Vulkane
+Olympus Mons, der mit 26 km höchste Berg im Sonnensystem
+Die komplexe Caldera des Olympus Mons
+
+Dem Hellas-Becken exakt gegenüber befindet sich der Vulkanriese Alba Patera. Er ragt unmittelbar am Nordrand des Tharsis-Rückens rund 6 km über das umgebende Tiefland und ist mit einem Basisdurchmesser von über 1200 km der flächengrößte Vulkan im Sonnensystem. Patera ist die Bezeichnung für unregelmäßig begrenzte Vulkane mit flachem Relief. Alba Patera ist anscheinend einmal durch einen Kollaps in sich zusammengefallen.
+
+Unmittelbar westlich neben dem Tharsis-Rücken und südwestlich von Alba Patera ragt der höchste Vulkan, Olympus Mons, 26,4 km über die Umgebung des nördlichen Tieflands. Mit einer Gipfelhöhe von etwa 21,3 km über dem mittleren Null-Niveau ist er die höchste bekannte Erhebung im Sonnensystem.
+
+Ein weiteres, wenn auch weniger ausgedehntes vulkanisches Gebiet ist die Elysium-Region nördlich des Äquators mit den Schildvulkanen Elysium Mons, Hecates Tholus und Albor Tholus.
+Stromtäler
+Kasei Vallis, das größte Stromtal des Mars
+
+Auf der Marsoberfläche verlaufen Stromtäler, die mehrere hundert Kilometer lang und mehrere Kilometer breit sein können. Die heutigen Trockentäler beginnen ziemlich abrupt und haben keine Zuflüsse. Die meisten entspringen an den Enden der Mariner-Täler und laufen nördlich im Chryse-Becken zusammen. In den Tälern erheben sich mitunter stromlinienförmige Inseln. Sie weisen auf eine vergangene Flutperiode hin, bei der über einen geologisch relativ kurzen Zeitraum große Mengen Wasser geflossen sein müssen. Es könnte sich um Wassereis gehandelt haben, das sich unter der Marsoberfläche befand, danach durch vulkanische Prozesse geschmolzen wurde und dann abgeflossen ist.
+
+Darüber hinaus finden sich an Abhängen und Kraterrändern Spuren von Erosionen, die möglicherweise ebenfalls durch flüssiges Wasser verursacht wurden.
+
+2006 proklamierte die NASA einen einzigartigen Fund: Auf einigen NASA-Fotografien, die im Abstand von sieben Jahren vom Mars gemacht wurden, lassen sich Veränderungen auf der Marsoberfläche erkennen, die eine gewisse Ähnlichkeit mit Veränderungen durch fließendes Wasser haben. Innerhalb der NASA wird nun diskutiert, ob es neben Wassereis auch flüssiges Wasser geben könnte.[19]
+Delta-Strukturen
+
+In alten Marslandschaften, z. B. im Eberswalde-Krater auf der Südhalbkugel oder in der äquatornahen Hochebene Xanthe Terra, finden sich typische Ablagerungen einstiger Flussdeltas.
+Tharsis-Tholus-Streifen, aufgenommen mit der Hirise-Kamera des Mars Reconnaissance Orbiters. Der Streifen ist links in der Mitte zu sehen. Rechts sind die Ausläufer von Tharsis Tholus.
+
+Seit längerem vermutet man, dass die tief eingeschnittenen Täler in Xanthe Terra einst durch Flüsse geformt wurden. Wenn ein solcher Fluss in ein größeres Becken, beispielsweise einen Krater, mündete, lagerte er erodiertes Gesteinsmaterial als Sedimente ab. Die Art der Ablagerung hängt dabei von der Natur dieses Beckens ab: Ist es mit dem Wasser eines Sees gefüllt, so bildet sich ein Delta. Ist das Becken jedoch trocken, so verliert der Fluss an Geschwindigkeit und versickert langsam. Es bildet sich ein sogenannter Schwemmkegel, der sich deutlich vom Delta unterscheidet.
+
+Jüngste Analysen von Sedimentkörpern auf Basis von Orbiter-Fotos weisen an zahlreichen Stellen in Xanthe Terra auf Deltas hin – Flüsse und Seen waren in der Marsfrühzeit also recht verbreitet.[20]
+Dark Slope Streaks
+
+Dunkle Streifen an Hängen sind auf dem Mars häufig zu sehen. Sie treten an steilen Hängen von Kratern, Mulden und Tälern auf und werden mit zunehmendem Alter heller. Manchmal beginnen sie in einem kleinen punktförmigen Bereich und werden dann zunehmend breiter. Man beobachtete, dass sie sich um Hindernisse, wie Mulden, weiterbewegen.
+
+Es wird angenommen, dass die Farbe von dunklen darunterliegenden Schichten stammt, die durch Lawinen von hellem Staub freigelegt werden. Es wurden jedoch auch andere Hypothesen aufgestellt, wie Wasser oder sogar der Wuchs von Organismen. Das Interessanteste an diesen dunklen Streifen (engl. dark slope streaks) ist, dass sie sich auch heute noch bilden.[21]
+Chaotische Gebiete
+
+Auf dem Mars gibt es zahlreiche Regionen mit einer Häufung von unterschiedlich großen Gesteinsbrocken und tafelbergähnlichen Erhebungen. Sie werden auch „chaotische Gebiete“ genannt. Ariadnes Colles ist mit einer Fläche von etwa 29.000 km² so ein Gebiet. Es liegt im Terra Sirenum, einem südlichen Hochland des Mars. Dabei haben die Blöcke Ausmaße von einem bis zu zehn Kilometern Ausdehnung. Die größeren Blöcke ähneln Tafelbergen mit Erhebungen von bis zu 300 Metern.
+
+Es treten hierbei riefenartige Strukturen und „Runzelrücken“ (engl. wrinkle ridges) auf. Die Ursachen dafür sind vulkanisch-tektonische Bewegungen.[22]
+Gesteinsschichten und Ablagerungen
+Salzlager
+
+Mit Hilfe der Sonde Mars Odyssey wies die NASA ein umfangreiches Salzlager in den Hochebenen der Südhalbkugel des Mars nach. Vermutlich entstanden diese Ablagerungen durch Oberflächenwasser vor etwa 3,5 bis 3,9 Milliarden Jahren.[23]
+Carbonatvorkommen
+
+Mit Hilfe der Compact Reconnaissance Imaging Spectrometer for Mars (CRISM) an Bord der NASA-Sonde Mars Reconnaissance Orbiter konnten Wissenschaftler Carbonat-Verbindungen in Gesteinsschichten rund um das knapp 1500 Kilometer große Isidis-Einschlagbecken nachweisen. Demnach wäre das vor mehr als 3,6 Milliarden Jahren existierende Wasser hier nicht sauer, sondern eher alkalisch oder neutral gewesen.
+
+Carbonatgestein entsteht, wenn Wasser und Kohlendioxid mit Kalzium, Eisen oder Magnesium in vulkanischem Gestein reagiert. Bei diesem Vorgang wird Kohlendioxid aus der Atmosphäre in dem Gestein eingelagert. Dies könnte bedeuten, dass der Mars früher eine dichte kohlendioxidreiche Atmosphäre hatte, wodurch ein wärmeres Klima möglich wurde, in dem es auch flüssiges Wasser gab.[24]
+
+Mit Hilfe von Daten des MRO wurden 2010 Gesteine entdeckt, die durch kosmische Einschläge aus der Tiefe an die Oberfläche befördert worden waren. Anhand ihrer spezifischen spektroskopischen Fingerabdrücke konnte festgestellt werden, dass sie hydrothermal (unter Einwirkung von Wasser) verändert wurden. Neben diesen Karbonat-Mineralen wurden auch Silikate nachgewiesen, die vermutlich auf die gleiche Weise entstanden sind. Dieser neue Fund beweise, dass es sich dabei nicht um örtlich begrenzte Vorkommen handele, sondern dass Karbonate in einer sehr großen Region des frühen Mars entstanden seien.[25]
+Hämatitkügelchen
+Hämatitkügelchen auf dem Felsen „Berry Bowl“
+
+Die Marssonde Opportunity fand im Gebiet des Meridiani Planum millimetergroße Kügelchen des Eisenminerals Hämatit. Diese könnten sich vor Milliarden Jahren unter Einwirkung von Wasser abgelagert haben. Darüber hinaus wurden Minerale gefunden, die aus Schwefel-, Eisen- oder Bromverbindungen aufgebaut sind, wie zum Beispiel Jarosit. Auf der entgegengesetzten Hemisphäre[26] des Mars fand die Sonde Spirit in den „Columbia Hills“ das Mineral Goethit, das ausschließlich unter dem Einfluss von Wasser gebildet werden kann.
+Kieselsäure
+
+Forscher entdeckten 2010 mit Hilfe von MRO Ablagerungen auf einem Vulkankegel, die von Wasser verursacht wurden. Sie konnten das Mineral als Kieselsäurehydrat identifizieren, das nur in Verbindung mit Wasser entstanden sein kann. Die Wissenschaftler nehmen an, dass, falls es auf dem Mars Leben gegeben hat, es sich dort in der hydrothermalen Umgebung am längsten hätte halten können.[27]
+Polkappen
+Die Nordpolregion, aufgenommen von Mars Global Surveyor
+→ Hauptartikel: Polkappen des Mars
+
+Der Mars besitzt zwei auffällige Polkappen, die zum größten Teil aus gefrorenem Kohlendioxid (Trockeneis) sowie einem geringen Anteil an Wassereis zusammengesetzt sind. Die nördliche Polkappe hat während des nördlichen Marssommers einen Durchmesser von rund 1000 Kilometern. Ihre Dicke wird auf 5 km geschätzt. Die südliche Polkappe ist mit 350 km Durchmesser und einer Dicke von 1½ km weniger ausgedehnt. Die Polarkappen zeigen spiralförmige Einschnitte, deren Entstehung bislang nicht geklärt ist.
+
+Wenn im Sommer die jeweiligen Polkappen teilweise abschmelzen, werden darunter geschichtete Ablagerungen sichtbar, die möglicherweise abwechselnd aus Staub und Eis zusammengesetzt sind. Im Marswinter nimmt der Durchmesser der dann jeweils der Sonne abgewandten Polkappe durch ausfrierendes Kohlendioxid wieder zu.
+
+Da ein größerer, stabilisierender Mond fehlt, taumelt der Mars mit einer Periode von etwa 5 Millionen Jahren. Die Polarregionen werden daher immer wieder so stark erwärmt, dass das Wassereis schmilzt. Durch das abfließende Wasser entstehen die Riemen und Streifen an den Polkappen.
+Wasservorkommen
+
+Der Mars erscheint heute als trockener Wüstenplanet. Die bislang vorliegenden Ergebnisse der Marsmissionen lassen jedoch den Schluss zu, dass die Marsatmosphäre in der Vergangenheit (vor Milliarden Jahren) wesentlich dichter war und auf der Oberfläche des Planeten reichlich flüssiges Wasser vorhanden war.
+Eisvorkommen an den Polen
+Die Südpolregion, aufgenommen von Viking Orbiter
+
+Durch Radarmessungen mit der Sonde Mars Express wurden in der Südpolarregion, dem Planum Australe, Ablagerungsschichten mit eingelagertem Wassereis entdeckt, die weit größer und tiefreichender als die hauptsächlich aus Kohlendioxideis bestehende Südpolkappe sind. Die Wassereisschichten bedecken eine Fläche, die fast der Größe Europas entspricht, und reichen in eine Tiefe von bis zu 3,7 Kilometern. Das in ihnen gespeicherte Wasservolumen wird auf bis zu 1,6 Millionen Kubikkilometer geschätzt – circa zwei Drittel des irdischen Grönlandeispanzers – was laut der Europäischen Weltraumorganisation (ESA) ausreichen würde, die Marsoberfläche mit einer etwa 11 Meter dicken Wasserschicht zu bedecken.[28]
+Weitere Eisvorkommen
+Beobachtete Veränderungen könnten Anzeichen für fließendes Wasser innerhalb der letzten Jahre sein.[19]
+
+Die schon lange gehegte Vermutung, dass sich unter der Oberfläche des Mars Wassereis befinden könnte, erwies sich 2005 durch Entdeckungen der ESA-Sonde Mars Express als richtig.
+
+Geologen gehen von wiederkehrenden Vereisungsperioden auf dem Mars aus, ähnlich irdischen Eiszeiten. Dabei sollen Gletscher bis in subtropische Breiten vorgestoßen sein. Die Forscher schließen dies aus Orbiter-Fotos, die Spuren einstiger Gletscher in diesen äquatornahen Gebieten zeigen. Zusätzlich stützen auch Radarmessungen aus der Umlaufbahn die Existenz beträchtlicher Mengen an Bodeneis in ebendiesen Gebieten. Diese Bodeneisvorkommen werden als Reste solcher „Mars-Eiszeiten“ gedeutet.[29]
+
+Auf der Europäischen Planetologenkonferenz EPSC im September 2008 in Münster wurden hochauflösende Bilder des Mars Reconnaissance Orbiters der NASA vorgestellt, die jüngste Einschlagkrater zeigen. Wegen der sehr dünnen Atmosphäre stürzen die Meteoriten praktisch ohne Verglühen auf die Marsoberfläche. Die fünf neuen Krater, die nur drei bis sechs Meter Durchmesser und eine Tiefe von 30 bis 60 cm aufweisen, wurden in mittleren nördlichen Breiten gefunden. Sie zeigen an ihrem Boden ein gleißend weißes Material. Wenige Monate später waren die weißen Flecken durch Sublimation verschwunden. Damit erhärten sich die Hinweise, dass auch weit außerhalb der Polgebiete Wassereis dicht unter der Marsoberfläche begraben ist.[30][31]
+Flüssiges Wasser
+
+Unter der Kryosphäre des Mars werden große Mengen flüssigen Wassers vermutet. Nahe oder an der Oberfläche ist es für flüssiges Wasser zu kalt, und Eis würde langsam verdunsten, da der Partialdruck von Wasser in der Marsatmosphäre zu gering ist.
+
+Es gibt jedoch Hinweise, dass die Raumsonde Phoenix Wassertropfen auf der Oberfläche entdeckt habe. Dabei könnten Perchlorate als Frostschutz wirken. Diese Salze haben die Eigenschaft, Wasser anzuziehen. Dies kann auch Wasserdampf aus der Atmosphäre sein. Bei ausreichender Konzentration der Salze könnte Wasser sogar bis −70 °C flüssig bleiben. Durch eine Durchmischung mit Perchloraten könnte Wasser auch unter der Oberfläche in flüssigem Zustand vorhanden sein.[32] 2010 fanden Forscher der Uni Münster Belege dafür, dass zumindest im Frühjahr und in Kratern wie dem Russell-Krater flüssiges Wasser auf der Marsoberfläche existiert. Auf Fotos, die vom Mars Reconnaissance Orbiter aufgenommen wurden, entdeckten sie an steilen Hängen Erosionsrinnen, die sich zwischen November 2006 und Mai 2009 verlängert hatten. Dass die Rinnen nach unten dünner werden, deuten die Forscher als Versickern,[33] andere als Verdunsten.[34]
+
+Eine alternative Erklärung für die Erosionsrinnen schlugen Wissenschaftler der NASA 2010 vor: Kohlendioxid, das sich im marsianischen Winter bei unter -100 °C aus der Atmosphäre an den Berghängen als Trockeneis ansammelt, bei Erwärmung des Planeten als sublimiertes Gas die Hänge hinab"fließt" und dabei Staub erodiert.[35][36]
+
+Mit dem abbildenden Spektrometer (CRISM) des Mars Reconnaissance Orbiters konnten Spektren von aktiven (jahreszeitlich dunkleren) Rinnen gewonnen werden, deren Auswertung, 2015 veröffentlicht,[37] Magnesiumperchlorat, Magnesiumchlorat und Natriumperchlorat ergaben.
+Siehe auch: Extraterrestrischer Ozean
+Innerer Aufbau
+Illustration des vermuteten Marsaufbaus
+
+Ãœber den inneren Aufbau des Mars ist nur wenig bekannt, da bislang nur begrenzt seismische Messungen vorgenommen werden konnten.
+
+Sein Inneres gliedert sich ähnlich dem Schalenaufbau der Erde in eine Kruste, einen Gesteinsmantel und einen Kern, der überwiegend aus Eisen und zu etwa 14 bis 17 Prozent aus Schwefel besteht. Der Kern beinhaltet etwa doppelt so viele leichte Elemente wie der Erdkern. Deshalb ist die Dichte des Kerns niedriger, als es bei einem reinen Eisenkern der Fall wäre.[38]
+
+Laut neueren experimentellen Simulationen der Bedingungen in der Übergangszone zwischen Mantel und Kern (Messungen des Mars Global Surveyor ergaben eine Temperatur von 1500 Grad Celsius und einen Druck von 23 Gigapascal) hat der Kern des Mars im Unterschied zu dem der Erde keinen inneren festen Bereich, sondern ist vollständig flüssig.[39] Dies belegt auch die Analyse der Bahndaten des Mars Global Surveyor. Dabei konnte nachgewiesen werden, dass der Mars einen flüssigen Kern mit einem Radius zwischen 1520 und 1840 km besitzt und damit eine höhere Temperatur hat, als zuvor angenommen wurde.
+
+Der Kern ist von einem Mantel aus Silicaten umgeben, der viele der tektonischen und vulkanischen Merkmale des Planeten formte, nun aber inaktiv zu sein scheint. Die durchschnittliche Dicke der Planetenkruste beträgt etwa 50 km, mit einem Maximum von 125 km.[38] Im Vergleich dazu ist die Erdkruste mit einer Dicke von durchschnittlich 40 km nur etwa ein Drittel so dick, wenn man die relative Größe der beiden Planeten berücksichtigt.
+Magnetfeld
+Magnetisierung des Mars – Rot und Blau kennzeichnen entgegengesetzte Richtungen des Magnetfelds, ein Drittel der Südhalbkugel
+
+Anders als die Erde und der Merkur besitzt der Mars kein globales Magnetfeld mehr, seit er es ca. 500 Millionen Jahre nach seiner Entstehung verlor. Vermutlich erlosch es, als der Zerfall radioaktiver Elemente nicht mehr genügend Wärmeenergie produzierte, um im flüssigen Kern Konvektionsströmungen anzutreiben. Weil der Mars keinen festen inneren Kern besitzt, konnte er den Dynamo-Effekt nicht auf die gleiche Art aufbauen wie die Erde.
+
+Dennoch ergaben Messungen einzelne und sehr schwache lokale Magnetfelder. Die Messung des Magnetfeldes wird erschwert durch die Magnetisierung der Kruste mit Feldstärken von bis zu 220 Nanotesla und durch externe Magnetfelder mit Stärken zwischen wenigen Nanotesla und bis zu 100 Nanotesla, die durch die Wechselwirkung des Sonnenwindes mit der Marsatmosphäre entstehen und zeitlich sehr stark variieren. Nach den Analysen der Daten des Mars Global Surveyor konnte die Stärke des Magnetfeldes trotzdem sehr genau bestimmt werden – sie liegt bei weniger als 0,5 Nanotesla gegenüber 30 bis 60 Mikrotesla des Erdmagnetfeldes.
+
+Messungen von Magnetfeldlinien durch Mars Global Surveyor ergaben, dass Teile der planetaren Kruste durch das einstige Magnetfeld stark magnetisiert sind, aber mit unterschiedlicher Orientierung, wobei gleichgerichtete Bänder von etwa 1000 km Länge und 150 km Breite auftreten. Ihre Größe und Verteilung erinnert an die streifenförmigen Magnetanomalien auf den Ozeanböden der Erde. Durch sie wurde die Theorie der Plattentektonik gestützt, weshalb 1991 auch eine ähnliche Theorie für den Mars entwickelt wurde. Magnetische Beobachtungen auf dem Mars sind jedoch noch nicht detailliert genug, um sichere Schlussfolgerungen zu erlauben oder gar die Theorie zu bestätigen.
+
+Möglicherweise werden bei der mit der Zeit zwangsläufigen Abkühlung des Marskerns durch die damit einsetzende Auskristallisation des Eisens und die freigesetzte Kristallisationswärme wieder Konvektionen einsetzen, die ausreichen, dass der Planet in ein paar Milliarden Jahren wieder über ein globales Magnetfeld in alter Stärke verfügt.[39]
+Monde
+Die Umlaufbahnen von Phobos und Deimos
+Phobos (oben) und Deimos (unten) im Größenvergleich
+
+Zwei kleine Monde, Phobos und Deimos (griech. Furcht und Schrecken), umkreisen den Mars. Sie wurden 1877 von dem US-amerikanischen Astronomen Asaph Hall entdeckt und nach den in der Ilias überlieferten beiden Begleitern, die den Wagen des Kriegsgottes Ares (lat. Mars) ziehen, benannt.
+
+Phobos (Durchmesser 26,8 × 22,4 × 18,4 km) und Deimos (Durchmesser 15,0 × 12,2 × 10,4 km) sind zwei unregelmäßig geformte Felsbrocken. Möglicherweise handelt es sich um Asteroiden, die vom Mars eingefangen wurden. Phobos’ große Halbachse beträgt 9.376 km, diejenige von Deimos 23.459 km. Phobos ist damit kaum mehr als 6.000 km von der Oberfläche des Mars entfernt, der Abstand ist geringer als der Durchmesser des Planeten.
+
+Die periodischen Umlaufbewegungen der beiden Monde befinden sich mit der Größe von 0,31891 (Phobos) und 1,262 Tagen (Deimos) zueinander in einer 1:4-Bahnresonanz.
+
+Die Umlaufzeit von Phobos ist kürzer als die Rotationszeit von Mars. Der Mond kommt dem Planeten durch die Gezeitenwechselwirkung auf einer Spiralbahn langsam immer näher und wird schließlich auf diesen stürzen oder durch die Gezeitenkräfte auseinandergerissen werden, so dass er für kurze Zeit zu einem Marsring wird. Für ihn berechneten DLR-Forscher, basierend auf neueren Daten der europäischen Raumsonde Mars Express, dass dies in ca. 50 Millionen Jahren geschehen wird. Deimos wird dagegen in einer noch ferneren Zukunft dem Mars entfliehen. Er driftet durch die Gezeitenwechselwirkung langsam nach außen, wie alle Monde, die langsamer (und nicht retrograd) um einen Planeten kreisen, als dieser rotiert.
+
+Ihre Existenz war schon lange vorher mehrmals literarisch beschrieben worden, zuletzt von Voltaire, der in seiner 1750 erschienenen Geschichte Micromégas über zwei Marsmonde schreibt. Es ist wahrscheinlich, dass Voltaire diese Idee von Jonathan Swift übernahm, dessen Buch Gullivers Reisen 1726 erschienen war. Darin wird im dritten Teil beschrieben, die Astronomen des Landes Laputa hätten „ebenfalls zwei kleinere Sterne oder Satelliten entdeckt, die um den Mars kreisen, wovon der innere vom Zentrum des Hauptplaneten genau drei seiner Durchmesser entfernt ist und der äußere fünf.“ Es wird vermutet, dass Swift von einer Fehlinterpretation Johannes Keplers gehört hatte. Der hatte das Anagramm, das Galileo Galilei 1609 an ihn schickte, um ihm die Entdeckung der Phasen der Venus mitzuteilen, als die Entdeckung zweier Marsmonde aufgefasst.
+Entstehungsgeschichte
+Datei:Mars.ogvMediendatei abspielen
+Animation, welche die Topographie des Mars zeigt. Olympus Mons → Mariner-Täler → Mars Südpol → Hellas-Becken → Mars Nordpol
+
+Anhand der astrogeologischen Formationenvielfalt und der Verteilung von Einschlagskratern kann ein Großteil der Geschichte des Planeten abgeleitet werden. Der Mars entstand, wie die übrigen Planeten des Sonnensystems, vor etwa 4,5 Milliarden Jahren durch Zusammenballung kleinerer Körper, sogenannter Planetesimale, innerhalb der protoplanetaren Scheibe zu einem Protoplaneten. Vor 4 Milliarden Jahren bildete der im Innern noch glutflüssige planetare Körper eine feste Gesteinskruste aus, die einem heftigen Bombardement von Asteroiden und Kometen ausgesetzt war.
+Noachische Periode
+
+Die ältesten der heute noch vorhandenen Formationen, wie das Hellas-Becken, und die verkraterten Hochländer, wie Noachis Terra, wurden vor 3,8 bis 3,5 Milliarden Jahren, in der so genannten Noachischen Periode, gebildet. In dieser Periode setzte die Zweiteilung der Marsoberfläche ein, wobei die nördlichen Tiefländer gebildet wurden. Durch starke vulkanische Eruptionen wurden weite Teile des Planeten von Ablagerungen aus vulkanischer Lava und Asche bedeckt. Diese wurden an vielen Stellen durch Wind und Wasser wieder abgetragen und ließen ein Netzwerk von Tälern zurück.
+Hesperianische Periode
+
+Das geologische „Mittelalter“ des Mars wird als Hesperianische Periode bezeichnet. Sie umfasst den Zeitraum von vor 3,5 bis 1,8 Milliarden Jahren. In dieser Periode ergossen sich riesige Lavamengen aus ausgedehnten Spalten in der Marskruste und bildeten weite Ebenen, wie Hesperia Planum. Es entstanden auch die ältesten Vulkane der Tharsis- und der Elysium-Region, wobei die Gesteinskruste stark verformt wurde und sich das Grabensystem der Mariner-Täler öffnete. Es bildeten sich die gewaltigen Stromtäler, in denen große Wassermengen flossen und sich stellenweise aufstauten.
+
+Es entwickelte sich auf dem Mars ein Wasserkreislauf. Im Unterschied zur Erde gab es jedoch keinen Wetterzyklus mit Verdunstung, Wolkenbildung und anschließendem Niederschlag. Das Wasser versickerte im Untergrund und wurde später durch hydrothermale Prozesse wieder an die Oberfläche getrieben. Da jedoch der Planet immer weiter abkühlte, endete dieser Prozess vor etwa 1,5 Milliarden Jahren, und es hielten sich nur noch Gletscher an der Oberfläche. Zeichen dieser Aktivität sind vor kurzem entdeckte Moränen am Olympus Mons.[40]
+Amazonische Periode
+
+Das jüngste geologische Zeitalter des Mars wird als Amazonische Periode bezeichnet und begann vor 1,8 Milliarden Jahren. In dieser Phase entstanden die jüngeren Vulkane der Tharsis- und der Elysium-Region, aus denen große Lavamassen flossen. So bildeten sich weite Ebenen aus wie zum Beispiel Amazonis Planitia.
+
+2008 fanden Forscher Hinweise auf Geysire auf dem Mars, die vor einigen Millionen Jahren aktiv gewesen sein dürften. Dabei hätten sie Fontänen von kohlensäurehaltigem Wasser einige Kilometer weit in die Höhe geschossen. Darauf deuten auch die Formen von Ablagerungen hin, die britische Forscher in der Nähe zweier ausgedehnter Grabensysteme entdeckten. Wahrscheinlich wurden diese Eruptionen durch Blasen aus Kohlendioxid ausgelöst. Dadurch wurde das Wasser aus einer Tiefe von bis zu vier Kilometern durch Spalten im Marsboden an die Oberfläche gedrückt. Die Fontänen müssen dabei mit einem so großen Druck herausgepresst worden sein, dass das schlammige Wasser erst in einer Entfernung von mehreren Kilometern von der Austrittsstelle wieder auf den Boden regnete oder, bedingt durch die tiefen Temperaturen, als Hagel niederging.[41]
+
+Gegenwärtig wird die Oberfläche des Mars hauptsächlich durch Winderosion und Hangrutschung geformt.
+Erforschung
+
+Aufgrund seiner großen Helligkeit war der Mars schon im frühen Altertum als Planet bekannt. Wegen seiner langen Planetenschleifen (die alle 2 Jahre in der Opposition auftreten) galten seine Bewegungen den Ägyptern als unvorhersehbar. Den Babyloniern gelang es zwar, sie näherungsweise vorauszusagen, sie schrieben die Bahnanomalien aber den Launen und der Gewalttätigkeit des Gottes Nergal zu.
+Vor dem Raumfahrtzeitalter
+Marsoberfläche nach Schiaparelli (1888)
+Mars auf einer astronomischen Zeichnung des 19. Jahrhunderts (Trouvelot, 1881)
+
+ Tycho Brahe (1546–1601) vermaß die Planetenpositionen des Mars mit bis dahin nicht gekannter Genauigkeit und ermöglichte es so Johannes Kepler (1571–1630), die elliptische Bahn des Planeten zu berechnen und die drei Keplerschen Gesetze abzuleiten.
+ Christiaan Huygens entdeckte 1659 eine dunkle, dreieckige Zone (Syrtis Major) auf der Marsoberfläche. Aus deren Positionsveränderungen errechnete er die Eigenrotation des Mars zu 24,5 Stunden (heutiger Wert: 24,623 Stunden).
+ Giovanni Domenico Cassini beschrieb 1666 die weißen Polkappen des Mars.
+ Wilhelm Herschel bestimmte 1784 die Neigung der Rotationsachse gegenüber der Umlaufbahn mit 25° (heutiger Wert 25,19°).
+ Wilhelm Beer fertigte 1830 die erste Marskarte an, Angelo Secchi 1863 schon in Farbe.
+ Richard Proctor veröffentlichte 1869 eine detaillierte Marskarte, die er aus Zeichnungen von William Rutter Dawes erstellte.
+ Giovanni Schiaparelli nahm 1877 auf der Marsoberfläche zarte Linienstrukturen wahr, die er „Canali“ (italienisch für „Rinnen“ oder „Gräben“) nannte und in eine detaillierte Karte eintrug. Er machte zunächst keine Angaben über den Ursprung der Canali (die er für breiter als 100 km schätzte), doch wurden sie in englischen Medien fälschlich als „Channel“ (Kanäle) übersetzt und bald als Werk intelligenter Marsbewohner interpretiert. Auf älteren Marskarten erhielten viele dieser Linien auch Namen. Während einige Astronomen Schiaparellis Beobachtungen bestätigten, wurde die Existenz der Canali von anderen angezweifelt und als Ergebnis optischer Täuschungen bezeichnet. Erst der Vorbeiflug der amerikanischen Mariner-Sonden beendete die Spekulationen, denn Fotos der Marsoberfläche zeigten keine so breiten Rinnen. Drei Canali entsprechen aber den riesigen Canyons Valles Marineris, andere zeichnen Geländestufen und Schattenlinien nach, einige auch längere Kraterketten.
+
+→ Hauptartikel: Marskanäle
+
+ Asaph Hall entdeckt bei der günstigen Opposition 1877 die beiden Marsmonde Phobos und Deimos.
+ Percival Lowell gründet 1894 das Lowell-Observatorium in Arizona, um die Marskanäle, ihre jahreszeitlichen Verfärbungen und allfällige Lebensspuren zu erforschen. Spektroskopisch findet man biologische Moleküle, die sich allerdings später als terrestrisch erweisen. In der Atmosphäre werden Spektrallinien von Sauerstoff entdeckt, dessen Volumsanteil aber überschätzt wird.
+ Eugène Antoniadi bestätigte zunächst die Marskanäle, kam aber 1909 am Riesenteleskop Meudon zum Schluss, sie würden nur in kleineren Fernrohren als solche erscheinen. In seinen detaillierten Marskarten – die bis zu den ersten Marssonden kaum mehr übertroffen wurden – zeichnete er sie als Folge diffuser Flecken ein.
+ Gerard Kuiper wies in den 1950ern Kohlendioxid in der Marsatmosphäre nach und glaubte bis zu den ersten Marssonden an die mögliche Existenz von Moosen oder Flechten.
+
+Im Raumfahrtzeitalter
+Die erste Nahaufnahme vom Mars, aufgenommen von Mariner 4
+
+Viele unbemannte Raumsonden wurden schon zum Mars entsandt, von denen einige sehr erfolgreich waren. Etwa die Hälfte der Missionen endete in einem Misserfolg, die meisten davon waren sowjetische Sonden. Im Unterschied zur Erkundung des Erdmondes gibt es bis heute keine Gesteinsproben, die vom Mars geholt wurden, so dass Marsmeteoriten die einzige Möglichkeit sind, Material vom Mars in irdischen Laboratorien zu erforschen. Bislang hat es auch noch keine bemannte Marsmission gegeben — ein aktueller Ansatz dafür ist Mars One.
+→ Hauptartikel: Chronologie der Marsmissionen
+1960er Jahre
+Darstellung auf einer ungarischen Sondermarke von 1964
+
+Die beiden sowjetischen Sonden Marsnik 1 und 2 wurden im Oktober 1960 gestartet, um am Mars vorbeizufliegen, erreichten aber noch nicht einmal die Erdumlaufbahn. 1962 versagten drei weitere sowjetische Sonden (Sputnik 22, Mars 1 und Sputnik 24), zwei von ihnen blieben im Erdorbit, die dritte verlor auf dem Weg zum Mars den Kontakt mit der Erde. Auch ein weiterer Versuch im Jahre 1964 schlug fehl.
+
+Zwischen 1962 und 1973 wurden zehn Mariner-Raumsonden vom Jet Propulsion Laboratory der NASA entwickelt und gebaut, um das innere Sonnensystem zu erforschen. Es waren relativ kleine Sonden, die meistens nicht einmal eine halbe Tonne wogen.
+
+Mariner 3 und Mariner 4 waren identische Raumsonden, die am Mars vorbeifliegen sollten. Mariner 3 wurde am 5. November 1964 gestartet, aber die Transportverkleidung löste sich nicht richtig, und die Sonde erreichte den Mars nicht.
+
+Drei Wochen später, am 28. November 1964, wurde Mariner 4 erfolgreich auf eine achtmonatige Reise zum Roten Planeten geschickt. Am 15. Juli 1965 flog die Sonde am Mars vorbei und lieferte die ersten Nahaufnahmen – insgesamt 22 Fotos – des Planeten. Die Bilder zeigten mondähnliche Krater, von denen einige mit Reif bedeckt zu sein scheinen.
+
+1969 folgten Mariner 6 und Mariner 7 und lieferten insgesamt 200 Fotos.
+1970er Jahre
+
+1971 missglückte der Start von Mariner 8, dafür erhielt die NASA im gleichen Jahr von Mariner 9 mehrere tausend Bilder.
+
+Ebenfalls 1971 landete mit der sowjetischen Mars 3 die erste Sonde weich auf dem Mars, nachdem Mars 2 wenige Tage zuvor gescheitert war. Der Funkkontakt brach jedoch 20 Sekunden nach der Landung ab. Mögliche Ursache war ein gerade tobender globaler Staubsturm, der den Lander umgeworfen haben könnte.
+Bild von Viking 1. Der große Felsen links von der Mitte ist etwa zwei Meter breit. Er wurde Big Joe getauft.
+
+In den 1970er-Jahren landeten die Viking-Sonden erfolgreich auf dem Mars und lieferten die ersten Farbbilder sowie Daten von Bodenproben: Viking 1 schaffte am 20. Juli 1976 als erste US-amerikanische Sonde eine weiche Landung. Die Sowjetunion versuchte noch weitere Landungen auf dem Mars, scheiterte jedoch.
+1980er Jahre
+
+Die einzigen Raumsonden, die in den 1980er Jahren zum Mars flogen, waren die beiden sowjetischen Fobos-Sonden. Sie wurden 1988 von Baikonur aus gestartet und sollten den Mars und seinen Mond Phobos untersuchen. Dafür waren sie im Rahmen einer internationalen Kooperation neben sowjetischen auch mit zahlreichen westlichen Instrumenten bestückt. Der Kontakt zu Fobos 1 brach jedoch schon auf dem Weg zum Mars wegen eines falschen Steuerbefehls ab. Fobos 2 erreichte eine Marsumlaufbahn und einige Daten und Bilder vom Mars wurden zur Erde übertragen. Danach wurde die Sonde zu Phobos gelenkt. Jedoch brach kurz vor dem Rendezvous auch der Kontakt zu Fobos 2 ab.
+1990er Jahre
+
+1992 wurde die US-Sonde Mars Observer gestartet. Sie ging 1993 kurz vor dem Einschwenken in die Umlaufbahn verloren.
+
+Am 16. November 1996 startete Mars 96, die erste russische Raumsonde seit dem Zusammenbruch der Sowjetunion. Doch versagte die Proton-Trägerrakete, so dass Mars 96 wieder in die Erdatmosphäre eintrat und verglühte.
+Der Marsrover Sojourner
+
+Besonderes Aufsehen erregte 1997 der Mars Pathfinder, bei dem zum ersten Mal ein kleines Marsmobil, der Rover Sojourner, eingesetzt wurde. Er landete publikumswirksam am 4. Juli, dem amerikanischen Unabhängigkeitstag, und lieferte viele Aufnahmen von der Umgebung der Landestelle, die von der NASA zum ersten Mal sofort im Internet veröffentlicht wurden.
+
+Eine weitere erfolgreiche Mission war 1997 die des Mars Global Surveyor, bei der die Marsoberfläche in einer hohen Auflösung kartografiert wurde. Am 2. November 2006 – fünf Tage vor dem 10-jährigen Jubiläum seines Starts – brach der Kontakt mit dem Satelliten ab.
+
+Das Scheitern der Marssonden Mars Climate Orbiter, der wegen eines Programmierfehlers in der Navigation verlorenging, und Mars Polar Lander, der wahrscheinlich wegen eines fehlerhaften Sensors bei der Landung aus größerer Höhe abstürzte, stellte 1999 einen herben Rückschlag für die Marsforschung dar.
+
+Auch die 1998 gestartete japanische Raumsonde Nozomi konnte den Mars nicht erreichen.
+2000er Jahre
+
+Seit dem 24. Oktober 2001 umkreist außer dem Global Surveyor noch 2001 Mars Odyssey den roten Planeten, der spezielle Instrumente zur Fernerkundung von Wasservorkommen an Bord hat.
+
+Von den bis 2002 insgesamt 33 Missionen zum Mars waren nur acht erfolgreich, allesamt US-amerikanisch.
+
+Am 2. Juni 2003 startete im Rahmen der ersten europäischen Marsmission die ESA-Raumsonde Mars Express mit dem Landegerät Beagle 2 erfolgreich zum Mars. Zwar landete Beagle 2 am 25. Dezember 2003 auf der Marsoberfläche, allerdings konnte der Funkkontakt niemals aufgebaut werden. 2014 wurde er auf Bildern des MRO entdeckt. Der Orbiter Mars Express arbeitet jedoch erfolgreich in der Marsumlaufbahn und konnte unter anderem viele Aufnahmen von Formationen machen, von denen man annimmt, dass sie ausgetrocknete oder ausgefrorene Flusstäler seien. Er kartiert den Planeten u. a. mittels Radar und einer Stereokamera im sichtbaren Licht, sowie spektroskopisch auch in Infrarot. Am 30. November 2005 fand die Sonde unter der Ebene Chryse Planitia ein Eisfeld mit 250 km Durchmesser.
+Marsrover Opportunity (MER-B)
+
+Am 10. Juni 2003 wurde die US-amerikanische Marssonde Spirit (MER-A) zum Mars gestartet. An Bord befand sich ein Rover, der nach der Landung drei Monate lang Gesteinsproben entnehmen und nach Spuren von früher vorhandenem Wasser suchen sollte. Die Landung erfolgte am 4. Januar 2004 im Krater Gusev, in den das Ma'adim Vallis mündet. Im April 2009 fuhr sich der Rover in einer Sandanhäufung fest und konnte seit dem 22. März 2010 auch nicht mehr kontaktiert werden (Stand: März 2011).
+
+Am 8. Juli 2003 wurde die baugleiche Sonde Opportunity (MER-B) mit einer Delta-II-Rakete gestartet. Sie landete am 25. Januar 2004 in der Tiefebene Meridiani Planum nahe dem Marsäquator, fast genau gegenüber von Spirit.[26] Die vom Rover gesammelten Beweise, dass der Mars einst warm und feucht war, wurden im Jahresrückblick der Fachzeitschrift Science mit der Wahl zum „Durchbruch des Jahres 2004“ gewürdigt. Opportunity ist noch immer aktiv (Stand: April 2015).
+vergrößern und Informationen zum Bild anzeigen
+Das Panoramabild, von dem hier nur ein Ausschnitt zu sehen ist, wurde aus hunderten Einzelbildern montiert, die Opportunity vom 6. Oktober bis 6. November 2006 aufgenommen hat. Es zeigt annähernd in Echtfarben den Victoria-Krater vom Cap Verde
+
+Am 12. August 2005 wurde die US-Sonde Mars Reconnaissance Orbiter mit einer Atlas-V-Rakete auf die Reise geschickt und erreichte am 10. März 2006 den Mars. Sie soll ihn mit hochauflösenden Kameras kartografieren und auch nach geeigneten Landestellen für spätere Rover-Missionen suchen. Außerdem soll sie zur Hochgeschwindigkeits-Kommunikation zwischen zukünftigen Raumsonden auf der Marsoberfläche und der Erde dienen.
+Sonnenuntergang auf dem Mars beim Krater Gusev (Spirit am 19. Mai 2005)
+
+2007 fotografierte Mars Reconnaissance sieben fast kreisrunde schwarze und strukturlosen Flecken, die im Nordosten des Marsvulkans Arsia Mons liegen.[42] Der größte, genannt Jeanne, hat einen Durchmesser von etwa 150 Meter. Eine Schrägaufnahme der sonnenbeschienenen Seitenwand im August 2007 zeigte, dass es sich um einen mindestens 78 Meter tiefen senkrechten Schacht handeln muss. Diese Strukturen sind sehr wahrscheinlich vulkanischer Natur und durch den Einbruch einer nicht mehr tragfähigen Deckschicht entstanden.[43]
+
+Am 26. Dezember 2007 machte die High Resolution Stereo Camera des Mars Express Aufnahmen von Eumenides Dorsum, einem Bergrücken westlich der Tharsis-Region. Die Aufnahmen zeigen kilometerlange lineare Strukturen, die von Kanälen unterbrochen sind. Es handelt sich um durch Winderosion entstandene Yardangs (Windhöcker bzw. Sandwälle).
+
+Mit der Sonde Mars Odyssey wies die NASA im März 2008 eine umfangreiche Salzlagerstätte in den Hochebenen der Südhalbkugel nach. Die Wissenschaftler des JPL in Pasadena meinen, sie habe sich vor 3,5 bis 3,9 Milliarden Jahren gebildet. Vermutlich entstanden die Salze durch mineralienreiches Grundwasser, das an die Oberfläche gelangte und dort verdunstete. Die Bilder von „Mars Odyssey“ zeigen kanalähnliche Strukturen, die in den Salzbecken enden.[23] Insgesamt wurden über 200 Gebiete mit Salzvorkommen ausgemacht, die zwischen 1 und 25 km² groß sind. Die Entdeckung deutet darauf hin, dass der Mars vor langer Zeit ein wärmeres und deutlich feuchteres Klima hatte.[44]. Solche Klimaschwankungen dürften durch aperiodische Änderungen der Rotationsachse entstehen, deren Neigung (derzeit 25°) zwischen 14 und 50° variiert.[45]
+Die Orte der sieben erfolgreichen Marslandungen
+
+Am 26. Mai 2008 landete die Sonde Phoenix im nördlichen Polargebiet des Planeten. Sie suchte dort bis November 2008 im Boden nach Wassereis und „habitablen Zonen“, also für primitive Organismen bewohnbare Umgebungen. Ihr Roboterarm konnte Proben aus etwa 50 cm Tiefe holen, um sie dann in einem Minilabor zu analysieren. Phoenix entdeckte bei einer Grabung weiße Klümpchen, die nach einigen Tagen verschwanden. Man vermutete, dass es sich dabei um Wassereis handelt,[46] was am 31. Juli bestätigt wurde – beim Erhitzen einer Gesteinsprobe trat Wasserdampf aus.[47] Mit dem nasschemischen Labor MECA, das die wasserlöslichen Ionen im Marsboden bestimmte, konnten erhebliche Mengen an Perchloraten detektiert werden. Auf der Erde kommen Perchlorate in den ariden Wüstengebieten vor. Natriumperchlorat wird durch Oxidation von Natriumchlorid in der Atmosphäre erzeugt und dann mit dem Staub abgelagert.
+
+
+2010er Jahre
+Curiosity auf dem Mars
+
+Am 26. November 2011 um 15:02 UTC startete die Rover-Mission Mars Science Laboratory (Curiosity) der NASA mit einer Atlas V(541) von Cape Canaveral und landete am 6. August 2012 auf dem Mars. Der Rover kann weite Strecken zurücklegen und umfassende Untersuchungen eines großen Umkreises durchführen. Wichtigstes Projektziel sind geologische Analysen des Marsbodens.
+
+Am 18. November 2013 startete eine weitere NASA-Sonde zum Mars. Die Mission mit dem Projektnamen „Mars Atmosphere and Volatile Evolution“ (MAVEN) soll das Rätsel der verlorenen Atmosphäre aufklären.[48] Der Orbiter umkreist den Planeten seit dem 22. September 2014 und soll sich in fünf Tiefflügen annähern. Weiters wurde am 5. November 2013 eine indische Marsmission gestartet. Sie soll ebenfalls die Atmosphäre sowie verschiedene Oberflächenphänomene untersuchen.[49]
+
+ExoMars Rover ist ein europäischer Rover, dessen Start für 2020 geplant ist. Er soll speziell nach Spuren von Leben suchen. Die Finanzierung dieser Mission ist allerdings noch ungewiss.
+Geplante Missionen
+
+Weitere Pläne der NASA und ESA zur Marserforschung enthalten unter anderem das Aussetzen von kleineren Flugzeugen in der Atmosphäre und – nach 2020 – die Rückführung von Marsproben zur Erde (Mission Mars Sample Return).
+vergrößern und Informationen zum Bild anzeigen
+Panoramabild der Marsoberfläche, aufgenommen von der Sonde Pathfinder
+
+Im Januar 2004 kündigte der US-amerikanische Präsident George W. Bush Anstrengungen der USA für eine bemannte Marsmission an. Im Rahmen des Raumfahrtprogramms Constellation plante die NASA diese Flüge für die Zeit nach 2020. Der ehemalige NASA-Direktor Michael Griffin nannte die Zeit bis 2037. Constellation wurde aber durch die Nachfolgeregierung unter Barack Obama aus Kostengründen gestrichen.[50]
+
+Auch das langfristig angelegte europäische Raumfahrtprogramm Aurora strebt insbesondere die Landung eines Menschen auf dem Mars an und plant sie für das Jahr 2033.
+
+Darüber hinaus existieren im Rahmen von Visionen einer Marskolonisation Vorstellungen, den Mars durch Terraforming in weiter Zukunft in einen für den Menschen lebensfreundlicheren Planeten umzuwandeln. Robert Zubrin, Edwin Aldrin und andere namhafte Stimmen in den Vereinigten Staaten von Amerika treten mittlerweile dafür ein, auf dem Mars unter dem Motto Mars to Stay schon in naher Zukunft die erste menschliche Siedlung zu begründen: Das sei möglich und sinnvoll, weil es wesentlich weniger Aufwand erfordere, die ersten Marsfahrer dauerhaft auf dem Planeten siedeln zu lassen, als sie sogleich wieder zur Erde zurückzuholen.
+Möglichkeit von Leben
+→ Hauptartikel: Leben auf dem Mars
+
+Die Ökosphäre (oder habitable Zone) des Sonnensystems reicht von 0,95 bis 1,37 AE Abstand zur Sonne. Im Sonnensystem befindet sich nur die Erde innerhalb dieses Gürtels um die Sonne, der Mars liegt knapp außerhalb.
+
+Höheres oder gar intelligentes Leben gibt es auf dem Mars nicht, Wissenschaftler halten jedoch primitive Lebensformen (Mikroben) tiefer im Boden, um vor UV-Strahlen geschützt zu sein, für denkbar.[51] Tatsächlich haben die in der Antarktis im Inneren von Gesteinen lebenden Pilzarten Cryomyces antarcticus und Cryomyces minteri simulierte Mars-Umweltbedingungen relativ gut überstanden: Nach 18 Monaten auf der Internationalen Raumstation[52] enthielten knapp 10 % der Proben noch fortpflanzungsfähige Zellen.[53] Auch die Flechte Xanthoria elegans hat die simulierten Marsbedingungen während des Experiments überlebt.
+Vermutungen vor dem Raumzeitalter
+Marsoberfläche nach Oswald Lohse (1888). Auf der Karte ist das Kanalsystem Schiaparellis nicht eingezeichnet. Die von Lohse gewählten Namen für die „Seen“ und „Ozeane“ sind heute nicht mehr gebräuchlich
+
+Der Gedanke an die Möglichkeit von Leben auf dem Mars beflügelte oft die Fantasie der Menschen. Im 18. Jahrhundert beobachtete man, dass die dunklen Flecken auf der Marsoberfläche ihre Farbe änderten und wuchsen oder schrumpften. Man hielt sie für ausgedehnte Vegetationszonen, deren Ausdehnung sich mit den Jahreszeiten änderte.
+
+Durch Schiaparellis „Entdeckung“ der Marskanäle wurden die Spekulationen um intelligentes Leben auf dem Mars angefacht.
+
+So entstanden zahlreiche Legenden um vermeintliche Zivilisationen auf dem Mars. Die Diskussionen um die „Marsmenschen“ hielten etwa ein Jahrhundert an. Der US-Amerikaner Percival Lowell, einer der heftigsten Verfechter der Marskanäle-Theorie, gründete sogar eine eigene Sternwarte, um die Marsbewohner zu erforschen. Für ihn waren die Kanäle das Produkt außerirdischer Ingenieure, die geschaffen wurden, um die Marszivilisation vor einer großen Trockenheit zu retten. Lowell beschrieb seine Vorstellungen der Marswelt in zahlreichen Publikationen, die weite Verbreitung fanden.
+
+Obwohl nicht alle Astronomen die Kanäle sehen konnten und keine Fotos existierten, hielt sich die Theorie, begleitet von einer heftigen Debatte. Die Vorstellung von außerirdischem Leben übt bis heute eine Faszination auf die Menschen aus, die mit wissenschaftlichem Interesse alleine oft nicht erklärt werden kann. Erst die Ergebnisse der unbemannten Marsmissionen beendeten den Streit um die Kanäle.
+
+Untersuchungen durch Viking
+
+Als im Juli 1976 der Orbiter 1 der Viking-Mission Bilder der Cydonia-Region machte und diese zur Erde schickte, wurde der Mars in der Öffentlichkeit wieder zum Gesprächsthema. Eine der Aufnahmen zeigte eine Formation auf der Marsoberfläche, die einem menschlichen Gesicht ähnelte, das gen Himmel blickt. In der unmittelbaren Nähe wurden außerdem Strukturen entdeckt, die Pyramiden auf der Erde ähneln, sowie rechteckige Strukturen (von den Wissenschaftlern „Inka-Stadt“ getauft). Erst die Mission Mars Global Surveyor der NASA brachte im April 1998 für viele die Ernüchterung: Alle entdeckten Strukturen waren das Ergebnis natürlicher Erosion. Durch neue Bilder mit wesentlich höherer Auflösung wurde deutlich, dass auf dem Mars keine künstlichen Strukturen außerirdischer Intelligenz ersichtlich sind.
+Das Marsgesicht in der Cydonia-Region; Aufnahme des Orbiters von Viking 1, 1976
+
+Viking 1 und 2 hatten unter anderem die Aufgabe, der Frage nach dem Leben auf dem Mars nachzugehen. Dabei wurden ein chemisches und drei biologische Experimente durchgeführt. In dem chemischen Experiment wurde versucht, organische Substanzen im Marsboden nachzuweisen. Dazu wurde eine am MIT entwickelte GC/MS-Einheit (Kopplung eines Gaschromatographen mit einem Massenspektrometer) benutzt. Es konnten allerdings keine auf Kohlenstoff aufbauenden organischen Substanzen nachgewiesen werden.
+
+Das erste biologische Experiment beruhte auf Stoffwechselaktivitäten von Organismen. Eine Bodenprobe wurde mit einer Nährlösung benetzt und entstehende Gase registriert. Der Marsboden reagierte auf das Experiment mit Abgabe großer Mengen Sauerstoff. Im zweiten Experiment wurde eine Nährlösung mit radioaktiven Kohlenstoffatomen versehen und auf eine Probe gegeben. Als Ergebnis eines Stoffwechsels hätten sie unter den ausgeschiedenen Gasen nachgewiesen werden müssen. Tatsächlich wurden radioaktive Kohlenstoffatome nachgewiesen. Das dritte Experiment war ein Photosynthese-Experiment. Radioaktiv markiertes Kohlendioxid wurde dem Marsboden zugesetzt. Dieses Kohlendioxid hätte assimiliert werden und später nachgewiesen werden müssen. Auch dieses Ergebnis war positiv. Obwohl die Ergebnisse der biologischen Experimente positiv waren, gaben sie aufgrund des negativen Ergebnisses des GC/MS-Versuchs keinen schlüssigen Beweis für die Existenz oder Nichtexistenz von Leben auf dem Mars.
+1990er und 2000er Jahre
+Marsgesicht, Aufnahme von Mars Global Surveyor, 2001
+
+Im Jahr 1996 fanden David S. McKay und seine Mitarbeiter Strukturen im Marsmeteoriten ALH 84001, die sie als Spuren von fossilen Bakterien deuteten. Das in diesem Meteoriten gefundene, kettenartig angeordnete Magnetit ähnelt morphologisch dem bakteriellen Magnetit aus Magnetospirillum magnetotacticum. Allerdings wird die Beweiskraft der gefundenen Strukturen von vielen Wissenschaftlern angezweifelt, da diese auch auf rein chemischem Wege entstehen konnten.
+
+Am 23. Januar 2004 entdeckte die europäische Marssonde Mars Express am Südpol des Mars große Mengen gefrorenen Wassers, Ende Juli 2005 auch in einem nahe dem Nordpol gelegenen Krater.
+
+Ende März 2004 wurde bekannt, dass Forscher der NASA und der ESA unabhängig voneinander Methan in der Marsatmosphäre nachgewiesen haben. Ob das Methan geologischen Ursprungs ist oder etwa durch den Stoffwechsel von Mikroorganismen gebildet wurde, sollen weitere Untersuchungen zeigen.
+
+Ebenfalls Anfang 2004 entdeckte die Marssonde Opportunity Gesteine, die in offenstehendem Wasser abgelagert worden sein müssen und viele regelmäßig verteilte kugelige, bis 1 cm große Hämatit-Konkretionen enthalten. Solche Konkretionen kommen auch auf der Erde vor. Unter irdischen Bedingungen ist es wahrscheinlich, dass bei ihrer Entstehung Bakterien beteiligt sind. Ob dies auch für den Mars gilt, könnten nur Laboruntersuchungen auf der Erde zeigen.
+
+Weitere Mikrostrukturen, welche die Rover Spirit und Opportunity 2004 entdeckt hatten und in denen ein Teil der interessierten Öffentlichkeit Hinweise auf Leben hatte sehen wollen, erwiesen sich bei näherer Untersuchung als abiotisch oder künstlich, so zum Beispiel Schleifspuren auf durch die Instrumente bearbeiteten Gesteinsoberflächen oder Filamente, die sich als Textilfasern der Lande-Airbags herausstellten.
+
+Forschungsergebnisse auf der Erde bestätigen, dass es Leben auch in extremen Bedingungen geben kann. Bei Bohrungen im grönländischen Eis entdeckten Forscher der University of California, Berkeley im Jahre 2005 in drei Kilometern Tiefe eine auffallende Menge Methan. Dieses Gas produzierten methanogene Bakterien, die trotz unwirtlicher Lebensbedingungen wie Kälte, Dunkelheit und Nährstoffmangel im Eis überleben. Dabei erhalten sie sich nur mühsam am Leben – sie reparieren Erbgutschäden, vermehren jedoch nicht nennenswert ihre Population. Methanogene Mikroben sind eine Untergruppe der Archaebakterien, die sich auf Extremstandorte spezialisiert haben. So fanden sich im Jahr 2002 Mikroben in einer 15.000 Jahre alten heißen Quelle in Idaho. Die Bakterien zählen, wie schon der Name besagt, zu den ältesten Mikroorganismen der Erde. Die Wissenschaftler schätzen das Alter der in Grönland entdeckten Bakterienkolonie auf 100.000 Jahre und vermuten, dass das in der Atmosphäre des Roten Planeten nachgewiesene Methan nicht nur von chemischen Prozessen, sondern auch von solchen Mikroben stammen könnte.
+Aktuelle Forschung
+
+Mit dem Mars Science Laboratory wird versucht, neue Aufschlüsse über mögliches Leben auf dem Mars zu liefern. Es ist fraglich, ob der Mars-Rover tief genug bohren kann, um Leben oder zumindest Lebensreste zu finden. Aber eine Isotopenanalyse des Methans kann bereits weitere Aufschlüsse geben. Leben, wie es auf der Erde bekannt ist, bevorzugt leichtere Wasserstoffisotope.
+Beobachtung
+Stellung zur Erde und Bahneigenschaften
+Planetenschleife des Mars im Sternbild Wassermann im Jahr 2003
+
+Aufgrund der Bahneigenschaften der Planeten „überholt“ die Erde den Mars durchschnittlich alle 779 Tage auf ihrer inneren Bahn. Diesen Zeitraum, der zwischen 764 und 811 Tagen schwankt, nennt man synodische Periode. Befinden sich Sonne, Erde und Mars in dieser Anordnung auf einer Linie, so steht der Mars von der Erde aus gesehen in Opposition zur Sonne. Zu diesem Zeitpunkt ist Mars besonders gut zu beobachten, er steht dann als rötlicher „Stern“ auffallend hell am Nachthimmel. Beobachtet man den Mars regelmäßig, kann man feststellen, dass er vor und nach einer Opposition am Himmel eine Schleifenbewegung vollführt. Diese Planetenschleife (Oppositionsschleife) ergibt sich aus den Sichtwinkeln, die Mars bietet, während er von der Erde überholt wird.
+Marsoppositionen von 2003 bis 2018, relative Bewegung des Mars zur Erde, mit der Erde im Zentrum; Ansicht auf die Ekliptikebene
+
+Da die Planeten sich nicht auf idealen Kreisbahnen, sondern auf mehr oder weniger stark ausgeprägten elliptischen Bahnen bewegen, haben Erde und Mars zum Zeitpunkt der Oppositionen unterschiedliche Entfernungen zueinander. Diese können zwischen 55,6 und 101,3 Millionen Kilometern bzw. 0,37 und 0,68 AE betragen. Bei einer geringen Oppositionsentfernung spricht man von einer Perihelopposition, bei einer großen von einer Aphelopposition.
+Schwankung des minimalen Abstands Erde–Mars bei Oppositionen. Die tatsächlichen Werte sind an den einzelnen Punkten ablesbar. Die verbindende Kurve veranschaulicht die mathematische Gesetzmäßigkeit.
+
+Die alle 15 bis 17 Jahre stattfindenden Periheloppositionen bieten die besten Gelegenheiten, den Mars von der Erde aus mittels Teleskop zu beobachten. Der Planet hat dann einen scheinbaren Durchmesser von bis zu 25,8 Bogensekunden. Bei einer Aphelopposition ist er mit 14,1 Bogensekunden nur etwa halb so groß. Besonders erdnahe Oppositionen fanden im Abstand von jeweils 79 Jahren, zum Beispiel in den Jahren 1766, 1845, 1924 und 2003 statt. Am 28. August 2003 betrug der Abstand Erde–Mars 55,76 Millionen Kilometer. Dies war die geringste Distanz seit etwa 60.000 Jahren.[54][55] Erst im Jahre 2287 wird der Mars der Erde noch näher kommen, der Abstand beträgt dann 55,69 Millionen Kilometer.
+
+Im Teleskop erscheint der Mars zunächst als rötliches Scheibchen. Bei stärkerer Vergrößerung können die Polkappen und dunkle Oberflächenmerkmale wie die Große Syrte ausgemacht werden. Treten auf dem Mars größere Staubstürme auf, verblassen die Merkmale, da die Oberfläche von einer rötlichen Dunstschicht eingehüllt wird, die sich mitunter über Wochen halten kann. Durch den Einsatz von CCD-Kameras sind mittlerweile auch Amateurastronomen in der Lage, detailreiche Aufnahmen der Marsoberfläche zu erzielen, wie sie vor etwa zehn Jahren nur von den leistungsfähigsten Großteleskopen erstellt werden konnten.
+
+Ereignisse (Jahreszeitenbeginn gilt für die Nordhalbkugel):[56][57][58]
+Ereignis Frühlingsbeginn Aphel Sommerbeginn Herbstbeginn Perihel Winterbeginn
+Datum 18. Juni 2015 20. November 2015 15. Februar 2014 17. August 2014 12. Dezember 2014 11. Januar 2015
+Nächste
+Termine 5. Mai 2017 7. Oktober 2017 3. Januar 2016 4. Juli 2016 29. Oktober 2016 28. November 2016
+Ereignis Konjunktion Opposition
+Datum 14. Juni 2015 8. April 2014
+Nächste
+Termine 27. Juli 2017 22. Mai 2016
+Sichtbarkeiten
+→ Hauptartikel: Marspositionen
+
+Wegen der Exzentrizität der Marsbahn kann der erdnächste Punkt bis zu einer Woche vor oder nach der Opposition erreicht werden, und die scheinbare Helligkeit während der Opposition sowie der Erdabstand und der scheinbare Durchmesser während der Erdnähe können recht unterschiedlich ausfallen.
+
+Eine Opposition findet etwa alle zwei Jahre (779,94 Tage) statt. Dabei kann bei einer Perihelopposition die maximale scheinbare Helligkeit bis zu −2,91m erreichen. Zu diesem Zeitpunkt sind nur die Sonne, der Erdmond, die Venus und in seltenen Fällen Jupiter (bis zu −2,94m) noch heller. Bei Konjunktion hingegen erscheint Mars nur mehr mit einer Helligkeit von +1,8m.[1]
+Kulturgeschichte
+Beschäftigung mit dem Mars von der Antike bis in die Neuzeit
+Allegorische Darstellung des Mars als Herrscher der Tierkreiszeichen Widder und Skorpion, von Hans Sebald Beham, 16. Jahrhundert
+
+Der Mars bewegte die Menschheit von alters her besonders. Im alten Ägypten wurde Mars als „Horus der Rote“ bezeichnet. Da der Planet sich während seiner Oppositionsschleife (Planetenschleife) zeitweise rückläufig bewegt, sprachen die Ägypter davon, dass Mars rückwärts wandere. Der Name der ägyptischen Hauptstadt „Kairo“ leitet sich von „Al Qahira“ ab, dem altarabischen Namen für den Planeten Mars.
+
+Im indischen Sanskrit wird der Mars als „Mangal“ (verheißungsvoll), „Angaraka“ (Glühende Kohle) und „Kuja“ (der Blonde) bezeichnet. Er repräsentiert kraftvolle Aktion, Vertrauen und Zuversicht.
+
+Aufgrund seiner roten Färbung wurde der Mars in verschiedenen Kulturen mit den Gottheiten des Krieges in Verbindung gebracht. Die Babylonier sahen in ihm Nergal, den Gott der Unterwelt, des Todes und des Krieges. Für die Griechen und Römer der Antike repräsentierte er deren Kriegsgötter Ares beziehungsweise Mars. In der nordischen Mythologie steht er für Tyr, den Gott des Rechts und des Krieges. Die Azteken nannten ihn Huitzilopochtli, der Zerstörer von Menschen und Städten. Für die Chinesen war er Huoxing (chin. HuÅxÄ«ng, ç«æ˜Ÿ), Stern des Feuers.
+
+In der Astrologie ist Mars unter anderem das Symbol der Triebkraft. Es wird dem Element Feuer, dem Planetenmetall Eisen, den Tierkreiszeichen Widder und Skorpion sowie dem 1. Haus zugeordnet.
+Rezeption in Literatur, Film, Videospiele und Musik
+
+Der Mars und seine fiktiven Bewohner sind auch Thema zahlreicher Romane und Verfilmungen.
+
+Ein Beispiel des 18. Jahrhunderts ist Carl Ignaz Geigers Roman Reise eines Erdbewohners in den Mars von 1790.
+
+1880 veröffentlichte Percy Greg seinen Roman Across the Zodiac, in dem er eine Reise in einem Raumschiff namens Astronaut zum Mars beschrieb.
+
+Die klassische Figur des kleinen grünen Männchens mit Antennen auf dem Kopf erschien erstmals 1913 in einem Comic und ist seitdem Klischee.
+
+Als der Astronom Percival Lowell Ende des 19. Jahrhunderts die Vorstellung entwickelte, die mit dem Fernrohr wahrnehmbaren Marskanäle seien künstlich angelegte Wasserkanäle, wurde diese Idee in der Science-Fiction-Literatur aufgegriffen und weitergesponnen. Dort wurde der Mars häufig als eine sterbende Welt vorgestellt, in deren kalten Wüstenregionen alte und weit entwickelte Zivilisationen ums Überleben kämpften.
+
+Kurd Laßwitz brachte 1897 seinen sehr umfangreichen Roman Auf zwei Planeten über einen Besuch bei den Marsbewohnern heraus.
+Angriff der Marsianer in Krieg der Welten von H. G. Wells. Buchillustration der französischen Ausgabe von Alvim Corréa von 1906
+
+In H. G. Wells’ bekanntem Roman Krieg der Welten, der 1898 erschien, verlassen die Marsianer ihre Heimatwelt, um die lebensfreundlichere Erde zu erobern. Die Menschheit, die den hochtechnisierten kriegerischen Marsianern hoffnungslos unterlegen ist, entgeht ihrer Auslöschung nur dadurch, dass die Invasoren von für Menschen harmlosen, irdischen Mikroben dahingerafft werden. Orson Welles verwendete den Stoff im Jahre 1938 in einem Hörspiel, wobei er die Marsianer in New Jersey landen ließ. Das Hörspiel wurde im Stil einer realistischen Reportage ausgestrahlt. Hörer, die sich später einschalteten, hielten die Invasion der Marsianer für Realität.
+
+Wells’ Romanvorlage wurde 1952 verfilmt, wobei die Handlung wiederum in die USA der Gegenwart verlegt wurde. Der Film erhielt für die damals bahnbrechenden Spezialeffekte einen Oscar.
+
+1923 brachte Tolstoi seinen Roman Aelita heraus, der von der Liebe eines sowjetischen Ingenieurs zur Marsprinzessin und dem Untergang der Zivilisation auf dem Planeten handelt. Dieses Werk wurde 1924 verfilmt.
+
+Im Jahr 1978 entstand der Film Unternehmen Capricorn. Er griff das Thema der Verschwörungstheorien zur Mondlandung auf, indem er es in sehr zugespitzter Form auf eine im Filmstudio vorgetäuschte Marsexpedition übertrug.
+
+Der 1996 entstandene Film Mars Attacks! setzt sich ironisch mit dem Thema Marsinvasion auseinander, wobei den Marsianern amerikanische Schnulzenmusik aus den 1950er Jahren zum Verhängnis wird.
+
+Unter der Regie von Brian De Palma wurden im Jahr 2000 mit dem Film Mission to Mars die Spekulationen um das Marsgesicht der Cydonia-Region als hinterlassenes Bauwerk dramatisch weitgehend thematisiert.
+
+Steven Spielbergs 2005 entstandenes Remake von Krieg der Welten nahm noch einmal das Thema auf und zeigte die Invasion von Außerirdischen auf der Erde aus der Sicht eines Familienvaters aus den USA.
+
+Weitere bekannte Science-Fiction-Filme, die auf dem Mars handeln, sind Red Planet (2000) und Die totale Erinnerung – Total Recall (1990).
+
+Edgar Rice Burroughs, der Autor von Tarzan, schrieb von 1917 bis 1943 die elfbändige Saga John Carter vom Mars, in der sich der irdische Held in marsianische Prinzessinnen verliebt und gegen Luftpiraten, grünhäutige Unholde, weiße Riesenaffen und andere Untiere kämpft.
+
+Die Mars-Chroniken (1950), eine stimmungsvolle Sammlung von Erzählungen des Schriftstellers Ray Bradbury, sind ebenfalls auf dem Mars angesiedelt.
+
+Große Beachtung erhielt die Marstrilogie, eine von Kim Stanley Robinson von 1993 bis 1996 verfasste Romanserie über die Besiedelung des Mars. Der besondere Ansatz dieser Geschichten liegt in der vorwiegend technischen Schilderung unter vollständigem Verzicht phantastischer Elemente.
+Die Route von Mark Watney in einer nachgestellten topographischen Kartierung des DLR-Instituts für Planetenforschung
+
+Der wohl prominenteste Auftritt des Mars in der Musik dürfte der erste Satz von Gustav Holsts Orchestersuite Die Planeten (1914–1916) sein, deren erster Satz Mars, the Bringer of War mit seinem drohend-martialischen Charakter die mythologische Gestalt Mars eindrucksvoll porträtiert.
+
+Bestsellerautor Andreas Eschbach verfasste von 2001 bis 2008 die Pentalogie Das Marsprojekt.
+
+2011 veröffentlichte Andy Weir den Science-Fiction-Roman Der Marsianer, in dem ein Astronaut nach einem Unfall auf dem Mars zurückgelassen wird und fortan um sein Überleben kämpfen muss. Mit Der Marsianer – Rettet Mark Watney erschien 2015 eine Verfilmung dieses Bestsellers.
+
+Helga Abret und Lucian Boa geben in ihrem Buch Das Jahrhundert der Marsianer (1984) einen literarischen Überblick über Erzählungen und Romane über den Mars und seine fiktiven Bewohner. Von der Beschreibung einer „ekstatischen Reise“ zum Mars (Itinerarium exstaticum coeleste, 1656) des Jesuitenpaters Athanasius Kircher bis hin zu Science-Fiction-Erzählungen des 20. Jahrhunderts reicht die Bandbreite der kommentierten Werke, mit denen die Autoren aufzuzeigen versuchen, dass „sich aus dem Zusammenwirken von Naturwissenschaften, Astronomie und Literatur ein moderner Mythos“[59] entwickelte.
+
diff --git a/xpcom/tests/gtest/wikipedia/ja.txt b/xpcom/tests/gtest/wikipedia/ja.txt
new file mode 100644
index 0000000000..f5ad44dc5f
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/ja.txt
@@ -0,0 +1,151 @@
+
+
+ç«æ˜Ÿï¼ˆã‹ã›ã„ã€ãƒ©ãƒ†ãƒ³èªž: Mars マールスã€è‹±èªž: マーズã€ã‚®ãƒªã‚·ã‚¢èªž: ΆÏης アレース)ã¯ã€å¤ªé™½ç³»ã®å¤ªé™½ã«è¿‘ã„æ–¹ã‹ã‚‰4番目ã®æƒ‘星ã§ã‚る。地çƒåž‹æƒ‘星ã«åˆ†é¡žã•ã‚Œã€åœ°çƒã®å¤–å´ã®è»Œé“を公転ã—ã¦ã„る。
+
+英語åœã§ã¯ã€ãã®è¡¨é¢ã®è‰²ã‹ã‚‰ã€Red Planet(レッド・プラãƒãƒƒãƒˆã€ã€Œèµ¤ã„惑星ã€ã®æ„)ã¨ã„ã†é€šç§°ãŒã‚る。
+
+物ç†çš„性質[編集]
+地çƒã¨ç«æ˜Ÿã®å¤§ãã•æ¯”較。
+
+ç«æ˜Ÿã¯åœ°çƒåž‹æƒ‘星ã«åˆ†é¡žã•ã‚Œã‚‹ã€ã„ã‚ゆる硬ã„岩石ã®åœ°è¡¨ã‚’æŒã£ãŸæƒ‘星ã§ã‚る。ç«æ˜ŸãŒèµ¤ã見ãˆã‚‹ã®ã¯ã€ãã®è¡¨é¢ã«åœ°çƒã®ã‚ˆã†ãªæ°´ã®æµ·ãŒç„¡ãã€åœ°è¡¨ã«é…¸åŒ–鉄(赤ã•ã³ï¼‰ãŒå¤§é‡ã«å«ã¾ã‚Œã¦ã„ã‚‹ãŸã‚ã§ã‚る。直径ã¯åœ°çƒã®åŠåˆ†ã»ã©ã§ã€è³ªé‡ã¯åœ°çƒã®ç´„ 1/10 ã«éŽãŽãªã„ãŸã‚ã€ç«æ˜Ÿã®åœ°è¡¨ã§ã®é‡åŠ›ã®å¼·ã•ã¯åœ°çƒã®40%ã»ã©ã—ã‹ãªã„。ç«æ˜Ÿã®è¡¨é¢ç©ã¯ã€åœ°çƒã®è¡¨é¢ç©ã®ç´„ 1/4ã§ã‚ã‚‹ãŒã€ã“ã‚Œã¯åœ°çƒã®é™¸åœ°ã®é¢ç©ï¼ˆç´„1.5å„„km2)ã¨ã»ã¼ç­‰ã—ã„。ç«æ˜Ÿã®è‡ªè»¢å‘¨æœŸã¯åœ°çƒã®ãã‚Œã¨éžå¸¸ã«è¿‘ãã€ç«æ˜Ÿã®1日(1ç«æ˜Ÿå¤ªé™½æ—¥ã€1 sol)ã¯ã€24時間39分35.244秒ã§ã‚る。ã¾ãŸåœ°çƒã¨åŒã˜ã‚ˆã†ã«å¤ªé™½ã«å¯¾ã—ã¦è‡ªè»¢è»¸ã‚’傾ã‘ãŸã¾ã¾å…¬è»¢ã—ã¦ã„ã‚‹ã®ã§ã€ç«æ˜Ÿã«ã¯å­£ç¯€ãŒå­˜åœ¨ã™ã‚‹ã€‚
+質é‡[編集]
+
+地çƒã‚„金星ã¨æ¯”ã¹ã¦ç«æ˜Ÿã®è³ªé‡ã¯å°ã•ã„[2]。太陽系ã®æƒ‘星移動ã®ãƒ¢ãƒ‡ãƒ«ã§ã‚るグランド・タックモデルã«ã‚ˆã‚‹ã¨ã€æœ¨æ˜Ÿã¯ç«æ˜Ÿå½¢æˆå‰ã«ä¸€åº¦ç«æ˜Ÿè»Œé“程度ã¾ã§å¤ªé™½ã«è¿‘ã¥ãã€å¾Œã«ç¾åœ¨ã®è»Œé“ã«è½ã¡ç€ã„ãŸã¨ã—ã¦ã„ã‚‹[2]。ãã®éš›ã€ç«æ˜Ÿã®æ§‹æˆã«ä½¿ç”¨ã•ã‚ŒãŸã§ã‚ã‚ã†è³ªé‡ã®å°å¤©ä½“ã‚’ã¯ã˜ã飛ã°ã—ã¦ã—ã¾ã£ãŸãŸã‚ã€ç«æ˜ŸãŒå分æˆé•·ã§ããªã‹ã£ãŸå¯èƒ½æ€§ã‚’示唆ã—ã¦ã„ã‚‹[2]。
+大気[編集]
+詳細ã¯ã€Œç«æ˜Ÿã®å¤§æ°—ã€ã‚’å‚ç…§
+ç«æ˜Ÿï¼ˆã“ã®ä½Žè»Œé“写真ã®ä¸­ã®åœ°å¹³ç·šã§è¦‹ãˆã‚‹ï¼‰ã®è–„ã„大気
+
+ç«æ˜Ÿã®å¤§æ°—ã¯å¸Œè–„ã§ã€åœ°è¡¨ã§ã®å¤§æ°—圧ã¯ç´„750Paã¨åœ°çƒã§ã®å¹³å‡å€¤ã®ç´„0.75%ã«éŽãŽãªã„。逆ã«å¤§æ°—ã®åŽšã•ã‚’示ã™ã‚¹ã‚±ãƒ¼ãƒ«ãƒã‚¤ãƒˆã¯ç´„11kmã«é”ã—ã€ãŠã‚ˆã6kmã§ã‚る地çƒã‚ˆã‚Šã‚‚高ã„。ã“れらã¯ã„ãšã‚Œã‚‚ã€ç«æ˜Ÿã®é‡åŠ›ãŒåœ°çƒã‚ˆã‚Šã‚‚å¼±ã„ã“ã¨ã«èµ·å› ã—ã¦ã„る。大気ãŒå¸Œè–„ãªãŸã‚ã«ç†±ã‚’ä¿æŒã™ã‚‹ä½œç”¨ãŒå¼±ãã€è¡¨é¢æ¸©åº¦ã¯æœ€é«˜ã§ã‚‚ç´„20℃ã§ã‚る。大気ã®çµ„æˆã¯äºŒé…¸åŒ–ç‚­ç´ ãŒ95%ã€çª’ç´ ãŒ3%ã€ã‚¢ãƒ«ã‚´ãƒ³ãŒ1.6%ã§ã€ä»–ã«é…¸ç´ ã‚„水蒸気ãªã©ã®å¾®é‡æˆåˆ†ã‚’å«ã‚€ã€‚ãŸã ã—ã€ç«æ˜Ÿã®å¤§æ°—ã®ä¸Šå±¤éƒ¨ã¯å¤ªé™½é¢¨ã®å½±éŸ¿ã‚’å—ã‘ã¦å®‡å®™ç©ºé–“ã¸ã¨æµå‡ºã—ã¦ã„ã‚‹ã“ã¨ãŒã€ã‚½ãƒ“エト連邦ã®ç„¡äººç«æ˜ŸæŽ¢æŸ»æ©Ÿã®ãƒ•ã‚©ãƒœã‚¹2å·ã«ã‚ˆã£ã¦è¦³æ¸¬ã•ã‚Œã¦ã„る。ã—ãŸãŒã£ã¦ä¸Šè¨˜ã®ç«æ˜Ÿã®å¤§æ°—圧や大気組æˆã¯ã€é•·ã„ç›®ã§è¦‹ã‚‹ã¨å¤‰åŒ–ã—ã¦ã„ã‚‹å¯èƒ½æ€§ã€ãã—ã¦ä»Šå¾Œã‚‚変化ã—ã¦ã‚†ãå¯èƒ½æ€§ãŒæŒ‡æ‘˜ã•ã‚Œã¦ã„る。
+
+2003å¹´ã«åœ°çƒã‹ã‚‰ã®æœ›é é¡ã«ã‚ˆã‚‹è¦³æ¸¬ã§å¤§æ°—ã«ãƒ¡ã‚¿ãƒ³ãŒå«ã¾ã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ãŒæµ®ä¸Šã—ã€2004å¹´3月ã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機ã®èª¿æŸ»ã«ã‚ˆã‚‹å¤§æ°—ã®è§£æžã§ãƒ¡ã‚¿ãƒ³ã®å­˜åœ¨ãŒç¢ºèªã•ã‚ŒãŸã€‚ç¾åœ¨è¦³æ¸¬ã•ã‚Œã¦ã„るメタンã®é‡ã®å¹³å‡å€¤ã¯ä½“ç©æ¯”ã§ç´„11±4 ppb ã§ã‚る。
+
+ç«æ˜Ÿã®ç’°å¢ƒä¸‹ã§ã¯ä¸å®‰å®šãªæ°—体ã§ã‚るメタンã®å­˜åœ¨ã¯ã€ç«æ˜Ÿã«ãƒ¡ã‚¿ãƒ³ã®ã‚¬ã‚¹æºãŒå­˜åœ¨ã™ã‚‹ï¼ˆã¾ãŸã¯ã€å°‘ãªãã¨ã‚‚最近100年以内ã«ã¯å­˜åœ¨ã—ã¦ã„ãŸï¼‰ã¨ã„ã†èˆˆå‘³æ·±ã„事実を示唆ã—ã¦ã„る。ガスã®ç”Ÿæˆæºã¨ã—ã¦ã¯ç«å±±æ´»å‹•ã‚„彗星ã®è¡çªã€ã‚ã‚‹ã„ã¯ãƒ¡ã‚¿ãƒ³èŒã®ã‚ˆã†ãªå¾®ç”Ÿç‰©ã®å½¢ã§ç”Ÿå‘½ãŒå­˜åœ¨ã™ã‚‹ãªã©ã®å¯èƒ½æ€§ãŒè€ƒãˆã‚‰ã‚Œã¦ã„ã‚‹ãŒã€ã„ãšã‚Œã‚‚未確èªã§ã‚る。地çƒã®æµ·ã§ã¯ã€ç”Ÿç‰©ã«ã‚ˆã£ã¦ãƒ¡ã‚¿ãƒ³ãŒç”Ÿæˆã•ã‚Œã‚‹éš›ã«ã¯åŒæ™‚ã«ã‚¨ã‚¿ãƒ³ã‚‚生æˆã•ã‚Œã‚‹å‚¾å‘ãŒã‚る。一方ã€ç«å±±æ´»å‹•ã‹ã‚‰æ”¾å‡ºã•ã‚Œã‚‹ãƒ¡ã‚¿ãƒ³ã«ã¯äºŒé…¸åŒ–硫黄ãŒä»˜éšã™ã‚‹ã€‚メタンã¯ç«æ˜Ÿè¡¨é¢ã®æ‰€ã€…ã«å±€æ‰€çš„ã«å­˜åœ¨ã—ã¦ã„るよã†ã«è¦‹ãˆã‚‹äº‹ã‹ã‚‰ã€ç™ºç”Ÿã—ãŸãƒ¡ã‚¿ãƒ³ã¯å¤§æ°—中ã«ä¸€æ§˜ã«åˆ†å¸ƒã™ã‚‹ã‚ˆã‚Šã‚‚短時間ã§åˆ†è§£ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒã†ã‹ãŒãˆã‚‹ã€‚ãれゆãˆã€ãŠãらãæŒç¶šçš„ã«å¤§æ°—中ã«æ”¾å‡ºã•ã‚Œã¦ã„ã‚‹ã¨ã‚‚推測ã•ã‚Œã‚‹ã€‚発生æºã«é–¢ã™ã‚‹ä»®èª¬ã§ã©ã‚ŒãŒæœ€ã‚‚有力ã‹ã‚’推定ã™ã‚‹ãŸã‚ã«ã€ãƒ¡ã‚¿ãƒ³ã¨åŒæ™‚ã«æ”¾å‡ºã•ã‚Œã‚‹åˆ¥ã®æ°—体を検出ã™ã‚‹è¨ˆç”»ã‚‚ç¾åœ¨é€²ã‚られã¦ã„る。
+
+ç«æ˜Ÿå¤§æ°—ã«ã¯å¤§ãã変化ã™ã‚‹é¢ã‚‚ã‚る。冬ã®æ•°ãƒ¶æœˆé–“ã«æ¥µåœ°æ–¹ã§å¤œãŒç¶šãã¨ã€åœ°è¡¨ã¯éžå¸¸ã«ä½Žæ¸©ã«ãªã‚Šã€å¤§æ°—全体ã®25%ã‚‚ãŒå‡å›ºã—ã¦åŽšã•æ•°ãƒ¡ãƒ¼ãƒˆãƒ«ã«é”ã™ã‚‹äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ï¼ˆãƒ‰ãƒ©ã‚¤ã‚¢ã‚¤ã‚¹ï¼‰ã®å±¤ã‚’ã¤ãる。やãŒã¦ã€æ¥µã«å†ã³æ—¥å…‰ãŒå½“ãŸã‚‹å­£ç¯€ã«ãªã‚‹ã¨äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ã¯æ˜‡è¯ã—ã¦ã€æ¥µåœ°æ–¹ã«å¹ã付ã‘ã‚‹400km/hã«é”ã™ã‚‹å¼·ã„風ãŒç™ºç”Ÿã™ã‚‹ã€‚ã“れらã®å­£ç¯€çš„活動ã«ã‚ˆã£ã¦å¤§é‡ã®å¡µã‚„水蒸気ãŒé‹ã°ã‚Œã€åœ°çƒã¨ä¼¼ãŸéœœã‚„大è¦æ¨¡ãªå·»é›²ãŒç”Ÿã˜ã‚‹ã€‚ã“ã®ã‚ˆã†ãªæ°´ã®æ°·ã‹ã‚‰ãªã‚‹é›²ã®å†™çœŸãŒ2004å¹´ã«ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚Œã¦ã„る(撮影画åƒï¼‰ã€‚ã¾ãŸã€å—極ã§äºŒé…¸åŒ–ç‚­ç´ ãŒçˆ†ç™ºçš„ã«å™´å‡ºã—ãŸè·¡ãŒãƒžãƒ¼ã‚ºãƒ»ã‚ªãƒ‡ãƒƒã‚»ã‚¤ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚Œã¦ã„ã‚‹[3]。
+
+ç«æ˜Ÿã¯çŸ­ã„時間尺度ã§ã¯æ¸©æš–化ã—ã¦ã„ã‚‹ã“ã¨ã‚’示唆ã™ã‚‹è¨¼æ‹ ã‚‚発見ã•ã‚Œã¦ã„ã‚‹[4]。ã—ã‹ã—21世紀åˆé ­ã®ç«æ˜Ÿã¯1970年代よりã¯å¯’冷ã§ã‚ã‚‹[5]。
+地質[編集]
+スピリットãŒæŠ‰ã£ãŸåœ°è¡¨ã€‚明るã„シリカ(二酸化ケイ素)ãŒå‰ã出ã—ã«ãªã£ã¦ã„る。
+
+ç«æ˜Ÿã®è¡¨é¢ã¯ä¸»ã¨ã—ã¦çŽ„武岩ã¨å®‰å±±å²©ã®å²©çŸ³ã‹ã‚‰ãªã£ã¦ã„る。ã„ãšã‚Œã‚‚地çƒä¸Šã§ã¯ãƒžã‚°ãƒžãŒåœ°è¡¨è¿‘ãã§å›ºã¾ã£ã¦ç”Ÿæˆã™ã‚‹å²©çŸ³ã§ã‚ã‚Šã€å«ã¾ã‚Œã‚‹äºŒé…¸åŒ–ケイ素 (SiO2) ã®é‡ã§åŒºåˆ¥ã•ã‚Œã‚‹ã€‚ç«æ˜Ÿã§ã¯å¤šãã®å ´æ‰€ãŒåŽšã•æ•°ãƒ¡ãƒ¼ãƒˆãƒ«ã‚ã‚‹ã„ã¯ãれ以上ã®æ»‘石粉ã®ã‚ˆã†ãªç´°ã‹ã„塵ã§è¦†ã‚ã‚Œã¦ã„る。
+
+マーズ・グローãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼æŽ¢æŸ»æ©Ÿã«ã‚ˆã‚‹ç«æ˜Ÿã®ç£å ´ã®è¦³æ¸¬ã‹ã‚‰ã€ç«æ˜Ÿã®åœ°æ®»ãŒå‘ãã®å転を繰り返ã™ãƒãƒ³ãƒ‰çŠ¶ã«ç£åŒ–ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒåˆ†ã‹ã£ã¦ã„る。ã“ã®ç£åŒ–ãƒãƒ³ãƒ‰ã¯å…¸åž‹çš„ã«ã¯å¹…160kmã€é•·ã•1,000kmã«ã‚ãŸã£ã¦ã„る。ã“ã®ã‚ˆã†ãªç£åŒ–ã®ãƒ‘ターンã¯åœ°çƒã®æµ·åº•ã«è¦‹ã‚‰ã‚Œã‚‹ã‚‚ã®ã¨ä¼¼ã¦ã„る。1999å¹´ã«ç™ºè¡¨ã•ã‚ŒãŸèˆˆå‘³æ·±ã„説ã«ã‚ˆã‚‹ã¨ã€ã“れらã®ãƒãƒ³ãƒ‰ã¯éŽåŽ»ã®ç«æ˜Ÿã®ãƒ—レートテクトニクス作用ã®è¨¼æ‹ ã‹ã‚‚ã—ã‚Œãªã„ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。ã—ã‹ã—ãã®ã‚ˆã†ãªãƒ—レート活動ãŒã‚ã£ãŸè¨¼æ‹ ã¯ã¾ã ç¢ºèªã•ã‚Œã¦ã„ãªã„[6]。2005å¹´10月ã«ç™ºè¡¨ã•ã‚ŒãŸæ–°ãŸãªç™ºè¦‹ã¯ä¸Šè¨˜ã®èª¬ã‚’支æŒã™ã‚‹ã‚‚ã®ã§ã€åœ°çƒã§ç™ºè¦‹ã•ã‚Œã¦ã„る海底拡大ã«ã‚ˆã‚‹ãƒ†ã‚¯ãƒˆãƒ‹ã‚¯ã‚¹æ´»å‹•ã¨åŒæ§˜ã®æ´»å‹•ãŒå¤ªå¤ã®ç«æ˜Ÿã«ã‚ã£ãŸã“ã¨ã‚’示ã—ã¦ã„ã‚‹[7]。もã—ã“れらãŒæ­£ã—ã‘ã‚Œã°ã€ã“れらã®æ´»å‹•ã«ã‚ˆã£ã¦ç‚­ç´ ã®è±Šå¯Œãªå²©çŸ³ãŒåœ°è¡¨ã«é‹ã°ã‚Œã‚‹ã“ã¨ã«ã‚ˆã£ã¦åœ°çƒã«è¿‘ã„大気ãŒç¶­æŒã•ã‚Œã€ä¸€æ–¹ã§ç£å ´ã®å­˜åœ¨ã«ã‚ˆã£ã¦ç«æ˜Ÿè¡¨é¢ãŒå®‡å®™æ”¾å°„ç·šã‹ã‚‰å®ˆã‚‰ã‚Œã‚‹ã“ã¨ã«ãªã£ãŸã‹ã‚‚ã—ã‚Œãªã„。ã¾ãŸã“れらã¨ã¯åˆ¥ã®ç†è«–的説明もæ案ã•ã‚Œã¦ã„る。
+オãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸç«æ˜Ÿã®å²©çŸ³ã®é¡•å¾®é¡å†™çœŸã€‚éŽåŽ»ã«æ°´ã®ä½œç”¨ã«ã‚ˆã£ã¦ä½œã‚‰ã‚ŒãŸã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。
+
+オãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã‚‹ç™ºè¦‹ã®ä¸­ã«ã€ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹å¹³åŽŸã§æŽ¡å–ã—ãŸå²©çŸ³ã‹ã‚‰å°ã•ãªçƒå½¢ã®èµ¤é‰„鉱(ヘマタイト)ãŒç™ºè¦‹ã•ã‚ŒãŸã€‚ã“ã®çƒä½“ã¯ç›´å¾„ã‚ãšã‹æ•°mmã—ã‹ãªãã€æ•°åå„„å¹´å‰ã«æ°´ã®å¤šã„環境ã®ä¸‹ã§å †ç©å²©ã¨ã—ã¦ä½œã‚‰ã‚ŒãŸã‚‚ã®ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。他ã«ã‚‚鉄ミョウãƒãƒ³çŸ³ãªã©ã€ç¡«é»„ã€é‰„ã€è‡­ç´ ã‚’å«ã‚€é‰±ç‰©ãŒç™ºè¦‹ã•ã‚Œã¦ã„る。ã“れらをå«ã‚€å¤šãã®è¨¼æ‹ ã‹ã‚‰ã€å­¦è¡“誌「サイエンス〠2004å¹´12月9æ—¥å·ã«ãŠã„ã¦50åã®ç ”究者ã‹ã‚‰ãªã‚‹ç ”究グループã¯ã€ã€Œç«æ˜Ÿè¡¨é¢ã®ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹å¹³åŽŸã§ã¯éŽåŽ»ã«æ¶²ä½“ã®æ°´ãŒæ–­ç¶šçš„ã«å­˜åœ¨ã—ã€åœ°è¡¨ã®ä¸‹ãŒæ°´ã§æº€ãŸã•ã‚Œã¦ã„ãŸæ™‚代ãŒä½•å›žã‹ã‚ã£ãŸã€‚液体ã®æ°´ã¯ç”Ÿå‘½ã«ã¨ã£ã¦éµã¨ãªã‚‹å¿…è¦æ¡ä»¶ã§ã‚ã‚‹ãŸã‚ã€æˆ‘々ã¯ç«æ˜Ÿã®æ­´å²ã®ä¸­ã§ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹ã§ã¯ç”Ÿå‘½ã®å­˜åœ¨å¯èƒ½ãªç’°å¢ƒãŒä½•åº¦ã‹ä½œã‚‰ã‚Œã¦ã„ãŸã¨æŽ¨æ¸¬ã—ã¦ã„ã‚‹ã€ã¨çµè«–ã—ã¦ã„る。メリディアニã®å対å´ã®ç«æ˜Ÿè¡¨é¢ã§ã¯ã€ã‚³ãƒ­ãƒ³ãƒ“ア・ヒルズã«ãŠã„ã¦ã‚¹ãƒ”リットãŒé‡é‰„鉱を発見ã—ã¦ã„る。ã“ã‚Œã¯ï¼ˆèµ¤é‰„鉱ã¨ã¯ç•°ãªã‚Šï¼‰æ°´ãŒå­˜åœ¨ã™ã‚‹ç’°å¢ƒã§ã€Œã®ã¿ã€ä½œã‚‰ã‚Œã‚‹é‰±ç‰©ã§ã‚る。スピリットã¯ä»–ã«ã‚‚æ°´ã®å­˜åœ¨ã‚’示ã™è¨¼æ‹ ã‚’発見ã—ã¦ã„る。
+
+マーズ・グローãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ãŒ2006å¹´ã«æ’®å½±ã—ãŸå†™çœŸã‹ã‚‰ã€ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼å†…å£ã®æ–œé¢ã‚’液体ãŒæµã‚ŒãŸç—•è·¡ãŒè¦‹ã¤ã‹ã£ãŸãŒã€1999å¹´ã«åŒã˜å ´æ‰€ã‚’撮影ã—ãŸå†™çœŸã«ã¯å†™ã£ã¦ãŠã‚‰ãšã€ãれ以é™ã«ã§ããŸã‚‚ã®ã¨æ€ã‚れる。
+
+1996å¹´ã€ç«æ˜Ÿèµ·æºã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る隕石「ALH84001ã€ã‚’調査ã—ã¦ã„ãŸç ”究者ãŒã€ç«æ˜Ÿã®ç”Ÿå‘½ã«ã‚ˆã£ã¦æ®‹ã•ã‚ŒãŸã¨æ€ã‚れる微å°åŒ–石ãŒã“ã®éš•çŸ³ã«å«ã¾ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’報告ã—ãŸã€‚2005å¹´ç¾åœ¨ã€ã“ã®è§£é‡ˆã«ã¤ã„ã¦ã¯ã„ã¾ã ã«è­°è«–ãŒã‚ã‚Šã€åˆæ„ã¯å¾—られã¦ã„ãªã„。
+地形[編集]
+ç«æ˜Ÿã®åœ°å½¢å›³ã€‚特徴的ãªåœ°å½¢ã¨ã—ã¦ã€è¥¿éƒ¨ã®ã‚¿ãƒ«ã‚·ã‚¹ç«å±±ç¾¤ï¼ˆã‚ªãƒªãƒ³ãƒã‚¹å±±ã‚’å«ã‚€ï¼‰ã€ã‚¿ãƒ«ã‚·ã‚¹ã®æ±ã«ã‚るマリãƒãƒªã‚¹å³¡è°·ã€å—åŠçƒã®ãƒ˜ãƒ©ã‚¹ç›†åœ°ãªã©ãŒã‚ã‚‹
+「ç«æ˜Ÿã®åœ°å½¢ä¸€è¦§ã€ã‚‚å‚ç…§
+
+ç«æ˜Ÿã®åœ°å½¢ã¯å¤§ãã二通りã«åˆ†ã‹ã‚Œã¦ãŠã‚Šã€ç‰¹å¾´çš„ã§ã‚る。北åŠçƒã¯æº¶å²©æµã«ã‚ˆã£ã¦å¹³ã‚‰ã«å‡ã•ã‚ŒãŸå¹³åŽŸï¼ˆåŒ—部平原ã®æˆå› ã¨ã—ã¦ã¯å¤§é‡ã®æ°´ã«ã‚ˆã‚‹ä¾µé£Ÿèª¬ã‚‚ã‚る)ãŒåºƒãŒã£ã¦ãŠã‚Šã€ä¸€æ–¹ã€å—åŠçƒã¯å¤ªå¤ã®éš•çŸ³è¡çªã«ã‚ˆã‚‹çªªåœ°ã‚„クレーターãŒå­˜åœ¨ã™ã‚‹é«˜åœ°ãŒå¤šã„。地çƒã‹ã‚‰è¦‹ãŸç«æ˜Ÿè¡¨é¢ã‚‚ã“ã®ãŸã‚ã«äºŒç¨®é¡žã®åœ°åŸŸã«åˆ†ã‘られã€ä¸¡è€…ã¯å…‰ã®å射率ã§ã‚るアルベドãŒç•°ãªã£ã¦ã„る。明るã見ãˆã‚‹å¹³åŽŸã¯èµ¤ã„酸化鉄を多ãå«ã‚€å¡µã¨ç ‚ã«è¦†ã‚ã‚Œã¦ãŠã‚Šã€ã‹ã¤ã¦ã¯ç«æ˜Ÿã®å¤§é™¸ã¨è¦‹ç«‹ã¦ã‚‰ã‚Œã¦ã‚¢ãƒ©ãƒ“ア大陸 (Arabia Terra) やアマゾニス平原 (Amazonis Planitia) ãªã©ã¨å‘½åã•ã‚Œã¦ã„る。暗ã„模様ã¯æµ·ã¨è€ƒãˆã‚‰ã‚Œã€ã‚¨ãƒªãƒˆãƒªã‚¢æµ· (Mare Erythraeum)ã€ã‚·ãƒ¬ãƒ¼ãƒŒã‚¹ï¼ˆã‚»ã‚¤ãƒ¬ãƒ¼ãƒ³ãŸã¡ï¼‰ã®æµ· (Mare Sirenum)ã€ã‚ªãƒ¼ãƒ­ãƒ©æ¹¾ (Aurorae Sinus) ãªã©ã¨åã¥ã‘られã¦ã„る。地çƒã‹ã‚‰è¦‹ãˆã‚‹æœ€ã‚‚大ããªæš—ã„模様ã¯å¤§ã‚·ãƒ«ãƒã‚¹ (Syrtis Major) ã§ã‚る。
+北極地ã®åˆå¤æ¥µå† 
+
+ç«æ˜Ÿã«ã¯æ°´ã¨äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ã‹ã‚‰ãªã‚‹æ¥µå† ãŒã‚ã‚Šã€ç«æ˜Ÿã®å­£ç¯€ã«ã‚ˆã£ã¦å¤‰åŒ–ã™ã‚‹ã€‚二酸化炭素ã®æ°·ã¯å¤ã«ã¯æ˜‡è¯ã—ã¦å²©çŸ³ã‹ã‚‰ãªã‚‹è¡¨é¢ãŒç¾ã‚Œã€å†¬ã«ã¯å†ã³æ°·ãŒã§ãる。楯状ç«å±±ã§ã‚るオリンãƒã‚¹å±±ã¯æ¨™é«˜27kmã®å¤ªé™½ç³»æœ€é«˜ã®å±±ã§ã‚ã‚‹[8]。ã“ã®å±±ã¯ã‚¿ãƒ«ã‚·ã‚¹é«˜åœ°ã¨å‘¼ã°ã‚Œã‚‹åºƒå¤§ãªé«˜åœ°ã«ã‚ã‚Šã€ã“ã®åœ°æ–¹ã«ã¯ã„ãã¤ã‹ã®å¤§ããªç«å±±ãŒã‚る。ç«æ˜Ÿã«ã¯å¤ªé™½ç³»æœ€å¤§ã®å³¡è°·ã§ã‚るマリãƒãƒªã‚¹å³¡è°·ã‚‚存在ã™ã‚‹ã€‚ã“ã®å³¡è°·ã¯å…¨é•·4,000kmã€æ·±ã•7kmã«é”ã™ã‚‹ã€‚ç«æ˜Ÿã«ã¯å¤šãã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‚‚存在ã™ã‚‹ã€‚最大ã®ã‚‚ã®ã¯ãƒ˜ãƒ©ã‚¹ç›†åœ°ã§ã€æ˜Žã‚‹ã„赤色ã®ç ‚ã§è¦†ã‚ã‚Œã¦ã„る。
+
+ç«æ˜Ÿã®æœ€é«˜åœ°ç‚¹ã¨æœ€ä½Žåœ°ç‚¹ã®æ¨™é«˜å·®ã¯ç´„31kmã§ã‚る。オリンãƒã‚¹å±±ã®å±±é ‚ 27km ãŒæœ€ã‚‚高ãã€ãƒ˜ãƒ©ã‚¹ç›†åœ°ã®åº•éƒ¨ã€æ¨™é«˜åŸºæº–é¢ã®ç´„ 4km 下ãŒæœ€ã‚‚低ã„。ã“ã‚Œã¨æ¯”ã¹ã¦åœ°çƒã®æœ€é«˜ç‚¹ã¨æœ€ä½Žç‚¹ï¼ˆã‚¨ãƒ™ãƒ¬ã‚¹ãƒˆã¨ãƒžãƒªã‚¢ãƒŠæµ·æºï¼‰ã®å·®ã¯19.7kmã«éŽãŽãªã„。両惑星ã®åŠå¾„ã®å·®ã‚’考ãˆã‚‹ã¨ã€ç«æ˜ŸãŒåœ°çƒã‚ˆã‚Šã‚‚ãŠã‚ˆã3å€ã‚‚凸凹ã§ã‚ã‚‹ã“ã¨ã‚’示ã—ã¦ã„る。
+
+21世紀åˆé ­ç¾åœ¨ã§ã¯ã€å›½éš›å¤©æ–‡å­¦é€£åˆ (IAU) ã®æƒ‘星系命åワーキンググループãŒç«æ˜Ÿè¡¨é¢ã®åœ°å½¢åã®å‘½åを担当ã—ã¦ã„る。
+座標ã®åŸºæº–[編集]
+
+ç«æ˜Ÿã«ã¯æµ·ãŒãªã„ã®ã§æµ·æŠœã¨ã„ã†å®šç¾©ã¯ä½¿ãˆãªã„。従ã£ã¦é«˜åº¦0ã®é¢ã€ã™ãªã‚ã¡å¹³å‡é‡åŠ›é¢ã‚’é¸ã¶å¿…è¦ãŒã‚る。ç«æ˜Ÿã®åŸºæº–測地系ã¯4階4次ã®çƒé¢èª¿å’Œé–¢æ•°é‡åŠ›å ´ã§å®šç¾©ã•ã‚Œã€é«˜åº¦0ã¯æ¸©åº¦273.16Kã§ã®å¤§æ°—圧ãŒ610.5Pa(地çƒã®ç´„0.6%)ã¨ãªã‚‹é¢ã¨ã—ã¦å®šç¾©ã•ã‚Œã¦ã„る。ã“ã®åœ§åŠ›ã¨æ¸©åº¦ã¯æ°´ã®ä¸‰é‡ç‚¹ã«å¯¾å¿œã—ã¦ã„る。
+
+ç«æ˜Ÿã®èµ¤é“ã¯ãã®è‡ªè»¢ã‹ã‚‰å®šç¾©ã•ã‚Œã¦ã„ã‚‹ãŒã€åŸºæº–å­åˆç·šã®ä½ç½®ã¯åœ°çƒã®å ´åˆã¨åŒæ§˜ã«ä»»æ„ã®ç‚¹ãŒé¸ã°ã‚Œã€å¾Œä¸–ã®è¦³æ¸¬è€…ã«ã‚ˆã£ã¦å—ã‘入れられã¦ã„ã£ãŸã€‚ドイツã®å¤©æ–‡å­¦è€…ヴィルヘルム・ベーアã¨ãƒ¨ãƒãƒ³ãƒ»ãƒã‚¤ãƒ³ãƒªãƒƒãƒ’・メドラーã¯1830å¹´ã‹ã‚‰32å¹´ã«ã‹ã‘ã¦æœ€åˆã®ç«æ˜Ÿã®ä½“系的ãªåœ°å›³ã‚’作æˆã—ãŸéš›ã«ã€ã‚ã‚‹å°ã•ãªå††å½¢ã®æ¨¡æ§˜ã‚’基準点ã¨ã—ãŸã€‚彼らã®é¸æŠžã—ãŸåŸºæº–点ã¯1877å¹´ã«ã€ã‚¤ã‚¿ãƒªã‚¢ã®å¤©æ–‡å­¦è€…ジョヴァンニ・スキアパレッリãŒæœ‰åãªç«æ˜Ÿå›³ã®ä½œæˆã‚’始ã‚ãŸéš›ã«åŸºæº–å­åˆç·šã¨ã—ã¦æŽ¡ç”¨ã•ã‚ŒãŸã€‚1972å¹´ã«æŽ¢æŸ»æ©ŸãƒžãƒªãƒŠãƒ¼9å·ãŒç«æ˜Ÿã®åºƒç¯„囲ã®ç”»åƒã‚’撮影ã—ãŸå¾Œã€å­åˆç·šã®æ¹¾ã®ãƒ™ãƒ¼ã‚¢ã¨ãƒ¡ãƒ‰ãƒ©ãƒ¼ã®å­åˆç·šä¸Šã«ã‚ã‚‹å°ã•ãªã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ï¼ˆå¾Œã«ã‚¨ã‚¢ãƒªãƒ¼0ã¨å‘¼ã°ã‚Œã‚‹ï¼‰ãŒã‚¢ãƒ¡ãƒªã‚«ã€RAND社ã®ãƒ¡ãƒ«ãƒˆãƒ³ãƒ»ãƒ‡ãƒ¼ãƒ´ã‚£ã‚¹ã«ã‚ˆã£ã¦ã€æƒ‘星撮影時ã®åˆ¶å¾¡ç‚¹ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚’決ã‚ã‚‹éš›ã«ã‚ˆã‚Šæ­£ç¢ºãªçµŒåº¦0.0度ã®å®šç¾©ã¨ã—ã¦æŽ¡ç”¨ã•ã‚ŒãŸã€‚
+「é‹æ²³ã€[編集]
+
+ç«æ˜Ÿã«ã¯ã‹ã¤ã¦ç”Ÿå‘½ãŒå­˜åœ¨ã—ãŸã¨ã„ã†è€ƒãˆã®ãŸã‚ã«ã€ç«æ˜Ÿã¯äººé¡žã®æƒ³åƒã®ä¸–ç•Œã®ä¸­ã§é‡è¦ãªä½ç½®ã‚’å ã‚ã¦ã„る。ã“ã†ã„ã£ãŸè€ƒãˆã¯ä¸»ã«19世紀ã«å¤šãã®äººã€…ã«ã‚ˆã£ã¦è¡Œã‚ã‚Œã€ç‰¹ã«ãƒ‘ーシヴァル・ローウェルやジョヴァンニ・スキアパレッリã«ã‚ˆã‚‹ç«æ˜Ÿè¦³æ¸¬ã‹ã‚‰ç”Ÿã¾ã‚Œã€ä¸€èˆ¬ã«çŸ¥ã‚‰ã‚Œã‚‹ã‚ˆã†ã«ãªã£ãŸã€ã‚¹ã‚­ã‚¢ãƒ‘レッリã¯è¦³æ¸¬ã•ã‚ŒãŸæ¨¡æ§˜ã‚’イタリア語: canali(æºï¼‰ã¨ã„ã†èªžã§è¨˜è¿°ã—ãŸã€‚ã“ã‚ŒãŒè‹±èªž: canal(é‹æ²³ï¼‰ã¨èª¤è¨³ã•ã‚Œã€ã“ã“ã‹ã‚‰ã€Œç«æ˜Ÿã®é‹æ²³ã€ã¨ã„ã†èª¬ãŒå§‹ã¾ã£ãŸ[9]。ã“れらã®ç«æ˜Ÿè¡¨é¢ã®æ¨¡æ§˜ã¯äººå·¥çš„ãªç›´ç·šçŠ¶ã®æ¨¡æ§˜ã®ã‚ˆã†ã«è¦‹ãˆãŸãŸã‚ã«é‹æ²³ã§ã‚ã‚‹ã¨ä¸»å¼µã•ã‚Œã€ã¾ãŸã‚る領域ã®æ˜Žã‚‹ã•ãŒå­£ç¯€ã«ã‚ˆã£ã¦å¤‰åŒ–ã™ã‚‹ã®ã¯æ¤ç‰©ã®æˆé•·ã«ã‚ˆã‚‹ã‚‚ã®ã ã¨è€ƒãˆã‚‰ã‚ŒãŸã€‚ã“れらã®è€ƒãˆã‹ã‚‰ç«æ˜Ÿäººã«é–¢é€£ã—ãŸå¤šãã®è©±ãŒç”Ÿã¾ã‚ŒãŸã€‚ã—ã‹ã—今日ã§ã¯è‰²ã®å¤‰åŒ–ã¯å¡µã®åµã®ãŸã‚ã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。
+ç«æ˜Ÿã®è¡›æ˜Ÿ[編集]
+詳細ã¯ã€Œç«æ˜Ÿã®è¡›æ˜Ÿã€ã‚’å‚ç…§
+
+ç«æ˜Ÿã«ã¯ãƒ•ã‚©ãƒœã‚¹ã¨ãƒ€ã‚¤ãƒ¢ã‚¹ã®2ã¤ã®è¡›æ˜ŸãŒå­˜åœ¨ã™ã‚‹ã€‚ã¨ã‚‚ã«1877å¹´ã«ã‚¢ã‚µãƒ•ãƒ»ãƒ›ãƒ¼ãƒ«ã«ã‚ˆã£ã¦ç™ºè¦‹ã•ã‚Œã€ã‚®ãƒªã‚·ã‚¢ç¥žè©±ã§è»ç¥žã‚¢ãƒ¬ãƒ¼ã‚¹ã®æˆ¦ã„ã«åŒè¡Œã—ãŸæ¯å­ã®ãƒ•ã‚©ãƒœã‚¹ï¼ˆã€Œç‹¼ç‹½ã€ã®æ„)ã€ãƒ€ã‚¤ãƒ¢ã‚¹ï¼ˆã€Œæ怖ã€ã®æ„)ã‹ã‚‰å付ã‘られãŸã€‚アレースã¯ãƒ­ãƒ¼ãƒžç¥žè©±ã§ã¯æˆ¦äº‰ã®ç¥žãƒžãƒ«ã‚¹ã¨ã—ã¦çŸ¥ã‚‰ã‚Œã¦ã„る。
+ç«æ˜ŸæŽ¢æŸ»[編集]
+ヴァイキング1å·ã®ç€é™¸åœ°ç‚¹
+詳細ã¯ã€Œç«æ˜ŸæŽ¢æŸ»ã€ã€ã€Œç«æ˜ŸæŽ¢æŸ»æ©Ÿã€ã€ãŠã‚ˆã³ã€Œç«æ˜Ÿã«ã‚る人工物ã®ä¸€è¦§ã€ã‚’å‚ç…§
+
+ç«æ˜Ÿã®åœ°è¡¨ã‚„気候ã€åœ°å½¢ã‚’研究ã™ã‚‹ãŸã‚ã«ã€ã‚½é€£ã€ã‚¢ãƒ¡ãƒªã‚«ã€ãƒ¨ãƒ¼ãƒ­ãƒƒãƒ‘ã€æ—¥æœ¬ã«ã‚ˆã£ã¦ä»Šã¾ã§ã«è»Œé“探査機ã€ç€é™¸æ©Ÿã€ãƒ­ãƒ¼ãƒãƒ¼ãªã©ã®å¤šãã®æŽ¢æŸ»æ©ŸãŒç«æ˜Ÿã«é€ã‚Šè¾¼ã¾ã‚ŒãŸã€‚ç«æ˜Ÿã‚’目指ã—ãŸæŽ¢æŸ»æ©Ÿã®ã†ã¡ã€ç´„ 2/3 ãŒãƒŸãƒƒã‚·ãƒ§ãƒ³å®Œäº†å‰ã«ã€ã¾ãŸã¯ãƒŸãƒƒã‚·ãƒ§ãƒ³é–‹å§‹ç›´å¾Œã«ä½•ã‚‰ã‹ã®å¤±æ•—ã‚’èµ·ã“ã—ã¦ã„る。ã“ã®é«˜ã„失敗率ã®ä¸€éƒ¨ã¯æŠ€è¡“上ã®å•é¡Œã«ã‚ˆã‚‹ã‚‚ã®ã¨è€ƒãˆã‚‰ã‚Œã‚‹ãŒã€ç‰¹ã«è€ƒãˆã‚‰ã‚Œã‚‹åŽŸå› ãŒãªã„ã¾ã¾å¤±æ•—ã—ãŸã‚Šäº¤ä¿¡ãŒé€”絶ãˆãŸã‚Šã—ãŸã‚‚ã®ã‚‚多ãã€ç ”究者ã®ä¸­ã«ã¯å†—談åŠåˆ†ã«åœ°çƒ-ç«æ˜Ÿé–“ã®ã€ŒãƒãƒŸãƒ¥ãƒ¼ãƒ€ãƒˆãƒ©ã‚¤ã‚¢ãƒ³ã‚°ãƒ«ã€ã¨å‘¼ã‚“ã ã‚Šã€ç«æ˜ŸæŽ¢æŸ»æ©Ÿã‚’食ã¹ã¦æš®ã‚‰ã—ã¦ã„る宇宙悪霊ãŒã„ã‚‹ã¨è¨€ã£ãŸã‚Šã€ç«æ˜Ÿã®å‘ªã„ã¨è¨€ã†äººã‚‚ã„る。
+
+最もæˆåŠŸã—ãŸãƒŸãƒƒã‚·ãƒ§ãƒ³ã¨ã—ã¦ã¯ã€ã‚½é€£ã®ç«æ˜ŸæŽ¢æŸ»æ©Ÿè¨ˆç”»ã‚„アメリカã®ãƒžãƒªãƒŠãƒ¼è¨ˆç”»ã€ãƒã‚¤ã‚­ãƒ³ã‚°è¨ˆç”»ã€ãƒžãƒ¼ã‚ºãƒ»ã‚°ãƒ­ãƒ¼ãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ã€ãƒžãƒ¼ã‚ºãƒ»ãƒ‘スファインダーã€2001マーズ・オデッセイãªã©ãŒã‚る。グローãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ã¯å³¡è°·ã‚„土石æµã®å†™çœŸã‚’撮影ã—ã€å¸¯æ°´å±¤ã¨åŒæ§˜ã®æ¶²ä½“ã®æ°´ãŒæµã‚Œã‚‹æ°´æºãŒç«æ˜Ÿã®åœ°è¡¨ã¾ãŸã¯åœ°è¡¨è¿‘ãã«å­˜åœ¨ã™ã‚‹å¯èƒ½æ€§ã‚’示唆ã—ãŸã€‚2001マーズ・オデッセイã¯ã€ç«æ˜Ÿã®å—ç·¯60度以å—ã®å—極地方ã®åœ°ä¸‹ç´„3m以内ã®è¡¨åœŸã«ã¯å¤§é‡ã®æ°´ã®æ°·ãŒå †ç©ã—ã¦ã„ã‚‹ã“ã¨ã‚’明らã‹ã«ã—ãŸã€‚
+
+2003å¹´ã€æ¬§å·žå®‡å®™æ©Ÿé–¢ (ESA) ã¯ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス・オービタã¨ç€é™¸æ©Ÿãƒ“ーグル2ã‹ã‚‰ãªã‚‹ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機を打ã¡ä¸Šã’ãŸã€‚マーズ・エクスプレス・オービタã¯ç«æ˜Ÿã®å—極ã«æ°´ã¨äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ãŒå­˜åœ¨ã™ã‚‹ã“ã¨ã‚’確èªã—ãŸã€‚NASA ã¯ãれ以å‰ã«åŒ—極ã«ã¤ã„ã¦ã€åŒæ§˜ã®æ°·ãŒå­˜åœ¨ã™ã‚‹ã“ã¨ã‚’確èªã—ã¦ã„ãŸã€‚ビーグル2ã¨ã®äº¤ä¿¡ã«ã¯å¤±æ•—ã—ã€2004å¹´2月åˆæ—¬ã«ãƒ“ーグル2ãŒå¤±ã‚ã‚ŒãŸã“ã¨ãŒå®£è¨€ã•ã‚ŒãŸã€‚
+スピリットã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸã‚³ãƒ­ãƒ³ãƒ“ア・ヒルズã®ãƒ‘ノラマ画åƒã€‚アメリカã«ã‚るカホキア墳丘ã¨ã„ã†å…ˆä½æ°‘éºè·¡ã«ã¡ãªã‚“㧠Cahokia panorama ã¨å‘¼ã°ã‚Œã¦ã„ã‚‹
+
+åŒã˜2003å¹´ã« NASA ã¯ã‚¹ãƒ”リット (MER-A)ã€ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ (MER-B) ã¨å‘½åã•ã‚ŒãŸ2æ©Ÿã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—ロレーション・ローãƒãƒ¼ã‚’打ã¡ä¸Šã’ãŸã€‚2æ©Ÿã¨ã‚‚2004å¹´1月ã«ç„¡äº‹ã«ç€é™¸ã—ã€å…¨ã¦ã®æŽ¢æŸ»ç›®æ¨™ã‚’調査ã—ãŸã€‚当åˆè¨ˆç”»ã•ã‚ŒãŸãƒŸãƒƒã‚·ãƒ§ãƒ³ã¯90日間ã ã£ãŸãŒã€ãƒŸãƒƒã‚·ãƒ§ãƒ³ã¯æ•°å›žå»¶é•·ã•ã‚Œã€ã„ãã¤ã‹ã®æ©Ÿæ¢°çš„トラブルã¯èµ·ããŸã‚‚ã®ã®ã€2007å¹´ç¾åœ¨ã‚‚ãªãŠç§‘学的æˆæžœã‚’地çƒã«é€ã‚Šç¶šã‘ã¦ã„る。最大ã®ç§‘学的æˆæžœã¯ã€ä¸¡æ–¹ã®ç€é™¸åœ°ç‚¹ã§éŽåŽ»ã®ã‚る時期ã«æ¶²ä½“ã®æ°´ãŒå­˜åœ¨ã—ãŸè¨¼æ‹ ã‚’発見ã—ãŸã“ã¨ã§ã‚る。ã¾ãŸã€ç«æ˜Ÿã®åœ°ä¸Šã§æ’®å½±ã•ã‚ŒãŸæ—‹é¢¨ (dust devil) ãŒç«æ˜Ÿã®åœ°è¡¨ã‚’å‹•ã„ã¦ã„ã様å­ãŒã‚¹ãƒ”リットã«ã‚ˆã£ã¦æ¤œå‡ºã•ã‚ŒãŸã€‚ã“ã®æ—‹é¢¨ã¯ãƒžãƒ¼ã‚ºãƒ»ãƒ‘スファインダーã§åˆã‚ã¦æ’®å½±ã•ã‚Œã¦ã„ãŸã€‚
+スピリットã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸç«æ˜Ÿã®æ—‹é¢¨
+
+2012å¹´ã«ãƒžãƒ¼ã‚ºãƒ»ã‚µã‚¤ã‚¨ãƒ³ã‚¹ãƒ»ãƒ©ãƒœãƒ©ãƒˆãƒªãƒ¼ãŒç«æ˜Ÿã«åˆ°ç€ã—ã€ã‚­ãƒ¥ãƒªã‚ªã‚·ãƒ†ã‚£ãƒ¼ç€é™¸ã®éŽç¨‹ã‚’撮影ã—ãŸ720p10fpsã®é«˜ç²¾ç´°ãªå‹•ç”»ãŒåœ°çƒã«é€ã‚‰ã‚ŒãŸã€‚ キュリオシティーã«ã¯éŽåŽ»ç«æ˜Ÿã«æŠ•å…¥ã•ã‚ŒãŸæŽ¢æŸ»æ©Ÿã®ä¸­ã§ã¯æœ€é«˜ã®è§£åƒåº¦ (1600×1200) ã®ã‚«ãƒ¡ãƒ©ãŒæ­è¼‰ã•ã‚Œã¦ãŠã‚Šã€æ¬¡ã€…ã«é«˜ç²¾ç´°ãªãƒ‘ノラマ画åƒãŒé€ã‚‰ã‚Œã¦ã„る。
+有人ç«æ˜ŸæŽ¢æŸ»[編集]
+詳細ã¯ã€Œæœ‰äººç«æ˜ŸæŽ¢æŸ»ã€ãŠã‚ˆã³ã€Œç«æ˜Ÿã®æ¤æ°‘ã€ã‚’å‚ç…§
+有人ç«æ˜ŸæŽ¢æŸ»ã®æƒ³åƒå›³ã€‚
+
+ヴェルナー・フォン・ブラウンをã¯ã˜ã‚ã€å¤šãã®äººã€…ãŒæœ‰äººæœˆæŽ¢æŸ»ã®æ¬¡ã®ã‚¹ãƒ†ãƒƒãƒ—ã¯ã€æœ‰äººç«æ˜ŸæŽ¢æŸ»ã§ã‚ã‚‹ã¨è€ƒãˆã¦ããŸã€‚有人探査ã®è³›åŒè€…ã¯ã€äººé–“ã¯ç„¡äººæŽ¢æŸ»æ©Ÿã‚ˆã‚Šã‚‚幾分優れã¦ãŠã‚Šã€æœ‰äººæŽ¢æŸ»ã‚’進ã‚ã‚‹ã¹ãã ã¨ä¸»å¼µã—ã¦ã„る。
+
+アメリカåˆè¡†å›½ã®ãƒ–ッシュ大統領(父)ã¯1989å¹´ã«æœˆãŠã‚ˆã³ç«æ˜Ÿã®æœ‰äººæŽ¢æŸ»æ§‹æƒ³ã‚’明らã‹ã«ã—ãŸãŒã€å¤šé¡ã®äºˆç®—ã‚’å¿…è¦ã¨ã™ã‚‹ãŸã‚ã«æ–­å¿µã•ã‚ŒãŸã€‚ã¾ãŸã€ãƒ–ッシュ大統領(æ¯å­ï¼‰ã‚‚2004å¹´1月14æ—¥ã«ã€Œå®‡å®™æŽ¢æŸ»ã®å°†æ¥ã€ã¨é¡Œã—ãŸæ–°ãŸãªè¨ˆç”»ã‚’発表ã—ãŸã€‚ã“ã‚Œã«ã‚ˆã‚‹ã¨ã€ã‚¢ãƒ¡ãƒªã‚«ã¯2015å¹´ã¾ã§ã«ã‚‚ã†ä¸€åº¦æœˆã«æœ‰äººæŽ¢æŸ»æ©Ÿã‚’é€ã‚Šã€ãã®å¾Œæœ‰äººã§ã®ç«æ˜ŸæŽ¢æŸ»ã®å¯èƒ½æ€§ã‚’探るã“ã¨ã¨ãªã£ã¦ã„ãŸï¼ˆã‚³ãƒ³ã‚¹ãƒ†ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³è¨ˆç”»ï¼‰ã€‚ã¾ãŸã€ãƒ­ã‚·ã‚¢ã‚‚å°†æ¥çš„ã«æœ‰äººç«æ˜ŸæŽ¢æŸ»ã‚’è¡Œã†ã“ã¨ã‚’予定ã—ã¦ãŠã‚Šã€æŠ€è¡“的・経済的ã«åˆ¤æ–­ã—ã¦2025å¹´ã¾ã§ã«ã¯å®Ÿç¾å¯èƒ½ã§ã‚ã‚‹ã¨ã—ã¦ã„る。更ã«ESAã‚‚ã€2030å¹´ã¾ã§ã«äººé–“ã‚’ç«æ˜Ÿã«é€ã‚‹ã€Œã‚ªãƒ¼ãƒ­ãƒ©ãƒ»ãƒ—ログラムã€ã¨å‘¼ã°ã‚Œã‚‹é•·æœŸè¨ˆç”»ã‚’æŒã£ã¦ã„る。
+
+特ã«ãƒãƒƒã‚¯ã¨ãªã‚‹ã®ã¯ã€ç«æ˜Ÿã¸ã®å¾€å¾©ã¨æ»žåœ¨æœŸé–“ã®åˆè¨ˆã§1å¹´å¼·ã‹ã‚‰3å¹´å¼±ã¨ã„ã†ã€æœˆæŽ¢æŸ»ã¨ã¯æ¯”較ã«ãªã‚‰ãªã„長期間ã®ãƒŸãƒƒã‚·ãƒ§ãƒ³ã§ã‚ã‚‹ã“ã¨ã¨ã€é‹ã°ãªã‘ã‚Œã°ãªã‚‰ãªã„物資ã®é‡ã§ã‚る。ã“ã®ãŸã‚ã€ç«æ˜Ÿã®å¤§æ°—ã‹ã‚‰å¸°é‚„用燃料を製造ã™ã‚‹ç„¡äººå·¥å ´ã‚’先行ã—ã¦é€ã‚Šè¾¼ã¿ã€æœ‰äººå®‡å®™èˆ¹ã¯å¾€è·¯åˆ†ã®ã¿ã®ç‡ƒæ–™ã§ç«æ˜Ÿã«åˆ°é”ã—ã€æŽ¢æŸ»å¾Œã«ç„¡äººå·¥å ´ã§è£½é€ ã•ã‚Œã¦ã„ãŸç‡ƒæ–™ã§å¸°é‚„ã™ã‚‹ã¨ã„ã†ãƒ—ラン「マーズ・ダイレクトã€ãªã©ã‚‚æ案ã•ã‚Œã¦ã„る。
+
+2010å¹´ã€ã‚ªãƒãƒžå¤§çµ±é ˜ã¯ã‚³ãƒ³ã‚¹ãƒ†ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³è¨ˆç”»ã®ä¸­æ­¢ã‚’表明ã—ãŸãŒã€åŒæ™‚ã«äºˆç®—ã‚’æ–°åž‹ã®ãƒ­ã‚±ãƒƒãƒˆã‚¨ãƒ³ã‚¸ãƒ³é–‹ç™ºãªã©ã®å°†æ¥æ€§ã®é«˜ã„新技術開発ã«æŒ¯ã‚Šå‘ã‘ã‚‹ã¨ã—ã¦ãŠã‚Šã€ã‚ˆã‚ŠçŸ­æœŸé–“ã§ç«æ˜Ÿã«åˆ°é”ã§ãる航行手段ãŒå®Ÿç”¨åŒ–ã•ã‚Œã‚‹äº‹ãŒæœŸå¾…ã•ã‚Œã‚‹ã€‚ã¾ãŸã€åŒè¨ˆç”»ã®ä»£ã‚ã‚Šã«ã‚ªãƒãƒžå¤§çµ±é ˜ã¯ã€2030年代åŠã°ã‚’目標ã«ã—ãŸæ–°ãŸãªæœ‰äººç«æ˜ŸæŽ¢æŸ»è¨ˆç”»ã‚‚発表ã—ã¦ã„る。
+ç«æ˜ŸæŽ¢æŸ»æ‰¹åˆ¤[編集]
+
+ç«æ˜ŸæŽ¢æŸ»ã¯è¿‘年根強ã実施ã•ã‚Œã¦ã„ã‚‹ãŒã€å‰è¿°ã®ã‚ˆã†ã«æŽ¢æŸ»è¨ˆç”»ã®ç´„2/3ãŒå¤±æ•—ã«çµ‚ã‚る上ã«ã€èŽ«å¤§ãªäºˆç®—ãŒã‹ã‹ã‚‹ã¨ã—ã¦æ‰¹åˆ¤ã™ã‚‹å£°ã‚‚大ãã„。「ç«æ˜Ÿã«æ°´ãŒã‹ã¤ã¦ã‚ã£ãŸã€‚ãã‚ŒãŒã©ã†ã—ãŸã€‚我々ã®ç”Ÿæ´»ã«é–¢ä¿‚ã‚ã‚‹ã®ã‹? 予算を地çƒã®ãŸã‚ã«ä½¿ã†ã¹ãã ã€ã¨ã„ã†ã‚ˆã†ãªã‚‚ã®ã§ã‚る。実際ã«ã¯ï¼ˆã‚¢ãƒ¡ãƒªã‚«åˆè¡†å›½ã‚’例ã«å–ã‚Œã°ï¼‰å›½é˜²è²»ã®1/20以下ã®NASAã®äºˆç®—ã®ã€æ›´ã«ã”ã一部ãŒç«æ˜ŸæŽ¢æŸ»ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ã‚‹ã«éŽãŽãªã„ã®ã ãŒã€ã“ã†ã—ãŸå£°ã‚’無視ã™ã‚‹ã“ã¨ã‚‚出æ¥ãšã€æŽ¢æŸ»è¨ˆç”»ã®ä½Žã‚³ã‚¹ãƒˆåŒ–ãŒé€²ã‚られã¦ã„る。
+ç«æ˜Ÿã®è¦³æ¸¬[編集]
+天çƒä¸Šã®ç«æ˜Ÿã®å‹•ã。
+
+16世紀デンマークã®å¤©æ–‡å­¦è€…ティコ・ブラーエã¯ã€åœ°çƒã‚’中心ã«å¤ªé™½ï¼ˆç«æ˜Ÿãªã©æƒ‘星ã¯å¤ªé™½ã®å‘¨ã‚Šã‚’廻る)ãŒå»»ã‚‹å¤‰å‰‡çš„ãªå¤©å‹•èª¬ã‚’ã¨ã£ã¦ã„ãŸãŒã€è‚‰çœ¼ã«ã‚ˆã‚‹ã‚‚ã®ã§ã¯æœ€ã‚‚精密ã«ç«æ˜Ÿã®è»Œé“を観測ã—ãŸã€‚ティコ(慣習ã¨ã—ã¦å§“ã§ãªãåを通称ã¨ã™ã‚‹ï¼‰ã®åŠ©æ‰‹ã§ã‚ã£ãŸãƒ¨ãƒãƒã‚¹ãƒ»ã‚±ãƒ—ラーã¯å¸«ã®æ­»å¾Œã€è¦³æ¸¬ãƒ‡ãƒ¼ã‚¿ã‚’解æžã™ã‚‹ã“ã¨ã§æƒ‘星ã®è»Œé“ãŒå††ã§ã¯ãªã楕円ã§ã‚ã‚‹ã“ã¨ã€ã•ã‚‰ã«ç«æ˜Ÿã®è»Œé“ã‹ã‚‰ä»–ã®æƒ‘星ã®è»Œé“も楕円ã§ã‚りケプラーã®æ³•å‰‡ã«å¾“ã†ã¨ã„ã†åœ°å‹•èª¬ã‚’主張ã—ãŸã€‚公転速度ãŒé€Ÿã観測ã—ã‚„ã™ã„ç«æ˜Ÿã®è»Œé“離心率ãŒå†¥çŽ‹æ˜Ÿã‚„水星ã«æ¬¡ã„ã§å¤§ãã„0.0934ã§ã‚ã£ãŸã“ã¨ã‚‚幸é‹ã§ã‚ã£ãŸã€‚
+
+1877å¹´ã®ç«æ˜Ÿå¤§æŽ¥è¿‘ã¨ã‚¹ã‚­ã‚¢ãƒ‘レッリã®ç™ºè¡¨ã«å§‹ã¾ã£ãŸç«æ˜Ÿé‹æ²³èª¬ã«é‡å¤§ãªç–‘å•ã‚’投ã’ã‹ã‘ãŸã®ãŒã€ã‚¨ãƒƒã‚¸ãƒ¯ãƒ¼ã‚¹ãƒ»ã‚«ã‚¤ãƒ‘ーベルトã®æ唱者ã®ä¸€äººã§ã‚るカイパーã§ã‚る。1947å¹´ã€ç«æ˜Ÿã‚’赤外線帯ã§è¦³æ¸¬ã—ã€å¤§æ°—ã®æˆåˆ†ãŒäºŒé…¸åŒ–ç‚­ç´ ã§ã‚ã‚‹ã¨ä¸»å¼µã—ãŸã€‚地çƒå¤§æ°—ã®é‡è¦ãªæˆåˆ†ã§ã‚る窒素ã€é…¸ç´ ã€æ°´è’¸æ°—ã®ç—•è·¡ã¯è¦‹å½“ãŸã‚‰ãšã€æ–‡æ˜Žã‚’æŒã¤ç«æ˜Ÿäººã®å­˜åœ¨ã¯ã»ã¼å¦å®šã•ã‚ŒãŸã€‚
+ãƒãƒƒãƒ–ル宇宙望é é¡ãŒå†™ã—ãŸç«æ˜Ÿã€‚
+
+地çƒã¯780日(2å¹´ã¨7週間ã¨1日)ã”ã¨ã«ç«æ˜Ÿã‚’追ã„越ã—ã€ãã®ã¨ãã®è·é›¢ã¯ç´„8000万km(約4光分)ã¾ã§æŽ¥è¿‘ã™ã‚‹ã€‚ã—ã‹ã—ã€ç«æ˜Ÿè»Œé“ãŒæ¥•å††ã§ã‚ã‚‹ãŸã‚ã«æœ€æŽ¥è¿‘時ã®è·é›¢ã¯å¤‰åŒ–ã™ã‚‹ã€‚ç«æ˜Ÿã®è¿‘日点付近ã§æŽ¥è¿‘ã™ã‚Œã°æŽ¥è¿‘è·é›¢ã¯5600万km程度ã¨ãªã‚‹ãŒã€é æ—¥ç‚¹ä»˜è¿‘ã§æŽ¥è¿‘ã™ã‚Œã°1å„„km程度ã¨2å€è¿‘ãè·é›¢ãŒç•°ãªã‚‹ã€‚肉眼ã§è¦³æ¸¬ã—ã¦ã„ã‚‹ã¨ã€ç«æ˜Ÿã¯é€šå¸¸ã€ä»–ã®æ˜Ÿã¨ã¯ã£ãã‚Šç•°ãªã‚‹é»„色ã‚ã‚‹ã„ã¯ã‚ªãƒ¬ãƒ³ã‚¸è‰²ã‚„赤ã£ã½ã„色ã«è¦‹ãˆã€è»Œé“を公転ã™ã‚‹ã«ã¤ã‚Œã¦åœ°çƒã‹ã‚‰è¦‹ã‚‹ä»–ã®ã©ã®æƒ‘星よりも大ãã明るã•ãŒå¤‰åŒ–ã™ã‚‹ã€‚ã“ã‚Œã¯ã€ç«æ˜ŸãŒåœ°çƒã‹ã‚‰æœ€ã‚‚離れる時ã«ã¯æœ€ã‚‚è¿‘ã¥ã„ãŸæ™‚ã®7å€ä»¥ä¸Šã‚‚è·é›¢ãŒé›¢ã‚Œã‚‹ãŸã‚ã§ã‚る。ãªãŠã€å¤ªé™½ã¨åŒã˜æ–¹å‘ã«ã‚ã‚‹åˆå‰å¾Œã®æ•°ãƒ¶æœˆé–“ã¯å¤ªé™½ã®å…‰ã§è¦‹ãˆãªããªã‚‹ã“ã¨ã‚‚ã‚る。最も観測ã«é©ã—ãŸæ™‚期ã¯32å¹´ã”ã¨ã«2回ã€15å¹´ã¨17å¹´ã‚’ãŠã„ã¦äº¤äº’ã«ã‚„ã£ã¦ãã¦ã€Œå¤§æŽ¥è¿‘ã€ã¨å‘¼ã°ã‚Œã‚‹ã€‚ã“ã®æ™‚期ã¯å¸¸ã«7月終ã‚ã‚Šã‹ã‚‰9月終ã‚ã‚Šã®é–“ã«ãªã‚‹ã€‚ã“ã®æ™‚期ã«ç«æ˜Ÿã‚’望é é¡ã§è¦‹ã‚‹ã¨è¡¨é¢ã®æ§˜ã€…ãªæ§˜å­ã‚’詳細ã«è¦‹ã‚‹ã“ã¨ãŒã§ãる。低å€çŽ‡ã§ã‚‚見ãˆã‚‹ç‰¹ã«ç›®ç«‹ã¤ç‰¹å¾´ã¯æ¥µå† ã§ã‚る。
+
+2003å¹´8月27æ—¥9時51分13秒(世界時)ã«ç«æ˜Ÿã¯éŽåŽ»60,000å¹´ã§æœ€ã‚‚è¿‘ãã€55,758,006 kmã¾ã§åœ°çƒã«æŽ¥è¿‘ã—ãŸï¼ˆæƒ‘星光行差補正ãªã—ã§ã®å€¤ï¼‰ã€‚ã“ã®å¤§æŽ¥è¿‘ã¯ç«æ˜Ÿã®è¿‘日点通éŽã®3日後ãŒç«æ˜Ÿã®è¡ã®ç¿Œæ—¥ã¨é‡ãªã£ãŸãŸã‚ã«ç”Ÿã˜ãŸã‚‚ã®ã§ã€åœ°çƒã‹ã‚‰ç«æ˜Ÿã‚’特ã«è¦‹ã‚„ã™ããªã£ãŸã€‚ã“れ以å‰ã«æœ€ã‚‚è¿‘ã接近ã—ãŸã®ã¯ç´€å…ƒå‰57617å¹´9月12æ—¥ã¨è¨ˆç®—ã•ã‚Œã¦ã„ã‚‹[10]。太陽系ã®é‡åŠ›è¨ˆç®—ã®è©³ç´°ãªè§£æžã‹ã‚‰ã€2287å¹´ã«ã¯2003年よりも近ã„接近ãŒèµ·ã“ã‚‹ã¨è¨ˆç®—ã•ã‚Œã¦ã„る。ã—ã‹ã—正確ã«è¦‹ã¦ã„ãã¨ã€ã“ã®è¨˜éŒ²çš„ãªå¤§æŽ¥è¿‘ã¯284å¹´ã”ã¨ã«4回起ãã¦ã„る別ã®å¤§æŽ¥è¿‘よりもã”ãã‚ãšã‹ã«è¿‘ã„ã ã‘ã§ã‚ã‚‹ã“ã¨ãŒåˆ†ã‹ã‚‹ã€‚例ãˆã°ã€2003å¹´8月27æ—¥ã®æœ€æŽ¥è¿‘è·é›¢ãŒ 0.37271AU ã§ã‚ã‚‹ã®ã«å¯¾ã—ã¦1924å¹´8月22æ—¥ã®æœ€æŽ¥è¿‘è·é›¢ã¯ 0.37284AU ã§ã‚ã‚Šã€2208å¹´8月24æ—¥ã®æŽ¥è¿‘㯠0.37278AU ã§ã‚る。
+
+2084å¹´11月10æ—¥ã«ã¯ç«æ˜Ÿã‹ã‚‰è¦‹ã¦åœ°çƒã®å¤ªé™½é¢é€šéŽãŒèµ·ã“る。ã“ã®æ™‚ã«ã¯å¤ªé™½ã¨åœ°çƒã€ç«æ˜ŸãŒä¸€ç›´ç·šä¸Šã«ä¸¦ã¶ã€‚åŒæ§˜ã«ç«æ˜Ÿã‹ã‚‰è¦‹ãŸæ°´æ˜Ÿã‚„金星ã®å¤ªé™½é¢é€šéŽã‚‚èµ·ã“る。ç«æ˜Ÿã®è¡›æ˜Ÿã§ã‚るダイモスã¯ç«æ˜Ÿã‹ã‚‰è¦‹ãŸè§’直径ãŒå¤ªé™½ã®ãれよりå分ã«å°ã•ã„ãŸã‚ã€ãƒ€ã‚¤ãƒ¢ã‚¹ã«ã‚ˆã‚‹éƒ¨åˆ†æ—¥é£Ÿã‚‚太陽é¢é€šéŽã¨è¦‹ãªã›ã‚‹ã€‚
+
+1590å¹´10月13æ—¥ã«ã¯éŽåŽ»å”¯ä¸€ã®é‡‘星ã«ã‚ˆã‚‹ç«æ˜Ÿé£ŸãŒèµ·ã“ã‚Š[11]ã€ãƒ‰ã‚¤ãƒ„ã®ãƒã‚¤ãƒ‡ãƒ«ãƒ™ãƒ«ã‚¯ã§ãƒ¡ã‚¹ãƒˆãƒªãƒ³ã«ã‚ˆã£ã¦è¦³æ¸¬ã•ã‚ŒãŸã€‚
+ç«æ˜Ÿèµ·æºã®éš•çŸ³[編集]
+「ALH84001ã€éš•çŸ³
+
+地çƒä¸Šã§ç™ºè¦‹ã•ã‚ŒãŸã‚‚ã®ã®ã†ã¡ã€ç¢ºå®Ÿã«éš•çŸ³ã§ã‚ã‚Šã€ã‹ã¤ç«æ˜Ÿã«èµ·æºã‚’æŒã¤ã¨æ€ã‚れる岩石ãŒã„ãã¤ã‹çŸ¥ã‚‰ã‚Œã¦ã„る。ã“れらã®éš•çŸ³ã®ã†ã¡2ã¤ã‹ã‚‰ã¯å¤ä»£ã®ç´°èŒã®æ´»å‹•ã®ç—•è·¡ã‹ã‚‚ã—ã‚Œãªã„特徴ãŒè¦‹ã¤ã‹ã£ã¦ã„る。1996å¹´8月6æ—¥ã€NASAã¯ç«æ˜Ÿèµ·æºã¨è€ƒãˆã‚‰ã‚Œã¦ã„る「ALH84001ã€éš•çŸ³ã®åˆ†æžã‹ã‚‰ã€å˜ç´°èƒžç”Ÿå‘½ä½“ã®åŒ–石ã®å¯èƒ½æ€§ãŒã‚る特徴ãŒç™ºè¦‹ã•ã‚ŒãŸã¨ç™ºè¡¨ã—ãŸã€‚ã—ã‹ã—ã“ã®è§£é‡ˆã«ã¯ã„ã¾ã ã«è­°è«–ã®ä½™åœ°ãŒã‚る。
+
+『Solar System Researchã€2004å¹´3æœˆå· (38, p.97) ã«æŽ²è¼‰ã•ã‚ŒãŸè«–æ–‡ã§ã¯ã€ã‚¤ã‚¨ãƒ¡ãƒ³ã§ç™ºè¦‹ã•ã‚ŒãŸã‚«ã‚¤ãƒ‰ã‚¥ãƒ³éš•çŸ³ãŒç«æ˜Ÿã®è¡›æ˜Ÿãƒ•ã‚©ãƒœã‚¹ã«èµ·æºã‚’æŒã¤å¯èƒ½æ€§ãŒã‚ã‚‹ã¨ç¤ºå”†ã—ã¦ã„る。
+
+2004å¹´4月14æ—¥ã«NASAã¯ã€ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦èª¿æŸ»ã•ã‚ŒãŸ "Bounce" ã¨ã„ã†åå‰ã®å²©çŸ³ãŒã€1979å¹´ã«å—極ã§ç™ºè¦‹ã•ã‚ŒãŸéš•çŸ³ã€ŒEETA79001-Bã€ã¨ä¼¼ãŸçµ„æˆã‚’æŒã£ã¦ã„ã‚‹ã“ã¨ã‚’明らã‹ã«ã—ãŸ[12]。ã“ã®å²©çŸ³ã¯ã“ã®éš•çŸ³ã¨åŒã˜ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‹ã‚‰é£›æ•£ã—ãŸã‹ã€ã‚ã‚‹ã„ã¯ç«æ˜Ÿè¡¨é¢ã®åŒã˜åœ°åŸŸã«ã‚る別々ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‹ã‚‰é£›ã°ã•ã‚ŒãŸå¯èƒ½æ€§ãŒã‚る。
+æ°·ã®æ¹–[編集]
+
+2005å¹´7月29æ—¥ã€BBCã¯ç«æ˜Ÿã®åŒ—極地方ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã§æ°·ã®æ¹–ãŒç™ºè¦‹ã•ã‚ŒãŸã¨å ±ã˜ãŸ[13]。ESAã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機ã«æ­è¼‰ã•ã‚ŒãŸé«˜è§£åƒåº¦ã‚¹ãƒ†ãƒ¬ã‚ªã‚«ãƒ¡ãƒ©ã§æ’®å½±ã•ã‚ŒãŸã“ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®ç”»åƒã«ã¯ã€åŒ—ç·¯70.5度ã€æ±çµŒ103度ã«ä½ç½®ã—ç«æ˜ŸåŒ—極域ã®å¤§åŠã‚’å ã‚るボレアリス平野ã«ã‚ã‚‹ç„¡åã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®åº•ã«å¹³ã‚‰ãªæ°·ãŒåºƒãŒã£ã¦ã„る様å­ãŒã¯ã£ãã‚Šã¨å†™ã£ã¦ã„る。ã“ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ç›´å¾„35kmã§æ·±ã•ç´„2kmã§ã‚る。
+
+BBCã®å ±é“ã§ã¯ã‚„や誇張ã•ã‚Œã¦ã„ã‚‹ãŒã€å…ƒã€…ã®ESAã®ç™ºè¡¨ã§ã¯ã“ã‚ŒãŒæ¹–ã§ã‚ã‚‹ã¨ã¯ä¸»å¼µã—ã¦ã„ãªã„[14]。ç«æ˜Ÿã®æ•°å¤šãã®ä»–ã®å ´æ‰€ã«è¦‹ã‚‰ã‚Œã‚‹ã‚‚ã®ã¨åŒæ§˜ã«ã€ã“ã®å††æ¿çŠ¶ã®æ°·ã¯æš—ã低温ã®ç ‚丘ã®é ‚上(高度約200m)ã«è–„ã„層状ã®éœœãŒå‡çµã—ã¦ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®åº•ã«åºƒãŒã£ãŸã‚‚ã®ã§ã‚る。報ã˜ã‚‰ã‚ŒãŸã“ã®æ°·ãŒç‰¹ã«çã—ã„ã®ã¯ã€éœœã®ã„ãらã‹ãŒä¸€å¹´ä¸­æ®‹ã‚Šã†ã‚‹ã»ã©ã“ã®å ´æ‰€ãŒé«˜ç·¯åº¦ã«ã‚ã‚‹ã¨ã„ã†ç‚¹ã ã‘ã§ã‚る。赤é“付近ã¯æ—¥ä¸­20℃を越ã™ã“ã¨ã‚‚ã‚ã‚Šã€é«˜ç·¯åº¦ã§ãªã‘ã‚Œã°æ°·ã¯å­˜åœ¨ã§ããªã„[15]。ã¾ãŸã€æ¶²ä½“ã®æ°´ã‚‚ã€ç«æ˜Ÿã®å¤§æ°—ã¯å¸Œè–„ã€ã™ãªã‚ã¡å¤§æ°—中ã®æ°´è’¸æ°—圧ãŒå°ã•ã„ãŸã‚ã€ç«æ˜Ÿè¡¨é¢ã®ã»ã¨ã‚“ã©ã®åœ°åŸŸã§ã¯ã™ã蒸発ã—ã¦ã—ã¾ã†ã®ã§å­˜åœ¨ã§ããªã„。液体ã®æ°´ãŒå­˜åœ¨ã§ãã‚‹ã®ã¯ãƒ˜ãƒ©ã‚¹ç›†åœ°ãªã©é™ã‚‰ã‚ŒãŸå ´æ‰€ã®ã¿ã§ã‚る。
+ç«æ˜Ÿã®ç”Ÿå‘½[編集]
+詳細ã¯ã€Œç«æ˜Ÿã®ç”Ÿå‘½ã€ã‚’å‚ç…§
+å¡©æ°´ã®ã‚ˆã†ãªæ¶²ä½“ãŒæµã‚Œå‡ºãŸã¨ã¿ã‚‰ã‚Œã‚‹è·¡ã€‚
+
+ç«æ˜Ÿã¯ã‹ã¤ã¦ã¯ç¾åœ¨ã‚ˆã‚Šã‚‚確実ã«ç”Ÿå‘½ã«é©ã—ãŸç’°å¢ƒã ã£ãŸã¨ã„ã†è¨¼æ‹ ãŒå­˜åœ¨ã™ã‚‹ãŒã€ç«æ˜Ÿã«ã‹ã¤ã¦å®Ÿéš›ã«ç”Ÿå‘½ä½“ãŒç”Ÿå­˜ã—ã¦ã„ãŸã‹ã©ã†ã‹ã¨ã„ã†ç–‘å•ã¯æœªè§£æ±ºã§ã‚る。ç«æ˜Ÿèµ·æºã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る岩石(特ã«ã€ŒALH84001ã€éš•çŸ³ï¼‰ã«éŽåŽ»ã®ç”Ÿå‘½æ´»å‹•ã®è¨¼æ‹ ãŒå«ã¾ã‚Œã¦ã„ã‚‹ã¨è€ƒãˆã¦ã„る研究者もã„ã‚‹ãŒã€ã“ã®ä¸»å¼µã«å¯¾ã—ã¦ã¯ç¾çŠ¶ã§ã¯åˆæ„ã¯å¾—られã¦ã„ãªã„。ã“ã®éš•çŸ³ã¯æ•°åå„„å¹´å‰ã«ç”Ÿã¾ã‚Œã¦ä»¥æ¥ã€æ¶²ä½“ã®æ°´ãŒå­˜åœ¨ã§ãるよã†ãªæ¸©åº¦ã«ä¸€å®šæœŸé–“ã•ã‚‰ã•ã‚ŒãŸã“ã¨ã¯ãªã„ã“ã¨ã‚’示ã™ç ”究もã‚る。
+
+ãƒã‚¤ã‚­ãƒ³ã‚°æŽ¢æŸ»æ©Ÿã«ã¯ãã‚Œãžã‚Œã®ç€é™¸åœ°ç‚¹ã§ç«æ˜Ÿã®åœŸå£Œã«å«ã¾ã‚Œã‚‹å¾®ç”Ÿç‰©ã‚’検出ã™ã‚‹ãŸã‚ã®å®Ÿé¨“装置ãŒæ­è¼‰ã•ã‚Œã€é™½æ€§ã®çµæžœã‚’ã„ãã¤ã‹å¾—ãŸãŒã€å¾Œã«å¤šãã®ç§‘学者ã«ã‚ˆã£ã¦å¦å®šã•ã‚ŒãŸã€‚ã“ã®ä»¶ã«ã¤ã„ã¦ã¯ç¾åœ¨ã‚‚è­°è«–ãŒç¶šã„ã¦ã„る。ã¾ãŸã€ç«æ˜Ÿã®å¤§æ°—ã«ãƒ¡ã‚¿ãƒ³ãŒã”ãå¾®é‡å­˜åœ¨ã—ã¦ã„る原因ã«ã¤ã„ã¦ã€ç¾åœ¨ç”Ÿå‘½æ´»å‹•ãŒé€²è¡Œã—ã¦ã„ã‚‹ã¨ã„ã†èª¬ãŒä¸€ã¤ã®è§£é‡ˆã¨ã—ã¦æ案ã•ã‚Œã¦ã„ã‚‹ãŒã€ç”Ÿå‘½æ´»å‹•ã«ç”±æ¥ã—ãªã„別ã®èª¬ã®æ–¹ãŒã‚ˆã‚Šã‚‚ã£ã¨ã‚‚らã—ã„ã¨ä¸€èˆ¬ã«è€ƒãˆã‚‰ã‚Œã¦ã„る。
+
+ç¾åœ¨ã®ç«æ˜Ÿã¯ã€ãƒãƒ“タブルゾーン内(生命存在ã®å¯èƒ½ãªå¤©ä½“ãŒã€å­˜åœ¨ã§ãる領域)ã«ã‚ã‚‹ã¨ã„ã†[16]。
+
+å°†æ¥æ¤æ°‘地化ãŒè¡Œãªã‚れるã¨ã™ã‚Œã°ã€ç«æ˜Ÿã¯ï¼ˆå¤ªé™½ç³»ã«å±žã™ã‚‹åœ°çƒä»¥å¤–ã®æƒ‘星ã¨æ¯”較ã—ã¦ï¼‰ã‹ãªã‚Šç”Ÿå‘½ã®ç”Ÿå­˜ã«é©ã—ãŸæ¡ä»¶ã«ã‚ã‚‹ãŸã‚ã€æœ‰åŠ›ãªé¸æŠžè‚¢ã¨ãªã‚‹ã¨æ€ã‚れる。
+人類ã¨ç«æ˜Ÿ[編集]
+æ­´å²ã¨ç¥žè©±[編集]
+
+ç«æ˜Ÿã®å称 (Marsï¼ãƒžãƒ¼ã‚º) ã¯ã€ãƒ­ãƒ¼ãƒžç¥žè©±ã®ç¥žãƒžãƒ«ã‚¹ï¼ˆã‚®ãƒªã‚·ã‚¢ç¥žè©±ã®è»ç¥žã‚¢ãƒ¬ãƒ¼ã‚¹ï¼‰ã‹ã‚‰å付ã‘られãŸã€‚メソãƒã‚¿ãƒŸã‚¢ã®æ°‘ã¯èµ¤ã„惑星ã«æˆ¦ç«ã¨è¡€ã‚’連想ã—ã¦å½¼ã‚‰ã®æˆ¦ç¥žãƒãƒ«ã‚¬ãƒ«ã®åを冠ã—ã¦ä»¥æ¥ã€ç«æ˜Ÿã«ã¯å„々ã®åœ°ã§ãã®åœ°ã®æˆ¦ç¥žã®åãŒã¤ã‘られã¦ã„る(他ã®æƒ‘星åã«ã¤ã„ã¦ã‚‚ã»ã¼åŒæ§˜ã®ç¶™æ‰¿ãŒèªã‚られる)。
+æ±æ´‹[編集]
+
+ç«æ˜Ÿã¯äº”行説ã«åŸºã¥ãオカルト的ãª[è¦å‡ºå…¸]呼ã³åã§ã‚ã£ã¦ï¼ˆäº”行説ã¯æ±æ´‹åŒ»å­¦ã®åŸºç¤Žç†è«–ã§ã‚‚ã‚る)ã€å­¦å•ä¸Šï¼ˆå¤©æ–‡å²æ–™ï¼‰ã§ã¯ç†’惑(ケイコクã€ã‚¨ã‚¤ã‚³ã‚¯ï¼‰ã¨ã„ã£ãŸã€‚「熒ã€ã¯ã—ã°ã—ã°åŒéŸ³ã®ã€Œèž¢ã€ã¨èª¤ã‚‰ã‚Œã‚‹ã€‚ã¾ãŸã€ã“ã®å ´åˆã®ã€Œæƒ‘ã€ã¯ã€Œãƒ¯ã‚¯ã€ã§ã¯ãªã「コクã€ã¨èª­ã‚€ã€‚営惑ã¨ã‚‚書ã。江戸時代ã«ã¯ã€Œãªã¤ã²ã¼ã—ã€ã¨è¨“ã˜ã‚‰ã‚ŒãŸã€‚ãã®ãŸã‚å¤æ—¥æ˜Ÿã¨ã„ã†å’Œåã‚‚ã‚る。
+
+ç«æ˜ŸãŒã•ãり座ã®ã‚¢ãƒ³ã‚¿ãƒ¬ã‚¹ï¼ˆé»„é“ã®è¿‘ãã«ä½ç½®ã—ã¦ã„ã‚‹ãŸã‚)ã«æŽ¥è¿‘ã™ã‚‹ã“ã¨ã‚’熒惑守心(熒惑心を守る)ã¨ã„ã„ã€ä¸å‰ã®å‰å…†ã¨ã•ã‚ŒãŸã€‚「心ã€ã¨ã¯ã€ã‚¢ãƒ³ã‚¿ãƒ¬ã‚¹ãŒæ‰€å±žã™ã‚‹æ˜Ÿå®˜ï¼ˆä¸­å›½ã®æ˜Ÿåº§ï¼‰å¿ƒå®¿ã®ã“ã¨ã€‚
+å æ˜Ÿè¡“[編集]
+
+ç«æ˜Ÿã¯ä¸ƒæ›œãƒ»ä¹æ›œã®1ã¤ã§ã€10大天体ã®1ã¤ã§ã‚る。
+
+西洋å æ˜Ÿè¡“ã§ã¯ã€ç™½ç¾Šå®®ã®æ”¯é…星ã§ã€å¤©èŽå®®ã®å‰¯æ”¯é…星ã§ã€å‡¶æ˜Ÿã§ã‚る。ç©æ¥µæ€§ã‚’示ã—ã€é‹å‹•ã€äº‰ã„ã€å¤–科ã€å¹´ä¸‹ã®ç”·ã«å½“ã¦ã¯ã¾ã‚‹[17]。
+惑星記å·[編集]
+Mars symbol.ant.png
+
+ç«æ˜Ÿã®æƒ‘星記å·ã¯ãƒžãƒ«ã‚¹ã‚’象徴ã™ã‚‹ç›¾ã¨æ§ã‚’図案化ã—ãŸã‚‚ã®ãŒã€å æ˜Ÿè¡“・天文学を通ã—ã¦ç”¨ã„られる。ã“れを雌雄ã®è¡¨è¨˜ã«è»¢ç”¨ã—ãŸã®ã¯ã‚«ãƒ¼ãƒ«ãƒ»ãƒ•ã‚©ãƒ³ãƒ»ãƒªãƒ³ãƒã§ã‚ã‚Šã€ç”Ÿæ®–器ã®å›³æ¡ˆã§ã¯ãªã„。
+ç«æ˜Ÿã‚’扱ã£ãŸä½œå“[編集]
+詳細ã¯ã€Œç«æ˜Ÿã‚’扱ã£ãŸä½œå“一覧ã€ã‚’å‚ç…§
diff --git a/xpcom/tests/gtest/wikipedia/ko.txt b/xpcom/tests/gtest/wikipedia/ko.txt
new file mode 100644
index 0000000000..7c11333ad4
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/ko.txt
@@ -0,0 +1,110 @@
+화성(ç«æ˜Ÿ, Mars)ì€ íƒœì–‘ê³„ì˜ ë„¤ 번째 행성ì´ë‹¤. 붉ì€ìƒ‰ì„ ë ê¸° ë•Œë¬¸ì— ë™ì–‘권ì—서는 ë¶ˆì„ ëœ»í•˜ëŠ” í™”(ç«)를 ì¨ì„œ 화성 ë˜ëŠ” 형혹성(熒惑星)ì´ë¼ 부르고, 서양권ì—서는 로마 ì‹ í™”ì˜ ì „ìŸì˜ ì‹  ë§ˆë¥´ìŠ¤ì˜ ì´ë¦„ì„ ë”° Marsë¼ ë¶€ë¥¸ë‹¤. 오늘날 ì˜ì–´ì—ì„œ 3ì›”ì„ ëœ»í•˜ëŠ” Marchë„ ì—¬ê¸°ì„œ ìƒê²¼ë‹¤.
+
+매리너 4호가 1965ë…„ì— í™”ì„±ì„ ì²˜ìŒìœ¼ë¡œ 근접 ë¹„í–‰ì„ í•˜ê¸° 전까지 과학계 ì•ˆíŒŽì˜ ì‚¬ëžŒë“¤ì€ í™”ì„±ì— ëŒ€ëŸ‰ì˜ ë¬¼ì´ ì¡´ìž¬í•˜ë¦¬ë¼ê³  기대하였다. ì´ëŸ¬í•œ ê¸°ëŒ€ì˜ ê·¼ê±°ëŠ” í™”ì„±ì˜ ê·¹ì§€ë°©ì—ì„œ ë°ê³  ì–´ë‘ìš´ 무늬가 주기ì ìœ¼ë¡œ 변화한다는 사실ì´ì—ˆë‹¤. 60년대 중반 ì´ì „까지 ì‚¬ëžŒë“¤ì€ ë†ì—…ì„ ìœ„í•œ 관개수로가 í™”ì„±ì— ìžˆìœ¼ë¦¬ë¼ ê¸°ëŒ€í•˜ê¸°ê¹Œì§€ 했다. ì´ëŠ” 사실 20세기 ì´ˆÂ·ì¤‘ë°˜ì˜ ê³µìƒê³¼í•™ ìž‘ê°€ë“¤ì˜ ìƒìƒì— ì˜í–¥ë°›ì€ 것으로, 1950년대 ì´í›„ì˜ íƒì‚¬ì„ ì— ì˜í•œ 관측으로 화성 운하는 존재하지 않았ìŒì´ ë°í˜€ì¡Œë‹¤.
+
+물과 ìƒëª…ì²´ì˜ ë°œê²¬ì— ëŒ€í•œ 기대로 ë§Žì€ íƒì‚¬ì„ ë“¤ì— 미ìƒë¬¼ì„ 찾기 위한 ì„¼ì„œë“¤ì´ íƒ‘ìž¬ë˜ì–´ í™”ì„±ì— ë³´ë‚´ì¡Œë‹¤. 화성ì—서는 ë‹¤ëŸ‰ì˜ ì–¼ìŒì´ 발견ë˜ì—ˆê³ , ìƒëª…ì²´ê°€ 존재할 ê°€ëŠ¥ì„±ì´ ì œê¸°ë˜ê³  있다.[4]
+
+í™”ì„±ì˜ ìžì „ 주기와 ê³„ì ˆì˜ ë³€í™” 주기는 지구와 비슷하다. 화성ì—는 태양계ì—ì„œ 가장 ë†’ì€ ì‚°ì¸ ì˜¬ë¦¼í‘¸ìŠ¤ í™”ì‚°ì´ ìžˆìœ¼ë©°, ì—­ì‹œ 태양계ì—ì„œ 가장 í° ê³„ê³¡ì¸ ë§¤ë¦¬ë„ˆìŠ¤ 협곡과 ê·¹ê´€ì„ ê°€ì§€ê³  있다.
+
+물리ì ì¸ 특성[편집]
+지구와 í™”ì„±ì˜ í¬ê¸° 비êµ
+
+í™”ì„±ì€ ë¶‰ê²Œ 타는 듯한 ì™¸í˜•ì„ ê°€ì§€ê³  있다. í™”ì„±ì˜ í‘œë©´ì ì€ ì§€êµ¬ì˜ 4ë¶„ì˜ 1ë°–ì— ë˜ì§€ 않으며, 부피는 10ë¶„ì˜ 1ë°–ì— ë˜ì§€ 않는다. í™”ì„±ì€ ë‘ ê°œì˜ ìž‘ì€ ìœ„ì„±ì„ ê°€ì§€ê³  있다. í™”ì„±ì˜ ëŒ€ê¸°ê¶Œì€ ë§¤ìš° 얇으며, í‘œë©´ì˜ ê¸°ì••ì€ 7.5ë°€ë¦¬ë°”ë°–ì— ë˜ì§€ 않는다. 화성 í‘œë©´ì˜ 95%는 ì´ì‚°í™”탄소로 ë®ì—¬ 있으며, ì´ ë°–ì— 3%ì˜ ì§ˆì†Œ, 1.6%ì˜ ì•„ë¥´ê³¤ê³¼ í”ì ë§Œì´ 남아 있는 산소와 2015ë…„ NASAì—ì„œ 발견한 ì•¡ì²´ ìƒíƒœì˜ ë¬¼ì´ í¬í•¨ë˜ì–´ 있다.
+지질[편집]
+
+궤ë„ì„ ì˜ ê´€ì¸¡ê³¼ 화성 기ì›ì˜ ìš´ì„ì— ëŒ€í•œ ë¶„ì„ ê²°ê³¼ì— ì˜í•˜ë©´, í™”ì„±ì˜ í‘œë©´ì€ ê¸°ë³¸ì ìœ¼ë¡œ 현무암으로 ë˜ì–´ 있다. 화성 í‘œë©´ì˜ ì¼ë¶€ëŠ” ì§€êµ¬ì˜ ì•ˆì‚°ì•”ê³¼ ê°™ì´ ì¢€ ë” ì´ì‚°í™”규소가 í’부하다는 ì¦ê±°ê°€ 있으나 ì´ëŸ¬í•œ ê´€ì¸¡ì€ ê·œì‚°ì—¼ê³¼ ê°™ì€ ìœ ë¦¬ì˜ ì¡´ìž¬ë¥¼ 통해서 ì„¤ëª…ë  ìˆ˜ë„ ìžˆê¸° ë•Œë¬¸ì— ê²°ì •ì ì´ì§€ëŠ” 않다. í‘œë©´ì˜ ëŒ€ë¶€ë¶„ì€ ì‚°í™”ì² ì˜ ë¨¼ì§€ë¡œ ë®ì—¬ìžˆë‹¤. í™”ì„±ì˜ í‘œë©´ì— ì¼ì‹œì ì´ë‚˜ë§ˆ ë¬¼ì´ ì¡´ìž¬í–ˆë‹¤ëŠ” ê²°ì •ì ì¸ ì¦ê±°ê°€ 있다. 화성 표면ì—ì„œ ë°œê²¬ëœ ì•”ì—¼ì´ë‚˜ 침철ì„ê³¼ ê°™ì´ ëŒ€ì²´ë¡œ ë¬¼ì´ ì¡´ìž¬í•  ë•Œ ìƒì„±ë˜ëŠ” ê´‘ë¬¼ì´ ë°œê²¬ë˜ì—ˆê¸° 때문ì´ë‹¤.
+
+ë¹„ë¡ í™”ì„± ìžì²´ì˜ ìžê¸°ìž¥ì€ 없지만, 과거 행성 í‘œë©´ì˜ ì¼ë¶€ëŠ” ìží™”ëœ ì ì´ 있ìŒì´ ê´€ì¸¡ì„ í†µí•´ ë°í˜€ì¡Œë‹¤. 화성ì—ì„œ ë°œê²¬ëœ ìží™”ì˜ í”ì (고지ìžê¸°)ì€ ì§€êµ¬ì˜ í•´ì–‘ì§€ê°ì—ì„œ 발견ë˜ëŠ” êµëŒ€í•˜ëŠ” ë  ëª¨ì–‘ì˜ ê³ ì§€ìžê¸°ì™€ 비êµë˜ì–´ 왔다. 1999ë…„ì— ë°œí‘œë˜ê³  2005ë…„ì— ë§ˆìŠ¤ 글로벌 서베ì´ì–´ë¡œë¶€í„°ì˜ 관측 ê²°ê³¼ì˜ ë„움으로 ìž¬ê²€í† ëœ ì´ë¡ ì— 따르면, ì´ë“¤ 지ìžê¸°ì˜ ë ë“¤ì€ ê³¼ê±°ì— ìžˆì—ˆë˜ í™”ì„±ì˜ íŒêµ¬ì¡° 활ë™ì˜ ì¦ê±°ì¼ 수 있다. ê·¹ ì´ë™(polar wandering)ìœ¼ë¡œë„ í™”ì„±ì—ì„œ ë°œê²¬ëœ ê³ ì§€ìžê¸°ë¥¼ 설명할 수 있었다.
+
+í™”ì„±ì˜ ë‚´ë¶€ë¥¼ 설명하는 ì´ë¡ ì— 따르면, 화성 í•µì˜ ë°˜ì§€ë¦„ì€ ì•½ 1,480kmë¡œ 주로 ì² ê³¼ 15~17%ì˜ í™©ìœ¼ë¡œ ì´ë£¨ì–´ì ¸ 있다. í™©í™”ì² ì˜ í•µì€ ë¶€ë¶„ì ìœ¼ë¡œ 용융ë˜ì–´ 있으며, ì§€êµ¬ì˜ í•µì— ë¹„í•˜ë©´ 가벼운 ì›ì†Œì˜ í•¨ëŸ‰ì´ ì•½ 2ë°°ì •ë„ ëœë‹¤. í•µì€ ê·œì‚°ì—¼ì§ˆ ë§¨í‹€ì— ë‘˜ëŸ¬ì‹¸ì—¬ 있다. ë§¨í‹€ì€ í™”ì„±ì—ì„œ ë³¼ 수 있는 ë§Žì€ íŒêµ¬ì¡° 활ë™ê³¼ 화산 활ë™ì„ ì¼ìœ¼ì¼œ 왔으나 현재는 ë” ì´ìƒ 활ë™í•˜ì§€ 않는다. 화성 지ê°ì˜ ë‘께는 약 50kmì´ê³ , ìµœëŒ“ê°’ì€ 125kmì •ë„ì´ë‹¤.
+
+í™”ì„±ì˜ ì§€ì§ˆ 시대는 세 시대로 구분ëœë‹¤.
+
+노아키안 시대는 노아키스 í…Œë¼ì˜ ì´ë¦„ì„ ë”°ì„œ 붙여진 ì´ë¦„ì´ë‹¤. í™”ì„±ì˜ í˜•ì„±ìœ¼ë¡œë¶€í„° 38ì–µ~35ì–µ ë…„ ì „ê¹Œì§€ì˜ ì‹œëŒ€ì´ë‹¤. 노아키안 ì‹œëŒ€ì˜ í‘œë©´ì€ ë§Žì€ ê±°ëŒ€í•œ í¬ë ˆì´í„°ë¡œ ë®ì—¬ 있다. 타르시스 벌지는 ì´ ì‹œëŒ€ì— í˜•ì„±ëœ ê²ƒìœ¼ë¡œ 여겨진다. ì´ ì‹œëŒ€ì˜ í›„ê¸°ì—는 ì—„ì²­ë‚œ ì–‘ì˜ ì•¡ì²´ ë¬¼ì— ì˜í•œ í™ìˆ˜ê°€ 있었다고 ìƒê°ëœë‹¤.
+
+헤스í¼ë¦¬ì•ˆ 시대는 헤스í¼ë¦¬ì•ˆ í‰ì›ìœ¼ë¡œë¶€í„° ì´ë¦„ì´ ë¶™ì—¬ì¡Œë‹¤. 35ì–µ ë…„ 전부터 18ì–µ ë…„ ì „ê¹Œì§€ì˜ ì‹œëŒ€ì´ë‹¤. 헤스í¼ë¦¬ì•ˆ ì‹œëŒ€ì˜ í™”ì„±ì—서는 ë„“ì€ ìš©ì•”ëŒ€ì§€ê°€ 형성ë˜ì—ˆë‹¤.
+
+아마조니안 시대는 아마조니스 í‰ì›ì˜ ì´ë¦„ì„ ë”°ì„œ 붙여졌다. 18ì–µ ë…„ 전부터 í˜„ìž¬ì— ì´ë¥´ëŠ” 시대ì´ë‹¤. 아마조니안 ì§€ì—­ì€ í¬ë ˆì´í„°ê°€ ê±°ì˜ ì—†ìœ¼ë‚˜ ìƒë‹¹í•œ 변화가 있는 지형ì´ë‹¤. 올림푸스 í™”ì‚°ì´ ì´ ì‹œëŒ€ì— í˜•ì„±ë˜ì—ˆê³ , 다른 지역ì—ì„œ 용암류가 형성ë˜ì—ˆë‹¤.
+
+마스 ìµìŠ¤í”„레스 ì˜¤ë¹„í„°ì˜ OMEGA 가시광-ì ì™¸ì„  광물학 매핑 스팩트로메터 ìžë£Œë¥¼ 기초로 ë˜ ë‹¤ë¥¸ 시대 êµ¬ë¶„ì´ ì œì‹œë˜ê³  있다.
+지형[편집]
+
+í™”ì„±ì˜ ì¢Œí‘œë¥¼ 설정하기 위하여서는 ìžì˜¤ì„ ê³¼ 0ì  ê³ ë„ê°€ 정해져야 한다. 화성ì—는 바다가 없기 때문ì—, '해수면'ì´ ì—†ì–´ì„œ, 0ì  ê³ ë„ë©´ì´ë‚˜ í‰ê·  중력 í‘œë©´ì´ ìž„ì˜ì˜ 지ì ìœ¼ë¡œ ì„ íƒë  ìˆ˜ë°–ì— ì—†ë‹¤. ë˜í•œ ì ë„와는 달리 ê²½ë„ì˜ ê¸°ì¤€ì ì€ ìž„ì˜ë¡œ ì„ íƒì´ 가능하기 ë•Œë¬¸ì— ê³µí†µëœ ê·œì•½ì„ ì •í•  필요가 있다. 그리하여 ìž„ì˜ì ìœ¼ë¡œ 사ì´ë„ˆìŠ¤ 메리디아니(Sinus Meridiani, ì ë„만('Equatorial Gulf')) ì•ˆì˜ ë¶„í™”êµ¬ê°€ 0ì  ìžì˜¤ì„ ì„ 나타내는 것으로 ì„ íƒë˜ì—ˆë‹¤.
+
+화성 ì§€í˜•ì˜ ëª‡ 가지 기본ì ì¸ íŠ¹ì§•ì€ ë‹¤ìŒê³¼ 같다. í™”ì„±ì€ ê·¹ ì§€ë°©ì´ ì–¸ 물과 ì´ì‚°í™”탄소를 í¬í•¨í•˜ëŠ” ì–¼ìŒ ì§€ëŒ€ë¡œ ë®ì—¬ 있다. ë˜í•œ 화성ì—는 발레스 매리너리스(Valles Marineris) ë˜ëŠ” í™”ì„±ì˜ í‰í„°ë¼ê³  불리는 태양계ì—ì„œ 가장 í° [협곡 지대]ê°€ 있다. ì´ í˜‘ê³¡ 지대는 4000kmì˜ ê¸¸ì´ì— 깊ì´ëŠ” 7kmì— ì´ë¥¸ë‹¤.
+
+화성 ë¶ë°˜êµ¬ì™€ 남반구 ì§€í˜•ì˜ ë¹„ëŒ€ì¹­ì„±ì€ ë§¤ìš° ì¸ìƒì ì´ë‹¤. ë¶ìª½ ë¶€ë¶„ì€ ìš©ì•”ì¸µì´ í˜ëŸ¬ë‚´ë¦¼ìœ¼ë¡œ ì¸í•´ í‰í‰í•˜ê³ , ë‚¨ìª½ì€ ê³ ì§€ëŒ€ì— ì˜¤ëž˜ì „ì˜ ì¶©ê²©ìœ¼ë¡œ ì¸í•´ 구ë©ì´ 파ì´ê³  분화구가 ìƒê²¨ë‚˜ 있다. 지구ì—ì„œ 본 í™”ì„±ì˜ í‘œë©´ì€ í™•ì‹¤ížˆ ë‘ ë¶€ë¶„ì˜ êµ¬ì—­ìœ¼ë¡œ 나뉘어 있다. 먼지와 ì‚°í™”ì² ì´ ì„žì¸ ëª¨ëž˜ë¡œ ë’¤ë®ì¸ 좀 ë” ì°½ë°±í•œ ë¶€ë¶„ì€ í•œë•Œ 'ì•„ë¼ë¹„ì•„ì˜ ë•…'ì´ë¼ 불리며 í™”ì„±ì˜ ëŒ€ë¥™ìœ¼ë¡œ 여겨졌고, ì–´ë‘ìš´ ë¶€ë¶„ì€ ë°”ë‹¤ë¡œ 여겨졌다. 지구ì—ì„œ ë³´ì´ëŠ” 가장 ì–´ë‘ìš´ ë¶€ë¶„ì€ ì‹œë¥´í‹°ìŠ¤ ë©”ì´ì €(Syrtis Major)ì´ë‹¤. 화성ì—ì„œ 가장 í° ë¶„í™”êµ¬ëŠ” í—¬ë¼ìŠ¤ ì¶©ëŒ ë¶„ì§€(Hellas impact basin)ì¸ë°, 가벼운 ë¶‰ì€ ëª¨ëž˜ë¡œ ë®ì—¬ 있다.
+
+화성 표면 ì§€ì—­ì˜ ì´ë¦„ì„ ì§“ëŠ” ìž‘ì—…ì€ êµ­ì œ 천문 ì—°ë§¹ì˜ '행성계 명명법 워킹 그룹'ì´ ë‹´ë‹¹í•˜ê³  있다..
+대기[편집]
+ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„±ì˜ ëŒ€ê¸°ìž…ë‹ˆë‹¤.
+í™”ì„±ì˜ ì„ì–‘. '스피릿'호 ì´¬ì˜
+
+í™”ì„±ì˜ ëŒ€ê¸°ì••ì€ 0.7ì—ì„œ 0.9kPaë¡œ, ì§€êµ¬ì˜ ëŒ€ê¸° ë°€ë„와 비êµí•˜ë©´ 1/100 ì •ë„ë¡œ 매우 낮다. 대기가 ì ìœ¼ë¯€ë¡œ ê¸°ì••ì´ ë§¤ìš° 낮고 ë¬¼ì´ ìžˆë”ë¼ë„ 기압 ë•Œë¬¸ì— ë¹¨ë¦¬ ì¦ë°œí•˜ê²Œ ëœë‹¤. 과학ìžë“¤ì€ ê³¼ê±°ì˜ í™”ì„±ì€ ë¬¼ì´ í’부하고 ëŒ€ê¸°ë„ ì§€ê¸ˆë³´ë‹¤ 컸으리ë¼ê³  추측한다. ëŒ€ê¸°ì˜ ì£¼ì„±ë¶„ì¸ ì´ì‚°í™”탄소가 얼어 거대한 ê·¹ê´€ì„ í˜•ì„±í•˜ëŠ” ê³¼ì •ì´ ì–‘ê·¹ì—ì„œ êµëŒ€ë¡œ ì¼ì–´ë‚˜ê³  ì´ì‚°í™”탄소는 ëˆˆì¸µì„ í˜•ì„±í•˜ê³  ë´„ì´ ë˜ë©´ ì¦ë°œí•œë‹¤.
+ìžê¸°ê¶Œ[편집]
+
+아주 오래전 í™”ì„±ì€ íƒœì–‘í’ì„ ë§‰ì„ ìˆ˜ ìžˆì„ ë§Œí¼ ì¶©ë¶„ížˆ ê°•í•œ ìžê¸°ê¶Œì„ 가지고 ìžˆì—ˆìœ¼ë¦¬ë¼ ì—¬ê²¨ì§„ë‹¤. 그러나 40ì–µ ë…„ ì „ í™”ì„±ì˜ ë‹¤ì´ë‚˜ëª¨ê°€ 멈추고 ë‚œ ë’¤ì—는 투ìžìœ¨ì´ ë†’ì€ ê´‘ë¬¼ì— ìž”ë¥˜ìžê¸°ê°€ 남아있는 ì •ë„ë°–ì—는 ìžê¸°ìž¥ì„ 가지고 있지 않다. ì‹œê°„ì´ ì§€ë‚¨ì— ë”°ë¼ ì´ëŸ° ê´‘ë¬¼ì€ í’í™”ë˜ì—ˆê¸° ë•Œë¬¸ì— í˜„ìž¬ëŠ” ë‚¨ë°˜êµ¬ì˜ ê³ ì§€ì˜ ì¼ë¶€ì—서만 고지ìžê¸°ë¥¼ 관측할 수 있다. 태양í’ì€ í™”ì„±ì˜ ì „ë¦¬ì¸µì— ì§ì ‘ 닿기 ë•Œë¬¸ì— í™”ì„±ì˜ ëŒ€ê¸°ëŠ” 조금씩 벗겨져 나가고 있다고 여겨지나 ê·¸ ì–‘ì€ ì•„ì§ í™•ì‹¤í•˜ì§€ 않다. 마스 글로벌 서베ì´ì–´ì™€ 마스 ìµìŠ¤í”„레스는 í™”ì„±ì´ ì§€ë‚˜ê°„ ìžë¦¬ì— 남아있는 ì´ì˜¨í™”ëœ ëŒ€ê¸°ì˜ ìž…ìžë¥¼ íƒì§€í•˜ì˜€ë‹¤.
+공전과 ìžì „[편집]
+
+í™”ì„±ì˜ ê¶¤ë„ ì´ì‹¬ë¥ ì€ 약 9%ë¡œ ìƒëŒ€ì ìœ¼ë¡œ í° íŽ¸ì´ë‹¤. 태양계ì—ì„œ ì´ë³´ë‹¤ ë” ì´ì‹¬ë¥ ì´ í° ê¶¤ë„를 가지는 í–‰ì„±ì€ ìˆ˜ì„±ë°–ì— ì—†ë‹¤. íƒœì–‘ê¹Œì§€ì˜ í‰ê· ê±°ë¦¬ëŠ” 약 2ì–µ 2천만 km(1.5 천문단위)ì´ë©°, 공전 주기는 686.98ì¼ì´ë‹¤. í™”ì„±ì˜ íƒœì–‘ì¼(솔; sol)ì€ ì§€êµ¬ë³´ë‹¤ 약간 길어서 24시간 39분 35.244ì´ˆ ì •ë„ì´ë‹¤.
+
+í™”ì„±ì˜ ìžì „ì¶•ì€ 25.19ë„ë§Œí¼ ê¸°ìš¸ì–´ì ¸ 있어서 ì§€êµ¬ì˜ ê¸°ìš¸ê¸°ì™€ ê±°ì˜ ë¹„ìŠ·í•˜ë‹¤. ê·¸ ê²°ê³¼ 화성ì—서는 지구와 마찬가지로 ê³„ì ˆì´ ë‚˜íƒ€ë‚œë‹¤. 하지만 공전 ê°ì†ë„ê°€ ëŠë¦¬ê¸° ë•Œë¬¸ì— ê³„ì ˆì˜ ê¸¸ì´ëŠ” ì§€êµ¬ì— ë¹„í•´ 약 2ë°°ì •ë„ ëœë‹¤.
+위성[편집]
+ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„±ì˜ ìœ„ì„±ìž…ë‹ˆë‹¤.
+
+í¬ë³´ìŠ¤(Phobos)와 ë°ì´ëª¨ìŠ¤(Deimos)ê°€ í™”ì„±ì˜ ìœ„ì„±ì´ë‹¤. ì´ë“¤ì€ 늘 달 쪽으로 ê°™ì€ ë©´ì„ í–¥í•˜ê³  있다. í¬ë³´ìŠ¤ì˜ 화성 주위 궤ë„ê°€ 화성 ìžì²´ê°€ ë„는 ì†ë„보다 빠르며 아주 서서히 그러나 꾸준히 í™”ì„±ì— ê°€ê¹Œì›Œì§€ê³  있다. 언젠가 미래ì—는 í¬ë³´ìŠ¤ê°€ 화성 í‘œë©´ì— ì¶©ëŒí•˜ê²Œ ë  ê²ƒì´ë¼ê³  예측한다. ë°˜ë©´ì— ë°ì´ëª¨ìŠ¤ëŠ” 충분히 멀리 떨어져 있고 서서히 멀어지고 있다.
+
+ë‘ ìœ„ì„±ì€ ëª¨ë‘ 1877ë…„ ë¯¸êµ­ì¸ ì²œë¬¸í•™ìž ì•„ì‚¬í”„ 홀(Asaph Hall)ì´ ë°œê²¬í–ˆê³ , 그리스 ì‹ í™”ì— ë‚˜ì˜¤ëŠ” ë§ˆë¥´ìŠ¤ì˜ ë‘ ì•„ë“¤ì˜ ì´ë¦„ì„ ë”° 명명ë˜ì—ˆë‹¤.
+í™”ì„±ì˜ ìœ„ì„± ì´ë¦„ ì§ê²½ (km) 질량 (kg) í‰ê·  ê¶¤ë„ ë°˜ì§€ë¦„ (km) 공전 주기
+í¬ë³´ìŠ¤ 22.2 (27 × 21.6 × 18.8) 1.08×1016 9378 7.66 시간
+ë°ì´ëª¨ìŠ¤ 12.6 (10 × 12 × 16) 2×1015 23,400 30.35 시간
+ìƒëª…ì²´[편집]
+
+여러 ì¦ê±°ë¡œë¶€í„° 미루어 ë³¼ ë•Œ í™”ì„±ì´ ê³¼ê±°ì—는 지금보다 ë” ìƒëª…ì´ ì‚´ê¸°ì— ì í•©í•œ 환경ì´ì—ˆë˜ 것으로 추정ë˜ì—ˆìœ¼ë‚˜, 지금까지는, 실제 í™”ì„±ì— ìƒëª…ì´ ì¡´ìž¬í•œ ì ì´ 있는가 하는 ì§ˆë¬¸ì— ëŒ€í•´ì„œëŠ” ì•„ì§ í™•ì‹¤í•œ ë‹µì„ ì–»ì§€ 못하고 있다. ë°”ì´í‚¹ íƒì‚¬ì„ ì€ 70년대 ì¤‘ë°˜ì— í™”ì„± 표면ì—ì„œ 미ìƒë¬¼ì„ íƒì§€í•˜ê¸° 위한 ì‹¤í—˜ì„ ìˆ˜í–‰í•˜ì—¬, 과학ìžë“¤ 사ì´ì—ì„œ ë§Žì€ ë…¼ìŸì´ ë˜ê³  있다. 존슨 우주센터 연구소는 화성ì—ì„œ ë‚ ì•„ì™”ì„ ê²ƒìœ¼ë¡œ 추정ë˜ëŠ” ìš´ì„ AL[5] 빨리 분해ë˜ê¸° ë•Œë¬¸ì— ì†ŒëŸ‰ì˜ ì´ë“¤ 분ìžëŠ” í™”ì„±ì— ìƒë¬¼ì´ 사는 ì¦ê±°ë¡œ 여겨질 수 있으나, ì´ë“¤ ì›ì†ŒëŠ” 화산ì´ë‚˜ 사문함화작용 ê°™ì€ ì§€ì§ˆí•™ì  ìž‘ìš©ì— ì˜í•´ì„œë„ ê³µê¸‰ë  ìˆ˜ 있다.
+
+ í™”ì„±ì€ ìƒë¬¼ì´ ì‚´ê¸°ì— ë¶€ì í•©í•œ 특성 ì—­ì‹œ 가지고 있다. í™”ì„±ì˜ ìœ„ì¹˜ëŠ” íƒœì–‘ì˜ ê±°ì£¼ 가능 지대보다 ë°˜ ì²œë¬¸ë‹¨ìœ„ì •ë„ ë©€ë¦¬ 떨어져 있고[6] ë¬¼ì€ ì–¼ì–´ 있다.
+
+물론 ê³¼ê±°ì— ë¬¼ì´ í˜ë €ë˜ ì ì´ 있기는 하다. 화성ì—는 ë˜í•œ ìžê¸°ê¶Œì´ 없으며 대기가 í¬ë°•í•˜ë©°, ì§€ê° ì—´ë¥˜ëŸ‰ì€ ë§¤ìš° ì ìœ¼ë©°, ì™¸ë¶€ì˜ ìš´ì„ ë˜ëŠ” ì†Œí–‰ì„±ë“¤ê³¼ì˜ ì¶©ëŒ~ ë˜ëŠ” 태양í’으로부터 보호받지 못한다. ë‚®ì€ ëŒ€ê¸°ì•• ë•Œë¬¸ì— ì–¼ìŒì€ ì•¡ì²´ìƒíƒœë¥¼ 거치지 ì•Šê³  곧바로 기화해버리며, 지질학ì ìœ¼ë¡œ ì‚¬ì‹¤ìƒ ì™„ì „ížˆ ì£½ì€ í–‰ì„±ìœ¼ë¡œ 본다. {화산 활ë™ì´ 없기 ë•Œë¬¸ì— í‘œë©´ê³¼ 행성 내부 사ì´ì˜ 화학 물질과 ê´‘ë¬¼ì˜ ìˆœí™˜ì´ ì¼ì–´ë‚˜ì§€ 않는다.}
+
+ 다른한편으론, ì•„ì§ ìƒëª…ì²´ê°€ 존재하고 있다는 ì£¼ìž¥ì˜ ê·¼ê±°ë¡œ, 대기ì—ì„œ ë©”íƒ„ì´ ê²€ì¶œì„ ë“ ë‹¤.
+
+그러나, ì´ëŠ” 지질활ë™ì´ 멈춘 í™”ì„±ì˜ í™˜ê²½ì—ì„œ ìžì—°ì ìœ¼ë¡œ ë°œìƒí•  수 없으며, ìƒëª…활ë™ì— ì˜í•´ì„œë§Œ 공급ë˜ë¯€ë¡œ, 안면ì„ì´ë‚˜ 화성 피ë¼ë¯¸ë“œì™€ ê°™ì€ ìŒëª¨ë¡ ì ì¸ ê°€ì„¤ë„ ìžˆìœ¼ë‚˜ 과학ì ì¸ ì˜ë¯¸ë¡œ 주목받지는 못하다.
+화성 íƒì‚¬[편집]
+ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„± íƒì‚¬ìž…니다.
+ë¬´ì¸ íƒì‚¬ì„ [편집]
+ë°”ì´í‚¹ 1호 ì°©ë¥™ì„ ì´ ì „ì†¡í•œ 사진
+1978ë…„ 2ì›” 11ì¼ Sol 556ì—ì„œ ì´¬ì˜
+
+지금까지 ì¸ë¥˜ëŠ” ë‹¤ìˆ˜ì˜ ë¡œë´‡ íƒì‚¬ì„ ì„ í™”ì„±ì— ë³´ëƒˆê³ , 그중 ëª‡ëª‡ì€ ëŒ€ë‹¨í•œ 성과를 ê±°ë‘었지만, íƒì‚¬ì˜ ì‹¤íŒ¨ìœ¨ì€ ë§¤ìš° 높았다. 실패 사례 중 ëª‡ì€ ëª…ë°±í•œ ê¸°ìˆ ì  ê²°í•¨ì— ë”°ë¥¸ 것ì´ì—ˆì§€ë§Œ, ë§Žì€ ê²½ìš° 연구ìžë“¤ì€ 확실한 실패 ì´ìœ ë¥¼ ì°¾ì„ ìˆ˜ 없었다. 그래서 ì´ëŸ° 사례는 지구-화성 "버뮤다 삼ê°ì§€ëŒ€" í˜¹ì€ í™”ì„±íƒì‚¬ì„ ì„ 먹고 사는 ì€í•˜ê·€ì‹ (Ghoul)ë¼ëŠ” ë†ë‹´ì„ 낳았다. 화성 로봇 íƒì‚¬ì˜ 역사를 ì´í•´í•˜ê¸° 위해서는, 발사 시간대가 약 2ë…„ 남짓(í™”ì„±ì˜ ê³µì „ 주기)ì˜ ê¸°ê°„ì„ ì£¼ê¸°ë¡œ ë°œìƒí•œë‹¤ëŠ” ì‚¬ì‹¤ì„ ì•Œì•„ë‘어야 한다.
+
+1960ë…„ ì†Œë ¨ì€ ë‘ ê¸°ì˜ íƒì‚¬ì„ ì„ 화성궤ë„를 ì§€ë‚˜ì³ ëŒì•„오는 계íšìœ¼ë¡œ 발사하였으나, 지구궤ë„ì— ë„달하는 ë°ì— 실패한다. 1962ë…„ ì†Œë ¨ì€ ì„¸ 기를 ë” ì‹œë„하지만, 실패했다. ë‘ ê¸°ëŠ” 지구 궤ë„ì— ë¨¸ë¬¼ë €ê³ , 나머지 하나는 í™”ì„±ì„ ëŒì•„오는 ë™ì•ˆ ì§€êµ¬ì™€ì˜ êµì‹ ì´ ëŠì–´ì¡Œë‹¤. 1964ë…„ì— ë˜ í•œë²ˆì˜ ì‹œë„ê°€ 실패한다.
+
+1962ë…„ì—ì„œ 1973ë…„ 사ì´ì—, NASA(나사)ì˜ ì œíŠ¸ 추진 연구소(Jet Propulsion Laboratory)는 내태양계(inner solar system)를 íƒí—˜í•  10ê°œì˜ ë§¤ë¦¬ë„ˆ ìš°ì£¼ì„ ì„ ì„¤ê³„Â·ì œìž‘í•˜ì˜€ë‹¤. ì´ ìš°ì£¼ì„ ì€ ê¸ˆì„±, 화성, ìˆ˜ì„±ì„ ìµœì´ˆë¡œ íƒì‚¬í•˜ê¸° 위해서 만들어졌다. 매리너 ìš°ì£¼ì„ ì€ ë¹„êµì  ìž‘ì€ ë¡œë´‡ íƒì‚¬ì„ ìœ¼ë¡œ ì•„í‹€ë¼ìŠ¤ ë¡œì¼“ì— ì‹¤ë ¤ 발사ë˜ì—ˆë‹¤. ê° ìš°ì£¼ì„ ì˜ ë¬´ê²ŒëŠ” 0.5í†¤ì„ ë„˜ì§€ 않았다.
+
+매리너 3호와 4호는 ë™ì¼í•œ 기체로, 최초로 í™”ì„±ì„ ì§€ë‚˜ì¹˜ë©° 관찰하ë„ë¡ ì„¤ê³„ë˜ì—ˆë‹¤. 매리너 3호는 1964ë…„ 11ì›” 5ì¼ ë°œì‚¬ë˜ì—ˆìœ¼ë‚˜, ìš°ì£¼ì„ ì˜ ìœ—ë¶€ë¶„ì„ ë®ì€ ëšœê»‘ì´ ì ë‹¹ížˆ 열리지 않았고, í™”ì„±ì— ë„달하지 못했다. 3주 후 1964ë…„ 11ì›” 28ì¼ ë§¤ë¦¬ë„ˆ 4호는 성공ì ìœ¼ë¡œ 발사ë˜ì–´ 8ê°œì›”ì˜ í•­í•´ë¥¼ 시작한다.
+
+매리너 4호는 1965ë…„ 6ì›” 14ì¼ í™”ì„±ì„ ì§€ë‚˜ë©°, 다른 í–‰ì„±ì˜ ê·¼ì ‘ ì‚¬ì§„ì„ ìµœì´ˆë¡œ ì°ì–´ëƒˆë‹¤. 오랜 기간 ë™ì•ˆ ìž‘ì€ í…Œì´í”„ 레코ë”ì— ê¸°ë¡ëœ ê·¸ ì‚¬ì§„ë“¤ì€ ë‹¬ ëª¨ì–‘ì˜ ë¶„í™”êµ¬ë“¤ì„ ë³´ì—¬ 주었다. ê·¸ 분화구 들 중 ëª‡ëª‡ì€ ì„œë¦¬ê°€ ë®ì—¬ 추운 í™”ì„±ì˜ ë°¤ì„ ë³´ì—¬ì£¼ì—ˆë‹¤.
+
+NASA는 계ì†í•´ì„œ 매리너 계íšì„ 수행했다. ê·¸ë“¤ì€ ë‹¤ìŒ ë°œì‚¬ ì‹œê°„ëŒ€ì— ê·¼ì ‘ 비행 ì‹œí—˜ì„ ë˜ë‹¤ì‹œ 수행하였다. ì´ ë¹„í–‰ì„ ë“¤ì€ 1969ë…„ì— í™”ì„±ì— ë„달하였다. ì´ì— 관해서는 매리너 6호 와 7호를 참조하ë¼. ë‹¤ìŒ ë°œì‚¬ ë•Œ 매리너 계íšì€ ë‘ ëŒ€ì˜ ë¹„í–‰ì„  중 í•œ 대를 잃는 사고를 겪었다. ì‚´ì•„ë‚¨ì€ ë§¤ë¦¬ë„ˆ 9호는 성공ì ìœ¼ë¡œ 화성 궤ë„ì— ì§„ìž…í•˜ì˜€ë‹¤. 매리너 9호가 í™”ì„±ì— ë„ë‹¬í–ˆì„ ë•Œ, 그것과 ë‘ ëŒ€ì˜ ì†Œë ¨ ì¸ê³µìœ„ì„±ì€ í–‰ì„± ì „ì˜ì—­ì— ê±¸ì³ ë¨¼ì§€ í­í’ì´ ì¼ì–´ë‚˜ê³  있는 ê²ƒì„ ë°œê²¬í•˜ì˜€ë‹¤. ê·¸ í­í’ì´ ê°€ë¼ì•‰ëŠ” ê²ƒì„ ê¸°ë‹¤ë¦¬ëŠ” ë™ì•ˆ 화성 í‘œë©´ì˜ ì‚¬ì§„ì„ ì°ëŠ” ê²ƒì€ ë¶ˆê°€ëŠ¥í•˜ì˜€ìœ¼ë¯€ë¡œ, 매리너 9호는 í¬ë³´ìŠ¤ì˜ ì‚¬ì§„ì„ ì°ì—ˆë‹¤. í­í’ì´ í™”ì„±ì˜ í‘œë©´ ì‚¬ì§„ì„ ì°ê¸°ì— 충분할 ë§Œí¼ ê°€ë¼ì•‰ì•˜ì„ ë•Œ, ì „ì†¡ëœ ì‚¬ì§„ì€ ì´ì „ ìž„ë¬´ì˜ ê²°ê³¼ë¡œ 온 사진보다 ë” ë†’ì€ í’ˆì§ˆì„ ê°€ì§€ê³  있었다. ì´ ì‚¬ì§„ë“¤ì´ í™”ì„±ì— í•œë•Œ ì•¡ì²´ í˜•íƒœì˜ ë¬¼ì´ ìžˆì—ˆì„ëŠ”ì§€ë„ ëª¨ë¥¸ë‹¤ëŠ” ê²ƒì„ ì¦ê±°í•˜ëŠ” 첫 번째 사진ì´ì—ˆë‹¤.
+
+1976ë…„ì— ë‘ ëŒ€ì˜ ë°”ì´í‚¹ 호가 화성 궤ë„ì— ë“¤ì–´ê°€ ê°ê° 착륙 ëª¨ë“ˆì„ ë‚´ë ¤ 화성 í‘œë©´ì— ë‚´ë ¤ 앉았다. ì´ ìž„ë¬´ë¥¼ 통해 ì¸ë¥˜ëŠ” 첫 번째 컬러 사진과 ë”ìš± í™•ìž¥ëœ ê³¼í•™ì  ì •ë³´ë¥¼ ì–»ì„ ìˆ˜ 있었다.
+
+소비ì—트 ì—°ë°©ì˜ í™”ì„± íƒì‚¬ 계íšì—ì„œ 발사한 ìš°ì£¼ì„ ë“¤ì€ ë°”ì´í‚¹ë³´ë‹¤ 몇 ë…„ ì¼ì° ìˆ˜ë§Žì€ ì°©ë¥™ì„ ì‹œë„했다. 그러나 매리너 계íšì´ ìˆ˜í–‰í–ˆë˜ ê²ƒë³´ë‹¤ 성공ì ì¸ 결과를 얻지는 못했다.
+
+마스 패스파ì¸ë”는 1997ë…„ 7ì›” 4ì¼ì— í™”ì„±ì— ì°©ë¥™í•˜ì—¬, 소저너ë¼ëŠ” 매우 ìž‘ì€ ì›ê²© 조정체를 움ì§ì—¬ 착륙 ì§€ì  ì£¼ìœ„ì˜ ëª‡ 미터를 여행하고, í™”ì„±ì˜ í™˜ê²½ ì¡°ê±´ì„ íƒìƒ‰í•˜ê³  í‘œë©´ì˜ ëŒë“¤ì„ 수집해왔다.
+
+ë‹¤ìŒ íƒì‚¬ëŠ” 마스 글로벌 서베ì´ì–´(Mars Global Surveyor)ì— ì˜í•´ ì´ë£¨ì–´ì¡Œë‹¤. ì´ ìž„ë¬´ëŠ” 20ì—¬ ë…„ê°„ì˜ í™”ì„± íƒì‚¬ì—­ì‚¬ì—ì„œ 첫 번째로 성공ì ì¸ 것ì´ì—ˆê³ , 1996ë…„ 11ì›” 7ì¼ì— 발사ë˜ì–´ 1997ë…„ 9ì›” 12ì¼ì— 화성 궤ë„ì— ë„달하였다. 1ë…„ ë°˜ ì •ë„ê°€ í른 후, 회전 궤ë„ê°€ 타ì›í˜•ì—ì„œ ì›í˜•ìœ¼ë¡œ ìžë¦¬ë¥¼ 잡았고, ìš°ì£¼ì„ ì€ 1999ë…„ 3월부터 기초ì ì¸ 매핑 ìž„ë¬´ì— ëŒìž…했다. ìš°ì£¼ì„ ì€ í™”ì„±ì„ í™”ì„±ë ¥ìœ¼ë¡œ 1ë…„, 지구력으로는 ê±°ì˜ 2ë…„ê°„ 저고ë„ì—ì„œ 관찰했다. 마스 글로벌 서베ì´ì–´í˜¸ëŠ” ìµœê·¼ì¸ 2001ë…„ 1ì›” 31ì¼ ê·¸ 기초ì ì¸ 임무를 완료하고 현재는 2단계 임무를 수행하고 있다.
+
+ì´ íƒì‚¬ëŠ” 화성 표면, 대기권, 그리고 ë‚´ë¶€ì— ëŒ€í•œ ì „ì²´ì ì¸ 연구를 수행하고, 지난 íƒì‚¬ 계íšì—ì„œ ê±°ë‘¬ë“¤ì¸ ëª¨ë“  결과물보다 ë” ë§Žì€ ë°ì´í„°ë¥¼ 가져왔다. ì´ ê°€ì¹˜ìžˆëŠ” ë°ì´í„°ë“¤ì€ 마스 글로벌 서베ì´ì–´: MOLA ì—ì„œ 찾아볼 수 있다.
+
+2008ë…„ 7ì›” 31ì¼ ë¯¸êµ­ êµ­ë¦½í•­ê³µìš°ì£¼êµ­ì€ í™”ì„±íƒì‚¬ì„  피닉스가 í™”ì„±ì— ë¬¼ì´ ì¡´ìž¬í•¨ì„ í™•ì¸í•˜ì˜€ë‹¤ê³  발표했다. 피닉스는 2008ë…„ 11ì›” 10ì¼ ìž„ë¬´ê°€ 종료ë˜ì—ˆë‹¤.
+ê´€ì¸¡ì˜ ì—­ì‚¬[편집]
+
+기ì›ì „ 1600ë…„ê²½ì— í™”ì„±ì— ëŒ€í•œ ê´€ì¸¡ì´ ì‹œìž‘ë˜ì—ˆë‹¤ê³  여겨지며, í™”ì„±ì€ ë¶ˆê³¼ ê°™ì´ ë¶‰ê²Œ 빛나고 다른 천체와 달리 하늘ì—ì„œ ì´ìƒí•˜ê²Œ 움ì§ì¸ë‹¤ê³  알려졌다.
+
+ 바빌로니아ì¸ì€ ì´ë¯¸ 기ì›ì „ 400ë…„ê²½ì— ì²œë¬¸í˜„ìƒì„ 연구했었으며 ì¼ì‹, ì›”ì‹ê³¼ ê°™ì€ ì²œë¬¸í˜„ìƒì„ 예측하기 위해 ê³ ë„ë¡œ ë°œë‹¬ëœ ë°©ë²•ì„ ì‚¬ìš©í•˜ì˜€ë‹¤. ê·¸ë“¤ì€ ê·¸ë“¤ì˜ ë‹¬ë ¥ê³¼ 종êµì ì¸ ì´ìœ ì—ì„œ ê·¸ë“¤ì„ ì£¼ì˜ê¹Šê²Œ 연구하였다. 그러나 ê·¸ë“¤ì´ ëª©ê²©í•œ 현ìƒì— 대해서 깊게 분ì„한다거나 설명하려고 하지는 않았다. 바빌로니아ì¸ë“¤ì€ í™”ì„±ì„ ë„¤ë¥´ê°ˆ(Nergal, ‘위대한 ì˜ì›…’ ë˜ëŠ” ‘전ìŸì˜ 왕.’ ì›ëœ»ì€ ‘커다란 ì§‘ì˜ ì£¼ì¸â€™)ì´ë¼ 불렀다.
+ ì´ì§‘트ì¸ì€ ë³„ì´ â€œê³ ì •ëœâ€ ë“¯ì´ ë³´ì´ë©°, íƒœì–‘ì´ ê³ ì •ëœ ë³„ì— ëŒ€í•˜ì—¬ ìƒëŒ€ì ìœ¼ë¡œ ì´ë™í•œë‹¤ê³  ìƒê°í–ˆë‹¤. ë˜í•œ ê·¸ë“¤ì€ í•˜ëŠ˜ì˜ 5ê°œì˜ ë¹›ë‚˜ëŠ” 천체가 ê³ ì •ëœ ë³„ 사ì´ë¥¼ 움ì§ì¸ë‹¤ëŠ” ê²ƒì„ ì•Œì•˜ë‹¤. ì´ì§‘트ì¸ì€ í™”ì„±ì„ Har Decher(ë¶‰ì€ ê²ƒ) í˜¹ì€ '죽ìŒì˜ 별'ì´ë¼ê³  불렀다.
+ 그리스ì¸ì€ í™”ì„±ì„ ì „ìŸì˜ ì‹ ì˜ ì´ë¦„ì„ ë”°ì„œ 아레스(Ares)ë¼ê³  불렀다. 로마ì—ì„œë„ ì´ ì´ë¦„ì„ ê·¸ëŒ€ë¡œ 번역하여 í™”ì„±ì„ ë§ˆë¥´ìŠ¤(Mars)ë¼ê³  불렀다. í™”ì„±ì˜ ê¸°í˜¸ëŠ” ë§ˆë¥´ìŠ¤ì˜ ë°©íŒ¨ì™€ 칼로 여겨진다.
+ 조반니 스키아파ë ë¦¬(Giovanni Virginio Schiaparelli, 1835ë…„~1910ë…„)는 1877ë…„, 화성ì—ì„œ "cannali"ë¡œ ë³´ì´ëŠ” ê²ƒì´ ë°œê²¬ë˜ì—ˆë‹¤ê³  발표했다. ì´ ë‹¨ì–´ëŠ” ì´íƒˆë¦¬ì•„ì–´ë¡œ "거대한 홈"ì„ ëœ»í•œë‹¤. ì´ê²ƒì´ 제대로 번역ë˜ì—ˆë‹¤ë©´ "channels"ê°€ ë˜ì–´ì•¼ 했다. 하지만 당시 수ì—즈 ìš´í•˜ë„ ê±´ì„¤ë˜ê³  ê´€ì‹¬ì´ ê°€ë˜ ì°¨ì— "운하(canals)"ë¡œ 번역ë˜ì—ˆë‹¤. ì´ê²ƒìœ¼ë¡œ 화성 íƒì‚¬ ì—´í’ì˜ ì—­ì‚¬ê°€ ì‹œìž‘ëœ ê²ƒì´ë‹¤.
+
+※ ë™ì–‘ì˜ ê³ ëŒ€ê¸°ë¡ì—는 ë‚®ì— í™”ì„±ì„ ë³¸ ê²ƒì´ ìžˆìœ¼ë‚˜, ê²€ì¦ê²°ê³¼ ê¸ˆì„±ì˜ ì°©ì˜¤ì˜€ìœ¼ë©°, í™”ì„±ì„ ë‚®ì— ë§¨ 눈으로 본다는 ê²ƒì€ ì‚¬ì‹¤ìƒ ë¶ˆê°€ëŠ¥í•˜ë‹¤
diff --git a/xpcom/tests/gtest/wikipedia/ru.txt b/xpcom/tests/gtest/wikipedia/ru.txt
new file mode 100644
index 0000000000..9467849e6f
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/ru.txt
@@ -0,0 +1,410 @@
+ÐœÐ°Ñ€Ñ â€” Ñ‡ÐµÑ‚Ð²Ñ‘Ñ€Ñ‚Ð°Ñ Ð¿Ð¾ удалённоÑти от Солнца и ÑÐµÐ´ÑŒÐ¼Ð°Ñ Ð¿Ð¾ размерам планета Солнечной ÑиÑтемы; маÑÑа планеты ÑоÑтавлÑет 10,7 % маÑÑÑ‹ Земли. Ðазвана в чеÑÑ‚ÑŒ МарÑа — древнеримÑкого бога войны, ÑоответÑтвующего древнегречеÑкому ÐреÑу. Иногда ÐœÐ°Ñ€Ñ Ð½Ð°Ð·Ñ‹Ð²Ð°ÑŽÑ‚ «краÑной планетой» из-за краÑноватого оттенка поверхноÑти, придаваемого ей окÑидом железа.
+
+ÐœÐ°Ñ€Ñ â€” планета земной группы Ñ Ñ€Ð°Ð·Ñ€ÐµÐ¶ÐµÐ½Ð½Ð¾Ð¹ атмоÑферой (давление у поверхноÑти в 160 раз меньше земного). ОÑобенноÑÑ‚Ñми поверхноÑтного рельефа МарÑа можно Ñчитать ударные кратеры наподобие лунных, а также вулканы, долины, пуÑтыни и полÑрные ледниковые шапки наподобие земных.
+
+У МарÑа еÑÑ‚ÑŒ два еÑтеÑтвенных Ñпутника — Ð¤Ð¾Ð±Ð¾Ñ Ð¸ Ð”ÐµÐ¹Ð¼Ð¾Ñ (в переводе Ñ Ð´Ñ€ÐµÐ²Ð½ÐµÐ³Ñ€ÐµÑ‡ÐµÑкого — «Ñтрах» и «ужаÑ», имена двух Ñыновей ÐреÑа, Ñопровождавших его в бою), которые отноÑительно малы (Ð¤Ð¾Ð±Ð¾Ñ â€” 26,8×22,4×18,4 км, Ð”ÐµÐ¹Ð¼Ð¾Ñ â€” 15×12,2×10,4 км)[6][7] и имеют неправильную форму.
+
+ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1960-Ñ… годов непоÑредÑтвенным иÑÑледованием МарÑа Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ÐМС занималиÑÑŒ СССР (программы «МарÑ» и «ФобоÑ»), СШР(программы «Маринер», «Викинг», «Mars Global Surveyor» и другие), ЕвропейÑкое коÑмичеÑкое агентÑтво (программа «МарÑ-ÑкÑпреÑÑ») и Ð˜Ð½Ð´Ð¸Ñ (программа «МангальÑн»). Ðа ÑегоднÑшний день, поÑле Земли, ÐœÐ°Ñ€Ñ â€” ÑÐ°Ð¼Ð°Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ð° Солнечной ÑиÑтемы.
+
+ОÑновные ÑведениÑ[править | править вики-текÑÑ‚]
+
+ÐœÐ°Ñ€Ñ â€” Ñ‡ÐµÑ‚Ð²Ñ‘Ñ€Ñ‚Ð°Ñ Ð¿Ð¾ удалённоÑти от Солнца (поÑле МеркуриÑ, Венеры и Земли) и ÑÐµÐ´ÑŒÐ¼Ð°Ñ Ð¿Ð¾ размерам (превоÑходит по маÑÑе и диаметру только Меркурий) планета Солнечной ÑиÑтемы[8]. МаÑÑа МарÑа ÑоÑтавлÑет 10,7 % маÑÑÑ‹ Земли (6,423·1023 кг против 5,9736·1024 кг Ð´Ð»Ñ Ð—ÐµÐ¼Ð»Ð¸), объём — 0,15 объёма Земли, а Ñредний линейный диаметр — 0,53 диаметра Земли (6800 км)[7].
+
+Рельеф МарÑа обладает многими уникальными чертами. МарÑианÑкий потухший вулкан гора Олимп — ÑÐ°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð³Ð¾Ñ€Ð° на планетах Солнечной ÑиÑтемы[9] (ÑÐ°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð³Ð¾Ñ€Ð° в Солнечной ÑиÑтеме — на аÑтероиде ВеÑта[10]), а долины Маринер — Ñамый крупный извеÑтный каньон на планетах (Ñамый большой каньон в Ñолнечной ÑиÑтеме обнаружен на Ñпутнике Плутона — Хароне[11]). Помимо Ñтого, в июне 2008 года три Ñтатьи, опубликованные в журнале «Nature», предÑтавили доказательÑтва ÑущеÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² Ñеверном полушарии МарÑа Ñамого крупного извеÑтного ударного кратера в Солнечной ÑиÑтеме. Его длина — 10,6 Ñ‚Ñ‹Ñ. км, а ширина — 8,5 Ñ‚Ñ‹Ñ. км, что примерно в четыре раза больше, чем крупнейший ударный кратер, до того также обнаруженный на МарÑе, вблизи его южного полюÑа[12].
+
+ÐœÐ°Ñ€Ñ Ð¸Ð¼ÐµÐµÑ‚ период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¸ Ñмену времён года, аналогичные земным, но его климат значительно холоднее и Ñуше земного.
+
+Вплоть до полёта к МарÑу автоматичеÑкой межпланетной Ñтанции «Маринер-4» в 1965 году многие иÑÑледователи полагали, что на его поверхноÑти еÑÑ‚ÑŒ вода в жидком ÑоÑтоÑнии. Это мнение было оÑновано на наблюдениÑÑ… за периодичеÑкими изменениÑми в Ñветлых и тёмных учаÑтках, оÑобенно в полÑрных широтах, которые были похожи на континенты и морÑ. Тёмные длинные линии на поверхноÑти МарÑа интерпретировалиÑÑŒ некоторыми наблюдателÑми как ирригационные каналы Ð´Ð»Ñ Ð¶Ð¸Ð´ÐºÐ¾Ð¹ воды. Позднее было доказано, что большинÑтво Ñтих тёмных линий ÑвлÑÑŽÑ‚ÑÑ Ð¾Ð¿Ñ‚Ð¸Ñ‡ÐµÑкой иллюзией[13].
+Великие противоÑтоÑÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа (раÑÑтоÑние до Земли менее 60 млн. км), 1830—2050 годы Дата РаÑÑÑ‚.,
+а. e.
+19 ÑентÑÐ±Ñ€Ñ 1830 0,388
+18 авгуÑта 1845 0,373
+17 Ð¸ÑŽÐ»Ñ 1860 0,393
+5 ÑентÑÐ±Ñ€Ñ 1877 0,377
+4 авгуÑта 1892 0,378
+24 ÑентÑÐ±Ñ€Ñ 1909 0,392
+23 авгуÑта 1924 0,373
+23 Ð¸ÑŽÐ»Ñ 1939 0,390
+10 ÑентÑÐ±Ñ€Ñ 1956 0,379
+10 авгуÑта 1971 0,378
+22 ÑентÑÐ±Ñ€Ñ 1988 0,394
+28 авгуÑта 2003 0,373
+27 Ð¸ÑŽÐ»Ñ 2018 0,386
+15 ÑентÑÐ±Ñ€Ñ 2035 0,382
+14 авгуÑта 2050 0,374
+
+Ðа Ñамом деле из-за низкого Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð²Ð¾Ð´Ð° не может ÑущеÑтвовать в жидком ÑоÑтоÑнии на большей чаÑти (около 70 %) поверхноÑти МарÑа[14]. Вода в ÑоÑтоÑнии льда была обнаружена в марÑианÑком грунте коÑмичеÑким аппаратом ÐÐСР«ФеникÑ»[15][16]. Ð’ то же Ð²Ñ€ÐµÐ¼Ñ Ñобранные марÑоходами «Спирит» и «Opportunity» геологичеÑкие данные позволÑÑŽÑ‚ предположить, что в далёком прошлом вода покрывала значительную чаÑÑ‚ÑŒ поверхноÑти МарÑа. ÐÐ°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð² течение поÑледнего деÑÑÑ‚Ð¸Ð»ÐµÑ‚Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ð¸Ð»Ð¸ обнаружить в некоторых меÑтах на поверхноÑти МарÑа Ñлабую гейзерную активноÑÑ‚ÑŒ[17]. По наблюдениÑм Ñ ÐºÐ¾ÑмичеÑкого аппарата «Mars Global Surveyor», некоторые чаÑти южной полÑрной шапки МарÑа поÑтепенно отÑтупают[18].
+
+С Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 2009 по наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¸ÑÑледовательÑÐºÐ°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð¸Ñ€Ð¾Ð²ÐºÐ° на орбите МарÑа наÑчитывает три функционирующих коÑмичеÑких аппарата: Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей», «МарÑ-ÑкÑпреÑÑ» и «Mars Reconnaissance Orbiter». Это больше, чем около любой другой планеты, помимо Земли.
+
+ПоверхноÑÑ‚ÑŒ МарÑа в наÑтоÑщий момент иÑÑледуют два марÑохода: «Opportunity» и «Curiosity». Ðа поверхноÑти МарÑа также находÑÑ‚ÑÑ Ð½ÐµÑколько неактивных поÑадочных модулей и марÑоходов, завершивших иÑÑледованиÑ.
+
+ÐœÐ°Ñ€Ñ Ñ…Ð¾Ñ€Ð¾ÑˆÐ¾ виден Ñ Ð—ÐµÐ¼Ð»Ð¸ невооружённым глазом. Его Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° доÑтигает −2,91m (при макÑимальном Ñближении Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹), уÑÑ‚ÑƒÐ¿Ð°Ñ Ð¿Ð¾ ÑркоÑти лишь Юпитеру (и то далеко не вÑегда во Ð²Ñ€ÐµÐ¼Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ð³Ð¾ противоÑтоÑниÑ) и Венере (но лишь утром или вечером). ПротивоÑтоÑние МарÑа можно наблюдать каждые два года. ПоÑледний раз такое Ñвление на Земле наблюдалоÑÑŒ Ñ 9 по 14 Ð°Ð¿Ñ€ÐµÐ»Ñ 2014 года[129 1]. Как правило, во Ð²Ñ€ÐµÐ¼Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ð³Ð¾ противоÑтоÑÐ½Ð¸Ñ (то еÑÑ‚ÑŒ при Ñовпадении противоÑтоÑÐ½Ð¸Ñ Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹ и Ð¿Ñ€Ð¾Ñ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñом Ð¿ÐµÑ€Ð¸Ð³ÐµÐ»Ð¸Ñ Ñвоей орбиты) оранжевый ÐœÐ°Ñ€Ñ ÑвлÑетÑÑ Ñрчайшим объектом земного ночного неба (не ÑÑ‡Ð¸Ñ‚Ð°Ñ Ð›ÑƒÐ½Ñ‹), но Ñто проиÑходит лишь один раз в 15—17 лет в течение одной-двух недель.
+Орбитальные характериÑтики[править | править вики-текÑÑ‚]
+
+Минимальное раÑÑтоÑние от МарÑа до Земли ÑоÑтавлÑет 55,76 млн. км[19] (когда Ð—ÐµÐ¼Ð»Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ñ‚Ð¾Ñ‡Ð½Ð¾ между Солнцем и МарÑом), макÑимальное — около 401 млн. км (когда Солнце находитÑÑ Ñ‚Ð¾Ñ‡Ð½Ð¾ между Землёй и МарÑом).
+РаÑÑтоÑние между Землёй и МарÑом (в а. е.) во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾ÑтоÑний 2014—2061 гг.
+
+Среднее раÑÑтоÑние от МарÑа до Солнца ÑоÑтавлÑет 228 млн. км (1,52 а. e.), период Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð²Ð¾ÐºÑ€ÑƒÐ³ Солнца равен 687 земным Ñуткам[2]. Орбита МарÑа имеет довольно заметный ÑкÑцентриÑитет (0,0934), поÑтому раÑÑтоÑние до Солнца менÑетÑÑ Ð¾Ñ‚ 206,6 до 249,2 млн. км. Ðаклонение орбиты МарÑа к плоÑкоÑти Ñклиптики равно 1,85°[2].
+
+ÐœÐ°Ñ€Ñ Ð±Ð»Ð¸Ð¶Ðµ вÑего к Земле во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾ÑтоÑниÑ, когда планета находитÑÑ Ð½Ð° небе в направлении, противоположном Солнцу. ПротивоÑтоÑÐ½Ð¸Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑÑŽÑ‚ÑÑ ÐºÐ°Ð¶Ð´Ñ‹Ðµ 26 меÑÑцев в разных точках орбиты МарÑа и Земли. Раз в 15—17 лет противоÑтоÑÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð´ÑÑ‚ÑÑ Ð½Ð° то времÑ, когда ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð±Ð»Ð¸Ð·Ð¸ Ñвоего перигелиÑ; в Ñтих традиционно называемых великими противоÑтоÑниÑÑ… раÑÑтоÑние до планеты минимально (менее 60 млн км), и ÐœÐ°Ñ€Ñ Ð´Ð¾Ñтигает наибольшего углового размера 25,1″ и ÑркоÑти −2,88m[20].
+ФизичеÑкие характериÑтики[править | править вики-текÑÑ‚]
+
+По линейному размеру ÐœÐ°Ñ€Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¸ вдвое меньше Земли — его Ñкваториальный Ñ€Ð°Ð´Ð¸ÑƒÑ Ñ€Ð°Ð²ÐµÐ½ 3396,9 км (53,2 % земного). Площадь поверхноÑти МарÑа примерно равна площади Ñуши на Земле[21].
+
+ПолÑрный Ñ€Ð°Ð´Ð¸ÑƒÑ ÐœÐ°Ñ€Ñа примерно на 20 км меньше Ñкваториального, Ñ…Ð¾Ñ‚Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´ Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñƒ планеты больший, чем у Земли, что даёт повод предположить изменение ÑкороÑти Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа Ñо временем[22].
+Сравнение размеров Земли (Ñредний Ñ€Ð°Ð´Ð¸ÑƒÑ 6371 км) и МарÑа (Ñредний Ñ€Ð°Ð´Ð¸ÑƒÑ 3386,2 км)
+
+МаÑÑа планеты — 6,418·1023 кг (11 % маÑÑÑ‹ Земли). УÑкорение Ñвободного Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ Ð½Ð° Ñкваторе равно 3,711 м/Ѳ (0,378 земного); Ð¿ÐµÑ€Ð²Ð°Ñ ÐºÐ¾ÑмичеÑÐºÐ°Ñ ÑкороÑÑ‚ÑŒ ÑоÑтавлÑет 3,6 км/Ñ, Ð²Ñ‚Ð¾Ñ€Ð°Ñ â€” 5,027 км/Ñ.
+
+Период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ — 24 чаÑа 37 минут 22,7 Ñекунд (отноÑительно звёзд), длина Ñредних Ñолнечных Ñуток (называемых Ñолами) ÑоÑтавлÑет 24 чаÑа 39 минут 35,24409 Ñекунды, вÑего на 2,7 % длиннее земных Ñуток. МарÑианÑкий год ÑоÑтоит из 668,6 марÑианÑких Ñолнечных Ñуток.
+
+ÐœÐ°Ñ€Ñ Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ÑÑ Ð²Ð¾ÐºÑ€ÑƒÐ³ Ñвоей оÑи, наклонённой к перпендикулÑру плоÑкоÑти орбиты под углом 25,19°[2]. Ðаклон оÑи Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа обеÑпечивает Ñмену времён года. При Ñтом вытÑнутоÑÑ‚ÑŒ орбиты приводит к большим различиÑм в их продолжительноÑти — так, ÑÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð²ÐµÑна и лето, вмеÑте взÑтые, длÑÑ‚ÑÑ 371 Ñол, то еÑÑ‚ÑŒ заметно больше половины марÑианÑкого года. Ð’ то же Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð½Ð¸ приходÑÑ‚ÑÑ Ð½Ð° учаÑток орбиты МарÑа, удалённый от Солнца. ПоÑтому на МарÑе Ñеверное лето долгое и прохладное, а южное — короткое и отноÑительно тёплое.
+ÐтмоÑфера и климат[править | править вики-текÑÑ‚]
+ОÑновные Ñтатьи: ÐтмоÑфера МарÑа, Климат МарÑа
+ÐтмоÑфера МарÑа, Ñнимок получен иÑкуÑÑтвенным Ñпутником «Викинг» в 1976. Слева виден «кратер-Ñмайлик» Галле
+
+Температура на планете колеблетÑÑ Ð¾Ñ‚ −153 °C[23] на полюÑе зимой и до более +20 °C[24] на Ñкваторе в полдень. СреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° ÑоÑтавлÑет −50 °C[23].
+
+ÐтмоÑфера МарÑа, ÑоÑтоÑÑ‰Ð°Ñ Ð² оÑновном из углекиÑлого газа, очень разрежена. Давление у поверхноÑти МарÑа в 160 раз меньше земного — 6,1 мбар на Ñреднем уровне поверхноÑти. Из-за большого перепада выÑот на МарÑе давление у поверхноÑти Ñильно изменÑетÑÑ. ÐŸÑ€Ð¸Ð¼ÐµÑ€Ð½Ð°Ñ Ñ‚Ð¾Ð»Ñ‰Ð¸Ð½Ð° атмоÑферы — 110 км.
+
+По данным ÐÐСР(2004), атмоÑфера МарÑа ÑоÑтоит на 95,32 % из углекиÑлого газа; также в ней ÑодержитÑÑ 2,7 % азота, 1,6 % аргона, 0,13 % киÑлорода, 210 ppm водÑного пара, 0,08 % угарного газа, окÑид азота (NO) — 100 ppm, неон (Ne) — 2,5 ppm, полутÑÐ¶Ñ‘Ð»Ð°Ñ Ð²Ð¾Ð´Ð° водород-дейтерий-киÑлород (HDO) 0,85 ppm, криптон (Kr) 0,3 ppm, кÑенон (Xe) — 0,08 ppm[2] (ÑоÑтав приведён в объёмных долÑÑ…).
+
+По данным ÑпуÑкаемого аппарата ÐМС «Викинг» (1976), в марÑианÑкой атмоÑфере было определено около 1—2 % аргона, 2—3 % азота, а 95 % — углекиÑлый газ[25]. СоглаÑно данным ÐМС «МарÑ-2» и «МарÑ-3», нижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ионоÑферы находитÑÑ Ð½Ð° выÑоте 80 км, макÑимум Ñлектронной концентрации 1,7×105 Ñлектронов/Ñм³ раÑположен на выÑоте 138 км, другие два макÑимума находÑÑ‚ÑÑ Ð½Ð° выÑотах 85 и 107 км[26].
+
+РадиопроÑвечивание атмоÑферы на радиоволнах 8 и 32 Ñм, проведённое ÐМС «МарÑ-4» 10 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 1974 года, показало наличие ночной ионоÑферы МарÑа Ñ Ð³Ð»Ð°Ð²Ð½Ñ‹Ð¼ макÑимумом ионизации на выÑоте 110 км и концентрацией Ñлектронов 4,6×103 Ñлектронов/Ñм³, а также вторичными макÑимумами на выÑоте 65 и 185 км[26].
+
+РазреженноÑÑ‚ÑŒ марÑианÑкой атмоÑферы и отÑутÑтвие магнитоÑферы ÑвлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð¾Ð¹ того, что уровень ионизирующей радиации на поверхноÑти МарÑа ÑущеÑтвенно выше, чем на поверхноÑти Земли. МощноÑÑ‚ÑŒ Ñквивалентной дозы на поверхноÑти МарÑа ÑоÑтавлÑет в Ñреднем 0,7 мЗв/Ñутки (изменÑÑÑÑŒ в завиÑимоÑти от Ñолнечной активноÑти и атмоÑферного Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² пределах от 0,35 до 1,15 мЗв/Ñутки)[27] и обуÑловлена главным образом коÑмичеÑким излучением; Ð´Ð»Ñ ÑравнениÑ, на Земле ÑÑ€ÐµÐ´Ð½ÐµÐ¼Ð¸Ñ€Ð¾Ð²Ð°Ñ ÑÐºÐ²Ð¸Ð²Ð°Ð»ÐµÐ½Ñ‚Ð½Ð°Ñ Ð´Ð¾Ð·Ð° Ð¾Ð±Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚ еÑтеÑтвенных иÑточников, Ð½Ð°ÐºÐ°Ð¿Ð»Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð·Ð° год, равна 2,4 мЗв, в том чиÑле от коÑмичеÑких лучей 0,4 мЗв[28]. Таким образом, за один-два Ð´Ð½Ñ ÐºÐ¾Ñмонавт на поверхноÑти МарÑа получит такую же Ñквивалентную дозу облучениÑ, какую на поверхноÑти Земли он получил бы за год.
+ÐтмоÑферное давление[править | править вики-текÑÑ‚]
+
+По данным ÐÐСРна 2004 год, давление атмоÑферы на Ñреднем радиуÑе ÑоÑтавлÑет 636 Па (6,36 мбар). ПлотноÑÑ‚ÑŒ атмоÑферы у поверхноÑти — около 0,020 кг/м³, Ð¾Ð±Ñ‰Ð°Ñ Ð¼Ð°ÑÑа атмоÑферы МарÑа — около 2,5×1016 кг[2].
+Изменение атмоÑферного Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð° МарÑе в завиÑимоÑти от времени Ñуток, зафикÑированное поÑадочным модулем «Mars Pathfinder» в 1997 году
+
+Ð’ отличие от Земли, маÑÑа марÑианÑкой атмоÑферы Ñильно изменÑетÑÑ Ð² течение года в ÑвÑзи Ñ Ñ‚Ð°Ñнием и намерзанием полÑрных шапок, Ñодержащих углекиÑлый газ. Зимой 20—30 процентов вÑей атмоÑферы намораживаетÑÑ Ð½Ð° полÑрной шапке, ÑоÑтоÑщей из углекиÑлоты[29]. Сезонные перепады давлениÑ, по разным иÑточникам, ÑоÑтавлÑÑŽÑ‚ Ñледующие значениÑ:
+
+ По данным ÐÐСР(2004): от 4,0 до 8,7 мбар на Ñреднем радиуÑе[2];
+ По данным Encarta (2000): от 6 до 10 мбар[30];
+ По данным Zubrin и Wagner (1996): от 7 до 10 мбар[31];
+ По данным поÑадочного аппарата «Викинг-1»: от 6,9 до 9 мбар[2];
+ По данным поÑадочного аппарата «Mars Pathfinder»: от 6,7 мбар[29].
+
+Ð’ меÑте поÑадки зонда ÐМС «МарÑ-6» в районе ЭритрейÑкого Ð¼Ð¾Ñ€Ñ Ð±Ñ‹Ð»Ð¾ зафикÑировано давление у поверхноÑти 6,1 мбар, что на тот момент ÑчиталоÑÑŒ Ñредним давлением на планете, и от Ñтого ÑƒÑ€Ð¾Ð²Ð½Ñ Ð±Ñ‹Ð»Ð¾ уÑловлено отÑчитывать выÑоÌÑ‚Ñ‹ и глубиÌны на МарÑе. По данным Ñтого аппарата, полученным во Ð²Ñ€ÐµÐ¼Ñ ÑпуÑка, тропопауза находитÑÑ Ð½Ð° выÑоте примерно 30 км, где давление ÑоÑтавлÑет 5×10−7 г/Ñм³ (как на Земле на выÑоте 57 км)[32].
+Ð£Ð´Ð°Ñ€Ð½Ð°Ñ Ð²Ð¿Ð°Ð´Ð¸Ð½Ð° Эллада — Ñамое глубокое меÑто МарÑа, где можно зафикÑировать Ñамое выÑокое атмоÑферное давление.
+
+ОблаÑÑ‚ÑŒ Эллада наÑтолько глубока, что атмоÑферное давление доÑтигает примерно 12,4 мбар[14], что выше тройной точки воды (около 6,1 мбар)[33], поÑтому при доÑтаточно выÑокой температуре вода могла бы ÑущеÑтвовать там в жидком ÑоÑтоÑнии; при таком давлении, однако, вода закипает и превращаетÑÑ Ð² пар уже при +10 °C[14].
+
+Ðа вершине выÑочайшей горы МарÑа, 27-километрового вулкана Олимп, давление может ÑоÑтавлÑÑ‚ÑŒ от 0,5 до 1 мбар[33].
+
+До выÑадки на поверхноÑÑ‚ÑŒ МарÑа поÑадочных модулей давление было измерено за Ñчёт оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð´Ð¸Ð¾Ñигналов Ñ ÐМС «Маринер-4», «Маринер-6», «Маринер-7» и «Маринер-9» при их захождении за марÑианÑкий диÑк и выходе из-за марÑианÑкого диÑка — 6,5±2,0 мбар на Ñреднем уровне поверхноÑти, что в 160 раз меньше земного; такой же результат показали Ñпектральные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐМС «МарÑ-3». При Ñтом в раÑположенных ниже Ñреднего ÑƒÑ€Ð¾Ð²Ð½Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑÑ… (например, в марÑианÑкой Ðмазонии) давление, ÑоглаÑно Ñтим измерениÑм, доÑтигает 12 мбар[34].
+
+ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1930-Ñ… годов, ÑоветÑкие аÑтрономы пыталиÑÑŒ определÑÑ‚ÑŒ давление атмоÑферы методами фотографичеÑкой фотометрии — по раÑпределению ÑркоÑти вдоль диаметра диÑка в разных диапазонах Ñветовых волн. ФранцузÑкие учёные Б. Лио и О. Ð”Ð¾Ð»ÑŒÑ„ÑŽÑ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ð»Ð¸ Ñ Ñтой целью Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñризации раÑÑеÑнного атмоÑферой МарÑа Ñвета. Сводку оптичеÑких наблюдений опубликовал американÑкий аÑтроном Ж. де Вокулёр в 1951 году, и по ним получалоÑÑŒ давление 85\мбар, завышенное почти в 15 раз, поÑкольку не было отдельно учтено раÑÑеÑние Ñвета пылью, взвешенной в атмоÑфере МарÑа. Вклад пыли был припиÑан газовой атмоÑфере[35].
+Климат[править | править вики-текÑÑ‚]
+Циклон возле Ñеверного полюÑа МарÑа, Ñнимки Ñ Ñ‚ÐµÐ»ÐµÑкопа «Хаббл» (27 Ð°Ð¿Ñ€ÐµÐ»Ñ 1999 года).
+
+Климат, как и на Земле, ноÑит Ñезонный характер. Угол наклона МарÑа к плоÑкоÑти орбиты почти равен земному и ÑоÑтавлÑет 25,1919°[5]; ÑоответÑтвенно, на МарÑе, так же как и на Земле, проиÑходит Ñмена времён года. ОÑобенноÑтью марÑианÑкого климата также ÑвлÑетÑÑ Ñ‚Ð¾, что ÑкÑцентриÑитет орбиты МарÑа значительно больше земного, и на климат также влиÑет раÑÑтоÑние до Солнца. Перигелий ÐœÐ°Ñ€Ñ Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¸Ñ‚ во Ð²Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð·Ð³Ð°Ñ€Ð° зимы в Ñеверном полушарии и лета в южном, афелий — во Ð²Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð·Ð³Ð°Ñ€Ð° зимы в южном полушарии и ÑоответÑтвенно лета в Ñеверном. Ð’ÑледÑтвие Ñтого климат Ñеверного и южного полушарий различаетÑÑ. Ð”Ð»Ñ Ñеверного Ð¿Ð¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ñ Ñ…Ð°Ñ€Ð°ÐºÑ‚ÐµÑ€Ð½Ñ‹ более мÑÐ³ÐºÐ°Ñ Ð·Ð¸Ð¼Ð° и прохладное лето; в южном полушарии зима более холоднаÑ, а лето более жаркое[36]. Ð’ холодное Ð²Ñ€ÐµÐ¼Ñ Ð³Ð¾Ð´Ð° даже вне полÑрных шапок на поверхноÑти может образовыватьÑÑ Ñветлый иней. Ðппарат «ФеникÑ» зафикÑировал Ñнегопад, однако Ñнежинки иÑпарÑлиÑÑŒ, не доÑÑ‚Ð¸Ð³Ð°Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти[37].
+
+По ÑведениÑм ÐÐСР(2004 год), ÑреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° ÑоÑтавлÑет ~210 K (−63 °C). По данным поÑадочных аппаратов «Викинг», Ñуточный температурный диапазон ÑоÑтавлÑет от 184 K до 242 K (от −89 до −31 °C) («Викинг-1»), а ÑкороÑÑ‚ÑŒ ветра 2—7 м/Ñ (лето), 5—10 м/Ñ (оÑень), 17—30 м/Ñ (пылевой шторм)[2].
+
+По данным поÑадочного зонда «МарÑ-6», ÑреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° тропоÑферы МарÑа ÑоÑтавлÑет 228 K, в тропоÑфере температура убывает в Ñреднем на 2,5 градуÑа на километр, а находÑщаÑÑÑ Ð²Ñ‹ÑˆÐµ тропопаузы (30 км) ÑтратоÑфера имеет почти поÑтоÑнную температуру 144 K[32].
+
+ИÑÑледователи из Центра имени Карла Сагана в 2007—2008 годах пришли к выводу, что в поÑледние деÑÑÑ‚Ð¸Ð»ÐµÑ‚Ð¸Ñ Ð½Ð° МарÑе идёт процеÑÑ Ð¿Ð¾Ñ‚ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ. СпециалиÑÑ‚Ñ‹ ÐÐСРподтвердили Ñту гипотезу на оÑнове анализа изменений альбедо разных чаÑтей планеты. Другие ÑпециалиÑÑ‚Ñ‹ Ñчитают, что такие выводы делать пока рано[38][39]. Ð’ мае 2016 года иÑÑледователи из Юго-Западного иÑÑледовательÑкого инÑтитута в Боулдере (Колорадо) опубликовали в журнале Science Ñтатью, в которой предъÑвили новые доказательÑтва идущего Ð¿Ð¾Ñ‚ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð¸Ð¼Ð°Ñ‚Ð° (на оÑнове анализа данных Mars Reconnaissance Orbiter). По их мнению, Ñтот процеÑÑ Ð´Ð»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ð¹ и идёт, возможно, уже в течение 370 Ñ‚Ñ‹Ñ. лет.[40]
+
+СущеÑтвуют предположениÑ, что в прошлом атмоÑфера могла быть более плотной, а климат — тёплым и влажным, и на поверхноÑти МарÑа ÑущеÑтвовала Ð¶Ð¸Ð´ÐºÐ°Ñ Ð²Ð¾Ð´Ð° и шли дожди[41][42]. ДоказательÑтвом Ñтой гипотезы ÑвлÑетÑÑ Ð°Ð½Ð°Ð»Ð¸Ð· метеорита ALH 84001, показавший, что около 4 миллиардов лет назад температура МарÑа ÑоÑтавлÑла 18 ± 4 °C[43].
+
+Главной оÑобенноÑтью общей циркулÑции атмоÑферы МарÑа ÑвлÑÑŽÑ‚ÑÑ Ñ„Ð°Ð·Ð¾Ð²Ñ‹Ðµ переходы углекиÑлого газа в полÑрных шапках, приводÑщие к значительным меридиональным потокам. ЧиÑленное моделирование общей циркулÑции атмоÑферы МарÑа[44] указывает на ÑущеÑтвенный годовой ход Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ Ð´Ð²ÑƒÐ¼Ñ Ð¼Ð¸Ð½Ð¸Ð¼ÑƒÐ¼Ð°Ð¼Ð¸ незадолго перед равноденÑтвиÑми, что подтверждаетÑÑ Ð¸ наблюдениÑми по программе «Викинг». Ðнализ данных о давлении[45] выÑвил годовой и полугодовой циклы. ИнтереÑно, что, как и на Земле, макÑимум полугодовых колебаний зональной ÑкороÑти ветра Ñовпадает Ñ Ñ€Ð°Ð²Ð½Ð¾Ð´ÐµÐ½ÑтвиÑми[46]. ЧиÑленное моделирование[44] выÑвлÑет также и ÑущеÑтвенный цикл индекÑа Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¾Ð¼ 4—6 Ñуток в периоды ÑолнцеÑтоÑний. «Викингом» обнаружено подобие цикла индекÑа на МарÑе Ñ Ð°Ð½Ð°Ð»Ð¾Ð³Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ колебаниÑми в атмоÑферах других планет.
+Пылевые бури и пыльные вихри[править | править вики-текÑÑ‚]
+
+ВеÑеннее таÑние полÑрных шапок приводит к резкому повышению Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферы и перемещению больших маÑÑ Ð³Ð°Ð·Ð° в противоположное полушарие. СкороÑÑ‚ÑŒ дующих при Ñтом ветров ÑоÑтавлÑет 10—40 м/Ñ, иногда до 100 м/Ñ. Ветер поднимает Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти большое количеÑтво пыли, что приводит к пылевым бурÑм. Сильные пылевые бури практичеÑки полноÑтью Ñкрывают поверхноÑÑ‚ÑŒ планеты. Пылевые бури оказывают заметное воздейÑтвие на раÑпределение температуры в атмоÑфере МарÑа[47].
+Фотографии МарÑа, на которых видна Ð¿Ñ‹Ð»ÑŒÐ½Ð°Ñ Ð±ÑƒÑ€Ñ (июнь — ÑентÑбрь 2001).
+
+22 ÑентÑÐ±Ñ€Ñ 1971 года в Ñветлой облаÑти Noachis в южном полушарии началаÑÑŒ Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ. К 29 ÑентÑÐ±Ñ€Ñ Ð¾Ð½Ð° охватила двеÑти градуÑов по долготе от Ausonia до Thaumasia, а 30 ÑентÑÐ±Ñ€Ñ Ð·Ð°ÐºÑ€Ñ‹Ð»Ð° южную полÑрную шапку. Ð‘ÑƒÑ€Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð°Ð»Ð° бушевать вплоть до Ð´ÐµÐºÐ°Ð±Ñ€Ñ 1971 года, когда на орбиту МарÑа прибыли ÑоветÑкие Ñтанции «МарÑ-2» и «МарÑ-3». «МарÑы» проводили Ñъёмку поверхноÑти, но пыль полноÑтью Ñкрывала рельеф — не видно было даже горы Олимп, возвышающейÑÑ Ð½Ð° 27 км. Ð’ одном из ÑеанÑов Ñъёмки была получена Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð³Ð¾ диÑка МарÑа Ñ Ñ‡Ñ‘Ñ‚ÐºÐ¾ выраженным тонким Ñлоем марÑианÑких облаков над пылью. Во Ð²Ñ€ÐµÐ¼Ñ Ñтих иÑÑледований в декабре 1971 года Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ Ð¿Ð¾Ð´Ð½Ñла в атмоÑферу Ñтолько пыли, что планета выглÑдела мутным краÑноватым диÑком. Только примерно к 10 ÑÐ½Ð²Ð°Ñ€Ñ 1972 года Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ Ð¿Ñ€ÐµÐºÑ€Ð°Ñ‚Ð¸Ð»Ð°ÑÑŒ, и ÐœÐ°Ñ€Ñ Ð¿Ñ€Ð¸Ð½Ñл обычный вид[48].
+Пыльные вихри, Ñфотографированные марÑоходом «Спирит» 15 Ð¼Ð°Ñ 2005 года. Цифры в левом нижнем углу отображают Ð²Ñ€ÐµÐ¼Ñ Ð² Ñекундах Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° первого кадра.
+
+ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1970-Ñ… годов, в рамках программы «Викинг», а также марÑоходом «Спирит» и другими аппаратами были зафикÑированы многочиÑленные пыльные вихри. Это воздушные завихрениÑ, возникающие у поверхноÑти планеты и поднимающие в воздух большое количеÑтво пеÑка и пыли. Вихри чаÑто наблюдаютÑÑ Ð¸ на Земле (в англоÑзычных Ñтранах их называют «пыльными демонами» — англ. dust devil), однако на МарÑе они могут доÑтигать гораздо больших размеров: в 10 раз выше и в 50 раз шире земных. Ð’ марте 2005 года такой вихрь очиÑтил Ñолнечные батареи у марÑохода «Спирит»[49][50].
+ПоверхноÑÑ‚ÑŒ[править | править вики-текÑÑ‚]
+ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ПоверхноÑÑ‚ÑŒ МарÑа
+ОÑновные регионы[править | править вики-текÑÑ‚]
+Иней на поверхноÑти МарÑа (Ñнимок марÑианÑкой Ñтанции «Викинг-2», 18 Ð¼Ð°Ñ 1979 года).
+
+УчаÑток кратера ГуÑева (мозаика Ñнимков марÑохода «Спирит»).
+
+ТопографичеÑÐºÐ°Ñ ÐºÐ°Ñ€Ñ‚Ð° МарÑа, по данным Mars Global Surveyor (1999). Ðулевой меридиан МарÑа принÑÑ‚ проходÑщим через кратер Эйри-0.
+
+Две трети поверхноÑти МарÑа занимают Ñветлые облаÑти, получившие название материков, около трети — тёмные учаÑтки, называемые морÑми. ÐœÐ¾Ñ€Ñ ÑоÑредоточены главным образом в южном полушарии планеты, между 10 и 40° широты. Ð’ Ñеверном полушарии еÑÑ‚ÑŒ только два крупных Ð¼Ð¾Ñ€Ñ â€” ÐцидалийÑкое и Большой Сирт.
+
+Характер тёмных учаÑтков до Ñих пор оÑтаётÑÑ Ð¿Ñ€ÐµÐ´Ð¼ÐµÑ‚Ð¾Ð¼ Ñпоров. Они ÑохранÑÑŽÑ‚ÑÑ, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° то, что на МарÑе бушуют пылевые бури. Ð’ Ñвоё Ð²Ñ€ÐµÐ¼Ñ Ñто Ñлужило доводом в пользу предположениÑ, что тёмные учаÑтки покрыты раÑтительноÑтью. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ð¾Ð»Ð°Ð³Ð°ÑŽÑ‚, что Ñто проÑто учаÑтки, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ…, в Ñилу их рельефа, легко выдуваетÑÑ Ð¿Ñ‹Ð»ÑŒ. КрупномаÑштабные Ñнимки показывают, что на Ñамом деле тёмные учаÑтки ÑоÑтоÑÑ‚ из групп тёмных Ð¿Ð¾Ð»Ð¾Ñ Ð¸ пÑтен, ÑвÑзанных Ñ ÐºÑ€Ð°Ñ‚ÐµÑ€Ð°Ð¼Ð¸, холмами и другими препÑÑ‚ÑтвиÑми на пути ветров. Сезонные и долговременные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ñ… размера и формы ÑвÑзаны, по-видимому, Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸ÐµÐ¼ ÑÐ¾Ð¾Ñ‚Ð½Ð¾ÑˆÐµÐ½Ð¸Ñ ÑƒÑ‡Ð°Ñтков поверхноÑти, покрытых Ñветлым и тёмным вещеÑтвом.
+
+ÐŸÐ¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ñ ÐœÐ°Ñ€Ñа довольно Ñильно различаютÑÑ Ð¿Ð¾ характеру поверхноÑти. Ð’ южном полушарии поверхноÑÑ‚ÑŒ находитÑÑ Ð½Ð° 1—2 км над Ñредним уровнем и гуÑто уÑеÑна кратерами. Эта чаÑÑ‚ÑŒ МарÑа напоминает лунные материки. Ðа Ñевере Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ñ‡Ð°ÑÑ‚ÑŒ поверхноÑти находитÑÑ Ð½Ð¸Ð¶Ðµ Ñреднего уровнÑ, здеÑÑŒ мало кратеров, и оÑновную чаÑÑ‚ÑŒ занимают отноÑительно гладкие равнины, вероÑтно, образовавшиеÑÑ Ð² результате Ð·Ð°Ñ‚Ð¾Ð¿Ð»ÐµÐ½Ð¸Ñ Ð»Ð°Ð²Ð¾Ð¹ и Ñрозии. Такое различие полушарий оÑтаётÑÑ Ð¿Ñ€ÐµÐ´Ð¼ÐµÑ‚Ð¾Ð¼ диÑкуÑÑий. Граница между полушариÑми Ñледует примерно по большому кругу, наклонённому на 30° к Ñкватору. Граница ÑˆÐ¸Ñ€Ð¾ÐºÐ°Ñ Ð¸ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð¸ образует Ñклон в направлении на Ñевер. Вдоль неё вÑтречаютÑÑ Ñамые Ñродированные учаÑтки марÑианÑкой поверхноÑти.
+
+Выдвинуто две альтернативных гипотезы, объÑÑнÑющих аÑимметрию полушарий. СоглаÑно одной из них, на раннем геологичеÑком Ñтапе литоÑферные плиты «ÑъехалиÑь» (возможно, Ñлучайно) в одно полушарие, подобно континенту ÐŸÐ°Ð½Ð³ÐµÑ Ð½Ð° Земле, а затем «заÑтыли» в Ñтом положении. Ð”Ñ€ÑƒÐ³Ð°Ñ Ð³Ð¸Ð¿Ð¾Ñ‚ÐµÐ·Ð° предполагает Ñтолкновение МарÑа Ñ ÐºÐ¾ÑмичеÑким телом размером Ñ ÐŸÐ»ÑƒÑ‚Ð¾Ð½[51].
+
+Большое количеÑтво кратеров в южном полушарии предполагает, что поверхноÑÑ‚ÑŒ здеÑÑŒ древнÑÑ â€” 3—4 млрд. лет. ВыделÑÑŽÑ‚ неÑколько типов кратеров: большие кратеры Ñ Ð¿Ð»Ð¾Ñким дном, более мелкие и молодые чашеобразные кратеры, похожие на лунные, кратеры, окружённые валом, и возвышенные кратеры. ПоÑледние два типа уникальны Ð´Ð»Ñ ÐœÐ°Ñ€Ñа — кратеры Ñ Ð²Ð°Ð»Ð¾Ð¼ образовалиÑÑŒ там, где по поверхноÑти текли жидкие выброÑÑ‹, а возвышенные кратеры образовалиÑÑŒ там, где покрывало выброÑов кратера защитило поверхноÑÑ‚ÑŒ от ветровой Ñрозии. Самой крупной деталью ударного проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÑвлÑетÑÑ Ñ€Ð°Ð²Ð½Ð¸Ð½Ð° Эллада (примерно 2100 км в поперечнике[52]).
+
+Ð’ облаÑти хаотичеÑкого ландшафта вблизи границы полушарий поверхноÑÑ‚ÑŒ иÑпытала разломы и ÑÐ¶Ð°Ñ‚Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ñ… учаÑтков, за которыми иногда Ñледовала ÑÑ€Ð¾Ð·Ð¸Ñ (вÑледÑтвие оползней или катаÑтрофичеÑкого выÑÐ²Ð¾Ð±Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð·ÐµÐ¼Ð½Ñ‹Ñ… вод), а также затопление жидкой лавой. ХаотичеÑкие ландшафты чаÑто находÑÑ‚ÑÑ Ñƒ иÑтока больших каналов, прорезанных водой. Ðаиболее приемлемой гипотезой их ÑовмеÑтного Ð¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑвлÑетÑÑ Ð²Ð½ÐµÐ·Ð°Ð¿Ð½Ð¾Ðµ таÑние подповерхноÑтного льда.
+Долины Маринер на МарÑе.
+
+Ð’ Ñеверном полушарии, помимо обширных вулканичеÑких равнин, находÑÑ‚ÑÑ Ð´Ð²Ðµ облаÑти крупных вулканов — ФарÑида и Элизий. ФарÑида — Ð¾Ð±ÑˆÐ¸Ñ€Ð½Ð°Ñ Ð²ÑƒÐ»ÐºÐ°Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ñ€Ð°Ð²Ð½Ð¸Ð½Ð° протÑжённоÑтью 2000 км, доÑÑ‚Ð¸Ð³Ð°ÑŽÑ‰Ð°Ñ Ð²Ñ‹Ñоты 10 км над Ñредним уровнем. Ðа ней находÑÑ‚ÑÑ Ñ‚Ñ€Ð¸ крупных щитовых вулкана — гора ÐÑ€ÑиÑ, гора Павлина и гора ÐÑкрийÑкаÑ. Ðа краю ФарÑиды находитÑÑ Ð²Ñ‹ÑÐ¾Ñ‡Ð°Ð¹ÑˆÐ°Ñ Ð½Ð° МарÑе и выÑÐ¾Ñ‡Ð°Ð¹ÑˆÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð² Солнечной ÑиÑтеме[9] гора Олимп. Олимп доÑтигает 27 км выÑоты по отношению к его оÑнованию[9] и 25 км по отношению к Ñреднему уровню поверхноÑти МарÑа, и охватывает площадь 550 км диаметром, окружённую обрывами, меÑтами доÑтигающими 7 км выÑоты. Объём Олимпа в 10 раз превышает объём крупнейшего вулкана Земли Мауна-Кеа. ЗдеÑÑŒ же раÑположено неÑколько менее крупных вулканов. Элизий — возвышенноÑÑ‚ÑŒ до шеÑти километров над Ñредним уровнем, Ñ Ñ‚Ñ€ÐµÐ¼Ñ Ð²ÑƒÐ»ÐºÐ°Ð½Ð°Ð¼Ð¸ — купол Гекаты, гора Элизий и купол Ðльбор.
+
+По другим данным, выÑота Олимпа ÑоÑтавлÑет 21 287 метров над нулевым уровнем и 18 километров над окружающей меÑтноÑтью, а диаметр оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ â€” примерно 600 км. ОÑнование охватывает площадь 282 600 км²[53]. Кальдера (углубление в центре вулкана) имеет ширину 70 км и глубину 3 км[54].
+
+ВозвышенноÑÑ‚ÑŒ ФарÑида также переÑечена множеÑтвом тектоничеÑких разломов, чаÑто очень Ñложных и протÑжённых. Крупнейший из них — долины Маринер — Ñ‚ÑнетÑÑ Ð² широтном направлении почти на 4000 км (четверть окружноÑти планеты), доÑÑ‚Ð¸Ð³Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ñ‹ 600 и глубины 7—10 км[55][56]; по размерам Ñтот разлом Ñравним Ñ Ð’Ð¾ÑточноафриканÑким рифтом на Земле. Ðа его крутых Ñклонах проиÑходÑÑ‚ крупнейшие в Солнечной ÑиÑтеме оползни. Долины Маринер ÑвлÑÑŽÑ‚ÑÑ Ñамым большим извеÑтным каньоном в Солнечной ÑиÑтеме. Каньон, который был открыт коÑмичеÑким аппаратом «Маринер-9» в 1971 году, мог бы занÑÑ‚ÑŒ вÑÑŽ территорию СШÐ, от океана до океана.
+Панорама ударного кратера Ð’Ð¸ÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¸Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ около 800 метров, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити». Панорама ÑоÑтавлена из Ñнимков, которые были получены за три недели, в период Ñ 16 октÑÐ±Ñ€Ñ Ð¿Ð¾ 6 ноÑÐ±Ñ€Ñ 2006.
+Панорама ударного кратера Ð’Ð¸ÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¸Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ около 800 метров, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити». Панорама ÑоÑтавлена из Ñнимков, которые были получены за три недели, в период Ñ 16 октÑÐ±Ñ€Ñ Ð¿Ð¾ 6 ноÑÐ±Ñ€Ñ 2006.
+Панорама поверхноÑти МарÑа в районе Husband Hill, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Спирит» 23-28 ноÑÐ±Ñ€Ñ 2005.
+Панорама поверхноÑти МарÑа в районе Husband Hill, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Спирит» 23-28 ноÑÐ±Ñ€Ñ 2005.
+Лёд и полÑрные шапки[править | править вики-текÑÑ‚]
+Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¿Ð¾Ð»ÑÑ€Ð½Ð°Ñ ÑˆÐ°Ð¿ÐºÐ° в летний период, фото ÐœÐ°Ñ€Ñ Ð“Ð»Ð¾Ð±Ð°Ð» Сервейор. Длинный широкий разлом, раÑÑекающий шапку Ñлева — Каньон Северный.
+
+Внешний вид МарÑа Ñильно изменÑетÑÑ Ð² завиÑимоÑти от времени года. Прежде вÑего, броÑаютÑÑ Ð² глаза Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñрных шапок. Они разраÑтаютÑÑ Ð¸ уменьшаютÑÑ, ÑÐ¾Ð·Ð´Ð°Ð²Ð°Ñ Ñезонные ÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð² атмоÑфере и на поверхноÑти МарÑа. ПолÑрные шапки в макÑимуме разраÑÑ‚Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ доÑтигать широты 50°. Диаметр поÑтоÑнной чаÑти Ñеверной полÑрной шапки ÑоÑтавлÑет 1000 км[57]. По мере того, как веÑной полÑÑ€Ð½Ð°Ñ ÑˆÐ°Ð¿ÐºÐ° в одном из полушарий отÑтупает, детали поверхноÑти планеты начинают темнеть.
+
+Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¸ Ð®Ð¶Ð½Ð°Ñ Ð¿Ð¾Ð»Ñрные шапки ÑоÑтоÑÑ‚ из двух ÑоÑтавлÑющих: Ñезонной — углекиÑлого газа[57] и вековой — водÑного льда[58]. По данным Ñо Ñпутника «МарÑ-ÑкÑпреÑÑ», толщина шапок может ÑоÑтавлÑÑ‚ÑŒ от 1 м до 3,7 км. Ðппарат Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» обнаружил на южной полÑрной шапке МарÑа дейÑтвующие гейзеры. Как Ñчитают ÑпециалиÑÑ‚Ñ‹ ÐÐСÐ, Ñтруи углекиÑлого газа Ñ Ð²ÐµÑенним потеплением вырываютÑÑ Ð²Ð²ÐµÑ€Ñ… на большую выÑоту, уноÑÑ Ñ Ñобой пыль и пеÑок[59][60].
+
+Ð’ 1784 году аÑтроном У. Гершель обратил внимание на Ñезонные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° полÑрных шапок, по аналогии Ñ Ñ‚Ð°Ñнием и намерзанием льдов в земных полÑрных облаÑÑ‚ÑÑ…[61]. Ð’ 1860-Ñ… годах французÑкий аÑтроном Э. ЛÑи наблюдал волну Ð¿Ð¾Ñ‚ÐµÐ¼Ð½ÐµÐ½Ð¸Ñ Ð²Ð¾ÐºÑ€ÑƒÐ³ тающей веÑенней полÑрной шапки, что тогда было иÑтолковано как раÑтекание талых вод и развитие раÑтительноÑти. СпектрометричеÑкие измерениÑ, которые были проведены в начале XX века в обÑерватории Ловелла во ФлагÑтаффе Ð’. Слайфером, однако, не показали Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ð»Ð¸Ð½Ð¸Ð¸ хлорофилла — зелёного пигмента земных раÑтений[62].
+
+По фотографиÑм «Маринера-7» удалоÑÑŒ определить, что полÑрные шапки имеют толщину в неÑколько метров, а Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° 115 K (−158 °C) подтвердила возможноÑÑ‚ÑŒ того, что она ÑоÑтоит из замёрзшей углекиÑлоты — «Ñухого льда»[63].
+
+ВозвышенноÑÑ‚ÑŒ, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ð»Ð° название гор Митчелла, раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ð°Ñ Ð±Ð»Ð¸Ð· южного полюÑа МарÑа, при таÑнии полÑрной шапки выглÑдит как белый оÑтровок, поÑкольку в горах ледники тают позднее, в том чиÑле и на Земле[64].
+
+Данные аппарата Mars Reconnaissance Orbiter позволили обнаружить под камениÑтыми оÑыпÑми у Ð¿Ð¾Ð´Ð½Ð¾Ð¶Ð¸Ñ Ð³Ð¾Ñ€ значительный Ñлой льда. Ледник толщиной в Ñотни метров занимает площадь в Ñ‚Ñ‹ÑÑчи квадратных километров, и его дальнейшее изучение ÑпоÑобно дать информацию об иÑтории марÑианÑкого климата[65][66].
+РуÑла «рек» и другие оÑобенноÑти[править | править вики-текÑÑ‚]
+Дельта выÑохшей реки в кратере ЭберÑвальде (фото Mars Global Surveyor).
+ÐœÐ¸ÐºÑ€Ð¾Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ†Ð¸Ð¸ гематита в марÑианÑком грунте, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити» 2 марта 2004 года (поле Ð·Ñ€ÐµÐ½Ð¸Ñ 1,3 Ñм), что ÑвидетельÑтвует о приÑутÑтвии в геологичеÑком прошлом воды в жидком ÑоÑтоÑнии[67].
+Так Ð½Ð°Ð·Ñ‹Ð²Ð°ÐµÐ¼Ð°Ñ Â«Ñ‡Ñ‘Ñ€Ð½Ð°Ñ Ð´Ñ‹Ñ€Ð°Â» (колодец) диаметром более 150 м на поверхноÑти МарÑа. Видна чаÑÑ‚ÑŒ боковой Ñтенки. Склон горы ÐÑ€ÑÐ¸Ñ (фото «МарÑианÑкого разведывательного Ñпутника»).
+ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ГидроÑфера МарÑа
+
+Ðа МарÑе имеетÑÑ Ð¼Ð½Ð¾Ð¶ÐµÑтво геологичеÑких образований, напоминающих водную Ñрозию, в чаÑтноÑти, выÑохшие руÑла рек. СоглаÑно одной из гипотез, Ñти руÑла могли ÑформироватьÑÑ Ð² результате кратковременных катаÑтрофичеÑких Ñобытий и не ÑвлÑÑŽÑ‚ÑÑ Ð´Ð¾ÐºÐ°Ð·Ð°Ñ‚ÐµÐ»ÑŒÑтвом длительного ÑущеÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÑ‡Ð½Ð¾Ð¹ ÑиÑтемы. Однако поÑледние данные ÑвидетельÑтвуют о том, что реки текли в течение геологичеÑки значимых промежутков времени. Ð’ чаÑтноÑти, обнаружены инвертированные руÑла (то еÑÑ‚ÑŒ руÑла, приподнÑтые над окружающей меÑтноÑтью). Ðа Земле подобные Ð¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð¾Ñ€Ð¼Ð¸Ñ€ÑƒÑŽÑ‚ÑÑ Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ð´Ð»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¼Ñƒ накоплению плотных донных отложений Ñ Ð¿Ð¾Ñледующим выÑыханием и выветриванием окружающих пород. Кроме того, еÑÑ‚ÑŒ ÑвидетельÑтва ÑÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ñ€ÑƒÑел в дельте реки при поÑтепенном поднÑтии поверхноÑти[68].
+
+Ð’ юго-западном полушарии, в кратере ЭберÑвальде обнаружена дельта реки площадью около 115 км²[69]. ÐÐ°Ð¼Ñ‹Ð²ÑˆÐ°Ñ Ð´ÐµÐ»ÑŒÑ‚Ñƒ река имела в длину более 60 км[70].
+
+Данные марÑоходов ÐÐСР«Спирит» и «Оппортьюнити» ÑвидетельÑтвуют также о наличии воды в прошлом (найдены минералы, которые могли образоватьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ в результате длительного воздейÑÑ‚Ð²Ð¸Ñ Ð²Ð¾Ð´Ñ‹). Ðппарат «ФеникÑ» обнаружил залежи льда непоÑредÑтвенно в грунте.
+
+Кроме того, обнаружены тёмные полоÑÑ‹ на Ñклонах холмов, ÑвидетельÑтвующие о поÑвлении жидкой Ñолёной воды на поверхноÑти в наше времÑ. Они поÑвлÑÑŽÑ‚ÑÑ Ð²Ñкоре поÑле наÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð»ÐµÑ‚Ð½ÐµÐ³Ð¾ периода и иÑчезают к зиме, «обтекают» различные препÑÑ‚ÑтвиÑ, ÑливаютÑÑ Ð¸ раÑходÑÑ‚ÑÑ. «Сложно предÑтавить, что подобные Ñтруктуры могли ÑформироватьÑÑ Ð½Ðµ из потоков жидкоÑти, а из чего-то иного», — заÑвил Ñотрудник ÐÐСРРичард Зурек[71]. Дальнейший Ñпектральный анализ показал приÑутÑтвие в указанных облаÑÑ‚ÑÑ… перхлоратов — Ñолей, ÑпоÑобных обеÑпечить ÑущеÑтвование жидкой воды в уÑловиÑÑ… марÑианÑкого давлениÑ[72][73].
+
+28 ÑентÑÐ±Ñ€Ñ 2012 года на МарÑе обнаружены Ñледы переÑохшего водного потока. Об Ñтом объÑвили ÑпециалиÑÑ‚Ñ‹ американÑкого коÑмичеÑкого агентÑтва ÐÐСРпоÑле Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ð¹, полученных Ñ Ð¼Ð°Ñ€Ñохода «КьюриоÑити», на тот момент работавшего на планете лишь Ñемь недель. Речь идёт о фотографиÑÑ… камней, которые, по мнению учёных, Ñвно подвергалиÑÑŒ воздейÑтвию воды[74].
+
+Ðа вулканичеÑкой возвышенноÑти ФарÑида обнаружено неÑколько необычных глубоких колодцев. Ð¡ÑƒÐ´Ñ Ð¿Ð¾ Ñнимку аппарата «МарÑианÑкий разведывательный Ñпутник», Ñделанному в 2007 году, один из них имеет диаметр 150 метров, а оÑÐ²ÐµÑ‰Ñ‘Ð½Ð½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ Ñтенки уходит в глубину не менее чем на 178 метров. Ð’Ñ‹Ñказана гипотеза о вулканичеÑком проиÑхождении Ñтих образований[75].
+
+Ðа МарÑе имеетÑÑ Ð½ÐµÐ¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¹ регион — Лабиринт Ðочи, предÑтавлÑющий Ñобой ÑиÑтему переÑекающихÑÑ ÐºÐ°Ð½ÑŒÐ¾Ð½Ð¾Ð²[76]. Их образование не было ÑвÑзано Ñ Ð²Ð¾Ð´Ð½Ð¾Ð¹ Ñрозией, и вероÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° поÑÐ²Ð»ÐµÐ½Ð¸Ñ â€” тектоничеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ[77][78]. Когда ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð±Ð»Ð¸Ð·Ð¸ перигелиÑ, над лабиринтом Ðочи и долинами Маринера поÑвлÑÑŽÑ‚ÑÑ Ð²Ñ‹Ñокие (40—50 км) облака. ВоÑточный ветер вытÑгивает их вдоль Ñкватора и ÑноÑит к западу, где они поÑтепенно размываютÑÑ. Их длина доÑтигает неÑкольких Ñотен (до Ñ‚Ñ‹ÑÑчи) километров, а ширина — неÑкольких деÑÑтков. СоÑтоÑÑ‚ они, ÑÑƒÐ´Ñ Ð¿Ð¾ уÑловиÑм в Ñтих ÑлоÑÑ… атмоÑферы, тоже из водÑного льда. Они довольно гуÑтые и отбраÑывают на поверхноÑÑ‚ÑŒ хорошо заметные тени. Их поÑвление объÑÑнÑÑŽÑ‚ тем, что неровноÑти рельефа вноÑÑÑ‚ Ð²Ð¾Ð·Ð¼ÑƒÑ‰ÐµÐ½Ð¸Ñ Ð² воздушные потоки, направлÑÑ Ð¸Ñ… вверх. Там они охлаждаютÑÑ, а ÑодержащийÑÑ Ð² них водÑной пар конденÑируетÑÑ[79].
+Грунт[править | править вики-текÑÑ‚]
+Ð¤Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкого грунта в меÑте поÑадки аппарата «ФеникÑ».
+
+Элементный ÑоÑтав поверхноÑтного ÑÐ»Ð¾Ñ Ð³Ñ€ÑƒÐ½Ñ‚Ð°, определённый по данным поÑадочных аппаратов, неодинаков в разных меÑтах. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑоÑтавлÑÑŽÑ‰Ð°Ñ Ð¿Ð¾Ñ‡Ð²Ñ‹ — кремнезём (20—25 %), Ñодержащий примеÑÑŒ гидратов окÑидов железа (до 15 %), придающих почве краÑноватый цвет. ИмеютÑÑ Ð·Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ðµ примеÑи Ñоединений Ñеры, кальциÑ, алюминиÑ, магниÑ, Ð½Ð°Ñ‚Ñ€Ð¸Ñ (единицы процентов Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾)[80][81].
+
+СоглаÑно данным зонда ÐÐСР«ФеникÑ» (поÑадка на ÐœÐ°Ñ€Ñ 25 Ð¼Ð°Ñ 2008 года), Ñоотношение pH и некоторые другие параметры марÑианÑких почв близки к земным, и на них теоретичеÑки можно было бы выращивать раÑтениÑ[82][83]. «ФактичеÑки мы обнаружили, что почва на МарÑе отвечает требованиÑм, а также Ñодержит необходимые Ñлементы Ð´Ð»Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸ как в прошлом, так и в наÑтоÑщем и будущем», Ñообщил ведущий иÑÑледователь-химик проекта СÑм КунейвÑ[84]. Также, по его Ñловам, данный щелочной тип грунта (pH = 7,7) многие могут вÑтретить на «Ñвоём заднем дворе», и он вполне пригоден Ð´Ð»Ñ Ð²Ñ‹Ñ€Ð°Ñ‰Ð¸Ð²Ð°Ð½Ð¸Ñ Ñпаржи[85].
+
+Ð’ меÑте поÑадки аппарата в грунте имеетÑÑ Ñ‚Ð°ÐºÐ¶Ðµ значительное количеÑтво водÑного льда[86]. Орбитальный зонд Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» также обнаружил, что под поверхноÑтью краÑной планеты еÑÑ‚ÑŒ залежи водÑного льда[87]. Позже Ñто предположение было подтверждено и другими аппаратами, но окончательно Ð²Ð¾Ð¿Ñ€Ð¾Ñ Ð¾ наличии воды на МарÑе был решён в 2008 году, когда зонд «ФеникÑ», Ñевший вблизи Ñеверного полюÑа планеты, получил воду из марÑианÑкого грунта[15][88].
+
+Данные, полученные марÑоходом Curiosity и обнародованные в ÑентÑбре 2013 года, показали, что Ñодержание воды под поверхноÑтью МарÑа гораздо выше, чем ÑчиталоÑÑŒ ранее. Ð’ породе, из которой брал образцы марÑоход, её Ñодержание может доÑтигать 2 % по веÑу[89].
+Ð“ÐµÐ¾Ð»Ð¾Ð³Ð¸Ñ Ð¸ внутреннее Ñтроение[править | править вики-текÑÑ‚]
+
+Ð’ прошлом на МарÑе, как и на Земле, проиÑходило движение литоÑферных плит. Это подтверждаетÑÑ Ð¾ÑобенноÑÑ‚Ñми магнитного Ð¿Ð¾Ð»Ñ ÐœÐ°Ñ€Ñа, меÑтами раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… вулканов, например, в провинции ФарÑида, а также формой долины Маринер[90]. Современное положение дел, когда вулканы могут ÑущеÑтвовать гораздо более длительное времÑ, чем на Земле, и доÑтигать гигантÑких размеров, говорит о том, что ÑÐµÐ¹Ñ‡Ð°Ñ Ð´Ð°Ð½Ð½Ð¾Ðµ движение Ñкорее отÑутÑтвует. Ð’ пользу Ñтого говорит тот факт, что щитовые вулканы раÑтут в результате повторных извержений из одного и того же жерла в течение длительного времени. Ðа Земле из-за Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ‚Ð¾Ñферных плит вулканичеÑкие точки поÑтоÑнно менÑли Ñвоё положение, что ограничивало роÑÑ‚ щитовых вулканов и, возможно, не позволÑло доÑтичь им такой выÑоты, как на МарÑе. С другой Ñтороны, разница в макÑимальной выÑоте вулканов может объÑÑнÑÑ‚ÑŒÑÑ Ñ‚ÐµÐ¼, что из-за меньшей Ñилы Ñ‚ÑжеÑти на МарÑе возможно поÑтроение более выÑоких Ñтруктур, которые не обрушилиÑÑŒ бы под ÑобÑтвенным веÑом[91]. Возможно, на планете имеетÑÑ ÑÐ»Ð°Ð±Ð°Ñ Ñ‚ÐµÐºÑ‚Ð¾Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ, приводÑÑ‰Ð°Ñ Ðº образованию наблюдаемых Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ пологих каньонов[92][93].
+Сравнение ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и других планет земной группы
+
+Современные модели внутреннего ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа предполагают, что ÐœÐ°Ñ€Ñ ÑоÑтоит из коры Ñо Ñредней толщиной 50 км (макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾Ñ†ÐµÐ½ÐºÐ° — не более 125 км)[94], Ñиликатной мантии и Ñдра радиуÑом, по разным оценкам, от 1480[94] до 1800 км[95]. ПлотноÑÑ‚ÑŒ в центре планеты должна доÑтигать 8,5 г/Ñм³. Ядро чаÑтично жидкое и ÑоÑтоит в оÑновном из железа Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑью 14—18 % (по маÑÑе) Ñеры[95], причём Ñодержание лёгких Ñлементов вдвое выше, чем в Ñдре Земли. СоглаÑно Ñовременным оценкам, формирование Ñдра Ñовпало Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¾Ð¼ раннего вулканизма и продолжалоÑÑŒ около миллиарда лет. Примерно то же Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð½Ñло чаÑтичное плавление мантийных Ñиликатов[91]. Из-за меньшей Ñилы Ñ‚ÑжеÑти на МарÑе диапазон давлений в мантии МарÑа гораздо меньше, чем на Земле, а значит, в ней меньше фазовых переходов. ПредполагаетÑÑ, что фазовый переход оливина в шпинелевую модификацию начинаетÑÑ Ð½Ð° довольно больших глубинах — 800 км (400 км на Земле). Характер рельефа и другие признаки позволÑÑŽÑ‚ предположить наличие аÑтеноÑферы, ÑоÑтоÑщей из зон чаÑтично раÑплавленного вещеÑтва[96]. Ð”Ð»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… районов МарÑа ÑоÑтавлена Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð³ÐµÐ¾Ð»Ð¾Ð³Ð¸Ñ‡ÐµÑÐºÐ°Ñ ÐºÐ°Ñ€Ñ‚Ð°[97].
+
+СоглаÑно наблюдениÑм Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ и анализу коллекции марÑианÑких метеоритов, поверхноÑÑ‚ÑŒ МарÑа ÑоÑтоит главным образом из базальта. ЕÑÑ‚ÑŒ некоторые оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ´Ð¿Ð¾Ð»Ð°Ð³Ð°Ñ‚ÑŒ, что на чаÑти марÑианÑкой поверхноÑти материал ÑвлÑетÑÑ Ð±Ð¾Ð»ÐµÐµ кварцеÑодержащим, чем обычный базальт, и может быть подобен андезитным камнÑм на Земле. Однако Ñти же Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð¶Ð½Ð¾ толковать в пользу Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ ÐºÐ²Ð°Ñ€Ñ†ÐµÐ²Ð¾Ð³Ð¾ Ñтекла. Ð—Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ более глубокого ÑÐ»Ð¾Ñ ÑоÑтоит из зерниÑтой пыли окÑида железа[98][99].
+Магнитное поле[править | править вики-текÑÑ‚]
+
+У МарÑа было зафикÑировано Ñлабое магнитное поле.
+
+СоглаÑно показаниÑм магнетометров Ñтанций «МарÑ-2» и «МарÑ-3», напрÑжённоÑÑ‚ÑŒ магнитного Ð¿Ð¾Ð»Ñ Ð½Ð° Ñкваторе ÑоÑтавлÑет около 60 гамм, на полюÑе — 120 гамм, что в 500 раз Ñлабее земного. По данным ÐМС «МарÑ-5», напрÑжённоÑÑ‚ÑŒ магнитного Ð¿Ð¾Ð»Ñ Ð½Ð° Ñкваторе ÑоÑтавлÑла 64 гаммы, а магнитный момент планетарного Ð´Ð¸Ð¿Ð¾Ð»Ñ â€” 2,4×1022 ÑÑ€Ñтед·Ñм²[100].
+Магнитное поле МарÑа
+
+Магнитное поле МарÑа крайне неуÑтойчиво, в различных точках планеты его напрÑжённоÑÑ‚ÑŒ может отличатьÑÑ Ð¾Ñ‚ 1,5 до 2 раз, а магнитные полюÑа не Ñовпадают Ñ Ñ„Ð¸Ð·Ð¸Ñ‡ÐµÑкими. Это говорит о том, что железное Ñдро МарÑа находитÑÑ Ð² Ñравнительной неподвижноÑти по отношению к его коре, то еÑÑ‚ÑŒ механизм планетарного динамо, ответÑтвенный за магнитное поле Земли, на МарÑе не работает. Ð¥Ð¾Ñ‚Ñ Ð½Ð° МарÑе не имеетÑÑ ÑƒÑтойчивого вÑепланетного магнитного полÑ[101], Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ð¸, что чаÑти планетной коры намагничены и что наблюдалаÑÑŒ Ñмена магнитных полюÑов Ñтих чаÑтей в прошлом. ÐамагниченноÑÑ‚ÑŒ данных чаÑтей оказалаÑÑŒ похожей на полоÑовые магнитные аномалии в мировом океане[102].
+
+По одной теории, опубликованной в 1999 году и перепроверенной в 2005 году (Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ беÑпилотной Ñтанции Â«ÐœÐ°Ñ€Ñ Ð“Ð»Ð¾Ð±Ð°Ð» Сервейор»), Ñти полоÑÑ‹ демонÑтрируют тектонику плит 4 миллиарда лет назад — до того, как динамо-машина планеты прекратила выполнÑÑ‚ÑŒ Ñвою функцию, что поÑлужило причиной резкого оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð¼Ð°Ð³Ð½Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ полÑ[103]. Причины такого резкого оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð½ÐµÑÑны. СущеÑтвует предположение, что функционирование динамо-машины 4 млрд. лет назад объÑÑнÑетÑÑ Ð½Ð°Ð»Ð¸Ñ‡Ð¸ÐµÐ¼ аÑтероида, который вращалÑÑ Ð½Ð° раÑÑтоÑнии 50—75 Ñ‚Ñ‹ÑÑч километров вокруг МарÑа и вызывал неÑтабильноÑÑ‚ÑŒ в его Ñдре. Затем аÑтероид ÑнизилÑÑ Ð´Ð¾ предела Роша и разрушилÑÑ[104]. Тем не менее, Ñто объÑÑнение Ñамо Ñодержит неÑÑные моменты и оÑпариваетÑÑ Ð² научном ÑообщеÑтве[105].
+Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð¾Ð·Ð°Ð¸ÐºÐ° из 102 Ñнимков, полученных иÑкуÑÑтвенным Ñпутником МарÑа «Викинг-1» 22 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 1980
+ГеологичеÑÐºÐ°Ñ Ð¸ÑториÑ[править | править вики-текÑÑ‚]
+
+СоглаÑно одной из гипотез, в далёком прошлом в результате ÑÑ‚Ð¾Ð»ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ñ ÐºÑ€ÑƒÐ¿Ð½Ñ‹Ð¼ небеÑным телом произошла оÑтановка Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñдра[106], а также Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¾Ñновного объёма атмоÑферы. ÐŸÐ¾Ñ‚ÐµÑ€Ñ Ð»ÐµÐ³ÐºÐ¸Ñ… атомов и молекул из атмоÑферы — ÑледÑтвие Ñлабого притÑÐ¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа. СчитаетÑÑ, что Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¼Ð°Ð³Ð½Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° около 4 млрд. лет назад. Ð’ÑледÑтвие ÑлабоÑти магнитного Ð¿Ð¾Ð»Ñ Ñолнечный ветер практичеÑки беÑпрепÑÑ‚Ñтвенно проникает в атмоÑферу МарÑа, и многие из фотохимичеÑких реакций под дейÑтвием Ñолнечной радиации, которые на Земле проиÑходÑÑ‚ в ионоÑфере и выше, на МарÑе могут наблюдатьÑÑ Ð¿Ñ€Ð°ÐºÑ‚Ð¸Ñ‡ÐµÑки у Ñамой его поверхноÑти.
+
+ГеологичеÑÐºÐ°Ñ Ð¸ÑÑ‚Ð¾Ñ€Ð¸Ñ ÐœÐ°Ñ€Ñа заключает в ÑÐµÐ±Ñ Ñ‚Ñ€Ð¸ нижеÑледующие Ñпохи[107][108]:
+
+ÐойÑÐºÐ°Ñ Ñра[109] (названа в чеÑÑ‚ÑŒ «Ðоевой земли», района МарÑа): формирование наиболее Ñтарой ÑохранившейÑÑ Ð´Ð¾ наших дней поверхноÑти МарÑа. ПродолжалаÑÑŒ в период 4,5—3,5 млрд. лет назад. Ð’ Ñту Ñпоху поверхноÑÑ‚ÑŒ была изрубцована многочиÑленными ударными кратерами. Плато провинции ФарÑида было, вероÑтно, Ñформировано в Ñтот период Ñ Ð¸Ð½Ñ‚ÐµÐ½Ñивным обтеканием водой позднее.
+
+ГеÑперийÑÐºÐ°Ñ Ñра: от 3,5 млрд. лет назад до 2,9—3,3 млрд. лет назад. Эта Ñпоха отмечена образованием огромных лавовых полей.
+
+ÐмазонийÑÐºÐ°Ñ Ñра (названа в чеÑÑ‚ÑŒ «ÐмазонÑкой равнины» на МарÑе): 2,9—3,3 млрд. лет назад до наших дней. Районы, образовавшиеÑÑ Ð² Ñту Ñпоху, имеют очень мало метеоритных кратеров, но во вÑём оÑтальном они полноÑтью различаютÑÑ. Гора Олимп Ñформирована в Ñтот период. Ð’ Ñто Ð²Ñ€ÐµÐ¼Ñ Ð² других чаÑÑ‚ÑÑ… МарÑа разливалиÑÑŒ лавовые потоки.
+Спутники[править | править вики-текÑÑ‚]
+ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: Спутники МарÑа
+См. также: ТроÑнÑкие аÑтероиды МарÑа
+
+ ФобоÑ, ÑнÑтый 23 марта 2008 года Ñпутником Mars Reconnaissance Orbiter
+
+ ДеймоÑ, ÑнÑтый 21 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 2009 года Ñпутником Mars Reconnaissance Orbiter
+
+ Прохождение ФобоÑа по диÑку Солнца. Снимки «Оппортьюнити»
+
+ЕÑтеÑтвенными Ñпутниками МарÑа ÑвлÑÑŽÑ‚ÑÑ Ð¤Ð¾Ð±Ð¾Ñ Ð¸ ДеймоÑ. Оба они открыты американÑким аÑтрономом ÐÑафом Холлом в 1877 году. Ð¤Ð¾Ð±Ð¾Ñ Ð¸ Ð”ÐµÐ¹Ð¼Ð¾Ñ Ð¸Ð¼ÐµÑŽÑ‚ неправильную форму и очень маленькие размеры. По одной из гипотез, они могут предÑтавлÑÑ‚ÑŒ Ñобой захваченные гравитационным полем МарÑа аÑтероиды наподобие (5261) Эврика из ТроÑнÑкой группы аÑтероидов. Спутники названы в чеÑÑ‚ÑŒ перÑонажей, Ñопровождающих бога ÐреÑа (то еÑÑ‚ÑŒ МарÑа), — ФобоÑа и ДеймоÑа, олицетворÑющих Ñтрах и ужаÑ, которые помогали богу войны в битвах[110].
+
+Оба Ñпутника вращаютÑÑ Ð²Ð¾ÐºÑ€ÑƒÐ³ Ñвоих оÑей Ñ Ñ‚ÐµÐ¼ же периодом, что и вокруг МарÑа, поÑтому вÑегда повёрнуты к планете одной и той же Ñтороной (Ñто вызвано Ñффектом приливного захвата и характерно Ð´Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ð½Ñтва Ñпутников планет в Солнечной ÑиÑтеме, в том чиÑле Ð´Ð»Ñ Ð›ÑƒÐ½Ñ‹). Приливное воздейÑтвие МарÑа поÑтепенно замедлÑет движение ФобоÑа, и, в конце концов, приведёт к падению Ñпутника на ÐœÐ°Ñ€Ñ (при Ñохранении текущей тенденции), или к его раÑпаду[111]. Ðапротив, Ð”ÐµÐ¹Ð¼Ð¾Ñ ÑƒÐ´Ð°Ð»ÑетÑÑ Ð¾Ñ‚ МарÑа.
+
+Орбитальный период ФобоÑа меньше, чем период Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа, поÑтому Ð´Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° поверхноÑти планеты Ð¤Ð¾Ð±Ð¾Ñ (в отличие от ДеймоÑа и вообще от вÑех извеÑтных еÑтеÑтвенных Ñпутников планет Солнечной ÑиÑтемы, кроме Метиды и ÐдраÑтеи) воÑходит на западе и заходит на воÑтоке[111].
+
+Оба Ñпутника имеют форму, приближающуюÑÑ Ðº трёхоÑному ÑллипÑоиду, Ð¤Ð¾Ð±Ð¾Ñ (26,8×22,4×18,4 км)[6] неÑколько крупнее ДеймоÑа (15×12,2×11 км)[112]. ПоверхноÑÑ‚ÑŒ ДеймоÑа выглÑдит гораздо более гладкой за Ñчёт того, что большинÑтво кратеров покрыто тонкозерниÑтым вещеÑтвом. Очевидно, на ФобоÑе, более близком к планете и более маÑÑивном, вещеÑтво, выброшенное при ударах метеоритов, либо наноÑило повторные удары по поверхноÑти, либо падало на МарÑ, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº на ДеймоÑе оно долгое Ð²Ñ€ÐµÐ¼Ñ Ð¾ÑтавалоÑÑŒ на орбите вокруг Ñпутника, поÑтепенно оÑаждаÑÑÑŒ и ÑÐºÑ€Ñ‹Ð²Ð°Ñ Ð½ÐµÑ€Ð¾Ð²Ð½Ð¾Ñти рельефа.
+Жизнь[править | править вики-текÑÑ‚]
+ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: Жизнь на МарÑе
+ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ð¾Ð¿Ñ€Ð¾Ñа[править | править вики-текÑÑ‚]
+
+ПопулÑÑ€Ð½Ð°Ñ Ð¸Ð´ÐµÑ, что ÐœÐ°Ñ€Ñ Ð½Ð°Ñелён разумными марÑианами, широко раÑпроÑтранилаÑÑŒ в конце XIX века.
+
+ÐÐ°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¡ÐºÐ¸Ð°Ð¿Ð°Ñ€ÐµÐ»Ð»Ð¸ так называемых каналов, в Ñочетании Ñ ÐºÐ½Ð¸Ð³Ð¾Ð¹ ПерÑÐ¸Ð²Ð°Ð»Ñ Ð›Ð¾ÑƒÑлла по той же теме Ñделали популÑрной идею о планете, климат которой ÑтановилÑÑ Ð²ÑÑ‘ Ñуше, холоднее, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑƒÐ¼Ð¸Ñ€Ð°Ð»Ð° и на которой ÑущеÑтвовала древнÑÑ Ñ†Ð¸Ð²Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ, выполнÑÑŽÑ‰Ð°Ñ Ð¸Ñ€Ñ€Ð¸Ð³Ð°Ñ†Ð¸Ð¾Ð½Ð½Ñ‹Ðµ работы[113].
+
+ Карта МарÑа Скиапарелли, 1888 г.
+
+ МарÑианÑкие каналы, зариÑованные аÑтрономом П. ЛоуÑллом, 1898.
+
+Другие многочиÑленные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¸ объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð·Ð²ÐµÑтных лиц породили вокруг Ñтой темы так называемую «МарÑианÑкую лихорадку» (англ. Mars Fever)[114]. Ð’ 1899 году во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферных радиопомех Ñ Ð¸Ñпользованием приёмников в КолорадÑкой обÑерватории, изобретатель Ðикола ТеÑла наблюдал повторÑющийÑÑ Ñигнал. Он выÑказал догадку, что Ñто может быть радиоÑигнал Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… планет, например, МарÑа. Ð’ интервью 1901 года ТеÑла Ñказал, что ему пришла в голову мыÑль о том, что помехи могут быть вызваны иÑкуÑÑтвенно. Ð¥Ð¾Ñ‚Ñ Ð¾Ð½ не Ñмог раÑшифровать их значение, Ð´Ð»Ñ Ð½ÐµÐ³Ð¾ было невозможным то, что они возникли Ñовершенно Ñлучайно. По его мнению, Ñто было приветÑтвие одной планеты другой[115].
+
+Гипотеза ТеÑлы вызвала горÑчую поддержку извеÑтного британÑкого учёного-физика УильÑма ТомÑона (лорда Кельвина), который, поÑетив СШРв 1902 году, Ñказал, что, по его мнению, ТеÑла поймал Ñигнал марÑиан, поÑланный в СШÐ[116]. Однако ещё до Ð¾Ñ‚Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸Ð· Ðмерики Кельвин Ñтал решительно отрицать Ñто заÑвление: «Ðа Ñамом деле Ñ Ñказал, что жители МарÑа, еÑли они ÑущеÑтвуют, неÑомненно могут видеть Ðью-Йорк, в чаÑтноÑти, Ñвет от ÑлектричеÑтва»[117].
+ФактичеÑкие данные[править | править вики-текÑÑ‚]
+
+Ðаучные гипотезы о ÑущеÑтвовании в прошлом жизни на МарÑе приÑутÑтвуют давно. По результатам наблюдений Ñ Ð—ÐµÐ¼Ð»Ð¸ и данным коÑмичеÑкого аппарата «МарÑ-ÑкÑпреÑÑ» в атмоÑфере МарÑа обнаружен метан. Позднее, в 2014 году, марÑоход ÐÐСРCuriosity зафикÑировал вÑплеÑк ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð½Ð° в атмоÑфере МарÑа и обнаружил органичеÑкие молекулы в образцах, извлечённых в ходе Ð±ÑƒÑ€ÐµÐ½Ð¸Ñ Ñкалы Камберленд.[118]
+РаÑпределение метана в атмоÑфере МарÑа в летний период в Ñеверном полушарии.
+
+Ð’ уÑловиÑÑ… МарÑа Ñтот газ довольно быÑтро разлагаетÑÑ, поÑтому должен ÑущеÑтвовать поÑтоÑнный иÑточник его пополнениÑ. Таким иÑточником может быть либо геологичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ (но дейÑтвующие вулканы на МарÑе не обнаружены), либо жизнедеÑтельноÑÑ‚ÑŒ бактерий. ИнтереÑно, что в некоторых метеоритах марÑианÑкого проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ñ‹ образованиÑ, по форме напоминающие клетки, Ñ…Ð¾Ñ‚Ñ Ð¾Ð½Ð¸ и уÑтупают мельчайшим земным организмам по размерам[118][119]. Одним из таких метеоритов ÑвлÑетÑÑ ALH 84001, найденный в Ðнтарктиде в 1984 году.
+ALH84001 под микроÑкопом.
+
+Важные Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñделаны марÑоходом «Curiosity». Ð’ декабре 2012 года были получены данные о наличии на МарÑе органичеÑких вещеÑтв, а также перхлоратов. Те же иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ð¸ наличие водÑного пара в нагретых образцах грунта[120]. ИнтереÑным фактом ÑвлÑетÑÑ Ñ‚Ð¾, что «Curiosity» на МарÑе приземлилÑÑ Ð½Ð° дно выÑохшего озера[121].
+
+Ðнализ наблюдений говорит, что планета ранее имела значительно более благоприÑтные Ð´Ð»Ñ Ð¶Ð¸Ð·Ð½Ð¸ уÑловиÑ, нежели теперь. СоглаÑно программе «Викинг», оÑущеÑтвлённой в Ñередине 1970-Ñ… годов, была проведена ÑÐµÑ€Ð¸Ñ ÑкÑпериментов Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð¼Ð¾Ð² в марÑианÑкой почве. Она дала положительные результаты: например, временное увеличение Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ CO2 при помещении чаÑтиц почвы в воду и питательную Ñреду. Однако затем данное ÑвидетельÑтво жизни на МарÑе было оÑпорено учёными команды «Викингов»[122]. Это привело к их продолжительным Ñпорам Ñ ÑƒÑ‡Ñ‘Ð½Ñ‹Ð¼ из NASA Гильбертом Левиным, который утверждал, что «Викинг» обнаружил жизнь. ПоÑле переоценки данных «Викинга» в Ñвете Ñовременных научных знаний об ÑкÑтремофилах было уÑтановлено, что проведённые ÑкÑперименты были недоÑтаточно Ñовершенны Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ñтих форм жизни. Более того, Ñти теÑÑ‚Ñ‹ могли убить организмы, даже еÑли поÑледние ÑодержалиÑÑŒ в пробах[123]. ТеÑÑ‚Ñ‹, проведённые в рамках программы «ФеникÑ», показали, что почва имеет очень щелочной pH и Ñодержит магний, натрий, калий и хлориды[124]. Питательных вещеÑтв в почве доÑтаточно Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸, однако жизненные формы должны иметь защиту от интенÑивного ультрафиолетового Ñвета[125].
+
+Ðа ÑегоднÑшний день уÑловием Ð´Ð»Ñ Ñ€Ð°Ð·Ð²Ð¸Ñ‚Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸ на планете ÑчитаетÑÑ Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ðµ жидкой воды на её поверхноÑти, а также нахождение орбиты планеты в так называемой зоне обитаемоÑти, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð² Солнечной ÑиÑтеме начинаетÑÑ Ð·Ð° орбитой Венеры и заканчиваетÑÑ Ð±Ð¾Ð»ÑŒÑˆÐ¾Ð¹ полуоÑью орбиты МарÑа[126]. Вблизи Ð¿ÐµÑ€Ð¸Ð³ÐµÐ»Ð¸Ñ ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð½ÑƒÑ‚Ñ€Ð¸ Ñтой зоны, однако Ñ‚Ð¾Ð½ÐºÐ°Ñ Ð°Ñ‚Ð¼Ð¾Ñфера Ñ Ð½Ð¸Ð·ÐºÐ¸Ð¼ давлением препÑÑ‚Ñтвует поÑвлению жидкой воды на длительный период. Ðедавние ÑвидетельÑтва говорÑÑ‚ о том, что Ð»ÑŽÐ±Ð°Ñ Ð²Ð¾Ð´Ð° на поверхноÑти МарÑа ÑвлÑетÑÑ Ñлишком Ñолёной и киÑлотной Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¾ÑтоÑнной земноподобной жизни[127].
+
+ОтÑутÑтвие магнитоÑферы и крайне Ñ€Ð°Ð·Ñ€ÐµÐ¶Ñ‘Ð½Ð½Ð°Ñ Ð°Ñ‚Ð¼Ð¾Ñфера МарÑа также ÑвлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¾Ð¹ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸. Ðа поверхноÑти планеты идёт очень Ñлабое перемещение тепловых потоков, она плохо изолирована от бомбардировки чаÑтицами Ñолнечного ветра; помимо Ñтого, при нагревании вода мгновенно иÑпарÑетÑÑ, Ð¼Ð¸Ð½ÑƒÑ Ð¶Ð¸Ð´ÐºÐ¾Ðµ ÑоÑтоÑние из-за низкого давлениÑ. Кроме того, ÐœÐ°Ñ€Ñ Ñ‚Ð°ÐºÐ¶Ðµ находитÑÑ Ð½Ð° пороге Ñ‚. н. «геологичеÑкой Ñмерти». Окончание вулканичеÑкой активноÑти, по вÑей видимоÑти, оÑтановило круговорот минералов и химичеÑких Ñлементов между поверхноÑтью и внутренней чаÑтью планеты[128].
+Терраформированный ÐœÐ°Ñ€Ñ Ð² предÑтавлении художника.
+
+БлизоÑÑ‚ÑŒ МарÑа и отноÑительное его ÑходÑтво Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹ породило Ñ€Ñд фантаÑтичеÑких проектов Ñ‚ÐµÑ€Ñ€Ð°Ñ„Ð¾Ñ€Ð¼Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ колонизации МарÑа землÑнами в будущем.
+
+МарÑоход Curiosity обнаружил Ñразу два иÑточника органичеÑких молекул на поверхноÑти МарÑа. Помимо кратковременного ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¸ метана в атмоÑфере, аппарат зафикÑировал наличие углеродных Ñоединений в порошкообразном образце, оÑтавшемÑÑ Ð¾Ñ‚ Ð±ÑƒÑ€ÐµÐ½Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкой Ñкалы. Первое открытие позволил Ñделать инÑтрумент SAM на борту марÑохода. За 20 меÑÑцев он 12 раз измерил ÑоÑтав марÑианÑкой атмоÑферы. Ð’ двух ÑлучаÑÑ… — в конце 2013 года и начале 2014 — Curiosity удалоÑÑŒ обнаружить деÑÑтикратное увеличение Ñредней доли метана. Этот вÑплеÑк, по мнению членов научной команды марÑохода, ÑвидетельÑтвует об обнаружении локального иÑточника метана. Имеет ли он биологичеÑкое или же иное проиÑхождение, ÑпециалиÑÑ‚Ñ‹ утверждать затруднÑÑŽÑ‚ÑÑ Ð²ÑледÑтвие нехватки данных Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ñ†ÐµÐ½Ð½Ð¾Ð³Ð¾ анализа.
+ÐÑтрономичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти МарÑа[править | править вики-текÑÑ‚]
+
+ПоÑле поÑадок автоматичеÑких аппаратов на поверхноÑÑ‚ÑŒ МарÑа поÑвилаÑÑŒ возможноÑÑ‚ÑŒ веÑти аÑтрономичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð½ÐµÐ¿Ð¾ÑредÑтвенно Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти планеты. Ð’ÑледÑтвие аÑтрономичеÑкого Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа в Солнечной ÑиÑтеме, характериÑтик атмоÑферы, периода Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и его Ñпутников картина ночного неба МарÑа (и аÑтрономичеÑких Ñвлений, наблюдаемых Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹) отличаетÑÑ Ð¾Ñ‚ земной и во многом предÑтавлÑетÑÑ Ð½ÐµÐ¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ и интереÑной.
+ÐебеÑÐ½Ð°Ñ Ñфера[править | править вики-текÑÑ‚]
+
+Северный Ð¿Ð¾Ð»ÑŽÑ Ð½Ð° МарÑе, вÑледÑтвие наклона оÑи планеты, находитÑÑ Ð² Ñозвездии Ð›ÐµÐ±ÐµÐ´Ñ (Ñкваториальные координаты: прÑмое воÑхождение 21ч 10м 42Ñ, Ñклонение +52° 53.0′ и не отмечен Ñркой звездой: Ð±Ð»Ð¸Ð¶Ð°Ð¹ÑˆÐ°Ñ Ðº полюÑу — туÑÐºÐ»Ð°Ñ Ð·Ð²ÐµÐ·Ð´Ð° шеÑтой величины BD +52 2880 (другие её Ð¾Ð±Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ â€” HR 8106, HD 201834, SAO 33185). Южный Ð¿Ð¾Ð»ÑŽÑ Ð¼Ð¸Ñ€Ð° (координаты 9ч 10м 42Ñ Ð¸ −52° 53,0) находитÑÑ Ð² паре градуÑов от звезды Каппа ПаруÑов (Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° 2,5) — её, в принципе, можно Ñчитать Южной ПолÑрной звездой МарÑа.
+
+Вид неба похож на наблюдаемый Ñ Ð—ÐµÐ¼Ð»Ð¸, Ñ Ð¾Ð´Ð½Ð¸Ð¼ отличием: при наблюдении годичного Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð¡Ð¾Ð»Ð½Ñ†Ð° по ÑозвездиÑм Зодиака оно (как и другие планеты, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð—ÐµÐ¼Ð»ÑŽ), Ð²Ñ‹Ð¹Ð´Ñ Ð¸Ð· воÑточной чаÑти ÑÐ¾Ð·Ð²ÐµÐ·Ð´Ð¸Ñ Ð Ñ‹Ð±, будет проходить в течение 6 дней через Ñеверную чаÑÑ‚ÑŒ ÑÐ¾Ð·Ð²ÐµÐ·Ð´Ð¸Ñ ÐšÐ¸Ñ‚Ð° перед тем, как Ñнова вÑтупить в западную чаÑÑ‚ÑŒ Рыб.
+
+Во Ð²Ñ€ÐµÐ¼Ñ Ð²Ð¾Ñхода и захода Солнца марÑианÑкое небо в зените имеет краÑновато-розовый цвет[129], а в непоÑредÑтвенной близоÑти к диÑку Солнца — от голубого до фиолетового, что Ñовершенно противоположно картине земных зорь.
+Закат на МарÑе 19 Ð¼Ð°Ñ 2005 года. Снимок марÑохода «Спирит», который находилÑÑ Ð² кратере ГуÑева.
+Закат на МарÑе 19 Ð¼Ð°Ñ 2005 года. Снимок марÑохода «Спирит», который находилÑÑ Ð² кратере ГуÑева.
+
+Ð’ полдень небо МарÑа жёлто-оранжевое. Причина таких отличий от цветовой гаммы земного неба — ÑвойÑтва тонкой, разреженной, Ñодержащей взвешенную пыль атмоÑферы МарÑа. Ðа МарÑе Ñ€ÑлеевÑкое раÑÑеÑние лучей (которое на Земле и ÑвлÑетÑÑ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð¾Ð¹ голубого цвета неба) играет незначительную роль, Ñффект его Ñлаб, но проÑвлÑетÑÑ Ð² виде голубого ÑÐ²ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ воÑходе\закате Солнца, когда Ñвет проходит более толÑтый Ñлой воздуха. Предположительно, жёлто-Ð¾Ñ€Ð°Ð½Ð¶ÐµÐ²Ð°Ñ Ð¾ÐºÑ€Ð°Ñка неба также вызываетÑÑ Ð¿Ñ€Ð¸ÑутÑтвием 1 % магнетита в чаÑтицах пыли, поÑтоÑнно взвешенной в марÑианÑкой атмоÑфере и поднимаемой Ñезонными пылевыми бурÑми. Сумерки начинаютÑÑ Ð·Ð°Ð´Ð¾Ð»Ð³Ð¾ до воÑхода Солнца и длÑÑ‚ÑÑ Ð´Ð¾Ð»Ð³Ð¾ поÑле его захода. Иногда цвет марÑианÑкого неба приобретает фиолетовый оттенок в результате раÑÑеÑÐ½Ð¸Ñ Ñвета на микрочаÑтицах водÑного льда в облаках (поÑледнее — довольно редкое Ñвление)[129].
+Солнце и планеты[править | править вики-текÑÑ‚]
+
+Угловой размер Солнца, наблюдаемый Ñ ÐœÐ°Ñ€Ñа, меньше видимого Ñ Ð—ÐµÐ¼Ð»Ð¸ и ÑоÑтавлÑет 2â„3 от поÑледнего. Меркурий Ñ ÐœÐ°Ñ€Ñа будет практичеÑки недоÑтупен Ð´Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ð¹ невооружённым глазом из-за чрезвычайной близоÑти к Солнцу. Самой Ñркой планетой на небе МарÑа ÑвлÑетÑÑ Ð’ÐµÐ½ÐµÑ€Ð°, на втором меÑте — Юпитер (его четыре крупнейших Ñпутника чаÑÑ‚ÑŒ времени можно наблюдать без телеÑкопа), на третьем — ЗемлÑ[130].
+
+Ð—ÐµÐ¼Ð»Ñ Ð¿Ð¾ отношению к МарÑу ÑвлÑетÑÑ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ¹ планетой, так же, как Венера Ð´Ð»Ñ Ð—ÐµÐ¼Ð»Ð¸. СоответÑтвенно, Ñ ÐœÐ°Ñ€Ñа Ð—ÐµÐ¼Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°ÐµÑ‚ÑÑ ÐºÐ°Ðº утреннÑÑ Ð¸Ð»Ð¸ вечернÑÑ Ð·Ð²ÐµÐ·Ð´Ð°, воÑходÑÑ‰Ð°Ñ Ð¿ÐµÑ€ÐµÐ´ раÑÑветом или Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð½Ð° вечернем небе поÑле захода Солнца.
+
+МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑÐ»Ð¾Ð½Ð³Ð°Ñ†Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸ на небе МарÑа ÑоÑтавлÑет 38 градуÑов. Ð”Ð»Ñ Ð½ÐµÐ²Ð¾Ð¾Ñ€ÑƒÐ¶Ñ‘Ð½Ð½Ð¾Ð³Ð¾ глаза Ð—ÐµÐ¼Ð»Ñ Ð±ÑƒÐ´ÐµÑ‚ видна как ÑÑ€ÐºÐ°Ñ (макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° около −2,5m) Ð·ÐµÐ»ÐµÐ½Ð¾Ð²Ð°Ñ‚Ð°Ñ Ð·Ð²ÐµÐ·Ð´Ð°, Ñ€Ñдом Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ будет легко различима Ð¶ÐµÐ»Ñ‚Ð¾Ð²Ð°Ñ‚Ð°Ñ Ð¸ более туÑÐºÐ»Ð°Ñ (около +0,9m) звёздочка Луны[131]. Ð’ телеÑкоп оба объекта будут видны Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼Ð¸ фазами. Обращение Луны вокруг Земли будет наблюдатьÑÑ Ñ ÐœÐ°Ñ€Ñа Ñледующим образом: на макÑимальном угловом удалении Луны от Земли невооружённый глаз легко разделит Луну и Землю: через неделю «звёздочки» Луны и Земли ÑольютÑÑ Ð² неразделимую глазом единую звезду, ещё через неделю Луна будет Ñнова видна на макÑимальном раÑÑтоÑнии, но уже Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¹ Ñтороны от Земли. ПериодичеÑки наблюдатель на МарÑе Ñможет видеть проход (транзит) Луны по диÑку Земли либо, наоборот, покрытие Луны диÑком Земли. МакÑимальное видимое удаление Луны от Земли (и их Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ ÑркоÑÑ‚ÑŒ) при наблюдении Ñ ÐœÐ°Ñ€Ñа будет значительно изменÑÑ‚ÑŒÑÑ Ð² завиÑимоÑти от взаимного Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸ и МарÑа, и, ÑоответÑтвенно, раÑÑтоÑÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ планетами. Ð’ Ñпохи противоÑтоÑний оно ÑоÑтавит около 17 минут дуги (около половины углового диаметра Солнца и Луны при наблюдении Ñ Ð—ÐµÐ¼Ð»Ð¸), на макÑимальном удалении Земли и МарÑа — 3,5 минуты дуги. ЗемлÑ, как и другие планеты, будет наблюдатьÑÑ Ð² полоÑе Ñозвездий Зодиака. ÐÑтроном на МарÑе также Ñможет наблюдать прохождение Земли по диÑку Солнца; ближайшее такое Ñвление произойдёт 10 ноÑÐ±Ñ€Ñ 2084 года[132].
+ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ[править | править вики-текÑÑ‚]
+ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ИÑÑледование МарÑа
+ИÑÑледование МарÑа клаÑÑичеÑкими методами аÑтрономии[править | править вики-текÑÑ‚]
+Ð˜Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа Ñ Ñ€Ð°Ð·Ð½Ð¾Ð¹ Ñтепенью детализации в разные годы.
+
+Первые Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа проводилиÑÑŒ до Ð¸Ð·Ð¾Ð±Ñ€ÐµÑ‚ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ»ÐµÑкопа. Это были позиционные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ñ Ñ†ÐµÐ»ÑŒÑŽ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹ планеты по отношению к звёздам. СущеÑтвование МарÑа как блуждающего объекта в ночном небе было пиÑьменно заÑвидетельÑтвовано древнеегипетÑкими аÑтрономами в 1534 году до н. Ñ. Ими же было уÑтановлено ретроградное (попÑтное) движение планеты и раÑÑчитана Ñ‚Ñ€Ð°ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¼ÐµÑте Ñ Ñ‚Ð¾Ñ‡ÐºÐ¾Ð¹, где планета менÑет Ñвоё движение отноÑительно Земли Ñ Ð¿Ñ€Ñмого на попÑтное[133].
+
+Ð’ вавилонÑкой планетарной теории были впервые получены временные Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ð°Ñ€Ð½Ð¾Ð³Ð¾ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и уточнено положение планеты на ночном небе[134][135]. ПользуÑÑÑŒ данными египтÑн и вавилонÑн, древнегречеÑкие (ÑллиниÑтичеÑкие) филоÑофы и аÑтрономы разработали подробную геоцентричеÑкую модель Ð´Ð»Ñ Ð¾Ð±ÑŠÑÑÐ½ÐµÐ½Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚. СпуÑÑ‚Ñ Ð½ÐµÑколько веков индийÑкими и иÑламÑкими аÑтрономами был оценен размер МарÑа и раÑÑтоÑние до него от Земли. Ð’ XVI веке Ðиколай Коперник предложил гелиоцентричеÑкую модель Ð´Ð»Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ Ð¡Ð¾Ð»Ð½ÐµÑ‡Ð½Ð¾Ð¹ ÑиÑтемы Ñ ÐºÑ€ÑƒÐ³Ð¾Ð²Ñ‹Ð¼Ð¸ планетарными орбитами. Его результаты были переÑмотрены Иоганном Кеплером, который ввёл более точную ÑллиптичеÑкую орбиту МарÑа, Ñовпадающую Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°ÐµÐ¼Ð¾Ð¹.
+
+ГолландÑкий аÑтроном ХриÑтиан Ð“ÑŽÐ¹Ð³ÐµÐ½Ñ Ð¿ÐµÑ€Ð²Ñ‹Ð¼ ÑоÑтавил карту поверхноÑти МарÑа, отражающую множеÑтво деталей. 28 ноÑÐ±Ñ€Ñ 1659 года он Ñделал неÑколько риÑунков МарÑа, на которых были отображены различные темные облаÑти, позже ÑопоÑтавленные Ñ Ð¿Ð»Ð°Ñ‚Ð¾ Большой Сирт[136].
+
+Предположительно первые наблюдениÑ, уÑтановившие ÑущеÑтвование у МарÑа ледÑной шапки на южном полюÑе, были Ñделаны итальÑнÑким аÑтрономом Джованни Доменико КаÑÑини в 1666 году. Ð’ том же году он при наблюдениÑÑ… МарÑа делал зариÑовки видимых деталей поверхноÑти и выÑÑнил, что через 36 или 37 дней Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´ÐµÑ‚Ð°Ð»ÐµÐ¹ поверхноÑти повторÑÑŽÑ‚ÑÑ, а затем вычиÑлил период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ â€” 24 ч. 40 м. (Ñтот результат отличаетÑÑ Ð¾Ñ‚ правильного Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÐ½ÐµÐµ чем на 3 минуты)[136].
+
+Ð’ 1672 году ХриÑтиан Ð“ÑŽÐ¹Ð³ÐµÐ½Ñ Ð·Ð°Ð¼ÐµÑ‚Ð¸Ð» нечёткую белую шапочку и на Ñеверном полюÑе[137]
+
+Ð’ 1888 году Джованни Скиапарелли дал первые имена отдельным деталÑм поверхноÑти[138]: Ð¼Ð¾Ñ€Ñ Ðфродиты, ЭритрейÑкое, ÐдриатичеÑкое, КиммерийÑкое; озёра Солнца, Лунное и ФеникÑ.
+
+РаÑцвет телеÑкопичеÑких наблюдений МарÑа пришёлÑÑ Ð½Ð° конец XIX — Ñередину XX века. Во многом он обуÑловлен общеÑтвенным интереÑом и извеÑтными научными Ñпорами вокруг наблюдавшихÑÑ Ð¼Ð°Ñ€ÑианÑких каналов. Среди аÑтрономов докоÑмичеÑкой Ñры, проводивших телеÑкопичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа в Ñтот период, наиболее извеÑтны Скиапарелли, ПерÑиваль Ловелл, Слайфер, Ðнтониади, Барнард, Жарри-Делож, Л. Эдди, Тихов, Вокулёр. Именно ими были заложены оÑновы ареографии и ÑоÑтавлены первые подробные карты поверхноÑти МарÑа — Ñ…Ð¾Ñ‚Ñ Ð¾Ð½Ð¸ и оказалиÑÑŒ практичеÑки полноÑтью неверными поÑле полётов к МарÑу автоматичеÑких зондов.
+ИÑÑледование МарÑа коÑмичеÑкими аппаратами[править | править вики-текÑÑ‚]
+Изучение Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ орбитальных телеÑкопов[править | править вики-текÑÑ‚]
+КоÑмичеÑкий телеÑкоп «Хаббл».
+
+Ð”Ð»Ñ ÑиÑтематичеÑкого иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа были иÑпользованы[139] возможноÑти коÑмичеÑкого телеÑкопа «Хаббл» (КТХ или HST — Hubble Space Telescope), при Ñтом были получены фотографии МарÑа Ñ Ñамым выÑоким разрешением из когда-либо Ñделанных на Земле[140]. КТХ может Ñоздать Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ð¹, что позволÑет промоделировать погодные ÑиÑтемы. Ðаземные телеÑкопы, оÑнащенные ПЗС, могут Ñделать Ñ„Ð¾Ñ‚Ð¾Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа выÑокой чёткоÑти, что позволÑет в противоÑтоÑнии регулÑрно проводить мониторинг планетной погоды[141].
+
+РентгеновÑкое излучение Ñ ÐœÐ°Ñ€Ñа, впервые обнаруженное аÑтрономами в 2001 году Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ коÑмичеÑкой рентгеновÑкой обÑерватории «Чандра», ÑоÑтоит из двух компонентов. ÐŸÐµÑ€Ð²Ð°Ñ ÑоÑтавлÑÑŽÑ‰Ð°Ñ ÑвÑзана Ñ Ñ€Ð°ÑÑеиванием в верхней атмоÑфере МарÑа рентгеновÑких лучей Солнца, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ñ€Ð¾Ð¸Ñходит от взаимодейÑÑ‚Ð²Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ ионами Ñ Ð¾Ð±Ð¼ÐµÐ½Ð¾Ð¼ зарÑдами[142].
+ИÑÑледование МарÑа межпланетными ÑтанциÑми[править | править вики-текÑÑ‚]
+
+С 1960-Ñ… годов к МарÑу Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾Ð³Ð¾ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ и Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти были направлены неÑколько автоматичеÑких межпланетных Ñтанций (ÐМС). Кроме того, продолжалоÑÑŒ диÑтанционное зондирование МарÑа Ñ Ð—ÐµÐ¼Ð»Ð¸ в большей чаÑти Ñлектромагнитного Ñпектра Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ наземных и орбитальных телеÑкопов, например, в инфракраÑном Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑоÑтава поверхноÑти[143], в ультрафиолетовом и Ñубмиллиметровом диапазонах — Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÑоÑтава атмоÑферы[144][145], в радиодиапазоне — Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ ÑкороÑти ветра[146].
+СоветÑкие иÑÑледованиÑ[править | править вики-текÑÑ‚]
+Одна из первых цветных фотографий МарÑа, полученных Ñ ÐМС «МарÑ-3».
+
+СоветÑкие иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа включали в ÑÐµÐ±Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñƒ «МарÑ», в рамках которой Ñ 1962 по 1973 год были запущены автоматичеÑкие межпланетные Ñтанции четырёх поколений Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ ÐœÐ°Ñ€Ñ Ð¸ околопланетного проÑтранÑтва. Первые ÐМС («МарÑ-1», «Зонд-2») иÑÑледовали также и межпланетное проÑтранÑтво.
+
+КоÑмичеÑкие аппараты четвёртого Ð¿Ð¾ÐºÐ¾Ð»ÐµÐ½Ð¸Ñ (ÑÐµÑ€Ð¸Ñ Ðœ-71 — «МарÑ-2», «МарÑ-3», запущены в 1971 году) ÑоÑтоÑли из орбитальной Ñтанции — иÑкуÑÑтвенного Ñпутника МарÑа и ÑпуÑкаемого аппарата Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой марÑианÑкой Ñтанцией, комплектовавшейÑÑ Ð¼Ð°Ñ€Ñоходом «ПрОП-М». КоÑмичеÑкие аппараты Ñерии Ðœ-73С «МарÑ-4» и «МарÑ-5» должны были выйти на орбиту вокруг МарÑа и обеÑпечивать ÑвÑзь Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкими марÑианÑкими ÑтанциÑми, которые неÑли ÐМС Ñерии Ðœ-73П «МарÑ-6» и «МарÑ-7»; Ñти четыре ÐМС были запущены в 1973 году.
+
+Из-за неудач ÑпуÑкаемых аппаратов Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ñ‚ÐµÑ…Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° вÑей программы «МарÑ» — проведение иÑÑледований на поверхноÑти планеты Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ автоматичеÑкой марÑианÑкой Ñтанции — не была решена. Тем не менее, многие научные задачи, такие, как получение фотографий поверхноÑти МарÑа и различные Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферы, магнитоÑферы, ÑоÑтава почвы ÑвлÑлиÑÑŒ передовыми Ð´Ð»Ñ Ñвоего времени[147]. Ð’ рамках программы была оÑущеÑтвлена Ð¿ÐµÑ€Ð²Ð°Ñ Ð¼ÑÐ³ÐºÐ°Ñ Ð¿Ð¾Ñадка ÑпуÑкаемого аппарата на поверхноÑÑ‚ÑŒ МарÑа («МарÑ-3», 2 Ð´ÐµÐºÐ°Ð±Ñ€Ñ 1971 года) и Ð¿ÐµÑ€Ð²Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° передачи Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти.
+
+СССР оÑущеÑтвил также программу «ФобоÑ» — две автоматичеÑкие межпланетные Ñтанции, предназначенные Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа и его Ñпутника ФобоÑа.
+
+ÐŸÐµÑ€Ð²Ð°Ñ ÐМС «ФобоÑ-1» была запущена 7 июлÑ, а втораÑ, «ФобоÑ-2» — 12 Ð¸ÑŽÐ»Ñ 1988 года[148]. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° — доÑтавка на поверхноÑÑ‚ÑŒ ФобоÑа ÑпуÑкаемых аппаратов (ПрОП-Ф и ДÐС) Ð´Ð»Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñпутника МарÑа — оÑталаÑÑŒ невыполненной. Однако, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° потерю ÑвÑзи Ñ Ð¾Ð±Ð¾Ð¸Ð¼Ð¸ КÐ, иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа, ФобоÑа и околомарÑианÑкого проÑтранÑтва, выполненные в течение 57 дней на Ñтапе орбитального Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Â«Ð¤Ð¾Ð±Ð¾Ñа-2» вокруг МарÑа, позволили получить новые научные результаты о тепловых характериÑтиках ФобоÑа, плазменном окружении МарÑа, взаимодейÑтвии его Ñ Ñолнечным ветром.
+ÐмериканÑкие иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² XX веке[править | править вики-текÑÑ‚]
+Ð¤Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ñ€Ð°Ð¹Ð¾Ð½Ð° КидониÑ, ÑÐ´ÐµÐ»Ð°Ð½Ð½Ð°Ñ Ñтанцией «Викинг-1» в 1976 году.
+
+Ð’ 1964 году в СШРбыл оÑущеÑтвлён первый удачный запуÑк к МарÑу в рамках программы «Маринер». «Маринер-4» оÑущеÑтвил первое иÑÑледование Ñ Ð¿Ñ€Ð¾Ð»Ñ‘Ñ‚Ð½Ð¾Ð¹ траектории и Ñделал первые Ñнимки поверхноÑти[149]. «Маринер-6» и «Маринер-7», запущенные в 1969 году, произвели Ñ Ð¿Ñ€Ð¾Ð»Ñ‘Ñ‚Ð½Ð¾Ð¹ траектории первое иÑÑледование ÑоÑтава атмоÑферы Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸ÐµÐ¼ ÑпектроÑкопичеÑких методик и определение температуры поверхноÑти по измерениÑм инфракраÑного излучениÑ. Ð’ 1971 году «Маринер-9» Ñтал первым иÑкуÑÑтвенным Ñпутником МарÑа и оÑущеÑтвил первое картографирование поверхноÑти.
+
+Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° СШР— «Викинг» — включала запуÑк в 1975 году двух идентичных коÑмичеÑких аппаратов — «Викинг-1» и «Викинг-2», которые провели иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ Ð¾ÐºÐ¾Ð»Ð¾Ð¼Ð°Ñ€ÑианÑкой орбиты и на поверхноÑти МарÑа, в чаÑтноÑти, поиÑк жизни в пробах грунта. Каждый «Викинг» ÑоÑтоÑл из орбитальной Ñтанции — иÑкуÑÑтвенного Ñпутника МарÑа — и ÑпуÑкаемого аппарата Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой марÑианÑкой Ñтанцией. ÐвтоматичеÑкие марÑианÑкие Ñтанции «Викингов» — первые коÑмичеÑкие аппараты, уÑпешно работавшие на поверхноÑти МарÑа и передавшие фотографии Ñ Ð¼ÐµÑта поÑадки. Жизнь не удалоÑÑŒ обнаружить.
+
+Mars Pathfinder — поÑадочный аппарат ÐÐСÐ, работавший на поверхноÑти в 1996—1997 годах.
+Карта МарÑа
+ОпиÑание изображениÑ
+
+Спирит Спирит
+
+Mars rover msrds simulation.jpg Оппортьюнити
+
+МарÑопроходец Соджорнер
+
+Viking Lander model.jpg
+
+Викинг-1
+
+Viking Lander model.jpg Викинг-2
+
+Ð¤ÐµÐ½Ð¸ÐºÑ Ð¤ÐµÐ½Ð¸ÐºÑ
+
+Mars3 lander vsm.jpg МарÑ-3
+
+КьюриоÑити КьюриоÑити
+
+Maquette EDM salon du Bourget 2013 DSC 0192.JPG
+
+Скиапарелли
+Ð’ наше времÑ[править | править вики-текÑÑ‚]
+
+ Соединённые Штаты Ðмерики Mars Global Surveyor — орбитальный аппарат ÐÐСÐ, оÑущеÑтвлÑвший картографирование поверхноÑти в 1999—2007 годах.
+ Соединённые Штаты Ðмерики «ФеникÑ» — поÑадочный аппарат ÐÐСÐ, работавший на поверхноÑти в 2008 году.
+ Соединённые Штаты Ðмерики «Спирит» — марÑоход, работавший на поверхноÑти в 2004—2010 годах.
+
+Ðа наÑтоÑщий момент (2016 год) на орбитах иÑкуÑÑтвенных Ñпутников МарÑа находÑÑ‚ÑÑ Ð½ÐµÑколько работающих ÐМС:
+
+ Соединённые Штаты Ðмерики Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» (Ñ 24 октÑÐ±Ñ€Ñ 2001 года),
+ Европа «МарÑ-ÑкÑпреÑÑ» (Ñ 25 Ð´ÐµÐºÐ°Ð±Ñ€Ñ 2003 года),
+ Соединённые Штаты Ðмерики «МарÑианÑкий разведывательный Ñпутник» (Ñ 10 марта 2006 года),
+ Соединённые Штаты Ðмерики «MAVEN» (Ñ 21/22 ÑентÑÐ±Ñ€Ñ 2014 года)[150][151],
+ Ð˜Ð½Ð´Ð¸Ñ Â«Mangalyaan» (c 24 ÑентÑÐ±Ñ€Ñ 2014 года)[152].
+ Европа Â«Ð¢Ñ€ÐµÐ¹Ñ Ð“Ð°Ñ ÐžÑ€Ð±Ð¸Ñ‚ÐµÑ€Â» (Ñ 19 октÑÐ±Ñ€Ñ 2016 г.)
+
+Ðа поверхноÑти планеты работают марÑоходы:
+
+ Соединённые Штаты Ðмерики «Оппортьюнити» (Ñ 25 ÑÐ½Ð²Ð°Ñ€Ñ 2004 года),
+ Соединённые Штаты Ðмерики «КьюриоÑити» (Mars Science Laboratory) (Ñ 6 авгуÑта 2012 года).
+
+Кроме того, к МарÑу летит
+
+ РоÑÑÐ¸Ñ Ð•Ð²Ñ€Ð¾Ð¿Ð° ДеÑантный модуль Ñ Ð¿Ð¾Ñадочной платформой «ЭкзомарÑ» (Ñ 14 марта 2016 года).
+
+Ð’ культуре[править | править вики-текÑÑ‚]
+ИллюÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкого треножника из французÑкого Ð¸Ð·Ð´Ð°Ð½Ð¸Ñ Â«Ð’Ð¾Ð¹Ð½Ñ‹ миров» 1906 года.
+Кадр из фильма Я. Протазанова «ÐÑлита» (1924).
+ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ÐœÐ°Ñ€Ñ Ð² культуре
+
+К Ñозданию фантаÑтичеÑких произведений о МарÑе пиÑателей подталкивали начавшиеÑÑ Ð² конце XIX века диÑкуÑÑии учёных о возможноÑти того, что на поверхноÑти МарÑа ÑущеÑтвует не проÑто жизнь, а Ñ€Ð°Ð·Ð²Ð¸Ñ‚Ð°Ñ Ñ†Ð¸Ð²Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ[153]. Ð’ Ñто Ð²Ñ€ÐµÐ¼Ñ Ð±Ñ‹Ð» Ñоздан, например, знаменитый роман Г. УÑллÑа «Война миров», в котором марÑиане пыталиÑÑŒ покинуть Ñвою умирающую планету Ð´Ð»Ñ Ð·Ð°Ð²Ð¾ÐµÐ²Ð°Ð½Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸. Ð’ 1938 году в СШРрадиоверÑÐ¸Ñ Ñтого Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð±Ñ‹Ð»Ð° предÑтавлена в виде новоÑтной радиопередачи, что поÑлужило причиной маÑÑовой паники, когда многие Ñлушатели по ошибке принÑли Ñтот «репортаж» за правду[154]. Ð’ 1966 году пиÑатели Ðркадий и Ð‘Ð¾Ñ€Ð¸Ñ Ð¡Ñ‚Ñ€ÑƒÐ³Ð°Ñ†ÐºÐ¸Ðµ напиÑали ÑатиричеÑкое «продолжение» данного Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´ названием «Второе нашеÑтвие марÑиан».
+
+Ð’ 1917—1964 годах вышло одиннадцать книг о БарÑуме. Так называлаÑÑŒ планета ÐœÐ°Ñ€Ñ Ð² фантаÑтичеÑком мире, Ñозданном Эдгаром РайÑом Берроузом. Ð’ его произведениÑÑ… планета была предÑтавлена как умирающаÑ, жители которой находÑÑ‚ÑÑ Ð² непрерывной войне вÑех Ñо вÑеми за Ñкудные природные реÑурÑÑ‹. Ð’ 1938 году К. Ð›ÑŒÑŽÐ¸Ñ Ð½Ð°Ð¿Ð¸Ñал роман «За пределы безмолвной планеты».
+
+Ð’ чиÑле важных произведений о МарÑе также Ñтоит отметить вышедший в 1950 году роман РÑÑ Ð‘Ñ€Ñдбери «МарÑианÑкие хроники», ÑоÑтоÑщий из отдельных Ñлабо ÑвÑзанных между Ñобой новелл, а также Ñ€Ñд примыкающих к Ñтому циклу раÑÑказов; роман повеÑтвует об Ñтапах оÑÐ²Ð¾ÐµÐ½Ð¸Ñ Ñ‡ÐµÐ»Ð¾Ð²ÐµÐºÐ¾Ð¼ МарÑа и контактах Ñ Ð³Ð¸Ð±Ð½ÑƒÑ‰ÐµÐ¹ древней марÑианÑкой цивилизацией.
+
+Ð’ вымышленной вÑеленной Warhammer 40,000 ÐœÐ°Ñ€Ñ ÑвлÑетÑÑ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ цитаделью Adeptus Mechanicus, первым из миров-кузниц. Фабрики МарÑа, покрывающие вÑÑŽ поверхноÑÑ‚ÑŒ планеты, круглоÑуточно выпуÑкают оружие и боевую технику Ð´Ð»Ñ Ð±ÑƒÑˆÑƒÑŽÑ‰ÐµÐ¹ в Галактике войны.
+
+Примечательно, что Джонатан Свифт упомÑнул о Ñпутниках МарÑа за 150 лет до того, как они были реально открыты, в 19-й чаÑти Ñвоего романа «ПутешеÑÑ‚Ð²Ð¸Ñ Ð“ÑƒÐ»Ð»Ð¸Ð²ÐµÑ€Ð°Â»[155].
diff --git a/xpcom/tests/gtest/wikipedia/th.txt b/xpcom/tests/gtest/wikipedia/th.txt
new file mode 100644
index 0000000000..3341946a13
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/th.txt
@@ -0,0 +1,412 @@
+ดาวอังคาร (อังà¸à¸¤à¸©: Mars) เป็นดาวเคราะห์ลำดับที่สี่จาà¸à¸”วงอาทิตย์ เป็นดาวเคราะห์เล็à¸à¸—ี่สุดอันดับที่สองในระบบสุริยะรองจาà¸à¸”าวพุธ ในภาษาอังà¸à¸¤à¸©à¹„ด้ชื่อตามเทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามของโรมัน มัà¸à¹„ด้รับขนานนาม "ดาวà¹à¸”ง" เพราะมีออà¸à¹„ซด์ของเหล็à¸à¸”าษดื่นบนพื้นผิวทำให้มีสีออà¸à¹à¸”งเรื่อ[15] ดาวอังคารเป็นดาวเคราะห์หินที่มีบรรยาà¸à¸²à¸¨à¹€à¸šà¸²à¸šà¸²à¸‡ มีลัà¸à¸©à¸“ะพื้นผิวคล้ายคลึงà¸à¸±à¸šà¸—ั้งหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”วงจันทร์ à¹à¸¥à¸°à¸ à¸¹à¹€à¸‚าไฟ หุบเขา ทะเลทราย ตลอดจนพิดน้ำà¹à¸‚็งขั้วดาวที่ปราà¸à¸à¸šà¸™à¹‚ลภคาบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¹à¸¥à¸°à¸§à¸±à¸à¸ˆà¸±à¸à¸£à¸¤à¸”ูà¸à¸²à¸¥à¸‚องดาวอังคารà¸à¹‡à¸¡à¸µà¸„วามคล้ายคลึงà¸à¸±à¸šà¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸„วามเอียงà¸à¹ˆà¸­à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”ฤดูà¸à¸²à¸¥à¸•à¹ˆà¸²à¸‡ ๆ ดาวอังคารเป็นที่ตั้งของโอลิมปัสมอนส์ ภูเขาไฟใหà¸à¹ˆà¸—ี่สุดบนดาวอังคารà¹à¸¥à¸°à¸ªà¸¹à¸‡à¸ªà¸¸à¸”อันดับสองในระบบสุริยะเท่าที่มีà¸à¸²à¸£à¸„้นพบ à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸—ี่ตั้งของเวลส์มาริเนริส à¹à¸„นยอนขนาดใหà¸à¹ˆà¸­à¸±à¸™à¸”ับต้น ๆ ในระบบสุริยะ à¹à¸­à¹ˆà¸‡à¸šà¸­à¹€à¸£à¸µà¸¢à¸¥à¸´à¸ªà¸—ี่ราบเรียบในซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวปà¸à¸„ลุมà¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 40 ของพื้นที่ทั้งหมดà¹à¸¥à¸°à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸¥à¸±à¸à¸©à¸“ะà¸à¸²à¸£à¸–ูà¸à¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸Šà¸™à¸„รั้งใหà¸à¹ˆ[16][17] ดาวอังคารมีดาวบริวารสองดวง คือ โฟบอสà¹à¸¥à¸°à¸”ีมอสซึ่งต่างà¸à¹‡à¸¡à¸µà¸‚นาดเล็à¸à¹à¸¥à¸°à¸¡à¸µà¸£à¸¹à¸›à¸£à¹ˆà¸²à¸‡à¸šà¸´à¸”เบี้ยว ทั้งคู่อาจเป็นดาวเคราะห์น้อยที่ถูà¸à¸ˆà¸±à¸šà¹„ว้[18][19] คล้ายà¸à¸±à¸šà¸—รอยของดาวอังคาร เช่น 5261 ยูเรà¸à¸²
+
+à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸à¸²à¸£à¸šà¸´à¸™à¸œà¹ˆà¸²à¸™à¸”าวอังคารที่สำเร็จครั้งà¹à¸£à¸à¸‚อง มาริเนอร์ 4 เมื่อ 1965 หลายคนคาดว่ามีน้ำในรูปของเหลวบนพื้นผิวดาวอังคาร à¹à¸™à¸§à¸„ิดนี้อาศัยผลต่างเป็นคาบที่สังเà¸à¸•à¹„ด้ของรอยมืดà¹à¸¥à¸°à¸£à¸­à¸¢à¸ªà¸§à¹ˆà¸²à¸‡ โดยเฉพาะในละติจูดขั้วดาวซึ่งดูเป็นทะเลà¹à¸¥à¸°à¸—วีป บางคนà¹à¸›à¸¥à¸„วามรอยมืดริ้วลายขนานเป็นร่องทดน้ำสำหรับน้ำในรูปของเหลว ภายหลัง มีà¸à¸²à¸£à¸­à¸˜à¸´à¸šà¸²à¸¢à¸§à¹ˆà¸²à¸ à¸¹à¸¡à¸´à¸›à¸£à¸°à¹€à¸—ศเส้นตรงเหล่านั้นเป็นภาพลวงตา à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸—างธรณีวิทยาที่ภารà¸à¸´à¸ˆà¹„ร้คนบังคับรวบรวมชี้ว่า ครั้งหนึ่งดาวอังคารเคยมีน้ำปริมาณมาà¸à¸›à¸à¸„ลุมบนพื้นผิว ณ ช่วงใดช่วงหนึ่งในระยะต้น ๆ ของอายุ[20] ในปี 2005 เรดาร์เผยว่ามีน้ำà¹à¸‚็งน้ำ (water ice) ปริมาณมาà¸à¸‚ั้วทั้งสองของดาว[21] à¹à¸¥à¸°à¸—ี่ละติจูดà¸à¸¥à¸²à¸‡[22][23] ยานสำรวจภาคพื้นดาวอังคารสปิริต พบตัวอย่างสารประà¸à¸­à¸šà¹€à¸„มีที่มีโมเลà¸à¸¸à¸¥à¸™à¹‰à¸³à¹€à¸¡à¸·à¹ˆà¸­à¹€à¸”ือนมีนาคม 2007 ส่วนลงจอดฟีนิà¸à¸‹à¹Œ พบตัวอย่างน้ำà¹à¸‚็งน้ำโดยตรงในดินส่วนตื้นของดาวอังคารเมื่อวันที่ 31 à¸à¸£à¸à¸Žà¸²à¸„ม 2008[24]
+
+มียานอวà¸à¸²à¸¨à¸—ี่à¸à¸³à¸¥à¸±à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸‡à¸²à¸™à¸­à¸¢à¸¹à¹ˆà¹€à¸ˆà¹‡à¸”ลำ ห้าลำอยู่ในวงโคจร ได้à¹à¸à¹ˆ 2001 มาร์สโอดิสซี มาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เมเว็น à¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™ à¹à¸¥à¸°à¸ªà¸­à¸‡à¸¥à¸³à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ ได้à¹à¸à¹ˆ ยานสำรวจภาคพื้นดาวอังคารออปพอร์ทูนิตี à¹à¸¥à¸°à¸¢à¸²à¸™à¸¡à¸²à¸£à¹Œà¸ªà¹„ซà¹à¸­à¸™à¸‹à¹Œà¹à¸¥à¸šà¸­à¸£à¸²à¸—อรีคิวริออซิตี à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹‚ดย มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เปิดเผยว่ามีความเป็นไปได้ที่จะมีน้ำไหลในช่วงเดือนที่ร้อนที่สุดบนดาวอังคาร[25] ในปี 2013 ยานคิวริออซิตี ของนาซาค้นพบว่าดินของดาวอังคารมีน้ำเป็นองค์ประà¸à¸­à¸šà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸£à¹‰à¸­à¸¢à¸¥à¸° 1.5 ถึง 3 โดยมวล à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸™à¹‰à¸³à¸™à¸±à¹‰à¸™à¸ˆà¸°à¸•à¸´à¸”อยู่à¸à¸±à¸šà¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸­à¸šà¸­à¸·à¹ˆà¸™ ทำให้ไม่สามารถเข้าถึงได้โดยอิสระ[26]
+
+à¸à¸³à¸¥à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¸ªà¸·à¸šà¸„้นเพื่อประเมินศัà¸à¸¢à¸ à¸²à¸žà¸„วามสามารถอยู่อาศัยได้ในอดีตของดาวอังคาร ตลอดจนความเป็นไปได้ที่จะมีสิ่งมีชีวิตหลงเหลืออยู่ มีà¸à¸²à¸£à¸ªà¸·à¸šà¸„้นบริเวณนั้นโดยส่วนลงจอด ไวà¸à¸´à¸‡ โรเวอร์ สปิริต à¹à¸¥à¸°à¸­à¸­à¸›à¸žà¸­à¸£à¹Œà¸—ูนิตี ส่วนลงจอดฟีนิà¸à¸‹à¹Œ à¹à¸¥à¸°à¹‚รเวอร์ คิวริออซิตี[27][28] มีà¸à¸²à¸£à¸§à¸²à¸‡à¹à¸œà¸™à¸ à¸²à¸£à¸à¸´à¸ˆà¸—างชีวดาราศาสตร์ไว้à¹à¸¥à¹‰à¸§ ซึ่งรวม มาร์ส 2020 à¹à¸¥à¸°à¹€à¸­à¹‡à¸à¹‚ซมาร์สโรเวอร์ [29][30]
+
+ดาวอังคารสามารถมองเห็นได้ด้วยตาเปล่าจาà¸à¹‚ลà¸à¹‚ดยง่ายซึ่งจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¹€à¸›à¹‡à¸™à¸ªà¸µà¸­à¸­à¸à¹à¸”ง มีความส่องสว่างปราà¸à¸à¹„ด้ถึง −2.91[6] ซึ่งเป็นรองเพียงดาวพฤหัสบดี ดาวศุà¸à¸£à¹Œ ดวงจันทร์ à¹à¸¥à¸°à¸”วงอาทิตย์ à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ภาคพื้นดินโดยทั่วไปมีขีดจำà¸à¸±à¸”à¸à¸²à¸£à¸¡à¸­à¸‡à¹€à¸«à¹‡à¸™à¸£à¸²à¸¢à¸¥à¸°à¹€à¸­à¸µà¸¢à¸”ของภูมิประเทศขนาดประมาณ 300 à¸à¸´à¹‚ลเมตรเมื่อโลà¸à¹à¸¥à¸°à¸”าวอังคารเข้าใà¸à¸¥à¹‰à¸à¸±à¸™à¸¡à¸²à¸à¸—ี่สุดอันเป็นผลจาà¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚องโลà¸[31]
+
+ลัà¸à¸©à¸“ะทางà¸à¸²à¸¢à¸ à¸²à¸ž[à¹à¸à¹‰]
+โลà¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวอังคารโลà¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวอังคาร
+ไฟล์:Mars.ogvPlay media
+ภาพเคลื่อนไหว (00:40) à¹à¸ªà¸”งภูมิประเทศสำคัà¸
+ไฟล์:GMM-3 Mars Gravity.webmPlay media
+วีดีโอ (01:28) à¹à¸ªà¸”งให้เห็นสนามà¹à¸£à¸‡à¹‚น้มถ่วงของดาวอังคาร.
+
+ดาวอังคารมีขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ครึ่งหนึ่งของโลภà¹à¸¥à¸°à¸¡à¸µà¸žà¸·à¹‰à¸™à¸—ี่ผิวน้อยà¸à¸§à¹ˆà¸²à¸žà¸·à¹‰à¸™à¸—ี่ผิวดินทั้งหมดของโลà¸à¸£à¸§à¸¡à¸à¸±à¸™à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸­à¸¢[6] ดาวอังคารมีความหนาà¹à¸™à¹ˆà¸™à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸²à¹‚ลภมีปริมาตรประมาณร้อยละ 15 ของโลภà¹à¸¥à¸°à¸¡à¸µà¸¡à¸§à¸¥à¸›à¸£à¸°à¸¡à¸²à¸“ร้อยละ 11 ของมวลของโลภถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวอังคารจะมีขนาดใหà¸à¹ˆà¸à¸§à¹ˆà¸²à¹à¸¥à¸°à¸¡à¸µà¸¡à¸§à¸¥à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸”าวพุธà¸à¹‡à¸•à¸²à¸¡ à¹à¸•à¹ˆà¸”าวพุธมีความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸² เป็นผลให้à¹à¸£à¸‡à¹‚น้มถ่วงบริเวณพื้นผิวดาวเคราะห์ทั้งสองนั้นà¹à¸—บจะเท่าà¸à¸±à¸™ โดยดาวอังคารมีà¹à¸£à¸‡à¸”ึงโน้มถ่วงสูงà¸à¸§à¹ˆà¸²à¹€à¸žà¸µà¸¢à¸‡à¹„ม่ถึงร้อยละหนึ่ง ลัà¸à¸©à¸“ะปราà¸à¸à¸ªà¸µà¹à¸”งปนส้มของพื้นผิวดาวอังคารมีสาเหตุมาจาà¸à¹„อเอิร์น(III) ออà¸à¹„ซด์ รู้จัà¸à¸à¸±à¸™à¹ƒà¸™à¸Šà¸·à¹ˆà¸­à¸ªà¸²à¸¡à¸±à¸à¸„ือฮีมาไทต์หรือสนิมเหล็à¸[32] อาจมองเห็นคล้ายà¸à¸±à¸šà¸šà¸±à¸•à¹€à¸•à¸­à¸£à¹Œà¸ªà¸à¸­à¸•à¸Šà¹Œ[33] à¹à¸¥à¸°à¸ªà¸µà¸­à¸·à¹ˆà¸™ ๆ ที่ปราà¸à¸à¸—ั่วไปตามพื้นผิวนั้นมีได้ทั้งสีทอง สีน้ำตาล สีน้ำตาลอ่อน หรือสีออà¸à¹€à¸‚ียวขึ้นอยู่à¸à¸±à¸šà¹à¸£à¹ˆà¸­à¸‡à¸„์ประà¸à¸­à¸š[33]
+โครงสร้างภายใน[à¹à¸à¹‰]
+
+เช่นเดียวà¸à¸±à¸™à¸à¸±à¸šà¹‚ลภดาวอังคารมีà¸à¸²à¸£à¹à¸¢à¸à¸Šà¸±à¹‰à¸™à¸­à¸‡à¸„์ประà¸à¸­à¸šà¸­à¸­à¸à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¹à¸à¹ˆà¸™à¹‚ลหะความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸‹à¸¶à¹ˆà¸‡à¸–ูà¸à¸«à¹ˆà¸­à¸«à¸¸à¹‰à¸¡à¸­à¸¢à¸¹à¹ˆà¸ à¸²à¸¢à¹ƒà¸•à¹‰à¸ªà¹ˆà¸§à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸­à¸·à¹ˆà¸™ ๆ ที่มีความหนาà¹à¸™à¹ˆà¸™à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸²[34] à¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸‚องโครงสร้างภายในà¹à¸ªà¸”งรัศมีอาณาบริเวณของà¹à¸à¹ˆà¸™à¸”าวอยู่ที่ประมาณ 1,794±65 à¸à¸´à¹‚ลเมตร (1,115±40 ไมล์) มีองค์ประà¸à¸­à¸šà¸«à¸¥à¸±à¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸™à¸´à¸à¹€à¸à¸´à¸¥ โดยมีà¸à¸³à¸¡à¸°à¸–ันรวมอยู่ด้วยประมาณร้อยละ 16-17[35] คาดว่าà¹à¸à¹ˆà¸™à¹„อเอิร์น(II) ซัลไฟด์นั้นมีธาตุเบาเป็นองค์ประà¸à¸­à¸šà¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹à¸à¹ˆà¸™à¸‚องโลà¸à¸–ึงสองเท่า[36] à¹à¸à¹ˆà¸™à¸”าวล้อมรอบไปด้วยเนื้อดาวซิลิเà¸à¸•à¸‹à¸¶à¹ˆà¸‡à¸›à¸£à¸°à¸à¸­à¸šà¸‚ึ้นเป็นโครงสร้างทางธรณีสัณà¸à¸²à¸™à¹à¸¥à¸°à¸ à¸¹à¹€à¸‚าไฟต่าง ๆ บนดาวเคราะห์ซึ่งในปัจจุบันเหมือนจะสงบนิ่ง นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸‹à¸´à¸¥à¸´à¸à¸­à¸™à¹à¸¥à¸°à¸­à¸­à¸à¸‹à¸´à¹€à¸ˆà¸™ ธาตุที่มีมาà¸à¸—ี่สุดในเปลือà¸à¸œà¸´à¸§à¸‚องดาวอังคารได้à¹à¸à¹ˆ เหล็ภà¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ อะลูมิเนียม à¹à¸„ลเซียม à¹à¸¥à¸°à¹‚พà¹à¸—สเซียม ความหนาเฉลี่ยของเปลือà¸à¸”าวอยู่ที่ประมาณ 50 à¸à¸´à¹‚ลเมตร (31 ไมล์) มีความหนาสูงสุดที่ประมาณ 125 à¸à¸´à¹‚ลเมตร (78 ไมล์)[36] เปลือà¸à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸„วามหนาเฉลี่ย 40 à¸à¸´à¹‚ลเมตร (25 ไมล์) ถือว่ามีความหนาเพียงหนึ่งในสามของเปลือà¸à¸”าวอังคารเมื่อเปรียบสัมพัทธ์à¸à¸±à¸šà¸‚นาดของดาวเคราะห์ทั้งคู่ ยานส่วนลงจอดอินไซต์ตามà¹à¸œà¸™à¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¹ƒà¸™à¸›à¸µ 2016 (พ.ศ. 2559) จะมีà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸„รื่องมือตรวจวัดความไหวสะเทือนเพื่อให้ได้à¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¹‚ครงสร้างภายในดาวที่ชัดเจนมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้น[37]
+ธรณีวิทยาพื้นผิว[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ธรณีวิทยาดาวอังคาร
+
+ดาวอังคารเป็นดาวเคราะห์หินประà¸à¸­à¸šà¸‚ึ้นจาà¸à¹à¸£à¹ˆà¸Šà¸™à¸´à¸”ต่าง ๆ ที่มีซิลิà¸à¸­à¸™ ออà¸à¸‹à¸´à¹€à¸ˆà¸™ โลหะ ตลอดจนธาตุอื่น ๆ อีà¸à¸«à¸¥à¸²à¸¢à¸Šà¸™à¸´à¸”เป็นองค์ประà¸à¸­à¸šà¸£à¸§à¸¡à¸à¸±à¸™à¹€à¸‚้าเป็นหิน พื้นผิวของดาวอังคารมีหินบะซอลต์ชนิดโทเลอิทิà¸à¹€à¸›à¹‡à¸™à¸­à¸‡à¸„์ประà¸à¸­à¸šà¸«à¸¥à¸±à¸[38] à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸«à¸¥à¸²à¸¢à¸ªà¹ˆà¸§à¸™à¹€à¸›à¹‡à¸™à¸«à¸´à¸™à¸Šà¸™à¸´à¸”ที่มีซิลิà¸à¸²à¸ªà¸¹à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸«à¸´à¸™à¸šà¸°à¸‹à¸­à¸¥à¸•à¹Œà¸—ั่วไปà¹à¸¥à¸°à¸­à¸²à¸ˆà¸¡à¸µà¸„วามคล้ายคลึงà¸à¸±à¸šà¸«à¸´à¸™à¹à¸­à¸™à¸”ีไซต์บนโลà¸à¸«à¸£à¸·à¸­à¹à¸à¹‰à¸§à¸‹à¸´à¸¥à¸´à¹€à¸à¸• ภูมิภาคที่มีอัตราส่วนสะท้อนต่ำà¹à¸ªà¸”งà¸à¸²à¸£à¸¡à¸µà¹€à¸Ÿà¸¥à¸”์สปาร์à¸à¸¥à¸¸à¹ˆà¸¡à¹€à¸žà¸¥à¸ˆà¸´à¹‚อเคลสหนาà¹à¸™à¹ˆà¸™ ในขณะที่ภูมิภาคที่มีอัตราส่วนสะท้อนต่ำทางตอนเหนือเผยให้เห็นà¸à¸²à¸£à¸¡à¸µà¹à¸œà¹ˆà¸™à¸‹à¸´à¸¥à¸´à¹€à¸à¸•à¹à¸¥à¸°à¹à¸à¹‰à¸§à¸Šà¸™à¸´à¸”ที่มีซิลิà¸à¸­à¸™à¸ªà¸¹à¸‡à¸”้วยความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²à¸›à¸à¸•à¸´ ในหลายส่วนของภูมิภาคที่ราบสูงตอนใต้ตรวจพบไพรอà¸à¸‹à¸µà¸™à¸Šà¸™à¸´à¸”à¹à¸„ลเซียมสูงรวมอยู่เป็นปริมาณมาภนอà¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¸žà¸šà¸®à¸µà¸¡à¸²à¹„ทต์à¹à¸¥à¸°à¹‚อลิวีนหนาà¹à¸™à¹ˆà¸™à¹ƒà¸™à¸ à¸¹à¸¡à¸´à¸ à¸²à¸„จำเพาะบางà¹à¸«à¹ˆà¸‡[39] พื้นที่ผิวส่วนใหà¸à¹ˆà¸–ูà¸à¸›à¸à¸„ลุมด้วยชั้นหนาของเม็ดà¸à¸¸à¹ˆà¸™à¹„อเอิร์น(III) ออà¸à¹„ซด์ละเอียด[40][41]
+à¹à¸œà¸™à¸—ี่ธรณีวิทยาของดาวอังคาร (USGS; 14 à¸à¸£à¸à¸Žà¸²à¸„ม 2014) (à¹à¸œà¸™à¸—ี่เต็ม / วิดีโอ)[42][43][44]
+
+ถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวอังคารจะไม่มีหลัà¸à¸à¸²à¸™à¸‚องโครงสร้างสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸£à¸°à¸”ับครอบคลุมทั่วทั้งดาวในปัจจุบัน[45] à¹à¸•à¹ˆà¸œà¸¥à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹à¸ªà¸”งให้ทราบว่าหลายส่วนของเปลือà¸à¸”าวถูà¸à¸à¸£à¸°à¸—ำด้วยอำนาจà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸à¸²à¸£à¸žà¸¥à¸´à¸à¸œà¸±à¸™à¸ªà¸¥à¸±à¸šà¸‚ั้วของสนามไดโพลเคยปราà¸à¸à¸¡à¸²à¹à¸¥à¹‰à¸§à¹ƒà¸™à¸­à¸”ีต เพราะในทางบรรพวิทยาà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸ à¹à¸£à¹ˆà¸—ี่มีความไวต่อà¹à¸£à¸‡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸™à¸±à¹‰à¸™à¸¢à¹ˆà¸­à¸¡à¹à¸ªà¸”งคุณสมบัติเช่นเดียวà¸à¸±à¸™à¸à¸±à¸šà¹à¸–บสลับที่พบบนพื้นมหาสมุทรของโลภทฤษฎีหนึ่งที่มีà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œà¹ƒà¸™à¸›à¸µ 1999 (พ.ศ. 2542) à¹à¸¥à¸°à¸¡à¸µà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¸­à¸µà¸à¸„รั้งในเดือนตุลาคม ปี 2005 (พ.ศ. 2548) (โดยอาศัยข้อมูลจาà¸à¸¡à¸²à¸£à¹Œà¸ªà¹‚à¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œ) ชี้ว่าà¹à¸™à¸§à¹à¸–บต่าง ๆ ที่เà¸à¸´à¸”ขึ้นà¹à¸ªà¸”งถึงà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸à¸²à¸£à¹à¸›à¸£à¸ªà¸±à¸“à¸à¸²à¸™à¹à¸œà¹ˆà¸™à¸˜à¸£à¸“ีภาคบนดาวอังคารเมื่อเวลาà¸à¸§à¹ˆà¸²à¸ªà¸µà¹ˆà¸žà¸±à¸™à¸¥à¹‰à¸²à¸™à¸›à¸µà¸à¹ˆà¸­à¸™ à¸à¹ˆà¸­à¸™à¸—ี่ไดนาโมของดาวเคราะห์จะหยุดลงเป็นผลให้สนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸‚องดาวจางหายไป[46]
+
+ในช่วงà¸à¸²à¸£à¸à¹ˆà¸­à¸à¸³à¹€à¸™à¸´à¸”ระบบสุริยะ ดาวอังคารได้ถือà¸à¸³à¹€à¸™à¸´à¸”ขึ้นจาà¸à¸œà¸¥à¸‚องà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸ªà¸¸à¹ˆà¸¡à¸‚องมวลที่พอà¸à¸žà¸¹à¸™à¸‚ึ้นà¹à¸¢à¸à¸­à¸­à¸à¸ˆà¸²à¸à¸ˆà¸²à¸™à¸”าวเคราะห์à¸à¹ˆà¸­à¸™à¹€à¸à¸´à¸”ที่โคจรรอบดวงอาทิตย์ ดาวอังคารจึงมีคุณลัà¸à¸©à¸“ะทางเคมีที่จำเพาะพิเศษหลายประà¸à¸²à¸£à¸•à¸²à¸¡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¹ƒà¸™à¸£à¸°à¸šà¸šà¸ªà¸¸à¸£à¸´à¸¢à¸° ธาตุต่าง ๆ ที่มีจุดเดือดค่อนข้างต่ำตัวอย่างเช่นคลอรีน ฟอสฟอรัส à¹à¸¥à¸°à¸à¸³à¸¡à¸°à¸–ัน จะพบเป็นปà¸à¸•à¸´à¸šà¸™à¸”าวอังคารในระดับที่มาà¸à¸à¸§à¹ˆà¸²à¹‚ลภเป็นไปได้ว่าธาตุเหล่านี้ถูà¸à¸‚ับออà¸à¸¡à¸²à¸ˆà¸²à¸à¸šà¸£à¸´à¹€à¸§à¸“ใà¸à¸¥à¹‰à¸”วงอาทิตย์โดยลมสุริยะอันทรงพลังในช่วงต้นของอายุขัย[47]
+
+หลังà¸à¸²à¸£à¸à¹ˆà¸­à¸à¸³à¹€à¸™à¸´à¸”ดาวเคราะห์à¹à¸¥à¹‰à¸§ ทั้งหมดล้วนตà¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸¢à¸·à¹ˆà¸­à¸‚อง "à¸à¸²à¸£à¸£à¸°à¸”มชนหนัà¸à¸„รั้งสุดท้าย" à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 60 ของพื้นที่ผิวดาวอังคารà¹à¸ªà¸”งบันทึà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์à¸à¸²à¸£à¸£à¸°à¸”มชนจาà¸à¸¢à¸¸à¸„นั้น[48][49][50] ในขณะที่เป็นไปได้ว่าพื้นที่ผิวส่วนที่เหลืออีà¸à¸¡à¸²à¸à¸¡à¸²à¸¢à¸§à¸²à¸‡à¸•à¸±à¸§à¸­à¸¢à¸¹à¹ˆà¸ à¸²à¸¢à¹ƒà¸•à¹‰à¹à¸­à¹ˆà¸‡à¸‚นาดมโหฬารซึ่งà¸à¹‡à¹€à¸à¸´à¸”ขึ้นจาà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ดังà¸à¸¥à¹ˆà¸²à¸§ มีหลัà¸à¸à¸²à¸™à¸‚องà¹à¸­à¹ˆà¸‡à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดมหึมาในบริเวณซีà¸à¹‚ลà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคารซึ่งà¹à¸œà¹ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸£à¸²à¸§ 8,500 à¸à¸´à¹‚ลเมตร à¹à¸¥à¸°à¸¢à¸²à¸§à¸£à¹ˆà¸§à¸¡ 10,600 à¸à¸´à¹‚ลเมตร (5,300 x 6,600 ไมล์) หรือมีขนาดใหà¸à¹ˆà¹€à¸›à¹‡à¸™à¸ªà¸µà¹ˆà¹€à¸—่าของà¹à¸­à¹ˆà¸‡à¹„อต์เค็น-ขั้วใต้ของดวงจันทร์ ทำให้เป็นà¹à¸­à¹ˆà¸‡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸—ี่มีขนาดใหà¸à¹ˆà¸—ี่สุดเท่าที่มีà¸à¸²à¸£à¸„้นพบ[16][17] ทฤษฎีนี้เสนอว่าดาวอังคารถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยวัตถุขนาดเท่าดาวพลูโตเมื่อประมาณสี่พันล้านปีà¸à¹ˆà¸­à¸™ à¹à¸¥à¸°à¸„าดว่าเหตุà¸à¸²à¸£à¸“์นี้เองเป็นสาเหตุทำให้ดาวอังคารมีซีà¸à¸”าวà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸ªà¸­à¸‡à¸¥à¸±à¸à¸©à¸“ะอย่างชัดเจน เà¸à¸´à¸”à¹à¸­à¹ˆà¸‡à¸šà¸­à¹€à¸£à¸µà¸¢à¸¥à¸´à¸ªà¸­à¸±à¸™à¸£à¸²à¸šà¹€à¸£à¸µà¸¢à¸šà¸›à¸à¸„ลุมพื้นที่à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 40 ทางซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวเคราะห์[51][52]
+ภาพรังสรรค์โดยศิลปินà¹à¸ªà¸”งภาพของดาวอังคารว่าน่าจะเป็นอย่างไรเมื่อสี่พันล้านปีà¸à¹ˆà¸­à¸™[53]
+
+ประวัติศาสตร์ธรณีวิทยาของดาวอังคารสามารถà¹à¸šà¹ˆà¸‡à¸­à¸­à¸à¹„ด้เป็นหลายช่วงเวลา à¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¸«à¸¥à¸±à¸à¹à¸¥à¹‰à¸§à¸ªà¸²à¸¡à¸²à¸£à¸–à¹à¸šà¹ˆà¸‡à¹„ด้เป็นสามยุคด้วยà¸à¸±à¸™[54][55]
+
+ ยุคโนอาเคียน (ตั้งชื่อตาม โนอาคิสเทร์รา หรือà¹à¸œà¹ˆà¸™à¸”ินของโนอาห์): เป็นช่วงà¸à¸³à¹€à¸™à¸´à¸”พื้นผิวดาวอังคารที่เà¸à¹ˆà¸²à¹à¸à¹ˆà¸—ี่สุดเท่าที่ปราà¸à¸ อยู่ในช่วงเวลาประมาณ 4.5 พันล้านปีà¸à¹ˆà¸­à¸™à¸ˆà¸™à¸–ึง 3.5 พันล้านปีที่ผ่านมา พื้นผิวยุคโนอาเคียนเต็มไปด้วยริ้วรอยจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดใหà¸à¹ˆà¸„รั้งà¹à¸¥à¹‰à¸§à¸„รั้งเล่า ส่วนโป่งธาร์ซิส ที่ราบสูงภูเขาไฟที่คาดว่าเà¸à¸´à¸”ขึ้นในระหว่างยุคนี้พร้อมด้วยà¸à¸²à¸£à¸—่วมท้นอย่างà¸à¸§à¹‰à¸²à¸‡à¸‚วางของน้ำของเหลวในช่วงปลายยุค
+ ยุคเฮสเพียเรียน (ตั้งขื่อตาม เฮสเพียเรียนเพลนัม หรือที่ราบสูงตะวันตà¸): ราว 3.5 พันล้านปีà¸à¹ˆà¸­à¸™ จนถึงช่วงเวลาประมาณ 2.9 - 3.3 พันล้านปีที่ผ่านมา เป็นยุคที่มีรอยปราà¸à¸à¸Šà¸±à¸”เจนของà¸à¸²à¸£à¹€à¸à¸´à¸”ที่ราบลาวาขนาดใหà¸à¹ˆ
+ ยุคà¹à¸­à¸¡à¸°à¹‚ซเนียน (ตั้งขื่อตาม à¹à¸­à¸¡à¸°à¹‚ซนิสเพลนิเชีย หรือที่ราบà¹à¸­à¸¡à¸°à¸‹à¸­à¸™): นับตั้งà¹à¸•à¹ˆ 2.9 - 3.3 พันล้านปีà¸à¹ˆà¸­à¸™à¸ˆà¸™à¸–ึงปัจจุบัน พิ้นผิวยุคนี้มีหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸™à¹‰à¸­à¸¢à¹à¸•à¹ˆà¸„่อนข้างหลาà¸à¸«à¸¥à¸²à¸¢ ภูเขาไฟโอลิมปัสเà¸à¸´à¸”ขึ้นในยุคนี้ร่วมไปà¸à¸±à¸šà¸à¸²à¸£à¹„หลของลาวาอีà¸à¸«à¸¥à¸²à¸¢à¸—ี่บนดาวอังคาร
+
+à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸—างธรณีวิทยาบางอย่างยังคงเà¸à¸´à¸”ขึ้นบนดาวอังคาร ที่หุบเขาอะธาบาสà¸à¸²à¸¡à¸µà¸£à¹ˆà¸­à¸‡à¸£à¸­à¸¢à¸à¸²à¸£à¹„หลของลาวาในลัà¸à¸©à¸“ะเป็นà¹à¸œà¹ˆà¸™à¸­à¸²à¸¢à¸¸à¸à¸§à¹ˆà¸² 200 ล้านปี ปราà¸à¸à¸£à¹ˆà¸­à¸‡à¸£à¸­à¸¢à¸à¸²à¸£à¹„หลของน้ำในพื้นผิวท่ามà¸à¸¥à¸²à¸‡à¸£à¸­à¸¢à¹€à¸¥à¸·à¹ˆà¸­à¸™à¸‹à¸¶à¹ˆà¸‡à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸£à¹ˆà¸­à¸‡à¹à¸¢à¸à¹€à¸‹à¸­à¸£à¹Œà¹€à¸šà¸­à¸£à¸±à¸ªà¸”้วยอายุน้อยà¸à¸§à¹ˆà¸² 20 ล้านปี บ่งชี้ว่าเป็นà¸à¸²à¸£à¸žà¸¥à¸¸à¹ˆà¸‡à¸‚ึ้นของภูเขาไฟเมื่อไม่นานมานี้เช่นà¸à¸±à¸™[56] วันที่ 19 à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ 2008 (พ.ศ. 2551) ภาพจาà¸à¸¢à¸²à¸™à¸¡à¸²à¸£à¹Œà¸ªà¸£à¸µà¸„อนเนสเซนซ์ออร์บิเตอร์à¹à¸ªà¸”งให้เห็นหลัà¸à¸à¸²à¸™à¸‚องหิมะที่พังทลายลงมาจาà¸à¸«à¸™à¹‰à¸²à¸œà¸²à¸„วามสูง 700 เมตร[57]
+ดิน[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ดินดาวอังคาร
+à¸à¸¸à¹ˆà¸™à¸—ี่มีซิลิà¸à¸²à¸›à¸£à¸´à¸¡à¸²à¸“สูง เผยให้เห็นโดยยานสำรวจดาวอังคารสปิริต
+
+ข้อมูลจาà¸à¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸­à¸”ฟีนิà¸à¸‹à¹Œà¸—ี่ส่งà¸à¸¥à¸±à¸šà¸¡à¸²à¹à¸ªà¸”งว่าดินดาวอังคารมีความเป็นด่างเล็à¸à¸™à¹‰à¸­à¸¢à¹à¸¥à¸°à¸›à¸£à¸°à¸à¸­à¸šà¸”้วยธาตุต่าง ๆ อาทิเช่น à¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ โซเดียม โพà¹à¸—สเซียม à¹à¸¥à¸°à¸„ลอรีน สารอาหารเหล่านี้สามารถพบได้ทั่วไปในสวนบนโลà¸à¹à¸¥à¸°à¸•à¹ˆà¸²à¸‡à¸à¹‡à¸ˆà¸³à¹€à¸›à¹‡à¸™à¸•à¹ˆà¸­à¸à¸²à¸£à¹€à¸ˆà¸£à¸´à¸à¹€à¸•à¸´à¸šà¹‚ตของพืช[58] à¸à¸²à¸£à¸—ดสอบโดนยานสำรวจเผยว่าดินดาวอังคารมีสมบัติเป็นด่างด้วยค่า พีเอชที่ 7.7 à¹à¸¥à¸°à¸¡à¸µà¹€à¸à¸¥à¸·à¸­à¹€à¸›à¸­à¸£à¹Œà¸„ลอเรตอยู่ราวร้อยละ 0.6[59][60][61][62]
+
+มีภูมิประเทศที่เป็นเส้นพาดขวางอยู่ทั่วไปบนดาวอังคารà¹à¸¥à¸°à¸—ี่เà¸à¸´à¸”ขึ้นใหม่ ๆ ปราà¸à¸à¸šà¹ˆà¸­à¸¢à¸„รั้งในบริเวณส่วนลาดที่สูงชันของหลุมตà¸à¸à¸£à¸°à¸—บ ร่องลึภà¹à¸¥à¸°à¸«à¸¸à¸šà¹€à¸«à¸§ รอยเส้นพาดจะมีสีคล้ำในช่วงà¹à¸£à¸à¹à¸¥à¹‰à¸§à¸„่อย ๆ จางลงเมื่อเวลาผ่านไป ในบางครั้งรอยเส้นเริ่มต้นในพื้นที่เล็ภๆ à¸à¹ˆà¸­à¸™à¸—ี่จะà¹à¸œà¹ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸­à¸­à¸à¹„ปได้เป็นหลายร้อยเมตร สามารถมองเห็นได้ตามขอบของหินขนาดใหà¸à¹ˆà¹à¸¥à¸°à¹€à¸„รื่องà¸à¸µà¸”ขวางต่าง ๆ ตามเส้นทางอีà¸à¸”้วย ทฤษฎีที่ได้รับà¸à¸²à¸£à¸¢à¸­à¸¡à¸£à¸±à¸šà¹‚ดยทั่วไปà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸²à¸£à¸­à¸¢à¹€à¸ªà¹‰à¸™à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸±à¹‰à¸™à¹€à¸›à¹‡à¸™à¸”ินชั้นล่างซึ่งมีสีคล้ำà¹à¸•à¹ˆà¸–ูà¸à¹€à¸›à¸´à¸”ออà¸à¸¡à¸²à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸±à¸‡à¸—ลายของà¸à¸¸à¹ˆà¸™à¸ªà¸µà¸ˆà¸²à¸‡à¸—างด้านบนหรือโดยพายุà¸à¸¸à¹ˆà¸™[63] มีà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¸„ำอธิบายไปอีà¸à¸«à¸¥à¸²à¸¢à¹à¸™à¸§à¸—าง บางส่วนอธิบายว่าเà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸™à¹‰à¸³à¸«à¸£à¸·à¸­à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งว่าเป็นà¸à¸²à¸£à¹€à¸ˆà¸£à¸´à¸à¹€à¸•à¸´à¸šà¹‚ตของสิ่งมีชีวิต[64][65]
+อุทà¸à¸§à¸´à¸—ยา[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: น้ำบนดาวอังคาร
+ภาพถ่ายà¸à¸³à¸¥à¸±à¸‡à¸‚ยายสูงถ่ายโดยยานออปพอร์ทูนิตี à¹à¸ªà¸”งà¸à¸²à¸£à¸žà¸­à¸à¸•à¸±à¸§à¸‚องฮีมาไทต์สีเทา ซึ่งบ่งชี้ว่าเคยมีน้ำในสถานะของเหลวปราà¸à¸à¹ƒà¸™à¸­à¸”ีต
+
+น้ำของเหลวนั้นไม่สามารถดำรงอยู่ได้บนดาวอังคารเนื่องจาà¸à¸„วามà¸à¸”อาà¸à¸²à¸¨à¸—ี่ต่ำมาà¸à¹€à¸žà¸µà¸¢à¸‡à¹à¸„่หนึ่งในร้อยของโลà¸[66] เว้นà¹à¸•à¹ˆà¸žà¸·à¹‰à¸™à¸—ี่ลุ่มต่ำบางบริเวณในช่วงเวลาเพียงสั้น ๆ[67][68] à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งที่ขั้วดาวทั้งคู่มีสภาพที่พอจะให้น้ำในปริมาณมาภๆ ได้[69][70] เฉพาะปริมาตรของน้ำà¹à¸‚็งขั้วใต้ของดาวหาà¸à¸¥à¸°à¸¥à¸²à¸¢à¸¥à¸‡à¸à¹‡à¸ˆà¸°à¹ƒà¸«à¹‰à¸™à¹‰à¸³à¹€à¸žà¸µà¸¢à¸‡à¸žà¸­à¸ªà¸³à¸«à¸£à¸±à¸šà¸›à¸à¸„ลุมพื้นผิวทั้งหมดของดาวเคราะห์ได้ด้วยความลึภ11 เมตร (36 ฟุต)[71] ชั้นดินเยือà¸à¹à¸‚็งคงตัวà¹à¸œà¹ˆà¸‚ยายจาà¸à¸‚ั้วดาวลงมาจนถึงประมาณละติจูดที่ 60 องศา[69]
+
+คาดว่าน้ำà¹à¸‚็งปริมาณมาà¸à¸–ูà¸à¸ˆà¸±à¸šà¹€à¸­à¸²à¹„ว้ภายในไครโอสเฟียร์หนาของดาวอังคาร ข้อมูลเรดาร์จาภมาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª à¹à¸¥à¸° มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เมื่อà¸à¸£à¸à¸Žà¸²à¸„ม 2005 (พ.ศ. 2548) à¹à¸ªà¸”งน้ำà¹à¸‚็งปริมาณมหาศาลที่ขั้วทั้งสองของดาว[21][72] à¹à¸¥à¸°à¹ƒà¸™à¹€à¸”ือนพฤศจิà¸à¸²à¸¢à¸™ 2008 (พ.ศ. 2551) พบในบริเวณละติจูดà¸à¸¥à¸²à¸‡[22] ยานส่วนลงจอดฟีนิà¸à¸‹à¹Œà¸žà¸šà¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸‚็งโดยตรงในดินส่วนตื้นของดาวอังคารเมื่อวันที่ 31 à¸à¸£à¸à¸Žà¸²à¸„ม 2008[24]
+
+ลัà¸à¸©à¸“ะทางธรณีสัณà¸à¸²à¸™à¸—ี่มองเห็นบนดาวอังคารบ่งชี้อย่างหนัà¸à¹à¸™à¹ˆà¸™à¸§à¹ˆà¸²à¸¡à¸µà¸™à¹‰à¸³à¸‚องเหลวปราà¸à¸à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวเคราะห์ เส้นทางคดเคี้ยวขนาดใหà¸à¹ˆà¸—ี่โอบคลุมพื้นดินที่ถูà¸à¸à¸±à¸”เซาะหรือช่องทางà¸à¸²à¸£à¹„หลออà¸à¸™à¸±à¹‰à¸™à¸•à¸±à¸”ผ่านพื้นผิวโดยรอบà¸à¸§à¹ˆà¸² 25 à¹à¸«à¹ˆà¸‡ คาดว่าร่องรอยเหล่านี้เป็นบันทึà¸à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸‚องà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸à¸±à¸”เซาะระหว่างที่มีà¸à¸²à¸£à¸›à¸¥à¸”ปล่อยน้ำอย่างถล่มทลายออà¸à¸¡à¸²à¸ˆà¸²à¸à¸Šà¸±à¹‰à¸™à¸«à¸´à¸™à¸­à¸¸à¹‰à¸¡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ อย่างไรà¸à¹‡à¸•à¸²à¸¡à¹‚ครงสร้างบางส่วนถูà¸à¸•à¸±à¹‰à¸‡à¸ªà¸¡à¸¡à¸•à¸´à¸à¸²à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™à¸œà¸¥à¸¡à¸²à¸ˆà¸²à¸à¸à¸²à¸£à¸à¸£à¸°à¸—ำของธารน้ำà¹à¸‚็งหรือลาวา[73][74] ตัวอย่างหนึ่งที่มีขนาดใหà¸à¹ˆà¸„ือ มาดดิมวัลลิส ซึ่งมีความยาว 700 à¸à¸´à¹‚ลเมตร (430 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸‚นาดใหà¸à¹ˆà¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹à¸à¸£à¸™à¸”์à¹à¸„นยอนด้วยความà¸à¸§à¹‰à¸²à¸‡ 20 à¸à¸´à¹‚ลเมตร (12 ไมล์) à¹à¸¥à¸°à¸„วามลึภ2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) ในบางท้องที่ คาดว่าภูมิประเทศถูà¸à¸à¸±à¸”สร้างขึ้นมาโดยà¸à¸²à¸£à¹„หลของน้ำตั้งà¹à¸•à¹ˆà¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™ ๆ ของประวัติศาสตร์ดาวอังคาร[75] ช่องทางà¸à¸²à¸£à¹„หลเหล่านี้ที่มีอายุน้อยที่สุดคาดว่าเพิ่งจะเà¸à¸´à¸”ขึ้นเมื่อเวลาเพียงไม่à¸à¸µà¹ˆà¸¥à¹‰à¸²à¸™à¸›à¸µà¸—ี่à¹à¸¥à¹‰à¸§[76] สำหรับที่อื่น ๆ โดยเฉพาะพื้นที่ที่เà¸à¹ˆà¸²à¹à¸à¹ˆà¸—ี่สุดบนผิวดาวอังคาร โครงสร้างระดับเล็à¸à¸¢à¹ˆà¸­à¸¢à¸•à¸¥à¸­à¸”จนเครือข่ายหุบเขาที่à¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸›à¹‡à¸™à¸à¸´à¹ˆà¸‡à¸à¹‰à¸²à¸™à¸ªà¸²à¸‚าล้วนà¹à¸œà¹ˆà¸‚ยายพาดขวางเป็นสัดส่วนอย่างมีนัยสำคัà¸à¹ƒà¸™à¸ à¸²à¸„พื้นภูมิประเทศ รูปลัà¸à¸©à¸“ะของหุบเขาเหล่านี้รวมทั้งà¸à¸²à¸£à¸à¸£à¸°à¸ˆà¸²à¸¢à¸•à¸±à¸§à¹à¸ªà¸”งนัยอย่างเด่นชัดว่าถูà¸à¹€à¸‹à¸²à¸°à¸ªà¸£à¹‰à¸²à¸‡à¹‚ดยà¸à¸²à¸£à¹„หลบ่าซึ่งเป็นผลลัพธ์มาจาà¸à¸à¸™à¸«à¸£à¸·à¸­à¸«à¸´à¸¡à¸°à¸—ี่ตà¸à¸¥à¸‡à¸¡à¸²à¹€à¸¡à¸·à¹ˆà¸­à¸¢à¸¸à¸„à¹à¸£à¸à¸‚องประวัติศาสตร์ดาวอังคาร à¸à¸²à¸£à¹„หลของน้ำใต้ผิวดินà¹à¸¥à¸°à¸à¸²à¸£à¸œà¸¸à¸”เซาะของน้ำบาดาลอาจà¹à¸ªà¸”งบทบาทย่อยสำคัà¸à¹ƒà¸™à¸«à¸¥à¸²à¸¢à¹€à¸„รือข่าย à¹à¸•à¹ˆà¸«à¸¢à¸²à¸”น้ำฟ้าน่าจะเป็นสาเหตุหลัà¸à¸‚องริ้วร่องเà¸à¸·à¸­à¸šà¸—ั้งหมดในà¹à¸•à¹ˆà¸¥à¸°à¸à¸£à¸“ี[77]
+
+ร่วมไปà¸à¸±à¸šà¸œà¸™à¸±à¸‡à¸‚องหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸«à¸£à¸·à¸­à¸«à¸¸à¸šà¹€à¸‚าลึภมีลัà¸à¸©à¸“ะภูมิประเทศนับพันที่ปราà¸à¸à¸„ล้ายคลึงà¸à¸±à¸šà¹‚ตรà¸à¸«à¹‰à¸§à¸¢à¸šà¸™à¸žà¸·à¹‰à¸™à¸”ิน ห้วยต่าง ๆ นี้มัà¸à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่ราบสูงทางซีà¸à¹ƒà¸•à¹‰à¸‚องดาวà¹à¸¥à¸°à¹€à¸œà¸Šà¸´à¸à¸à¸±à¸šà¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£ ทั้งหมดชี้ไปในà¹à¸™à¸§à¸‚ั้วดาวที่ละติจูด 30 องศา นัà¸à¸§à¸´à¸ˆà¸±à¸¢à¸ˆà¸³à¸™à¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¹€à¸ªà¸™à¸­à¸§à¹ˆà¸²à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸à¹ˆà¸­à¸à¸³à¹€à¸™à¸´à¸”เà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวซึ่งอาจมาจาà¸à¸™à¹‰à¸³à¹à¸‚็งที่ละลาย[78][79] à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¸¡à¸µà¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸„นà¹à¸¢à¹‰à¸‡à¸§à¹ˆà¸²à¸à¸¥à¹„à¸à¹ƒà¸™à¸à¸²à¸£à¹€à¸à¸´à¸”เà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸„าร์บอนไดออà¸à¹„ซด์เยือà¸à¹à¸‚็งหรือà¸à¸²à¸£à¹€à¸„ลื่อนที่ของà¸à¸¸à¹ˆà¸™à¹à¸«à¹‰à¸‡[80][81] ไม่ปราà¸à¸à¸§à¹ˆà¸²à¸¡à¸µà¹‚ตรà¸à¸«à¹‰à¸§à¸¢à¸—ี่ถูà¸à¸à¸£à¹ˆà¸­à¸™à¸—ำลายบางส่วนโดยà¸à¸²à¸£à¸œà¸¸à¸à¸£à¹ˆà¸­à¸™à¸•à¸²à¸¡à¸ªà¸ à¸²à¸žà¸­à¸²à¸à¸²à¸¨ à¹à¸¥à¸°à¸à¹‡à¸ªà¸±à¸‡à¹€à¸à¸•à¹„ม่พบในหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸—ั้งหลายที่มีความเด่นชัด จึงเป็นเครื่องชี้ว่าภูมิประเทศดังà¸à¸¥à¹ˆà¸²à¸§à¸¢à¸±à¸‡à¸¡à¸µà¸­à¸²à¸¢à¸¸à¸™à¹‰à¸­à¸¢à¹à¸¥à¸°à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¹„ด้ว่ายังคงเà¸à¸´à¸”ขึ้นในปัจจุบัน[79]
+
+ลัà¸à¸©à¸“ะทางธรณีวิทยาอื่นอีà¸à¸«à¸¥à¸²à¸¢à¸›à¸£à¸°à¸à¸²à¸£ เช่น ดินดอนสามเหลี่ยมปาà¸à¹à¸¡à¹ˆà¸™à¹‰à¸³ à¹à¸¥à¸°à¸•à¸°à¸à¸­à¸™à¸™à¹‰à¸³à¸žà¸²à¸£à¸¹à¸›à¸žà¸±à¸”ที่ถูà¸à¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¹„ว้ในหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸•à¹ˆà¸²à¸‡ ๆ เป็นพยานหลัà¸à¸à¸²à¸™à¸—ี่เสริมให้ทราบว่ามีสภาพà¹à¸§à¸”ล้อมอุ่น-ชื้น ณ บางช่วงเวลาหรือหลายช่วงเวลาในประวัติศาสตร์ยุคต้นของดาวอังคาร[82] สภาวะà¹à¸§à¸”ล้อมเช่นนี้เป็นสิ่งที่จำเป็นสำหรับà¸à¸²à¸£à¹€à¸à¸´à¸”มีอย่างà¸à¸§à¹‰à¸²à¸‡à¸‚วางของทะเลสาบหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่ข้ามผ่านเป็นสัดส่วนขนาดใหà¸à¹ˆà¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ à¹à¸¥à¸°à¸™à¸±à¸šà¸§à¹ˆà¸²à¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸­à¸´à¸ªà¸£à¸°à¸—ั้งในทางà¹à¸£à¹ˆà¸§à¸´à¸—ยา ตะà¸à¸­à¸™à¸§à¸´à¸—ยา à¹à¸¥à¸°à¸˜à¸£à¸“ีสัณà¸à¸²à¸™à¸§à¸´à¸—ยาอีà¸à¸”้วย[83]
+ส่วนประà¸à¸­à¸šà¸‚องหินบริเวณ "เยลโลไนฟ์เบย์" - หินเวนมีà¹à¸„ลเซียมà¹à¸¥à¸°à¸à¸³à¸¡à¸°à¸–ันมาà¸à¸à¸§à¹ˆà¸²à¸”ินที่ถูà¸à¸žà¸²à¸¡à¸² - ผลจาà¸à¹€à¸­à¸žà¸µà¹€à¸­à¸à¸‹à¹Œà¹€à¸­à¸ª - คิวริออซิตี (มีนาคม 2013)
+
+หลัà¸à¸à¸²à¸™à¸™à¸­à¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰à¸—ี่ยืนยันà¸à¸²à¸£à¸—ี่ครั้งหนึ่งเคยมีน้ำของเหลวปราà¸à¸à¸šà¸™à¸œà¸´à¸§à¸”าวอังคารมาจาà¸à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¹à¸£à¹ˆà¸—ี่มีความจำเพาะ เช่น ฮีมาไทต์ à¹à¸¥à¸°à¹€à¸à¸­à¹„ทต์ ซึ่งทั้งคู่บางครั้งจะà¸à¹ˆà¸­à¸•à¸±à¸§à¹ƒà¸™à¸—ี่ที่มีน้ำ[84] ในปี 2004 (พ.ศ. 2547) ยานออปพอร์ทูนิตี ตรวจพบà¹à¸£à¹ˆà¸ˆà¸²à¹‚รไซต์ซึ่งà¸à¹ˆà¸­à¸•à¸±à¸§à¸‚ึ้นเฉพาะเมื่อมีน้ำในสภาพเป็นà¸à¸£à¸” เป็นเครื่องพิสูจน์ว่าครั้งหนึ่งเคยมีน้ำอยู่บนดาวอังคาร[85] หลัà¸à¸à¸²à¸™à¹€à¸žà¸´à¹ˆà¸¡à¹€à¸•à¸´à¸¡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวเมื่อไม่นานมานี้มาจาà¸à¸à¸²à¸£à¸„้นพบà¹à¸£à¹ˆà¸¢à¸´à¸›à¸‹à¸±à¸¡à¸šà¸™à¸žà¸·à¹‰à¸™à¸”ินโดยยานสำรวจออปพอร์ทูนิตีของนาซา เมื่อธันวาคม 2011 (พ.ศ. 2554)[86][87] นอà¸à¸ˆà¸²à¸à¸™à¸µà¹‰ ฟรานซิส à¹à¸¡à¸„คับบิน หัวหน้าà¸à¹ˆà¸²à¸¢à¸¨à¸¶à¸à¸©à¸² นัà¸à¸§à¸´à¸—ยาศาสตร์ดาวเคราะห์ที่มหาวิทยาลัยนิวเม็à¸à¸‹à¸´à¹‚à¸à¹ƒà¸™à¹à¸­à¸¥à¸šà¸¹à¹€à¸„อร์คี ตรวจสอบลัà¸à¸©à¸“ะไฮดรอà¸à¹„ซด์ในผลึà¸à¹à¸£à¹ˆà¸ˆà¸²à¸à¸”าวอังคาร à¹à¸–ลงว่าน้ำในà¹à¸¡à¸™à¹€à¸—ิลส่วนบนของดาวอังคารมีปริมาณเท่าà¸à¸±à¸šà¸«à¸£à¸·à¸­à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸—ี่โลà¸à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸—ี่ระดับ 50 - 300 ส่วนในล้านส่วน ซึ่งมาà¸à¹€à¸žà¸µà¸¢à¸‡à¸žà¸­à¸—ี่จะครอบคลุมพื้นผิวทั้งหมดของดาวได้ด้วยความลึภ200 ถึง 1,000 เมตร (660 ถึง 3,280 ฟุต)[88]
+
+เมื่อวันที่ 18 มีนาคม 2013 (พ.ศ. 2556) นาซารายงานหลัà¸à¸à¸²à¸™à¸ˆà¸²à¸à¹€à¸„รื่องตรวจวัดบนยานสำรวจคิวริออซิตี ของà¹à¸£à¹ˆà¸—ี่เà¸à¸´à¸”ขึ้นโดยมีน้ำเป็นองค์ประà¸à¸­à¸š อย่างเช่นไฮเดรตของà¹à¸„ลเซียมซัลเฟต ในตัวอย่างหินหลายชนิดรวมทั้งชิ้นส่วนที่à¹à¸•à¸à¸­à¸­à¸à¸¡à¸²à¸‚องหิน "ทินทินา" à¹à¸¥à¸°à¸«à¸´à¸™ "ซัตตันอินเลียร์" เช่นเดียวà¸à¸±à¸šà¹€à¸§à¸™à¹à¸¥à¸°à¹‚นดูลในหินอื่น ๆ เช่นหิน "นอร์" à¹à¸¥à¸°à¸«à¸´à¸™ "เวอนิà¸à¹€à¸"[89][90][91] à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์โดยใช้เครื่องมือดีเอเอ็นของยานสำรวจภาคพื้นให้หลัà¸à¸à¸²à¸™à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸œà¸´à¸§à¸”ินว่ามีปริมาณà¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 4 ลึà¸à¸¥à¸‡à¹„ปจนถึงระดับ 60 เซนติเมตร (24 นิ้ว) ในเส้นทางเคลื่อนผ่านของยานจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸ˆà¸¸à¸”ลงจอดà¹à¸šà¸£à¸”บูรี ไปจนถึงพื้นที่ เยลโลไนฟ์เบย์ ในบริเวณภูมิภาคเà¸à¸¥à¹€à¸™à¸ [89]
+
+นัà¸à¸§à¸´à¸ˆà¸±à¸¢à¸šà¸²à¸‡à¸ªà¹ˆà¸§à¸™à¹€à¸Šà¸·à¹ˆà¸­à¸§à¹ˆà¸²à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆà¸‚องพิ้นที่ราบต่ำทางตอนเหนือของดาวเคยถูà¸à¸¡à¸«à¸²à¸ªà¸¡à¸¸à¸—รปà¸à¸„ลุมด้วยความลึà¸à¸«à¸¥à¸²à¸¢à¸£à¹‰à¸­à¸¢à¹€à¸¡à¸•à¸£ ทั้งนี้ยังอยู่ในระหว่างà¸à¸²à¸£à¹‚ต้à¹à¸¢à¹‰à¸‡[92] ในเดือนมีนาคม 2015 (พ.ศ. 2558) นัà¸à¸§à¸´à¸—ยาศาสตร์ระบุว่ามหาสมุทรดังà¸à¸¥à¹ˆà¸²à¸§à¸­à¸²à¸ˆà¸¡à¸µà¸‚นาดราวมหาสมุทรอาร์à¸à¸•à¸´à¸à¸‚องโลภà¸à¸²à¸£à¸§à¸´à¸™à¸´à¸ˆà¸‰à¸±à¸¢à¸™à¸µà¹‰à¹„ด้มาจาà¸à¸à¸²à¸£à¸›à¸£à¸°à¹€à¸¡à¸´à¸™à¸­à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸¥à¸°à¸”ิวเทอเรียมในบรรยาà¸à¸²à¸¨à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸‚องดาวอังคารเทียบà¸à¸±à¸™à¸à¸±à¸šà¸­à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸—ี่พบบนโลภปริมาณดิวเทอเรียมที่พบบนดาวอังคารมีมาà¸à¸à¸§à¹ˆà¸²à¸—ี่ดำรงอยู่บนโลà¸à¸–ึงà¹à¸›à¸”เท่า บ่งชี้ว่าดาวอังคารครั้งโบราณà¸à¸²à¸¥à¸¡à¸µà¸™à¹‰à¸³à¹€à¸›à¹‡à¸™à¸›à¸£à¸´à¸¡à¸²à¸“มาà¸à¸­à¸¢à¹ˆà¸²à¸‡à¸¡à¸µà¸™à¸±à¸¢à¸ªà¸³à¸„ัภผลสำรวจจาà¸à¸¢à¸²à¸™à¸„ิวริออซิตี มาพบในภายหลังว่ามีดิวเทอเรียมในอัตราส่วนสูงในหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¹€à¸à¸¥ อย่างไรà¸à¹‡à¸•à¸²à¸¡à¸„่าที่ได้ยังไม่สูงพอที่จะสนับสนุนว่าเคยมีมหาสมุทรอยู่ นัà¸à¸§à¸´à¸—ยาศาสตร์รายอื่น ๆ เตือนว่าà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¹ƒà¸«à¸¡à¹ˆà¸™à¸µà¹‰à¸¢à¸±à¸‡à¹„ม่ได้รับà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™ à¹à¸¥à¸°à¸Šà¸µà¹‰à¸›à¸£à¸°à¹€à¸”็นว่าà¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¸ à¸¹à¸¡à¸´à¸­à¸²à¸à¸²à¸¨à¸”าวอังคารยังไม่ได้à¹à¸ªà¸”งว่าดาวเคราะห์มีความอบอุ่นเพียงพอในอดีตที่ผ่านมาที่จะเอื้อให้น้ำคงอยู่ในรูปของเหลวได้[93]
+à¹à¸œà¹ˆà¸™à¸‚ั้วโลà¸[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: น้ำà¹à¸‚็งขั้วโลà¸à¸”าวอังคาร
+à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วเหนือช่วงต้นฤดูร้อน 1999 (พ.ศ. 2542)
+à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ในช่วงฤดูร้อน 2000 (พ.ศ. 2543)
+
+ดาวอังคารมีà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งถาวรอยู่ที่ขั้วทั้งสอง เมื่อถึงฤดูหนาวของà¹à¸•à¹ˆà¸¥à¸°à¸‚ั้วพื้นที่โดยรอบà¸à¹‡à¸ˆà¸°à¸•à¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸„วามมืดอย่างต่อเนื่อง à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸à¹€à¸¢à¹‡à¸™à¸¥à¸‡à¸‚องพื้นผิวเป็นสาเหตุให้เà¸à¸´à¸”à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸à¹à¸‚็งสะสมของบรรยาà¸à¸²à¸¨à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 25 - 30 ลงมาเป็นà¹à¸œà¹ˆà¸™ CO2 เยือà¸à¹à¸‚็ง (น้ำà¹à¸‚็งà¹à¸«à¹‰à¸‡)[94] เมื่อà¹à¸•à¹ˆà¸¥à¸°à¸‚ั้วà¸à¸¥à¸±à¸šà¸¡à¸²à¹„ด้รับà¹à¸ªà¸‡à¹à¸”ดอีà¸à¸„รั้ง CO2 เยือà¸à¹à¸‚็งà¸à¹‡à¸ˆà¸°à¸£à¸°à¹€à¸«à¸´à¸” เà¸à¸´à¸”เป็นลมขนาดมหึมาà¸à¸§à¸²à¸”ซัดไปทั่วบริเวณขั้วด้วยอัตราเร็วถึง 400 à¸à¸´à¹‚ลเมตร/ชั่วโมง (250 ไมล์/ชั่วโมง) ปราà¸à¸à¸à¸²à¸£à¸“์ตามฤดูà¸à¸²à¸¥à¸™à¸µà¹‰à¸Šà¹ˆà¸§à¸¢à¹€à¸„ลื่อนย้ายà¸à¸¸à¹ˆà¸™à¹à¸¥à¸°à¹„อน้ำปริมาณมหาศาลให้ลอยสูงขึ้นคล้ายà¸à¸±à¸šà¹€à¸¡à¸†à¹€à¸‹à¸­à¸£à¹Œà¸£à¸±à¸ªà¹€à¸¢à¸·à¸­à¸à¹à¸‚็งขนาดใหà¸à¹ˆà¸šà¸™à¹‚ลภยานสำรวจออปพอร์ทูนิตี ถ่ายภาพเมฆที่เป็นน้ำเยือà¸à¹à¸‚็งนี้ได้ในปี 2004 (พ.ศ. 2547)[95]
+
+à¹à¸œà¹ˆà¸™à¸—ี่ขั้วโลà¸à¸—ั้งสองมีองค์ประà¸à¸­à¸šà¸«à¸¥à¸±à¸à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 70 เป็นน้ำเยือà¸à¹à¸‚็ง สำหรับคาร์บอนไดออà¸à¹„ซด์เยือà¸à¹à¸‚็งจะสะสมตัวเป็นชั้นที่บางà¸à¸§à¹ˆà¸²à¹€à¸¡à¸·à¹ˆà¸­à¹€à¸—ียบà¸à¸±à¸™à¹‚ดยหนาประมาณหนึ่งเมตรบนà¹à¸œà¹ˆà¸™à¸‚ั้วเหนือเฉพาะในช่วงฤดูหนาวเท่านั้น ในขณะที่à¹à¸œà¹ˆà¸™à¸‚ั้วใต้เป็นà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸„งตัวปà¸à¸„ลุมด้วยความหนาประมาณà¹à¸›à¸”เมตร à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸„งตัวที่ปà¸à¸„ลุมยังขั้วใต้นี้เà¸à¸¥à¸·à¹ˆà¸­à¸™à¸à¸¥à¹ˆà¸™à¹„ปด้วยหลุมตื้น ๆ พื้นเรียบขอบโค้งเว้าไม่à¹à¸™à¹ˆà¸™à¸­à¸™à¸«à¸£à¸·à¸­à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศà¹à¸šà¸šà¹€à¸™à¸¢à¹à¸‚็งสวิส ภาพถ่ายซ้ำยังสถานที่เดิมà¹à¸ªà¸”งให้เห็นà¸à¸²à¸£à¸‚ยายตัวของรอยเหล่านี้ได้หลายเมตรต่อปี บอà¸à¹ƒà¸«à¹‰à¸—ราบว่าà¹à¸œà¹ˆà¸™ CO2 คงตัวที่ปà¸à¸„ลุมขั้วใต้เบื้องบนà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งจาà¸à¸™à¹‰à¸³à¸™à¸±à¹‰à¸™à¸¡à¸µà¸à¸²à¸£à¸ªà¸¥à¸²à¸¢à¸•à¸±à¸§à¹„ปตามเวลา[96] à¹à¸œà¹ˆà¸™à¸›à¸à¸„ลุมขั้วเหนือมีขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 1,000 à¸à¸´à¹‚ลเมตร (620 ไมล์) ระหว่างฤดูร้อนของซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคาร[97] à¹à¸¥à¸°à¸¡à¸µà¸›à¸£à¸´à¸¡à¸²à¸•à¸£à¸™à¹‰à¸³à¹à¸‚็งประมาณ 1.6 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร (380,000 ลูà¸à¸šà¸²à¸¨à¸à¹Œà¹„มล์) ซึ่งหาà¸à¸à¸£à¸°à¸ˆà¸²à¸¢à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸ªà¸¡à¹ˆà¸³à¹€à¸ªà¸¡à¸­à¸—ั่วทั้งà¹à¸œà¹ˆà¸™à¸à¹‡à¸ˆà¸°à¸¡à¸µà¸„วามหนาถึง 2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) [98] (เปรียบเทียบà¸à¸±à¸šà¸™à¹‰à¸³à¹à¸‚็งปริมาตร 2.85 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร (680,000 ลูà¸à¸šà¸²à¸¨à¸à¹Œà¹„มล์) ของà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¸à¸£à¸µà¸™à¹à¸¥à¸™à¸”์) à¹à¸œà¹ˆà¸™à¸Šà¸±à¹‰à¸§à¹ƒà¸•à¹‰à¸¡à¸µà¹€à¸ªà¹‰à¸™à¸œà¹ˆà¸²à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡ 350 à¸à¸´à¹‚ลเมตร (220 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามหนา 3 à¸à¸´à¹‚ลเมตร (1.9 ไมล์)[99] ปริมาตรรวมของน้ำà¹à¸‚็งในà¹à¸œà¹ˆà¸™à¸‚ั้วใต้รวมทั้งที่เà¸à¹‡à¸šà¸ªà¸°à¸ªà¸¡à¹ƒà¸™à¸Šà¸±à¹‰à¸™à¸šà¸£à¸´à¹€à¸§à¸“ใà¸à¸¥à¹‰à¹€à¸„ียงประมาณว่ามีอยู่à¸à¸§à¹ˆà¸² 1.6 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร[100] à¹à¸œà¹ˆà¸™à¸‚ั้วโลà¸à¸—ั้งคู่มีร่องรูปเà¸à¸¥à¸µà¸¢à¸§à¸›à¸£à¸²à¸à¸ ตามข้อมูลà¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์จาà¸à¸Šà¸²à¹€à¸£à¸”หรือเรดาร์สำรวจส่วนตื้นของดาวอังคารผ่านน้ำà¹à¸‚็ง à¹à¸ªà¸”งว่าร่องดังà¸à¸¥à¹ˆà¸²à¸§à¹€à¸›à¹‡à¸™à¸œà¸¥à¸ˆà¸²à¸à¸¥à¸¡à¸žà¸±à¸”ลาดลงซี่งหมุนเป็นเà¸à¸¥à¸µà¸¢à¸§à¹€à¸™à¸·à¹ˆà¸­à¸‡à¸ˆà¸²à¸à¸œà¸¥à¸à¸£à¸°à¸—บโคริโอลิส[101][102]
+
+à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸à¹à¸‚็งตามฤดูà¸à¸²à¸¥à¹ƒà¸™à¸šà¸²à¸‡à¸—้องที่ใà¸à¸¥à¹‰à¸à¸±à¸šà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ทำให้เà¸à¸´à¸”ชั้นใสของà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸«à¸™à¸²à¸›à¸£à¸°à¸¡à¸²à¸“หนึ่งเมตรเหนือพื้นดิน เมื่อถึงฤดูใบไม้ผลิ à¹à¸ªà¸‡à¸­à¸²à¸—ิตย์ทำให้ใต้พื้นผิวอุ่นขึ้น ความดันจาภCO2 ระเหิดบริเวณข้างใต้à¹à¸œà¹ˆà¸™à¸ˆà¸°à¸”ัน ยภà¹à¸¥à¸°à¸ªà¸¸à¸”ท้ายทำให้à¹à¸œà¹ˆà¸™à¹à¸•à¸à¸­à¸­à¸ ซึ่งนำไปสู่à¸à¸²à¸£à¸›à¸°à¸—ุà¹à¸šà¸šà¹„à¸à¹€à¸‹à¸­à¸£à¹Œà¸‚องà¹à¸à¹Šà¸ª CO2 ผสมà¸à¸±à¸šà¸—รายบะซอลต์สีคล้ำหรือà¸à¸¸à¹ˆà¸™ à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸™à¸µà¹‰à¹€à¸à¸´à¸”ขึ้นเร็ว สังเà¸à¸•à¸ˆà¸²à¸à¸­à¸§à¸à¸²à¸¨à¹„ด้ในเวลาเพียงไม่à¸à¸µà¹ˆà¸§à¸±à¸™à¸«à¸£à¸·à¸­à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸«à¸¥à¸²à¸¢à¸ªà¸±à¸›à¸”าห์ถึงหลายเดือน อัตราà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸„่อนข้างจะไม่ปà¸à¸•à¸´à¹ƒà¸™à¸—างธรณีวิทยาโดยเฉพาะà¸à¸±à¸šà¸”าวอังคาร à¹à¸à¹Šà¸ªà¸—ี่เคลื่อนไหลไปข้างใต้à¹à¸œà¹ˆà¸™à¸ˆà¸™à¸–ึงตำà¹à¸«à¸™à¹ˆà¸‡à¹„à¸à¹€à¸‹à¸­à¸£à¹Œà¸ˆà¸°à¸à¸±à¸”สลัà¸à¸£à¸¹à¸›à¹à¸šà¸šà¸„ล้ายใยà¹à¸¡à¸‡à¸¡à¸¸à¸¡à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸­à¸à¹€à¸›à¹‡à¸™à¸£à¸±à¸¨à¸¡à¸µà¸•à¸²à¸¡à¸Šà¹ˆà¸­à¸‡à¸—างที่ผ่านใต้น้ำà¹à¸‚็ง à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—ี่เà¸à¸´à¸”ขึ้นเหมือนà¸à¸±à¸šà¸ à¸²à¸„ตรงข้ามของโครงข่ายà¸à¸²à¸£à¸à¸±à¸”เซาะจาà¸à¸™à¹‰à¸³à¸—ี่ระบายลงหลุมที่ดึงจุà¸à¸­à¸¸à¸”ออà¸à¹„ป[103][104][105][106]
+ภูมิศาสตร์à¹à¸¥à¸°à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸ à¸¹à¸¡à¸´à¸›à¸£à¸°à¹€à¸—ศพื้นผิว[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ภูมิศาสตร์ดาวอังคาร
+à¹à¸œà¸™à¸—ี่ภูมิประเทศจาà¸à¹‚มลา à¹à¸ªà¸”งพื้นที่มีระดับสูง (สีà¹à¸”งà¹à¸¥à¸°à¸ªà¸µà¸ªà¹‰à¸¡) เป็นพื้นที่ส่วนใหà¸à¹ˆà¹ƒà¸™à¸‹à¸µà¸à¹‚ลà¸à¹ƒà¸•à¹‰à¸‚องดาวอังคาร ที่ราบลุ่ม (สีฟ้า) ทางตอนเหนือ ที่ราบสูงภูเขาไฟà¸à¸³à¸«à¸™à¸”ขอบเขตที่ราบทางเหนือในบางบริเวณ ในขณะที่พื้นที่สูงมีà¹à¸­à¹ˆà¸‡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดใหà¸à¹ˆà¸«à¸¥à¸²à¸¢à¹à¸«à¹ˆà¸‡
+
+à¹à¸¡à¹‰à¸§à¹ˆà¸²à¹‚ยฮันน์ ไฮน์ริภฟอน เมดเลอร์ à¹à¸¥à¸°à¸§à¸´à¸¥à¹€à¸®à¸¥à¹Œà¸¡ เบียร์จะเป็นที่จดจำอย่างดียิ่งว่าเป็นผู้วาดà¹à¸œà¸™à¸—ี่ดวงจันทร์à¹à¸•à¹ˆà¸žà¸§à¸à¹€à¸‚าà¸à¹‡à¹€à¸›à¹‡à¸™ "นัà¸à¸§à¸²à¸”à¹à¸œà¸™à¸—ี่ดาวอังคาร" อันดับà¹à¸£à¸ พวà¸à¹€à¸‚าเริ่มโดยà¸à¸³à¸«à¸™à¸”ภูมิประเทศพื้นผิวดาวอังคารส่วนใหà¸à¹ˆà¹ƒà¸«à¹‰à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸¡à¸±à¹ˆà¸™à¸„ง à¹à¸¥à¸°à¹‚ดยà¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸¶à¸‡à¸ªà¸²à¸¡à¸²à¸£à¸–วัดคาบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¸‚องดาวอังคารได้อย่างà¹à¸¡à¹ˆà¸™à¸¢à¸³à¸¡à¸²à¸à¸‚ึ้น ในปี 1840 (พ.ศ. 2383) เมดเลอร์รวบรวมผลà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸•à¸¥à¸­à¸”สิบปีของเขาà¹à¸¥à¹‰à¸§à¸§à¸²à¸”à¹à¸œà¸™à¸—ี่ดาวอังคารขึ้นเป็นครั้งà¹à¸£à¸ à¹à¸—นที่จะมีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¹ƒà¸«à¹‰à¸à¸±à¸šà¸ˆà¸¸à¸”สังเà¸à¸•à¸•à¹ˆà¸²à¸‡ ๆ อันหลายหลาà¸à¸™à¸±à¹‰à¸™ เบียร์à¹à¸¥à¸°à¹€à¸¡à¸”เลอร์à¸à¸¥à¸±à¸šà¹ƒà¸Šà¹‰à¸§à¸´à¸˜à¸µà¸‡à¹ˆà¸²à¸¢ ๆ โดยระบุด้วยตัวอัà¸à¸©à¸£ เมอริเดียนเบย์ (ไซนัสเมอริเดียนี) ถูà¸à¹€à¸£à¸µà¸¢à¸à¹€à¸›à¹‡à¸™à¸ à¸¹à¸¡à¸´à¸›à¸£à¸°à¹€à¸—ศ "a"[107]
+
+ปัจจุบันนี้ภูมิประเทศบนดาวอังคารได้รับà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸ˆà¸²à¸à¸«à¸¥à¸²à¸¢à¹à¸«à¸¥à¹ˆà¸‡à¸—ี่มา ภูมิประเทศที่เห็นโดดเด่นจะตั้งชื่อตามเทววิทยาคลาสสิภหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่ใหà¸à¹ˆà¸à¸§à¹ˆà¸² 60 à¸à¸´à¹‚ลเมตรตั้งชื่อตามชื่อของนัà¸à¸§à¸´à¸—ยาศาสตร์ นัà¸à¹€à¸‚ียน à¹à¸¥à¸°à¸šà¸¸à¸„คลอื่นใดที่มีบทบาทช่วยเหลือสนับสนุนในà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸”าวอังคารซึ่งได้ล่วงลับไปà¹à¸¥à¹‰à¸§ หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่เล็à¸à¸à¸§à¹ˆà¸² 60 à¸à¸´à¹‚ลเมตรลงมา ตั้งชื่อตามชื่อเมืองหรือหมู่บ้านบนโลà¸à¸‹à¸¶à¹ˆà¸‡à¸ˆà¸°à¸•à¹‰à¸­à¸‡à¸¡à¸µà¸›à¸£à¸°à¸Šà¸²à¸à¸£à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸² 100,000 คน หุบเขาขนาดใหà¸à¹ˆà¹„ด้ชื่อมาจาภคำ "ดาวอังคาร" หรือ ดาวฤà¸à¸©à¹Œ" ในภาษาต่าง ๆ นานา ส่วนหุบเขาขนาดเล็à¸à¸™à¸±à¹‰à¸™à¹„ด้ชื่อจาà¸à¸Šà¸·à¹ˆà¸­à¸‚องà¹à¸¡à¹ˆà¸™à¹‰à¸³[108]
+
+ภูมิประเทศที่มีความโดดเด่นขนาดใหà¸à¹ˆà¸¢à¸±à¸‡à¸„งมีชื่อเรียà¸à¹€à¸”ิมอยู่หลายชื่อ à¹à¸•à¹ˆà¸à¹‡à¸¡à¸±à¸à¸¡à¸µà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹€à¸žà¸·à¹ˆà¸­à¹ƒà¸«à¹‰à¸ªà¸°à¸—้อนองค์ความรู้ใหม่เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸˜à¸£à¸£à¸¡à¸Šà¸²à¸•à¸´à¸‚องภูมิประเทศนั้น ตัวอย่างเช่น นิà¸à¸‹à¹Œà¹‚อลิมปิà¸à¸² (หิมะà¹à¸«à¹ˆà¸‡à¹‚อลิมปัส) à¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™ โอลิมปัสมอนส์ (ภูเขาโอลิมปัส)[109] พื้นผิวดาวอังคารที่มองเห็นจาà¸à¹‚ลà¸à¹à¸šà¹ˆà¸‡à¸­à¸­à¸à¹„ด้เป็นสองà¸à¸¥à¸¸à¹ˆà¸¡à¸žà¸·à¹‰à¸™à¸—ี่จาà¸à¸„วามà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸‚องà¸à¸²à¸£à¸ªà¸°à¸—้อนà¹à¸ªà¸‡ ที่ราบสีจางที่ปà¸à¸„ลุมด้วยà¸à¸¸à¹ˆà¸™à¹à¸¥à¸°à¸—รายอันอุดมไปด้วยออà¸à¹„ซด์ของเหล็à¸à¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸ªà¸µà¹à¸”งนั้น ครั้งหนึ่งเคยคิดà¸à¸±à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™ "ทวีป" ของดาวอังคาร จึงมีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸—ำนอง อะเรเบียเทร์รา (à¹à¸œà¹ˆà¸™à¸”ินà¹à¸«à¹ˆà¸‡à¸­à¸²à¸£à¸°à¹€à¸šà¸µà¸¢) หรืออย่าง à¹à¸­à¸¡à¸°à¹‚ซนิสเพลนิเชีย (ที่ราบà¹à¸­à¸¡à¸°à¸‹à¸­à¸™) ภูมิประเทศคล้ำถูà¸à¸„ิดว่าเป็นทะเล ดังนั้นจึงตั้งชื่ออย่าง à¹à¸¡à¸£à¹Œà¹€à¸­à¸£à¸´à¹€à¸•à¸£à¸µà¸¢à¸¡ (ทะเลà¹à¸”ง) à¹à¸¡à¸£à¹Œà¹„ซเรนัม à¹à¸¥à¸°à¸­à¸­à¹‚รรีไซนัส ภูมิประเทศมืดคล้ำที่มีขนาดใหà¸à¹ˆà¸—ี่สุดที่มองเห็นจาà¸à¹‚ลà¸à¸„ือ เซียทิสเมเจอร์เพลนัม[110] à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งคงตัวทางขั้วเหนือได้ชื่อว่า เพลนัมบอเรียม ในขณะที่à¹à¸œà¹ˆà¸™à¸—างขั้วใต้เรียà¸à¸§à¹ˆà¸² เพลนัมออสเทรล
+
+เส้นศูนย์สูตรของดาวอังคารถูà¸à¸à¸³à¸«à¸™à¸”โดยà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸‚องดาว à¹à¸•à¹ˆà¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚องเมริเดียนà¹à¸£à¸à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸—ี่ถูà¸à¸£à¸°à¸šà¸¸à¸‚ึ้นเอง ดังเช่นตำà¹à¸«à¸™à¹ˆà¸‡à¸à¸£à¸µà¸™à¸´à¸Šà¸‚องโลภคือต้องเลือà¸à¸à¸³à¸«à¸™à¸”จุดชี้ขาดขึ้นมา เมดเลอร์à¹à¸¥à¸°à¹€à¸šà¸µà¸¢à¸£à¹Œà¹„ด้เลือà¸à¹€à¸ªà¹‰à¸™à¹€à¸¡à¸­à¸£à¸´à¹€à¸”ียนในปี 1830 (พ.ศ. 2373) สำหรับà¹à¸œà¸™à¸—ี่à¹à¸£à¸à¸‚องดาวอังคาร ต่อมาภายหลังยานอวà¸à¸²à¸¨à¸¡à¸²à¸£à¸´à¹€à¸™à¸­à¸£à¹Œ 9 ได้ให้ภาพดาวอังคารมาà¸à¸¡à¸²à¸¢à¹ƒà¸™à¸›à¸µ 1972 (พ.ศ. 2515) หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็à¸à¸‹à¸¶à¹ˆà¸‡à¹„ด้ชื่อภายหลังว่า à¹à¸­à¸£à¸µ-0 ในบริเวณ ไซนัสเมอริเดียนี ("อ่าวตรงà¸à¸¥à¸²à¸‡" หรือ "อ่าวเมอริเดียน") ได้ถูà¸à¹€à¸¥à¸·à¸­à¸à¹€à¸›à¹‡à¸™à¸ˆà¸¸à¸”นิยามลองจิจูดที่ 0.0 องศา เพื่อให้พ้องตรงà¸à¸±à¸™à¸à¸±à¸šà¹€à¸ªà¹‰à¸™à¸—ี่ได้à¸à¸³à¸«à¸™à¸”ไว้เดิม[111]
+
+เพราะดาวอังคารไม่มีมหาสมุทรดังนั้นจึงไม่มี "ระดับน้ำทะเล" พื้นผิวที่มีระดับà¸à¸²à¸£à¸¢à¸à¸•à¸±à¸§à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ˆà¸¶à¸‡à¸–ูà¸à¹€à¸¥à¸·à¸­à¸à¹ƒà¸Šà¹‰à¹€à¸›à¹‡à¸™à¸£à¸°à¸”ับอ้างอิงà¹à¸—นซึ่งเรียà¸à¸§à¹ˆà¸² à¹à¸­à¸£à¸µà¸­à¸­à¸¢à¸”์ [112] ของดาวอังคาร เปรียบดังจีออยด์บนพื้นผิวโลภระดับความสูงที่มีค่าเท่าà¸à¸±à¸šà¸¨à¸¹à¸™à¸¢à¹Œà¸–ูà¸à¸à¸³à¸«à¸™à¸” ณ ความสูงที่มีความดันบรรยาà¸à¸²à¸¨à¹€à¸—่าà¸à¸±à¸š 610.5 ปาสà¸à¸²à¸¥ (6.105 มิลลิบาร์)[113] ค่าความดันนี้สอดคล้องà¸à¸±à¸šà¸ˆà¸¸à¸”สามสถานะของน้ำà¹à¸¥à¸°à¸¡à¸µà¸„่าประมาณร้อยละ 0.6 ของความดันพื้นผิวที่ระดับน้ำทะเลบนโลภ(0.006 บรรยาà¸à¸²à¸¨)[114] ในทางปà¸à¸´à¸šà¸±à¸•à¸´ ณ ปัจจุบัน พื้นผิวนี้ถูà¸à¸à¸³à¸«à¸™à¸”โดยตรงจาà¸à¸”าวเทียมตรวจวัดความโน้มถ่วง
+à¹à¸œà¸™à¸—ี่สี่มุมดาวอังคาร[à¹à¸à¹‰]
+
+ภาพอิมเมจà¹à¸¡à¸žà¸”ังต่อไปนี้ของดาวอังคารà¹à¸šà¹ˆà¸‡à¸­à¸­à¸à¹€à¸›à¹‡à¸™à¹à¸œà¸™à¸—ี่สี่มุมจำนวน 30 ชิ้น à¸à¸³à¸«à¸™à¸”โดยองค์à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸˜à¸£à¸“ีวิทยาสหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸²[115][116] à¹à¸œà¸™à¸—ี่à¹à¸•à¹ˆà¸¥à¸°à¸Šà¸´à¹‰à¸™à¸¡à¸µà¸à¸²à¸£à¸à¸³à¸à¸±à¸šà¸•à¸±à¸§à¹€à¸¥à¸‚พร้อมอัà¸à¸©à¸£à¸™à¸³à¸«à¸™à¹‰à¸² "MC" ย่อมาจาภ"Mars Chart" หรือà¹à¸œà¸™à¸ à¸²à¸žà¸”าวอังคาร[117] ด้านบนคือà¹à¸œà¸™à¸—ี่ตอนเหนือสุด ตำà¹à¸«à¸™à¹ˆà¸‡ 0°N 180°W / 0°N 180°W อยู่ทางซ้ายสุดเหนือเส้นศูนย์สูตร ภาพà¹à¸œà¸™à¸—ี่ได้มาจาà¸à¸¡à¸²à¸£à¹Œà¸ªà¹‚à¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œ
+Mars Quad Map
+เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ à¸²à¸žà¸™à¸µà¹‰
+0°N 180°W / 0°N 180°W
+0°N 0°W / 0°N -0°E
+90°N 0°W / 90°N -0°E
+MC-01
+
+à¹à¸¡à¸£à¹Œà¸šà¸­à¹€à¸£à¸µà¸¢à¸¡
+MC-02
+
+ไดอะเครีย
+MC-03
+
+อาร์คาเดีย
+MC-04
+
+à¹à¸¡à¸£à¹Œà¹à¸­à¸‹à¸´à¹€à¸”เลียม
+MC-05
+
+อิสมีเนียสลาคัส
+MC-06
+
+เคเซียส
+MC-07
+
+ซีเบรเนีย
+MC-08
+
+à¹à¸­à¸¡à¸°à¹‚ซนิส
+MC-09
+
+ธาร์ซิส
+MC-10
+
+ลูนีเพลัส
+MC-11
+
+ออà¸à¹€à¸‹à¸µà¸¢à¹€à¸žà¸¥à¸±à¸ª
+MC-12
+
+อะเรเบีย
+MC-13
+
+เซียทิสเมเจอร์
+MC-14
+
+อะเมนเธส
+MC-15
+
+อิลีเซียม
+MC-16
+
+เมมโนเนีย
+MC-17
+
+ฟีนีซิส
+MC-18
+
+โคเพรตส์
+MC-19
+
+มาร์à¸à¸²à¸£à¸´à¸•à¸´à¹€à¸Ÿà¸­à¸£à¹Œ
+MC-20
+
+ซาบีอัส
+MC-21
+
+ไออาพีเจีย
+MC-22
+
+ทีร์รีนัม
+MC-23
+
+อีโอลิส
+MC-24
+
+à¹à¸Ÿà¸˜à¸­à¸™à¸•à¸´à¸ª
+MC-25
+
+ธอเมเซีย
+MC-26
+
+อาร์จีเร
+MC-27
+
+โนอาคิส
+MC-28
+
+เฮลลาส
+MC-29
+
+เอริเดเนีย
+MC-30
+
+à¹à¸¡à¸£à¹Œà¸­à¸­à¸ªà¹€à¸—รล
+
+
+ภูมิประเทศจาà¸à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™[à¹à¸à¹‰]
+หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸­à¸™à¹€à¸™à¸§à¸´à¸¥à¸¥à¹Œà¹à¸¥à¸°à¸à¸²à¸™à¸Šà¹ˆà¸§à¸¢à¸¥à¸‡à¸ˆà¸­à¸”ของยานโรเวอร์สปิริต
+
+ภูมิประเทศของดาวอังคารมีà¸à¸²à¸£à¹à¸¢à¸à¸­à¸­à¸à¹€à¸›à¹‡à¸™à¸ªà¸­à¸‡à¸¥à¸±à¸à¸©à¸“ะอย่างโดดเด่นคือ พื้นที่ราบà¹à¸šà¸™à¸ˆà¸²à¸à¸à¸²à¸£à¹„หลของลาวาทางซีà¸à¹€à¸«à¸™à¸·à¸­à¸‹à¸¶à¹ˆà¸‡à¸œà¸´à¸”à¹à¸œà¸à¹€à¸”่นชัดจาà¸à¸—ี่ราบสูงอันอุดมไปด้วยหลุมเล็à¸à¸«à¸¥à¸¸à¸¡à¸™à¹‰à¸­à¸¢à¸ˆà¸²à¸à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸¡à¸²à¹à¸•à¹ˆà¸„รั้งโบราณà¸à¸²à¸¥à¸—างซีà¸à¹ƒà¸•à¹‰ à¸à¸²à¸£à¸§à¸´à¸ˆà¸±à¸¢à¹ƒà¸™à¸›à¸µ 2008 (พ.ศ. 2551) à¹à¸ªà¸”งหลัà¸à¸à¸²à¸™à¹‚น้มเอียงไปยังทฤษฎีที่เสนอขึ้นในปี 1980 (พ.ศ. 2523) ซึ่งà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² ราวสี่พันล้านปีà¸à¹ˆà¸­à¸™ ซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคารถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยวัตถุขนาดใหà¸à¹ˆà¸£à¸²à¸§à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸ªà¸´à¸šà¸–ึงสองในสามของดวงจันทร์ของโลภถ้าทฤษฎีนี้เป็นจริงย่อมทำให้ซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคารเป็นตำà¹à¸«à¸™à¹ˆà¸‡à¸‚องหลุมà¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸”้วยขนาดยาว 10,600 à¸à¸´à¹‚ลเมตร à¹à¸¥à¸°à¸à¸§à¹‰à¸²à¸‡ 8,500 à¸à¸´à¹‚ลเมตร (6,600 x 5,300 ไมล์) หรือโดยคร่าว ๆ à¹à¸¥à¹‰à¸§à¹€à¸—่าà¸à¸±à¸šà¸žà¸·à¹‰à¸™à¸—ี่ของยุโรป เอเชีย à¹à¸¥à¸°à¸­à¸­à¸ªà¹€à¸•à¸£à¹€à¸¥à¸µà¸¢à¸—ั้งหมดรวมà¸à¸±à¸™ มีขนาดใหà¸à¹ˆà¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹à¸­à¹ˆà¸‡à¹„อต์เค็น-ขั้วใต้ของดวงจันทร์à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸«à¸¥à¸¸à¸¡à¸•à¸à¸à¸£à¸°à¸—บที่ใหà¸à¹ˆà¸—ี่สุดในระบบสุริยะ[16][17]
+รอยจาà¸à¸”าวเคราะห์น้อยพุ่งชนดาวอังคารที่ใหม่มาภ3°20′N 219°23′E / 3.34°N 219.38°E - ซ้าย-à¸à¹ˆà¸­à¸™/27 มีนาคม & ขวา-หลัง/28 มีนาคม 2012 (MRO)[118]
+
+ดาวอังคารมีรอยตำหนิของหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸¡à¸²à¸à¸¡à¸²à¸¢ เฉพาะที่มีเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸² 5 à¸à¸´à¹‚ลเมตร (3.1 ไมล์) ขึ้นไป พบว่ามีจำนวนรวมà¸à¸§à¹ˆà¸² 43,000 à¹à¸«à¹ˆà¸‡[119] หลุมใหà¸à¹ˆà¸—ี่สุดที่มีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¹à¸¥à¹‰à¸§à¸„ือà¹à¸­à¹ˆà¸‡à¸•à¸à¸à¸£à¸°à¸—บเฮลลาส ภูมิประเทศอัลเบโดจางมองเห็นได้ชัดเจนจาà¸à¹‚ลà¸[120] จาà¸à¸à¸²à¸£à¸—ี่ดาวอังคารมีมวลน้อย ความน่าจะเป็นที่จะถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸ˆà¸²à¸à¸§à¸±à¸•à¸–ุต่าง ๆ จึงอยู่ราวครึ่งหนึ่งของโลภà¹à¸•à¹ˆà¸”้วยตำà¹à¸«à¸™à¹ˆà¸‡à¸‚องดาวอังคารซึ่งใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¹à¸–บดาวเคราะห์น้อย ฉะนั้นจึงมีโอà¸à¸²à¸ªà¸¡à¸²à¸à¸‚ึ้นที่จะโดนจู่โจมโดยวัตถุมาà¸à¸¡à¸²à¸¢à¸ˆà¸²à¸à¹à¸–บดังà¸à¸¥à¹ˆà¸²à¸§ ดาวอังคารยังคล้ายว่าจะถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยดาวหางคาบสั้นอยู่บ่อยครั้งอีà¸à¸”้วย อย่างเช่นà¸à¸¥à¸¸à¹ˆà¸¡à¸—ี่อยู่ในวงโคจรของดาวพฤหัสบดี[121] นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰ หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่พบบนดาวอังคารเมื่อเทียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§à¸¢à¸±à¸‡à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸²à¸—ี่พบบนดวงจันทร์ค่อนข้างมาภเพราะบรรยาà¸à¸²à¸¨à¸‚องดาวอังคารสามารถปà¸à¸›à¹‰à¸­à¸‡à¸•à¹‰à¸²à¸™à¸—านต่ออุà¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็à¸à¹„ด้ หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸²à¸‡à¹à¸«à¹ˆà¸‡à¸¡à¸µà¸¥à¸±à¸à¸©à¸“ะทางสัณà¸à¸²à¸™à¸§à¸´à¸—ยาที่à¹à¸ªà¸”งว่าพื้นบริเวณนั้นเปียà¸à¸Šà¸·à¹‰à¸™à¸ à¸²à¸¢à¸«à¸¥à¸±à¸‡à¸ˆà¸²à¸à¸—ี่อุà¸à¸à¸²à¸šà¸²à¸•à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹à¸¥à¹‰à¸§[122]
+ภูเขาไฟ[à¹à¸à¹‰]
+ภาพโอลิมปัสมอนส์จาà¸à¸¢à¸²à¸™à¹„วà¸à¸´à¸‡à¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œ
+ดูบทความหลัà¸à¸—ี่: ภูเขาไฟบนดาวอังคาร
+
+ภูเขาไฟรูปโล่โอลิมปัสมอนส์ (เมาท์โอลิมปัส) เป็นภูเขาไฟที่ดับà¹à¸¥à¹‰à¸§à¹ƒà¸™à¸šà¸£à¸´à¹€à¸§à¸“ธาร์ซิส พื้นที่ราบสูงà¸à¸§à¹‰à¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸‹à¸¶à¹ˆà¸‡à¸¢à¸±à¸‡à¸¡à¸µà¸ à¸¹à¹€à¸‚าไฟขนาดใหà¸à¹ˆà¸­à¸·à¹ˆà¸™à¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸¥à¸¹à¸ โอลิมปัสมอนส์มีความสูงโดยประมาณà¸à¸§à¹ˆà¸²à¸ªà¸²à¸¡à¹€à¸—่าของความสูงของเขาเอเวอเรสต์ซึ่งเทียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§à¸ªà¸¹à¸‡à¹€à¸žà¸µà¸¢à¸‡ 8.8 à¸à¸´à¹‚ลเมตร (5.5 ไมล์)[123] ทำให้ภูเขาไฟลูà¸à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¹€à¸‚าที่สูงที่สุดหรือสูงเป็นอันดับสองในระบบสุริยะขึ้นอยู่à¸à¸±à¸šà¸§à¸´à¸˜à¸µà¸à¸²à¸£à¸§à¸±à¸”ซึ่งà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™à¸­à¸­à¸à¹„ป ทำให้ได้ค่าตัวเลขตั้งà¹à¸•à¹ˆ 21 ถึง 27 à¸à¸´à¹‚ลเมตร (13 ถึง 17 ไมล์)[124][125]
+ตำà¹à¸«à¸™à¹ˆà¸‡à¸˜à¸£à¸“ีภาค[à¹à¸à¹‰]
+เวลส์มาริเนริส (2001 Mars Odyssey)
+
+เวลส์มาริเนริส (เป็นรูปละตินของ หุบเขามาริเนอร์ หรือรู้จัà¸à¹ƒà¸™à¸Šà¸·à¹ˆà¸­ อะà¸à¸²à¸˜à¸²à¸”ีมอน ในà¹à¸œà¸™à¸—ี่คลองเà¸à¹ˆà¸²) เป็นหุบเขาขนาดใหà¸à¹ˆ มีความยาวร่วม 4,000 à¸à¸´à¹‚ลเมตร (2,500 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามลึà¸à¹„ด้มาà¸à¸–ึง 7 à¸à¸´à¹‚ลเมตร (4.3 ไมล์) ความยาวของเวลส์มาริเนริสเทียบเท่าà¸à¸±à¸šà¸„วามยาวของทวีปยุโรปà¹à¸¥à¸°à¸—อดยาวà¸à¸´à¸™à¸£à¸°à¸¢à¸°à¸—างà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸«à¹‰à¸²à¸‚องเส้นรอบวงของดาวอังคาร หาà¸à¹€à¸—ียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§ à¹à¸à¸£à¸™à¸”์à¹à¸„นยอนบนโลà¸à¸¡à¸µà¸„วามยาวเพียง 446 à¸à¸´à¹‚ลเมตร (277 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามลึà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸à¸·à¸­à¸š 2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) เท่านั้น เวลส์มาริเนริสà¸à¸³à¹€à¸™à¸´à¸”ขึ้นจาà¸à¸à¸²à¸£à¸›à¸¹à¸”นูนขึ้นของพื้นที่ธาร์ซิสจนเป็นสาเหตุให้เปลือà¸à¸”าวเคราะห์ในพื้นที่เวลส์มาริเนริสà¹à¸•à¸à¸—ลายออภมีà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¹ƒà¸™à¸›à¸µ 2012 (พ.ศ. 2555) ว่าเวลส์มาริเนริสไม่ได้เป็นเพียงà¹à¸„่à¸à¸£à¸²à¹€à¸šà¸™à¹à¸•à¹ˆà¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸‚อบเขตระหว่างà¹à¸œà¹ˆà¸™à¹€à¸›à¸¥à¸·à¸­à¸à¸”าวที่ปราà¸à¸à¸à¸²à¸£à¹€à¸„ลื่อนตัวà¹à¸šà¸šà¹€à¸¥à¸·à¹ˆà¸­à¸™à¸œà¹ˆà¸²à¸™à¸à¸±à¸™à¸à¸§à¹ˆà¸² 150 à¸à¸´à¹‚ลเมตร (93 ไมล์) ทำให้ดาวอังคารเป็นดาวเคราะห์ที่อาจจะมีà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸‚องà¹à¸œà¹ˆà¸™à¸˜à¸£à¸“ีภาคเป็นสองà¹à¸œà¹ˆà¸™[126][127]
+หลุมโพรง[à¹à¸à¹‰]
+
+ภาพจาà¸à¹€à¸˜à¸¡à¸´à¸ªà¸«à¸£à¸·à¸­à¸£à¸°à¸šà¸šà¸–่ายภาพจาà¸à¸à¸²à¸£à¸›à¸¥à¹ˆà¸­à¸¢à¸„วามร้อนซึ่งอยู่บนยาน 2001 มาร์สโอดิสซีของนาซา ได้เผยให้เห็นถึงปาà¸à¸—างเข้าถ้ำที่เป็นไปได้เจ็ดà¹à¸«à¹ˆà¸‡à¸šà¸£à¸´à¹€à¸§à¸“ด้านข้างของภูเขาไฟอาร์เซียมอนส์[128] มีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸–้ำเหล่านี้ตามชื่อของคนรัà¸à¹à¸•à¹ˆà¸¥à¸°à¸„นของบรรดาผู้คนพบซึ่งเรียà¸à¸£à¸§à¸¡ ๆ à¸à¸±à¸™à¸§à¹ˆà¸² "น้องสาวทั้งเจ็ด"[129] ปาà¸à¸—างเข้าถ้ำมีความà¸à¸§à¹‰à¸²à¸‡à¸§à¸±à¸”ได้ตั้งà¹à¸•à¹ˆ 100 ไปจนถึง 252 เมตร (328 ถึง 827 ฟุต) à¹à¸¥à¸°à¹€à¸Šà¸·à¹ˆà¸­à¸§à¹ˆà¸²à¸¡à¸µà¸„วามลึà¸à¸­à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸­à¸¢ 73 ถึง 96 เมตร (240 ถึง 315 ฟุต) เนื่องจาà¸à¹à¸ªà¸‡à¹„ม่สามารถส่องลงถึงพื้นของเà¸à¸·à¸­à¸šà¸—ุà¸à¸–้ำ จึงเป็นไปได้ว่าตัวถ้ำอาจทอดยาวลึà¸à¹€à¸‚้าไปมาà¸à¸à¸§à¹ˆà¸²à¸„่าขั้นต่ำที่ประเมินไว้à¹à¸¥à¸°à¸­à¸²à¸ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸­à¸­à¸à¹ƒà¸•à¹‰à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ มีเฉพาะ "เดนา" เท่านั้นที่เป็นข้อยà¸à¹€à¸§à¹‰à¸™à¹€à¸žà¸£à¸²à¸°à¸ªà¸²à¸¡à¸²à¸£à¸–มองเห็นพื้นถ้ำà¹à¸¥à¸°à¸§à¸±à¸”ความลึà¸à¹„ด้เท่าà¸à¸±à¸š 130 เมตร (430 ฟุต) ภายในถ้ำโพรงเหล่านี้น่าจะเป็นบริเวณที่ปลอดภัยจาà¸à¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็ภรังสีอัลตราไวโอเลต เปลวสุริยะ à¹à¸¥à¸°à¸­à¸™à¸¸à¸ à¸²à¸„พลังงานสูงต่าง ๆ ที่à¸à¸£à¸°à¸«à¸™à¹ˆà¸³à¸Šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸‚องดาวเคราะห์[130]
+บรรยาà¸à¸²à¸¨[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: บรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร
+บรรยาà¸à¸²à¸¨à¸—ี่หลุดหนีไปจาà¸à¸”าวอังคาร (คาร์บอน ออà¸à¸‹à¸´à¹€à¸ˆà¸™ à¹à¸¥à¸°à¹„ฮโดรเจน) โดยเมเว็นในรังสียูวี[131]
+
+ดาวอังคารสูà¸à¹€à¸ªà¸µà¸¢à¹à¸¡à¹‡à¸à¸™à¸µà¹‚ตสเฟียร์ไปเมื่อสี่พันล้านปีà¸à¹ˆà¸­à¸™[132] อาจเพราะà¸à¸²à¸£à¸Šà¸™à¸¡à¸²à¸à¸¡à¸²à¸¢à¸«à¸¥à¸²à¸¢à¸„รั้งโดยดาวเคราะห์น้อย[133] ทำให้ลมสุริยะมีปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸à¸£à¸°à¸—บโดยตรงà¸à¸±à¸šà¹„อโอโนสเฟียร์ของดาวอังคาร ลดความหนาà¹à¸™à¹ˆà¸™à¸‚องบรรยาà¸à¸²à¸¨à¸¥à¸‡à¹„ปเรื่อย ๆ โดยปอà¸à¹€à¸›à¸¥à¸·à¹‰à¸­à¸‡à¸­à¸°à¸•à¸­à¸¡à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸Šà¸±à¹‰à¸™à¸™à¸­à¸à¹ƒà¸«à¹‰à¸«à¸¥à¸¸à¸”ลอยออà¸à¹„ป ทั้งมาร์สโà¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œà¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸­à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ªà¸•à¹ˆà¸²à¸‡à¸à¹‡à¸•à¸£à¸§à¸ˆà¸žà¸šà¸­à¸™à¸¸à¸ à¸²à¸„ของบรรยาà¸à¸²à¸¨à¸—ี่à¹à¸•à¸à¸•à¸±à¸§à¹€à¸›à¹‡à¸™à¸›à¸£à¸°à¸ˆà¸¸à¸¥à¸²à¸à¹€à¸›à¹‡à¸™à¸«à¸²à¸‡à¸¢à¸²à¸§à¹ƒà¸™à¸«à¹‰à¸§à¸‡à¸­à¸§à¸à¸²à¸¨à¹€à¸šà¸·à¹‰à¸­à¸‡à¸«à¸¥à¸±à¸‡à¸”าวอังคาร[132][134] à¹à¸¥à¸°à¸à¸²à¸£à¸ªà¸¹à¸à¹€à¸ªà¸µà¸¢à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¹„ปนี้à¸à¸³à¸¥à¸±à¸‡à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¹‚ดยยานเมเว็น เมื่อเทียบà¸à¸±à¸šà¹‚ลà¸à¹à¸¥à¹‰à¸§à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚องดาวอังคารเบาบางà¸à¸§à¹ˆà¸²à¸¡à¸²à¸ ความà¸à¸”อาà¸à¸²à¸¨à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ ณ ปัจจุบันอยู่ในช่วงตั้งà¹à¸•à¹ˆà¸™à¹‰à¸­à¸¢à¸ªà¸¸à¸”ที่ 30 ปาสà¸à¸²à¸¥ (0.030 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) บนยอดโอลิมปัสมอนส์ ไปจนถึง 1,155 ปาสà¸à¸²à¸¥ (1.155 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) ในเฮลลาสเพลนิเชีย โดยมีความà¸à¸”อาà¸à¸²à¸¨à¹€à¸‰à¸¥à¸µà¹ˆà¸¢à¸—ี่ระดับพื้นผิวเท่าà¸à¸±à¸š 600 ปาสà¸à¸²à¸¥ (0.60 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥)[135] ความหนาà¹à¸™à¹ˆà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸ªà¸¹à¸‡à¸ªà¸¸à¸”บนดาวอังคารมีค่าเทียบเท่าà¸à¸±à¸šà¸„วามดัน ณ จุดที่สูง 35 à¸à¸´à¹‚ลเมตร (22 ไมล์)[136] เหนือพื้นผิวโลภเป็นผลให้ความหนาà¹à¸™à¹ˆà¸™à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸„ิดเป็นเพียงร้อยละ 0.6 ของโลà¸à¹€à¸—่านั้น (101.3 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) มีมาตราความสูงของบรรยาà¸à¸²à¸¨à¸—ี่ประมาณ 10.8 à¸à¸´à¹‚ลเมตร (6.7 ไมล์)[137] ซึ่งสูงà¸à¸§à¹ˆà¸²à¹‚ลภ(6 à¸à¸´à¹‚ลเมตร (3.7 ไมล์)) เพราะความโน้มถ่วงที่พื้นผิวดาวอังคารมีค่าเพียงร้อยละ 38 ของโลภรวมถึงผลชดเชยจาà¸à¸—ั้งà¸à¸²à¸£à¸¡à¸µà¸­à¸¸à¸“หภูมิต่ำà¹à¸¥à¸°à¸™à¹‰à¸³à¸«à¸™à¸±à¸à¹‚มเลà¸à¸¸à¸¥à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²à¸„่าเฉลี่ยร้อยละ 50 ของบรรยาà¸à¸²à¸¨à¸”าวอังคาร
+บรรยาà¸à¸²à¸¨à¸—ี่เบาบางของดาวอังคาร มองเห็นจาà¸à¸‚อบฟ้า
+
+บรรยาà¸à¸²à¸¨à¸‚องดาวอังคารประà¸à¸­à¸šà¸”้วยคาร์บอนไดออà¸à¹„ซด์ร้อยละ 96 อาร์à¸à¸­à¸™à¸£à¹‰à¸­à¸¢à¸¥à¸° 1.93 à¹à¸¥à¸°à¹„นโตรเจนร้อยละ 1.89 ร่วมไปà¸à¸±à¸šà¸­à¸­à¸à¸‹à¸´à¹€à¸ˆà¸™à¹à¸¥à¸°à¸™à¹‰à¸³à¹ƒà¸™à¸›à¸£à¸´à¸¡à¸²à¸“เล็à¸à¸™à¹‰à¸­à¸¢[6][138] บรรยาà¸à¸²à¸¨à¸¡à¸µà¸à¸¸à¹ˆà¸™à¸„่อนข้างมาà¸à¹‚ดยเป็นอนุภาคขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 1.5 ไมโครเมตร ซึ่งทำให้ท้องฟ้าของดาวอังคารดูเป็นสีน้ำตาลปนเหลืองเมื่อมองจาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§[139]
+
+มีà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸¡à¸µà¹€à¸—นในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคารโดยมีเศษส่วนโมลที่ประมาณ 30 ส่วนในพันล้านส่วน[14][140] พบปราà¸à¸à¹ƒà¸™à¸à¸²à¸£à¸žà¸¥à¸¹à¸¡à¸‚องà¹à¸à¹Šà¸ªà¹à¸¥à¸°à¸ à¸²à¸§à¸°à¸à¸²à¸£à¸“์à¹à¸ªà¸”งไปในทางว่ามีà¸à¸²à¸£à¸›à¸¥à¸”ปล่อยมีเทนออà¸à¸¡à¸²à¸ˆà¸²à¸à¹à¸–บท้องที่เฉพาะบางà¹à¸«à¹ˆà¸‡ ในช่วงà¸à¸¥à¸²à¸‡à¸¤à¸”ูร้อนของซีà¸à¹€à¸«à¸™à¸·à¸­ à¸à¸²à¸£à¸žà¸¥à¸¹à¸¡à¸«à¸¥à¸±à¸à¸¡à¸µà¸›à¸£à¸´à¸¡à¸²à¸“มีเทนอยู่ถึง 19,000 เมตริà¸à¸•à¸±à¸™ คาดà¸à¸²à¸£à¸“์ว่าà¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”มีà¸à¸³à¸¥à¸±à¸‡à¸à¸²à¸£à¸›à¸¥à¸”ปล่อยราว 0.6 à¸à¸´à¹‚ลà¸à¸£à¸±à¸¡à¸•à¹ˆà¸­à¸§à¸´à¸™à¸²à¸—ี[141][142] ข้อมูลที่พบชี้ว่าน่าจะมีบริเวณท้องที่ที่เป็นà¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”สองà¹à¸«à¹ˆà¸‡ ศูนย์à¸à¸¥à¸²à¸‡à¹à¸«à¹ˆà¸‡à¹à¸£à¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸à¸¥à¹‰ 30°N 260°W / 30°N 260°W à¹à¸¥à¸°à¹à¸«à¹ˆà¸‡à¸—ี่สองใà¸à¸¥à¹‰ 0°N 310°W / 0°N 310°W[141] ประมาณà¸à¸²à¸£à¸§à¹ˆà¸²à¸”าวอังคารจะต้องมีà¸à¸²à¸£à¸œà¸¥à¸´à¸•à¸¡à¸µà¹€à¸—นปริมาณ 270 ตันต่อปี[141][143]
+
+มีเทนสามารถอยู่ในบรรยาà¸à¸²à¸¨à¸”าวอังคารได้เพียงเฉพาะช่วงเวลาจำà¸à¸±à¸”ระยะหนึ่งเท่านั้นà¸à¹ˆà¸­à¸™à¸—ี่จะถูà¸à¸—ำลาย ประมาณว่ามีช่วงชีวิตยืนยาวได้ตั้งà¹à¸•à¹ˆ 0.6 ถึง 4 ปี[141][144] à¸à¸²à¸£à¸—ี่มีมีเทนดำรงอยู่ทั้ง ๆ ที่เป็นสารที่ช่วงชีวิตสั้นเช่นนี้จึงบ่งชี้ว่าจะต้องมีà¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¹à¸à¹Šà¸ªà¸”ังà¸à¸¥à¹ˆà¸²à¸§à¸—ี่ยังดำเนินà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™ ทั้งà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องภูเขาไฟ à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยดาวหาง à¹à¸¥à¸°à¸à¸²à¸£à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸‚องสิ่งมีชีวิตพวà¸à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸—ี่สร้างมีเทนล้วนเป็นà¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¸—ี่เป็นไปได้ นอà¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¡à¸µà¹€à¸—นยังสามารถผลิตขึ้นได้โดยà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—ี่ไม่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸² เซอร์เพนทิไนเซชัน [b] (à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹€à¸‹à¸­à¸£à¹Œà¹€à¸žà¸™à¸—ีน) โดยอาศัยน้ำ คาร์บอนไดออà¸à¹„ซด์ à¹à¸¥à¸°à¹à¸£à¹ˆà¹‚อลิวีนซึ่งต่างà¸à¹‡à¸žà¸šà¹„ด้ทั่วไปบนดาวอังคาร[145]
+à¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¹à¸¥à¸°à¸à¸±à¸à¹€à¸à¹‡à¸šà¸¡à¸µà¹€à¸—น (CH4) ที่มีศัà¸à¸¢à¸ à¸²à¸žà¸šà¸™à¸”าวอังคาร
+
+ยานสำรวจภาคพื้นคิวริออซิตี ซึ่งลงจอดบนดาวอังคารในเดือนสิงหาคม 2012 (พ.ศ. 2555) นั้นมีความสามารถตรวจวัดเพื่อà¹à¸¢à¸à¹à¸¢à¸°à¸„วามà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸‚องมีเทนที่ได้จาà¸à¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”ที่ต่างà¸à¸±à¸™à¸­à¸­à¸à¸ˆà¸²à¸à¸à¸±à¸™à¹„ด้[146] à¹à¸•à¹ˆà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸à¸²à¸£à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ à¸²à¸£à¸à¸´à¸ˆà¸™à¸±à¹‰à¸™à¸ˆà¸°à¸Šà¸µà¹‰à¸‚าดได้จริง ๆ ว่าสิ่งมีชีวิตขนาดเล็à¸à¸ˆà¸´à¹‹à¸§à¸šà¸™à¸”าวอังคารเป็นผู้ให้à¸à¸³à¹€à¸™à¸´à¸”มีเทน บรรดาสิ่งมีชีวิตเหล่านั้นà¸à¹‡à¹€à¸«à¸¡à¸·à¸­à¸™à¸ˆà¸°à¸­à¸¢à¸¹à¹ˆà¸•à¹ˆà¸³à¸¥à¸‡à¹„ปเบื้องล่างพื้นผิวนอà¸à¹€à¸«à¸™à¸·à¸­à¸‚อบเขตที่ตัวยานจะเข้าถึง[147] à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”à¹à¸£à¸à¹‚ดยเครื่องวัดสเปà¸à¸•à¸£à¸±à¸¡à¹€à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹à¸šà¸šà¸›à¸£à¸±à¸šà¹„ด้à¹à¸ªà¸”งข้อมูลว่ามีมีเทนต่ำà¸à¸§à¹ˆà¸² 5 ส่วนในพันล้านส่วน ณ จุดที่ทำà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”ในตำà¹à¸«à¸™à¹ˆà¸‡à¸¥à¸‡à¸ˆà¸­à¸”[148][149][150][151] เมื่อ 19 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2013 (พ.ศ. 2556) นัà¸à¸§à¸´à¸—ยาศาสตร์นาซาได้เผยผลà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸„ืบหน้าจาà¸à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”โดยคิวริออซิตี ว่า ตรวจไม่พบมีเทนในบรรยาà¸à¸²à¸¨à¹ƒà¸™à¸„่าà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸” 0.18±0.67 ส่วนในพันล้านส่วนปริมาตร สอดคล้องà¸à¸±à¸šà¸‚อบเขตบนที่เฉพาะ 1.3 ส่วนในพันล้านส่วนปริมาตร (ขอบเขตความเชื่อมั่นร้อยละ 95) à¹à¸¥à¸°à¸ˆà¸²à¸à¸œà¸¥à¸¥à¸±à¸žà¸˜à¹Œà¸™à¸µà¹‰à¸—ำให้สรุปได้ว่าความเป็นไปได้ที่จะมีà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องจุลชีพที่สร้างมีเทนบนดาวอังคารในปัจจุบันนั้นลดลง[152][153][154]
+
+ยานมาร์สออร์บิเตอร์มิชชันของอินเดียมีปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸„้นหามีเทนในบรรยาà¸à¸²à¸¨[155] ในขณะที่เอ็à¸à¹‚ซมาร์สเทรซà¹à¸à¹Šà¸ªà¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œà¸¡à¸µà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸‚ึ้นปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹ƒà¸™à¸›à¸µ 2016 (พ.ศ. 2559) เพื่อศีà¸à¸©à¸²à¹ƒà¸«à¹‰à¹€à¸‚้าใจมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸¡à¸µà¹€à¸—นรวมไปถึงสารที่ได้จาà¸à¸à¸²à¸£à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¸‚องมีเทนด้วย เช่น ฟอร์มาลดีไฮด์ à¹à¸¥à¸°à¹€à¸¡à¸—านอล[156]
+
+ในวันที่ 16 ธันวาคม 2014 (พ.ศ. 2557) นาซารายงานว่ายานโรเวอร์คิวริออซิตี ตรวจพบปริมาณมีเทนในบรรยาà¸à¸²à¸¨à¸”าวอังคารเพิ่มสูงนับสิบเท่าในเฉพาะถิ่น ตัวอย่างที่ตรวจวัดได้ถือว่าสูงเป็นสิบเท่าในรอบ 20 เดือน à¹à¸ªà¸”งà¸à¸²à¸£à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นในปลายปี 2013 à¹à¸¥à¸°à¸•à¹‰à¸™à¸›à¸µ 2014 โดยมีค่าเฉลี่ยของมีเทนเป็น 7 ส่วนในพันล้านส่วนในบรรยาà¸à¸²à¸¨ ซึ่งในเวลาà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸«à¸£à¸·à¸­à¸«à¸¥à¸±à¸‡à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸„่าเฉลี่ยที่วัดได้อยู่ประมาณหนึ่งในสิบของค่าดังà¸à¸¥à¹ˆà¸²à¸§[157][158]
+
+มีà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¹à¸­à¸¡à¹‚มเนียอย่างคร่าว ๆ บนดาวอังคารà¹à¸¥à¹‰à¸§à¹€à¸Šà¹ˆà¸™à¸à¸±à¸™à¹‚ดยยานดาวเทียมมาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª à¹à¸•à¹ˆà¸”้วยความที่เป็นสารช่วงชีวิตค่อนข้างสั้นจึงไม่เป็นที่à¹à¸™à¹ˆà¸Šà¸±à¸”ว่าถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸¡à¸²à¸ˆà¸²à¸à¸­à¸°à¹„ร[159] à¹à¸­à¸¡à¹‚มเนียนั้นไม่เสถียรในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร à¹à¸¥à¸°à¸ˆà¸°à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¹„ปในเวลาเพียงไม่à¸à¸µà¹ˆà¸Šà¸±à¹ˆà¸§à¹‚มง à¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”หนึ่งที่น่าจะเป็นไปได้คือà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องภูเขาไฟ[159]
+ออโรรา[à¹à¸à¹‰]
+
+ในปี 1994 (พ.ศ. 2537) ยานอวà¸à¸²à¸¨à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸­à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ªà¸‚ององค์à¸à¸²à¸£à¸­à¸§à¸à¸²à¸¨à¸¢à¸¸à¹‚รปพบà¸à¸²à¸£à¹€à¸£à¸·à¸­à¸‡à¹à¸ªà¸‡à¸­à¸±à¸¥à¸•à¸£à¸²à¹„วโอเลตจาภ"ร่มà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸" ในซีà¸à¹ƒà¸•à¹‰à¸‚องดาว ดาวอังคารไม่มีสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸—ี่ครอบคลุมทั้งดาวซึ่งจะนำทางอนุภาคมีประจุทั้งหลายให้เข้าสู่ชั้นบรรยาà¸à¸²à¸¨ ดาวอังคารมีสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸£à¸¹à¸›à¸£à¹ˆà¸¡à¸­à¸¢à¸¹à¹ˆà¸«à¸¥à¸²à¸¢à¹à¸«à¹ˆà¸‡ ส่วนใหà¸à¹ˆà¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸‹à¸µà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¹€à¸›à¹‡à¸™à¸‹à¸²à¸à¸«à¸¥à¸‡à¹€à¸«à¸¥à¸·à¸­à¸‚องสนามซึ่งเคยครอบคลุมทั้งพิภพดาวà¹à¸•à¹ˆà¹€à¸ªà¸·à¹ˆà¸­à¸¡à¸ªà¸¥à¸²à¸¢à¹„ปเมื่อหลายพันล้านปีà¸à¹ˆà¸­à¸™
+
+ในปลายเดือนธันวาคม 2014 (พ.ศ. 2557) ยานอวà¸à¸²à¸¨à¹€à¸¡à¹€à¸§à¹‡à¸™à¸‚องนาซาตรวจพบหลัà¸à¸à¸²à¸™à¸à¸²à¸£à¹à¸œà¹ˆà¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸›à¹‡à¸™à¸šà¸£à¸´à¹€à¸§à¸“à¸à¸§à¹‰à¸²à¸‡à¸‚องออโรราบนซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคาร à¹à¸¥à¸°à¸—อดต่ำลงถึงละติจูดประมาณ 20-30 องศาเหนือจาà¸à¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£à¸”าวอังคาร ในขณะที่ออโรราบนโลà¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸£à¸°à¸¢à¸°à¸ªà¸¹à¸‡ 100 ถึง 500 à¸à¸´à¹‚ลเมตรจาà¸à¸œà¸´à¸§à¸”าวเคราะห์ à¹à¸•à¹ˆà¸šà¸™à¸”าวอังคารอนุภาคที่à¸à¹ˆà¸­à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”ออโรราทะลวงผ่านบรรยาà¸à¸²à¸¨à¸‚องดาวเข้ามาสร้างออโรราขึ้นในระดับต่ำà¸à¸§à¹ˆà¸² 100 à¸à¸´à¹‚ลเมตรจาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ สนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹ƒà¸™à¸¥à¸¡à¸ªà¸¸à¸£à¸´à¸¢à¸°à¹‚อบคลุมดาวอังคาร เข้าสู่บรรยาà¸à¸²à¸¨ à¹à¸¥à¸°à¸­à¸™à¸¸à¸ à¸²à¸„มีประจุตามเส้นà¹à¸£à¸‡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸‚องลมสุริยะเข้าสู่บรรยาà¸à¸²à¸¨à¸—ำให้ออโรราเà¸à¸´à¸”ขึ้นภายนอà¸à¸£à¹ˆà¸¡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸[160]
+
+วันที่ 18 มีนาคม 2015 (พ.ศ. 2558) นาซารายงานà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸­à¸­à¹‚รราที่ยังไม่เป็นที่เข้าใจà¹à¸™à¹ˆà¸Šà¸±à¸” à¹à¸¥à¸°à¹€à¸¡à¸†à¸à¸¸à¹ˆà¸™à¸—ี่ยังไมมีคำอธิบายภายในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร[161]
+ภูมิอาà¸à¸²à¸¨[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ภูมิอาà¸à¸²à¸¨à¸‚องดาวอังคาร
+18 พฤศจิà¸à¸²à¸¢à¸™ 2012
+25 พฤศจิà¸à¸²à¸¢à¸™ 2012
+พายุà¸à¸¸à¹ˆà¸™à¸šà¸™à¸”าวอังคาร ยานออปพอร์ทูนิตีà¹à¸¥à¸°à¸¢à¸²à¸™à¸„ิวริออซิตี มีเครื่องหมายà¸à¸³à¸à¸±à¸š
+
+จาà¸à¸”าวเคราะห์ทั้งหมดในระบบสุริยะ ฤดูà¸à¸²à¸¥à¸‚องดาวอังคารมีความใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด เนื่องจาà¸à¸„วามเอียงของà¹à¸à¸™à¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸‚องดาวทั้งสองที่คล้ายคลึงà¸à¸±à¸™ ระยะเวลาของà¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¸”าวอังคารมีความยาวประมาณสองเท่าของฤดูà¸à¸²à¸¥à¸šà¸™à¹‚ลภเพราะดาวอังคารมีระยะห่างจาà¸à¸”วงอาทิตย์มาà¸à¸à¸§à¹ˆà¸² หนึ่งปีของดาวอังคารจึงยาวนานร่วมสองปีของโลภอุณหภูมิบนพื้นผิวดาวอังคารผันà¹à¸›à¸£à¸ˆà¸²à¸à¸„่าต่ำสุดที่ประมาณ -143 องศาเซลเซียส (-225 องศาฟาเรนไฮต์) ที่บริเวณà¹à¸œà¹ˆà¸™à¸‚ั้วดาวในฤดูหนาว[8] จนถึงค่าสูงสุดที่ประมาณ 35 องศาเซลเซียส (95 องศาฟาเรนไฮต์) ในฤดูร้อนบริเวณศูนย์สูตร[9] à¸à¸²à¸£à¸¡à¸µà¸Šà¹ˆà¸§à¸‡à¸­à¸¸à¸“หภูมิที่à¸à¸§à¹‰à¸²à¸‡à¸¡à¸²à¸à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸œà¸¥à¸¡à¸²à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่เบาบางจนไม่สามารถà¸à¸±à¸à¹€à¸à¹‡à¸šà¸„วามร้อนจาà¸à¸”วงอาทิตย์ได้มาà¸à¸™à¸±à¸ à¸à¸²à¸£à¸¡à¸µà¸„วามà¸à¸”อาà¸à¸²à¸¨à¸—ี่ต่ำ à¹à¸¥à¸°à¸à¸²à¸£à¸—ี่มีค่าความเฉื่อยความร้อนต่ำของดินบนดาวอังคาร[162] ระยะห่างจาà¸à¸”วงอาทิตย์ถึงดาวอังคารคิดเป็น 1.52 เท่าเมื่อเทียบà¸à¸±à¸šà¸£à¸°à¸¢à¸°à¸ˆà¸²à¸à¸”วงอาทิตย์ถึงโลภทำให้ดาวอังคารได้รับà¹à¸ªà¸‡à¸ˆà¸²à¸à¸”วงอาทิตย์เพียงร้อยละ 43 ต่อหน่วยพื้นที่เมื่อเทียบà¸à¸±à¸šà¹‚ลà¸[163]
+
+ถ้าหาà¸à¸”าวอังคารมีวงโคจรà¹à¸šà¸šà¹€à¸”ียวà¸à¸±à¸šà¹‚ลà¸à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¸‚องดาวอังคารà¸à¹‡à¸ˆà¸°à¹€à¸«à¸¡à¸·à¸­à¸™à¹‚ลภà¹à¸•à¹ˆà¸à¸²à¸£à¸¡à¸µà¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรมาà¸à¸à¸§à¹ˆà¸²à¹€à¸¡à¸·à¹ˆà¸­à¹€à¸›à¸£à¸µà¸¢à¸šà¸à¸±à¸™à¸™à¸µà¹‰à¹€à¸­à¸‡à¸—ี่ส่งผลà¸à¸£à¸°à¸—บสำคัภดาวอังคารเข้าใà¸à¸¥à¹‰à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดเมื่อเป็นฤดูร้อนในดาวซีà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸­à¸à¹‡à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸¤à¸”ูหนาว à¹à¸¥à¸°à¹€à¸‚้าใà¸à¸¥à¹‰à¸ˆà¸¸à¸”ไà¸à¸¥à¸”วงอาทิตย์ที่สุดเมื่อเป็นฤดูหนาวในดาวซีà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸­à¸à¹‡à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸¤à¸”ูร้อน ผลที่ตามมาคือฤดูà¸à¸²à¸¥à¹ƒà¸™à¸”าวซีà¸à¹ƒà¸•à¹‰à¸ˆà¸°à¸£à¸¸à¸™à¹à¸£à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹à¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¹ƒà¸™à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸°à¸­à¹ˆà¸­à¸™à¹€à¸šà¸²à¸à¸§à¹ˆà¸²à¸­à¸µà¸à¸‹à¸µà¸à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¹à¸•à¹ˆà¸¥à¸°à¸à¸£à¸“ี อุณหภูมิในฤดูร้อนของดาวซีà¸à¹ƒà¸•à¹‰à¸ªà¸²à¸¡à¸²à¸£à¸–อุ่นได้มาà¸à¸à¸§à¹ˆà¸²à¸­à¸¸à¸“หภูมิในฤดูร้อนของดาวซีà¸à¹€à¸«à¸™à¸·à¸­à¹„ด้ถึง 30 เคลวิน (30 องศาเซลเซียส หรือ 54 องศาฟาเรนไฮต์)[164]
+
+ดาวอังคารมีพายุà¸à¸¸à¹ˆà¸™à¸—ี่ใหà¸à¹ˆà¸—ี่สุดในระบบสุริยะ มีได้ตั้งà¹à¸•à¹ˆà¸žà¸²à¸¢à¸¸à¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่เล็ภๆ ไปจนถึงพายุขนาดมโหฬารที่ครอบคลุมทั่วทั้งดาวเคราะห์ พายุเหล่านี้มัà¸à¸ˆà¸°à¹€à¸à¸´à¸”ขึ้นเมื่อดาวอังคารเข้าใà¸à¸¥à¹‰à¸”วงอาทิตย์à¹à¸¥à¸°à¹à¸ªà¸”งให้เห็นà¸à¸²à¸£à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นของอุณหภูมิบนดาว[165]
+วงโคจรà¹à¸¥à¸°à¸à¸²à¸£à¸«à¸¡à¸¸à¸™[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: วงโคจรของดาวอังคาร
+ดาวอังคารห่างจาà¸à¸”วงอาทิตย์ประมาณ 230 ล้านà¸à¸´à¹‚ลเมตร (143 ล้านไมล์) คาบà¸à¸²à¸£à¹‚คจรเท่าà¸à¸±à¸š 687 วัน (โลà¸) à¹à¸ªà¸”งด้วยวงสีà¹à¸”ง สีน้ำเงินคือวงโคจรโลà¸
+
+ดาวอังคารไà¸à¸¥à¸ˆà¸²à¸à¸”วงอาทิตย์ด้วยระยะทางเฉลี่ย 230 ล้านà¸à¸´à¹‚ลเมตรโดยประมาณ (143 ล้านไมล์, 1.5 หน่วยดาราศาสตร์) à¹à¸¥à¸°à¸¡à¸µà¸„าบà¸à¸²à¸£à¹‚คจรเท่าà¸à¸±à¸š 687 วันของโลภหนึ่งวันสุริยะบนดาวอังคารยาวà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™à¸‚องโลà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸­à¸¢à¸„ือเท่าà¸à¸±à¸š 24 ชั่วโมง 39 นาที 35.244 วินาที หนึ่งปีของดาวอังคารเท่าà¸à¸±à¸š 1.8809 ปีของโลภหรือ 1 ปี 320 วัน à¸à¸±à¸šà¸­à¸µà¸ 18.2 ชั่วโมง[6]
+
+ดาวอังคารมีความเอียงของà¹à¸à¸™à¹€à¸—่าà¸à¸±à¸š 25.19 องศา สัมพัทธ์à¸à¸±à¸šà¸£à¸°à¸™à¸²à¸šà¸à¸²à¸£à¹‚คจรซึ่งคล้ายคลึงà¸à¸±à¸šà¸„วามเอียงของà¹à¸à¸™à¹‚ลà¸[6] เป็นผลให้ดาวอังคารมีฤดูà¸à¸²à¸¥à¸„ล้ายโลà¸à¹à¸¡à¹‰à¸§à¹ˆà¸²à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูบนดาวอังคารจะยาวเà¸à¸·à¸­à¸šà¸ªà¸­à¸‡à¹€à¸—่าเพราะคาบà¸à¸²à¸£à¹‚คจรที่ยาวนานà¸à¸§à¹ˆà¸² ณ ปัจจุบัน ขั้วเหนือของดาวอังคารมีà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸Šà¸µà¹‰à¹„ปใà¸à¸¥à¹‰à¸à¸±à¸šà¸”าวฤà¸à¸©à¹Œà¹€à¸”เนบ[11] ดาวอังคารผ่านจุดไà¸à¸¥à¸”วงอาทิตย์ที่สุดในเดือนà¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ 2012 (พ.ศ. 2555)[166][167] ผ่านจุดใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดในเดือนมà¸à¸£à¸²à¸„ม 2013 (พ.ศ. 2556)[166] จุดไà¸à¸¥à¸”วงอาทิตย์ที่สุดถัดไปคือมà¸à¸£à¸²à¸„ม 2014 (พ.ศ. 2557)[166] à¹à¸¥à¸°à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดถัดไปคือธันวาคมปีเดียวà¸à¸±à¸™[166]
+
+ดาวอังคารมีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรค่อนข้างเด่นชัดที่ประมาณ 0.09 เมื่อเทียบà¸à¸±à¸šà¸”าวเคราะห์อื่นอีà¸à¹€à¸ˆà¹‡à¸”ดวงในระบบสุริยะà¹à¸¥à¹‰à¸§ มีเพียงดาวพุธเท่านั้นที่มีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรมาà¸à¸à¸§à¹ˆà¸² เป็นที่ทราบว่าในอดีตดาวอังคารมีวงโคจรที่à¸à¸¥à¸¡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹ƒà¸™à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸¡à¸²à¸ ที่ขณะหนึ่งเมื่อ 1.35 ล้านปีà¸à¹ˆà¸­à¸™ ดาวอังคารมีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸—ี่ราว 0.002 ซึ่งน้อยยิ่งà¸à¸§à¹ˆà¸²à¹‚ลà¸à¹ƒà¸™à¸•à¸­à¸™à¸™à¸µà¹‰[168] วัà¸à¸ˆà¸±à¸à¸£à¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องดาวอังคารอยู่ที่ 96,000 ปีโลภเทียบà¸à¸±à¸šà¹‚ลà¸à¸—ี่วัà¸à¸ˆà¸±à¸à¸£à¹€à¸”ียวà¸à¸±à¸™à¸­à¸¢à¸¹à¹ˆà¸—ี่ 100,000 ปี[169] ดาวอังคารยังมีวัà¸à¸ˆà¸±à¸à¸£à¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸­à¸µà¸à¹à¸šà¸šà¸«à¸™à¸¶à¹ˆà¸‡à¸—ี่à¸à¸´à¸™à¹€à¸§à¸¥à¸²à¸¢à¸²à¸§à¸™à¸²à¸™à¸à¸§à¹ˆà¸²à¸™à¸µà¹‰à¸”้วยคาบราว 2.2 ล้านปีโลภซึ่งมีความสำคัà¸à¸šà¸”บังà¸à¸£à¸²à¸Ÿà¸§à¸±à¸à¸ˆà¸±à¸à¸£ 96,000 ปี นับจาภ35,000 ปีที่ผ่านมา วงโคจรของดาวอังคารมีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นทีละน้อยเพราะผลà¸à¸£à¸°à¸—บเชิงโน้มถ่วงจาà¸à¸”าวเคราะห์ดวงอื่น ๆ ระยะที่ใà¸à¸¥à¹‰à¸—ี่สุดระหว่างโลà¸à¹à¸¥à¸°à¸”าวอังคารจะลดลงอย่างค่อยเป็นค่อยไปต่อเนื่องตลอดระยะเวลา 25,000 ปีข้างหน้า[170]
+à¸à¸²à¸£à¸„้นหาสิ่งมีชีวิต[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: สิ่งมีชีวิตบนดาวอังคาร à¹à¸¥à¸° à¸à¸²à¸£à¸—ดลองทางชีววิทยาโดยยานส่วนลงจอดไวà¸à¸´à¸‡
+ยานส่วนลงจอดไวà¸à¸´à¸‡ 1 - à¹à¸‚นสุ่มตัวอย่างสร้างร่องลึภตัà¸à¸§à¸±à¸ªà¸”ุเพื่อทำà¸à¸²à¸£à¸—ดสอบ (คริสเพลนิเชีย)
+
+ตามความเข้าใจในปัจจุบันเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸„วามสามารถอยู่อาศัยได้ของดาวเคราะห์ หรือความสามารถที่โลà¸à¹ƒà¸”โลà¸à¸«à¸™à¸¶à¹ˆà¸‡à¸¡à¸µà¸ à¸²à¸§à¸°à¸à¸²à¸£à¸“์ทางสิ่งà¹à¸§à¸”ล้อมเจริà¸à¸žà¸±à¸’นาขึ้นจนชีวิตอุบัติขึ้นได้ เช่นดาวเคราะห์ที่เอื้อให้มีน้ำของเหลวอยู่บนพื้นผิว เà¸à¸“ฑ์ที่ต้องà¸à¸²à¸£à¹‚ดยมาà¸à¸„ือวงโคจรของดาวเคราะห์นั้นต้องอยู่ในเขตอาศัยได้ ซึ่งในà¸à¸£à¸“ีของดวงอาทิตย์คือตั้งà¹à¸•à¹ˆà¹à¸–บพ้นจาà¸à¸”าวศุà¸à¸£à¹Œà¸­à¸­à¸à¹„ปจนถึงระยะประมาณà¸à¸¶à¹ˆà¸‡à¹à¸à¸™à¹€à¸­à¸à¸‚องดาวอังคาร[171] ระหว่างà¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุด ดาวอังคารได้ล่วงเข้าไปในเขตนี้ à¹à¸•à¹ˆà¸”้วยความที่มีบรรยาà¸à¸²à¸¨à¹€à¸šà¸²à¸šà¸²à¸‡ ความà¸à¸”อาà¸à¸²à¸¨à¸—ี่ต่ำเป็นอุปสรรคไม่ให้น้ำของเหลวปà¸à¸„ลุมภูมิประเทศเป็นบริเวณà¸à¸§à¹‰à¸²à¸‡à¹„ด้ในช่วงระยะเวลาที่นานพอ à¸à¸²à¸£à¹„หลของน้ำของเหลวในอดีตเป็นเครื่องพิสูจน์ว่าดาวอังคารมีศัà¸à¸¢à¸ à¸²à¸žà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¸­à¸¢à¸¹à¹ˆà¸­à¸²à¸¨à¸±à¸¢à¸‚องสิ่งมีชีวิต หลัà¸à¸à¸²à¸™à¸—ี่พบใหม่บางประà¸à¸²à¸£à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸™à¹‰à¸³à¸šà¸™à¸œà¸´à¸§à¸”าวอังคารนั้นอาจจะเค็มเà¸à¸´à¸™à¹„ปà¹à¸¥à¸°à¸¡à¸µà¸„วามเป็นà¸à¸£à¸”มาà¸à¹€à¸à¸´à¸™à¹„ปที่จะค้ำจุนสิ่งมีชีวิตบà¸à¹‚ดยทั่ว ๆ ไปได้[172]
+
+à¸à¸²à¸£à¸›à¸£à¸²à¸¨à¸ˆà¸²à¸à¸ªà¸™à¸²à¸¡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่เบาบางอย่างยิ่งของดาวอังคารเป็นปัà¸à¸«à¸²à¸—ี่ท้าทาย ดาวเคราะห์เองมีà¸à¸²à¸£à¸–่ายเทความร้อนผ่านพื้นผิวที่ต่ำ à¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸­à¸±à¸™à¸¢à¹ˆà¸³à¹à¸¢à¹ˆà¸•à¹ˆà¸­à¸à¸²à¸£à¸à¸£à¸°à¸«à¸™à¹ˆà¸³à¹‚จมตีของลมสุริยะ à¹à¸¥à¸°à¸„วามอ่อนด้อยของความดันบรรยาà¸à¸²à¸¨à¸ˆà¸™à¹„ม่อาจà¸à¸”น้ำลงมาให้อยู่ในสภาพของเหลวเพราะน้ำà¹à¸‚็งจะระเหิดไปจนหมด à¸à¸¥à¹ˆà¸²à¸§à¹„ด้ว่าดาวอังคารนั้นจวนที่จะตายหรือไม่à¸à¹‡à¸­à¸²à¸ˆà¸ˆà¸°à¸•à¸²à¸¢à¹„ปà¹à¸¥à¹‰à¸§à¹ƒà¸™à¸—างธรณีวิทยา เพราะà¸à¸²à¸£à¸ˆà¸šà¸ªà¸´à¹‰à¸™à¸¥à¸‡à¸‚องà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸ à¸¹à¹€à¸‚าไฟย่อมเป็นที่ประจัà¸à¸©à¹Œà¸§à¹ˆà¸²à¸à¸²à¸£à¹à¸›à¸£à¹ƒà¸Šà¹‰à¹ƒà¸«à¸¡à¹ˆà¸‚องà¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸•à¸¥à¸­à¸”จนองค์ประà¸à¸­à¸šà¹€à¸„มีต่าง ๆ ระหว่างพื้นผิวà¸à¸±à¸šà¸šà¸£à¸´à¹€à¸§à¸“ภายในดาวเคราะห์นั้นย่อมต้องจบสิ้นไปด้วย[173]
+หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸­à¸±à¸¥à¸à¸² - ตรวจพบà¸à¸²à¸£à¸—ับถมของอุลà¸à¸¡à¸“ี (จุดสีเขียว), ตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่เป็นไปได้ว่าจะมีสิ่งมีชีวิตโบราณถูà¸à¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¹„ว้[174]
+
+หลัà¸à¸à¸²à¸™à¸šà¹ˆà¸‡à¸šà¸­à¸à¸§à¹ˆà¸²à¸„รั้งหนึ่งดาวอังคารมีความเป็นมิตรต่อà¸à¸²à¸£à¸­à¸¢à¸¹à¹ˆà¸­à¸²à¸¨à¸±à¸¢à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹ƒà¸™à¸—ุà¸à¸§à¸±à¸™à¸™à¸µà¹‰à¸­à¸¢à¹ˆà¸²à¸‡à¸¡à¸²à¸ à¹à¸•à¹ˆà¸ˆà¸°à¸¡à¸µà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸”ำรงสืบต่อมาบนดาวหรือไม่นั้นยังไม่มีคำตอบที่à¹à¸™à¹ˆà¸Šà¸±à¸” ยานสำรวจไวà¸à¸´à¸‡à¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸à¸¥à¸²à¸‡à¸—ศวรรษ 1970 (พ.ศ. 2513-) มีอุปà¸à¸£à¸“์ที่ออà¸à¹à¸šà¸šà¸¡à¸²à¹€à¸žà¸·à¹ˆà¸­à¸•à¸£à¸§à¸ˆà¸«à¸²à¸ˆà¸¸à¸¥à¸´à¸™à¸—รีย์ต่าง ๆ ในดินของดาวอังคาร ณ บริเวณลงจอดของà¹à¸•à¹ˆà¸¥à¸°à¸¢à¸²à¸™à¹à¸¥à¸°à¸•à¹ˆà¸²à¸‡à¸à¹‡à¹„ด้ผลลัพธ์เป็นบวภรวมถึงà¸à¸²à¸£à¸œà¸¥à¸´à¸• CO2 เพิ่มขึ้นเป็นครั้งคราวเมื่อได้สัมผัสà¸à¸±à¸šà¸™à¹‰à¸³à¹à¸¥à¸°à¸ªà¸²à¸£à¸­à¸²à¸«à¸²à¸£ สัà¸à¸à¸²à¸“ของสิ่งมีชีวิตเหล่านี้ภายหลังได้ถูà¸à¹‚ต้à¹à¸¢à¹‰à¸‡à¹‚ดยนัà¸à¸§à¸´à¸—ยาศาสตร์จำนวนหนึ่ง ผลที่ได้ยังคงเป็นที่อภิปรายถà¸à¹€à¸–ียงเรื่อยมา โดยนัà¸à¸§à¸´à¸—ยาศาสตร์นาซา à¸à¸´à¸¥à¹€à¸šà¸´à¸£à¹Œà¸• เลวิน ยืนยันว่ายานไวà¸à¸´à¸‡à¸­à¸²à¸ˆà¸•à¸£à¸§à¸ˆà¸žà¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸• à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ข้อมูลจาà¸à¹„วà¸à¸´à¸‡à¸‹à¹‰à¸³à¸­à¸µà¸à¸„รั้งภายใต้องค์ความรู้ใหม่ในปัจจุบันเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸ªà¸¸à¸”ขั้วรูปà¹à¸šà¸šà¸•à¹ˆà¸²à¸‡ ๆ ชี้ว่า à¸à¸²à¸£à¸—ดสอบโดยไวà¸à¸´à¸‡à¹„ม่ได้ละเอียดซับซ้อนเพียงพอที่จะตรวจหารูปà¹à¸šà¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰ ตัวà¸à¸²à¸£à¸—ดสอบเองยังอาจà¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งฆ่าสิ่งมีชีวิต (ตามสมมติà¸à¸²à¸™) เหล่านั้นไปเสียด้วยซ้ำ[175] ปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸—ดสอบโดยยานส่วนลงจอดฟีนิà¸à¸‹à¹Œà¹à¸ªà¸”งให้ทราบว่าดินมีค่าพีเอชเป็นด่าง ประà¸à¸­à¸šà¸”้วยà¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ โซเดียม โพà¹à¸—สเซียม à¹à¸¥à¸°à¸„ลอไรด์[176] ลำพังสารอาหารในดินอาจสามารถเà¸à¸·à¹‰à¸­à¸«à¸™à¸¸à¸™à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹„ด้ à¹à¸•à¹ˆà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸¢à¸±à¸‡à¸„งต้องได้รับà¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸ˆà¸²à¸à¹à¸ªà¸‡à¸­à¸±à¸¥à¸•à¸£à¸²à¹„วโอเลตอันà¹à¸£à¸‡à¸à¸¥à¹‰à¸²[177] à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ล่าสุดเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸”าวอังคาร EETA79001 พบ ClO4- 0.6 ส่วนในล้านส่วน ClO3- 1.4 ส่วนในล้านส่วน à¹à¸¥à¸° NO3- 16 ส่วนในล้านส่วน เà¸à¸·à¸­à¸šà¸—ั้งหมดน่าจะมีที่มาจาà¸à¸”าวอังคารโดยตรง à¸à¸²à¸£à¸¡à¸µ ClO3- ชี้ว่าน่าจะมีสารประà¸à¸­à¸šà¸­à¸­à¸à¸‹à¸´à¹€à¸ˆà¸™-คลอรีนที่สภาพออà¸à¸‹à¸´à¹„ดซ์สูงชนิดอื่น อย่างเช่น ClO2- หรือ ClO ด้วย ทั้งสองถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นโดยปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²à¸­à¸­à¸à¸‹à¸´à¹€à¸”ชันของคลอรีนโดยรังสียูวี à¹à¸¥à¸°à¸à¸²à¸£à¹à¸•à¸à¸ªà¸¥à¸²à¸¢ ClO4- ด้วยรังสีเอà¸à¸‹à¹Œ ด้วยเหตุนี้รูปà¹à¸šà¸šà¸­à¸´à¸™à¸—รีย์หรือสิ่งมีชีวิตที่ทนทายาดà¹à¸¥à¸°à¹„ด้รับà¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸­à¸¢à¹ˆà¸²à¸‡à¸”ี (ใต้พื้นผิว) เท่านั้นที่อาจอยู่รอดมาได้[178]
+
+นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰ à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ใหม่จาà¸à¸‚้อมูลของห้องปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹€à¸„มีเปียà¸à¸‚องยานฟีนิà¸à¸‹à¹Œ à¹à¸ªà¸”งให้เห็นว่า Ca(ClO4)2 ในดินตรงที่ยานฟีนิà¸à¸‹à¹Œà¸­à¸¢à¸¹à¹ˆà¹„ม่ได้มีปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวไม่ว่าจะรูปà¹à¸šà¸šà¹ƒà¸” ๆ อาจเป็นระยะเวลายาวนานถึง 600 ล้านปี เพราะหาà¸à¸§à¹ˆà¸²à¸¡à¸µà¸™à¹‰à¸³ สาร Ca(ClO4)2 ซึ่งละลายได้ดีมาà¸à¹€à¸¡à¸·à¹ˆà¸­à¹„ด้สัมผัสà¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวย่อมเปลี่ยนไปเà¸à¸´à¸”เฉพาะ CaSO4 ขึ้น ผลที่ได้จึงเป็นเครื่องบ่งบอà¸à¸–ึงà¸à¸²à¸£à¸¡à¸µà¸ªà¸ à¸²à¸žà¹à¸§à¸”ล้อมà¹à¸«à¹‰à¸‡à¹à¸¥à¹‰à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¸ªà¸²à¸«à¸±à¸ªà¹‚ดยมีน้ำเล็à¸à¸™à¹‰à¸­à¸¢à¸«à¸£à¸·à¸­à¹„ม่มีเลยที่จะมาปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸”้วย[179]
+
+นัà¸à¸§à¸´à¸—ยาศาสตร์บางส่วนเสนอว่าเม็ดคาร์บอเนตเล็ภๆ ที่พบในอุà¸à¸à¸²à¸šà¸²à¸•à¹€à¸­à¹à¸­à¸¥à¹€à¸­à¸Š 84001ซึ่งคาดว่ามาจาà¸à¸”าวอังคารนั้น อาจเป็นซาà¸à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸”ึà¸à¸”ำบรรพ์ที่หลงเหลืออยู่บนดาวอังคารเมื่อà¸à¹‰à¸­à¸™à¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸£à¸°à¹€à¸šà¸´à¸”à¸à¸£à¸°à¹€à¸”็นออà¸à¸¡à¸²à¸ˆà¸²à¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวอังคารโดยà¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚องดาวตà¸à¹€à¸¡à¸·à¹ˆà¸­à¸£à¸²à¸§ 15 ล้านปีà¸à¹ˆà¸­à¸™ ข้อเสนอดังà¸à¸¥à¹ˆà¸²à¸§à¸¢à¸±à¸‡à¸„งเป็นที่เคลือบà¹à¸„ลง à¹à¸¥à¸°à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¸§à¹ˆà¸²à¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่เห็นอาจมีต้นà¸à¸³à¹€à¸™à¸´à¸”à¹à¸šà¸šà¸­à¸™à¸´à¸™à¸—รีย์ที่พิเศษออà¸à¹„ปà¸à¹‡à¹„ด้[180]
+
+à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸—ั้งมีเทนà¹à¸¥à¸°à¸Ÿà¸­à¸£à¹Œà¸¡à¸²à¸¥à¸”ีไฮด์ปริมาณเล็à¸à¸™à¹‰à¸­à¸¢à¹‚ดยยานโคจรรอบดาวอังคารล้วนถูà¸à¸™à¸³à¹„ปอ้างเป็นหลัà¸à¸à¸²à¸™à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸„วามเป็นไปได้ว่ามีสิ่งมีชีวิต เนื่องจาà¸à¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸­à¸šà¹€à¸„มีทั้งคู่จะà¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¹„ปอย่างรวดเร็วในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร[181][182] ในอีà¸à¸—างหนึ่ง สารเหล่านี้อาจมีà¸à¸²à¸£à¸œà¸¥à¸´à¸•à¸—ดà¹à¸—นโดยภูเขาไฟหรือà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—างธรณีวิทยาอื่น เช่น à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹€à¸‹à¸­à¸£à¹Œà¹€à¸žà¸™à¸—ีน[145]
+
+อุลà¸à¸¡à¸“ีซึ่งเà¸à¸´à¸”ขึ้นภายหลังดาวตà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹ƒà¸™à¸à¸£à¸“ีของโลà¸à¸™à¸±à¹‰à¸™à¸ªà¸²à¸¡à¸²à¸£à¸–เà¸à¹‡à¸šà¸£à¹ˆà¸­à¸‡à¸£à¸­à¸¢à¸‚องสิ่งมีชีวิตไว้ได้ มีรายงานà¸à¸²à¸£à¸žà¸šà¸­à¸¸à¸¥à¸à¸¡à¸“ีบนพื้นผิวของหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”าวอังคาร[183][184] ในทำนองเดียวà¸à¸±à¸™ à¹à¸à¹‰à¸§à¸—ี่พบในหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”าวอังคารà¸à¹‡à¸­à¸²à¸ˆà¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¸£à¹ˆà¸­à¸‡à¸£à¸­à¸‡à¸šà¸²à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¸‚องสิ่งมีชีวิตไว้หาà¸à¸ªà¸–านที่นั้นเคยมีสิ่งมีชีวิตอยู่[185][186][187]
+ความสามารถอยู่อาศัยได้[à¹à¸à¹‰]
+ดูเพิ่มเติมที่: ความสามารถอยู่อาศัยได้ของดาวเคราะห์
+
+ศูนย์à¸à¸²à¸£à¸šà¸´à¸™à¹à¸¥à¸°à¸­à¸§à¸à¸²à¸¨à¹€à¸¢à¸­à¸£à¸¡à¸±à¸™à¸„้นพบว่าไลเคนของโลà¸à¸ªà¸²à¸¡à¸²à¸£à¸–อยู่รอดได้ในสภาพà¹à¸§à¸”ล้อมดาวอังคารจำลอง ทำให้à¸à¸²à¸£à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸‚องสิ่งมีชีวิตบนดาวอังคารเป็นเรื่องน่าเชื่อถือมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นตามที่นัà¸à¸§à¸´à¸ˆà¸±à¸¢ ทิลà¹à¸¡à¸™ สปอห์น รายงาน[188] เงื่อนไขด้านอุณหภูมิ ความà¸à¸”อาà¸à¸²à¸¨ à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸ à¹à¸¥à¸°à¹à¸ªà¸‡à¸ˆà¸³à¸¥à¸­à¸‡à¸‚ึ้นโดยอาศัยข้อมูลจาà¸à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร[188] เครื่องมือตรวจวัดสภาพà¹à¸§à¸”ล้อมหรือเรมส์ออà¸à¹à¸šà¸šà¸¡à¸²à¹€à¸žà¸·à¹ˆà¸­à¸ªà¸·à¸šà¸„้นเบาะà¹à¸ªà¹ƒà¸«à¸¡à¹ˆ ๆ เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸„ุณลัà¸à¸©à¸“ะà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¹€à¸§à¸µà¸¢à¸™à¸—ั่วไปบนดาวอังคาร ระบบสภาพอาà¸à¸²à¸¨à¹ƒà¸™à¸£à¸°à¸”ับเล็ภวัà¸à¸ˆà¸±à¸à¸£à¸­à¸¸à¸—à¸à¸§à¸´à¸—ยาท้องถิ่น ศัà¸à¸¢à¸ à¸²à¸žà¹ƒà¸™à¸à¸²à¸£à¸—ำลายล้างของรังสียูวี à¹à¸¥à¸°à¸„วามสามารถอยู่อาศัยได้ใต้พื้นผิวซึ่งวางอยู่บนปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸žà¸·à¹‰à¸™à¸”ินà¸à¸±à¸šà¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨[189][190] เครื่องมือนี้เป็นส่วนหนึ่งของยาน คิวริออซิตี (มาร์สไซà¹à¸­à¸™à¸‹à¹Œà¹à¸¥à¸šà¸­à¸£à¸²à¸—อรี (MSL) หรือ ห้องปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸§à¸´à¸—ยาศาสตร์ดาวอังคาร) ซึ่งลงจอดบนดาวอังคารเมื่อเดือนสิงหาคม 2012 (พ.ศ. 2555)
+à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆ[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร
+ทัศนียภาพของหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸à¸¹à¹€à¸‹à¸Ÿ ตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่ยานสปิริตโรเวอร์ สำรวจหินบะซอลต์ภูเขาไฟ
+
+นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸ˆà¸²à¸à¹‚ลภส่วนหนึ่งของข้อมูลใหม่ ๆ ของดาวอังคารได้มาจาà¸à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¹€à¸ˆà¹‡à¸”ลำที่ยังอยู่ในระหว่างà¸à¸²à¸£à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ à¸²à¸£à¸à¸´à¸ˆà¸—ั้งบนà¹à¸¥à¸°à¹‚คจรเหนือดาวอังคาร ประà¸à¸­à¸šà¸”้วยยานในวงโคจรห้าลำà¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นอีà¸à¸ªà¸­à¸‡à¸¥à¸³ ได้à¹à¸à¹ˆ 2001 มาร์สโอดิสซี [191] มาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เมเว็น มาร์สออร์บิเตอร์มิชชัน ออปพอร์ทูนิตี à¹à¸¥à¸°à¸„ิวริออซิตี
+
+มีà¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸­à¸§à¸à¸²à¸¨à¹„ร้คนบังคับหลายสิบลำทั้งที่โคจรรอบ ยานส่วนลงจอด à¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นไปยังดาวอังคารโดยสหภาพโซเวียต สหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸² ยุโรป à¹à¸¥à¸° อินเดีย เพื่อศึà¸à¸©à¸²à¸ªà¸ à¸²à¸žà¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸‚องดาว ภูมิอาà¸à¸²à¸¨ à¹à¸¥à¸°à¸˜à¸£à¸“ีวิทยา สาธารณะชนทั่วไปสามารถขอดูรูปภาพดาวอังคารได้ผ่านทางโปรà¹à¸à¸£à¸¡à¹„ฮวิช
+
+ยานคิวริออซิตี จาà¸à¸ à¸²à¸£à¸à¸´à¸ˆà¸¡à¸²à¸£à¹Œà¸ªà¹„ซà¹à¸­à¸™à¸‹à¹Œà¹à¸¥à¸šà¸­à¸£à¸²à¸—อรีซึ่งส่งขึ้นสู่อวà¸à¸²à¸¨à¹€à¸¡à¸·à¹ˆà¸­ 26 พฤศจิà¸à¸²à¸¢à¸™ 2011 (พ.ศ. 2554) à¹à¸¥à¸°à¹„ปถึงดาวอังคารวันที่ 6 สิงหาคม 2012 (พ.ศ. 2555) ตามเวลาสาà¸à¸¥ มีขนาดใหà¸à¹ˆà¹à¸¥à¸°à¸¥à¹‰à¸³à¸«à¸™à¹‰à¸²à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นดาวอังคารรุ่นà¸à¹ˆà¸­à¸™ โดยสามารถเคลื่อนที่ด้วยอัตราเร็ว 90 เมตร (300 ฟุต) ต่อชั่วโมง[192] à¸à¸²à¸£à¸—ดลองประà¸à¸­à¸šà¸”้วยà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸¥à¹€à¸‹à¸­à¸£à¹Œà¸—ดสอบตัวอย่างเพื่อหาองค์ประà¸à¸­à¸šà¸—างเคมี สามารถประเมินสรุปหินต่าง ๆ ที่พบว่ามีองค์ประà¸à¸­à¸šà¸­à¸¢à¹ˆà¸²à¸‡à¹„รได้ที่ระยะห่าง 7 เมตร (23 ฟุต)[193] วันที่ 10 à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ 2013 (พ.ศ. 2556) ยานคิวริออซิตี ได้มีà¸à¸²à¸£à¹€à¸à¹‡à¸šà¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸¶à¸à¸‹à¸¶à¹ˆà¸‡à¸–ือเป็นà¸à¸²à¸£à¹€à¸ˆà¸²à¸°à¸¨à¸¶à¸à¸©à¸²à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸šà¸™à¸”าวเคราะห์ดวงอื่นเป็นครั้งà¹à¸£à¸à¹‚ดยà¸à¸²à¸£à¹€à¸ˆà¸²à¸°à¸”้วยสว่านบนยาน[194]
+
+วันที่ 24 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2014 (พ.ศ. 2557) ยานมาร์สออร์บิเตอร์มิชชัน (มงคลยาน หรือ เอ็มโอเอ็ม) ซึ่งส่งขึ้นสู่อวà¸à¸²à¸¨à¹‚ดยองค์à¸à¸²à¸£à¸§à¸´à¸ˆà¸±à¸¢à¸­à¸§à¸à¸²à¸¨à¸­à¸´à¸™à¹€à¸”ียได้เข้าสู่วงโคจรดาวอังคาร โครงà¸à¸²à¸£à¹€à¸£à¸´à¹ˆà¸¡à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ˆà¸²à¸à¹‚ลà¸à¹€à¸¡à¸·à¹ˆà¸­ 5 พฤศจิà¸à¸²à¸¢à¸™ 2013 โดยมีเป้าหมายเพื่อศึà¸à¸©à¸²à¸§à¸´à¹€à¸„ราะห์บรรยาà¸à¸²à¸¨à¹à¸¥à¸°à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศของดาวอังคาร ยานมาร์สออร์บิเตอร์มิชชันใช้วงโคจรส่งเฮาห์à¹à¸¡à¸™à¸™à¹Œà¹€à¸žà¸·à¹ˆà¸­à¸«à¸¥à¸¸à¸”ออà¸à¸ˆà¸²à¸à¸­à¸´à¸—ธิพลโน้มถ่วงของโลภà¹à¸¥à¸°à¹€à¸«à¸§à¸µà¹ˆà¸¢à¸‡à¹„ปสู่เส้นทางยาวไà¸à¸¥à¹€à¸à¹‰à¸²à¹€à¸”ือนสู่ดาวอังคาร ภารà¸à¸´à¸ˆà¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸ à¸²à¸£à¸à¸´à¸ˆà¹€à¸”ินทางสู่ดาวเคราะห์อื่นโดยเอเชียที่ประสบความสำเร็จเป็นครั้งà¹à¸£à¸[195]
+อนาคต[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร § เส้นเวลาà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร
+
+มีà¹à¸œà¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸­à¸”อินไซต์ในเดือนมีนาคม 2016 (พ.ศ. 2559) ร่วมไปà¸à¸±à¸šà¸„ิวบ์à¹à¸‹à¸•à¸„ู่à¹à¸à¸”ซึ่งจะบินผ่านดาวอังคารà¹à¸¥à¸°à¸„อยช่วยเชื่อมโยงà¸à¸±à¸šà¸ à¸²à¸„พื้นดิน ยานส่วนลงจอดà¹à¸¥à¸°à¸„ิวบ์à¹à¸‹à¸•à¸—ั้งคู่มีà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¹„ปถึงดาวอังคารในเดือนà¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2016[196]
+
+องค์à¸à¸²à¸£à¸­à¸§à¸à¸²à¸¨à¸¢à¸¸à¹‚รปโดยความร่วมมือà¸à¸±à¸šà¸£à¸­à¸ªà¸„อสมอสจะมีà¸à¸²à¸£à¸ªà¹ˆà¸‡à¹€à¸­à¹‡à¸à¹‚ซมาร์สเทรซà¹à¸à¹Šà¸ªà¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œà¸à¸±à¸šà¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸­à¸”สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ ในปี 2016 à¹à¸¥à¸°à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นเอ็à¸à¹‚ซมาร์สในปี 2018 (พ.ศ. 2561) นาซามีà¹à¸œà¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¡à¸²à¸£à¹Œà¸ª 2020 ยานสำรวจภาคพื้นชีววิทยาดาราศาสตร์ในปี 2020 (พ.ศ. 2563)
+
+ยานโคจรมาร์สโฮปของสหรัà¸à¸­à¸²à¸«à¸£à¸±à¸šà¹€à¸­à¸¡à¸´à¹€à¸£à¸•à¸ªà¹Œà¸¡à¸µà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸ªà¹ˆà¸‡à¹ƒà¸™à¸›à¸µ 2020 ซึ่งจะไปถึงวงโคจรของดาวอังคารในปี 2021 (พ.ศ. 2564) ยานจะทำà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ั่วทั้งหมดของดาวอังคาร[197]
+
+มีà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¹à¸œà¸™à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¡à¸™à¸¸à¸©à¸¢à¹Œà¸ªà¸¹à¹ˆà¸”าวอังคารหลายต่อหลายครั้งตลอดช่วงศตวรรษที่ 20 ต่อเนื่องมาจนถึงศตวรรษที่ 21 à¹à¸•à¹ˆà¸¢à¸±à¸‡à¹„ม่มีà¹à¸œà¸™à¹ƒà¸”ที่ดำเนินà¸à¸²à¸£à¸ˆà¸£à¸´à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¹€à¸£à¹‡à¸§à¸—ี่สุดà¸à¹ˆà¸­à¸™à¸›à¸µ 2025 (พ.ศ. 2568)
+ดาราศาสตร์บนดาวอังคาร[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ดาราศาสตร์บนดาวอังคาร
+โฟบอสผ่านหน้าดวงอาทิตย์ (ออปพอร์ทูนิตี, 10 มีนาคม 2004)
+à¸à¸²à¸£à¹€à¸à¹‰à¸²à¸•à¸´à¸”ตามจุดมืดดวงอาทิตย์จาà¸à¸”าวอังคาร
+
+ด้วยà¸à¸²à¸£à¸—ี่มีทั้งยานอวà¸à¸²à¸¨à¹ƒà¸™à¸§à¸‡à¹‚คจร ยานส่วนลงจอด à¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นมาà¸à¸¡à¸²à¸¢à¸«à¸¥à¸²à¸¢à¸¥à¸³ ทำให้à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸”าราศาสตร์จาà¸à¸”าวอังคารในปัจจุบันเป็นเรื่องที่เป็นไปได้ à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวบริวารโฟบอสของดาวอังคารจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸”้วยขนาดเชิงมุมประมาณหนึ่งในสามของดวงจันทร์เต็มดวงที่มองเห็นจาà¸à¹‚ลภà¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸”าวบริวารดีมอสà¹à¸¥à¹‰à¸§à¸à¸¥à¸±à¸šà¸›à¸£à¸²à¸à¸à¸„ล้ายà¸à¸±à¸šà¸”าวทั่วไปมาà¸à¸™à¹‰à¸­à¸¢à¹à¸¥à¹‰à¸§à¹à¸•à¹ˆà¸à¸£à¸“ีà¹à¸¥à¸°à¸¡à¸­à¸‡à¹€à¸«à¹‡à¸™à¸ªà¸§à¹ˆà¸²à¸‡à¸à¸§à¹ˆà¸²à¸”าวศุà¸à¸£à¹Œà¹€à¸¡à¸·à¹ˆà¸­à¸¡à¸­à¸‡à¸ˆà¸²à¸à¹‚ลà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸­à¸¢[198]
+
+มีปราà¸à¸à¸à¸²à¸£à¸“์หลายอย่างที่รู้จัà¸à¸à¸±à¸™à¸šà¸™à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸šà¸šà¸™à¸”าวอังคาร เช่น ดาวตภà¹à¸¥à¸°à¸­à¸­à¹‚รรา[199] ปราà¸à¸à¸à¸²à¸£à¸“์โลà¸à¹€à¸„ลื่อนผ่านหน้าดวงอาทิตย์มองเห็นจาà¸à¸”าวอังคารจะเà¸à¸´à¸”ขึ้นในวันที่ 10 พฤศจิà¸à¸²à¸¢à¸™ 2084 (พ.ศ. 2627)[200] นอà¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¹€à¸„ลื่อนผ่านโดยดาวพุธ à¸à¸²à¸£à¹€à¸„ลื่อนผ่านโดยดาวศุà¸à¸£à¹Œ ตลอดจนดาวบริวารโฟบอสà¹à¸¥à¸°à¸”ีมอสซึ่งมีขนาดเชิงมุมค่อนข้างเล็à¸à¸—ำให้อย่างมาà¸à¸—ี่สุดเà¸à¸´à¸”เป็น "สุริยุปราคา" บางส่วนเมื่อดาวทั้งสองเคลื่อนผ่าน (ดู à¸à¸²à¸£à¹€à¸„ลื่อนผ่านของดีมอสจาà¸à¸”าวอังคาร)[201][202]
+
+วันที่ 19 ตุลาคม 2014 (พ.ศ. 2557) ดาวหางไซดิงสปริงผ่านเฉียดใà¸à¸¥à¹‰à¸”าวอังคารอย่างมาภจนโคม่าอาจครอบคลุมดาวอังคาร[203][204][205][206][207][208]
+à¸à¸²à¸£à¸Šà¸¡[à¹à¸à¹‰]
+ภาพเคลื่อนไหวà¹à¸ªà¸”งà¸à¸²à¸£à¹€à¸„ลื่อนถอยหลังปราà¸à¸à¸‚องดาวอังคารในปี 2003 เมื่อมองจาà¸à¹‚ลà¸
+
+เนื่องจาà¸à¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรดาวอังคาร เมื่อดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามà¸à¸±à¸šà¸”วงอาทิตย์จะมีความส่องสว่างปราà¸à¸à¹„ด้ตั้งà¹à¸•à¹ˆ -2.91[6] จนถึง -1.4 ความสว่างน้อยที่สุดของดาวอังคารคือ +1.6 เà¸à¸´à¸”ขึ้นเมื่อดาวอยู่ด้านเดียวà¸à¸±à¸™à¸à¸±à¸šà¸”วงอาทิตย์[10] ดาวอังคารมัà¸à¸›à¸£à¸²à¸à¸à¸Šà¸±à¸”ว่ามีสีเหลือง สีส้ม หรือสีà¹à¸”ง à¹à¸•à¹ˆà¸ªà¸µà¸•à¸²à¸¡à¸ˆà¸£à¸´à¸‡à¸‚องดาวอังคารนั้นใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¸ªà¸µà¸‚องบัตเตอร์สà¸à¸­à¸•à¸Šà¹Œ สีà¹à¸”งที่มองเห็นนั้นเป็นเพียงà¸à¸¸à¹ˆà¸™à¹ƒà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚องดาวเคราะห์ ยานสำรวจภาคพื้นสปิริต ของนาซาได้ทำà¸à¸²à¸£à¸–่ายภาพภูมิทัศน์โคลนสีเขียวอมน้ำตาลร่วมà¸à¸±à¸šà¸«à¸´à¸™à¸ªà¸µà¸™à¹‰à¸³à¹€à¸‡à¸´à¸™à¸›à¸™à¹€à¸—าà¹à¸¥à¸°à¸«à¸¢à¹ˆà¸­à¸¡à¸—รายสีà¹à¸”งจาง ๆ เอาไว้[209] ขณะที่อยู่ห่างออà¸à¹„ปจาà¸à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด จะมีระยะทางมาà¸à¸à¸§à¹ˆà¸²à¸•à¸­à¸™à¸—ี่อยู่ใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดมาà¸à¸à¸§à¹ˆà¸²à¹€à¸ˆà¹‡à¸”เท่า เมื่อถึงตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่ไม่เหมาะสมสำหรับà¸à¸²à¸£à¸Šà¸¡ ดาวอังคารà¸à¹‡à¸ˆà¸°à¸–ูà¸à¸šà¸”บังโดยความเจิดจ้าของดวงอาทิตย์ได้เป็นเวลานานà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¹€à¸”ือน สำหรับเวลาที่เหมาะสมที่สุดในà¸à¸²à¸£à¸Šà¸¡à¹€à¸à¸´à¸”ขึ้นทุภๆ ช่วง 15 - 17 ปี à¹à¸¥à¸°à¸¡à¸±à¸à¹€à¸à¸´à¸”ขึ้นระหว่างปลายเดือนà¸à¸£à¸à¸Žà¸²à¸„มถึงปลายเดือนà¸à¸±à¸™à¸¢à¸²à¸¢à¸™ เป็นจุดที่สามารถมองเห็นรายละเอียดพื้นผิวดาวอังคารได้ค่อนข้างมาà¸à¸”้วยà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ สำหรับส่วนที่สังเà¸à¸•à¹€à¸«à¹‡à¸™à¹„ด้ง่ายà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¹ƒà¸Šà¹‰à¸à¸¥à¹‰à¸­à¸‡à¸à¸³à¸¥à¸±à¸‡à¸‚ยายต่ำคือà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วดาว[210]
+
+เมื่อดาวอังคารเข้ามายังตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามดวงอาทิตย์ à¸à¹‡à¸ˆà¸°à¹€à¸£à¸´à¹ˆà¸¡à¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¹à¸«à¹ˆà¸‡à¸à¸²à¸£à¹€à¸„ลื่อนถอยหลัง หมายความว่าดาวอังคารจะมองเห็นเสมือนเคลื่อนที่ย้อนทางà¸à¸¥à¸±à¸šà¸«à¸¥à¸±à¸‡à¹ƒà¸™à¸¥à¸±à¸à¸©à¸“ะเป็นวงเมื่อเทียบดาวฤà¸à¸©à¹Œà¸žà¸·à¹‰à¸™à¸«à¸¥à¸±à¸‡à¸•à¹ˆà¸²à¸‡ ๆ ระยะเวลาของà¸à¸²à¸£à¹€à¸„ลื่อนถอยหลังนี้ยาวได้จนถึงราว 72 วัน à¹à¸¥à¸°à¸”าวอังคารจะมีความสว่างเพิ่มขึ้นสูงสุดท่ามà¸à¸¥à¸²à¸‡à¸à¸²à¸£à¹€à¸„ลื่อนที่ดังà¸à¸¥à¹ˆà¸²à¸§[211]
+à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸—ี่สุด[à¹à¸à¹‰]
+สัมพัทธ์[à¹à¸à¹‰]
+
+ณ จุดที่เส้นลองจิจูดของดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡ 180 องศาจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚องดวงอาทิตย์เมื่อโลà¸à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸™à¸±à¹‰à¸™à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้าม ซึ่งเป็นเวลาที่ใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¸ˆà¸¸à¸”ที่เข้ามาใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด เวลาà¸à¸²à¸£à¹€à¸à¸´à¸”ของตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้าม สามารถห่างจาà¸à¸ˆà¸¸à¸”เข้ามาใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดได้มาà¸à¸–ึง 8.5 วัน ระยะทางเข้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดผันà¹à¸›à¸£à¹„ด้ตั้งà¹à¸•à¹ˆà¸›à¸£à¸°à¸¡à¸²à¸“ 54[212] ถึง 103 ล้านà¸à¸´à¹‚ลเมตรขึ้นอยู่à¸à¸±à¸šà¸„วามรีของวงโคจรดาวเคราะห์ ซึ่งเป็นสาเหตุทำให้ขนาดเชิงมุมผันà¹à¸›à¸£à¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™[213] ดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามเมื่อวันที่ 8 เมษายน 2014 (พ.ศ. 2557) ด้วยระยะทางประมาณ 93 ล้านà¸à¸´à¹‚ลเมตร[214] à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามครั้งถัดไปของดาวอังคารจะเà¸à¸´à¸”ขึ้นในวันที่ 22 พฤษภาคม 2016 (พ.ศ. 2559) ด้วยระยะทาง 76 ล้านà¸à¸´à¹‚ลเมตร[214] ระยะเวลาเฉลี่ยระหว่างà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามของดาวอังคารà¹à¸•à¹ˆà¸¥à¸°à¸„รั้งหรือคาบซินอดิà¸à¸„ือ 780 วัน โดยจำนวนวันที่เà¸à¸´à¸”จริงอาจยาวนานจาภ764 ถึง 812 วัน[215]
+ดาวอังคารในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามจาà¸à¸›à¸µ 2003-2018 มองจาà¸à¸”้านบนของสุริยวิถีโดยมีโลà¸à¸­à¸¢à¸¹à¹ˆà¸•à¸£à¸‡à¸à¸¥à¸²à¸‡
+ค่าที่à¹à¸™à¹ˆà¸™à¸­à¸™à¹ƒà¸à¸¥à¹‰à¹€à¸„ียงเวลาปัจจุบัน[à¹à¸à¹‰]
+
+ดาวอังคารเข้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดà¹à¸¥à¸°à¸¡à¸µà¸„วามสว่างปราà¸à¸à¸ªà¸¹à¸‡à¸—ี่สุดในรอบเà¸à¸·à¸­à¸š 60,000 ปี ด้วยระยะทาง 55,758,006 à¸à¸´à¹‚ลเมตร (34,646,419 ไมล์, 0.37271925 หน่วยดาราศาสตร์) à¹à¸¥à¸°à¸¡à¸µà¸„วามส่องสว่าง -2.88 เมื่อวันที่ 27 สิงหาคม 2003 (พ.ศ. 2546) 9:51:13 ตามเวลาสาà¸à¸¥ à¸à¸²à¸£à¹€à¸à¸´à¸”ครั้งนี้ห่างจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามของดาวอังคารหนึ่งวัน à¹à¸¥à¸°à¸›à¸£à¸°à¸¡à¸²à¸“สามวันจาà¸à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุด ทำให้มองเห็นจาà¸à¹‚ลà¸à¹„ด้ง่ายเป็นพิเศษ à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸ªà¸¸à¸”à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸™à¸µà¹‰à¸„าดว่าเà¸à¸´à¸”ขึ้นในวันที่ 12 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 57,617 ปีà¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š ครั้งต่อไปจะเà¸à¸´à¸”ขึ้นในปี 2287 (พ.ศ. 2830)[216] à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¹€à¸›à¹‡à¸™à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸à¸²à¸£à¸“์นี้จัดว่าใà¸à¸¥à¹‰à¸à¸§à¹ˆà¸²à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸—ี่สุดร่วมสมัยอื่นเพียงเล็à¸à¸™à¹‰à¸­à¸¢ ตัวอย่างเช่น ระยะใà¸à¸¥à¹‰à¸—ี่สุดเมื่อ 22 สิงหาคม 1924 (พ.ศ. 2467) ที่ 0.37285 หน่วยดาราศาสตร์ à¹à¸¥à¸°à¸£à¸°à¸¢à¸°à¹ƒà¸à¸¥à¹‰à¸—ี่สุดที่จะเà¸à¸´à¸”ขึ้นเมื่อ 24 สิงหาคม 2208 (พ.ศ. 2751) ที่ 0.37279 หน่วยดาราศาสตร์[169]
+ประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸”าวอังคาร
+
+จุดที่โดดเด่นในประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸”าวอังคารคือเมื่อดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามใà¸à¸¥à¹‰à¸à¸±à¸šà¹‚ลà¸à¹à¸¥à¸°à¸—ำให้มองเห็นได้ง่ายที่สุดซึ่งเà¸à¸´à¸”ขึ้นในทุà¸à¸ªà¸­à¸‡à¸›à¸µ ที่เด่นชัดยิ่งขึ้นอีà¸à¸„ือà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามขณะอยู่ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดของดาวอังคารซึ่งเà¸à¸´à¸”ขึ้นทุภๆ 15 - 17 ปี นั่นหมายถึงà¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นด้วยจนทำให้เห็นความà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¸Šà¸±à¸”เจน
+à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹ƒà¸™à¸¢à¸¸à¸„โบราณà¹à¸¥à¸°à¸¢à¸¸à¸„à¸à¸¥à¸²à¸‡[à¹à¸à¹‰]
+
+à¸à¸²à¸£à¸”ำรงอยู่ของดาวอังคารในà¸à¸²à¸™à¸°à¸§à¸±à¸•à¸–ุหนึ่งที่เคลื่อนผ่านท้องฟ้ายามiราตรีได้ถูà¸à¸šà¸±à¸™à¸—ึà¸à¹„ว้โดยนัà¸à¸”าราศาสตร์อียิปต์โบราณ à¹à¸¥à¸°à¹€à¸¡à¸·à¹ˆà¸­ 1534 ปีà¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š พวà¸à¹€à¸‚าà¸à¹‡à¸„ุ้นเคยดีà¹à¸¥à¹‰à¸§à¸à¸±à¸šà¸à¸²à¸£à¹€à¸„ลื่อนถอยหลังของดาวเคราะห์[217]ในยุคจัà¸à¸£à¸§à¸£à¸£à¸”ิบาบิโลเนียใหม่ นัà¸à¸”าราศาสตร์ชาวบาบิโลเนียได้มีà¸à¸²à¸£à¸šà¸±à¸™à¸—ึà¸à¸›à¸¹à¸¡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚องดาวเคราะห์ต่าง ๆ ตลอดจนพฤติà¸à¸£à¸£à¸¡à¸‚องดาวเคราะห์ที่สังเà¸à¸•à¹„ด้เอาไว้อย่างเป็นระบบà¹à¸¥à¸°à¸ªà¸¡à¹ˆà¸³à¹€à¸ªà¸¡à¸­ สำหรับดาวอังคาร พวà¸à¹€à¸‚าทราบว่าดาวจะโคจรครบ 37 คาบซินอดิภหรือ 42 รอบจัà¸à¸£à¸£à¸²à¸¨à¸µà¹ƒà¸™à¸—ุภๆ 79 ปี พวà¸à¹€à¸‚ายังได้คิดค้นระเบียบวิธีทางคณิตศาสตร์ขึ้นมาเพื่อให้เà¸à¸´à¸”ความคลาดเคลื่อนเพียงเล็à¸à¸™à¹‰à¸­à¸¢à¹ƒà¸™à¸à¸²à¸£à¸—ำนายตำà¹à¸«à¸™à¹ˆà¸‡à¸‚องดาวเคราะห์ทั้งหลาย[218][219]
+
+ในศตวรรษที่สี่à¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š อาริสโตเติลตั้งข้อสังเà¸à¸•à¸§à¹ˆà¸²à¸”าวอังคารได้หายไปเบื้องหลังดวงจันทร์ระหว่างà¸à¸²à¸£à¸–ูà¸à¸šà¸”บัง บ่งบอà¸à¸§à¹ˆà¸²à¸”าวอังคารนั้นต้องอยู่ห่างไà¸à¸¥à¸­à¸­à¸à¹„ป[220] ทอเลมี ชาวà¸à¸£à¸µà¸à¸—ี่อาศัยในอะเล็à¸à¸‹à¸²à¸™à¹€à¸”รีย[221] ได้พยายามà¹à¸à¹‰à¹„ขปัà¸à¸«à¸²à¸à¸²à¸£à¹€à¸„ลื่อนไหวในวงโคจรของดาวอังคาร à¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¸‚องทอเลมีà¹à¸¥à¸°à¸‡à¸²à¸™à¸—างดาราศาสตร์ที่เขารวบรวมขึ้น ปราà¸à¸à¸•à¹ˆà¸­à¸¡à¸²à¹€à¸›à¹‡à¸™à¸Šà¸¸à¸”หนังสือหลายเล่มรู้จัà¸à¸à¸±à¸™à¹ƒà¸™à¸Šà¸·à¹ˆà¸­à¸­à¸±à¸¥à¸¡à¸²à¹€à¸ˆà¸ªà¸•à¹Œ ซึ่งได้à¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™à¸•à¸³à¸£à¸²à¸­à¸±à¸™à¸—รงอิทธิพลต่อดาราศาสตร์ตะวันตà¸à¸•à¸¥à¸­à¸”สิบสี่ศตวรรษถัดมา[222] งานนิพนธ์จาà¸à¸ªà¸¡à¸±à¸¢à¸ˆà¸µà¸™à¹‚บราณยืนยันว่าดาวอังคารเป็นที่รู้จัà¸à¹‚ดยนัà¸à¸”าราศาสตร์ชาวจีนไม่ช้าไปà¸à¸§à¹ˆà¸²à¸¨à¸•à¸§à¸£à¸£à¸©à¸—ี่สี่à¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š[223] ในคริสต์ศตวรรษที่ห้า สุริยสิทธันต์ ตำราทางดาราศาสตร์อินเดีย มีà¸à¸²à¸£à¸›à¸£à¸°à¸¡à¸²à¸“เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸‚องดาวอังคารไว้[c][224] ในวัฒนธรรมเอเชียตะวันออภมัà¸à¹€à¸£à¸µà¸¢à¸à¸”าวอังคารตามประเพณีว่า "ดาวไฟ" (ç«æ˜Ÿ) โดยวางอยู่บนหลัà¸à¸˜à¸²à¸•à¸¸à¸—ั้งห้า[225][226][227]
+
+ในช่วงคริสต์ศตวรรษที่สิบเจ็ด ไทโค บราเฮทำà¸à¸²à¸£à¸§à¸±à¸”พารัลà¹à¸¥à¸à¸‹à¹Œà¹ƒà¸™à¹à¸•à¹ˆà¸¥à¸°à¸§à¸±à¸™à¸‚องดาวอังคาร ซึ่งต่อมาโยฮันเนส เคปเลอร์ได้นำไปใช้คำนวณเบื้องต้นหาระยะทางสัมพัทธ์สู่ดาวเคราะห์[228] เมื่อà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์เป็นที่à¹à¸žà¸£à¹ˆà¸«à¸¥à¸²à¸¢ ได้มีà¸à¸²à¸£à¸§à¸±à¸”ค่าพารัลà¹à¸¥à¸à¸‹à¹Œà¸£à¸²à¸¢à¸§à¸±à¸™à¸‚องดาวอังคารซ้ำอีà¸à¸„รั้งเนื่องในความพยายามที่จะหาระยะทางที่à¹à¸¡à¹ˆà¸™à¸¢à¸³à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¹‚ลà¸à¸à¸±à¸šà¸”วงอาทิตย์ โจวันนี โดเมนีโภà¸à¸±à¸ªà¸‹à¸µà¸™à¸µà¹€à¸›à¹‡à¸™à¸œà¸¹à¹‰à¸”ำเนินà¸à¸²à¸£à¸”ังà¸à¸¥à¹ˆà¸²à¸§à¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¹ƒà¸™à¸›à¸µ 1672 (พ.ศ. 2215) à¸à¸²à¸£à¸§à¸±à¸”ค่าพารัลà¹à¸¥à¸à¸‹à¹Œà¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¹à¸£à¸ ๆ นั้นมีอุปสรรคสำคัà¸à¸ˆà¸²à¸à¸„ุณภาพของตัวเครื่องมือเอง[229] à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸›à¸£à¸²à¸à¸à¸à¸²à¸£à¸“์ดาวศุà¸à¸£à¹Œà¸šà¸”บังดาวอังคารเพียงครั้งเดียวเà¸à¸´à¸”ขึ้นในวันที่ 13 ตุลาคม 1590 (พ.ศ. 2133) โดยมิคาเอล à¹à¸¡à¸ªà¸•à¹Œà¸¥à¸´à¸™à¸—ี่ไฮเดลà¹à¸šà¸£à¹Œà¸[230] ในปี 1610 (พ.ศ. 2153) à¸à¸²à¸¥à¸´à¹€à¸¥à¹‚อ à¸à¸²à¸¥à¸´à¹€à¸¥à¸­à¸µà¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¸—ี่มองดูดาวอังคารผ่านà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์[231] บุคคลà¹à¸£à¸à¸—ี่วาดภาพดาวอังคารโดยà¹à¸ªà¸”งลัà¸à¸©à¸“ะภูมิประเทศต่าง ๆ ด้วยคือนัà¸à¸”าราศาสตร์ชาวดัตช์ คริสตียาน เฮยเคินส์[232]
+"คลอง" ดาวอังคาร[à¹à¸à¹‰]
+à¹à¸œà¸™à¸—ี่ดาวอังคารโดยโจวานนี สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ
+ภาพร่างดาวอังคารจาà¸à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹‚ดยโลเวลล์ เวลาใดเวลาหนึ่งà¸à¹ˆà¸­à¸™à¸›à¸µ 1914 (ขั้วใต้อยู่ด้านบน)
+à¹à¸œà¸™à¸—ี่ดาวอังคารจาà¸à¸à¸¥à¹‰à¸­à¸‡à¸®à¸±à¸šà¹€à¸šà¸´à¸¥ เห็นใà¸à¸¥à¹‰à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามปี 1999 (ขั้วเหนืออยู่ด้านบน)
+ดูบทความหลัà¸à¸—ี่: คลองบนดาวอังคาร
+
+เมื่อถึงคริสต์ศตวรรษที่สิบเà¸à¹‰à¸² à¸à¸³à¸¥à¸±à¸‡à¸‚ยายของà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ได้เพิ่มมาà¸à¸‚ึ้นจนถึงระดับที่พอจำà¹à¸™à¸à¹à¸¢à¸à¹à¸¢à¸°à¸£à¸²à¸¢à¸¥à¸°à¹€à¸­à¸µà¸¢à¸”ต่าง ๆ บนพื้นผิวได้ à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดของดาวอังคารเมื่อวันที่ 5 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 1877 (พ.ศ. 2420) ปีนั้นเอง โจวานนี สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ นัà¸à¸”าราศาสตร์ชาวอิตาลี ใช้à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาด 22 เซนติเมตร (8.7 นิ้ว) ในมิลานได้สร้างà¹à¸œà¸™à¸—ี่ดาวอังคารที่มีรายละเอียดปลีà¸à¸¢à¹ˆà¸­à¸¢à¸‚ึ้นเป็นฉบับà¹à¸£à¸ à¹à¸œà¸™à¸—ี่นี้มีเอà¸à¸¥à¸±à¸à¸©à¸“์โดดเด่นด้วยภูมิประเทศที่เขาเรียà¸à¸Šà¸·à¹ˆà¸­à¸§à¹ˆà¸² คานาลี ซึ่งได้รับà¸à¸²à¸£à¹€à¸›à¸´à¸”เผยต่อมาในภายหลังว่าเป็นเพียงภาพลวงตา รอยเส้นตรงยืดยาวบนพื้นผิวดาวอังคารที่ถูà¸à¸—ึà¸à¸—ัà¸à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸„านาลี เหล่านี้ โจวานนีได้ตั้งชื่อให้ตามอย่างชื่อà¹à¸¡à¹ˆà¸™à¹‰à¸³à¸—ี่มีชื่อเสียงเป็นที่รู้จัà¸à¸šà¸™à¹‚ลภศัพท์ที่เขาใช้มีความหมายว่า "ทางน้ำ" หรือ "ร่องน้ำ" ซึ่งนิยมà¹à¸›à¸¥à¸à¸±à¸™à¸­à¸¢à¹ˆà¸²à¸‡à¸œà¸´à¸” ๆ ในภาษาอังà¸à¸¤à¸©à¸§à¹ˆà¸² "คลอง"[233][234]
+
+จาà¸à¸­à¸´à¸—ธิพลของà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² เพอร์ซิวัล โลเวลล์ นัà¸à¸•à¸°à¸§à¸±à¸™à¸­à¸­à¸à¸¨à¸¶à¸à¸©à¸²à¹„ด้ตั้งหอดูดาวขึ้นโดยมีà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาด 30 à¹à¸¥à¸° 45 เซนติเมตร (12 à¹à¸¥à¸° 18 นิ้ว) หอดูดาวนี้ได้ใช้ในà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคารระหว่างโอà¸à¸²à¸ªà¸­à¸±à¸™à¸”ีที่ผ่านมาในปี 1894 (พ.ศ. 2437) ตลอดจนà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามที่ดีลดหลั่นลงมาหลังจาà¸à¸™à¸±à¹‰à¸™ เขาตีพิมพ์หนังสือหลายเล่มเรื่องดาวอังคารรวมไปถึงสิ่งมีชีวิตบนนั้นซึ่งส่งอิทธิพลอย่างใหà¸à¹ˆà¸«à¸¥à¸§à¸‡à¸•à¹ˆà¸­à¸ªà¸²à¸˜à¸²à¸£à¸“ะ[235] ยังมีà¸à¸²à¸£à¸žà¸š คานาลี โดยนัà¸à¸”าราศาสตร์คนอื่น ๆ เช่น อองรี โฌเซฟ เพร์โรà¹à¸•à¸‡ à¹à¸¥à¸°à¸«à¸¥à¸¸à¸¢à¸ªà¹Œ ตอลลง ที่เมืองนิสโดยใช้หนึ่งในà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ที่ใหà¸à¹ˆà¸—ี่สุดในเวลานั้น[236][237]
+
+à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸•à¸²à¸¡à¸¤à¸”ูà¸à¸²à¸¥à¸­à¸±à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸”้วยà¸à¸²à¸£à¸–อยร่นของà¹à¸œà¹ˆà¸™à¸‚ั้วดาวà¹à¸¥à¸°à¸à¸²à¸£à¹€à¸à¸´à¸”พื้นที่มืดในช่วงฤดูร้อนของดาวอังคาร เมื่อประจวบเข้าà¸à¸±à¸šà¸„ลองมาà¸à¸¡à¸²à¸¢à¸ˆà¸¶à¸‡à¸™à¸³à¹„ปสู่à¸à¸²à¸£à¸„าดเดาเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸”าวอังคาร à¹à¸¥à¸°à¸„วามเชื่อที่ยึดมั่นถือมั่นอย่างยาวนานว่าดาวอังคารมีผืนทะเลที่à¸à¸§à¹‰à¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸à¸±à¸šà¸žà¸·à¸Šà¸™à¸²à¸™à¸²à¸žà¸±à¸™à¸˜à¸¸à¹Œ à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ในขณะนั้นยังไม่มีà¸à¸³à¸¥à¸±à¸‡à¸‚ยายถึงขั้นที่สามารถให้หลัà¸à¸à¸²à¸™à¸¢à¸·à¸™à¸¢à¸±à¸™à¸à¸²à¸£à¸„าดเดาใด ๆ ได้ เมื่อใช้à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาดใหà¸à¹ˆà¸‚ึ้นà¸à¹‡à¸ˆà¸°à¸ªà¸±à¸‡à¹€à¸à¸•à¹€à¸«à¹‡à¸™ คานาลี ตรงยาวที่ขนาดเล็à¸à¸¥à¸‡ ระหว่างà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹ƒà¸™à¸›à¸µ 1909 (พ.ศ. 2452) โดยใช้à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาด 84 เซนติเมตร (33 นิ้ว) à¹à¸Ÿà¸¥à¸¡à¸¡à¸²à¸£à¸´à¸¢à¸‡à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸šà¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่ไม่เป็นระเบียบà¹à¸•à¹ˆà¹„ม่เห็นคานาลี [238]
+
+à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งบทความในทศวรรษ 1960 (พ.ศ. 2503-) ยังมีà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œà¹€à¸£à¸·à¹ˆà¸­à¸‡à¸Šà¸µà¸§à¸§à¸´à¸—ยาบนดาวอังคารโดยผลัà¸à¹„สคำอธิบายà¹à¸™à¸§à¸—างอื่นออà¸à¹„ป คงไว้à¹à¸•à¹ˆà¸§à¹ˆà¸²à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸™à¸±à¹‰à¸™à¸™à¸±à¹ˆà¸™à¹€à¸­à¸‡à¹€à¸›à¹‡à¸™à¹€à¸«à¸•à¸¸à¸‚องà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸•à¸²à¸¡à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¸”าวอังคาร ภาวะà¸à¸²à¸£à¸“์โดยละเอียดทั้งเมà¹à¸—บอลิซึมà¹à¸¥à¸°à¸§à¸±à¸à¸ˆà¸±à¸à¸£à¸—างเคมีต่าง ๆ สำหรับระบบนิเวศที่ดำเนินได้จริงได้รับà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œ[239]
+à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸™à¹‚ดยยานอวà¸à¸²à¸¨[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร
+
+ครั้นยานอวà¸à¸²à¸¨à¹„ปเยือนถึงดาวอังคารระหว่างปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸¡à¸²à¸£à¸´à¹€à¸™à¸­à¸£à¹Œà¸‚องนาซาในช่วงทศวรรษ 1960 à¹à¸¥à¸° 70 à¹à¸™à¸§à¸„ิดเดิม ๆ à¸à¹‡à¸žà¸´à¸™à¸²à¸¨à¹„ปà¹à¸šà¸šà¹„ม่มีชิ้นดี นอà¸à¸ˆà¸²à¸à¸™à¸µà¹‰à¸œà¸¥à¸à¸²à¸£à¸—ดลองตรวจหาสิ่งมีชีวิตโดยยานไวà¸à¸´à¸‡à¹ƒà¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ à¸²à¸£à¸à¸´à¸ˆ ทำให้สมมติà¸à¸²à¸™à¸”าวเคราะห์มรณะที่ไม่น่าอยู่อย่างยิ่งà¸à¹‡à¹„ด้มาเป็นที่ยอมรับอย่างà¹à¸žà¸£à¹ˆà¸«à¸¥à¸²à¸¢[240]
+
+ข้อมูลจาà¸à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹‚ดยยานมาริเนอร์ 9 à¹à¸¥à¸°à¹„วà¸à¸´à¸‡à¹„ด้นำมาใช้สร้างà¹à¸œà¸™à¸—ี่ดาวอังคารที่ดียิ่งขึ้น à¹à¸¥à¸°à¸¢à¸´à¹ˆà¸‡à¸”ียิ่งขึ้นอย่างà¸à¹‰à¸²à¸§à¸à¸£à¸°à¹‚ดดด้วยปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹‚ดยมาร์สโà¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œà¸‹à¸¶à¹ˆà¸‡à¸ªà¹ˆà¸‡à¸‚ึ้นในปี 1996 (พ.ศ. 2539) à¹à¸¥à¸°à¸”ำเนินงานต่อเนื่องจนà¸à¸£à¸°à¸—ั่งปลายปี 2006 (พ.ศ. 2549) ทำให้ได้à¹à¸œà¸™à¸—ี่à¹à¸ªà¸”งภูมิประเทศดาวอังคารที่ละเอียดลออครบถ้วนสมบูรณ์à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸à¹‡à¹€à¸›à¹‡à¸™à¸—ี่รับทราบ[241] à¹à¸œà¸™à¸—ี่เหล่านี้สามารถเข้าถึงได้ทางออนไลน์ ตัวอย่างเช่น à¸à¸¹à¹€à¸à¸´à¸¥à¸¡à¸²à¸£à¹Œà¸ª สำหรับมาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ à¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸­à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª ยังทำà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸•à¹ˆà¸­à¹€à¸™à¸·à¹ˆà¸­à¸‡à¸”้วยเครื่องไม้เครื่องมือใหม่ ๆ à¹à¸¥à¸°à¸Šà¹ˆà¸§à¸¢à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸¥à¸‡à¸ˆà¸­à¸” นาซาได้เปิดให้เข้าใช้เครื่องมือทางออนไลน์คือ มาร์สเทร็ค ซึ่งให้ภาพปราà¸à¸à¸‚องดาวอังคารจาà¸à¸‚้อมูลà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸•à¸¥à¸­à¸” 50 ปี à¹à¸¥à¸° เอ็à¸à¸‹à¹Œà¸žà¸µà¹€à¸£à¸µà¸¢à¸™à¸‹à¹Œà¸„ิวริออซิตี ซึ่งให้ภาพจำลองà¸à¸²à¸£à¸—่องไปบนดาวอังคารà¹à¸šà¸šà¸ªà¸²à¸¡à¸¡à¸´à¸•à¸´à¸žà¸£à¹‰à¸­à¸¡à¸à¸±à¸šà¸¢à¸²à¸™à¸„ิวริออซิตี[242]
+ในวัฒนธรรม[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ดาวอังคารในวัฒนธรรม à¹à¸¥à¸° ดาวอังคารในบันเทิงคดี
+Mars symbol.svg
+
+ดาวอังคารทางสาà¸à¸¥à¸™à¸´à¸¢à¸¡à¹„ด้ชื่อตามเทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามของโรมัน ในต่างวัฒนธรรม ดาวอังคารเป็นตัวà¹à¸—นของความเข้มà¹à¸‚็ง ความเป็นชาย à¹à¸¥à¸°à¸„วามเยาว์วัย มีสัà¸à¸¥à¸±à¸à¸©à¸“์เป็นรูปวงà¸à¸¥à¸¡à¸—ี่มีลูà¸à¸¨à¸£à¸Šà¸µà¹‰à¸­à¸­à¸à¸¡à¸²à¸ˆà¸²à¸à¸”้านขวาบน ซึ่งยังใช้เป็นสัà¸à¸¥à¸±à¸à¸©à¸“์à¹à¸—นเพศชายอีà¸à¸”้วย
+
+จà¸à¸à¸„วามล้มเหลวหลายต่อหลายครั้งของยาน-โครงà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร เป็นผลให้à¸à¸¥à¸¸à¹ˆà¸¡à¸§à¸±à¸’นธรรมนอà¸à¸à¸£à¸°à¹à¸ªà¸™à¸³à¹„ปเยาะเย้ยเสียดสีโดยà¸à¸¥à¹ˆà¸²à¸§à¹‚ทษตำหนิติเตียนว่าความล้มเหลวต่าง ๆ เป็นเพราะ "สามเหลี่ยมเบอร์มิวดา" ของโลà¸-ดาวอังคาร "คำสาปเทพอังคาร" หรือไม่à¸à¹‡ "ผีปอบมหาดาราจัà¸à¸£" ที่ได้เขมือบเอายานสำรวจดาวอังคารไป[243]
+"ชาวดาวอังคาร" ผู้ทรงปัà¸à¸à¸²[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ดาวอังคารในบันเทิงคดี
+
+ความคิดตามสมัยนิยมที่ว่าดาวอังคารเต็มไปด้วยชาวดาวอังคารผู้ทรงปัà¸à¸à¸²à¹€à¸‰à¸¥à¸µà¸¢à¸§à¸‰à¸¥à¸²à¸”ลงหลัà¸à¸›à¸±à¸à¸à¸²à¸™à¸­à¸¢à¸¹à¹ˆà¸­à¸²à¸¨à¸±à¸¢ ได้ปะทุขึ้นในช่วงปลายคริสต์ศตวรรษที่ 19 à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸š "คานาลี" ของสเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µà¹€à¸¡à¸·à¹ˆà¸­à¸›à¸£à¸°à¸ªà¸²à¸™à¹€à¸‚้าà¸à¸±à¸šà¸«à¸™à¸±à¸‡à¸ªà¸·à¸­à¸‚องเพอร์ซิวัล โลเวลล์ในประเด็นดังà¸à¸¥à¹ˆà¸²à¸§ ได้ผลัà¸à¸”ันà¹à¸™à¸§à¸„ิดมาตรà¸à¸²à¸™à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸”าวอังคารว่าเป็นดาวเคราะห์ที่à¹à¸«à¹‰à¸‡à¹à¸¥à¹‰à¸‡ หนาวเย็น ใà¸à¸¥à¹‰à¸”ับสูภร่วมไปà¸à¸±à¸šà¸à¸²à¸£à¸¡à¸µà¸­à¸²à¸£à¸¢à¸˜à¸£à¸£à¸¡à¹‚บราณที่à¸à¹ˆà¸­à¸ªà¸£à¹‰à¸²à¸‡à¸‡à¸²à¸™à¸Šà¸¥à¸›à¸£à¸°à¸—านมาà¸à¸¡à¸²à¸¢à¹€à¸­à¸²à¹„ว้[244]
+โฆษณาสบู่ในปี 1893 (พ.ศ. 2436) บนมโนคตินิยมว่าดาวอังคารมีคนอยู่อาศัย
+
+ด้วยหลายà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹à¸¥à¸°à¸–้อยà¹à¸–ลงโดยบุคคลผู้มีความโดดเด่นในสังคมได้ทำให้เà¸à¸´à¸”สิ่งที่เรียà¸à¸§à¹ˆà¸² "โรคคลั่งดาวอังคาร"[245] ในปี 1899 (พ.ศ. 2442) ขณะà¸à¸³à¸¥à¸±à¸‡à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¸„ลื่นวิทยุในบรรยาà¸à¸²à¸¨à¸”้วยเครื่องรับสัà¸à¸à¸²à¸“ของเขาในห้องทดลองโคโลราโดสปริงส์ นิโคลา เทสลา นัà¸à¸›à¸£à¸°à¸”ิษà¸à¹Œ ได้สังเà¸à¸•à¸žà¸šà¸ªà¸±à¸à¸à¸²à¸“ซ้ำ ๆ เขาสันนิษà¸à¸²à¸™à¹ƒà¸™à¸ à¸²à¸¢à¸«à¸¥à¸±à¸‡à¸§à¹ˆà¸²à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸à¸²à¸£à¸•à¸´à¸”ต่อสื่อสารทางวิทยุมาจาà¸à¸”าวเคราะห์ดวงอื่น ซึ่งเป็นไปได้ว่าคือดาวอังคาร บทสัมภาษณ์ในปี 1901 (พ.ศ. 2444) เทสลาà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸²:
+
+ มันเป็นบางครั้งภายหลังจาà¸à¸„วามคิดที่ได้ผุดวาบขึ้นมาในใจของผม à¸à¸²à¸£à¸£à¸šà¸à¸§à¸™à¸—ี่ผมสังเà¸à¸•à¸žà¸šà¸™à¸±à¹ˆà¸™à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¹„ด้ว่าคือà¸à¸²à¸£à¸„วบคุมทางปัà¸à¸à¸² à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸œà¸¡à¸ˆà¸°à¹„ม่สามารถไขรหัสความหมายเหล่านั้นได้ มันเป็นไปไม่ได้เลยสำหรับผมที่จะคิดว่าสิ่งเหล่านั้นทั้งหมดเป็นเพียงอุบัติเหตุ ความรู้สึà¸à¸—ี่ทวีขึ้นอย่างมั่นคงในตัวผมà¸à¹‡à¸„ือผมเป็นบุคคลà¹à¸£à¸à¸—ี่ได้ยินà¸à¸²à¸£à¸›à¸à¸´à¸ªà¸±à¸™à¸–ารของดาวเคราะห์หนึ่งสู่ดาวเคราะห์อื่น[246]
+
+ทฤษฎีของเทสลาได้รับà¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¹‚ดยลอร์ดเคลวิน ผู้ซึ่งไปเยือนสหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸²à¹ƒà¸™à¸›à¸µ 1902 (พ.ศ. 2445) มีรายงานถึงคำพูดของเขาว่าเขาคิดว่าเทสลาจับสัà¸à¸à¸²à¸“ของชาวดาวอังคารที่ส่งมายังสหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸²à¹„ว้ได้[247] เคลวินปà¸à¸´à¹€à¸ªà¸˜ "อย่างหนัà¸à¹à¸™à¹ˆà¸™" ในรายงานฉบับนี้ไม่นานà¸à¹ˆà¸­à¸™à¸à¸²à¸£à¹€à¸”ินทางออà¸à¸ˆà¸²à¸à¸­à¹€à¸¡à¸£à¸´à¸à¸² เขาà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² "อะไรที่ผมพูดไปจริง ๆ à¸à¹‡à¸„ือ ชนชาวดาวอังคาร ถ้าพวà¸à¹€à¸‚ามีอยู่ à¸à¹‡à¹„ม่ต้องสงสัยเลยว่าคงเห็นนิวยอร์ภเพราะไฟฟ้าจะเรืองà¹à¸ªà¸‡à¸­à¸­à¸à¸¡à¸²à¸ˆà¸™à¹€à¸«à¹‡à¸™à¹„ด้ชัด"[248]
+
+ในบทความของนิวยอร์à¸à¹„ทมส์ ในปี 1901 เอ็ดเวิร์ด ชาลส์ พิà¸à¹€à¸„อริง ผู้อำนวยà¸à¸²à¸£à¸«à¸­à¸”ูดาววิทยาลัยฮาร์วาร์ดà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² พวà¸à¹€à¸‚าได้รับโทรเลขจาà¸à¸«à¸­à¸”ูดาวโลเวลล์ในรัà¸à¹à¸­à¸£à¸´à¹‚ซนาที่ดูเหมือนจะยืนยันว่าดาวอังคารได้พยายามติดต่อสื่อสารà¸à¸±à¸šà¹‚ลà¸[249]
+
+ ในต้นเดือนธันวาคมปี 1900 (พ.ศ. 2443) เราได้รับโทรเลขจาà¸à¸«à¸­à¸”ูดาวโลเวลล์ในà¹à¸­à¸£à¸´à¹‚ซนาว่าเห็นลำของà¹à¸ªà¸‡à¸‰à¸²à¸¢à¸ªà¹ˆà¸‡à¸­à¸­à¸à¸ˆà¸²à¸à¸”าวอังคาร (หอดูดาวโลเวลล์มีความชำนาà¸à¹€à¸›à¹‡à¸™à¸žà¸´à¹€à¸¨à¸©à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸”าวอังคาร) เป็นเวลาเจ็ดสิบนาที ผมส่งต่อข้อเท็จจริงนี้ไปยังยุโรปà¹à¸¥à¸°à¸ªà¹ˆà¸‡à¸ªà¸³à¹€à¸™à¸²à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¹ƒà¸«à¸¡à¹ˆà¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸¸à¸”ไปทั่วประเทศ ผู้สังเà¸à¸•à¸žà¸šà¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลที่ละเอียดถี่ถ้วน เชื่อถือได้ à¹à¸¥à¸°à¹€à¸‚าà¸à¹‡à¹„ม่มีเหตุผลอะไรที่จะสงสัยว่าà¹à¸ªà¸‡à¸™à¸±à¹ˆà¸™à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸ˆà¸£à¸´à¸‡ มันส่งมาจาà¸à¸ˆà¸¸à¸”ทางภูมิศาสตร์ที่รู้จัà¸à¸à¸±à¸™à¸”ีบนดาวอังคาร นั่นà¹à¸«à¸¥à¸°à¸„ือทั้งหมด ตอนนี้เรื่องได้ไปทั่วโลà¸à¹à¸¥à¹‰à¸§ ในยุโรปà¸à¹‡à¸¡à¸µà¸à¸²à¸£à¸à¸¥à¹ˆà¸²à¸§à¸à¸±à¸™à¸§à¹ˆà¸²à¸‰à¸±à¸™à¸à¹‡à¸¡à¸µà¸à¸²à¸£à¸•à¸´à¸”ต่อสื่อสารà¸à¸±à¸šà¸”าวอังคาร à¹à¸¥à¸°à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸žà¸´à¸ªà¸”ารเà¸à¸´à¸™à¸ˆà¸£à¸´à¸‡à¸ªà¸²à¸£à¸žà¸±à¸”อย่างà¸à¹‡à¸žà¸¸à¹ˆà¸‡à¸žà¸£à¸§à¸” ไม่ว่าà¹à¸ªà¸‡à¸™à¸±à¹ˆà¸™à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸­à¸°à¹„ร พวà¸à¹€à¸£à¸²à¹„ม่มีทางล่วงรู้ ไม่ว่านั่นจะทรงปัà¸à¸à¸²à¸«à¸£à¸·à¸­à¹„ม่ ใครà¸à¹‡à¸•à¸­à¸šà¹„ม่ได้ มันเป็นเรื่องที่อธิบายไม่ได้โดยà¹à¸—้[249]
+
+ต่อมาภายหลังพิà¸à¹€à¸„อริงได้เสนอให้มีà¸à¸²à¸£à¸à¹ˆà¸­à¸ªà¸£à¹‰à¸²à¸‡à¸Šà¸¸à¸”à¸à¸£à¸°à¸ˆà¸à¹€à¸‡à¸²à¸ˆà¸³à¸™à¸§à¸™à¸¡à¸²à¸à¹ƒà¸™à¸£à¸±à¸à¹€à¸—à¸à¸‹à¸±à¸ªà¹‚ดยมุ่งหมายเพื่อส่งสัà¸à¸à¸²à¸“ถึงชาวดาวอังคาร[250]
+
+ในทศวรรษที่ผ่านมา à¹à¸œà¸™à¸—ี่พื้นผิวดาวอังคารความละเอียดสูงได้สำเร็จสมบูรณ์โดยมาร์สโà¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œ เปิดเผยให้เห็นว่าไม่มีสิ่งประดิษà¸à¹Œà¹à¸›à¸¥à¸à¸›à¸¥à¸­à¸¡à¹ƒà¸” ๆ เลยที่à¹à¸ªà¸”งว่ามีสิ่งมีชีวิตที่ "ทรงปัà¸à¸à¸²" อยู่อาศัย à¹à¸•à¹ˆà¸à¸²à¸£à¸™à¸¶à¸à¸à¸±à¸™à¹ƒà¸™à¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์เทียมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—รงปัà¸à¸à¸²à¸šà¸™à¸”าวอังคารยังดำเนินต่อไปจาà¸à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸±à¸à¸§à¸´à¸ˆà¸²à¸£à¸“์ เช่น ริชาร์ด ซี. ฮอà¸à¹à¸¥à¸™à¸”์ à¸à¸²à¸£à¹‚ต้à¹à¸¢à¹‰à¸‡à¹€à¸£à¸·à¹ˆà¸­à¸‡ คานาลี ดั้งเดิม à¸à¸²à¸£à¸„าดà¸à¸±à¸™à¸šà¸²à¸‡à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸§à¸²à¸‡à¸­à¸¢à¸¹à¹ˆà¸šà¸™à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศเล็ภๆ ที่เห็นรายละเอียดไม่ชัดà¹à¸•à¹ˆà¸™à¸¶à¸à¸„ิดเอาผ่านภาพที่ได้จาà¸à¸¢à¸²à¸™à¸­à¸§à¸à¸²à¸¨ อย่างเช่น 'พีระมิด' à¹à¸¥à¸° 'ใบหน้าบนดาวอังคาร' นัà¸à¸”าราศาสตร์ดาวเคราะห์ คาร์ล เซà¹à¸à¸™ เขียนไว้ว่า::
+
+ ดาวอังคารà¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™à¸ªà¸¡à¸£à¸ à¸¹à¸¡à¸´à¹à¸«à¹ˆà¸‡à¹€à¸—พนิยายชนิดหนึ่งที่พวà¸à¹€à¸£à¸²à¸Šà¸²à¸§à¹‚ลà¸à¹„ด้ฉายออà¸à¸¡à¸²à¸‹à¸¶à¹ˆà¸‡à¸„วามหวังà¹à¸¥à¸°à¸„วามà¸à¸¥à¸±à¸§[234]
+
+ภาพประà¸à¸­à¸šà¸¡à¸²à¸£à¹Œà¹€à¸Šà¸µà¸¢à¸™à¸ªà¸²à¸¡à¸‚าจาà¸à¸«à¸™à¸±à¸‡à¸ªà¸·à¸­à¹€à¸”อะวอร์ออฟเดอะเวิลด์ส ของ เอช. จี. เวลส์ ฉบับà¸à¸£à¸±à¹ˆà¸‡à¹€à¸¨à¸ª ปี 1906 (พ.ศ. 2449)
+
+à¸à¸²à¸£à¸žà¸£à¸£à¸“นาเรื่องดาวอังคารในนิยายได้รับà¸à¸²à¸£à¸à¸£à¸°à¸•à¸¸à¹‰à¸™à¹€à¸ªà¸£à¸´à¸¡à¸”้วยโทนสีà¹à¸”งเร้าอารมณ์ ผนวà¸à¸à¸±à¸šà¸à¸²à¸£à¸„าดเดาตามà¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์ในสมัยคริสต์ศตวรรษที่สิบเà¸à¹‰à¸²à¸§à¹ˆà¸²à¸ à¸²à¸§à¸°à¸à¸²à¸£à¸“์ต่าง ๆ บนพื้นผิวดาวจะต้องเà¸à¸·à¹‰à¸­à¸«à¸™à¸¸à¸™à¹„ม่เฉพาะชีวิตเท่านั้นà¹à¸•à¹ˆà¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—รงปัà¸à¸à¸²à¸­à¸µà¸à¸”้วย[251] นำไปสู่à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸ªà¸£à¸£à¸„์งานในà¸à¸²à¸™à¸šà¸—ดำเนินเรื่องของนิยายวิทยาศาสตร์จำนวนมาภหนึ่งในนั้นคือเรื่อง เดอะวอร์ออฟเดอะเวิลด์ส ของ เอช. จี. เวลส์ ซึ่งตีพิมพ์ในปี 1898 (พ.ศ. 2441) มีเนื้อหาว่าชาวดาวอังคารพยายามหลบหนีออà¸à¸ˆà¸²à¸à¸”าวเคราะห์ใà¸à¸¥à¹‰à¸•à¸²à¸¢à¸‚องพวà¸à¹€à¸‚าโดยà¸à¸²à¸£à¸¡à¸²à¸£à¸¸à¸à¸£à¸²à¸™à¹‚ลภต่อมาภายหลังได้มีà¸à¸²à¸£à¸—ำเดอะวอร์ออฟเดอะเวิลด์ส ฉบับวิทยุในอเมริà¸à¸² à¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸ªà¸µà¸¢à¸‡à¹€à¸¡à¸·à¹ˆà¸­à¸§à¸±à¸™à¸—ี่ 30 ตุลาคม 1938 (พ.ศ. 2481) โดยออร์สัน เวลส์ซึ่งà¹à¸ªà¸”งในรูปà¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™à¸‚่าวà¹à¸šà¸šà¸ªà¸” à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸—ี่ลือà¸à¸£à¸°à¸‰à¹ˆà¸­à¸™à¸‚ึ้นมาทันทีเพราะไปทำให้สาธารณชนเà¸à¸´à¸”à¸à¸²à¸£à¸•à¸·à¹ˆà¸™à¸•à¸£à¸°à¸«à¸™à¸à¹€à¸¡à¸·à¹ˆà¸­à¸œà¸¹à¹‰à¸Ÿà¸±à¸‡à¸ˆà¸³à¸™à¸§à¸™à¸¡à¸²à¸à¹€à¸‚้าใจผิดไปว่าสิ่งที่พวà¸à¹€à¸‚าได้ยินเป็นเรื่องจริง[252]
+
+งานที่มีอิทธิพลประà¸à¸­à¸šà¸”้วย เดอะมาร์เชียนครอนิเคิลส์ ของ เรย์ à¹à¸šà¸£à¸”บูรี ซึ่งมีเนื้อหาว่านัà¸à¸ªà¸³à¸£à¸§à¸ˆà¸¡à¸™à¸¸à¸©à¸¢à¹Œà¹„ด้ทำลายอารยธรรมชาวดาวอังคารโดยบังเอิภนิยายชุด บาร์ซูม ของเอ็ดà¸à¸²à¸£à¹Œ ไรซ์ เบอร์โรห์ นวนิยายเรื่องเอาท์ออฟเดอะไซเลนต์à¹à¸žà¸¥à¹€à¸™à¹‡à¸• ของซี. เอส. ลิวอิส ในปี 1938[253] à¹à¸¥à¸°à¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸´à¹‰à¸™à¸‡à¸²à¸™à¸‚องโรเบิร์ต เอ. ไฮน์ไลน์à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸Šà¹ˆà¸§à¸‡à¸à¸¥à¸²à¸‡à¸„ริสต์ทศวรรษหà¸à¸ªà¸´à¸š[254]
+
+โจนาธาน สวิฟท์ได้มีà¸à¸²à¸£à¸­à¹‰à¸²à¸‡à¸­à¸´à¸‡à¸–ึงดวงจันทร์บริวารของดาวอังคารซึ่งเป็นเวลาà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸à¸²à¸£à¸„้นพบจริงโดยเอเสฟ ฮอลล์à¸à¸§à¹ˆà¸² 150 ปี โดยบรรยายรายละเอียดลัà¸à¸©à¸“ะวงโคจรของดาวเหล่านั้นได้ใà¸à¸¥à¹‰à¹€à¸„ียงเป็นเหตุเป็นผลในบทที่ 19 ในนวนิยายของเขาเรื่อง à¸à¸±à¸¥à¸¥à¸´à¹€à¸§à¸­à¸£à¹Œà¹à¸—รฟเวลส์[255]
+
+มาร์วินเดอะมาร์เชียน เป็นตัวà¸à¸²à¸£à¹Œà¸•à¸¹à¸™à¸¥à¸±à¸à¸©à¸“ะชาวดาวอังคารที่เฉลียวฉลาด เริ่มปราà¸à¸à¹ƒà¸™à¹‚ทรทัศน์เมื่อปี 1948 (พ.ศ. 2491) ในà¸à¸²à¸™à¸°à¸•à¸±à¸§à¸¥à¸°à¸„รหนึ่งในà¸à¸²à¸£à¹Œà¸•à¸¹à¸™à¸ à¸²à¸žà¹€à¸„ลื่อนไหวเรื่องลูนีทูนส์ของวอร์เนอร์บราเธอร์ส à¹à¸¥à¸°à¸¢à¸±à¸‡à¸”ำเนินต่อมาในà¸à¸²à¸™à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¸‚องวัฒนธรรมนิยมจนปัจจุบัน[256]
+
+หลังจาà¸à¸¢à¸²à¸™à¸­à¸§à¸à¸²à¸¨à¸¡à¸²à¸£à¸´à¹€à¸™à¸­à¸£à¹Œà¹à¸¥à¸°à¹„วà¸à¸´à¸‡à¹„ด้ส่งภาพดาวอังคารตามสภาพที่เป็นจริงมาà¸à¸¡à¸²à¸¢à¸à¸¥à¸±à¸šà¸¡à¸² ว่าเป็นโลà¸à¸—ี่à¹à¸¥à¹‰à¸‡à¸£à¹‰à¸²à¸‡ ไร้ซึ่งชีวิตอย่างชัดà¹à¸ˆà¹‰à¸‡ à¹à¸¥à¸°à¸›à¸£à¸²à¸¨à¸ˆà¸²à¸à¸„ลองใด ๆ à¹à¸™à¸§à¸„ิดดั้งเดิมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸”าวอังคารà¸à¹‡à¸–ูà¸à¹‚ละทิ้ง นำมาสู่สมัยนิยมà¹à¸«à¹ˆà¸‡à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸£à¸²à¸§à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸´à¸„มอยู่อาศัยของมนุษย์บนดาวอังคารà¹à¸šà¸šà¸ªà¸­à¸”คล้องเที่ยงตรงตามจริง เรื่องที่เป็นที่รู้จัà¸à¸à¸±à¸™à¸”ีที่สุดเรื่องหนึ่งในลัà¸à¸©à¸“ะนี้คือ มาร์สไตรโลจี ของคิม สà¹à¸•à¸™à¸¥à¸µà¸¢à¹Œ โรบินสัน อย่างไรà¸à¹‡à¸•à¸²à¸¡ à¸à¸²à¸£à¸„าดเดาà¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์เทียมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¹ƒà¸šà¸«à¸™à¹‰à¸²à¸šà¸™à¸”าวอังคารตลอดจนจุดลึà¸à¸¥à¸±à¸šà¸™à¹ˆà¸²à¸žà¸´à¸¨à¸§à¸‡à¸­à¸·à¹ˆà¸™ ๆ ซึ่งยานสำรวจอวà¸à¸²à¸¨à¸ˆà¸±à¸šà¸ à¸²à¸žà¹„ด้ว่าเป็นร่องรอยของอารยธรรมโบราณ ยังเป็นà¹à¸™à¸§à¸—างยอดนิยมในบันเทิงคดีà¹à¸™à¸§à¸§à¸´à¸—ยาศาสตร์มาอย่างต่อเนื่อง โดยเฉพาะอย่างยิ่งในภาพยนตร์[257]
+ดาวบริวาร[à¹à¸à¹‰]
+ดูบทความหลัà¸à¸—ี่: ดาวบริวารของดาวอังคาร, โฟบอส à¹à¸¥à¸° ดีมอส
+ภาพ ไฮไรส์ ปรับระดับสีของโฟบอสà¹à¸ªà¸”งชุดร่องที่ขนานà¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆ à¹à¸¥à¸°à¹‚ซ่หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸à¸±à¸šà¸«à¸¥à¸¸à¸¡à¸ªà¸•à¸´à¸à¸™à¸µà¸¢à¹Œà¸—างด้านขวา
+ภาพไฮไรส์ ปรับระดับสีของดีมอส (ไม่ตามสัดส่วนà¸à¸±à¸šà¸£à¸¹à¸›à¸šà¸™) à¹à¸ªà¸”งผืนเรโà¸à¸¥à¸´à¸˜à¸£à¸²à¸šà¹€à¸£à¸µà¸¢à¸šà¸›à¸à¸„ลุมดาว
+
+ดาวอังคารมีดาวบริวารค่อนข้างเล็à¸à¸ªà¸­à¸‡à¸”วง ได้à¹à¸à¹ˆ โฟบอส (เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 22 à¸à¸´à¹‚ลเมตร (14 ไมล์)) à¹à¸¥à¸° ดีมอส (เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 12 à¸à¸´à¹‚ลเมตร (7.5 ไมล์)) โดยมีวงโคจรใà¸à¸¥à¹‰à¸à¸±à¸šà¸”าวเคราะห์à¹à¸¡à¹ˆ ทฤษฎีที่อธิบายว่าทั้งคู่เป็นดาวเคราะห์น้อยที่ถูà¸à¸ˆà¸±à¸šà¹€à¸­à¸²à¹„ว้เป็นที่นิยมมายาวนาน à¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸³à¹€à¸™à¸´à¸”ที่มานั้นยังคลุมเครือ[258] ดาวบริวารทั้งสองถูà¸à¸„้นพบในปี 1877 (พ.ศ. 2420) โดยเอเสฟ ฮอลล์ ตั้งชื่อตามโฟบอส (ตระหนà¸/à¸à¸¥à¸±à¸§) à¹à¸¥à¸°à¸”ีมอส (สยอง/น่าขนลุà¸) ซึ่งเป็นเทพในตำนานà¸à¸£à¸µà¸ ร่วมไปà¸à¸±à¸šà¹€à¸—พà¹à¸­à¸£à¸µà¸ª เทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามบิดาของพวà¸à¹€à¸‚า ชื่อดาวอังคารว่า "มาร์ส" นั้นคือชื่อเทพà¹à¸­à¸£à¸µà¸ªà¸•à¸²à¸¡à¹à¸šà¸šà¹‚รมัน[259][260] ในà¸à¸£à¸µà¸à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™ ดาวอังคารยังคงใช้ชื่อตามอย่างโบราณว่า Ares (Aris: ΆÏης)[261]
+
+จาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวอังคาร à¸à¸²à¸£à¹€à¸„ลื่อนที่ของโฟบอสà¹à¸¥à¸°à¸”ีมอสจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸­à¸­à¸à¹„ปจาà¸à¸”วงจันทร์ โฟบอสจะขึ้นทางทิศตะวันตภตà¸à¸—างทิศตะวันออภà¹à¸¥à¸°à¸à¸¥à¸±à¸šà¸¡à¸²à¸‚ึ้นอีà¸à¸„รั้งในเวลาเพียง 11 ชั่วโมง ส่วนดีมอสซึ่งอยู่นอà¸à¸§à¸‡à¹‚คจรพ้องคาบพอดี ระยะคาบà¸à¸²à¸£à¹‚คจรของดาวจึงไม่ตรงพอดีà¸à¸±à¸šà¸„าบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¸‚องดาวเคราะห์à¹à¸¡à¹ˆ ดาวจะไม่ลอยค้างฟ้าในตำà¹à¸«à¸™à¹ˆà¸‡à¹€à¸”ิมà¹à¸•à¹ˆà¸ˆà¸°à¸‚ึ้นตามปà¸à¸•à¸´à¸—างทิศตะวันออà¸à¸­à¸¢à¹ˆà¸²à¸‡à¸Šà¹‰à¸² ๆ à¹à¸¡à¹‰à¸”ีมอสจะมีคาบà¸à¸²à¸£à¹‚คจรราว 30 ชั่วโมง à¹à¸•à¹ˆà¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸²à¸–ึง 2.7 วันระหว่างà¸à¸²à¸£à¸‚ึ้นจนตà¸à¸¥à¸±à¸šà¸Ÿà¹‰à¸²à¹„ปสำหรับผู้สังเà¸à¸•à¸—ี่ศูนย์สูตร ซึ่งà¸à¹‡à¸ˆà¸°à¸¥à¸±à¸šà¹„ปอย่างช้า ๆ คล้อยหลังà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¸‚องดาวอังคาร[262]
+วงโคจรของโฟบอสà¹à¸¥à¸°à¸”ีมอส (ตามสัดส่วน)
+
+เนื่องจาà¸à¸§à¸‡à¹‚คจรของโฟบอสต่ำà¸à¸§à¹ˆà¸²à¸£à¸°à¸”ับความสูงพ้องคาบ à¹à¸£à¸‡à¹„ทดัลจาà¸à¸”าวอังคารจึงดึงวงโคจรของดาวให้ต่ำลงไปเรื่อย ๆ ทีละน้อย อีà¸à¸›à¸£à¸°à¸¡à¸²à¸“ 50 ล้านปีข้างหน้า เป็นไปได้ว่าโฟบอสอาจพุ่งเข้าชนà¸à¸±à¸šà¸”าวอังคารหรือไม่à¸à¹‡à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¸­à¸­à¸à¸à¸¥à¸²à¸¢à¹€à¸›à¹‡à¸™à¹‚ครงสร้างวงà¹à¸«à¸§à¸™à¸£à¸­à¸šà¸”าวเคราะห์[262]
+
+à¸à¸³à¹€à¸™à¸´à¸”ของดาวบริวารทั้งสองนั้นยังไม่เป็นที่เข้าใจดีนัภà¸à¸²à¸£à¸¡à¸µà¸­à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸ªà¸°à¸—้อนต่ำà¹à¸¥à¸°à¸¡à¸µà¸­à¸‡à¸„์ประà¸à¸­à¸šà¹à¸šà¸šà¸«à¸´à¸™à¸„อนไดรต์à¸à¸¥à¸¸à¹ˆà¸¡à¸„าร์บอเนเชียสทำให้มีความคล้ายคลึงà¸à¸±à¸šà¸”าวเคราะห์น้อยซึ่งช่วยสนับสนุนทฤษฎีà¸à¸²à¸£à¸ˆà¸±à¸šà¸¢à¸¶à¸” วงโคจรที่ไม่เสถียรของโฟบอสเหมือนจะชี้ให้เห็นว่าเป็นà¸à¸²à¸£à¸ˆà¸±à¸šà¹€à¸­à¸²à¹„ว้ที่ค่อนข้างใหม่ à¹à¸•à¹ˆà¸—ั้งคู่มีวงโคจรที่à¸à¸¥à¸¡à¹ƒà¸à¸¥à¹‰à¸à¸±à¸šà¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£à¸‹à¸¶à¹ˆà¸‡à¸ˆà¸±à¸”ว่าไม่ปà¸à¸•à¸´à¸ªà¸³à¸«à¸£à¸±à¸šà¸§à¸±à¸•à¸–ุที่ถูà¸à¸ˆà¸±à¸šà¹„ว้ได้à¹à¸¥à¸°à¸¢à¸±à¸‡à¸•à¹‰à¸­à¸‡à¸à¸²à¸£à¸žà¸¥à¸§à¸±à¸•à¸à¸²à¸£à¸¢à¸¶à¸”จับที่สลับซับซ้อน à¸à¸²à¸£à¸ˆà¸±à¸šà¸•à¸±à¸§à¸žà¸­à¸à¸žà¸¹à¸™à¸‚ึ้นตั้งà¹à¸•à¹ˆà¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™à¸‚องประวัติศาสตร์ดาวอังคารยังเป็นà¸à¸£à¸“ีที่ถือว่าเป็นไปได้ ถ้าหาà¸à¸§à¹ˆà¸²à¸ˆà¸°à¹„ม่นับรวมลัà¸à¸©à¸“ะองค์ประà¸à¸­à¸šà¸‚องทั้งคู่ที่คล้ายคลึงà¸à¸±à¸šà¸”าวเคราะห์น้อยมาà¸à¸à¸§à¹ˆà¸²à¸—ี่จะเหมือนà¸à¸±à¸šà¸”าวอังคารซึ่งยังต้องรอà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™
+
+ความเป็นไปได้ในรูปà¹à¸šà¸šà¸—ี่สามคือà¸à¸²à¸£à¸¡à¸µà¸§à¸±à¸•à¸–ุที่สามเข้ามาเà¸à¸µà¹ˆà¸¢à¸§à¸‚้องหรือเป็นชนิดหนึ่งของà¸à¸²à¸£à¹à¸•à¸à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸­à¸à¸¡à¸²à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™[263] หลัà¸à¸à¸²à¸™à¸«à¸¥à¸²à¸¢à¸›à¸£à¸°à¸à¸²à¸£à¸—ี่ได้มาค่อนข้างใหม่พบว่าโครงสร้างภายในของโฟบอสมีความพรุนสูง[264] à¹à¸¥à¸°à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸­à¸‡à¸„์ประà¸à¸­à¸šà¸ à¸²à¸¢à¹ƒà¸™à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆà¹€à¸›à¹‡à¸™à¸Ÿà¸´à¸¥à¹‚ลซิลิเà¸à¸•à¹à¸¥à¸°à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸­à¸·à¹ˆà¸™ ๆ ที่ทราบว่ามีบนดาวอังคาร[265] ทำให้ประเด็นà¸à¸²à¸£à¸à¸³à¹€à¸™à¸´à¸”ของโฟบอสว่ามาจาà¸à¹€à¸¨à¸©à¸§à¸±à¸•à¸–ุที่à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸­à¸à¸¡à¸²à¸ à¸²à¸¢à¸«à¸¥à¸±à¸‡à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚องดาวอังคารà¹à¸¥à¹‰à¸§à¹„ด้มารวมà¸à¸±à¸™à¹ƒà¸™à¸§à¸‡à¹‚คจรรอบดาวà¹à¸¡à¹ˆà¸™à¸±à¹‰à¸™à¸™à¹ˆà¸²à¹€à¸Šà¸·à¹ˆà¸­à¸–ือมาà¸à¸‚ึ้น[266] คล้ายà¸à¸±à¸™à¸à¸±à¸šà¸—ฤษฎีà¸à¸£à¸°à¹à¸ªà¸«à¸¥à¸±à¸à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸à¸²à¸£à¸à¸³à¹€à¸™à¸´à¸”ดวงจันทร์ของโลภอย่างไรà¸à¹‡à¸•à¸²à¸¡ ค่าสเปà¸à¸•à¸£à¸±à¸¡à¸‚องà¹à¸ªà¸‡à¸—ี่มองเห็นได้ถึงช่วงใà¸à¸¥à¹‰à¸­à¸´à¸™à¸Ÿà¸£à¸²à¹€à¸£à¸” (VNIR) ของดาวบริวารทั้งสองของดาวอังคารมีความคล้ายคลึงà¸à¸±à¸šà¸—ี่วัดได้จาà¸à¹à¸–บดาวเคราะห์น้อยด้านนอภà¹à¸¥à¸°à¸¡à¸µà¸£à¸²à¸¢à¸‡à¸²à¸™à¸§à¹ˆà¸²à¸ªà¹€à¸›à¸à¸•à¸£à¸±à¸¡à¸£à¸±à¸‡à¸ªà¸µà¸­à¸´à¸™à¸Ÿà¸£à¸²à¹€à¸£à¸”ของโฟบอสไม่สอดคล้องà¸à¸±à¸šà¸„อนไดรต์ไม่ว่าจะà¸à¸¥à¸¸à¹ˆà¸¡à¹ƒà¸”[265]
+
+ดาวอังคารอาจจะมีดาวบริวารอื่นนอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰à¹à¸•à¹ˆà¸¡à¸µà¸‚นาดเล็à¸à¸”้วยเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸£à¸²à¸§ 50 - 100 เมตร (160 ถึง 330 ฟุต) à¹à¸¥à¸°à¸„าดว่ามีวงà¹à¸«à¸§à¸™à¸à¸¸à¹ˆà¸™à¸­à¸¢à¸¹à¹ˆà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¹‚ฟบอสà¸à¸±à¸šà¸”ีมอส[19]
diff --git a/xpcom/tests/gtest/wikipedia/tr.txt b/xpcom/tests/gtest/wikipedia/tr.txt
new file mode 100644
index 0000000000..7c83510231
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/tr.txt
@@ -0,0 +1,245 @@
+Latince Mars veya Arapça Merih (Türkçe: Bakırsokum[1] ya da Sakıt[2]), Güneş Sistemi'nin Güneş'ten itibâren dördüncü gezegeni. Roma mitolojisindeki savaş tanrısı Mars'a ithâfen adlandırılmıştır. Yüzeyindeki yaygın demiroksitten dolayı kızılımsı bir görünüme sahip olduğu için Kızıl Gezegen de denir.
+
+İnce bir atmosferi olan Mars gerek Ay'daki gibi meteor kraterlerini, gerekse Dünya'daki gibi volkan, vadi, çöl ve kutup bölgelerini içeren çehresiyle bir yerbenzeri gezegendir. Ayrıca dönme periyodu ve mevsim dönemleri Dünya’nınkine çok benzer. 2 adet uydusu bulunmaktadır.
+
+Mars’taki Olimpos Dağı (Olympus Mons) adı verilen dağ Güneş Sistemi’nde bilinen en yüksek dağ ve Marineris Vadisi (Valles Marineris) adı verilen kanyon en büyük kanyondur. Ayrıca Haziran 2008’de Nature dergisinde yayımlanan üç makalede açıklandığı gibi, Mars’ın kuzey yarımküresinde 10.600 km. uzunluğunda ve 8.500 km. genişliğindeki dev bir meteor kraterinin varlığı saptanmıştır. Bu krater, bugüne kadar keşfedilmiş en büyük meteor kraterinin (Ay'ın güney kutbu kısmındaki Atkien Havzası) dört misli büyüklüğündedir.[3][4]
+
+Mars, Dünya hariç tutulursa, halen Güneş Sistemi’ndeki gezegenler içinde sıvı su ve yaşam içermesi en muhtemel gezegen olarak görülmektedir.[5] Mars Express ve Mars Reconnaissance Orbiter keşif projelerinin radar verileri gerek kutuplarda (Temmuz 2005)[6] gerekse orta bölgelerde (Kasım 2008)[7] geniş miktarlarda su buzlarının var olduğunu ortaya koymuş bulunmaktadır. 31 Temmuz 2008’de Phoenix Mars Lander adlı robotik uzay gemisi Mars toprağının sığ bölgelerindeki su buzlarından örnekler almayı başarmıştır.[8]
+
+Günümüzde, Mars, yörüngelerine oturmuş üç uzay gemisine evsahipliği yapmaktadır: Mars Odyssey, Mars Express ve Mars Reconnaissance Orbiter. Mars, Dünya hariç tutulursa, Güneş Sistemi’ndeki herhangi bir sıradan gezegenden ibaret değildir. Yüzeyi pek çok uzay aracına evsahipliği yapmıştır. Bu uzay araçlarıyla elde edilen jeolojik veriler şunu ortaya koymuştur ki, Mars önceden su konusunda geniş bir çeşitliliğe sahipti; hatta geçen on yıllık süre sırasında gayzer (kaynaç) türü su fışkırma veya akıntıları meydana gelmişti.[9] NASA’nın Mars Global Surveyor projesi kapsamında sürdürülen incelemeler Mars’ın güney kutbu buz bölgesinin geri çekilmiş olduğunu ortaya koymuştur.[10] Bilim insanları, 2006'da Mars yörüngesine oturtulan "Mars Reconnaissance Orbiter" (Mars Yörünge Kaşifi) uydusundan alınan veriler sonucu, Mars'ta sıcak aylarda tuzlu su akıntılarının oluştuğunu bildirmişlerdir.[11]
+
+Mars’ın 1877 yılında astronom Asaph Hall tarafından keşfedilen Phobos ve Deimos adları verilmiş, düzensiz biçimli iki küçük uydusu vardır. Mars Dünya’dan çıplak gözle görülebilmektedir. "Görünür kadir"i −2,9’a[12] ulaşır ki bu, çıplak gözle çoğu zaman Jüpiter Mars’tan daha parlak görünmesine karşın, ancak Venüs, Ay ve Güneş’çe aşılabilen bir parlaklıktır.
+
+Fiziksel özellikler[değiştir | kaynağı değiştir]
+
+Mars’ın yarıçapı Dünya’nınkinin yaklaşık yarısı kadardır. Yoğunluğu Dünya’nınkinden daha az olup, hacmi Dünya’nın hacminin % 15’i, kütlesi ise Dünya’nınkinin % 11’i kadardır. Mars’ın Merkür’den daha büyük ve daha ağır olmasına karşılık, Merkür ondan daha yoğundur. Bu yüzden Merkürün yüzeyindeki yerçekimi Mars’ınkinden daha fazladır. Mars, boyutu, kütlesi ve yüzeyindeki yerçekimi bakımından Dünya ile Ay arasında yer alır. Mars yüzeyinin kızıl-turuncu görünümü hematit ya da pas adıyla tanınan demiroksitten (Fe2O3) kaynaklanır.[13]
+Jeoloji ("arkeoloji")[değiştir | kaynağı değiştir]
+Dört "yerbenzeri gezegen"in[14] boyutlarının mukayesesi: Soldan sağa doğru Merkür, Venüs, Dünya ve Mars.
+Mars’ın üstteki topoğrafik haritasında daha ziyade volkanik platolar (kırmızı) ve çarpışma havzaları (mavi) hakim görünmektedir.
+Mars Pathfinder tarafından çekilmiş Mars’ın dağınık kaya oluşumlu bir yüzey fotoğrafı
+
+Uydu gözlemleri ile Mars meteorlarının incelenmesi Mars yüzeyinin esas olarak bazalttan oluştuğunu göstermektedir. Bazı kanıtlar Mars yüzeyinin bir kısmının tipik bazalttan ziyade, yeryüzündeki andezit kayalarının benzeri olabilecek zengin silisyum oluşumlarından meydana geldiğini göstermektedir; fakat gözlemlerdeki veriler bunların silisli cam olduğu şeklinde de yorumlanabilir. Her ne kadar Mars’ın asli manyetik alanı yoksa da, gözlemler gezegen kabuğunun parçalarının vaktiyle iki kutuplu bir manyetik alanın etkisinde bulunmuş olduğunu göstermektedir. Minerallerde gözlemlenen bu paleomanyetizm[15] yeryüzünün okyanus diplerinde bulunan tabakalarındakilere çok benzer özelliklere sahiptir. 1999’da ortaya atılan ve 2005’te Mars Global Surveyor verileriyle yeniden gözden geçirilen bir teoriye göre bu tabakalar, Mars’ta 4 milyar yıl önce, manyetik kutuplaşmanın yani manyetik alanın henüz etkin olduğu dönemde mevcut olan tektonik plakaların kanıtıdır.[16]
+
+Gezegenin iç yapısına ilişkin güncel modellere göre, gezegen, esas olarak demir ve % 14-17 civarında sülfürden oluşan, yarıçapı yaklaşık 1480 km. olan bir çekirdek bölgesi içerir. Bu demir sülfür (FeS) bileşiği kısmen akışkandır. Çekirdek, günümüzde etkin olmadığı görülen, gezegendeki birçok tektonik ve volkanik oluşumlardan oluşmuş bir silikat mantosuyla çevrilidir. Gezegenin kabuğunun ortalama kalınlığı 50 km. olup, azami kalınlığı 120 km. civarındadır.[17] Dünya’nın ortalama kalınlığı 40 km. olan kabuğu, her iki gezegenin boyutları gözönüne alındığında Mars’ınkine göre üç misli daha ince kalır.
+
+Mars’ın temel jeolojik devirleri şunlardır:
+
+ Nuh Devri: Devre bu ad, Mars’ın güney yarımküresindeki bir bölgenin Nuh’un Toprağı (Noachis Terra) olarak adlandırılması nedeniyle verilmiştir. Mars’ın en eski yüzey oluşumuna ilişkin devirdir, 3,8 milyar yıl öncesi ile 3,5 milyar yıl öncesi arasındaki dönemi kapsar. Nuh Devri yüzeyleri birçok büyük çarpma kraterleriyle oyulmuş haldedir. Tharsis volkanik plato bölgesinin bu devirdeki büyük bir sıvı su baskınıyla oluştuğu sanılmaktadır.
+ Hesperian devri: 3,5 milyar yıl öncesi ile 1,8 milyar yıl öncesi arasındaki dönemi kapsar. Bu devir, geniş lav ovalarının oluşumu ile nitelenir.
+ Amazon Devri: 1,8 milyar yıl öncesi ile günümüze kadarki dönemi kapsar. Amazon Devri bölgeleri, meteor çarpmalarıyla açılmış kraterleri pek içermez ve tamamen değişiktir. Ünlü Olimpos Dağı bu dönemdeki lav akıntılarıyla oluşmuştur.
+
+19 Şubat 2008’de Mars’ta muhteşem bir çığ meydana geldi. Mars Reconnaissance Orbiter uzay gemisinin kamerasınca filme kaydedilen görüntülerde 700 m. yükseklikteki bir uçurumun tepesinden kopan buz bloklarının ardında toz bulutları bırakarak yuvarlanışları görülüyordu.[18] Son incelemeler ilk kez 1980’lerde ortaya atılmış bir teoriyi desteklemektedir: Bu teoriye göre 4 milyar önce Mars’a Plüton gezegeni boyutlarındaki bir meteor çarpmıştır. Gezegenin kuzey kutup bölgesini kapsadığı gibi, yaklaşık % 40’ını kapsayan Borealis basin adı verilen garip havzanın bu çarpmayla oluştuğu sanılmaktadır.[19][20]
+Toprak[değiştir | kaynağı değiştir]
+
+Haziran 2008’de Phoenix uzay gemisi tarafından gönderilen veriler Mars toprağının hafifçe alkalin olduğunu ve hepsi de organik maddenin gelişmesi için elzem olan magnezyum, sodyum, potasyum ve klorür içerdiğini ortaya koydu. Bilim insanları Mars’ın kuzey kutbuna yakın toprağın kuşkonmaz gibi bitkilerin yetiştirilebileceği bir bahçe oluşturulması için elverişli olduğu sonucuna vardı.[21] Ağustos 2008’de Phoenix uzay gemisi Dünya suyu ile Mars toprağının karıştırılması gibi basit kimya deneylerine başladı ve önceden Mars toprağı konusunda ortaya atılmış birçok teoriyi doğrulayan bir keşifte bulundu: Mars toprağında perklorat tuzlarının izlerini keşfetti. Perklorat tuzlarının varlığı Mars toprağının daha da ilginç bulunmasını sağlamıştı[22] Fakat perklorat tuzlarının varlığının Mars’a taşınan Dünya toprağından, çeşitli örneklerden veya aletlerden kaynaklanmış olma olasılığı da vardı; bu yüzden, kaynağın Mars toprağı olup olmadığından iyice emin olunması için bu konuda daha fazla deneyler yapılması gerekmektedir.[23]
+2005 yılı Kasım ayı sonunda Mars Exploration Rover A Spirit 'in Husband Hill'in zirvesinden inerken çektiği Marstan bir panoramik fotoğraf.
+Hidroloji[değiştir | kaynağı değiştir]
+Cerberus Fossae adı verilen yüzey yarıkları
+Opportunity adlı uzay keşif aracı (astromobil) tarafından çekilmiş, Mars yüzeyinde geçmişte sıvı su bulunduğunu gösteren mikroskobik kaya oluşumlarının fotoğrafı
+10 Eylül 2005'te Mars Global Surveyor sonda aracı tarafından alınmış bu fotoğraf (sağda) 30 Ağustos 1999'daki fotoğrafta (solda) mevcut olmayan su buzuna benzer beyazımsı bir çökeltinin meydana geldiğini, yani geçici de olsa, yüzeyde sıvı su akışının varlığını ortaya koymaktadır.[24][25]
+
+1965’te Mariner-4’le gerçekleştirilen ilk Mars alçak uçuşuna kadar, gezegenin yüzeyinde sıvı su olup olmadığı çok tartışılmıştı. Bu tartışma özellikle kutup bölgelerindeki periyodik olarak değişim gösteren, deniz ve kıtaları andıran açık ve koyu renkli lekelerin gözlemlenmiş olmasından kaynaklanıyordu. Koyu renkli çizgiler bazı gözlemciler tarafından uzun zaman sıvı su içeren sulama kanalları olarak yorumlanmıştı. Bu düz çizgi oluşumları sonraki dönemlerde gözlemlenemediğinden optik illüzyonlar olarak yorumlandı. Kısa dönemlerde alçak irtifalarda olabilecek oluşumlar hariç tutulursa, günümüzdeki atmosferik basınç altında Mars yüzeyinde sıvı su mevcut olamaz, ancak geçici sıvı su akışları olabilir.[24][25][26][27] Buna karşılık özellikle iki kutup bölgesinde geniş su buzları mevcuttur.[28] Mart 2007’de NASA, güney kutbu bölgesindeki su buzlarının erimeleri halinde suların gezegenin tüm yüzeyini kaplayacağını ve oluşacak bu okyanusun derinliğinin 11 m. olacağının hesaplandığını açıkladı.[29] Ayrıca gezegende kutuptan 60° enlemine kadar bir buz permafrost[30] mantosu uzanır.[28] Mars’ta kalın kriyosfer[31] tabakasının altında, büyük miktarlarda, sıkışık halde tutulmuş (yüzeye çıkamayan) su rezervlerinin bulunduğu sanılmaktadır. Mars Express ve Mars Reconnaissance Orbiter’dan gelen radar verileri her iki kutupta (Temmuz 2005)[6] ve orta enlemlerde (Kasım 2008)[7] büyük miktarlarda su buzlarının bulunduğunu ortaya koymuştur. Phoenix Mars Lander ise 31 Temmuz 2008’de Mars toprağındaki su buzlarından örnek parçalar almayı başarmıştır.[32]
+
+Mars tarihinin nispeten erken bir döneminde Valles Marineris Vadisi (4000 km.) oluştuğunda su kanallarının oluşmasına neden olan, serbest kalmış yeraltı sularının yol açtığı büyük bir sıvı su baskınının meydana geldiği sanılmaktadır. Bu su baskının biraz daha küçüğü de daha sonra Cerberus Fossae denilen büyük yüzey yarıklarının açıldığı dönemde, yani yaklaşık 5 milyon yıl önce meydana gelmiştir ki, Cerberus Palus bölgesindeki Elysium Planitia’da halen görülebilen donmuş denizin bu olayın bir sonucu olduğu sanılmaktadır.[33] Bununla birlikte bölgenin buz akıntılarını[34] andıran lav akıntıları gölcüklerinin oluşabileceği bir morfolojiye de sahip olduğu gözden uzak tutulmamalıdır. Kısa zaman önce Mars Global Surveyor’daki Mars Orbiter’ın yüksek çözünürlüğe sahip kamerasıyla çekilen fotoğraflar Mars yüzeyindeki sıvı suyun tarihi hakkında daha ayrıntılı bilgiler sağlamıştır. İlginçtir ki, bu verilerde Mars’ta dev kanalların, ağacın dallanmasına benzeyen ağ biçimli geniş yolların bulunmasına karşın su akışlarını gösteren daha küçük ölçekli damar ve oluşumlara rastlanamamıştır. Bunun üzerine hava koşullarının bu küçük izleri zamanla yok etmiş olabilecekleri (erozyon) düşünüldü. Mars Global Surveyor uzay gemisiyle edinilen yüksek çözünürlüklü veriler, kraterlerde ve kanyonların duvarları boyunca yüzlerce yarık bulunduğunu ortaya koymuştur. Araştırmalar bu oluşumların genç yaşta olduğunu göstermektedir. Dikkat çeken bir yarığın altı yıl arayla çekilen iki fotoğrafı karşılaştırıldığında yarıkta yeni tortul çökeltilerinin biriktiği farkedilmiştir. NASA’nın Mars Keşif Programı yetkili uzmanlarından Michael Meyer bu tür renkli tortul çökelti oluşumlarına ancak güçlü bir sıvı su akışının yol açabileceği görüşündedir.
+
+"Mars Reconnaissance Orbiter" (Mars Yörünge Kaşifi) uydusundan alınan veriler sonucu, Mars'ta sıcak aylarda tuzlu su akıntılarının oluştuğu belirlenmiştir.[11][35]
+
+İster yağıştan (yağmurdan), ister yeraltı su kaynaklarından, ister başka bir kaynaktan kaynaklansın, sonuç olarak Mars’ta su mevcuttur.[36] Öte yandan söz konusu çökelti oluşumlarına donmuş karbondioksidin veya gezegen yüzeyindeki toz akımlarının neden olduğunu ileri süren senaryolar da ortaya atılmıştır.[37][38] Mars yüzeyinde geçmişte sıvı suyun bulunduğunun bir başka kanıtı da yüzeyde saptanan minerallerden gelmektedir: Hematit, goetit gibi mineraller genellikle suyun varlığını işaret eden minerallerdir (goetit serin topraklardaki yegane demir oksittir).[39]
+Coğrafya[değiştir | kaynağı değiştir]
+
+Globo de Marte - Valles Marineris.gifGlobo de Marte - Elysium Planitia.gifGlobo de Marte - Syrtis Major.gif
+
+Ay’ın haritasının yapılmasında ilk çalışmalarda bulunanlardan biri olan Johann Heinrich Mädler on yıl süren gözlemlerinden sonra, 1840’ta da ilk Mars haritasını çizdi. İlk areografi uzmanları olan Mädler ve kendisiyle Ay haritasının yapımında da çalışmış arkadaşı Wilhelm Beer, Mars haritasındaki işaretlemelerde, isimler vererek belirlemek yerine, sade bir şekilde, harfler kullanmayı tercih ettiler.[40]
+Mars’ın ve Güneş Sistemi’nin en yüksek dağı olan, 27.000 m. yükseklikteki Olimpos Dağı'nın (Olympus Mons) Mars’ın yörüngesinden çekilmiş fotoğrafı
+
+Mars’taki coğrafi oluşumlara Dünya coğrafyasından veya tarihsel ve mitolojik isimler verilmiştir. Mars’ın ekvatoru doğal olarak kendi çevresinde dönmesiyle belirlenmiştir, başlangıç meridyeni ise Dünya’daki Greenwich meridyeni gibi keyfi olarak, 1830’da ilk Mars haritalarının yapımı çalışmasında Mädler and Beer tarafından belirlenmiştir. 1972’de Mariner 9 uzay aracının Mars’le ilgili yeterince veri toplamasından itibaren, Sinus Meridiani’deki (Meridian Bay), sonradan Airy-0 olarak adlandırılan küçük bir krater, eski belirlemeyle uyuşacak tarzda 0.0° boylamı olarak seçildi (Beer ve Mädler tarafından “a†harfi ile işaretlenen boylam).
+
+Mars’ta deniz olmadığından Olimpos Dağı’nın yüksekliği “ortalama çekim yüzeyi†(İng. mean gravity surface) esas alınarak hesaplanmış ve yüksekliği 27 km. olarak saptanmıştır. (Bir başka deyişle, Mars’ta irtifalar atmosfer basıncının 610,5 Pa (6.105 mbar) olduğu seviye esas alınarak hesaplanır. Bu da Dünya’daki deniz seviyesinde mevcut basıncın yaklaşık ‰ 6’sıdır.)[41]
+Gezegen fotoğrafının tam ortasındaki devasa kanal, Valles Marineris kanyon oluşumunu göstermektedir.
+Mars’taki 7 mağaranın girişlerinin THEMIS tarafından çekilen fotoğrafı: A-Dena, B-Chloe, C-Wendy, D-Annie, E-Abby (solda) ve Nikki F-Jeanne.
+
+Mars topoğrafyası ilginç bir ikilem göstermesiyle dikkat çeker. Kuzey yarımkürenin lav akıntılarıyla düzleşmiş ovalar içermesine karşın, güney yarımküre eski çarpışmalarla çukurlar ve kraterlerle oyulmuş haldeki bir dağlık arazidir. 2008’de yapılan araştırma ve incelemeler 1980’de ortaya atılmış, Mars’ın kuzey yarımküresine dört milyar yıl önce Ay’ın boyutunun %6,6’sı büyüklükteki bir cismin çarpmış olduğunu ileri süren teoriyi kanıtlar görünmektedir. Bu görüş doğru olduğu takdirde Mars’ın kuzey yarımküresinde 10.600 km. uzunluğunda ve 8.500 km. genişliğinde bir krater alanının açılmış olması gerekirdi ki, bu, Avrupa, Asya ve Avustralya toprakları bütününe denk bir alandır.[42][43] Mars’ın yüzeyi Dünya’dan görünüşle, farklı albedo’su olan iki tür alana ayrılır. Kızılımsı demiroksit içeren tuz ve kumla kaplı soluk ovalar geçmişte Mars kıtaları olarak yorumlanmış ve bunlara Arabistan Ülkesi (Arabia Terra), Amazon Ovası (Amazonis Planitia) gibi adlar verilmiştir. Koyu renkli oluşumlar ise denizler olarak yorumlanmış ve bunlara Mare Erythraeum, Mare Sirenum ve Aurorae Sinus adları verilmiştir. Dünya’dan görünüşe göre en koyu renkli coğrafi oluşum Syrtis Major’dur.[44]
+Mars'taki Victoria Krateri'nin bir görüntüsü.
+
+Everest’in üç misli yüksekliğindeki Olimpos Dağı birçok büyük volkan içeren dağlık Tharsis bölgesindeki, yumuşak eğimli bir sönmüş volkandır. Mars aynı zamanda çarpma kraterlerinin gözlemlendiği bir gezegendir; yarıçapı 5 km. ve daha büyük olabilen bu krater oluşumlarının toplam sayısı 43.000 olarak belirlenmiştir.[45] En büyükleri hafif bir albedo oluşumuna sahip, Dünya’dan kolayca görülebilen Hellas çarpma havzasıdır (Hellas Planitia).[46] Hacmi açısından, bir kozmik cismin Dünya’ya oranla daha küçük olan Mars’a çarpma olasılığı, Dünya’ya çarpma olasılığının yarısı kadardır. Bununla birlikte Mars’ın asteroit kuşağına daha yakın olması, bu kuşaktan gelen cisimlerle çarpışma olasılığını çok fazla arttırmaktadır. Mars aynı zamanda kısa periyotlu (yörüngeleri Jüpiter’e uzanan) kuyruklu yıldızların çarpmalarına (veya süpürmelerine) da maruz kalmaktadır. Bununla birlikte Ay’ın yüzeyi ile kıyaslandığında, atmosferi kendisine küçük meteorlara karşı koruma sağladığından Mars yüzeyinde daha az krater görülür. Bazı kraterler meteor düştüğünde yerin nemli olduğunu gösteren bir morfolojiye sahiptir.
+
+Valles Marineris adlı ünlü büyük kanyon 4.000 km uzunluğunda ve 200 km genişliğinde olup, 7 km'ye varan bir derinliğe sahiptir. Yani uzunluğu Avrupa’nın uzunluğuna eş olup, gezegenin çevresinin beşte biridir. Büyüklüğünün devasa boyutlarının anlaşılması amacıyla Dünya’daki Büyük Kanyon'un boyutları göz önüne getirilebilir. (Büyük Kanyon 446 km uzunluğunda ve yaklaşık 2 km derinliğindedir.) Bir başka geniş kanyon olan Ma'adim Vallis 700 km uzunluğunda, 20 km genişliğinde ve yer yer 2 km derinliğindedir. Bu kanyonun geçmişte bir sıvı su baskınıyla oluştuğu sanılmaktadır.[47] 2001 Mars Odyssey robotik uzay gemisindeki kısa adı THEMIS (Thermal Emission Imaging System) olan kamera sayesinde Arsia Mons volkanının yamaçlarında 7 muhtemel mağara girişi saptanmıştır.[48] Bunlar günümüzde “yedi kızkardeşler†adıyla bilinmektedirler.[49] Mağara girişlerinin genişliklerinin 100 m ile 252 m arasında değiştiği sanılmakta ve ışık genellikle mağaraların dibine kadar giremediğinden bu mağaraların yeraltında sanılandan daha derin ve geniş bir halde uzandıkları düşünülmektedir. Bunlar içinden tek istisna dibi görünen Dena adlı mağaradır. Mars’ın kuzey kutbu dairesine Planum Boreum ve güney kutbu dairesine Planum Australe adı verilmiştir.
+Atmosfer[değiştir | kaynağı değiştir]
+Mars gezegeninde en bol bulunan gazlar – (Curiosity rover, Ekim 2012).
+Kuzey yarımkürenin yaz döneminde Mars atmosferinde saptanan metan gazı izleri-NASA
+Mars’ın yörüngeden çekilmiş, ufukta görülebilen ince atmosferi.
+Mars Pathfinder tarafından çekilmiş, Mars semalarındaki buz bulutlarının fotoğrafı
+
+.
+
+Mars manyetosferini 4 milyar yıl önce kaybetmiştir. Böylece Güneş rüzgârları Mars’ın iyonosfer tabakasıyla doğrudan etkileşime girerek atmosferi ince halde tutmaktadır. Mars Global Surveyor ve Mars Express’in her ikisi de, iyonize atmosfer parçacıklarının uzaya sürüklendiklerini saptamışlardır.[50][51] Mars atmosferi günümüzde nispeten incedir. Yüzeydeki atmosfer basıncı gezegenin en yüksek kısmında saptanan 30 Pa (0.03 kPa) ile en derin kısmında saptanan 1,155 Pa (1.155 kPa) arasında değişmektedir. Yani ortalama yüzey basıncı 600 Pa’dır (0.6 kPa) ki, bu da Dünya yüzeyinden 35 km. yükseklikte rastlanan basınca eştir. Bir başka deyişle Dünya yüzey basıncının %1’inden daha düşük bir değerdir. Mars’taki düşük yerçekiminden dolayı da atmosferinin "ölçek irtifa"sı (İng. scale height)[52] Dünya’nınkinden (6 km.) daha yüksek olup, 11 km.’dir. Mars yüzeyinde yerçekimi Dünya yüzeyindeki yerçekiminin %38’i kadardır.
+
+Mars atmosferi % 95 karbondioksit, % 3 nitrojen, % 1,6 argondan oluşmakla birlikte, oksijen ve su izleri de taşımaktadır.[53] 1,5 µm yarıçapındaki toz parçacıklarını içeren atmosferi tümüyle tozludur ki, bu, Mars yüzeyinden bakıldığında Mars gökyüzünün soluk bir turuncu-kahverengimsi renkte (İng. tawny) görülmesine neden olmaktadır.[54]
+
+Birçok araştırmacı Mars atmosferinde hacim itibariyle 30 ppb oranında metanın varlığını saptamışlardır.[55][56] Metan morötesi ışınlarla bozunan ve Mars’ınki gibi bir atmosferde[57] yaklaşık 340 yılda bozunacak kararsız bir gaz olduğundan, bu, gezegende güncel veya kısa zaman öncesine dek mevcut bir gaz kaynağının varlığını göstermektedir. Buna da ancak volkanik etkinlik, kuyruklu yıldız çarpmaları ve metanojenik mikroorganizma türleri neden olabilir. Bununla birlikte kısa zaman önce metanın biyolojik olmayan bir süreçle de üretilebileceği görüşü ortaya atılmıştır.[58]
+
+Kutup bölgelerinde kışın sürekli bir karanlık ve yüzeyde dondurucu bir soğuk hakim olur, bu da atmosferin % 25–30 civarındaki kısmının yoğunlaşmasına ve karbondioksitin “kuru buz†(İng. dry ice)[59] denilen halde katılaşmasına yol açar.[60] Kutuplar kış mevsimi geçip yeniden Güneş ışıklarına maruz kalmaya başladığında, buzlaşmış karbondioksit, hızı saatte 400 km.’ye ulaşan müthiş rüzgarlar yaratarak uçmaya başlar. Bu mevsimlik değişimler, büyük miktarlarda toz ve su buharı taşırlar ve Dünya’dakine benzer kırağı ve "sirüs bulutları"nın (saçakbulut) oluşmasına neden olurlar. Su-buzu bulutlarının fotoğrafı Opportunity tarafından 2004’te çekilmiştir.[61]
+İklim[değiştir | kaynağı değiştir]
+Mars’ın Eylül 2001'deki toz fırtınasından önceki (solda) ve toz fırtınası sırasındaki (sağda) görünümlerinin karşılaştırılması
+
+Gezegenler içinde mevsimleri Dünya’nınkilere en çok benzeyen gezegen, kendi çevresinde dönme ekseninin yörüngeye eÄŸikliÄŸinin Dünya’nınkine benzer olması nedeniyle, Mars’tır. Bununla birlikte Mars mevsimlerinin süreleri gezegenin Güneş’e daha uzak olması nedeniyle Dünya’nınkilerin iki mislidir ve “Mars yılıâ€nın süresi de iki Dünya yılı süresi kadardır. Mars’ın yüzey sıcaklıkları kutup kışı sırasındaki −140 °C (133 K) ile yaz sırasındaki 20 °C (293 K) arasında deÄŸiÅŸir.[62] Sıcaklık farklarının büyük olması, ince atmosferinin GüneÅŸ ısısını yeterince depolayamaması, atmosfer basıncının düşük olması ve toprağın ısı kapasitesinin (Ä°ng. thermal inertia) düşük olması gibi nedenlerden ileri gelir.[63]
+
+Mars Dünya’nınki gibi bir yörüngeye sahip olsaydı "eksen eğikliği"nin de benzeşmesi sayesinde, mevsimleri de Dünya’nınkilere daha benzer olacaktı. Bununla birlikte Mars yörüngesinin geniş eksantrikliği ilginç bir sonuç sağlamaktadır. Mars, güney yarımkürede yaz, kuzey yarımkürede kış olduğu zaman günberiye yakındır, güney yarımkürede kış, kuzey yarımkürede yaz olduğu zaman da günöteye yakındır. Bunun sonucunda da güney yarımkürede mevsimlerin daha aşırı farklar göstermesine karşın kuzey yarımkürede mevsimler olması gerekenden daha yumuşak geçerler. Böylece güneyde 30 °C ‘yi (303 K) bulan yaz sıcaklıkları kuzeydeki yaz sıcaklıklarına kıyasla biraz daha fazladır.[64]
+Mars’ın kuzey kutbu buz bölgesi
+
+Mars aynı zamanda GüneÅŸ Sistemi’ndeki en büyük “toz fırtınalarıâ€na[65] sahne olan gezegendir. Bu toz fırtınaları mahalli bir bölgedeki küçük fırtınalar biçiminde olabildiÄŸi gibi, tüm gezegeni kaplar büyüklükteki dev fırtınalar biçiminde de olabilmektedir. Bunlar özellikle Mars Güneş’e en yakın konumuna geldiÄŸinde ve küresel sıcaklığın arttığı hallerde oluÅŸmaya eÄŸilimlidirler.[66]
+
+Kutup dairelerinin her ikisi de esas olarak su buzundan oluşmaktadırlar. Ayrıca yüzeylerinde “kuru buz†da mevcuttur. Katılaşan karbondioksit olan “kuru buz†(İng. dry ice) kuzey kutup dairesinde yalnızca kışın yaklaşık bir metre kalınlıkta bir ince tabaka oluşturacak şekilde birikir; güney kutup dairesine ise bu tabaka kalıcıdır ve kalınlığı 8 m.’yi bulur.[67] Kuzey kutup dairesinin yarıçapı kuzey yarımkürenin yazı sırasında 1000 km. olup yaklaşık 1.6 milyon {\displaystyle km^{3}} buz içerir. (Grönland buz kitlesinin hacmi 2,85 milyon {\displaystyle km^{3}}’tür.) Bu buz tabakasının kalınlığı 2 km.’ye ulaşır. Güney kutbu dairesinin yarıçapı ise 350 km. olup, buradaki buz kalınlığı 3 km.’dir.[68] Buradaki buz kitlesinin hacminin de kuzeydeki kadar olduğu sanılmaktadır.[69] Her iki kutup dairesinde de diferansiyel güneş ısısından kaynaklandığı sanılan, buzların uçması ve su buharının yoğunlaşması olaylarıyla etkileşim içinde bulunan spiral oluşumlar gözlemlenmiştir.[70][71] Her iki kutup dairesi de Mars mevsimlerinin ısı dalgalanmalarına bağlı olarak küçülüp büyürler.
+Evrim[değiştir | kaynağı değiştir]
+
+Mars’la ilgili son keşifler gezegenin tarihi boyunca çeşitli belirleyici anlar yaşamış olduğunu ortaya koymuştur. Örneğin sıvı su izleri gezegenin atmosferinin vaktiyle bugünkünden daha kalın olduğunu, Kuzey Havzası izleri de çok büyük kütleli bir cisimle büyük bir çarpışma geçirmiş olduğunu ortaya koymaktadır. Gezegenin evrimiyle ilgili muhtemel açıklamalar şunlardır:
+
+ Geçmişte büyük bir uydu iç kısmın üzerindeki gelgit etkisiyle kalıcı bir manyetik alanın oluşmasını sağlamış olabilir. Bu alan Mars atmosferini güneş rüzgarlarından korumuş ve yüzeyde sıvı su hareketlerinin meydana gelmesine olanak sağlamış olmalıdır.
+ Bu çarpışma bir yarımküresinin kabuğunun kalkmasına ve atmosfer tabakasının tahrip olmasına yol açmış olmalıdır. Mars’a geçmişte kuzey kutbu bölgesinden çarpan bu büyük cisim muhtemelen yörüngesi gelgit gücünün etkisiye bozulmuş bir uydusu olabilir. Düşen uydunun artık gelgit etkisi olmadığından manyetik alan zayıflamış ve yüzeye çarpan güneş rüzgarları atmosferin yeniden oluşmasını engellemiş olmalıdır.
+ Gezegende belirli bir kararlılığı sağlayan uydunun yokluğu beş milyon yıllık dengenin yalpalaması ya da bozulması demekti. Dengedeki bu bozulma, kutup bölgelerinin düzenli olarak ısınmasına, buzların bir parça erimesiyle sıvı suların oluşmasına ve dolayısıyla kutup dairesinde çizgilerin meydana gelmesine neden oldu.
+
+Yörünge ve kendi çevresinde dönüş[değiştir | kaynağı değiştir]
+Mars ve yörüngesi (kırmızı) ile asteroit kuşağındaki cüce gezegen Ceres’in (sarı) mukayesesi (kuzey tutulum kutbundan görünüşle). Tutulumun güney yörünge parçaları koyu renkle gösterilmiştir. Günberi (q) ve günöte (Q) en yakın geçiş tarihleriyle belirtilmiştir.
+
+Mars’ın Güneş’ten ortalama uzaklığı yaklaşık 230.000.000 km. (1,5 AU), yörünge süresi ise 687 Dünya günüdür. Mars günü Dünya gününden biraz daha uzun olup, tam olarak 24 saat, 39 dakika ve 35,244 saniyedir. Bir Mars yılı 1.8809 Dünya yılıdır, yani Dünya zaman birimiyle tam olarak 1 yıl, 320 gün ve 18,2 saattir.
+
+Mars’ın eksen eğikliği Dünya’nın eksen eğikliğine çok yakın olup, 25,19 derecedir. Dolayısıyla Mars’ta de Dünya’dakini andıran mevsimler meydana gelir. Fakat Mars mevsimlerinin süreleri Mars’ın yörünge süresinin uzunluğundan dolayı, Dünya mevsimlerinin sürelerinin iki katıdır. Mars Mayıs 2008’de günöteye Nisan 2009’de günberiye geçmiştir. Bir sonraki günöte tarihi haziran 2010’dur.
+
+Mars’ın nispi olarak söylenebilecek yörünge eksantrikliği (eksenel kaçıklık, dışmerkezlik) 0,09'dur; Güneş Sistemi’nde yalnızca Merkür bundan daha büyük bir eksantrikliğe sahiptir. Bununla birlikte Mars’ın geçmişte bugünkünden daha dairesel bir yörünge çizdiği bilinmektedir. 1,35 milyon Dünya yılı öncesinde Mars’ın eksantrikliği yaklaşık 0,002 idi, yani Dünya’nın bugünkü eksantrikliğinden de daha azdı.[72] Mars’ın eksantriklik devresi 96.000 Dünya yılıdır.[73] Bununla birlikte Mars’ın 2,2 milyon yıllık bir eksantriklik devresi daha vardır. Son 35.000 yılda Mars’ın yörüngesinin eksantrikliği diğer gezegenlerin çekimsel etkileri dolayısıyla artmıştır. Mars ve Dünya’nın birbirlerine en yaklaştıkları zamanlarda aralarında bulunan mesafe gelecek 25.000 yılda biraz daha azalacaktır.[74]
+Doğal uyduları[değiştir | kaynağı değiştir]
+Phobos deimos diff.jpg
+İsim Çap
+(km) Kütle
+(kg) Ortalama yörünge
+yarıçapı (km) Yörünge süresi
+(saat)
+Phobos 22,2 (27 × 21.6 × 18.8) 1,08×1016 9 378 7,66
+Deimos 12,6 (10 × 12 × 16) 2×1015 23 400 30.35
+
+Mars’ın düzensiz biçimli, iki küçük doğal uydusu vardır. Kendilerine eski Yunan mitolojisindeki savaş ilahı Ares’e (Romalılar’da Mars) yardım eden çocuklarının adlarından esinlenerek Phobos ve Deimos adları verilmiş, gezegene çok yakın yörüngeler izleyen bu uydular muhtemelen bir Mars "Trojan asteroiti"[75] olan 5261 Eureka gibi, gezegenin çekim alanına kapılarak uydu haline gelmiş asteroitlerdir.[76] Fakat hava tabakası olmayan Mars’ın bu iki uyduya nasıl ve ne zaman sahip olduğu tam olarak anlaşılmış değildir. Üstelik bu büyüklükteki asteroitler çok nadirdir, özellikle ikili olanları. Bu büyüklükteki asteroitlere asteroit kuşağının dışında rastlanması durumu daha da garip kılmaktadır.[77]
+
+Her iki uydu da 1877’de Asaph Hall tarafından keşfedilmiştir. Phobos ve Deimos’un hareketleri Mars yüzeyinden bizim ‘ay’ımızın Dünya’dan görünüşüne kıyasla çok farklı olarak görünür. Phobos 11 saatte bir, batıdan doğar. Deimos ise, dolanım süresi 30 saat olmakla birlikte, 2,7 günde bir doğar.[78] Her iki uydu da ekvatora yakın dairesel yörüngeler izlerler. Phobos ‘un yörüngesi Mars’tan kaynaklanan gelgit etkileri nedeniyle giderek küçülmektedir. Bu yüzden Phobos yaklaşık 50 milyon yıl içinde Mars’a çarpacaktır.[78]
+Yaşam[değiştir | kaynağı değiştir]
+ALH84001 adı verilen Mars meteoru, bakteri düzeyinde yaşam belirtileri olduğu ileri sürülen mikroskobik oluşumlar göstermektedir.
+Mars Global Surveyor (MGS) tarafından çekilmiÅŸ fotoÄŸrafta görülen, mahiyeti anlaşılamamış “koyu kumul lekeleriâ€.
+“Koyu kumul lekeleriâ€nin Mars Global Surveyor tarafından çekilen yüksek çözünürlüğe sahip fotoÄŸrafında lekelerin yakın plandan görünümü.
+
+Evrende yaşamın Dünya’daki koşullara benzer koşullar altında ortaya çıkabileceği varsayımından hareketle, günümüzde bir gezegenin yaşanabilirlik (İng. planetary habitability)[79] ölçüsü, yani bir gezegende yaşamın gelişebilme ve sürebilmesinin ölçüsü yüzeyinde su bulunup bulunmamasıyla yakından ilgili görülmektedir. Bu da bir güneş sistemindeki gezegenin güneşine uzaklığının gereken uygun uzaklıkta olup olmamasına bağlıdır. Mars’ın yörüngesinin Dünya’nın yer aldığı bu uygun kuşağın yarım astronomik birim kadar daha uzağında olması, ince bir atmosfere sahip bu gezegenin yüzeyinde suyun donmasına neden olmaktadır. Bununla birlikte gezegenin geçmişindeki sıvı su akışları Mars’ın yaşanabilirlik potansiyeli taşıdığını ortaya koymaktadır. Verilere göre, Mars yüzeyindeki sular yaşam için gerekenden çok daha tuzlu ve çok daha asitlidir.[80]
+
+Gezegenin manyetosferinin olmayışı ve son derece ince bir atmosfere sahip oluşu büyük bir handikaptır. Yüzeyindeki ısı tranferi (İng. heat transfer)[81] pek büyük değildir, meteorlara ve güneş rüzgarlarına karşı savunması hemen hemen yok gibidir ve suyu sıvı halde tutacak atmosfer basıncı yetersizdir (dolayısıyla su gaz haline geçer). Verilere göre gezegen geçmişte günümüzdeki haline kıyasla daha yaşanabilir haldeydi. Bütün bu olumsuzluklara rağmen Mars’ta organizmaların olmadığı ya da hiç yaşamamış olduğu söylenemez. Nitekim 1970’lerdeki Viking Programı sırasında Mars toprağındaki mikroorganizmaların saptanması amacıyla Mars’tan getirilen örneklerde bazı pozitif görünen sonuçlar elde edildi. Fakat bu sonuçlar birçok bilim insanının katıldığı bir tartışmaya yol açtı ve kesin bir sonuca ulaşılamadı. Buna karşılık Viking Programı’yla edinilen verilerden yararlanan profesör Gilbert Levin,[82] Rafaël Navarro-González[83] ve Ronalds Paepe yeni bir taksonomik sistem hazırladılar ve bu sistemde Mars’taki yaşam türü Gillevinia straata[84] adı altında ele alındı.[85][86][87]
+
+Sonraki yıllarda Phoenix Mars Lander tarafından yürütülen deneyler Mars toprağında sodyum, potasyum ve klorür içeren bir alkali bulunduğunu gösterdi.[88] Bu besleyici toprak yaşamı taşımaya gayet elverişliydi, fakat unutulmaması gereken bir sorun daha vardı: Yaşamın yoğun morötesi ışınlardan korunabilmesi.
+
+Nihayet Johnson Uzay Merkezi Laboratuvarı’nda[89] Mars kökenli ALH84001 meteoru üzerinde organik bileşimler saptandı; varılan sonuca göre bunlar Mars üzerindeki ilk yaşam türleriydi.[90][91][92][93] Öte yandan Mars yörüngesindeki uzay gemileri kısa zaman önce düşük miktarlarda metan ve formaldehit saptadılar ki, bunlar da yaşamın varlığını ima eden işaretler olarak yorumlandılar; zira bu kimyasal bileşimler Mars atmosferinde hızla çözünmektedirler.[94][95]
+
+Mars’ta biyolojik kökenli oldukları ileri sürülen oluşumlardan en tanınmışları “koyu kumul lekeleri†adıyla bilinen oluşumlardır.[96] İlk kez Mars Global Surveyor tarafından 1998-1999 yıllarında gönderilen fotoğraflarla keşfedilen “koyu kumul lekeleri†Mars’ın özellikle güney kutup bölgesinde (60°-80°enlemleri arasında) görülebilen, buz tabakasının üzerinde veya altında beliren, mahiyeti henüz anlaşılamamış oluşumlardır. Mars ilkbaharının başlarında belirmekte ve kış başlarında yok olmaktadırlar. Bunların kış boyunca buz tabakasının altında kalan fotosentetik koloniler, yani fotosentez yapan ve yakın çevrelerini ısıtan mikroorganizmalar oldukları ileri sürülmektedir.[97][98][99][100][101][102]
+
+28 Eylül 2015'te Mars'ta sıvı halde tuzlu su bulunduğu açıklanmıştır. Tuzlu suyun bulunması ile birlikte, bilim adamları Mars'ta yaşam bulma olasılığının da arttığını ifade etmişlerdir.[103]
+Keşif[değiştir | kaynağı değiştir]
+
+Mars’a günümüze dek, gezegenin yüzeyini, iklimini ve jeolojisini incelemek üzere, ABD, Avrupa ülkeleri, Japonya ve SSCB tarafından düzinelerce uzay gemisi (İng. spacecraft), uydu/yörünge aracı (İng. orbiter), iniş aracı/uzay gemisi (İng. lander) ve sonda/uzay keşif aracı (İng. rover) gibi çeşitli uzay araçları gönderilmiştir. Fakat bu uzay gemisi gönderme denemelerinin yaklaşık üçte ikisi araçlar ya görevlerini tamamlayamadan ya da görevlerine daha başlayamadan bilinen veya bilinmeyen nedenlerle başarısızlıkla sonuçlanmıştır.
+Tamamlanmış keşif projeleri[değiştir | kaynağı değiştir]
+Phoenix'in temsilî inişi
+
+Görevini tamamlama konusunda ilk başarı 1964’te NASA tarafından gönderilen Mariner-4’ten gelmiştir. Yüzeye ilk başarılı inişler ise SSCB’nin Mars Probe Projesi kapsamında 1971’de fırlattığı Mars-2 ve Mars-3 tarafından gerçekleştirilmiş, fakat her iki araçla irtibat, inişlerinden kısa bir süre sonra kesilmiştir. Sonraki yıllarda NASA Viking Projesi'ni başlattı ve 1975’te her biri birer "iniş aracı" taşıyan iki "uydu aracı" fırlatıldı. Her iki araç 1976’da başarıyla iniş yaptılar. Gezegende Viking-1 altı yıl, Viking-2 ise üç yıl kaldı. Bunlar Mars’ın ilk renkli fotoğraflarını gönderdiler[104]; gezegenin yüzeyinin haritasının çıkarılması amacıyla gönderdikleri fotoğraflara günümüzde bile zaman zaman başvurulmaktadır.
+
+Sovyetler 1988’de Mars’a gezegeni ve doğal uydularını incelemek üzere Phobos-1 ve Phobos-2 adlı sonda araçları gönderdiler. Phobos-1’le irtibat Mars yolundayken kesilmiş olmasına karşın, Phobos-2 fotoğraflar göndermede başarılı oldu. Fakat Phobos-2 de tam Phobos adlı doğal uydunun yüzeyine iki iniş aracını salmak üzereyken başarısızlığa uğradı.
+
+Mars Observer uydusunun 1992’deki başarısızlığından sonra NASA tarafından 1996’da Mars Global Surveyor fırlatıldı. Görevinde tümüyle başarılı oldu. Harita çıkarma görevini 2001’de tamamladı. Kasım 2006’da üçüncü uzatılmış görevi sırasında sonda aracıyla irtibat kesildi, uzayda 10 yıl çalışır halde kalmayı başardı. NASA Surveyor’ın fırlatılmasından bir ay sonra da Mars Pathfinder’ı fırlattı. Bu, robotik bir keşif aracı olan Sojourner’ı taşıyordu. 1997 yazında Mars’taki Ares Vallis bölgesine iniş yaptı. Bu proje de başarıyla sonuçlandı.[105]
+
+Mars’la ilgili son tamamlanmış görevde 4 Ağustos 2007’de fırlatılan iniş yeteneğine sahip Phoenix uzay gemisi kullanılmıştır. Araç 25 Mayıs 2007’de Mars’ın kuzey kutbu bölgesine iniş yaptı.[106] 2,5 m.’ye uzanan robot koluyla Mars toprağını bir metre kazabilecek kapasitede olup, mikroskobik bir kamerayla donatılmıştı. Bu mikroskobik kamera insan saçının binde biri kadar inceliği ayırt edebilecek bir hassasiyete sahipti. 15 Haziran 2008’de indiği yerde su buzlarını keşfetti.[107][108] Görevini 10 Kasım 2008’de tamamladı.
+
+Rusya ve Çin’in ortak projesi olan Phobos-Grunt projesiyle Mars'ın uydusu Phobos’tan örnekler toplanarak analiz için Dünya'ya geri getirilmesi planlanıyordu. Ancak bu sonda 9 Kasım 2011’de fırlatılmasının ardından bozulmuş, 15 Ocak 2012'de dünyaya düşerek imha olmuştur.
+Sürdürülen keşif projeleri[değiştir | kaynağı değiştir]
+Spirit adlı sonda aracı
+
+ 2001’de NASA Eylül 2010’a kadar görevini sürdürmesi planlanan ve halen görevini başarıyla yerine getiren Mars Odyssey uydu aracını fırlattı.[109] Aracın Gamma Işını Spektrometresi,[110] regolit[111] incelemesi sırasında ilginç miktarlarda hidrojen tespit etti.[112]
+ 2003’te Avrupa Uzay Ajansı (ESA), Mars Express Projesi kapsamında Mars Express Orbiter adlı uydu aracı ile Beagle-2 adlı iniş aracını fırlattı. Beagle-2 iniş sırasında başarısızlığa uğradı ve Şubat 2004’te kaybolduğuna dair bir açıklama yapıldı.
+ ESA’nın Beagle-2’yi fırlattığı yıl NASA Spirit ve Opportunity adlarında iki keşif sondasını fırlattı. Her ikisi de görevini başarıyla yerine getirdi. Bu çalışmalarla Mars’ta en azından geçmişte sıvı suyun bulunduğu kesinlik kazanmıştır. 2004 yılında Mars'ın kuzey kutbuna inen ve görev süresi 6 yıl olarak belirtilen Opportunity, bilim adamlarını şaşırtan bir performansla halen bütün fonksiyonlarını en üst düzeyde kullanarak Mars görevine devam etmektedir.[113]
+ 2004’te Planetary Fourier Spectrometer ekibi Mars atmosferinde metan saptadıklarını açıkladı.
+ NASA 12 Ağustos 2005’te Mars Reconnaissance Orbiter sonda aracını fırlattı, 10 Mart 2006’da yörüngeye oturan araç iki yıl boyunca bilimsel incelemelerde bulundu. Araç aynı zamanda gelecekteki projeler için en uygun iniş platformlarını bulmak üzere Mars toprağı ve iklimini incelemekle görevlidir.
+ ESA Haziran 2006’da Mars’ta “kutup ışıkları†olayını saptadıklarını açıkladı.[114] Vesta ve Ceres’i incelemek üzere gönderilen Dawn uzay gemisinin Şubat 2009’da Mars’a ulaştı.
+ Mars Bilim Laboratuvarı adlı keşif sondası (curiosity) "merak" Kasım 2011'de fırlatıldı. Sonda 2012 Ağustos'unda Mars'ın Gale kraterine yumuşak iniş yaptı. Yapması planlanan deneyler arasında kayalar üzerinde kimyasal lazer yardımıyla uzaktan (azami 13 m.) çalışarak örnekler toplaması da bulunmaktadır.
+
+Gelecekte uygulanacak projeler[değiştir | kaynağı değiştir]
+
+ ESA 2013’te Mars toprağındaki organik molekülleri araştırmak üzere ExoMars adlı sonda aracını fırlatmayı planlamıştır.[115]
+ 15 Eylül 2008’de NASA Mars atmosferi hakkında bilgi edinilmesini sağlamak üzere 2013’te MAVEN adı verilen robot programını uygulamaya koyacağını açıklamıştır.[116]
+ Fin-Rus ortak projesi olan MetNet projesinde, gezegenin atmosferik, fiziksel ve meteorolojik yapısını yeterince inceleyebilmek için yaygın bir gözlem ağı kurmak üzere on kadar küçük taşıtın Mars yüzeyine gönderilmesi planlanmıştır.[117] Öncü görevinde bulunacak ilk iniş araçlarının 2009’da ya da 2011’de fırlatılması planlanmıştır.[118]
+ NASA ve Lockheed Martin şirketi önce Ay’a (2020’de), ardından Mars’a insan taşıması planlanan Orion adı verilen uzay gemisi için çalışmalara başladılar. 28 Eylül 2007’de NASA başkanı Michael D. Griffin NASA tarafından Mars’a yollanacak ilk insanın 2037’de Mars’ta olacağını açıkladı.[119]
+ Nasa, Marsa insan göndermek için ADEPT adında 1704 derece ısıya dayanıklı materyal geliştiriyor. Bu materyalin Mars atmosferine giriş için ve Mars'ta kurulacak seranın yapımında kullanılacağı açıklandı.[120]
+ ESA Mars’a insan göndermelerinin 2030 ve 2035 yılları arasında yer alacağını umduklarını açıkladı.[121]
+ Mars One adlı proje ile de insan bulunduran araçlarla koloni kurulması ön görülüyor.[122]
+
+Mars’ta astronomi gözlemleri[değiştir | kaynağı değiştir]
+Mars’tan Güneş’in batışı manzarası. Fotoğraf Gusev Krateri’nden Spirit adlı uzay keşif sonda aracınca 19 Mayıs 2005’te çekilmiştir.
+
+Çeşitli uydu araçlarının, iniş araçlarının ve sonda araçlarının Mars’taki varlıkları sayesinde günümüzde astronomi araştırmaları Mars gökyüzünden de yapılabilmektedir. Bunun pek çok yönden avantajları bulunmaktadır. Mars’ın doğal uydularından Phobos doğal olarak Mars’tan daha iyi gözlemlenebilmektedir (dolunayın üçte biri açısal çapta görülür). Diğer doğal uydu olan Deimos ise Mars yüzeyinden az çok bir yıldızı andırır tarzda görünür; Dünya’dan Venüs’ün görünüşüne kıyasla, Venüs’ten hafifçe daha parlaktır.[123]
+
+Öte yandan Dünya’da iyi bilinen, kutup ışıkları, meteorlar gibi birçok fenomen artık Mars yüzeyinde de gözlemlenebilmektedir.[124] Dünya’nın Mars ile Güneş arasına girecek ve Güneş üzerinde bir leke oluşturacak şekilde Güneş önünden geçişi Mars’tan 10 Kasım 2084’te izlenebilecektir. Mars’tan aynı şekilde Merkür ve Venüs’ün transit geçişi (bir kütlenin başka bir kütlenin önünden geçmesinin gözlemlenebildiği geçiş) ve Deimos’un kısmi güneş tutulmaları izlenebilir.
+Görünüş ve "karşı konum"lar[değiştir | kaynağı değiştir]
+Mars’ın 2003 yılında küçük bir teleskoptan görünüşü esas alınarak hazırlanmış dönme hareketi.
+Mars’ın 2003’te Dünya’dan görünüşle hazırlanan görünürdeki “geri devimâ€i(tersinir hareketi)
+Dünya'yı merkez alan, tutulum üzerinden 2003-2018 Mars "karşı konum"larının görünüşü
+
+Çıplak gözle bakıldığında Mars genellikle, farklı olarak sarı, turuncu ya da kırmızımsı renklerde görünür. Parlaklığı da, yörüngesindeki yolculuÄŸu sırasındaki konumlarına baÄŸlı olarak, Dünya’dan görünen diÄŸer gezegenlerden daha fazla deÄŸiÅŸiklik gösterir. Görünürdeki kadiri “ kavuÅŸum konumuâ€ndaki +1,8’den “günberi karşı konumuâ€ndaki −2,9 aralığında deÄŸiÅŸir.[12] Kimi konumlarında güneÅŸin güçlü ışığından dolayı görünmez hale gelir. 32 yılda iki kez – Temmuz ve Eylül sonunda – en uygun konuma gelir. Mars teleskopla bakıldığında bolca yüzey ayrıntısı sunan bir gezegendir, özellikle kutuplardaki buzul bölgeleri elveriÅŸsiz koÅŸullarda bile belirgin olarak görülürler.[125]
+
+Mars’ın Dünya’ya en yakın olduÄŸu konum karşı konum olarak bilinir. “KavuÅŸum dönümü†(Ä°ng. synodic period) olarak bilinen iki “karşı konum†arasındaki süre Mars için 780 gündür. Yörünge eksantriklikleri nedeniyle bu sürede 8,5 güne varan oynamalar olabilir. Dünya’ya en yaklaÅŸtığı zamanlarda Dünya ile Mars arasındaki en kısa uzaklık, gezegenlerin eliptik yörüngelerine baÄŸlı olarak 55.000.000 km. ile 100.000.000 km. arasında deÄŸiÅŸir.[12] Önümüzdeki Mars “karşı konumâ€u 29 Ocak 2010’da meydana gelecektir. Mars “karşı konum†pozisyonuna yaklaÅŸtığında “geri devim “ (tersinir hareket, Ä°ng. retrograde motion) periyodu baÅŸlar.
+
+27 Ağustos 2003 günü, saat 9:51:13’de (UT) Mars son 60.000 yıl boyunca Dünya’ya en yaklaştığı konuma geldi. Bu konumunda Dünya ile arasındaki uzaklık 55.758.006 km. (0,372719 AU) idi. Bu olay Mars’ın karşı konumundan bir gün, günberisinden yaklaşık üç gün farkla meydana geldiğinden Mars Dünya’dan kolaylıkla izlenebildi. Yapılan hesaplamalara göre, Mars’ın Dünya’ya bu denli yaklaşmasının söz konusu olduğu son tarih M.Ö. 57.617 yılıdır, bir sonraki tarih ise 2287 yılıdır. 24 Ağustos 2208’deki yakınlaşmada ise iki gezegen arasındaki uzaklığın yalnızca 0.372254 AU olacağı hesaplanmıştır.
+Gözlem tarihi[değiştir | kaynağı değiştir]
+
+Mars’ın eski uygarlıklarla baÅŸlayan gözlem tarihinin özellikle, her iki yılda bir meydana gelen, gezegenin Dünya’ya yaklaÅŸtığı ve dolayısıyla görünürlüğünün arttığı “karşı konumâ€larına dayandığı görülür. Ayrıca tarihsel kayıtlarda her 15-17 yılda bir meydana gelen, farkedilebilen günberi “karşı konumâ€larına da yer verildiÄŸi görülmektedir, çünkü Mars günberiye yaklaÅŸtığında Dünya’ya da yaklaÅŸmaktadır.
+
+Batı tarihinde Mars gözlemlerine iliÅŸkin pek fazla kayıt olduÄŸu söylenemez. Aristo Mars gözlemlerini tarif eden ilk yazarlardan biri olmuÅŸtur. Mars’ın 3 Ekim 1590’da Venüs’çe “örtülmeâ€si (Ä°ng. Occultation) Heidelberg’te M. Möstlin tarafından kaydedilmiÅŸtir.[126] Nihayet 1609’da Mars Galile tarafından gözlemlendi. Bu aynı zamanda Mars’ın bir teleskop aracılığıyla yapılan ilk gözlemiydi.
+Mars kanalları[değiştir | kaynağı değiştir]
+Giovanni Schiaparelli tarafından yapılan Mars haritası.
+
+19. yy.’da teleskobun yaygınlaşması gök cisimlerinin tanımlanmasında belirli bir düzeye gelinmesini sağladı. 5 Eylül 1877’de Mars’ın bir günberi karşı konumu meydana geldi. O yıl İtalyan astronom Giovanni Schiaparelli Milano’da ilk ayrıntılı Mars haritalarını çıkarmak üzere 22 cm.’lik bir teleskop kullandı. Gözlemlerinde Mars yüzeyinde kendisinin “kanallar†adını verdiği, günümüzde kimilerince “optik illüzyon†olarak açıklanan birtakım oluşumlar saptadı ve bunları hazırladığı Mars haritalarına işaretledi. Mars yüzeyinde gözlemlediği bu uzun doğrusal hatlara Dünya’daki ünlü nehirlerin adlarını verdi.[127][128]
+
+Bu gözlemlerden etkilenen şarkiyatçı Percival Lowell 300 mm. 450 mm.’lik teleskoplara sahip bir gözlemevi kurdu. Gözlemevi Mars’ın keşfine ağırlık verdi. Mars’ın pozisyonları bakımından 1894 yılı son uygun fırsattı. Lowell Mars ve Mars’ta yaşam üzerine kamuda büyük bir yankı uyandıran kitaplar yayımladı. “Kanallar†dönemin en büyük teleskoplarını kullanan Henri Joseph Perrotin ve Louis Thollon gibi başka astronomlarca da saptanmıştı. Sonraki yıllarda daha büyük teleskoplarla yapılan gözlemler sonucunda, boyları önceden belirtildiği kadar uzun olmamakla doğrusal kanalların bulunduğu doğrulandı.
+Lowell tarafından 1914’ten önce yapılan gözlemlere göre hazırlanmış Mars kanalları
+
+Fakat daha sonra, 1909’da Flammarion, 840 mm.’lik bir teleskopla yaptığı gözlemler sonucunda, düzensiz bazı izler gözlemlemekle birlikte sözü edilen kanallara rastlamadığını açıkladı.[129] 1960’lı yıllara gelindiğinde farklı yaşam biçimleri olan Marslılar hakkında çeşitli senaryolar içeren bir sürü makale ve kitap yayımlanmış bulunuyordu.[130] NASA’nın Mariner Projesi kapsamında gönderdiği uzay gemisinin Mars’a ulaşmasından sonra bu tür senaryolar azalmış ve şekil değiştirmiştir. Örneğin bu kez, Marslılar’in bir başka boyutta ya da frekansta oldukları, gezegenlerine inilse de algılanamayacakları yönünde yeni senaryolar üretildi.
+Kültürde[değiştir | kaynağı değiştir]
+Mars’ın çeşitli adları[değiştir | kaynağı değiştir]
+
+Bu gezegene Batı kültüründe Mars adının verilmesi, eski Yunan mitolojisinde savaş ilahı olan Ares’e Roma mitolojisinde tekabül eden ilahın adının Mars olmasından ileri gelir.[131] Mars’a çeşitli dillerde verilmiş adlardan bazıları şunlardır:
+
+ Babil mitolojisinde Mars, gezegenin kızılımsı görünüşünden olsa gerek, ateş ve yıkım ilahı Nergal’in adıyla ifade edilirdi.[132]
+ Eski Yunanlar Nergal’i Ares’e denk tuttukları zaman bu gezegene Areos aster (ἌÏεως ἀστἡÏ), yani “Ares’in yıldızı†adını verdiler.[133] Daha sonra Ares ile Mars ilahlarının özdeÅŸ kılınmasıyla gezegen Romalılar’da stella Martis, yani “Mars’ın yıldızı†ya da kısaca Mars adını aldı. Eski Yunanlarda Mars’ın bir baÅŸka adı “ateÅŸli, ateÅŸten †anlamına gelen Pyroeis (ΠυÏόεις) idi.[134]
+ Mars’a Hindu mitolojisinde Mangala (मंगल),[135][136][137] Sanskrit dilinde ise, Koç Burcu ve Akrep Burcu işaretlerine sahip olan ve okült bilimleri öğreten savaş ilahından dolayı Angaraka denir.[138]
+ Mars eski Mısırlılar’da “kızıl Horusâ€[139] anlamında "Ḥr DÅ¡r";;;; adını almıştır.
+ Ä°branicede Mars’a “kızaran†anlamında Ma'adim (מ×די×) denir ki, Mars’taki en büyük kanyona da bu ad (Ma'adim Vallis) verilmiÅŸtir.[140]
+ Gezegenin Arapça’daki adı El-Mirrih’tir, oradan da Türkçe’ye Merih olarak geçmiştir. Eski Türkler’de Mars’a Sakit adı verilirdi.
+ Urdu ve Acem dillerinde de Merih (مریخ) olarak telaffuz edilir. Merih teriminin etimolojik kökeni bilinmemektedir. Eski İranlılar’da ise gezegene "iman ilahı"yla ilgili olarak Bahram (بهرام.) adı verilirdi.[141]
+ Çin, Japon, Kore ve Vietnam kültürlerinde Mars eski Çin mitolosindeki beÅŸ unsurdan ateÅŸle iliÅŸkilendirilerek “ateÅŸ yıldızı†( ç«æ˜Ÿ) adıyla geçer.[142]
+
+Mars symbol.svg
+
+Mars’ın sembolü astrolojik sembolünden yararlanılarak hazırlanmış bir sembol olup, bir daire ve küçük bir oktan oluşur. Bu, aslında, savaş ilahı Mars’ın kalkan ve mızrağının stilize bir temsilidir. Biyolojide de eril cinsiyeti göstermede kullanılan bu sembol, simyada karakteristik rengi kırmızı olan Mars’ın hükmettiği demir elementini simgeler; Mars da kırmızımsı rengini demiroksite borçludur.[143][144]
+“Zeki Marslılarâ€[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir]
+Cydonia bölgesinde insan suratına benzetilen, doğal olmayabileceği iddia edilen oluşum. Viking-1,1976
+İnsan suratına benzetilen oluşum yakınlarında doğal olmayabileceği iddia edilen piramit benzeri oluşumlar.
+
+Mars’ta zeki bir yaşam olabileceği konusunda 19.yy.’da ve 20. yy.’da, özellikle Mars’ın modern uzay araçlarınca incelenmesinden önce çeşitli iddialarda bulunulmuştur. Bu iddialardan bazıları şöyle özetlenebilir:
+
+ 19. yy. sonlarındaki popüler görüşe göre Mars zeki Marslılar’ca meskundu. Schiaparelli’nin gözlemlediği kanallar ile Percival Lowell’ın kitapları insanlarda şu kavramın doğmasına neden olmuştu: Mars soğuk, çorak bir gezegen olmakla birlikte burada sulama çalışmaları yapan eski uygarlıklar mevcuttu.[145]
+ 1899 Colorado Springs Laboratuvarı’nda alıcılarını kullanarak atmosferdeki radyo gürültülerini incelemeye çalışan mucit Nikola Tesla, sonradan bir başka gezegenden, muhtemelen Mars’tan gelmekte olduğunu iddia ettiği, tekrarlanan sinyaller saptadı. Bu düşüncesini 1901’deki bir konuşmasında açıkladı.[146] Lord Kelvin, Tesla’nın Dünya-dışı yaşamla ilgili varsayımlarını önceleri desteklemişse de, sonradan reddetmiştir.[147][148]
+ 1901’de ise Harvard College Gözlemevi müdürü Edward Charles Pickering, New York Times’taki bir makalede Arizona’daki Lowell Gözlemevi’nden Mars’tan irtibat kurma girişimlerinin olduğunu doğrulayıcı bir telgraf almış bulunduğunu açıkladı.[149]
+ 20. yy.’ın son çeyreğinde Mars’a giden uzay araçları Mars’ta “zeki yaşam†ürünü olabilecek ikamet yapıları olmadığını ortaya koyduğunda, bu kez, Marslılar konusunda yeni görüşler ileri sürüldü. Bunlardan bazılarına göre, Marslılar farklı bir boyutta yaşamaktaydılar, kimilerine göre de Mars’ta saptanan insan suratı ve piramitler biçimindeki oluşumlar doğal oluşumlar değildiler.[150][151][152][153]
+
+Bilimkurgu[değiştir | kaynağı değiştir]
+
+Bilimkurguda Mars kızıl renkte temsil edilmiş ve ilk zamanlardaki bilimsel spekülasyonlar doğrultusunda zeki canlılarca meskun olarak canlandırılmıştı. Marslılar’a ilişkin ilk bilimkurgu senaryoları içinde en tanınmışı H.G. Wells’in 1898’de yayımlanan, ölmekte olan gezegenlerinden kaçan Marslılar’ın Dünya’yı istila etmesini konu alan Dünyalar Savaşı'dır. Kitap, 1938’de radyoya, daha sonra sinemaya uyarlandı.[154] Mars ya da Marslılar’a ilişkin diğer bilimkurgu eserlerinin arasında şunlar sayılabilir:
+
+ The Martian Chronicles (hikâye), Ray Bradbury.
+ Barsoom (hikâyeler serisi), Edgar Rice Burroughs.
+ Marvin the Martian (çizgi film), Warner Brothers.
+ Gulliver'in Gezileri, Jonathan Swift.
+ Mars Trilogy (hikâyeler serisi), Kim Stanley Robinson.
+ We Can Remember It for You Wholesale (hikâye), Philip K. Dick. (Bu hikâyeden uyarlanan Total Recall isimli film de bulunmaktadır.)
+ Babylon 5 (televizyon dizisi).
+ Gerçeğe Çağrı (sinema filmi).
+ Marslı (bilim kurgu romanı), Andy Weir.
+
diff --git a/xpcom/tests/gtest/wikipedia/vi.txt b/xpcom/tests/gtest/wikipedia/vi.txt
new file mode 100644
index 0000000000..48270cc62d
--- /dev/null
+++ b/xpcom/tests/gtest/wikipedia/vi.txt
@@ -0,0 +1,333 @@
+
+
+Sao Há»a còn gá»i là: Há»a Tinh, (Tiếng Anh: Mars) là hành tinh thứ tÆ° tính từ Mặt Trá»i trong Thái DÆ°Æ¡ng Hệ. Äôi khi hành tinh này còn được gá»i tên là Há»a Tinh. Nó thÆ°á»ng được gá»i vá»›i tên khác là "Hành tinh Äá»", do sắt ôxít có mặt rất nhiá»u trên bá» mặt hành tinh làm cho bá» mặt nó hiện lên vá»›i màu đỠđặc trÆ°ng.[14] Sao Há»a là má»™t hành tinh đất đá vá»›i má»™t khí quyển má»ng, vá»›i những đặc Ä‘iểm trên bá» mặt có nét giống vá»›i cả các hố va chạm trên Mặt Trăng và các núi lá»­a, thung lÅ©ng, sa mạc và chá»m băng ở cá»±c trên Trái Äất. Chu kỳ tá»± quay và sá»± tuần hoàn của các mùa trên Há»a Tinh khá giống vá»›i của Trái Äất do sá»± nghiêng của trục quay tạo ra. Trên Sao Há»a có ngá»n núi Olympus Mons, ngá»n núi cao nhất trong Hệ Mặt Trá»i, và hẻm núi Valles Marineris, hẻm núi dài và rá»™ng nhất trong Thái DÆ°Æ¡ng Hệ. Lòng chảo Borealis bằng phẳng trên bán cầu bắc bao phủ tá»›i 40% diện tích bá» mặt hành tinh Ä‘á» và có thể là má»™t hố va chạm khổng lồ trong quá khứ.[15][16]
+
+Cho đến khi tàu Mariner 4 lần đầu tiên bay ngang qua Sao Há»a vào năm 1965, đã có nhiá»u suy Ä‘oán vá» sá»± có mặt của nÆ°á»›c lá»ng trên bá» mặt hành tinh này. Chúng dá»±a trên những quan sát vá» sá»± biến đổi chu kỳ vá» Ä‘á»™ sáng và tối của những nÆ¡i trên bá» mặt hành tinh, đặc biệt tại những vÄ© Ä‘á»™ vùng cá»±c, nÆ¡i có đặc Ä‘iểm của biển và lục địa; những Ä‘Æ°á»ng kẻ sá»c dài và tối ban đầu được cho là những kênh tÆ°á»›i tiêu chứa nÆ°á»›c lá»ng. Những Ä‘Æ°á»ng sá»c thẳng này sau đó được giải thích nhÆ° là những ảo ảnh quang há»c, mặc dù các chứng cứ địa chất thu thập bởi các tàu thăm dò không gian cho thấy Sao Há»a có khả năng đã từng có nÆ°á»›c lá»ng bao phủ trên diện rá»™ng ở bá» mặt của nó.[17] Năm 2005, dữ liệu từ tín hiệu radar cho thấy sá»± có mặt của má»™t lượng lá»›n nÆ°á»›c đóng băng ở hai cá»±c,[18] và tại các vÅ©ng vÄ© Ä‘á»™ trung bình.[19][20] Robot tá»± hành Spirit đã lấy được mẫu các hợp chất hóa há»c chứa phân tá»­ nÆ°á»›c vào tháng 3 năm 2007. Tàu đổ bá»™ Phoenix đã trá»±c tiếp lấy được mẫu nÆ°á»›c đóng băng trong lá»›p đất nông trên bá» mặt vào ngày 31 tháng 7 năm 2008.[21]
+
+Sao Há»a có hai vệ tinh, Phobos và Deimos, chúng là các vệ tinh nhá» và dị hình. Äây có thể là các tiểu hành tinh bị Há»a Tinh bắt được, tÆ°Æ¡ng tá»± nhÆ° 5261 Eureka-má»™t tiểu hành tinh Trojan của Há»a Tinh. Hiện nay có ba tàu quỹ đạo còn hoạt Ä‘á»™ng Ä‘ang bay quanh Sao Há»a: Mars Odyssey, Mars Express, và Mars Reconnaissance Orbiter. Trên bá» mặt nó có robot tá»± hành thám hiểm Sao Há»a (Mars Exploration Rover) Opportunity còn hoạt Ä‘á»™ng và cặp song sinh vá»›i nó robot tá»± hành Spirit đã ngừng hoạt Ä‘á»™ng, cùng vá»›i đó là những tàu đổ bá»™ và robot tá»± hành trong quá khứ-cả thành công lẫn không thành công. Tàu đổ bá»™ Phoenix đã hoàn thành phi vụ của nó vào năm 2008. Những quan sát bởi tàu quỹ đạo đã ngừng hoạt Ä‘á»™ng của NASA Mars Global Surveyor chỉ ra chứng cứ vá» sá»± dịch chuyển thu nhá» và mở rá»™ng của chá»m băng cá»±c bắc theo các mùa.[22] Tàu quỹ đạo Mars Reconnaissance Orbiter của NASA đã thu nhận được các bức ảnh cho thấy khả năng có nÆ°á»›c chảy trong những tháng nóng nhất trên Há»a Tinh.[23]
+
+Sao Há»a có thể dá»… dàng nhìn từ Trái Äất bằng mắt thÆ°á»ng. Cấp sao biểu kiến của nó đạt giá trị −3,0[7] chỉ đứng sau so vá»›i Sao Má»™c, Sao Kim, Mặt Trăng, và Mặt Trá»i.
+
+Äặc tính vật lý[sá»­a | sá»­a mã nguồn]
+So sánh kích cỡ của Trái Äất và Sao Há»a.
+
+Bán kính của Sao Há»a xấp xỉ bằng má»™t ná»­a bán kính của Trái Äất. Tá»· trá»ng của nó nhá» hÆ¡n của Trái Äất, vá»›i thể tích chỉ bằng 15% thể tích Trái Äất và khối lượng chỉ bằng 11%. Diện tích bá» mặt của hành tinh Ä‘á» chỉ hÆ¡i nhá» hÆ¡n tổng diện tích đất liá»n trên Trái Äất.[6] Trong khi Sao Há»a có Ä‘Æ°á»ng kính và khối lượng lá»›n hÆ¡n Sao Thủy, nhÆ°ng Sao Thủy lại có tá»· trá»ng cao hÆ¡n. Äiá»u này làm cho hai hành tinh có giá trị gia tốc hấp dẫn tại bá» mặt gần bằng nhau-của Sao Há»a chỉ lá»›n hÆ¡n có 1%. Sao Há»a cÅ©ng là hành tinh có giá trị kích thÆ°á»›c, khối lượng và gia tốc hấp dẫn bá» mặt ở giữa khi so vá»›i Trái Äất và Mặt Trăng (Mặt Trăng có Ä‘Æ°á»ng kính bằng má»™t ná»­a của Sao Há»a, trong khi Trái Äất có Ä‘Æ°á»ng kính gấp đôi Há»a Tinh; Trái Äất có khối lượng gấp chín lần khối lượng Sao Há»a trong khi Mặt Trăng có khối lượng chỉ bằng má»™t phần chín so vá»›i Há»a Tinh). Màu sắc vàng cam của bá» mặt Sao Há»a là do lá»›p phủ chứa sắt(III) ôxít, thÆ°á»ng được gá»i là hematit, hay rỉ sét.[24]
+Äịa chất[sá»­a | sá»­a mã nguồn]
+
+ Bài chi tiết: Äịa chất trên Sao Há»a
+
+Minh há»a cấu trúc bên trong Sao Há»a
+
+Dá»±a trên những quan sát từ các tàu quỹ đạo và kết quả phân tích mẫu thiên thạch Sao Há»a, các nhà khoa há»c nhận thấy bá» mặt Sao Há»a có thành phần chủ yếu từ đá bazan. Má»™t số chứng cứ cho thấy có nÆ¡i trên bá» mặt Sao Há»a giàu silic hÆ¡n bazan, và có thể giống vá»›i đá andesit ở trên Trái Äất; những chứng cứ này cÅ©ng có thể được giải thích bởi sá»± có mặt của silic Ä‘iôxít (silica glass). Äa phần bá» mặt của hành tinh được bao phủ má»™t lá»›p bụi mịn, dày của sắt(III) ôxít.[25][26]
+
+Mặc dù Há»a Tinh không còn thấy sá»± hoạt Ä‘á»™ng của má»™t từ trÆ°á»ng trên toàn cầu,[27] các quan sát cÅ©ng chỉ ra là nhiá»u phần trên lá»›p vá» hành tinh bị từ hóa, và sá»± đảo ngược cá»±c từ luân phiên đã xảy ra trong quá khứ. Những đặc Ä‘iểm cổ từ há»c đối vá»›i những khoáng chất dá»… bị từ hóa này có tính chất rất giống vá»›i những dải vằn từ luân phiên nhau trên ná»n đại dÆ°Æ¡ng của Trái Äất. Má»™t lý thuyết được công bố năm 1999 và được tái kiểm tra vào tháng 10 năm 2005 (nhá» những dữ liệu từ tàu Mars Global Surveyor), theo đó những dải này thể hiện hoạt Ä‘á»™ng kiến tạo mảng trên Sao Há»a khoảng 4 tá»· năm trÆ°á»›c, trÆ°á»›c khi sá»± vận Ä‘á»™ng dynamo của hành tinh bị suy giảm và dẫn đến sá»± mất hoàn toàn của từ trÆ°á»ng toàn cầu bao quanh hành tinh Ä‘á».[28]
+
+Những mô hình hiện tại vá» cấu trúc bên trong hành tinh cho rằng vùng lõi Há»a Tinh có bán kính khoảng 1.480 km, vá»›i thành phần chủ yếu là sắt vá»›i khoảng 14–17% lÆ°u huỳnh. Lõi sắt sunfit này có trạng thái lá»ng má»™t phần, vá»›i sá»± tập trung các nguyên tố nhẹ hÆ¡n cao gấp hai lần so vá»›i lõi của Trái Äất. Lõi được bao quanh bởi má»™t lá»›p phủ silicat, lá»›p này hình thành lên sá»± kiến tạo và đặc Ä‘iểm núi lá»­a của hành tinh, nhÆ°ng hiện nay những hoạt Ä‘á»™ng này đã ngừng hẳn. Chiá»u dày trung bình của lá»›p vá» Sao Há»a vào khoảng 50 km, vá»›i chiá»u dày lá»›n nhất bằng 125 km.[29] Lá»›p vá» Trái Äất vá»›i chiá»u dày trung bình 40 km, chỉ dày bằng má»™t phần ba so vá»›i Sao Há»a khi so vá»›i tỉ lệ Ä‘Æ°á»ng kính của hai hành tinh.
+
+Trong thá»i gian hình thành hệ Mặt Trá»i, Sao Há»a được tạo ra từ Ä‘Ä©a tiá»n hành tinh quay quanh Mặt Trá»i do kết quả của các quá trình ngẫu nhiên của sá»± vận Ä‘á»™ng Ä‘Ä©a tiá»n hành tinh. Trên Há»a Tinh có nhiá»u đặc trÆ°ng hóa há»c khác biệt do vị trí của nó trong hệ Mặt Trá»i. Trên hành tinh này, các nguyên tố vá»›i Ä‘iểm sôi tÆ°Æ¡ng đối thấp nhÆ° clo, phốt pho và lÆ°u huỳnh có mặt nhiá»u hÆ¡n so vá»›i trên Trái Äất; các nguyên tố này có lẽ đã bị đẩy khá»i những vùng gần Mặt Trá»i bởi gió Mặt Trá»i trong giai Ä‘oạn hình thành Thái DÆ°Æ¡ng hệ.[30]
+
+Ngay sau khi hình thành lên các hành tinh, tất cả chúng Ä‘á»u chịu những đợt bắn phá lá»›n của các thiên thạch ("Late Heavy Bombardment"). Khoảng 60% bá» mặt Sao Há»a còn để lại chứng tích những đợt va chạm trong thá»i kỳ này.[31][32][33] Phần bá» mặt còn lại có lẽ thuá»™c vá» má»™t lòng chảo va chạm rá»™ng lá»›n hình thành trong thá»i gian đó—chứng tích của má»™t lòng chảo va chạm khổng lồ ở bán cầu bắc Sao Há»a, dài khoảng 10.600 km và rá»™ng 8.500 km, hay gần bốn lần lá»›n hÆ¡n lòng chảo cá»±c nam Aitken của Mặt Trăng, lòng chảo lá»›n nhất từng được phát hiện.[15][16] Các nhà thiên văn cho rằng trong thá»i kỳ này Sao Há»a đã bị va chạm vá»›i má»™t thiên thể kích cỡ tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i Pluto cách nay bốn tá»· năm. Sá»± kiện này được cho là nguyên nhân gây nên sá»± khác biệt vỠđịa hình giữa bán cầu bắc và bán cầu nam của Há»a Tinh, tạo nên lòng chảo Borealis bao phủ 40% diện tích bá» mặt hành tinh.[34][35]
+
+Lịch sá»­ địa chất của Sao Há»a có thể tách ra thành nhiá»u chu kỳ, nhÆ°ng bao gồm ba giai Ä‘oạn lá»›n sau:[36][37]
+
+ Ká»· Noachis (đặt tên theo Noachis Terra): Giai Ä‘oạn hình thành những bá» mặt cổ nhất hiện còn tồn tại trên Sao Há»a, cách nay từ 4,5 tá»· năm đến 3,5 tá»· năm trÆ°á»›c. Bá» mặt hành tinh ở thá»i kỳ Noachis đã bị cày xá»›i bởi rất nhiá»u cú va chạm lá»›n. Cao nguyên Tharsis, cao nguyên núi lá»­a, được cho là đã hình thành trong thá»i kỳ này. Cuối thá»i kỳ này bá» mặt hành tinh bị bao phủ bởi những trận lụt lá»›n.
+ Kỷ Hesperia (đặt tên theo Hesperia Planum): 3,5 tỷ năm đến 2,9–3,3 tỷ năm trước. Kỷ Hesperia đánh dấu bởi sự hình thành và mở rộng của các đồng bằng nham thạch núi lửa.
+ Ká»· Amazon (đặt tên theo Amazonis Planitia): thá»i gian từ 2,9–3,3 tá»· năm trÆ°á»›c cho đến ngày nay. Bá» mặt hành tinh trong ká»· Amazon chịu má»™t số ít các hố va chạm thiên thạch, nhÆ°ng đặc tính của các hố va chạm cÅ©ng khá Ä‘a dạng. Ngá»n núi Olympus Mons hình thành trong ká»· này, cùng vá»›i các dòng nham thạch ở khắp nÆ¡i trên Sao Há»a.
+
+Má»™t số hoạt Ä‘á»™ng địa chất vẫn còn diá»…n ra trên Há»a Tinh. Trong thung lÅ©ng Athabasca (Athabasca Valles) có những vỉa dung nham niên đại tá»›i 200 triệu năm (Mya). NÆ°á»›c chảy trong những địa hào (graben) dá»c ở vùng Cerberus diá»…n ra ở thá»i Ä‘iểm 20 Mya, ám chỉ những sá»± xâm thá»±c của mac ma núi lá»­a trong thá»i gian gần đây.[38] Ngày 19 tháng 2 năm 2008, ảnh chụp từ tàu Mars Reconnaissance Orbiter cho thấy chứng cứ vá» vụ sạt lở đất đá từ má»™t vách núi cao 700 m.[39]
+Äất[sá»­a | sá»­a mã nguồn]
+
+ Bài chi tiết: Äất trên Sao Há»a
+
+Tàu đổ bá»™ Phoenix gá»­i dữ liệu vá» cho thấy đất trên Sao Há»a có tính kiá»m yếu và chứa các nguyên tố nhÆ° magiê, natri, kali và clo. Những dưỡng chất này được tìm thấy trong đất canh tác trên Trái Äất, và cần thiết cho sá»± phát triển của thá»±c vật.[40] Các thí nghiệm thá»±c hiện bởi tàu đổ bá»™ cho thấy đất của Há»a Tinh có Ä‘á»™ bazÆ¡ pH bằng 8,3, và chứa dấu vết của muối peclorat.[41][42]
+Khe đất hẹp (streak) ở vùng Tharsis Tholus, chụp bởi thiết bị Hirise. Nó nằm ở giữa bên trái bức ảnh này (hình mũi kim). Tharsis Tholus nằm ở ngay bên phải ngoài bức ảnh.
+
+Khe đất hẹp (streak) thÆ°á»ng xuất hiện trên khắp bá» mặt Sao Há»a và những cái má»›i thÆ°á»ng xuất hiện trên các sÆ°á»n dốc của các hố va chạm, trÅ©ng hẹp và thung lÅ©ng. Khe đất hẹp ban đầu có màu tối và theo thá»i gian nó bị nhạt màu dần. Thỉnh thoảng các rãnh này có mặt ở trong má»™t vùng nhá» hẹp sau đó chúng bắt đầu kéo dài ra hàng trăm mét. Khe rãnh hẹp cÅ©ng đã được quan sát thấy ở cạnh của các tảng đá lá»›n và những chÆ°á»›ng ngại vật trên Ä‘Æ°á»ng lan rá»™ng của chúng. Các lý thuyết Ä‘a số cho rằng những khe rãnh hẹp có màu tối do chúng nằm ở dÆ°á»›i những lá»›p đất sau đó bị lá»™ ra bá» mặt do tác Ä‘á»™ng của quá trình sạt nở đất của bụi sáng màu hay các trận bão bụi.[43] Má»™t số cách giải thích khác lại trá»±c tiếp hÆ¡n khi cho rằng có sá»± tham gia của nÆ°á»›c hay thậm chí là sá»± sinh trưởng của các tổ chức sống.[44][45]
+Thủy văn[sửa | sửa mã nguồn]
+
+ Bài chi tiết: NÆ°á»›c trên Sao Há»a
+
+Ảnh vi mô chụp bởi robot Opportunity vá» dạng kết hạch màu xám của khoáng vật hematit, ám chỉ sá»± tồn tại trong quá khứ của nÆ°á»›c lá»ng
+Khe xói mới hình thành trong hố ở vùng Centauri Montes
+
+NÆ°á»›c lá»ng không thể tồn tại trên bá» mặt Sao Há»a do áp suất khí quyển của nó hiện nay rất thấp, ngoại trừ những nÆ¡i có cao Ä‘á»™ thấp nhất trong má»™t chu kỳ ngắn.[46][47] Hai mÅ© băng ở các cá»±c dÆ°á»ng nhÆ° chứa má»™t lượng lá»›n nÆ°á»›c.[48][49] Thể tích của nÆ°á»›c băng ở mÅ© băng cá»±c nam nếu bị tan chảy có thể đủ bao phủ toàn bá»™ bá» mặt hành tinh Ä‘á» vá»›i Ä‘á»™ dày 11 mét.[50] Lá»›p manti của tầng băng vÄ©nh cá»­u mở rá»™ng từ vùng cá»±c đến vÄ© Ä‘á»™ khoảng 60°.[48]
+
+Má»™t lượng lá»›n nÆ°á»›c băng được cho là nằm ẩn dÆ°á»›i băng quyển dày của Sao Há»a. Dữ liệu radar từ tàu Mars Express và Mars Reconnaissance Orbiter đã chỉ ra trữ lượng lá»›n nÆ°á»›c băng ở hai cá»±c (tháng 7 năm 2005)[18][51] và ở những vùng vÄ© Ä‘á»™ trung bình (tháng 11 năm 2008).[19] Tàu đổ bá»™ Phoenix đã trá»±c tiếp lấy được mẫu nÆ°á»›c băng trong lá»›p đất nông vào ngày 31 tháng 7 năm 2008.[21]
+
+Äịa mạo trên Sao Há»a gợi ra má»™t cách mạnh mẽ rằng nÆ°á»›c lá»ng đã từng có thá»i Ä‘iểm tồn tại trên bá» mặt hành tinh này. Cụ thể, những mạng lÆ°á»›i thÆ°a khổng lồ phân tán trên bá» mặt, gá»i là thung lÅ©ng chảy thoát (outflow channels), xuất hiện ở 25 vị trí trên bá» mặt hành tinh. Äây được cho là dấu tích của sá»± xâm thá»±c diá»…n ra trong thá»i kỳ đại hồng thủy giải phóng nÆ°á»›c lÅ© từ tầng chứa nÆ°á»›c dÆ°á»›i bá» mặt, mặc dù má»™t vài đặc Ä‘iểm cấu trúc này được giả thuyết là do kết quả của tác Ä‘á»™ng từ băng hà hoặc dung nham núi lá»­a.[52][53] Những con kênh trẻ nhất có thể hình thành trong thá»i gian gần đây vá»›i chỉ vài triệu năm tuổi.[54] Ở những nÆ¡i khác, đặc biệt là những vùng cổ nhất trên bá» mặt Há»a Tinh, ở tá»· lệ nhá» hÆ¡n, những mạng lÆ°á»›i thung lÅ©ng (networks of valleys) hình cây trải rá»™ng trên má»™t tá»· lệ diện tích lá»›n của cảnh quan địa hình. Những thung lÅ©ng đặc trÆ°ng này và sá»± phân bố của chúng hàm ý mạnh mẽ rằng chúng hình thành từ các dòng chảy mặt, kết quả của những trận mÆ°a hay tuyết rÆ¡i trong giai Ä‘oạn sá»›m của lịch sá»­ Sao Há»a. Sá»± vận Ä‘á»™ng của dòng nÆ°á»›c ngầm và sá»± thoát của nó (groundwater sapping) có thể đóng má»™t vai trò phụ quan trá»ng trong má»™t số mạng lÆ°á»›i, nhÆ°ng có lẽ lượng mÆ°a là nguyên nhân gây ra những khe rãnh trong má»i trÆ°á»ng hợp.[55]
+
+CÅ©ng có hàng nghìn đặc Ä‘iểm dá»c các hố va chạm và hẻm vá»±c giống vá»›i các khe xói (gully) trên Trái Äất. Những khe xói này có xu hÆ°á»›ng xuất hiện trên các cao nguyên ở bán cầu nam và gần xích đạo. Má»™t số nhà khoa há»c Ä‘á» xuất là quá trình hình thành của chúng đòi há»i có sá»± tham gia của nÆ°á»›c lá»ng, có lẽ từ sá»± tan chảy của băng,[56][57] mặc dù những ngÆ°á»i khác lại cho rằng cÆ¡ chế hình thành có sá»± tham gia của lá»›p băng cacbon Ä‘iôxít vÄ©nh cá»­u hoặc do sá»± chuyển Ä‘á»™ng của bụi khô.[58][59] Không má»™t phần biến dạng nào của các khe xói được hình thành bởi quá trình phong hóa và không có má»™t hố va chạm nào xuất hiện trên những khe xói, Ä‘iá»u này chứng tá» những đặc Ä‘iểm này còn rất trẻ, thậm chí các khe xói được hình thành chỉ trong những ngày gần đây.[57]
+
+Những đặc trÆ°ng địa chất khác, nhÆ° châu thổ và quạt bồi tích (alluvial fans) tồn tại trong các miệng hố lá»›n, cÅ©ng là bằng chứng mạnh vá» những Ä‘iá»u kiện nóng hÆ¡n, ẩm Æ°á»›t hÆ¡n trên bá» mặt trong má»™t số thá»i Ä‘iểm ở giai Ä‘oạn lịch sá»­ ban đầu của Sao Há»a.[60] Những Ä‘iá»u kiện này cÅ©ng yêu cầu cần sá»± xuất hiện của những hồ nÆ°á»›c lá»›n phân bố trên diện rá»™ng của bá» mặt hành tinh, mà ở những hồ này cÅ©ng có những bằng chứng Ä‘á»™c lập vá» khoáng vật há»c, trầm tích há»c và địa mạo há»c.[61] Má»™t số nhà khoa há»c thậm chí còn lập luận rằng ở má»™t số thá»i Ä‘iểm trong quá khứ, nhiá»u đồng bằng thấp ở bán cầu bắc của hành tinh đã thá»±c sá»± bị bao phủ bởi những đại dÆ°Æ¡ng sâu hàng trăm mét, mặc dù vậy vấn Ä‘á» này vẫn còn nhiá»u tranh luận.[62]
+
+CÅ©ng có thêm các dữ kiện khác vá» nÆ°á»›c lá»ng đã từng tồn tại trên bá» mặt Há»a Tinh nhá» việc xác định được những chất khoáng đặc biệt nhÆ° hematit và goethit, cả hai đôi khi được hình thành trong sá»± có mặt của nÆ°á»›c lá»ng.[63] Má»™t số chứng cứ trÆ°á»›c đây được cho là ám chỉ sá»± tồn tại của các đại dÆ°Æ¡ng và các con sông cổ đã bị phủ nhận bởi việc nghiên cứu từ các bức ảnh Ä‘á»™ phân giải cao từ tàu Mars Reconnaissance Orbiter.[64] Năm 2004, robot Opportunity phát hiện khoáng chất jarosit. Khoáng chất này chỉ hình thành trong môi trÆ°á»ng nÆ°á»›c a xít, đây cÅ©ng là biểu hiện của việc nÆ°á»›c lá»ng đã từng tồn tại trên Sao Há»a.[65]
+Chá»m băng ở các cá»±c[sá»­a | sá»­a mã nguồn]
+Tàu Viking chụp chá»m băng cá»±c bắc Sao Há»a
+Chá»m băng cá»±c nam chụp bởi Mars Global Surveyor năm 2000
+
+Sao Há»a có hai chá»m băng vÄ©nh cá»­u ở các cá»±c. Khi mùa đông tràn đến má»™t cá»±c, chá»m băng liên tục nằm trong bóng tối, bá» mặt bị đông lạnh và gây ra sá»± tích tụ của 25–30% bầu khí quyển thành những phiến băng khô CO2.[66] Khi vùng cá»±c chuyển sang mùa hè, các chá»m băng bị ánh sáng Mặt Trá»i chiếu liên tục, băng khô CO2 thăng hoa, dẫn đến những cÆ¡n gió khổng lồ quét qua vùng cá»±c vá»›i tốc Ä‘á»™ 400 km/h. Những hoạt Ä‘á»™ng theo mùa này đã vận chuyển lượng lá»›n bụi và hÆ¡i nÆ°á»›c, tạo ra những đám mây ti lá»›n, băng giá giống nhÆ° trên Trái Äất. Những đám mây băng giá này đã được robot Opportunity chụp vào năm 2004.[67]
+
+Hai chá»m băng ở cá»±c chứa chủ yếu nÆ°á»›c đóng băng. Cacbon Ä‘iôxít đóng băng thành má»™t lá»›p tÆ°Æ¡ng đối má»ng dày khoảng 1 mét trên bá» mặt chá»m băng cá»±c bắc chỉ trong thá»i gian mùa đông, trong khi chá»m băng cá»±c nam có lá»›p băng khô cacbon Ä‘iôxít vÄ©nh cá»­u dày tá»›i 8 mét.[68] Chá»m băng cá»±c bắc có Ä‘Æ°á»ng kính khoảng 1.000 kilômét trong thá»i gian mùa hè ở bán cầu bắc Sao Há»a,[69] và chứa khoảng 1,6 triệu km khối băng, và nếu trải Ä‘á»u ra thì chá»m băng này dày khoảng 2 km.[70] (Lá»›p phủ băng ở Greenland có thể tích khoảng 2,85 triệu km3.) Chá»m băng cá»±c nam có Ä‘Æ°á»ng kính khoảng 350 km và dày tá»›i 3 km.[71] Tổng thể tích của chá»m băng cá»±c nam cá»™ng vá»›i lượng băng tàng trữ ở những lá»›p kế tiếp Æ°á»›c lượng vào khoảng 1,6 triệu km3.[72] Cả hai cá»±c có những rãnh băng hà hình xoắn ốc, mà các nhà khoa há»c cho là được hình thành từ sá»± nhận được lượng nhiệt Mặt Trá»i khác nhau theo từng nÆ¡i, kết hợp vá»›i sá»± thăng hoa của băng và tích tụ của hÆ¡i nÆ°á»›c.[73][74]
+
+Sá»± đóng băng theo mùa ở má»™t số vùng gần chá»m băng cá»±c nam làm hình thành má»™t lá»›p băng khô (hoặc tấm) trong suốt dày 1 mét trên bá» mặt. Khi mùa xuân đến, những vùng này ấm dần lên, áp suất được tạo ra ở dÆ°á»›i lá»›p băng khô do sá»± thăng hoa của CO2, đẩy lá»›p này căng lên và cuối cùng phá bung nó ra. Äiá»u này dẫn đến sá»± hình thành những mạch phun trên Sao Há»a ở cá»±c nam (Martian geyser) chứa há»—n hợp khí CO2 vá»›i bụi hoặc cát bazan Ä‘en. Quá trình này diá»…n ra nhanh chóng, được quan sát từ các tàu quỹ đạo trong không gian vá»›i tốc Ä‘á»™ thay đổi chỉ diá»…n ra trong vài ngày, tuần hoặc tháng, má»™t tốc Ä‘á»™ rất nhanh so vá»›i các hiện tượng địa chất khác—đặc biệt đối vá»›i Sao Há»a. Ãnh sáng Mặt Trá»i xuyên qua lá»›p băng khô trong suốt, làm ấm lá»›p vật liệu tối ở bên dÆ°á»›i, tạo ra áp suất đẩy khí lên tá»›i 161 km/h qua những vị trí băng má»ng. Bên dÆ°á»›i những phiến băng, khí cÅ©ng làm xói mòn ná»n đất, giật những hạt cát lá»ng lẻo và tạo ra những hình thù giống mạng nhện bên dÆ°á»›i lá»›p băng.[75][76][77][78]
+Äịa lý[sá»­a | sá»­a mã nguồn]
+
+ Bài chi tiết: Äịa lý trên Sao Há»a
+
+Cao nguyên núi lá»­a (Ä‘á») và lòng chảo va chạm (xanh) chiếm phần lá»›n trong bản đồ địa hình của Sao Há»a
+
+Mặc dù được công nhận nhiá»u là những ngÆ°á»i đã vẽ bản đồ Mặt Trăng, Johann Heinrich Mädler và Wilhelm Beer cÅ©ng là hai ngÆ°á»i đầu tiên vẽ bản đồ Sao Há»a. HỠđã nhận ra rằng hầu hết đặc Ä‘iểm trên bá» mặt Sao Há»a là vÄ©nh cá»­u và nhỠđó đã xác định được má»™t cách chính xác chu kỳ tá»± quay của hành tinh này. Năm 1840, Mädler kết hợp 10 năm quan sát để vẽ ra tấm bản đồ địa hình đầu tiên trên Há»a Tinh. Tuy không đặt tên cho những vị trí đặc trÆ°ng, Beer và Mädler Ä‘Æ¡n giản chỉ gán chữ cho chúng; ví dụ Vịnh Meridiani (Sinus Meridiani) được đặt tên vá»›i chữ "a."[79]
+
+Ngày nay, các đặc Ä‘iểm trên Sao Há»a được đặt tên theo nhiá»u nguồn khác nhau. Những đặc Ä‘iểm theo suất phản chiếu quang há»c được đặt tên trong thần thoại. Các hố lá»›n hÆ¡n 60 km được đặt tên để tưởng nhá»› những nhà khoa há»c và văn chÆ°Æ¡ng và những ngÆ°á»i đã đóng góp cho việc nghiên cứu Há»a Tinh. Những hố nhá» hÆ¡n 60 km được đặt tên theo các thị trấn và ngôi làng trên Trái Äất vá»›i dân số nhá» hÆ¡n 100.000 ngÆ°á»i. Những thung lÅ©ng lá»›n được đặt tên theo từ "Sao Há»a" và các ngôi sao trong nghÄ©a của các ngôn ngữ khác nhau, những thung lÅ©ng nhỠđược đặt tên theo các con sông.[80]
+
+Những đặc Ä‘iểm có suất phản chiếu hình há»c lá»›n (albedo) mang nhiá»u tên gá»i cÅ©, nhÆ°ng thÆ°á»ng được thay đổi để phản ánh những hiểu biết má»›i vá» bản chất của đặc Ä‘iểm. Ví dụ, tên gá»i Nix Olympica (tuyết ở ngá»n Olympus) được đổi thành Olympus Mons (núi Olympus).[81] Bá» mặt Sao Há»a khi quan sát từ Trái Äất được chia ra thành loại vùng, vá»›i suất phản chiếu hình há»c khác nhau. Những đồng bằng nhạt màu bao phủ bởi bụi và cát trong màu Ä‘á» của sắt ôxít từng được cho là các 'lục địa' và đặt tên nhÆ° Arabia Terra (vùng đất Ả Rập) hay Amazonis Planitia (đồng bằng Amazon). Những vùng tối màu được coi là các biển, nhÆ° Mare Erythraeum (biển Erythraeum), Mare Sirenum và Aurorae Sinus. Vùng tối lá»›n nhất khi nhìn từ Trái Äất là Syrtis Major Planum.[82] Chá»m băng vÄ©nh cá»­u cá»±c bắc được đặt tên là Planum Boreum, và Planum Australe.
+
+Xích đạo của Sao Há»a được xác định bởi sá»± tá»± quay của nó, nhÆ°ng vị trí của kinh tuyến gốc được quy Æ°á»›c cụ thể, nhÆ° kinh tuyến Greenwich của Trái Äất. Bằng cách lá»±a chá»n má»™t Ä‘iểm bất kỳ, năm 1830 Mädler và Beer đã chá»n lấy má»™t Ä‘Æ°á»ng trong bản đồ đầu tiên của há» vá» hành tinh Ä‘á». Sau khi tàu Mariner 9 cung cấp thêm những bức ảnh vá» bá» mặt Sao Há»a năm 1972, má»™t miệng hố nhá» (sau này gá»i là Airy-0), nằm trong Sinus Meridiani ("vịnh Kinh Tuyến"), được chá»n làm định nghÄ©a cho kinh Ä‘á»™ 0,0° để phù hợp vá»›i lá»±a chá»n ban đầu của hai ông.[83]
+
+Do Sao Há»a không có đại dÆ°Æ¡ng và vì vậy không có 'má»±c nÆ°á»›c biển', nên các nhà khoa há»c phải lá»±a chá»n má»™t bá» mặt có cao Ä‘á»™ bằng 0, tÆ°Æ¡ng tá»± nhÆ° má»±c nÆ°á»›c biển, làm bá» mặt tham chiếu; mặt này được gá»i là areoid [84] của Sao Há»a, tÆ°Æ¡ng tá»± nhÆ° geoid của Trái Äất. Cao Ä‘á»™ 0 được xác định tại Ä‘á»™ cao mà ở đó áp suất khí quyển Há»a Tinh bằng 610,5 Pa (6,105 mbar).[85] Ãp suất này tÆ°Æ¡ng ứng vá»›i Ä‘iểm ba trạng thái của nÆ°á»›c, và bằng khoảng 0,6% áp suất tại má»±c nÆ°á»›c biển trên Trái Äất (0,006 atm).[86] Ngày nay, mặt geoid hay areoid được xác định má»™t cách chính xác nhá» những vệ tinh khảo sát trÆ°á»ng hấp dẫn của Trái Äất và Sao Há»a.
+Ảnh màu gần đúng vá» miệng hố Victoria, chụp bởi robot tá»± hành Opportunity. Nó được chụp trong thá»i gian ba tuần từ 16 tháng 10 – 6 tháng 11, 2006.
+Äịa hình va chạm[sá»­a | sá»­a mã nguồn]
+
+Äịa hình Sao Há»a có hai Ä‘iểm khác biệt rõ rệt: những vùng đồng bằng bắc bán cầu bằng phẳng do tác Ä‘á»™ng của dòng chảy dung nham ngược hẳn vá»›i vùng cao nguyên, những hố va chạm cổ ở bán cầu nam. Má»™t nghiên cứu năm 2008 cho thấy chứng cứ ủng há»™ lý thuyết Ä‘á» xuất năm 1980 rằng, khoảng bốn tá»· năm trÆ°á»›c, bán cầu bắc của Sao Há»a đã bị má»™t thiên thể kích cỡ má»™t phần mÆ°á»i đến má»™t phần ba Mặt Trăng đâm vào. Nếu Ä‘iá»u này đúng, bán cầu bắc Sao Há»a sẽ có má»™t hố va chạm vá»›i chiá»u dài tá»›i 10.600 km và rá»™ng tá»›i 8.500 km, hay gần bằng diện tích của châu Âu, châu à và lục địa Australia cá»™ng lại, và hố va chạm này sẽ vượt qua lòng chảo cá»±c nam Aitken, được coi là lòng chảo va chạm lá»›n nhất trong hệ Mặt Trá»i hiện nay.[15][16]
+
+Bá» mặt Sao Há»a có rất nhiá»u hố va chạm: có khoảng 43.000 hố vá»›i Ä‘Æ°á»ng kính lá»›n hÆ¡n hoặc bằng 5 km đã được phát hiện.[87] Hố lá»›n nhất được công nhận là lòng chảo va chạm Hellas, vá»›i đặc trÆ°ng suất phản chiếu hình há»c có thể nhìn thấy rõ từ Trái Äất.[88] Do Sao Há»a có kích thÆ°á»›c và khối lượng nhá» hÆ¡n, nên xác suất để má»™t vật thể va chạm vào Há»a Tinh bằng khoảng má»™t ná»­a so vá»›i Trái Äất. Sao Há»a nằm gần vành Ä‘ai tiểu hành tinh hÆ¡n, nên khả năng nó bị những vật thể từ nÆ¡i này va chạm vào là cao hÆ¡n. Hành tinh Ä‘á» cÅ©ng bị các sao chổi chu kỳ ngắn va vào vá»›i khả năng lá»›n, do những sao chổi này nằm gần bên trong quỹ đạo của Sao Má»™c.[89] Mặc dù vậy, hố va chạm trên Sao Há»a vẫn ít hÆ¡n nhiá»u so vá»›i trên Mặt Trăng, do bầu khí quyển má»ng của nó cÅ©ng có tác dụng bảo vệ những thiên thạch nhá» chạm tá»›i bá» mặt. Má»™t số hố va chạm có hình thái gợi ra rằng chúng bị ẩm Æ°á»›t sau má»™t thá»i gian thiên thạch va chạm xuống bá» mặt.[90]
+Những vùng kiến tạo[sửa | sửa mã nguồn]
+Ảnh chụp núi lá»­a Olympus Mons, núi cao nhất trong hệ Mặt Trá»i
+
+Núi lá»­a hình khiên Olympus Mons có chiá»u cao tá»›i 27 km và là ngá»n núi cao nhất trong hệ Mặt Trá»i.[91] Nó là ngá»n núi lá»­a đã tắt nằm trong vùng cao nguyên rá»™ng lá»›n Tharsis, vùng này cÅ©ng chứa má»™t vài ngá»n núi lá»­a lá»›n khác. Olympus Mons cao gấp ba lần núi Everest, vá»›i chiá»u cao trên 8,8 km. CÅ©ng chú ý rằng, ngoài những hoạt Ä‘á»™ng kiến tạo, kích thÆ°á»›c của hành tinh cÅ©ng giá»›i hạn cho chiá»u cao của những ngá»n núi trên bá» mặt của nó.[92]
+
+Hẻm vá»±c lá»›n Valles Marineris (tiếng Latin của thung lÅ©ng Mariner, hay còn gá»i là Agathadaemon trong những tấm bản đồ kênh đào Sao Há»a cÅ©), có chiá»u dài tá»›i 4.000 km và Ä‘á»™ sâu khoảng 7 km. Chiá»u dài của Valles Marineris tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i chiá»u dài của châu Âu và chiếm tá»›i má»™t phần năm chu vi của Sao Há»a. Hẻm núi Grand Canyon trên Trái Äất có chiá»u dài 446 km và sâu gần 2 km. Valles Marineris được hình thành là do sá»± trồi lên của vùng cao nguyên Tharsis làm cho lá»›p vá» hành tinh ở vùng Valles Marineris bị tách giãn và sụt xuống. Má»™t hẻm vá»±c lá»›n khác là Ma'adim Vallis (Ma'adim trong tiếng Hebrew là Sao Há»a). Nó dài 700 km và bá» rá»™ng cÅ©ng lá»›n hÆ¡n Grand Canyon vá»›i chiá»u rá»™ng 20 km và Ä‘á»™ sâu 2 km ở má»™t số vị trí. Trong quá khứ Ma'adim Vallis có thể đã bị ngập bởi nÆ°á»›c lÅ©.[93]
+Hang động[sửa | sửa mã nguồn]
+Ảnh chụp từ thiết bị THEMIS vá» má»™t số hang trên bá» mặt Há»a Tinh. Những hang này được đặt tên không chính thức là (A) Dena, (B) Chloe, (C) Wendy, (D) Annie, (E) Abby (trái) và Nikki, và (F) Jeanne.
+
+Ảnh chụp từ thiết bị THEMIS trên tàu Mars Odyssey của NASA cho thấy khả năng có tá»›i 7 cá»­a hang Ä‘á»™ng trên sÆ°á»n núi lá»­a Arsia Mons.[94] Những hang này được đặt tên tạm thá»i theo những ngÆ°á»i phát hiện ra nó đôi khi còn được gá»i là "bảy chị em."[95] Cá»­a vào hang có bá» rá»™ng từ 100 m tá»›i 252 m và chiá»u sâu ít nhất từ 73 m tá»›i 96 m. Bởi vì ánh sáng không thể chiếu tá»›i đáy của hầu hết các hang, do vậy các nhà thiên văn cho rằng thá»±c tế chúng có chiá»u sâu lá»›n hÆ¡n và rá»™ng hÆ¡n ở trong hang so vá»›i giá trị Æ°á»›c lượng. Hang "Dena" là má»™t ngoại lệ; có thể quan sát thấy đáy của nó và vì vậy chiá»u sâu của nó bằng 130 m. Bên trong những hang này có thể giúp tránh khá»i tác Ä‘á»™ng từ những thiên thạch nhá», bức xạ cá»±c tím, gió Mặt Trá»i và những tia vÅ© trụ năng lượng cao bắn phá xuống hành tinh Ä‘á».[96]
+Khí quyển[sửa | sửa mã nguồn]
+
+ Bài chi tiết: Khí quyển Sao Há»a
+
+Bầu khí quyển má»ng manh của Há»a Tinh, nhìn từ chân trá»i trong bức ảnh chụp từ quỹ đạo thấp.
+
+Sao Há»a đã mất từ quyển của nó từ 4 tá»· năm trÆ°á»›c,[97] do vậy gió Mặt Trá»i tÆ°Æ¡ng tác trá»±c tiếp đến tầng Ä‘iện li của hành tinh, làm giảm mật Ä‘á»™ khí quyển do dần dần tÆ°á»›c Ä‘i các nguyên tá»­ ở lá»›p ngoài cùng của bầu khí quyển. Cả hai tàu Mars Global Surveyor và Mars Express đã thu nhận được những hạt bị ion hóa từ khí quyển khi chúng Ä‘ang thoát vào không gian.[97][98] So vá»›i Trái Äất, khí quyển của Sao Há»a khá loãng. Ãp suất khí quyển tại bá» mặt thay đổi từ 30 Pa (0,030 kPa) ở ngá»n Olympus Mons tá»›i 1.155 Pa (1,155 kPa) ở lòng chảo Hellas Planitia, và áp suất trung bình bằng 600 Pa (0,600 kPa).[99] Ãp suất lá»›n nhất trên Há»a Tinh bằng vá»›i áp suất ở những Ä‘iểm có Ä‘á»™ cao 35 km[100] trên bá» mặt Trái Äất. Con số này nhá» hÆ¡n 1% áp suất trung bình tại bá» mặt Trái Äất (101,3 kPa). Tỉ lệ Ä‘á»™ cao (scale height) của khí quyển Sao Há»a bằng 10,8 km,[101] lá»›n hÆ¡n của Trái Äất (bằng 6 km) bởi vì gia tốc hấp dẫn bá» mặt Sao Há»a chỉ bằng 38% của Trái Äất, và nhiệt Ä‘á»™ trung bình trong khí quyển Sao Há»a thấp hÆ¡n đồng thá»i khối lượng trung bình của các phân tá»­ cao hÆ¡n 50% so vá»›i trên Trái Äất.
+
+Bầu khí quyển Sao Há»a chứa 95% cacbon Ä‘iôxít, 3% nitÆ¡, 1,6% argon và chứa dấu vết của ôxy và hÆ¡i nÆ°á»›c.[6] Khí quyển khá là bụi bặm, chứa các hạt bụi Ä‘Æ°á»ng kính khoảng 1,5 µm khiến cho bầu trá»i Sao Há»a có màu vàng nâu khi đứng nhìn từ bá» mặt của nó.[102]
+Bản đồ mêtan
+
+Mêtan đã được phát hiện trong khí quyển hành tinh Ä‘á» vá»›i tá»· lệ mol vào khoảng 30 ppb;[12][103] nó xuất hiện theo những luồng mở rá»™ng và ở những vị trí rá»i rạc khác nhau. Vào giữa mùa hè ở bán cầu bắc, luồng chính chứa tá»›i 19.000 tấn mêtan, và các nhà thiên văn Æ°á»›c lượng cÆ°á»ng Ä‘á»™ ở nguồn vào khoảng 0,6 kilôgam trên giây.[104][105] Nghiên cứu cÅ©ng cho thấy có hai nguồn chính phát ra mêtan, nguồn thứ nhất gần tá»a Ä‘á»™ 30° B, 260° T và nguồn hai gần tá»a Ä‘á»™ 0° B, 310° T.[104] Các nhà khoa há»c cÅ©ng Æ°á»›c lượng được Sao Há»a sản sinh ra khoảng 270 tấn mêtan trong má»™t năm.[104][106]
+
+Nghiên cứu cÅ©ng chỉ ra khoảng thá»i gian để lượng mêtan phân hủy có thể dài bằng 4 năm hoặc ngắn bằng 0,6 năm Trái Äất.[104][107] Sá»± phân hủy nhanh chóng và lượng mêtan được bổ sung ám chỉ có những nguồn còn hoạt Ä‘á»™ng Ä‘ang giải phóng lượng khí này. Những hoạt Ä‘á»™ng núi lá»­a, sao chổi rÆ¡i xuống, và khả năng có mặt của các dạng sống vi sinh vật sản sinh ra mêtan. Mêtan cÅ©ng có thể sinh ra từ quá trình vô cÆ¡ nhÆ° sá»± serpentin hóa (serpentinization)[b] vá»›i sá»± tham gia của nÆ°á»›c, cacbon Ä‘iôxít và khoáng vật olivin, nó tồn tại khá phổ biến trên Sao Há»a.[108]
+Khí hậu[sửa | sửa mã nguồn]
+
+ Bài chi tiết: Khí hậu Sao Há»a
+
+Trong số các hành tinh trong hệ Mặt Trá»i, các mùa trên Sao Há»a là gần giống vá»›i trên Trái Äất nhất, do sá»± gần bằng vá» Ä‘á»™ nghiêng của trục tá»± quay ở hai hành tinh. Äá»™ dài các mùa trên Há»a Tinh bằng khoảng hai lần trên Trái Äất, do khoảng cách từ Sao Há»a đến Mặt Trá»i lá»›n hÆ¡n dẫn đến má»™t năm trên hành tinh này bằng khoảng hai năm Trái Äất. Nhiệt Ä‘á»™ Sao Há»a thay đổi từ nhiệt Ä‘á»™ rất thấp -87 °C trong thá»i gian mùa đông ở các cá»±c cho đến -5 °C vào mùa hè.[46] Biên Ä‘á»™ nhiệt Ä‘á»™ lá»›n nhÆ° vậy là vì bầu khí quyển quá má»ng không thể giữ lại được nhiệt lượng từ Mặt Trá»i, do áp suất khí quyển thấp, và do tỉ số thể tích nhiệt rung riêng (thermal inertia) của đất Sao Há»a thấp.[109] Hành tinh cÅ©ng nằm xa Mặt Trá»i gấp 1,52 lần so vá»›i Trái Äất, do vậy nó chỉ nhận được khoảng 43% lượng ánh sáng so vá»›i Trái Äất.[110]
+
+Nếu Sao Há»a nằm vào quỹ đạo của Trái Äất, các mùa trên hành tinh này sẽ giống vá»›i trên địa cầu do Ä‘á»™ lá»›n góc nghiêng trục quay hai hành tinh giống nhau. Äá»™ lệch tâm quỹ đạo tÆ°Æ¡ng đối lá»›n của nó cÅ©ng có má»™t tác Ä‘á»™ng quan trá»ng. Khi Há»a Tinh gần cận Ä‘iểm quỹ đạo thì ở bán cầu bắc là mùa đông và bán cầu nam là mùa hè, khi nó gần viá»…n Ä‘iểm quỹ đạo thì ngược lại. Các mùa ở bán cầu nam diá»…n ra khắc nghiệt hÆ¡n so vá»›i bán cầu bắc. Nhiệt Ä‘á»™ trong mùa hè ở bán cầu nam có thể cao hÆ¡n tá»›i 30 °C (86 °F) so vá»›i mùa hè ở bán cầu bắc.[111]
+
+Sao Há»a cÅ©ng có những trận bão bụi lá»›n nhất trong hệ Mặt Trá»i. Chúng có thể biến đổi từ má»™t cÆ¡n bão trong má»™t vùng nhá» cho đến hình thành cÆ¡n bão khổng lồ bao phủ toàn bá»™ hành tinh. Những trận bão bụi thÆ°á»ng xuất hiện khi Sao Há»a nằm gần Mặt Trá»i và khi đó nhiệt Ä‘á»™ toàn cầu cÅ©ng tăng lên do tác Ä‘á»™ng của bão bụi.[112]
+Ảnh chụp qua kính thiên văn Hubble so sánh Sao Há»a trÆ°á»›c và sau trận bão bụi bao phủ toàn cầu.
+Quỹ đạo và chu kỳ quay[sửa | sửa mã nguồn]
+
+Khoảng cách trung bình từ Sao Há»a đến Mặt Trá»i vào khoảng 230 triệu km (1,5 AU) và chu kỳ quỹ đạo của nó bằng 687 ngày Trái Äất. Ngày mặt trá»i (viết tắt sol) trên Sao Há»a hÆ¡i dài hÆ¡n ngày Trái Äất và bằng: 24 giá», 39 phút, và 35,244 giây. Má»™t năm Sao Há»a bằng 1,8809 năm Trái Äất; hay 1 năm, 320 ngày, và 18,2 giá».[6]
+
+Äá»™ nghiêng trục quay bằng 25,19 Ä‘á»™ và gần bằng vá»›i Ä‘á»™ nghiêng trục quay của Trái Äất.[6] Kết quả là Sao Há»a có các mùa gần giống vá»›i Trái Äất mặc dù chúng có thá»i gian kéo dài gần gấp đôi trong má»™t năm dài hÆ¡n. Hiện tại hÆ°á»›ng của cá»±c bắc Há»a Tinh nằm gần vá»›i ngôi sao Deneb.[13] Sao Há»a đã vượt qua cận Ä‘iểm quỹ đạo vào tháng 3, 2011 và vượt qua viá»…n Ä‘iểm quỹ đạo vào tháng 2, 2012.[113]
+
+Sao Há»a có Ä‘á»™ lệch tâm quỹ đạo tÆ°Æ¡ng đối lá»›n vào khoảng 0,09; trong bảy hành tinh còn lại của hệ Mặt Trá»i, chỉ có Sao Thủy có Ä‘á»™ lệch tâm lá»›n hÆ¡n. Các nhà khoa há»c biết rằng trong quá khứ Sao Há»a có quỹ đạo tròn hÆ¡n so vá»›i bây giá». Cách đây khoảng 1,35 triệu năm Trái Äất, Sao Há»a có Ä‘á»™ lệch tâm gần bằng 0,002, nhá» hÆ¡n nhiá»u so vá»›i Trái Äất ngày nay.[114] Chu kỳ Ä‘á»™ lệch tâm của Sao Há»a bằng 96.000 năm Trái Äất so vá»›i chu kỳ lệch tâm của Trái Äất bằng 100.000 năm.[115] Sao Há»a cÅ©ng đã từng có chu kỳ lệch tâm bằng 2,2 triệu năm Trái Äất. Trong vòng 35.000 năm trÆ°á»›c đây, quỹ đạo Sao Há»a trở lên elip hÆ¡n do ảnh hưởng hấp dẫn từ những hành tinh khác. Khoảng cách gần nhất giữa Trái Äất và Sao Há»a sẽ giảm nhẹ dần trong vòng 25.000 năm tá»›i.[116]
+ThePlanets Orbits Ceres Mars PolarView.svg Ảnh bên trái so sánh quỹ đạo của Sao Há»a và hành tinh lùn Ceres nằm trong vành Ä‘ai tiểu hành tinh, khi nhìn từ cá»±c bắc của hoàng đạo, trong khi bức ảnh bên phải nhìn từ Ä‘iểm nút lên của quỹ đạo. Các Ä‘oạn của quỹ đạo nằm ở phía nam hoàng đạo được vẽ bằng màu tối. Cận Ä‘iểm quỹ đạo (q) và viá»…n Ä‘iểm quỹ đạo (Q) được đánh dấu vá»›i ngày gần nhất thiên thể sẽ vượt qua. Quỹ đạo Sao Há»a có màu Ä‘á», Ceres có màu vàng. ThePlanets Orbits Ceres Mars.svg
+Vệ tinh tự nhiên[sửa | sửa mã nguồn]
+
+ Bài chi tiết: Vệ tinh tá»± nhiên của Sao Há»a, Phobos (vệ tinh), và Deimos (vệ tinh)
+
+Ảnh màu chụp bởi Mars Reconnaissance Orbiter – HiRISE, ngày 23 tháng 3, 2008
+Ảnh màu Deimos chụp ngày 21 tháng 2, 2009 cũng bởi tàu này (không theo tỷ lệ)
+
+Sao Há»a có hai vệ tinh tá»± nhiên tÆ°Æ¡ng đối nhá», Phobos và Deimos, chúng quay quanh trên những quỹ đạo khá gần hành tinh. Lý thuyết vá» tiểu hành tinh bị hành tinh Ä‘á» bắt giữ đã thu hút sá»± quan tâm từ lâu nhÆ°ng nguồn gốc của nó vẫn còn nhiá»u bí ẩn.[117] Nhà thiên văn há»c Asaph Hall đã phát hiện ra 2 vệ tinh này vào năm 1877, và ông đặt tên chúng theo tên các nhân vật trong thần thoại Hy Lạp là Phobos (Ä‘au Ä‘á»›n/sợ hãi) và Deimos (kinh hoàng/khiếp sợ), hai ngÆ°á»i con cùng tham gia những trận đánh của vị thần chiến tranh Ares. Ares trong thần thoại La Mã tên là Mars (mà ngÆ°á»i La Mã dùng tên của vị thần đó đặt tên cho Sao Há»a).[118][119]
+
+Nhìn từ bá» mặt Há»a Tinh, chuyển Ä‘á»™ng của Phobos và Deimos hiện lên rất khác lạ so vá»›i chuyển Ä‘á»™ng của Mặt Trăng. Phobos má»c lên ở phía tây, lặn ở phía đông, và lại má»c lên chỉ sau 11 giá». Deimos nằm ngay bên ngoài quỹ đạo đồng bộ—tại đó chu kỳ quỹ đạo bằng vá»›i chu kỳ tá»± quay của hành tinh—nó má»c lên ở phía đông nhÆ°ng rất chậm. Mặc dù chu kỳ quỹ đạo của nó bằng 30 giá», nó phải mất 2,7 ngày để lặn ở phía tây khi nó chậm dần Ä‘i vá» phía sau sá»± quay của Sao Há»a, và sau đó phải khá lâu nó má»›i má»c trở lại.[120]
+
+Bởi vì quỹ đạo của Phobos nằm bên trong quỹ đạo đồng bá»™, lá»±c thủy triá»u từ Há»a Tinh Ä‘ang dần dần hút vệ tinh này vá» phía nó. Trong khoảng 50 triệu năm nữa vệ tinh này sẽ đâm xuống bá» mặt Sao Há»a hoặc bị phá tan thành má»™t cái vành bụi quay quanh hành tinh.[120]
+
+Nguồn gốc của hai vệ tinh này vẫn chÆ°a được hiểu đầy đủ. Äặc tính suất phản chiếu hình há»c thấp và thành phần cấu tạo bằng "thiên thạch hạt chứa than" (carbonaceous chondrite) giống vá»›i tính chất của các tiểu hành tinh là má»™t trong những bằng chứng ủng há»™ lý thuyết tiểu hành tinh bị bắt. Quỹ đạo không ổn định của Phobos dÆ°á»ng nhÆ° là má»™t chứng cứ khác cho thấy nó bị bắt trong thá»i gian khá gần ngày nay. Tuy vậy, cả hai vệ tinh có quỹ đạo tròn, mặt phẳng quỹ đạo rất gần vá»›i mặt phẳng xích đạo hành tinh, lại là má»™t Ä‘iá»u không thông thÆ°á»ng cho các vật thể bị bắt và nhÆ° thế đòi há»i quá trình Ä‘á»™ng lá»±c bắt giữ 2 vệ tinh này rất phức tạp. Sá»± bồi tụ trong buổi đầu lịch sá»­ hình thành Sao Há»a cÅ©ng là má»™t khả năng khác nhÆ°ng lý thuyết này lại không giải thích được thành phần cấu tạo của 2 vệ tinh giống vá»›i các tiểu hành tinh hÆ¡n là giống vá»›i thành phần của Há»a Tinh.
+
+Má»™t khả năng khác đó là sá»± tham gia của má»™t vật thể thứ ba hoặc má»™t kiểu va chạm gây nhiá»…u loạn.[121] Những dữ liệu gần đây cho thấy khả năng vệ tinh Phobos có cấu trúc bên trong khá rá»—ng[122] và các nhà khoa há»c Ä‘á» xuất thành phần chính của nó là khoáng phyllosilicat và những loại khoáng vật khác đã có trên Sao Há»a,[123] và há» chỉ ra trá»±c tiếp rằng nguồn gốc của Phobos là từ những vật liệu bắn ra từ má»™t thiên thể va chạm vá»›i Sao Há»a và sau đó tích tụ lại trên quỹ đạo quanh hành tinh này,[124] tÆ°Æ¡ng tá»± nhÆ° lý thuyết giải thích cho nguồn gốc Mặt Trăng. Trong khi phổ VNIR của các vệ tinh Sao Há»a giống vá»›i phổ của các tiểu hành tinh trong vành Ä‘ai tiểu hành tinh, thì phổ hồng ngoại nhiệt (thermal infrared) của Phobos lại không hoàn toàn tÆ°Æ¡ng thích vá»›i phổ của bất kỳ lá»›p khoáng vật chondrit.[123]
+Tên ÄÆ°á»ng kính
+(km) Khối lượng
+(kg) Bán trục
+lớn (km) Chu kỳ
+quỹ đạo (giá») Chu kỳ
+trăng má»c
+trung bình
+(giá», ngày)
+Phobos 22,2 km (27×21,6×18,8) 1,08×1016 9 377 km 7,66 11,12 giá»
+(0,463 ngày)
+Deimos 12,6 km (10×12×16) 2×1015 23 460 km 30,35 131 giá»
+(5,44 ngày)
+Sự sống[sửa | sửa mã nguồn]
+
+ Bài chi tiết: NÆ°á»›c trên Sao Há»a và Sá»± sống trên Sao Há»a
+
+Những hiểu biết hiện tại vá» hành tinh ở được—khả năng má»™t thế giá»›i cho sá»± sống phát triển và duy trì—ưu tiên những hành tinh có nÆ°á»›c lá»ng tồn tại trên bá» mặt của chúng. Äiá»u này trÆ°á»›c tiên đòi há»i quỹ đạo hành tinh nằm trong vùng ở được, mà đối vá»›i Mặt Trá»i hiện nay là vùng mở rá»™ng ngày bên ngoài quỹ đạo Sao Kim đến bán trục lá»›n của Sao Há»a.[125] Trong thá»i gian Sao Há»a nằm gần cận Ä‘iểm quỹ đạo thì nó cÅ©ng nằm sâu bên trong vùng ở được, nhÆ°ng bầu khí quyển má»ng của hành tinh (và do đó áp suất khí quyển thấp) không đủ để cho nÆ°á»›c lá»ng tồn tại trên diện rá»™ng và trong thá»i gian dài. Những dòng chảy trong quá khứ của nÆ°á»›c lá»ng có khả năng mang lại tính ở được cho hành tinh Ä‘á». Má»™t số chứng cứ hiện nay cÅ©ng cho thấy nếu nÆ°á»›c lá»ng có tồn tại trên bá» mặt Sao Há»a thì nó sẽ quá mặn và có tính a xít cao để có thể duy trì má»™t sá»± sống thông thÆ°á»ng.[126]
+
+Sao Há»a thiếu Ä‘i từ quyển và có má»™t bầu khí quyển cá»±c má»ng cÅ©ng là má»™t thách thức: sẽ có ít sá»± truyá»n nhiệt trên toàn bá» mặt hành tinh, đồng thá»i khí quyển cÅ©ng không thể ngăn được sá»± bắn phá của gió Mặt Trá»i và má»™t áp suất quá thấp để duy trì nÆ°á»›c dÆ°á»›i dạng lá»ng (thay vào đó nÆ°á»›c sẽ lập tức thăng hoa thành dạng hÆ¡i). Sao Há»a cÅ©ng gần nhÆ°, hay có lẽ hoàn toàn không còn các hoạt Ä‘á»™ng địa chất; sá»± ngÆ°ng hoạt Ä‘á»™ng của các núi lá»­a rõ ràng làm ngừng sá»± tuần hoàn của các khoáng chất và hợp chất hóa há»c giữa bá» mặt và phần bên trong hành tinh.[127]
+
+Nhiá»u bằng chứng ủng há»™ cho Há»a Tinh trÆ°á»›c đây đã từng có những Ä‘iá»u kiện cho sá»± sống phát triển hÆ¡n so vá»›i ngày nay, nhÆ°ng liệu các sinh vật sống có từng tồn tại hay không vẫn còn là bí ẩn. Các tàu thăm dò Viking trong giữa thập niên 1970 đã thá»±c hiện những thí nghiệm được thiết kế nhằm xác định các vi sinh vật trong đất Sao Há»a ở những vị trí chúng đổ bá»™ và đã cho kết quả khả quan, bao gồm sá»± tăng tạm thá»i của sản phẩm CO2 khi trá»™n những mẫu đất vá»›i nÆ°á»›c và khoáng chất. Dấu hiệu của sá»± sống này đã gây ra tranh cãi trong cá»™ng đồng các nhà khoa há»c, và vẫn còn là má»™t vấn Ä‘á» mở, trong đó nhà khoa há»c NASA Gilbert Levin cho rằng tàu Viking có thể đã tìm thấy sá»± sống. Má»™t cuá»™c phân tích lại những dữ liệu từ Viking, trong ánh sáng của hiểu biết hiện đại vá» dạng sống trong môi trÆ°á»ng cá»±c kỳ khắc nghiệt (extremophile forms), cho thấy các thí nghiệm trong chÆ°Æ¡ng trình Viking không đủ Ä‘á»™ phức tạp để xác định được những dạng sống này. Thậm chí những thí nghiệm này có thể đã giết chết những dạng vi sinh vật (giả thuyết là tồn tại).[128] Các thí nghiệm thá»±c hiện bởi tàu đổ bá»™ Phoenix đã chỉ ra đất ở vị trí đáp xuống có tính kiá»m pH khá cao và nó chứa magiê, natri, kali và clo.[129] Những chất dinh dưỡng trong đất có thể giúp phát triển sá»± sống những sá»± sống vẫn cần phải được bảo vệ từ những ánh sáng cá»±c tím rất mạnh.[130]
+
+Tại phòng thí nghiệm Trung tâm không gian Johnson, má»™t số hình dạng thú vị đã được tìm thấy trong khối vẫn thạch ALH84001. Má»™t số nhà khoa há»c Ä‘á» xuất là những hình dạng này có khả năng là hóa thạch của những vi sinh vật đã từng tồn tại trên Sao Há»a trÆ°á»›c khi vẫn thạch này bị bắn vào không gian bởi má»™t vụ chạm của thiên thạch vá»›i hành tinh Ä‘á» và gá»­i nó Ä‘i trong chuyến hành trình khoảng 15 triệu năm tá»›i Trái Äất. Äá» xuất vá» nguồn gốc phi hữu cÆ¡ cho những hình dạng này cÅ©ng đã được nêu ra.[131]
+
+Những lượng nhá» mêtan và fomanđêhít xác định được gần đây bởi các tàu quỹ đạo Ä‘á»u được coi là những dấu hiệu cho sá»± sống, và những hợp chất hóa há»c này cÅ©ng nhanh chóng bị phân hủy trong bầu khí quyển của Há»a Tinh.[132][133] CÅ©ng có khả năng những hợp chất này được bổ sung bởi hoạt Ä‘á»™ng địa chất hay núi lá»­a cÅ©ng nhÆ° sá»± serpentin hóa của khoáng chất (serpentinization).[108]
+
+Trong tÆ°Æ¡ng lai, có thể là nhiệt Ä‘á»™ bá» mặt Sao Há»a sẽ tăng từ từ, hÆ¡i nÆ°á»›c và CO2 hiện tại Ä‘ang đóng băng dÆ°á»›i regolith bá» mặt sẽ giải phóng vào khí quyển tạo nên hiệu ứng nhà kính nung nóng hành tinh cho đến khi nó đạt những Ä‘iá»u kiện tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i Trái Äất ngày nay, do đó cung cấp nÆ¡i trú chân tiá»m năng trong tÆ°Æ¡ng lai cho sinh vật trên Trái Äất.[134]
+Quá trình thám hiểm[sửa | sửa mã nguồn]
+
+ Bài chi tiết: Thám hiểm Sao Há»a
+
+Tàu đổ bộ Viking 1 vào tháng 2, 1978.
+
+Hàng tá tàu không gian, bao gồm tàu quỹ đạo, tàu đổ bá»™, và robot tá»± hành, đã được gá»­i đến Sao Há»a bởi Liên Xô, Hoa Kỳ, châu Âu, và Nhật Bản nhằm nghiên cứu bá» mặt, khí hậu và địa chất hành tinh Ä‘á». Äến năm 2008, chi phí cho vận chuyển vật liệu từ bá» mặt Trái Äất lên bá» mặt Sao Há»a có giá xấp xỉ 309.000US$ trên má»™t kilôgam.[135]
+
+Những tàu còn hoạt động cho đến năm 2011 bao gồm Mars Reconnaissance Orbiter (từ 2006), Mars Express (từ 2003), 2001 Mars Odyssey (từ 2001), và trên bỠmặt là robot tự hành Opportunity (từ 2004). Những phi vụ kết thúc gần đây bao gồm Mars Global Surveyor (1997–2006) và Robot tự hành Spirit (2004–2010).
+
+Gần hai phần ba số tàu không gian được thiết kế đến Sao Há»a đã bị lá»—i trong giai Ä‘oạn phóng, hành trình hoặc trÆ°á»›c khi bắt đầu thá»±c hiện phi vụ hoặc không hoàn tất phi vụ của chúng, chủ yếu trong giai Ä‘oạn cuối thế ká»· 20. Sang thế ká»· 21, những thất bại trong các phi vụ đã được giảm bá»›t nhiá»u.[136] Những lá»—i trong các phi vụ chủ yếu là do vấn Ä‘á» kÄ© thuật, nhÆ° mất liên lạc hoặc sai lầm trong thiết kế, và thÆ°á»ng do hạn chế vá» tài chính và thiếu năng lá»±c trong các phi vụ.[136] Số thất bại nhiá»u nhÆ° vậy đã làm cho công chúng liên tưởng đến những Ä‘iá»u viá»…n tưởng nhÆ° "Tam giác Bermuda", "Lá»i nguyá»n" Sao Há»a, hoặc "ma cà rồng" trong thiên hà đã ăn những tàu không gian này.[136] Những thất bại gần đây bao gồm phi vụ Beagle 2 (2003), Mars Climate Orbiter (1999), và Mars 96 (1996).
+Các phi vụ trong quá khứ[sửa | sửa mã nguồn]
+Tàu Mars 3 trên con tem năm 1972.
+
+Chuyến bay ngang qua Sao Há»a thành công đầu tiên bởi tàu Mariner 4 của NASA vào ngày 14–15 tháng 7, 1965. Ngày 14 tháng 11, 1971 tàu Mariner 9 trở thành tàu không gian đầu tiên quay quanh má»™t hành tinh khác khi nó Ä‘i vào quỹ đạo quanh Sao Há»a.[137] Con tàu đầu tiên đổ bá»™ thành công xuống bá» mặt là hai tàu của Liên Xô: Mars 2 vào ngày 27 tháng 11 và Mars 3 vào ngày 2 tháng 12, 1971, nhÆ°ng cả hai đã bị mất tín hiệu liên lạc chỉ vài giây sau khi đổ bá»™ thành công. Năm 1975 NASA triển khai chÆ°Æ¡ng trình Viking bao gồm hai tàu quỹ đạo, má»—i tàu có má»™t thiết bị đổ bá»™; và cả hai đã đổ bá»™ thành công vào năm 1976. Tàu quỹ đạo Viking 1 còn hoạt Ä‘á»™ng tiếp được 6 năm, trong khi Viking 2 hoạt Ä‘á»™ng được 3 năm. Các thiết bị đổ bá»™ đã gá»­i bức ảnh màu toàn cảnh tại vị trí đổ bá»™ vá» Sao Há»a[138] và hai tàu quỹ đạo đã chụp ảnh bá» mặt hành tinh mà vẫn còn được sá»­ dụng cho tá»›i ngày nay.
+
+Tàu thám hiểm của Liên Xô Phobos 1 và 2 được gá»­i đến Sao Há»a năm 1988 nhằm nghiên cứu hành tinh và hai vệ tinh của nó. Phobos 1 bị mất liên lạc trong hành trình đến Sao Há»a còn Phobos 2 đã thành công khi chụp ảnh được Sao Há»a và vệ tinh Phobos nhÆ°ng đã không thành công khi gá»­i thiết bị đổ bá»™ xuống bá» mặt Phobos.[139]
+
+Sau thất bại của tàu quỹ đạo Mars Observer vào năm 1992, tàu Mars Global Surveyor của NASA đã Ä‘i vào quỹ đạo hành tinh này năm 1997. Phi vụ này đã thành công và kết thúc nhiệm vụ chính là vẽ bản đồ vào đầu năm 2001. Trong chÆ°Æ¡ng trình mở rá»™ng lần thứ 3, con tàu này đã bị mất liên lạc vào tháng 11 năm 2006, tổng cá»™ng nó đã hoạt Ä‘á»™ng tá»›i 10 năm trong không gian. Tàu quỹ đạo Mars Pathfinder của NASA, mang theo má»™t robot thám hiểm là Sojourner, đã đổ bá»™ xuống thung lÅ©ng Ares Vallis vào mùa hè năm 1997, và gá»­i vá» nhiá»u bức ảnh giá trị.[140]
+Robot Spirit đổ bá»™ lên Sao Há»a năm 2004
+Nhìn từ tàu đổ bộ Phoenix năm 2008
+
+Tàu đổ bá»™ Phoenix đã hạ cánh xuống vùng cá»±c bắc Sao Há»a vào ngày 25 tháng 5, 2008.[141] Cánh tay robot của nó được sá»­ dụng để đào đất và sá»± có mặt của băng nÆ°á»›c đã được xác nhận vào ngày 20 tháng 6.[142][143][143] Phi vụ này kết thúc vào ngày 10 tháng 11, 2008 sau khi liên lạc vá»›i tàu thất bại.[144]
+
+Tháng 11 năm 2011, phi vụ Fobos-Grunt và Huỳnh Há»a 1 được phóng lên trong chÆ°Æ¡ng trình hợp tác giữa Liên bang Nga và Trung Quốc. NhÆ°ng tàu Fobos-Grunt đã không khởi Ä‘á»™ng được Ä‘á»™ng cÆ¡ đẩy sau khi nó được phóng lên quỹ đạo quanh Trái Äất. Fobos-Grunt là phi vụ gá»­i má»™t tàu quỹ đạo đến Sao Há»a đồng thá»i phóng má»™t thiết bị đổ bá»™ xuống vệ tinh Phobos nhằm thu thập mẫu đất đá sau đó gá»­i vá» Trái Äất. Các nhà khoa há»c Nga đã không thể liên lạc được vá»›i tàu và khả năng con tàu sẽ rÆ¡i trở lại Trái Äất vào tháng 1 năm 2012.
+Phi vụ hiện tại[sửa | sửa mã nguồn]
+
+Tàu Mars Odyssey của NASA Ä‘i vào quỹ đạo Há»a Tinh năm 2001.[145] Phổ kế tia gamma trên tàu Odyssey đã phát hiện má»™t lượng đáng kể hiÄ‘rô chỉ cách lá»›p phủ regolith ở bá» mặt có vài mét trên Sao Há»a. Lượng hiÄ‘rô này được chứa trong lá»›p băng tàng trữ ở phía dÆ°á»›i.[146]
+
+Tàu quỹ đạo Mars Express của cÆ¡ quan không gian châu Âu (ESA) đến Sao Há»a năm 2003. Nó mang theo thiết bị đổ bá»™ Beagle 2 nhÆ°ng đã đổ bá»™ không thành công trong quá trình Ä‘i vào bầu khí quyển và được coi là mất hoàn toàn vào tháng 2 năm 2004.[147] Äầu năm 2004, Ä‘á»™i phân tích phổ kế Fourier hành tinh (Planetary Fourier Spectrometer team) đã thông báo rằng tàu quỹ đạo đã xác định được sá»± có mặt của mêtan trong bầu khí quyển Há»a Tinh. CÆ¡ quan ESA thông báo tàu của hỠđã quan sát được hiện tượng cá»±c quang trên Sao Há»a vào tháng 6 năm 2006.[148]
+
+Tháng 1 năm 2004, hai tàu giống nhau của NASA thuá»™c chÆ°Æ¡ng trình robot tá»± hành thám hiểm Sao Há»a là Spirit (MER-A) và Opportunity (MER-B) đã đáp thành công xuống bá» mặt hành tinh Ä‘á». Cả hai Ä‘á»u đã hoàn thành mục tiêu của chúng. Má»™t trong những kết quả khoa há»c quan trá»ng nhất đó là chứng cứ thu được vá» sá»± tồn tại của nÆ°á»›c lá»ng trong quá khứ ở cả hai địa Ä‘iểm đổ bá»™. Bão bụi (dust devils) và gió bão đã thÆ°á»ng xuyên làm sạch các tấm pin mặt trá»i ở 2 robot tá»± hành, do vậy hai robot có Ä‘iá»u kiện để mở rá»™ng thá»i gian tìm kiếm trên Há»a Tinh.[149] Tháng 3 năm 2010 robot Spirit đã ngừng hoạt Ä‘á»™ng sau má»™t thá»i gian bị mắc kẹt trong cát.
+
+Ngày 10 tháng 3 năm 2006, tàu Mars Reconnaissance Orbiter (MRO) của NASA Ä‘i vào quỹ đạo hành tinh này để thá»±c hiện nhiệm vụ 2 năm khảo sát khoa há»c. Con tàu đã vẽ bản đổ địa hình và khí hậu Sao Há»a nhằm tìm những địa Ä‘iểm phù hợp cho các phi vụ đổ bá»™ trong tÆ°Æ¡ng lai. Ngày 3 tháng 3 năm 2008, các nhà khoa há»c thông báo tàu MRO đã lần đầu tiên chụp được bức ảnh vá» má»™t chuá»—i các hoạt Ä‘á»™ng sạt lở đất đá gần cá»±c bắc hành tinh.[150]
+
+Tàu Dawn đã bay ngang qua Sao Há»a vào tháng 2 năm 2009 để nhận thêm lá»±c đẩy hấp dẫn nhằm tăng tốc đến tiểu hành tinh Vesta và sau đó là hành tinh lùn Ceres.[151]
+ Wikimedia Commons có thÆ° viện hình ảnh và phÆ°Æ¡ng tiện truyá»n tải vá» Hình do Curiosity truyá»n vá»
+
+Chương trình Mars Science Laboratory, với robot tự hành mang tên Curiosity, được phóng lên ngày 26 tháng 12 năm 2011. Robot tự hành này là một phiên bản lớn hơn và hiện đại hơn so với hai robot tự hành trong chương trình Mars Exploration Rovers, với khả năng di chuyển tới 90 m/h. Nó cũng được thiết kế với khả năng thực hiện thí nghiệm với các mẫu đất đá lấy từ mũi khoan ở cánh tay robot hoặc thu được thành phần đất đá từ việc chiếu tia laser có tầm xa tới. Robot này cũng sẽ thực hiện khả năng đổ bộ chính xác trong vùng bán kính khoảng 20 km nằm trong hố Gale nhỠlần đầu tiên sử dụng thiết bị phản lực có tên "Sky crane".[152]
+
+Năm 2008, NASA tài trợ cho chÆ°Æ¡ng trình MAVEN, má»™t phi vụ gá»­i tàu quỹ đạo được phóng lên năm 2013 nhằm nghiên cứu bầu khí quyển của Sao Há»a. Con tàu sẽ Ä‘i vào quỹ đạo hành tinh Ä‘á» vào năm 2014.[153]
+Các phi vụ trong tương lai[sửa | sửa mã nguồn]
+
+Năm 2018 cơ quan ESA có kế hoạch phóng robot tự hành đầu tiên của hỠlên hành tinh này; robot ExoMars có khả năng khoan sâu 2 m vào đất nhằm tìm kiếm các phân tử hữu cơ.[154]
+
+NASA sẽ gá»­i robot đổ bá»™ InSight dá»±a trên thiết kế tàu đổ bá»™ Phoenix nhằm nghiên cứu cấu trúc sâu bên trong Sao Há»a vào năm 2016.[155]
+
+Năm 2020, một robot tự hành có thiết kế tương tự như Curiosity sẽ được phóng lên nhằm mục đích tiếp tục nghiên cứu hành tinh này của cơ quan NASA.[156]
+
+ChÆ°Æ¡ng trình MetNet hợp tác giữa Phần Lan-Nga sẽ gá»­i má»™t tàu quỹ đạo nhằm nghiên cứu cấu trúc khí quyển, khí tượng hành tinh đồng thá»i nó sẽ gá»­i má»™t thiết bị nhá» xuống bá» mặt hành tinh.[157][158]
+Kế hoạch Ä‘Æ°a ngÆ°á»i lên Sao Há»a[sá»­a | sá»­a mã nguồn]
+
+ Bài chi tiết: Phi vụ Ä‘Æ°a ngÆ°á»i lên Sao Há»a
+
+CÆ¡ quan ESA hi vá»ng Ä‘Æ°a ngÆ°á»i đặt chân lên Sao Há»a trong khoảng thá»i gian 2030 và 2035.[159] Quá trình này sẽ tiếp bÆ°á»›c sau khi phóng những con tàu lá»›n má»™t cách thành công đến hành tinh, mà bắt đầu từ tàu ExoMars[160] và phi vụ hợp tác NASA-ESA nhằm gá»­i vá» Trái Äất mẫu đất của Sao Há»a.[161]
+
+Quá trình thám hiểm có con ngÆ°á»i của Hoa Kỳ đã được định ra là má»™t mục tiêu lâu dài trong chÆ°Æ¡ng trình Viá»…n cảnh thám hiểm không gian công bố năm 2004 bởi Tổng thống George W. Bush.[162] Vá»›i kế hoạch chế tạo tàu Orion nhằm Ä‘Æ°a ngÆ°á»i trở lại Mặt Trăng trong thập niên 2020 được coi là má»™t bÆ°á»›c cÆ¡ bản trong quá trình Ä‘Æ°a ngÆ°á»i lên Sao Há»a. Ngày 28 tháng 9 năm 2007, ngÆ°á»i đứng đầu cÆ¡ quan NASA Michael D. Griffin phát biểu NASA hÆ°á»›ng mục tiêu Ä‘Æ°a ngÆ°á»i lên Sao Há»a vào năm 2037.[163]
+
+Mars Direct, má»™t chÆ°Æ¡ng trình thám hiểm Há»a Tinh có ngÆ°á»i lái vá»›i chi phí thấp được Ä‘á» xuất bởi Robert Zubrin, sáng lập viên của Mars Society, sẽ sá»­ dụng lá»›p tên lá»­a sức nâng lá»›n Saturn V, nhÆ° Space X Falcon X, hoặc Ares V, để bá» qua giai Ä‘oạn trên quỹ đạo quanh Trái Äất và nạp nhiên liệu trên Mặt Trăng.[164]
+
+MARS-500 là má»™t dá»± án hợp tác giữa Nga (Roskosmos, Viện Hàn lâm Khoa há»c Nga), Liên minh châu Âu (ESA) và Trung Quốc[165] mô phá»ng các Ä‘iá»u kiện y-sinh trên Sao Há»a nhằm nghiên cứu khả năng thích nghi của con ngÆ°á»i vá»›i hành trình dài trên 500 ngày-thá»i gian tối thiểu theo tính toán để hoàn thành chuyến bay lên hành tinh Ä‘á» và quay vá». 3 mô-Ä‘un lắp đặt năm 2006, 2 mô-Ä‘un xây dá»±ng năm 2007 và 2008[166] là nÆ¡i để 6 tình nguyện viên đã sống và làm việc cô lập trong 520 ngày.[167]
+Thiên văn trên Sao Há»a[sá»­a | sá»­a mã nguồn]
+
+ Bài chi tiết: Thiên văn trên Sao Há»a
+
+Phobos Ä‘i qua Mặt Trá»i, chụp từ robot Opportunity vào ngày 10 tháng 3, 2004.
+
+Vá»›i những tàu quỹ đạo, tàu đổ bá»™ và robot tá»± hành Ä‘ang hoạt Ä‘á»™ng trên Sao Há»a mà các nhà thiên văn há»c có thể nghiên cứu thiên văn há»c từ bầu trá»i Sao Há»a. Vệ tinh Phobos hiện lên có Ä‘Æ°á»ng kính góc chỉ bằng má»™t phần ba so vá»›i lúc Trăng tròn trên Trái Äất, trong khi đó Deimos hiện lên nhÆ° má»™t ngôi sao, chỉ hÆ¡i sáng hÆ¡n Sao Kim má»™t chút khi nhìn Sao Kim từ Trái Äất.[168]
+
+CÅ©ng có nhiá»u hiện tượng từng được biết trên Trái Äất mà đã được quan sát trên Sao Há»a, nhÆ° thiên thạch rÆ¡i và cá»±c quang.[148] Sá»± kiện Trái Äất Ä‘i qua Ä‘Ä©a Mặt Trá»i khi quan sát từ Sao Há»a được tiên Ä‘oán sẽ xảy ra vào ngày 10 tháng 11 năm 2084.[169] TÆ°Æ¡ng tá»±, sá»± kiện Sao Thủy và Sao Kim Ä‘i qua Ä‘Ä©a Mặt Trá»i khi nhìn từ Sao Há»a cÅ©ng được tiên Ä‘oán. Do Ä‘Æ°á»ng kính góc của hai vệ tinh Phobos và Deimos quá nhá» cho nên sẽ chỉ có hiện tượng nhật thá»±c má»™t phần (hay Ä‘i ngang qua) trên Sao Há»a.[170][171]
+Quan sát Sao Há»a[sá»­a | sá»­a mã nguồn]
+Chuyển Ä‘á»™ng nghịch hành biểu kiến của Sao Há»a vào năm 2003 khi nhìn từ Trái Äất
+
+Bởi vì quỹ đạo Sao Há»a có Ä‘á»™ lệch tâm đáng kể cho nên Ä‘á»™ sáng biểu kiến của nó ở vị trí xung đối vá»›i Mặt Trá»i có thể thay đổi trong khoảng −3,0 đến −1,4. Äá»™ sáng nhá» nhất của nó tÆ°Æ¡ng ứng vá»›i cấp sao +1,6 khi hành tinh ở vị trí giao há»™i vá»›i Mặt Trá»i.[7] Sao Há»a khi quan sát qua kính thiên văn nhá» thÆ°á»ng hiện lên có màu vàng, cam hay Ä‘á» nâu; trong khi màu sắc thá»±c sá»± của Sao Há»a gần vá»›i màu bÆ¡, và màu Ä‘á» là do khí quyển Sao Há»a chứa rất nhiá»u bụi; bên dÆ°á»›i là bức ảnh mà robot Spirit chụp được trên Sao Há»a vá»›i màu nâu-xanh nhạt, màu bùn vá»›i những tảng đá xám-xanh và cát màu Ä‘á» nhạt.[172] Khi hành tinh hÆ°á»›ng vá» phía gần Mặt Trá»i, nó sẽ rất khó quan sát trong má»™t vài tháng bởi ánh sáng mạnh của Mặt Trá»i. Ở những thá»i Ä‘iểm thích hợp—khoảng thá»i gian 15 hoặc 17 năm, và luôn luôn là giữa cuối tháng 7 cho đến tháng 9—có thể quan sát những chi tiết trên bá» mặt Sao Há»a qua kính thiên văn nghiệp dÆ°. Thậm chí đối vá»›i các kính thiên văn Ä‘á»™ phóng đại nhá», vẫn có thể quan sát thấy các chá»m băng ở cá»±c.[173]
+
+Khi Sao Há»a tiến gần vào vị trí xung đối nó bắt đầu vào giai Ä‘oạn của chuyển Ä‘á»™ng nghịch hành biểu kiến khi quan sát từ Trái Äất, có nghÄ©a là nó dÆ°á»ng nhÆ° di chuyển ngược lại thành vòng tròn trên ná»n bầu trá»i. Khoảng thá»i gian diá»…n ra chuyển Ä‘á»™ng nghịch hành trong khoảng 72 ngày và Sao Há»a đạt đến Ä‘á»™ sáng biểu kiến cá»±c đại vào giữa giai Ä‘oạn này.[174]
+Ảnh chụp Mặt Trá»i lặn ở hố va chạm Gusev chụp bởi robot Spirit vào ngày 19 tháng 5, 2005.
+Những lần tiếp cận gần nhất[sửa | sửa mã nguồn]
+Gần tương đối[sửa | sửa mã nguồn]
+
+Khi Sao Há»a ở gần vị trí xung đối vá»›i Mặt Trá»i thì đây là thá»i Ä‘iểm hành tinh nằm gần vá»›i Trái Äất nhất. Giai Ä‘oạn xung đối có thể kéo dài trong khoảng 8½ ngày xung quanh thá»i Ä‘iểm hai hành tinh nằm gần nhau. Khoảng cách lúc hai hành tinh tiếp cận gần nhau nhất có thể thay đổi trong khoảng từ 54[175] đến 103 triệu km do quỹ đạo của hai hành tinh có hình elip, và do đó cÅ©ng làm thay đổi Ä‘Æ°á»ng kính góc của Sao Há»a khi nhìn từ Trái Äất.[176] Lần xung đối gần đây nhất (2011) diá»…n ra vào ngày 29 tháng 1 năm 2010. Lần tiếp theo sẽ xảy ra vào ngày 3 tháng 3 năm 2012 ở khoảng cách khoảng 100 triệu km.[177] Thá»i gian trung bình giữa hai lần xung đối, hay chu kỳ giao há»™i của hành tinh, là 780 ngày nhÆ°ng số ngày chính xác giữa hai lần xung đối kế tiếp có thể thay đổi từ 764 đến 812 ngày.[178]
+
+Khi Há»a Tinh vào thá»i kỳ xung đối nó cÅ©ng bắt đầu vào giai Ä‘oạn chuyển Ä‘á»™ng biểu kiến nghịch hành vá»›i thá»i gian khoảng 72 ngày.
+Lần tiếp cận gần nhất[sửa | sửa mã nguồn]
+Vị trí xung đối của hành tinh Ä‘á» trong thá»i gian 2003–2018, khi nhìn trên mặt phẳng hoàng đạo vá»›i Trái Äất ở chính giữa.
+
+Sao Há»a nằm gần Trái Äất nhất trong vòng khoảng 60.000 năm qua là vào thá»i Ä‘iểm 9:51:13 UT ở khoảng cách 55.758.006 km (0,372719 AU), Ä‘á»™ sáng biểu kiến đạt −2,88. Thá»i Ä‘iểm này xảy ra khi Sao Há»a đã vào ở vị trí xung đối được má»™t ngày và khoảng ba ngày từ cận Ä‘iểm quỹ đạo làm cho Sao Há»a dá»… dàng nhìn thấy từ Trái Äất. Lần cuối hành tinh Ä‘á» nằm gần nhất vá»›i Trái Äất được Æ°á»›c tính đã diá»…n ra vào ngày 12 tháng 9 năm 57.617 trÆ°á»›c Công nguyên, lần tiếp theo được Æ°á»›c tính diá»…n ra vào năm 2287.[179] Ká»· lục tiếp cận gần nhất năm 2003 chỉ hÆ¡i bé hÆ¡n so vá»›i má»™t số lần tiếp cận gần nhất trong thá»i gian gần đây. Ví dụ, khoảng cách nhá» nhất giữa hai hành tinh xảy ra vào ngày 22 tháng 8 năm 1924 là 0,37285 AU, và vào ngày 24 tháng 8 năm 2208 sẽ là 0,37279 AU.[115]
+
+Trong năm 2003, và những năm sau, đã có má»™t trò chÆ¡i khăm phát tán trên internet nói rằng năm 2003 Sao Há»a sẽ nằm gần Trái Äất nhất trong hàng nghìn năm qua và nó sẽ hiện lên to nhÆ° Mặt Trăng trên bầu trá»i.[180]
+Lịch sá»­ quan sát Sao Há»a[sá»­a | sá»­a mã nguồn]
+
+ Bài chi tiết: Lịch sá»­ quan sát Sao Há»a
+
+Lịch sá»­ quan sát Sao Há»a được đánh dấu bởi những lần hành tinh này ở vị trí xung đối, khi nó nằm gần Trái Äất và vì vậy dá»… dàng có thể quan sát bằng mắt thÆ°á»ng, và những lần xung đối xảy ra khoảng 2 năm má»™t lần. Những lần xảy ra xung đối nổi bật hÆ¡n cả trong lịch sá»­ đó là khoảng thá»i gian cách nhau 15 đến 17 năm khi lần xung đối xảy ra trùng hoặc gần vá»›i cận Ä‘iểm quỹ đạo của Há»a Tinh, Ä‘iá»u này càng làm cho nó dá»… dàng quan sát được từ Trái Äất.
+
+Sá»± tồn tại của Sao Há»a nhÆ° má»™t thiên thể Ä‘i lang thang trên bầu trá»i đêm đã được ghi lại bởi những nhà thiên văn há»c Ai Cập cổ đại và vào năm 1534 TCN hỠđã nhận thấy được chuyển Ä‘á»™ng nghịch hành biểu kiến của hành tinh Ä‘á».[181] Trong lịch sá»­ của đế chế Babylon lần hai, các nhà thiên văn Babylon đã quan sát má»™t cách có hệ thống và ghi chép thÆ°á»ng xuyên vị trí của các hành tinh. Äối vá»›i Sao Há»a, há» biết rằng hành tinh này thá»±c hiện được 37 chu kỳ giao há»™i, hay Ä‘i được 42 vòng trên vòng hoàng đạo, trong khoảng 79 năm Trái Äất. Há» cÅ©ng đã phát minh ra phÆ°Æ¡ng pháp số há»c nhằm hiệu chỉnh những Ä‘á»™ lệch nhá» trong việc tiên Ä‘oán vị trí của các hành tinh.[182][183]
+
+Trong thế ká»· thứ tÆ° trÆ°á»›c Công nguyên, Aristoteles đã phát hiện ra Sao Há»a biến mất đằng sau Mặt Trăng trong má»™t lần che khuất, và ông nhận xét rằng hành tinh này phải nằm xa hÆ¡n Mặt Trăng.[184] Ptolemaeus, nhà thiên văn Hy Lạp cổ đại ở Alexandria,[185] đã cố gắng giải quyết vấn Ä‘á» chuyển Ä‘á»™ng quỹ đạo của Há»a Tinh. Mô hình của Ptolemaeus và tập hợp những nghiên cứu của ông vá» thiên văn há»c đã được trình bày trong bản thảo nhiá»u tập mang tên Almagest, và nó đã trở thành ná»™i dung được phổ biến trong thiên văn há»c phÆ°Æ¡ng Tây trong gần mÆ°á»i bốn thế ká»· sau.[186] Các tÆ° liệu lịch sá»­ Trung Hoa cổ đại cho thấy Sao Há»a được các nhà thiên văn Trung Hoa cổ đại biết đến không muá»™n hÆ¡n thế ká»· thứ tÆ° trÆ°á»›c Công nguyên.[187] Ở thế ká»· thứ năm, trong tài liệu ghi chép thiên văn của Ấn Äá»™ mang tên Surya Siddhanta đã ghi lại Æ°á»›c tính Ä‘Æ°á»ng kính Sao Há»a của những nhà thiên văn Ấn Äá»™.[188]
+
+Trong thế ká»· thứ mÆ°á»i bảy, Tycho Brahe đã Ä‘o thị sai ngày của Sao Há»a và dữ liệu này được Johannes Kepler sá»­ dụng để tính toán sÆ¡ bá»™ vá» khoảng cách tÆ°Æ¡ng đối đến hành tinh Ä‘á».[189] Khi kính thiên văn được phát minh ra và trở lên phổ biến hÆ¡n, thị sai ngày của Sao Há»a đã được Ä‘o lại cẩn thận trong ná»— lá»±c nhằm xác định khoảng cách Trái Äất-Mặt Trá»i. Ná»— lá»±c này lần đầu tiên được thá»±c hiện bởi Giovanni Domenico Cassini năm 1672. Những Ä‘o đạc thị sai trong thá»i kỳ này đã bị cản trở bởi chất lượng của dụng cụ quan sát.[190] Ngày 13 tháng 10 năm 1590, sá»± kiện Sao Há»a bị Sao Kim che khuất đã được Michael Maestlin ở Heidelberg ghi nhận.[191] Năm 1610, Galileo Galilei là ngÆ°á»i đầu tiên đã quan sát Sao Há»a qua má»™t kính thiên văn.[192] NgÆ°á»i đầu tiên cố gắng vẽ ra tấm bản đồ Sao Há»a thể hiện những đặc Ä‘iểm trên bá» mặt của nó là nhà thiên văn há»c ngÆ°á»i Hà Lan Christiaan Huygens.[193]
+"Kênh đào" Sao Há»a[sá»­a | sá»­a mã nguồn]
+Bản đồ Sao Há»a của Giovanni Schiaparelli.
+Phác há»a bản đồ Sao Há»a bởi Lowell trÆ°á»›c năm 1914.
+Bản đồ Sao Há»a chụp bởi kính thiên văn không gian Hubble khi hành tinh ở gần vị trí xung đối năm 1999.
+
+ Bài chi tiết: Kênh đào Sao Há»a
+
+Cho đến thế ká»· 19, Ä‘á»™ phóng đại của các kính thiên văn đã đạt đến mức cần thiết cho việc phân giải các đặc Ä‘iểm trên bá» mặt hành tinh Ä‘á». Trong tháng 9 năm 1877, sá»± kiện Sao Há»a tiến đến vị trí xung đối đã được dá»± Ä‘oán xảy ra vào ngày 5 tháng 9. Nhá» vào sá»± kiện này, nhà thiên văn ngÆ°á»i Italia Giovanni Schiaparelli sá»­ dụng kính thiên văn 22 cm ở Milano nhằm quan sát hành tinh này để vẽ ra tấm bản đồ chi tiết đầu tiên vá» Sao Há»a mà ông thấy qua ống kính. Trên bản đồ này có đánh dấu những đặc Ä‘iểm mà ông gá»i là canali, mặc dù sau đó được chỉ ra là những ảo ảnh quang há»c. Những canali được vẽ là những Ä‘Æ°á»ng thẳng trên bá» mặt Sao Há»a và ông đặt tên của chúng theo tên của những con sông nổi tiếng trên Trái Äất. Trong ngôn ngữ của ông, canali có nghÄ©a là "kênh đào" hoặc "rãnh", và được dịch má»™t cách hiểu nhầm sang tiếng Anh là "canals" (kênh đào).[194][195]
+
+Ảnh hưởng bởi những quan sát này, nhà Äông phÆ°Æ¡ng há»c Percival Lowell đã xây dá»±ng má»™t đài quan sát mà sau này mang tên đài quan sát Lowell vá»›i hai kính thiên văn Ä‘Æ°á»ng kính 300 và 450 mm. Äài quan sát này được sá»­ dụng để quan sát Sao Há»a trong lần xung đối hiếm có vào năm 1894 và những lần xung đối thông thÆ°á»ng vá» sau. Lowell đã xuất bản má»™t vài cuốn sách vá» Há»a Tinh và Ä‘á» cập đến sá»± sống trên hành tinh này, chúng đã có những ảnh hưởng nhất định đối vá»›i công chúng vá» hành tinh này.[196] Äặc Ä‘iểm canali cÅ©ng đã được má»™t số nhà thiên văn há»c tìm thấy, nhÆ° Henri Joseph Perrotin và Louis Thollon ở Nice, nhá» sá»­ dụng má»™t trong những kính thiên văn lá»›n nhất thá»i bấy giá».[197][198]
+
+Sá»± thay đổi theo mùa (bao gồm sá»± thu hẹp diện tích của các chá»m băng vùng cá»±c và những miá»n tối hình thành trong mùa hè trên Há»a Tinh) kết hợp vá»›i ý niệm vá» kênh đào đã dẫn đến những phá»ng Ä‘oán vá» sá»± sống trên Sao Há»a, và nhiá»u ngÆ°á»i có niá»m tin lâu dài rằng Sao Há»a có những vùng biển rá»™ng lá»›n và những cánh đồng bạt ngàn. Tuy nhiên những kính thiên văn thá»i này không đủ Ä‘á»™ phân giải đủ lá»›n để chứng minh hay bác bá» những phá»ng Ä‘oán này. Khi những kính thiên văn lá»›n hÆ¡n ra Ä‘á»i, những canali thẳng, ngắn hÆ¡n được quan sát rõ hÆ¡n. Khi Camille Flammarion thá»±c hiện quan sát năm 1909 vá»›i kính Ä‘Æ°á»ng kính 840 mm, những địa hình không đồng Ä‘á»u được nhận ra nhÆ°ng không má»™t đặc Ä‘iểm canali được trông thấy.[199]
+
+Thậm chí những bài báo trong thập niên 1960 vá» sinh há»c vÅ© trụ trên Sao Há»a, nhiá»u tác giả đã giải thích theo khía cạnh sá»± sống cho những đặc Ä‘iểm thay đổi theo mùa trên hành tinh này. Những kịch bản cụ thể vá» quá trình trao đổi chất và chu trình hóa há»c cho những hệ sinh thái cÅ©ng đã được xuất bản.[200]
+
+Cho đến khi những tàu vÅ© trụ viếng thăm hành tinh này trong chÆ°Æ¡ng trình Mariner của NASA trong thập niên 1960 thì những bí ẩn này má»›i được sáng tá». Những chấp nhận chung vá» má»™t hành tinh đã chết được khẳng định trong thí nghiệm nhằm xác định sá»± sống của tàu Viking và những ảnh chụp tại nÆ¡i nó đổ bá»™.[201]
+
+Má»™t vài bản đồ vá» Sao Há»a đã được lập ra nhá» sá»­ dụng các dữ liệu thu được từ các phi vụ này, nhÆ°ng cho đến tận phi vụ của tàu Mars Global Surveyor, phóng lên vào năm 1996 và ngừng hoạt Ä‘á»™ng năm 2006, đã mang lại những chi tiết đầy đủ nhất vá» bản đồ địa hình, từ trÆ°á»ng và sá»± phân bố khoáng chất trên bá» mặt.[202] Những bản đồ vá» Sao Há»a hiện nay đã được cung cấp trên má»™t số dịch vụ trá»±c tuyến, nhÆ° Google Mars.
+Trong văn hóa[sửa | sửa mã nguồn]
+
+ Bài chi tiết: Sao Há»a trong văn hóa
+
+Sao Há»a trong ngôn ngữ phÆ°Æ¡ng Tây được mang tên của vị thần chiến tranh trong thần thoại. Từ há»a cÅ©ng là tên của má»™t trong năm yếu tố của ngÅ© hành trong triết há»c cổ Trung Hoa. Biểu tượng Sao Há»a, gồm má»™t vòng tròn vá»›i má»™t mÅ©i tên chỉ ra ngoài, cÅ©ng là biểu tượng cho giống Ä‘á»±c.
+
+à tưởng cho rằng trên Sao Há»a có những sinh vật có trí thông minh đã xuất hiện từ cuối thế ká»· 19. Quan sát các "canali" (kênh đào) của Giovanni Schiaparelli kết hợp vá»›i cuốn sách của Percival Lowell vỠý tưởng này đã làm cÆ¡ sở cho những bàn luận vá» má»™t hành tinh Ä‘ang hạn hát, lạnh lẽo, má»™t thế giá»›i chết vá»›i ná»n văn minh trên đó Ä‘ang xây dá»±ng những hệ thống tÆ°á»›i tiêu.[203]
+
+Nhiá»u quan sát khác và những lá»i tuyên bố bởi những ngÆ°á»i có ảnh hưởng đã làm dấy lên cái gá»i là "CÆ¡n sốt Sao Há»a".[204] Năm 1899, khi Ä‘ang nghiên cứu Ä‘á»™ ồn vô tuyến trong khí quyển bằng cách sá»­ dụng máy thu ở phòng thí nghiệm Colorado Springs, nhà sáng chế Nikola Tesla đã nhận ra sá»± lặp lại trong tín hiệu mà sau đó ông Ä‘oán có thể là tín hiệu liên lạc vô tuyến đến từ má»™t hành tinh khác, và khả năng là Sao Há»a. Năm 1901, trong má»™t cuá»™c phá»ng vấn, Tesla nói:
+
+ Ở thá»i Ä‘iểm sau khi có má»™t ý nghÄ© lóe lên trong đầu tôi rằng những nhiá»…u loạn mà tôi đã thu được có thể là do sá»± Ä‘iá»u khiển từ má»™t ná»n văn minh. Mặc dù tôi không thể giải mã ý nghÄ©a của chúng, nhÆ°ng tôi không thể nghÄ© rằng đó chỉ hoàn toàn là sá»± ngẫu nhiên. Cảm giác tăng dần trong tôi rằng lần đầu tiên tôi đã nghe được lá»i chào từ má»™t hành tinh khác.[205]
+
+à nghÄ© của Tesla nhận được sá»± ủng há»™ từ Lord Kelvin, ông này khi viếng thăm Hoa Kỳ năm 1902, đã nói là ông nghÄ© rằng những tín hiệu mà Tesla thu được là do từ hành tinh Ä‘á» gá»­i đến Hoa Kỳ.[206] Kelvin "nhấn mạnh" từ chối lá»i nói này ngay trÆ°á»›c khi ông rá»i Hoa Kỳ: "Cái mà tôi thá»±c sá»± nói rằng những cÆ° dân Sao Há»a, nếu có, sẽ không nghi ngá» khi há» có thể nhìn thấy New York, đặc biệt từ ánh sáng đèn Ä‘iện."[207]
+
+Trong má»™t bài viết trên tá» New York Times năm 1901, Edward Charles Pickering, giám đốc Äài quan sát Harvard College, Ä‘Æ°a tin hỠđã nhận được má»™t Ä‘iện tín từ Äài quan sát Lowell ở Arizona vá»›i ná»™i dung xác nhận là dÆ°á»ng nhÆ° ná»n văn minh trên Sao Há»a Ä‘ang cố liên lạc vá»›i Trái Äất.[208]
+
+ Äầu tháng 12 năm 1900, chúng tôi nhận được bức Ä‘iện tín từ Äài quan sát Lowell ở Arizona rằng má»™t luồng ánh sáng chiếu từ Sao Há»a (đài quan sát Lowell luôn dành sá»± quan tâm đặc biệt đến Sao Há»a) kéo dài trong khoảng 70 phút. Tôi đã gá»­i những thông tin này sang châu Âu cÅ©ng nhÆ° bản sao của Ä‘iện tín đến khắp nÆ¡i trên đất nÆ°á»›c này. Những ngÆ°á»i quan sát đã rất cẩn thận, đáng tin và do vậy không có lý do gì để nghi ngá» vá» sá»± tồn tại của tia sáng. NgÆ°á»i ta cho rằng nó bắt nguồn từ má»™t vị trí địa lý nổi tiếng trên Sao Há»a. Tất cả là thế. Bây giá» câu chuyện đã lan ra trên toàn thế giá»›i. Ở châu Âu, ngÆ°á»i ta nói rằng tôi đã liên lạc vá»›i ngÆ°á»i Sao Há»a và đủ má»i thông tin cÆ°á»ng Ä‘iệu đã xuất hiện. Cho dù thứ ánh sáng đó là gì, chúng ta cÅ©ng không biết ý nghÄ©a của nó. Không ai có thể nói được đó là từ má»™t ná»n văn minh hay không phải. Nó tuyệt đối không thể giải thích được.[208]
+
+Pickering sau đó Ä‘á» xuất lắp đặt má»™t loạt tấm gÆ°Æ¡ng ở Texas nhằm thu các tín hiệu từ Sao Há»a.[209]
+
+Trong những thập ká»· gần đây, nhá» những tấm bản đồ Ä‘á»™ phân giải cao vá» bá» mặt Sao Há»a, đặc biệt từ tàu Mars Global Surveyor và Mars Reconnaissance Orbiter, cho thấy không há» có má»™t dấu hiệu của sá»± sống có trí tuệ trên hành tinh này, mặc dù những phá»ng Ä‘oán giả khoa há»c vá» sá»± sống có trí thông minh trên Sao Há»a vẫn xuất hiện từ những biên tập viên nhÆ° Richard C. Hoagland. Nhá»› lại những tranh luận trÆ°á»›c đây vỠđặc Ä‘iểm canali, xuất hiện má»™t số suy Ä‘oán vá» những hình tượng kích cỡ nhá» trên má»™t số bức ảnh từ tàu không gian, nhÆ° 'kim tá»± tháp' và 'khuôn mặt trên Sao Há»a'. Nhà thiên văn há»c hành tinh Carl Sagan đã viết:
+
+ Sao Há»a đã trở thành má»™t sân khấu cho những vở kịch thần thoại mà ở đó chúng ta chiếu lên những hi vá»ng và sợ hãi của chúng ta trên Trái Äất.[195]
+
+Minh há»a sinh vật ba chân Há»a Tinh trong tác phẩm ấn bản tiếng Pháp xuất bản năm 1906, The War of the Worlds của nhà văn H.G. Wells.
+
+Các miêu tả Sao Há»a trong tiểu thuyết đã bị kích thích bởi màu đỠđặc trÆ°ng của nó và bởi những suy Ä‘oán mang tính khoa há»c ở thế ká»· 19 vá» các Ä‘iá»u kiện bá» mặt hành tinh không những duy trì cho sá»± sống mà còn tồn tại ná»n văn minh trên đó.[210] Äã có nhiá»u những tác phẩm khoa há»c viá»…n tưởng được ra Ä‘á»i, trong số đó có tác phẩm The War of the Worlds của H. G. Wells xuất bản năm 1898, vá»›i ná»™i dung vá» những sinh vật Sao Há»a Ä‘ang cố gắng thoát khá»i hành tinh Ä‘ang chết dần và chúng xuống xâm lược Äịa cầu. Sau đó, ngày 30 tháng 10 năm 1938, phát thanh viên Orson Welles đã dá»±a vào tác phẩm này và gây ra trò đùa trên đài phát thanh làm cho nhiá»u thính giả thiếu hiểu biết bị hiểu nhầm.[211]
+
+Những tác phẩm có tính ảnh hưởng bao gồm The Martian Chronicles của Ray Bradbury, trong đó cuá»™c thám hiểm của con ngÆ°á»i đã trở thành má»™t tai nạn phá hủy ná»n văn minh Há»a Tinh, Barsoom của Edgar Rice Burroughs, tiểu thuyết Out of the Silent Planet của C. S. Lewis (1938),[212] và má»™t số câu chuyện của Robert A. Heinlein trong những năm 60.[213]
+
+Tác giả Jonathan Swift đã từng miêu tả vá» các Mặt Trăng của Sao Há»a, khoảng 150 năm trÆ°á»›c khi chúng được nhà thiên văn há»c Asaph Hall phát hiện ra. J.Swift đã miêu tả khá chính xác và chi tiết vá» quỹ đạo của chúng trong chÆ°Æ¡ng 19 của tiểu thuyết Gulliver's Travels.[214]
+
+Má»™t nhân vật truyện tranh thể hiện trí thông minh Sao Há»a, Marvin, đã xuất hiện trên truyá»n hình năm 1948 trong bá»™ phim hoạt hình Looney Tunes của hãng Warner Brothers, và nó vẫn còn tiếp tục xuất hiện trong văn hóa đại chúng phÆ°Æ¡ng Tây hiện nay.[215]
+
+Sau khi các tàu Mariner và Viking gá»­i vá» các bức ảnh chụp Há»a Tinh, má»™t thế giá»›i không có sá»± sống và những kênh đào, thì những quan niệm vá» ná»n văn minh Sao Há»a ngay lập tức bị từ bá», và thay vào đó là những miêu tả vá» viá»…n cảnh con ngÆ°á»i sẽ đến khai phá hành tinh này, nổi tiếng nhất có lẽ là tác phẩm bá»™ ba Sao Há»a của Kim Stanley Robinson. Những suy Ä‘oán giả khoa há»c vá» Khuôn mặt trên Sao Há»a và những địa hình bí ẩn khác được chụp bởi các tàu quỹ đạo đã trở thành bối cảnh phổ biến cho những tác phẩm khoa há»c viá»…n tưởng, đặc biệt trong phim ảnh.[216]
+
+Bối cảnh con ngÆ°á»i trên Sao Há»a đấu tranh giành Ä‘á»™c lập khá»i Trái Äất cÅ©ng là má»™t ná»™i dung chính trong tiểu thuyết của Greg Bear cÅ©ng nhÆ° bá»™ phim Total Recall (dá»±a trên câu chuyện ngắn của Philip K. Dick) và sê ri truyá»n hình Babylon 5. Má»™t số trò chÆ¡i cÅ©ng sá»­ dụng bối cảnh này, bao gồm Red Faction và Zone of the Enders. Sao Há»a (và vệ tinh của nó) cÅ©ng xuất hiện trong video game nhượng quyá»n thÆ°Æ¡ng mại Doom và Martian Gothic.
diff --git a/xpcom/tests/moz.build b/xpcom/tests/moz.build
new file mode 100644
index 0000000000..83105e7ddd
--- /dev/null
+++ b/xpcom/tests/moz.build
@@ -0,0 +1,57 @@
+# -*- 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 += [
+ "gtest",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ TEST_DIRS += ["windows"]
+
+if CONFIG["OS_TARGET"] == "Linux":
+ CppUnitTests(
+ [
+ "TestMemoryPressureWatcherLinux",
+ ]
+ )
+
+EXPORTS.testing += [
+ "TestHarness.h",
+]
+
+test_progs = [
+ "TestArguments",
+ "TestBlockingProcess",
+ "TestPRIntN",
+ "TestQuickReturn",
+ "TestUnicodeArguments",
+]
+SimplePrograms(test_progs)
+
+USE_LIBS += ["mozglue"]
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.xpcshell.xpcom.tests.unit += [
+ "!%s%s" % (f, CONFIG["BIN_SUFFIX"]) for f in test_progs
+ ]
+
+XPIDL_MODULE = "xpcomtest"
+XPIDL_SOURCES += [
+ "NotXPCOMTest.idl",
+]
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../ds",
+]
+
+RESOURCE_FILES += [
+ "test.properties",
+]
+
+CRASHTEST_MANIFESTS += ["crashtests/crashtests.list"]
diff --git a/xpcom/tests/resources.h b/xpcom/tests/resources.h
new file mode 100644
index 0000000000..7ba590e475
--- /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 0000000000..19cae97028
--- /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/data/SmallApp.app/Contents/Info.plist b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist
new file mode 100644
index 0000000000..8388fa2a55
--- /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 0000000000..c821003d34
--- /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 0000000000..bd04210fb4
--- /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 0000000000..5e45963c38
--- /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 0000000000..59f8803c5d
--- /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 0000000000..bb27d4a5d6
--- /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 0000000000..f7885e4fca
--- /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 = a b
+
+3 = test2
diff --git a/xpcom/tests/unit/data/bug121341.properties b/xpcom/tests/unit/data/bug121341.properties
new file mode 100644
index 0000000000..b45fc9698c
--- /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/iniparser01-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini
new file mode 100644
index 0000000000..46b134b197
--- /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 0000000000..5f282702bb
--- /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 0000000000..e69de29bb2
--- /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 0000000000..49cc8ef0e1
--- /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 0000000000..e02abfc9b0
--- /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 0000000000..d3f5a12faa
--- /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 0000000000..05255100a2
--- /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 0000000000..b76e44e194
--- /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 0000000000..60b0742537
--- /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 0000000000..e95d971134
--- /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 0000000000..47ef32c0a9
--- /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 0000000000..23a50d155f
--- /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 0000000000..a49491816c
--- /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 0000000000..eb33b5ccf1
--- /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 0000000000..ade1373377
--- /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 0000000000..e9023ac7c9
--- /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 0000000000..073d841cf3
--- /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 0000000000..c24821e6ed
--- /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 0000000000..d1c167e6e3
--- /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 0000000000..38176d9444
--- /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 0000000000..49816873b2
--- /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 0000000000..e450566a0f
--- /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 0000000000..5fa7d2495c
--- /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 0000000000..cfa15c9ffe
--- /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 0000000000..ef1da39e27
--- /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 0000000000..e3edce4d49
--- /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 0000000000..1c87762ba4
--- /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 0000000000..e5e70b6612
--- /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 0000000000..bda15fcc7b
--- /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 0000000000..037fd79303
--- /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 0000000000..932d4004bc
--- /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 0000000000..78caafaba5
--- /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 0000000000..f8d573a284
--- /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 0000000000..8a29127222
--- /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 0000000000..09ca62779d
--- /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 0000000000..2727940c09
--- /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 0000000000..ebd9a51d3e
--- /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 0000000000..8c9499b669
--- /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 0000000000..21d40b140c
--- /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 0000000000..bbc3413aa1
--- /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 0000000000..d109052c8d
--- /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 0000000000..744af4cb65
--- /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 0000000000..e60525dec6
--- /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 0000000000..172803f90b
--- /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 0000000000..608a27d8fb
--- /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 0000000000..142b175902
--- /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 0000000000..bba1018dab
--- /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 0000000000..b94607d15d
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser16.ini
@@ -0,0 +1,13 @@
+#á¿»á¹Ò³Ï–·Ì˄ȡǨŅ©&
+[☺♫]
+#ѼΏá¹Ò³Ï–
+♫=☻
+#·Ì˄ȡǨŅ©
+♪=♥
+#‽ἧᵿΏá¹Ò³
+#ϖ·Ì˄ȡǨŅ©&
+[☼]
+♣=♠
+♦=♥
+#‽ἧᵿΏá¹Ò³
+#·Ì˄ȡǨŅ©
diff --git a/xpcom/tests/unit/data/iniparser17.ini b/xpcom/tests/unit/data/iniparser17.ini
new file mode 100644
index 0000000000..bc4815b8c7
--- /dev/null
+++ b/xpcom/tests/unit/data/iniparser17.ini
@@ -0,0 +1,7 @@
+[section]
+key=
+
+[]
+
+[empty]
+=foo
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 0000000000..e69de29bb2
--- /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 0000000000..b0bc8e0761
--- /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 0000000000..26178d809e
--- /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 0000000000..8b49316b4d
--- /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 0000000000..3deb2ec444
--- /dev/null
+++ b/xpcom/tests/unit/data/process_directive.manifest
@@ -0,0 +1,2 @@
+category directives-test main-process @mozilla.org/supports-cstring;1 process=main
+category directives-test content-process @mozilla.org/supports-cstring;1 process=content
diff --git a/xpcom/tests/unit/head_xpcom.js b/xpcom/tests/unit/head_xpcom.js
new file mode 100644
index 0000000000..e2ae79cb12
--- /dev/null
+++ b/xpcom/tests/unit/head_xpcom.js
@@ -0,0 +1,21 @@
+let CC = Components.Constructor;
+
+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() {
+ // Importing Services here messes up appInfo for some of the tests.
+ // eslint-disable-next-line mozilla/use-services
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(
+ Ci.nsIProperties
+ );
+ var greBinDir = dirSvc.get("GreBinD", Ci.nsIFile);
+ Services.env.set("DYLD_LIBRARY_PATH", greBinDir.path);
+ // For Linux
+ Services.env.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 0000000000..2e66970092
--- /dev/null
+++ b/xpcom/tests/unit/test_bug121341.js
@@ -0,0 +1,64 @@
+const { NetUtil } = ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+);
+
+function run_test() {
+ var dataFile = do_get_file("data/bug121341.properties");
+ var channel = NetUtil.newChannel({
+ uri: Services.io.newFileURI(dataFile, null, null),
+ loadUsingSystemPrincipal: true,
+ });
+ var inp = channel.open();
+
+ var properties = Cu.createPersistentProperties();
+ properties.load(inp);
+
+ var value;
+
+ value = properties.getStringProperty("1");
+ Assert.equal(value, "abc");
+
+ value = properties.getStringProperty("2");
+ Assert.equal(value, "xy");
+
+ value = properties.getStringProperty("3");
+ Assert.equal(value, "\u1234\t\r\n\u00AB\u0001\n");
+
+ value = properties.getStringProperty("4");
+ Assert.equal(value, "this is multiline property");
+
+ value = properties.getStringProperty("5");
+ Assert.equal(value, "this is another multiline property");
+
+ value = properties.getStringProperty("6");
+ Assert.equal(value, "test\u0036");
+
+ value = properties.getStringProperty("7");
+ Assert.equal(value, "yet another multiline propery");
+
+ value = properties.getStringProperty("8");
+ Assert.equal(value, "\ttest5\u0020");
+
+ value = properties.getStringProperty("9");
+ Assert.equal(value, " test6\t");
+
+ value = properties.getStringProperty("10a\u1234b");
+ Assert.equal(value, "c\uCDEFd");
+
+ value = properties.getStringProperty("11");
+ Assert.equal(value, "\uABCD");
+
+ dataFile = do_get_file("data/bug121341-2.properties");
+
+ var channel2 = NetUtil.newChannel({
+ uri: Services.io.newFileURI(dataFile, null, null),
+ loadUsingSystemPrincipal: true,
+ });
+ inp = channel2.open();
+
+ var properties2 = Cu.createPersistentProperties();
+ try {
+ properties2.load(inp);
+ do_throw("load() didn't fail");
+ } catch (e) {}
+}
diff --git a/xpcom/tests/unit/test_bug1434856.js b/xpcom/tests/unit/test_bug1434856.js
new file mode 100644
index 0000000000..a8dfa08079
--- /dev/null
+++ b/xpcom/tests/unit/test_bug1434856.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+function run_test() {
+ let complete = false;
+
+ let runnable = {
+ internalQI: ChromeUtils.generateQI(["nsIRunnable"]),
+ // eslint-disable-next-line mozilla/use-chromeutils-generateqi
+ QueryInterface(iid) {
+ // Attempt to schedule another runnable. This simulates a GC/CC
+ // being scheduled while executing the JS QI.
+ Services.tm.dispatchToMainThread(() => false);
+ return this.internalQI(iid);
+ },
+
+ run() {
+ complete = true;
+ },
+ };
+
+ Services.tm.dispatchToMainThread(runnable);
+ Services.tm.spinEventLoopUntil(
+ "Test(test_bug1434856.js:run_test)",
+ () => complete
+ );
+}
diff --git a/xpcom/tests/unit/test_bug325418.js b/xpcom/tests/unit/test_bug325418.js
new file mode 100644
index 0000000000..5840aacf74
--- /dev/null
+++ b/xpcom/tests/unit/test_bug325418.js
@@ -0,0 +1,72 @@
+// 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.
+ Assert.equal(
+ Math.round((Date.now() - gStartTime1) / 1000),
+ kExpectedDelay1
+ );
+
+ timer = null;
+
+ info(
+ "1st timer triggered (before being cancelled). Should not have happened!"
+ );
+ Assert.ok(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.
+ Assert.equal(
+ 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 0000000000..605772f12a
--- /dev/null
+++ b/xpcom/tests/unit/test_bug332389.js
@@ -0,0 +1,14 @@
+function run_test() {
+ var f = Services.dirsvc.get("CurProcD", Ci.nsIFile);
+
+ var terminated = false;
+ for (var i = 0; i < 100; i++) {
+ if (f == null) {
+ terminated = true;
+ break;
+ }
+ f = f.parent;
+ }
+
+ Assert.ok(terminated);
+}
diff --git a/xpcom/tests/unit/test_bug333505.js b/xpcom/tests/unit/test_bug333505.js
new file mode 100644
index 0000000000..97016519da
--- /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 0000000000..167e94e852
--- /dev/null
+++ b/xpcom/tests/unit/test_bug364285-1.js
@@ -0,0 +1,43 @@
+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() {
+ return Services.dirsvc.get("TmpD", Ci.nsIFile);
+}
+
+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();
+
+ Assert.equal(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 0000000000..0d20d90b2c
--- /dev/null
+++ b/xpcom/tests/unit/test_bug374754.js
@@ -0,0 +1,65 @@
+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: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe(subject, topic, data) {
+ if (topic == "timer-callback") {
+ Assert.equal(result, expected);
+
+ Services.obs.removeObserver(this, addedTopic);
+ Services.obs.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();
+
+ Services.obs.addObserver(observer, addedTopic);
+ Services.obs.addObserver(observer, removedTopic);
+
+ Services.catMan.addCategoryEntry(
+ testCategory,
+ testEntry,
+ testValue,
+ false,
+ true
+ );
+ Services.catMan.addCategoryEntry(
+ testCategory,
+ testEntry,
+ testValue,
+ false,
+ true
+ );
+ Services.catMan.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 0000000000..5ba64758c7
--- /dev/null
+++ b/xpcom/tests/unit/test_bug476919.js
@@ -0,0 +1,25 @@
+/* global __LOCATION__ */
+
+function run_test() {
+ 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.nsIFile);
+ 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);
+ Assert.ok(link.isSymlink());
+}
diff --git a/xpcom/tests/unit/test_bug478086.js b/xpcom/tests/unit/test_bug478086.js
new file mode 100644
index 0000000000..6debb58fbc
--- /dev/null
+++ b/xpcom/tests/unit/test_bug478086.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+function run_test() {
+ var nsIFile = Ci.nsIFile;
+ var root = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile);
+
+ // 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;
+ Assert.ok(drives.hasMoreElements());
+ while (drives.hasMoreElements()) {
+ var newPath = drives.nextFile.path;
+ Assert.equal(newPath.indexOf("\0"), -1);
+ }
+}
diff --git a/xpcom/tests/unit/test_bug745466.js b/xpcom/tests/unit/test_bug745466.js
new file mode 100644
index 0000000000..a655bf45b4
--- /dev/null
+++ b/xpcom/tests/unit/test_bug745466.js
@@ -0,0 +1,7 @@
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+
+function run_test() {
+ Assert.ok(FileUtils.File("~").equals(FileUtils.getDir("Home", [])));
+}
diff --git a/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js b/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js
new file mode 100644
index 0000000000..1ca97ed6ec
--- /dev/null
+++ b/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js
@@ -0,0 +1,265 @@
+let lastMessage;
+const consoleListener = {
+ observe(message) {
+ dump(" >> new message: " + message.errorMessage + "\n");
+ lastMessage = message;
+ },
+};
+Services.console.registerListener(consoleListener);
+
+// The Console Service notifies its listener after one event loop cycle.
+// So wait for one tick after each action dispatching a message/error to the service.
+function waitForATick() {
+ return new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+}
+
+add_task(async function customScriptError() {
+ const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
+ Ci.nsIScriptError
+ );
+ scriptError.init(
+ "foo",
+ "file.js",
+ null,
+ 1,
+ 2,
+ Ci.nsIScriptError.warningFlag,
+ "some javascript"
+ );
+ Services.console.logMessage(scriptError);
+
+ await waitForATick();
+
+ Assert.equal(
+ lastMessage,
+ scriptError,
+ "We receive the exact same nsIScriptError object"
+ );
+
+ Assert.equal(lastMessage.errorMessage, "foo");
+ Assert.equal(lastMessage.sourceName, "file.js");
+ Assert.equal(lastMessage.lineNumber, 1);
+ Assert.equal(lastMessage.columnNumber, 2);
+ Assert.equal(lastMessage.flags, Ci.nsIScriptError.warningFlag);
+ Assert.equal(lastMessage.category, "some javascript");
+
+ Assert.equal(
+ lastMessage.stack,
+ undefined,
+ "Custom nsIScriptError object created from JS can't convey any stack"
+ );
+});
+
+add_task(async function callFunctionAndLogExceptionWithChromeGlobal() {
+ try {
+ Services.console.callFunctionAndLogException(globalThis, function () {
+ throw new Error("custom exception");
+ });
+ Assert.fail("callFunctionAndLogException should throw");
+ } catch (e) {
+ Assert.equal(
+ e.name,
+ "NS_ERROR_XPC_JAVASCRIPT_ERROR",
+ "callFunctionAndLogException thrown"
+ );
+ }
+
+ await waitForATick();
+
+ Assert.ok(!!lastMessage, "Got the message");
+ Assert.ok(
+ lastMessage instanceof Ci.nsIScriptError,
+ "This is a nsIScriptError"
+ );
+
+ Assert.equal(lastMessage.errorMessage, "Error: custom exception");
+ Assert.equal(lastMessage.sourceName, _TEST_FILE);
+ Assert.equal(lastMessage.lineNumber, 56);
+ Assert.equal(lastMessage.columnNumber, 13);
+ Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag);
+ Assert.equal(lastMessage.category, "chrome javascript");
+ Assert.ok(lastMessage.stack, "It has a stack");
+ Assert.equal(lastMessage.stack.source, _TEST_FILE);
+ Assert.equal(lastMessage.stack.line, 56);
+ Assert.equal(lastMessage.stack.column, 13);
+ Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame");
+ Assert.equal(
+ lastMessage.innerWindowID,
+ 0,
+ "The message isn't bound to any WindowGlobal"
+ );
+});
+
+add_task(async function callFunctionAndLogExceptionWithContentGlobal() {
+ const window = createContentWindow();
+ try {
+ Services.console.callFunctionAndLogException(window, function () {
+ throw new Error("another custom exception");
+ });
+ Assert.fail("callFunctionAndLogException should throw");
+ } catch (e) {
+ Assert.equal(
+ e.name,
+ "NS_ERROR_XPC_JAVASCRIPT_ERROR",
+ "callFunctionAndLogException thrown"
+ );
+ }
+
+ await waitForATick();
+
+ Assert.ok(!!lastMessage, "Got the message");
+ Assert.ok(
+ lastMessage instanceof Ci.nsIScriptError,
+ "This is a nsIScriptError"
+ );
+
+ Assert.equal(lastMessage.errorMessage, "Error: another custom exception");
+ Assert.equal(lastMessage.sourceName, _TEST_FILE);
+ Assert.equal(lastMessage.lineNumber, 97);
+ Assert.equal(lastMessage.columnNumber, 13);
+ Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag);
+ Assert.equal(lastMessage.category, "content javascript");
+ Assert.ok(lastMessage.stack, "It has a stack");
+ Assert.equal(lastMessage.stack.source, _TEST_FILE);
+ Assert.equal(lastMessage.stack.line, 97);
+ Assert.equal(lastMessage.stack.column, 13);
+ Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame");
+ Assert.ok(
+ !!window.windowGlobalChild.innerWindowId,
+ "The window has a innerWindowId"
+ );
+ Assert.equal(
+ lastMessage.innerWindowID,
+ window.windowGlobalChild.innerWindowId,
+ "The message is bound to the content window"
+ );
+});
+
+add_task(async function callFunctionAndLogExceptionForContentScriptSandboxes() {
+ const { sandbox, window } = createContentScriptSandbox();
+ Cu.evalInSandbox(
+ `function foo() { throw new Error("sandbox exception"); }`,
+ sandbox,
+ null,
+ "sandbox-file.js",
+ 1,
+ 0
+ );
+ try {
+ Services.console.callFunctionAndLogException(window, sandbox.foo);
+ Assert.fail("callFunctionAndLogException should throw");
+ } catch (e) {
+ Assert.equal(
+ e.name,
+ "NS_ERROR_XPC_JAVASCRIPT_ERROR",
+ "callFunctionAndLogException thrown"
+ );
+ }
+
+ await waitForATick();
+
+ Assert.ok(!!lastMessage, "Got the message");
+ // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes.
+ Assert.ok(
+ lastMessage instanceof Ci.nsIScriptError,
+ "This is a nsIScriptError"
+ );
+
+ Assert.equal(lastMessage.errorMessage, "Error: sandbox exception");
+ Assert.equal(lastMessage.sourceName, "sandbox-file.js");
+ Assert.equal(lastMessage.lineNumber, 1);
+ Assert.equal(lastMessage.columnNumber, 24);
+ Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag);
+ Assert.equal(lastMessage.category, "content javascript");
+ Assert.ok(lastMessage.stack, "It has a stack");
+ Assert.equal(lastMessage.stack.source, "sandbox-file.js");
+ Assert.equal(lastMessage.stack.line, 1);
+ Assert.equal(lastMessage.stack.column, 24);
+ Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame");
+ Assert.ok(
+ !!window.windowGlobalChild.innerWindowId,
+ "The sandbox's prototype is a window and has a innerWindowId"
+ );
+ Assert.equal(
+ lastMessage.innerWindowID,
+ window.windowGlobalChild.innerWindowId,
+ "The message is bound to the sandbox's prototype WindowGlobal"
+ );
+});
+
+add_task(
+ async function callFunctionAndLogExceptionForContentScriptSandboxesWrappedInChrome() {
+ const { sandbox, window } = createContentScriptSandbox();
+ Cu.evalInSandbox(
+ `function foo() { throw new Error("sandbox exception"); }`,
+ sandbox,
+ null,
+ "sandbox-file.js",
+ 1,
+ 0
+ );
+ try {
+ Services.console.callFunctionAndLogException(window, function () {
+ sandbox.foo();
+ });
+ Assert.fail("callFunctionAndLogException should throw");
+ } catch (e) {
+ Assert.equal(
+ e.name,
+ "NS_ERROR_XPC_JAVASCRIPT_ERROR",
+ "callFunctionAndLogException thrown"
+ );
+ }
+
+ await waitForATick();
+
+ Assert.ok(!!lastMessage, "Got the message");
+ // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes.
+ Assert.ok(
+ lastMessage instanceof Ci.nsIScriptError,
+ "This is a nsIScriptError"
+ );
+
+ Assert.ok(
+ !!window.windowGlobalChild.innerWindowId,
+ "The sandbox's prototype is a window and has a innerWindowId"
+ );
+ Assert.equal(
+ lastMessage.innerWindowID,
+ window.windowGlobalChild.innerWindowId,
+ "The message is bound to the sandbox's prototype WindowGlobal"
+ );
+ }
+);
+
+add_task(function teardown() {
+ Services.console.unregisterListener(consoleListener);
+});
+
+// We are in xpcshell, so we can't have a real DOM Window as in Firefox
+// but let's try to have a fake one.
+function createContentWindow() {
+ const principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.com/"
+ );
+
+ const webnav = Services.appShell.createWindowlessBrowser(false);
+
+ webnav.docShell.createAboutBlankDocumentViewer(principal, principal);
+
+ return webnav.document.defaultView;
+}
+
+// Create a Sandbox as in WebExtension content scripts
+function createContentScriptSandbox() {
+ const window = createContentWindow();
+ // The sandboxPrototype is the key here in order to
+ // make xpc::SandboxWindowOrNull ignore the sandbox
+ // and instead retrieve its prototype and link the error message
+ // to the window instead of the sandbox.
+ return {
+ sandbox: Cu.Sandbox(window, { sandboxPrototype: window }),
+ window,
+ };
+}
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 0000000000..3141d8c2c3
--- /dev/null
+++ b/xpcom/tests/unit/test_debugger_malloc_size_of.js
@@ -0,0 +1,32 @@
+/* -*- 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.
+
+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) {
+ info(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 0000000000..510002bda2
--- /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/. */
+
+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 = Services.dirsvc.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
+ )
+ ) {
+ throw e;
+ }
+ // 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 0000000000..74ea8046d8
--- /dev/null
+++ b/xpcom/tests/unit/test_file_equality.js
@@ -0,0 +1,37 @@
+/* -*- 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 LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
+
+function run_test() {
+ test_normalized_vs_non_normalized();
+}
+
+function test_normalized_vs_non_normalized() {
+ // get a directory that exists on all platforms
+ var tmp1 = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ var exists = tmp1.exists();
+ Assert.ok(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);
+ Assert.ok(tmp1.equals(tmp2));
+
+ // this is a non-normalized version of tmp1, it should not equal tmp1
+ tmp2.appendRelativePath(".");
+ Assert.ok(!tmp1.equals(tmp2));
+
+ // normalize and make sure they are equivalent again
+ tmp2.normalize();
+ Assert.ok(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 0000000000..a6e8633773
--- /dev/null
+++ b/xpcom/tests/unit/test_file_renameTo.js
@@ -0,0 +1,55 @@
+/* -*- 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/. */
+
+function run_test() {
+ // Create the base directory.
+ let base = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ 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");
+ Assert.ok(exists(subdir, "file1.txt"));
+
+ // Test moving across directories
+ tempFile = subdir.clone();
+ tempFile.append("file1.txt");
+ tempFile.renameTo(base, "");
+ Assert.ok(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");
+ Assert.ok(exists(subdir, "file2.txt"));
+
+ // Test moving a directory
+ subdir.renameTo(base, "renamed");
+ Assert.ok(exists(base, "renamed"));
+ let renamed = base.clone();
+ renamed.append("renamed");
+ Assert.ok(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_getTimers.js b/xpcom/tests/unit/test_getTimers.js
new file mode 100644
index 0000000000..58a048a4bc
--- /dev/null
+++ b/xpcom/tests/unit/test_getTimers.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const timerManager = Cc["@mozilla.org/timer-manager;1"].getService(
+ Ci.nsITimerManager
+);
+
+function newTimer(name, delay, type) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(
+ {
+ QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsINamed"]),
+ name,
+ notify: () => {},
+ },
+ delay,
+ type
+ );
+ return timer;
+}
+
+function getTimers() {
+ return timerManager.getTimers().filter(t => {
+ if (t.name == "BackgroundHangThread_timer") {
+ // BHR is Nightly-only, so just ignore it.
+ return false;
+ }
+
+ if (AppConstants.platform == "win" && t.name == "nsAnonTempFileRemover") {
+ // On Windows there's a 3min timer added at startup to then add an
+ // idle observer that finally triggers removing leftover temp files.
+ // Ignore that too.
+ return false;
+ }
+
+ return true;
+ });
+}
+
+function run_test() {
+ {
+ let timers = getTimers();
+ for (let timer of timers) {
+ // Print info about unexpected startup timers to help debugging.
+ info(`${timer.name}: ${timer.delay}ms, ${timer.type}`);
+ }
+ Assert.equal(
+ timers.length,
+ 0,
+ "there should be no timer at xpcshell startup"
+ );
+ }
+
+ let timerData = [
+ ["t1", 500, Ci.nsITimer.TYPE_ONE_SHOT],
+ ["t2", 1500, Ci.nsITimer.TYPE_REPEATING_SLACK],
+ ["t3", 2500, Ci.nsITimer.TYPE_REPEATING_PRECISE],
+ ["t4", 3500, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP],
+ ["t5", 5500, Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY],
+ ["t6", 7500, Ci.nsITimer.TYPE_ONE_SHOT_LOW_PRIORITY],
+ ];
+
+ info("Add timers one at a time.");
+ for (let [name, delay, type] of timerData) {
+ let timer = newTimer(name, delay, type);
+ let timers = getTimers();
+ Assert.equal(timers.length, 1, "there should be only one timer");
+ Assert.equal(name, timers[0].name, "the name is correct");
+ Assert.equal(delay, timers[0].delay, "the delay is correct");
+ Assert.equal(type, timers[0].type, "the type is correct");
+
+ timer.cancel();
+ Assert.equal(getTimers().length, 0, "no timer left after cancelling");
+ }
+
+ info("Add all timers at once.");
+ let timers = [];
+ for (let [name, delay, type] of timerData) {
+ timers.push(newTimer(name, delay, type));
+ }
+ while (timers.length) {
+ Assert.equal(getTimers().length, timers.length, "correct timer count");
+ timers.pop().cancel();
+ }
+ Assert.equal(getTimers().length, 0, "no timer left after cancelling");
+}
diff --git a/xpcom/tests/unit/test_hidden_files.js b/xpcom/tests/unit/test_hidden_files.js
new file mode 100644
index 0000000000..27d87e6f54
--- /dev/null
+++ b/xpcom/tests/unit/test_hidden_files.js
@@ -0,0 +1,24 @@
+const NS_OS_TEMP_DIR = "TmpD";
+
+var hiddenUnixFile;
+function createUNIXHiddenFile() {
+ var tmpDir = Services.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;
+ }
+
+ Assert.ok(createUNIXHiddenFile());
+ Assert.ok(hiddenUnixFile.isHidden());
+}
diff --git a/xpcom/tests/unit/test_home.js b/xpcom/tests/unit/test_home.js
new file mode 100644
index 0000000000..e3a4af9796
--- /dev/null
+++ b/xpcom/tests/unit/test_home.js
@@ -0,0 +1,18 @@
+const CWD = do_get_cwd();
+function checkOS(os) {
+ const nsILocalFile_ = "nsILocalFile" + os;
+ return nsILocalFile_ in Ci && CWD instanceof Ci[nsILocalFile_];
+}
+
+const isWin = checkOS("Win");
+
+function run_test() {
+ var envVar = isWin ? "USERPROFILE" : "HOME";
+
+ var homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
+
+ var expected = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ expected.initWithPath(Services.env.get(envVar));
+
+ Assert.equal(homeDir.path, expected.path);
+}
diff --git a/xpcom/tests/unit/test_iniParser.js b/xpcom/tests/unit/test_iniParser.js
new file mode 100644
index 0000000000..a586c4c060
--- /dev/null
+++ b/xpcom/tests/unit/test_iniParser.js
@@ -0,0 +1,476 @@
+var testnum = 0;
+var factory;
+
+function parserForFile(filename) {
+ let parser = null;
+ try {
+ let file = do_get_file(filename);
+ Assert.ok(!!file);
+ parser = factory.createINIParser(file);
+ Assert.ok(!!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) {
+ Assert.equal(parser, null);
+ Assert.equal(expected, null);
+ return;
+ }
+
+ let output = getParserOutput(parser);
+ for (let section in expected) {
+ Assert.ok(section in output);
+ for (let key in expected[section]) {
+ Assert.ok(key in output[section]);
+ Assert.equal(output[section][key], expected[section][key]);
+ delete output[section][key];
+ }
+ for (let key in output[section]) {
+ Assert.equal(key, "wasn't expecting this key!");
+ }
+ delete output[section];
+ }
+ for (let section in output) {
+ Assert.equal(section, "wasn't expecting this section!");
+ }
+}
+
+function getParserOutput(parser) {
+ let output = {};
+
+ for (let section of parser.getSections()) {
+ Assert.equal(false, section in output); // catch dupes
+ output[section] = {};
+
+ for (let key of parser.getKeys(section)) {
+ Assert.equal(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: {
+ "☺♫": { "♫": "☻", "♪": "♥" },
+ "☼": { "♣": "♠", "♦": "♥" },
+ },
+ },
+ { filename: "data/iniparser17.ini", reference: { section: { key: "" } } },
+ ];
+
+ 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,
+ });
+
+ // Intentional test for appInfo that can't be preloaded.
+ // eslint-disable-next-line mozilla/use-services
+ 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-parser-factory;1"].getService(
+ Ci.nsIINIParserFactory
+ );
+ Assert.ok(!!factory);
+
+ // Test reading from a variety of files and strings. 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;
+ }
+ Assert.ok(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);
+
+ // ensure that `writeString` works correctly
+ Assert.ok(parser instanceof Ci.nsIINIParserWriter);
+ let formatted = parser.writeToString();
+ parser = factory.createINIParser(null);
+ // re-parsing the formatted string is the easiest
+ // way to verify correctness...
+ parser.initFromString(formatted);
+ checkParserOutput(parser, testdata[testnum - 1].reference);
+ }
+
+ 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);
+ }
+ Assert.ok(!newfile.exists());
+
+ try {
+ var parser = factory.createINIParser(newfile);
+ Assert.ok(false, "Should have thrown an exception");
+ } catch (e) {
+ Assert.equal(
+ e.result,
+ Cr.NS_ERROR_FILE_NOT_FOUND,
+ "Caught a file not found exception"
+ );
+ }
+ parser = factory.createINIParser();
+ Assert.ok(!!parser);
+ Assert.ok(parser instanceof Ci.nsIINIParserWriter);
+ checkParserOutput(parser, {});
+ parser.writeFile(newfile);
+ Assert.ok(newfile.exists());
+
+ // test adding a new section and new key
+ parser.setString("section", "key", "value");
+ parser.setString("section", "key2", "");
+ parser.writeFile(newfile);
+ Assert.ok(newfile.exists());
+ checkParserOutput(parser, { section: { key: "value", key2: "" } });
+ // read it in again, check for same data.
+ parser = parserForFile("data/nonexistent-file.ini");
+ checkParserOutput(parser, { section: { key: "value", key2: "" } });
+ // 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" } });
+
+ Assert.ok(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 = null;
+ checkParserOutput(parser, { section1: { name1: "value2" } });
+
+ // Bad characters in section name
+ try {
+ parser.setString("bad\0", "ok", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("bad\r", "ok", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("bad\n", "ok", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("bad[", "ok", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("bad]", "ok", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("", "ok", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+
+ // Bad characters in key name
+ caughtError = null;
+ try {
+ parser.setString("ok", "bad\0", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "bad\r", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "bad\n", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "bad=", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "", "ok");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+
+ // Bad characters in value
+ caughtError = null;
+ try {
+ parser.setString("ok", "ok", "bad\0");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "ok", "bad\r");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "ok", "bad\n");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(caughtError);
+ Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG);
+ caughtError = null;
+ try {
+ parser.setString("ok", "ok", "good=");
+ } catch (e) {
+ caughtError = e;
+ }
+ Assert.ok(!caughtError);
+ caughtError = null;
+ } catch (e) {
+ throw new Error(`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 0000000000..e269b424a2
--- /dev/null
+++ b/xpcom/tests/unit/test_ioutil.js
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+
+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) {
+ Assert.equal(e.result, Cr.NS_ERROR_INVALID_POINTER);
+ }
+
+ try {
+ util.outputStreamIsBuffered(null);
+ do_throw("outputStreamIsBuffered should have thrown");
+ } catch (e) {
+ Assert.equal(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);
+ Assert.equal(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 0000000000..c90d91b278
--- /dev/null
+++ b/xpcom/tests/unit/test_localfile.js
@@ -0,0 +1,288 @@
+/* -*- 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 { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const MAX_TIME_DIFFERENCE = 2500;
+const MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
+
+var LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
+
+function sleep(ms) {
+ // We are measuring timestamps, which are slightly fuzzed, and just need to
+ // measure that they are increasing.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+add_task(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
+ Assert.ok(lf.path.length == 2);
+
+ Assert.ok(lf.parent === null);
+ } catch (e) {
+ // not Windows
+ Assert.equal(e.result, Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
+ }
+});
+
+add_task(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
+add_task(async function test_file_modification_time() {
+ let file = do_get_profile();
+ file.append("testfile");
+
+ // Should never happen but get rid of it anyway
+ if (file.exists()) {
+ file.remove(true);
+ }
+
+ const now = Date.now();
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ Assert.ok(file.exists());
+
+ const atime = file.lastAccessedTime;
+
+ // 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.
+ let diff = Math.abs(file.lastModifiedTime - now);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ const yesterday = now - MILLIS_PER_DAY;
+ file.lastModifiedTime = yesterday;
+
+ diff = Math.abs(file.lastModifiedTime - yesterday);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+ Assert.equal(
+ file.lastAccessedTime,
+ atime,
+ "Setting lastModifiedTime should not set lastAccessedTime"
+ );
+
+ const tomorrow = now + MILLIS_PER_DAY;
+ file.lastModifiedTime = tomorrow;
+
+ diff = Math.abs(file.lastModifiedTime - tomorrow);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ const bug377307 = 1172950238000;
+ file.lastModifiedTime = bug377307;
+
+ diff = Math.abs(file.lastModifiedTime - bug377307);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ await sleep(1000);
+
+ file.lastModifiedTime = 0;
+ Assert.greater(
+ file.lastModifiedTime,
+ now,
+ "Setting lastModifiedTime to 0 should set it to current date and time"
+ );
+
+ file.remove(true);
+});
+
+add_task(async function test_lastAccessedTime() {
+ const file = do_get_profile();
+
+ file.append("test-atime");
+ if (file.exists()) {
+ file.remove(true);
+ }
+
+ const now = Date.now();
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ Assert.ok(file.exists());
+
+ const mtime = file.lastModifiedTime;
+
+ // 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.
+ let diff = Math.abs(file.lastModifiedTime - now);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ const yesterday = now - MILLIS_PER_DAY;
+ file.lastAccessedTime = yesterday;
+
+ diff = Math.abs(file.lastAccessedTime - yesterday);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE, `${diff} < ${MAX_TIME_DIFFERENCE}`);
+ Assert.equal(
+ file.lastModifiedTime,
+ mtime,
+ "Setting lastAccessedTime should not set lastModifiedTime"
+ );
+
+ const tomorrow = now + MILLIS_PER_DAY;
+ file.lastAccessedTime = tomorrow;
+
+ diff = Math.abs(file.lastAccessedTime - tomorrow);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ const bug377307 = 1172950238000;
+ file.lastAccessedTime = bug377307;
+
+ diff = Math.abs(file.lastAccessedTime - bug377307);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ await sleep(1000);
+
+ file.lastAccessedTime = 0;
+ Assert.greater(
+ file.lastAccessedTime,
+ now,
+ "Setting lastAccessedTime to 0 should set it to the current date and time"
+ );
+});
+
+// Tests that changing a directory's modification time is possible
+add_task(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);
+ Assert.ok(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);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ var yesterday = now - MILLIS_PER_DAY;
+ dir.lastModifiedTime = yesterday;
+
+ diff = Math.abs(dir.lastModifiedTime - yesterday);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ var tomorrow = now - MILLIS_PER_DAY;
+ dir.lastModifiedTime = tomorrow;
+
+ diff = Math.abs(dir.lastModifiedTime - tomorrow);
+ Assert.ok(diff < MAX_TIME_DIFFERENCE);
+
+ dir.remove(true);
+});
+
+add_task(function test_diskSpaceAvailable() {
+ let file = do_get_profile();
+ file.QueryInterface(Ci.nsIFile);
+
+ let bytes = file.diskSpaceAvailable;
+ Assert.ok(bytes > 0);
+
+ file.append("testfile");
+ if (file.exists()) {
+ file.remove(true);
+ }
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+ bytes = file.diskSpaceAvailable;
+ Assert.ok(bytes > 0);
+
+ file.remove(true);
+});
+
+add_task(function test_diskCapacity() {
+ let file = do_get_profile();
+ file.QueryInterface(Ci.nsIFile);
+
+ const startBytes = file.diskCapacity;
+ Assert.ok(!!startBytes); // Not 0, undefined etc.
+
+ file.append("testfile");
+ if (file.exists()) {
+ file.remove(true);
+ }
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+ const endBytes = file.diskCapacity;
+ Assert.ok(!!endBytes); // Not 0, undefined etc.
+ Assert.ok(startBytes === endBytes);
+
+ file.remove(true);
+});
+
+add_task(
+ {
+ // nsIFile::CreationTime is only supported on macOS and Windows.
+ skip_if: () => !["macosx", "win"].includes(AppConstants.platform),
+ },
+ function test_file_creation_time() {
+ const file = do_get_profile();
+ // If we re-use the same file name from the other tests, even if the
+ // file.exists() check fails at 165, this test will likely fail due to the
+ // creation time being copied over from the previous instance of the file on
+ // Windows.
+ file.append("testfile-creation-time");
+
+ if (file.exists()) {
+ file.remove(true);
+ }
+
+ const now = Date.now();
+
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ Assert.ok(file.exists());
+
+ const creationTime = file.creationTime;
+ Assert.ok(creationTime === file.lastModifiedTime);
+
+ file.lastModifiedTime = now + MILLIS_PER_DAY;
+
+ Assert.ok(creationTime !== file.lastModifiedTime);
+ Assert.ok(creationTime === file.creationTime);
+
+ file.remove(true);
+ }
+);
+
+add_task(function test_file_append_parent() {
+ const SEPARATOR = AppConstants.platform === "win" ? "\\" : "/";
+
+ const file = do_get_profile();
+
+ Assert.throws(
+ () => file.append(".."),
+ /NS_ERROR_FILE_UNRECOGNIZED_PATH/,
+ `nsLocalFile::Append("..") throws`
+ );
+
+ Assert.throws(
+ () => file.appendRelativePath(".."),
+ /NS_ERROR_FILE_UNRECOGNIZED_PATH/,
+ `nsLocalFile::AppendRelativePath("..") throws`
+ );
+
+ Assert.throws(
+ () => file.appendRelativePath(`foo${SEPARATOR}..${SEPARATOR}baz`),
+ /NS_ERROR_FILE_UNRECOGNIZED_PATH/,
+ `nsLocalFile::AppendRelativePath(path) fails when path contains ".."`
+ );
+});
diff --git a/xpcom/tests/unit/test_mac_bundle.js b/xpcom/tests/unit/test_mac_bundle.js
new file mode 100644
index 0000000000..6703e8a2b8
--- /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");
+
+ Assert.ok(keynoteBundle instanceof Ci.nsILocalFileMac);
+ Assert.ok(appBundle instanceof Ci.nsILocalFileMac);
+
+ Assert.ok(keynoteBundle.isPackage());
+ Assert.ok(appBundle.isPackage());
+}
diff --git a/xpcom/tests/unit/test_mac_xattrs.js b/xpcom/tests/unit/test_mac_xattrs.js
new file mode 100644
index 0000000000..b387358d74
--- /dev/null
+++ b/xpcom/tests/unit/test_mac_xattrs.js
@@ -0,0 +1,98 @@
+/* -*- 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 { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+
+const ATTR = "bogus.attr";
+const VALUE = new TextEncoder().encode("bogus");
+
+async function run_test() {
+ const path = PathUtils.join(
+ Services.dirsvc.get("TmpD", Ci.nsIFile).path,
+ "macos-xattrs.tmp.d"
+ );
+ await IOUtils.makeDirectory(path);
+
+ try {
+ await test_macos_xattr(path);
+ } finally {
+ await IOUtils.remove(path, { recursive: true });
+ }
+}
+
+async function test_macos_xattr(tmpDir) {
+ const path = PathUtils.join(tmpDir, "file.tmp");
+
+ await IOUtils.writeUTF8(path, "");
+
+ const file = new FileUtils.File(path);
+ file.queryInterface(Cc.nsILocalFileMac);
+
+ Assert.ok(!file.exists(), "File should not exist");
+
+ info("Testing reading an attribute on a file that does not exist");
+ Assert.throws(
+ () => file.hasXAttr(ATTR),
+ /NS_ERROR_FILE_NOT_FOUND/,
+ "Non-existant files can't have attributes checked"
+ );
+ Assert.throws(
+ () => file.getXAttr(ATTR),
+ /NS_ERROR_FILE_NOT_FOUND/,
+ "Non-existant files can't have attributes read"
+ );
+
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ Assert.ok(file.exists(), "File exists after creation");
+
+ info("Testing reading an attribute that does not exist");
+ Assert.ok(
+ !file.hasXAttr(ATTR),
+ "File should not have attribute before being set."
+ );
+ Assert.throws(
+ () => file.getXAttr(ATTR),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Attempting to get an attribute that does not exist throws"
+ );
+
+ {
+ info("Testing setting and reading an attribute");
+ file.setXAttr(ATTR, VALUE);
+ Assert.ok(
+ file.hasXAttr(ATTR),
+ "File should have attribute after being set"
+ );
+ const result = file.getXAttr(ATTR);
+
+ Assert.deepEqual(
+ Array.from(result),
+ Array.from(VALUE),
+ "File should have attribute value matching what was set"
+ );
+ }
+
+ info("Testing removing an attribute");
+ file.delXAttr(ATTR);
+ Assert.ok(
+ !file.hasXAttr(ATTR),
+ "File should no longer have the attribute after removal"
+ );
+ Assert.throws(
+ () => file.getXAttr(ATTR),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Attempting to get an attribute after removal results in an error"
+ );
+
+ info("Testing removing an attribute that does not exist");
+ Assert.throws(
+ () => file.delXAttr(ATTR),
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Attempting to remove an attribute that does not exist throws"
+ );
+}
diff --git a/xpcom/tests/unit/test_notxpcom_scriptable.js b/xpcom/tests/unit/test_notxpcom_scriptable.js
new file mode 100644
index 0000000000..5362894b70
--- /dev/null
+++ b/xpcom/tests/unit/test_notxpcom_scriptable.js
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+const kCID = Components.ID("{1f9f7181-e6c5-4f4c-8f71-08005cec8468}");
+const kContract = "@testing/notxpcomtest";
+
+function run_test() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ ok(Ci.nsIScriptableWithNotXPCOM);
+
+ let method1Called = false;
+
+ let testObject = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIScriptableOK",
+ "nsIScriptableWithNotXPCOM",
+ ]),
+
+ method1() {
+ method1Called = true;
+ },
+
+ method2() {
+ ok(false, "method2 should not have been called!");
+ },
+
+ method3() {
+ ok(false, "mehod3 should not have been called!");
+ },
+
+ jsonly: true,
+ };
+
+ let factory = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
+
+ createInstance(iid) {
+ return testObject.QueryInterface(iid);
+ },
+ };
+
+ registrar.registerFactory(kCID, null, kContract, factory);
+
+ let xpcomObject = Cc[kContract].createInstance();
+ ok(xpcomObject);
+ strictEqual(xpcomObject.jsonly, undefined);
+
+ xpcomObject.QueryInterface(Ci.nsIScriptableOK);
+
+ xpcomObject.method1();
+ ok(method1Called);
+
+ try {
+ xpcomObject.QueryInterface(Ci.nsIScriptableWithNotXPCOM);
+ ok(false, "Should not have implemented nsIScriptableWithNotXPCOM");
+ } catch (e) {
+ ok(
+ true,
+ "Should not have implemented nsIScriptableWithNotXPCOM. Correctly threw error: " +
+ e
+ );
+ }
+ strictEqual(xpcomObject.method2, undefined);
+}
diff --git a/xpcom/tests/unit/test_nsIMutableArray.js b/xpcom/tests/unit/test_nsIMutableArray.js
new file mode 100644
index 0000000000..525196a48f
--- /dev/null
+++ b/xpcom/tests/unit/test_nsIMutableArray.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 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);
+ }
+ return arr;
+}
+
+function test_appending_null_actually_inserts() {
+ var arr = new MutableArray();
+ Assert.equal(0, arr.length);
+ arr.appendElement(null);
+ Assert.equal(1, arr.length);
+}
+
+function test_object_gets_appended() {
+ var arr = new MutableArray();
+ var str = new SupportsString();
+ str.data = "hello";
+ arr.appendElement(str);
+ Assert.equal(1, arr.length);
+ var obj = arr.queryElementAt(0, Ci.nsISupportsString);
+ Assert.equal(str, obj);
+}
+
+function test_insert_at_beginning() {
+ var arr = create_n_element_array(5);
+ // just a sanity check
+ Assert.equal(5, arr.length);
+ var str = new SupportsString();
+ str.data = "hello";
+ arr.insertElementAt(str, 0);
+ Assert.equal(6, arr.length);
+ var obj = arr.queryElementAt(0, Ci.nsISupportsString);
+ Assert.equal(str, obj);
+ // check the data of all the other objects
+ for (let i = 1; i < arr.length; i++) {
+ let obj2 = arr.queryElementAt(i, Ci.nsISupportsString);
+ Assert.equal("element " + (i - 1), obj2.data);
+ }
+}
+
+function test_replace_element() {
+ var arr = create_n_element_array(5);
+ // just a sanity check
+ Assert.equal(5, arr.length);
+ var str = new SupportsString();
+ str.data = "hello";
+ // replace first element
+ arr.replaceElementAt(str, 0);
+ Assert.equal(5, arr.length);
+ var obj = arr.queryElementAt(0, Ci.nsISupportsString);
+ Assert.equal(str, obj);
+ // replace last element
+ arr.replaceElementAt(str, arr.length - 1);
+ Assert.equal(5, arr.length);
+ obj = arr.queryElementAt(arr.length - 1, Ci.nsISupportsString);
+ Assert.equal(str, obj);
+ // replace after last element, should insert empty elements
+ arr.replaceElementAt(str, 9);
+ Assert.equal(10, arr.length);
+ obj = arr.queryElementAt(9, Ci.nsISupportsString);
+ Assert.equal(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
+ Assert.equal(5, arr.length);
+ arr.clear();
+ Assert.equal(0, arr.length);
+}
+
+function test_enumerate() {
+ var arr = create_n_element_array(5);
+ Assert.equal(5, arr.length);
+ var i = 0;
+ for (let str of arr.enumerate()) {
+ Assert.ok(str instanceof Ci.nsISupportsString);
+ Assert.equal(str.data, "element " + i);
+ i++;
+ }
+ Assert.equal(arr.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.
+ Assert.equal(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);
+ Assert.notEqual(elm, null);
+ let str = elm.QueryInterface(Ci.nsISupportsString);
+ Assert.notEqual(str, null);
+ Assert.equal(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_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 0000000000..582d10440c
--- /dev/null
+++ b/xpcom/tests/unit/test_nsIProcess.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/. */
+// 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 = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ Assert.ok(!process.isRunning);
+
+ try {
+ process.kill();
+ do_throw("Attempting to kill a not-running process should throw");
+ } catch (e) {}
+
+ process.run(false, [], 0);
+
+ Assert.ok(process.isRunning);
+
+ process.kill();
+
+ Assert.ok(!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 = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ // to get an exit value it must be a blocking process
+ process.run(true, [], 0);
+
+ Assert.equal(process.exitValue, 42);
+}
+
+function test_args(file, args, argsAreASCII) {
+ var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ if (argsAreASCII) {
+ process.run(true, args, args.length);
+ } else {
+ process.runw(true, args, args.length);
+ }
+
+ Assert.equal(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 = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ process.runAsync([], 0, {
+ observe(subject, topic, data) {
+ process = subject.QueryInterface(Ci.nsIProcess);
+ Assert.equal(topic, "process-failed");
+ Assert.equal(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 = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ process.runAsync(TEST_ARGS, TEST_ARGS.length, {
+ observe(subject, topic, data) {
+ process = subject.QueryInterface(Ci.nsIProcess);
+ Assert.equal(topic, "process-finished");
+ Assert.equal(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 = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ process.runAsync([], 0, {
+ observe(subject, topic, data) {
+ process = subject.QueryInterface(Ci.nsIProcess);
+ Assert.equal(topic, "process-failed");
+ 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 0000000000..802f9d70aa
--- /dev/null
+++ b/xpcom/tests/unit/test_nsIProcess_stress.js
@@ -0,0 +1,24 @@
+function run_test() {
+ set_process_running_environment();
+
+ var file = get_test_program("TestQuickReturn");
+ var tm = Cc["@mozilla.org/thread-manager;1"].getService();
+
+ for (var i = 0; i < 1000; i++) {
+ var process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.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.
+ tm.spinEventLoopUntilEmpty();
+ }
+}
diff --git a/xpcom/tests/unit/test_pipe.js b/xpcom/tests/unit/test_pipe.js
new file mode 100644
index 0000000000..e3ccc6ef3f
--- /dev/null
+++ b/xpcom/tests/unit/test_pipe.js
@@ -0,0 +1,55 @@
+/* -*- 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 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;
+ dump("dummy: " + dummy + "\n");
+ throw Components.Exception("", 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);
+ Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE));
+ Assert.ok(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);
+ Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE));
+ Assert.ok(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);
+ Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE));
+ Assert.ok(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);
+ Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE));
+ Assert.ok(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 0000000000..b1975a43da
--- /dev/null
+++ b/xpcom/tests/unit/test_process_directives.js
@@ -0,0 +1,20 @@
+function categoryExists(category, entry) {
+ try {
+ Services.catMan.getCategoryEntry(category, entry);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+function run_test() {
+ Components.manager.autoRegister(
+ do_get_file("data/process_directive.manifest")
+ );
+
+ let isChild =
+ Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+ Assert.equal(categoryExists("directives-test", "main-process"), !isChild);
+ Assert.equal(categoryExists("directives-test", "content-process"), isChild);
+}
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 0000000000..dca63b3563
--- /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 0000000000..7a469d9f48
--- /dev/null
+++ b/xpcom/tests/unit/test_seek_multiplex.js
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// 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"
+ );
+ Assert.equal(1, 1);
+
+ 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);
+ Assert.equal(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);
+ Assert.equal(seekable.tell(), 2);
+ Assert.equal(seekable.available(), 98);
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 9);
+ Assert.equal(seekable.tell(), 9);
+ Assert.equal(seekable.available(), 91);
+ // Seek across stream boundary.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 35);
+ Assert.equal(seekable.tell(), 35);
+ Assert.equal(seekable.available(), 65);
+ readData = sis.read(5);
+ Assert.equal(readData, data.slice(5));
+ Assert.equal(seekable.available(), 60);
+ // Seek at stream boundaries.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 40);
+ Assert.equal(seekable.tell(), 40);
+ Assert.equal(seekable.available(), 60);
+ readData = sis.read(10);
+ Assert.equal(readData, data);
+ Assert.equal(seekable.tell(), 50);
+ Assert.equal(seekable.available(), 50);
+ // Rewind and read across streams.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 39);
+ Assert.equal(seekable.tell(), 39);
+ Assert.equal(seekable.available(), 61);
+ readData = sis.read(11);
+ Assert.equal(readData, data.slice(9) + data);
+ Assert.equal(seekable.tell(), 50);
+ Assert.equal(seekable.available(), 50);
+ // Rewind to the beginning.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ Assert.equal(seekable.tell(), 0);
+ Assert.equal(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);
+ Assert.equal(seekable.tell(), 65);
+ Assert.equal(seekable.available(), 35);
+ readData = sis.read(10);
+ Assert.equal(readData, data.slice(5) + data.slice(0, 5));
+ Assert.equal(seekable.tell(), 75);
+ Assert.equal(seekable.available(), 25);
+ // Negative seek.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15);
+ Assert.equal(seekable.tell(), 60);
+ Assert.equal(seekable.available(), 40);
+ readData = sis.read(10);
+ Assert.equal(readData, data);
+ Assert.equal(seekable.tell(), 70);
+ Assert.equal(seekable.available(), 30);
+
+ // -- Tests for NS_SEEK_END
+ // Normal read.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -5);
+ Assert.equal(seekable.tell(), data.length * count - 5);
+ readData = sis.read(5);
+ Assert.equal(readData, data.slice(5));
+ Assert.equal(seekable.tell(), data.length * count);
+ // Read across streams.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -15);
+ Assert.equal(seekable.tell(), data.length * count - 15);
+ readData = sis.read(15);
+ Assert.equal(readData, data.slice(5) + data);
+ Assert.equal(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;
+ }
+ Assert.equal(caught, true);
+ Assert.equal(seekable.tell(), data.length * count);
+ // Backward seek from the beginning, should be clamped.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ Assert.equal(seekable.tell(), 0);
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15);
+ Assert.equal(seekable.tell(), 0);
+ // Seek too far: should be clamped.
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ Assert.equal(seekable.tell(), 0);
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 3 * data.length * count);
+ Assert.equal(seekable.tell(), 100);
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, data.length * count);
+ Assert.equal(seekable.tell(), 100);
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -2 * data.length * count);
+ Assert.equal(seekable.tell(), 0);
+}
+
+function test_multiplex_bug797871() {
+ var data2 = "1234567890123456789012345678901234567890";
+
+ var MultiplexStream = CC(
+ "@mozilla.org/io/multiplex-input-stream;1",
+ "nsIMultiplexInputStream"
+ );
+ Assert.equal(1, 1);
+
+ var multiplex = new MultiplexStream();
+ let s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ s.setData(data2, data2.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);
+ Assert.equal(seekable.tell(), 8);
+ sis.read(2);
+ Assert.equal(seekable.tell(), 10);
+
+ seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 20);
+ Assert.equal(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 0000000000..2a0fb1b569
--- /dev/null
+++ b/xpcom/tests/unit/test_storagestream.js
@@ -0,0 +1,152 @@
+/* -*- 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/. */
+
+/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "unusedVariable" }] */
+
+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 unusedVariable = ss.getOutputStream(0);
+ var inp2 = ss.newInputStream(0);
+ Assert.equal(inp2.available(), 0);
+ Assert.ok(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;
+ } else {
+ throw ex;
+ }
+ }
+ Assert.ok(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 {
+ 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);
+ }
+
+ Assert.ok(inp.isNonBlocking(), "next test expects a non-blocking stream");
+
+ try {
+ var threw = false;
+ var bis = BIS(inp);
+ bis.readByteArray(5);
+ } catch (e) {
+ if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ do_throw("wrong error thrown: " + e);
+ }
+ threw = true;
+ }
+ Assert.ok(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);
+ bos.close();
+
+ var inp = ss.newInputStream(0);
+ var bis = BIS(inp);
+
+ var count = 0;
+ while (count < bytes.length) {
+ var data = bis.read8(1);
+ Assert.equal(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 0000000000..c0c644bf19
--- /dev/null
+++ b/xpcom/tests/unit/test_streams.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 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.from(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);
+ Assert.equal(is.readBoolean(), true);
+ os.write8(4);
+ Assert.equal(is.read8(), 4);
+ os.write16(4);
+ Assert.equal(is.read16(), 4);
+ os.write16(1024);
+ Assert.equal(is.read16(), 1024);
+ os.write32(7);
+ Assert.equal(is.read32(), 7);
+ os.write32(LargeNum);
+ Assert.equal(is.read32(), LargeNum);
+ os.write64(LargeNum);
+ Assert.equal(is.read64(), LargeNum);
+ os.write64(1024);
+ Assert.equal(is.read64(), 1024);
+ os.write64(HugeNum);
+ Assert.equal(is.read64(), HugeNum);
+ os.writeFloat(2.5);
+ Assert.equal(is.readFloat(), 2.5);
+ // os.writeDouble(Math.SQRT2);
+ // do_check_eq(is.readDouble(), Math.SQRT2);
+ os.writeStringZ("Mozilla");
+ Assert.equal(is.readCString(), "Mozilla");
+ os.writeWStringZ("Gecko");
+ Assert.equal(is.readString(), "Gecko");
+ os.writeBytes(HelloStr, HelloStr.length);
+ Assert.equal(is.available(), HelloStr.length);
+ msg = is.readBytes(HelloStr.length);
+ Assert.equal(msg, HelloStr);
+ msg = null;
+ countObj.value = -1;
+ os.writeByteArray(HelloArray);
+ Assert.equal(is.available(), HelloStr.length);
+ msg = is.readByteArray(HelloStr.length);
+ Assert.equal(typeof msg, typeof HelloArray);
+ Assert.equal(msg.toSource(), HelloArray.toSource());
+ Assert.equal(is.available(), 0);
+ os.writeByteArray(HelloArray);
+ Assert.equal(
+ is.readArrayBuffer(buffer.byteLength, buffer),
+ HelloArray.length
+ );
+ Assert.equal([...new Uint8Array(buffer)].toSource(), HelloArray.toSource());
+ Assert.equal(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);
+ // Available should not be zero after a long write like this.
+ Assert.notEqual(is.available(), 0);
+
+ // Test reading in one big chunk.
+ Assert.equal(is.readBoolean(), true);
+ Assert.equal(is.read8(), 4);
+ Assert.equal(is.read16(), 4);
+ Assert.equal(is.read16(), 1024);
+ Assert.equal(is.read32(), 7);
+ Assert.equal(is.read32(), LargeNum);
+ Assert.equal(is.read64(), LargeNum);
+ Assert.equal(is.read64(), 1024);
+ Assert.equal(is.read64(), HugeNum);
+ Assert.equal(is.readFloat(), 2.5);
+ // do_check_eq(is.readDouble(), Math.SQRT2);
+ Assert.equal(is.readCString(), "Mozilla");
+ Assert.equal(is.readString(), "Gecko");
+ // Remember, we wrote HelloStr twice - once as a string, and then as an array.
+ Assert.equal(is.available(), HelloStr.length * 2);
+ msg = is.readBytes(HelloStr.length);
+ Assert.equal(msg, HelloStr);
+ msg = null;
+ countObj.value = -1;
+ Assert.equal(is.available(), HelloStr.length);
+ msg = is.readByteArray(HelloStr.length);
+ Assert.equal(typeof msg, typeof HelloArray);
+ Assert.equal(msg.toSource(), HelloArray.toSource());
+ Assert.equal(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)
+ ) {
+ throw e;
+ }
+ // do nothing
+ }
+
+ try {
+ is.available();
+ do_throw("Not reached!");
+ } catch (e) {
+ if (
+ !(e instanceof Ci.nsIException && e.result == Cr.NS_BASE_STREAM_CLOSED)
+ ) {
+ throw e;
+ }
+ // do nothing
+ }
+
+ try {
+ is.readBoolean();
+ do_throw("Not reached!");
+ } catch (e) {
+ if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) {
+ throw e;
+ }
+ // 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 0000000000..0b9dcf3c5e
--- /dev/null
+++ b/xpcom/tests/unit/test_stringstream.js
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+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);
+ Assert.equal(s.available(), body.length);
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sis.init(s);
+
+ Assert.equal(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 0000000000..8c0d25f792
--- /dev/null
+++ b/xpcom/tests/unit/test_symlinks.js
@@ -0,0 +1,139 @@
+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 = Ci.nsIFile;
+
+var process;
+function createSymLink(from, to) {
+ if (!process) {
+ var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ ln.initWithPath("/bin/ln");
+
+ process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(ln);
+ }
+
+ const args = ["-s", from, to];
+ process.run(true, args, args.length);
+ Assert.equal(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);
+ }
+
+ Assert.ok(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.
+ Assert.equal(from.target, to.target);
+ } else {
+ Assert.equal(from.path, to.target);
+ }
+
+ return to;
+}
+
+function setupTestDir(testDir, relative) {
+ var targetDir = testDir.clone();
+ targetDir.append(DIR_TARGET);
+
+ if (testDir.exists()) {
+ testDir.remove(true);
+ }
+ Assert.ok(!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);
+ Assert.ok(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.nextFile;
+ const name = file.leafName;
+ print(
+ name +
+ spaces.substring(name.length) +
+ bools[file.isDirectory()] +
+ bools[file.isFile()] +
+ bools[file.isSymlink()]
+ );
+ Assert.equal(file.isDirectory(), dirs.includes(name));
+ Assert.equal(file.isFile(), files.includes(name));
+ Assert.equal(file.isSymlink(), links.includes(name));
+ }
+}
+
+function run_test() {
+ 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 0000000000..6ab67796c9
--- /dev/null
+++ b/xpcom/tests/unit/test_systemInfo.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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",
+ "arch",
+ "version",
+ "pagesize",
+ "pageshift",
+ "memmapalign",
+ "memsize",
+ ];
+ let sysInfo = Services.sysinfo;
+
+ PROPERTIES.forEach(function (aPropertyName) {
+ print("Testing property: " + aPropertyName);
+ let value = sysInfo.getProperty(aPropertyName);
+ Assert.ok(!!value);
+ });
+
+ // This property must exist, but its value might be zero.
+ print("Testing property: umask");
+ Assert.equal(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 0000000000..5744d62721
--- /dev/null
+++ b/xpcom/tests/unit/test_versioncomparator.js
@@ -0,0 +1,55 @@
+// Versions to test listed in ascending order, none can be equal
+var comparisons = [
+ "pre",
+ // A number that is too large to be supported should be normalized to 0.
+ String(0x1f0000000),
+ "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() {
+ for (var i = 0; i < comparisons.length; i++) {
+ for (var j = 0; j < comparisons.length; j++) {
+ var result = Services.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 (Services.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 0000000000..318f93ebec
--- /dev/null
+++ b/xpcom/tests/unit/test_windows_cmdline_file.js
@@ -0,0 +1,22 @@
+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;
+ info(`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 0000000000..691c0461c3
--- /dev/null
+++ b/xpcom/tests/unit/test_windows_registry.js
@@ -0,0 +1,204 @@
+/* -*- 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 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);
+
+ //* clean up
+ cleanup_test_run(testKey, keyName);
+}
+
+function setup_test_run(testKey, keyName) {
+ info("Setup test run");
+ try {
+ testKey.open(
+ nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ keyName,
+ nsIWindowsRegKey.ACCESS_READ
+ );
+ info("Test key exists. Needs cleanup.");
+ cleanup_test_run(testKey, keyName);
+ } catch (e) {
+ if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) {
+ throw e;
+ }
+ }
+
+ 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)) {
+ throw e;
+ }
+ }
+
+ 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)) {
+ throw e;
+ }
+ }
+
+ 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)) {
+ throw e;
+ }
+ }
+
+ 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)) {
+ throw e;
+ }
+ }
+}
+
+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 cleanup_test_run(testKey, keyName) {
+ info("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 = 9223372036854775000;
+const TESTDATA_BINARYNAME = "ABinary";
+const TESTDATA_BINARYVALUE = "She sells seashells by the seashore";
+const TESTDATA_CHILD_KEY = "TestChildKey";
diff --git a/xpcom/tests/unit/xpcshell.toml b/xpcom/tests/unit/xpcshell.toml
new file mode 100644
index 0000000000..a542804b64
--- /dev/null
+++ b/xpcom/tests/unit/xpcshell.toml
@@ -0,0 +1,113 @@
+[DEFAULT]
+head = "head_xpcom.js"
+support-files = [
+ "data/**",
+ "xpcomtest.xpt",
+]
+generated-files = "xpcomtest.xpt"
+
+["test_bug121341.js"]
+
+["test_bug325418.js"]
+
+["test_bug332389.js"]
+
+["test_bug333505.js"]
+
+["test_bug364285-1.js"]
+
+["test_bug374754.js"]
+
+["test_bug476919.js"]
+# Creating a symlink requires admin or developer mode on Windows.
+skip-if = ["os == 'win'"]
+# Bug 676998: test fails consistently on Android
+fail-if = ["os == 'android'"]
+
+["test_bug478086.js"]
+
+["test_bug745466.js"]
+skip-if = ["os == 'win'"]
+# Bug 676998: test fails consistently on Android
+fail-if = ["os == 'android'"]
+
+["test_bug1434856.js"]
+
+["test_console_service_callFunctionAndLogException.js"]
+
+["test_debugger_malloc_size_of.js"]
+
+["test_file_createUnique.js"]
+
+["test_file_equality.js"]
+
+["test_file_renameTo.js"]
+
+["test_getTimers.js"]
+skip-if = ["os == 'android'"]
+
+["test_hidden_files.js"]
+
+["test_home.js"]
+# Bug 676998: test fails consistently on Android
+fail-if = ["os == 'android'"]
+
+["test_iniParser.js"]
+
+["test_ioutil.js"]
+
+["test_localfile.js"]
+
+["test_mac_bundle.js"]
+
+["test_mac_xattrs.js"]
+run-if = ["os == 'mac'"]
+
+["test_notxpcom_scriptable.js"]
+
+["test_nsIMutableArray.js"]
+
+["test_nsIProcess.js"]
+skip-if = [
+ "os == 'win'", # Bug 1325609
+ "os == 'linux'", # Bug 676998
+ "os == 'android'", # Bug 1631671
+]
+
+["test_nsIProcess_stress.js"]
+skip-if = [
+ "os == 'win'", # bug 676412, test isn't needed on windows and runs really slowly
+ "ccov", # bug 1394989
+]
+
+["test_pipe.js"]
+
+["test_process_directives.js"]
+
+["test_process_directives_child.js"]
+skip-if = ["os == 'android'"]
+
+["test_seek_multiplex.js"]
+
+["test_storagestream.js"]
+
+["test_streams.js"]
+
+["test_stringstream.js"]
+
+["test_symlinks.js"]
+# Creating a symlink requires admin or developer mode on Windows.
+skip-if = ["os == 'win'"]
+# Bug 676998: test fails consistently on Android
+fail-if = ["os == 'android'"]
+
+["test_systemInfo.js"]
+
+["test_versioncomparator.js"]
+skip-if = ["os == 'win' && asan"] # Bug 1763002
+
+["test_windows_cmdline_file.js"]
+run-if = ["os == 'win'"]
+
+["test_windows_registry.js"]
+run-if = ["os == 'win'"]
diff --git a/xpcom/tests/windows/TestCOM.cpp b/xpcom/tests/windows/TestCOM.cpp
new file mode 100644
index 0000000000..49f67db835
--- /dev/null
+++ b/xpcom/tests/windows/TestCOM.cpp
@@ -0,0 +1,97 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/RefPtr.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() override { 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;
+}
+
+TEST(TestCOM, WindowsInterop)
+{
+ // Test that we can QI an nsITestCom to an IUnknown.
+ RefPtr<nsTestCom> t = new nsTestCom();
+ IUnknown* iUnknown = nullptr;
+ nsresult rv = t->QueryInterface(NS_GET_IID(nsISupports), (void**)&iUnknown);
+ ASSERT_NS_SUCCEEDED(rv);
+ ASSERT_TRUE(iUnknown);
+
+ // Test we can QI an IUnknown to nsITestCom.
+ nsCOMPtr<nsITestCom> iTestCom;
+ GUID testGUID = NS_ITEST_COM_IID;
+ HRESULT hr = iUnknown->QueryInterface(testGUID, getter_AddRefs(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_NS_SUCCEEDED(rv);
+
+ iUnknown->Release();
+ iTestCom = nullptr;
+ t = nullptr;
+
+ ASSERT_EQ(nsTestCom::sDestructions, 1);
+}
diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp
new file mode 100644
index 0000000000..ac29ddac50
--- /dev/null
+++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp
@@ -0,0 +1,176 @@
+/* -*- 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:
+ explicit 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(u"\\\\127.0.0.1\\C$"_ns);
+ // 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/TestPoisonIOInterposer.cpp b/xpcom/tests/windows/TestPoisonIOInterposer.cpp
new file mode 100644
index 0000000000..9b29307f34
--- /dev/null
+++ b/xpcom/tests/windows/TestPoisonIOInterposer.cpp
@@ -0,0 +1,155 @@
+/* -*- 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 "gtest/gtest.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/NativeNt.h"
+#include "nsWindowsHelpers.h"
+
+using namespace mozilla;
+
+class TempFile final {
+ wchar_t mFullPath[MAX_PATH + 1];
+
+ public:
+ TempFile() : mFullPath{0} {
+ wchar_t tempDir[MAX_PATH + 1];
+ DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir);
+ if (!len) {
+ return;
+ }
+
+ len = ::GetTempFileNameW(tempDir, L"poi", 0, mFullPath);
+ if (!len) {
+ return;
+ }
+ }
+
+ operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; }
+};
+
+class Overlapped final {
+ nsAutoHandle mEvent;
+ OVERLAPPED mOverlapped;
+
+ public:
+ Overlapped()
+ : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), mOverlapped{} {
+ mOverlapped.hEvent = mEvent.get();
+ }
+
+ operator OVERLAPPED*() { return &mOverlapped; }
+
+ bool Wait(HANDLE aHandle) {
+ DWORD numBytes;
+ if (!::GetOverlappedResult(aHandle, &mOverlapped, &numBytes, TRUE)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+const uint32_t kMagic = 0x12345678;
+
+void FileOpSync(const wchar_t* aPath) {
+ nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ, nullptr, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, nullptr));
+ ASSERT_NE(file.get(), INVALID_HANDLE_VALUE);
+
+ DWORD buffer = kMagic, numBytes = 0;
+ OVERLAPPED seek = {};
+ EXPECT_TRUE(WriteFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek));
+ EXPECT_TRUE(::FlushFileBuffers(file.get()));
+
+ seek.Offset = 0;
+ buffer = 0;
+ EXPECT_TRUE(
+ ::ReadFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek));
+ EXPECT_EQ(buffer, kMagic);
+
+ WIN32_FILE_ATTRIBUTE_DATA fullAttr = {};
+ EXPECT_TRUE(::GetFileAttributesExW(aPath, GetFileExInfoStandard, &fullAttr));
+}
+
+void FileOpAsync(const wchar_t* aPath) {
+ constexpr int kNumPages = 10;
+ constexpr int kPageSize = 4096;
+
+ Array<UniquePtr<void, VirtualFreeDeleter>, kNumPages> pages;
+ Array<FILE_SEGMENT_ELEMENT, kNumPages + 1> segments;
+ for (int i = 0; i < kNumPages; ++i) {
+ auto p = reinterpret_cast<uint32_t*>(::VirtualAlloc(
+ nullptr, kPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
+ ASSERT_TRUE(p);
+
+ pages[i].reset(p);
+ segments[i].Buffer = p;
+
+ p[0] = kMagic;
+ }
+
+ nsAutoHandle file(::CreateFileW(
+ aPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
+ nullptr));
+ ASSERT_NE(file.get(), INVALID_HANDLE_VALUE);
+
+ Overlapped writeOp;
+ if (!::WriteFileGather(file.get(), segments.begin(), kNumPages * kPageSize,
+ nullptr, writeOp)) {
+ EXPECT_EQ(::GetLastError(), static_cast<DWORD>(ERROR_IO_PENDING));
+ EXPECT_TRUE(writeOp.Wait(file.get()));
+ }
+
+ for (int i = 0; i < kNumPages; ++i) {
+ *reinterpret_cast<uint32_t*>(pages[i].get()) = 0;
+ }
+
+ Overlapped readOp;
+ if (!::ReadFileScatter(file.get(), segments.begin(), kNumPages * kPageSize,
+ nullptr, readOp)) {
+ EXPECT_EQ(::GetLastError(), static_cast<DWORD>(ERROR_IO_PENDING));
+ EXPECT_TRUE(readOp.Wait(file.get()));
+ }
+
+ for (int i = 0; i < kNumPages; ++i) {
+ EXPECT_EQ(*reinterpret_cast<uint32_t*>(pages[i].get()), kMagic);
+ }
+}
+
+TEST(PoisonIOInterposer, NormalThread)
+{
+ mozilla::AutoIOInterposer ioInterposerGuard;
+ ioInterposerGuard.Init();
+
+ TempFile tempFile;
+ FileOpSync(tempFile);
+ FileOpAsync(tempFile);
+ EXPECT_TRUE(::DeleteFileW(tempFile));
+}
+
+TEST(PoisonIOInterposer, NullTlsPointer)
+{
+ void* originalTls = mozilla::nt::RtlGetThreadLocalStoragePointer();
+ mozilla::AutoIOInterposer ioInterposerGuard;
+ ioInterposerGuard.Init();
+
+ // Simulate a loader worker thread (TEB::LoaderWorker = 1)
+ // where ThreadLocalStorage is never allocated.
+ mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(nullptr);
+
+ TempFile tempFile;
+ FileOpSync(tempFile);
+ FileOpAsync(tempFile);
+ EXPECT_TRUE(::DeleteFileW(tempFile));
+
+ mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(originalTls);
+}
diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build
new file mode 100644
index 0000000000..d77ec264e1
--- /dev/null
+++ b/xpcom/tests/windows/moz.build
@@ -0,0 +1,17 @@
+# -*- 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",
+ "TestPoisonIOInterposer.cpp",
+]
+
+OS_LIBS += [
+ "mpr",
+]
+
+FINAL_LIBRARY = "xul-gtest"