blob: 49283d9ebd075c0abe78d911dfd21418195571c9 [file] [log] [blame]
// Copyright 2017 The Chromium OS 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 <array>
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <brillo/test_helpers.h>
#include <gtest/gtest.h>
#include "midis/device.h"
#include "midis/tests/seq_handler_mock.h"
#include "midis/tests/test_helper.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace {
const std::array<uint8_t, 3> kValidBuffer1 = {{0x90, 0x3C, 0x40}};
const std::array<uint8_t, 3> kValidBuffer2 = {{0xC0, 0x0B}};
const std::array<uint8_t, 4> kInvalidBuffer3 = {{0x0A, 0x0B, 0x0C, 0x0D}};
const int kCorrectOutputDirectReturn = 28;
const int kOutClientId = 2;
// Set of mock function calls which we can set expectations on. These are passed
// into the constructor of SeqHandler() when needed.
class CallbacksMock {
public:
MOCK_METHOD(void, FakeAddDeviceCallbackMock, (midis::Device*));
void FakeAddDeviceCallback(std::unique_ptr<midis::Device> device) {
FakeAddDeviceCallbackMock(device.get());
}
MOCK_METHOD(void, RemoveDeviceCallback, (uint32_t, uint32_t));
MOCK_METHOD(void,
HandleReceiveDataCallback,
(uint32_t, uint32_t, uint32_t, const char*, size_t));
MOCK_METHOD(bool, IsDevicePresentCallback, (uint32_t, uint32_t));
MOCK_METHOD(bool, IsPortPresentCallback, (uint32_t, uint32_t, uint32_t));
};
} // namespace
namespace midis {
class SeqHandlerTest : public ::testing::Test {};
// Check whether Device gets created successfully.
TEST_F(SeqHandlerTest, TestEncodeBytes) {
auto seq_handler = std::make_unique<SeqHandlerMock>();
EXPECT_CALL(*seq_handler, SndSeqEventOutputDirect(_, _))
.WillOnce(Return(kCorrectOutputDirectReturn))
.WillOnce(Return(kCorrectOutputDirectReturn))
.WillOnce(Return(kCorrectOutputDirectReturn + 1));
snd_midi_event_t* encoder;
// Test that encoding works correctly.
ASSERT_EQ(snd_midi_event_new(kValidBuffer1.size(), &encoder), 0);
EXPECT_EQ(seq_handler->EncodeMidiBytes(0, nullptr, kValidBuffer1.data(),
kValidBuffer1.size(), encoder),
true);
snd_midi_event_free(encoder);
// Test that encoding works correctly - 2.
ASSERT_EQ(snd_midi_event_new(kValidBuffer2.size(), &encoder), 0);
EXPECT_EQ(seq_handler->EncodeMidiBytes(0, nullptr, kValidBuffer2.data(),
kValidBuffer2.size(), encoder),
true);
snd_midi_event_free(encoder);
// Test for failure when OutputDirect returns incorrect value.
ASSERT_EQ(snd_midi_event_new(kValidBuffer1.size(), &encoder), 0);
EXPECT_EQ(seq_handler->EncodeMidiBytes(0, nullptr, kValidBuffer1.data(),
kValidBuffer1.size(), encoder),
false);
snd_midi_event_free(encoder);
// Test for failure when we supply gibberish data.
ASSERT_EQ(snd_midi_event_new(kInvalidBuffer3.size(), &encoder), 0);
EXPECT_EQ(seq_handler->EncodeMidiBytes(0, nullptr, kInvalidBuffer3.data(),
kInvalidBuffer3.size(), encoder),
false);
snd_midi_event_free(encoder);
}
// Check that ProcessAlsaClientFd errors out correctly for various error inputs.
TEST_F(SeqHandlerTest, TestProcessAlsaClientFdNegative) {
auto seq_handler = std::make_unique<SeqHandlerMock>();
// None of these functions should ever be called.
EXPECT_CALL(*seq_handler, AddSeqDevice(_)).Times(0);
EXPECT_CALL(*seq_handler, AddSeqPort(_, _)).Times(0);
EXPECT_CALL(*seq_handler, RemoveSeqDevice(_)).Times(0);
EXPECT_CALL(*seq_handler, RemoveSeqPort(_, _)).Times(0);
EXPECT_CALL(*seq_handler, ProcessMidiEvent(_)).Times(0);
EXPECT_CALL(*seq_handler, SndSeqEventInput(_, _))
.WillOnce(DoAll(SetArgPointee<1>(nullptr), Return(-ENOSPC)));
EXPECT_CALL(*seq_handler, SndSeqEventInputPending(_, _)).WillOnce(Return(0));
seq_handler->ProcessAlsaClientFd();
snd_seq_event_t invalid_event = {
// This event type should never show up on this client+port.
.type = SND_SEQ_EVENT_SONGPOS,
.source = {
.client = SND_SEQ_CLIENT_SYSTEM,
.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE,
}};
// Check invalid events.
EXPECT_CALL(*seq_handler, SndSeqEventInput(_, _))
.WillOnce(DoAll(SetArgPointee<1>(&invalid_event), Return(0)));
EXPECT_CALL(*seq_handler, SndSeqEventInputPending(_, _)).WillOnce(Return(0));
seq_handler->ProcessAlsaClientFd();
}
// Check that ProcessAlsaClientFd handles various valid events correctly.
TEST_F(SeqHandlerTest, TestProcessAlsaClientFdPositive) {
auto seq_handler = std::make_unique<SeqHandlerMock>();
snd_seq_event_t valid_event1 = {.type = SND_SEQ_EVENT_PORT_START,
.source = {
.client = SND_SEQ_CLIENT_SYSTEM,
.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE,
}};
EXPECT_CALL(*seq_handler, AddSeqDevice(_)).Times(1);
EXPECT_CALL(*seq_handler, AddSeqPort(_, _)).Times(1);
EXPECT_CALL(*seq_handler, RemoveSeqDevice(_)).Times(0);
EXPECT_CALL(*seq_handler, RemoveSeqPort(_, _)).Times(0);
EXPECT_CALL(*seq_handler, ProcessMidiEvent(_)).Times(0);
EXPECT_CALL(*seq_handler, SndSeqEventInput(_, _))
.WillOnce(DoAll(SetArgPointee<1>(&valid_event1), Return(0)));
EXPECT_CALL(*seq_handler, SndSeqEventInputPending(_, _)).WillOnce(Return(0));
seq_handler->ProcessAlsaClientFd();
snd_seq_event_t valid_event2 = {
.type = SND_SEQ_EVENT_CLIENT_EXIT,
.source = {.client = SND_SEQ_CLIENT_SYSTEM,
.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE},
.data = {.addr = {.client = 3, .port = 4}}};
seq_handler->out_client_id_ = kOutClientId;
EXPECT_CALL(*seq_handler, AddSeqDevice(_)).Times(0);
EXPECT_CALL(*seq_handler, AddSeqPort(_, _)).Times(0);
EXPECT_CALL(*seq_handler, RemoveSeqDevice(_)).Times(1);
EXPECT_CALL(*seq_handler, RemoveSeqPort(_, _)).Times(0);
EXPECT_CALL(*seq_handler, ProcessMidiEvent(_)).Times(0);
EXPECT_CALL(*seq_handler, SndSeqEventInput(_, _))
.WillOnce(DoAll(SetArgPointee<1>(&valid_event2), Return(0)));
EXPECT_CALL(*seq_handler, SndSeqEventInputPending(_, _)).WillOnce(Return(0));
seq_handler->ProcessAlsaClientFd();
}
// Check that ProcessMidiEvent can successfully decode certain MIDI messages.
// TODO(pmalani): Check SysEx messages.
TEST_F(SeqHandlerTest, TestProcessMidiEventsPositive) {
CallbacksMock callbacks;
EXPECT_CALL(callbacks, FakeAddDeviceCallbackMock(_)).Times(0);
EXPECT_CALL(callbacks, RemoveDeviceCallback(_, _)).Times(0);
EXPECT_CALL(callbacks, HandleReceiveDataCallback(_, _, _, _, _)).Times(3);
EXPECT_CALL(callbacks, IsDevicePresentCallback(_, _)).Times(0);
EXPECT_CALL(callbacks, IsPortPresentCallback(_, _, _)).Times(0);
auto seq_handler = std::make_unique<SeqHandler>(
base::Bind(&CallbacksMock::FakeAddDeviceCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::RemoveDeviceCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::HandleReceiveDataCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::IsDevicePresentCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::IsPortPresentCallback,
base::Unretained(&callbacks)));
// Initialize decoder.
seq_handler->decoder_ = SeqHandler::CreateMidiEvent(0);
snd_seq_event_t valid_event1 = {
.type = SND_SEQ_EVENT_NOTEON,
.data = {.raw8 = {{0x00, 0x30, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00}}}};
seq_handler->ProcessMidiEvent(&valid_event1);
snd_seq_event_t valid_event2 = {
.type = SND_SEQ_EVENT_PITCHBEND,
.data = {
.raw8 = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1,
0xff, 0xff}},
}};
seq_handler->ProcessMidiEvent(&valid_event2);
snd_seq_event_t valid_event3 = {
.type = SND_SEQ_EVENT_CONTROLLER,
.data = {
.raw8 = {{0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x41, 0x00,
0x00, 0x00}},
}};
seq_handler->ProcessMidiEvent(&valid_event3);
}
// Check that ProcessMidiEvent can detect invalid MIDI messages.
TEST_F(SeqHandlerTest, TestProcessMidiEventsNegative) {
CallbacksMock callbacks;
EXPECT_CALL(callbacks, FakeAddDeviceCallbackMock(_)).Times(0);
EXPECT_CALL(callbacks, RemoveDeviceCallback(_, _)).Times(0);
EXPECT_CALL(callbacks, HandleReceiveDataCallback(_, _, _, _, _)).Times(0);
EXPECT_CALL(callbacks, IsDevicePresentCallback(_, _)).Times(0);
EXPECT_CALL(callbacks, IsPortPresentCallback(_, _, _)).Times(0);
auto seq_handler = std::make_unique<SeqHandler>(
base::Bind(&CallbacksMock::FakeAddDeviceCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::RemoveDeviceCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::HandleReceiveDataCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::IsDevicePresentCallback,
base::Unretained(&callbacks)),
base::Bind(&CallbacksMock::IsPortPresentCallback,
base::Unretained(&callbacks)));
// Initialize decoder.
seq_handler->decoder_ = SeqHandler::CreateMidiEvent(0);
snd_seq_event_t invalid_event1 = {
.type = SND_SEQ_EVENT_PORT_EXIT,
.data = {
.raw8 = {{0x00, 0xff, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x41, 0x00,
0x00, 0x00}},
}};
seq_handler->ProcessMidiEvent(&invalid_event1);
}
} // namespace midis