blob: 1853ae438f2b576f52099f5969ff7f82de99539e [file] [log] [blame]
// Copyright (c) 2013 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 <stdio.h>
#include <gtest/gtest.h>
extern "C" {
#include "cras_messages.h"
// Include C file to test static functions.
#include "cras_client.c"
}
static const cras_stream_id_t FIRST_STREAM_ID = 1;
static int pthread_create_called;
static int pthread_join_called;
static int pthread_cond_timedwait_called;
static int pthread_cond_timedwait_retval;
static int close_called;
static int pipe_called;
static int sendmsg_called;
static int write_called;
static void *mmap_return_value;
static int samples_ready_called;
static int samples_ready_frames_value;
static uint8_t *samples_ready_samples_value;
static int pthread_create_returned_value;
namespace {
void InitStaticVariables() {
pthread_create_called = 0;
pthread_join_called = 0;
pthread_cond_timedwait_called = 0;
pthread_cond_timedwait_retval = 0;
close_called = 0;
pipe_called = 0;
sendmsg_called = 0;
write_called = 0;
pthread_create_returned_value = 0;
mmap_return_value = NULL;
samples_ready_called = 0;
samples_ready_frames_value = 0;
}
class CrasClientTestSuite : public testing::Test {
protected:
void InitShm(struct cras_audio_shm* shm) {
shm->area = static_cast<cras_audio_shm_area*>(
calloc(1, sizeof(*shm->area)));
cras_shm_set_frame_bytes(shm, 4);
cras_shm_set_used_size(shm, shm_writable_frames_ * 4);
memcpy(&shm->area->config, &shm->config, sizeof(shm->config));
}
void FreeShm(struct cras_audio_shm* shm) {
if (shm->area) {
free(shm->area);
shm->area = NULL;
}
}
virtual void SetUp() {
shm_writable_frames_ = 100;
InitStaticVariables();
memset(&client_, 0, sizeof(client_));
client_.server_fd_state = CRAS_SOCKET_STATE_CONNECTED;
memset(&stream_, 0, sizeof(stream_));
stream_.id = FIRST_STREAM_ID;
struct cras_stream_params* config =
static_cast<cras_stream_params*>(calloc(1, sizeof(*config)));
config->buffer_frames = 1024;
config->cb_threshold = 512;
stream_.config = config;
}
virtual void TearDown() {
if (stream_.config) {
free(stream_.config);
stream_.config = NULL;
}
}
void StreamConnected(CRAS_STREAM_DIRECTION direction);
void StreamConnectedFail(CRAS_STREAM_DIRECTION direction);
struct client_stream stream_;
struct cras_client client_;
int shm_writable_frames_;
};
void set_audio_format(struct cras_audio_format* format,
snd_pcm_format_t pcm_format,
size_t frame_rate,
size_t num_channels) {
format->format = pcm_format;
format->frame_rate = frame_rate;
format->num_channels = num_channels;
for (size_t i = 0; i < CRAS_CH_MAX; ++i)
format->channel_layout[i] = i < num_channels ? i : -1;
}
int capture_samples_ready(cras_client* client,
cras_stream_id_t stream_id,
uint8_t* samples,
size_t frames,
const timespec* sample_ts,
void* arg) {
samples_ready_called++;
samples_ready_samples_value = samples;
samples_ready_frames_value = frames;
return frames;
}
TEST_F(CrasClientTestSuite, HandleCaptureDataReady) {
struct cras_audio_shm *shm = &stream_.capture_shm;
stream_.direction = CRAS_STREAM_INPUT;
shm_writable_frames_ = 480;
InitShm(shm);
stream_.config->buffer_frames = 480;
stream_.config->cb_threshold = 480;
stream_.config->aud_cb = capture_samples_ready;
stream_.config->unified_cb = 0;
shm->area->write_buf_idx = 0;
shm->area->read_buf_idx = 0;
shm->area->write_offset[0] = 480 * 4;
shm->area->read_offset[0] = 0;
/* Normal scenario: read buffer has full of data written,
* handle_capture_data_ready() should consume all 480 frames and move
* read_buf_idx to the next buffer. */
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(480, samples_ready_frames_value);
EXPECT_EQ(cras_shm_buff_for_idx(shm, 0), samples_ready_samples_value);
EXPECT_EQ(1, shm->area->read_buf_idx);
EXPECT_EQ(0, shm->area->write_offset[0]);
EXPECT_EQ(0, shm->area->read_offset[0]);
/* At the beginning of overrun: handle_capture_data_ready() should not
* proceed to call audio_cb because there's no data captured. */
shm->area->read_buf_idx = 0;
shm->area->write_offset[0] = 0;
shm->area->read_offset[0] = 0;
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(0, shm->area->read_buf_idx);
/* In the middle of overrun: partially written buffer should trigger
* audio_cb, feed the full-sized read buffer to client. */
shm->area->read_buf_idx = 0;
shm->area->write_offset[0] = 123;
shm->area->read_offset[0] = 0;
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(0, shm->area->read_buf_idx);
}
void CrasClientTestSuite::StreamConnected(CRAS_STREAM_DIRECTION direction) {
struct cras_client_stream_connected msg;
int shm_fds[2] = {0, 1};
int shm_max_size = 600;
size_t format_bytes;
struct cras_audio_shm_area area;
stream_.direction = direction;
set_audio_format(&stream_.config->format, SND_PCM_FORMAT_S16_LE, 48000, 4);
struct cras_audio_format server_format;
set_audio_format(&server_format, SND_PCM_FORMAT_S16_LE, 44100, 2);
// Initialize shm area
format_bytes = cras_get_format_bytes(&server_format);
memset(&area, 0, sizeof(area));
area.config.frame_bytes = format_bytes;
area.config.used_size = shm_writable_frames_ * format_bytes;
mmap_return_value = &area;
cras_fill_client_stream_connected(
&msg,
0,
stream_.id,
&server_format,
shm_max_size);
stream_connected(&stream_, &msg, shm_fds, 2);
EXPECT_EQ(CRAS_THREAD_RUNNING, stream_.thread.state);
if (direction == CRAS_STREAM_OUTPUT) {
EXPECT_EQ(NULL, stream_.capture_shm.area);
EXPECT_EQ(&area, stream_.play_shm.area);
} else {
EXPECT_EQ(NULL, stream_.play_shm.area);
EXPECT_EQ(&area, stream_.capture_shm.area);
}
}
TEST_F(CrasClientTestSuite, InputStreamConnected) {
StreamConnected(CRAS_STREAM_INPUT);
}
TEST_F(CrasClientTestSuite, OutputStreamConnected) {
StreamConnected(CRAS_STREAM_OUTPUT);
}
void CrasClientTestSuite::StreamConnectedFail(
CRAS_STREAM_DIRECTION direction) {
struct cras_client_stream_connected msg;
int shm_fds[2] = {0, 1};
int shm_max_size = 600;
size_t format_bytes;
struct cras_audio_shm_area area;
int rc;
stream_.direction = direction;
set_audio_format(&stream_.config->format, SND_PCM_FORMAT_S16_LE, 48000, 4);
struct cras_audio_format server_format;
set_audio_format(&server_format, SND_PCM_FORMAT_S16_LE, 44100, 2);
// Thread setup
rc = pipe(stream_.wake_fds);
ASSERT_EQ(0, rc);
stream_.thread.state = CRAS_THREAD_WARMUP;
// Initialize shm area
format_bytes = cras_get_format_bytes(&server_format);
memset(&area, 0, sizeof(area));
area.config.frame_bytes = format_bytes;
area.config.used_size = shm_writable_frames_ * format_bytes;
mmap_return_value = &area;
// Put an error in the message.
cras_fill_client_stream_connected(
&msg,
1,
stream_.id,
&server_format,
shm_max_size);
stream_connected(&stream_, &msg, shm_fds, 2);
EXPECT_EQ(CRAS_THREAD_STOP, stream_.thread.state);
EXPECT_EQ(4, close_called); // close the pipefds and shm_fds
}
TEST_F(CrasClientTestSuite, InputStreamConnectedFail) {
StreamConnectedFail(CRAS_STREAM_INPUT);
}
TEST_F(CrasClientTestSuite, OutputStreamConnectedFail) {
StreamConnectedFail(CRAS_STREAM_OUTPUT);
}
TEST_F(CrasClientTestSuite, AddAndRemoveStream) {
cras_stream_id_t stream_id;
// Dynamically allocat the stream so that it can be freed later.
struct client_stream* stream_ptr = (struct client_stream *)
malloc(sizeof(*stream_ptr));
memcpy(stream_ptr, &stream_, sizeof(client_stream));
stream_ptr->config = (struct cras_stream_params *)
malloc(sizeof(*(stream_ptr->config)));
memcpy(stream_ptr->config, stream_.config, sizeof(*(stream_.config)));
pthread_cond_timedwait_retval = ETIMEDOUT;
EXPECT_EQ(-ETIMEDOUT, client_thread_add_stream(
&client_, stream_ptr, &stream_id, NO_DEVICE));
EXPECT_EQ(pthread_cond_timedwait_called, 1);
EXPECT_EQ(pthread_join_called, 0);
InitStaticVariables();
EXPECT_EQ(0, client_thread_add_stream(
&client_, stream_ptr, &stream_id, NO_DEVICE));
EXPECT_EQ(&client_, stream_ptr->client);
EXPECT_EQ(stream_id, stream_ptr->id);
EXPECT_EQ(pthread_create_called, 1);
EXPECT_EQ(pipe_called, 1);
EXPECT_EQ(1, sendmsg_called); // send connect message to server
EXPECT_EQ(stream_ptr, stream_from_id(&client_, stream_id));
stream_ptr->thread.state = CRAS_THREAD_RUNNING;
EXPECT_EQ(0, client_thread_rm_stream(&client_, stream_id));
// One for the disconnect message to server,
// the other is to wake_up the audio thread
EXPECT_EQ(2, write_called);
EXPECT_EQ(1, pthread_join_called);
EXPECT_EQ(NULL, stream_from_id(&client_, stream_id));
}
} // namepsace
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
/* stubs */
extern "C" {
ssize_t write(int fd, const void *buf, size_t count) {
++write_called;
return count;
}
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {
++sendmsg_called;
return msg->msg_iov->iov_len;
}
int pipe(int pipefd[2]) {
pipefd[0] = 1;
pipefd[1] = 2;
++pipe_called;
return 0;
}
int close(int fd) {
++close_called;
return 0;
}
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg) {
++pthread_create_called;
return pthread_create_returned_value;
}
int pthread_join(pthread_t thread, void **retval) {
++pthread_join_called;
return 0;
}
int pthread_cond_timedwait(pthread_cond_t *__restrict cond,
pthread_mutex_t *__restrict mutex,
const struct timespec *__restrict timeout) {
++pthread_cond_timedwait_called;
return pthread_cond_timedwait_retval;
}
int clock_gettime(clockid_t clk_id, struct timespec *tp) {
tp->tv_sec = 0;
tp->tv_nsec = 0;
return 0;
}
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
return mmap_return_value;
}
}