run.sh 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. #!/bin/sh
  2. # basedir is the root of the test directory in the package
  3. basedir="$(dirname "$0")"
  4. [ "$(echo "$basedir" | cut -c1)" = '/' ] || basedir="$PWD/$basedir"
  5. # rootdir is the root of the package
  6. rootdir="$basedir/.."
  7. failfile_write()
  8. {
  9. (
  10. flock -x 9 || die "Failed to take lock"
  11. echo "$*" >> "$test_fail_file"
  12. ) 9< "$test_fail_file"
  13. }
  14. infomsg()
  15. {
  16. [ -z "$AWLSIM_TEST_QUIET" ] && echo "$@"
  17. }
  18. warnmsg()
  19. {
  20. echo "WARNING: $@" >&2
  21. }
  22. errormsg()
  23. {
  24. echo "$@" >&2
  25. }
  26. die()
  27. {
  28. if [ -n "$*" ]; then
  29. errormsg "$*"
  30. # We might be in a sub-job. So write to fail-file.
  31. failfile_write "$*"
  32. fi
  33. exit 1
  34. }
  35. # Create a temporary file. $1=name, $2=subdir
  36. maketemp()
  37. {
  38. local prefix="$1"
  39. local subdir="$2"
  40. if [ -z "$subdir" ]; then
  41. local subdir="."
  42. else
  43. mkdir -p "$tmp_dir/$subdir"
  44. fi
  45. mktemp --tmpdir="$tmp_dir" "${subdir}/awlsim-test-${prefix}.XXXXXX"
  46. }
  47. # $1=message
  48. test_failed()
  49. {
  50. errormsg "=== TEST FAILED ==="
  51. die "$@"
  52. }
  53. cleanup()
  54. {
  55. wait
  56. rm -rf "$tmp_dir" >/dev/null 2>&1
  57. }
  58. cleanup_and_exit()
  59. {
  60. cleanup
  61. exit 1
  62. }
  63. # Get a configuration option.
  64. # $1=configured_file
  65. # $2=option_name
  66. # ($3=default_value)
  67. get_conf()
  68. {
  69. local configured_file="$1"
  70. local option_name="$2"
  71. local default_value="$3"
  72. local conf="${configured_file}.conf"
  73. local val="$default_value"
  74. if [ -r "$conf" ]; then
  75. local regex="^${option_name}="
  76. if grep -qEe "$regex" "$conf"; then
  77. local val="$(grep -Ee "$regex" "$conf" | cut -d'=' -f2-)"
  78. fi
  79. fi
  80. printf '%s' "$val"
  81. }
  82. # Allocate a new port number.
  83. get_port()
  84. {
  85. (
  86. flock -x 8 || die "Failed to take port lock"
  87. local port="$(cat "$port_alloc_file")"
  88. local next="$(expr "$port" + 1)"
  89. echo "$next" > "$port_alloc_file" ||\
  90. die "Failed to update port allocation file"
  91. echo -n "$port"
  92. ) 8> "${port_alloc_file}.lock"
  93. }
  94. # Returns true (0), if there are more than 1 jobs.
  95. is_parallel_run()
  96. {
  97. [ $opt_jobs -gt 1 ]
  98. }
  99. # Wait until there is at least one free job slot.
  100. wait_for_free_job_slot()
  101. {
  102. while true; do
  103. jobs -l > "$jobs_tmp_file" # can't use pipe on dash
  104. [ "$(cat "$jobs_tmp_file" | wc -l)" -lt $opt_jobs ] && break
  105. # Too many jobs. Waiting...
  106. sleep 0.1
  107. done
  108. }
  109. # $1 is the PID of the job to wait for.
  110. wait_for_job_pid()
  111. {
  112. local jobpid="$1"
  113. while true; do
  114. jobs -l > "$jobs_tmp_file" # can't use pipe on dash
  115. cat "$jobs_tmp_file" | tr -d '+-' |\
  116. sed -e 's/[[:blank:]]\+/\t/g' | cut -f2 |\
  117. grep -qe '^'"$jobpid"'$' || break
  118. # Job is still running...
  119. sleep 0.1
  120. done
  121. }
  122. # Returns true (0), if at least one background job failed.
  123. check_job_failure()
  124. {
  125. is_parallel_run &&\
  126. [ -e "$test_fail_file" ] &&\
  127. [ "0" != "$(du -s "$test_fail_file" | cut -f1)" ]
  128. }
  129. wait_for_all_background_jobs()
  130. {
  131. is_parallel_run || return
  132. infomsg "Waiting for background jobs..."
  133. wait
  134. # Print the fail information.
  135. if check_job_failure; then
  136. errormsg
  137. errormsg "===== FAILURES in parallel run: ====="
  138. cat "$test_fail_file" >&2
  139. errormsg "====================================="
  140. global_retval=1
  141. fi
  142. }
  143. # $1=interpreter
  144. # Returns version on stdout as: MAJOR MINOR PATCHLEVEL
  145. get_interpreter_version()
  146. {
  147. local interpreter="$1"
  148. [ "$interpreter" = "cython3" ] && local interpreter=python3
  149. "$interpreter" -c 'import sys; print("%d %d %d" % sys.version_info[0:3]);' 2>/dev/null
  150. }
  151. # Check if an interpreter is able to run GUI code.
  152. # $1=interpreter
  153. interpreter_is_gui_compat()
  154. {
  155. local interpreter="$1"
  156. [ $opt_nogui -eq 0 ] &&\
  157. [ "$interpreter" = "python3" -o \
  158. "$interpreter" = "cython3" ]
  159. }
  160. # $1=program_name
  161. have_prog()
  162. {
  163. local program="$1"
  164. which "$program" >/dev/null 2>&1
  165. }
  166. # $1=executable_name
  167. find_executable()
  168. {
  169. local executable_name="$1"
  170. local executable_path="$(which "$executable_name")"
  171. [ -n "$executable_path" ] ||\
  172. die "$executable_name executable not found."\
  173. "Please install $executable_name."
  174. RET="$executable_path"
  175. }
  176. # Check DOS file encoding.
  177. # $1=file
  178. check_dos_text_encoding()
  179. {
  180. local file="$1"
  181. if [ x"$(du -b "$file" | cut -f1)" != x"0" ]; then
  182. # Check CR/LF
  183. file -L "$file" | grep -qe 'CRLF line terminators' || {
  184. die "ERROR: '$file' is not in DOS format."
  185. }
  186. # Check file encoding
  187. file -L "$file" | grep -qEe '(ISO-8859 text)|(ASCII text)' || {
  188. die "ERROR: '$file' invalid file encoding."
  189. }
  190. fi
  191. }
  192. # $1=interpreter $2=tested_file [$3=test_name]
  193. setup_test_environment()
  194. {
  195. local interpreter="$1"
  196. local tested_file="$2"
  197. local test_name="$3"
  198. [ -z "$test_name" ] && local test_name="$tested_file"
  199. local test_name="$(realpath -m --no-symlinks --relative-base="$rootdir" "$test_name" | tr '/\\' _)"
  200. # Check if we want to run on Cython3 and set up the environment.
  201. local use_cython=0
  202. if [ "$interpreter" = "cython3" ] ||\
  203. [ "$interpreter" = "python3" -a "$AWLSIM_CYTHON" != "" ]; then
  204. # We want to run the test using Cython3
  205. local use_cython=3
  206. for i in "$rootdir"/build/lib.linux-*-3.*; do
  207. export PYTHONPATH="$i"
  208. break
  209. done
  210. # Enforce cython module usage
  211. export AWLSIM_CYTHON=2
  212. # The actual interpreter is Python
  213. local interpreter=python3
  214. elif [ "$interpreter" = "micropython" ]; then
  215. # We want to run the test using Micropython
  216. local interpreter="$rootdir/maintenance/micropython-wrapper.sh"
  217. else
  218. # Not Cython
  219. export PYTHONPATH=
  220. export AWLSIM_CYTHON=
  221. fi
  222. # Extra environment variables
  223. RAW_EXTRA_ENV="$(get_conf "$tested_file" env)"
  224. for env in $(printf '%s' "$RAW_EXTRA_ENV" | tr ':' ' '); do
  225. eval export "$env"
  226. done
  227. # Get extra PYTHONPATH from test case config file.
  228. local conf_pythonpath=
  229. if [ -n "$tested_file" ]; then
  230. local raw_conf_pythonpath="$(get_conf "$tested_file" PYTHONPATH)"
  231. local onepath=
  232. for onepath in $(printf '%s' "$raw_conf_pythonpath" | tr ':' ' '); do
  233. if [ -n "$conf_pythonpath" ]; then
  234. local conf_pythonpath="$conf_pythonpath:"
  235. fi
  236. local conf_pythonpath="${conf_pythonpath}$(realpath -m --no-symlinks "$rootdir/$onepath")"
  237. done
  238. fi
  239. # Export PYTHONPATHs
  240. export PYTHONPATH="$PYTHONPATH:$EXTRA_PYTHONPATH:$conf_pythonpath"
  241. export JYTHONPATH="$JYTHONPATH:$EXTRA_PYTHONPATH:$conf_pythonpath"
  242. export IRONPYTHONPATH="$IRONPYTHONPATH:$EXTRA_PYTHONPATH:$conf_pythonpath"
  243. export MICROPYPATH="$MICROPYPATH:$EXTRA_PYTHONPATH:$conf_pythonpath"
  244. # Disable Python optimization so that assert statements are enabled.
  245. # Enable warnings
  246. # Enable hash seed randomization.
  247. unset PYTHONSTARTUP
  248. unset PYTHONY2K
  249. unset PYTHONOPTIMIZE
  250. unset PYTHONDEBUG
  251. unset PYTHONDONTWRITEBYTECODE
  252. unset PYTHONINSPECT
  253. unset PYTHONIOENCODING
  254. unset PYTHONNOUSERSITE
  255. unset PYTHONUNBUFFERED
  256. unset PYTHONVERBOSE
  257. if [ $use_cython -eq 0 ]; then
  258. export PYTHONWARNINGS=once
  259. else
  260. export PYTHONWARNINGS=once,ignore::ImportWarning
  261. fi
  262. export PYTHONHASHSEED=random
  263. # Disable CPU affinity
  264. unset AWLSIM_AFFINITY
  265. # Setup coverage tracing
  266. if [ $coverage_enabled -eq 0 ]; then
  267. unset AWLSIM_COVERAGE
  268. else
  269. local coverage_data_file="$(maketemp "coverage_${test_name}" "$coverage_data_subdir")"
  270. rm "$coverage_data_file"
  271. export AWLSIM_COVERAGE="$coverage_data_file"
  272. fi
  273. RET="$interpreter"
  274. }
  275. cleanup_test_environment()
  276. {
  277. export AWLSIM_CYTHON=
  278. export PYTHONPATH=
  279. export JYTHONPATH=
  280. export IRONPYTHONPATH=
  281. export MICROPYPATH=
  282. export EXTRA_PYTHONPATH=
  283. # Unexport all extra envs
  284. for env in $(printf '%s' "$RAW_EXTRA_ENV" | tr ':' ' '); do
  285. eval export "$(printf '%s' "$env" | cut -d'=' -f1)"=
  286. done
  287. }
  288. # $1=interpreter $2=awl_file ($3ff additional options to awlsim-test)
  289. run_awl_test()
  290. {
  291. local interpreter="$1"
  292. local awl="$2"
  293. shift; shift
  294. # By default run once with all optimizers enabled.
  295. local optimizer_runs="$(get_conf "$awl" optimizer_runs all)"
  296. local first_opti=1
  297. for optimizers in $optimizer_runs; do
  298. [ $first_opti -eq 0 ] && infomsg -n " / "
  299. local first_opti=0
  300. local tries="$(get_conf "$awl" tries 1)"
  301. [ $tries -lt 1 ] && local tries=1
  302. local first_try=1
  303. local ok=0
  304. local exit_code=-1
  305. local expected_exit_code=-2
  306. while [ $tries -gt 0 -a $ok -eq 0 ]; do
  307. local tries="$(expr "$tries" - 1)"
  308. (
  309. [ $first_try -ne 0 ] && adjust_niceness "$($SHELL -c 'echo $PPID')"
  310. setup_test_environment "$interpreter" "$awl"
  311. local actual_interpreter="$RET"
  312. local loglevel="$(get_conf "$awl" loglevel "$opt_loglevel")"
  313. local expected_exit_code="$(get_conf "$awl" exit_code 0)"
  314. [ $expected_exit_code -eq 0 ] || local loglevel=0
  315. local cycle_limit="$(get_conf "$awl" cycle_limit 60)"
  316. local max_runtime="$(get_conf "$awl" max_runtime -1)"
  317. local accus="$(get_conf "$awl" accus)"
  318. if [ "$accus" = "2" ]; then
  319. local accus=--twoaccu
  320. elif [ "$accus" = "4" ]; then
  321. local accus=--fouraccu
  322. elif [ -n "$accus" ]; then
  323. cleanup_test_environment
  324. die "Invalid 'accus' value in .conf"
  325. fi
  326. local dump_opt=
  327. [ $loglevel -ge 3 ] && local dump_opt="--no-cpu-dump"
  328. "$actual_interpreter" "$rootdir/awlsim-test" \
  329. --loglevel $loglevel \
  330. --extended-insns \
  331. --hardware debug:inputAddressBase=7:outputAddressBase=8:dummyParam=True \
  332. --cycle-limit "$cycle_limit" \
  333. --max-runtime "$max_runtime" \
  334. --optimizers "$optimizers" \
  335. $accus \
  336. $dump_opt \
  337. "$@" \
  338. "$awl"
  339. local exit_code=$?
  340. if [ $exit_code -ne $expected_exit_code ]; then
  341. # Test failed
  342. cleanup_test_environment
  343. if [ $tries -gt 0 ]; then
  344. infomsg "Test '$(basename "$awl")' FAILED, but retrying ($tries tries left)..."
  345. sleep 1
  346. die # Next try
  347. else
  348. test_failed "\nTest '$(basename "$awl")' FAILED" \
  349. "\nInterpreter = $interpreter" \
  350. "\nOptimizers = $optimizers" \
  351. "\nActual exit code = $exit_code" \
  352. "\nExpected exit code = $expected_exit_code"
  353. fi
  354. fi
  355. cleanup_test_environment
  356. ) && local ok=1
  357. local first_try=0
  358. done
  359. if [ $ok -eq 0 ]; then
  360. die # Test failed
  361. fi
  362. if is_parallel_run; then
  363. infomsg "$(basename "$awl"): O=$optimizers -> OK"
  364. else
  365. infomsg -n "O=$optimizers -> OK"
  366. fi
  367. done
  368. is_parallel_run || infomsg
  369. }
  370. # $1=interpreter $2=sh_file
  371. run_sh_test()
  372. {
  373. local interpreter="$1"
  374. local sh_file="$2"
  375. shift; shift
  376. [ -x "$sh_file" ] && die "SH-file '$sh_file' must NOT be executable"
  377. [ "$(echo "$sh_file" | cut -c1)" = '/' ] || local sh_file="$(pwd)/$sh_file"
  378. # Run the test
  379. (
  380. # Source the test file
  381. . "$basedir/sh-test.defaults"
  382. . "$sh_file"
  383. adjust_niceness "$($SHELL -c 'echo $PPID')"
  384. setup_test_environment "$interpreter" "$sh_file"
  385. local interpreter="$RET"
  386. local test_dir="$(dirname "$sh_file")"
  387. local test_name="$(basename "$sh_file" .sh)"
  388. sh_test "$interpreter" "$test_dir" "$test_name"
  389. cleanup_test_environment
  390. )
  391. local result=$?
  392. [ $result -eq 0 ] || die "Test failed with error code $result"
  393. if is_parallel_run; then
  394. infomsg "$(basename "$sh_file"): OK"
  395. else
  396. infomsg "OK"
  397. fi
  398. }
  399. # $1=interpreter $2=test_file
  400. run_pyunit_test()
  401. {
  402. local interpreter="$1"
  403. local test_case="$2"
  404. shift; shift
  405. [ -z "$test_case" ] &&\
  406. die "Python unittest test case is missing"
  407. [ -d "$test_case" ] &&\
  408. die "Python unittest test case '$test_case' must not be a directory"
  409. [ -x "$test_case" ] &&\
  410. die "Python unittest test case '$test_case' must NOT be executable"
  411. # Resolve relative path
  412. [ "$(echo "$test_case" | cut -c1)" = '/' ] ||\
  413. local test_case="$(pwd)/$test_case"
  414. (
  415. # Add awlsim_tstlib.py to PYTHONPATH
  416. EXTRA_PYTHONPATH="$rootdir:$rootdir/tests:$EXTRA_PYTHONPATH"
  417. # Setup python environment
  418. adjust_niceness "$($SHELL -c 'echo $PPID')"
  419. local orig_interpreter="$interpreter"
  420. setup_test_environment "$interpreter" "$test_case"
  421. local interpreter="$RET"
  422. export PYTHONDONTWRITEBYTECODE=1
  423. if [ "$orig_interpreter" = "cython3" ] && ! [ -e "$(dirname "$test_case")/no_cython" ]; then
  424. # Get the relative test case path starting in 'tests' directory.
  425. local relpath="$(realpath -m --no-symlinks --relative-base="$rootdir/tests" "$test_case")"
  426. # Patch the module name to Cython name (append _cython).
  427. local patch_re='s/(tc[0-9][0-9][0-9]_[0-9a-zA-Z]*)/\1_cython/'
  428. local relpath_cython="$(printf "%s" "$relpath" | sed -Ee "$patch_re")"
  429. # Get the relative directory of the test case.
  430. local reldir_cython="$(dirname "$relpath_cython")"
  431. # Go to the unittest subdir to run the Cython unittest.
  432. cd "$rootdir/tests/build/"lib.*-3.*"/$reldir_cython" || die "Failed to cd to test directory."
  433. else
  434. # Go to the unittest subdir to run the Python unittest.
  435. cd "$(dirname "$test_case")" || die "Failed to cd to test directory."
  436. fi
  437. # Convert test name to module name (for python2)
  438. local test_case="$(basename "$test_case" .py)"
  439. # Run it.
  440. if [ -n "$AWLSIM_TEST_QUIET" ]; then
  441. "$interpreter" -m unittest "$test_case" >/dev/null 2>&1 ||\
  442. die "Python unittest test case '$(basename "$test_case")' failed."
  443. else
  444. "$interpreter" -m unittest "$test_case" ||\
  445. die "Python unittest test case '$(basename "$test_case")' failed."
  446. fi
  447. infomsg "$(basename "$test_case"): OK"
  448. cleanup_test_environment
  449. ) || die "'$(basename "$test_case")' FAILED"
  450. }
  451. # $1=interpreter $2=testfile(.awl/.sh) ($3ff additional options to awlsim-test or testfile)
  452. run_test()
  453. {
  454. local interpreter="$1"
  455. local testfile="$2"
  456. shift; shift
  457. # Don't run ourself
  458. [ "$(basename "$testfile")" = "run.sh" ] && return
  459. # Don't run artifacts that aren't actual test cases.
  460. [ "$(basename "$testfile")" = "awlsim_tstlib.py" ] && return
  461. [ "$(basename "$testfile")" = "setup-cython-tests.py" ] && return
  462. [ "$(basename "$testfile")" = "__init__.py" ] && return
  463. local disabled="$(get_conf "$testfile" disabled)"
  464. if [ -z "$disabled" ]; then
  465. # Print test headline
  466. local nl="-n"
  467. is_parallel_run && local nl=
  468. infomsg $nl "$(basename "$testfile") @ $(basename "$interpreter"): "
  469. local prev_dir="$(pwd)"
  470. cd "$rootdir" || die "cd to $rootdir failed"
  471. # Check the file type and run the tester
  472. if [ "$(echo -n "$testfile" | tail -c4)" = ".awl" ]; then
  473. check_dos_text_encoding "$testfile"
  474. run_awl_test "$interpreter" "$testfile" "$@"
  475. elif [ "$(echo -n "$testfile" | tail -c7)" = ".awlpro" ]; then
  476. run_awl_test "$interpreter" "$testfile" "$@"
  477. elif [ "$(echo -n "$testfile" | tail -c3)" = ".sh" ]; then
  478. run_sh_test "$interpreter" "$testfile" "$@"
  479. elif [ "$(echo -n "$testfile" | tail -c3)" = ".py" ]; then
  480. run_pyunit_test "$interpreter" "$testfile" "$@"
  481. else
  482. die "Test file type of '$testfile' not recognized"
  483. fi
  484. cd "$prev_dir" || die "cd to $prev_dir failed"
  485. else
  486. warnmsg "Skipping '$testfile' as it is disabled."
  487. fi
  488. }
  489. run_test_parallel()
  490. {
  491. if is_parallel_run; then
  492. # Run tests in parallel.
  493. wait_for_free_job_slot
  494. run_test "$@" &
  495. else
  496. # Run tests one-by-one.
  497. run_test "$@"
  498. fi
  499. }
  500. # $1=interpreter, $2=directory
  501. run_test_directory()
  502. {
  503. local interpreter="$1"
  504. local directory="$2"
  505. [ "$(basename "$directory")" = "build" ] && return
  506. local prettydir="$(realpath -m --no-symlinks --relative-base="$rootdir" "$directory")/"
  507. infomsg ">>> entering $prettydir"
  508. # run .awlpro tests
  509. for entry in "$directory"/*; do
  510. [ -d "$entry" ] && continue
  511. [ "$(echo -n "$entry" | tail -c7)" = ".awlpro" ] || continue
  512. [ -e "$(dirname "$entry")/$(basename "$entry" .awlpro).sh" ] && continue
  513. run_test_parallel "$interpreter" "$entry"
  514. check_job_failure && return
  515. done
  516. # run .awl tests
  517. for entry in "$directory"/*; do
  518. [ -d "$entry" ] && continue
  519. [ "$(echo -n "$entry" | tail -c4)" = ".awl" ] || continue
  520. [ -e "$(dirname "$entry")/$(basename "$entry" .awl).awlpro" ] && continue
  521. [ -e "$(dirname "$entry")/$(basename "$entry" .awl).sh" ] && continue
  522. run_test_parallel "$interpreter" "$entry"
  523. check_job_failure && return
  524. done
  525. # run .sh tests
  526. for entry in "$directory"/*; do
  527. [ -d "$entry" ] && continue
  528. [ "$(echo -n "$entry" | tail -c3)" = ".sh" ] || continue
  529. run_test_parallel "$interpreter" "$entry"
  530. check_job_failure && return
  531. done
  532. # run .py unittest tests
  533. for entry in "$directory"/*; do
  534. [ -d "$entry" ] && continue
  535. [ "$(echo -n "$entry" | tail -c3)" = ".py" ] || continue
  536. [ "$entry" = "__init__.py" ] && continue
  537. run_test_parallel "$interpreter" "$entry"
  538. check_job_failure && return
  539. done
  540. # Recurse into subdirectories
  541. for entry in "$directory"/*; do
  542. [ -d "$entry" ] || continue
  543. run_test_directory "$interpreter" "$entry"
  544. done
  545. infomsg "<<< leaving $prettydir"
  546. }
  547. # $1=interpreter
  548. warn_skipped()
  549. {
  550. local interpreter="$1"
  551. warnmsg "=== WARNING: '$interpreter' interpreter not found. Test skipped."
  552. warnmsg
  553. }
  554. __build_cython()
  555. {
  556. local cython="$1"
  557. local python="$2"
  558. have_prog "$cython" && have_prog "$python" || {
  559. warnmsg "=== WARNING: Cannot build $cython modules"
  560. return 1
  561. }
  562. (
  563. infomsg "=== Building awlsim $cython modules with $python"
  564. cd "$rootdir" || die "cd to $rootdir failed"
  565. CFLAGS="-O0" CPPFLAGS= CXXFLAGS="-O0" LDFLAGS= \
  566. AWLSIM_CYTHON_BUILD=1 \
  567. AWLSIM_CYTHON_PARALLEL=1 \
  568. nice -n 5 \
  569. "$python" ./setup.py build >/dev/null ||\
  570. die "'$python ./setup.py build' failed"
  571. ) || die
  572. (
  573. infomsg "=== Building awlsim $cython test cases with $python"
  574. cd "$rootdir/tests" || die "cd to $rootdir/tests failed"
  575. rm -rf build || die "Failed to clean test cases build"
  576. nice -n 5 \
  577. "$python" ./setup-cython-tests.py build >/dev/null ||\
  578. die "'$python ./setup-cython-tests.py build' failed"
  579. ) || die
  580. return 0
  581. }
  582. build_cython3()
  583. {
  584. __build_cython cython3 python3
  585. }
  586. # $@=testfiles
  587. do_tests()
  588. {
  589. cleanup_test_environment
  590. if [ $opt_quick -eq 0 ]; then
  591. local all_interp="python3 python2 cython3 pypy3"
  592. if [ $opt_extended -ne 0 ]; then
  593. local all_interp="$all_interp jython"
  594. fi
  595. else
  596. local all_interp="python3 python2"
  597. if [ $opt_extended -ne 0 ]; then
  598. die "The options --quick and --extended are mutually exclusive."
  599. fi
  600. fi
  601. for interpreter in "$opt_interpreter" $all_interp; do
  602. [ -z "$interpreter" ] && continue
  603. cleanup_test_environment
  604. # Create an interpreter name suitable as path component
  605. local interpreter_name="$(printf '%s' "$interpreter" | tr '/\\' _)"
  606. # Check if we should enable coverage tracing
  607. coverage_enabled=$opt_coverage
  608. if [ $coverage_enabled -ne 0 ] &&\
  609. [ "$interpreter" = "pypy" -o "$interpreter" = "pypy3" ]; then
  610. # Performance impact of coverage on PyPy is too big.
  611. # Disable coverage to avoid test failures.
  612. warnmsg "Disabling code coverage tracing (-c|--coverage) on PyPy due to bad performace."
  613. coverage_enabled=0
  614. fi
  615. # Prepare code coverage directory
  616. coverage_data_subdir="coverage-$interpreter_name"
  617. mkdir -p "$tmp_dir/$coverage_data_subdir" || die "Failed to create coverage data dir"
  618. # Basic interpreter setup. Build Cython modules.
  619. if [ "$interpreter" = "cython3" ]; then
  620. have_prog cython3 && have_prog python3 || {
  621. warn_skipped "$interpreter"
  622. [ -n "$opt_interpreter" ] && break || continue
  623. }
  624. wait_for_all_background_jobs
  625. build_cython3 || die "Cython3 build failed."
  626. else
  627. have_prog "$interpreter" || {
  628. warn_skipped "$interpreter"
  629. [ -n "$opt_interpreter" ] && break || continue
  630. }
  631. fi
  632. local interp_ver="$(get_interpreter_version "$interpreter")"
  633. local interp_ver_dot="$(echo "$interp_ver" | tr ' ' '.')"
  634. local interp_major="$(echo "$interp_ver" | cut -d' ' -f 1)"
  635. local interp_minor="$(echo "$interp_ver" | cut -d' ' -f 2)"
  636. [ -z "$interp_ver" ] &&\
  637. die "Failed to get '$interpreter' version."
  638. [ "$interp_major" -eq 2 -a "$interp_minor" -lt 7 ] &&\
  639. die "'$interpreter' interpreter version '$interp_ver_dot' too old."
  640. infomsg "=== Running tests with '$interpreter'"
  641. if [ $# -eq 0 ]; then
  642. run_test_directory "$interpreter" "$basedir"
  643. else
  644. for opt in "$@"; do
  645. local opt="$(realpath -m --no-symlinks "$opt")"
  646. if [ -d "$opt" ]; then
  647. run_test_directory "$interpreter" "$opt"
  648. else
  649. run_test_parallel "$interpreter" "$opt"
  650. fi
  651. check_job_failure && break
  652. done
  653. fi
  654. infomsg
  655. check_job_failure && break
  656. # Generate code coverage report
  657. if [ $coverage_enabled -ne 0 ]; then
  658. # Wait for background jobs to finish
  659. wait_for_all_background_jobs
  660. if [ $global_retval -eq 0 ]; then
  661. infomsg "\nGenerating code coverage report..."
  662. local reportbase="$rootdir/code-coverage-report"
  663. local reportdir="$reportbase/awlsim-coverage-$interpreter_name"
  664. rm -rf "$reportdir"
  665. "$rootdir/awlsim-covreport" \
  666. "$reportdir" \
  667. "$tmp_dir/$coverage_data_subdir/" ||\
  668. die "Failed to generate code coverage report."
  669. fi
  670. fi
  671. [ -n "$opt_interpreter" ] && break
  672. done
  673. # Wait for background jobs to finish
  674. wait_for_all_background_jobs
  675. # Print summary
  676. if [ $global_retval -eq 0 ]; then
  677. infomsg
  678. infomsg -n "All tests succeeded"
  679. else
  680. errormsg
  681. errormsg -n "Some tests FAILED"
  682. fi
  683. if [ -n "$opt_interpreter" ]; then
  684. infomsg " (with interpreter '$opt_interpreter')"
  685. else
  686. if [ $opt_quick -eq 0 ]; then
  687. if [ $opt_extended -eq 0 ]; then
  688. infomsg " (full run)"
  689. else
  690. infomsg " (extended run)"
  691. fi
  692. else
  693. infomsg " (quick run)"
  694. fi
  695. fi
  696. }
  697. show_help()
  698. {
  699. infomsg "awlsim unit test script"
  700. infomsg
  701. infomsg "Usage: run.sh [OPTIONS] [testdirectory/testscript.awl/.awlpro/.sh/.py]"
  702. infomsg
  703. infomsg "Options:"
  704. infomsg " -i|--interpreter INTER Use INTER as interpreter for the tests"
  705. infomsg " -j|--jobs NR Set the number of jobs to run in parallel."
  706. infomsg " 0 means number-of-CPUs"
  707. infomsg " Default: 0"
  708. infomsg " -q|--quick Only run python2 and python3 tests"
  709. infomsg " -g|--no-gui Avoid tests that need GUI libraries"
  710. infomsg " -x|--extended Run tests on additional interpreters"
  711. infomsg " -n|--renice NICENESS Renice by NICENESS. Defaults to 10."
  712. infomsg " -Q|--quiet Less messages"
  713. infomsg " -L|--loglevel Default log level."
  714. infomsg " -l|--loop COUNT Number of test loops to execute."
  715. infomsg " Default: 1"
  716. infomsg " Set to 0 for infinite looping."
  717. infomsg " -c|--coverage Enable code coverage tracing."
  718. }
  719. tmp_dir="/tmp/awlsim-test-$$"
  720. rm -rf "$tmp_dir" >/dev/null 2>&1
  721. mkdir -p "$tmp_dir" || die "Failed to create temp dir '$tmp_dir'"
  722. trap cleanup_and_exit INT TERM
  723. trap cleanup EXIT
  724. test_fail_file="$(maketemp fail)"
  725. port_alloc_file="$(maketemp port)"
  726. jobs_tmp_file="$(maketemp jobs)"
  727. touch "${port_alloc_file}.lock"
  728. echo 4096 > "$port_alloc_file" || die "Failed to initialize port file"
  729. opt_interpreter=
  730. opt_quick=0
  731. opt_nogui=0
  732. opt_extended=0
  733. opt_renice=
  734. opt_jobs=0
  735. opt_loglevel=2
  736. opt_loop=1
  737. opt_coverage=0
  738. while [ $# -ge 1 ]; do
  739. [ "$(printf '%s' "$1" | cut -c1)" != "-" ] && break
  740. case "$1" in
  741. -h|--help)
  742. show_help
  743. exit 0
  744. ;;
  745. -i|--interpreter)
  746. shift
  747. opt_interpreter="$1"
  748. have_prog "$opt_interpreter" ||\
  749. die "Interpreter '${opt_interpreter}' not found"
  750. ;;
  751. -j|--jobs)
  752. shift
  753. opt_jobs="$1"
  754. ;;
  755. -q|--quick)
  756. opt_quick=1
  757. ;;
  758. -g|--no-gui)
  759. opt_nogui=1
  760. ;;
  761. -x|--extended)
  762. opt_extended=1
  763. ;;
  764. -n|--renice)
  765. shift
  766. opt_renice="$1"
  767. ;;
  768. -Q|--quiet)
  769. export AWLSIM_TEST_QUIET=1
  770. ;;
  771. -L|--loglevel)
  772. shift
  773. opt_loglevel="$1"
  774. ;;
  775. -l|--loop)
  776. shift
  777. opt_loop="$1"
  778. ;;
  779. -c|--coverage)
  780. opt_coverage=1
  781. ;;
  782. *)
  783. errormsg "Unknown option: $1"
  784. exit 1
  785. ;;
  786. esac
  787. shift
  788. done
  789. [ -z "$opt_jobs" -o -n "$(printf '%s' "$opt_jobs" | tr -d '[0-9]')" ] &&\
  790. die "--jobs: '$opt_jobs' is not a positive integer number."
  791. if [ $opt_jobs -eq 0 ]; then
  792. opt_jobs="$(getconf _NPROCESSORS_ONLN)"
  793. opt_jobs="$(expr $opt_jobs + 2)"
  794. fi
  795. [ -z "$opt_jobs" ] &&\
  796. die "Could not detect number of CPUs."
  797. if [ -z "$opt_loop" -o -n "$(printf '%s' "$opt_loop" | tr -d '[0-9]')" ] || [ $opt_loop -le 0 ]; then
  798. opt_loop=infinite
  799. fi
  800. do_renice()
  801. {
  802. local niceness="$1"
  803. local pid="$2"
  804. renice "$niceness" "$pid" >/dev/null
  805. }
  806. adjust_niceness()
  807. {
  808. local pid="$1"
  809. if [ -n "$opt_renice" ]; then
  810. do_renice "$opt_renice" "$pid" || die "Failed to renice"
  811. else
  812. # Try to renice. Ignore failure.
  813. do_renice 10 "$pid"
  814. fi
  815. }
  816. # Run the tests
  817. global_retval=0
  818. loop_iteration=0
  819. while [ "$opt_loop" = "infinite" ] || [ $opt_loop -gt 0 ]; do
  820. infomsg "Running test loop iteration $(expr "$loop_iteration" + 1)"
  821. do_tests "$@"
  822. if [ $global_retval -ne 0 ]; then
  823. break
  824. fi
  825. if [ "$opt_loop" != "infinite" ]; then
  826. opt_loop="$(expr "$opt_loop" - 1)"
  827. fi
  828. loop_iteration="$(expr "$loop_iteration" + 1)"
  829. done
  830. exit $global_retval