blob: d7bceb13910c1a0fef7a36b0c4a798c80a8c4bba [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/midi/midi_manager.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/system/system_monitor.h"
#include "base/test/scoped_task_environment.h"
#include "build/build_config.h"
#include "media/midi/midi_service.h"
#include "media/midi/task_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN)
#include "media/midi/midi_manager_win.h"
#endif // defined(OS_WIN)
namespace midi {
namespace {
using mojom::PortState;
using mojom::Result;
class FakeMidiManager : public MidiManager {
public:
explicit FakeMidiManager(MidiService* service)
: MidiManager(service), weak_factory_(this) {}
~FakeMidiManager() override = default;
base::WeakPtr<FakeMidiManager> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// MidiManager implementation.
void StartInitialization() override {
DCHECK(!initialized_);
initialized_ = true;
}
void DispatchSendMidiData(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
base::TimeTicks timestamp) override {}
// Utility functions for testing.
void CallCompleteInitialization(Result result) {
CompleteInitialization(result);
}
size_t GetClientCount() { return GetClientCountForTesting(); }
size_t GetPendingClientCount() { return GetPendingClientCountForTesting(); }
bool IsInitialized() const { return initialized_; }
private:
bool initialized_ = false;
base::WeakPtrFactory<FakeMidiManager> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(FakeMidiManager);
};
class FakeMidiManagerFactory : public MidiService::ManagerFactory {
public:
FakeMidiManagerFactory() : weak_factory_(this) {}
~FakeMidiManagerFactory() override = default;
std::unique_ptr<MidiManager> Create(MidiService* service) override {
std::unique_ptr<FakeMidiManager> manager =
std::make_unique<FakeMidiManager>(service);
manager_ = manager->GetWeakPtr();
return manager;
}
base::WeakPtr<FakeMidiManagerFactory> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
base::WeakPtr<FakeMidiManager> manager() {
#if defined(OS_MACOSX)
// To avoid Core MIDI issues, MidiManager won't be destructed on macOS.
// See https://crbug.com/718140.
if (!manager_ ||
(!manager_->GetClientCount() && !manager_->GetPendingClientCount())) {
return nullptr;
}
#endif
return manager_;
}
private:
base::WeakPtr<FakeMidiManager> manager_ = nullptr;
base::WeakPtrFactory<FakeMidiManagerFactory> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(FakeMidiManagerFactory);
};
class FakeMidiManagerClient : public MidiManagerClient {
public:
FakeMidiManagerClient() = default;
~FakeMidiManagerClient() override = default;
// MidiManagerClient implementation.
void AddInputPort(const mojom::PortInfo& info) override {}
void AddOutputPort(const mojom::PortInfo& info) override {}
void SetInputPortState(uint32_t port_index, PortState state) override {}
void SetOutputPortState(uint32_t port_index, PortState state) override {}
void CompleteStartSession(Result result) override {
EXPECT_TRUE(wait_for_result_);
result_ = result;
wait_for_result_ = false;
}
void ReceiveMidiData(uint32_t port_index,
const uint8_t* data,
size_t size,
base::TimeTicks timestamp) override {}
void AccumulateMidiBytesSent(size_t size) override {}
void Detach() override {}
Result result() const { return result_; }
Result WaitForResult() {
while (wait_for_result_) {
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
return result();
}
private:
Result result_ = Result::NOT_SUPPORTED;
bool wait_for_result_ = true;
DISALLOW_COPY_AND_ASSIGN(FakeMidiManagerClient);
};
class MidiManagerTest : public ::testing::Test {
public:
MidiManagerTest() {
std::unique_ptr<FakeMidiManagerFactory> factory =
std::make_unique<FakeMidiManagerFactory>();
factory_ = factory->GetWeakPtr();
service_ = std::make_unique<MidiService>(std::move(factory));
}
~MidiManagerTest() override {
service_->Shutdown();
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
protected:
void StartTheFirstSession(FakeMidiManagerClient* client) {
DCHECK(factory_);
EXPECT_FALSE(factory_->manager());
service_->StartSession(client);
ASSERT_TRUE(factory_->manager());
EXPECT_TRUE(factory_->manager()->IsInitialized());
EXPECT_EQ(0U, factory_->manager()->GetClientCount());
EXPECT_EQ(1U, factory_->manager()->GetPendingClientCount());
}
void StartTheNthSession(FakeMidiManagerClient* client, size_t nth) {
DCHECK(factory_);
DCHECK_NE(1U, nth);
ASSERT_TRUE(factory_->manager());
EXPECT_TRUE(factory_->manager()->IsInitialized());
EXPECT_EQ(0U, factory_->manager()->GetClientCount());
EXPECT_EQ(nth - 1U, factory_->manager()->GetPendingClientCount());
service_->StartSession(client);
EXPECT_EQ(nth, factory_->manager()->GetPendingClientCount());
}
void StartSession(FakeMidiManagerClient* client) {
service_->StartSession(client);
}
void EndSession(FakeMidiManagerClient* client, size_t before, size_t after) {
DCHECK(factory_);
ASSERT_TRUE(factory_->manager());
EXPECT_EQ(before, factory_->manager()->GetClientCount());
EXPECT_TRUE(service_->EndSession(client));
if (after) {
ASSERT_TRUE(factory_->manager());
EXPECT_EQ(after, factory_->manager()->GetClientCount());
} else {
EXPECT_FALSE(factory_->manager());
}
}
bool CompleteInitialization(Result result) {
DCHECK(factory_);
if (!factory_->manager())
return false;
factory_->manager()->CallCompleteInitialization(result);
return true;
}
void RunLoopUntilIdle() {
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
base::WeakPtr<FakeMidiManagerFactory> factory() { return factory_; }
private:
base::test::ScopedTaskEnvironment env_;
base::WeakPtr<FakeMidiManagerFactory> factory_;
std::unique_ptr<MidiService> service_;
DISALLOW_COPY_AND_ASSIGN(MidiManagerTest);
};
TEST_F(MidiManagerTest, StartAndEndSession) {
std::unique_ptr<FakeMidiManagerClient> client =
std::make_unique<FakeMidiManagerClient>();
StartTheFirstSession(client.get());
EXPECT_TRUE(CompleteInitialization(Result::OK));
EXPECT_EQ(Result::OK, client->WaitForResult());
EndSession(client.get(), 1U, 0U);
}
TEST_F(MidiManagerTest, StartAndEndSessionWithError) {
std::unique_ptr<FakeMidiManagerClient> client =
std::make_unique<FakeMidiManagerClient>();
StartTheFirstSession(client.get());
EXPECT_TRUE(CompleteInitialization(Result::INITIALIZATION_ERROR));
EXPECT_EQ(Result::INITIALIZATION_ERROR, client->WaitForResult());
EndSession(client.get(), 1U, 0U);
}
TEST_F(MidiManagerTest, StartMultipleSessions) {
std::unique_ptr<FakeMidiManagerClient> client1 =
std::make_unique<FakeMidiManagerClient>();
std::unique_ptr<FakeMidiManagerClient> client2 =
std::make_unique<FakeMidiManagerClient>();
std::unique_ptr<FakeMidiManagerClient> client3 =
std::make_unique<FakeMidiManagerClient>();
StartTheFirstSession(client1.get());
StartTheNthSession(client2.get(), 2);
StartTheNthSession(client3.get(), 3);
EXPECT_TRUE(CompleteInitialization(Result::OK));
EXPECT_EQ(Result::OK, client1->WaitForResult());
EXPECT_EQ(Result::OK, client2->WaitForResult());
EXPECT_EQ(Result::OK, client3->WaitForResult());
EndSession(client1.get(), 3U, 2U);
EndSession(client2.get(), 2U, 1U);
EndSession(client3.get(), 1U, 0U);
}
TEST_F(MidiManagerTest, TooManyPendingSessions) {
// Push as many client requests for starting session as possible.
std::vector<std::unique_ptr<FakeMidiManagerClient>> many_existing_clients;
many_existing_clients.resize(MidiManager::kMaxPendingClientCount);
many_existing_clients[0] = std::make_unique<FakeMidiManagerClient>();
StartTheFirstSession(many_existing_clients[0].get());
for (size_t i = 1; i < MidiManager::kMaxPendingClientCount; ++i) {
many_existing_clients[i] = std::make_unique<FakeMidiManagerClient>();
StartTheNthSession(many_existing_clients[i].get(), i + 1);
}
ASSERT_TRUE(factory()->manager());
EXPECT_TRUE(factory()->manager()->IsInitialized());
// Push the last client that should be rejected for too many pending requests.
std::unique_ptr<FakeMidiManagerClient> additional_client =
std::make_unique<FakeMidiManagerClient>();
StartSession(additional_client.get());
EXPECT_EQ(Result::INITIALIZATION_ERROR, additional_client->result());
// Other clients still should not receive a result.
RunLoopUntilIdle();
for (size_t i = 0; i < many_existing_clients.size(); ++i)
EXPECT_EQ(Result::NOT_SUPPORTED, many_existing_clients[i]->result());
// The Result::OK should be distributed to other clients.
EXPECT_TRUE(CompleteInitialization(Result::OK));
for (size_t i = 0; i < many_existing_clients.size(); ++i)
EXPECT_EQ(Result::OK, many_existing_clients[i]->WaitForResult());
// Close all successful sessions in FIFO order.
size_t sessions = many_existing_clients.size();
for (size_t i = 0; i < many_existing_clients.size(); ++i, --sessions)
EndSession(many_existing_clients[i].get(), sessions, sessions - 1);
}
TEST_F(MidiManagerTest, AbortSession) {
// A client starting a session can be destructed while an asynchronous
// initialization is performed.
std::unique_ptr<FakeMidiManagerClient> client =
std::make_unique<FakeMidiManagerClient>();
StartTheFirstSession(client.get());
EndSession(client.get(), 0, 0);
client.reset();
// Following function should not call the destructed |client| function.
EXPECT_FALSE(CompleteInitialization(Result::OK));
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
class PlatformMidiManagerTest : public ::testing::Test {
public:
PlatformMidiManagerTest()
: client_(std::make_unique<FakeMidiManagerClient>()),
service_(std::make_unique<MidiService>()) {
//
}
~PlatformMidiManagerTest() override {
service_->Shutdown();
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
MidiService* service() { return service_.get(); }
void StartSession() { service_->StartSession(client_.get()); }
void EndSession() { service_->EndSession(client_.get()); }
Result WaitForResult() { return client_->WaitForResult(); }
// This #ifdef needs to be identical to the one in media/midi/midi_manager.cc.
// Do not change the condition for disabling this test.
bool IsSupported() {
#if !defined(OS_MACOSX) && !defined(OS_WIN) && \
!(defined(USE_ALSA) && defined(USE_UDEV)) && !defined(OS_ANDROID)
return false;
#else
return true;
#endif
}
private:
// SystemMonitor is needed on Windows.
base::SystemMonitor system_monitor;
base::test::ScopedTaskEnvironment env_;
std::unique_ptr<FakeMidiManagerClient> client_;
std::unique_ptr<MidiService> service_;
DISALLOW_COPY_AND_ASSIGN(PlatformMidiManagerTest);
};
#if defined(OS_ANDROID)
// The test sometimes fails on Android. https://crbug.com/844027
#define MAYBE_CreatePlatformMidiManager DISABLED_CreatePlatformMidiManager
#else
#define MAYBE_CreatePlatformMidiManager CreatePlatformMidiManager
#endif
TEST_F(PlatformMidiManagerTest, MAYBE_CreatePlatformMidiManager) {
StartSession();
Result result = WaitForResult();
#if defined(USE_ALSA)
// Temporary until http://crbug.com/371230 is resolved.
EXPECT_TRUE(result == Result::OK || result == Result::INITIALIZATION_ERROR);
#else
EXPECT_EQ(IsSupported() ? Result::OK : Result::NOT_SUPPORTED, result);
#endif
}
TEST_F(PlatformMidiManagerTest, InstanceIdOverflow) {
service()->task_service()->OverflowInstanceIdForTesting();
#if defined(OS_WIN)
MidiManagerWin::OverflowInstanceIdForTesting();
#endif // defined(OS_WIN)
StartSession();
EXPECT_EQ(
IsSupported() ? Result::INITIALIZATION_ERROR : Result::NOT_SUPPORTED,
WaitForResult());
EndSession();
}
} // namespace
} // namespace midi