diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/media/doctor/test | |
parent | Initial commit. (diff) | |
download | firefox-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.toml | 7 | ||||
-rw-r--r-- | dom/media/doctor/test/browser/browser_decoderDoctor.js | 356 | ||||
-rw-r--r-- | dom/media/doctor/test/browser/browser_doctor_notification.js | 265 | ||||
-rw-r--r-- | dom/media/doctor/test/gtest/TestMultiWriterQueue.cpp | 382 | ||||
-rw-r--r-- | dom/media/doctor/test/gtest/TestRollingNumber.cpp | 146 | ||||
-rw-r--r-- | dom/media/doctor/test/gtest/moz.build | 19 |
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" |