diff options
Diffstat (limited to 'dom/media/gtest/TestAudioSinkWrapper.cpp')
-rw-r--r-- | dom/media/gtest/TestAudioSinkWrapper.cpp | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/dom/media/gtest/TestAudioSinkWrapper.cpp b/dom/media/gtest/TestAudioSinkWrapper.cpp new file mode 100644 index 0000000000..87f17042a8 --- /dev/null +++ b/dom/media/gtest/TestAudioSinkWrapper.cpp @@ -0,0 +1,148 @@ +/* -*- 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 "AudioSink.h" +#include "AudioSinkWrapper.h" +#include "CubebUtils.h" +#include "MockCubeb.h" +#include "TimeUnits.h" +#include "gmock/gmock.h" +#include "gtest/gtest-printers.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/gtest/WaitFor.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +// This is a crashtest to check that AudioSinkWrapper::mEndedPromiseHolder is +// not settled twice when sync and async AudioSink initializations race. +TEST(TestAudioSinkWrapper, AsyncInitFailureWithSyncInitSuccess) +{ + MockCubeb* cubeb = new MockCubeb(); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + MediaQueue<AudioData> audioQueue; + MediaInfo info; + info.EnableAudio(); + auto audioSinkCreator = [&]() { + return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(), + audioQueue, info.mAudio, + /*resistFingerprinting*/ false)}; + }; + const double initialVolume = 0.0; // so that there is initially no AudioSink + RefPtr wrapper = new AudioSinkWrapper( + AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator), + initialVolume, /*playbackRate*/ 1.0, /*preservesPitch*/ true, + /*sinkDevice*/ nullptr); + + wrapper->Start(media::TimeUnit::Zero(), info); + // The first AudioSink init occurs on a background thread. Listen for this, + // but don't process any events on the current thread so that the + // AudioSinkWrapper does not yet handle the result of AudioSink + // initialization. + RefPtr backgroundQueue = + nsThreadManager::get().CreateBackgroundTaskQueue(__func__); + Monitor monitor(__func__); + bool initDone = false; + MediaEventListener initListener = cubeb->StreamInitEvent().Connect( + backgroundQueue, [&](RefPtr<SmartMockCubebStream> aStream) { + EXPECT_EQ(aStream, nullptr); + MonitorAutoLock lock(monitor); + initDone = true; + lock.Notify(); + }); + cubeb->ForceStreamInitError(); + wrapper->SetVolume(0.5); // triggers async sink init, which fails + { + // Wait for the async init to complete. + MonitorAutoLock lock(monitor); + while (!initDone) { + lock.Wait(); + } + } + initListener.Disconnect(); + wrapper->SetPlaying(false); + // The second AudioSink init is synchronous. + nsIThread* currentThread = NS_GetCurrentThread(); + RefPtr<SmartMockCubebStream> stream; + initListener = cubeb->StreamInitEvent().Connect( + currentThread, [&](RefPtr<SmartMockCubebStream> aStream) { + stream = std::move(aStream); + }); + wrapper->SetPlaying(true); // sync sink init, which succeeds + // Let AudioSinkWrapper handle the (first) AudioSink initialization failure + // and allow `stream` to be set. + NS_ProcessPendingEvents(currentThread); + initListener.Disconnect(); + cubeb_state state = CUBEB_STATE_STARTED; + MediaEventListener stateListener = stream->StateEvent().Connect( + currentThread, [&](cubeb_state aState) { state = aState; }); + // Run AudioSinkWrapper::OnAudioEnded(). + // This test passes if there is no crash. Bug 1845811. + audioQueue.Finish(); + SpinEventLoopUntil("stream state change"_ns, + [&] { return state != CUBEB_STATE_STARTED; }); + stateListener.Disconnect(); + EXPECT_EQ(state, CUBEB_STATE_DRAINED); + wrapper->Stop(); + wrapper->Shutdown(); +} + +// This is a crashtest to check that AudioSinkWrapper::mEndedPromiseHolder is +// not settled twice when the audio ends during async AudioSink initialization. +TEST(TestAudioSinkWrapper, AsyncInitWithEndOfAudio) +{ + MockCubeb* cubeb = new MockCubeb(); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + MediaQueue<AudioData> audioQueue; + MediaInfo info; + info.EnableAudio(); + auto audioSinkCreator = [&]() { + return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(), + audioQueue, info.mAudio, + /*resistFingerprinting*/ false)}; + }; + const double initialVolume = 0.0; // so that there is initially no AudioSink + RefPtr wrapper = new AudioSinkWrapper( + AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator), + initialVolume, /*playbackRate*/ 1.0, /*preservesPitch*/ true, + /*sinkDevice*/ nullptr); + + wrapper->Start(media::TimeUnit::Zero(), info); + // The first AudioSink init occurs on a background thread. Listen for this, + // but don't process any events on the current thread so that the + // AudioSinkWrapper does not yet use the initialized AudioSink. + RefPtr backgroundQueue = + nsThreadManager::get().CreateBackgroundTaskQueue(__func__); + Monitor monitor(__func__); + RefPtr<SmartMockCubebStream> stream; + MediaEventListener initListener = cubeb->StreamInitEvent().Connect( + backgroundQueue, [&](RefPtr<SmartMockCubebStream> aStream) { + EXPECT_NE(aStream, nullptr); + MonitorAutoLock lock(monitor); + stream = std::move(aStream); + lock.Notify(); + }); + wrapper->SetVolume(0.5); // triggers async sink init + { + // Wait for the async init to complete. + MonitorAutoLock lock(monitor); + while (!stream) { + lock.Wait(); + } + } + initListener.Disconnect(); + // Finish the audio before AudioSinkWrapper considers using the initialized + // AudioSink. + audioQueue.Finish(); + // Wait for AudioSinkWrapper to destroy the initialized stream. + // This test passes if there is no crash. Bug 1846854. + WaitFor(cubeb->StreamDestroyEvent()); + wrapper->Stop(); + wrapper->Shutdown(); +} |