模拟acm测试系统(更新4.13)

模拟acm测试系统

1. 前言

最近和朋友在做一些acm的训练,虽然算法功力比较薄弱,但对acm的测试系统产生
了一点兴趣,于是就尝试使用shell脚本做了一样小型的模拟程序。
 
运行环境主要是linux和mac。我的环境是mac。目前只支持c语言。
 
主要功能有:
  1. 第一次运行时,会初始化设置你的项目根目录,并询问是否将脚本加入到PATH
  2. 根据用户输入的题目命名,生成题目目录,包括源文件,测试数据文件,期望数据文件
  3. 可以生成题目目录的时候,选择是否在控制台输入测试和期望数据
  4. 根据测试数据文件运行源代码,并生成结果输出文件,和期望数据文件进行比对,输出比对结果
  5. 提供运行时间的测试,毫秒级别,不过需要安装python
  6. 启动使用gdb命令调试程序
  7. 一些简单的错误处理

优点:

因为acm题目要求标准的输入和输出,这样导致测试的时候不好进行大数据量的测试。
一般的做法是在源代码中加入读测试文件的代码,再在提交前再修改成标准输入这些代码,
或是使用重定向的宏,提交前再注释这些宏。总之觉得有点麻烦。
理想的状态是本地的代码直接原封不动的提交上去。这个脚本正是为了达到这个目的而进行实现的。

注意:

因为在mac环境中,c语言的编译器默认选择clang,如果没有安装,则选择gcc。
在测试时间的功能里,是调用python来完成的,mac下没有找到系统自带的好用的时间命令。
这次的脚本编写,也可以是看作是一次shell命令学习,挺有意思,不过工具不重要,算法的提高
才是重点啊~~

想试试的同学,复制代码,保存到名为acm_manage.sh的文件中,在赋上可执行权限,运行./acm_manage.sh
就可以根据提示信息立即体验。
 
欢迎留言讨论。
 
(4.8更新)
有一个致命错误:
之前是使用
while read -r line
do
   echo $line | $EXECUTABLE_FILE_PATH >> $RESULT_FILE_PATH
done < $TEST_DATA_FILE_PATH
来逐行读取文件,并运行程序,这样表面上可以运行通过一些题目,不过一个
重要的问题是,它不能处理需要连续读取多行内容,再运行程序的题目。因为
它是每读取一行,就重新运行一次main函数。显然是错的。。。。
 
改成一种简单的方式,一次性读取所有内容,并运行main函数:
cat $TEST_DATA_FILE_PATH | $EXECUTABLE_FILE_PATH >> $RESULT_FILE_PATH
 
(4.12更新)
新添加gdb自动从测试文件输入数据功能
原来是这样:
debug_code()
{
    echo "Debug is start."
    $DEBUG_TOOL $EXECUTABLE_FILE_PATH
    echo "Debug is done."
}
改成:
debug_code()
{
    echo "Debug is start."
    echo "start < $TEST_DATA_FILE_PATH" > $DEBUG_COMMAND_FILE_PATH
    $DEBUG_TOOL -x $DEBUG_COMMAND_FILE_PATH $EXECUTABLE_FILE_PATH
    rm $DEBUG_COMMAND_FILE_PATH
    echo "Debug is done."
}
实现方法是,使用-x命令,从文件中读取gdb shell的命令,在这里使用start带参数的方式来完成
gdb的重定向输入。所以我就先创建一个这样的命令文件,调试结束的时候再删除。之前使用gdb -ex
尝试直接输入启动命令,好像不成功。目前不知道还有没有其它的方式。

(4.13更新)
去掉编译-O2优化选项,便于调试。改成:
OPTIMIZE_OPTION=
在调试时,需要重新编译代码,之前没有写。。。
select_compile_tool
compile_code
加入到最后while循环部分的,debug分支中去。

2. 代码

#!/bin/bash

#################################################################################################
# Test Shell Script for acm program.
#
# Usage is:
# ./acm_manage.sh i questionName
# ./acm_manage.sh r questionName
# ./acm_manage.sh d questionName
# ./acm_manage.sh h
# ./acm_manage.sh help
#
# Main Function is:
# 1. init acm directory and create source, test, expect files for you.
# 2. run your code with your test data file, and check its correctness with your expect file.
# 3. debug your code with your test data file.
#
# Platform:
# Linux, Mac OSX, Unix
#
# Support Program Language:
# C
#
# Author:
# Hanks
#
# Version:
# 1.0
#################################################################################################

########################
# varibles define start
########################

# script name
SCRIPT_NAME="acm_manage.sh"

# diretory for question
QUESTION_DIR=
# question name
QUESTION_NAME=

# file name prefix
TEST_FILE_PREFIX="test_"
EXPECT_FILE_PREFIX="expect_"
RESULT_FILE_PREFIX="result_"

# file type
SOURCE_FILE_TYPE=".c"
DATA_FILE_TYPE=".txt"

# path for test data file
TEST_DATA_FILE_PATH=
# path for executable file
EXECUTABLE_FILE_PATH=
# path for result file
RESULT_FILE_PATH=
# path for source code file
SOURCE_CODE_PATH=
# path for expect data file
EXPECT_FILE_PATH=
# default executable file name
DEFAILT_EXECUTABLE_FILE_NAME="a.out"
# gdb command temp file
DEFAULT_DEBUG_COMMAND_FILE_NAME="gdb_cmd_temp"
DEBUG_COMMAND_FILE_PATH=

# cost second time
RUN_START_TIME=
RUN_END_TIME=
TIME_COST=

# accept time is 3 millisecond
ACCEPT_TIME=3000

# make format of each line of content from echo be corrent
IFS="
"


# init compiler command, default is clang, if no, use gcc
COMPILE_COMMAND=
DEBUG_OPTION="-g"
OPTIMIZE_OPTION=
DEBUG_TOOL="gdb"

# acm workspace existed flag
IS_WORKSPACE_NOT_EXIST=false
IS_ACM_ROOT_NOT_EXIST=false

#config variable
SCRIPT_DIRECTORY=$(cd "$(dirname "$0")"; pwd)
ACM_ROOT_NAME=
ACM_ROOT=
CONFIG_FILE_NAME="$SCRIPT_DIRECTORY/acm_manage.ini"
SET_PATH=

######################
# varibles define end
######################

#######################
# function define start
#######################

# add acm_manage.sh to your PATH environment varibale,
# so you can use this command defaultly.
set_script_to_path()
{
    # ask user whether to add to path or not
    loop=true
    while $loop; do
    read -n1 -p "Add this script to your PATH to run anywhere. [y/n]?" answer
        case $answer in
            Y | y)
                echo
                echo "Fine, continue."
                SET_PATH=true
                loop=false
                ;;
            N | n)
                echo
                echo "Ok, I got it. Donot set to path."
                SET_PATH=fasle
                loop=false 
                return
                ;;
            *)
                echo "Error choice, please answer with y or n."
                ;;
        esac
        echo
    done
    PROFILE_PATH=~/.profile
    BASH_PROFILE_PATH=~/.bash_profile
    OS_TYPE=`uname`

    ADD_PATH_COMMAND="export PATH=\$PATH:$SCRIPT_DIRECTORY"
    TARGET_PROFILE_PATH=

    if [ $OS_TYPE = "Darwin" ]; then
        echo "You are a Mac system user."
        TARGET_PROFILE_PATH=$PROFILE_PATH
    elif [ $OS_TYPE = "Linux" ]; then
        echo "You are a Linux system user."
        TARGET_PROFILE_PATH=$BASH_PROFILE_PATH
    elif [ $OS_TYPE = "FreeBSD" ]; then
        echo "You are a FreeBSD system user."
        TARGET_PROFILE_PATH=$BASH_PROFILE_PATH
    fi
    echo "Start to add this script to your path."
    echo "#Add acm_manage.sh command to your PATH" >> $TARGET_PROFILE_PATH
    echo $ADD_PATH_COMMAND >> $TARGET_PROFILE_PATH
    echo "Add path is done. You can check $TARGET_PROFILE_PATH."
    echo "So you can run this script anywhere. Enjoy."
}


# print help info for user
print_help()
{
    echo
    echo "Usage: acm_manage.sh [i|r|d|h|help] questionName"
    echo "Options: These are optional argument"
    echo " i init acm directory, create directory, source, test file and expect file automatically for you."
    echo " h|help show help info."
    echo " r run your code with your test data. And diff output and expect file to check correctness "
    echo " d start debug tool (like gdb, lldb) to debug your code."
    echo
    echo "When you first run this shell, you should "
    echo "enter the directory the script is in "
    echo "and run the script. Have fun."
    echo
}

read_conf_info_from_file()
{
    ACM_ROOT=`cat $CONFIG_FILE_NAME | grep ACM_ROOT | awk -F"=" '{print $2}'`
    SET_PATH=`cat $CONFIG_FILE_NAME | grep SET_PATH | awk -F"=" '{print $2}'`
    echo "ACM_ROOT is $ACM_ROOT"
    echo "SET_PATH is $SET_PATH"
}

create_conf_file()
{
    # create configure init file
    echo "ACM_ROOT=$ACM_ROOT" >> $CONFIG_FILE_NAME
    echo "SET_PATH=$SET_PATH" >> $CONFIG_FILE_NAME
    echo
    echo "Create config file $CONFIG_FILE_NAME in the current directory."
    echo
}

set_acm_root()
{
    echo "This is your first time and last time to see this message, just config some info. ^_^"
    echo "Please input your acm root directory name, it will be created in your current directory:"
    read ACM_ROOT_NAME
    ACM_ROOT=$SCRIPT_DIRECTORY/$ACM_ROOT_NAME/
    if [ ! -d $ACM_ROOT ]; then
        mkdir $ACM_ROOT
        echo "Create $ACM_ROOT directory for you."
    fi
}

init_acm_root_directory()
{
    if [ ! -f $CONFIG_FILE_NAME ]; then
        # config file is not existed, create it
        set_acm_root
        set_script_to_path
        create_conf_file
        echo "Now you can see usage to rock acm."
        print_help
        echo
    else
        # read config info from file and init ACM_ROOT
        read_conf_info_from_file
    fi
}

# init files path
init_path()
{
    echo
    # build paths for files
    QUESTION_DIR=$ACM_ROOT$QUESTION_NAME
    #echo "questoin directory is $QUESTION_DIR"
    SOURCE_CODE_PATH=$QUESTION_DIR/$QUESTION_NAME$SOURCE_FILE_TYPE
    echo "Source file is $SOURCE_CODE_PATH"
    TEST_DATA_FILE_PATH=$QUESTION_DIR/$TEST_FILE_PREFIX$QUESTION_NAME$DATA_FILE_TYPE
    #echo "test data is $TEST_DATA_FILE_PATH"
    EXPECT_FILE_PATH=$QUESTION_DIR/$EXPECT_FILE_PREFIX$QUESTION_NAME$DATA_FILE_TYPE
    #echo "expect is $EXPECT_FILE_PATH"
    EXECUTABLE_FILE_PATH=$QUESTION_DIR/$DEFAILT_EXECUTABLE_FILE_NAME
    #echo "executable is $EXECUTABLE_FILE_PATH"
    RESULT_FILE_PATH=$QUESTION_DIR/$RESULT_FILE_PREFIX$QUESTION_NAME$DATA_FILE_TYPE
    #echo "result is $RESULT_FILE_PATH"
    DEBUG_COMMAND_FILE_PATH=$QUESTION_DIR/$DEFAULT_DEBUG_COMMAND_FILE_NAME
}

# init source code template, default is c language
init_source_code()
{
    echo "#include <stdio.h>" >> $SOURCE_CODE_PATH
    echo >> $SOURCE_CODE_PATH
    echo "int main(int argc, char *args[]) {" >> $SOURCE_CODE_PATH
    echo >> $SOURCE_CODE_PATH
    echo " return 0;" >> $SOURCE_CODE_PATH
    echo "}" >> $SOURCE_CODE_PATH
}

# init test and expect data file from user input
init_test_and_expect_data()
{
    loop=true
    while $loop; do
        read -n1 -p "Do you want to input test and expect data now. [y/n]?" answer
        case $answer in
            Y | y)
                echo "Input start:"
                echo "Please pay attention to the command to end your input."
                echo "or else you will need modify these file by yourself again."
                echo
                echo "Please input your test input data and Press Enter and then ctrl+D to end input:"
                cat > $TEST_DATA_FILE_PATH
                echo "Please input your expect output data and Press Enter and then ctrl+D to end input:"
                cat > $EXPECT_FILE_PATH
                echo "Input end."
                loop=false
                ;;
            N | n)
                touch $TEST_DATA_FILE_PATH
                touch $EXPECT_FILE_PATH
                echo
                echo "Create empty test and expect file. You need add test data by yourself."
                loop=false
                ;;
            *)
                echo "Wrong input, please answer just by y or n."
                ;;
        esac
     done

}

# create acm directory and files for user
init_acm_workspace()
{
    echo "Init acm workspace for you."
    # if directory is not existed, create a new one
    # or else, do nothing to protect the existed source files
    if $IS_WORKSPACE_NOT_EXIST; then
        # create acm diretory
        mkdir $QUESTION_DIR

        # init source and test data
        init_source_code
        init_test_and_expect_data
        echo "Init is done, let's rock."
    else
        echo "Sorry, workspace is already exsited, you can start rock."
    fi

    # if directory existed, do nothing
    return
}

# check workspace existed
workspace_exist_check()
{
    if $IS_WORKSPACE_NOT_EXIST; then
        echo "Workspace \"$QUESTION_NAME\" is not existed. Please use command \"$SCRIPT_NAME i $QUESTION_NAME\" to init workspace."
        exit 1
    fi
}

compile_code()
{
    echo "Compile start."
    $COMPILE_COMMAND $DEBUG_OPTION $SOURCE_CODE_PATH $OPTIMIZE_OPTION -o $EXECUTABLE_FILE_PATH
    echo "Compile end."
}

run_code()
{
    # delete result file firstly if existed, to avoid
    # result confict with old one
    echo
    echo "Clear result file."
    rm $RESULT_FILE_PATH > /dev/null

    # create a new empty result file
    touch $RESULT_FILE_PATH

    # run code with test data, and redirect output to result file
    echo
    echo "Start to run code:"
    # record start time, %N means nanoseconds
    # second is 1
    # millisecond is 0.001
    # macrosecond is 0.000001
    # nanosecond is 0.000000001
    #RUN_START_TIME=`date +%s%N`
    # get total millisecond from epoth
    RUN_START_TIME=$(python -c 'import time; print int(round(time.time()*1000))')
    #while read -r line
    #do
    #    echo $line | $EXECUTABLE_FILE_PATH >> $RESULT_FILE_PATH
    #done < $TEST_DATA_FILE_PATH
    cat $TEST_DATA_FILE_PATH | $EXECUTABLE_FILE_PATH >> $RESULT_FILE_PATH
    # record end time
    RUN_END_TIME=$(python -c 'import time; print int(round(time.time()*1000))')
    echo "Run is done."
}

print_run_time_cost()
{
    echo
    # cost in milliseconds
    TIME_COST=$((RUN_END_TIME-RUN_START_TIME))
    # cost in seconds
    SECOND_TIME_COST=`echo "$TIME_COST / 1000 " | bc -l`
    echo "Run time cost is ${SECOND_TIME_COST:0:4} seconds."
}

print_output()
{
    echo
    echo "Output is:"
    cat $RESULT_FILE_PATH
}

judge_result()
{
    echo
    echo "Diff result is:"
    if diff "$RESULT_FILE_PATH" "$EXPECT_FILE_PATH"; then
        if [ $TIME_COST -le $ACCEPT_TIME ]; then
            echo "Accept. Congratulations"
        else
            echo "Time limit exceeded. Cost time $TIME_COST > Accept time $ACCEPT_TIME milliseconds"
        fi
    else
        echo "Wrong answer. Try again."
    fi
}

debug_code()
{
    echo "Debug is start."
    echo "start < $TEST_DATA_FILE_PATH" > $DEBUG_COMMAND_FILE_PATH
    #$DEBUG_TOOL $EXECUTABLE_FILE_PATH
    $DEBUG_TOOL -x $DEBUG_COMMAND_FILE_PATH $EXECUTABLE_FILE_PATH
    rm $DEBUG_COMMAND_FILE_PATH
    echo "Debug is done."

}

# shift command arguments for while loop process
skip_command()
{
    shift 2
}

# detect clang installed, or else use gcc
select_compile_tool()
{
    which clang > /dev/null 2>&1
    if [ $? -eq 0 ]
    then
        COMPILE_COMMAND="clang"
    else
        COMPILE_COMMAND="gcc"
    fi
    echo "Use $COMPILE_COMMAND to compile source code."
}

#######################
# function define end
#######################

##################
# Main part start
##################

# if no right number of argument, show help and exit
# if use h or help command, show help and exit
if [ "$1" = "h" ]
then
    print_help
    exit 0
elif [ "$1" = "help" ]
then
    print_help
    exit 0
elif [ $# -lt 2 ]
then
    print_help
    exit 1
fi

init_acm_root_directory

# get question name and init path for all files
QUESTION_NAME="$2"

init_path

# check if workspace is existed.
if [ ! -d $QUESTION_DIR ]; then
    IS_WORKSPACE_NOT_EXIST=true
fi

# option argument command implementation
while [ "$1" ]
    do
    echo
    if [ "$1" = "i" ]; then
        init_acm_workspace
    elif [ "$1" = "r" ]
    then
        workspace_exist_check
        echo "Run code with your test data and check correctness."
        select_compile_tool
        compile_code
        run_code
        print_output
        print_run_time_cost
        judge_result
    elif [ "$1" = "d" ]
    then
        workspace_exist_check
        select_compile_tool
        compile_code
        echo "Debug your code with debug tool, default is gdb"
        debug_code
    else
        echo "$SCRIPT_NAME does not recognize option $1."
        print_help
        exit 1
    fi
    # skip command for loop argument process now
    # maybe there are some extensions in the future
    shift 2
done

##################
# Main part end
##################





 
 
 
 
 
 
 
 
 
posted @ 2013-04-06 15:42  btchenguang  阅读(2816)  评论(1编辑  收藏  举报