summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/test/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/xre/test/gtest
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.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 'toolkit/xre/test/gtest')
-rw-r--r--toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp227
-rw-r--r--toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp372
-rw-r--r--toolkit/xre/test/gtest/TestCompatVersionCompare.cpp53
-rw-r--r--toolkit/xre/test/gtest/TestGeckoArgs.cpp179
-rw-r--r--toolkit/xre/test/gtest/moz.build24
5 files changed, 855 insertions, 0 deletions
diff --git a/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
new file mode 100644
index 0000000000..e36c62e0db
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
@@ -0,0 +1,227 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "mozilla/AssembleCmdLine.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "WinRemoteMessage.h"
+
+using namespace mozilla;
+
+template <typename T>
+struct TestCase {
+ const T* mArgs[4];
+ const wchar_t* mExpected;
+};
+
+#define ALPHA_IN_UTF8 "\xe3\x82\xa2\xe3\x83\xab\xe3\x83\x95\xe3\x82\xa1"
+#define OMEGA_IN_UTF8 "\xe3\x82\xaa\xe3\x83\xa1\xe3\x82\xac"
+#define ALPHA_IN_UTF16 L"\u30A2\u30EB\u30D5\u30A1"
+#define OMEGA_IN_UTF16 L"\u30AA\u30E1\u30AC"
+#define UPPER_CYRILLIC_P_IN_UTF8 "\xd0\xa0"
+#define LOWER_CYRILLIC_P_IN_UTF8 "\xd1\x80"
+#define UPPER_CYRILLIC_P_IN_UTF16 L"\u0420"
+#define LOWER_CYRILLIC_P_IN_UTF16 L"\u0440"
+
+TestCase<char> testCases[] = {
+ // Copied from TestXREMakeCommandLineWin.ini
+ {{"a:\\", nullptr}, L"a:\\"},
+ {{"a:\"", nullptr}, L"a:\\\""},
+ {{"a:\\b c", nullptr}, L"\"a:\\b c\""},
+ {{"a:\\b c\"", nullptr}, L"\"a:\\b c\\\"\""},
+ {{"a:\\b c\\d e", nullptr}, L"\"a:\\b c\\d e\""},
+ {{"a:\\b c\\d e\"", nullptr}, L"\"a:\\b c\\d e\\\"\""},
+ {{"a:\\", nullptr}, L"a:\\"},
+ {{"a:\"", "b:\\c d", nullptr}, L"a:\\\" \"b:\\c d\""},
+ {{"a", "b:\" c:\\d", "e", nullptr}, L"a \"b:\\\" c:\\d\" e"},
+ {{"abc", "d", "e", nullptr}, L"abc d e"},
+ {{"a b c", "d", "e", nullptr}, L"\"a b c\" d e"},
+ {{"a\\\\\\b", "de fg", "h", nullptr}, L"a\\\\\\b \"de fg\" h"},
+ {{"a", "b", nullptr}, L"a b"},
+ {{"a\tb", nullptr}, L"\"a\tb\""},
+ {{"a\\\"b", "c", "d", nullptr}, L"a\\\\\\\"b c d"},
+ {{"a\\\"b", "c", nullptr}, L"a\\\\\\\"b c"},
+ {{"a\\\\\\b c", nullptr}, L"\"a\\\\\\b c\""},
+ {{"\"a", nullptr}, L"\\\"a"},
+ {{"\\a", nullptr}, L"\\a"},
+ {{"\\\\\\a", nullptr}, L"\\\\\\a"},
+ {{"\\\\\\\"a", nullptr}, L"\\\\\\\\\\\\\\\"a"},
+ {{"a\\\"b c\" d e", nullptr}, L"\"a\\\\\\\"b c\\\" d e\""},
+ {{"a\\\\\"b", "c d e", nullptr}, L"a\\\\\\\\\\\"b \"c d e\""},
+ {{"a:\\b", "c\\" ALPHA_IN_UTF8, OMEGA_IN_UTF8 "\\d", nullptr},
+ L"a:\\b c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d"},
+ {{"a:\\b", "c\\" ALPHA_IN_UTF8 " " OMEGA_IN_UTF8 "\\d", nullptr},
+ L"a:\\b \"c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d\""},
+ {{ALPHA_IN_UTF8, OMEGA_IN_UTF8, nullptr},
+ ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16},
+
+ // More single-argument cases
+ {{"a\fb", nullptr}, L"\"a\fb\""},
+ {{"a\nb", nullptr}, L"\"a\nb\""},
+ {{"a\rb", nullptr}, L"\"a\rb\""},
+ {{"a\vb", nullptr}, L"\"a\vb\""},
+ {{"\"a\" \"b\"", nullptr}, L"\"\\\"a\\\" \\\"b\\\"\""},
+ {{"\"a\\b\" \"c\\d\"", nullptr}, L"\"\\\"a\\b\\\" \\\"c\\d\\\"\""},
+ {{"\\\\ \\\\", nullptr}, L"\"\\\\ \\\\\\\\\""},
+ {{"\"\" \"\"", nullptr}, L"\"\\\"\\\" \\\"\\\"\""},
+ {{ALPHA_IN_UTF8 "\\" OMEGA_IN_UTF8, nullptr},
+ ALPHA_IN_UTF16 L"\\" OMEGA_IN_UTF16},
+ {{ALPHA_IN_UTF8 " " OMEGA_IN_UTF8, nullptr},
+ L"\"" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\""},
+
+ // Empty string cases
+ {{"", nullptr}, L"\"\""},
+ {{"foo", "", nullptr}, L"foo \"\""},
+ {{"", "bar", nullptr}, L"\"\" bar"},
+ {{"foo", "", "bar", nullptr}, L"foo \"\" bar"},
+};
+
+TEST(AssembleCommandLineWin, assembleCmdLine)
+{
+ for (const auto& testCase : testCases) {
+ UniqueFreePtr<wchar_t> assembled;
+ wchar_t* assembledRaw = nullptr;
+ EXPECT_EQ(assembleCmdLine(testCase.mArgs, &assembledRaw, CP_UTF8), 0);
+ assembled.reset(assembledRaw);
+
+ EXPECT_STREQ(assembled.get(), testCase.mExpected);
+ }
+}
+
+TEST(CommandLineParserWin, HandleCommandLine)
+{
+ CommandLineParserWin<char> parser;
+ for (const auto& testCase : testCases) {
+ NS_ConvertUTF16toUTF8 utf8(testCase.mExpected);
+ parser.HandleCommandLine(utf8);
+
+ if (utf8.Length() == 0) {
+ EXPECT_EQ(parser.Argc(), 0);
+ continue;
+ }
+
+ for (int i = 0; i < parser.Argc(); ++i) {
+ EXPECT_NE(testCase.mArgs[i], nullptr);
+ EXPECT_STREQ(parser.Argv()[i], testCase.mArgs[i]);
+ }
+ EXPECT_EQ(testCase.mArgs[parser.Argc()], nullptr);
+ }
+}
+
+TEST(WinRemoteMessage, SendReceive)
+{
+ const char kCommandline[] =
+ "dummy.exe /arg1 --arg2 \"3rd arg\" "
+ "4th=\"" UPPER_CYRILLIC_P_IN_UTF8 " " LOWER_CYRILLIC_P_IN_UTF8 "\"";
+ const wchar_t kCommandlineW[] =
+ L"dummy.exe /arg1 --arg2 \"3rd arg\" "
+ L"4th=\"" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16 L"\"";
+ const wchar_t* kExpectedArgsW[] = {
+ L"-arg1", L"-arg2", L"3rd arg",
+ L"4th=" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16};
+
+ char workingDirA[MAX_PATH];
+ wchar_t workingDirW[MAX_PATH];
+ EXPECT_NE(getcwd(workingDirA, MAX_PATH), nullptr);
+ EXPECT_NE(_wgetcwd(workingDirW, MAX_PATH), nullptr);
+
+ WinRemoteMessageSender v0(kCommandline);
+ WinRemoteMessageSender v1(kCommandline, workingDirA);
+ WinRemoteMessageSender v2(kCommandlineW, workingDirW);
+
+ WinRemoteMessageReceiver receiver;
+ int32_t len;
+ nsAutoString arg;
+ nsCOMPtr<nsIFile> workingDir;
+
+ receiver.Parse(v0.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ receiver.Parse(v1.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir))));
+ EXPECT_NS_SUCCEEDED(workingDir->GetPath(arg));
+ EXPECT_STREQ(arg.get(), workingDirW);
+
+ receiver.Parse(v2.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir))));
+ EXPECT_NS_SUCCEEDED(workingDir->GetPath(arg));
+ EXPECT_STREQ(arg.get(), workingDirW);
+}
+
+TEST(WinRemoteMessage, NonNullTerminatedBuffer)
+{
+ // Reserve two pages and commit the first one
+ const uint32_t kPageSize = 4096;
+ UniquePtr<void, VirtualFreeDeleter> pages(
+ ::VirtualAlloc(nullptr, kPageSize * 2, MEM_RESERVE, PAGE_NOACCESS));
+ EXPECT_TRUE(pages);
+ EXPECT_TRUE(
+ ::VirtualAlloc(pages.get(), kPageSize, MEM_COMMIT, PAGE_READWRITE));
+
+ // Test strings with lengths between 0 and |kMaxBufferSize| bytes
+ const int kMaxBufferSize = 10;
+
+ // Set a string just before the boundary between the two pages.
+ uint8_t* bufferEnd = reinterpret_cast<uint8_t*>(pages.get()) + kPageSize;
+ memset(bufferEnd - kMaxBufferSize, '$', kMaxBufferSize);
+
+ nsCOMPtr<nsIFile> workingDir;
+ COPYDATASTRUCT copyData = {};
+ for (int i = 0; i < kMaxBufferSize; ++i) {
+ WinRemoteMessageReceiver receiver;
+
+ copyData.cbData = i;
+ copyData.lpData = bufferEnd - i;
+
+ copyData.dwData =
+ static_cast<ULONG_PTR>(WinRemoteMessageVersion::CommandLineOnly);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ copyData.dwData = static_cast<ULONG_PTR>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDir);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ copyData.dwData = static_cast<ULONG_PTR>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+ }
+}
diff --git a/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp b/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp
new file mode 100644
index 0000000000..a5f8a19a9c
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp
@@ -0,0 +1,372 @@
+/* -*- 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 <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+
+// Insulate these test-classes from the outside world.
+//
+// (In particular, distinguish this `CommandLine` from the one in ipc/chromium.
+// The compiler has no issues here, but the MSVC debugger gets somewhat
+// confused.)
+namespace testzilla {
+
+// Auxiliary class to simplify the declaration of test-cases for
+// EnsureCommandlineSafe.
+class CommandLine {
+ const size_t size;
+ std::vector<std::string> _data_8;
+ std::vector<std::wstring> _data_16;
+ // _should_ be const all the way through, but...
+ char const** argv_8;
+ wchar_t const** argv_16;
+
+ public:
+ inline int argc() const { return (int)(unsigned int)(size); }
+
+ template <typename CharT>
+ CharT const** argv() const;
+
+ CommandLine(CommandLine const&) = delete;
+ CommandLine(CommandLine&& that) = delete;
+
+ template <typename Container>
+ explicit CommandLine(Container const& container)
+ : size{std::size(container) + 1}, // plus one for argv[0]
+ argv_8(new const char*[size + 1]), // plus one again for argv[argc]
+ argv_16(new const wchar_t*[size + 1]) {
+ _data_8.reserve(size + 1);
+ _data_16.reserve(size + 1);
+ size_t pos = 0;
+
+ const auto append = [&](const char* val) {
+ size_t const len = ::strlen(val);
+ _data_8.emplace_back(val, val + len);
+ _data_16.emplace_back(val, val + len);
+ argv_8[pos] = _data_8.back().data();
+ argv_16[pos] = _data_16.back().data();
+ ++pos;
+ };
+
+ append("GECKOAPP.COM"); // argv[0] may be anything
+ for (const char* item : container) {
+ append(item);
+ }
+ assert(pos == size);
+
+ // C99 §5.1.2.2.1 states "argv[argc] shall be a null pointer", and
+ // `CheckArg` implicitly relies on this.
+ argv_8[pos] = nullptr;
+ argv_16[pos] = nullptr;
+ }
+
+ // debug output
+ friend std::ostream& operator<<(std::ostream& o, CommandLine const& cl) {
+ for (const auto& item : cl._data_8) {
+ o << '"';
+ for (const char c : item) {
+ if (c == '"') {
+ o << "\\\"";
+ } else {
+ o << c;
+ }
+ }
+ o << "\", ";
+ }
+ return o;
+ }
+};
+
+template <>
+char const** CommandLine::argv<char>() const {
+ return argv_8;
+}
+template <>
+wchar_t const** CommandLine::argv<wchar_t>() const {
+ return argv_16;
+}
+
+enum TestCaseState : bool {
+ FAIL = false,
+ PASS = true,
+};
+constexpr TestCaseState operator!(TestCaseState s) {
+ return TestCaseState(!bool(s));
+}
+
+#ifdef XP_WIN
+constexpr static const TestCaseState WIN_ONLY = PASS;
+#else
+constexpr static const TestCaseState WIN_ONLY = FAIL;
+#endif
+
+using NarrowTestCase = std::pair<const char*, const char*>;
+constexpr std::pair<TestCaseState, NarrowTestCase> kStrMatches8[] = {
+ {PASS, {"", ""}},
+
+ {PASS, {"i", "i"}},
+ {PASS, {"i", "I"}},
+
+ {FAIL, {"", "i"}},
+ {FAIL, {"i", ""}},
+ {FAIL, {"i", "j"}},
+
+ {PASS, {"mozilla", "mozilla"}},
+ {PASS, {"mozilla", "Mozilla"}},
+ {PASS, {"mozilla", "MOZILLA"}},
+ {PASS, {"mozilla", "mOZILLA"}},
+ {PASS, {"mozilla", "mOZIlLa"}},
+ {PASS, {"mozilla", "MoZiLlA"}},
+
+ {FAIL, {"mozilla", ""}},
+ {FAIL, {"mozilla", "mozill"}},
+ {FAIL, {"mozilla", "mozillo"}},
+ {FAIL, {"mozilla", "mozillam"}},
+ {FAIL, {"mozilla", "mozilla-"}},
+ {FAIL, {"mozilla", "-mozilla"}},
+ {FAIL, {"-mozilla", "mozilla"}},
+
+ // numbers are permissible
+ {PASS, {"m0zi11a", "m0zi11a"}},
+ {PASS, {"m0zi11a", "M0ZI11A"}},
+ {PASS, {"m0zi11a", "m0Zi11A"}},
+
+ {FAIL, {"mozilla", "m0zi11a"}},
+ {FAIL, {"m0zi11a", "mozilla"}},
+
+ // capital letters are not accepted in the left comparand
+ {FAIL, {"I", "i"}},
+ {FAIL, {"I", "i"}},
+ {FAIL, {"Mozilla", "mozilla"}},
+ {FAIL, {"Mozilla", "Mozilla"}},
+
+ // punctuation other than `-` is rejected
+ {FAIL, {"*", "*"}},
+ {FAIL, {"*", "word"}},
+ {FAIL, {".", "."}},
+ {FAIL, {".", "a"}},
+ {FAIL, {"_", "_"}},
+
+ // spaces are rejected
+ {FAIL, {" ", " "}},
+ {FAIL, {"two words", "two words"}},
+
+ // non-ASCII characters are rejected
+ //
+ // (the contents of this test case may differ depending on the source and
+ // execution character sets, but the result should always be failure)
+ {FAIL, {"à", "a"}},
+ {FAIL, {"a", "à"}},
+ {FAIL, {"à", "à"}},
+};
+
+#ifdef XP_WIN
+using WideTestCase = std::pair<const char*, const wchar_t*>;
+std::pair<TestCaseState, WideTestCase> kStrMatches16[] = {
+ // (Turkish 'İ' may lowercase to 'i' in some locales, but
+ // we explicitly prevent that from being relevant)
+ {FAIL, {"i", L"İ"}},
+ {FAIL, {"mozilla", L"ṁozilla"}},
+};
+#endif
+
+constexpr const char* kRequiredArgs[] = {"aleph", "beth"};
+
+std::pair<TestCaseState, std::vector<const char*>> const kCommandLines[] = {
+ // the basic admissible forms
+ {PASS, {"-osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"-osint", "-beth", "http://www.example.com/"}},
+
+ // GNU-style double-dashes are also allowed
+ {PASS, {"--osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"--osint", "-beth", "http://www.example.com/"}},
+ {PASS, {"--osint", "--aleph", "http://www.example.com/"}},
+ {PASS, {"--osint", "--beth", "http://www.example.com/"}},
+
+ // on Windows, slashes are also permitted
+ {WIN_ONLY, {"-osint", "/aleph", "http://www.example.com/"}},
+ {WIN_ONLY, {"--osint", "/aleph", "http://www.example.com/"}},
+ // - These should pass on Windows because they're well-formed.
+ // - These should pass elsewhere because the first parameter is
+ // just a local filesystem path, not an option.
+ {PASS, {"/osint", "/aleph", "http://www.example.com/"}},
+ {PASS, {"/osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"/osint", "--aleph", "http://www.example.com/"}},
+
+ // the required argument's argument may not itself be a flag
+ {FAIL, {"-osint", "-aleph", "-anything"}},
+ {FAIL, {"-osint", "-aleph", "--anything"}},
+ // - On Windows systems this is a switch and should be rejected.
+ // - On non-Windows systems this is potentially a valid local path.
+ {!WIN_ONLY, {"-osint", "-aleph", "/anything"}},
+
+ // wrong order
+ {FAIL, {"-osint", "http://www.example.com/", "-aleph"}},
+ {FAIL, {"-aleph", "-osint", "http://www.example.com/"}},
+ {FAIL, {"-aleph", "http://www.example.com/", "-osint"}},
+ {FAIL, {"http://www.example.com/", "-osint", "-aleph"}},
+ {FAIL, {"http://www.example.com/", "-aleph", "-osint"}},
+
+ // missing arguments
+ {FAIL, {"-osint", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph"}},
+ {FAIL, {"-osint"}},
+
+ // improper arguments
+ {FAIL, {"-osint", "-other-argument", "http://www.example.com/"}},
+ {FAIL, {"-osint", "", "http://www.example.com/"}},
+
+ // additional arguments
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-other-argument"}},
+ {FAIL, {"-osint", "-other-argument", "-aleph", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-other-argument", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-a", "-b"}},
+ {FAIL, {"-osint", "-aleph", "-a", "http://www.example.com/", "-b"}},
+ {FAIL, {"-osint", "-a", "-aleph", "http://www.example.com/", "-b"}},
+
+ // multiply-occurring otherwise-licit arguments
+ {FAIL, {"-osint", "-aleph", "-beth", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-aleph", "http://www.example.com/"}},
+
+ // if -osint is not supplied, anything goes
+ {PASS, {}},
+ {PASS, {"http://www.example.com"}},
+ {PASS, {"-aleph", "http://www.example.com/"}},
+ {PASS, {"-beth", "http://www.example.com/"}},
+ {PASS, {"-aleph", "http://www.example.com/", "-other-argument"}},
+ {PASS, {"-aleph", "-aleph", "http://www.example.com/"}},
+};
+
+constexpr static char const* const kOptionalArgs[] = {"mozilla", "allizom"};
+
+// Additional test cases for optional parameters.
+//
+// Test cases marked PASS should pass iff the above optional parameters are
+// permitted. (Test cases marked FAIL should fail regardless, and are only
+// grouped here for convenience and semantic coherence.)
+std::pair<TestCaseState, std::vector<const char*>> kCommandLinesOpt[] = {
+ // one permitted optional argument
+ {PASS, {"-osint", "-mozilla", "-aleph", "http://www.example.com/"}},
+ {PASS, {"-osint", "-allizom", "-aleph", "http://www.example.com/"}},
+
+ // multiple permitted optional arguments
+ {PASS,
+ {"-osint", "-mozilla", "-allizom", "-aleph", "http://www.example.com/"}},
+ {PASS,
+ {"-osint", "-allizom", "-mozilla", "-aleph", "http://www.example.com/"}},
+
+ // optional arguments in the wrong place
+ {FAIL, {"-mozilla", "-osint", "-aleph", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-mozilla", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-mozilla"}},
+
+ // optional arguments in both right and wrong places
+ {FAIL,
+ {"-mozilla", "-osint", "-allizom", "-aleph", "http://www.example.com/"}},
+ {FAIL,
+ {"-osint", "-allizom", "-aleph", "-mozilla", "http://www.example.com/"}},
+ {FAIL,
+ {"-osint", "-allizom", "-aleph", "http://www.example.com/", "-mozilla"}},
+};
+
+enum WithOptionalState : bool {
+ NoOptionalArgs = false,
+ WithOptionalArgs = true,
+};
+
+template <typename CharT>
+bool TestCommandLineImpl(CommandLine const& cl,
+ WithOptionalState withOptional) {
+ int argc = cl.argc();
+ // EnsureCommandlineSafe's signature isn't const-correct here for annoying
+ // reasons, but it is indeed non-mutating.
+ CharT** argv = const_cast<CharT**>(cl.argv<CharT>());
+ if (withOptional) {
+ return mozilla::internal::EnsureCommandlineSafeImpl(
+ argc, argv, kRequiredArgs, kOptionalArgs);
+ }
+ return mozilla::internal::EnsureCommandlineSafeImpl(argc, argv,
+ kRequiredArgs);
+}
+
+// Test that `args` produces `expectation`. On Windows, test against both
+// wide-character and narrow-character implementations.
+void TestCommandLine(TestCaseState expectation, CommandLine const& cl,
+ WithOptionalState withOptional) {
+ EXPECT_EQ(TestCommandLineImpl<char>(cl, withOptional), expectation)
+ << "cl is: " << cl << std::endl
+ << "withOptional is: " << bool(withOptional);
+#ifdef XP_WIN
+ EXPECT_EQ(TestCommandLineImpl<wchar_t>(cl, withOptional), expectation)
+ << "cl is: " << cl << std::endl
+ << "withOptional is: " << bool(withOptional);
+#endif
+}
+} // namespace testzilla
+
+/***********************
+ * Test cases
+ */
+
+TEST(CmdLineAndEnvUtils, strimatch)
+{
+ using namespace testzilla;
+ using mozilla::strimatch;
+ for (auto const& [result, data] : kStrMatches8) {
+ auto const& [left, right] = data;
+ EXPECT_EQ(strimatch(left, right), result)
+ << '<' << left << "> !~ <" << right << '>';
+
+#ifdef XP_WIN
+ wchar_t right_wide[200];
+ ::mbstowcs(right_wide, right, 200);
+ EXPECT_EQ(strimatch(left, right_wide), result)
+ << '<' << left << "> !~ L<" << right << '>';
+#endif
+ }
+
+#ifdef XP_WIN
+ for (auto const& [result, data] : kStrMatches16) {
+ auto const& [left, right] = data;
+ EXPECT_EQ(strimatch(left, right), result)
+ << '<' << left << "> !~ L<" << right << '>';
+ }
+#endif
+}
+
+TEST(CmdLineAndEnvUtils, ensureSafe)
+{
+ using namespace testzilla;
+ for (auto const& [result, data] : kCommandLines) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, NoOptionalArgs);
+ }
+ for (auto const& [_unused, data] : kCommandLinesOpt) {
+ MOZ_UNUSED(_unused); // silence gcc
+ CommandLine const cl(data);
+ TestCommandLine(FAIL, cl, NoOptionalArgs);
+ }
+}
+
+TEST(CmdLineAndEnvUtils, ensureSafeWithOptional)
+{
+ using namespace testzilla;
+ for (auto const& [result, data] : kCommandLines) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, WithOptionalArgs);
+ }
+ for (auto const& [result, data] : kCommandLinesOpt) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, WithOptionalArgs);
+ }
+}
diff --git a/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp
new file mode 100644
index 0000000000..bce64aacd5
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+
+void CheckCompatVersionCompare(const nsCString& aOldCompatVersion,
+ const nsCString& aNewCompatVersion,
+ bool aExpectedSame, bool aExpectedDowngrade) {
+ printf("Comparing '%s' to '%s'.\n", aOldCompatVersion.get(),
+ aNewCompatVersion.get());
+
+ int32_t result = CompareCompatVersions(aOldCompatVersion, aNewCompatVersion);
+
+ ASSERT_EQ(aExpectedSame, result == 0)
+ << "Version sameness check should match.";
+ ASSERT_EQ(aExpectedDowngrade, result > 0)
+ << "Version downgrade check should match.";
+}
+
+void CheckExpectedResult(const char* aOldAppVersion, const char* aNewAppVersion,
+ bool aExpectedSame, bool aExpectedDowngrade) {
+ nsCString oldCompatVersion;
+ BuildCompatVersion(aOldAppVersion, "", "", oldCompatVersion);
+
+ nsCString newCompatVersion;
+ BuildCompatVersion(aNewAppVersion, "", "", newCompatVersion);
+
+ CheckCompatVersionCompare(oldCompatVersion, newCompatVersion, aExpectedSame,
+ aExpectedDowngrade);
+}
+
+TEST(CompatVersionCompare, CompareVersionChange)
+{
+ // Identical
+ CheckExpectedResult("67.0", "67.0", true, false);
+
+ // Version changes
+ CheckExpectedResult("67.0", "68.0", false, false);
+ CheckExpectedResult("68.0", "67.0", false, true);
+ CheckExpectedResult("67.0", "67.0.1", true, false);
+ CheckExpectedResult("67.0.1", "67.0", true, false);
+ CheckExpectedResult("67.0.1", "67.0.1", true, false);
+ CheckExpectedResult("67.0.1", "67.0.2", true, false);
+ CheckExpectedResult("67.0.2", "67.0.1", true, false);
+
+ // Check that if the last run was safe mode then we consider this an upgrade.
+ CheckCompatVersionCompare(
+ "Safe Mode"_ns, "67.0.1_20000000000000/20000000000000"_ns, false, false);
+}
diff --git a/toolkit/xre/test/gtest/TestGeckoArgs.cpp b/toolkit/xre/test/gtest/TestGeckoArgs.cpp
new file mode 100644
index 0000000000..42c7a41b2b
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestGeckoArgs.cpp
@@ -0,0 +1,179 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/GeckoArgs.h"
+
+using namespace mozilla;
+using namespace mozilla::geckoargs;
+
+static CommandLineArg<const char*> kCharParam{"-charParam", "charparam"};
+static CommandLineArg<uint64_t> kUint64Param{"-Uint64Param", "uint64param"};
+static CommandLineArg<bool> kFlag{"-Flag", "flag"};
+
+template <size_t N>
+bool CheckArgv(char** aArgv, const char* const (&aExpected)[N]) {
+ for (size_t i = 0; i < N; ++i) {
+ if (aArgv[i] == nullptr && aExpected[i] == nullptr) {
+ return true;
+ }
+ if (aArgv[i] == nullptr || aExpected[i] == nullptr) {
+ return false;
+ }
+ if (strcmp(aArgv[i], aExpected[i]) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+char kFirefox[] = "$HOME/bin/firefox/firefox-bin";
+
+TEST(GeckoArgs, const_char_ptr)
+{
+ char kCharParamStr[] = "-charParam";
+ char kCharParamValue[] = "paramValue";
+
+ {
+ char* argv[] = {kFirefox, kCharParamStr, kCharParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<const char*> charParam = kCharParam.Get(argc, argv);
+ EXPECT_TRUE(charParam.isSome());
+ EXPECT_EQ(*charParam, kCharParamValue);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kBlahBlah[] = "-blahblah";
+ char* argv[] = {kFirefox, kCharParamStr, kBlahBlah, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<const char*> charParam = kCharParam.Get(argc, argv);
+ EXPECT_TRUE(charParam.isNothing());
+
+ const char* const expArgv[] = {kFirefox, kBlahBlah, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kCharParam.Put("ParamValue", extraArgs);
+ EXPECT_EQ(extraArgs.size(), 2U);
+ EXPECT_EQ(extraArgs[0], "-charParam");
+ EXPECT_EQ(extraArgs[1], "ParamValue");
+ }
+ { EXPECT_EQ(kCharParam.Name(), "-charParam"); }
+}
+
+TEST(GeckoArgs, uint64)
+{
+ char kUint64ParamStr[] = "-Uint64Param";
+
+ {
+ char* argv[] = {kFirefox, kUint64ParamStr, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 3);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char* argv[] = {kFirefox, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 2);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kUint64ParamValue[] = "42";
+ char* argv[] = {kFirefox, kUint64ParamStr, kUint64ParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isSome());
+ EXPECT_EQ(*uint64Param, 42U);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kUint64ParamValue[] = "aa";
+ char* argv[] = {kFirefox, kUint64ParamStr, kUint64ParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kUint64Param.Put(1234, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 2U);
+ EXPECT_EQ(extraArgs[0], "-Uint64Param");
+ EXPECT_EQ(extraArgs[1], "1234");
+ }
+ { EXPECT_EQ(kUint64Param.Name(), "-Uint64Param"); }
+}
+
+TEST(GeckoArgs, bool)
+{
+ char kFlagStr[] = "-Flag";
+
+ {
+ char* argv[] = {kFirefox, kFlagStr, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 3);
+
+ Maybe<bool> Flag = kFlag.Get(argc, argv);
+ EXPECT_TRUE(Flag.isSome());
+ EXPECT_TRUE(*Flag);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char* argv[] = {kFirefox, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 2);
+
+ Maybe<bool> Flag = kFlag.Get(argc, argv);
+ EXPECT_TRUE(Flag.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kFlag.Put(true, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 1U);
+ EXPECT_EQ(extraArgs[0], "-Flag");
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kFlag.Put(false, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 0U);
+ }
+ { EXPECT_EQ(kFlag.Name(), "-Flag"); }
+}
diff --git a/toolkit/xre/test/gtest/moz.build b/toolkit/xre/test/gtest/moz.build
new file mode 100644
index 0000000000..bec881fa72
--- /dev/null
+++ b/toolkit/xre/test/gtest/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+Library("xretest")
+
+UNIFIED_SOURCES = [
+ "TestCmdLineAndEnvUtils.cpp",
+ "TestCompatVersionCompare.cpp",
+ "TestGeckoArgs.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/remote",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestAssembleCommandLineWin.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"