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

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue Apr 23 17:03:20 CDT 2013


Author: mjordan
Date: Tue Apr 23 17:03:16 2013
New Revision: 386397

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=386397
Log:
Update with unit tests for ForkCDR, NoCDR, ResetCDR and other fixes

This fixes a few other areas, including some dialing problems and some hangup
cause code issues.

Modified:
    team/mjordan/cdrs-of-doom/include/asterisk/cdr.h
    team/mjordan/cdrs-of-doom/main/asterisk.c
    team/mjordan/cdrs-of-doom/main/cdr.c
    team/mjordan/cdrs-of-doom/main/stasis_channels.c
    team/mjordan/cdrs-of-doom/tests/test_cdr.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=386397&r1=386396&r2=386397
==============================================================================
--- team/mjordan/cdrs-of-doom/include/asterisk/cdr.h (original)
+++ team/mjordan/cdrs-of-doom/include/asterisk/cdr.h Tue Apr 23 17:03:16 2013
@@ -173,6 +173,14 @@
 int ast_cdr_isset_unanswered(void);
 
 /*!
+ * \brief Return whether or not a channel has a CDR where it is the Party A
+ *
+ * \retval 0 if the channel does not have a CDR
+ * \retval 1 if the channel does have a CDR
+ */
+int ast_cdr_exists(const char *channel_name);
+
+/*!
  * \since 12
  * \brief Format a CDR variable from an already posted CDR
  *
@@ -197,7 +205,7 @@
  * \retval 0 on success
  * \retval non-zero on failure
  */
-int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length);
+int ast_cdr_getvar(const char *channel_name, const char *name, char **value, size_t length);
 
 /*!
  * \brief Set a variable on a CDR

Modified: team/mjordan/cdrs-of-doom/main/asterisk.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/asterisk.c?view=diff&rev=386397&r1=386396&r2=386397
==============================================================================
--- team/mjordan/cdrs-of-doom/main/asterisk.c (original)
+++ team/mjordan/cdrs-of-doom/main/asterisk.c Tue Apr 23 17:03:16 2013
@@ -4225,11 +4225,6 @@
 
 	ast_http_init();		/* Start the HTTP server, if needed */
 
-	if (init_manager()) {
-		printf("%s", term_quit());
-		exit(1);
-	}
-
 	if (ast_cdr_engine_init()) {
 		printf("%s", term_quit());
 		exit(1);
@@ -4273,12 +4268,17 @@
 		exit(1);
 	}
 
+	if (ast_features_init()) {
+		printf("%s", term_quit());
+		exit(1);
+	}
+
 	if (ast_bridging_init()) {
 		printf("%s", term_quit());
 		exit(1);
 	}
 
-	if (ast_features_init()) {
+	if (init_manager()) {
 		printf("%s", term_quit());
 		exit(1);
 	}

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=386397&r1=386396&r2=386397
==============================================================================
--- team/mjordan/cdrs-of-doom/main/cdr.c (original)
+++ team/mjordan/cdrs-of-doom/main/cdr.c Tue Apr 23 17:03:16 2013
@@ -482,17 +482,19 @@
 {
 	struct cdr_object *new_cdr;
 	struct cdr_object *it_cdr;
-
-	new_cdr = cdr_object_alloc(cdr->party_a.snapshot);
+	struct cdr_object *cdr_last;
+
+	cdr_last = cdr->last;
+	new_cdr = cdr_object_alloc(cdr_last->party_a.snapshot);
 	if (!new_cdr) {
 		return NULL;
 	}
 	/* Copy over the linkedid, as it may have changed */
-	ast_string_field_set(new_cdr, linkedid, cdr->linkedid);
+	ast_string_field_set(new_cdr, linkedid, cdr_last->linkedid);
 	/* Copy over other Party A information */
-	strcpy(new_cdr->party_a.userfield, cdr->party_a.userfield);
-	new_cdr->party_a.flags = cdr->party_a.flags;
-	copy_vars(&new_cdr->party_a.variables, &cdr->party_a.variables);
+	strcpy(new_cdr->party_a.userfield, cdr_last->party_a.userfield);
+	new_cdr->party_a.flags = cdr_last->party_a.flags;
+	copy_vars(&new_cdr->party_a.variables, &cdr_last->party_a.variables);
 
 	/* Append the CDR to the end of the list */
 	for (it_cdr = cdr; it_cdr->next; it_cdr = it_cdr->next) {
@@ -563,9 +565,9 @@
 		ast_copy_string(cdr_copy->lastdata, party_a->data, sizeof(cdr_copy->lastdata));
 		ast_copy_string(cdr_copy->dst, party_a->exten, sizeof(cdr_copy->dst));
 		ast_copy_string(cdr_copy->dcontext, party_a->context, sizeof(cdr_copy->dcontext));
-		ast_copy_string(cdr_copy->userfield, party_a->userfield, sizeof(cdr_copy->userfield));
+		ast_copy_string(cdr_copy->userfield, cdr->party_a.userfield, sizeof(cdr_copy->userfield));
 		/* XXX TODO: this should just happen automatically */
-		cdr_copy->flags = party_a->flags.flags;
+		ast_copy_flags(cdr_copy, &cdr->flags, AST_FLAGS_ALL);
 
 		/* Party B */
 		if (party_b) {
@@ -646,6 +648,7 @@
 	case AST_CAUSE_UNREGISTERED:
 		cdr->disposition = AST_CDR_FAILED;
 		break;
+	case AST_CAUSE_NORMAL_CLEARING:
 	case AST_CAUSE_NO_ANSWER:
 		cdr->disposition = AST_CDR_NOANSWER;
 		break;
@@ -664,7 +667,7 @@
 	cdr->end = ast_tvnow();
 
 	if (cdr->disposition == AST_CDR_NULL) {
-		if (!ast_tvzero(cdr->answer) && cdr->disposition == AST_CDR_NOANSWER) {
+		if (!ast_tvzero(cdr->answer)) {
 			cdr->disposition = AST_CDR_ANSWERED;
 		} else if (cdr->party_a.snapshot->hangupcause) {
 			cdr_object_set_disposition(cdr, cdr->party_a.snapshot->hangupcause);
@@ -857,7 +860,7 @@
 		return 0;
 	} else if (!strcmp(dial_status, "BUSY")) {
 		cdr->disposition = AST_CDR_BUSY;
-	} else if (!strcmp(dial_status, "CANCEL")) {
+	} else if (!strcmp(dial_status, "CANCEL") || !strcmp(dial_status, "NOANSWER")) {
 		cdr->disposition = AST_CDR_NOANSWER;
 	} else if (!strcmp(dial_status, "CONGESTION")) {
 		cdr->disposition = AST_CDR_CONGESTION;
@@ -929,7 +932,7 @@
 	}
 
 	dial_status_blob = ast_json_object_get(ast_multi_channel_blob_get_json(payload), "dialstatus");
-	if (!dial_status_blob) {
+	if (dial_status_blob) {
 		dial_status = ast_json_string_get(dial_status_blob);
 	}
 
@@ -1053,6 +1056,7 @@
 		cdr = ao2_find(active_cdrs_by_channel, name, OBJ_KEY);
 		if (cdr) {
 			SCOPED_AO2LOCK(lock, cdr);
+
 			if (new_snapshot) {
 				for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
 					if (it_cdr->fn_table->process_party_a_update) {
@@ -1061,7 +1065,9 @@
 					}
 				}
 			} else {
-				cdr_object_finalize(cdr);
+				for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+					cdr_object_finalize(it_cdr);
+				}
 				cdr_object_dispatch(cdr);
 				ao2_unlink(active_cdrs_by_channel, cdr);
 			}
@@ -1172,6 +1178,15 @@
 {
 	RAII_VAR(struct module_config *, mod_cfg, ao2_global_obj_ref(module_configs), ao2_cleanup);
 	return ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED);
+}
+
+int ast_cdr_exists(const char *channel_name)
+{
+	RAII_VAR(struct cdr_object *, cdr,
+			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
+			ao2_cleanup);
+
+	return (cdr != NULL);
 }
 
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
@@ -1327,10 +1342,11 @@
 	struct ast_var_t *newvariable;
 	struct varshead *headp;
 
-	if (cdr->fn_table == &hangup_state_fn_table) {
+	if (cdr->fn_table == &finalized_state_fn_table) {
 		return;
 	}
 
+	/* If we're Party B, update its variables. Otherwise, we must be Party A */
 	if (!ast_strlen_zero(channel_name) && cdr->party_b.snapshot
 			&& !strcmp(cdr->party_b.snapshot->name, channel_name)) {
 		headp = &cdr->party_b.variables;
@@ -1361,6 +1377,7 @@
 int ast_cdr_setvar(const char *channel_name, const char *name, const char *value)
 {
 	struct cdr_object *cdr;
+	struct cdr_object *it_cdr;
 	struct ao2_iterator *it_cdrs;
 	char *arg = ast_strdupa(channel_name);
 	int x;
@@ -1380,8 +1397,12 @@
 
 	while ((cdr = ao2_iterator_next(it_cdrs))) {
 		ao2_lock(cdr);
-		for (; cdr->next; cdr = cdr->next) { }
-		cdr_object_setvar(cdr, channel_name, name, value);
+		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+			if (it_cdr->fn_table == &finalized_state_fn_table) {
+				continue;
+			}
+			cdr_object_setvar(it_cdr, channel_name, name, value);
+		}
 		ao2_unlock(cdr);
 		ao2_ref(cdr, -1);
 	}
@@ -1390,75 +1411,75 @@
 	return 0;
 }
 
-static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char *value, size_t length)
+static void cdr_object_format_var_internal(struct cdr_object *cdr, const char *name, char **value, size_t length)
 {
 	struct ast_var_t *variable;
 
 	AST_LIST_TRAVERSE(&cdr->party_a.variables, variable, entries) {
 		if (!strcasecmp(name, ast_var_name(variable))) {
-			ast_copy_string(value, ast_var_value(variable), length);
+			ast_copy_string(*value, ast_var_value(variable), length);
 			return;
 		}
 	}
 
-	*value = '\0';
-}
-
-static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char *value, size_t length)
+	**value = '\0';
+}
+
+static int cdr_object_format_property(struct cdr_object *cdr_obj, const char *name, char **value, size_t length)
 {
 	struct ast_channel_snapshot *party_a = cdr_obj->party_a.snapshot;
 	struct ast_channel_snapshot *party_b = cdr_obj->party_b.snapshot;
 
 	if (!strcasecmp(name, "clid")) {
-		ast_callerid_merge(value, length, party_a->caller_name, party_a->caller_number, "");
+		ast_callerid_merge(*value, length, party_a->caller_name, party_a->caller_number, "");
 	} else if (!strcasecmp(name, "src")) {
-		ast_copy_string(value, party_a->caller_number, length);
+		ast_copy_string(*value, party_a->caller_number, length);
 	} else if (!strcasecmp(name, "dst")) {
-		ast_copy_string(value, party_a->exten, length);
+		ast_copy_string(*value, party_a->exten, length);
 	} else if (!strcasecmp(name, "dcontext")) {
-		ast_copy_string(value, party_a->context, length);
+		ast_copy_string(*value, party_a->context, length);
 	} else if (!strcasecmp(name, "channel")) {
-		ast_copy_string(value, party_a->name, length);
+		ast_copy_string(*value, party_a->name, length);
 	} else if (!strcasecmp(name, "dstchannel")) {
 		if (party_b) {
-			ast_copy_string(value, party_b->name, length);
+			ast_copy_string(*value, party_b->name, length);
 		} else {
-			ast_copy_string(value, "", length);
+			ast_copy_string(*value, "", length);
 		}
 	} else if (!strcasecmp(name, "lastapp")) {
-		ast_copy_string(value, party_a->appl, length);
+		ast_copy_string(*value, party_a->appl, length);
 	} else if (!strcasecmp(name, "lastdata")) {
-		ast_copy_string(value, party_a->data, length);
+		ast_copy_string(*value, party_a->data, length);
 	} else if (!strcasecmp(name, "start")) {
-		cdr_get_tv(cdr_obj->start, NULL, value, length);
+		cdr_get_tv(cdr_obj->start, NULL, *value, length);
 	} else if (!strcasecmp(name, "answer")) {
-		cdr_get_tv(cdr_obj->answer, NULL, value, length);
+		cdr_get_tv(cdr_obj->answer, NULL, *value, length);
 	} else if (!strcasecmp(name, "end")) {
-		cdr_get_tv(cdr_obj->end, NULL, value, length);
+		cdr_get_tv(cdr_obj->end, NULL, *value, length);
 	} else if (!strcasecmp(name, "duration")) {
-		snprintf(value, length, "%ld", cdr_object_get_duration(cdr_obj));
+		snprintf(*value, length, "%ld", cdr_object_get_duration(cdr_obj));
 	} else if (!strcasecmp(name, "billsec")) {
-		snprintf(value, length, "%ld", cdr_object_get_billsec(cdr_obj));
+		snprintf(*value, length, "%ld", cdr_object_get_billsec(cdr_obj));
 	} else if (!strcasecmp(name, "disposition")) {
-		snprintf(value, length, "%d", cdr_obj->disposition);
+		snprintf(*value, length, "%d", cdr_obj->disposition);
 	} else if (!strcasecmp(name, "amaflags")) {
-		snprintf(value, length, "%d", party_a->amaflags);
+		snprintf(*value, length, "%d", party_a->amaflags);
 	} else if (!strcasecmp(name, "accountcode")) {
-		ast_copy_string(value, party_a->accountcode, length);
+		ast_copy_string(*value, party_a->accountcode, length);
 	} else if (!strcasecmp(name, "peeraccount")) {
 		if (party_b) {
-			ast_copy_string(value, party_b->accountcode, length);
+			ast_copy_string(*value, party_b->accountcode, length);
 		} else {
-			ast_copy_string(value, "", length);
+			ast_copy_string(*value, "", length);
 		}
 	} else if (!strcasecmp(name, "uniqueid")) {
-		ast_copy_string(value, party_a->uniqueid, length);
+		ast_copy_string(*value, party_a->uniqueid, length);
 	} else if (!strcasecmp(name, "linkedid")) {
-		ast_copy_string(value, cdr_obj->linkedid, length);
+		ast_copy_string(*value, cdr_obj->linkedid, length);
 	} else if (!strcasecmp(name, "userfield")) {
-		ast_copy_string(value, party_a->userfield, length);
+		ast_copy_string(*value, cdr_obj->party_a.userfield, length);
 	} else if (!strcasecmp(name, "sequence")) {
-		snprintf(value, length, "%d", cdr_obj->sequence);
+		snprintf(*value, length, "%d", cdr_obj->sequence);
 	} else {
 		return 1;
 	}
@@ -1466,7 +1487,7 @@
 	return 0;
 }
 
-int ast_cdr_getvar(const char *channel_name, const char *name, char *value, size_t length)
+int ast_cdr_getvar(const char *channel_name, const char *name, char **value, size_t length)
 {
 	RAII_VAR(struct cdr_object *, cdr,
 		ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
@@ -1503,8 +1524,12 @@
 	struct cdr_object *it_cdr;
 	struct ast_var_t *variable;
 	const char *var;
-	char workspace[256];
+	RAII_VAR(char *, workspace, ast_malloc(256), ast_free);
 	int total = 0, x = 0, i;
+
+	if (!workspace) {
+		return 1;
+	}
 
 	if (!cdr) {
 		ast_debug(3, "Unable to find CDR for channel %s\n", channel_name);
@@ -1534,7 +1559,7 @@
 		for (i = 0; cdr_readonly_vars[i]; i++) {
 			/* null out the workspace, because the cdr_get_tv() won't write anything if time is NULL, so you get old vals */
 			workspace[0] = 0;
-			cdr_object_format_property(it_cdr, cdr_readonly_vars[i], workspace, sizeof(workspace));
+			cdr_object_format_property(it_cdr, cdr_readonly_vars[i], &workspace, sizeof(workspace));
 
 			if (!ast_strlen_zero(workspace)
 				&& ast_str_append(buf, 0, "level %d: %s%c%s%c", x, cdr_readonly_vars[i], delim, workspace, sep) < 0) {
@@ -1635,6 +1660,9 @@
 	struct party_b_userfield_update *info = arg;
 	struct cdr_object *it_cdr;
 	for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+		if (it_cdr->fn_table == &finalized_state_fn_table) {
+			continue;
+		}
 		if (it_cdr->party_b.snapshot
 			&& !strcmp(it_cdr->party_b.snapshot->name, info->channel_name)) {
 			strcpy(it_cdr->party_b.userfield, info->userfield);
@@ -1652,20 +1680,25 @@
 			.channel_name = channel_name,
 			.userfield = userfield,
 	};
-
+	struct cdr_object *it_cdr;
 
 	/* Handle Party A */
 	if (cdr) {
-		SCOPED_AO2LOCK(lock, cdr);
-		for (; cdr; cdr = cdr->next) {
-			strcpy(cdr->party_a.userfield, userfield);
-		}
+		ao2_lock(cdr);
+		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
+			if (it_cdr->fn_table == &finalized_state_fn_table) {
+				continue;
+			}
+			strcpy(it_cdr->party_a.userfield, userfield);
+		}
+		ao2_unlock(cdr);
 	}
 
 	/* Handle Party B */
 	ao2_callback(active_cdrs_by_channel, OBJ_NODATA,
 			cdr_object_update_party_b_userfield,
 			&party_b_info);
+
 }
 
 static void post_cdr(struct ast_cdr *cdr)
@@ -1782,6 +1815,7 @@
 			ao2_find(active_cdrs_by_channel, channel_name, OBJ_KEY),
 			ao2_cleanup);
 	struct cdr_object *new_cdr;
+	struct cdr_object *it_cdr;
 	struct cdr_object *cdr_obj;
 
 	if (!cdr) {
@@ -1801,7 +1835,7 @@
 		/* Copy over the basic CDR information. The Party A information is
 		 * copied over automatically as part of the append
 		 */
-		new_cdr = cdr_object_create_and_append_cdr(cdr_obj);
+		new_cdr = cdr_object_create_and_append_cdr(cdr);
 		if (!new_cdr) {
 			return -1;
 		}
@@ -1824,9 +1858,6 @@
 		new_cdr->answer = cdr_obj->answer;
 
 		/* Modify the times based on the flags passed in */
-		if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) {
-			cdr_object_transition_state(cdr_obj, &finalized_state_fn_table);
-		}
 		if (ast_test_flag(options, AST_CDR_FLAG_SET_ANSWER)
 				&& new_cdr->party_a.snapshot->state == AST_STATE_UP) {
 			new_cdr->answer = ast_tvnow();
@@ -1835,9 +1866,20 @@
 			new_cdr->answer = ast_tvnow();
 			new_cdr->start = ast_tvnow();
 		}
+
 		/* Create and append, by default, copies over the variables */
 		if (!ast_test_flag(options, AST_CDR_FLAG_KEEP_VARS)) {
 			cdr_free_vars(&new_cdr->party_a.variables);
+		}
+
+		/* Finalize any current CDRs */
+		if (ast_test_flag(options, AST_CDR_FLAG_FINALIZE)) {
+			for (it_cdr = cdr; it_cdr != new_cdr; it_cdr = it_cdr->next) {
+				if (it_cdr->fn_table == &finalized_state_fn_table) {
+					continue;
+				}
+				cdr_object_transition_state(it_cdr, &finalized_state_fn_table);
+			}
 		}
 	}
 
@@ -1956,7 +1998,6 @@
 	/* maybe they disabled CDR stuff completely, so just drop it */
 	if (!ast_test_flag(&mod_cfg->general->settings, CDR_ENABLED)) {
 		ast_debug(1, "Dropping CDR !\n");
-		ast_set_flag(cdr, AST_CDR_FLAG_DISABLE);
 		ast_cdr_free(cdr);
 		return;
 	}

Modified: team/mjordan/cdrs-of-doom/main/stasis_channels.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/cdrs-of-doom/main/stasis_channels.c?view=diff&rev=386397&r1=386396&r2=386397
==============================================================================
--- team/mjordan/cdrs-of-doom/main/stasis_channels.c (original)
+++ team/mjordan/cdrs-of-doom/main/stasis_channels.c Tue Apr 23 17:03:16 2013
@@ -445,6 +445,7 @@
 
 	message = stasis_message_create(ast_channel_snapshot_type(), snapshot);
 	if (!message) {
+		ast_log(LOG_ERROR, "FARK ME\n");
 		return;
 	}
 

Modified: 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=diff&rev=386397&r1=386396&r2=386397
==============================================================================
--- team/mjordan/cdrs-of-doom/tests/test_cdr.c (original)
+++ team/mjordan/cdrs-of-doom/tests/test_cdr.c Tue Apr 23 17:03:16 2013
@@ -33,6 +33,7 @@
 
 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
+#include <math.h>
 #include "asterisk/module.h"
 #include "asterisk/test.h"
 #include "asterisk/cdr.h"
@@ -43,6 +44,8 @@
 #include "asterisk/time.h"
 #include "asterisk/stasis_channels.h"
 
+#define EPSILON 0.001
+
 #define TEST_CATEGORY "/main/cdr/"
 
 #define MOCK_CDR_BACKEND "mock_cdr_backend"
@@ -51,10 +54,20 @@
 
 /*! \brief A placeholder for Asterisk's 'real' CDR configuration */
 static struct ast_cdr_config *saved_config;
+
+/*! \brief A configuration suitable for 'normal' CDRs */
+static struct ast_cdr_config debug_cdr_config = {
+	.settings.flags = CDR_ENABLED | CDR_DEBUG,
+};
 
 /*! \brief A configuration suitable for CDRs with unanswered records */
 static struct ast_cdr_config unanswered_cdr_config = {
 	.settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG,
+};
+
+/*! \brief A configuration suitable for CDRs with congestion enabled */
+static struct ast_cdr_config congestion_cdr_config = {
+	.settings.flags = CDR_ENABLED | CDR_UNANSWERED | CDR_DEBUG | CDR_CONGESTION,
 };
 
 #define SWAP_CONFIG(ao2_config, template) do { \
@@ -210,7 +223,8 @@
 		if (expected->billsec) {
 			VERIFY_TIME_VALUE(answer, actual);
 		}
-
+		ast_test_debug(test, "Finished expected record %s, %s\n",
+				expected->channel, S_OR(expected->dstchannel, "<none>"));
 		expected = expected->next;
 		++count;
 	}
@@ -235,16 +249,6 @@
 	message = stasis_message_create(ast_channel_snapshot_type(), snapshot);
 	ast_assert(message != NULL);
 
-	stasis_publish(ast_channel_topic(chan), message);
-}
-
-static void publish_channel_cache_clear(struct ast_channel *chan)
-{
-	RAII_VAR(struct ast_channel_snapshot *, snapshot, ast_channel_snapshot_create(chan), ao2_cleanup);
-	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-
-	ast_assert(snapshot != NULL);
-	message = stasis_cache_clear_create(ast_channel_snapshot_type(), ast_channel_uniqueid(chan));
 	stasis_publish(ast_channel_topic(chan), message);
 }
 
@@ -408,7 +412,10 @@
 	publish_channel_snapshot(chan);
 
 	/* Clear from cache */
-	publish_channel_cache_clear(chan);
+	ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
 
 	result = verify_mock_cdr_record(test, &expected, 1);
 
@@ -677,7 +684,7 @@
 		break;
 	}
 
-	SWAP_CONFIG(config, unanswered_cdr_config);
+	SWAP_CONFIG(config, congestion_cdr_config);
 
 	chan_caller = 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_caller), sizeof(expected.uniqueid));
@@ -955,7 +962,7 @@
 		break;
 	}
 
-	SWAP_CONFIG(config, unanswered_cdr_config);
+	SWAP_CONFIG(config, congestion_cdr_config);
 
 	chan_caller = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
 	ast_copy_string(bob_expected.uniqueid, ast_channel_uniqueid(chan_caller), sizeof(bob_expected.uniqueid));
@@ -1030,36 +1037,78 @@
 	return result;
 }
 
-AST_TEST_DEFINE(test_cdr_single_fields)
-{
-	RAII_VAR(struct ast_channel *, chan_caller, NULL, safe_channel_release);
+AST_TEST_DEFINE(test_cdr_fields)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
 	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
 			ao2_cleanup);
-
-	struct ast_cdr expected = {
-		.clid = "\"Alice\" <100>",
-		.src = "100",
-		.dst = "100",
-		.dcontext = "default",
-		.channel = CHANNEL_TECH_NAME "/Alice",
-		.dstchannel = CHANNEL_TECH_NAME "/Bob",
-		.lastapp = "Dial",
-		.lastdata = CHANNEL_TECH_NAME "/Bob&" CHANNEL_TECH_NAME "/Charlie&" CHANNEL_TECH_NAME "/David",
+	RAII_VAR(char *, varbuffer, ast_malloc(128), ast_free);
+	int int_buffer;
+	double db_buffer;
+	struct timespec to_sleep = {2, 0};
+	struct ast_flags fork_options = { 0, };
+
+	struct ast_cdr original = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "Wait",
+		.lastdata = "10",
 		.billsec = 0,
-		.amaflags = AST_AMA_DOCUMENTATION,
-		.disposition = AST_CDR_NOANSWER,
-		.accountcode = "100",
-		.peeraccount = "200",
+		.amaflags = AST_AMA_OMIT,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "XXX",
+		.peeraccount = "",
+		.userfield = "yackity",
+	};
+	struct ast_cdr fork_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "Wait",
+		.lastdata = "10",
+		.billsec = 0,
+		.amaflags = AST_AMA_OMIT,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "XXX",
+		.peeraccount = "",
+		.userfield = "yackity",
+	};
+	struct ast_cdr fork_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "Answer",
+		.lastdata = "",
+		.billsec = 0,
+		.amaflags = AST_AMA_OMIT,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "ZZZ",
+		.peeraccount = "",
+		.userfield = "schmackity",
 	};
 	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	struct ast_cdr *expected = &original;
+	original.next = &fork_expected_one;
+	fork_expected_one.next = &fork_expected_two;
 
 	switch (cmd) {
 	case TEST_INIT:
 		info->name = __func__;
 		info->category = TEST_CATEGORY;
-		info->summary = "Test field access on a single CDR record";
+		info->summary = "Test field access CDRs";
 		info->description =
-			"This tests setting/retrieving data on a single CDR.\n";
+			"This tests setting/retrieving data on CDR records.\n";
 		return AST_TEST_NOT_RUN;
 	case TEST_EXECUTE:
 		break;
@@ -1067,18 +1116,348 @@
 
 	SWAP_CONFIG(config, unanswered_cdr_config);
 
-	chan_caller = 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_caller), sizeof(expected.uniqueid));
-	ast_copy_string(expected.linkedid, ast_channel_linkedid(chan_caller), sizeof(expected.linkedid));
-
-
-	result = verify_mock_cdr_record(test, &expected, 3);
+	chan = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
+	ast_copy_string(original.uniqueid, ast_channel_uniqueid(chan), sizeof(original.uniqueid));
+	ast_copy_string(original.linkedid, ast_channel_linkedid(chan), sizeof(original.linkedid));
+	ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+	ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+	ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+	ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+	/* Channel enters Wait app */
+	ast_channel_appl_set(chan, "Wait");
+	ast_channel_data_set(chan, "10");
+	ast_channel_priority_set(chan, 1);
+	publish_channel_snapshot(chan);
+
+	/* Set properties on the channel that propagate to the CDR */
+	ast_channel_amaflags_set(chan, AST_AMA_OMIT);
+	ast_channel_accountcode_set(chan, "XXX");
+
+	/* Wait one second so we get a duration. Note that sleep/usleep can get
+	 * a signal, so we use nanosleep here */
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	if (!ast_cdr_exists(ast_channel_name(chan))) {
+		ast_test_status_update(test, "Failed to find CDR for channel %s\n", ast_channel_name(chan));
+		return AST_TEST_FAIL;
+	}
+
+	ast_cdr_setuserfield(ast_channel_name(chan), "foobar");
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+
+	/* Verify that we can't set read-only fields or other fields directly */
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "clid", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "src", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dst", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dcontext", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "channel", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "dstchannel", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastapp", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "lastdata", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "start", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "answer", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "end", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "duration", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "billsec", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "disposition", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "amaflags", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "accountcode", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "uniqueid", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "linkedid", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "userfield", "junk") != 0);
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "sequence", "junk") != 0);
+
+	/* Verify the values */
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "userfield", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "foobar") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "amaflags", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%d", &int_buffer);
+	ast_test_validate(test, int_buffer == AST_AMA_OMIT);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "accountcode", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "XXX") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "clid", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "\"Alice\" <100>") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "src", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dst", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "100") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dcontext", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "default") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "channel", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, CHANNEL_TECH_NAME "/Alice") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "dstchannel", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastapp", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "Wait") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "lastdata", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "10") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) > 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) < EPSILON);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "end", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) < EPSILON);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "duration", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) > 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "billsec", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%lf", &db_buffer);
+	ast_test_validate(test, fabs(db_buffer) < EPSILON);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "disposition", &varbuffer, 128) == 0);
+	sscanf(varbuffer, "%d", &int_buffer);
+	ast_test_validate(test, int_buffer == AST_CDR_NULL);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "uniqueid", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, ast_channel_uniqueid(chan)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "linkedid", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, ast_channel_linkedid(chan)) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "sequence", &varbuffer, 128) == 0);
+
+	/* Fork the CDR, and check that we change the properties on both CDRs. */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Change some properties */
+	ast_cdr_setuserfield(ast_channel_name(chan), "yackity");
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1b") == 0);
+
+	/* Fork the CDR again, finalizing all current CDRs */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS | AST_CDR_FLAG_FINALIZE);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Channel enters Answer app */
+	ast_channel_appl_set(chan, "Answer");
+	ast_channel_data_set(chan, "");
+	ast_channel_priority_set(chan, 1);
+	publish_channel_snapshot(chan);
+	ast_setstate(chan, AST_STATE_UP);
+
+	/* Set properties on the last record */
+	ast_channel_accountcode_set(chan, "ZZZ");
+	ast_cdr_setuserfield(ast_channel_name(chan), "schmackity");
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+
+	/* Hang up and verify */
+	ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+	result = verify_mock_cdr_record(test, expected, 3);
 
 	return result;
 }
 
-
-
+AST_TEST_DEFINE(test_cdr_no_reset_cdr)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	struct ast_flags fork_options = { 0, };
+	struct timespec to_sleep = {1, 0};
+
+	struct ast_cdr expected = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.dstchannel = "",
+		.lastapp = "",
+		.lastdata = "",
+		.billsec = 0,
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_FAILED,
+		.accountcode = "100",
+		.peeraccount = "",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test field access CDRs";
+		info->description =
+			"This tests setting/retrieving data on CDR records.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, unanswered_cdr_config);
+
+	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));
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	if (!ast_cdr_exists(ast_channel_name(chan))) {
+		ast_test_status_update(test, "Unable to obtain CDR for channel\n");
+		return AST_TEST_FAIL;
+	}
+
+	/* Disable the CDR */
+	ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+	/* Fork the CDR. This should be enabled */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Disable and enable the forked CDR */
+	ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+	ast_test_validate(test, ast_cdr_clear_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE) == 0);
+
+	/* Fork and finalize again. This CDR should be propagated */
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Disable all future CDRs */
+	ast_test_validate(test, ast_cdr_set_property(ast_channel_name(chan), AST_CDR_FLAG_DISABLE_ALL) == 0);
+
+	/* Fork a few more */
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL);
+	if (!ast_hangup(chan)) {
+		chan = NULL;
+	}
+	result = verify_mock_cdr_record(test, &expected, 1);
+
+	return result;
+}
+
+AST_TEST_DEFINE(test_cdr_fork_cdr)
+{
+	RAII_VAR(struct ast_channel *, chan, NULL, safe_channel_release);
+	RAII_VAR(struct ast_cdr_config *, config, ao2_alloc(sizeof(*config), NULL),
+			ao2_cleanup);
+	RAII_VAR(char *, varbuffer, ast_malloc(128), ast_free);
+	RAII_VAR(char *, fork_varbuffer, ast_malloc(128), ast_free);
+	RAII_VAR(char *, answer_time, ast_malloc(128), ast_free);
+	RAII_VAR(char *, fork_answer_time, ast_malloc(128), ast_free);
+	RAII_VAR(char *, start_time, ast_malloc(128), ast_free);
+	RAII_VAR(char *, fork_start_time, ast_malloc(128), ast_free);
+	struct ast_flags fork_options = { 0, };
+	struct timespec to_sleep = {1, 10000};
+
+	struct ast_cdr original = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	struct ast_cdr fork_expected_one = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	struct ast_cdr fork_expected_two = {
+		.clid = "\"Alice\" <100>",
+		.src = "100",
+		.dst = "100",
+		.dcontext = "default",
+		.channel = CHANNEL_TECH_NAME "/Alice",
+		.amaflags = AST_AMA_DOCUMENTATION,
+		.disposition = AST_CDR_ANSWERED,
+		.accountcode = "100",
+	};
+	enum ast_test_result_state result = AST_TEST_NOT_RUN;
+	struct ast_cdr *expected = &original;
+	original.next = &fork_expected_one;
+	fork_expected_one.next = &fork_expected_two;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->category = TEST_CATEGORY;
+		info->summary = "Test field access CDRs";
+		info->description =
+			"This tests setting/retrieving data on CDR records.\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	SWAP_CONFIG(config, debug_cdr_config);
+
+	chan = ast_channel_alloc(0, AST_STATE_DOWN, "100", "Alice", "100", "100", "default", NULL, 0, CHANNEL_TECH_NAME "/Alice");
+	ast_copy_string(original.uniqueid, ast_channel_uniqueid(chan), sizeof(original.uniqueid));
+	ast_copy_string(original.linkedid, ast_channel_linkedid(chan), sizeof(original.linkedid));
+	ast_copy_string(fork_expected_one.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_one.uniqueid));
+	ast_copy_string(fork_expected_one.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_one.linkedid));
+	ast_copy_string(fork_expected_two.uniqueid, ast_channel_uniqueid(chan), sizeof(fork_expected_two.uniqueid));
+	ast_copy_string(fork_expected_two.linkedid, ast_channel_linkedid(chan), sizeof(fork_expected_two.linkedid));
+
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR) && (!ast_cdr_exists(ast_channel_name(chan))));
+	if (!ast_cdr_exists(ast_channel_name(chan))) {
+		ast_test_status_update(test, "Unable to obtain CDR for channel\n");
+		return AST_TEST_FAIL;
+	}
+
+	/* Test blowing away variables */
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_1") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_1") == 0);
+	ast_copy_string(varbuffer, "", 128);
+
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", &fork_varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_1") != 0);
+
+	/* Test finalizing previous CDRs */
+	ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+
+	/* Test keep variables; setting a new answer time */
+	ast_setstate(chan, AST_STATE_UP);
+	while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));
+	ast_test_validate(test, ast_cdr_setvar(ast_channel_name(chan), "test_variable", "record_2") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "test_variable", &varbuffer, 128) == 0);
+	ast_test_validate(test, strcmp(varbuffer, "record_2") == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", &answer_time, 128) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", &start_time, 128) == 0);
+
+	ast_set_flag(&fork_options, AST_CDR_FLAG_FINALIZE);
+	ast_set_flag(&fork_options, AST_CDR_FLAG_KEEP_VARS);
+	ast_set_flag(&fork_options, AST_CDR_FLAG_SET_ANSWER);
+	ast_test_validate(test, ast_cdr_fork(ast_channel_name(chan), &fork_options) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "answer", &fork_answer_time, 128) == 0);
+	ast_test_validate(test, ast_cdr_getvar(ast_channel_name(chan), "start", &fork_start_time, 128) == 0);

[... 64 lines stripped ...]



More information about the asterisk-commits mailing list