🐳 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