/* -*- 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 "gtest/gtest.h" #include "mozilla/SharedThreadPool.h" #include "mozilla/TaskQueue.h" #include "mozilla/UniquePtr.h" #include "MediaEventSource.h" #include "VideoUtils.h" using namespace mozilla; /* * Test if listeners receive the event data correctly. */ TEST(MediaEventSource, SingleListener) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource SingleListener"); MediaEventProducer source; int i = 0; auto func = [&](int j) { i += j; }; MediaEventListener listener = source.Connect(queue, func); // Call Notify 3 times. The listener should be also called 3 times. source.Notify(3); source.Notify(5); source.Notify(7); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); // Verify the event data is passed correctly to the listener. EXPECT_EQ(i, 15); // 3 + 5 + 7 listener.Disconnect(); } TEST(MediaEventSource, MultiListener) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource MultiListener"); MediaEventProducer source; int i = 0; int j = 0; auto func1 = [&](int k) { i = k * 2; }; auto func2 = [&](int k) { j = k * 3; }; MediaEventListener listener1 = source.Connect(queue, func1); MediaEventListener listener2 = source.Connect(queue, func2); // Both listeners should receive the event. source.Notify(11); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); // Verify the event data is passed correctly to the listener. EXPECT_EQ(i, 22); // 11 * 2 EXPECT_EQ(j, 33); // 11 * 3 listener1.Disconnect(); listener2.Disconnect(); } /* * Test if disconnecting a listener prevents events from coming. */ TEST(MediaEventSource, DisconnectAfterNotification) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource DisconnectAfterNotification"); MediaEventProducer source; int i = 0; MediaEventListener listener; auto func = [&](int j) { i += j; listener.Disconnect(); }; listener = source.Connect(queue, func); // Call Notify() twice. Since we disconnect the listener when receiving // the 1st event, the 2nd event should not reach the listener. source.Notify(11); source.Notify(11); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); // Check only the 1st event is received. EXPECT_EQ(i, 11); } TEST(MediaEventSource, DisconnectBeforeNotification) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource DisconnectBeforeNotification"); MediaEventProducer source; int i = 0; int j = 0; auto func1 = [&](int k) { i = k * 2; }; auto func2 = [&](int k) { j = k * 3; }; MediaEventListener listener1 = source.Connect(queue, func1); MediaEventListener listener2 = source.Connect(queue, func2); // Disconnect listener2 before notification. Only listener1 should receive // the event. listener2.Disconnect(); source.Notify(11); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); EXPECT_EQ(i, 22); // 11 * 2 EXPECT_EQ(j, 0); // event not received listener1.Disconnect(); } /* * Test we don't hit the assertion when calling Connect() and Disconnect() * repeatedly. */ TEST(MediaEventSource, DisconnectAndConnect) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource DisconnectAndConnect"); MediaEventProducerExc source; MediaEventListener listener = source.Connect(queue, []() {}); listener.Disconnect(); listener = source.Connect(queue, []() {}); listener.Disconnect(); } /* * Test void event type. */ TEST(MediaEventSource, VoidEventType) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource VoidEventType"); MediaEventProducer source; int i = 0; // Test function object. auto func = [&]() { ++i; }; MediaEventListener listener1 = source.Connect(queue, func); // Test member function. struct Foo { Foo() : j(1) {} void OnNotify() { j *= 2; } int j; } foo; MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify); // Call Notify 2 times. The listener should be also called 2 times. source.Notify(); source.Notify(); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); // Verify the event data is passed correctly to the listener. EXPECT_EQ(i, 2); // ++i called twice EXPECT_EQ(foo.j, 4); // |j *= 2| called twice listener1.Disconnect(); listener2.Disconnect(); } /* * Test listeners can take various event types (T, T&&, const T& and void). */ TEST(MediaEventSource, ListenerType1) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource ListenerType1"); MediaEventProducer source; int i = 0; // Test various argument types. auto func1 = [&](int&& j) { i += j; }; auto func2 = [&](const int& j) { i += j; }; auto func3 = [&]() { i += 1; }; MediaEventListener listener1 = source.Connect(queue, func1); MediaEventListener listener2 = source.Connect(queue, func2); MediaEventListener listener3 = source.Connect(queue, func3); source.Notify(1); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); EXPECT_EQ(i, 3); listener1.Disconnect(); listener2.Disconnect(); listener3.Disconnect(); } TEST(MediaEventSource, ListenerType2) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource ListenerType2"); MediaEventProducer source; struct Foo { Foo() : mInt(0) {} void OnNotify1(int&& i) { mInt += i; } void OnNotify2(const int& i) { mInt += i; } void OnNotify3() { mInt += 1; } void OnNotify4(int i) const { mInt += i; } void OnNotify5(int i) volatile { mInt = mInt + i; } mutable int mInt; } foo; // Test member functions which might be CV qualified. MediaEventListener listener1 = source.Connect(queue, &foo, &Foo::OnNotify1); MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify2); MediaEventListener listener3 = source.Connect(queue, &foo, &Foo::OnNotify3); MediaEventListener listener4 = source.Connect(queue, &foo, &Foo::OnNotify4); MediaEventListener listener5 = source.Connect(queue, &foo, &Foo::OnNotify5); source.Notify(1); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); EXPECT_EQ(foo.mInt, 5); listener1.Disconnect(); listener2.Disconnect(); listener3.Disconnect(); listener4.Disconnect(); listener5.Disconnect(); } struct SomeEvent { explicit SomeEvent(int& aCount) : mCount(aCount) {} // Increment mCount when copy constructor is called to know how many times // the event data is copied. SomeEvent(const SomeEvent& aOther) : mCount(aOther.mCount) { ++mCount; } SomeEvent(SomeEvent&& aOther) : mCount(aOther.mCount) {} int& mCount; }; /* * Test we don't have unnecessary copies of the event data. */ TEST(MediaEventSource, CopyEvent1) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource CopyEvent1"); MediaEventProducer source; int i = 0; auto func = [](SomeEvent&& aEvent) {}; struct Foo { void OnNotify(SomeEvent&& aEvent) {} } foo; MediaEventListener listener1 = source.Connect(queue, func); MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify); // We expect i to be 2 since SomeEvent should be copied only once when // passing to each listener. source.Notify(SomeEvent(i)); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); EXPECT_EQ(i, 2); listener1.Disconnect(); listener2.Disconnect(); } TEST(MediaEventSource, CopyEvent2) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource CopyEvent2"); MediaEventProducer source; int i = 0; auto func = []() {}; struct Foo { void OnNotify() {} } foo; MediaEventListener listener1 = source.Connect(queue, func); MediaEventListener listener2 = source.Connect(queue, &foo, &Foo::OnNotify); // SomeEvent won't be copied at all since the listeners take no arguments. source.Notify(SomeEvent(i)); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); EXPECT_EQ(i, 0); listener1.Disconnect(); listener2.Disconnect(); } /* * Test move-only types. */ TEST(MediaEventSource, MoveOnly) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource MoveOnly"); MediaEventProducerExc> source; auto func = [](UniquePtr&& aEvent) { EXPECT_EQ(*aEvent, 20); }; MediaEventListener listener = source.Connect(queue, func); // It is OK to pass an rvalue which is move-only. source.Notify(UniquePtr(new int(20))); // It is an error to pass an lvalue which is move-only. // UniquePtr event(new int(30)); // source.Notify(event); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); listener.Disconnect(); } struct RefCounter { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCounter) explicit RefCounter(int aVal) : mVal(aVal) {} int mVal; private: ~RefCounter() = default; }; /* * Test we should copy instead of move in NonExclusive mode * for each listener must get a copy. */ TEST(MediaEventSource, NoMove) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource NoMove"); MediaEventProducer> source; auto func1 = [](RefPtr&& aEvent) { EXPECT_EQ(aEvent->mVal, 20); }; auto func2 = [](RefPtr&& aEvent) { EXPECT_EQ(aEvent->mVal, 20); }; MediaEventListener listener1 = source.Connect(queue, func1); MediaEventListener listener2 = source.Connect(queue, func2); // We should copy this rvalue instead of move it in NonExclusive mode. RefPtr val = new RefCounter(20); source.Notify(std::move(val)); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); listener1.Disconnect(); listener2.Disconnect(); } /* * Rvalue lambda should be moved instead of copied. */ TEST(MediaEventSource, MoveLambda) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource MoveLambda"); MediaEventProducer source; int counter = 0; SomeEvent someEvent(counter); auto func = [someEvent]() {}; // someEvent is copied when captured by the lambda. EXPECT_EQ(someEvent.mCount, 1); // someEvent should be copied for we pass |func| as an lvalue. MediaEventListener listener1 = source.Connect(queue, func); EXPECT_EQ(someEvent.mCount, 2); // someEvent should be moved for we pass |func| as an rvalue. MediaEventListener listener2 = source.Connect(queue, std::move(func)); EXPECT_EQ(someEvent.mCount, 2); listener1.Disconnect(); listener2.Disconnect(); } template struct DestroyChecker { explicit DestroyChecker(Bool* aIsDestroyed) : mIsDestroyed(aIsDestroyed) { EXPECT_FALSE(*mIsDestroyed); } ~DestroyChecker() { EXPECT_FALSE(*mIsDestroyed); *mIsDestroyed = true; } private: Bool* const mIsDestroyed; }; class ClassForDestroyCheck final : private DestroyChecker { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ClassForDestroyCheck); explicit ClassForDestroyCheck(bool* aIsDestroyed) : DestroyChecker(aIsDestroyed) {} int32_t RefCountNums() const { return mRefCnt; } protected: ~ClassForDestroyCheck() = default; }; TEST(MediaEventSource, ResetFuncReferenceAfterDisconnect) { const RefPtr queue = TaskQueue::Create( GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource ResetFuncReferenceAfterDisconnect"); MediaEventProducer source; // Using a class that supports refcounting to check the object destruction. bool isDestroyed = false; auto object = MakeRefPtr(&isDestroyed); EXPECT_FALSE(isDestroyed); EXPECT_EQ(object->RefCountNums(), 1); // Function holds a strong reference to object. MediaEventListener listener = source.Connect(queue, [ptr = object] {}); EXPECT_FALSE(isDestroyed); EXPECT_EQ(object->RefCountNums(), 2); // This should destroy the function and release the object reference from the // function on the task queue, listener.Disconnect(); queue->BeginShutdown(); queue->AwaitShutdownAndIdle(); EXPECT_FALSE(isDestroyed); EXPECT_EQ(object->RefCountNums(), 1); // No one is holding reference to object, it should be destroyed // immediately. object = nullptr; EXPECT_TRUE(isDestroyed); } TEST(MediaEventSource, ResetTargetAfterDisconnect) { RefPtr queue = TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), "TestMediaEventSource ResetTargetAfterDisconnect"); MediaEventProducer source; MediaEventListener listener = source.Connect(queue, [] {}); // MediaEventListener::Disconnect eventually gives up its target listener.Disconnect(); queue->AwaitIdle(); // `queue` should be the last reference to the TaskQueue, meaning that this // Release destroys it. EXPECT_EQ(queue.forget().take()->Release(), 0u); }