🐳 chore(工具): 增加 启动 脚本
parent
7f4359e75d
commit
f02ccab557
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -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…
Reference in New Issue