[asterisk-commits] mnicholson: branch mnicholson/asttest r193192 - in /team/mnicholson/asttest: ...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Fri May 8 07:34:39 CDT 2009


Author: mnicholson
Date: Fri May  8 07:34:35 2009
New Revision: 193192

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=193192
Log:
Added asttest manager event type.  Also added res_asttest which will be used by
asttest for accessing the internals of asterisk.

Added:
    team/mnicholson/asttest/configs/res_asttest.lua.sample   (with props)
    team/mnicholson/asttest/res/res_asttest.c   (with props)
Modified:
    team/mnicholson/asttest/include/asterisk/manager.h
    team/mnicholson/asttest/main/manager.c

Added: team/mnicholson/asttest/configs/res_asttest.lua.sample
URL: http://svn.asterisk.org/svn-view/asterisk/team/mnicholson/asttest/configs/res_asttest.lua.sample?view=auto&rev=193192
==============================================================================
--- team/mnicholson/asttest/configs/res_asttest.lua.sample (added)
+++ team/mnicholson/asttest/configs/res_asttest.lua.sample Fri May  8 07:34:35 2009
@@ -1,0 +1,5 @@
+-- sample config file for res_asttest
+
+function tests.test(args)
+end
+

Propchange: team/mnicholson/asttest/configs/res_asttest.lua.sample
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/mnicholson/asttest/configs/res_asttest.lua.sample
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/mnicholson/asttest/configs/res_asttest.lua.sample
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: team/mnicholson/asttest/include/asterisk/manager.h
URL: http://svn.asterisk.org/svn-view/asterisk/team/mnicholson/asttest/include/asterisk/manager.h?view=diff&rev=193192&r1=193191&r2=193192
==============================================================================
--- team/mnicholson/asttest/include/asterisk/manager.h (original)
+++ team/mnicholson/asttest/include/asterisk/manager.h Fri May  8 07:34:35 2009
@@ -72,6 +72,7 @@
 #define EVENT_FLAG_DIALPLAN		(1 << 11) /* Dialplan events (VarSet, NewExten) */
 #define EVENT_FLAG_ORIGINATE	(1 << 12) /* Originate a call to an extension */
 #define EVENT_FLAG_AGI			(1 << 13) /* AGI events */
+#define EVENT_FLAG_ASTTEST		(1 << 14) /* asttest events */
 /*@} */
 
 /*! \brief Export manager structures */

Modified: team/mnicholson/asttest/main/manager.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/mnicholson/asttest/main/manager.c?view=diff&rev=193192&r1=193191&r2=193192
==============================================================================
--- team/mnicholson/asttest/main/manager.c (original)
+++ team/mnicholson/asttest/main/manager.c Fri May  8 07:34:35 2009
@@ -391,6 +391,7 @@
 	{ EVENT_FLAG_DIALPLAN, "dialplan" },
 	{ EVENT_FLAG_ORIGINATE, "originate" },
 	{ EVENT_FLAG_AGI, "agi" },
+	{ EVENT_FLAG_ASTTEST, "asttest" },
 	{ -1, "all" },
 	{ 0, "none" },
 };

Added: team/mnicholson/asttest/res/res_asttest.c
URL: http://svn.asterisk.org/svn-view/asterisk/team/mnicholson/asttest/res/res_asttest.c?view=auto&rev=193192
==============================================================================
--- team/mnicholson/asttest/res/res_asttest.c (added)
+++ team/mnicholson/asttest/res/res_asttest.c Fri May  8 07:34:35 2009
@@ -1,0 +1,1295 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2009, Digium, Inc.
+ *
+ * Matthew Nicholson <mnicholson at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \author Matthew Nicholson <mnicholson at digium.com>
+ * \brief Automated Asterisk Testing Framework
+ *
+ */
+
+/*** MODULEINFO
+	<depend>lua</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/utils.h"
+#include "asterisk/term.h"
+#include "asterisk/paths.h"
+#include "asterisk/manager.h"
+
+#include <lua5.1/lua.h>
+#include <lua5.1/lauxlib.h>
+#include <lua5.1/lualib.h>
+
+static char *config = "res_asttest.lua";
+#if 0
+static char *registrar = "res_asttest";
+#endif
+
+#define LUA_EXT_DATA_SIZE 256
+#define LUA_BUF_SIZE 4096
+
+static char manager_run_test_help[] =
+"Description: Run the specified test.\n"
+"Variables: (Names marked with * are required)\n"
+"	*Test: The test to run\n"
+"	Async: If the test should run asynchronously\n"
+"	ActionID: Optional ActionID for message matching\n"
+"Any additional headers will be passed to the test as a table.  Multiple\n"
+"arguments with the same name will be placed in that table as a list.\n";
+
+static char *lua_read_config_file(lua_State *L, long *size);
+static int lua_load_config(lua_State *L);
+static int lua_reload_config(lua_State *L);
+static void lua_free_config(void);
+static int lua_pbx_findapp(lua_State *L);
+static int lua_pbx_exec(lua_State *L);
+
+static int lua_get_variable_value(lua_State *L);
+static int lua_set_variable_value(lua_State *L);
+static int lua_get_variable(lua_State *L);
+static int lua_set_variable(lua_State *L);
+static int lua_func_read(lua_State *L);
+
+static int lua_autoservice_start(lua_State *L);
+static int lua_autoservice_stop(lua_State *L);
+static int lua_autoservice_status(lua_State *L);
+static int lua_check_hangup(lua_State *L);
+#if 0
+static int lua_error_function(lua_State *L);
+
+static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority);
+#endif
+static void lua_push_variable_table(lua_State *L, const char *name);
+static void lua_create_app_table(lua_State *L);
+static void lua_create_channel_table(lua_State *L);
+static void lua_create_variable_metatable(lua_State *L);
+static void lua_create_application_metatable(lua_State *L);
+static void lua_create_autoservice_functions(lua_State *L);
+static void lua_create_hangup_function(lua_State *L);
+
+static int lua_push_test(lua_State *L, const char *test);
+
+static lua_State *lua_get_state(void);
+
+static void manager_table_from_headers(lua_State *L, const struct message *m);
+static int manager_parse_header(const char *header, const char **value);
+static int manager_run_test(struct mansession *s, const struct message *m);
+static int manager_start_test_async(lua_State *L);
+static void *manager_run_test_async(void *data);
+
+AST_MUTEX_DEFINE_STATIC(config_file_lock);
+char *config_file_data = NULL;
+long config_file_size = 0;
+
+
+/*!
+ * \brief Parse a single header value pair.
+ * \param header the header value pair to parse
+ * \param value a pointer to a pointer where the start of the value will be
+ * stored
+ *
+ * This function will parse a header value pair seperated by ': '.
+ *
+ * \return the length of the header, zero on error
+ */
+static int manager_parse_header(const char *header, const char **value)
+{
+	const char *c = header;
+	int state = 0;
+	int res = 0;
+
+	for (; state != 3 && c && *c; c++) {
+		switch (state) {
+		case 0:
+			/* look for a ':', then calculate the length of the
+			 * header, that will be our return value */
+			if (*c == ':') {
+				state++;
+				res = c - header;
+			}
+			break;
+		case 1:
+			/* look for a space, it should come directly after the
+			 * ':' */
+			if (*c == ' ') {
+				state++;
+			} else {
+				state = 0;
+				res = 0;
+			}
+			break;
+		case 2:
+			/* the next character after the space is the start of
+			 * the value */
+			*value = c;
+			state++;
+			break;
+		}
+	}
+
+	return res;
+}
+
+/*!
+ * \brief Generate a lua table from the given manager message and push it on
+ * the stack.
+ * \param L the lua_State to use
+ * \param m the manager message to parse
+ *
+ * This function generate a table from the given manger message.  The Test,
+ * Async, and ActionID headers are not included in the generated table.
+ *
+ * Example message:
+ * \code
+ * Test: test_name
+ * Async: false
+ * ActionID: 1234
+ * Priority: 1
+ * Animal: duck
+ * Animal: frog
+ * Animal: moo cow
+ * Old McDonald: Had a farm.
+ * \endcode
+ *
+ * Generated table
+ * \code
+ * table = {
+ *   ["Priority"] = "1";
+ *   ["Animal"] = {"duck", "frog", "moo cow"};
+ *   ["Old McDonald"] = "Had a farm.";
+ * }
+ * \endcode
+ */
+static void manager_table_from_headers(lua_State *L, const struct message *m)
+{
+	int seen_test = 0, seen_async = 0, seen_id = 0, len, i, args_index;
+	const char *value;
+	size_t objlen;
+	int test_len = strlen("Test");
+	int async_len = strlen("Async");
+	int id_len = strlen("ActionID");
+
+	lua_newtable(L);
+	args_index = lua_gettop(L);
+	for (i = 0; i < m->hdrcount; i++) {
+		value = NULL;
+		if (!(len = manager_parse_header(m->headers[i], &value))) {
+			continue;
+		}
+
+		if (!value) {
+			continue;
+		}
+
+		if (!seen_test && !strncasecmp("Test", m->headers[i], test_len)) {
+			seen_test = 1;
+			continue;
+		}
+
+		if (!seen_async && !strncasecmp("Async", m->headers[i], async_len)) {
+			seen_async = 1;
+			continue;
+		}
+
+		if (!seen_id && !strncasecmp("ActionID", m->headers[i], id_len)) {
+			seen_id = 1;
+			continue;
+		}
+
+
+		lua_pushlstring(L, m->headers[i], len);
+		lua_gettable(L, args_index);
+
+		if (lua_isnil(L, -1)) {
+			/* value is not in our table yet, add it */
+			lua_pushlstring(L, m->headers[i], len);
+			lua_pushstring(L, value);
+			lua_settable(L, args_index);
+		} else if (lua_istable(L, -1)) {
+			/* multiple values found, add this one */
+			objlen = lua_objlen(L, -1);
+			lua_pushinteger(L, objlen + 1);
+			lua_pushstring(L, value);
+			lua_settable(L, -3);
+		} else {
+			/* value exists, convert it to a list/table */
+			lua_pushlstring(L, m->headers[i], len);
+			lua_newtable(L);
+
+			/* add the existing value */
+			lua_pushinteger(L, 1);
+			lua_pushvalue(L, -3);
+			lua_settable(L, -3);
+
+			/* add the new value */
+			lua_pushinteger(L, 1);
+			lua_pushstring(L, value);
+			lua_settable(L, -3);
+
+			/* store the new table */
+			lua_settable(L, args_index);
+		}
+		lua_pop(L, 1);
+	}
+}
+
+/*!
+ * \brief The test running thread.
+ * \param data a lua state
+ *
+ * This function runs a test in a thread.  It expects the test and is arguments
+ * to be on the given lua stack.
+ */
+static void *manager_run_test_async(void *data)
+{
+	lua_State *L = (lua_State *) data;
+	const char *id;
+	const char *test = "error";
+	const char *message;
+
+	/* retreive our registry values */
+	lua_getfield(L, LUA_REGISTRYINDEX, "asttest_action_id");
+	if ((id = lua_tostring(L, -1))) {
+		id = ast_strdupa(id);
+	}
+	lua_pop(L, 1);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "asttest_test_name");
+	if ((test = lua_tostring(L, -1))) {
+		test = ast_strdupa(test);
+	} else {
+		message = "test name not set";
+		goto e_error;
+	}
+	lua_pop(L, 1);
+
+
+	/* run the test */
+	if (lua_pcall(L, 1, 0, 0)) {
+		message = lua_tostring(L, -1);
+		goto e_error;
+	}
+
+	manager_event(EVENT_FLAG_ASTTEST, "TestEnd",
+		"Test: %s\r\n"
+		"Result: finished\r\n"
+		"Message: test finished\r\n"
+		"%s%s%s",
+		test,
+		id ? "ActionID: " : "",
+		id ? id : "",
+		id ? "\r\n" : ""
+	);
+
+	goto e_lua_close;
+
+e_error:
+	manager_event(EVENT_FLAG_ASTTEST, "TestEnd",
+		"Test: %s\r\n"
+		"Result: error\r\n"
+		"Message: %s\r\n"
+		"%s%s%s",
+		S_OR(test, "error"),
+		S_OR(message, ""),
+		id ? "ActionID: " : "",
+		id ? id : "",
+		id ? "\r\n" : ""
+	);
+e_lua_close:
+	lua_close(L);
+	return NULL;
+}
+
+/*!
+ * \brief Execute a test asynchronously.
+ * \param L the lua_State to use
+ *
+ * This function spawns a thread that executes the given test.  It expects the
+ * test and its argments to be on the given lua stack.
+ *
+ * \note If this function returns success, the lua state should not be freed.
+ * The async thread will handle that.
+ *
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int manager_start_test_async(lua_State *L)
+{
+	pthread_t thread;
+
+	if (ast_pthread_create_detached(&thread, NULL, manager_run_test_async, L)) {
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \brief Manager command to execute a test.
+ * \param s the manager session
+ * \param m the manager messate
+ *
+ * This manager action will execute the given test.  It accepts Test, Async,
+ * and ActionID parameters.  Any additional manager headers will be packed into
+ * a lua table and passed to the test and an argument.
+ */
+static int manager_run_test(struct mansession *s, const struct message *m)
+{
+	const char *test = astman_get_header(m, "Test");
+	const char *async = astman_get_header(m, "Async");
+	const char *id = astman_get_header(m, "ActionID");
+	lua_State *L;
+
+	if (ast_strlen_zero(test)) {
+		astman_send_error(s, m, "test not specified");
+		goto e_return;
+	}
+
+	if (!(L = lua_get_state())) {
+		astman_send_error(s, m, "internal error");
+		goto e_return;
+	}
+
+	lua_pushstring(L, test);
+	lua_setfield(L, LUA_REGISTRYINDEX, "asttest_test_name");
+
+	if (id) {
+		lua_pushstring(L, id);
+		lua_setfield(L, LUA_REGISTRYINDEX, "asttest_action_id");
+	}
+
+	if (lua_push_test(L, test)) {
+		astman_send_error(s, m, "test not found");
+		goto e_lua_close;
+	}
+
+	manager_table_from_headers(L, m);
+
+	if (ast_true(async)) {
+		if (manager_start_test_async(L)) {
+			astman_send_error(s, m, "error running test asynchronously");
+			goto e_lua_close;
+		}
+
+		astman_send_ack(s, m, "test successfully started");
+		goto e_return;
+	} else {
+		if (lua_pcall(L, 1, 0, 0)) {
+			astman_send_error(s, m, "error running test");
+			goto e_lua_close;
+		}
+	}
+
+	astman_send_ack(s, m, "test successfully executed");
+
+e_lua_close:
+	lua_close(L);
+e_return:
+	return 0;
+}
+
+/*!
+ * \brief Locate the given test and push it to the stack.
+ * \param L the lua_State to use
+ * \param test the name of the test to retrieve
+ *
+ * \retval 0 success
+ * \retval 1 failure
+ */
+static int lua_push_test(lua_State *L, const char *test)
+{
+	lua_getglobal(L, "tests");
+	if (lua_isnil(L, -1)) {
+		lua_pop(L, 1);
+		return 1;
+	}
+
+	lua_getfield(L, -1, test);
+	if (lua_isnil(L, -1)) {
+		lua_pop(L, 2);
+		return 1;
+	}
+
+	lua_remove(L, -2);
+	return 0;
+}
+
+/*!
+ * \brief [lua_CFunction] Find an app and return it in a lua table (for access from lua, don't
+ * call directly)
+ *
+ * This function would be called in the following example as it would be found
+ * in extensions.lua.
+ *
+ * \code
+ * app.dial
+ * \endcode
+ */
+static int lua_pbx_findapp(lua_State *L)
+{
+	const char *app_name = luaL_checkstring(L, 2);
+
+	lua_newtable(L);
+
+	lua_pushstring(L, "name");
+	lua_pushstring(L, app_name);
+	lua_settable(L, -3);
+
+	luaL_getmetatable(L, "application");
+	lua_setmetatable(L, -2);
+
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] This function is part of the 'application' metatable
+ * and is used to execute applications similar to pbx_exec() (for access from
+ * lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ * \return nothing
+ *
+ * This funciton is executed as the '()' operator for apps accessed through the
+ * 'app' table.
+ *
+ * \code
+ * app.playback('demo-congrats')
+ * \endcode
+ */
+static int lua_pbx_exec(lua_State *L)
+{
+	int res, nargs = lua_gettop(L);
+	char data[LUA_EXT_DATA_SIZE] = "";
+	char *data_next = data, *app_name;
+	char *context, *exten;
+	char tmp[80], tmp2[80], tmp3[LUA_EXT_DATA_SIZE];
+	int priority, autoservice;
+	size_t data_left = sizeof(data);
+	struct ast_app *app;
+	struct ast_channel *chan;
+
+	lua_getfield(L, 1, "name");
+	app_name = ast_strdupa(lua_tostring(L, -1));
+	lua_pop(L, 1);
+
+	if (!(app = pbx_findapp(app_name))) {
+		lua_pushstring(L, "application '");
+		lua_pushstring(L, app_name);
+		lua_pushstring(L, "' not found");
+		lua_concat(L, 3);
+		return lua_error(L);
+	}
+
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "context");
+	context = ast_strdupa(lua_tostring(L, -1));
+	lua_pop(L, 1);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "exten");
+	exten = ast_strdupa(lua_tostring(L, -1));
+	lua_pop(L, 1);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "priority");
+	priority = lua_tointeger(L, -1);
+	lua_pop(L, 1);
+
+
+	if (nargs > 1) {
+		int i;
+
+		if (!lua_isnil(L, 2))
+			ast_build_string(&data_next, &data_left, "%s", luaL_checkstring(L, 2));
+
+		for (i = 3; i <= nargs; i++) {
+			if (lua_isnil(L, i))
+				ast_build_string(&data_next, &data_left, ",");
+			else
+				ast_build_string(&data_next, &data_left, ",%s", luaL_checkstring(L, i));
+		}
+	}
+
+	ast_verb(3, "Executing [%s@%s:%d] %s(\"%s\", \"%s\")\n",
+			exten, context, priority,
+			term_color(tmp, app_name, COLOR_BRCYAN, 0, sizeof(tmp)),
+			term_color(tmp2, chan->name, COLOR_BRMAGENTA, 0, sizeof(tmp2)),
+			term_color(tmp3, data, COLOR_BRMAGENTA, 0, sizeof(tmp3)));
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+	autoservice = lua_toboolean(L, -1);
+	lua_pop(L, 1);
+
+	if (autoservice)
+		ast_autoservice_stop(chan);
+
+	res = pbx_exec(chan, app, data);
+
+	if (autoservice)
+		ast_autoservice_start(chan);
+
+	/* error executing an application, report it */
+	if (res) {
+		lua_pushinteger(L, res);
+		return lua_error(L);
+	}
+	return 0;
+}
+
+/*!
+ * \brief [lua_CFunction] Used to get the value of a variable or dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * The value of the variable or function is returned.  This function is the
+ * 'get()' function in the following example as would be seen in
+ * extensions.lua.
+ *
+ * \code
+ * channel.variable:get()
+ * \endcode
+ */
+static int lua_get_variable_value(lua_State *L)
+{
+	struct ast_channel *chan;
+	char *value = NULL, *name;
+	char *workspace = alloca(LUA_BUF_SIZE);
+	int autoservice;
+
+	workspace[0] = '\0';
+
+	if (!lua_istable(L, 1)) {
+		lua_pushstring(L, "User probably used '.' instead of ':' for retrieving a channel variable value");
+		return lua_error(L);
+	}
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	lua_getfield(L, 1, "name");
+	name = ast_strdupa(lua_tostring(L, -1));
+	lua_pop(L, 1);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+	autoservice = lua_toboolean(L, -1);
+	lua_pop(L, 1);
+
+	if (autoservice)
+		ast_autoservice_stop(chan);
+
+	/* if this is a dialplan function then use ast_func_read(), otherwise
+	 * use pbx_retrieve_variable() */
+	if (!ast_strlen_zero(name) && name[strlen(name) - 1] == ')') {
+		value = ast_func_read(chan, name, workspace, LUA_BUF_SIZE) ? NULL : workspace;
+	} else {
+		pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead);
+	}
+
+	if (autoservice)
+		ast_autoservice_start(chan);
+
+	if (value) {
+		lua_pushstring(L, value);
+	} else {
+		lua_pushnil(L);
+	}
+
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Used to set the value of a variable or dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * This function is the 'set()' function in the following example as would be
+ * seen in extensions.lua.
+ *
+ * \code
+ * channel.variable:set("value")
+ * \endcode
+ */
+static int lua_set_variable_value(lua_State *L)
+{
+	const char *name, *value;
+	struct ast_channel *chan;
+	int autoservice;
+
+	if (!lua_istable(L, 1)) {
+		lua_pushstring(L, "User probably used '.' instead of ':' for setting a channel variable");
+		return lua_error(L);
+	}
+
+	lua_getfield(L, 1, "name");
+	name = ast_strdupa(lua_tostring(L, -1));
+	lua_pop(L, 1);
+
+	value = luaL_checkstring(L, 2);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+	autoservice = lua_toboolean(L, -1);
+	lua_pop(L, 1);
+
+	if (autoservice)
+		ast_autoservice_stop(chan);
+
+	pbx_builtin_setvar_helper(chan, name, value);
+
+	if (autoservice)
+		ast_autoservice_start(chan);
+
+	return 0;
+}
+
+#if 0
+/*!
+ * \brief Update the lua registry with the given context, exten, and priority.
+ *
+ * \param L the lua_State to use
+ * \param context the new context
+ * \param exten the new exten
+ * \param priority the new priority
+ */
+static void lua_update_registry(lua_State *L, const char *context, const char *exten, int priority)
+{
+	lua_pushstring(L, context);
+	lua_setfield(L, LUA_REGISTRYINDEX, "context");
+
+	lua_pushstring(L, exten);
+	lua_setfield(L, LUA_REGISTRYINDEX, "exten");
+
+	lua_pushinteger(L, priority);
+	lua_setfield(L, LUA_REGISTRYINDEX, "priority");
+}
+#endif
+
+/*!
+ * \brief Push a 'variable' table on the stack for access the channel variable
+ * with the given name.
+ *
+ * \param L the lua_State to use
+ * \param name the name of the variable
+ */
+static void lua_push_variable_table(lua_State *L, const char *name)
+{
+	lua_newtable(L);
+	luaL_getmetatable(L, "variable");
+	lua_setmetatable(L, -2);
+
+	lua_pushstring(L, name);
+	lua_setfield(L, -2, "name");
+
+	lua_pushcfunction(L, &lua_get_variable_value);
+	lua_setfield(L, -2, "get");
+
+	lua_pushcfunction(L, &lua_set_variable_value);
+	lua_setfield(L, -2, "set");
+}
+
+/*!
+ * \brief Create the global 'app' table for executing applications
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_app_table(lua_State *L)
+{
+	lua_newtable(L);
+	luaL_newmetatable(L, "app");
+
+	lua_pushstring(L, "__index");
+	lua_pushcfunction(L, &lua_pbx_findapp);
+	lua_settable(L, -3);
+
+	lua_setmetatable(L, -2);
+	lua_setglobal(L, "app");
+}
+
+/*!
+ * \brief Create the global 'channel' table for accesing channel variables
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_channel_table(lua_State *L)
+{
+	lua_newtable(L);
+	luaL_newmetatable(L, "channel_data");
+
+	lua_pushstring(L, "__index");
+	lua_pushcfunction(L, &lua_get_variable);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "__newindex");
+	lua_pushcfunction(L, &lua_set_variable);
+	lua_settable(L, -3);
+
+	lua_setmetatable(L, -2);
+	lua_setglobal(L, "channel");
+}
+
+/*!
+ * \brief Create the 'variable' metatable, used to retrieve channel variables
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_variable_metatable(lua_State *L)
+{
+	luaL_newmetatable(L, "variable");
+
+	lua_pushstring(L, "__call");
+	lua_pushcfunction(L, &lua_func_read);
+	lua_settable(L, -3);
+
+	lua_pop(L, 1);
+}
+
+/*!
+ * \brief Create the 'application' metatable, used to execute asterisk
+ * applications from lua
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_application_metatable(lua_State *L)
+{
+	luaL_newmetatable(L, "application");
+
+	lua_pushstring(L, "__call");
+	lua_pushcfunction(L, &lua_pbx_exec);
+	lua_settable(L, -3);
+
+	lua_pop(L, 1);
+}
+
+/*!
+ * \brief Create the autoservice functions
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_autoservice_functions(lua_State *L)
+{
+	lua_pushcfunction(L, &lua_autoservice_start);
+	lua_setglobal(L, "autoservice_start");
+
+	lua_pushcfunction(L, &lua_autoservice_stop);
+	lua_setglobal(L, "autoservice_stop");
+
+	lua_pushcfunction(L, &lua_autoservice_status);
+	lua_setglobal(L, "autoservice_status");
+
+	lua_pushboolean(L, 0);
+	lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
+}
+
+/*!
+ * \brief Create the hangup check function
+ *
+ * \param L the lua_State to use
+ */
+static void lua_create_hangup_function(lua_State *L)
+{
+	lua_pushcfunction(L, &lua_check_hangup);
+	lua_setglobal(L, "check_hangup");
+}
+
+/*!
+ * \brief [lua_CFunction] Return a lua 'variable' object (for access from lua, don't call
+ * directly)
+ *
+ * This function is called to lookup a variable construct a 'variable' object.
+ * It would be called in the following example as would be seen in
+ * extensions.lua.
+ *
+ * \code
+ * channel.variable
+ * \endcode
+ */
+static int lua_get_variable(lua_State *L)
+{
+	struct ast_channel *chan;
+	char *name = ast_strdupa(luaL_checkstring(L, 2));
+	char *value = NULL;
+	char *workspace = alloca(LUA_BUF_SIZE);
+	workspace[0] = '\0';
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	lua_push_variable_table(L, name);
+
+	/* if this is not a request for a dialplan funciton attempt to retrieve
+	 * the value of the variable */
+	if (!ast_strlen_zero(name) && name[strlen(name) - 1] != ')') {
+		pbx_retrieve_variable(chan, name, &value, workspace, LUA_BUF_SIZE, &chan->varshead);
+	}
+
+	if (value) {
+		lua_pushstring(L, value);
+		lua_setfield(L, -2, "value");
+	}
+
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Set the value of a channel variable or dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * This function is called to set a variable or dialplan function.  It would be
+ * called in the following example as would be seen in extensions.lua.
+ *
+ * \code
+ * channel.variable = "value"
+ * \endcode
+ */
+static int lua_set_variable(lua_State *L)
+{
+	struct ast_channel *chan;
+	int autoservice;
+	const char *name = luaL_checkstring(L, 2);
+	const char *value = luaL_checkstring(L, 3);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+	autoservice = lua_toboolean(L, -1);
+	lua_pop(L, 1);
+
+	if (autoservice)
+		ast_autoservice_stop(chan);
+
+	pbx_builtin_setvar_helper(chan, name, value);
+
+	if (autoservice)
+		ast_autoservice_start(chan);
+
+	return 0;
+}
+
+/*!
+ * \brief [lua_CFunction] Create a 'variable' object for accessing a dialplan
+ * function (for access from lua, don't call directly)
+ *
+ * This function is called to create a 'variable' object to access a dialplan
+ * function.  It would be called in the following example as would be seen in
+ * extensions.lua.
+ *
+ * \code
+ * channel.func("arg1", "arg2", "arg3")
+ * \endcode
+ *
+ * To actually do anything with the resulting value you must use the 'get()'
+ * and 'set()' methods (the reason is the resulting value is not a value, but
+ * an object in the form of a lua table).
+ */
+static int lua_func_read(lua_State *L)
+{
+	int nargs = lua_gettop(L);
+	char fullname[LUA_EXT_DATA_SIZE] = "";
+	char *fullname_next = fullname, *name;
+	size_t fullname_left = sizeof(fullname);
+
+	lua_getfield(L, 1, "name");
+	name = ast_strdupa(lua_tostring(L, -1));
+	lua_pop(L, 1);
+
+	ast_build_string(&fullname_next, &fullname_left, "%s(", name);
+
+	if (nargs > 1) {
+		int i;
+
+		if (!lua_isnil(L, 2))
+			ast_build_string(&fullname_next, &fullname_left, "%s", luaL_checkstring(L, 2));
+
+		for (i = 3; i <= nargs; i++) {
+			if (lua_isnil(L, i))
+				ast_build_string(&fullname_next, &fullname_left, ",");
+			else
+				ast_build_string(&fullname_next, &fullname_left, ",%s", luaL_checkstring(L, i));
+		}
+	}
+
+	ast_build_string(&fullname_next, &fullname_left, ")");
+
+	lua_push_variable_table(L, fullname);
+
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Tell pbx_lua to maintain an autoservice on this
+ * channel (for access from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * This function will set a flag that will cause pbx_lua to maintain an
+ * autoservice on this channel.  The autoservice will automatically be stopped
+ * and restarted before calling applications and functions.
+ *
+ * \return This function returns the result of the ast_autoservice_start()
+ * function as a boolean to its lua caller.
+ */
+static int lua_autoservice_start(lua_State *L)
+{
+	struct ast_channel *chan;
+	int res;
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	res = ast_autoservice_start(chan);
+
+	lua_pushboolean(L, !res);
+	lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
+
+	lua_pushboolean(L, !res);
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Tell pbx_lua to stop maintaning an autoservice on
+ * this channel (for access from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * This function will stop any autoservice running and turn off the autoservice
+ * flag.  If this function returns false, it's probably because no autoservice
+ * was running to begin with.
+ *
+ * \return This function returns the result of the ast_autoservice_stop()
+ * function as a boolean to its lua caller.
+ */
+static int lua_autoservice_stop(lua_State *L)
+{
+	struct ast_channel *chan;
+	int res;
+
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	res = ast_autoservice_stop(chan);
+
+	lua_pushboolean(L, 0);
+	lua_setfield(L, LUA_REGISTRYINDEX, "autoservice");
+
+	lua_pushboolean(L, !res);
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Get the status of the autoservice flag (for access
+ * from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * \return This function returns the status of the autoservice flag as a
+ * boolean to its lua caller.
+ */
+static int lua_autoservice_status(lua_State *L)
+{
+	lua_getfield(L, LUA_REGISTRYINDEX, "autoservice");
+	return 1;
+}
+
+/*!
+ * \brief [lua_CFunction] Check if this channel has been hungup or not (for
+ * access from lua, don't call directly)
+ *
+ * \param L the lua_State to use
+ *
+ * \return This function returns true if the channel was hungup
+ */
+static int lua_check_hangup(lua_State *L)
+{
+	struct ast_channel *chan;
+	lua_getfield(L, LUA_REGISTRYINDEX, "channel");
+	chan = lua_touserdata(L, -1);
+	lua_pop(L, 1);
+
+	lua_pushboolean(L, ast_check_hangup(chan));
+	return 1;
+}
+
+#if 0
+/*!
+ * \brief [lua_CFunction] Handle lua errors (for access from lua, don't call
+ * directly)
+ *
+ * \param L the lua_State to use
+ */
+static int lua_error_function(lua_State *L)
+{
+	int message_index;
+
+	/* pass number arguments right through back to asterisk*/
+	if (lua_isnumber(L, -1)) {
+		return 1;
+	}
+
+	/* if we are here then we have a string error message, let's attach a
+	 * backtrace to it */
+	message_index = lua_gettop(L);
+
+	lua_getglobal(L, "debug");
+	lua_getfield(L, -1, "traceback");
+	lua_remove(L, -2); /* remove the 'debug' table */
+
+	lua_pushvalue(L, message_index);
+	lua_remove(L, message_index);
+
+	lua_pushnumber(L, 2);
+
+	lua_call(L, 2, 1);
+
+	return 1;
+}
+#endif
+
+/*!
+ * \brief Load the config file in to a buffer and execute the file
+ *
+ * \param L the lua_State to use
+ * \param size a pointer to store the size of the buffer
+ *
+ * \note The caller is expected to free the buffer at some point.
+ *
+ * \return a pointer to the buffer
+ */
+static char *lua_read_config_file(lua_State *L, long *size)
+{
+	FILE *f;
+	char *data;
+	char *path = alloca(strlen(config) + strlen(ast_config_AST_CONFIG_DIR) + 2);
+	sprintf(path, "%s/%s", ast_config_AST_CONFIG_DIR, config);
+
+	if (!(f = fopen(path, "r"))) {
+		lua_pushstring(L, "cannot open '");
+		lua_pushstring(L, path);
+		lua_pushstring(L, "' for reading: ");
+		lua_pushstring(L, strerror(errno));
+		lua_concat(L, 4);
+
+		return NULL;
+	}
+
+	fseek(f, 0l, SEEK_END);
+	*size = ftell(f);
+
+	fseek(f, 0l, SEEK_SET);
+
+	if (!(data = ast_malloc(*size))) {
+		*size = 0;
+		fclose(f);
+		lua_pushstring(L, "not enough memory");
+		return NULL;
+	}
+
+	if (fread(data, sizeof(char), *size, f) != *size) {
+		ast_log(LOG_WARNING, "fread() failed: %s\n", strerror(errno));
+	}
+	fclose(f);
+
+	if (luaL_loadbuffer(L, data, *size, config)
+			|| lua_pcall(L, 0, LUA_MULTRET, 0)) {
+		ast_free(data);
+		data = NULL;
+		*size = 0;
+	}
+	return data;
+}
+
+/*!
+ * \brief Load the config file from the internal buffer
+ *
+ * \param L the lua_State to use
+ *
+ * This function also sets up some constructs used by the extensions.lua file.
+ * In the event of an error, an error string will be pushed onto the lua stack.
+ *
+ * \retval 0 success
+ * \retval 1 failure
+ */
+static int lua_load_config(lua_State *L)
+{
+	luaL_openlibs(L);
+
+	/* create the tests table */
+	lua_newtable(L);
+	lua_setfield(L, LUA_GLOBALSINDEX, "tests");
+
+	/* load the config file */
+	ast_mutex_lock(&config_file_lock);
+	if (luaL_loadbuffer(L, config_file_data, config_file_size, config)
+			|| lua_pcall(L, 0, LUA_MULTRET, 0)) {
+		ast_mutex_unlock(&config_file_lock);
+		return 1;
+	}
+	ast_mutex_unlock(&config_file_lock);
+
+	/* now we setup special tables and functions */
+
+	lua_create_app_table(L);
+	lua_create_channel_table(L);
+
+	lua_create_variable_metatable(L);
+	lua_create_application_metatable(L);
+
+	lua_create_autoservice_functions(L);
+	lua_create_hangup_function(L);
+
+	return 0;
+}
+
+/*!
+ * \brief Reload the config file and update the internal buffers if it
+ * loads correctly.
+ *
+ * \warning This function should not be called on a lua_State returned from
+ * lua_get_state().
+ *
+ * \param L the lua_State to use (must be freshly allocated with
+ * luaL_newstate(), don't use lua_get_state())
+ */
+static int lua_reload_config(lua_State *L)
+{
+	long size = 0;
+	char *data = NULL;
+
+	luaL_openlibs(L);
+
+	if (!(data = lua_read_config_file(L, &size))) {
+		return 1;
+	}
+
+	ast_mutex_lock(&config_file_lock);
+
+	if (config_file_data)
+		ast_free(config_file_data);
+
+	config_file_data = data;
+	config_file_size = size;
+
+	ast_mutex_unlock(&config_file_lock);
+	return 0;
+}
+
+/*!
+ * \brief Free the internal config file buffer.
+ */
+static void lua_free_config()
+{
+	ast_mutex_lock(&config_file_lock);
+	config_file_size = 0;
+	ast_free(config_file_data);
+	ast_mutex_unlock(&config_file_lock);
+}
+
+/*!
+ * \brief Get a new lua state.
+ *
+ * The state that is returned will be initilized with all of the required
+ * asttest objects.
+ *
+ * \return a lua_State
+ */
+static lua_State *lua_get_state()
+{
+	lua_State *L = luaL_newstate();
+	if (!L) {
+		ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
+		return NULL;
+	}
+
+	if (lua_load_config(L)) {
+		const char *error = lua_tostring(L, -1);
+		ast_log(LOG_ERROR, "Error loading %s: %s\n", config, error);
+		lua_close(L);
+		return NULL;
+	}
+	return L;
+}
+
+
+static int load_or_reload_lua_stuff(void)
+{
+	int res = AST_MODULE_LOAD_SUCCESS;
+
+	lua_State *L = luaL_newstate();
+	if (!L) {
+		ast_log(LOG_ERROR, "Error allocating lua_State, no memory\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (lua_reload_config(L)) {
+		const char *error = lua_tostring(L, -1);
+		ast_log(LOG_ERROR, "Error loading %s: %s\n", config, error);
+		res = AST_MODULE_LOAD_DECLINE;
+	}
+
+	lua_close(L);
+	return res;
+}
+
+static int unload_module(void)
+{
+	lua_free_config();
+	return 0;
+}
+
+static int reload(void)
+{
+	return load_or_reload_lua_stuff();
+}
+
+static int load_module(void)
+{
+	int res;
+
+	if ((res = load_or_reload_lua_stuff()))
+		return res;
+
+	ast_manager_register2("RunTest", EVENT_FLAG_ASTTEST, manager_run_test, "Execute the specified test", manager_run_test_help);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asttest Framework",
+		.load = load_module,
+		.unload = unload_module,
+		.reload = reload,
+	       );
+

Propchange: team/mnicholson/asttest/res/res_asttest.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/mnicholson/asttest/res/res_asttest.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: team/mnicholson/asttest/res/res_asttest.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the asterisk-commits mailing list