#!/bin/sh

verbose=false
if [ "x$1" = "x-v" ]; then
    verbose=true
    out=/dev/stdout
    err=/dev/stderr
else
    out=/dev/null
    err=/dev/null
fi

## make & makeopts
if gmake --version > /dev/null 2>&1; then
    make=gmake;
else
    make=make;
fi

makeopts="--quiet --no-print-directory -j"

make_print() {
    echo `$make $makeopts print-$1`
}

## command tools
awk='awk'
bc='bc'
date='date'
grep='grep'
rm='rm -f'
sed='sed'

## symbol table
sym_table='obj/kernel.sym'

## gdb & gdbopts
gdb="$(make_print GDB)"
gdbport='1234'

gdb_in="$(make_print GRADE_GDB_IN)"

## qemu & qemuopts
qemu="$(make_print qemu)"

qemu_out="$(make_print GRADE_QEMU_OUT)"

if $qemu -nographic -help | grep -q '^-gdb'; then
    qemugdb="-gdb tcp::$gdbport"
else
    qemugdb="-s -p $gdbport"
fi

## default variables
default_timeout=30
default_pts=5

pts=5
part=0
part_pos=0
total=0
total_pos=0

## default functions
update_score() {
    total=`expr $total + $part`
    total_pos=`expr $total_pos + $part_pos`
    part=0
    part_pos=0
}

get_time() {
    echo `$date +%s.%N 2> /dev/null`
}

show_part() {
    echo "Part $1 Score: $part/$part_pos"
    echo
    update_score
}

show_final() {
    update_score
    echo "Total Score: $total/$total_pos"
    if [ $total -lt $total_pos ]; then
        exit 1
    fi
}

show_time() {
    t1=$(get_time)
    time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null`
    echo "(${time}s)"
}

show_build_tag() {
    echo "$1:" | $awk '{printf "%-24s ", $0}'
}

show_check_tag() {
    echo "$1:" | $awk '{printf "  -%-40s  ", $0}'
}

show_msg() {
    echo $1
    shift
    if [ $# -gt 0 ]; then
        echo "$@" | awk '{printf "   %s\n", $0}'
        echo
    fi
}

pass() {
    show_msg OK "$@"
    part=`expr $part + $pts`
    part_pos=`expr $part_pos + $pts`
}

fail() {
    show_msg WRONG "$@"
    part_pos=`expr $part_pos + $pts`
}

run_qemu() {
    # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty,
    # wait until $brkfun is reached or $timeout expires, then kill QEMU
    qemuextra=
    if [ "$brkfun" ]; then
        qemuextra="-S $qemugdb"
    fi

    if [ -z "$timeout" ] || [ $timeout -le 0 ]; then
        timeout=$default_timeout;
    fi

    t0=$(get_time)
    (
        ulimit -t $timeout
        exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra
    ) > $out 2> $err &
    pid=$!

    # wait for QEMU to start
    sleep 1

    if [ -n "$brkfun" ]; then
        # find the address of the kernel $brkfun function
        brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'`
        (
            echo "target remote localhost:$gdbport"
            echo "break *0x$brkaddr"
            echo "continue"
        ) > $gdb_in

        $gdb -batch -nx -x $gdb_in > /dev/null 2>&1

        # make sure that QEMU is dead
        # on OS X, exiting gdb doesn't always exit qemu
        kill $pid > /dev/null 2>&1
    fi
}

build_run() {
    # usage: build_run <tag> <args>
    show_build_tag "$1"
    shift

    if $verbose; then
        echo "$make $@ ..."
    fi
    $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err

    if [ $? -ne 0 ]; then
        echo $make $@ failed
        exit 1
    fi

    # now run qemu and save the output
    run_qemu

    show_time
}

check_result() {
    # usage: check_result <tag> <check> <check args...>
    show_check_tag "$1"
    shift

    # give qemu some time to run (for asynchronous mode)
    if [ ! -s $qemu_out ]; then
        sleep 4
    fi

    if [ ! -s $qemu_out ]; then
        fail > /dev/null
        echo 'no $qemu_out'
    else
        check=$1
        shift
        $check "$@"
    fi
}

check_regexps() {
    okay=yes
    not=0
    reg=0
    error=
    for i do
        if [ "x$i" = "x!" ]; then
            not=1
        elif [ "x$i" = "x-" ]; then
            reg=1
        else
            if [ $reg -ne 0 ]; then
                $grep '-E' "^$i\$" $qemu_out > /dev/null
            else
                $grep '-F' "$i" $qemu_out > /dev/null
            fi
            found=$(($? == 0))
            if [ $found -eq $not ]; then
                if [ $found -eq 0 ]; then
                    msg="!! error: missing '$i'"
                else
                    msg="!! error: got unexpected line '$i'"
                fi
                okay=no
                if [ -z "$error" ]; then
                    error="$msg"
                else
                    error="$error\n$msg"
                fi
            fi
            not=0
            reg=0
        fi
    done
    if [ "$okay" = "yes" ]; then
        pass
    else
        fail "$error"
        if $verbose; then
            exit 1
        fi
    fi
}

run_test() {
    # usage: run_test [-tag <tag>] [-prog <prog>] [-Ddef...] [-check <check>] checkargs ...
    tag=
    prog=
    check=check_regexps
    while true; do
        select=
        case $1 in
            -tag|-prog)
                select=`expr substr $1 2 ${#1}`
                eval $select='$2'
                ;;
        esac
        if [ -z "$select" ]; then
            break
        fi
        shift
        shift
    done
    defs=
    while expr "x$1" : "x-D.*" > /dev/null; do
        defs="DEFS+='$1' $defs"
        shift
    done
    if [ "x$1" = "x-check" ]; then
        check=$2
        shift
        shift
    fi

    if [ -z "$prog" ]; then
        $make $makeopts touch > /dev/null 2>&1
        args="$defs"
    else
        if [ -z "$tag" ]; then
            tag="$prog"
        fi
        args="build-$prog $defs"
    fi

    build_run "$tag" "$args"

    check_result 'check result' "$check" "$@"
}

quick_run() {
    # usage: quick_run <tag> [-Ddef...]
    tag="$1"
    shift
    defs=
    while expr "x$1" : "x-D.*" > /dev/null; do
        defs="DEFS+='$1' $defs"
        shift
    done

    $make $makeopts touch > /dev/null 2>&1
    build_run "$tag" "$defs"
}

quick_check() {
    # usage: quick_check <tag> checkargs ...
    tag="$1"
    shift
    check_result "$tag" check_regexps "$@"
}

## kernel image
osimg=$(make_print ucoreimg)

## swap image
swapimg=$(make_print swapimg)

## set default qemu-options
qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback"

## set break-function, default is readline
brkfun=readline

default_check() {
    pts=7
    check_regexps "$@"

    pts=3
    quick_check 'check output'                                  \
    'memory management: default_pmm_manager'                      \
    'check_alloc_page() succeeded!'                             \
    'check_pgdir() succeeded!'                                  \
    'check_boot_pgdir() succeeded!'				\
    'PDE(0e0) c0000000-f8000000 38000000 urw'                   \
    '  |-- PTE(38000) c0000000-f8000000 38000000 -rw'           \
    'PDE(001) fac00000-fb000000 00400000 -rw'                   \
    '  |-- PTE(000e0) faf00000-fafe0000 000e0000 urw'           \
    '  |-- PTE(00001) fafeb000-fafec000 00001000 -rw'		\
    'check_slab() succeeded!'					\
    'check_vma_struct() succeeded!'                             \
    'page fault at 0x00000100: K/W [no page found].'            \
    'check_pgfault() succeeded!'                                \
    'check_vmm() succeeded.'					\
    'page fault at 0x00001000: K/W [no page found].'            \
    'page fault at 0x00002000: K/W [no page found].'            \
    'page fault at 0x00003000: K/W [no page found].'            \
    'page fault at 0x00004000: K/W [no page found].'            \
    'write Virt Page e in fifo_check_swap'			\
    'page fault at 0x00005000: K/W [no page found].'		\
    'page fault at 0x00001000: K/W [no page found]'		\
    'page fault at 0x00002000: K/W [no page found].'		\
    'page fault at 0x00003000: K/W [no page found].'		\
    'page fault at 0x00004000: K/W [no page found].'		\
    'check_swap() succeeded!'					\
    '++ setup timer interrupts'
}

## check now!!

run_test -prog 'badsegment' -check default_check                \
        'kernel_execve: pid = 2, name = "badsegment".'          \
      - 'trapframe at 0xc.......'                               \
        'trap 0x0000000d General Protection'                    \
        '  err  0x00000028'                                     \
      - '  eip  0x008.....'                                     \
      - '  esp  0xaff.....'                                     \
        '  cs   0x----001b'                                     \
        '  ss   0x----0023'                                     \
    ! - 'user panic at .*'

run_test -prog 'divzero' -check default_check                   \
        'kernel_execve: pid = 2, name = "divzero".'             \
      - 'trapframe at 0xc.......'                               \
        'trap 0x00000000 Divide error'                          \
      - '  eip  0x008.....'                                     \
      - '  esp  0xaff.....'                                     \
        '  cs   0x----001b'                                     \
        '  ss   0x----0023'                                     \
    ! - 'user panic at .*'

run_test -prog 'softint' -check default_check                   \
        'kernel_execve: pid = 2, name = "softint".'             \
      - 'trapframe at 0xc.......'                               \
        'trap 0x0000000d General Protection'                    \
        '  err  0x00000072'                                     \
      - '  eip  0x008.....'                                     \
      - '  esp  0xaff.....'                                     \
        '  cs   0x----001b'                                     \
        '  ss   0x----0023'                                     \
    ! - 'user panic at .*'

pts=10

run_test -prog 'faultread'  -check default_check                                     \
        'kernel_execve: pid = 2, name = "faultread".'           \
      - 'trapframe at 0xc.......'                               \
        'trap 0x0000000e Page Fault'                            \
        '  err  0x00000004'                                     \
      - '  eip  0x008.....'                                     \
    ! - 'user panic at .*'

run_test -prog 'faultreadkernel' -check default_check                                \
        'kernel_execve: pid = 2, name = "faultreadkernel".'     \
      - 'trapframe at 0xc.......'                               \
        'trap 0x0000000e Page Fault'                            \
        '  err  0x00000005'                                     \
      - '  eip  0x008.....'                                     \
    ! - 'user panic at .*'

run_test -prog 'hello' -check default_check                                          \
        'kernel_execve: pid = 2, name = "hello".'               \
        'Hello world!!.'                                        \
        'I am process 2.'                                       \
        'hello pass.'

run_test -prog 'testbss' -check default_check                                        \
        'kernel_execve: pid = 2, name = "testbss".'             \
        'Making sure bss works right...'                        \
        'Yes, good.  Now doing a wild write off the end...'     \
        'testbss may pass.'                                     \
      - 'trapframe at 0xc.......'                               \
        'trap 0x0000000e Page Fault'                            \
        '  err  0x00000006'                                     \
      - '  eip  0x008.....'                                     \
        'killed by kernel.'                                     \
    ! - 'user panic at .*'

run_test -prog 'pgdir' -check default_check                                          \
        'kernel_execve: pid = 2, name = "pgdir".'               \
        'I am 2, print pgdir.'                                  \
        'PDE(001) 00800000-00c00000 00400000 urw'               \
        '  |-- PTE(00002) 00800000-00802000 00002000 ur-'       \
        '  |-- PTE(00001) 00802000-00803000 00001000 urw'       \
        'PDE(001) afc00000-b0000000 00400000 urw'               \
        '  |-- PTE(00004) afffc000-b0000000 00004000 urw'       \
        'PDE(0e0) c0000000-f8000000 38000000 urw'               \
        '  |-- PTE(38000) c0000000-f8000000 38000000 -rw'       \
        'pgdir pass.'

run_test -prog 'yield' -check default_check                                          \
        'kernel_execve: pid = 2, name = "yield".'               \
        'Hello, I am process 2.'                                \
        'Back in process 2, iteration 0.'                       \
        'Back in process 2, iteration 1.'                       \
        'Back in process 2, iteration 2.'                       \
        'Back in process 2, iteration 3.'                       \
        'Back in process 2, iteration 4.'                       \
        'All done in process 2.'                                \
        'yield pass.'


run_test -prog 'badarg' -check default_check                    \
        'kernel_execve: pid = 2, name = "badarg".'              \
        'fork ok.'                                              \
        'badarg pass.'                                          \
        'all user-mode processes have quit.'                    \
        'init check memory pass.'                               \
    ! - 'user panic at .*'

pts=10

run_test -prog 'exit'  -check default_check                                          \
        'kernel_execve: pid = 2, name = "exit".'                \
        'I am the parent. Forking the child...'                 \
        'I am the parent, waiting now..'                        \
        'I am the child.'                                       \
      - 'waitpid [0-9]+ ok\.'                                   \
        'exit pass.'                                            \
        'all user-mode processes have quit.'                    \
        'init check memory pass.'                               \
    ! - 'user panic at .*'

run_test -prog 'spin'  -check default_check                                          \
        'kernel_execve: pid = 2, name = "spin".'                \
        'I am the parent. Forking the child...'                 \
        'I am the parent. Running the child...'                 \
        'I am the child. spinning ...'                          \
        'I am the parent.  Killing the child...'                \
        'kill returns 0'                                        \
        'wait returns 0'                                        \
        'spin may pass.'                                        \
        'all user-mode processes have quit.'                    \
        'init check memory pass.'                               \
    ! - 'user panic at .*'

run_test -prog 'waitkill'  -check default_check                                      \
        'kernel_execve: pid = 2, name = "waitkill".'            \
        'wait child 1.'                                         \
        'child 2.'                                              \
        'child 1.'                                              \
        'kill parent ok.'                                       \
        'kill child1 ok.'                                       \
        'all user-mode processes have quit.'                    \
        'init check memory pass.'                               \
    ! - 'user panic at .*'

pts=15

run_test -prog 'forktest'   -check default_check                                     \
        'kernel_execve: pid = 2, name = "forktest".'            \
        'I am child 31'                                         \
        'I am child 19'                                         \
        'I am child 13'                                         \
        'I am child 0'                                          \
        'forktest pass.'                                        \
        'all user-mode processes have quit.'                    \
        'init check memory pass.'                               \
    ! - 'fork claimed to work [0-9]+ times!'                    \
    !   'wait stopped early'                                    \
    !   'wait got too many'                                     \
    ! - 'user panic at .*'

pts=10
run_test -prog 'forktree'    -check default_check               \
        'kernel_execve: pid = 2, name = "forktree".'            \
      - '....: I am '\'''\'                                     \
      - '....: I am '\''0'\'                                    \
      - '....: I am '\'''\'                                     \
      - '....: I am '\''1'\'                                    \
      - '....: I am '\''0'\'                                    \
      - '....: I am '\''01'\'                                   \
      - '....: I am '\''00'\'                                   \
      - '....: I am '\''11'\'                                   \
      - '....: I am '\''10'\'                                   \
      - '....: I am '\''101'\'                                  \
      - '....: I am '\''100'\'                                  \
      - '....: I am '\''111'\'                                  \
      - '....: I am '\''110'\'                                  \
      - '....: I am '\''001'\'                                  \
      - '....: I am '\''000'\'                                  \
      - '....: I am '\''011'\'                                  \
      - '....: I am '\''010'\'                                  \
      - '....: I am '\''0101'\'                                 \
      - '....: I am '\''0100'\'                                 \
      - '....: I am '\''0111'\'                                 \
      - '....: I am '\''0110'\'                                 \
      - '....: I am '\''0001'\'                                 \
      - '....: I am '\''0000'\'                                 \
      - '....: I am '\''0011'\'                                 \
      - '....: I am '\''0010'\'                                 \
      - '....: I am '\''1101'\'                                 \
      - '....: I am '\''1100'\'                                 \
      - '....: I am '\''1111'\'                                 \
      - '....: I am '\''1110'\'                                 \
      - '....: I am '\''1001'\'                                 \
      - '....: I am '\''1000'\'                                 \
      - '....: I am '\''1011'\'                                 \
      - '....: I am '\''1010'\'                                 \
        'all user-mode processes have quit.'                    \
        'init check memory pass.'

## print final-score
show_final

