diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /xpcom/tests/gtest | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
101 files changed, 35312 insertions, 0 deletions
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..51c5b95aa9 --- /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 ? true : false; +} + +void FakeInputStream::CheckTest(nsACString& aResult) { + ASSERT_STREQ(aResult.BeginReading(), mTest->mResult); +} + +void FakeInputStream::CheckTest(nsAString& aResult) { + ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult)) + << "Actual: " << aResult.BeginReading() << std::endl + << "Expected: " << mTest->mResult; +} + +TEST(Base64, 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..4bc00a80aa --- /dev/null +++ b/xpcom/tests/gtest/TestExpirationTracker.cpp @@ -0,0 +1,194 @@ +/* -*- 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..915c189b97 --- /dev/null +++ b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp @@ -0,0 +1,208 @@ +#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); + + 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..f31d0b0402 --- /dev/null +++ b/xpcom/tests/gtest/TestGCPostBarriers.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/. */ + +/* + * 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; + 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..fd9f3ab95a --- /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), 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..2255e2e773 --- /dev/null +++ b/xpcom/tests/gtest/TestJSHolderMap.cpp @@ -0,0 +1,345 @@ +/* -*- 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; + 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..35f5bcb3e5 --- /dev/null +++ b/xpcom/tests/gtest/TestMacNSURLEscaping.mm @@ -0,0 +1,139 @@ +#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/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..bc64ef1e05 --- /dev/null +++ b/xpcom/tests/gtest/TestTArray2.cpp @@ -0,0 +1,1423 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Unused.h" + +#include <stdlib.h> +#include <stdio.h> +#include <iostream> +#include "nsTArray.h" +#include "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) {} + void AddRef() { ++rc; } + void Release() { + if (--rc == 0) delete this; + } + ~RefcountedObject() = default; + + private: + int32_t rc; +}; + +TEST(TArray, test_refptr_array) +{ + nsTArray<RefPtr<RefcountedObject>> objArray; + + RefcountedObject* a = new RefcountedObject(); + a->AddRef(); + RefcountedObject* b = new RefcountedObject(); + b->AddRef(); + RefcountedObject* c = new RefcountedObject(); + c->AddRef(); + + objArray.AppendElement(a); + objArray.AppendElement(b); + objArray.AppendElement(c); + + ASSERT_EQ(objArray.IndexOf(b), size_t(1)); + 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_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/TestThreadMetrics.cpp b/xpcom/tests/gtest/TestThreadMetrics.cpp new file mode 100644 index 0000000000..1a8e94b932 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadMetrics.cpp @@ -0,0 +1,320 @@ +/* -*- 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 "gmock/gmock.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Document.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/TaskCategory.h" +#include "mozilla/PerformanceCounter.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsThread.h" + +using namespace mozilla; +using mozilla::Runnable; +using mozilla::dom::DocGroup; +using mozilla::dom::Document; + +/* A struct that describes a runnable to run and, optionally, a + * docgroup to dispatch it to. + */ +struct RunnableDescriptor { + MOZ_IMPLICIT RunnableDescriptor(nsIRunnable* aRunnable, + DocGroup* aDocGroup = nullptr) + : mRunnable(aRunnable), mDocGroup(aDocGroup) {} + + RunnableDescriptor(RunnableDescriptor&& aDescriptor) + : mRunnable(std::move(aDescriptor.mRunnable)), + mDocGroup(std::move(aDescriptor.mDocGroup)) {} + + nsCOMPtr<nsIRunnable> mRunnable; + RefPtr<DocGroup> mDocGroup; +}; + +/* Timed runnable which simulates some execution time + * and can run some nested runnables. + */ +class TimedRunnable final : public Runnable { + public: + explicit TimedRunnable(uint32_t aExecutionTime1, uint32_t aExecutionTime2) + : Runnable("TimedRunnable"), + mExecutionTime1(aExecutionTime1), + mExecutionTime2(aExecutionTime2) {} + + NS_IMETHODIMP Run() { + Sleep(mExecutionTime1); + for (uint32_t index = 0; index < mNestedRunnables.Length(); ++index) { + if (index != 0) { + Sleep(mExecutionTime1); + } + (void)DispatchNestedRunnable(mNestedRunnables[index].mRunnable, + mNestedRunnables[index].mDocGroup); + } + Sleep(mExecutionTime2); + return NS_OK; + } + + void AddNestedRunnable(RunnableDescriptor aDescriptor) { + mNestedRunnables.AppendElement(std::move(aDescriptor)); + } + + void Sleep(uint32_t aMilliseconds) { + TimeStamp start = TimeStamp::Now(); + PR_Sleep(PR_MillisecondsToInterval(aMilliseconds + 5)); + TimeStamp stop = TimeStamp::Now(); + mTotalSlept += (stop - start).ToMicroseconds(); + } + + // Total sleep time, in microseconds. + uint64_t TotalSlept() const { return mTotalSlept; } + + static void DispatchNestedRunnable(nsIRunnable* aRunnable, + DocGroup* aDocGroup) { + // Dispatch another runnable so nsThread::ProcessNextEvent is called + // recursively + nsCOMPtr<nsIThread> thread = do_GetMainThread(); + if (aDocGroup) { + (void)DispatchWithDocgroup(aRunnable, aDocGroup); + } else { + thread->Dispatch(aRunnable, NS_DISPATCH_NORMAL); + } + (void)NS_ProcessNextEvent(thread, false); + } + + static nsresult DispatchWithDocgroup(nsIRunnable* aRunnable, + DocGroup* aDocGroup) { + nsCOMPtr<nsIRunnable> runnable = aRunnable; + runnable = new SchedulerGroup::Runnable(runnable.forget(), + aDocGroup->GetPerformanceCounter()); + return aDocGroup->Dispatch(TaskCategory::Other, runnable.forget()); + } + + private: + uint32_t mExecutionTime1; + uint32_t mExecutionTime2; + // When we sleep, the actual time we sleep might not match how long + // we asked to sleep for. Record how much we actually slept. + uint64_t mTotalSlept = 0; + nsTArray<RunnableDescriptor> mNestedRunnables; +}; + +/* test class used for all metrics tests + * + * - sets up the enable_scheduler_timing pref + * - provides a function to dispatch runnables and spin the loop + */ + +class ThreadMetrics : public ::testing::Test { + public: + explicit ThreadMetrics() = default; + + protected: + virtual void SetUp() { + // FIXME: This is horribly sketchy and relies a ton on BrowsingContextGroup + // not doing anything too fancy or asserting invariants it expects to be + // held. We should probably try to rework this test or remove it completely + // at some point when we can get away with it. Changes to BCGs frequently + // cause this test to start failing as it doesn't behave like normal. + + // building the DocGroup structure + RefPtr<dom::BrowsingContextGroup> group = + dom::BrowsingContextGroup::Create(); + MOZ_ALWAYS_SUCCEEDS(NS_NewHTMLDocument(getter_AddRefs(mDocument), true)); + MOZ_ALWAYS_SUCCEEDS(NS_NewHTMLDocument(getter_AddRefs(mDocument2), true)); + mDocGroup = group->AddDocument("key"_ns, mDocument); + mDocGroup2 = group->AddDocument("key2"_ns, mDocument2); + mCounter = mDocGroup->GetPerformanceCounter(); + mCounter2 = mDocGroup2->GetPerformanceCounter(); + mThreadMgr = do_GetService("@mozilla.org/thread-manager;1"); + mOther = DispatchCategory(TaskCategory::Other).GetValue(); + mDispatchCount = (uint32_t)TaskCategory::Other + 1; + } + + virtual void TearDown() { + // and remove the document from the doc group + mDocGroup->RemoveDocument(mDocument); + mDocGroup2->RemoveDocument(mDocument2); + mDocGroup = nullptr; + mDocGroup2 = nullptr; + mDocument = nullptr; + mDocument2 = nullptr; + ProcessAllEvents(); + } + + // this is used to get rid of transient events + void initScheduler() { ProcessAllEvents(); } + + nsresult Dispatch(nsIRunnable* aRunnable) { + ProcessAllEvents(); + return TimedRunnable::DispatchWithDocgroup(aRunnable, mDocGroup); + } + + void ProcessAllEvents() { mThreadMgr->SpinEventLoopUntilEmpty(); } + + uint32_t mOther; + bool mOldPref; + RefPtr<Document> mDocument; + RefPtr<Document> mDocument2; + RefPtr<DocGroup> mDocGroup; + RefPtr<DocGroup> mDocGroup2; + RefPtr<PerformanceCounter> mCounter; + RefPtr<PerformanceCounter> mCounter2; + nsCOMPtr<nsIThreadManager> mThreadMgr; + uint32_t mDispatchCount; +}; + +TEST_F(ThreadMetrics, CollectMetrics) { + nsresult rv; + initScheduler(); + + // Dispatching a runnable that will last for +50ms + RefPtr<TimedRunnable> runnable = new TimedRunnable(25, 25); + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // Let's look at the task category "other" counter + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + // Did we get incremented in the docgroup ? + uint64_t duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); +} + +TEST_F(ThreadMetrics, CollectRecursiveMetrics) { + nsresult rv; + + initScheduler(); + + // Dispatching a runnable that will last for +50ms + // and run another one recursively that lasts for 400ms + RefPtr<TimedRunnable> runnable = new TimedRunnable(25, 25); + nsCOMPtr<nsIRunnable> nested = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested}); + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // let's look at the counters + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + // did we get incremented in the docgroup ? + uint64_t duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); + + // let's make sure we don't count the time spent in recursive calls + ASSERT_LT(duration, runnable->TotalSlept() + 200000u); +} + +TEST_F(ThreadMetrics, CollectMultipleRecursiveMetrics) { + nsresult rv; + + initScheduler(); + + // Dispatching a runnable that will last for +75ms + // and run another two recursively that last for 400ms each. + RefPtr<TimedRunnable> runnable = new TimedRunnable(25, 25); + for (auto i : {1, 2}) { + Unused << i; + nsCOMPtr<nsIRunnable> nested = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested}); + } + + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // let's look at the counters + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + // did we get incremented in the docgroup ? + uint64_t duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); + + // let's make sure we don't count the time spent in recursive calls + ASSERT_LT(duration, runnable->TotalSlept() + 200000u); +} + +TEST_F(ThreadMetrics, CollectMultipleRecursiveMetricsWithTwoDocgroups) { + nsresult rv; + + initScheduler(); + + // Dispatching a runnable that will last for +75ms + // and run another two recursively that last for 400ms each. The + // first nested runnable will have a docgroup, but the second will + // not, to test that the time for first nested event is accounted + // correctly. + RefPtr<TimedRunnable> runnable = new TimedRunnable(25, 25); + RefPtr<TimedRunnable> nested1 = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested1, mDocGroup2}); + nsCOMPtr<nsIRunnable> nested2 = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested2}); + + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // let's look at the counters + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + uint64_t duration = mCounter2->GetExecutionDuration(); + // Make sure this we incremented the timings for the first nested + // runnable correctly. + ASSERT_GE(duration, nested1->TotalSlept()); + ASSERT_LT(duration, nested1->TotalSlept() + 20000u); + + // And now for the outer runnable. + duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); + + // let's make sure we don't count the time spent in recursive calls + ASSERT_LT(duration, runnable->TotalSlept() + 200000u); +} 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..c221452684 --- /dev/null +++ b/xpcom/tests/gtest/TestThreads_mac.mm @@ -0,0 +1,53 @@ +/* -*- 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..a080d13c01 --- /dev/null +++ b/xpcom/tests/gtest/TestTimers.cpp @@ -0,0 +1,924 @@ +/* -*- 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(); + mMiddle = mBefore + TimeDuration::FromMilliseconds( + kTimerOffset + kTimerInterval * kNumTimers / 2); + 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..3347c56ba5 --- /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", + "TestStateWatching.cpp", + "TestStorageStream.cpp", + "TestStrings.cpp", + "TestStringStream.cpp", + "TestSubstringTuple.cpp", + "TestSynchronization.cpp", + "TestTArray.cpp", + "TestTArray2.cpp", + "TestTaskQueue.cpp", + "TestTextFormatter.cpp", + "TestThreadManager.cpp", + "TestThreadMetrics.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["CPU_ARCH"] == "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["CPU_ARCH"] == "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. |