🐳 chore(工具): 增加 启动 脚本

develop
xiaojin 5 years ago
parent 7f4359e75d
commit f02ccab557

@ -1,3 +1,7 @@
删除所有 容器和镜像
docker stop $(docker ps -a -q) && docker system prune --all --force
代码安全指南: 代码安全指南:
https://github.com/Tencent/secguide https://github.com/Tencent/secguide

@ -1099,7 +1099,7 @@ update_iter(lua_State *L, int world_index, int lua_index, struct group_iter *ite
id = entity_sibling_index_(iter->world, dead_tag, 0, k->id); id = entity_sibling_index_(iter->world, dead_tag, 0, k->id);
entity_disable_tag_(iter->world, dead_tag, 0, dead_tag); entity_disable_tag_(iter->world, dead_tag, 0, dead_tag);
} else { } else {
id = entity_new_(iter->world, k->id, NULL, L, world_index); id = entity_new_(iter->world, k->id, NULL, L, world_index) + 1;
} }
if (c->stride == STRIDE_LUA) { if (c->stride == STRIDE_LUA) {
if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) { if (lua_getiuservalue(L, world_index, k->id * 2 + 2) != LUA_TTABLE) {
@ -1180,7 +1180,10 @@ read_component_in_field(lua_State *L, int lua_index, const char *name, int n, st
return; return;
} }
if (lua_getfield(L, lua_index, name) != LUA_TTABLE) { if (lua_getfield(L, lua_index, name) != LUA_TTABLE) {
luaL_error(L, ".%s is missing", name); lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setfield(L, lua_index, name);
} }
read_component(L, n , f, lua_gettop(L), buffer); read_component(L, n , f, lua_gettop(L), buffer);
lua_pop(L, 1); lua_pop(L, 1);

@ -0,0 +1,35 @@
local ecs = require "ecs"
local w = ecs.world()
w:register {
name = "vector",
"x:float",
"y:float",
}
w:register {
name = "text",
type = "lua",
}
w:register {
name = "mark"
}
w:new {
vector = { x = 1, y = 2 },
text = "Hello World",
mark = true,
}
for v in w:select "mark" do
w:readall(v)
for k,v in pairs(v) do
print(k,v)
end
end

@ -58,6 +58,32 @@ local function cache_world(obj, k)
return desc return desc
end end
local function gen_all_pat()
local desc = {}
local i = 1
for name,t in pairs(c.typenames) do
local a = {
name = t.name,
id = t.id,
type = t.type,
opt = true,
r = true,
}
table.move(t, 1, #t, 1, a)
desc[i] = a
i = i + 1
end
return desc
end
setmetatable(c, { __index = function(_, key)
if key == "all" then
local all = k:_groupiter(gen_all_pat())
c.all = all
return all
end
end })
local function gen_select_pat(pat) local function gen_select_pat(pat)
local typenames = c.typenames local typenames = c.typenames
local desc = {} local desc = {}
@ -190,6 +216,7 @@ do -- newtype
function M:register(typeclass) function M:register(typeclass)
local name = assert(typeclass.name) local name = assert(typeclass.name)
local ctx = context[self] local ctx = context[self]
ctx.all = nil -- clear all pattern
local typenames = ctx.typenames local typenames = ctx.typenames
local id = ctx.id + 1 local id = ctx.id + 1
assert(typenames[name] == nil and id <= ecs._MAXTYPE) assert(typenames[name] == nil and id <= ecs._MAXTYPE)
@ -314,6 +341,12 @@ function M:sync(pat, iter)
return iter return iter
end end
function M:readall(iter)
local p = context[self].all
self:_sync(p, iter)
return iter
end
function M:clear(name) function M:clear(name)
local id = assert(context[self].typenames[name].id) local id = assert(context[self].typenames[name].id)
self:_clear(id) self:_clear(id)

@ -0,0 +1,12 @@
# linux script framework header file
# Longwei Lai
###################################################################
____FWK_SCRIPT_PATH="$(cd "${MISC_SCRIPTS_PATH}" && pwd)"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_log.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_progress.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_faketime.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_uwsgi.sh"

@ -0,0 +1,12 @@
# linux script framework some common macros define
# Longwei Lai
###################################################################
# server options path, all server options save in here
____FWK_SVR_OPTIONS_PATH="${SVR_PATH}/.options"
# all server option files path define
____FWK_SVR_DEBUG_OPTION_FILE="${____FWK_SVR_OPTIONS_PATH}/debug_option"
____FWK_SVR_FAKETIME_OPTION_FILE="${____FWK_SVR_OPTIONS_PATH}/faketime_option"
____FWK_SVR_FAKETIME_BEGIN_OPTION_FILE="${____FWK_SVR_OPTIONS_PATH}/faketime_begin_option"

@ -0,0 +1,60 @@
# fake time about functions encapsulation
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_defs.sh"
# read current fake time setting
# arguments:
# N/A
# returns:
# echo style:
# faketime or "no faketime"
# return style:
# N/A
function read_faketime()
{
local faketime=
if [ -f "${____FWK_SVR_FAKETIME_OPTION_FILE}" ]; then
faketime="`cat "${____FWK_SVR_FAKETIME_OPTION_FILE}"`"
fi
if [ -z "${faketime}" ]; then
echo -n "no faketime"
return
fi
local now_time="`date "+%Y-%m-%d %H:%M:%S"`"
local faketime_begin=
if [ -f "${____FWK_SVR_FAKETIME_BEGIN_OPTION_FILE}" ]; then
faketime_begin="`cat "${____FWK_SVR_FAKETIME_BEGIN_OPTION_FILE}"`"
else
faketime_begin="${now_time}"
echo -n "${faketime_begin}" > "${____FWK_SVR_FAKETIME_BEGIN_OPTION_FILE}"
fi
local now_ts="`date -d "${now_time}" '+%s'`"
local faketime_begin_ts="`date -d "${faketime_begin}" '+%s'`"
local diff_time=$((${now_ts} - ${faketime_begin_ts}))
local faketime_ts="`date -d "${faketime}" '+%s'`"
local now_faketime_ts=$((${faketime_ts} + ${diff_time}))
local real_faketime="`date -d "@${now_faketime_ts}" '+%Y-%m-%d %H:%M:%S'`"
echo -n "${real_faketime}"
}
# clear fake time setting
# arguments:
# N/A
# returns:
# echo style:
# N/A
# return style:
# N/A
function clear_faketime()
{
if [ -f "${____FWK_SVR_FAKETIME_OPTION_FILE}" ]; then
echo -n "" > "${____FWK_SVR_FAKETIME_OPTION_FILE}"
fi
}

@ -0,0 +1,18 @@
# log about function encapsulation
# Longwei Lai
###################################################################
function log_info()
{
echo "$@"
}
function log_warn()
{
echo -e "\e[1;33m$@\e[0m"
}
function log_err()
{
echo -e "\e[1;31m$@\e[0m"
}

@ -0,0 +1,27 @@
# linux script framework path about function encapsulation
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_log.sh"
if [ -z "${SVR_PATH}" ]; then
log_err "please define SVR_PATH first"
exit 1
fi
____FWK_PID_SUFFIX=pid
____FWK_NOHUP_SUFFIX=nohup
# make server pid file path
function make_server_pid_file_path()
{
local svr_name_or_path="$1"
local path_part="`dirname "${svr_name_or_path}"`"
local name_part="`basename "${svr_name_or_path}"`"
if [ "${path_part}" = "." ]; then
path_part="${SVR_PATH}"
fi
echo -n "${path_part}/.${name_part}.${____FWK_PID_SUFFIX}"
}

@ -0,0 +1,71 @@
# linux script framework progress about functions encapsulation
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_log.sh"
# check specific progress is running or not
function is_progress_running()
{
local pid="$1"
local pids="`top -b -n1 | sed -e 's/\s*\(.*\)/\1/; s/\([0-9]*\).*/\1/; /^$/d' | tr -s '\n' " "`"
for now_pid in $pids; do
if [ "$now_pid" -eq "${pid}" ]; then
echo -n "TRUE"
return
fi
done
echo -n "FALSE"
}
# kill specific progress(included child progresses)
function kill_progress()
{
# kill child processes
local will_kill_pid="$1"
for child in $(ps -o pid --no-headers --ppid ${will_kill_pid}); do
kill -9 "${child}" 1>/dev/null 2>&1
done
# kill self
kill -9 "${will_kill_pid}" 1>/dev/null 2>&1
}
# check specific pidfile's progress is running or not
function is_pidfile_running()
{
local pid_file="$1"
if [ ! -e "${pid_file}" ]; then
echo -n "FALSE"
return
fi
local pid="`cat "${pid_file}"`"
if [ $(is_progress_running "${pid}") = "FALSE" ]; then
rm -rf "${pid_file}" 1>/dev/null 2>&1
echo -n "FALSE"
return
fi
echo -n "TRUE"
}
# kill specific pidfile's progress(included child progresses)
function stop_pidfile()
{
local pid_file="$1"
if [ "$(is_pidfile_running "${pid_file}")" = "TRUE" ]; then
pid="`cat "${pid_file}"`"
kill_progress "$pid"
sleep 1.618
fi
if [ "$(is_pidfile_running "${pid_file}")" = "TRUE" ]; then
echo "stop pidfile failed"
stop_pidfile "${pid_file}"
else
rm -rf "${pid_file}"
fi
}

@ -0,0 +1,230 @@
# linux script framework server about functions encapsulation
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_log.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_defs.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_progress.sh"
# control commands define
____FWK_SERVER_CTRL_START=start
____FWK_SERVER_CTRL_STOP=stop
____FWK_SERVER_CTRL_RESTART=restart
____FWK_SERVER_CTRL_IS_RUNNING=is_running
____FWK_SERVER_CTRL_LIST_STATUS=list_status
# ctrl specific server
# arguments:
# arg1: server name
# arg2: server control cmd
# returns:
# echo style:
# N/A
# return style:
# 0: success
# 1: failed
function ctrl_server()
{
local svr_name="$1"
local ctrl_cmd="$2"
local args=($@)
local svr_args=${args[@]:2}
if [ "${ctrl_cmd}" = "${____FWK_SERVER_CTRL_START}" ]; then
start_server "${svr_name}" ${svr_args}
elif [ "${ctrl_cmd}" = "${____FWK_SERVER_CTRL_STOP}" ]; then
stop_server "${svr_name}" ${svr_args}
elif [ "${ctrl_cmd}" = "${____FWK_SERVER_CTRL_RESTART}" ]; then
restart_server "${svr_name}" ${svr_args}
elif [ "${ctrl_cmd}" = "${____FWK_SERVER_CTRL_IS_RUNNING}" ]; then
is_server_running "${svr_name}" ${svr_args}
elif [ "${ctrl_cmd}" = "${____FWK_SERVER_CTRL_LIST_STATUS}" ]; then
list_server_status "${svr_name}" ${svr_args}
else
log_err "unknown ctrl command: ${ctrl_cmd}"
return 1
fi
}
# start specific server
# arguments:
# arg1: server name
# arg2: server entry
# arg3-n: server start arguments
# returns:
# echo style:
# N/A
# return style:
# 0: success
# 1: failed
# 2: already start, don't need start again
function start_server()
{
local svr_name="$1"
local svr_entry="$2"
local show_name="$(get_server_show_name "${svr_name}")"
local pid_file="$(make_server_pid_file_path "${svr_name}")"
local args=($@)
local svr_args="${args[@]:2}"
# switch current directory to server path
local old_path="`pwd`"
cd "`dirname "${svr_path}"`"
# makesure just running one instance
if [ "$(is_server_running "${svr_name}")" = TRUE ]; then
log_warn "${show_name} is running(pid: `cat "${pid_file}"`), don't need start!"
cd "${old_path}"
return 2
fi
# start server
local nohup_file=""`dirname "${svr_path}"`"/."${svr_name}".${____FWK_NOHUP_SUFFIX}"
nohup ${svr_entry} ${svr_args} 1>"${nohup_file}" 2>&1 &
local svr_pid=$!
if [ $? -ne 0 ]; then
log_err "start ${show_name} failed, return code: $?, timeout?: ${check_timeout}"
return 1
fi
echo -n "${svr_pid}" > "${pid_file}"
# back to old path
cd "${old_path}"
log_info "start ${show_name} success, pid: ${svr_pid}"
}
# stop specific server
# arguments:
# arg1: server name
# returns:
# echo style:
# N/A
# return style:
# N/A
function stop_server()
{
local svr_name="$1"
local pid_file="$(make_server_pid_file_path "${svr_name}")"
local server_running=`is_server_running "${svr_name}"`
if [ "${server_running}" = TRUE ]; then
stop_pidfile "${pid_file}"
fi
if [ "${server_running}" = TRUE ]; then
local nohup_file=""`dirname "${pid_file}"`"/."${svr_name}".${____FWK_NOHUP_SUFFIX}"
if [ -f "${nohup_file}" ]; then
log_info "Found ${svr_name} nohup file, backup it"
local backup_nohup_file="${nohup_file}.`date "+%m%d%H%M%S"`"
\cp -f "${nohup_file}" "${backup_nohup_file}"
fi
fi
}
# restart specific server
# arguments:
# arg1: server name
# returns:
# echo style:
# N/A
# return style:
# N/A
function restart_server()
{
stop_server $@
start_server $@
}
# check specific server is running or not
# arguments:
# arg1: server name
# returns:
# echo style:
# TRUE: server running
# FALSE: server not running
# return style:
# N/A
function is_server_running()
{
local svr_name="$1"
local pid_file="$(make_server_pid_file_path "${svr_name}")"
echo -n "$(is_pidfile_running "${pid_file}")"
}
# list specific server status
# arguments:
# arg1: server name
# returns:
# echo style:
# N/A
# return style:
# N/A
function list_server_status()
{
local svr_name="$1"
echo -n -e "${svr_name} status: "
if [ "$(is_server_running "${svr_name}")" = "TRUE" ]; then
local pid_file="$(make_server_pid_file_path "${svr_name}")"
echo -n "Running(pid: "
echo "`cat "${pid_file}"`)"
else
echo "Stopped"
fi
}
# get server debug option
# arguments:
# arg1: server name
# returns:
# echo style:
# TRUE: build with debug
# FALSE: build with release
# return style:
# N/A
function get_server_debug_option()
{
# get debug option(from option directory)
local dbg_opt_file="${____FWK_SVR_DEBUG_OPTION_FILE}"
if [ ! -e "${dbg_opt_file}" ]; then
echo -n FALSE
return
fi
local dbg_opt="`cat "${dbg_opt_file}"`"
if [ "${dbg_opt}" != TRUE ]; then
echo -n FALSE
else
echo -n TRUE
fi
}
# get server show name(appended all server options)
# arguments:
# arg1: server name
# returns:
# echo style:
# <server show name>: the server show name, appended all server options
# return style:
# N/A
function get_server_show_name()
{
local svr_name="$1"
local show_name="${svr_name}("
# append debug/release option
local dbg_opt="$(get_server_debug_option "${svr_name}")"
if [ "${dbg_opt}" = TRUE ]; then
show_name+="debug"
else
show_name+="release"
fi
show_name+=")"
echo -n "${show_name}"
}

@ -0,0 +1,139 @@
# linux script framework uwsgi about function encapsulation
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_log.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_progress.sh"
____UWSGI_PID_FILE=${WEBSVR_PATH}/.uwsgi.pid
____UWSGI_FIFO_FILE=${WEBSVR_PATH}/.uwsgi_master_fifo
____UWSGI_CFG_FILE="${WEBSVR_PATH}/uwsgi.ini"
____UWSGI_LOG_PATH="${WEBSVR_PATH}/logs"
____UWSGI_LOG_FILE_NAME="${____UWSGI_LOG_PATH}/.webserver_`date "+%m_%d_%H_%M_%S.nohuplog"`"
# makesure log path created
# !!! comment for onemt
# mkdir -p "${____UWSGI_LOG_PATH}"
# start uwsgi
function start_uwsgi()
{
if [ `is_uwsgi_running` = TRUE ]; then
log_err "uwsgi running(pid:`get_uwsgi_pid`), please stop first"
return 1
fi
# enter working directory
local old_pwd="`pwd`"
if [ "${old_pwd}" != "${WEBSVR_PATH}" ]; then
cd "${WEBSVR_PATH}"
fi
# startup uwsgi
nohup uwsgi "${____UWSGI_CFG_FILE}" >>"${____UWSGI_LOG_FILE_NAME}" 2>&1 &
local uwsgi_pid=$!
# check startup or not
local ret_code=$?
if [ ${ret_code} -ne 0 ]; then
log_err "start uwsgi failed, return code: ${ret_code}"
cd "${old_pwd}"
return 1
fi
# store uwsgi parent process pid to pid file.
echo -n ${uwsgi_pid} > "${____UWSGI_PID_FILE}"
cd "${old_pwd}"
log_info "uwsgi started, master progress pid: ${uwsgi_pid}"
}
# stop uwsgi
function stop_uwsgi()
{
if [ `is_uwsgi_running` = FALSE ]; then
log_err 'uwsgi not running, stop failed!'
return 1
fi
local uwsgi_pid=`get_uwsgi_pid`
stop_pidfile "${____UWSGI_PID_FILE}"
log_info "uwsgi(pid:${uwsgi_pid}) stopped!"
}
# 重新载入py代码(在代码有修改后调用,将完成所有workers的优雅重启)
function reload_uwsgi()
{
reload_uwsgi_check
if [ $? != 0 ]; then
return 1
fi
# Send SIGHUP to reload.
# local uwsgi_pid=`get_uwsgi_pid`
# kill -SIGHUP ${uwsgi_pid}
# Write 'r' command to fifo file
echo 'r' > "${____UWSGI_FIFO_FILE}"
log_info 'all uwsgi workers reloaded!'
}
function chain_reload_uwsgi()
{
reload_uwsgi_check
if [ $? != 0 ]; then
return 1
fi
# Write 'c' command to fifo file
echo 'c' > "${____UWSGI_FIFO_FILE}"
sleep 6.18
log_info 'all uwsgi workers reloaded!'
}
function reload_uwsgi_check()
{
if [ `is_uwsgi_running` = FALSE ]; then
log_warn "uwsgi not start, will startup first"
start_uwsgi
if [ $? != 0 ]; then
return 1
fi
log_info 'wait 5 seconds to wait all uwsgi workers startup done...'
sleep 5
fi
return 0
}
function show_uwsgi_status()
{
if [ `is_uwsgi_running` = TRUE ]; then
log_info "uwsgi running, pid:`get_uwsgi_pid`"
echo 's' > "${____UWSGI_FIFO_FILE}"
else
log_info "uwsgi stopped!"
fi
}
# 确认uwsgi是否在运行中
function is_uwsgi_running()
{
is_pidfile_running "${____UWSGI_PID_FILE}"
}
# 取得uwsgi pid
function get_uwsgi_pid()
{
if [ `is_uwsgi_running` != "TRUE" ]; then
echo -n "0"
else
echo -n "`cat "${____UWSGI_PID_FILE}"`"
fi
}

@ -0,0 +1,16 @@
# linux script logic header file
# Longwei Lai
###################################################################
____LOGIC_SCRIPT_PATH="$(cd "${MISC_SCRIPTS_PATH}" && pwd)"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_misc.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverupdate.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverbuild.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverctrl.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_servertar.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverhotfix.sh"
# source "${____LOGIC_SCRIPT_PATH}/lnx_logic_mcctrl.sh"

@ -0,0 +1,31 @@
# linux script logic macros define
# Longwei Lai
###################################################################
# server names define
____LOGIC_LOGIN=loginserver
____LOGIC_GLOBAL=globalserver
____LOGIC_GAME=gameserver
____LOGIC_WEB=webserver
# server start files define
____START_FILE_LOGIN=.startsuccess_login
____START_FILE_GLOBAL=.startsuccess_global
____START_FILE_GAME=.startsuccess_game
____START_FILE_WEB=.startsuccess_web
# server start/stop result files define
____START_RESULT_FILE_LOGIN=.startresult_login
____START_RESULT_FILE_GLOBAL=.startresult_global
____START_RESULT_FILE_GAME=.startresult_game
____START_RESULT_FILE_WEB=.startresult_web
# game server some max-wait time define
____GAME_SERVER_START_MAX_WAIT=7200
____GAME_SERVER_STOP_MAX_WAIT=7200
# server http key, use to sign message
____SERVER_HTTP_KEY="cGyZCP2QXgG#"
# server start config file name
____APP_START_CONF_NAME=.app.lnx.cfg

@ -0,0 +1,147 @@
# linux script logic misc functions encapsulation
# Longwei Lai
###################################################################
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
function list_help()
{
# foreground/background: 3:foreground 4:background
# colors comments: 0:black 1:red 2:green 3:yellow 4:blue 5:purple 6:sky-blue 7:white
# reset all: \e[0m
clear
echo -e "\e[1;35musage: ./run <command> [command-options...]\e[0m"
echo -e "\e[1;37m-----------------------------------------------------------------------\e[0m"
echo -e " \e[1;32mserver build/tarball commands:\e[0m"
echo -e " \e[1;33mup\e[0m: update from git"
echo -e " \e[1;33mver_info\e[0m: show version info"
echo -e " \e[1;33mswitch_branch\e[0m: <\e[1;37mthe_new_branch\e[0m>: switch branch"
echo -e " \e[1;33mmake\e[0m/\e[1;33mbuild\e[0m <\e[1;37mdebug\e[0m/\e[1;37mrelease\e[0m> <\e[1;37mproduct\e[0m/\e[1;37minternal\e[0m/\e[1;37mpressure_test\e[0m\e[0m>: "
echo " build servers, default build release version servers"
echo " -- default use release setting to build"
echo " -- default use product setting to build"
echo -e " \e[1;33mtar\e[0m <\e[1;37mdebug\e[0m/\e[1;37mrelease\e[0m> <\e[1;37mproduct\e[0m/\e[1;37minternal\e[0m/\e[1;37mpressure_test\e[0m> <\e[1;37mfull\e[0m/\e[1;37mpure\e[0m>: tarball servers"
echo " tarball servers default build all versions servers"
echo " -- default use release setting to build"
echo " -- default use product setting to build"
echo " -- default use pure setting to puild"
echo -e "\e[1;37m-----------------------------------------------------------------------\e[0m"
echo -e " \e[1;32mserver control commands:\e[0m"
echo -e " \e[1;33mstart_xxx\e[0m: start server"
echo -e " start specific server:"
echo -e " \e[1;33mstop_xxx\e[0m: stop server"
echo -e " \e[1;33msafestop_xxx\e[0m: safe stop server"
echo -e " \e[1;33mrestart_xxx\e[0m: restart server"
echo -e " \e[1;33mset_xxx_logic_status\e[0m: set server logic status(progress independent)"
echo -e " \e[1;32mserver types:\e[0m"
echo -e " * \e[1;36mgameserver:\e[0m game server, for get more informations about gameserver, run \e[1;37m./run help_gameserver\e[0m"
echo -e " * \e[1;36mloginserver:\e[0m login server, for get more informations about loginserver, run \e[1;37m./run help_loginserver\e[0m"
echo -e "\e[1;37m-----------------------------------------------------------------------\e[0m"
echo -e " \e[1;32mfaketime commands:\e[0m"
echo -e " \e[1;33mread_faketime\e[0m: read now fake time setting"
echo -e " \e[1;33mclear_faketime\e[0m: clear fake time setting"
echo -e "\e[1;37m-----------------------------------------------------------------------\e[0m"
echo -e " \e[1;32mtest commands:\e[0m"
echo " ...."
}
function list_help_gameserver()
{
clear
echo -e "\e[1;35mgameserver manaual page:\e[0m"
echo -e "\e[1;37m-----------------------------------------------------------------------\e[0m"
echo -e " \e[1;33mstart_gameserver\e[0m: <\e[1;37mfakedate=xxxx-xx-xx\e[0\e[0m> <\e[1;37mfaketime=xx:xx:xx\e[0m> <\e[1;37mdontfaketime=YES/NO\e[0m>"
echo -e " \e[0;32mstart gameserver\e[0m"
echo -e " \e[1;37mfakedate=xxxx-xx-xx\e[0m: fake date start, eg: 2017-08-08"
echo -e " \e[1;37mfaketime=xx:xx:xx\e[0m: fake time start, eg: 08-30-59"
echo -e " \e[1;37mdontfaketime=YES/NO\e[0m: use fake time setting or not, default is NO"
echo -e " \e[1;33mstop_gameserver\e[0m:"
echo -e " \e[0;32mstop gameserver, for now, don't need any arguments to specific\e[0m"
echo -e " \e[1;33mrestart_gameserver\e[0m: <\e[1;37mfakedate=xxxx-xx-xx\e[0\e[0m> <\e[1;37mfaketime=xx:xx:xx\e[0m> <\e[1;37mdontfaketime=YES/NO\e[0m>"
echo -e " \e[0;32mrestart gameserver\e[0m"
echo -e " \e[1;37mfakedate=xxxx-xx-xx\e[0m: fake date start, eg: 2017-08-08"
echo -e " \e[1;37mfaketime=xx:xx:xx\e[0m: fake time start, eg: 08-30-59"
echo -e " \e[1;37mdontfaketime=YES/NO\e[0m: use fake time setting or not, default is NO"
echo -e " \e[1;33msafestop_gameserver\e[0m:"
echo -e " \e[0;32mlike stop_gameserver, but this command will safety stop gameserver(all game data will be saved before gameservers killed)\e[0m"
echo -e " \e[1;33mgameserver_status\e[0m:"
echo -e " \e[0;32mlist gameserver status\e[0m"
echo -e " \e[1;33mgameserver_logic_status\e[0m:"
echo -e " \e[0;32mlist gameserver logic status(progress independent)\e[0m"
echo -e " \e[1;33mset_gameserver_logic_status\e[0m:"
echo -e " \e[0;32mset gameserver logic status(progress independent)\e[0m"
}
function list_help_loginserver()
{
clear
echo -e "\e[1;35mloginserver manaual page:\e[0m"
echo -e "\e[1;37m-----------------------------------------------------------------------\e[0m"
echo -e " \e[1;33mstart_loginserver\e[0m: <\e[1;37mfakedate=xxxx-xx-xx\e[0\e[0m> <\e[1;37mfaketime=xx:xx:xx\e[0m> <\e[1;37mdontfaketime=YES/NO\e[0m>"
echo -e " \e[0;32mstart loginserver\e[0m"
echo -e " \e[1;37mfakedate=xxxx-xx-xx\e[0m: fake date start, eg: 2017-08-08"
echo -e " \e[1;37mfaketime=xx:xx:xx\e[0m: fake time start, eg: 08-30-59"
echo -e " \e[1;37mdontfaketime=YES/NO\e[0m: use fake time setting or not, default is NO"
echo -e " \e[1;33mstop_loginserver\e[0m:"
echo -e " \e[0;32mstop loginserver, for now, don't need any arguments to specific\e[0m"
echo -e " \e[1;33mrestart_loginserver\e[0m: <\e[1;37mfakedate=xxxx-xx-xx\e[0\e[0m> <\e[1;37mfaketime=xx:xx:xx\e[0m> <\e[1;37mdontfaketime=YES/NO\e[0m>"
echo -e " \e[0;32mrestart loginserver\e[0m"
echo -e " \e[1;37mfakedate=xxxx-xx-xx\e[0m: fake date start, eg: 2017-08-08"
echo -e " \e[1;37mfaketime=xx:xx:xx\e[0m: fake time start, eg: 08-30-59"
echo -e " \e[1;37mdontfaketime=YES/NO\e[0m: use fake time setting or not, default is NO"
echo -e " \e[1;33msafestop_loginserver\e[0m:"
echo -e " \e[0;32mlike stop_loginserver, but this command will safety stop loginserver(all game data will be saved before loginservers killed)\e[0m"
echo -e " \e[1;33mloginserver_status\e[0m:"
echo -e " \e[0;32mlist loginserver status\e[0m"
echo -e " \e[1;33mloginserver_logic_status\e[0m:"
echo -e " \e[0;32mlist loginserver logic status(progress independent)\e[0m"
echo -e " \e[1;33mset_loginserver_logic_status\e[0m:"
echo -e " \e[0;32mset loginserver logic status(progress independent)\e[0m"
}
function env_ensure()
{
# force source /etc/profile
source /etc/profile
# add SVR_PATH to LD_LIBRARY_PATH variable and export it
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${SVR_PATH}"
# setup linux sys listen backlog size(dynamic change) to ${willsetbacklog}
local willsetbacklog=4096
local smcfile=/proc/sys/net/core/somaxconn
if [ -w "${smcfile}" ]; then
local oldbacklog=`cat "${smcfile}"`
if [ "$oldbacklog" -lt "${willsetbacklog}" ]; then
log_warn "system listen backlog config[${oldbacklog}] too small, try to config to 4096..."
echo -n ${willsetbacklog} > "${smcfile}"
local retcode=$?
if [ "${retcode}" -ne 0 ]; then
log_err "modify listen backklog size failed, permission deny? return code: ${retcode}"
exit $retcode
fi
fi
fi
# setup max open file limits to 65535
ulimit -n 65535
retcode=$?
if [ "${retcode}" -ne 0 ]; then
echo "mofify max open file size failed, return code: ${retcode}"
exit $retcode
fi
# setup coredump file limits to ulimited
ulimit -c unlimited
retcode=$?
if [ "${retcode}" -ne 0 ]; then
echo "mofify coredump file limits to ulimited failed, return code: ${retcode}"
exit $retcode
fi
export SKYNET_THREAD=`cat /proc/cpuinfo | grep "processor" |wc -l`
echo "SKYNET_THREAD is" ${SKYNET_THREAD}
}

@ -0,0 +1,65 @@
# linux script logic server build(included tarball) about functions encapsulation
# Longwei Lai
###################################################################
source "${____LOGIC_SCRIPT_PATH}/lnx_fwk_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
# return make server
function make_server()
{
local debug_opt=FALSE
local isdebug_prompt=release
local make_flags="config=release"
local debug_flag="`echo -n "$1" | tr [:lower:] [:upper:]`"
if [ "${debug_flag}" = "DEBUG" -o "${debug_flag}" = "DBG" -o "${debug_flag}" = "D" ]; then
debug_opt=TRUE
isdebug_prompt=debug
make_flags="config=debug"
fi
local product_opt="product"
local premake_path="${____LOGIC_SCRIPT_PATH}/"
local internal_flag="`echo -n "$2" | tr [:lower:] [:upper:]`"
if [ "${internal_flag}" = "INTERNAL" -o "${internal_flag}" = "INTERNAL_ENV" -o "${internal_flag}" = "INL" ]; then
product_opt="internal"
elif [ "${internal_flag}" = "PRESSURE_TEST" -o "${internal_flag}" = "PRESSURE_TEST_ENV" -o "${internal_flag}" = "PT" ]; then
product_opt="pressure_test"
fi
# stop all servers
log_info "stop all servers..."
stop_all_servers 1>/dev/null 2>&1
# make
log_info "making(${isdebug_prompt}, ${product_opt})..."
local make_log_path="${SVR_PATH}/.make_logs"
mkdir -p "${make_log_path}"
local make_log_file="${make_log_path}/make_log.`date '+%Y%m%d_%H%M%S_%N'`"
log_info "make log redirect to [${make_log_file}]..."
( cd "${SVR_PATH}" && make linux "${make_flags}" 2>&1 | tee "${make_log_file}" | grep -e '\(error\)\|\(warning\)'; exit ${PIPESTATUS[0]} )
if [ $? -ne 0 ]; then
log_err "make server failed"
return 1
fi
# generate option file
log_info "generate server options..."
mkdir -p "${____FWK_SVR_OPTIONS_PATH}"
echo -n ${debug_opt} > "${____FWK_SVR_DEBUG_OPTION_FILE}"
}
function make_project()
{
local projname="$1"
local make_flags="$2"
local make_log_file="$3"
log_info "building ${projname} project..."
( cd "${make_path}" && make -f "${projname}.make" "${make_flags}" 2>&1 | tee "${make_log_file}" | grep -v '): warning CS.*'; exit ${PIPESTATUS[0]} )
if [ $? -ne 0 ]; then
log_err "make failed(build ${projname} project failed)"
return 1
fi
}

@ -0,0 +1,37 @@
# linux script server config file about functions encapsulation.
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_server.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
# get game node id config from server config file
function get_serverconf_gamenode_id()
{
local dbconf_file="${SVR_PATH}/dbconf.lua"
echo -n "`sed -n -r 's/dbconf\.gamenodeid\s*=\s*([0-9]+).*/\1/p' "${dbconf_file}"`"
}
# get login node id config from server config file
function get_serverconf_loginnode_id()
{
local dbconf_file="${SVR_PATH}/dbconf.lua"
echo -n "`sed -n -r 's/dbconf\.loginnodeid\s*=\s*([0-9]+).*/\1/p' "${dbconf_file}"`"
}
# get login node id config from server config file
function get_serverconf_globalnode_id()
{
local dbconf_file="${SVR_PATH}/dbconf.lua"
echo -n "`sed -n -r 's/dbconf\.globalnodeid\s*=\s*([0-9]+).*/\1/p' "${dbconf_file}"`"
}
# get login node id config from server config file
function get_serverconf_webnode_id()
{
local dbconf_file="${SVR_PATH}/dbconf.lua"
echo -n "`sed -n -r 's/dbconf\.webnodeid\s*=\s*([0-9]+).*/\1/p' "${dbconf_file}"`"
}

@ -0,0 +1,699 @@
# linux script server control logic functions encapsulation.
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_server.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverhttp.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serversql.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverconf.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverstatus.sh"
function server_ctrl_cmd_adapt()
{
local args=($@)
local cmd=${args[0]}
local cmd_type=${cmd%%_*}
local server_type=${cmd#*_}
local ctrl_args=${args[@]:1}
if [ "${server_type}" = "status" -o "${server_type}" = "st" -o\
"${server_type}" = "logic_status" -o "${server_type}" = "logic_st" ]; then
local tmp=${cmd_type}
cmd_type=${server_type}
server_type=${tmp}
fi
case "${cmd_type}" in
start)
server_ctrl_start ${server_type} ${ctrl_args}
;;
stop)
server_ctrl_stop ${server_type} ${ctrl_args}
;;
safestop)
server_ctrl_safestop ${server_type} ${ctrl_args}
;;
restart)
server_ctrl_restart ${server_type} ${ctrl_args}
;;
status)
server_ctrl_status ${server_type} ${ctrl_args}
;;
logic_status)
server_ctrl_logic_status ${server_type} ${ctrl_args}
;;
set)
server_ctrl_set ${server_type} ${ctrl_args}
;;
*)
list_help
return 0
;;
esac
}
function server_ctrl_start()
{
local args=($@)
local server_type=${args[0]}
local start_args=${args[@]:1}
case "${server_type}" in
devilserver)
start_gameserver ${start_args}
;;
ruinsserver)
start_gameserver ${start_args}
;;
gameserver)
start_gameserver ${start_args}
;;
pyramidserver)
start_gameserver ${start_args}
;;
legendserver)
start_gameserver ${start_args}
;;
cloudserver)
start_gameserver ${start_args}
;;
loginserver)
start_loginserver ${start_args}
;;
globalserver)
start_globalserver ${start_args}
;;
webserver)
start_webserver ${start_args}
;;
*)
log_err "unknown server[${server_type}], start server failed"
return 1
;;
esac
}
function server_ctrl_stop()
{
local args=($@)
local server_type=${args[0]}
local stop_args=${args[@]:1}
case "${server_type}" in
devilserver)
stop_gameserver ${stop_args}
;;
ruinsserver)
stop_gameserver ${stop_args}
;;
gameserver)
stop_gameserver ${stop_args}
;;
pyramidserver)
stop_gameserver ${stop_args}
;;
legendserver)
stop_gameserver ${stop_args}
;;
cloudserver)
stop_gameserver ${stop_args}
;;
loginserver)
stop_loginserver ${stop_args}
;;
globalserver)
stop_globalserver ${stop_args}
;;
webserver)
stop_webserver ${stop_args}
;;
*)
log_err "unknown server[${server_type}], stop server failed"
return 1
;;
esac
}
function server_ctrl_safestop()
{
local args=($@)
local server_type=${args[0]}
local stop_args=${args[@]:1}
case "${server_type}" in
devilserver)
safestop_gameserver ${stop_args}
;;
ruinsserver)
safestop_gameserver ${stop_args}
;;
gameserver)
safestop_gameserver ${stop_args}
;;
pyramidserver)
safestop_gameserver ${stop_args}
;;
legendserver)
safestop_gameserver ${stop_args}
;;
cloudserver)
safestop_gameserver ${stop_args}
;;
loginserver)
safestop_loginserver ${stop_args}
;;
globalserver)
safestop_globalserver ${stop_args}
;;
webserver)
safestop_webserver ${stop_args}
;;
*)
log_err "unknown server[${server_type}], safestop server failed"
return 1
;;
esac
}
function server_ctrl_restart()
{
local args=($@)
local server_type=${args[0]}
local restart_args=${args[@]:1}
case "${server_type}" in
devilserver)
restart_gameserver ${restart_args}
;;
ruinsserver)
restart_gameserver ${restart_args}
;;
gameserver)
restart_gameserver ${restart_args}
;;
pyramidserver)
restart_gameserver ${restart_args}
;;
legendserver)
restart_gameserver ${restart_args}
;;
cloudserver)
restart_gameserver ${restart_args}
;;
loginserver)
restart_loginserver ${restart_args}
;;
globalserver)
restart_globalserver ${restart_args}
;;
webserver)
restart_webserver ${restart_args}
;;
*)
log_err "unknown server[${server_type}], restart server failed"
return 1
;;
esac
}
function server_ctrl_status()
{
local args=($@)
local server_type=${args[0]}
local list_args=${args[@]:1}
case "${server_type}" in
devilserver)
list_gameserver_status ${list_args}
;;
ruinsserver)
list_gameserver_status ${list_args}
;;
gameserver)
list_gameserver_status ${list_args}
;;
pyramidserver)
list_gameserver_status ${list_args}
;;
legendserver)
list_gameserver_status ${list_args}
;;
cloudserver)
list_gameserver_status ${list_args}
;;
loginserver)
list_loginserver_status ${list_args}
;;
globalserver)
list_globalserver_status ${list_args}
;;
webserver)
list_webserver_status ${list_args}
;;
*)
log_err "unknown server[${server_type}], restart server failed"
return 1
;;
esac
}
function server_ctrl_logic_status()
{
local args=($@)
local server_type=${args[0]}
local list_args=${args[@]:1}
case "${server_type}" in
gameserver)
echo "gameserver logic status:"
echo "`get_gameserver_logic_status`"
;;
pyramidserver)
echo "pyramidserver logic status:"
echo "`get_gameserver_logic_status`"
;;
legendserver)
echo "legendserver logic status:"
echo "`get_gameserver_logic_status`"
;;
devilserver)
echo "devilserver logic status:"
echo "`get_gameserver_logic_status`"
;;
cloudserver)
echo "cloudserver logic status:"
echo "`get_gameserver_logic_status`"
;;
*)
echo "${server_type} logic status: Unknown"
;;
esac
}
function server_ctrl_set()
{
local args=($@)
local server_type_and_set_type=${args[0]}
local list_args=${args[@]:1}
case "${server_type_and_set_type}" in
gameserver_logic_status|pyramidserver_logic_status|legendserver_logic_status|devilserver_logic_status)
set_gameserver_logic_status ${list_args}
;;
*)
log_info "for now, not support <${server_type_and_set_type}> command!"
;;
esac
}
function start_gameserver()
{
echo "game start config";
check_app_type "gs"
local args=$@
local server_entry="${FRAMEWORK_PATH}/skynet/skynet"
local start_file="${SVR_PATH}/${____START_FILE_GAME}"
local result_file="${SVR_PATH}/${____START_RESULT_FILE_GAME}"
start_server_and_wait "${____LOGIC_GAME}" "${server_entry}" "${____APP_START_CONF_NAME}" "${start_file}" "${result_file}" ${args}
}
function stop_gameserver()
{
local args=$@
stop_server "${____LOGIC_GAME}" ${args}
local start_file="${SVR_PATH}/${____START_FILE_GAME}"
remove_server_start_file "${start_file}"
}
function restart_gameserver()
{
local args=$@
stop_gameserver ${args}
echo "Sleep 5 second..."
sleep 5
start_gameserver ${args}
}
function list_gameserver_status()
{
local args=$@
list_server_status "${____LOGIC_GAME}" ${args}
}
function start_loginserver()
{
echo "login start config";
check_app_type "ls"
local args=$@
local server_entry="${FRAMEWORK_PATH}/skynet/skynet"
local start_file="${SVR_PATH}/${____START_FILE_LOGIN}"
local result_file="${SVR_PATH}/${____START_RESULT_FILE_LOGIN}"
start_server_and_wait "${____LOGIC_LOGIN}" "${server_entry}" "${____APP_START_CONF_NAME}" "${start_file}" "${result_file}" ${args}
}
function stop_loginserver()
{
local args=$@
stop_server "${____LOGIC_LOGIN}" ${args}
local start_file="${SVR_PATH}/${____START_FILE_LOGIN}"
remove_server_start_file "${start_file}"
}
function restart_loginserver()
{
local args=$@
stop_loginserver $args
echo "Sleep 5 second..."
sleep 5
start_loginserver $args
}
function list_loginserver_status()
{
local args=$@
list_server_status "${____LOGIC_LOGIN}" ${args}
}
function auto_update_db()
{
local arg_env=`cat ${____APP_START_CONF_NAME} | grep app_env | awk -F\" '{print $2}'`
if [[ ${arg_env} == "dev" ]] || [[ ${arg_env} == "sit" ]]; then
local dbconf_name=dbconf.lua
sh ${MISC_SCRIPTS_PATH}/lnx_logic_updatedb.sh ${dbconf_name}
fi
}
function start_globalserver()
{
echo "global start config";
check_app_type "ms"
auto_update_db
local args=$@
local server_entry="${FRAMEWORK_PATH}/skynet/skynet"
local start_file="${SVR_PATH}/${____START_FILE_GLOBAL}"
local result_file="${SVR_PATH}/${____START_RESULT_FILE_GLOBAL}"
start_server_and_wait "${____LOGIC_GLOBAL}" "${server_entry}" "${____APP_START_CONF_NAME}" "${start_file}" "${result_file}" ${args}
}
function stop_globalserver()
{
local args=$@
stop_server "${____LOGIC_GLOBAL}" ${args}
local start_file="${SVR_PATH}/${____START_FILE_GLOBAL}"
remove_server_start_file "${start_file}"
}
function restart_globalserver()
{
local args=$@
stop_globalserver $args
echo "Sleep 5 second..."
sleep 5
start_globalserver $args
}
function list_globalserver_status()
{
local args=$@
list_server_status "${____LOGIC_GLOBAL}" ${args}
}
function check_app_type() {
cat ${____APP_START_CONF_NAME};
local tar_type=${1}
local app_type=`cat ${____APP_START_CONF_NAME} | grep app_type | awk -F\" '{print $2}'`
if [[ "${app_type}" != "${tar_type}" ]]; then
echo "app_type[${app_type}] error"
exit 1
fi
}
function start_webserver()
{
echo "web start config";
check_app_type "ws"
local args=$@
local server_entry="${FRAMEWORK_PATH}/skynet/skynet"
local start_file="${SVR_PATH}/${____START_FILE_WEB}"
local result_file="${SVR_PATH}/${____START_RESULT_FILE_WEB}"
start_server_and_wait "${____LOGIC_WEB}" "${server_entry}" "${____APP_START_CONF_NAME}" "${start_file}" "${result_file}" ${args}
}
function stop_webserver()
{
local args=$@
stop_server "${____LOGIC_WEB}" ${args}
local start_file="${SVR_PATH}/${____START_FILE_WEB}"
remove_server_start_file "${start_file}"
}
function restart_webserver()
{
local args=$@
stop_webserver $args
echo "Sleep 5 second..."
sleep 5
start_webserver $args
}
function list_webserver_status()
{
local args=$@
list_server_status "${____LOGIC_WEB}" ${args}
}
function start_server_and_wait()
{
local server_name="$1"
local server_entry="$2"
local server_config_file="$3"
local start_file="$4"
local result_file="$5"
# parse arguments and get server start args
local args=($@)
local start_args="${args[@]:5}"
# remove start result file first
\rm -rf "${result_file}"
\rm -rf "${start_file}"
# start loginserver
start_server "${server_name}" "${server_entry}" ${server_config_file} ${start_args}
local start_ret=$?
if [ ${start_ret} -ne 0 ]; then
echo -n "Start ${server_name} failed(error code: 1)" > "${result_file}"
return 1
fi
# sleep 0.618 seconds, and then wait server login success
sleep 0.618
# wait loginserver start file
wait_server_start_file ${server_name} "${start_file}"
if [ $? -ne 0 ]; then
log_err "Wait server start file failed, start ${server_name} failed!"
\rm -rf "${start_file}" # force cleanup start file again
echo -n "Start ${server_name} failed(error code: 2)" > "${result_file}"
return 2
fi
echo -n "Start ${server_name} success" > "${result_file}"
log_info "done!"
}
function wait_server_start_file()
{
local server_name="$1"
local start_file="$2"
local begin_wait=`date "+%s"`
local wait_time=0
local temp_file=".${server_name}.temp"
log_info "wait for the ${server_name} to generate the startup successfully file..."
while [ ! -f "${start_file}" ]; do
echo -n "`list_server_status "${server_name}"`" > "${temp_file}"
if egrep "Stopped" "${temp_file}" > /dev/null; then
log_err "${server_name} stopped, failed to start server!"
\rm -rf "${temp_file}"
return 1
fi
sleep 10
wait_time=$((${wait_time} + 10))
log_info "wait for the ${server_name} to generate the startup successfully file(${wait_time} seconds)..."
if [ ${wait_time} -ge ${____GAME_SERVER_START_MAX_WAIT} ]; then
log_err "wait for the ${server_name} startup time too long, kill it!"
\rm -rf "${temp_file}"
stop_server "${____LOGIC_GAME}"
\rm -rf "${start_file}"
return 2
fi
done
\rm -rf "${temp_file}"
\rm -rf "${start_file}"
return 0
}
function remove_server_start_file()
{
local start_file="$1"
\rm -rf "${start_file}"
}
function debugcontrol()
{
local port=${1}
local commond=${2}
exec 3<>/dev/tcp/127.0.0.1/${port}
if [ $? -ne 0 ]; then
log_err ${commond} "failed"
return 1
fi
echo "${commond}" >&3
while read -r line
do if [[ "$line" == "<CMD OK>" || "$line" == "<CMD Error>" ]]; then
log_info "send ${commond}" $line;
break;
fi done <&3;exec 3<&-;exec 3>&-;
return 0
}
function safestop_loginserver()
{
local nodeid="`get_serverconf_loginnode_id`"
local port="`exec_sql_cmd_on_confdb "SELECT port FROM conf_debug WHERE nodeid = ${nodeid}"`"
debugcontrol ${port} "safestop exitlog"
local args=$@
stop_loginserver ${args}
}
function safestop_globalserver()
{
local nodeid="`get_serverconf_globalnode_id`"
local port="`exec_sql_cmd_on_confdb "SELECT port FROM conf_debug WHERE nodeid = ${nodeid}"`"
debugcontrol ${port} "safestop exitlog"
local args=$@
stop_globalserver ${args}
}
function safestop_webserver()
{
local nodeid="`get_serverconf_webnode_id`"
local port="`exec_sql_cmd_on_confdb "SELECT port FROM conf_debug WHERE nodeid = ${nodeid}"`"
debugcontrol ${port} "safestop exitlog"
local args=$@
stop_webserver ${args}
}
function safestop_gameserver()
{
# check running or not
local gs_st="`list_gameserver_status`"
if `list_gameserver_status | egrep 'Stopped' > /dev/null`; then
log_warn "gameserver not running"
return 0
fi
# set gameserver status to maintain
set_gameserver_logic_status 2
if [ $? -ne 0 ]; then
log_err "send gameserver status to <maintain> failed!"
log_err "safe stop gameserver failed!"
return 1
fi
# get telnet port
log_info "get telnet port..."
local gamenode_id="`get_serverconf_gamenode_id`"
local telnet_port="`exec_sql_cmd_on_confdb "SELECT port FROM conf_debug WHERE nodeid = ${gamenode_id}"`"
log_info "telnet port got: ${telnet_port}"
# wait all agentlt services count down to 10 or less(we use python telnetlib standard lib to implement telnet access)
local max_agentlt_count=20
local temp_pycode_file=".safestop_temp_pycode_file"
echo -e "from telnetlib import Telnet\nt = Telnet('127.0.0.1', ${telnet_port})\nt.write('list\\\n')\nagentlt_svcs_count = len([agentlt_svc for agentlt_svc in t.read_until('OK', 120).split('\\\n') if 'agentlt' in agentlt_svc])\nprint agentlt_svcs_count" > "${temp_pycode_file}"
local check_agentlt_ok=FALSE
local begin_check_time=`date "+%s"`
local max_check_time=${____GAME_SERVER_STOP_MAX_WAIT}
while [ "${check_agentlt_ok}" != "TRUE" ]; do
local used_time=$((`date "+%s"` - ${begin_check_time}))
if [ ${used_time} -ge ${max_check_time} ]; then
log_err "wait <agentlt services count> down to ${max_agentlt_count} or less timeout(used: ${used_time})!"
log_err "safe stop gameserver failed!"
return 1
fi
local now_agentlt_svcs_count="`python "${temp_pycode_file}"`"
if [ $? -ne 0 ]; then
log_err "failed to get server <agentlt services> count, telnet error!"
log_err "safe stop gameserver failed!"
return 2
fi
log_info "wait for <agentlt services> count down to ${max_agentlt_count} or less(now: ${now_agentlt_svcs_count}, used ${used_time} seconds)..."
if [ "${now_agentlt_svcs_count}" -lt 10 ]; then
check_agentlt_ok=TRUE
else
sleep 3
fi
done
rm -rf "${temp_pycode_file}"
# wait 5 seconds, and then execute kill
log_info "wait 5 seconds, and then execute force stop..."
sleep 5
debugcontrol ${telnet_port} "safestop exitlog"
local args=$@
stop_gameserver ${args}
}

@ -0,0 +1,88 @@
# linux script logic server build(included tarball) about functions encapsulation
# HongWei Zheng
###################################################################
source "${____LOGIC_SCRIPT_PATH}/lnx_fwk_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
############################################################
# 解压热更包
function _unzip_hotfix()
{
echo "解压热更包... [${SVR_PATH}/${HF_ZIP_NAME}]"
cd ${SVR_PATH} && unzip -d ./ -o ./${HF_ZIP_NAME}
}
function _exec_debug_cmd()
{
local url='http://localhost:3001/'
local cmd=${1}
local ret=`curl ${url}${cmd}|grep 'CMD OK'`
if [ "${ret}" = "<CMD OK>" ]; then
return 0
fi
return 1
}
# 清除代码缓存
function _clear_code_cache()
{
echo "清理代码缓存..."
_exec_debug_cmd 'clearcache'
return $?
}
# 检查外部参数
function _check_hotfix_args()
{
if [ -z "${HF_ZIP_NAME}" ]; then
echo "热更包名错误 [${HF_ZIP_NAME}]"
return 1
fi
local game=`ls ${SVR_PATH}|grep 'game'`
if [ "${game}" != "game" ]; then
echo "源码目录错误 [${SVR_PATH} ${game}]"
return 1
fi
if [ ! -s "${SVR_PATH}/${HF_ZIP_NAME}" ]; then
echo "热更包不存在 [${SVR_PATH}/${HF_ZIP_NAME}]"
return 1
fi
return 0
}
function hotfix_zip_gs()
{
HF_ZIP_NAME=${1} #热更包名
_check_hotfix_args
if [ $? -ne 0 ]; then
echo "参数错误"
exit 1
fi
_exec_debug_cmd 'help'
if [ $? -ne 0 ]; then
echo "连接debug_control失败"
exit 1
fi
_unzip_hotfix
if [ $? -ne 0 ]; then
echo "解压热更包失败"
exit 1
fi
_clear_code_cache
if [ $? -ne 0 ]; then
echo "清除代码缓存失败"
exit 1
fi
echo "热更完成" ${HF_ZIP_NAME}
echo "热更完成" ${HF_ZIP_NAME} >> ./hotfixPkgLog.txt
}

@ -0,0 +1,56 @@
# linux script server http interactive logic functions encapsulation.
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_server.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serversql.sh"
# send http message to specific node
# arguments:
# arg1: node
# arg2: message cmd(integer)
# arg3: sub message cmd(integer)
# arg4: http message(what message do you want to send?)
# returns:
# echo style:
# server return message
# return style:
# 0: success
# <other>: failed
function send_httpmsg_to_node()
{
local node="$1"
local msg_cmd="$2"
local msg_subcmd="$3"
local http_msg="$4"
local verify_key="${____SERVER_HTTP_KEY}"
local now_time=`date "+%s"`
# calculate sign
local verify_sign="`echo -n "${now_time}${verify_key}" | md5sum | cut -d ' ' -f 1`"
# get http access info
local http_info=(`exec_sql_cmd_on_confdb "SELECT web, port FROM conf_http WHERE nodeid = ${node}"`)
if [ $? -ne 0 ]; then
log_err "failed to get http access port from mysql!"
log_err "failed to send http message[${http_msg}] to node: ${node}!"
return 1
fi
local http_addr="http://${http_info[0]}:${http_info[1]}"
local http_packet="{\"cmd\":${msg_cmd}, \"subcmd\": ${msg_subcmd}, \"data\": ${http_msg}, \"sign\": \"${verify_sign}\", \"time\":${now_time}}"
local curl_ret="`curl --max-time 8 -s -d "${http_packet}" "${http_addr}"`"
local curl_ret_code=$?
if [ ${curl_ret_code} -ne 0 ]; then
echo -n "${curl_ret}"
return ${curl_ret_code}
else
echo -n "${curl_ret}"
return 0
fi
}

@ -0,0 +1,122 @@
# linux script server sql about functions encapsulation.
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_server.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
# execute sql command on config database
# arguments:
# arg1: sql command
# returns:
# echo style:
# sql query return
# return style:
# 0: success
# <other>: failed
function exec_sql_cmd_on_confdb()
{
exec_sql_cmd mysql_confdb "$1"
}
# execute sql command on game database
# arguments:
# arg1: sql command
# returns:
# echo style:
# sql query return
# return style:
# 0: success
# <other>: failed
function exec_sql_cmd_on_gamedb()
{
exec_sql_cmd mysql_gamedb "$1"
}
# execute sql command on specific config database(in dbconf.lua)
# arguments:
# arg1: config name
# arg2: sql command
# returns:
# echo style:
# sql query return
# return style:
# 0: success
# <other>: failed
function exec_sql_cmd()
{
local dbcfg_name="$1"
local sql_stmt="$2"
local access_info=(`_get_db_access_info "${dbcfg_name}"`)
local sql_ret="`python2.6 ${____FWK_SCRIPT_PATH}/mysql_handler.py ${access_info[0]} ${access_info[1]} "${access_info[2]}" "${access_info[3]}" "${access_info[4]}" "${sql_stmt}"`"
if [ $? -ne 0 ]; then
echo -n "${sql_ret}"
return 1
fi
echo -n "${sql_ret}"
}
# -------------------- internal implementation ----------------------
function _get_db_access_info()
{
local dbcfg_name="$1"
local dbconf_file="${SVR_PATH}/dbconf.lua"
if [ ! -f "${dbconf_file}" ]; then
log_err "not found db config file: ${dbconf_file}"
return 1
fi
local can_grep=FALSE
local sql_host=
local sql_port=
local sql_user=
local sql_passwd=
local sql_db=
while read line; do
if [ ! -z "`echo -n "${line}" | grep "dbconf.${dbcfg_name}"`" ]; then
can_grep=TRUE
continue
fi
if [ "${can_grep}" != TRUE ]; then
continue
fi
local isend="`echo -n "${line}" | sed -n -r 's/\s*(})\s*/\1/p'`"
if [ ! -z "${isend}" ]; then
break
fi
local gethost="`echo -n "${line}" | sed -n -r 's/\s*host\s*=\s*"(.*)"\s*,/\1/p'`"
if [ ! -z "${gethost}" ]; then
sql_host="${gethost}"
fi
local getport="`echo -n "${line}" | sed -n -r 's/\s*port\s*=\s*(.*)\s*,/\1/p'`"
if [ ! -z "${getport}" ]; then
sql_port="${getport}"
fi
local getuser="`echo -n "${line}" | sed -n -r 's/\s*user\s*=\s*"([^"]*)"\s*,/\1/p'`"
if [ ! -z "${getuser}" ]; then
sql_user="${getuser}"
fi
local getpasswd="`echo -n "${line}" | sed -n -r 's/\s*password\s*=\s*"([^"]*)"\s*,/\1/p'`"
if [ ! -z "${getpasswd}" ]; then
sql_passwd="${getpasswd}"
fi
local getdb="`echo -n "${line}" | sed -n -r 's/\s*database\s*=\s*"([^"]*)"\s*,/\1/p'`"
if [ ! -z "${getdb}" ]; then
sql_db="${getdb}"
fi
done < "${dbconf_file}"
echo ${sql_host} ${sql_port} ${sql_user} ${sql_passwd} ${sql_db}
}

@ -0,0 +1,130 @@
# linux script server logic status about functions encapsulation.
# Longwei Lai
###################################################################
source "${____FWK_SCRIPT_PATH}/lnx_fwk_path.sh"
source "${____FWK_SCRIPT_PATH}/lnx_fwk_server.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverhttp.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serversql.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverconf.sh"
# get gameserver logic status describe
# arguments:
# arg1: logic status(1,2,...)
# returns:
# echo style:
# the logic status describe.
# return style:
# N/A
function get_gameserver_logic_status_desc()
{
local logic_status="$1"
if [ "${logic_status}" = 1 ]; then
echo -n "Normal(正常)"
elif [ "${logic_status}" = 2 ]; then
echo -n "Maintain(维护)"
elif [ "${logic_status}" = 3 ]; then
echo -n "New(新服)"
else
echo -n "Unknown(未知):${logic_status}"
fi
}
# get gameserver logic status
# arguments:
# N/A
# returns:
# echo style:
# the gameserver all kingdoms status describe.
# return style:
# N/A
function get_gameserver_logic_status()
{
# get all kingdom ids
local gamenode_id="`get_serverconf_gamenode_id`"
local kingdom_ids="`exec_sql_cmd_on_confdb "SELECT kid from conf_kingdom WHERE nodeid = ${gamenode_id}"`"
for kingdom_id in ${kingdom_ids}; do
local sql_cmd="SELECT status FROM conf_kingdom WHERE kid = ${kingdom_id}"
local kingdom_status="`exec_sql_cmd_on_confdb "${sql_cmd}"`"
echo "kingdom ${kingdom_id} status: `get_gameserver_logic_status_desc "${kingdom_status}"`"
done
}
# set gameserver logic status
# arguments:
# arg1: the new gameserver logic status(1, 2, ...)
# returns:
# echo style:
# N/A
# return style:
# 0: success
# <other>: failed
function set_gameserver_logic_status()
{
# get gameserver all kingdom ids
local gamenode_id="`get_serverconf_gamenode_id`"
local kingdom_ids="`exec_sql_cmd_on_confdb "SELECT kid from conf_kingdom WHERE nodeid = ${gamenode_id}"`"
local new_status="$1"
log_info "set gameserver status to ${new_status}..."
# if set status to maintain then enable ipwhitelist, otherwise disable ipwhitelist
local new_ipwhitelist_status=0
if [ "${new_status}" = 2 ]; then
new_ipwhitelist_status=1
fi
log_info "set gameserver ipwhitelist status to ${new_ipwhitelist_status}..."
local send_http_msg_failed=FALSE
local loginnode_id="`get_serverconf_loginnode_id`"
log_info "call HTTP API to set status(send to login node:${loginnode_id})..."
for kingdom_id in ${kingdom_ids}; do
# modify conf_kingdom table
log_info "set kingdom ${kingdom_id} status to: ${new_status}..."
local msg="{\"kingdoms\": [${kingdom_id}], \"status\": ${new_status}}"
local res_msg="`send_httpmsg_to_node ${loginnode_id} 1 2 "${msg}"`"
local send_succ=$?
if [ ${send_succ} -ne 0 -o -z "`echo "${res_msg}" | egrep '"result"\s*:\s*1\s*,'`" ]; then
log_warn "failed to set kingdom ${kingdom_id} status, http res:${res_msg}, skip..."
send_http_msg_failed=TRUE
else
log_info "set kingdom ${kingdom_id} status to ${new_status} success, return: ${res_msg}"
fi
# modify conf_ipwhitelist table
log_info "set kingdom ${kingdom_id} ipwhitelist status to: ${new_ipwhitelist_status}..."
local msg="{\"kid\": ${kingdom_id}, \"status\": ${new_ipwhitelist_status}}"
local res_msg="`send_httpmsg_to_node ${loginnode_id} 1 17 "${msg}"`"
local send_succ=$?
if [ ${send_succ} -ne 0 -o -z "`echo "${res_msg}" | egrep '"result"\s*:\s*true\s*,'`" ]; then
log_warn "failed to set kingdom ${kingdom_id} ipwhitelist status, res:${res_msg}!, skip..."
send_http_msg_failed=TRUE
fi
done
if [ ${send_http_msg_failed} = TRUE ]; then
log_info "set some kingdom status failed, try update sql some tables to set status..."
for kingdom_id in ${kingdom_ids}; do
log_info "set kingdom ${kingdom_id} status to: ${new_status}..."
local sql_cmd="UPDATE conf_kingdom SET status = ${new_status} WHERE kid = ${kingdom_id}"
exec_sql_cmd_on_confdb "${sql_cmd}"
local sql_succ=$?
if [ ${sql_succ} -ne 0 ]; then
log_err "failed to set kingdom ${kingdom_id} status, res:${res_msg}!"
return ${sql_succ}
fi
sql_cmd="UPDATE conf_ipwhitelist SET status = ${new_ipwhitelist_status} WHERE kid = ${kingdom_id}"
exec_sql_cmd_on_confdb "${sql_cmd}"
sql_succ=$?
if [ ${sql_succ} -ne 0 ]; then
log_err "failed to set kingdom ${kingdom_id} ipwhitelist status, res:${res_msg}!"
return ${sql_succ}
fi
done
fi
}

@ -0,0 +1,314 @@
# linux script server tarball functions encapsulation
# Longwei Lai
###################################################################
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_serverbuild.sh"
# 创建启动配置
function _create_start_file() {
log_info "create start config ${____APP_START_CONF_NAME} ..."
local server_name=${1}
local proj_name=${2}
local product_opt=${3}
local working_dir=${4}
local cfg_file="${SVR_PATH}/${____APP_START_CONF_NAME}"
function _append_line() {
echo "${1}=\"${2}\"" >> ${cfg_file}
}
function _append_include() {
echo "include \"${1}\"" >> ${cfg_file}
}
rm -rf ${cfg_file}
_append_include "framework/snconfig"
case ${server_name} in
loginserver)
_append_include "login/snconfig"
_append_line "app_type" "ls"
;;
globalserver)
_append_include "global/snconfig"
_append_line "app_type" "ms"
;;
webserver)
_append_include "web/snconfig"
_append_line "app_type" "ws"
;;
gameserver)
_append_include "game/snconfig"
_append_line "app_type" "gs"
;;
ruinsserver)
_append_include "game/snconfig"
_append_line "app_type" "rs"
;;
devilserver)
_append_include "game/snconfig"
_append_line "app_type" "ds"
;;
pyramidserver)
_append_include "game/snconfig"
_append_line "app_type" "ps"
;;
cloudserver)
_append_include "game/snconfig"
_append_line "app_type" "cp"
;;
*)
log_info "未知[应用类型]参数"
return 1
;;
esac
case ${proj_name} in
boe|ww)
_append_line "app_project" ${proj_name}
;;
*)
log_info "未知[应用项目]参数"
return 1
;;
esac
case ${product_opt} in
product)
_append_line "app_env" "pro"
;;
internal)
_append_line "app_env" "sit"
;;
*)
log_info "未知[应用环境]参数"
return 1
;;
esac
\cp -rf "${cfg_file}" "${working_dir}/"
log_info "`cat ${cfg_file}`"
return 0
}
function _copy_skynet() {
log_info "copy skynet ..."
local super_dir=${1}
local skynet_from="${FRAMEWORK_PATH}/skynet"
local skynet_to="${super_dir}/skynet"
mkdir -p "${skynet_to}"
local skynet_copy_files=(service cservice lualib luaclib skynet LICENSE HISTORY.md README.md)
for item in ${skynet_copy_files[@]}; do
\cp -rf "${skynet_from}/${item}" "${skynet_to}/${item}"
done
}
function _copy_3rd() {
log_info "copy 3rd ..."
local super_dir=${1}
local thirdrd_from="${FRAMEWORK_PATH}/3rd"
local thirdrd_to="${super_dir}/3rd"
mkdir -p ${thirdrd_to}
for item in `ls ${thirdrd_from}`; do
local item_from=${thirdrd_from}/${item}
local item_to=${thirdrd_to}/${item}
if [ -d "${item_from}" ]; then
mkdir -p ${item_to}
if [ ! -z "`find "${item_from}" -type f -name '*.so'`" ]; then
\cp -rf "${item_from}/"*.so "${item_to}/"
fi
if [ ! -z "`find "${item_from}" -type f -name '*.lua'`" ]; then
\cp -rf "${item_from}/"*.lua "${item_to}/"
fi
else
\cp -rf "${item_from}" "${item_to}"
fi
done
}
function _copy_luasrc() {
local working_dir=${1}
local server_name=${2}
function _copy_dir() {
local name=${1}
log_info "copy ${name} lua src ..."
\cp -rf "${SVR_PATH}/${name}" "${working_dir}/"
}
case ${server_name} in
loginserver) _copy_dir "login";;
globalserver) _copy_dir "global";;
webserver) _copy_dir "web";;
gameserver|ruinsserver|devilserver|pyramidserver|cloudserver) _copy_dir "game";;
*)
log_info "Unknown application type ${server_name}"
exit 1
;;
esac
}
function _copy_others() {
log_info "copy others ..."
local working_dir=${1}
\cp -rf "${____FWK_SVR_OPTIONS_PATH}" "${working_dir}/"
\cp -rf "${SVR_PATH}/run" "${working_dir}/run"
}
function _create_version_file() {
log_info "creating server version file[SERVER_VERSION.txt] ..."
local working_dir=${1}
local isdebug_prompt=${2}
local product_opt=${3}
local proj_name=${4}
local server_name=${5}
local svr_desc_file="${working_dir}/SERVER_VERSION.txt"
echo -e "SERVER BUILD OPTION: ${isdebug_prompt}, ${product_opt}\n" > "${svr_desc_file}"
echo -e "--------------------------------------------------------------------------\n" >> "${svr_desc_file}"
echo -e "PROJECT NAME: ${proj_name}" >> "${svr_desc_file}"
echo -e "SERVER NAME: ${server_name}" >> "${svr_desc_file}"
echo -e "BUILD NUMBER: ${BUILD_NUMBER}" >> "${svr_desc_file}"
echo -e "WHY BUILD: ${WHY_BUILD}" >> "${svr_desc_file}"
echo -e "" >> "${svr_desc_file}"
echo -e "GIT REMOTE INFO:\n`git remote -v`" >> "${svr_desc_file}"
echo -e "GIT BRANCH: `git branch -v | grep -e '\*\s\+' | cut -d' ' -f2-`" >> "${svr_desc_file}"
echo -e "LAST 10 COMMITS:\n`git --no-pager log -n 10`" >> "${svr_desc_file}"
echo -e "BUILD TIME: `date "+%Y-%m-%d %H:%M:%S"`" >> "${svr_desc_file}"
}
function _create_md5() {
local package_name=${1}
local checksum_file="${package_name}.md5"
md5sum "${package_name}" | cut -d' ' -f1 > "${checksum_file}"
log_info "file checksum: `cat "${checksum_file}"`"
}
function _clean_tmp_files() {
log_info "clean tmp files ..."
local working_dir=${1}
find "${working_dir}" -type f -name '.*.pid' -print0 |xargs -0 rm -rf # .*.pid: pid files
find "${working_dir}" -type f -name '*.log.*' -print0 |xargs -0 rm -rf # *.log.xxxx-xx-xx: log files
find "${working_dir}" -type f -name '*.nohup' -print0 |xargs -0 rm -rf # .*.nohup: nohup files
find "${working_dir}" -type f -name '.*.nohup' -print0 |xargs -0 rm -rf # .*.nohup: nohup files
}
function _tarball_files() {
log_info "tarball files ..."
local working_dir=${1}
local package_name=${2}
log_info "build reason: ${WHY_BUILD}"
log_info "tarball: ${package_name}"
( cd "${working_dir}" && tar -jcvf "${package_name}" ./ > /dev/null )
if [ $? -ne 0 ]; then
log_err " build server tarball file failed"
else
\rm -rf "${working_dir}"
fi
}
function _copy_framework() {
local working_dir=${1}
local item_to="${working_dir}/framework"
for item in `ls ${FRAMEWORK_PATH}`; do
case "${item}" in
skynet) _copy_skynet ${item_to};;
3rd) _copy_3rd ${item_to};;
*)
echo "copy ${item} ..."
\cp -rf "${FRAMEWORK_PATH}/${item}" "${item_to}/"
;;
esac
done
}
function tar_server() {
echo tar_server $@
local dbg_flag="`echo -n "$1" | tr [:lower:] [:upper:]`"
local internal_flag="`echo -n "$2" | tr [:lower:] [:upper:]`"
local pure_opt="`echo -n "$3" | tr [:upper:] [:lower:]`"
local is_make="`echo -n "$4" | tr [:upper:] [:lower:]`"
local dbg_opt=FALSE
local isdebug_prompt=release
if [ "${dbg_flag}" = "DEBUG" -o "${dbg_flag}" = "DBG" -o "${dbg_flag}" = "D" ]; then
dbg_opt=TRUE
isdebug_prompt=debug
fi
# if not specific compile environment, compile all environments packages
if [ -z "${internal_flag}" ]; then # TODO: all environments server tarball files build will open at later
tar_server "${dbg_flag}" product
test $? -eq 0 || return 1
return
fi
local begin_tarball_time=`date +%s`
local product_opt="product"
if [ "${internal_flag}" = "INTERNAL" -o "${internal_flag}" = "INTERNAL_ENV" -o "${internal_flag}" = "INL" ]; then
product_opt=internal
elif [ "${internal_flag}" = "PRESSURE_TEST" -o "${internal_flag}" = "PRESSURE_TEST_ENV" -o "${internal_flag}" = "PT" ]; then
product_opt=pressure_test
fi
local proj_name="${PROJECT_NAME}"
if [ -z "${proj_name}" ]; then
proj_name='ukn_proj'
fi
local server_name="${SERVER_NAME}"
if [ -z "${server_name}" ]; then
server_name='ukn_svr'
fi
# is need make
if [ "${is_make}" != "nomake" ]; then
log_info "making ..."
make_server "${dbg_flag}" $2
if [ $? -ne 0 ]; then
log_err "build server failed"
return 1
fi
else
log_info "no make ..."
fi
local tarball_dir="${SVR_PATH}/tarballs"
local working_dir="${tarball_dir}/onemt.server"
local package_name="${tarball_dir}/onemt.${proj_name}.server.${server_name}.${product_opt}.${pure_opt}.tar.bz2"
mkdir -p "${tarball_dir}"
\rm -rf "${working_dir}"
mkdir -p "${working_dir}"
_copy_framework ${working_dir}
_copy_others ${working_dir}
_copy_luasrc ${working_dir} ${server_name}
_create_start_file ${server_name} ${proj_name} ${product_opt} ${working_dir}
_create_version_file ${working_dir} ${isdebug_prompt} ${product_opt} ${proj_name} ${server_name}
_clean_tmp_files ${working_dir}
_tarball_files ${working_dir} ${package_name}
_create_md5 ${package_name}
local tarball_used=$((`date +%s` - ${begin_tarball_time}))
log_info "done! used time: ${tarball_used} seconds"
}

@ -0,0 +1,77 @@
# linux script logic server update about functions encapsulation
# Longwei Lai
###################################################################
source "${____LOGIC_SCRIPT_PATH}/lnx_fwk_defs.sh"
source "${____LOGIC_SCRIPT_PATH}/lnx_logic_defs.sh"
# get version info(from git)
function get_server_version_info()
{
log_info "Local Branch: `git branch -v | grep -e '\*\s\+' | cut -d ' ' -f 2`"
log_info "Remote Branch: `git remote`/`git branch -v | grep -e '\*\s\+' | cut -d ' ' -f 2`"
log_info "Last Commit Info:"
log_info "`git log -1 $(get_now_git_version_no)`"
}
# update server(from git)
function update_server()
{
local git_path="${SVR_PATH}"
log_info "now branch:$(get_now_branch)"
log_info "pull all updates from remote..."
( cd ${git_path} && git pull origin )
if [ $? -ne 0 ]; then
log_err "update server from git failed"
return 1
fi
log_info "update all submodules..."
( cd ${git_path} && git submodule update --init --recursive )
if [ $? -ne 0 ]; then
log_err "update server submodules failed"
return 1
fi
log_info "update success, update to: $(get_now_git_version_no)"
}
# switch branch
function switch_branch()
{
local new_branch="$1"
local old_branch="$(get_now_branch)"
if [ "${new_branch}" = "${old_branch}" ]; then
log_warn "same branch[${new_branch}], don't need switch"
return 0
fi
git checkout -b "${new_branch}" "origin/${new_branch}"
local co_ret=$?
if [ ${co_ret} -ne 0 ]; then
log_err "switch [${old_branch}] --> [${new_branch}] failed, checkout failed, return: ${co_ret}"
return 1
fi
git submodule update --init --recursive
local submodule_up_ret=$?
if [ ${submodule_up_ret} -ne 0 ]; then
log_err "switch [${old_branch}] --> [${new_branch}] failed, submodule update failed, return: ${co_ret}"
return 1
fi
log_info "switch [${old_branch}] --> [${new_branch}] success!"
}
# get current branch
function get_now_branch()
{
echo -n "`git branch | grep -e '\*\s\+' | cut -d ' ' -f 2-`"
}
# get current now git version no
function get_now_git_version_no()
{
echo -n "`git branch -v | grep -e '\*\s\+' | cut -d ' ' -f3`"
}

@ -0,0 +1,30 @@
function _log() {
echo "[Log] $@"
}
root_path=`pwd`
file_name=$1
version_sql_path=$root_path/global/sql
if [ ! -d $version_sql_path ];
then
mkdir -p $version_sql_path
fi
db_gamedata=`grep dbconf.mysql_gamedb $file_name -A 9 | grep database | awk -F\" '{print $2}'`
db_gameconf=`grep dbconf.mysql_confdb $file_name -A 9 | grep database | awk -F\" '{print $2}'`
user=`grep dbconf.mysql_gamedb $file_name -A 9 | grep user | awk -F\" '{print $2}'`
host=`grep dbconf.mysql_gamedb $file_name -A 9 | grep host | awk -F\" '{print $2}'`
port=`grep dbconf.mysql_gamedb $file_name -A 9 | grep port | awk -F\= '{print $2}' | awk -F, '{print $1}'`
password=`grep dbconf.mysql_gamedb $file_name -A 9 | grep password | awk -F\" '{print $2}'`
_log $db_gamedata $user $host $port $password
MYSQL="mysql -u$user -p$password -h$host -P$port"
cd ${version_sql_path}
files=$(ls)
for file in ${files[@]}
do
_log "执行 sql文件:" $file
${MYSQL} --force $db_gamedata < $file 2>/dev/null
done

@ -0,0 +1,53 @@
function _log() {
echo "[Log] $@"
}
root_path=`pwd`
file_name=${1}
branch_name=`git branch | awk '$1 == "*"{print}' | awk -F/ '{print $2}' | awk -F')' '{print $1}'`
version_sql_path=$root_path/global/sql
if [ ! -d $version_sql_path ];
then
mkdir -p $version_sql_path
fi
cat >> test.lua << EOF
local dbconf = require("$file_name")
print("database", dbconf.mysql_gamedb.database)
print("confdatabase", dbconf.mysql_confdb.database)
print("host", dbconf.mysql_gamedb.host)
print("port", dbconf.mysql_gamedb.port)
print("user", dbconf.mysql_gamedb.user)
print("password", dbconf.mysql_gamedb.password)
EOF
ret=(`lua test.lua | awk -F" " '{print $2}'`)
db_gamedata=${ret[0]}
db_gameconf=${ret[1]}
host=${ret[2]}
port=${ret[3]}
user=${ret[4]}
password=${ret[5]}
MYSQL="mysql -u$user -p$password -h$host -P$port"
notchange="nothing to commit, working tree clean"
rm -rf test.lua
cd ${version_sql_path}
files=$(ls)
sql_name=$version_sql_path/${branch_name}.sql
if [ ! -f "$sql_name" ]; then
echo "USE \`${db_gamedata}\`;" > ${sql_name}
fi
#排序后遍历文件
for file in ${files[@]}
do
_log "执行 sql文件:" $file
${MYSQL} --force $db_gamedata < $file 2>/dev/null
done
rm -rf out.txt

@ -0,0 +1,24 @@
#-*- coding:utf-8 -*-
try:
import MySQLdb as mdb
except:
import pymysql as mdb
mdb.install_as_MySQLdb()
from sys import argv
script, sql_host, sql_port, sql_user, sql_passwd, sql_db, sql_stmt = argv
connection = mdb.connect(host = sql_host, port = int(sql_port), user = sql_user, passwd = sql_passwd, db = sql_db)
cursor = connection.cursor()
try:
cursor.execute(sql_stmt)
records = cursor.fetchall()
fmttedRecords = '\n'.join(' '.join(str(col) for col in record) for record in records)
print fmttedRecords
finally:
cursor.close()
connection.close()

@ -0,0 +1,78 @@
#!/usr/bin/env bash
# game server control(build, tar, start, stop, ...) script file
# Longwei Lai
###################################################################
# define current path and script path
CUR_PATH="`pwd`"
SCRIPT_PATH="$(cd "`dirname "$0"`" && pwd)" # .../shells
FRAMEWORK_PATH="`dirname "${SCRIPT_PATH}"`" # .../framework
MISC_SCRIPTS_PATH="${SCRIPT_PATH}/misc_scripts" # .../misc_scripts
SVR_PATH="$(dirname "${FRAMEWORK_PATH}")" # .../*-server
# include framework header file and logic header file
cd "${MISC_SCRIPTS_PATH}"
source "${MISC_SCRIPTS_PATH}/lnx_fwk.sh"
source "${MISC_SCRIPTS_PATH}/lnx_logic.sh"
cd "${CUR_PATH}"
# main function(implemented command dispatch logic)
function main()
{
# firstly, ensure environment
env_ensure
# do command dispatch
local args=($@)
local cmd=${args[0]}
local cmd_args=${args[@]:1}
case "${cmd}" in
# server build/tarball commands
up|update)
update_server ${cmd_args}
;;
ver_info)
get_server_version_info ${cmd_args}
;;
switch_branch)
switch_branch ${cmd_args}
;;
make|build)
make_server ${cmd_args}
;;
tar|compress)
tar_server ${cmd_args}
;;
hfg|hotfix_gs)
hotfix_zip_gs ${cmd_args}
;;
# fake time about commands
read_faketime)
log_info $(read_faketime)
;;
clear_faketime)
clear_faketime
log_info "fake time setting cleared"
;;
# special command help pages show commands
help_gameserver|help_gs)
list_help_gameserver ${cmd_args}
;;
help_loginserver|help_ls)
list_help_loginserver ${cmd_args}
;;
# test commands
# ...
# other server control commands
*)
server_ctrl_cmd_adapt ${args[@]}
;;
esac
}
main $@

@ -0,0 +1,163 @@
#!/usr/bin/env bash
# @author 郑宏伟
# @brief MACOS服务器启动脚本
____APP_START_CONF_NAME=".app.mac.cfg"
SCRIPT_PATH="$(cd "`dirname "$0"`" && pwd)"
FRAMEWORK_PATH="`dirname "${SCRIPT_PATH}"`" # .../framework
MISC_SCRIPTS_PATH="${SCRIPT_PATH}/misc_scripts" # .../misc_scripts
function _log()
{
echo "[Log] $@"
}
function EnvEnsure()
{
local os_type=`uname`
if [ $os_type != "Darwin" ];then
_log "illegal os: $os_type"
exit 1
fi
local cpu=`sysctl -n machdep.cpu.core_count`
export SKYNET_THREAD=`expr ${cpu} \* 2`
}
function Main()
{
ParseAgrs $@
CreateConfigFile ${____APP_START_CONF_NAME}
EnvEnsure
${FRAMEWORK_PATH}/skynet/skynet ${____APP_START_CONF_NAME} "${ARG_TYPE}-${ARG_PRO}-${ARG_ENV}"
}
function Help()
{
echo 'Usage:'
echo 'start_server.sh -p 应用项目 -e 应用环境 -t 应用类型 [-d DBCONF名]'
echo 'Options:'
echo ' -p 必需 应用项目'
echo ' 奥斯曼大帝国:boe'
echo ' 西部:ww'
echo ' -e 必需 应用环境'
echo ' 开发环境:dev'
echo ' 集成测试:sit'
echo ' 产品环境:pro'
echo ' -t 必需 应用类型'
echo ' 游戏服:gs'
echo ' 登录服:ls'
echo ' 全局服:ms'
echo ' WEB服:ws'
echo ' -d 可选 DBCONF名'
}
function ProjectAdapter()
{
case ${1} in
boe|ww) ARG_PRO="$OPTARG";;
?)
_log "未知[应用项目]参数"
exit 1
;;
esac
}
function EnvAdapter()
{
case ${1} in
dev|sit|pro) ARG_ENV="$OPTARG";;
?)
_log "未知[应用环境]参数"
exit 1
;;
esac
}
function TypeAdapter()
{
case ${1} in
ls)
ARG_TYPE="$OPTARG"
ARG_START_CONF=login/snconfig
;;
ms)
ARG_TYPE="$OPTARG"
ARG_START_CONF=global/snconfig
;;
ws)
ARG_TYPE="$OPTARG"
ARG_START_CONF=web/webconf
;;
gs|rw|cp)
ARG_TYPE="$OPTARG"
ARG_START_CONF=game/snconfig
;;
?)
_log "未知[应用类型]参数"
exit 1
;;
esac
}
function ParseAgrs()
{
while getopts :p:e:t:hd: OPT
do
case $OPT in
p) ProjectAdapter $OPTARG;;
e) EnvAdapter $OPTARG;;
t) TypeAdapter $OPTARG;;
d) DBCONF_NAME="$OPTARG";;
h) Help;exit 0;;
?)
_log "未知参数"
exit 1
;;
esac
done
}
function auto_update_db()
{
local dbconf="dbconf"
if [[ ! -z ${DBCONF_NAME} ]]; then
dbconf="${DBCONF_NAME}"
fi
if [[ -z ${dbconf} ]]; then
_log "dbconf name err"
exit 1
fi
if [[ ${ARG_ENV} == "dev" && ${ARG_TYPE} == "ms" ]]; then
sh ${MISC_SCRIPTS_PATH}/mac_logic_updatedb.sh ${dbconf}
fi
}
function CreateConfigFile()
{
local STARTFILE=${1}
if [[ -z ${ARG_PRO} || -z ${ARG_ENV} || -z ${ARG_TYPE} ]]; then
_log "缺少参数";exit 2;
fi
rm -rf ${STARTFILE}
cat >> ./${STARTFILE} << EOF
include "framework/snconfig"
include "${ARG_START_CONF}"
app_type="${ARG_TYPE}"
app_project="${ARG_PRO}"
app_env="${ARG_ENV}"
EOF
if [[ ! -z ${DBCONF_NAME} ]]; then
echo "dbconf=\"${DBCONF_NAME}\"" >> ./${STARTFILE}
fi
# 自动更新数据库文件
auto_update_db
}
Main $@

@ -0,0 +1,21 @@
#!/usr/bin/env bash
RUN_PATH="$(cd "`dirname "$0"`" && pwd)/../framework/shells"
os_type=`uname`
if [ $os_type == "Darwin" ]; then
case ${1} in
boels) ${RUN_PATH}/run_mac -p boe -e dev -t ls -d login.dbconf;;
boems) ${RUN_PATH}/run_mac -p boe -e dev -t ms -d global.dbconf;;
boews) ${RUN_PATH}/run_mac -p boe -e dev -t ws -d web.dbconf;;
boegs) ${RUN_PATH}/run_mac -p boe -e dev -t gs -d game.dbconf;;
boerw) ${RUN_PATH}/run_mac -p boe -e dev -t gs -d game.dbconf_rw;;
boecp) ${RUN_PATH}/run_mac -p boe -e dev -t gs -d game.dbconf_cp;;
boede) ${RUN_PATH}/run_mac -p boe -e dev -t gs -d game.dbconf_de;;
*) echo "param error";exit 1;;
esac
elif [ $os_type == "Linux" ];then
${RUN_PATH}/run_linux $@
else
echo "env error"
exit 1
fi

@ -1,127 +0,0 @@
--- Module which contains the main logic of the CLI application
-- @module lqc.cli.app
-- @alias app
local Vector = require 'lqc.helpers.vector'
local reduce = require 'lqc.helpers.reduce'
local filter = require 'lqc.helpers.filter'
local fs = require 'lqc.helpers.fs'
local random = require 'lqc.random'
local lqc = require 'lqc.quickcheck'
local loader = require 'lqc.cli.loader'
local arg_parser = require 'lqc.cli.arg_parser'
local report = require 'lqc.report'
-- File used for remembering last used seed for generating test cases.
local check_file = '.lqc'
--- Tries to read the last quickcheck seed.
-- @return the last used seed (or nil on error).
local function read_from_check_file()
return fs.read_file(check_file)
end
--- Writes the seed to the check file (.lqc.lua).
-- @param seed Seed to write to the file
local function write_to_check_file(seed)
fs.write_file(check_file, seed)
end
--- Initializes the random seed; either with last used value (--check) or a
-- specific seed (-s, --seed) or default (current timestamp)
-- @param config Config which specifies how seed initialization should be handled
local function initialize_random_seed(config)
local seed = config.seed
if config.check then -- redo last generated test run (if --check specified)
seed = read_from_check_file()
end
local actual_used_seed = random.seed(seed)
write_to_check_file(actual_used_seed)
report.report_seed(actual_used_seed)
end
--- Depending on the config, returns a list of files that should be executed.
-- @return List of script files that should be executed.
local function find_files(files_or_dirs)
return reduce(files_or_dirs, Vector.new(), function(file_or_dir, acc)
if fs.is_file(file_or_dir) then
return acc:push_back(file_or_dir)
end
-- directory
return acc:append(Vector.new(fs.find_files(file_or_dir)))
end):to_table()
end
--- Checks if a file is a file ending in .lua or .moon (if moonscript available)
-- @param file Path to a file
-- @return true if it has the correct extension; otherwise false.
local function is_script_file(file)
return fs.is_lua_file(file) or fs.is_moonscript_file(file)
end
--- Filters out all files not ending in .lua or .moon
-- @param files list of all files
-- @return filtered list of files, containing only the Lua and Moonscript scripts
local function find_script_files(files)
return filter(files, is_script_file)
end
--- Executes all scripts, specified by a table of file paths.
-- @param files List of scripts to execute.
local function execute_scripts(files)
for _, file in pairs(files) do
-- TODO clear environment each time?
local script = loader.load_script(file)
script()
end
end
--- Verifies all properties, with the work divided over X number of threads.
-- @param numthreads Number of threads to divide the work over.
local function verify_properties(numthreads)
if numthreads == 1 then
lqc.check()
return
end
lqc.check_mt(numthreads)
end
--- Shows the test output (statistics)
local function show_output()
report.report_errors()
report.report_summary()
end
local app = {}
-- Exits the application
function app.exit()
os.exit(lqc.failed and 1 or 0)
end
--- Main function of the CLI application
-- 1. parse arguments
-- 2. find all files needed to run
-- 3. initialize random seed
-- 4. run 1 file, or all files in a directory (depending on args)
-- 5. execute properties (lqc.check)
-- 6. show output
-- @param cli_args Table containing the commandline arguments
function app.main(cli_args)
local config = arg_parser.parse(cli_args or {})
local files = find_files(config.files_or_dirs)
local script_files = find_script_files(files)
initialize_random_seed(config)
lqc.init(config.numtests, config.numshrinks)
report.configure(config.colors)
execute_scripts(script_files)
verify_properties(config.threads)
show_output()
app.exit()
end
return app

@ -1,43 +0,0 @@
--- Module for parsing of commandline arguments
-- @module lqc.cli.arg_parser
-- @alias lib
local argparse = require 'argparse'
local config = require 'lqc.config'
-- Module for easily parsing list of command line arguments
local name_of_executable = 'lqc'
local help_info = 'Property based testing tool written in Lua'
local parser = argparse(name_of_executable, help_info)
parser.error = function(msg)
error(msg)
end
-- Converts a string to an integer
-- Returns an integer representation of the input string or raises an error on failure.
local function str_to_int(x)
return tonumber(x)
end
parser:argument('files_or_dirs',
'List of input files or directories (recursive search) used for testing, default = "."', nil, nil, '*')
parser:mutex(parser:flag('--check', 'Re-checks the set of tests generated by the last seed'), parser:option('--seed -s',
'Value of the random seed to use, default = seed based on current time', nil, str_to_int))
parser:option('--numtests', 'Number of iterations per property, default = 100', nil, str_to_int)
parser:option('--numshrinks', 'Number of shrinks per failing property, default = 100', nil, str_to_int)
parser:flag('--colors -c', "Enable coloring of test output, default = disabled (doesn't work on Windows!).")
parser:option('--threads -t', "Executes properties in parallel, default = single-threaded (requires Lua Lanes!).", nil,
str_to_int)
local lib = {}
--- Parses the arguments
-- @return a table containing the config specified by the user;
-- raises an error if parsing failed.
function lib.parse(args)
local parsed_values = parser:parse(args)
return config.resolve(parsed_values)
end
return lib

@ -1,77 +0,0 @@
--- Module for pre-loading all lqc modules so the user does not have to do this.
-- @module lqc.cli.loader
-- @alias lib
local deep_copy = require 'lqc.helpers.deep_copy'
local fs = require 'lqc.helpers.fs'
local has_moonscript, moonscript = pcall(require, 'moonscript')
local lib = {}
-- Prepare new global env for easier use of property based testing library
local new_global_env = deep_copy(_G)
new_global_env.Generator = require 'lqc.generator'
new_global_env.any = require 'lqc.generators.any'
new_global_env.bool = require 'lqc.generators.bool'
new_global_env.byte = require 'lqc.generators.byte'
new_global_env.char = require 'lqc.generators.char'
new_global_env.float = require 'lqc.generators.float'
new_global_env.int = require 'lqc.generators.int'
new_global_env.str = require 'lqc.generators.string'
new_global_env.tbl = require 'lqc.generators.table'
new_global_env.random = require 'lqc.random'
new_global_env.property = require 'lqc.property'
new_global_env.fsm = require 'lqc.fsm'
new_global_env.state = require 'lqc.fsm.state'
new_global_env.command = require 'lqc.fsm.command'
do
local lqc_gen = require 'lqc.lqc_gen'
new_global_env.choose = lqc_gen.choose
new_global_env.frequency = lqc_gen.frequency
new_global_env.elements = lqc_gen.elements
new_global_env.oneof = lqc_gen.oneof
end
--- Compatibility workaround: setfenv is removed from Lua for versions > 5.1.
-- This function aims to provide same functionality.
-- Based mostly on http://leafo.net/guides/setfenv-in-lua52-and-above.html
-- @param func function for which the environment should be changed
-- @param new_env table containing the new environment to be set
local function setfenv_compat(func, new_env)
local idx = 1
repeat
local name = debug.getupvalue(func, idx)
if name == '_ENV' then
debug.upvaluejoin(func, idx, function()
return new_env
end, 1)
end
idx = idx + 1
until name == '_ENV' or name == nil
return func
end
local setfenv = setfenv or setfenv_compat
--- Loads a script, sets a new environment (for easier property based testing),
-- @param file_path Path to the file containing a Lua/Moonscript script
-- @return the modified script which can be called as a function.
function lib.load_script(file_path)
-- Check if Moonscript file and if Moonscript available
if fs.is_moonscript_file(file_path) then
if not has_moonscript then
return function()
end
end -- return empty 'script'
local script = moonscript.loadfile(file_path)
return setfenv(script, new_global_env)
end
-- Lua file
local script = loadfile(file_path)
return setfenv(script, new_global_env)
end
return lib

@ -1,46 +0,0 @@
--- Helper module for managing the config in the application.
-- @module lqc.config
-- @alias config
local config = {}
--- Helper function for getting the default random seed.
--
-- @return default seed used by the application (the current timestamp).
function config.default_seed()
return os.time()
end
local default_config = {
files_or_dirs = {'.'},
seed = config.default_seed(),
numtests = 100,
numshrinks = 100,
colors = false,
threads = 1,
check = false,
}
--- Checks if the argument is empty (nil or {}).
--
-- @param x Argument to check
-- @return true if arg is empty; otherwise false.
local function is_empty_arg(x)
return x == nil or (type(x) == 'table' and #x == 0)
end
--- Determines the config to use based on the table of supplied values.
-- If no value is supplied for a specific setting, a default value is used
-- (see top of file).
--
-- @return the updated config
function config.resolve(values)
for _, arg_name in ipairs {'files_or_dirs', 'seed', 'numtests', 'numshrinks', 'colors', 'threads', 'check'} do
if is_empty_arg(values[arg_name]) then
values[arg_name] = default_config[arg_name]
end
end
return values
end
return config

@ -1,102 +0,0 @@
--- Helper module for specifying finite state machines (FSMs) with. Provides a DSL.
-- @module lqc.fsm
-- @alias fsm
local algorithm = require 'lqc.fsm.algorithm'
local state = require 'lqc.fsm.state'
local lqc = require 'lqc.quickcheck'
--- Adds a stop state to the list of states.
-- This is a special predefined state that will stop the FSM from generating
-- more state transitions.
-- @param state_list List of states in the FSM (not including stop state)
-- @return the updated state list (variable modified in place).
local function add_stop_state(state_list)
table.insert(state_list, state 'stop' {
precondition = function()
return true
end, -- always succeeds
next_state = function()
return nil
end, -- not used
postcondition = function()
return true
end, -- always succeeds
})
return state_list
end
--- Checks if an object is callable (function or functable):
-- @param obj Object to be checked if it is callable
-- @return true if it is callable; otherwise false
local function is_callable(obj)
local type_obj = type(obj)
return type_obj == 'function' or type_obj == 'table'
end
--- Checks if the FSM table contains a valid specification of a state machine
-- @param fsm_table Table containing FSM information/description
-- @return nil; raises an error message if specification is not valid
local function check_valid_fsm_spec(fsm_table)
if not is_callable(fsm_table.commands) then
error 'Need to provide list of commands to FSM!'
end
if not is_callable(fsm_table.initial_state) then
error 'Need to provide initial state function to FSM!'
end
local states = fsm_table.states
if type(states) ~= 'table' then
error 'Need to provide a table of possible states of the FSM!'
end
-- States are already checked in state.lua
end
local function default_cleanup()
end
local function default_when_fail()
end
--- Constructs a new FSM
-- @param description text description of the FSM
-- @param fsm_table table containing FSM info
-- @return FSM object
local function new(description, fsm_table)
local FSM = {}
function FSM.check(_)
return algorithm.check(description, fsm_table)
end
return FSM
end
--- Creates a new FSM and inserts it into the list of properties.
-- @param descr Text description of the FSM
-- @param fsm_info_table Table containing information of the FSM
local function fsm(descr, fsm_info_table)
local function fsm_func(fsm_table)
fsm_table.states = add_stop_state(fsm_table.states)
fsm_table.cleanup = fsm_table.cleanup or default_cleanup
fsm_table.when_fail = fsm_table.when_fail or default_when_fail
fsm_table.numtests = fsm_table.numtests or lqc.numtests
fsm_table.numshrinks = fsm_table.numshrinks or lqc.numshrinks
check_valid_fsm_spec(fsm_table)
local new_fsm = new(descr, fsm_table)
table.insert(lqc.properties, new_fsm)
end
if fsm_info_table then
-- Called normally (most likely from Moonscript)
fsm_func(fsm_info_table)
return function()
end
end
-- Called with DSL syntax
return fsm_func
end
return fsm

@ -1,37 +0,0 @@
--- Module for describing an action in a 'declarative' way
-- @classmod lqc.fsm.action
-- @alias Action
local Action = {}
local Action_mt = {
__index = Action,
}
--- Creates a new action.
-- @param var (Symbolic) variable to store the result of the action in
-- @param cmd Command that was called during this action
-- @param command_generator Generator that generated the command, used for shrinking the command
-- @return new action object
function Action.new(var, cmd, command_generator) -- TODO rename to args_generators
if var == nil then
error 'Need to provide variable to action object!'
end
if cmd == nil then
error 'Need to provide command to action object!'
end
local action = {
variable = var,
command = cmd,
cmd_gen = command_generator,
}
return setmetatable(action, Action_mt)
end
--- Returns a string representation of the action
-- @return string representation of the action
function Action:to_string()
return '{ set, ' .. self.variable:to_string() .. ', ' .. self.command:to_string() .. ' }'
end
return Action

@ -1,471 +0,0 @@
--- Module describing the algorithm used by the FSM properties.
-- @module lqc.fsm.algorithm
-- @alias lib
local Vector = require 'lqc.helpers.vector'
local Var = require 'lqc.fsm.var'
local Command = require 'lqc.fsm.command'
local Action = require 'lqc.fsm.action'
local random = require 'lqc.random'
local deep_copy = require 'lqc.helpers.deep_copy'
local report = require 'lqc.report'
local unpack = unpack or table.unpack -- for compatibility reasons
local lib = {}
--- Creates a small helper object that keeps track of a counter
-- @return counter object
function lib.make_counter()
local Counter = {
val = 1,
increase = function(self)
self.val = self.val + 1
end,
value = function(self)
return self.val
end,
}
return setmetatable(Counter, {
__index = Counter,
})
end
--- Checks if x lies between min and max
-- @param x A number
-- @param min Minimum value
-- @param max Maximum value
-- @return true if min <= x and x <= max; otherwise false.
local function is_between(x, min, max)
return min <= x and x <= max
end
--- Determines how many items should be shrunk
-- @param max_amount Maximum amount of items that are allowed to be shrunk down
-- @return random number between 1 and max_amount (inclusive)
local function shrink_how_many(max_amount)
if max_amount <= 1 then
return 1
end
return random.between(1, max_amount)
end
--- Determines if an an action should be marked for deletion
-- @return true if it should be deleted; otherwise false
local function should_select_action()
return random.between(1, 4) == 1 -- 25% chance
end
--- Finds a specific state in the list of states based on name of the state.
-- @param states List of states to search in
-- @param state_name Name of the state to find
-- @return the state with that name;
-- raises an error if no state is present with the specified name
function lib.find_state(states, state_name)
for i = 1, #states do
local state = states[i]
if state.name == state_name then
return state
end
end
error('State "' .. state_name .. '" not found in list of FSM states!')
end
--- Finds the next command based on the FSM model and the current state.
-- @param fsm table describing the FSM property
-- @param current_state Variable containing the current state of the FSM
-- @param var_counter Number indicating how many variables have already been used
-- @return 3 values: chosen_command, cmd_generator, updated_current_state
function lib.find_next_action(fsm, current_state, var_counter)
local numtests, commands, states = fsm.numtests, fsm.commands, fsm.states
local cmd_gen = commands(current_state)
for _ = 1, 100 do -- TODO make configurable?
local cmd = cmd_gen:pick(numtests)
local selected_state = lib.find_state(states, cmd.state_name)
if selected_state.precondition(current_state, cmd.args) then -- valid command
local variable = Var.new(var_counter:value())
var_counter:increase()
current_state = selected_state.next_state(current_state, variable, cmd.args)
return Action.new(variable, cmd, cmd_gen), current_state
end
end
-- Could not find a next action -> stop generating further actions
return Action.new(Var.new(var_counter:value()), Command.stop, nil), current_state
end
--- Generates a list of steps for a FSM specification
-- @param fsm_table table containing description of a FSM property
-- @return list of generated actions
function lib.generate_actions(fsm_table)
local generated_actions = Vector.new()
local counter = lib.make_counter()
local state = fsm_table.initial_state()
repeat
local action, next_state = lib.find_next_action(fsm_table, state, counter)
state = next_state
generated_actions:push_back(action)
until action.command.state_name == 'stop'
return generated_actions
end
--- Slices of the last actions past index
-- @param action_vector Vector of actions
-- @param index Last position in the vector that should not be removed
-- @return the action vector (modified in place)
function lib.slice_last_actions(action_vector, index)
local action_vector_copy = deep_copy(action_vector)
local last_pos = action_vector_copy:size() - 1 -- -1 since we want to keep stop action!
for i = last_pos, index + 1, -1 do
action_vector_copy:remove_index(i)
end
return action_vector_copy
end
--- Selects at most 'how_many' amount of actions from the vector of actions
-- to be marked for deletion.
-- @param action_vector Vector containing list of actions
-- @return vector of actions which should be deleted.
function lib.select_actions(action_vector)
local selected_actions, idx_vector = Vector.new(), Vector.new()
if action_vector:size() <= 2 then
return selected_actions, idx_vector
end
local amount = 0
local size = action_vector:size() - 2 -- don't remove stop action, keep atleast 1 other action
local how_many = shrink_how_many(size)
for i = 1, size do
if amount >= how_many then
break
end
if should_select_action() then -- TODO make this a variable function and use a while loop?
idx_vector:push_back(i)
amount = amount + 1
end
end
for i = 1, amount do
selected_actions:push_back(action_vector:get(idx_vector:get(i)))
end
return selected_actions, idx_vector
end
--- Removes all elements of 'which_actions' from 'action_vector'
-- @param action_vector Vector of actions from which actions will be removed
-- @param which_actions actions to be removed
-- @return an updated vector
function lib.delete_actions(action_vector, which_actions)
local action_vector_copy = deep_copy(action_vector)
for i = 1, which_actions:size() do
action_vector_copy:remove(which_actions:get(i))
end
return action_vector_copy
end
--- Does the actual execution of the FSM by executing the list of actions
-- If one of the postconditions fail after an action is applied, then the
-- actions will be shrunk down to a simpler scenario.
-- At the end, the state is cleaned up by the cleanup-callback.
-- Returns 4 values:
-- 1) true if the FSM property succeeded for these actions; false otherwise.
-- 2) index of last successful step (1-based)
-- 3) state of the model right before the failing action
-- 4) result of the last failing action
-- @param fsm_table table containing description of a FSM property
-- @param generated_actions list of actions to be executed for this FSM
-- @return 4 values:
-- 1. bool: true = FSM succeeded; false = FSM failed
-- 2. Amount of actions executed
-- 3. Last state of the FSM
-- 4. Last result (return value of last command)
function lib.execute_fsm(fsm_table, generated_actions)
local state = fsm_table.initial_state()
local last_state, last_result = state, nil
for i = 1, generated_actions:size() do
local command = generated_actions:get(i).command
local selected_state = lib.find_state(fsm_table.states, command.state_name)
local result = command.func(unpack(command.args)) -- side effects happen here
local updated_state = selected_state.next_state(state, result, command.args)
last_state, last_result = state, result
-- and verify the model matches the actual system
-- NOTE: the state passed in is the state that the system had BEFORE
-- executing this specific action!
if not selected_state.postcondition(state, result, command.args) then
fsm_table.cleanup(state)
return false, i, last_state, last_result
end
state = updated_state -- update model
end
fsm_table.cleanup(state)
return true, generated_actions:size() - 1, last_state, last_result
end
--- Is the list of actions valid to execute on this FSM?
-- Replays sequence symbolically to verify if it is indeed valid.
-- @param fsm_table table containing description of a FSM property
-- @param action_vector list of actions to be checked
-- @return true if list of actions valid; otherwise false.
function lib.is_action_sequence_valid(fsm_table, action_vector)
local states = fsm_table.states
local state = fsm_table.initial_state()
for i = 1, action_vector:size() do
local action = action_vector:get(i)
local selected_state = lib.find_state(states, action.command.state_name)
if not selected_state.precondition(state, action.command.args) then
return false
end
state = selected_state.next_state(state, action.variable, action.command.args)
end
return true
end
--- Tries to shrink the list of actions to a simpler form by removing steps of
-- the sequence and checking if it is still valid.
-- The function is recursive and will loop until tries_left is 0.
-- @param fsm_table table containing description of a FSM property
-- @param action_list list of actions to be shrunk down
-- @return 2 values:
-- 1) action_list if shrinking was not possible after X amount of tries;
-- otherwise it will return a shrunk list of actions.
-- 2) list of deleted actions (empty if shrinking failed after X tries)
local function do_shrink_actions(fsm_table, action_list)
local which_actions = lib.select_actions(action_list)
local shrunk_actions = lib.delete_actions(action_list, which_actions)
if not lib.is_action_sequence_valid(fsm_table, shrunk_actions) then
return action_list, Vector.new()
end
return shrunk_actions, which_actions
end
--- Does the shrinking of the FSM actions
-- choose 1 - N steps and delete them from list of actions
-- repeat X amount of times (recursively)
-- @param fsm_table table containing description of a FSM property
-- @param generated_actions List of actions to be shrunk down
-- @param removed_actions Already removed actions
-- @param tries Remaining tries to recusively try shrinking the actions
-- @return the shrunk down list of actions
local function shrink_actions(fsm_table, generated_actions, removed_actions, tries)
if tries == 1 then
return generated_actions, removed_actions
end
local shrunk_actions, deleted_actions = do_shrink_actions(fsm_table, generated_actions)
local total_removed_actions = removed_actions:append(deleted_actions)
-- TODO add execute fsm here and shrink deleted actions?
return shrink_actions(fsm_table, shrunk_actions, total_removed_actions, tries - 1)
end
--- Tries to shrink the list of failing actions by selecting a subset and
-- checking if the combination is now valid and if the FSM still fails or not
-- This is a recursive function.
-- @param fsm_table table containing description of a FSM property
-- @param generated_actions List of actions to be shrunk down
-- @param deleted_actions Already deleted actions
-- @param tries Remaining tries to recusively try shrinking the actions
-- @return 3 things:
-- 1. the shrunk list of actions (or the original list if no better solution was found)
-- 2. the end state of the fsm after these actions
-- 3. result of the last action
local function shrink_deleted_actions(fsm_table, generated_actions, deleted_actions, tries)
if tries == 1 then
return generated_actions
end
local which_actions = lib.select_actions(deleted_actions)
local shrunk_actions = lib.delete_actions(generated_actions, which_actions)
-- Retry if invalid sequence
local is_valid = lib.is_action_sequence_valid(fsm_table, shrunk_actions)
if not is_valid then
return shrink_deleted_actions(fsm_table, generated_actions, deleted_actions, tries - 1)
end
-- Check FSM again
-- if FSM succeeds now, (one or more of) the failing actions have been deleted
-- -> simply retry TODO there is an optimisation possible here..
-- else FSM still failed -> chosen actions did not matter, ignore them and
-- further try shrinking
local is_successful = lib.execute_fsm(fsm_table, shrunk_actions)
if is_successful then
deleted_actions = lib.delete_actions(deleted_actions, which_actions)
end
return shrink_deleted_actions(fsm_table, shrunk_actions, deleted_actions, tries - 1)
end
--- Tries to shrink down the list of FSM actions. This function is called
-- recursively until all tries are used.
-- @param fsm_table table containing description of a FSM property
-- @param generated_actions List of actions to be shrunk down
-- @param step last step in the series of actions that succeeded while testing
-- the FSM property
-- @param tries Remaining tries to recusively try shrinking the actions
-- @return list of shrunk down actions
function lib.shrink_fsm_actions(fsm_table, generated_actions, step, tries)
if tries == 1 then
return generated_actions
end
local sliced_actions = lib.slice_last_actions(generated_actions, step) -- cut off actions after failure..
local shrunk_actions, deleted_actions =
shrink_actions(fsm_table, sliced_actions, Vector.new(), fsm_table.numshrinks)
if deleted_actions:size() == 0 then
-- shrinking did not help, try again
return lib.shrink_fsm_actions(fsm_table, sliced_actions, step, tries - 1)
end
-- shrinking did help, retry FSM:
local is_successful1, new_step1 = lib.execute_fsm(fsm_table, shrunk_actions)
if not is_successful1 then
-- FSM still fails, deleted actions can be ignored, try further shrinking
return lib.shrink_fsm_actions(fsm_table, shrunk_actions, new_step1, tries - 1)
end
-- now FSM works -> faulty action is in the just deleted actions
local shrunk_down_actions = shrink_deleted_actions(fsm_table, sliced_actions, deleted_actions, fsm_table.numshrinks)
-- retry fsm:
-- if a solution could not be found by shrinking down the deleted actions
-- -> sliced actions is smallest solution found
-- else if FSM still fails after shrinking deleted_actions
-- -> try further shrinking
local is_successful2, new_step2 = lib.execute_fsm(fsm_table, shrunk_down_actions)
local minimal_actions = is_successful2 and sliced_actions or shrunk_down_actions
return lib.shrink_fsm_actions(fsm_table, minimal_actions, new_step2, tries - 1)
end
--- Select a group of actions to be marked for shrinking down.
-- @param action_list List of actions to be shrunk down.
-- @return vector of indices for the actions which should be removed
local function select_actions_for_arg_shrinking(action_list)
local _, idx_vector = lib.select_actions(action_list)
if idx_vector:size() == 0 and is_between(action_list:size(), 2, 10) then
-- try shrinking 1 action anyway
local idx = random.between(1, action_list:size() - 1) -- don't shrink stop action!
idx_vector:push_back(idx)
end
return idx_vector
end
--- Does the actual shrinking of the command arguments of a sequence of actions.
-- @param fsm_table table containing description of a FSM property
-- @param action_list List of actions to be shrunk down
-- @return an updated sequence of the action list (original is modified!) with
-- shrunk arguments.
local function shrink_args(fsm_table, action_list)
local idx_vector = select_actions_for_arg_shrinking(action_list)
for i = idx_vector:size(), 1, -1 do -- shrunk from end to beginning (most likely to succeed)
local idx = idx_vector:get(i)
local action = action_list:get(idx)
for _ = 1, fsm_table.numshrinks do
local command_copy = action.command -- shallow copy (reference only)
action.command = action.cmd_gen:shrink(action.command)
-- revert if shrink is not valid
local is_valid = lib.is_action_sequence_valid(fsm_table, action_list)
if not is_valid then
action.command = command_copy;
break
end
end
end
return action_list
end
--- Shrinks down the arguments provided to the sequence of actions
-- This function is called recursively until all tries are used.
-- @param fsm_table table containing description of a FSM property
-- @param generated_actions List of actions to be shrunk down
-- @param tries Remaining tries to recusively try shrinking the actions
-- @return
local function shrink_fsm_args(fsm_table, generated_actions, tries)
if tries == 1 then
return generated_actions
end
local shrunk_actions = shrink_args(fsm_table, deep_copy(generated_actions)) -- , fsm_table.numshrinks)
-- retry FSM
local is_successful = lib.execute_fsm(fsm_table, shrunk_actions)
if not is_successful then
-- FSM still fails, shrinking of args was successful, try further shrinking
return shrink_fsm_args(fsm_table, shrunk_actions, tries - 1)
end
-- FSM works now, shrinking of args unsuccessful -> retry
return shrink_fsm_args(fsm_table, generated_actions, tries - 1)
end
--- Replays the FSM property.
-- @param fsm_table table containing description of a FSM property
-- @param action_vector List of actions to replay
-- @return last state and result while executing the FSM.
local function replay_fsm(fsm_table, action_vector)
local _, _, last_state, last_result = lib.execute_fsm(fsm_table, action_vector)
return last_state, last_result
end
--- Shrinks the list of generated actions for a given FSM.
-- This is a recursive function which keeps trying for X amount of times.
-- @param fsm_table table containing description of a FSM property
-- @param generated_actions List of actions to be shrunk down
-- @param step Last successful step that was executed before FSM failed
-- @return shrunk list of actions or original action list if shrinking did not help.
local function shrink_fsm(fsm_table, generated_actions, step)
local fsm_shrink_amount = fsm_table.numshrinks
local shrunk_actions = lib.shrink_fsm_actions(fsm_table, generated_actions, step, fsm_shrink_amount)
local shrunk_actions_and_args = shrink_fsm_args(fsm_table, shrunk_actions, fsm_shrink_amount)
local lst_state, lst_result = replay_fsm(fsm_table, shrunk_actions_and_args)
return shrunk_actions_and_args, lst_state, lst_result
end
--- The main checking function for FSM specifications.
-- Checks a number of times (according to FSM spec) if property is true.
-- If the specification failed, then the result will be shrunk down to a
-- simpler case.
-- @param description string description of the FSM property
-- @param fsm_table table containing description of a FSM property
-- @return nil on success; otherwise a table containing info related to FSM error.
function lib.check(description, fsm_table)
for _ = 1, fsm_table.numtests do
local generated_actions = lib.generate_actions(fsm_table)
local is_successful, last_step = lib.execute_fsm(fsm_table, generated_actions)
if not is_successful then
report.report_failed()
local shrunk_actions, last_state, last_result = shrink_fsm(fsm_table, generated_actions, last_step)
fsm_table.when_fail(shrunk_actions:to_table(), last_state, last_result)
return {
description = description,
generated_actions = generated_actions,
shrunk_actions = shrunk_actions,
}
end
report.report_success()
end
return nil
end
return lib

@ -1,104 +0,0 @@
--- Module for representing a command internally in a 'declarative' way
-- @module lqc.fsm.command
-- @alias command
local Gen = require 'lqc.generator'
local random = require 'lqc.random'
local deep_copy = require 'lqc.helpers.deep_copy'
--- Returns a string representation of the command.
-- @param cmd Command to transform into a string
-- @return string representation of the command
local function stringify(cmd)
local result = {'{ call, ', cmd.state_name}
local size_result = #result
for i = 1, #cmd.args do
-- TODO allow printing of table etc..
result[i + size_result] = ', ' .. cmd.args[i]
end
result[#result + 1] = ' }'
return table.concat(result)
end
--- Creates a function that picks a random value for each of the generators
-- specified in the argument list.
-- @param state_name Name of the state this command is associated with
-- @param command_func Function to be called when this command is selected
-- @param args_generators List of generators that generate the arguments for command_func
-- @return table with keys { state_name, func, args }
local function pick(state_name, command_func, args_generators)
local function do_pick(num_tests)
local args = {}
for i = 1, #args_generators do
args[i] = args_generators[i]:pick(num_tests)
end
return {
state_name = state_name,
func = command_func,
args = args,
to_string = stringify,
}
end
return do_pick
end
--- Does the actual shrinking of the args
-- Randomly picks 1 of the arguments and shrinks it, rest stays the same
-- @param prev_args Previously generated args for the command
-- @param args_generators Generators used to generate and shrink the args
-- @return shrunk down argument list
local function shrink_args(prev_args, args_generators)
local idx = random.between(1, #prev_args)
local shrunk_arg = args_generators[idx]:shrink(prev_args[idx])
local args_copy = deep_copy(prev_args)
args_copy[idx] = shrunk_arg
return args_copy
end
--- Shrinks the command to a simpler form.
-- Only args are shrunk, state_name and func are unmodified.
-- @param state_name Name of the state this command is associated with
-- @param command_func Function to be called when this command is selected
-- @param args_generators Generators used to generate and shrink the args
-- @return function that does the actual shrinking for this command
local function shrink(state_name, command_func, args_generators)
local function do_shrink(previous)
if #previous.args == 0 then
return previous
end
return {
state_name = state_name,
func = command_func,
args = shrink_args(previous.args, args_generators),
to_string = stringify,
}
end
return do_shrink
end
--- Creates a new command generator with a state_name, command function
-- and a list of generators (args will be passed into the command_func in the FSM)
-- @param state_name Name of the state this command is associated with
-- @param command_func Function to be called when this command is selected
-- @param args_generators Generators used to generate and shrink the args
-- @return generator for randomly creating a command
local function new(state_name, command_func, args_generators)
local generator = Gen.new(pick(state_name, command_func, args_generators),
shrink(state_name, command_func, args_generators))
generator.state_name = state_name
return generator
end
local command = {}
local command_mt = {
__call = function(_, command_tbl)
return new(command_tbl[1], command_tbl[2], command_tbl[3])
end,
}
command.stop = new('stop', function()
end, {})
return setmetatable(command, command_mt)

@ -1,59 +0,0 @@
--- Module that provides a DSL for specifying states in the FSM DSL framework.
-- @module lqc.fsm.state
-- @alias make_state
--- Checks if object is callable (a function or a functable)
-- @param obj Object to check
-- @return true if obj is callable; otherwise false.
local function is_callable(obj)
local type_obj = type(obj)
return type_obj == 'function' or type_obj == 'table'
end
--- Checks if the state contains all the necessary info for a valid state specification
-- @param state table containing precondition, next_state and postcondition
-- @param state_name name of the state
-- @return nil; raises an error if state contains wrong or missing information
local function check_valid_state(state, state_name)
if type(state_name) ~= 'string' then
error 'Missing state name!'
end
if type(state) ~= 'table' then
error 'State should be specified as a table!'
end
if not is_callable(state.precondition) then
error('Need to provide a precondition function to state ' .. state_name .. '!')
end
if not is_callable(state.next_state) then
error('Need to provide a next_state function to state ' .. state_name .. '!')
end
if not is_callable(state.postcondition) then
error('Need to provide a postcondition function to state ' .. state_name .. '!')
end
end
--- Helper function for specifying a state in the FSM
-- @param state_name Name to assign to the state
-- @param state_information Table containing precondition, next_state, postcondition functions
-- @return new table containing state information, ready to be added to the FSM
local function make_state(state_name, state_information)
local function make_state_helper(state_info)
check_valid_state(state_info, state_name)
return {
name = state_name,
precondition = state_info.precondition,
next_state = state_info.next_state,
postcondition = state_info.postcondition,
}
end
if state_information ~= nil then
-- Called with normal syntax, directly return result
return make_state_helper(state_information)
end
-- called with Lua DSL syntax, return closue which returns result
return make_state_helper
end
return make_state

@ -1,29 +0,0 @@
--- Helper module for symbolically representing a variable.
-- @classmod lqc.fsm.var
-- @alias var
local Var = {}
local Var_mt = {
__index = Var,
}
--- Creates a symbolic representation of a variable.
-- @param value Value of the variable
-- @return a new variable object
function Var.new(value)
if value == nil then
error 'Need to provide a value to Var!'
end
local var = {
value = value,
}
return setmetatable(var, Var_mt)
end
--- Returns a string representation of the variable
-- @return string representation of the variable
function Var:to_string()
return '{ var, ' .. self.value .. ' }'
end
return Var

@ -1,43 +0,0 @@
--- Class for generating (custom) generators.
-- @classmod lqc.generator
-- @alias Gen
local Gen = {}
local Gen_mt = {
__index = Gen,
}
--- Creates a new generator for generating random values.
-- @param pick_func a function that randomly creates a certain datatype.
-- @param shrink_func a function that shrinks (simplifies) a given input based
-- on last input.
-- @return a generator object
-- @see pick
-- @see shrink
function Gen.new(pick_func, shrink_func)
local Generator = {
pick_func = pick_func,
shrink_func = shrink_func,
}
return setmetatable(Generator, Gen_mt)
end
--- Generates a new random value based on this generator's pick value.
--
-- @param numtests amount of times a property will be run, can be used to guide
-- the choosing process.
-- @return a new randomly chosen value
function Gen:pick(numtests)
return self.pick_func(numtests)
end
--- Shrinks a generated value to a simpler value.
--
-- @param prev The previously generated value.
-- @return A newly generated value, simplified from the previous value.
function Gen:shrink(prev)
return self.shrink_func(prev)
end
return Gen

@ -1,30 +0,0 @@
--- Module for generating 'any' random value.
-- @lqc.generators.any
-- @alias new
local lqc_gen = require 'lqc.lqc_gen'
local tbl = require 'lqc.generators.table'
local int = require 'lqc.generators.int'
local float = require 'lqc.generators.float'
local str = require 'lqc.generators.string'
local bool = require 'lqc.generators.bool'
--- Creates a new generator that can generate a table, string, int, float or bool.
-- @param optional_samplesize Amount of times the property is tested, used to guide
-- the randomization process.
-- @return generator that can generate 1 of the previously mentioned types in
-- the description
local function new(optional_samplesize)
return lqc_gen.oneof {
tbl(optional_samplesize),
str(optional_samplesize),
int(optional_samplesize),
float(),
bool()
}
end
return new

@ -1,25 +0,0 @@
--- Module for generating a bool randomly.
-- @classmod lqc.generators.bool
-- @alias new
local Gen = require 'lqc.generator'
local random = require 'lqc.random'
--- Picks a random bool
-- @return true or false
local function pick()
return random.between(0, 1) == 0
end
--- Shrinks down a bool (always shrinks to false)
local function shrink(_)
return false
end
--- Creates a new bool generator
-- @return A generator object for randomly generating bools.
local function new()
return Gen.new(pick, shrink)
end
return new

@ -1,14 +0,0 @@
--- Module for generating bytes randomly.
-- A byte is an integer with value between 0 - 255 (inclusive)
-- @classmod lqc.generators.byte
-- @alias byte
local int = require 'lqc.generators.int'
-- Creates a new byte generator
-- @return generator for generating random byte values
local function byte()
return int(0, 255)
end
return byte

@ -1,36 +0,0 @@
--- Module for generating a random (ASCII) char (no 'special' characters such as NUL, NAK, ...)
-- @lqc.generators.char
-- @alias char
local Gen = require 'lqc.generator'
local int = require 'lqc.generators.int'
local lowest_ascii_value = 32 -- 'space'
local highest_ascii_value = 126 -- '~'
local int_gen = int(lowest_ascii_value, highest_ascii_value)
local space = string.char(lowest_ascii_value)
-- Generates a random character (ASCII value between 'space' and '~'
-- @return randomly chosen char (string of length 1)
local function char_pick()
return string.char(int_gen:pick_func())
end
--- Shrinks down a previously generated char to a simpler value. Shrinks
-- towards the 'space' ASCII character.
-- @param prev previously generated char value
-- @return shrunk down char value
local function char_shrink(prev)
if string.byte(prev) <= lowest_ascii_value then
return space
end
return string.char(string.byte(prev) - 1)
end
--- Creates a generator for ASCII-chars
-- @return generator that can randomly create ASCII values
local function char()
return Gen.new(char_pick, char_shrink)
end
return char

@ -1,30 +0,0 @@
--- Module for generating float values.
-- @lqc.generators.float
-- @alias new
local Gen = require 'lqc.generator'
-- Generates a random float.
-- @param numtests Number of times this generator is called in a test; used to
-- guide the randomization process.
-- @return random float (between - numtests / 2 and numtests / 2).
local function float_pick(numtests)
local lower_bound = -numtests / 2
local upper_bound = numtests / 2
return lower_bound + math.random() * (upper_bound - lower_bound)
end
--- Shrinks a float to a simpler value
-- @param prev a previously generated float value
-- @return shrunk down float value
local function float_shrink(prev)
return prev / 2
end
--- Creates a generator for float values
-- @return a generator that can generate float values.
local function new()
return Gen.new(float_pick, float_shrink)
end
return new

@ -1,108 +0,0 @@
--- Module for generating integer values
-- @module lqc.generators.int
-- @alias new
local Gen = require 'lqc.generator'
local random = require 'lqc.random'
local abs = math.abs
--- Helper function for picking a random integer, bounded by min and max.
-- @param min minimum value
-- @param max maximum value
-- @return function that can generate an integer (min <= int <= max)
local function pick_bounded(min, max)
local function do_pick()
return random.between(min, max)
end
return do_pick
end
--- Helper function for finding number closest to 0.
-- @param a number 1
-- @param b number 2
-- @return number closest to 0
local function find_closest_to_zero(a, b)
return (abs(a) < abs(b)) and a or b
end
--- Helper function for shrinking integer, bounded by min and max. (min <= int <= max)
-- @param min minimum value
-- @param max maximum value
-- @return shrunk integer (shrinks towards 0 / closest value to 0 determined
-- by min and max)
local function shrink_bounded(min, max)
local bound_limit = find_closest_to_zero(min, max)
local function do_shrink(previous)
if previous == 0 or previous == bound_limit then
return previous
end
if previous > 0 then
return math.floor(previous / 2)
end
return math.ceil(previous / 2)
end
return do_shrink
end
--- Picks a random integer, uniformy spread between +- sample_size / 2.
-- @param sample_size Number of times this generator is used in a property;
-- used to guide the optimatization process.
-- @return random integer
local function pick_uniform(sample_size)
local value = sample_size / 2
return random.between(value - sample_size, value)
end
--- Shrinks an integer by dividing it by 2 and rounding towards 0.
-- @param previous previously generated integer value
-- @return shrunk down integer value
local function shrink(previous)
if previous == 0 then
return 0
end
if previous > 0 then
return math.floor(previous / 2)
end
return math.ceil(previous / 2)
end
--- Creates a generator for generating an integer between min and max.
-- @param min minimum value
-- @param max maximum value
-- @return generator that generates integers between min and max.
local function integer_between(min, max)
return Gen.new(pick_bounded(min, max), shrink_bounded(min, max))
end
--- Creates a generator for generating a positive integer between 0 and max.
-- @param max maximum value
-- @return generator that generates integer between 0 and max.
local function positive_integer(max)
return Gen.new(pick_bounded(0, max), shrink)
end
--- Creates a generator for generating an integer uniformly chosen
-- between +- sample_size / 2.
-- @return generator that can generate an integer
local function integer()
return Gen.new(pick_uniform, shrink)
end
--- Creates a new integer generator.
-- @param nr1 number containing first bound
-- @param nr2 number containing second bound
-- @return generator that can generate integers according to the following strategy:
-- - nr1 and nr2 provided: nr1 <= int <= nr2
-- - only nr1 provided: 0 <= int <= max
-- - no bounds provided: -numtests/2 <= int <= numtests/2
local function new(nr1, nr2)
if nr1 and nr2 then
return integer_between(nr1, nr2)
end
if nr1 then
return positive_integer(nr1)
end
return integer()
end
return new

@ -1,200 +0,0 @@
--- Module for generating random strings
-- @module lqc.generators.string
-- @alias new
local random = require 'lqc.random'
local lqc = require 'lqc.quickcheck'
local Gen = require 'lqc.generator'
local char = require 'lqc.generators.char'
local char_gen = char()
-- NOTE: The shrink algorithms are *heavily* based on triq
-- https://github.com/krestenkrab/triq
--- Determines how many items to shrink.
-- @param length size of the string
-- @return integer indicating how many items to shrink
local function shrink_how_many(length)
-- 20% chance more than 1 member is shrunk
if random.between(1, 5) == 1 then
return random.between(1, length)
end
return 1
end
--- Replaces a character in the string 'str' at index 'idx' with 'new_char'.
-- @param str string to be modified
-- @param new_char value that will replace the old character in the string
-- @param idx position in the string where the character should be replaced
-- @return updated string
local function string_replace_char(str, new_char, idx)
local result = {}
result[1] = string.sub(str, 1, idx - 1)
result[2] = new_char
result[3] = string.sub(str, idx + 1)
return table.concat(result)
end
--- Replaces 1 character at a random location in the string, tries up to 100
-- times if shrink gave back same result.
-- @param prev previously generated string value
-- @param length size of the string
-- @param iterations_count remaining tries to shrink down this string
-- @return shrunk down string value
local function do_shrink_generic(prev, length, iterations_count)
local idx = random.between(1, length)
local old_char = string.sub(prev, idx, idx)
local new_char = char_gen:shrink(old_char)
if new_char == old_char and iterations_count ~= 0 then
-- Shrink introduced no simpler result, retry at other index.
return do_shrink_generic(prev, length, iterations_count - 1)
end
return string_replace_char(prev, new_char, idx)
end
--- Shrinks an amount of characters in the string.
-- @param str string to be shrunk down
-- @param length size of the string
-- @param how_many amount of characters to shrink
-- @return shrunk down string value
local function shrink_generic(str, length, how_many)
if how_many ~= 0 then
local new_str = do_shrink_generic(str, length, lqc.numshrinks)
return shrink_generic(new_str, length, how_many - 1)
end
return str
end
--- Determines if the string should be shrunk down to a shorter string
-- @param str_len size of the string
-- @return true: string should be made shorter; false: string should remain
-- size during shrinking
local function should_shrink_smaller(str_len)
if str_len == 0 then
return false
end
return random.between(1, 5) == 1
end
--- Shrinks the string by removing 1 character
-- @param str string to be shrunk down
-- @param str_len size of the string
-- @return new string with 1 random character removed
local function shrink_smaller(str, str_len)
local idx = random.between(1, str_len)
-- Handle edge cases (first or last char)
if idx == 1 then
return string.sub(str, 2)
end
if idx == str_len then
return string.sub(str, 1, idx - 1)
end
local new_str = {string.sub(str, 1, idx - 1), string.sub(str, idx + 1)}
return table.concat(new_str)
end
--- Generates a string with a specific size.
-- @param size size of the string to generate
-- @return string of a specific size
local function do_generic_pick(size)
local result = {}
for _ = 1, size do
result[#result + 1] = char_gen:pick()
end
return table.concat(result)
end
--- Generates a string with arbitrary length (0 <= size <= numtests).
-- @param numtests Number of times the property uses this generator; used to
-- guide the optimization process.
-- @return string of an arbitrary size
local function arbitrary_length_pick(numtests)
local size = random.between(0, numtests)
return do_generic_pick(size)
end
--- Shrinks a string to a simpler form (smaller / different chars).
-- 1. Returns empty strings instantly
-- 2. Determine if string should be made shorter
-- 2.1 if true: remove a char
-- 2.2 otherwise:
-- * simplify a random amount of characters
-- * remove a char if simplify did not help
-- * otherwise return the simplified string
-- @param prev previously generated string value
-- @return shrunk down string value
local function arbitrary_length_shrink(prev)
local length = #prev
if length == 0 then
return prev
end -- handle empty strings
if should_shrink_smaller(length) then
return shrink_smaller(prev, length)
end
local new_str = shrink_generic(prev, length, shrink_how_many(length))
if new_str == prev then
-- shrinking didn't help, remove an element
return shrink_smaller(new_str, length)
end
-- string shrunk succesfully!
return new_str
end
--- Helper function for generating a string with a specific size
-- @param size size of the string to generate
-- @return function that can generate strings of a specific size
local function specific_length_pick(size)
local function do_specific_pick()
return do_generic_pick(size)
end
return do_specific_pick
end
--- Shrinks a string to a simpler form (only different chars since length is fixed).
-- * "" -> ""
-- * non-empty string, shrinks upto max 5 chars of the string
-- @param prev previously generated string value
-- @return shrunk down string value
local function specific_length_shrink(prev)
local length = #prev
if length == 0 then
return prev
end -- handle empty strings
return shrink_generic(prev, length, shrink_how_many(length))
end
--- Generator for a string with an arbitrary size
-- @return generator for a string of arbitrary size
local function arbitrary_length_string()
return Gen.new(arbitrary_length_pick, arbitrary_length_shrink)
end
--- Creates a generator for a string of a specific size
-- @param size size of the string to generate
-- @return generator for a string of a specific size
local function specific_length_string(size)
return Gen.new(specific_length_pick(size), specific_length_shrink)
end
--- Creates a new ASCII string generator
-- @param size size of the string
-- @return generator that can generate the following:
-- 1. size provided: a string of a specific size
-- 2. no size provided: string of an arbitrary size
local function new(size)
if size then
return specific_length_string(size)
end
return arbitrary_length_string()
end
return new

@ -1,184 +0,0 @@
--- Module for generating tables of varying sizes and types
-- @module lqc.generators.table
-- @alias new_table
local Gen = require 'lqc.generator'
local random = require 'lqc.random'
local bool = require 'lqc.generators.bool'
local int = require 'lqc.generators.int'
local float = require 'lqc.generators.float'
local string = require 'lqc.generators.string'
local lqc = require 'lqc.quickcheck'
local lqc_gen = require 'lqc.lqc_gen'
local oneof = lqc_gen.oneof
local frequency = lqc_gen.frequency
local deep_equals = require 'lqc.helpers.deep_equals'
local deep_copy = require 'lqc.helpers.deep_copy'
--- Checks if an object is equal to another (shallow equals)
-- @param a first value
-- @param b second value
-- @return true if a equals b; otherwise false
local function normal_equals(a, b)
return a == b
end
--- Determines if the shrink mechanism should try to reduce the table in size.
-- @param size size of the table to shrink down
-- @return true if size should be reduced; otherwise false
local function should_shrink_smaller(size)
if size == 0 then
return false
end
return random.between(1, 5) == 1
end
--- Determines how many items in the table should be shrunk down
-- @param size size of the table
-- @return amount of values in the table that should be shrunk down
local function shrink_how_many(size)
-- 20% chance between 1 and size, 80% 1 element.
if random.between(1, 5) == 1 then
return random.between(1, size)
end
return 1
end
--- Creates a generator for a table of arbitrary or specific size
-- @param table_size size of the table to be generated
-- @return generator that can generate tables
local function new_table(table_size)
-- Keep a list of generators used in this new table
-- This variable is needed to share state (which generators) between
-- the pick and shrink function
local generators = {}
--- Shrinks the table by 1 randomly chosen element
-- @param tbl previously generated table value
-- @param size size of the table
-- @return shrunk down table
local function shrink_smaller(tbl, size)
local idx = random.between(1, size)
table.remove(tbl, idx)
table.remove(generators, idx) -- also update the generators for this table!!
return tbl
end
--- Shrink a value in the table.
-- @param tbl table to shrink down
-- @param size size of the table
-- @param iterations_count remaining amount of times the shrinking should be retried
-- @return shrunk down table
local function do_shrink_values(tbl, size, iterations_count)
local idx = random.between(1, size)
local old_value = tbl[idx]
local new_value = generators[idx]:shrink(old_value)
-- Check if we should retry shrinking:
if iterations_count ~= 0 then
local check_equality = (type(new_value) == 'table') and deep_equals or normal_equals
if check_equality(new_value, old_value) then
-- Shrink introduced no simpler result, retry at other index.
return do_shrink_values(tbl, size, iterations_count - 1)
end
end
tbl[idx] = new_value
return tbl
end
--- Shrinks an amount of values in the table
-- @param tbl table to shrink down
-- @param size size of the table
-- @param how_many amount of values to shrink down
-- @return shrunk down table
local function shrink_values(tbl, size, how_many)
if how_many ~= 0 then
local new_tbl = do_shrink_values(tbl, size, lqc.numshrinks)
return shrink_values(new_tbl, size, how_many - 1)
end
return tbl
end
--- Generates a random table with a certain size
-- @param size size of the table to generate
-- @return tableof a specific size with random values
local function do_generic_pick(size)
local result = {}
for idx = 1, size do
-- TODO: Figure out a better way to decrease table size rapidly, maybe use
-- math.log (e.g. ln(size + 1) ?
local subtable_size = math.floor(size * 0.01)
local generator = frequency {{10, new_table(subtable_size)},
{90, oneof {bool(), int(size), float(), string(size)}}}
generators[idx] = generator
result[idx] = generator:pick(size)
end
return result
end
-- Now actually generate a table:
if table_size then -- Specific size
--- Helper function for generating a table of a specific size
-- @param size size of the table to generate
-- @return function that can generate a table of a specific size
local function specific_size_pick(size)
local function do_pick()
return do_generic_pick(size)
end
return do_pick
end
--- Shrinks a table without removing any elements.
-- @param prev previously generated table value
-- @return shrunk down table
local function specific_size_shrink(prev)
local size = #prev
if size == 0 then
return prev
end -- handle empty tables
return shrink_values(prev, size, shrink_how_many(size))
end
return Gen.new(specific_size_pick(table_size), specific_size_shrink)
end
-- Arbitrary size
--- Generate a (nested / empty) table of an arbitrary size.
-- @param numtests Amount of times the property calls this generator; used to
-- guide the optimatization process.
-- @return table of an arbitrary size
local function arbitrary_size_pick(numtests)
local size = random.between(0, numtests)
return do_generic_pick(size)
end
--- Shrinks a table by removing elements or shrinking values in the table.
-- @param prev previously generated value
-- @return shrunk down table value
local function arbitrary_size_shrink(prev)
local size = #prev
if size == 0 then
return prev
end -- handle empty tables
if should_shrink_smaller(size) then
return shrink_smaller(prev, size)
end
local tbl_copy = deep_copy(prev)
local new_tbl = shrink_values(tbl_copy, size, shrink_how_many(size))
if deep_equals(prev, new_tbl) then
-- shrinking didn't help, remove an element
return shrink_smaller(prev, size)
end
-- table shrunk successfully!
return new_tbl
end
return Gen.new(arbitrary_size_pick, arbitrary_size_shrink)
end
return new_table

@ -1,39 +0,0 @@
--- Helper module for performing a deep copy.
-- @module lqc.helpers.deep_copy
-- @alias deep_copy
local pairs = pairs
local type = type
local setmetatable = setmetatable
local getmetatable = getmetatable
--- Deep copies an object recursively (including (nested) tables, metatables,
-- circular references, ...)
-- Heavily based on http://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value
-- @param obj Object to be copied
-- @param seen Table of previously seen objects (for handling circular references), default nil
-- @return deep copy of obj
local function deep_copy(obj, seen)
-- handle number, string, boolean, ...
if type(obj) ~= 'table' then
return obj
end
seen = seen or {}
if seen[obj] then
return seen[obj]
end -- handle circular references
-- handle table
local result = {}
seen[obj] = result
for key, value in pairs(obj) do
result[deep_copy(key, seen)] = deep_copy(value, seen)
end
-- handle metatable
return setmetatable(result, deep_copy(getmetatable(obj), seen))
end
return deep_copy

@ -1,37 +0,0 @@
--- Helper module for checking if 2 values are equal by value.
-- @module lqc.helpers.deep_equals
-- @alias deep_equals
local pairs = pairs
local type = type
--- Checks 1 value is equal to another. Also works for nested structures.
-- @param a value a
-- @param b value b
-- @return true if objects are equal; otherwise false
local function deep_equals(a, b)
local type_a = type(a)
if type_a ~= type(b) then
return false
end
if type_a ~= 'table' then
return a == b
end
if #a ~= #b then
return false
end
for k, v1 in pairs(a) do
local v2 = b[k]
if type(v1) == 'table' then
return deep_equals(v1, v2)
end
if v1 ~= v2 then
return false
end
end
return true
end
return deep_equals

@ -1,23 +0,0 @@
--- Helper module for filtering elements out of an array based on a predicate.
-- @module lqc.helpers.filter
-- @alias filter
--- Filters an array based on a predicate function
-- @param array List of values in a table
-- @param predicate Function taking 1 argument (element in the array), returns
-- a bool indicating if element should be removed or not
-- @return new array containing only the values for which the predicate is true
local function filter(array, predicate)
local result = {}
for idx = 1, #array do
local value = array[idx]
if predicate(value) then
result[#result + 1] = value
end
end
return result
end
return filter

@ -1,109 +0,0 @@
--- Helper module for everything filesystem related.
-- @module lqc.helpers.fs
-- @alias lib
local lfs = require 'lfs'
local Vector = require 'lqc.helpers.vector'
local lib = {}
--- Concatenates multiple strings together into 1 big string
-- @param strings array of strings to be concatenated together
-- @return the concatenated string
local function strcat(...)
return table.concat({...})
end
--- Checks if 'f' is a file?
-- @param f string of a file path
-- @return true if f is a file; otherwise false
function lib.is_file(f)
return lfs.attributes(f, 'mode') == 'file'
end
--- Check if 'd' is a directory?
-- @param d string of a directory path
-- @return true if d is a directory; otherwise false
function lib.is_dir(d)
return lfs.attributes(d, 'mode') == 'directory'
end
--- Is the file a Lua file? (=a file ending in .lua)
-- @param file path to a file
-- @return true if it is a Lua file; otherwise false.
function lib.is_lua_file(file)
return file:match('%.lua$') ~= nil
end
--- Is the file a Moonscript file? (= a file ending in .moon)
-- @param file path to a file
-- @return true if it is a Moonscript file; otherwise false.
function lib.is_moonscript_file(file)
return file:match('%.moon$') ~= nil
end
--- Removes a file from the filesystem.
-- @param path Path to the file that should be removed
function lib.remove_file(path)
os.remove(path)
end
--- Checks if a file exists.
-- @param path path to a file
-- @return true if the file does exist; otherwise false
function lib.file_exists(path)
return lfs.attributes(path) ~= nil
end
--- Reads the entire contents from a (binary) file and returns it.
-- @param path path to the file to read from
-- @return the contents of the file as a string or nil on error
function lib.read_file(path)
local file = io.open(path, 'rb')
if not file then
return nil
end
local contents = file:read '*a'
file:close()
return contents
end
--- Writes the 'new_contents' to the (binary) file specified by 'path'
-- @param path path to file the contents should be written to
-- @param new_contents the contents that will be written
-- @return nil; raises an error if file could not be opened.
function lib.write_file(path, new_contents)
if not new_contents then
return
end
local file = io.open(path, 'wb')
if not file then
error('Could not write to ' .. path .. '!')
end
file:write(new_contents)
file:close()
end
--- Finds all files in a directory.
-- @param directory_path String of a directory path
-- @return a table containing all files in this directory and it's
-- subdirectories. Raises an error if dir is not a valid
-- string to a directory path.
function lib.find_files(directory_path)
local result = Vector.new()
for file_name in lfs.dir(directory_path) do
if file_name ~= '.' and file_name ~= '..' then
local file = strcat(directory_path, '/', file_name)
if lib.is_dir(file) then
result:append(Vector.new(lib.find_files(file)))
elseif lib.is_file(file) then
result:push_back(file)
end
end
end
return result:to_table()
end
return lib

@ -1,19 +0,0 @@
--- Helper moduile for performing a function on each element in an array.
-- @module lqc.helpers.map
-- @alias map
--- Maps a function over an array
-- @param array List of elements on which a function will be applied
-- @param func Function to be applied over the array. Takes 1 argument (element of the array); returns a result
-- @return A new array with func applied to each element in the array
local function map(array, func)
local result = {}
for idx = 1, #array do
result[#result + 1] = func(array[idx])
end
return result
end
return map

@ -1,33 +0,0 @@
--- Helper module for reducing an array of values into a single value.
-- @module lqc.helpers.reduce
-- @alias reeduce
--- Helper function that performs the actual reduce operation
-- @param array List of elements to be reduced into 1 value
-- @param acc Accumulator containing the temporary result
-- @param func Function to be applied for each element in the array. Takes 2
-- arguments: element out of the array and the current state of the
-- accumulator. Returns the updated accumulator state
-- @param pos Position in the array to apply the reduce operation on
-- @return the result obtained by reducing the array into 1 value
local function do_reduce(array, acc, func, pos)
if pos < #array then
local new_pos = pos + 1
return do_reduce(array, func(array[new_pos], acc), func, new_pos)
end
return acc
end
--- Reduces an array of values into a single value
-- @param array List of elements to be reduced into 1 value
-- @param start Start value of the accumulator
-- @param func Function to be applied for each element in the array. Takes 2
-- arguments: element out of the array and the current state of the
-- accumulator. Returns the updated accumulator state
-- @return the result obtained by reducing the array into 1 value
local function reduce(array, start, func)
return do_reduce(array, start, func, 0)
end
return reduce

@ -1,115 +0,0 @@
--- Module for a data container that does not allow nil values.
-- @classmod lqc.helpers.vector
-- @alias Vector
local deep_equals = require 'lqc.helpers.deep_equals'
local Vector = {}
local Vector_mt = {
__index = Vector,
}
--- Constructs a new vector, possibly filled with data (a table value)
-- @param data[opt={}] the data to be stored in the vector initially
-- @return a new vector filled with the initial data if provided.
function Vector.new(data)
local begin_data = data or {}
local vector = {
data = begin_data,
}
return setmetatable(vector, Vector_mt)
end
--- Adds an element to the back of the vector.
-- @param obj a non-nil value
-- @return self (for method chaining); raises an error if trying to add nil to the vector
function Vector:push_back(obj)
if obj == nil then
error 'nil is not allowed in vector datastructure!'
end
table.insert(self.data, obj)
return self
end
--- Replaces an element in the vector.
-- @param idx Index of the element in the vector to be replaced
-- @param obj Object that the previous object should be replaced with
-- @return self (for method chaining); raises an error if idx is an index
-- not present in the vector
function Vector:replace(idx, obj)
local vec_size = self:size()
if idx < 1 or idx > vec_size then
error('Invalid index! Index should be between 1 and ' .. vec_size)
end
self.data[idx] = obj
return self
end
--- Appends another vector to this vector
-- @param other_vec Another vector object
-- @return a vector containing the data of both vectors
function Vector:append(other_vec)
for i = 1, other_vec:size() do
self:push_back(other_vec:get(i))
end
return self
end
--- Gets the element at position 'index' in the vector
-- @param index position of the value in the vector
-- @return element at position 'index
function Vector:get(index)
return self.data[index]
end
--- Checks if an element is contained in the vector
-- @param element element to be checked if it is in the vector
-- @return true if the element is already in the vector; otherwise false.
function Vector:contains(element)
for i = 1, #self.data do
if self.data[i] == element then
return true
end
end
return false
end
--- Returns the size of the vector.
-- @return length of the vector (0 if empty)
function Vector:size()
return #self.data
end
--- Removes an element from the vector by value
-- @param obj object to remove
function Vector:remove(obj)
-- Find element, then remove by index
local pos = -1
for i = 1, #self.data do
if deep_equals(self.data[i], obj) then
pos = i
break
end
end
if pos == -1 then
return
end
table.remove(self.data, pos)
end
--- Removes an element from the vector by index
-- @param idx Position of the element you want to remove
function Vector:remove_index(idx)
if idx > self:size() then
return
end
table.remove(self.data, idx)
end
--- Returns the vector, with the contents represented as a flat table
-- @return Table with the contents of the vector
function Vector:to_table()
return self.data
end
return Vector

@ -1,123 +0,0 @@
--- Helper module providing various generators for generating data.
-- @module lqc.lqc_gen
-- @alias lib
local Gen = require 'lqc.generator'
local random = require 'lqc.random'
local reduce = require 'lqc.helpers.reduce'
local lib = {}
--- Picks a number randomly between min and max.
-- @param min Minimum value to pick from
-- @param max Maximum value to pick from
-- @return a random value between min and max
local function choose_pick(min, max)
local function pick()
return random.between(min, max)
end
return pick
end
--- Shrinks a value between min and max by dividing the sum of the closest
-- number to 0 and the generated value with 2.
-- This effectively reduces it to the value closest to 0 gradually in the
-- chosen range.
-- @param min Minimum value to pick from
-- @param max Maximum value to pick from
-- @return a shrunk value between min and max
local function choose_shrink(min, max)
local shrink_to = (math.abs(min) < math.abs(max)) and min or max
local function shrink(value)
local shrunk_value = (shrink_to + value) / 2
if shrunk_value < 0 then
return math.ceil(shrunk_value)
else
return math.floor(shrunk_value)
end
end
return shrink
end
--- Creates a generator, chooses an integer between min and max (inclusive range).
-- @param min Minimum value to pick from
-- @param max Maximum value to pick from
-- @return a random value between min and max
function lib.choose(min, max)
return Gen.new(choose_pick(min, max), choose_shrink(min, max))
end
--- Select a generator from a list of generators
-- @param generators Table containing an array of generator objects.
-- @return A new generator that randomly uses 1 of the generators in the list.
function lib.oneof(generators)
local which
local function oneof_pick(numtests)
which = random.between(1, #generators)
return generators[which]:pick(numtests)
end
local function oneof_shrink(prev)
return generators[which]:shrink(prev)
end
return Gen.new(oneof_pick, oneof_shrink)
end
--- Select a generator from a list of weighted generators ({{weight1, gen1}, ... })
-- @param generators A table containing an array of weighted generators.
-- @return A new generator that randomly uses a generator from the list, taking the
-- weights into account.
function lib.frequency(generators)
local which
local function do_sum(generator, acc)
return generator[1] + acc
end
local function frequency_pick(numtests)
local sum = reduce(generators, 0, do_sum)
local val = random.between(1, sum)
which = reduce(generators, {0, 1}, function(generator, acc)
local current_sum = acc[1] + generator[1]
if current_sum >= val then
return acc
else
return {current_sum, acc[2] + 1}
end
end)[2]
return generators[which][2]:pick(numtests)
end
local function frequency_shrink(prev)
return generators[which][2]:shrink(prev)
end
return Gen.new(frequency_pick, frequency_shrink)
end
--- Create a generator that selects an element based on the input list.
-- @param array an array of constant values
-- @return Generator that can pick 1 of the values in the array, shrinks
-- towards beginning of the list.
function lib.elements(array)
local last_idx
local function elements_pick()
local idx = random.between(1, #array)
last_idx = idx
return array[idx]
end
local function elements_shrink(_)
if last_idx > 1 then
last_idx = last_idx - 1
end
return array[last_idx]
end
return Gen.new(elements_pick, elements_shrink)
end
return lib

@ -1,228 +0,0 @@
--- Module for creating properties. Provides a small domain specific language
-- for ease of use.
-- @module lqc.property
-- @alias property
local lqc = require 'lqc.quickcheck'
local report = require 'lqc.report'
local results = require 'lqc.property_result'
local unpack = unpack or table.unpack -- for compatibility reasons
--- Helper function, checks if x is an integer.
-- @param x a value to be checked
-- @return true if x is an integer; false otherwise.
local function is_integer(x)
return type(x) == 'number' and x % 1 == 0
end
--- Adds a small wrapper around the check function indicating success or failure
-- @param prop_table Property to be wrapped.
local function add_check_wrapper(prop_table)
local check_func = prop_table.check
prop_table.check = function(...)
if check_func(...) then
return results.SUCCESS
else
return results.FAILURE
end
end
end
--- Adds an 'implies' wrapper to the check function
-- @param prop_table Property to be wrapped.
local function add_implies(prop_table)
local check_func = prop_table.check
prop_table.check = function(...)
if prop_table.implies(...) == false then
return results.SKIPPED
end
return check_func(...)
end
end
--- Adds a 'when_fail' wrapper to the check function
-- @param prop_table Property to be wrapped.
local function add_when_fail(prop_table)
local check_func = prop_table.check
prop_table.check = function(...)
local result = check_func(...)
if result == results.FAILURE then
prop_table.when_fail(...)
end
return result
end
end
--- Shrinks a property that failed with a certain set of inputs.
-- This function is called recursively if a shrink fails.
-- This function returns a simplified list or inputs.
-- @param property the property that failed
-- @param generated_values array of values to be shrunk down
-- @param tries Amount of shrinking tries already done
-- @return array of shrunk down values
local function do_shrink(property, generated_values, tries)
if not tries then
tries = 1
end
local shrunk_values = property.shrink(unpack(generated_values))
local result = property(unpack(shrunk_values))
if tries == property.numshrinks then
-- Maximum amount of shrink attempts exceeded.
return generated_values
end
if result == results.FAILURE then
-- further try to shrink down
return do_shrink(property, shrunk_values, tries + 1)
elseif result == results.SKIPPED then
-- shrunk to invalid situation, retry
return do_shrink(property, generated_values, tries + 1)
end
-- return generated values since they were last values for which property failed!
return generated_values
end
--- Function that checks if the property is valid for a set amount of inputs.
-- 1. check result of property X amount of times:
-- - SUCCESS = OK, print '.'
-- - SKIPPED = OK, print 'x'
-- - FAILURE = NOT OK, see 2.
-- 2. if FAILURE:
-- 2.1 print property info, values for which it fails
-- 2.2 do shrink to find minimal error case
-- 2.3 when shrink stays the same or max amount exceeded -> print minimal example
-- @param property Property to be checked X amount of times.
-- @return nil on success; otherwise returns a table containing error info.
local function do_check(property)
for _ = 1, property.numtests do
local generated_values = property.pick()
local result = property(unpack(generated_values))
if result == results.SUCCESS then
report.report_success()
elseif result == results.SKIPPED then
report.report_skipped()
else
report.report_failed()
if #generated_values == 0 then
-- Empty list of generators -> no further shrinking possible!
return {
property = property,
generated_values = generated_values,
shrunk_values = generated_values,
}
end
local shrunk_values = do_shrink(property, generated_values)
return {
property = property,
generated_values = generated_values,
shrunk_values = shrunk_values,
}
end
end
return nil
end
--- Creates a new property.
-- NOTE: property is limited to 1 implies, for_all, when_fail
-- more complex scenarios should be handled with state machine.
-- @param descr String containing description of the property
-- @param property_func Function to be checked X amount of times
-- @param generators List of generators used to generate values supplied to 'property_func'
-- @param numtests Number of times the property should be checked
-- @param numshrinks Number of times a failing property should be shrunk down
-- @return property object
local function new(descr, property_func, generators, numtests, numshrinks)
local prop = {
description = descr,
numtests = numtests,
numshrinks = numshrinks,
}
-- Generates a new set of inputs for this property.
-- Returns the newly generated set of inputs as a table.
function prop.pick()
local generated_values = {}
for i = 1, #generators do
generated_values[i] = generators[i]:pick(numtests)
end
return generated_values
end
-- Shrink 1 value randomly out of the given list of values.
function prop.shrink(...)
local values = {...}
local which = math.random(#values)
local shrunk_value = generators[which]:shrink(values[which])
values[which] = shrunk_value
return values
end
-- Function that checks if the property is valid for a set amount of inputs.
function prop:check()
return do_check(self)
end
return setmetatable(prop, {
__call = function(_, ...)
return property_func(...)
end,
})
end
--- Inserts the property into the list of existing properties.
-- @param descr String containing text description of the property
-- @param prop_info_table Table containing information of the property
-- @return nil; raises an error if prop_info_table is not in a valid format
local function property(descr, prop_info_table)
local function prop_func(prop_table)
local generators = prop_table.generators
if not generators or type(generators) ~= 'table' then
error('Need to supply generators in property!')
end
local check_type = type(prop_table.check)
if check_type ~= 'function' and check_type ~= 'table' then
error('Need to provide a check function to property!')
end
add_check_wrapper(prop_table)
local implies_type = type(prop_table.implies)
if implies_type == 'function' or implies_type == 'table' then
add_implies(prop_table)
end
local when_fail_type = type(prop_table.when_fail)
if when_fail_type == 'function' or when_fail_type == 'table' then
add_when_fail(prop_table)
end
local it_amount = prop_table.numtests
local shrink_amount = prop_table.numshrinks
local numtests = is_integer(it_amount) and it_amount or lqc.numtests
local numshrinks = is_integer(shrink_amount) and shrink_amount or lqc.numshrinks
local new_prop = new(descr, prop_table.check, prop_table.generators, numtests, numshrinks)
table.insert(lqc.properties, new_prop)
end
-- property called without DSL-like syntax
if prop_info_table then
prop_func(prop_info_table)
return function()
end
end
-- property called with DSL syntax!
return prop_func
end
return property

@ -1,13 +0,0 @@
--- Helper module containing enumeration of all possible results after
-- evaluating a property.
-- @module lqc.property_result
--- List of possible results after executing property
-- @table result_enum
-- @field SUCCESS property succeeded
-- @field FAILURE property failed
-- @field SKIPPED property skipped (implies predicate not met)
return {
SUCCESS = 1,
FAILURE = 2,
SKIPPED = 3,
}

@ -1,81 +0,0 @@
--- Module which contains the core of the quickcheck engine.
-- @module lqc.quickcheck
-- @alias lib
local report = require 'lqc.report'
local map = require 'lqc.helpers.map'
local shuffle = pairs
local lib = {
properties = {}, -- list of all properties
numtests = nil, -- Default amount of times a property should be tested
numshrinks = nil, -- Default amount of times a failing property should be shrunk down
failed = false,
}
--- Checks if the quickcheck configuration is initialized
-- @return true if it is initialized; otherwise false.
local function is_initialized()
return lib.numtests ~= nil and lib.numshrinks ~= nil
end
--- Handles the result of a property.
-- @param result table containing information of the property (or nil on success)
-- @see lqc.property_result
local function handle_result(result)
if not result then
return
end -- successful
lib.failed = true
if type(result.property) == 'table' then -- property failed
report.report_failed_property(result.property, result.generated_values, result.shrunk_values)
return
end
-- FSM failed
report.report_failed_fsm(result.description, result.generated_values, result.shrunk_values)
end
--- Configures the amount of iterations and shrinks the check algorithm should perform.
-- @param numtests Default number of tests per property
-- @param numshrinks Default number of shrinks per property
function lib.init(numtests, numshrinks)
lib.numtests = numtests
lib.numshrinks = numshrinks
end
--- Iterates over all properties in a random order and checks if the property
-- holds true for each generated set of inputs. Raises an error if quickcheck
-- engine is not initialized yet.
function lib.check()
if not is_initialized() then
error 'quickcheck.init() has to be called before quickcheck.check()!'
end
for _, prop in shuffle(lib.properties) do
local result = prop:check()
handle_result(result)
end
end
--- Multithreaded version of check(), uses a thread pool in the underlying
-- implementation. Splits up the properties over different threads.
-- @param numthreads Number of the threads to divide the properties over.
function lib.check_mt(numthreads)
local ThreadPool = require 'lqc.threading.thread_pool'
if not is_initialized() then
error 'quickcheck.init() has to be called before quickcheck.check_mt()!'
end
local pool = ThreadPool.new(numthreads)
for _, prop in shuffle(lib.properties) do
pool:schedule(function()
return prop:check()
end)
end
local results = pool:join()
map(results, handle_result)
end
return lib

@ -1,29 +0,0 @@
--- Helper module for generating random numbers.
-- @module lqc.random
-- @alias lib
local time = os.time
local random_seed = math.randomseed
local random = math.random
local lib = {}
--- Seeds the random number generator
-- @param seed Random seed (number) or nil for current timestamp
-- @return The random seed used to initialize the random number generator with.
function lib.seed(seed)
if not seed then
seed = time()
end
random_seed(seed)
return seed
end
--- Get random number between min and max
-- @param min Minimum value to generate a random number in
-- @param max Maximum value to generate a random number in (inclusive)
function lib.between(min, max)
return random(min, max)
end
return lib

@ -1,126 +0,0 @@
--- Helper module for reporting test results to the user.
-- @module lqc.report
-- @alias lib
local map = require 'lqc.helpers.map'
local write = io.write
local ipairs = ipairs
-- Variables for reporting statistics after test run is over.
local passed_amount = 0
local failed_amount = 0
local skipped_amount = 0
local reported_errors = {}
local lib = {}
--- Formats a table to a human readable string
-- @param t table to be formatted
-- @return formatted table (as a string)
local function format_table(t)
local result = '{ '
for _, v in ipairs(t) do
local type_v = type(v)
if type_v == 'table' then
result = result .. format_table(v) .. ' '
elseif type_v == 'boolean' then
result = result .. (v and 'true ' or 'false ')
else
result = result .. v .. ' '
end
end
return result .. '}'
end
--- Writes a string to stdout (no newline at end).
-- @param s string to be written to stdout
function lib.report(s)
write(s)
end
--- Prints the used random seed to stdout.
-- @param seed Random seed to be printed to stdout
function lib.report_seed(seed)
lib.report('Random seed = ' .. seed .. '\n')
end
--- Prints a '.' to stdout
function lib.report_success()
passed_amount = passed_amount + 1
lib.report '.'
end
--- Prints a green '.' to stdout
local function report_success_colored()
passed_amount = passed_amount + 1
lib.report '\27[32m.\27[0m'
end
--- Prints a 'x' to stdout
function lib.report_skipped()
skipped_amount = skipped_amount + 1
lib.report 'x'
end
--- Prints a yellow 'x' to stdout
local function report_skipped_colored()
skipped_amount = skipped_amount + 1
lib.report '\27[33mx\27[0m'
end
--- Prints an 'F' to stdout
function lib.report_failed()
failed_amount = failed_amount + 1
lib.report 'F'
end
--- Prints a red 'F' to stdout
local function report_failed_colored()
failed_amount = failed_amount + 1
lib.report '\27[31mF\27[0m'
end
--- Saves an error to the list of errors.
function lib.save_error(failure_str)
table.insert(reported_errors, failure_str)
end
--- Prints out information regarding the failed property
function lib.report_failed_property(property, generated_values, shrunk_values)
lib.save_error('\nProperty "' .. property.description .. '" failed!\n' .. 'Generated values = ' ..
format_table(generated_values) .. '\n' .. 'Simplified solution to = ' ..
format_table(shrunk_values) .. '\n')
end
--- Prints out information regarding the failed FSM.
function lib.report_failed_fsm(description)
-- TODO output more information
lib.save_error('\nFSM ' .. description .. ' failed!\n')
end
--- Reports all errors to stdout.
function lib.report_errors()
map(reported_errors, lib.report)
lib.report '\n' -- extra newline as separator between errors
end
--- Prints a summary about certain statistics (test passed / failed, ...)
function lib.report_summary()
local total_tests = passed_amount + failed_amount + skipped_amount
lib.report('' .. total_tests .. ' tests, ' .. failed_amount .. ' failures, ' .. skipped_amount .. ' skipped.\n')
end
--- Configures this module to use ANSI colors when printing to terminal or not.
-- @param enable_colors true: colors will be used when printing to terminal;
-- otherwise plain text will be printed.
function lib.configure(enable_colors)
if not enable_colors then
return
end
lib.report_success = report_success_colored
lib.report_skipped = report_skipped_colored
lib.report_failed = report_failed_colored
end
return lib

@ -1,44 +0,0 @@
--- Helper module for a message processor that can process incoming messages
-- from other threads.
-- @classmod lqc.threading.msg_processor
-- @alias MsgProcessor
--- Checks if 'x' is callable.
-- @param x value to be checked
-- @return true if callable; otherwise false.
local function is_callable(x)
local type_x = type(x)
return type_x == 'function' or type_x == 'table'
end
local MsgProcessor = {
TASK_TAG = 'task',
RESULT_TAG = 'result',
STOP_VALUE = 'stop',
VOID_RESULT = '__VOID',
}
--- Creates an object that can handle incoming messages.
-- @param msg_box An object that can be used to send and receive incoming messages with
-- @return a new MsgProcessor object
function MsgProcessor.new(msg_box)
local function main_loop_msg_processor()
-- TODO init random seed per thread?
while true do
local _, cmd = msg_box:receive(nil, MsgProcessor.TASK_TAG)
if cmd == MsgProcessor.STOP_VALUE then
return
elseif is_callable(cmd) then
-- NOTE: threadpool hangs if it returns nil..
local result = cmd() or MsgProcessor.VOID_RESULT
msg_box:send(nil, MsgProcessor.RESULT_TAG, result)
else
return
end
end
end
return main_loop_msg_processor
end
return MsgProcessor

@ -1,83 +0,0 @@
--- Module for creating a thread pool, based on Lua Lanes.
-- @module lqc.threading.thread_pool
-- @alias ThreadPool
local MsgProcessor = require 'lqc.threading.msg_processor'
local map = require 'lqc.helpers.map'
local lanes = require('lanes').configure {
with_timers = false,
}
--- Checks if x is a positive integer (excluding 0)
-- @param x value to be checked
-- @return true if x is a non-zero positive integer; otherwise false.
local function is_positive_integer(x)
return type(x) == 'number' and x % 1 == 0 and x > 0
end
--- Checks if the thread pool args are valid.
-- @return nil; raises an error if invalid args are passed in.
local function check_threadpool_args(num_threads)
if not is_positive_integer(num_threads) then
error 'num_threads should be an integer > 0'
end
end
--- Creates and starts a thread.
-- @param func Function the thread should run after startup
-- @return a new thread object
local function make_thread(func)
return lanes.gen('*', func)()
end
local ThreadPool = {}
local ThreadPool_mt = {
__index = ThreadPool,
}
--- Creates a new thread pool with a specific number of threads
-- @param num_threads Amount of the threads the pool should have
-- @return thread pool with a specific number of threads
function ThreadPool.new(num_threads)
check_threadpool_args(num_threads)
local linda = lanes.linda()
local thread_pool = {
threads = {},
linda = linda,
numjobs = 0,
}
for _ = 1, num_threads do
table.insert(thread_pool.threads, make_thread(MsgProcessor.new(linda)))
end
return setmetatable(thread_pool, ThreadPool_mt)
end
--- Schedules a task to a thread in the thread pool
-- @param task A function that should be run on the thread
function ThreadPool:schedule(task)
self.numjobs = self.numjobs + 1
self.linda:send(nil, MsgProcessor.TASK_TAG, task)
end
--- Stops all threads in the threadpool. Blocks until all threads are finished
-- @return a table containing all results (in no specific order)
function ThreadPool:join()
map(self.threads, function()
self:schedule(MsgProcessor.STOP_VALUE)
end)
map(self.threads, function(thread)
thread:join()
end)
local results = {}
for _ = 1, self.numjobs - #self.threads do -- don't count stop job at end
local _, result = self.linda:receive(nil, MsgProcessor.RESULT_TAG)
if result ~= MsgProcessor.VOID_RESULT then
table.insert(results, result)
end
end
return results
end
return ThreadPool
Loading…
Cancel
Save