[svn-commits] kmoore: trunk r393410 - in /trunk: include/asterisk/ main/ tests/

SVN commits to the Digium repositories svn-commits at lists.digium.com
Tue Jul 2 09:02:03 CDT 2013


Author: kmoore
Date: Tue Jul  2 09:01:53 2013
New Revision: 393410

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393410
Log:
Add CEL unit tests and do some cleanup

This adds several unit tests for CEL functionality and provides the
requisite framework for creating additional unit tests.

This also cleans up some reference leaks that were occurring in
Stasis-Core message callback code.

Review: https://reviewboard.asterisk.org/r/2646/

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

Modified: trunk/include/asterisk/cel.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/cel.h?view=diff&rev=393410&r1=393409&r2=393410
==============================================================================
--- trunk/include/asterisk/cel.h (original)
+++ trunk/include/asterisk/cel.h Tue Jul  2 09:01:53 2013
@@ -263,6 +263,68 @@
  */
 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 */
+	/*! The apps for which to log app start and end events. This is
+	 * ast_str_container_alloc()ed and filled with ao2-allocated
+	 * char* which are all-lowercase application names. */
+	struct ao2_container *apps;
+};
+
+/*!
+ * \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: trunk/main/cel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/cel.c?view=diff&rev=393410&r1=393409&r2=393410
==============================================================================
--- trunk/main/cel.c (original)
+++ trunk/main/cel.c Tue Jul  2 09:01:53 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: trunk/tests/test_cel.c
URL: http://svnview.digium.com/svn/asterisk/trunk/tests/test_cel.c?view=auto&rev=393410
==============================================================================
--- trunk/tests/test_cel.c (added)
+++ trunk/tests/test_cel.c Tue Jul  2 09:01:53 2013
@@ -1,0 +1,1450 @@
+/*
+ * 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,,");
+

[... 690 lines stripped ...]



More information about the svn-commits mailing list