【競プロ】コンパイルとテストケースの確認できるシェルスクリプトを書いた
タイトルの通りです。競プロでコード書いていていちいちコンパイルしたりテストケース試したりってのがめんどくさいなと思ったので、書いてみました。
以下全文です。githubにも上げてあります。(途中からシンタックスハイライトがおかしくなってるのは気にしないでください)
https://github.com/hpano/CompetitiveProgramming/blob/master/run.sh
まあこんなことをしなくてもonline-judge-toolsという便利なツールがあるので、特にこだわりがなければこれを使うのがいいでしょう。
online-judge-tools.readthedocs.io
#!/bin/bash function usage() { echo "Usage: $0 [OPTIONS] FILE" echo echo "Options:" echo " -h, --help Display available options." echo " -c, --compile Compile the FILE." echo " -i, --input INPUT Specify input file as INPUT." echo " -n, --norun Not run code test." echo " -r, --remove Remove the existing executable file." echo exit 1 } declare -i argc=0 declare -a argv=() declare hflag=false declare cflag=false declare iflag=false declare nflag=false declare rflag=false declare in_file while test "$1" != ""; do case $1 in -*) if [[ "$1" =~ 'h' ]] || [[ "$1" =~ '-help' ]]; then hflag=true fi if [[ "$1" =~ 'c' ]] || [[ "$1" =~ '-compile' ]]; then cflag=true fi if [[ "$1" =~ 'n' ]] || [[ "$1" =~ '-norun' ]]; then nflag=true fi if [[ "$1" =~ 'r' ]] || [[ "$1" =~ '-remove' ]]; then rflag=true fi if [[ "$1" =~ 'i' ]] || [[ "$1" =~ '-input' ]]; then iflag=true in_file=$2 shift fi shift ;; *) ((++argc)) argv=("${argv[@]}" "${1%.cpp}") shift ;; esac done for i in ${argv[@]}; do declare eval cmissflag_${i}=false done if "${hflag}"; then usage exit fi if "${rflag}"; then $(dirname $0)/remove.sh ${argv[@]} fi if "${cflag}"; then for i in ${argv[@]}; do $(dirname $0)/compile.sh ${i} || eval cmissflag_${i}=true done fi if "${nflag}"; then exit fi for i in ${argv[@]}; do if eval '$cmissflag_'${i}; then continue fi if ! "${iflag}"; then in_file=${i}.in fi declare -a input=() declare -a output=() declare inflag=false declare outflag=false declare case_count=0 declare ac_count=0 declare wa_count=0 declare slowest_time=0 declare slowest_case echo "[run] Run ${i}.out < ${in_file}" # echo while read line; do if [ "${line}" == "<in>" ]; then inflag=true continue elif [ "${line}" == "</in>" ]; then inflag=false continue elif [ "${line}" == "<out>" ]; then outflag=true continue elif [ "${line}" == "</out>" ]; then outflag=false output[${case_count}]=`echo -e "${output[${case_count}]}"` case_count=$(( case_count + 1 )) continue fi if "${inflag}"; then input[${case_count}]+="${line}"" " elif "${outflag}"; then output[${case_count}]+="${line}""\n" fi done < ./${in_file} for j in `seq 0 $(( case_count - 1))`; do echo "[run] *** case `expr ${j} + 1` ***" # echo "[run] input:" # echo -e "${input[${j}]}" # echo "[run] output:" # echo -e "${output[${j}]}" declare start_time=`gdate +%s.%6N` declare result=$(./${i}.out << EOT ${input[${j}]} EOT) declare end_time=`gdate +%s.%6N` declare process_time=`echo "scale=6; (${end_time} - ${start_time})" | bc` if [ `echo "${process_time} > ${slowest_time}" | bc` == 1 ]; then slowest_time=${process_time} slowest_case=${j} fi echo "[run] time: ${process_time} sec" if [ "${output[${j}]}" = "${result}" ]; then ac_count=$(( ac_count + 1 )) echo -e "[run] \033[0;37;42m AC \033[0;39m" else wa_count=$(( wa_count + 1 )) echo -e "[run] \033[0;37;43m WA \033[0;39m" echo "output:" echo -e "${result}" echo "expected:" echo -e "${output[${j}]}" fi done echo "[run] slowest: ${slowest_time} sec (case ${slowest_case})" echo "[run] AC rate: ${ac_count} / $(( ac_count + wa_count )) (AC / cases)" done
コンパイルして欲しいことがあったりなかったり、あらかじめ用意したテストケースを試して欲しかったり欲しくなかったりすることがあると思うので、オプションで指定できるようにしました。まずこの部分でどのオプションが指定されているかフラグを立てています。-iオプションの場合はファイル指定があるので後に続くファイル名を読み取っています。オプション文字列にそのオプションに対応する文字が含まれているのかどうかで判定しているので、複数オプションをつなげて書いても認識できます(-cn など)。
また、オプション以外は全てテストしたいプログラムのファイル名として受け取り、一応複数ファイル同時にテストできるようにしてあります(実際にすることはなさそう)。
while test "$1" != ""; do case $1 in -*) if [[ "$1" =~ 'h' ]] || [[ "$1" =~ '-help' ]]; then hflag=true fi if [[ "$1" =~ 'c' ]] || [[ "$1" =~ '-compile' ]]; then cflag=true fi if [[ "$1" =~ 'n' ]] || [[ "$1" =~ '-norun' ]]; then nflag=true fi if [[ "$1" =~ 'r' ]] || [[ "$1" =~ '-remove' ]]; then rflag=true fi if [[ "$1" =~ 'i' ]] || [[ "$1" =~ '-input' ]]; then iflag=true in_file=$2 shift fi shift ;; *) ((++argc)) argv=("${argv[@]}" "${1%.cpp}") shift ;; esac done
続いてこの部分で各フラグに対応する処理をしています。-rオプションの場合は実行ファイルを削除するシェルスクリプト remove.shを実行、-cオプションの場合はコンパイルを行うシェルスクリプト compile.shを全ての入力プログラムに対して実行します。ここでコンパイルに失敗した場合は、それぞれのプログラムに対応したコンパイル失敗フラグを立てておきます。
for i in ${argv[@]}; do declare eval cmissflag_${i}=false done if "${hflag}"; then usage exit fi if "${rflag}"; then $(dirname $0)/remove.sh ${argv[@]} fi if "${cflag}"; then for i in ${argv[@]}; do $(dirname $0)/compile.sh ${i} || eval cmissflag_${i}=true done fi if "${nflag}"; then exit fi
その後の部分でコンパイルに成功した(orコンパイル済みの)各プログラムを実行していきます。
まずはこの部分でテストケースの入力ファイルを解析。
while read line; do if [ "${line}" == "<in>" ]; then inflag=true continue elif [ "${line}" == "</in>" ]; then inflag=false continue elif [ "${line}" == "<out>" ]; then outflag=true continue elif [ "${line}" == "</out>" ]; then outflag=false output[${case_count}]=`echo -e "${output[${case_count}]}"` case_count=$(( case_count + 1 )) continue fi if "${inflag}"; then input[${case_count}]+="${line}"" " elif "${outflag}"; then output[${case_count}]+="${line}""\n" fi done < ./${in_file}
続く部分でプログラムを実行。配列につっこんだ各テストケースの入力を取り出してプログラムに渡し、出力結果を受け取ります。結果が期待する出力と等しければAC、違っていればWAとして、結果と期待する出力を表示します。また、計測した実行時間も表示します。
for j in `seq 0 $(( case_count - 1))`; do echo "[run] *** case `expr ${j} + 1` ***" # echo "[run] input:" # echo -e "${input[${j}]}" # echo "[run] output:" # echo -e "${output[${j}]}" declare start_time=`gdate +%s.%6N` declare result=$(./${i}.out << EOT ${input[${j}]} EOT) declare end_time=`gdate +%s.%6N` declare process_time=`echo "scale=6; (${end_time} - ${start_time})" | bc` if [ `echo "${process_time} > ${slowest_time}" | bc` == 1 ]; then slowest_time=${process_time} slowest_case=${j} fi echo "[run] time: ${process_time} sec" if [ "${output[${j}]}" = "${result}" ]; then ac_count=$(( ac_count + 1 )) echo -e "[run] \033[0;37;42m AC \033[0;39m" else wa_count=$(( wa_count + 1 )) echo -e "[run] \033[0;37;43m WA \033[0;39m" echo "output:" echo -e "${result}" echo "expected:" echo -e "${output[${j}]}" fi done
最後に最遅だったテストケースとその実行時間、AC数とWA数を表示して終了です。
echo "[run] slowest: ${slowest_time} sec (case ${slowest_case})" echo "[run] AC rate: ${ac_count} / $(( ac_count + wa_count )) (AC / cases)"
実行するとこんな感じになります。
これでまあまあ便利に競プロやれるようになりました。あとは、inファイルのテストケースの入力と出力を手でコピペしているので、そこを自動でやってくれるようにしたいですね。