stream: Unit tests for stream read and tweaks framework

* Removed the AST_CHAN_TP_MULTISTREAM tech property.  We now rely
  on read_stream being set to indicate a multi stream channel.
* Added ast_channel_is_multistream convenience function.
* Fixed issue where stream and default_stream weren't being set on
  a frame retrieved from the queue.
* Now testing for NULL being returned from the driver's read or
  read_stream callback.
* Fixed issue where the dropnondefault code was crashing on a
  NULL f.
* Now enforcing that if either read_stream or write_stream are
  set when ast_channel_tech_set is called that BOTH are set.
* Added the unit tests.

ASTERISK-26816

Change-Id: If7792b20d782e71e823dabd3124572cf0a4caab2
This commit is contained in:
George Joseph
2017-02-24 14:30:33 -07:00
parent 6d3c1a4a21
commit 0560c32375
4 changed files with 350 additions and 50 deletions

View File

@@ -804,8 +804,75 @@ end:
return res;
}
struct mock_channel_pvt {
int mallocd;
unsigned int wrote;
unsigned int wrote_stream;
int stream_num;
int frame_limit;
int frame_count;
int streams;
int frames_per_read;
};
static struct ast_frame *mock_channel_read(struct ast_channel *chan)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
struct ast_frame f = { 0, };
struct ast_frame *head_frame = NULL;
struct ast_frame *tail_frame = NULL;
int i;
if (pvt->frames_per_read == 0) {
pvt->frames_per_read = 1;
}
for (i = 0; i < pvt->frames_per_read && pvt->frame_count < pvt->frame_limit; i++) {
struct ast_frame *fr;
if (pvt->frame_count % 2 == 0) {
f.frametype = AST_FRAME_VOICE;
f.subclass.format = ast_format_ulaw;
} else {
f.frametype = AST_FRAME_VIDEO;
f.subclass.format = ast_format_h264;
}
f.seqno = pvt->frame_count;
f.stream_num = pvt->frame_count % pvt->streams;
pvt->frame_count++;
fr = ast_frdup(&f);
if (!head_frame) {
head_frame = fr;
} else {
tail_frame->frame_list.next = fr;
}
tail_frame = fr;
}
return(head_frame);
}
static int mock_channel_write(struct ast_channel *chan, struct ast_frame *fr)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
pvt->wrote = 1;
return 0;
}
static int mock_channel_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame *fr)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
pvt->wrote_stream = 1;
pvt->stream_num = stream_num;
return 0;
}
static const struct ast_channel_tech mock_stream_channel_tech = {
.properties = AST_CHAN_TP_MULTISTREAM,
.read_stream = mock_channel_read,
.write_stream = mock_channel_write_stream,
};
AST_TEST_DEFINE(stream_topology_channel_set)
@@ -853,33 +920,14 @@ AST_TEST_DEFINE(stream_topology_channel_set)
return res;
}
struct mock_channel_pvt {
unsigned int wrote;
unsigned int wrote_stream;
int stream_num;
};
static int mock_channel_write(struct ast_channel *chan, struct ast_frame *fr)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
pvt->wrote = 1;
return 0;
}
static int mock_channel_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame *fr)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
pvt->wrote_stream = 1;
pvt->stream_num = stream_num;
return 0;
}
static int mock_channel_hangup(struct ast_channel *chan)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
if (pvt->mallocd) {
ast_free(pvt);
}
ast_channel_tech_pvt_set(chan, NULL);
return 0;
}
@@ -894,7 +942,7 @@ AST_TEST_DEFINE(stream_write_non_multistream)
{
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
struct ast_channel *mock_channel;
struct mock_channel_pvt pvt;
struct mock_channel_pvt pvt = { 0, };
enum ast_test_result_state res = AST_TEST_FAIL;
struct ast_frame frame = { 0, };
@@ -981,10 +1029,10 @@ end:
}
static const struct ast_channel_tech mock_channel_write_stream_tech = {
.properties = AST_CHAN_TP_MULTISTREAM,
.write = mock_channel_write,
.write_video = mock_channel_write,
.write_stream = mock_channel_write_stream,
.read_stream = mock_channel_read,
.hangup = mock_channel_hangup,
};
@@ -1268,6 +1316,232 @@ end:
return res;
}
static int load_stream_readqueue(struct ast_channel *chan, int frames)
{
struct mock_channel_pvt *pvt = ast_channel_tech_pvt(chan);
struct ast_frame f = { 0, };
struct ast_frame *frame = NULL;
int i;
while ((frame = AST_LIST_REMOVE_HEAD(ast_channel_readq(chan), frame_list)))
ast_frfree(frame);
for (i = 0; i < frames; i++) {
if (pvt->frame_count % 2 == 0) {
f.frametype = AST_FRAME_VOICE;
f.subclass.format = ast_format_ulaw;
} else {
f.frametype = AST_FRAME_VIDEO;
f.subclass.format = ast_format_h264;
}
f.stream_num = pvt->frame_count % pvt->streams;
f.seqno = pvt->frame_count;
ast_queue_frame(chan, ast_frdup(&f));
pvt->frame_count++;
}
return 0;
}
static struct ast_channel *make_channel(struct ast_test *test, int streams,
struct ast_channel_tech *tech)
{
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
struct ast_channel *mock_channel = NULL;
struct mock_channel_pvt *pvt = NULL;
struct ast_stream_topology *topology = NULL;
struct ast_stream *stream;
enum ast_test_result_state res = AST_TEST_PASS;
int i;
mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "TestChannel");
ast_test_validate_cleanup(test, mock_channel, res, done);
ast_channel_tech_set(mock_channel, tech);
if (tech->read_stream) {
topology = ast_stream_topology_alloc();
ast_test_validate_cleanup(test, topology, res, done);
for (i = 0; i < streams; i++) {
stream = ast_stream_alloc((i % 2 ? "video": "audio"), (i % 2 ? AST_MEDIA_TYPE_VIDEO : AST_MEDIA_TYPE_AUDIO));
ast_test_validate_cleanup(test, stream, res, done);
ast_test_validate_cleanup(test, ast_stream_topology_append_stream(topology, stream) == i, res, done);
}
ast_test_validate_cleanup(test, ast_stream_topology_get_count(topology) == streams, res, done);
ast_channel_set_stream_topology(mock_channel, topology);
topology = NULL;
} else {
caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
ast_test_validate_cleanup(test, caps, res, done);
ast_test_validate_cleanup(test, ast_format_cap_append(caps, ast_format_ulaw, 0) == 0, res, done);
ast_test_validate_cleanup(test, ast_format_cap_append(caps, ast_format_h264, 0) == 0, res, done);
ast_channel_nativeformats_set(mock_channel, caps);
}
pvt = ast_calloc(1, sizeof(*pvt));
ast_test_validate_cleanup(test, pvt, res, done);
pvt->mallocd = 1;
ast_channel_tech_pvt_set(mock_channel, pvt);
ast_channel_unlock(mock_channel);
done:
ast_stream_topology_free(topology);
if (res == AST_TEST_FAIL && mock_channel) {
ast_hangup(mock_channel);
}
return mock_channel;
}
enum CHANNEL_READ_TYPE {
CHANNEL_READ,
CHANNEL_READ_STREAM
};
static struct ast_frame *read_from_chan(enum CHANNEL_READ_TYPE rt, struct ast_channel *chan)
{
if (rt == CHANNEL_READ_STREAM) {
return ast_read_stream(chan);
} else {
return ast_read(chan);
}
}
static enum ast_test_result_state read_test(struct ast_test *test, struct ast_channel_tech *tech,
enum CHANNEL_READ_TYPE rt, int streams, int frames, int frames_per_read, int expected_nulls)
{
struct ast_channel *mock_channel;
struct mock_channel_pvt *pvt;
struct ast_frame *fr = NULL;
enum ast_test_result_state res = AST_TEST_PASS;
int i = 0;
int null_frames = 0;
ast_test_status_update(test, "ChanType: %s ReadType: %s Streams: %d Frames: %d Frames per read: %d Expected Nulls: %d\n",
tech->read_stream ? "MULTI" : "NON-MULTI",
rt == CHANNEL_READ_STREAM ? "STREAM" : "NON-STREAM",
streams, frames, frames_per_read, expected_nulls);
mock_channel = make_channel(test, 4, tech);
ast_test_validate_cleanup(test, mock_channel, res, done);
pvt = ast_channel_tech_pvt(mock_channel);
pvt->frame_count = 0;
pvt->frame_limit = frames;
pvt->streams = streams;
pvt->frames_per_read = frames_per_read;
load_stream_readqueue(mock_channel, frames / 2);
ast_channel_fdno_set(mock_channel, 0);
while ((fr = read_from_chan(rt, mock_channel))) {
ast_channel_fdno_set(mock_channel, 0);
if (fr->frametype != AST_FRAME_NULL) {
ast_test_validate_cleanup(test, i == fr->seqno, res, done);
ast_test_validate_cleanup(test, fr->frametype == ( i % 2 ? AST_FRAME_VIDEO : AST_FRAME_VOICE), res, done);
ast_test_validate_cleanup(test, fr->stream_num == ( i % streams ), res, done);
ast_frfree(fr);
} else {
null_frames++;
}
fr = NULL;
i++;
}
ast_test_validate_cleanup(test, i == frames, res, done);
ast_test_validate_cleanup(test, null_frames == expected_nulls, res, done);
done:
ast_test_status_update(test, " Frames read: %d NULL frames: %d\n", i, null_frames);
ast_hangup(mock_channel);
return res;
}
AST_TEST_DEFINE(stream_read_non_multistream)
{
struct ast_channel_tech tech = {
.read = mock_channel_read,
.hangup = mock_channel_hangup,
};
enum ast_test_result_state res = AST_TEST_PASS;
switch (cmd) {
case TEST_INIT:
info->name = "stream_read_non_multistream";
info->category = "/main/stream/";
info->summary = "stream reading from non-multistream capable channel test";
info->description =
"Test that reading frames from a non-multistream channel works as expected";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
res = read_test(test, &tech, CHANNEL_READ, 2, 16, 1, 0);
ast_test_validate(test, res == AST_TEST_PASS, "non multi, non read stream, 2 stream");
res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 1, 0);
ast_test_validate(test, res == AST_TEST_PASS, "non multi, read stream, 2 stream");
res = read_test(test, &tech, CHANNEL_READ, 2, 16, 3, 0);
ast_test_validate(test, res == AST_TEST_PASS, "non multi, non read stream, 2 stream, 3 frames per read");
res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 3, 0);
ast_test_validate(test, res == AST_TEST_PASS, "non multi, read stream, 2 stream, 3 frames per read");
return res;
}
AST_TEST_DEFINE(stream_read_multistream)
{
struct ast_channel_tech tech = {
.read_stream = mock_channel_read,
.write_stream = mock_channel_write_stream,
.hangup = mock_channel_hangup,
};
enum ast_test_result_state res = AST_TEST_PASS;
switch (cmd) {
case TEST_INIT:
info->name = "stream_read_multistream";
info->category = "/main/stream/";
info->summary = "stream reading from multistream capable channel test";
info->description =
"Test that reading frames from a multistream channel works as expected";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
res = read_test(test, &tech, CHANNEL_READ, 2, 16, 1, 0);
ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 2 stream");
res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 1, 0);
ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 2 stream");
res = read_test(test, &tech, CHANNEL_READ, 4, 16, 1, 8);
ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 4 stream");
res = read_test(test, &tech, CHANNEL_READ_STREAM, 4, 16, 1, 0);
ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 4 stream");
res = read_test(test, &tech, CHANNEL_READ, 2, 16, 3, 0);
ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 2 stream, 3 frames per read");
res = read_test(test, &tech, CHANNEL_READ_STREAM, 2, 16, 3, 0);
ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 2 stream, 3 frames per read");
res = read_test(test, &tech, CHANNEL_READ, 4, 16, 3, 8);
ast_test_validate(test, res == AST_TEST_PASS, "multi, non read stream, 4 stream, 3 frames per read");
res = read_test(test, &tech, CHANNEL_READ_STREAM, 4, 16, 3, 0);
ast_test_validate(test, res == AST_TEST_PASS, "multi, read stream, 4 stream, 3 frames per read");
return res;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(stream_create);
@@ -1286,6 +1560,8 @@ static int unload_module(void)
AST_TEST_UNREGISTER(stream_topology_channel_set);
AST_TEST_UNREGISTER(stream_write_non_multistream);
AST_TEST_UNREGISTER(stream_write_multistream);
AST_TEST_UNREGISTER(stream_read_non_multistream);
AST_TEST_UNREGISTER(stream_read_multistream);
return 0;
}
@@ -1306,6 +1582,8 @@ static int load_module(void)
AST_TEST_REGISTER(stream_topology_channel_set);
AST_TEST_REGISTER(stream_write_non_multistream);
AST_TEST_REGISTER(stream_write_multistream);
AST_TEST_REGISTER(stream_read_non_multistream);
AST_TEST_REGISTER(stream_read_multistream);
return AST_MODULE_LOAD_SUCCESS;
}