tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestAudioSinkWrapper.cpp (5924B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AudioSink.h"
      8 #include "AudioSinkWrapper.h"
      9 #include "CubebUtils.h"
     10 #include "MockCubeb.h"
     11 #include "TimeUnits.h"
     12 #include "gmock/gmock.h"
     13 #include "gtest/gtest-printers.h"
     14 #include "gtest/gtest.h"
     15 #include "mozilla/SpinEventLoopUntil.h"
     16 #include "mozilla/gtest/WaitFor.h"
     17 #include "nsThreadManager.h"
     18 #include "nsThreadUtils.h"
     19 
     20 using namespace mozilla;
     21 
     22 // This is a crashtest to check that AudioSinkWrapper::mEndedPromiseHolder is
     23 // not settled twice when sync and async AudioSink initializations race.
     24 TEST(TestAudioSinkWrapper, AsyncInitFailureWithSyncInitSuccess)
     25 {
     26  MockCubeb* cubeb = new MockCubeb();
     27  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
     28 
     29  MediaQueue<AudioData> audioQueue;
     30  MediaInfo info;
     31  info.EnableAudio();
     32  auto audioSinkCreator = [&]() {
     33    return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(),
     34                                              audioQueue, info.mAudio,
     35                                              /*resistFingerprinting*/ false)};
     36  };
     37  const double initialVolume = 0.0;  // so that there is initially no AudioSink
     38  RefPtr wrapper = new AudioSinkWrapper(
     39      AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator),
     40      initialVolume, /*playbackRate*/ 1.0, /*preservesPitch*/ true,
     41      /*sinkDevice*/ nullptr);
     42 
     43  wrapper->Start(media::TimeUnit::Zero(), info);
     44  // The first AudioSink init occurs on a background thread.  Listen for this,
     45  // but don't process any events on the current thread so that the
     46  // AudioSinkWrapper does not yet handle the result of AudioSink
     47  // initialization.
     48  RefPtr backgroundQueue =
     49      nsThreadManager::get().CreateBackgroundTaskQueue(__func__);
     50  Monitor monitor(__func__);
     51  bool initDone = false;
     52  MediaEventListener initListener = cubeb->StreamInitEvent().Connect(
     53      backgroundQueue, [&](RefPtr<SmartMockCubebStream> aStream) {
     54        EXPECT_EQ(aStream, nullptr);
     55        MonitorAutoLock lock(monitor);
     56        initDone = true;
     57        lock.Notify();
     58      });
     59  cubeb->ForceStreamInitError();
     60  wrapper->SetVolume(0.5);  // triggers async sink init, which fails
     61  {
     62    // Wait for the async init to complete.
     63    MonitorAutoLock lock(monitor);
     64    while (!initDone) {
     65      lock.Wait();
     66    }
     67  }
     68  initListener.Disconnect();
     69  wrapper->SetPlaying(false);
     70  // The second AudioSink init is synchronous.
     71  nsIThread* currentThread = NS_GetCurrentThread();
     72  RefPtr<SmartMockCubebStream> stream;
     73  initListener = cubeb->StreamInitEvent().Connect(
     74      currentThread, [&](RefPtr<SmartMockCubebStream> aStream) {
     75        stream = std::move(aStream);
     76      });
     77  wrapper->SetPlaying(true);  // sync sink init, which succeeds
     78  // Let AudioSinkWrapper handle the (first) AudioSink initialization failure
     79  // and allow `stream` to be set.
     80  NS_ProcessPendingEvents(currentThread);
     81  initListener.Disconnect();
     82  cubeb_state state = CUBEB_STATE_STARTED;
     83  MediaEventListener stateListener = stream->StateEvent().Connect(
     84      currentThread, [&](cubeb_state aState) { state = aState; });
     85  // Run AudioSinkWrapper::OnAudioEnded().
     86  // This test passes if there is no crash.  Bug 1845811.
     87  audioQueue.Finish();
     88  SpinEventLoopUntil("stream state change"_ns,
     89                     [&] { return state != CUBEB_STATE_STARTED; });
     90  stateListener.Disconnect();
     91  EXPECT_EQ(state, CUBEB_STATE_DRAINED);
     92  wrapper->Stop();
     93  wrapper->Shutdown();
     94 }
     95 
     96 // This is a crashtest to check that AudioSinkWrapper::mEndedPromiseHolder is
     97 // not settled twice when the audio ends during async AudioSink initialization.
     98 TEST(TestAudioSinkWrapper, AsyncInitWithEndOfAudio)
     99 {
    100  MockCubeb* cubeb = new MockCubeb();
    101  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
    102 
    103  MediaQueue<AudioData> audioQueue;
    104  MediaInfo info;
    105  info.EnableAudio();
    106  auto audioSinkCreator = [&]() {
    107    return UniquePtr<AudioSink>{new AudioSink(AbstractThread::GetCurrent(),
    108                                              audioQueue, info.mAudio,
    109                                              /*resistFingerprinting*/ false)};
    110  };
    111  const double initialVolume = 0.0;  // so that there is initially no AudioSink
    112  RefPtr wrapper = new AudioSinkWrapper(
    113      AbstractThread::GetCurrent(), audioQueue, std::move(audioSinkCreator),
    114      initialVolume, /*playbackRate*/ 1.0, /*preservesPitch*/ true,
    115      /*sinkDevice*/ nullptr);
    116 
    117  wrapper->Start(media::TimeUnit::Zero(), info);
    118  // The first AudioSink init occurs on a background thread.  Listen for this,
    119  // but don't process any events on the current thread so that the
    120  // AudioSinkWrapper does not yet use the initialized AudioSink.
    121  RefPtr backgroundQueue =
    122      nsThreadManager::get().CreateBackgroundTaskQueue(__func__);
    123  Monitor monitor(__func__);
    124  RefPtr<SmartMockCubebStream> stream;
    125  MediaEventListener initListener = cubeb->StreamInitEvent().Connect(
    126      backgroundQueue, [&](RefPtr<SmartMockCubebStream> aStream) {
    127        EXPECT_NE(aStream, nullptr);
    128        MonitorAutoLock lock(monitor);
    129        stream = std::move(aStream);
    130        lock.Notify();
    131      });
    132  wrapper->SetVolume(0.5);  // triggers async sink init
    133  {
    134    // Wait for the async init to complete.
    135    MonitorAutoLock lock(monitor);
    136    while (!stream) {
    137      lock.Wait();
    138    }
    139  }
    140  initListener.Disconnect();
    141  // Finish the audio before AudioSinkWrapper considers using the initialized
    142  // AudioSink.
    143  audioQueue.Finish();
    144  // Wait for AudioSinkWrapper to destroy the initialized stream.
    145  // This test passes if there is no crash.  Bug 1846854.
    146  WaitFor(cubeb->StreamDestroyEvent());
    147  wrapper->Stop();
    148  wrapper->Shutdown();
    149 }