diff options
Diffstat (limited to 'dom/media/gtest/TestCubebInputStream.cpp')
-rw-r--r-- | dom/media/gtest/TestCubebInputStream.cpp | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/dom/media/gtest/TestCubebInputStream.cpp b/dom/media/gtest/TestCubebInputStream.cpp new file mode 100644 index 0000000000..d5fb73edc9 --- /dev/null +++ b/dom/media/gtest/TestCubebInputStream.cpp @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */ + +#include "CubebInputStream.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/WaitFor.h" +#include "MockCubeb.h" + +using namespace mozilla; + +namespace { +#define DispatchFunction(f) \ + NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f)) +} // namespace + +class MockListener : public CubebInputStream::Listener { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockListener, override); + MOCK_METHOD2(DataCallback, long(const void* aBuffer, long aFrames)); + MOCK_METHOD1(StateCallback, void(cubeb_state aState)); + MOCK_METHOD0(DeviceChangedCallback, void()); + + private: + ~MockListener() = default; +}; + +TEST(TestCubebInputStream, DataCallback) +{ + using ::testing::Ne; + using ::testing::NotNull; + + MockCubeb* cubeb = new MockCubeb(); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + const CubebUtils::AudioDeviceID deviceId = nullptr; + const uint32_t channels = 2; + + uint32_t rate = 0; + ASSERT_EQ(cubeb_get_preferred_sample_rate(cubeb->AsCubebContext(), &rate), + CUBEB_OK); + + nsTArray<AudioDataValue> data; + auto listener = MakeRefPtr<MockListener>(); + EXPECT_CALL(*listener, DataCallback(NotNull(), Ne(0))) + .WillRepeatedly([&](const void* aBuffer, long aFrames) { + const AudioDataValue* source = + reinterpret_cast<const AudioDataValue*>(aBuffer); + size_t sampleCount = + static_cast<size_t>(aFrames) * static_cast<size_t>(channels); + data.AppendElements(source, sampleCount); + return aFrames; + }); + + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_STARTED)); + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_STOPPED)).Times(2); + + EXPECT_CALL(*listener, DeviceChangedCallback).Times(0); + + UniquePtr<CubebInputStream> cis; + DispatchFunction([&] { + cis = CubebInputStream::Create(deviceId, channels, rate, true, + listener.get()); + ASSERT_TRUE(cis); + }); + RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); + EXPECT_TRUE(stream->mHasInput); + + stream->SetInputRecordingEnabled(true); + + DispatchFunction([&] { ASSERT_EQ(cis->Start(), CUBEB_OK); }); + WaitFor(stream->FramesProcessedEvent()); + + DispatchFunction([&] { ASSERT_EQ(cis->Stop(), CUBEB_OK); }); + WaitFor(stream->OutputVerificationEvent()); + + nsTArray<AudioDataValue> record = stream->TakeRecordedInput(); + + DispatchFunction([&] { cis = nullptr; }); + WaitFor(cubeb->StreamDestroyEvent()); + + ASSERT_EQ(data, record); +} + +TEST(TestCubebInputStream, ErrorCallback) +{ + using ::testing::Ne; + using ::testing::NotNull; + using ::testing::ReturnArg; + + MockCubeb* cubeb = new MockCubeb(); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + const CubebUtils::AudioDeviceID deviceId = nullptr; + const uint32_t channels = 2; + + uint32_t rate = 0; + ASSERT_EQ(cubeb_get_preferred_sample_rate(cubeb->AsCubebContext(), &rate), + CUBEB_OK); + + auto listener = MakeRefPtr<MockListener>(); + EXPECT_CALL(*listener, DataCallback(NotNull(), Ne(0))) + .WillRepeatedly(ReturnArg<1>()); + + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_STARTED)); + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_ERROR)); + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_STOPPED)); + + EXPECT_CALL(*listener, DeviceChangedCallback).Times(0); + + UniquePtr<CubebInputStream> cis; + DispatchFunction([&] { + cis = CubebInputStream::Create(deviceId, channels, rate, true, + listener.get()); + ASSERT_TRUE(cis); + }); + RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); + EXPECT_TRUE(stream->mHasInput); + + DispatchFunction([&] { ASSERT_EQ(cis->Start(), CUBEB_OK); }); + WaitFor(stream->FramesProcessedEvent()); + + DispatchFunction([&] { stream->ForceError(); }); + WaitFor(stream->ErrorForcedEvent()); + + // If stream ran into an error state, then it should be stopped. + + DispatchFunction([&] { cis = nullptr; }); + WaitFor(cubeb->StreamDestroyEvent()); +} + +TEST(TestCubebInputStream, DeviceChangedCallback) +{ + using ::testing::Ne; + using ::testing::NotNull; + using ::testing::ReturnArg; + + MockCubeb* cubeb = new MockCubeb(); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + const CubebUtils::AudioDeviceID deviceId = nullptr; + const uint32_t channels = 2; + + uint32_t rate = 0; + ASSERT_EQ(cubeb_get_preferred_sample_rate(cubeb->AsCubebContext(), &rate), + CUBEB_OK); + + auto listener = MakeRefPtr<MockListener>(); + EXPECT_CALL(*listener, DataCallback(NotNull(), Ne(0))) + .WillRepeatedly(ReturnArg<1>()); + + // In real world, the stream might run into an error state when the + // device-changed event is fired (e.g., the last default output device is + // unplugged). But it's fine to not check here since we can control how + // MockCubeb behaves. + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_STARTED)); + EXPECT_CALL(*listener, StateCallback(CUBEB_STATE_STOPPED)).Times(2); + + EXPECT_CALL(*listener, DeviceChangedCallback); + + UniquePtr<CubebInputStream> cis; + DispatchFunction([&] { + cis = CubebInputStream::Create(deviceId, channels, rate, true, + listener.get()); + ASSERT_TRUE(cis); + }); + RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent()); + EXPECT_TRUE(stream->mHasInput); + + DispatchFunction([&] { ASSERT_EQ(cis->Start(), CUBEB_OK); }); + WaitFor(stream->FramesProcessedEvent()); + + DispatchFunction([&] { stream->ForceDeviceChanged(); }); + WaitFor(stream->DeviceChangeForcedEvent()); + + // The stream can keep running when its device is changed. + DispatchFunction([&] { ASSERT_EQ(cis->Stop(), CUBEB_OK); }); + cubeb_state state = WaitFor(stream->StateEvent()); + EXPECT_EQ(state, CUBEB_STATE_STOPPED); + + DispatchFunction([&] { cis = nullptr; }); + WaitFor(cubeb->StreamDestroyEvent()); +} |