[svn-commits] kmoore: branch kmoore/cel_transfers r393123 - in /team/kmoore/cel_transfers: ...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Fri Jun 28 09:10:51 CDT 2013


Author: kmoore
Date: Fri Jun 28 09:10:50 2013
New Revision: 393123

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393123
Log:
Pull in unit tests and cleanup from elsewhere

Added:
    team/kmoore/cel_transfers/tests/test_cel.c   (with props)
Modified:
    team/kmoore/cel_transfers/include/asterisk/cel.h
    team/kmoore/cel_transfers/main/cel.c

Modified: team/kmoore/cel_transfers/include/asterisk/cel.h
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/cel_transfers/include/asterisk/cel.h?view=diff&rev=393123&r1=393122&r2=393123
==============================================================================
--- team/kmoore/cel_transfers/include/asterisk/cel.h (original)
+++ team/kmoore/cel_transfers/include/asterisk/cel.h Fri Jun 28 09:10:50 2013
@@ -263,6 +263,65 @@
  */
 struct stasis_topic *ast_cel_topic(void);
 
+/*! \brief A structure to hold CEL global configuration options */
+struct ast_cel_general_config {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(date_format); /*!< The desired date format for logging */
+	);
+	int enable;			/*!< Whether CEL is enabled */
+	int64_t events;			/*!< The events to be logged */
+	struct ao2_container *apps;	/*!< The apps for which to log app start and end events */
+};
+
+/*!
+ * \brief Allocate a CEL configuration object
+ *
+ * \retval NULL on error
+ * \retval The new CEL configuration object
+ */
+void *ast_cel_general_config_alloc(void);
+
+/*!
+ * \since 12
+ * \brief Obtain the current CEL configuration
+ *
+ * The configuration is a ref counted object. The caller of this function must
+ * decrement the ref count when finished with the configuration.
+ *
+ * \retval NULL on error
+ * \retval The current CEL configuration
+ */
+struct ast_cel_general_config *ast_cel_get_config(void);
+
+/*!
+ * \since 12
+ * \brief Set the current CEL configuration
+ *
+ * \param config The new CEL configuration
+ */
+void ast_cel_set_config(struct ast_cel_general_config *config);
+
+struct ast_channel_snapshot;
+/*!
+ * \brief Allocate and populate a CEL event structure
+ *
+ * \param snapshot An ast_channel_snapshot of the primary channel associated
+ *        with this channel event.
+ * \param event_type The type of call event being reported.
+ * \param userdefevname Custom name for the call event. (optional)
+ * \param extra An opaque field that will go into the "CEL_EXTRA" information
+ *        element of the call event. (optional)
+ * \param peer_name The peer name to be placed into the event. (optional)
+ *
+ * \since 12
+ *
+ * \retval The created ast_event structure
+ * \retval NULL on failure
+ */
+struct ast_event *ast_cel_create_event(struct ast_channel_snapshot *snapshot,
+		enum ast_cel_event_type event_type, const char *userdefevname,
+		const char *extra, const char *peer_name);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif

Modified: team/kmoore/cel_transfers/main/cel.c
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/cel_transfers/main/cel.c?view=diff&rev=393123&r1=393122&r2=393123
==============================================================================
--- team/kmoore/cel_transfers/main/cel.c (original)
+++ team/kmoore/cel_transfers/main/cel.c Fri Jun 28 09:10:50 2013
@@ -180,28 +180,18 @@
  */
 static struct ao2_container *linkedids;
 
-/*! \brief A structure to hold global configuration-related options */
-struct cel_general_config {
-	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(date_format); /*!< The desired date format for logging */
-	);
-	int enable;			/*!< Whether CEL is enabled */
-	int64_t events;			/*!< The events to be logged */
-	struct ao2_container *apps;	/*!< The apps for which to log app start and end events */
-};
-
 /*! \brief Destructor for cel_config */
 static void cel_general_config_dtor(void *obj)
 {
-	struct cel_general_config *cfg = obj;
+	struct ast_cel_general_config *cfg = obj;
 	ast_string_field_free_memory(cfg);
 	ao2_cleanup(cfg->apps);
 	cfg->apps = NULL;
 }
 
-static void *cel_general_config_alloc(void)
-{
-	RAII_VAR(struct cel_general_config *, cfg, NULL, ao2_cleanup);
+void *ast_cel_general_config_alloc(void)
+{
+	RAII_VAR(struct ast_cel_general_config *, cfg, NULL, ao2_cleanup);
 
 	if (!(cfg = ao2_alloc(sizeof(*cfg), cel_general_config_dtor))) {
 		return NULL;
@@ -221,7 +211,7 @@
 
 /*! \brief A container that holds all config-related information */
 struct cel_config {
-	struct cel_general_config *general;
+	struct ast_cel_general_config *general;
 };
 
 
@@ -243,7 +233,7 @@
 		return NULL;
 	}
 
-	if (!(cfg->general = cel_general_config_alloc())) {
+	if (!(cfg->general = ast_cel_general_config_alloc())) {
 		return NULL;
 	}
 
@@ -251,7 +241,7 @@
 	return cfg;
 }
 
-/*! \brief An aco_type structure to link the "general" category to the cel_general_config type */
+/*! \brief An aco_type structure to link the "general" category to the ast_cel_general_config type */
 static struct aco_type general_option = {
 	.type = ACO_GLOBAL,
 	.name = "general",
@@ -559,7 +549,7 @@
 
 static int events_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
-	struct cel_general_config *cfg = obj;
+	struct ast_cel_general_config *cfg = obj;
 	char *events = ast_strdupa(var->value);
 	char *cur_event;
 
@@ -589,7 +579,7 @@
 
 static int apps_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
-	struct cel_general_config *cfg = obj;
+	struct ast_cel_general_config *cfg = obj;
 	char *apps = ast_strdupa(var->value);
 	char *cur_app;
 
@@ -642,52 +632,12 @@
 }
 
 static int cel_linkedid_ref(const char *linkedid);
-static int report_event_snapshot(struct ast_channel_snapshot *snapshot,
+struct ast_event *ast_cel_create_event(struct ast_channel_snapshot *snapshot,
 		enum ast_cel_event_type event_type, const char *userdefevname,
-		const char *extra, const char *peer2_name)
-{
-	struct timeval eventtime;
-	struct ast_event *ev;
-	char *linkedid = ast_strdupa(snapshot->linkedid);
-	const char *peer_name = peer2_name;
-	RAII_VAR(struct bridge_assoc *, assoc, NULL, ao2_cleanup);
-	RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
-
-	if (!cfg || !cfg->general) {
-		return 0;
-	}
-
-	if (!cfg->general->enable) {
-		return 0;
-	}
-
-	if (ast_strlen_zero(peer_name)) {
-		assoc = ao2_find(bridge_primaries, snapshot->uniqueid, OBJ_KEY);
-		if (assoc) {
-			peer_name = assoc->secondary_name;
-		}
-	}
-
-	/* Record the linkedid of new channels if we are tracking LINKEDID_END even if we aren't
-	 * reporting on CHANNEL_START so we can track when to send LINKEDID_END */
-	if (ast_cel_track_event(AST_CEL_LINKEDID_END) && event_type == AST_CEL_CHANNEL_START && linkedid) {
-		if (cel_linkedid_ref(linkedid)) {
-			return -1;
-		}
-	}
-
-	if (!ast_cel_track_event(event_type)) {
-		return 0;
-	}
-
-	if ((event_type == AST_CEL_APP_START || event_type == AST_CEL_APP_END)
-		&& !cel_track_app(snapshot->appl)) {
-		return 0;
-	}
-
-	eventtime = ast_tvnow();
-
-	ev = ast_event_new(AST_EVENT_CEL,
+		const char *extra, const char *peer_name)
+{
+	struct timeval eventtime = ast_tvnow();
+	return ast_event_new(AST_EVENT_CEL,
 		AST_EVENT_IE_CEL_EVENT_TYPE, AST_EVENT_IE_PLTYPE_UINT, event_type,
 		AST_EVENT_IE_CEL_EVENT_TIME, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_sec,
 		AST_EVENT_IE_CEL_EVENT_TIME_USEC, AST_EVENT_IE_PLTYPE_UINT, eventtime.tv_usec,
@@ -711,7 +661,51 @@
 		AST_EVENT_IE_CEL_EXTRA, AST_EVENT_IE_PLTYPE_STR, S_OR(extra, ""),
 		AST_EVENT_IE_CEL_PEER, AST_EVENT_IE_PLTYPE_STR, S_OR(peer_name, ""),
 		AST_EVENT_IE_END);
-
+}
+
+static int report_event_snapshot(struct ast_channel_snapshot *snapshot,
+		enum ast_cel_event_type event_type, const char *userdefevname,
+		const char *extra, const char *peer2_name)
+{
+	struct ast_event *ev;
+	char *linkedid = ast_strdupa(snapshot->linkedid);
+	const char *peer_name = peer2_name;
+	RAII_VAR(struct bridge_assoc *, assoc, NULL, ao2_cleanup);
+	RAII_VAR(struct cel_config *, cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
+
+	if (!cfg || !cfg->general) {
+		return 0;
+	}
+
+	if (!cfg->general->enable) {
+		return 0;
+	}
+
+	if (ast_strlen_zero(peer_name)) {
+		assoc = ao2_find(bridge_primaries, snapshot->uniqueid, OBJ_KEY);
+		if (assoc) {
+			peer_name = assoc->secondary_name;
+		}
+	}
+
+	/* Record the linkedid of new channels if we are tracking LINKEDID_END even if we aren't
+	 * reporting on CHANNEL_START so we can track when to send LINKEDID_END */
+	if (ast_cel_track_event(AST_CEL_LINKEDID_END) && event_type == AST_CEL_CHANNEL_START && linkedid) {
+		if (cel_linkedid_ref(linkedid)) {
+			return -1;
+		}
+	}
+
+	if (!ast_cel_track_event(event_type)) {
+		return 0;
+	}
+
+	if ((event_type == AST_CEL_APP_START || event_type == AST_CEL_APP_END)
+		&& !cel_track_app(snapshot->appl)) {
+		return 0;
+	}
+
+	ev = ast_cel_create_event(snapshot, event_type, userdefevname, extra, peer_name);
 	if (ev && ast_event_queue(ev)) {
 		ast_event_destroy(ev);
 		return -1;
@@ -1216,12 +1210,9 @@
 			cel_channel_monitors[i](old_snapshot, new_snapshot);
 		}
 	} else if (ast_bridge_snapshot_type() == update->type) {
-		RAII_VAR(struct bridge_assoc *, assoc, NULL, ao2_cleanup);
 		struct ast_bridge_snapshot *old_snapshot;
 		struct ast_bridge_snapshot *new_snapshot;
 
-		update = stasis_message_data(message);
-
 		old_snapshot = stasis_message_data(update->old_snapshot);
 		new_snapshot = stasis_message_data(update->new_snapshot);
 
@@ -1231,62 +1222,6 @@
 
 		if (!new_snapshot) {
 			clear_bridge_primary(old_snapshot->uniqueid);
-			return;
-		}
-
-		if (old_snapshot->capabilities == new_snapshot->capabilities) {
-			return;
-		}
-
-		/* handle 1:1/native -> multimix */
-		if ((old_snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE))
-			&& (new_snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)) {
-			assoc = find_bridge_primary_by_bridge_id(new_snapshot->uniqueid);
-			if (!assoc) {
-				ast_log(LOG_ERROR, "No association found for bridge %s\n", new_snapshot->uniqueid);
-				return;
-			}
-
-			/* this bridge will no longer be treated like a bridge, so mark the bridge_assoc as such */
-			assoc->track_as_conf = 1;
-			report_event_snapshot(assoc->primary_snapshot, AST_CEL_BRIDGE_TO_CONF, NULL, NULL, assoc->secondary_name);
-			return;
-		}
-
-		/* handle multimix -> 1:1/native */
-		if ((old_snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX)
-			&& (new_snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE))) {
-			struct ao2_iterator i;
-			RAII_VAR(char *, channel_id, NULL, ao2_cleanup);
-			RAII_VAR(struct ast_channel_snapshot *, chan_snapshot, NULL, ao2_cleanup);
-
-			assoc = find_bridge_primary_by_bridge_id(new_snapshot->uniqueid);
-			if (assoc) {
-				assoc->track_as_conf = 1;
-				return;
-			}
-
-			/* get the first item in the container */
-			i = ao2_iterator_init(new_snapshot->channels, 0);
-			while ((channel_id = ao2_iterator_next(&i))) {
-				break;
-			}
-			ao2_iterator_destroy(&i);
-
-			/* create a bridge_assoc for this bridge and mark it as being tracked appropriately */
-			chan_snapshot = ast_channel_snapshot_get_latest(channel_id);
-			if (!chan_snapshot) {
-				return;
-			}
-
-			ast_assert(chan_snapshot != NULL);
-			assoc = bridge_assoc_alloc(chan_snapshot, new_snapshot->uniqueid, chan_snapshot->name);
-			if (!assoc) {
-				return;
-			}
-			assoc->track_as_conf = 1;
-
-			ao2_link(bridge_primaries, assoc);
 			return;
 		}
 	}
@@ -1300,9 +1235,9 @@
 	struct ast_bridge_blob *blob = stasis_message_data(message);
 	struct ast_bridge_snapshot *snapshot = blob->bridge;
 	struct ast_channel_snapshot *chan_snapshot = blob->channel;
+	RAII_VAR(struct bridge_assoc *, assoc, find_bridge_primary_by_bridge_id(snapshot->uniqueid), ao2_cleanup);
 
 	if (snapshot->capabilities & (AST_BRIDGE_CAPABILITY_1TO1MIX | AST_BRIDGE_CAPABILITY_NATIVE)) {
-		RAII_VAR(struct bridge_assoc *, assoc, find_bridge_primary_by_bridge_id(snapshot->uniqueid), ao2_cleanup);
 		if (assoc && assoc->track_as_conf) {
 			report_event_snapshot(chan_snapshot, AST_CEL_CONF_ENTER, NULL, NULL, NULL);
 			return;
@@ -1331,8 +1266,25 @@
 
 			add_bridge_primary(latest_primary, snapshot->uniqueid, chan_snapshot->name);
 			report_event_snapshot(latest_primary, AST_CEL_BRIDGE_START, NULL, NULL, chan_snapshot->name);
+		} else if (ao2_container_count(snapshot->channels) > 2) {
+			if (!assoc) {
+				ast_log(LOG_ERROR, "No association found for bridge %s\n", snapshot->uniqueid);
+				return;
+			}
+
+			/* this bridge will no longer be treated like a bridge, so mark the bridge_assoc as such */
+			if (!assoc->track_as_conf) {
+				assoc->track_as_conf = 1;
+				report_event_snapshot(assoc->primary_snapshot, AST_CEL_BRIDGE_TO_CONF, NULL,
+					chan_snapshot->name, assoc->secondary_name);
+				ast_string_field_set(assoc, secondary_name, "");
+			}
 		}
 	} else if (snapshot->capabilities & AST_BRIDGE_CAPABILITY_MULTIMIX) {
+		if (!assoc) {
+			add_bridge_primary(chan_snapshot, snapshot->uniqueid, "");
+			return;
+		}
 		report_event_snapshot(chan_snapshot, AST_CEL_CONF_ENTER, NULL, NULL, NULL);
 	}
 }
@@ -1585,8 +1537,8 @@
 		return -1;
 	}
 
-	aco_option_register(&cel_cfg_info, "enable", ACO_EXACT, general_options, "no", OPT_BOOL_T, 1, FLDSET(struct cel_general_config, enable));
-	aco_option_register(&cel_cfg_info, "dateformat", ACO_EXACT, general_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct cel_general_config, date_format));
+	aco_option_register(&cel_cfg_info, "enable", ACO_EXACT, general_options, "no", OPT_BOOL_T, 1, FLDSET(struct ast_cel_general_config, enable));
+	aco_option_register(&cel_cfg_info, "dateformat", ACO_EXACT, general_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_cel_general_config, date_format));
 	aco_option_register_custom(&cel_cfg_info, "apps", ACO_EXACT, general_options, "", apps_handler, 0);
 	aco_option_register_custom(&cel_cfg_info, "events", ACO_EXACT, general_options, "", events_handler, 0);
 
@@ -1623,3 +1575,19 @@
 {
 	return cel_topic;
 }
+
+struct ast_cel_general_config *ast_cel_get_config(void)
+{
+	RAII_VAR(struct cel_config *, mod_cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
+	ao2_ref(mod_cfg->general, +1);
+	return mod_cfg->general;
+}
+
+void ast_cel_set_config(struct ast_cel_general_config *config)
+{
+	RAII_VAR(struct cel_config *, mod_cfg, ao2_global_obj_ref(cel_configs), ao2_cleanup);
+	ao2_cleanup(mod_cfg->general);
+	mod_cfg->general = config;
+	ao2_ref(mod_cfg->general, +1);
+}
+

Added: team/kmoore/cel_transfers/tests/test_cel.c
URL: http://svnview.digium.com/svn/asterisk/team/kmoore/cel_transfers/tests/test_cel.c?view=auto&rev=393123
==============================================================================
--- team/kmoore/cel_transfers/tests/test_cel.c (added)
+++ team/kmoore/cel_transfers/tests/test_cel.c Fri Jun 28 09:10:50 2013
@@ -1,0 +1,1445 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kinsey Moore <kmoore at 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 at digium.com>
+ *
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#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/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_bridging.h"
+
+#define TEST_CATEGORY "/main/cel/"
+
+#define CHANNEL_TECH_NAME "CELTestChannel"
+
+/*! \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 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, peer) do { \
+	if (append_expected_event(chan, ev_type, userevent, extra, peer)) { \
+		return AST_TEST_FAIL; \
+	} \
+	} 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 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, 0, CHANNEL_TECH_NAME "/Alice"); \
+	/*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \
+	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \
+	} 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, 0, CHANNEL_TECH_NAME "/Bob"); \
+	/*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \
+	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \
+	} 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, 0, CHANNEL_TECH_NAME "/Charlie"); \
+	/*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \
+	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \
+	} while (0)
+
+/*! \brief Create a \ref test_cel_chan_tech for Charlie. */
+#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, 0, CHANNEL_TECH_NAME "/David"); \
+	/*ast_channel_set_caller((channel_var), (caller_id), NULL);*/ \
+	APPEND_EVENT(channel_var, AST_CEL_CHANNEL_START, NULL, NULL, NULL); \
+	} 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, NULL); \
+	} while (0)
+
+/*! \brief Hang up a test channel safely */
+#define HANGUP_CHANNEL(channel, cause, hangup_extra) do { \
+	ast_channel_hangupcause_set((channel), (cause)); \
+	ao2_ref(channel, +1); \
+	if (!ast_hangup((channel))) { \
+		APPEND_EVENT(channel, AST_CEL_HANGUP, NULL, hangup_extra, NULL); \
+		APPEND_EVENT(channel, AST_CEL_CHANNEL_END, NULL, NULL, NULL); \
+		ao2_cleanup(stasis_cache_get_extended(ast_channel_topic_all_cached(), \
+			ast_channel_snapshot_type(), ast_channel_uniqueid(channel), 1)); \
+		ao2_cleanup(channel); \
+		channel = NULL; \
+	} else { \
+		APPEND_EVENT(channel, AST_CEL_HANGUP, NULL, hangup_extra, NULL); \
+		APPEND_EVENT(channel, AST_CEL_CHANNEL_END, NULL, NULL, NULL); \
+		ao2_cleanup(stasis_cache_get_extended(ast_channel_topic_all_cached(), \
+			ast_channel_snapshot_type(), ast_channel_uniqueid(channel), 1)); \
+		ao2_cleanup(channel); \
+	} \
+	} while (0)
+
+static int append_expected_event(
+	struct ast_channel *chan,
+	enum ast_cel_event_type type,
+	const char *userdefevname,
+	const char *extra, const char *peer);
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+	if (!chan) {
+		return;
+	}
+	ast_channel_release(chan);
+}
+
+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, "16,,");
+
+	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.\n";
+		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, "16,,");
+
+	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.\n";
+		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, "16,,");
+
+	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\n";
+		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, "16,,");
+
+	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, ao2_cleanup);
+
+	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.\n";
+		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();
+	ast_bridge_impart(bridge, chan, NULL, NULL, 0);
+
+	do_sleep();
+
+	ast_bridge_depart(chan);
+
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,");
+
+	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, ao2_cleanup);
+	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.\n";
+		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();
+	ast_bridge_impart(bridge, chan, NULL, NULL, 0);
+
+	do_sleep();
+
+	ast_bridge_depart(chan);
+
+	EMULATE_APP_DATA(chan, 3, "Wait", "");
+
+	/* And then it hangs up */
+	HANGUP_CHANNEL(chan, AST_CAUSE_NORMAL, "16,,");
+
+	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, ao2_cleanup);
+	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.\n";
+		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", "");
+
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+	do_sleep();
+
+	ANSWER_CHANNEL(chan_bob);
+	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+	do_sleep();
+	APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_bob));
+
+	ast_bridge_depart(chan_alice);
+	ast_bridge_depart(chan_bob);
+	APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_bob));
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,");
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,");
+
+	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, ao2_cleanup);
+	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.\n";
+		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();
+
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+	do_sleep();
+
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+	do_sleep();
+	APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_alice));
+
+	ast_bridge_depart(chan_alice);
+	ast_bridge_depart(chan_bob);
+	APPEND_EVENT(chan_bob, AST_CEL_BRIDGE_END, NULL, NULL, ast_channel_name(chan_alice));
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,");
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,");
+
+	return AST_TEST_PASS;
+}
+
+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, ao2_cleanup);
+	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.\n";
+		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();
+
+	ast_bridge_impart(bridge, chan_alice, NULL, NULL, 0);
+
+	ANSWER_CHANNEL(chan_bob);
+	EMULATE_APP_DATA(chan_bob, 2, "Bridge", "");
+	do_sleep();
+
+	ast_bridge_impart(bridge, chan_bob, NULL, NULL, 0);
+	do_sleep();
+	APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_START, NULL, NULL, ast_channel_name(chan_bob));
+
+	ANSWER_CHANNEL(chan_charlie);
+	EMULATE_APP_DATA(chan_charlie, 2, "Bridge", "");
+	ast_bridge_impart(bridge, chan_charlie, NULL, NULL, 0);
+	do_sleep();
+	APPEND_EVENT(chan_alice, AST_CEL_BRIDGE_TO_CONF, NULL, ast_channel_name(chan_charlie), ast_channel_name(chan_bob));
+
+	ast_bridge_depart(chan_alice);
+	APPEND_EVENT(chan_alice, AST_CEL_CONF_EXIT, NULL, NULL, NULL);
+	ast_bridge_depart(chan_bob);
+	APPEND_EVENT(chan_bob, AST_CEL_CONF_EXIT, NULL, NULL, NULL);
+	ast_bridge_depart(chan_charlie);
+	APPEND_EVENT(chan_charlie, AST_CEL_CONF_EXIT, NULL, NULL, NULL);
+
+	HANGUP_CHANNEL(chan_alice, AST_CAUSE_NORMAL, "16,,");
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,");
+	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_NORMAL, "16,,");
+
+	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, ast_channel_linkedid(caller), 0, CHANNEL_TECH_NAME "/" name); \
+	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\n";
+		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, "19,,NOANSWER");
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ANSWER, "19,,");
+
+	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\n";
+		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, "17,,BUSY");
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_BUSY, "17,,");
+
+	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\n";
+		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, "34,,CONGESTION");
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_CONGESTION, "34,,");
+
+	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\n";
+		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, "3,,CHANUNAVAIL");
+	HANGUP_CHANNEL(chan_callee, AST_CAUSE_NO_ROUTE_DESTINATION, "3,,");
+
+	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\n";
+		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, "16,,");
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,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.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	CREATE_ALICE_CHANNEL(chan_caller, &caller);
+
+	/* Channel enters Dial app */
+	EMULATE_DIAL(chan_caller, CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David");
+
+	/* Outbound channels are created */
+	START_DIALED_FULL(chan_caller, chan_bob, "200", "Bob");
+	START_DIALED_FULL(chan_caller, chan_charlie, "300", "Charlie");
+	START_DIALED_FULL(chan_caller, chan_david, "400", "David");
+
+	/* Dial starts */
+	ast_channel_state_set(chan_caller, AST_STATE_RINGING);
+
+	/* Charlie is busy */
+	ast_channel_publish_dial(chan_caller, chan_charlie, NULL, "BUSY");
+	HANGUP_CHANNEL(chan_charlie, AST_CAUSE_BUSY, "17,,");
+
+	/* David is congested */
+	ast_channel_publish_dial(chan_caller, chan_david, NULL, "CONGESTION");
+	HANGUP_CHANNEL(chan_david, AST_CAUSE_CONGESTION, "34,,");
+
+	/* Bob is canceled */
+	ast_channel_publish_dial(chan_caller, chan_bob, NULL, "CANCEL");
+	HANGUP_CHANNEL(chan_bob, AST_CAUSE_NORMAL, "16,,");
+
+	/* Alice hangs up */
+	HANGUP_CHANNEL(chan_caller, AST_CAUSE_NORMAL, "16,,BUSY");
+
+	return AST_TEST_PASS;

[... 681 lines stripped ...]



More information about the svn-commits mailing list