Skip to content
Snippets Groups Projects
test_cel.c 67.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Asterisk -- An open source telephony toolkit.
     *
     * Copyright (C) 2013, Digium, Inc.
     *
     * Kinsey Moore <kmoore@digium.com>
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    /*!
     * \file
     * \brief CEL unit tests
     *
     * \author Kinsey Moore <kmoore@digium.com>
     *
     */
    
    /*** MODULEINFO
    	<depend>TEST_FRAMEWORK</depend>
    	<support_level>core</support_level>
     ***/
    
    #include "asterisk.h"
    
    
    ASTERISK_REGISTER_FILE()
    
    
    #include <math.h>
    #include "asterisk/module.h"
    #include "asterisk/test.h"
    #include "asterisk/cel.h"
    #include "asterisk/channel.h"
    
    #include "asterisk/linkedlists.h"
    #include "asterisk/chanvars.h"
    #include "asterisk/utils.h"
    #include "asterisk/causes.h"
    #include "asterisk/time.h"
    
    #include "asterisk/bridge.h"
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    #include "asterisk/bridge_basic.h"
    #include "asterisk/pickup.h"
    
    #include "asterisk/stasis_channels.h"
    
    #include "asterisk/stasis_bridges.h"
    
    #include "asterisk/json.h"
    #include "asterisk/features.h"
    
    #include "asterisk/core_local.h"
    
    
    #define TEST_CATEGORY "/main/cel/"
    
    #define CHANNEL_TECH_NAME "CELTestChannel"
    
    
    #define TEST_BACKEND_NAME "CEL Test Logging"
    
    
    /*! \brief A placeholder for Asterisk's 'real' CEL configuration */
    static struct ast_cel_general_config *saved_config;
    
    /*! \brief The CEL config used for CEL unit tests */
    static struct ast_cel_general_config *cel_test_config;
    
    
    /*! \brief Lock used for synchronizing test execution stages with received events */
    ast_mutex_t mid_test_sync_lock;
    
    /*! \brief Lock used with sync_out for checking the end of test execution */
    ast_mutex_t sync_lock;
    
    /*! \brief Condition used for checking the end of test execution */
    ast_cond_t sync_out;
    
    /*! \brief Flag used to trigger a mid-test synchronization, access controlled by mid_test_sync_lock */
    int do_mid_test_sync = 0;
    
    
    /*! \brief A channel technology used for the unit tests */
    static struct ast_channel_tech test_cel_chan_tech = {
    	.type = CHANNEL_TECH_NAME,
    	.description = "Mock channel technology for CEL tests",
    };
    
    /*! \brief A 1 second sleep */
    static struct timespec to_sleep = {1, 0};
    
    static void do_sleep(void)
    {
    
    	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR)) {
    	}
    
    #define APPEND_EVENT(chan, ev_type, userevent, extra) do { \
    
    	if (append_expected_event(chan, ev_type, userevent, extra, NULL)) { \
    
    		return AST_TEST_FAIL; \
    	} \
    	} while (0)
    
    
    #define APPEND_EVENT_PEER(chan, ev_type, userevent, extra, peer) do { \
    	if (append_expected_event(chan, ev_type, userevent, extra, peer)) { \
    		return AST_TEST_FAIL; \
    	} \
    	} while (0)
    
    #define APPEND_EVENT_SNAPSHOT(snapshot, ev_type, userevent, extra, peer) do { \
    	if (append_expected_event_snapshot(snapshot, ev_type, userevent, extra, peer)) { \
    
    		return AST_TEST_FAIL; \
    	} \
    	} while (0)
    
    #define APPEND_DUMMY_EVENT() do { \
    	if (append_dummy_event()) { \
    		return AST_TEST_FAIL; \
    	} \
    	} while (0)
    
    
    #define BRIDGE_EXIT(channel, bridge) do { \
    
    	ast_test_validate(test, !ast_bridge_depart(channel)); \
    
    	BRIDGE_EXIT_EVENT(channel, bridge); \
    
    	mid_test_sync(); \
    
    	} while (0)
    
    
    #define BRIDGE_EXIT_EVENT(channel, bridge) do { \
    
    	RAII_VAR(struct ast_str *, peer_str, NULL, ast_free); \
    	peer_str = test_cel_generate_peer_str(channel, bridge); \
    	ast_test_validate(test, peer_str != NULL); \
    	BRIDGE_EXIT_EVENT_PEER(channel, bridge, ast_str_buffer(peer_str)); \
    	} while (0)
    
    #define BRIDGE_EXIT_EVENT_PEER(channel, bridge, peer) do { \
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \
    
    	extra = ast_json_pack("{s: s, s: s}", "bridge_id", bridge->uniqueid, "bridge_technology", bridge->technology->name); \
    
    	ast_test_validate(test, extra != NULL); \
    
    	APPEND_EVENT_PEER(channel, AST_CEL_BRIDGE_EXIT, NULL, extra, peer); \
    
    	} while (0)
    
    
    #define BRIDGE_EXIT_SNAPSHOT(channel, bridge) do { \
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \
    
    	RAII_VAR(struct ast_str *, peer_str, NULL, ast_free); \
    	peer_str = test_cel_generate_peer_str_snapshot(channel, bridge); \
    	ast_test_validate(test, peer_str != NULL); \
    
    	extra = ast_json_pack("{s: s, s: s}", "bridge_id", bridge->uniqueid, "bridge_technology", bridge->technology->name); \
    
    	ast_test_validate(test, extra != NULL); \
    
    	APPEND_EVENT_SNAPSHOT(channel, AST_CEL_BRIDGE_EXIT, NULL, extra, ast_str_buffer(peer_str)); \
    
    	} while (0)
    
    
    #define BRIDGE_ENTER(channel, bridge) do { \
    
    	ast_test_validate(test, !ast_bridge_impart(bridge, channel, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE)); \
    
    	do_sleep(); \
    	BRIDGE_ENTER_EVENT(channel, bridge); \
    	mid_test_sync(); \
    
    	} while (0)
    
    
    #define BRIDGE_ENTER_EVENT(channel, bridge) do { \
    
    	RAII_VAR(struct ast_str *, peer_str, NULL, ast_free); \
    	peer_str = test_cel_generate_peer_str(channel, bridge); \
    	ast_test_validate(test, peer_str != NULL); \
    	BRIDGE_ENTER_EVENT_PEER(channel, bridge, ast_str_buffer(peer_str)); \
    	} while (0)
    
    #define BRIDGE_ENTER_EVENT_PEER(channel, bridge, peer) do { \
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \
    
    	extra = ast_json_pack("{s: s, s: s}", "bridge_id", bridge->uniqueid, "bridge_technology", bridge->technology->name); \
    
    	ast_test_validate(test, extra != NULL); \
    
    	APPEND_EVENT_PEER(channel, AST_CEL_BRIDGE_ENTER, NULL, extra, peer); \
    
    	} while (0)
    
    #define BLINDTRANSFER_EVENT(channel, bridge, extension, context) do { \
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \
    
    	extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}", \
    
    		"extension", extension, \
    		"context", context, \
    
    		"bridge_id", bridge->uniqueid, \
    		"transferee_channel_name", "N/A", \
    		"transferee_channel_uniqueid", "N/A"); \
    
    	ast_test_validate(test, extra != NULL); \
    
    	APPEND_EVENT(channel, AST_CEL_BLINDTRANSFER, NULL, extra); \
    
    	} while (0)
    
    
    #define ATTENDEDTRANSFER_BRIDGE(channel1, bridge1, channel2, bridge2, channel3, channel4) do { \
    
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \
    
    	extra = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: s, s: s}", \
    
    		"bridge1_id", bridge1->uniqueid, \
    		"channel2_name", ast_channel_name(channel2), \
    
    		"channel2_uniqueid", ast_channel_uniqueid(channel2), \
    
    		"bridge2_id", bridge2->uniqueid, \
    		"transferee_channel_name", ast_channel_name(channel4), \
    
    		"transferee_channel_uniqueid", ast_channel_uniqueid(channel4), \
    		"transfer_target_channel_name", ast_channel_name(channel3), \
    		"transfer_target_channel_uniqueid", ast_channel_uniqueid(channel3)); \
    
    	ast_test_validate(test, extra != NULL); \
    
    	APPEND_EVENT(channel1, AST_CEL_ATTENDEDTRANSFER, NULL, extra); \
    
    	} while (0)
    
    
    /*! \brief Alice's Caller ID */
    #define ALICE_CALLERID { .id.name.str = "Alice", .id.name.valid = 1, .id.number.str = "100", .id.number.valid = 1, }
    
    /*! \brief Bob's Caller ID */
    #define BOB_CALLERID { .id.name.str = "Bob", .id.name.valid = 1, .id.number.str = "200", .id.number.valid = 1, }
    
    /*! \brief Charlie's Caller ID */
    #define CHARLIE_CALLERID { .id.name.str = "Charlie", .id.name.valid = 1, .id.number.str = "300", .id.number.valid = 1, }
    
    /*! \brief David's Caller ID */
    #define DAVID_CALLERID { .id.name.str = "David", .id.name.valid = 1, .id.number.str = "400", .id.number.valid = 1, }
    
    
    /*! \brief Set ulaw format on channel */
    #define SET_FORMATS(chan) do {\
    	struct ast_format_cap *caps;\
    	caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);\
    	ast_format_cap_append(caps, ast_format_ulaw, 0);\
    	ast_channel_nativeformats_set((chan), caps);\
    	ast_channel_set_writeformat((chan), ast_format_ulaw);\
    	ast_channel_set_rawwriteformat((chan), ast_format_ulaw);\
    	ast_channel_set_readformat((chan), ast_format_ulaw);\
    	ast_channel_set_rawreadformat((chan), ast_format_ulaw);\
    	ao2_ref(caps, -1);\
    } while (0)
    
    
    /*! \brief Create a \ref test_cel_chan_tech for Alice. */
    #define CREATE_ALICE_CHANNEL(channel_var, caller_id) do { \
    
    	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "100", "100", "default", NULL, NULL, 0, CHANNEL_TECH_NAME "/Alice"); \
    
    	SET_FORMATS((channel_var));\
    
    	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL); \
    
    	ast_channel_unlock((channel_var)); \
    
    	} while (0)
    
    /*! \brief Create a \ref test_cel_chan_tech for Bob. */
    #define CREATE_BOB_CHANNEL(channel_var, caller_id) do { \
    
    	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "200", "200", "default", NULL, NULL, 0, CHANNEL_TECH_NAME "/Bob"); \
    
    	SET_FORMATS((channel_var));\
    
    	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL); \
    
    	ast_channel_unlock((channel_var)); \
    
    	} while (0)
    
    /*! \brief Create a \ref test_cel_chan_tech for Charlie. */
    #define CREATE_CHARLIE_CHANNEL(channel_var, caller_id) do { \
    
    	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "300", "300", "default", NULL, NULL, 0, CHANNEL_TECH_NAME "/Charlie"); \
    
    	SET_FORMATS((channel_var));\
    
    	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL); \
    
    	ast_channel_unlock((channel_var)); \
    
    /*! \brief Create a \ref test_cel_chan_tech for David. */
    
    #define CREATE_DAVID_CHANNEL(channel_var, caller_id) do { \
    
    	(channel_var) = ast_channel_alloc(0, AST_STATE_DOWN, (caller_id)->id.number.str, (caller_id)->id.name.str, "400", "400", "default", NULL, NULL, 0, CHANNEL_TECH_NAME "/David"); \
    
    	SET_FORMATS((channel_var));\
    
    	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL); \
    
    	ast_channel_unlock((channel_var)); \
    
    	} while (0)
    
    
    /*! \brief Emulate a channel entering into an application */
    #define EMULATE_APP_DATA(channel, priority, application, data) do { \
    	if ((priority) > 0) { \
    		ast_channel_priority_set((channel), (priority)); \
    	} \
    	ast_channel_appl_set((channel), (application)); \
    	ast_channel_data_set((channel), (data)); \
    	ast_channel_publish_snapshot((channel)); \
    	} while (0)
    
    #define ANSWER_CHANNEL(chan) do { \
    	EMULATE_APP_DATA(chan, 1, "Answer", ""); \
    	ANSWER_NO_APP(chan); \
    	} while (0)
    
    #define ANSWER_NO_APP(chan) do { \
    	ast_setstate(chan, AST_STATE_UP); \
    
    	APPEND_EVENT(chan, AST_CEL_ANSWER, NULL, NULL); \
    
    	} while (0)
    
    /*! \brief Hang up a test channel safely */
    
    #define HANGUP_CHANNEL(channel, cause, dialstatus) do { \
    	ast_channel_hangupcause_set((channel), (cause)); \
    	ao2_ref(channel, +1); \
    	ast_hangup((channel)); \
    	HANGUP_EVENT(channel, cause, dialstatus); \
    
    	APPEND_EVENT(channel, AST_CEL_CHANNEL_END, NULL, NULL); \
    
    	ao2_cleanup(stasis_cache_get(ast_channel_cache(), \
    		ast_channel_snapshot_type(), ast_channel_uniqueid(channel))); \
    
    	ao2_cleanup(channel); \
    	channel = NULL; \
    	} while (0)
    
    #define HANGUP_EVENT(channel, cause, dialstatus) do { \
    	RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref); \
    	extra = ast_json_pack("{s: i, s: s, s: s}", \
    		"hangupcause", cause, \
    		"hangupsource", "", \
    		"dialstatus", dialstatus); \
    	ast_test_validate(test, extra != NULL); \
    
    	APPEND_EVENT(channel, AST_CEL_HANGUP, NULL, extra); \
    
    static void mid_test_sync(void);
    
    
    static int append_expected_event(
    	struct ast_channel *chan,
    	enum ast_cel_event_type type,
    	const char *userdefevname,
    
    	struct ast_json *extra,
    	const char *peer);
    
    
    static int append_expected_event_snapshot(
    	struct ast_channel_snapshot *snapshot,
    	enum ast_cel_event_type type,
    	const char *userdefevname,
    
    	struct ast_json *extra,
    	const char *peer);
    
    static int append_dummy_event(void);
    
    static struct ast_str *__test_cel_generate_peer_str(struct ast_channel_snapshot *chan, struct ast_bridge_snapshot *bridge)
    {
    	struct ast_str *peer_str = ast_str_create(32);
    	struct ao2_iterator i;
    	char *current_chan = NULL;
    
    	if (!peer_str) {
    		return NULL;
    	}
    
    	for (i = ao2_iterator_init(bridge->channels, 0);
    		(current_chan = ao2_iterator_next(&i));
    		ao2_cleanup(current_chan)) {
    		RAII_VAR(struct ast_channel_snapshot *, current_snapshot,
    			NULL,
    			ao2_cleanup);
    
    		/* Don't add the channel for which this message is being generated */
    		if (!strcmp(current_chan, chan->uniqueid)) {
    			continue;
    		}
    
    		current_snapshot = ast_channel_snapshot_get_latest(current_chan);
    		if (!current_snapshot) {
    			continue;
    		}
    
    		ast_str_append(&peer_str, 0, "%s,", current_snapshot->name);
    	}
    	ao2_iterator_destroy(&i);
    
    	/* Rip off the trailing comma */
    	ast_str_truncate(peer_str, -1);
    
    	return peer_str;
    }
    
    static struct ast_str *test_cel_generate_peer_str_snapshot(struct ast_channel_snapshot *chan, struct ast_bridge *bridge)
    {
    	RAII_VAR(struct ast_bridge_snapshot *, snapshot,
    		ast_bridge_snapshot_get_latest(bridge->uniqueid),
    		ao2_cleanup);
    
    	if (!snapshot) {
    		return NULL;
    	}
    
    	return __test_cel_generate_peer_str(chan, snapshot);
    }
    
    static struct ast_str *test_cel_generate_peer_str(struct ast_channel *chan, struct ast_bridge *bridge)
    {
    	RAII_VAR(struct ast_channel_snapshot *, snapshot,
    		ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan)),
    		ao2_cleanup);
    
    	if (!snapshot) {
    		return NULL;
    	}
    
    	return test_cel_generate_peer_str_snapshot(snapshot, bridge);
    }
    
    
    static void safe_channel_release(struct ast_channel *chan)
    {
    	if (!chan) {
    		return;
    	}
    	ast_channel_release(chan);
    }
    
    
    static void safe_bridge_destroy(struct ast_bridge *bridge)
    {
    	if (!bridge) {
    		return;
    	}
    	ast_bridge_destroy(bridge, 0);
    }
    
    
    AST_TEST_DEFINE(test_cel_channel_creation)
    {
    	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test the CEL records created when a channel is created";
    		info->description =
    			"Test the CEL records created when a channel is created";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan, (&caller));
    
    
    	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_unanswered_inbound_call)
    {
    	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test inbound unanswered calls";
    		info->description =
    			"Test CEL records for a call that is\n"
    			"inbound to Asterisk, executes some dialplan, but\n"
    
    			"is never answered.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan, &caller);
    
    	EMULATE_APP_DATA(chan, 1, "Wait", "1");
    
    
    	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_unanswered_outbound_call)
    {
    	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
    	struct ast_party_caller caller = {
    			.id.name.str = "",
    			.id.name.valid = 1,
    			.id.number.str = "",
    			.id.number.valid = 1, };
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test outbound unanswered calls";
    		info->description =
    			"Test CEL records for a call that is\n"
    
    			"outbound to Asterisk but is never answered.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan, &caller);
    
    	ast_channel_exten_set(chan, "s");
    	ast_channel_context_set(chan, "default");
    	ast_set_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED);
    	EMULATE_APP_DATA(chan, 0, "AppDial", "(Outgoing Line)");
    
    	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_single_party)
    {
    	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a single party";
    		info->description =
    			"Test CEL records for a call that is\n"
    
    			"answered, but only involves a single channel";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    	CREATE_ALICE_CHANNEL(chan, &caller);
    
    	ANSWER_CHANNEL(chan);
    	EMULATE_APP_DATA(chan, 2, "VoiceMailMain", "1");
    
    
    	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_single_bridge)
    {
    	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
    
    	RAII_VAR(struct ast_bridge *, bridge, NULL, safe_bridge_destroy);
    
    
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a single party entering/leaving a bridge";
    		info->description =
    			"Test CEL records for a call that is\n"
    
    			"answered, enters a bridge, and leaves it.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    	bridge = ast_bridge_basic_new();
    	ast_test_validate(test, bridge != NULL);
    
    	CREATE_ALICE_CHANNEL(chan, &caller);
    
    	ANSWER_CHANNEL(chan);
    	EMULATE_APP_DATA(chan, 2, "Bridge", "");
    
    	do_sleep();
    
    	BRIDGE_ENTER(chan, bridge);
    
    	BRIDGE_EXIT(chan, bridge);
    
    	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_single_bridge_continue)
    {
    	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
    
    	RAII_VAR(struct ast_bridge *, bridge, NULL, safe_bridge_destroy);
    
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a single party entering/leaving a bridge";
    		info->description =
    			"Test CEL records for a call that is\n"
    
    			"answered, enters a bridge, and leaves it.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    	bridge = ast_bridge_basic_new();
    	ast_test_validate(test, bridge != NULL);
    
    	CREATE_ALICE_CHANNEL(chan, &caller);
    
    	ANSWER_CHANNEL(chan);
    	EMULATE_APP_DATA(chan, 2, "Bridge", "");
    
    	do_sleep();
    
    	BRIDGE_ENTER(chan, bridge);
    
    	BRIDGE_EXIT(chan, bridge);
    
    
    	EMULATE_APP_DATA(chan, 3, "Wait", "");
    
    	/* And then it hangs up */
    
    	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_single_twoparty_bridge_a)
    {
    	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
    
    	RAII_VAR(struct ast_bridge *, bridge, NULL, safe_bridge_destroy);
    
    	struct ast_party_caller caller_alice = ALICE_CALLERID;
    	struct ast_party_caller caller_bob = BOB_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a single party entering/leaving a bridge";
    		info->description =
    			"Test CEL records for a call that is\n"
    			"answered, enters a bridge, and leaves it. In this scenario, the\n"
    
    			"Party A should answer the bridge first.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    	bridge = ast_bridge_basic_new();
    	ast_test_validate(test, bridge != NULL);
    
    	CREATE_ALICE_CHANNEL(chan_alice, &caller_alice);
    
    	CREATE_BOB_CHANNEL(chan_bob, &caller_bob);
    
    	ANSWER_CHANNEL(chan_alice);
    	EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
    
    
    	BRIDGE_ENTER(chan_alice, bridge);
    
    	do_sleep();
    
    	ANSWER_CHANNEL(chan_bob);
    	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
    
    
    	BRIDGE_ENTER(chan_bob, bridge);
    
    	BRIDGE_EXIT(chan_alice, bridge);
    	BRIDGE_EXIT(chan_bob, bridge);
    
    	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "");
    	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_single_twoparty_bridge_b)
    {
    	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
    
    	RAII_VAR(struct ast_bridge *, bridge, NULL, safe_bridge_destroy);
    
    	struct ast_party_caller caller_alice = ALICE_CALLERID;
    	struct ast_party_caller caller_bob = BOB_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a single party entering/leaving a bridge";
    		info->description =
    			"Test CEL records for a call that is\n"
    			"answered, enters a bridge, and leaves it. In this scenario, the\n"
    
    			"Party B should answer the bridge first.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    	bridge = ast_bridge_basic_new();
    	ast_test_validate(test, bridge != NULL);
    
    	CREATE_ALICE_CHANNEL(chan_alice, &caller_alice);
    
    	CREATE_BOB_CHANNEL(chan_bob, &caller_bob);
    
    	ANSWER_CHANNEL(chan_alice);
    	EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
    
    	ANSWER_CHANNEL(chan_bob);
    	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
    	do_sleep();
    
    
    	BRIDGE_ENTER(chan_bob, bridge);
    
    	BRIDGE_ENTER(chan_alice, bridge);
    
    	BRIDGE_EXIT(chan_alice, bridge);
    	BRIDGE_EXIT(chan_bob, bridge);
    
    	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "");
    	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "");
    
    /* XXX Validation needs to be reworked on a per-channel basis before
     * test_cel_single_multiparty_bridge and test_cel_dial_answer_multiparty
     * can operate properly. */
    
    #ifdef RACEY_TESTS
    
    AST_TEST_DEFINE(test_cel_single_multiparty_bridge)
    {
    	RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
    
    	RAII_VAR(struct ast_bridge *, bridge, NULL, safe_bridge_destroy);
    
    	struct ast_party_caller caller_alice = ALICE_CALLERID;
    	struct ast_party_caller caller_bob = BOB_CALLERID;
    	struct ast_party_caller caller_charlie = CHARLIE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a single party entering/leaving a multi-party bridge";
    		info->description =
    			"Test CEL records for a call that is\n"
    			"answered, enters a bridge, and leaves it. A total of three\n"
    
    			"parties perform this action.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    	bridge = ast_bridge_basic_new();
    	ast_test_validate(test, bridge != NULL);
    
    	CREATE_ALICE_CHANNEL(chan_alice, &caller_alice);
    	CREATE_BOB_CHANNEL(chan_bob, &caller_bob);
    	CREATE_CHARLIE_CHANNEL(chan_charlie, &caller_charlie);
    
    	ANSWER_CHANNEL(chan_alice);
    	EMULATE_APP_DATA(chan_alice, 2, "Bridge", "");
    
    	do_sleep();
    
    
    	BRIDGE_ENTER(chan_alice, bridge);
    
    
    	ANSWER_CHANNEL(chan_bob);
    	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
    	do_sleep();
    
    
    	BRIDGE_ENTER(chan_bob, bridge);
    
    
    	ANSWER_CHANNEL(chan_charlie);
    	EMULATE_APP_DATA(chan_charlie, 2, "Bridge", "");
    
    	do_sleep();
    
    	BRIDGE_ENTER(chan_charlie, bridge);
    
    	BRIDGE_EXIT(chan_alice, bridge);
    	BRIDGE_EXIT(chan_bob, bridge);
    	BRIDGE_EXIT(chan_charlie, bridge);
    
    	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "");
    	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "");
    	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, "");
    
    
    	return AST_TEST_PASS;
    }
    
    
    #define EMULATE_DIAL(channel, dialstring) do { \
    	EMULATE_APP_DATA(channel, 1, "Dial", dialstring); \
    
    	if (append_expected_event(channel, AST_CEL_APP_START, NULL, NULL, NULL)) { \
    
    		return AST_TEST_FAIL; \
    	} \
    	} while (0)
    
    #define START_DIALED(caller, callee) \
    	START_DIALED_FULL(caller, callee, "200", "Bob")
    
    #define START_DIALED_FULL(caller, callee, number, name) do { \
    
    	callee = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, number, NULL, NULL, NULL, caller, 0, CHANNEL_TECH_NAME "/" name); \
    
    	SET_FORMATS(callee);\
    
    	ast_channel_unlock(callee); \
    
    	if (append_expected_event(callee, AST_CEL_CHANNEL_START, NULL, NULL, NULL)) { \
    
    		return AST_TEST_FAIL; \
    	} \
    	ast_set_flag(ast_channel_flags(callee), AST_FLAG_OUTGOING); \
    	EMULATE_APP_DATA(callee, 0, "AppDial", "(Outgoing Line)"); \
    	ast_channel_publish_dial(caller, callee, name, NULL); \
    	} while (0)
    
    AST_TEST_DEFINE(test_cel_dial_unanswered)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a dial that isn't answered";
    		info->description =
    			"Test CEL records for a channel that\n"
    
    			"performs a dial operation that isn't answered";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob");
    
    	START_DIALED(chan_caller, chan_callee);
    
    	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER");
    
    
    	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER, "NOANSWER");
    	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER, "");
    
    AST_TEST_DEFINE(test_cel_dial_unanswered_filter)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a dial that isn't answered";
    		info->description =
    			"Test CEL records for a channel that\n"
    
    			"performs a dial operation that isn't answered";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob");
    
    	START_DIALED(chan_caller, chan_callee);
    
    	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOT A VALID DIAL STATUS");
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "NOANSWER");
    
    	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ANSWER, "NOANSWER");
    	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER, "");
    
    	return AST_TEST_PASS;
    }
    
    
    AST_TEST_DEFINE(test_cel_dial_busy)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a dial that results in a busy";
    		info->description =
    			"Test CEL records for a channel that\n"
    
    			"performs a dial operation to an endpoint that's busy";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob");
    
    	START_DIALED(chan_caller, chan_callee);
    
    	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "BUSY");
    
    
    	HANGUP_CHANNEL(chan_caller, AST_CAUSE_BUSY, "BUSY");
    	HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_dial_congestion)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a dial that results in congestion";
    		info->description =
    			"Test CEL records for a channel that\n"
    
    			"performs a dial operation to an endpoint that's congested";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob");
    
    	START_DIALED(chan_caller, chan_callee);
    
    	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CONGESTION");
    
    
    	HANGUP_CHANNEL(chan_caller, AST_CAUSE_CONGESTION, "CONGESTION");
    	HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_dial_unavailable)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a dial that results in unavailable";
    		info->description =
    			"Test CEL records for a channel that\n"
    
    			"performs a dial operation to an endpoint that's unavailable";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob");
    
    	START_DIALED(chan_caller, chan_callee);
    
    	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CHANUNAVAIL");
    
    
    	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NO_ROUTE_DESTINATION, "CHANUNAVAIL");
    	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION, "");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_dial_caller_cancel)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_callee, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test CEL for a dial where the caller cancels";
    		info->description =
    			"Test CEL records for a channel that\n"
    			"performs a dial operation to an endpoint but then decides\n"
    
    			"to hang up, cancelling the dial";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob");
    
    	START_DIALED(chan_caller, chan_callee);
    
    	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
    	ast_channel_publish_dial(chan_caller, chan_callee, NULL, "CANCEL");
    
    
    	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NORMAL, "");
    	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "CANCEL");
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(test_cel_dial_parallel_failed)
    {
    	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_charlie, NULL, safe_channel_release);
    	RAII_VAR(struct ast_channel *, chan_david, NULL, safe_channel_release);
    	struct ast_party_caller caller = ALICE_CALLERID;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = __func__;
    		info->category = TEST_CATEGORY;
    		info->summary = "Test a parallel dial where all channels fail to answer";
    		info->description =
    			"This tests dialing three parties: Bob, Charlie, David. Charlie\n"
    			"returns BUSY; David returns CONGESTION; Bob fails to answer and\n"
    
    			"Alice hangs up. Three records are created for Alice as a result.";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	CREATE_ALICE_CHANNEL(chan_caller, &caller);
    
    	/* Channel enters Dial app */