diff --git a/.jenkins.sh b/.jenkins.sh
index 5630511..56e2acc 100755
--- a/.jenkins.sh
+++ b/.jenkins.sh
@@ -44,8 +44,8 @@ run_test python tests/test.py --with-coverage -c tests/server-multi-ports.json
run_test python tests/test.py --with-coverage -s tests/server-multi-passwd.json -c tests/server-multi-passwd-client-side.json
run_test python tests/test.py --with-coverage -c tests/workers.json
run_test python tests/test.py --with-coverage -s tests/ipv6.json -c tests/ipv6-client-side.json
-run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081"
-run_test python tests/test.py --with-coverage -b "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081"
+run_test python tests/test.py --with-coverage -b "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -q" -a "-m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -vv"
+run_test python tests/test.py --with-coverage -b "-b 127.0.0.1 -m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 --workers 1" -a "-m aes-256-cfb -k testrc4 -s 127.0.0.1 -p 8388 -l 1081 -t 30 -qq"
if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then
if [ 3 -eq `cat /proc/sys/net/ipv4/tcp_fastopen` ] ; then
diff --git a/tests/assert.sh b/tests/assert.sh
new file mode 100644
index 0000000..b0c679c
--- /dev/null
+++ b/tests/assert.sh
@@ -0,0 +1,148 @@
+#!/bin/bash
+# assert.sh 1.0 - bash unit testing framework
+# Copyright (C) 2009, 2010, 2011, 2012 Robert Lehmann
+#
+# http://github.com/lehmannro/assert.sh
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+export DISCOVERONLY=${DISCOVERONLY:-}
+export DEBUG=${DEBUG:-}
+export STOP=${STOP:-}
+export INVARIANT=${INVARIANT:-}
+export CONTINUE=${CONTINUE:-}
+
+args="$(getopt -n "$0" -l \
+ verbose,help,stop,discover,invariant,continue vhxdic $*)" \
+|| exit -1
+for arg in $args; do
+ case "$arg" in
+ -h)
+ echo "$0 [-vxidc]" \
+ "[--verbose] [--stop] [--invariant] [--discover] [--continue]"
+ echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]"
+ exit 0;;
+ --help)
+ cat < [stdin]
+ (( tests_ran++ )) || :
+ [[ -n "$DISCOVERONLY" ]] && return || true
+ # printf required for formatting
+ printf -v expected "x${2:-}" # x required to overwrite older results
+ result="$(eval 2>/dev/null $1 <<< ${3:-})" || true
+ # Note: $expected is already decorated
+ if [[ "x$result" == "$expected" ]]; then
+ [[ -n "$DEBUG" ]] && echo -n . || true
+ return
+ fi
+ result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<< "$result")"
+ [[ -z "$result" ]] && result="nothing" || result="\"$result\""
+ [[ -z "$2" ]] && expected="nothing" || expected="\"$2\""
+ _assert_fail "expected $expected${_indent}got $result" "$1" "$3"
+}
+
+assert_raises() {
+ # assert_raises [stdin]
+ (( tests_ran++ )) || :
+ [[ -n "$DISCOVERONLY" ]] && return || true
+ status=0
+ (eval $1 <<< ${3:-}) > /dev/null 2>&1 || status=$?
+ expected=${2:-0}
+ if [[ "$status" -eq "$expected" ]]; then
+ [[ -n "$DEBUG" ]] && echo -n . || true
+ return
+ fi
+ _assert_fail "program terminated with code $status instead of $expected" "$1" "$3"
+}
+
+_assert_fail() {
+ # _assert_fail
+ [[ -n "$DEBUG" ]] && echo -n X
+ report="test #$tests_ran \"$2${3:+ <<< $3}\" failed:${_indent}$1"
+ if [[ -n "$STOP" ]]; then
+ [[ -n "$DEBUG" ]] && echo
+ echo "$report"
+ exit 1
+ fi
+ tests_errors[$tests_failed]="$report"
+ (( tests_failed++ )) || :
+}
+
+_assert_reset
+: ${tests_suite_status:=0} # remember if any of the tests failed so far
+_assert_cleanup() {
+ local status=$?
+ # modify exit code if it's not already non-zero
+ [[ $status -eq 0 && -z $CONTINUE ]] && exit $tests_suite_status
+}
+trap _assert_cleanup EXIT
diff --git a/tests/test_command.sh b/tests/test_command.sh
new file mode 100755
index 0000000..fc23665
--- /dev/null
+++ b/tests/test_command.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+. tests/assert.sh
+
+PYTHON="coverage run -a -p"
+LOCAL="$PYTHON shadowsocks/local.py"
+SERVER="$PYTHON shadowsocks/server.py"
+
+assert "$LOCAL 2>&1 | grep ERROR" "ERROR: config not specified"
+assert "$LOCAL 2>&1 | grep usage | cut -d: -f1" "usage"
+
+assert "$SERVER 2>&1 | grep ERROR" "ERROR: config not specified"
+assert "$SERVER 2>&1 | grep usage | cut -d: -f1" "usage"
+
+assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: server set to listen on 127.0.0.1:8388, are you sure?"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 0.0.0.0 -p 8388 -t10 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: your timeout 10 seems too short"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 0.0.0.0 -p 8388 -t1000 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: your timeout 1000 seems too long"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$LOCAL 2>&1 -m rc4 -k testrc4 -s 0.0.0.0 -p 8388 -d start | grep WARNING | awk -F\"WARNING\" '{print \$2}'" " warning: RC4 is not safe; please use a safer cipher, like AES-256-CFB"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$LOCAL 2>&1 -m rc4-md5 -k mypassword -s 0.0.0.0 -p 8388 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" " DON'T USE DEFAULT PASSWORD! Please change it in your config.json!"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -k testrc4 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": server addr not specified"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$LOCAL 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password not specified"
+$LOCAL 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+assert "$SERVER 2>&1 -m rc4-md5 -p 8388 -s 0.0.0.0 -d start | grep ERROR | awk -F\"ERROR\" '{print \$2}'" ": password or port_password not specified"
+$SERVER 2>&1 -m rc4-md5 -k testrc4 -s 127.0.0.1 -p 8388 -d stop
+
+
+assert_end command