new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestDecoderDoctor.cpp
@@ -0,0 +1,202 @@
+/* -*- 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 "nsString.h"
+
+namespace mozilla {
+
+// Very ugly, but needed for now! It gives some confidence that the code works.
+// This is a copy of the template code in DecoderDoctorDiagnostics.cpp.
+// TODO:
+// When this gets moved to a more public place, we should just include the
+// appropriate headers and test the real code.
+
+template <typename String>
+class StringListRange
+{
+ typedef typename String::char_type CharType;
+ typedef const CharType* Pointer;
+
+public:
+ // Iterator into range, trims items and skips empty items.
+ class Iterator
+ {
+ public:
+ bool operator!=(const Iterator& a) const
+ {
+ return mStart != a.mStart || mEnd != a.mEnd;
+ }
+ Iterator& operator++()
+ {
+ SearchItemAt(mComma + 1);
+ return *this;
+ }
+ typedef decltype(Substring(Pointer(), Pointer())) DereferencedType;
+ DereferencedType operator*()
+ {
+ return Substring(mStart, mEnd);
+ }
+ private:
+ friend class StringListRange;
+ Iterator(const CharType* aRangeStart, uint32_t aLength)
+ : mRangeEnd(aRangeStart + aLength)
+ {
+ SearchItemAt(aRangeStart);
+ }
+ void SearchItemAt(Pointer start)
+ {
+ // First, skip leading whitespace.
+ for (Pointer p = start; ; ++p) {
+ if (p >= mRangeEnd) {
+ mStart = mEnd = mComma = mRangeEnd;
+ return;
+ }
+ auto c = *p;
+ if (c == CharType(',')) {
+ // Comma -> Empty item -> Skip.
+ } else if (c != CharType(' ')) {
+ mStart = p;
+ break;
+ }
+ }
+ // Find comma, recording start of trailing space.
+ Pointer trailingWhitespace = nullptr;
+ for (Pointer p = mStart + 1; ; ++p) {
+ if (p >= mRangeEnd) {
+ mEnd = trailingWhitespace ? trailingWhitespace : p;
+ mComma = p;
+ return;
+ }
+ auto c = *p;
+ if (c == CharType(',')) {
+ mEnd = trailingWhitespace ? trailingWhitespace : p;
+ mComma = p;
+ return;
+ }
+ if (c == CharType(' ')) {
+ // Found a whitespace -> Record as trailing if not first one.
+ if (!trailingWhitespace) {
+ trailingWhitespace = p;
+ }
+ } else {
+ // Found a non-whitespace -> Reset trailing whitespace if needed.
+ if (trailingWhitespace) {
+ trailingWhitespace = nullptr;
+ }
+ }
+ }
+ }
+ const Pointer mRangeEnd;
+ Pointer mStart;
+ Pointer mEnd;
+ Pointer mComma;
+ };
+
+ explicit StringListRange(const String& aList) : mList(aList) {}
+ Iterator begin()
+ {
+ return Iterator(mList.Data(), mList.Length());
+ }
+ Iterator end()
+ {
+ return Iterator(mList.Data() + mList.Length(), 0);
+ }
+private:
+ const String& mList;
+};
+
+template <typename String>
+StringListRange<String>
+MakeStringListRange(const String& aList)
+{
+ return StringListRange<String>(aList);
+}
+
+template <typename ListString, typename ItemString>
+static bool
+StringListContains(const ListString& aList, const ItemString& aItem)
+{
+ for (const auto& listItem : MakeStringListRange(aList)) {
+ if (listItem.Equals(aItem)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+};
+
+using namespace mozilla;
+
+TEST(StringListRange, MakeStringListRange)
+{
+ static const struct
+ {
+ const char* mList;
+ const char* mExpected;
+ } tests[] =
+ {
+ { "", "" },
+ { " ", "" },
+ { ",", "" },
+ { " , ", "" },
+ { "a", "a|" },
+ { " a ", "a|" },
+ { "aa,bb", "aa|bb|" },
+ { " a a , b b ", "a a|b b|" },
+ { " , ,a 1,, ,b 2,", "a 1|b 2|" }
+ };
+
+ for (const auto& test : tests) {
+ nsCString list(test.mList);
+ nsCString out;
+ for (const auto& item : MakeStringListRange(list)) {
+ out += item;
+ out += "|";
+ }
+ EXPECT_STREQ(test.mExpected, out.Data());
+ }
+}
+
+TEST(StringListRange, StringListContains)
+{
+ static const struct
+ {
+ const char* mList;
+ const char* mItemToSearch;
+ bool mExpected;
+ } tests[] =
+ {
+ { "", "", false },
+ { "", "a", false },
+ { " ", "a", false },
+ { ",", "a", false },
+ { " , ", "", false },
+ { " , ", "a", false },
+ { "a", "a", true },
+ { "a", "b", false },
+ { " a ", "a", true },
+ { "aa,bb", "aa", true },
+ { "aa,bb", "bb", true },
+ { "aa,bb", "cc", false },
+ { "aa,bb", " aa ", false },
+ { " a a , b b ", "a a", true },
+ { " , ,a 1,, ,b 2,", "a 1", true },
+ { " , ,a 1,, ,b 2,", "b 2", true },
+ { " , ,a 1,, ,b 2,", "", false },
+ { " , ,a 1,, ,b 2,", " ", false },
+ { " , ,a 1,, ,b 2,", "A 1", false },
+ { " , ,A 1,, ,b 2,", "a 1", false }
+ };
+
+ for (const auto& test : tests) {
+ nsCString list(test.mList);
+ nsCString itemToSearch(test.mItemToSearch);
+ EXPECT_EQ(test.mExpected, StringListContains(list, itemToSearch))
+ << "trying to find \"" << itemToSearch.Data()
+ << "\" in \"" << list.Data() << "\"";
+ }
+}