test-release.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #!/usr/bin/env bash
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. # Tested on Linux, Armbian, Raspbian, MSYS2
  11. # cfg.sh must implement:
  12. # list_avail_tests()
  13. # init_groups()
  14. # init_tests()
  15. . 'test/test-release.d/cfg.sh'
  16. run_test() {
  17. set +x
  18. local tests_in="t_$1" skips="t_$1_skip" continue_on_error="e_$1" have_error= tests
  19. while read skip test; do
  20. [ "$test" ] || continue
  21. echo "${!skips}" | grep -q "\<$skip\>" && {
  22. echo -e "${GRAY}Skipping: $test$RESET"
  23. continue
  24. }
  25. tests+=("$test")
  26. done <<<${!tests_in}
  27. for test in "${tests[@]}"; do
  28. if [ "$LIST_CMDS" ]; then echo $test; continue; fi
  29. test_disp=$YELLOW${test/\#/$RESET$MAGENTA\#}$RESET
  30. if [ "${test:0:1}" == '#' ]; then
  31. echo -e "$test_disp"
  32. else
  33. echo -e "${GREEN}Running:$RESET $test_disp"
  34. eval "$test" || {
  35. echo -e $RED"test-release.sh: test '$CUR_TEST' failed at command '$test'"$RESET
  36. have_error=1
  37. [ "${!continue_on_error}" ] || exit 1
  38. }
  39. fi
  40. done
  41. if [ "$have_error" ]; then { echo -e "$RED${!continue_on_error}$RESET"; exit 1; }; fi
  42. }
  43. prompt_skip() {
  44. echo -n "Enter 's' to skip, or ENTER to continue: "; read -n1; echo
  45. [ "$REPLY" == 's' ] && return 0
  46. return 1
  47. }
  48. list_avail_tests() {
  49. echo "AVAILABLE TESTS:"
  50. init_tests
  51. for i in $all_tests; do
  52. z="d_$i"
  53. printf " %-8s - %s\n" $i "${!z}"
  54. done
  55. echo
  56. echo "AVAILABLE TEST GROUPS:"
  57. while read a b c; do
  58. [ "$a" ] && printf " %-8s - %s\n" $a "$c"
  59. done <<<$groups_desc
  60. echo
  61. echo "By default, all tests are run"
  62. }
  63. run_tests() {
  64. [ "$LIST_CMDS" ] || echo "Running tests: $1"
  65. for t in $1; do
  66. desc_id="d_$t" desc=${!desc_id}
  67. if [ "$SKIP_ALT_DEP" ]; then
  68. ok=$(for a in $noalt_tests $noalt_ok_tests; do if [ $t == $a ]; then echo 'ok'; fi; done)
  69. if [ ! "$ok" ]; then
  70. echo -e "${BLUE}Skipping altcoin test '$t'$RESET"
  71. continue
  72. fi
  73. fi
  74. if [ "$LIST_CMDS" ]; then
  75. echo -e "\n### $t: $desc"
  76. else
  77. echo -e "\n${BLUE}Testing:$RESET $GREEN$desc$RESET"
  78. fi
  79. [ "$PAUSE" ] && prompt_skip && continue
  80. CUR_TEST=$t
  81. run_test $t
  82. [ "$LIST_CMDS" ] || echo -e "${BLUE}Finished testing:$RESET $GREEN$desc$RESET"
  83. done
  84. }
  85. check_tests() {
  86. for i in $tests; do
  87. echo "$dfl_tests $extra_tests" | grep -q "\<$i\>" || {
  88. echo "$i: unrecognized argument"
  89. exit 1
  90. }
  91. done
  92. }
  93. remove_skipped_tests() {
  94. tests=$(for t in $tests; do
  95. [ "$(for s in $SKIP_LIST; do [ $t == $s ] && echo y; done)" ] && continue
  96. echo $t
  97. done)
  98. tests=$(echo $tests)
  99. }
  100. list_group_symbols() {
  101. echo -e "Default tests:\n $dfl_tests"
  102. echo -e "Extra tests:\n $extra_tests"
  103. echo -e "'noalt' test group:\n $noalt_tests"
  104. echo -e "'quick' test group:\n $quick_tests"
  105. echo -e "'qskip' test group:\n $qskip_tests"
  106. }
  107. print_ver_hash() {
  108. python3 -m pip freeze | grep "^$repo\>" | sed 's/.*sha256=//' | cut -c 1-12
  109. }
  110. do_typescript() {
  111. if [ "$DARWIN" ]; then script "$1" $2; else script -O "$1" -c "$2"; fi
  112. }
  113. install_package() {
  114. echo -e "${BLUE}Installing package$YELLOW $repo$RESET"
  115. rm -rf build dist *.egg-info
  116. ver=$(print_ver_hash)
  117. echo -e "${BLUE}Currently installed version is$MAGENTA $ver$RESET"
  118. cmd="python3 -m build --no-isolation --wheel --config-setting=quiet $STDOUT_DEVNULL"
  119. echo -e "${BLUE}Executing:$CYAN $cmd$RESET"
  120. eval $cmd
  121. cmd="python3 -m pip $QUIET install --break-system-packages dist/*.whl"
  122. echo -e "${BLUE}Executing:$CYAN $cmd$RESET"
  123. eval $cmd
  124. new_ver=$(print_ver_hash)
  125. if [ "$ver" == "$new_ver" ]; then
  126. echo -ne "${YELLOW}Version hash is unchanged. Force install? (y/N):$RESET "
  127. read -n1
  128. if [ "$REPLY" == 'y' ]; then
  129. echo
  130. cmd="python3 -m pip $QUIET install --break-system-packages --force --no-deps dist/*.whl"
  131. echo -e "${BLUE}Executing:$CYAN $cmd$RESET"
  132. eval $cmd
  133. elif [ "$REPLY" ]; then
  134. echo; return
  135. else
  136. return
  137. fi
  138. fi
  139. new_ver=$(print_ver_hash)
  140. if [ "$ver" == "$new_ver" ]; then
  141. echo -e "${RED}ERROR: version hash is unchanged$RESET"
  142. exit 1
  143. else
  144. echo -e "${GREEN}OK$RESET"
  145. fi
  146. }
  147. do_reexec() {
  148. [ -z "$exec_prog" ] && exec_prog="test/test-release.sh -X $ORIG_ARGS"
  149. if [ "$sdist_dir" ]; then
  150. target_dir=$sdist_dir
  151. elif [ "$clone_dir" ]; then
  152. target_dir="$orig_cwd/.clone-test"
  153. clone_dir=$target_dir
  154. else # TYPESCRIPT=1
  155. do_typescript "$orig_cwd/$typescript_file" "$exec_prog"
  156. return
  157. fi
  158. rm -rf $target_dir
  159. mkdir $target_dir
  160. if [ "$repo" != 'mmgen-wallet' ]; then
  161. echo -e "${BLUE}Cloning repo $MAGENTA'mmgen-wallet'$RESET ${BLUE}to $YELLOW$target_dir/mmgen-wallet$RESET"
  162. mkdir -p "$target_dir/mmgen-wallet"
  163. eval "git clone $orig_cwd/../mmgen-wallet $target_dir/mmgen-wallet $STDOUT_DEVNULL $STDERR_DEVNULL"
  164. fi
  165. if [ "$clone_dir" ]; then
  166. [ "$(git status --porcelain)" ] && VIM_GIT_COMMIT=1 git commit -a
  167. dest="$clone_dir/$repo"
  168. rm -rf $dest
  169. mkdir -p $dest
  170. echo -e "${BLUE}Cloning repo $MAGENTA'$repo'$BLUE to $YELLOW$dest$RESET"
  171. eval "git clone . $dest $STDOUT_DEVNULL $STDERR_DEVNULL"
  172. cd $dest
  173. echo -e "${BLUE}cd -> $YELLOW$PWD$RESET"
  174. fi
  175. if [ "$sdist_dir" ]; then
  176. rm -rf build dist *.egg-info
  177. echo -n 'Building sdist...'
  178. eval "python3 -m build --no-isolation --sdist --config-setting=quiet $STDOUT_DEVNULL"
  179. echo -e "done\n${BLUE}Unpacking sdist archive to $YELLOW$target_dir$RESET"
  180. tar -C $target_dir -zxf dist/*.tar.gz
  181. cd $target_dir/${repo//-/[-_]}-*
  182. echo -e "${BLUE}cd -> $YELLOW$PWD$RESET"
  183. if [ "$clone_dir" ]; then rm -rf $clone_dir; fi
  184. fi
  185. [ -e 'test/init.sh' ] && test/init.sh $VERBOSE_SHORTOPT
  186. echo -e "\n${BLUE}Executing test runner: ${CYAN}test/test-release $ORIG_ARGS$RESET\n"
  187. if [ "$TYPESCRIPT" ]; then
  188. do_typescript "$orig_cwd/$typescript_file" "$exec_prog"
  189. else
  190. eval $exec_prog
  191. fi
  192. }
  193. install_secp256k1_mod_maybe() {
  194. if [[ "$repo" =~ ^mmgen[-_]wallet ]]; then
  195. eval "python3 setup.py build_ext --inplace $STDOUT_DEVNULL"
  196. fi
  197. }
  198. in_nix_environment() {
  199. for path in ${PATH//:/ }; do
  200. realpath -q $path | grep -q '^/nix/store/' && break
  201. done
  202. }
  203. # start execution
  204. set -e
  205. set -o functrace
  206. set -o errtrace
  207. trap 'echo -e "${GREEN}Exiting at user request$RESET"; exit' INT
  208. umask 0022
  209. orig_cwd=$(pwd)
  210. repo=$(basename $orig_cwd)
  211. if [ "$(uname -m)" == 'armv7l' ]; then
  212. ARM32=1
  213. elif [ "$(uname -m)" == 'aarch64' ]; then
  214. ARM64=1
  215. elif [ "$(uname -s)" == 'Darwin' ]; then
  216. DARWIN=1
  217. DISTRO='DARWIN'
  218. elif [ "$MSYSTEM" ] && uname -a | grep -qi 'msys'; then
  219. MSYS2=1
  220. DISTRO='MSYS2'
  221. fi
  222. [ "$ARM32" -o "$ARM64" ] && {
  223. PEXPECT_LONG_TIMEOUT=' --pexpect-timeout=300'
  224. HTTP_LONG_TIMEOUT='MMGEN_HTTP_TIMEOUT=300 '
  225. }
  226. if [ -e '/etc/os-release' ]; then
  227. DISTRO=$(grep '^ID=' '/etc/os-release' | cut -c 4-)
  228. [ "$DISTRO" ] || {
  229. echo 'Unable to determine distro from /etc/os-release. Aborting'
  230. exit 1
  231. }
  232. fi
  233. cmdtest_py='test/cmdtest.py -n'
  234. objtest_py='test/objtest.py'
  235. objattrtest_py='test/objattrtest.py'
  236. modtest_py='test/modtest.py --names --quiet'
  237. daemontest_py='test/daemontest.py --names --quiet'
  238. tooltest_py='test/tooltest.py'
  239. tooltest2_py='test/tooltest2.py --names --quiet'
  240. gentest_py='test/gentest.py --quiet'
  241. scrambletest_py='test/scrambletest.py'
  242. altcoin_mod_opts='--quiet'
  243. mmgen_tool='cmds/mmgen-tool'
  244. pylint='PYTHONPATH=. pylint' # PYTHONPATH required by older Pythons (e.g. v3.9)
  245. python='python3'
  246. rounds=10
  247. typescript_file='test-release.out'
  248. STDOUT_DEVNULL='>/dev/null'
  249. STDERR_DEVNULL='2>/dev/null'
  250. QUIET='--quiet'
  251. ORIG_ARGS=$@
  252. PROGNAME=$(basename $0)
  253. init_groups
  254. while getopts hAbcCdDe:fFILlNOps:StTvVX OPT
  255. do
  256. case "$OPT" in
  257. h) printf " %-16s Test MMGen release\n" "${PROGNAME}:"
  258. echo " USAGE: $PROGNAME [options] [tests or test group]"
  259. echo " OPTIONS: -h Print this help message"
  260. echo " -A Skip tests requiring altcoin modules or daemons"
  261. echo " -b Buffer keypresses for all invocations of 'test/cmdtest.py'"
  262. echo " -c Run tests in coverage mode"
  263. echo " -C Test from cloned repo (can be combined with -S)"
  264. echo " -d Enable Python Development Mode"
  265. echo " -D Run tests in deterministic mode"
  266. echo " -e PROG With -C, -S or -T, execute PROG instead of this script"
  267. echo " -f Speed up the tests by using fewer rounds"
  268. echo " -F Reduce rounds even further"
  269. echo " -I Install the package"
  270. echo " -L List available tests and test groups with description"
  271. echo " -l List the test name symbols"
  272. echo " -N Pass the --no-timings switch to test/cmdtest.py"
  273. echo " -O Use pexpect.spawn rather than popen_spawn where applicable"
  274. echo " -p Pause between tests"
  275. echo " -s LIST Skip tests in LIST (space-separated)"
  276. echo " -S Build sdist distribution, unpack, and run test"
  277. echo " -t Print the tests without running them"
  278. echo " -T Record a typescript of the screen output in '$typescript_file'"
  279. echo " -v Run test/cmdtest.py with '--exact-output' and other commands"
  280. echo " with '--verbose' switch"
  281. echo " -V Run test/cmdtest.py and other commands with '--verbose' switch"
  282. echo
  283. echo " For traceback output and error file support, set the EXEC_WRAPPER_TRACEBACK"
  284. echo " environment variable"
  285. exit ;;
  286. A) SKIP_ALT_DEP=1
  287. cmdtest_py+=" --no-altcoin"
  288. modtest_py+=" --no-altcoin-deps"
  289. daemontest_py+=" --no-altcoin-deps"
  290. scrambletest_py+=" --no-altcoin"
  291. tooltest2_py+=" --no-altcoin" ;;
  292. b) cmdtest_py+=" --buf-keypress" ;;
  293. c) mkdir -p 'test/trace'
  294. touch 'test/trace.acc'
  295. cmdtest_py+=" --coverage"
  296. tooltest_py+=" --coverage"
  297. tooltest2_py+=" --fork --coverage"
  298. scrambletest_py+=" --coverage"
  299. python="python3 -m trace --count --file=test/trace.acc --coverdir=test/trace"
  300. modtest_py="$python $modtest_py"
  301. daemontest_py="$python $daemontest_py"
  302. objtest_py="$python $objtest_py"
  303. objattrtest_py="$python $objattrtest_py"
  304. gentest_py="$python $gentest_py"
  305. mmgen_tool="$python $mmgen_tool" ;&
  306. C) REEXEC=1 clone_dir="$orig_cwd/.cloned-repo" ;;
  307. d) export PYTHONDEVMODE=1
  308. export PYTHONWARNINGS='error' ;;
  309. D) export MMGEN_TEST_SUITE_DETERMINISTIC=1
  310. export MMGEN_DISABLE_COLOR=1 ;;
  311. e) exec_prog=$(realpath $OPTARG) ;;
  312. f) rounds=6 FAST=1 fast_opt='--fast' modtest_py+=" --fast" daemontest_py+=" --fast" ;;
  313. F) rounds=3 FAST=1 fast_opt='--fast' modtest_py+=" --fast" daemontest_py+=" --fast" ;;
  314. I) INSTALL_PACKAGE=1 ;;
  315. L) list_avail_tests; exit ;;
  316. l) list_group_symbols; exit ;;
  317. N) cmdtest_py+=" --no-timings" ;;
  318. O) cmdtest_py+=" --pexpect-spawn" ;;
  319. p) PAUSE=1 ;;
  320. s) SKIP_LIST+=" $OPTARG" ;;
  321. S) REEXEC=1 sdist_dir="$orig_cwd/.sdist-test" ;;
  322. t) LIST_CMDS=1 ;;
  323. T) REEXEC=1 TYPESCRIPT=1 ;;
  324. v) EXACT_OUTPUT=1 cmdtest_py+=" --exact-output" ;&
  325. V) VERBOSE='--verbose' VERBOSE_SHORTOPT='-v' QUIET=''
  326. [ "$EXACT_OUTPUT" ] || cmdtest_py+=" --verbose"
  327. STDOUT_DEVNULL='' STDERR_DEVNULL=''
  328. modtest_py="${modtest_py/--quiet/--verbose}"
  329. daemontest_py="${daemontest_py/--quiet/--verbose}"
  330. altcoin_mod_opts="${altcoin_mod_opts/--quiet/--verbose}"
  331. tooltest2_py="${tooltest2_py/--quiet/--verbose}"
  332. gentest_py="${gentest_py/--quiet/--verbose}"
  333. tooltest_py+=" --verbose"
  334. mmgen_tool+=" --verbose"
  335. objattrtest_py+=" --verbose"
  336. pylint+=" --verbose"
  337. scrambletest_py+=" --verbose" ;;
  338. X) IN_REEXEC=1 ;;
  339. *) exit ;;
  340. esac
  341. done
  342. in_nix_environment && parity --help >/dev/null 2>&1 || SKIP_PARITY=1
  343. [ "$MMGEN_DISABLE_COLOR" -o ! -t 1 ] || {
  344. GRAY="\e[30;1m"
  345. RED="\e[31;1m"
  346. GREEN="\e[32;1m"
  347. YELLOW="\e[33;1m"
  348. BLUE="\e[34;1m"
  349. MAGENTA="\e[35;1m"
  350. CYAN="\e[36;1m"
  351. RESET="\e[0m"
  352. }
  353. [ "$REEXEC" -a -z "$IN_REEXEC" ] && { do_reexec; exit; }
  354. [ "$exec_prog" ] && { echo "option -e makes no sense without -C, -S, or -T" ; exit; }
  355. [ "$INSTALL_PACKAGE" ] && { install_package; exit; }
  356. [ "$MSYS2" -a ! "$FAST" ] && tooltest2_py+=' --fork'
  357. [ "$EXACT_OUTPUT" -o "$VERBOSE" ] || objtest_py+=" -S"
  358. shift $((OPTIND-1))
  359. case $1 in
  360. '') tests=$dfl_tests ;;
  361. 'default') tests=$dfl_tests ;;
  362. 'extra') tests=$extra_tests ;;
  363. 'noalt') tests=$noalt_tests
  364. SKIP_ALT_DEP=1
  365. cmdtest_py+=" --no-altcoin"
  366. modtest_py+=" --no-altcoin-deps"
  367. daemontest_py+=" --no-altcoin-deps"
  368. scrambletest_py+=" --no-altcoin" ;;
  369. 'quick') tests=$quick_tests ;;
  370. 'qskip') tests=$qskip_tests ;;
  371. *) tests="$*" ;;
  372. esac
  373. rounds_min=$((rounds / 2))
  374. for n in 2 5 10 20 50 100 200 500 1000; do
  375. eval "rounds${n}x=$((rounds*n))"
  376. done
  377. init_tests
  378. remove_skipped_tests
  379. check_tests
  380. test/clean.py
  381. install_secp256k1_mod_maybe
  382. start_time=$(date +%s)
  383. run_tests "$tests"
  384. elapsed=$(($(date +%s)-start_time))
  385. elapsed_fmt=$(printf %02d:%02d $((elapsed/60)) $((elapsed%60)))
  386. [ "$LIST_CMDS" ] || {
  387. if [ "$MMGEN_TEST_SUITE_DETERMINISTIC" ]; then
  388. echo -e "\n${GREEN}All OK"
  389. else
  390. echo -e "\n${GREEN}All OK. Total elapsed time: $elapsed_fmt$RESET"
  391. fi
  392. }