[asterisk-commits] mjordan: branch mjordan/cdrs-of-doom r382762 - in /team/mjordan/cdrs-of-doom:...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri Mar 8 17:43:26 CST 2013


Author: mjordan
Date: Fri Mar  8 17:43:22 2013
New Revision: 382762

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=382762
Log:
Start the great migration over the cliff

* Remove some uncalled functions from cdr.c
* Remove some duplicate functions from cdr.c
* Add a unit test module that covers some single party
  scenarios
* Subscribe to channel cache topic, add a callback
* Create a new internal CDR object that is relatively sane(r)
* Implement a mini state machine for the new CDR object

And it almost compiles...

Added:
    team/mjordan/cdrs-of-doom/tests/test_cdr.c   (with props)
Modified:
    team/mjordan/cdrs-of-doom/include/asterisk/cdr.h
    team/mjordan/cdrs-of-doom/main/cdr.c
    team/mjordan/cdrs-of-doom/main/channel.c
    team/mjordan/cdrs-of-doom/main/features.c
    team/mjordan/cdrs-of-doom/main/pbx.c

Modified: team/mjordan/cdrs-of-doom/include/asterisk/cdr.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/include/asterisk/cdr.h?view=diff&rev=382762&r1=382761&r2=382762
==============================================================================
--- team/mjordan/cdrs-of-doom/include/asterisk/cdr.h (original)
+++ team/mjordan/cdrs-of-doom/include/asterisk/cdr.h Fri Mar  8 17:43:22 2013
@@ -201,12 +201,6 @@
  */
 void ast_cdr_free(struct ast_cdr *cdr);
 
-/*!
- * \brief Discard and free a CDR record
- * \param cdr ast_cdr structure to free
- * Returns nothing  -- same as free, but no checks or complaints
- */
-void ast_cdr_discard(struct ast_cdr *cdr);
 
 /*!
  * \brief Initialize based on a channel
@@ -410,13 +404,6 @@
 char *ast_cdr_flags2str(int flags);
 
 /*!
- * \brief Move the non-null data from the "from" cdr to the "to" cdr
- * \param to the cdr to get the goodies
- * \param from the cdr to give the goodies
- */
-void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from);
-
-/*!
  * \brief Set account code, will generate AMI event
  * \note The channel should be locked before calling.
  */

Modified: team/mjordan/cdrs-of-doom/main/cdr.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/cdr.c?view=diff&rev=382762&r1=382761&r2=382762
==============================================================================
--- team/mjordan/cdrs-of-doom/main/cdr.c (original)
+++ team/mjordan/cdrs-of-doom/main/cdr.c Fri Mar  8 17:43:22 2013
@@ -205,69 +205,6 @@
 	BATCH_MODE_SAFE_SHUTDOWN = 1 << 1,	/*< During safe shutdown, submit the batched CDRs */
 };
 
-static struct stasis_subscription *channel_state_sub;
-
-struct cdr_object;
-
-struct cdr_object_fn_table {
-	void (* const init_function)(struct cdr_object *cdr);
-	void (* const process_channel_message)(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
-	void (* const process_varset_message)(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot, const char *name, const char *value);
-};
-
-static void single_active_state_process_channel_message(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
-struct cdr_object_fn_table single_active_state_fn_table = {
-	.process_channel_message = single_active_state_process_channel_message,
-};
-
-struct cdr_object_fn_table hangup_state_fn_table = {
-	.process_channel_message = NULL,
-};
-
-struct cdr_object {
-	struct ast_channel_snapshot *party_a;
-	struct varshead party_a_variables;
-	struct ast_channel_snapshot *party_b;
-	struct varshead party_b_variables;
-	struct cdr_object_fn_table *fn_table;
-
-	AST_LIST_ENTRY(cdr_object) list;
-};
-
-/* NON-INHERITABLE FUNCTIONS */
-
-static void transition_state(struct cdr_object *cdr, cdr_object_fn_table *fn_table)
-{
-	cdr->fn_table = fn_table;
-	if (cdr->fn_table->init_function) {
-		cdr->fn_table->init_function(cdr);
-	}
-}
-
-static void check_for_hangup(struct cdr_object *cdr)
-{
-	if (ast_test_flag(&cdr->party_a->flags, AST_FLAG_ZOMBIE)) {
-		transition_state(cdr, &hangup_state_fn_table);
-	}
-}
-
-/* SINGLE STATE */
-
-static void single_active_state_process_channel_message(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
-{
-	ast_assert(cdr != NULL);
-	ast_assert(snapshot != NULL);
-
-	if (!strcmp(snapshot->name, cdr->party_a->name)) {
-		ao2_ref(cdr->party_a, -1);
-		cdr->party_a = snapshot;
-		ao2_ref(cdr->party_a, +1);
-	}
-	check_for_hangup();
-}
-
-
-
 /*! \brief The global options available for CDRs */
 struct cdr_config {
 	struct ast_flags settings;			/*< CDR settings */
@@ -376,6 +313,307 @@
 /* these are used to wake up the CDR thread when there's work to do */
 AST_MUTEX_DEFINE_STATIC(cdr_pending_lock);
 static ast_cond_t cdr_pending_cond;
+
+
+
+
+
+
+
+
+
+static struct stasis_subscription *channel_state_sub;
+
+struct cdr_object;
+
+struct cdr_object_fn_table {
+	void (* const init_function)(struct cdr_object *cdr);
+	void (* const process_channel_message)(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+	void (* const process_channel_varset_message)(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot, const char *name, const char *value);
+};
+
+static void single_state_process_channel_message(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot);
+static void single_state_process_channel_varset_message(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot, const char *name, const char *value);
+
+struct cdr_object_fn_table single_state_fn_table = {
+	.process_channel_message = single_state_process_channel_message,
+	.process_channel_varset_message = single_state_process_channel_varset_message,
+};
+
+static void hangup_state_init_function(struct cdr_object *cdr);
+
+struct cdr_object_fn_table hangup_state_fn_table = {
+	.init_function = hangup_state_init_function,
+	.process_channel_message = NULL,
+	.process_channel_varset_message = NULL,
+};
+
+enum cdr_object_timestamps {
+	CDR_OBJECT_TIME_CREATED = 0,
+	CDR_OBJECT_TIME_ANSWERED,
+	CDR_OBJECT_TIME_FINALIZED,
+	CDR_OBJECT_TIME_LENGTH, 	/* Must be last */
+};
+
+struct cdr_object {
+	struct ast_channel_snapshot *party_a;
+	struct varshead party_a_variables;
+	struct ast_channel_snapshot *party_b;
+	struct varshead party_b_variables;
+	struct cdr_object_fn_table *fn_table;
+
+	unsigned int disposition;
+	struct timeval timestamps[CDR_OBJECT_TIME_LENGTH];
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(linkedid);
+	);
+	struct cdr_object *next;
+};
+
+/* NON-VIRTUAL FUNCTIONS */
+
+static void cdr_object_destructor(void *obj)
+{
+	struct cdr_object *cdr = obj;
+	struct ast_var_t *it_var;
+
+	if (!cdr) {
+		return;
+	}
+
+	ao2_cleanup(cdr->party_a);
+	ao2_cleanup(cdr->party_b);
+	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_a_variables, entries))) {
+		ast_var_delete(it_var);
+	}
+	while ((it_var = AST_LIST_REMOVE_HEAD(&cdr->party_b_variables, entries))) {
+		ast_var_delete(it_var);
+	}
+	ast_string_field_free_memory(cdr);
+
+	if (cdr->next) {
+		ao2_cleanup(cdr->next);
+	}
+}
+
+static struct cdr_object *cdr_object_alloc(struct ast_channel_snapshot *chan)
+{
+	struct cdr_object *cdr;
+
+	ast_assert(chan != NULL);
+
+	cdr = ao2_alloc(sizeof(*cdr), cdr_object_destructor);
+	if (!cdr) {
+		return NULL;
+	}
+	ast_string_field_init(cdr, 64);
+
+	cdr->party_a = chan;
+	ast_string_field_set(cdr, linkedid, chan->linkedid);
+	ao2_ref(cdr->party_a, +1);
+
+	return cdr;
+}
+
+static struct ast_cdr *create_public_cdr_records(struct cdr_object *cdr)
+{
+	struct ast_cdr *pub_cdr = NULL, *cdr_prev;
+	struct ast_var_t *it_var, *it_copy_var;
+
+	while (cdr) {
+		struct ast_cdr *cdr_copy;
+		cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+		if (!cdr_copy) {
+			ast_free(pub_cdr);
+			return NULL;
+		}
+
+		/* Party A */
+		ast_copy_string(cdr_copy->accountcode, cdr->party_a->accountcode, sizeof(cdr_copy->accountcode));
+		cdr_copy->amaflags = cdr->party_a->amaflags;
+		ast_copy_string(cdr_copy->channel, cdr->party_a->name, sizeof(cdr_copy->channel));
+		/* XXX TODO: we need to get the full caller ID somehow */
+		ast_callerid_merge(cdr_copy->clid, sizeof(cdr_copy->clid), cdr->party_a->caller_name, cdr->party_a->caller_number, "");
+		ast_copy_string(cdr_copy->src, cdr->party_a->caller_number, sizeof(cdr_copy->src));
+		ast_copy_string(cdr_copy->uniqueid, cdr->party_a->uniqueid, sizeof(cdr_copy->uniqueid));
+		ast_copy_string(cdr_copy->lastapp, cdr->party_a->appl, sizeof(cdr_copy->lastapp));
+		ast_copy_string(cdr_copy->lastdata, cdr->party_a->data, sizeof(cdr_copy->lastdata));
+		ast_copy_string(cdr_copy->dst, cdr->party_a->exten, sizeof(cdr_copy->dst));
+		ast_copy_string(cdr_copy->dcontext, cdr->party_a->context, sizeof(cdr_copy->dcontext));
+		ast_copy_string(cdr_copy->userfield, cdr->party_a->userfield, sizeof(cdr_copy->userfield));
+
+
+		/* Party B */
+		ast_copy_string(cdr_copy->dstchannel, cdr->party_b->name, sizeof(cdr_copy->dstchannel));
+		ast_copy_string(cdr_copy->peeraccount, cdr->party_b->accountcode, sizeof(cdr_copy->peeraccount));
+
+		/* Timestamps */
+		cdr_copy->start = cdr->timestamps[CDR_OBJECT_TIME_CREATED];
+		cdr_copy->answer = cdr->timestamps[CDR_OBJECT_TIME_ANSWERED];
+		cdr_copy->end = cdr->timestamps[CDR_OBJECT_TIME_FINALIZED];
+
+		/* Durations */
+		cdr_copy->billsec = ast_tvzero(cdr->timestamps[CDR_OBJECT_TIME_ANSWERED]) ?
+				(long)(ast_tvdiff_ms(cdr->timestamps[CDR_OBJECT_TIME_FINALIZED], cdr->timestamps[CDR_OBJECT_TIME_CREATED]) / 1000) :
+				(long)(ast_tvdiff_ms(cdr->timestamps[CDR_OBJECT_TIME_FINALIZED], cdr->timestamps[CDR_OBJECT_TIME_ANSWERED]) / 1000);
+		cdr_copy->duration = (long)(ast_tvdiff_ms(cdr->timestamps[CDR_OBJECT_TIME_FINALIZED], cdr->timestamps[CDR_OBJECT_TIME_ANSWERED]) / 1000);
+
+		ast_copy_string(cdr_copy->linkedid, cdr->linkedid, sizeof(cdr_copy->linkedid));
+		cdr_copy->disposition = cdr->disposition;
+		cdr_copy->sequence = ast_atomic_fetchadd_int(&cdr_sequence, +1);
+
+		AST_LIST_TRAVERSE(&cdr->party_a_variables, it_var, entries) {
+			AST_LIST_INSERT_TAIL(&cdr_copy->varshead, ast_var_assign(ast_var_name(it_var), ast_var_value(it_var)), entries);
+		}
+		AST_LIST_TRAVERSE(&cdr->party_b_variables, it_var, entries) {
+			int found = 0;
+			AST_LIST_TRAVERSE(&cdr_copy->varshead, it_copy_var, entries) {
+				if (!strcmp(ast_var_name(it_var), ast_var_name(it_copy_var))) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				AST_LIST_INSERT_TAIL(&cdr_copy->varshead, ast_var_assign(ast_var_name(it_var), ast_var_value(it_var)), entries);
+			}
+		}
+
+		if (!pub_cdr) {
+			pub_cdr = cdr_copy;
+			cdr_prev = pub_cdr;
+		} else {
+			cdr_prev->next = cdr_copy;
+			cdr_prev = cdr_copy;
+		}
+		cdr = cdr->next;
+	}
+
+	return pub_cdr;
+}
+
+static void dispatch_cdr_record(struct cdr_object *cdr)
+{
+	struct ast_cdr *pub_cdr;
+
+	/* We shouldn't be dispatching unless party A is hung up */
+	ast_assert(cdr->fn_table == &hangup_state_fn_table);
+
+	pub_cdr = create_public_cdr_records(cdr);
+	ast_cdr_detach(pub_cdr);
+	ast_cdr_free(pub_cdr);
+
+
+}
+
+static void transition_state(struct cdr_object *cdr, struct cdr_object_fn_table *fn_table)
+{
+	cdr->fn_table = fn_table;
+	if (cdr->fn_table->init_function) {
+		cdr->fn_table->init_function(cdr);
+	}
+}
+
+static void check_for_hangup(struct cdr_object *cdr)
+{
+	if (ast_test_flag(&cdr->party_a->flags, AST_FLAG_ZOMBIE)) {
+		transition_state(cdr, &hangup_state_fn_table);
+	}
+}
+
+/* SINGLE STATE */
+
+static void single_state_process_channel_message(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot)
+{
+	ast_assert(cdr != NULL);
+	ast_assert(snapshot != NULL);
+
+	if (strcmp(snapshot->name, cdr->party_a->name)) {
+		return;
+	}
+
+	ao2_ref(cdr->party_a, -1);
+	cdr->party_a = snapshot;
+	ao2_ref(cdr->party_a, +1);
+	check_for_hangup(cdr);
+}
+
+static void single_state_process_channel_varset_message(struct cdr_object *cdr, struct ast_channel_snapshot *snapshot, const char *name, const char *value)
+{
+	return;
+}
+
+/* HANGUP STATE */
+
+static void hangup_state_init_function(struct cdr_object *cdr) {
+	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
+
+	if (ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+		/* DO something? */
+	}
+
+	/* Set the end time if it isn't yet set */
+	if (ast_tvzero(cdr->timestamps[CDR_OBJECT_TIME_FINALIZED])) {
+		cdr->timestamps[CDR_OBJECT_TIME_FINALIZED] = ast_tvnow();
+	}
+
+	if (!ast_tvzero(cdr->timestamps[CDR_OBJECT_TIME_ANSWERED])) {
+		cdr->disposition = AST_CDR_ANSWERED;
+	}
+
+	/* Change the disposition based on the hang up cause */
+	switch (cdr->party_a->hangupcause) {
+	case AST_CAUSE_BUSY:
+		cdr->disposition = AST_CDR_BUSY;
+		break;
+	case AST_CAUSE_CONGESTION:
+		cdr->disposition = AST_CDR_CONGESTION;
+		break;
+	case AST_CAUSE_NO_ROUTE_DESTINATION:
+	case AST_CAUSE_UNREGISTERED:
+		cdr->disposition = AST_CDR_FAILED;
+		break;
+	case AST_CAUSE_NO_ANSWER:
+		cdr->disposition = AST_CDR_NOANSWER;
+		break;
+	default:
+		break;
+	}
+
+	dispatch_cdr_record(cdr);
+}
+
+
+
+
+/* TOPIC CALLBACKS */
+
+static void channel_topic_callback(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+	if (stasis_message_type(message) == stasis_cache_update()) {
+		struct stasis_cache_update *update = stasis_message_data(message);
+		if (ast_channel_snapshot() == update->type) {
+			struct ast_channel_snapshot *old_snapshot =
+				stasis_message_data(update->old_snapshot);
+			struct ast_channel_snapshot *new_snapshot =
+				stasis_message_data(update->new_snapshot);
+
+		}
+	} else if (stasis_message_type(message) == ast_channel_varset()) {
+		struct ast_channel_varset *varset = stasis_message_data(message);
+		const char *name = varset->snapshot ? varset->snapshot->name : "none";
+		const char *uniqueid = varset->snapshot ? varset->snapshot->uniqueid : "none";
+
+	}
+}
+
+
+
+
+
+
+
+
+
 
 int check_cdr_enabled(void)
 {
@@ -753,18 +991,6 @@
 	}
 }
 
-/*! \brief the same as a cdr_free call, only with no checks; just get rid of it */
-void ast_cdr_discard(struct ast_cdr *cdr)
-{
-	while (cdr) {
-		struct ast_cdr *next = cdr->next;
-
-		ast_cdr_free_vars(cdr, 0);
-		ast_free(cdr);
-		cdr = next;
-	}
-}
-
 struct ast_cdr *ast_cdr_alloc(void)
 {
 	struct ast_cdr *x;
@@ -772,225 +998,6 @@
 	if (!x)
 		ast_log(LOG_ERROR,"Allocation Failure for a CDR!\n");
 	return x;
-}
-
-static void cdr_merge_vars(struct ast_cdr *to, struct ast_cdr *from)
-{
-	struct ast_var_t *variablesfrom,*variablesto;
-	struct varshead *headpfrom = &to->varshead;
-	struct varshead *headpto = &from->varshead;
-	AST_LIST_TRAVERSE_SAFE_BEGIN(headpfrom, variablesfrom, entries) {
-		/* for every var in from, stick it in to */
-		const char *fromvarname, *fromvarval;
-		const char *tovarname = NULL, *tovarval = NULL;
-		fromvarname = ast_var_name(variablesfrom);
-		fromvarval = ast_var_value(variablesfrom);
-		tovarname = 0;
-
-		/* now, quick see if that var is in the 'to' cdr already */
-		AST_LIST_TRAVERSE(headpto, variablesto, entries) {
-
-			/* now, quick see if that var is in the 'to' cdr already */
-			if ( strcasecmp(fromvarname, ast_var_name(variablesto)) == 0 ) {
-				tovarname = ast_var_name(variablesto);
-				tovarval = ast_var_value(variablesto);
-				break;
-			}
-		}
-		if (tovarname && strcasecmp(fromvarval,tovarval) != 0) {  /* this message here to see how irritating the userbase finds it */
-			ast_log(LOG_NOTICE, "Merging CDR's: variable %s value %s dropped in favor of value %s\n", tovarname, fromvarval, tovarval);
-			continue;
-		} else if (tovarname && strcasecmp(fromvarval,tovarval) == 0) /* if they are the same, the job is done */
-			continue;
-
-		/* rip this var out of the from cdr, and stick it in the to cdr */
-		AST_LIST_MOVE_CURRENT(headpto, entries);
-	}
-	AST_LIST_TRAVERSE_SAFE_END;
-}
-
-void ast_cdr_merge(struct ast_cdr *to, struct ast_cdr *from)
-{
-	struct ast_cdr *zcdr;
-	struct ast_cdr *lto = NULL;
-	struct ast_cdr *lfrom = NULL;
-	int discard_from = 0;
-
-	if (!to || !from)
-		return;
-
-	/* don't merge into locked CDR's -- it's bad business */
-	if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
-		zcdr = to; /* safety valve? */
-		while (to->next) {
-			lto = to;
-			to = to->next;
-		}
-
-		if (ast_test_flag(to, AST_CDR_FLAG_LOCKED)) {
-			ast_log(LOG_WARNING, "Merging into locked CDR... no choice.\n");
-			to = zcdr; /* safety-- if all there are is locked CDR's, then.... ?? */
-			lto = NULL;
-		}
-	}
-
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED)) {
-		struct ast_cdr *llfrom = NULL;
-		discard_from = 1;
-		if (lto) {
-			/* insert the from stuff after lto */
-			lto->next = from;
-			lfrom = from;
-			while (lfrom && lfrom->next) {
-				if (!lfrom->next->next)
-					llfrom = lfrom;
-				lfrom = lfrom->next;
-			}
-			/* rip off the last entry and put a copy of the to at the end */
-			if (llfrom) {
-				llfrom->next = to;
-			}
-			from = lfrom;
-		} else {
-			/* save copy of the current *to cdr */
-			struct ast_cdr tcdr;
-			memcpy(&tcdr, to, sizeof(tcdr));
-			/* copy in the locked from cdr */
-			memcpy(to, from, sizeof(*to));
-			lfrom = from;
-			while (lfrom && lfrom->next) {
-				if (!lfrom->next->next)
-					llfrom = lfrom;
-				lfrom = lfrom->next;
-			}
-			from->next = NULL;
-			/* rip off the last entry and put a copy of the to at the end */
-			if (llfrom == from) {
-				to = to->next = ast_cdr_dup(&tcdr);
-			} else if (llfrom) {
-				to = llfrom->next = ast_cdr_dup(&tcdr);
-			}
-			from = lfrom;
-		}
-	}
-
-	if (!ast_tvzero(from->start)) {
-		if (!ast_tvzero(to->start)) {
-			if (ast_tvcmp(to->start, from->start) > 0 ) {
-				to->start = from->start; /* use the earliest time */
-				from->start = ast_tv(0,0); /* we actively "steal" these values */
-			}
-			/* else nothing to do */
-		} else {
-			to->start = from->start;
-			from->start = ast_tv(0,0); /* we actively "steal" these values */
-		}
-	}
-	if (!ast_tvzero(from->answer)) {
-		if (!ast_tvzero(to->answer)) {
-			if (ast_tvcmp(to->answer, from->answer) > 0 ) {
-				to->answer = from->answer; /* use the earliest time */
-				from->answer = ast_tv(0,0); /* we actively "steal" these values */
-			}
-			/* we got the earliest answer time, so we'll settle for that? */
-		} else {
-			to->answer = from->answer;
-			from->answer = ast_tv(0,0); /* we actively "steal" these values */
-		}
-	}
-	if (!ast_tvzero(from->end)) {
-		if (!ast_tvzero(to->end)) {
-			if (ast_tvcmp(to->end, from->end) < 0 ) {
-				to->end = from->end; /* use the latest time */
-				from->end = ast_tv(0,0); /* we actively "steal" these values */
-				to->duration = to->end.tv_sec - to->start.tv_sec;  /* don't forget to update the duration, billsec, when we set end */
-				to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
-			}
-			/* else, nothing to do */
-		} else {
-			to->end = from->end;
-			from->end = ast_tv(0,0); /* we actively "steal" these values */
-			to->duration = to->end.tv_sec - to->start.tv_sec;
-			to->billsec = ast_tvzero(to->answer) ? 0 : to->end.tv_sec - to->answer.tv_sec;
-		}
-	}
-	if (to->disposition < from->disposition) {
-		to->disposition = from->disposition;
-		from->disposition = AST_CDR_NOANSWER;
-	}
-	if (ast_strlen_zero(to->lastapp) && !ast_strlen_zero(from->lastapp)) {
-		ast_copy_string(to->lastapp, from->lastapp, sizeof(to->lastapp));
-		from->lastapp[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->lastdata) && !ast_strlen_zero(from->lastdata)) {
-		ast_copy_string(to->lastdata, from->lastdata, sizeof(to->lastdata));
-		from->lastdata[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->dcontext) && !ast_strlen_zero(from->dcontext)) {
-		ast_copy_string(to->dcontext, from->dcontext, sizeof(to->dcontext));
-		from->dcontext[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->dstchannel) && !ast_strlen_zero(from->dstchannel)) {
-		ast_copy_string(to->dstchannel, from->dstchannel, sizeof(to->dstchannel));
-		from->dstchannel[0] = 0; /* theft */
-	}
-	if (!ast_strlen_zero(from->channel) && (ast_strlen_zero(to->channel) || !strncasecmp(from->channel, "Agent/", 6))) {
-		ast_copy_string(to->channel, from->channel, sizeof(to->channel));
-		from->channel[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->src) && !ast_strlen_zero(from->src)) {
-		ast_copy_string(to->src, from->src, sizeof(to->src));
-		from->src[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->clid) && !ast_strlen_zero(from->clid)) {
-		ast_copy_string(to->clid, from->clid, sizeof(to->clid));
-		from->clid[0] = 0; /* theft */
-	}
-	if (ast_strlen_zero(to->dst) && !ast_strlen_zero(from->dst)) {
-		ast_copy_string(to->dst, from->dst, sizeof(to->dst));
-		from->dst[0] = 0; /* theft */
-	}
-	if (!to->amaflags)
-		to->amaflags = AST_CDR_DOCUMENTATION;
-	if (!from->amaflags)
-		from->amaflags = AST_CDR_DOCUMENTATION; /* make sure both amaflags are set to something (DOC is default) */
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (to->amaflags == AST_CDR_DOCUMENTATION && from->amaflags != AST_CDR_DOCUMENTATION)) {
-		to->amaflags = from->amaflags;
-	}
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->accountcode) && !ast_strlen_zero(from->accountcode))) {
-		ast_copy_string(to->accountcode, from->accountcode, sizeof(to->accountcode));
-	}
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->peeraccount) && !ast_strlen_zero(from->peeraccount))) {
-		ast_copy_string(to->peeraccount, from->peeraccount, sizeof(to->peeraccount));
-	}
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED) || (ast_strlen_zero(to->userfield) && !ast_strlen_zero(from->userfield))) {
-		ast_copy_string(to->userfield, from->userfield, sizeof(to->userfield));
-	}
-	/* flags, varsead, ? */
-	cdr_merge_vars(from, to);
-
-	if (ast_test_flag(from, AST_CDR_FLAG_KEEP_VARS))
-		ast_set_flag(to, AST_CDR_FLAG_KEEP_VARS);
-	if (ast_test_flag(from, AST_CDR_FLAG_POSTED))
-		ast_set_flag(to, AST_CDR_FLAG_POSTED);
-	if (ast_test_flag(from, AST_CDR_FLAG_LOCKED))
-		ast_set_flag(to, AST_CDR_FLAG_LOCKED);
-	if (ast_test_flag(from, AST_CDR_FLAG_CHILD))
-		ast_set_flag(to, AST_CDR_FLAG_CHILD);
-	if (ast_test_flag(from, AST_CDR_FLAG_POST_DISABLED))
-		ast_set_flag(to, AST_CDR_FLAG_POST_DISABLED);
-
-	/* last, but not least, we need to merge any forked CDRs to the 'to' cdr */
-	while (from->next) {
-		/* just rip 'em off the 'from' and insert them on the 'to' */
-		zcdr = from->next;
-		from->next = zcdr->next;
-		zcdr->next = NULL;
-		/* zcdr is now ripped from the current list; */
-		ast_cdr_append(to, zcdr);
-	}
-	if (discard_from)
-		ast_cdr_discard(from);
 }
 
 void ast_cdr_start(struct ast_cdr *cdr)
@@ -1903,8 +1910,8 @@
 	}
 
 	channel_state_sub = stasis_subscribe(
-		stasis_caching_get_topic(ast_channel_events_all_cached()),
-		channel_message_cb, NULL);
+		stasis_caching_get_topic(ast_channel_topic_all_cached()),
+		channel_topic_callback, NULL);
 
 	sched = ast_sched_context_create();
 	if (!sched) {

Modified: team/mjordan/cdrs-of-doom/main/channel.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/channel.c?view=diff&rev=382762&r1=382761&r2=382762
==============================================================================
--- team/mjordan/cdrs-of-doom/main/channel.c (original)
+++ team/mjordan/cdrs-of-doom/main/channel.c Fri Mar  8 17:43:22 2013
@@ -2521,7 +2521,7 @@
 	ast_jb_destroy(chan);
 
 	if (ast_channel_cdr(chan)) {
-		ast_cdr_discard(ast_channel_cdr(chan));
+		ast_cdr_free(ast_channel_cdr(chan));
 		ast_channel_cdr_set(chan, NULL);
 	}
 
@@ -2581,7 +2581,7 @@
 		ast_var_delete(vardata);
 
 	if (ast_channel_cdr(chan)) {
-		ast_cdr_discard(ast_channel_cdr(chan));
+		ast_cdr_free(ast_channel_cdr(chan));
 		ast_channel_cdr_set(chan, NULL);
 	}
 
@@ -2892,6 +2892,12 @@
 
 	ast_channel_unlock(chan);
 
+	/* XXX We need to publish before the hangup extensions are run. Some
+	 * subscribers care about being in a particular state before those
+	 * dialplan subroutines are executed.
+	 */
+	publish_channel_state(chan);
+
 	/*
 	 * XXX if running the hangup handlers here causes problems
 	 * because the handlers take too long to execute, we could move
@@ -2950,7 +2956,6 @@
 
 	ast_cc_offer(chan);
 
-	publish_channel_state(chan);
 	publish_cache_clear(chan);
 
 	if (ast_channel_cdr(chan) && !ast_test_flag(ast_channel_cdr(chan), AST_CDR_FLAG_BRIDGED) &&

Modified: team/mjordan/cdrs-of-doom/main/features.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/features.c?view=diff&rev=382762&r1=382761&r2=382762
==============================================================================
--- team/mjordan/cdrs-of-doom/main/features.c (original)
+++ team/mjordan/cdrs-of-doom/main/features.c Fri Mar  8 17:43:22 2013
@@ -4796,7 +4796,7 @@
 	if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT)) {
 		ast_clear_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_DONT); /* its job is done */
 		if (bridge_cdr) {
-			ast_cdr_discard(bridge_cdr);
+			ast_cdr_free(bridge_cdr);
 			/* QUESTION: should we copy bridge_cdr fields to the peer before we throw it away? */
 		}
 		return res; /* if we shouldn't do the h-exten, we shouldn't do the bridge cdr, either! */

Modified: team/mjordan/cdrs-of-doom/main/pbx.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/pbx.c?view=diff&rev=382762&r1=382761&r2=382762
==============================================================================
--- team/mjordan/cdrs-of-doom/main/pbx.c (original)
+++ team/mjordan/cdrs-of-doom/main/pbx.c Fri Mar  8 17:43:22 2013
@@ -9555,13 +9555,13 @@
 	/* Do not hold any channel locks while calling channel_alloc() since the function
 	 * locks the channel container when linking the new channel in. */
 	if (!(tmpchan = ast_channel_alloc(0, tmpvars.state, 0, 0, tmpvars.accountcode, tmpvars.exten, tmpvars.context, tmpvars.linkedid, tmpvars.amaflags, "AsyncGoto/%s", tmpvars.name))) {
-		ast_cdr_discard(tmpvars.cdr);
+		ast_cdr_free(tmpvars.cdr);
 		return -1;
 	}
 
 	/* copy the cdr info over */
 	if (tmpvars.cdr) {
-		ast_cdr_discard(ast_channel_cdr(tmpchan));
+		ast_cdr_free(ast_channel_cdr(tmpchan));
 		ast_channel_cdr_set(tmpchan, tmpvars.cdr);
 		tmpvars.cdr = NULL;
 	}

Added: team/mjordan/cdrs-of-doom/tests/test_cdr.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/tests/test_cdr.c?view=auto&rev=382762
==============================================================================
--- team/mjordan/cdrs-of-doom/tests/test_cdr.c (added)
+++ team/mjordan/cdrs-of-doom/tests/test_cdr.c Fri Mar  8 17:43:22 2013
@@ -1,0 +1,455 @@
+/*
+ * test_cdrs.c
+ *
+ *  Created on: Mar 3, 2013
+ *      Author: mjordan
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/cdr.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/utils.h"
+
+#define MOCK_CDR_BACKEND "mock_cdr_backend"
+
+#define CHANNEL_TECH_NAME "CDRTestChannel"
+
+static AST_LIST_HEAD(, test_cdr_entry) actual_cdr_entries = AST_LIST_HEAD_INIT_VALUE;
+
+static struct ast_channel_tech test_cdr_chan_tech = {
+	.type = CHANNEL_TECH_NAME,
+	.description = "Mock channel technology for CDR tests",
+};
+
+struct test_cdr_entry {
+	struct ast_cdr *cdr;
+	AST_LIST_ENTRY(test_cdr_entry) list;
+};
+
+static int mock_cdr_backend(struct ast_cdr *cdr)
+{
+	struct ast_cdr *cdr_copy, *cdr_prev = NULL;
+	struct ast_cdr *mock_cdr = NULL;
+	struct test_cdr_entry *cdr_wrapper;
+
+	cdr_wrapper = ast_calloc(1, sizeof(*cdr_wrapper));
+	if (!cdr_wrapper) {
+		return -1;
+	}
+
+	for (; cdr; cdr = cdr->next) {
+		struct ast_var_t *var_entry, *var_copy;
+
+		cdr_copy = ast_calloc(1, sizeof(*cdr_copy));
+		if (!cdr_copy) {
+			return -1;
+		}
+		*cdr_copy = *cdr;
+		cdr_copy->varshead.first = NULL;
+		cdr_copy->varshead.last = NULL;
+		cdr_copy->next = NULL;
+
+		AST_LIST_TRAVERSE(&cdr->varshead, var_entry, entries) {
+			var_copy = ast_var_assign(var_entry->name, var_entry->value);
+			if (!var_copy) {
+				return -1;
+			}
+			AST_LIST_INSERT_TAIL(&cdr_copy->varshead, var_copy, entries);
+		}
+
+		if (!mock_cdr) {
+			mock_cdr = cdr_copy;
+		}
+		if (cdr_prev) {
+			cdr_prev->next = cdr_copy;
+		}
+		cdr_prev = cdr_copy;
+	}
+	cdr_wrapper->cdr = mock_cdr;
+
+	AST_LIST_LOCK(&actual_cdr_entries);
+	AST_LIST_INSERT_TAIL(&actual_cdr_entries, cdr_wrapper, list);
+	AST_LIST_UNLOCK(&actual_cdr_entries);
+	return 0;
+}
+
+static void clear_mock_cdr_backend(void)
+{
+	struct test_cdr_entry *cdr_wrapper;
+
+	AST_LIST_LOCK(&actual_cdr_entries);
+	while ((cdr_wrapper = AST_LIST_REMOVE_HEAD(&actual_cdr_entries, list))) {
+		ast_cdr_free(cdr_wrapper->cdr);
+		ast_free(cdr_wrapper);
+	}
+	AST_LIST_UNLOCK(&actual_cdr_entries);
+}
+
+#define VERIFY_STRING_FIELD(field, actual, expected) do { \
+	if (strcmp((actual)->field, (expected)->field)) { \
+		ast_test_status_update(test, "Field %s failed: actual %s, expected %s\n", #field, (actual)->field, (expected)->field); \
+		ast_test_set_result(test, AST_TEST_FAIL); \
+		res = AST_TEST_FAIL; \
+	} } while (0)
+
+#define VERIFY_NUMERIC_FIELD(field, actual, expected) do { \
+	if ((actual)->field != (expected)->field) { \
+		ast_test_status_update(test, "Field %s failed: actual %ld, expected %ld\n", #field, (long)(actual)->field, (long)(expected)->field); \
+		ast_test_set_result(test, AST_TEST_FAIL); \
+		res = AST_TEST_FAIL; \
+	} } while (0)
+
+static enum ast_test_result_state verify_mock_cdr_record(struct ast_test *test, struct ast_cdr *expected, int record)
+{
+	struct ast_cdr *actual = NULL;
+	struct test_cdr_entry *cdr_wrapper;
+	int pos = 0;
+	enum ast_test_result_state res = AST_TEST_PASS;
+
+	AST_LIST_LOCK(&actual_cdr_entries);
+	AST_LIST_TRAVERSE(&actual_cdr_entries, cdr_wrapper, list) {
+		if (pos == record) {
+			actual = cdr_wrapper->cdr;
+			break;
+		}
+		pos++;
+	}
+	AST_LIST_UNLOCK(&actual_cdr_entries);
+
+	if (!actual) {
+		ast_test_status_update(test, "Unable to find actual CDR record at %d\n", record);
+		return AST_TEST_FAIL;
+	}
+
+	for (; actual && expected; actual = actual->next, expected = expected->next) {
+		VERIFY_STRING_FIELD(accountcode, actual, expected);
+		VERIFY_NUMERIC_FIELD(amaflags, actual, expected);
+		VERIFY_NUMERIC_FIELD(billsec, actual, expected);
+		VERIFY_STRING_FIELD(channel, actual, expected);
+		VERIFY_STRING_FIELD(clid, actual, expected);
+		VERIFY_STRING_FIELD(dcontext, actual, expected);
+		VERIFY_NUMERIC_FIELD(disposition, actual, expected);
+		VERIFY_STRING_FIELD(dst, actual, expected);
+		VERIFY_STRING_FIELD(dstchannel, actual, expected);
+		VERIFY_NUMERIC_FIELD(duration, actual, expected);
+		VERIFY_NUMERIC_FIELD(flags, actual, expected);
+		VERIFY_STRING_FIELD(lastapp, actual, expected);
+		VERIFY_STRING_FIELD(lastdata, actual, expected);
+		VERIFY_STRING_FIELD(linkedid, actual, expected);
+		VERIFY_STRING_FIELD(peeraccount, actual, expected);
+		VERIFY_NUMERIC_FIELD(sequence, actual, expected);
+		VERIFY_STRING_FIELD(src, actual, expected);
+		VERIFY_STRING_FIELD(uniqueid, actual, expected);
+		VERIFY_STRING_FIELD(userfield, actual, expected);
+	}
+
+	if (actual || expected) {
+		ast_test_status_update(test, "Unbalanced CDR records - %s had more than %s\n",
+			actual ? "actual" : "expected",
+			actual ? "actual" : "expected");
+		res = AST_TEST_FAIL;
+	}
+
+	return res;
+}
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+	if (!chan) {
+		return;
+	}
+	ast_channel_release(chan);
+}
+
+AST_TEST_DEFINE(test_cdr_channel_creation)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	struct ast_cdr expected = {
+		.clid = "Alice <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "",
+		.lastdata = "",
+		.duration = 0,
+		.billsec = 0,
+		.amaflags = 0,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+		.peeraccount = "",
+		.flags = 0,
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "cdr_channel_creation";
+		info->category = "/main/cdr/";
+		info->summary = "Test that a CDR is created when a channel is created";
+		info->description =
+			"Test that a CDR is created when a channel is created";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	chan = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
+	ast_copy_string(expected.uniqueid, ast_channel_uniqueid(chan), sizeof(expected.uniqueid));
+	ast_copy_string(expected.linkedid, ast_channel_linkedid(chan), sizeof(expected.linkedid));
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+
+	result = verify_mock_cdr_record(test, &expected, 0);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_inbound_call)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+
+	struct ast_cdr expected = {
+		.clid = "Alice <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "Wait",
+		.lastdata = "1",
+		.duration = 0,
+		.billsec = 0,
+		.amaflags = 0,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+		.peeraccount = "",
+		.flags = 0,
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "cdr_unanswered_inbound_call";
+		info->category = "/main/cdr/";
+		info->summary = "Test inbound unanswered calls";
+		info->description =
+			"Test the properties of a CDR 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;
+	}
+
+	chan = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
+	ast_copy_string(expected.uniqueid, ast_channel_uniqueid(chan), sizeof(expected.uniqueid));
+	ast_copy_string(expected.linkedid, ast_channel_linkedid(chan), sizeof(expected.linkedid));
+
+	snapshot = ast_channel_snapshot_create(chan);
+	ast_assert(snapshot != NULL);
+	ast_string_field_set(snapshot, appl, "Wait");
+	ast_string_field_set(snapshot, data, "1");
+
+	message = stasis_message_create(ast_channel_snapshot(), snapshot);
+	ast_assert(message != NULL);
+
+	stasis_publish(ast_channel_topic(chan), message);
+
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+
+	result = verify_mock_cdr_record(test, &expected, 0);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_unanswered_outbound_call)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+
+	struct ast_cdr expected = {
+		.clid = "",
+		.src = "",
+		.dst = "s",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "",
+		.lastdata = "",
+		.duration = 0,
+		.billsec = 0,
+		.amaflags = 0,
+		.disposition = AST_CDR_NOANSWER,
+		.accountcode = "100",
+		.peeraccount = "",
+		.flags = 0,
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "cdr_unanswered_outbound_call";
+		info->category = "/main/cdr/";
+		info->summary = "Test outbound unanswered calls";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"outbound to Asterisk but is never answered.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
+	ast_copy_string(expected.uniqueid, ast_channel_uniqueid(chan), sizeof(expected.uniqueid));
+	ast_copy_string(expected.linkedid, ast_channel_linkedid(chan), sizeof(expected.linkedid));
+
+	snapshot = ast_channel_snapshot_create(chan);
+	ast_assert(snapshot != NULL);
+	ast_string_field_set(snapshot, appl, "Dial");
+	ast_string_field_set(snapshot, data, CHANNEL_TECH_NAME "/Alice");
+
+	message = stasis_message_create(ast_channel_snapshot(), snapshot);
+	ast_assert(message != NULL);
+
+	stasis_publish(ast_channel_topic(chan), message);
+
+	ast_channel_publish_varset(chan, "DIALSTATUS", "NOANSWER");
+
+	result = verify_mock_cdr_record(test, &expected, 0);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_single_party)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
+
+	struct ast_cdr expected = {
+		.clid = "Alice <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "Wait",
+		.lastdata = "1",
+		.duration = 0,
+		.billsec = 0,
+		.amaflags = 0,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+		.peeraccount = "",
+		.flags = 0,
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "cdr_single_party";
+		info->category = "/main/cdr/";
+		info->summary = "Test cdrs for a single party";
+		info->description =
+			"Test the properties of a CDR for a call that is\n"
+			"answered, but only involves a single channel\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
+	ast_copy_string(expected.uniqueid, ast_channel_uniqueid(chan), sizeof(expected.uniqueid));
+	ast_copy_string(expected.linkedid, ast_channel_linkedid(chan), sizeof(expected.linkedid));
+
+	/* Channel enters Answer app */
+	snapshot = ast_channel_snapshot_create(chan);
+	ast_assert(snapshot != NULL);
+	ast_string_field_set(snapshot, appl, "Answer");
+	snapshot->priority = 1;
+	message = stasis_message_create(ast_channel_snapshot(), snapshot);
+	ast_assert(message != NULL);
+
+	stasis_publish(ast_channel_topic(chan), message);
+	ao2_cleanup(snapshot);
+	ao2_cleanup(message);
+
+	/* Answer the channel */
+	snapshot = ast_channel_snapshot_create(chan);
+	ast_assert(snapshot != NULL);
+	ast_string_field_set(snapshot, appl, "Answer");
+	snapshot->priority = 1;
+	snapshot->state = AST_STATE_UP;
+	message = stasis_message_create(ast_channel_snapshot(), snapshot);
+	ast_assert(message != NULL);
+
+	stasis_publish(ast_channel_topic(chan), message);
+	ao2_cleanup(snapshot);
+	ao2_cleanup(message);
+
+	/* Channel enters the Wait app */
+	snapshot = ast_channel_snapshot_create(chan);
+	ast_assert(snapshot != NULL);
+	ast_string_field_set(snapshot, appl, "Wait");
+	ast_string_field_set(snapshot, data, "1");
+	snapshot->priority = 2;
+	message = stasis_message_create(ast_channel_snapshot(), snapshot);
+	ast_assert(message != NULL);
+
+	stasis_publish(ast_channel_topic(chan), message);
+
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+
+	result = verify_mock_cdr_record(test, &expected, 0);
+
+	return result;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(test_cdr_channel_creation);
+	AST_TEST_UNREGISTER(test_cdr_unanswered_inbound_call);
+	AST_TEST_UNREGISTER(test_cdr_unanswered_outbound_call);
+	AST_TEST_UNREGISTER(test_cdr_single_party);
+
+	ast_cdr_unregister(MOCK_CDR_BACKEND);
+	ast_channel_unregister(&test_cdr_chan_tech);
+	clear_mock_cdr_backend();
+	return 0;
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(test_cdr_channel_creation);
+	AST_TEST_REGISTER(test_cdr_unanswered_inbound_call);
+	AST_TEST_REGISTER(test_cdr_unanswered_outbound_call);
+	AST_TEST_REGISTER(test_cdr_single_party);
+
+	ast_channel_register(&test_cdr_chan_tech);
+	ast_cdr_register(MOCK_CDR_BACKEND, "Mock CDR backend", mock_cdr_backend);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+

[... 14 lines stripped ...]



More information about the asterisk-commits mailing list