diff options
Diffstat (limited to 'xpcom/tests')
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", + "<", + ">", + "&", + """, + "'", + "'bad'", + "Foo<T>& foo", + "'"&><abc", + "", + }; + + const char* dsts2[] = { + "a", + "abcdefgh", + "abcdefgh<", + "abcdefgh<>", + "abcdefgh<>&", + "abcdefgh<>&"", + "abcdefgh<>&"'", + "abcdefgh<>&"''bad'", + "abcdefgh<>&"''bad'Foo<T>& foo", + "abcdefgh<>&"''bad'Foo<T>& " + "foo'"&><abc", + "abcdefgh<>&"''bad'Foo<T>& " + "foo'"&><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(©Counter); + 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(©Counter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestCopyWithNoMove(©Counter)); + } + 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(©Counter); + 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(©Counter, &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(©Counter, &moveCounter)) + : RunnableFactory::Create( + "TestNewRunnableFunction", + TestCopyMove(©Counter, &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(©Counter); + // 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(©Counter); + // 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(©Counter, &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 Binary files differnew file mode 100755 index 0000000000..c821003d34 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..5e45963c38 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..bb27d4a5d6 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib diff --git a/xpcom/tests/unit/data/bug121341-2.properties b/xpcom/tests/unit/data/bug121341-2.properties new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..49cc8ef0e1 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser02-utf8BOM.ini b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..05255100a2 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser03-utf8BOM.ini b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..e95d971134 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser04-utf8BOM.ini b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..a49491816c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser05-utf8BOM.ini b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..e9023ac7c9 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser06-utf8BOM.ini b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..d1c167e6e3 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser07-utf8BOM.ini b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..e450566a0f --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser08-utf8BOM.ini b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..ef1da39e27 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser09-utf8BOM.ini b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..e5e70b6612 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser10-utf8BOM.ini b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..932d4004bc --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser11-utf8BOM.ini b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..8a29127222 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser12-utf8BOM.ini b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..ebd9a51d3e --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser13-utf8BOM.ini b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..bbc3413aa1 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser14-utf8BOM.ini b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..e60525dec6 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser15-utf8BOM.ini b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..142b175902 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini diff --git a/xpcom/tests/unit/data/iniparser16-utf8BOM.ini b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini new file mode 100644 index 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 Binary files differnew file mode 100644 index 0000000000..26178d809e --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/index.apxl.gz diff --git a/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff Binary files differnew file mode 100644 index 0000000000..8b49316b4d --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff diff --git a/xpcom/tests/unit/data/process_directive.manifest b/xpcom/tests/unit/data/process_directive.manifest new file mode 100644 index 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" |