Index: channels/chan_sip.c
===================================================================
--- channels/chan_sip.c	(.../trunk)	(revision 7708)
+++ channels/chan_sip.c	(.../team/russell/codetest)	(revision 7708)
@@ -86,6 +86,7 @@
 #include "asterisk/dnsmgr.h"
 #include "asterisk/devicestate.h"
 #include "asterisk/linkedlists.h"
+#include "asterisk/codetest.h"
 
 #ifdef OSP_SUPPORT
 #include "asterisk/astosp.h"
@@ -933,6 +934,15 @@
 int find_sip_method(char *msg);
 unsigned int parse_sip_options(struct sip_pvt *pvt, char *supported);
 
+#ifdef ENABLE_CODE_TEST
+static int test_tags_func(int fd, int argc, char *argv[]);
+static int test_callid_uniqueness_func(void);
+static struct sip_request *create_sip_message(int number);
+#endif
+
+AST_DEFINE_CODE_TEST(test_tags, "tags", test_tags_func);
+AST_DEFINE_CODE_TEST(test_callid_uniqueness, "callid_uniqueness", test_callid_uniqueness_func);
+
 /*! \brief Definition of this channel for PBX channel registration */
 static const struct ast_channel_tech sip_tech = {
 	.type = channeltype,
@@ -12977,6 +12987,159 @@
 	);
 }
 
+#ifdef ENABLE_CODE_TEST
+/* Create SIP messages for testing. Number indicates which test packet */
+static struct sip_request *create_sip_message(int number)
+{
+	struct sip_request *req;
+	char *demo_packet[3];
+	
+	/* 1. OPTIONS packet */
+	demo_packet[0] = 
+		"OPTIONS sip:3000@192.168.236.49 SIP/2.0\n"
+		"Via: SIP/2.0/UDP 192.168.11.11:5060;branch=z9hG4bK193148bb\n"
+		"From: \"asterisk\" <sip:asterisk@192.168.94.55>;tag=as2b0e6ece\n"
+		"To: <sip:3000@192.168.236.49>\n"
+		"Contact: <sip:asterisk@194.168.94.55>\n"
+		"Call-ID: 3f66e2987339aee36b54cc2f5e371b46@194.168.94.55\n"
+		"CSeq: 102 OPTIONS\n"
+		"User-Agent: Asterisk 1.0\n"
+		"Date: Fri, 23 Dec 2005 05:19:48 GMT\n"
+		"Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER\n"
+		"Content-Length: 0\n";
+
+	/* 2. Asterisk reply to options (without SDP) , fromtag and totag*/
+	demo_packet[1] = 
+		"SIP/2.0 200 OK\n"
+		"Via: SIP/2.0/UDP 192.168.94.55:5060;branch=z9hG4bK193148bb;received=194.168.94.55\n"
+		"From: \"asterisk\" <sip:asterisk@194.168.94.55>;tag=as2b0e6ece\n"
+		"To: <sip:3000@192.168.236.49>;tag=as220106ec\n"
+		"Call-ID: 3f66e2987339aee36b54cc2f5e371b46@192.168.94.55\n"
+		"CSeq: 102 OPTIONS\n"
+		"User-Agent: Asterisk 1.2\n"
+		"Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY\n"
+		"Max-Forwards: 70\n"
+		"Contact: <sip:192.168.236.49>\n"
+		"Accept: application/sdp\n"
+		"Content-Length: 0\n";
+
+	/* 3. Registration request without auth and no white space (WiSIP phone) */
+	demo_packet[2] = 
+		"REGISTER sip:jarl.digium.com:5060 SIP/2.0\n"
+		"Via:SIP/2.0/UDP 192.168.123.149:5060;branch=z9hG4bk723df946284bd7\n"
+		"From:<sip:3300@jarl.digium.com;user=phone>;tag=B5913DFE66749B9A26B\n"
+		"To:<sip:3300@jarl.digium.com;user=phone>\n"
+		"Call-ID:8870-D1B9-8CA6-CD71-6061F4366992@192.168.123.149\n"
+		"CSeq:86034 REGISTER\n"
+		"User-Agent: WiSIP\n"
+		"Contact: <sip:3300@192.168.123.149:5060;transport=udp>\n"
+		"Expires: 120\n"
+		"Content-Length: 0\n";
+
+	req = malloc(sizeof(struct sip_request));
+	if (!req) {
+		ast_log(LOG_DEBUG, "Memory allocation error.\n");
+		return NULL;
+	}
+	memset(req, 0, sizeof(struct sip_request));
+	/* Copy packet into packet if you know what I mean */
+	ast_copy_string(req->data, demo_packet[number], sizeof(req->data));
+	req->len = sizeof(demo_packet[number]);
+
+	/* Fix multiline headers - only done in pedantic mode in chan_sip (sipsock_read) */
+	req->len = lws2sws(req->data, req->len);
+	
+	/* Parse packet */
+	parse_request(req);
+	/* Find method */
+	req->method = find_sip_method(req->rlPart1);
+
+	/* We're done, have a full packet, parsed and ready */
+	return req;
+}
+#endif
+
+#ifdef ENABLE_CODE_TEST
+/*! \brief Test tag retrieval code */
+static int test_tags_func(int fd, int argc, char *argv[])
+{
+	struct sip_request *req;
+	char buf[BUFSIZ];
+	char *result;
+	int error = 0;
+
+	/* Test first packet, initial OPTIONS request with from tag and no to tag  */
+	req = create_sip_message(0);
+	result = gettag(req, "To", buf, sizeof(buf));
+	if (option_debug > 3)
+		ast_log(LOG_DEBUG, "Test-tags: To-tag = '%s'\n", result ? result : "<none>");
+	if (result) {
+		error = 1;
+		ast_log(LOG_ERROR, "Wrong to tag in packet one, parse error.");
+	}
+	result = gettag(req, "From", buf, sizeof(buf));
+	if (option_debug > 3)
+		ast_log(LOG_DEBUG, "Test-tags: From-tag = '%s'\n", result ? result : "<none>");
+	if (!result || strcmp(result, "as2b0e6ece")) {
+		error = 1;
+		ast_log(LOG_ERROR, "Wrong From tag in packet one, parse error. Tag = %s\n", result ? result : "<none>");
+	}
+	free(req);
+
+	/* More tests of other packets */
+	/* We need packets with bad syntax and weird syntaxes, examples */
+	/* And packets with UTF8 strings in strange places */
+
+	return error;	
+}
+#endif
+
+#ifdef ENABLE_CODE_TEST
+/* Test call ID generation */
+/* Generate 10.000 call id:s and check if they are identical */
+static int test_callid_uniqueness_func(void)
+{
+	char *call_ids[10000];
+	char *our_ip="192.168.40.1";
+	char buf[BUFSIZ];
+	int i, j;
+	int errors = 0, localerrors;
+	struct sockaddr_in our_ip_addr;
+	ast_get_ip(&our_ip_addr, our_ip);
+	
+	for (i=0; i < 10000; i++) {
+		localerrors = 0;
+		build_callid(buf, sizeof(buf), our_ip_addr.sin_addr, "edvina.net");
+		for (j = 0; j < i; j++) {
+			if (ast_strlen_zero(buf)) {
+				localerrors++;
+				if (option_debug)
+					ast_log(LOG_DEBUG, "  No call ID generated ?? \n");
+			} else if (!strcmp(buf, call_ids[j])) {
+				localerrors++;
+				if (option_debug)
+					ast_log(LOG_DEBUG, "  Duplicate call ID found. Bad! Now %d errors.\n", localerrors);
+			}
+		}
+		call_ids[i] = strdup(buf);
+		if (localerrors && option_debug)
+			ast_log(LOG_DEBUG, "-- Found %d duplicates for call ID #%d\n", localerrors, i);
+		errors += localerrors;
+	}
+	
+	/* clean up memory allocations */
+	for (i=0; i < 10000; i++) {
+		if (call_ids[i])
+			free(call_ids[i]);
+	}
+
+
+	if (errors && option_debug)
+		ast_log(LOG_DEBUG, "Found %d duplicates among 10.000 generated call IDs\n", errors);
+	return errors;
+}
+#endif
+
 /*! \brief  sip_do_reload: Reload module */
 static int sip_do_reload(void)
 {
@@ -13098,6 +13261,10 @@
 	ast_manager_register2("SIPshowpeer", EVENT_FLAG_SYSTEM, manager_sip_show_peer,
 			"Show SIP peer (text format)", mandescr_show_peer);
 
+	/* Register code tests */
+	ast_register_codetest(&test_tags);
+	ast_register_codetest(&test_callid_uniqueness);
+
 	sip_poke_all_peers();	
 	sip_send_all_registers();
 	
@@ -13114,22 +13281,31 @@
 	/* First, take us out of the channel type list */
 	ast_channel_unregister(&sip_tech);
 
+	/* Unregister functions */
 	ast_custom_function_unregister(&sipchaninfo_function);
 	ast_custom_function_unregister(&sippeer_function);
 	ast_custom_function_unregister(&sip_header_function);
 	ast_custom_function_unregister(&checksipdomain_function);
 
+	/* Unregister applications */
 	ast_unregister_application(app_dtmfmode);
 	ast_unregister_application(app_sipaddheader);
 	ast_unregister_application(app_sipgetheader);
 
+	/* Unregister CLI commands */
 	ast_cli_unregister_multiple(my_clis, sizeof(my_clis) / sizeof(my_clis[0]));
 
+	/* Unregister rtp proto */
 	ast_rtp_proto_unregister(&sip_rtp);
 
+	/* Unregister manager actions */
 	ast_manager_unregister("SIPpeers");
 	ast_manager_unregister("SIPshowpeer");
 
+	/* Unregister Code Tests */
+	ast_unregister_codetest(&test_tags);
+	ast_unregister_codetest(&test_callid_uniqueness);
+
 	if (!ast_mutex_lock(&iflock)) {
 		/* Hangup all interfaces if they have an owner */
 		p = iflist;
Index: Makefile
===================================================================
--- Makefile	(.../trunk)	(revision 7708)
+++ Makefile	(.../team/russell/codetest)	(revision 7708)
@@ -159,6 +159,9 @@
 # Determine by a grep 'ScriptAlias' of your httpd.conf file
 HTTP_CGIDIR=/var/www/cgi-bin
 
+# Uncomment the following to enable code testing code to be enabled :)
+ASTCFLAGS+=-DENABLE_CODE_TEST
+
 # If the file .asterisk.makeopts is present in your home directory, you can
 # include all of your favorite Makefile options so that every time you download
 # a new version of Asterisk, you don't have to edit the makefile to set them. 
@@ -353,6 +356,10 @@
 	netsock.o slinfactory.o ast_expr2.o ast_expr2f.o \
 	cryptostub.o
 
+ifeq ($(findstring -DENABLE_CODE_TEST,$(ASTCFLAGS)),-DENABLE_CODE_TEST)
+  OBJS+= codetest.o
+endif
+
 ifeq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/include/sys/poll.h),)
   OBJS+= poll.o
   ASTCFLAGS+=-DPOLLCOMPAT
Index: include/asterisk/codetest.h
===================================================================
--- include/asterisk/codetest.h	(.../trunk)	(revision 0)
+++ include/asterisk/codetest.h	(.../team/russell/codetest)	(revision 7708)
@@ -0,0 +1,51 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ *
+ * Russell Bryant <russell@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.
+ */
+
+#ifndef __ASTERISK_CODETEST_H
+#define __ASTERISK_CODETEST_H
+
+#ifdef ENABLE_CODE_TEST
+
+#include "asterisk/linkedlists.h"
+
+struct ast_codetest {
+	/*! The file that registered this test */
+	const char *filename;
+	/*! The name of the test */
+	const char *testname;
+	/*! The codetest callback function */
+	int (*codetest)(int fd, int argc, char *argv[]);
+	/*! For linking, used only in codetest.c */
+	AST_LIST_ENTRY(ast_codetest) list;
+};
+
+#define AST_DEFINE_CODE_TEST(name, tname, func) \
+	static struct ast_codetest name = { __FILE__, tname, func }
+
+int ast_register_codetest(struct ast_codetest *test);
+int ast_unregister_codetest(struct ast_codetest *test);
+
+#else
+
+#define ast_register_codetest(f)
+#define ast_unregister_codetest(f)
+#define AST_DEFINE_CODE_TEST(name, testname, func)
+
+#endif
+
+#endif /* __ASTERISK_CODETEST_H */
Index: include/asterisk.h
===================================================================
--- include/asterisk.h	(.../trunk)	(revision 7708)
+++ include/asterisk.h	(.../team/russell/codetest)	(revision 7708)
@@ -64,6 +64,10 @@
 int dnsmgr_init(void);
 void dnsmgr_start_refresh(void);
 void dnsmgr_reload(void);
+#ifdef ENABLE_CODE_TEST
+/* Provided by codetest.c */
+int codetest_init(void);
+#endif
 
 /*!
  * \brief Register the version of a source code file with the core.
Index: asterisk.c
===================================================================
--- asterisk.c	(.../trunk)	(revision 7708)
+++ asterisk.c	(.../team/russell/codetest)	(revision 7708)
@@ -2278,6 +2278,12 @@
 	srand((unsigned int) getpid() + (unsigned int) time(NULL));
 	srandom((unsigned int) getpid() + (unsigned int) time(NULL));
 
+#ifdef ENABLE_CODE_TEST
+	if (codetest_init()) {
+		printf(term_quit());
+		exit(1);
+	}
+#endif
 	if (init_logger()) {
 		printf(term_quit());
 		exit(1);
Index: codetest.c
===================================================================
--- codetest.c	(.../trunk)	(revision 0)
+++ codetest.c	(.../team/russell/codetest)	(revision 7708)
@@ -0,0 +1,401 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2005, Russell Bryant
+ *
+ * Russell Bryant <russell@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 Code Testing Framework
+ * 
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision: 1 $")
+
+#include "asterisk/channel.h"
+#include "asterisk/logger.h"
+#include "asterisk/cli.h"
+#include "asterisk/codetest.h"
+
+struct testfile {
+	const char *filename;
+	AST_LIST_HEAD_NOLOCK(, ast_codetest) codetests;
+	AST_LIST_ENTRY(testfile) list;
+};
+
+static AST_LIST_HEAD_STATIC(testfile_list, testfile);
+
+/* Prototypes for non-exported functions */
+struct testfile *find_testfile_by_name(const char *filename);
+static struct testfile *add_testfile(struct ast_codetest *test);
+static int count_tests(struct testfile *tf);
+static int handle_cli_show_files(int fd, int argc, char *argv[]);
+static int handle_cli_show_file(int fd, int argc, char *argv[]);
+static char *complete_testfiles(char *line, char *word, int pos, int state);
+static char *complete_cli_show_file(char *line, char *word, int pos, int state);
+static void check_test_result(int fd, struct ast_codetest *test, int res);
+static int handle_cli_run(int fd, int argc, char *argv[]);
+static char *complete_tests(char *line, char *word, int pos, int state);
+static char *complete_cli_run(char *line, char *word, int pos, int state);
+
+/*!
+  \brief Add a test to the list of tests for a file
+
+  \note This function is to be called with the testfile_list locked
+*/
+static struct testfile *add_testfile(struct ast_codetest *test)
+{
+	struct testfile *tf;
+
+	tf = calloc(1, sizeof(struct testfile));
+	if (!tf) {
+		ast_log(LOG_ERROR, "Memory Allocation Error!\n");
+		return NULL;
+	}
+
+	tf->filename = test->filename;
+
+	AST_LIST_INSERT_HEAD(&testfile_list, tf, list);
+
+	return tf;
+}
+
+/*! 
+  \brief search the list of files that have registered tests
+
+  \note This function is to be called with the testfile_list locked
+*/
+struct testfile *find_testfile_by_name(const char *filename)
+{
+	struct testfile *cur;
+
+	AST_LIST_TRAVERSE(&testfile_list, cur, list) {
+		if (!strcmp(cur->filename, filename))
+			break;
+	}
+
+	return cur;
+}
+
+int ast_register_codetest(struct ast_codetest *test)
+{
+	struct testfile *cur;
+
+	AST_LIST_LOCK(&testfile_list);
+	
+	cur = find_testfile_by_name(test->filename);
+
+	if (!cur)
+		cur = add_testfile(test);
+	if (!cur) {
+		AST_LIST_UNLOCK(&testfile_list);
+		return -1;
+	}
+
+	AST_LIST_INSERT_HEAD(&cur->codetests, test, list);
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	return 0;
+}
+
+int ast_unregister_codetest(struct ast_codetest *test)
+{
+	struct testfile *cur;
+
+	AST_LIST_LOCK(&testfile_list);
+
+	if (!(cur = find_testfile_by_name(test->filename))) {
+		ast_log(LOG_WARNING, "File '%s' not found in list of files with registered tests!\n", test->filename);
+		AST_LIST_UNLOCK(&testfile_list);
+		return -1;
+	}
+	
+	AST_LIST_REMOVE(&cur->codetests, test, list);
+
+	if (AST_LIST_EMPTY(&cur->codetests)) {
+		AST_LIST_REMOVE(&testfile_list, cur, list);
+		free(cur);
+	}
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	return 0;
+}
+
+/*!
+  \brief Count how many tests are registered by a file
+
+  \note This function is to be called with the testfile_list locked
+*/
+static int count_tests(struct testfile *tf)
+{
+	struct ast_codetest *cur;
+	int count = 0;
+
+	AST_LIST_TRAVERSE(&tf->codetests, cur, list)
+		count++;
+
+	return count;
+}
+
+static int handle_cli_show_files(int fd, int argc, char *argv[])
+{
+	struct testfile *cur;
+	int count;
+
+	ast_cli(fd, "Files with Registered Code Tests\n");
+	ast_cli(fd, "--------------------------------\n");
+
+	AST_LIST_LOCK(&testfile_list);
+
+	AST_LIST_TRAVERSE(&testfile_list, cur, list) {
+		count = count_tests(cur);
+		ast_cli(fd, "Filename: '%s'  (%d %s)\n", cur->filename, count, count > 1 ? "tests" : "test");
+	}
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	return RESULT_SUCCESS;
+}
+
+static struct ast_cli_entry cli_show_files = {
+	.cmda = { "codetest", "show", "files", NULL },
+	.handler = handle_cli_show_files,
+	.summary = "Show files with registered code tests",
+	.usage =
+	"Usage: codetest show files\n"
+	"       This command will list all files that have registered code tests.\n"
+};
+
+static int handle_cli_show_file(int fd, int argc, char *argv[])
+{
+	struct testfile *cur;
+	struct ast_codetest *test;
+	int count = 0;
+
+	if (argc != 4)
+		return RESULT_SHOWUSAGE;
+
+	AST_LIST_LOCK(&testfile_list);
+
+	if (!(cur = find_testfile_by_name(argv[3]))) {
+		ast_cli(fd, "File '%s' not found in list of files with registered code tests!\n", argv[3]);
+		AST_LIST_UNLOCK(&testfile_list);
+		return RESULT_SHOWUSAGE;
+	}
+
+	ast_cli(fd, "Registered Code Tests for File '%s'\n", cur->filename);
+	ast_cli(fd, "---------------------------------------------\n");
+
+	AST_LIST_TRAVERSE(&cur->codetests, test, list) {
+		count++;
+		ast_cli(fd, "Test Name: '%s'\n", test->testname);
+	}
+
+	ast_cli(fd, "\n(Total Tests Registered: %d)\n", count);
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	return RESULT_SUCCESS;
+}
+
+static char *complete_testfiles(char *line, char *word, int pos, int state)
+{
+	struct testfile *cur;
+	int match = 0;
+	int wordlen;
+	char *file;
+
+	wordlen = strlen(word);
+
+	AST_LIST_LOCK(&testfile_list);
+
+	AST_LIST_TRAVERSE(&testfile_list, cur, list) {	
+		if (!strncasecmp(word, cur->filename, wordlen)) {
+			match++;
+			if (match > state) {
+				file = strdup(cur->filename);
+				AST_LIST_UNLOCK(&testfile_list);
+				return file;
+			}
+		}
+	}
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	return NULL;
+
+}
+
+static char *complete_cli_show_file(char *line, char *word, int pos, int state)
+{
+	if (pos != 3) 
+		return NULL;
+
+	return complete_testfiles(line, word, pos, state);	
+}
+
+static struct ast_cli_entry cli_show_file = {
+	.cmda = { "codetest", "show", "file", NULL },
+	.handler = handle_cli_show_file,
+	.generator = complete_cli_show_file,
+	.summary = "Show registered code tests for a given file.",
+	.usage =
+	"Usage: codetest show file <file>\n"
+	"       This command will list all registered code tests for a file.\n"
+};
+
+static void check_test_result(int fd, struct ast_codetest *test, int res)
+{
+	if (res)
+		ast_cli(fd, "Test '%s' *** FAILED *** for File '%s' !!!\n", test->testname, test->filename);
+	else
+		ast_cli(fd, "Test '%s' PASSED for File '%s' !!!\n", test->testname, test->filename);
+}
+
+static int handle_cli_run(int fd, int argc, char *argv[])
+{
+	struct testfile *tf;
+	struct ast_codetest *test;
+	char *testname = NULL;
+	int res = 0;
+	int test_executed = 0;
+	int test_argc = 0;
+	char **test_argv = NULL;
+
+	if (argc > 3) {
+		testname = argv[3];
+		if (argc > 4) {
+			test_argc = argc - 4;
+			test_argv = argv + 4;	
+		}	
+	} else
+		return RESULT_SHOWUSAGE;
+
+	AST_LIST_LOCK(&testfile_list);
+
+	if (!(tf = find_testfile_by_name(argv[2]))) {
+		ast_cli(fd, "File '%s' not found in list of files with code tests.\n", argv[2]);
+		AST_LIST_UNLOCK(&testfile_list);
+		return RESULT_SHOWUSAGE;
+	}
+
+	AST_LIST_TRAVERSE(&tf->codetests, test, list) {
+		if (!ast_strlen_zero(testname)) {
+			if (!strcmp(test->testname, testname)) {
+				ast_cli(fd, "Executing test '%s' for file '%s' ...\n", test->testname, test->filename);
+				res = test->codetest(fd, test_argc, test_argv);
+				check_test_result(fd, test, res);
+				test_executed = 1;
+				break;
+			}
+		} else {
+			ast_cli(fd, "Executing test '%s' for file '%s' ...\n", test->testname, test->filename);
+			res = test->codetest(fd, test_argc, test_argv);
+			check_test_result(fd, test, res);
+			test_executed = 1;
+		}
+	}
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	if (!test_executed && !ast_strlen_zero(testname)) {
+		ast_cli(fd, "Test '%s' not found for file '%s'!\n", testname, argv[2]);
+		return RESULT_FAILURE;
+	}
+
+	return RESULT_SUCCESS;
+}
+
+static char *complete_tests(char *line, char *word, int pos, int state)
+{
+	char *file;
+	char *ret = NULL;
+	struct ast_codetest *test;
+	struct testfile *tf;
+	int match = 0;
+	int wordlen;
+
+	file = ast_strdupa(line);
+	if (!file) {
+		ast_log(LOG_ERROR, "Memory Allocation Error!\n");
+		return NULL;
+	}
+
+	wordlen = strlen(word);
+
+	strsep(&file, " ");
+	strsep(&file, " ");
+	file = strsep(&file, " ");
+
+	AST_LIST_LOCK(&testfile_list);
+
+	if (!(tf = find_testfile_by_name(file))) {
+		AST_LIST_UNLOCK(&testfile_list);
+		return NULL;
+	}
+
+	AST_LIST_TRAVERSE(&tf->codetests, test, list) {
+		if (!strncasecmp(word, test->filename, wordlen) && ++match > state) {
+			ret = strdup(test->filename);
+			break;
+		}
+	}	
+
+	AST_LIST_UNLOCK(&testfile_list);
+
+	return ret;
+}
+
+static char *complete_cli_run(char *line, char *word, int pos, int state)
+{
+	if (pos == 2)
+		return complete_testfiles(line, word, pos, state);
+	else if (pos == 3)
+		/* XXX Why isn't this being executed ?! XXX */
+		return complete_tests(line, word, pos, state);
+
+	return NULL;
+}
+
+static struct ast_cli_entry cli_run = {
+	.cmda = { "codetest", "run", NULL },
+	.handler = handle_cli_run,
+	.generator = complete_cli_run,
+	.summary = "Run code test(s) for a file.",
+	.usage =
+	"Usage: codetest run <file> [test]\n"
+	"       This command will run registered code tests for a file.\n"
+	"       If a test is specified, only that test will be executed.\n"
+	"       Otherwise, all available tests will be executed.\n"
+};
+
+int codetest_init(void)
+{
+	int res;	
+
+	res = ast_cli_register(&cli_show_files);
+	res |= ast_cli_register(&cli_show_file);
+	res |= ast_cli_register(&cli_run);
+
+	return res;
+}

