summaryrefslogtreecommitdiffstats
path: root/dom/media/doctor/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/media/doctor/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/doctor/test')
-rw-r--r--dom/media/doctor/test/browser/browser.toml7
-rw-r--r--dom/media/doctor/test/browser/browser_decoderDoctor.js356
-rw-r--r--dom/media/doctor/test/browser/browser_doctor_notification.js265
-rw-r--r--dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp382
-rw-r--r--dom/media/doctor/test/gtest/TestRollingNumber.cpp146
-rw-r--r--dom/media/doctor/test/gtest/moz.build19
6 files changed, 1175 insertions, 0 deletions
diff --git a/dom/media/doctor/test/browser/browser.toml b/dom/media/doctor/test/browser/browser.toml
new file mode 100644
index 0000000000..e60fc948fc
--- /dev/null
+++ b/dom/media/doctor/test/browser/browser.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+subsuite = "media-bc"
+tags = "decoderdoctor"
+
+["browser_decoderDoctor.js"]
+
+["browser_doctor_notification.js"]
diff --git a/dom/media/doctor/test/browser/browser_decoderDoctor.js b/dom/media/doctor/test/browser/browser_decoderDoctor.js
new file mode 100644
index 0000000000..3586515208
--- /dev/null
+++ b/dom/media/doctor/test/browser/browser_decoderDoctor.js
@@ -0,0 +1,356 @@
+"use strict";
+
+// 'data' contains the notification data object:
+// - data.type must be provided.
+// - data.isSolved and data.decoderDoctorReportId will be added if not provided
+// (false and "testReportId" resp.)
+// - Other fields (e.g.: data.formats) may be provided as needed.
+// 'notificationMessage': Expected message in the notification bar.
+// Falsy if nothing is expected after the notification is sent, in which case
+// we won't have further checks, so the following parameters are not needed.
+// 'label': Expected button label. Falsy if no button is expected, in which case
+// we won't have further checks, so the following parameters are not needed.
+// 'accessKey': Expected access key for the button.
+// 'tabChecker': function(openedTab) called with the opened tab that resulted
+// from clicking the button.
+async function test_decoder_doctor_notification(
+ data,
+ notificationMessage,
+ label,
+ accessKey,
+ isLink,
+ tabChecker
+) {
+ const TEST_URL = "https://example.org";
+ // A helper closure to test notifications in same or different origins.
+ // 'test_cross_origin' is used to determine if the observers used in the test
+ // are notified in the same frame (when false) or in a cross origin iframe
+ // (when true).
+ async function create_tab_and_test(test_cross_origin) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: TEST_URL },
+ async function (browser) {
+ let awaitNotificationBar;
+ if (notificationMessage) {
+ awaitNotificationBar = BrowserTestUtils.waitForNotificationBar(
+ gBrowser,
+ browser,
+ "decoder-doctor-notification"
+ );
+ }
+
+ await SpecialPowers.spawn(
+ browser,
+ [data, test_cross_origin],
+ /* eslint-disable-next-line no-shadow */
+ async function (data, test_cross_origin) {
+ if (!test_cross_origin) {
+ // Notify in the same origin.
+ Services.obs.notifyObservers(
+ content.window,
+ "decoder-doctor-notification",
+ JSON.stringify(data)
+ );
+ return;
+ // Done notifying in the same origin.
+ }
+
+ // Notify in a different origin.
+ const CROSS_ORIGIN_URL = "https://example.com";
+ let frame = content.document.createElement("iframe");
+ frame.src = CROSS_ORIGIN_URL;
+ await new Promise(resolve => {
+ frame.addEventListener("load", () => {
+ resolve();
+ });
+ content.document.body.appendChild(frame);
+ });
+
+ await content.SpecialPowers.spawn(
+ frame,
+ [data],
+ async function (
+ /* eslint-disable-next-line no-shadow */
+ data
+ ) {
+ Services.obs.notifyObservers(
+ content.window,
+ "decoder-doctor-notification",
+ JSON.stringify(data)
+ );
+ }
+ );
+ // Done notifying in a different origin.
+ }
+ );
+
+ if (!notificationMessage) {
+ ok(
+ true,
+ "Tested notifying observers with a nonsensical message, no effects expected"
+ );
+ return;
+ }
+
+ let notification;
+ try {
+ notification = await awaitNotificationBar;
+ } catch (ex) {
+ ok(false, ex);
+ return;
+ }
+ ok(notification, "Got decoder-doctor-notification notification");
+ if (label?.l10nId) {
+ // Without the following statement, the
+ // test_cannot_initialize_pulseaudio
+ // will permanently fail on Linux.
+ if (label.l10nId === "moz-support-link-text") {
+ MozXULElement.insertFTLIfNeeded(
+ "toolkit/global/mozSupportLink.ftl"
+ );
+ }
+ label = await document.l10n.formatValue(label.l10nId);
+ }
+ if (isLink) {
+ let link = notification.supportLinkEls[0];
+ if (link) {
+ // Seems to be a Windows specific quirk, but without this
+ // mutation observer the notification.messageText.textContent
+ // will not be updated. This will cause consistent failures
+ // on Windows.
+ await BrowserTestUtils.waitForMutationCondition(
+ link,
+ { childList: true },
+ () => link.textContent.trim()
+ );
+ }
+ }
+ is(
+ notification.messageText.textContent.trim(),
+ notificationMessage,
+ "notification message should match expectation"
+ );
+
+ let button = notification.buttonContainer.querySelector("button");
+ let link = notification.supportLinkEls[0];
+ if (!label) {
+ ok(!button, "There should not be a button");
+ ok(!link, "There should not be a link");
+ return;
+ }
+
+ if (isLink) {
+ ok(!button, "There should not be a button");
+ is(link.textContent, label, `notification link should be '${label}'`);
+ ok(
+ !link.hasAttribute("accesskey"),
+ "notification link should not have accesskey"
+ );
+ } else {
+ ok(!link, "There should not be a link");
+ is(
+ button.getAttribute("label"),
+ label,
+ `notification button should be '${label}'`
+ );
+ is(
+ button.getAttribute("accesskey"),
+ accessKey,
+ "notification button should have accesskey"
+ );
+ }
+
+ if (!tabChecker) {
+ ok(false, "Test implementation error: Missing tabChecker");
+ return;
+ }
+ let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser);
+ if (button) {
+ button.click();
+ } else {
+ link.click();
+ }
+ let openedTab = await awaitNewTab;
+ tabChecker(openedTab);
+ BrowserTestUtils.removeTab(openedTab);
+ }
+ );
+ }
+
+ if (typeof data.type === "undefined") {
+ ok(false, "Test implementation error: data.type must be provided");
+ return;
+ }
+ data.isSolved = data.isSolved || false;
+ if (typeof data.decoderDoctorReportId === "undefined") {
+ data.decoderDoctorReportId = "testReportId";
+ }
+
+ // Test same origin.
+ await create_tab_and_test(false);
+ // Test cross origin.
+ await create_tab_and_test(true);
+}
+
+function tab_checker_for_sumo(expectedPath) {
+ return function (openedTab) {
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ let url = baseURL + expectedPath;
+ is(
+ openedTab.linkedBrowser.currentURI.spec,
+ url,
+ `Expected '${url}' in new tab`
+ );
+ };
+}
+
+function tab_checker_for_webcompat(expectedParams) {
+ return function (openedTab) {
+ let urlString = openedTab.linkedBrowser.currentURI.spec;
+ let endpoint = Services.prefs.getStringPref(
+ "media.decoder-doctor.new-issue-endpoint",
+ ""
+ );
+ ok(
+ urlString.startsWith(endpoint),
+ `Expected URL starting with '${endpoint}', got '${urlString}'`
+ );
+ let params = new URL(urlString).searchParams;
+ for (let k in expectedParams) {
+ if (!params.has(k)) {
+ ok(false, `Expected ${k} in webcompat URL`);
+ } else {
+ is(
+ params.get(k),
+ expectedParams[k],
+ `Expected ${k}='${expectedParams[k]}' in webcompat URL`
+ );
+ }
+ }
+ };
+}
+
+add_task(async function test_platform_decoder_not_found() {
+ let message = "";
+ let decoderDoctorReportId = "";
+ let isLinux = AppConstants.platform == "linux";
+ if (isLinux) {
+ message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+ decoderDoctorReportId = "MediaPlatformDecoderNotFound";
+ } else if (AppConstants.platform == "win") {
+ message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+ decoderDoctorReportId = "MediaWMFNeeded";
+ }
+
+ await test_decoder_doctor_notification(
+ {
+ type: "platform-decoder-not-found",
+ decoderDoctorReportId,
+ formats: "testFormat",
+ },
+ message,
+ isLinux ? "" : { l10nId: "moz-support-link-text" },
+ isLinux ? "" : gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+ true,
+ tab_checker_for_sumo("fix-video-audio-problems-firefox-windows")
+ );
+});
+
+add_task(async function test_cannot_initialize_pulseaudio() {
+ let message = "";
+ // This is only sent on Linux.
+ if (AppConstants.platform == "linux") {
+ message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
+ }
+
+ await test_decoder_doctor_notification(
+ { type: "cannot-initialize-pulseaudio", formats: "testFormat" },
+ message,
+ { l10nId: "moz-support-link-text" },
+ gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+ true,
+ tab_checker_for_sumo("fix-common-audio-and-video-issues")
+ );
+});
+
+add_task(async function test_unsupported_libavcodec() {
+ let message = "";
+ // This is only sent on Linux.
+ if (AppConstants.platform == "linux") {
+ message = gNavigatorBundle.getString(
+ "decoder.unsupportedLibavcodec.message"
+ );
+ }
+
+ await test_decoder_doctor_notification(
+ { type: "unsupported-libavcodec", formats: "testFormat" },
+ message
+ );
+});
+
+add_task(async function test_decode_error() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "media.decoder-doctor.new-issue-endpoint",
+ "http://example.com/webcompat",
+ ],
+ ["browser.fixup.fallback-to-https", false],
+ ],
+ });
+ let message = gNavigatorBundle.getString("decoder.decodeError.message");
+ await test_decoder_doctor_notification(
+ {
+ type: "decode-error",
+ decodeIssue: "DecodeIssue",
+ docURL: "DocURL",
+ resourceURL: "ResURL",
+ },
+ message,
+ gNavigatorBundle.getString("decoder.decodeError.button"),
+ gNavigatorBundle.getString("decoder.decodeError.accesskey"),
+ false,
+ tab_checker_for_webcompat({
+ url: "DocURL",
+ label: "type-media",
+ problem_type: "video_bug",
+ details: JSON.stringify({
+ "Technical Information:": "DecodeIssue",
+ "Resource:": "ResURL",
+ }),
+ })
+ );
+});
+
+add_task(async function test_decode_warning() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "media.decoder-doctor.new-issue-endpoint",
+ "http://example.com/webcompat",
+ ],
+ ],
+ });
+ let message = gNavigatorBundle.getString("decoder.decodeWarning.message");
+ await test_decoder_doctor_notification(
+ {
+ type: "decode-warning",
+ decodeIssue: "DecodeIssue",
+ docURL: "DocURL",
+ resourceURL: "ResURL",
+ },
+ message,
+ gNavigatorBundle.getString("decoder.decodeError.button"),
+ gNavigatorBundle.getString("decoder.decodeError.accesskey"),
+ false,
+ tab_checker_for_webcompat({
+ url: "DocURL",
+ label: "type-media",
+ problem_type: "video_bug",
+ details: JSON.stringify({
+ "Technical Information:": "DecodeIssue",
+ "Resource:": "ResURL",
+ }),
+ })
+ );
+});
diff --git a/dom/media/doctor/test/browser/browser_doctor_notification.js b/dom/media/doctor/test/browser/browser_doctor_notification.js
new file mode 100644
index 0000000000..5789622e23
--- /dev/null
+++ b/dom/media/doctor/test/browser/browser_doctor_notification.js
@@ -0,0 +1,265 @@
+/**
+ * This test is used to test whether the decoder doctor would report the error
+ * on the notification banner (checking that by observing message) or on the web
+ * console (checking that by listening to the test event).
+ * Error should be reported after calling `DecoderDoctorDiagnostics::StoreXXX`
+ * methods.
+ * - StoreFormatDiagnostics() [for checking if type is supported]
+ * - StoreDecodeError() [when decode error occurs]
+ * - StoreEvent() [for reporting audio sink error]
+ */
+
+// Only types being listed here would be allowed to display on a
+// notification banner. Otherwise, the error would only be showed on the
+// web console.
+var gAllowedNotificationTypes =
+ "MediaWMFNeeded,MediaFFMpegNotFound,MediaUnsupportedLibavcodec,MediaDecodeError,MediaCannotInitializePulseAudio,";
+
+// Used to check if the mime type in the notification is equal to what we set
+// before. This mime type doesn't reflect the real world siutation, i.e. not
+// every error listed in this test would happen on this type. An example, ffmpeg
+// not found would only happen on H264/AAC media.
+const gMimeType = "video/mp4";
+
+add_task(async function setupTestingPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.decoder-doctor.testing", true],
+ ["media.decoder-doctor.verbose", true],
+ ["media.decoder-doctor.notifications-allowed", gAllowedNotificationTypes],
+ ],
+ });
+ // transfer types to lower cases in order to match with `DecoderDoctorReportType`
+ gAllowedNotificationTypes = gAllowedNotificationTypes.toLowerCase();
+});
+
+add_task(async function testWMFIsNeeded() {
+ const tab = await createTab("about:blank");
+ await setFormatDiagnosticsReportForMimeType(tab, {
+ type: "platform-decoder-not-found",
+ decoderDoctorReportId: "mediawmfneeded",
+ formats: gMimeType,
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testFFMpegNotFound() {
+ const tab = await createTab("about:blank");
+ await setFormatDiagnosticsReportForMimeType(tab, {
+ type: "platform-decoder-not-found",
+ decoderDoctorReportId: "mediaplatformdecodernotfound",
+ formats: gMimeType,
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testLibAVCodecUnsupported() {
+ const tab = await createTab("about:blank");
+ await setFormatDiagnosticsReportForMimeType(tab, {
+ type: "unsupported-libavcodec",
+ decoderDoctorReportId: "mediaunsupportedlibavcodec",
+ formats: gMimeType,
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testCanNotPlayNoDecoder() {
+ const tab = await createTab("about:blank");
+ await setFormatDiagnosticsReportForMimeType(tab, {
+ type: "cannot-play",
+ decoderDoctorReportId: "mediacannotplaynodecoders",
+ formats: gMimeType,
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function testNoDecoder() {
+ const tab = await createTab("about:blank");
+ await setFormatDiagnosticsReportForMimeType(tab, {
+ type: "can-play-but-some-missing-decoders",
+ decoderDoctorReportId: "medianodecoders",
+ formats: gMimeType,
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+const gErrorList = [
+ "NS_ERROR_DOM_MEDIA_ABORT_ERR",
+ "NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR",
+ "NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR",
+ "NS_ERROR_DOM_MEDIA_DECODE_ERR",
+ "NS_ERROR_DOM_MEDIA_FATAL_ERR",
+ "NS_ERROR_DOM_MEDIA_METADATA_ERR",
+ "NS_ERROR_DOM_MEDIA_OVERFLOW_ERR",
+ "NS_ERROR_DOM_MEDIA_MEDIASINK_ERR",
+ "NS_ERROR_DOM_MEDIA_DEMUXER_ERR",
+ "NS_ERROR_DOM_MEDIA_CDM_ERR",
+ "NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR",
+];
+
+add_task(async function testDecodeError() {
+ const type = "decode-error";
+ const decoderDoctorReportId = "mediadecodeerror";
+ for (let error of gErrorList) {
+ const tab = await createTab("about:blank");
+ info(`first to try if the error is not allowed to be reported`);
+ // No error is allowed to be reported in the notification banner.
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.decoder-doctor.decode-errors-allowed", ""]],
+ });
+ await setDecodeError(tab, {
+ type,
+ decoderDoctorReportId,
+ error,
+ shouldReportNotification: false,
+ });
+
+ // If the notification type is `MediaDecodeError` and the error type is
+ // listed in the pref, then the error would be reported to the
+ // notification banner.
+ info(`Then to try if the error is allowed to be reported`);
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.decoder-doctor.decode-errors-allowed", error]],
+ });
+ await setDecodeError(tab, {
+ type,
+ decoderDoctorReportId,
+ error,
+ shouldReportNotification: true,
+ });
+ BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(async function testAudioSinkFailedStartup() {
+ const tab = await createTab("about:blank");
+ await setAudioSinkFailedStartup(tab, {
+ type: "cannot-initialize-pulseaudio",
+ decoderDoctorReportId: "mediacannotinitializepulseaudio",
+ // This error comes with `*`, see `DecoderDoctorDiagnostics::StoreEvent`
+ formats: "*",
+ });
+ BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Following are helper functions
+ */
+async function createTab(url) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url);
+ // Create observer in the content process in order to check the decoder
+ // doctor's notification that would be sent when an error occurs.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
+ content._notificationName = "decoder-doctor-notification";
+ content._obs = {
+ observe(subject, topic, data) {
+ let { type, decoderDoctorReportId, formats } = JSON.parse(data);
+ decoderDoctorReportId = decoderDoctorReportId.toLowerCase();
+ info(`received '${type}:${decoderDoctorReportId}:${formats}'`);
+ if (!this._resolve) {
+ ok(false, "receive unexpected notification?");
+ }
+ if (
+ type == this._type &&
+ decoderDoctorReportId == this._decoderDoctorReportId &&
+ formats == this._formats
+ ) {
+ ok(true, `received correct notification`);
+ Services.obs.removeObserver(content._obs, content._notificationName);
+ this._resolve();
+ this._resolve = null;
+ }
+ },
+ // Return a promise that will be resolved once receiving a notification
+ // which has equal data with the input parameters.
+ waitFor({ type, decoderDoctorReportId, formats }) {
+ if (this._resolve) {
+ ok(false, "already has a pending promise!");
+ return Promise.reject();
+ }
+ Services.obs.addObserver(content._obs, content._notificationName);
+ return new Promise(resolve => {
+ info(`waiting for '${type}:${decoderDoctorReportId}:${formats}'`);
+ this._resolve = resolve;
+ this._type = type;
+ this._decoderDoctorReportId = decoderDoctorReportId;
+ this._formats = formats;
+ });
+ },
+ };
+ content._waitForReport = (params, shouldReportNotification) => {
+ const reportToConsolePromise = new Promise(r => {
+ content.document.addEventListener(
+ "mozreportmediaerror",
+ _ => {
+ r();
+ },
+ { once: true }
+ );
+ });
+ const reportToNotificationBannerPromise = shouldReportNotification
+ ? content._obs.waitFor(params)
+ : Promise.resolve();
+ info(
+ `waitForConsole=true, waitForNotificationBanner=${shouldReportNotification}`
+ );
+ return Promise.all([
+ reportToConsolePromise,
+ reportToNotificationBannerPromise,
+ ]);
+ };
+ });
+ return tab;
+}
+
+async function setFormatDiagnosticsReportForMimeType(tab, params) {
+ const shouldReportNotification = gAllowedNotificationTypes.includes(
+ params.decoderDoctorReportId
+ );
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [params, shouldReportNotification],
+ async (params, shouldReportNotification) => {
+ const video = content.document.createElement("video");
+ SpecialPowers.wrap(video).setFormatDiagnosticsReportForMimeType(
+ params.formats,
+ params.decoderDoctorReportId
+ );
+ await content._waitForReport(params, shouldReportNotification);
+ }
+ );
+ ok(true, `finished check for ${params.decoderDoctorReportId}`);
+}
+
+async function setDecodeError(tab, params) {
+ info(`start check for ${params.error}`);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [params],
+ async (params, shouldReportNotification) => {
+ const video = content.document.createElement("video");
+ SpecialPowers.wrap(video).setDecodeError(params.error);
+ await content._waitForReport(params, params.shouldReportNotification);
+ }
+ );
+ ok(true, `finished check for ${params.error}`);
+}
+
+async function setAudioSinkFailedStartup(tab, params) {
+ const shouldReportNotification = gAllowedNotificationTypes.includes(
+ params.decoderDoctorReportId
+ );
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [params, shouldReportNotification],
+ async (params, shouldReportNotification) => {
+ const video = content.document.createElement("video");
+ const waitPromise = content._waitForReport(
+ params,
+ shouldReportNotification
+ );
+ SpecialPowers.wrap(video).setAudioSinkFailedStartup();
+ await waitPromise;
+ }
+ );
+}
diff --git a/dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp b/dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp
new file mode 100644
index 0000000000..35a89c9267
--- /dev/null
+++ b/dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp
@@ -0,0 +1,382 @@
+/* -*- 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 "MultiWriterQueue.h"
+
+#include "DDTimeStamp.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/Assertions.h"
+#include "nsDeque.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+#include <gtest/gtest.h>
+#include <type_traits>
+
+using mozilla::MultiWriterQueue;
+using mozilla::MultiWriterQueueDefaultBufferSize;
+using mozilla::MultiWriterQueueReaderLocking_Mutex;
+using mozilla::MultiWriterQueueReaderLocking_None;
+
+template <size_t BufferSize>
+static void TestMultiWriterQueueST(const int loops) {
+ using Q = MultiWriterQueue<int, BufferSize>;
+ Q q;
+
+ int pushes = 0;
+ // Go through 2 cycles of pushes&pops, to exercize reusable buffers.
+ for (int max = loops; max <= loops * 2; max *= 2) {
+ // Push all numbers.
+ for (int i = 1; i <= max; ++i) {
+ bool newBuffer = q.Push(i);
+ // A new buffer should be added at the last push of each buffer.
+ EXPECT_EQ(++pushes % BufferSize == 0, newBuffer);
+ }
+
+ // Pop numbers, should be FIFO.
+ int x = 0;
+ q.PopAll([&](int& i) { EXPECT_EQ(++x, i); });
+
+ // We should have got all numbers.
+ EXPECT_EQ(max, x);
+
+ // Nothing left.
+ q.PopAll([&](int&) { EXPECT_TRUE(false); });
+ }
+}
+
+TEST(MultiWriterQueue, SingleThreaded)
+{
+ TestMultiWriterQueueST<1>(10);
+ TestMultiWriterQueueST<2>(10);
+ TestMultiWriterQueueST<4>(10);
+
+ TestMultiWriterQueueST<10>(9);
+ TestMultiWriterQueueST<10>(10);
+ TestMultiWriterQueueST<10>(11);
+ TestMultiWriterQueueST<10>(19);
+ TestMultiWriterQueueST<10>(20);
+ TestMultiWriterQueueST<10>(21);
+ TestMultiWriterQueueST<10>(999);
+ TestMultiWriterQueueST<10>(1000);
+ TestMultiWriterQueueST<10>(1001);
+
+ TestMultiWriterQueueST<8192>(8192 * 4 + 1);
+}
+
+template <typename Q>
+static void TestMultiWriterQueueMT(int aWriterThreads, int aReaderThreads,
+ int aTotalLoops, const char* aPrintPrefix) {
+ Q q;
+
+ const int threads = aWriterThreads + aReaderThreads;
+ const int loops = aTotalLoops / aWriterThreads;
+
+ nsIThread** array = new nsIThread*[threads];
+
+ mozilla::Atomic<int> pushThreadsCompleted{0};
+ int pops = 0;
+
+ nsCOMPtr<nsIRunnable> popper = NS_NewRunnableFunction("MWQPopper", [&]() {
+ // int popsBefore = pops;
+ // int allocsBefore = q.AllocatedBuffersStats().mCount;
+ q.PopAll([&pops](const int& i) { ++pops; });
+ // if (pops != popsBefore ||
+ // q.AllocatedBuffersStats().mCount != allocsBefore) {
+ // printf("%s threads=1+%d loops/thread=%d pops=%d "
+ // "buffers: live=%d (w %d) reusable=%d (w %d) "
+ // "alloc=%d (w %d)\n",
+ // aPrintPrefix,
+ // aWriterThreads,
+ // loops,
+ // pops,
+ // q.LiveBuffersStats().mCount,
+ // q.LiveBuffersStats().mWatermark,
+ // q.ReusableBuffersStats().mCount,
+ // q.ReusableBuffersStats().mWatermark,
+ // q.AllocatedBuffersStats().mCount,
+ // q.AllocatedBuffersStats().mWatermark);
+ // }
+ });
+ // Cycle through reader threads.
+ mozilla::Atomic<size_t> readerThread{0};
+
+ double start = mozilla::ToSeconds(mozilla::DDNow());
+
+ for (int k = 0; k < threads; k++) {
+ // First `aReaderThreads` threads to pop, all others to push.
+ if (k < aReaderThreads) {
+ nsCOMPtr<nsIThread> t;
+ nsresult rv = NS_NewNamedThread("MWQThread", getter_AddRefs(t));
+ EXPECT_NS_SUCCEEDED(rv);
+ NS_ADDREF(array[k] = t);
+ } else {
+ nsCOMPtr<nsIThread> t;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("MWQPusher", [&, k]() {
+ // Give a bit of breathing space to construct other threads.
+ PR_Sleep(PR_MillisecondsToInterval(100));
+
+ for (int i = 0; i < loops; ++i) {
+ if (q.Push(k * threads + i) && aReaderThreads != 0) {
+ // Run a popper task every time we push the last element of a
+ // buffer.
+ array[++readerThread % aReaderThreads]->Dispatch(
+ popper, nsIThread::DISPATCH_NORMAL);
+ }
+ }
+ ++pushThreadsCompleted;
+ });
+ nsresult rv = NS_NewNamedThread("MWQThread", getter_AddRefs(t), r);
+ EXPECT_NS_SUCCEEDED(rv);
+ NS_ADDREF(array[k] = t);
+ }
+ }
+
+ for (int k = threads - 1; k >= 0; k--) {
+ array[k]->Shutdown();
+ NS_RELEASE(array[k]);
+ }
+ delete[] array;
+
+ // There may be a few more elements that haven't been read yet.
+ q.PopAll([&pops](const int& i) { ++pops; });
+ const int pushes = aWriterThreads * loops;
+ EXPECT_EQ(pushes, pops);
+ q.PopAll([](const int& i) { EXPECT_TRUE(false); });
+
+ double duration = mozilla::ToSeconds(mozilla::DDNow()) - start - 0.1;
+ printf(
+ "%s threads=%dw+%dr loops/thread=%d pushes=pops=%d duration=%fs "
+ "pushes/s=%f buffers: live=%d (w %d) reusable=%d (w %d) "
+ "alloc=%d (w %d)\n",
+ aPrintPrefix, aWriterThreads, aReaderThreads, loops, pushes, duration,
+ pushes / duration, q.LiveBuffersStats().mCount,
+ q.LiveBuffersStats().mWatermark, q.ReusableBuffersStats().mCount,
+ q.ReusableBuffersStats().mWatermark, q.AllocatedBuffersStats().mCount,
+ q.AllocatedBuffersStats().mWatermark);
+}
+
+// skip test on windows10-aarch64 due to unexpected test timeout at
+// MultiWriterSingleReader, bug 1526001
+#if !defined(_M_ARM64)
+TEST(MultiWriterQueue, MultiWriterSingleReader)
+{
+ // Small BufferSize, to exercize the buffer management code.
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 1, 0, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 1, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 2, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 3, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 4, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 5, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 6, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 7, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 8, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 9, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 10, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 16, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 32, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_None>>(
+ 64, 1, 2 * 1024 * 1024, "MultiWriterQueue<int, 10, Locking_None>");
+
+ // A more real-life buffer size.
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, MultiWriterQueueDefaultBufferSize,
+ MultiWriterQueueReaderLocking_None>>(
+ 64, 1, 2 * 1024 * 1024,
+ "MultiWriterQueue<int, DefaultBufferSize, Locking_None>");
+
+ // DEBUG-mode thread-safety checks should make the following (multi-reader
+ // with no locking) crash; uncomment to verify.
+ // TestMultiWriterQueueMT<
+ // MultiWriterQueue<int, MultiWriterQueueDefaultBufferSize,
+ // MultiWriterQueueReaderLocking_None>>(64, 2, 2*1024*1024);
+}
+#endif
+
+// skip test on windows10-aarch64 due to unexpected test timeout at
+// MultiWriterMultiReade, bug 1526001
+#if !defined(_M_ARM64)
+TEST(MultiWriterQueue, MultiWriterMultiReader)
+{
+ static_assert(
+ std::is_same_v<
+ MultiWriterQueue<int, 10>,
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>,
+ "MultiWriterQueue reader locking should use Mutex by default");
+
+ // Small BufferSize, to exercize the buffer management code.
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 1, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 2, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 3, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 4, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 5, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 6, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 7, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 8, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 9, 2, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 10, 4, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 16, 8, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 32, 16, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, 10, MultiWriterQueueReaderLocking_Mutex>>(
+ 64, 32, 1024 * 1024, "MultiWriterQueue<int, 10, Locking_Mutex>");
+
+ // A more real-life buffer size.
+ TestMultiWriterQueueMT<
+ MultiWriterQueue<int, MultiWriterQueueDefaultBufferSize,
+ MultiWriterQueueReaderLocking_Mutex>>(
+ 64, 32, 1024 * 1024,
+ "MultiWriterQueue<int, DefaultBufferSize, Locking_Mutex>");
+}
+#endif
+
+// Single-threaded use only.
+struct DequeWrapperST {
+ nsDeque<void> mDQ;
+
+ bool Push(int i) {
+ mDQ.PushFront(reinterpret_cast<void*>(static_cast<uintptr_t>(i)));
+ return true;
+ }
+ template <typename F>
+ void PopAll(F&& aF) {
+ while (mDQ.GetSize() != 0) {
+ int i = static_cast<int>(reinterpret_cast<uintptr_t>(mDQ.Pop()));
+ aF(i);
+ }
+ }
+
+ struct CountAndWatermark {
+ int mCount = 0;
+ int mWatermark = 0;
+ } mLiveBuffersStats, mReusableBuffersStats, mAllocatedBuffersStats;
+
+ CountAndWatermark LiveBuffersStats() const { return mLiveBuffersStats; }
+ CountAndWatermark ReusableBuffersStats() const {
+ return mReusableBuffersStats;
+ }
+ CountAndWatermark AllocatedBuffersStats() const {
+ return mAllocatedBuffersStats;
+ }
+};
+
+// Multi-thread (atomic) writes allowed, make sure you don't pop unless writes
+// can't happen.
+struct DequeWrapperAW : DequeWrapperST {
+ mozilla::Atomic<bool> mWriting{false};
+
+ bool Push(int i) {
+ while (!mWriting.compareExchange(false, true)) {
+ }
+ mDQ.PushFront(reinterpret_cast<void*>(static_cast<uintptr_t>(i)));
+ mWriting = false;
+ return true;
+ }
+};
+
+// Multi-thread writes allowed, make sure you don't pop unless writes can't
+// happen.
+struct DequeWrapperMW : DequeWrapperST {
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+
+ DequeWrapperMW() : mMutex("DequeWrapperMW/MT") {}
+
+ bool Push(int i) {
+ mozilla::MutexAutoLock lock(mMutex);
+ mDQ.PushFront(reinterpret_cast<void*>(static_cast<uintptr_t>(i)));
+ return true;
+ }
+};
+
+// Multi-thread read&writes allowed.
+struct DequeWrapperMT : DequeWrapperMW {
+ template <typename F>
+ void PopAll(F&& aF) {
+ while (mDQ.GetSize() != 0) {
+ int i;
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ i = static_cast<int>(reinterpret_cast<uintptr_t>(mDQ.Pop()));
+ }
+ aF(i);
+ }
+ }
+};
+
+TEST(MultiWriterQueue, nsDequeBenchmark)
+{
+ TestMultiWriterQueueMT<DequeWrapperST>(1, 0, 2 * 1024 * 1024,
+ "DequeWrapperST ");
+
+ TestMultiWriterQueueMT<DequeWrapperAW>(1, 0, 2 * 1024 * 1024,
+ "DequeWrapperAW ");
+ TestMultiWriterQueueMT<DequeWrapperMW>(1, 0, 2 * 1024 * 1024,
+ "DequeWrapperMW ");
+ TestMultiWriterQueueMT<DequeWrapperMT>(1, 0, 2 * 1024 * 1024,
+ "DequeWrapperMT ");
+ TestMultiWriterQueueMT<DequeWrapperMT>(1, 1, 2 * 1024 * 1024,
+ "DequeWrapperMT ");
+
+ TestMultiWriterQueueMT<DequeWrapperAW>(8, 0, 2 * 1024 * 1024,
+ "DequeWrapperAW ");
+ TestMultiWriterQueueMT<DequeWrapperMW>(8, 0, 2 * 1024 * 1024,
+ "DequeWrapperMW ");
+ TestMultiWriterQueueMT<DequeWrapperMT>(8, 0, 2 * 1024 * 1024,
+ "DequeWrapperMT ");
+ TestMultiWriterQueueMT<DequeWrapperMT>(8, 1, 2 * 1024 * 1024,
+ "DequeWrapperMT ");
+}
diff --git a/dom/media/doctor/test/gtest/TestRollingNumber.cpp b/dom/media/doctor/test/gtest/TestRollingNumber.cpp
new file mode 100644
index 0000000000..cce06ae9ba
--- /dev/null
+++ b/dom/media/doctor/test/gtest/TestRollingNumber.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 "RollingNumber.h"
+
+#include "mozilla/Assertions.h"
+
+#include <cstdint>
+#include <gtest/gtest.h>
+#include <type_traits>
+
+using RN8 = mozilla::RollingNumber<uint8_t>;
+
+TEST(RollingNumber, Value)
+{
+ // Value type should reflect template argument.
+ static_assert(std::is_same_v<RN8::ValueType, uint8_t>);
+
+ // Default init to 0.
+ const RN8 n;
+ // Access through Value().
+ EXPECT_EQ(0, n.Value());
+
+ // Conversion constructor.
+ RN8 n42{42};
+ EXPECT_EQ(42, n42.Value());
+
+ // Copy Constructor.
+ RN8 n42Copied{n42};
+ EXPECT_EQ(42, n42Copied.Value());
+
+ // Assignment construction.
+ RN8 n42Assigned = n42;
+ EXPECT_EQ(42, n42Assigned.Value());
+
+ // Assignment.
+ n42 = n;
+ EXPECT_EQ(0, n42.Value());
+}
+
+TEST(RollingNumber, Operations)
+{
+ RN8 n;
+ EXPECT_EQ(0, n.Value());
+
+ RN8 nPreInc = ++n;
+ EXPECT_EQ(1, n.Value());
+ EXPECT_EQ(1, nPreInc.Value());
+
+ RN8 nPostInc = n++;
+ EXPECT_EQ(2, n.Value());
+ EXPECT_EQ(1, nPostInc.Value());
+
+ RN8 nPreDec = --n;
+ EXPECT_EQ(1, n.Value());
+ EXPECT_EQ(1, nPreDec.Value());
+
+ RN8 nPostDec = n--;
+ EXPECT_EQ(0, n.Value());
+ EXPECT_EQ(1, nPostDec.Value());
+
+ RN8 nPlus = n + 10;
+ EXPECT_EQ(0, n.Value());
+ EXPECT_EQ(10, nPlus.Value());
+
+ n += 20;
+ EXPECT_EQ(20, n.Value());
+
+ RN8 nMinus = n - 2;
+ EXPECT_EQ(20, n.Value());
+ EXPECT_EQ(18, nMinus.Value());
+
+ n -= 5;
+ EXPECT_EQ(15, n.Value());
+
+ uint8_t diff = nMinus - n;
+ EXPECT_EQ(3, diff);
+
+ // Overflows.
+ n = RN8(0);
+ EXPECT_EQ(0, n.Value());
+ n--;
+ EXPECT_EQ(255, n.Value());
+ n++;
+ EXPECT_EQ(0, n.Value());
+ n -= 10;
+ EXPECT_EQ(246, n.Value());
+ n += 20;
+ EXPECT_EQ(10, n.Value());
+}
+
+TEST(RollingNumber, Comparisons)
+{
+ uint8_t i = 0;
+ do {
+ RN8 n{i};
+ EXPECT_EQ(i, n.Value());
+ EXPECT_TRUE(n == n);
+ EXPECT_FALSE(n != n);
+ EXPECT_FALSE(n < n);
+ EXPECT_TRUE(n <= n);
+ EXPECT_FALSE(n > n);
+ EXPECT_TRUE(n >= n);
+
+ RN8 same = n;
+ EXPECT_TRUE(n == same);
+ EXPECT_FALSE(n != same);
+ EXPECT_FALSE(n < same);
+ EXPECT_TRUE(n <= same);
+ EXPECT_FALSE(n > same);
+ EXPECT_TRUE(n >= same);
+
+#ifdef DEBUG
+ // In debug builds, we are only allowed a quarter of the type range.
+ const uint8_t maxDiff = 255 / 4;
+#else
+ // In non-debug builds, we can go half-way up or down the type range, and
+ // still conserve the expected ordering.
+ const uint8_t maxDiff = 255 / 2;
+#endif
+ for (uint8_t add = 1; add <= maxDiff; ++add) {
+ RN8 bigger = n + add;
+ EXPECT_FALSE(n == bigger);
+ EXPECT_TRUE(n != bigger);
+ EXPECT_TRUE(n < bigger);
+ EXPECT_TRUE(n <= bigger);
+ EXPECT_FALSE(n > bigger);
+ EXPECT_FALSE(n >= bigger);
+ }
+
+ for (uint8_t sub = 1; sub <= maxDiff; ++sub) {
+ RN8 smaller = n - sub;
+ EXPECT_FALSE(n == smaller);
+ EXPECT_TRUE(n != smaller);
+ EXPECT_FALSE(n < smaller);
+ EXPECT_FALSE(n <= smaller);
+ EXPECT_TRUE(n > smaller);
+ EXPECT_TRUE(n >= smaller);
+ }
+
+ ++i;
+ } while (i != 0);
+}
diff --git a/dom/media/doctor/test/gtest/moz.build b/dom/media/doctor/test/gtest/moz.build
new file mode 100644
index 0000000000..7ae9eae130
--- /dev/null
+++ b/dom/media/doctor/test/gtest/moz.build
@@ -0,0 +1,19 @@
+# -*- 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/.
+
+if CONFIG["OS_TARGET"] != "Android":
+ UNIFIED_SOURCES += [
+ "TestMultiWriterQueue.cpp",
+ "TestRollingNumber.cpp",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/media/doctor",
+]
+
+FINAL_LIBRARY = "xul-gtest"