blob: a22cf5f02daabb6f65491a092d15f4aa51fa1362 [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 <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/callback.h>
#include <base/files/scoped_file.h>
#include <base/message_loop/message_loop.h>
#include <base/run_loop.h>
#include <base/threading/thread.h>
#include <base/time/time.h>
#include <brillo/daemons/daemon.h>
#include <brillo/test_helpers.h>
#include <gtest/gtest.h>
#include "midis/client_tracker.h"
#include "midis/clientlib.h"
#include "midis/constants.h"
#include "midis/tests/test_helper.h"
namespace {
const char kClientThreadName[] = "client_thread";
const char kFakeName1[] = "Sample MIDI Device - 1";
const char kFakeManufacturer1[] = "Foo";
const uint32_t kFakeSysNum1 = 2;
const uint32_t kFakeDevNum1 = 0;
const uint32_t kFakeSubdevs1 = 1;
const uint32_t kFakeFlags1 = 7;
const char kFakeMidiData1[] = "0xDEADBEEF";
} // namespace
namespace midis {
base::ScopedFD RequestFakePort(int server_fd,
const struct MidisRequestPort& port_msg) {
EXPECT_EQ(0, MidisRequestPort(server_fd, &port_msg));
// Receive the port back.
uint32_t payload_size;
uint32_t type;
EXPECT_EQ(0, MidisProcessMsgHeader(server_fd, &payload_size, &type));
EXPECT_EQ(REQUEST_PORT_RESPONSE, type);
struct MidisRequestPort port_msg_ret;
memset(&port_msg_ret, 0, sizeof(port_msg_ret));
base::ScopedFD portfd(
MidisProcessRequestPortResponse(server_fd, &port_msg_ret));
EXPECT_TRUE(portfd.is_valid());
// Make sure the returned FD is for the port we requested.
EXPECT_EQ(port_msg_ret.card, port_msg.card);
EXPECT_EQ(port_msg_ret.device_num, port_msg.device_num);
EXPECT_EQ(port_msg_ret.subdevice_num, port_msg.subdevice_num);
return portfd;
}
void ConnectToClient(base::FilePath socketDir, base::FilePath devNodePath) {
uint8_t buf[kMaxBufSize];
// Try connecting to the client
std::string socket_path = socketDir.Append("midis_socket").value();
base::ScopedFD server_fd =
base::ScopedFD(MidisConnectToServer(socket_path.c_str()));
ASSERT_TRUE(server_fd.is_valid());
ASSERT_EQ(0, MidisListDevices(server_fd.get()));
uint32_t payload_size;
uint32_t type;
ASSERT_EQ(0, MidisProcessMsgHeader(server_fd.get(), &payload_size, &type));
EXPECT_EQ(LIST_DEVICES_RESPONSE, type);
std::vector<uint8_t> buffer(payload_size);
ASSERT_EQ(payload_size,
MidisProcessListDevices(server_fd.get(), buffer.data(),
buffer.size(), payload_size));
EXPECT_EQ(buffer[0], 1);
struct MidisRequestPort port_msg;
memset(&port_msg, 0, sizeof(port_msg));
port_msg.card = kFakeSysNum1;
port_msg.device_num = kFakeDevNum1;
port_msg.subdevice_num = kFakeSubdevs1 - 1;
base::ScopedFD portfd = RequestFakePort(server_fd.get(), port_msg);
// Create another client
base::ScopedFD server_fd2 =
base::ScopedFD(MidisConnectToServer(socket_path.c_str()));
ASSERT_TRUE(server_fd2.is_valid());
base::ScopedFD portfd2 = RequestFakePort(server_fd2.get(), port_msg);
// Write data to the dev-node to fake the MIDI h/w generating messages.
ASSERT_EQ(sizeof(kFakeMidiData1), base::WriteFile(devNodePath, kFakeMidiData1,
sizeof(kFakeMidiData1)));
// Read it back and verify.
memset(buf, 0, kMaxBufSize);
EXPECT_EQ(sizeof(kFakeMidiData1), read(portfd.get(), buf, kMaxBufSize));
EXPECT_EQ(0, memcmp(buf, kFakeMidiData1, sizeof(kFakeMidiData1)));
memset(buf, 0, kMaxBufSize);
EXPECT_EQ(sizeof(kFakeMidiData1), read(portfd2.get(), buf, kMaxBufSize));
EXPECT_EQ(0, memcmp(buf, kFakeMidiData1, sizeof(kFakeMidiData1)));
}
void CheckClientCallback(ClientTracker* cli_tracker, base::Closure quit) {
EXPECT_EQ(cli_tracker->GetNumClientsForTesting(), 2);
quit.Run();
}
class ClientTest : public ::testing::Test {
protected:
void SetUp() override {
CreateNewTempDirectory(base::FilePath::StringType(), &temp_fp_);
message_loop_.SetAsCurrent();
}
void TearDown() override { base::DeleteFile(temp_fp_, true); }
base::FilePath temp_fp_;
private:
brillo::BaseMessageLoop message_loop_;
};
// Check the following basic sequence.
// - Connect to a client tracker.
// - Request list of devices (DeviceTracker has a device added)
// - Request for a sub-device from the device in question.
// - Confirm that the data received is what was sent by the device.
TEST_F(ClientTest, AddClientAndReceiveMessages) {
ASSERT_FALSE(temp_fp_.empty());
base::FilePath socket_dir = CreateFakeTempSubDir(temp_fp_, "run/midis");
ASSERT_NE(socket_dir.value(), "");
base::FilePath dev_path = CreateFakeTempSubDir(temp_fp_, "dev/snd");
ASSERT_NE(dev_path.value(), "");
base::FilePath dev_node_path =
CreateDevNodeFileName(dev_path, kFakeSysNum1, kFakeDevNum1);
// Create a fake devnode and allow a poll on it.
ASSERT_EQ(0, mkfifo(dev_node_path.value().c_str(),
S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR));
Device::SetBaseDirForTesting(temp_fp_);
DeviceTracker device_tracker;
ClientTracker cli_tracker;
cli_tracker.SetBaseDirForTesting(temp_fp_);
ASSERT_TRUE(cli_tracker.InitClientTracker(&device_tracker));
device_tracker.AddDevice(
std::make_unique<Device>(kFakeName1, kFakeManufacturer1, kFakeSysNum1,
kFakeDevNum1, kFakeSubdevs1, kFakeFlags1));
ASSERT_EQ(device_tracker.devices_.size(), 1);
// Start the monitoring for the device, so that the file handlers
// are created correctly.
auto dev = device_tracker.devices_.find(
device_tracker.udev_handler_->GenerateDeviceId(kFakeSysNum1,
kFakeDevNum1));
ASSERT_NE(dev, device_tracker.devices_.end());
dev->second->StartMonitoring();
base::Thread client_thread(kClientThreadName);
ASSERT_TRUE(client_thread.Start());
base::RunLoop run_loop;
client_thread.task_runner()->PostTaskAndReply(
FROM_HERE, base::Bind(&ConnectToClient, socket_dir, dev_node_path),
base::Bind(&CheckClientCallback, &cli_tracker, run_loop.QuitClosure()));
run_loop.Run();
}
} // namespace midis