run.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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. die()
  15. {
  16. echo "$*"
  17. # We might be in a sub-job. So write to fail-file.
  18. failfile_write "$*"
  19. exit 1
  20. }
  21. # $1=message
  22. test_failed()
  23. {
  24. echo "=== TEST FAILED ==="
  25. if [ $opt_softfail -eq 0 ]; then
  26. die "$@"
  27. else
  28. failfile_write "$*"
  29. echo "$*"
  30. echo "^^^ TEST FAILED ^^^"
  31. [ $global_retval -eq 0 ] && global_retval=1
  32. fi
  33. }
  34. cleanup()
  35. {
  36. wait
  37. rm -f "/tmp/$test_time_file_template"* >/dev/null 2>&1
  38. [ -n "$test_fail_file" ] &&\
  39. rm -f "$test_fail_file" >/dev/null 2>&1
  40. [ -n "$port_alloc_file" ] &&\
  41. rm -f "$port_alloc_file"* >/dev/null 2>&1
  42. [ -n "$jobs_tmp_file" ] &&\
  43. rm -f "$jobs_tmp_file"* >/dev/null 2>&1
  44. }
  45. cleanup_and_exit()
  46. {
  47. cleanup
  48. exit 1
  49. }
  50. # Get a configuration option.
  51. # $1=configured_file
  52. # $2=option_name
  53. get_conf()
  54. {
  55. local configured_file="$1"
  56. local option_name="$2"
  57. local conf="${configured_file}.conf"
  58. if [ -r "$conf" ]; then
  59. local val="$(grep -Ee "^${option_name}=" "$conf" | cut -d'=' -f2)"
  60. printf '%s' "$val"
  61. fi
  62. }
  63. # Allocate a new port number.
  64. get_port()
  65. {
  66. (
  67. flock -x 8 || die "Failed to take port lock"
  68. local port="$(cat "$port_alloc_file")"
  69. local next="$(expr "$port" + 1)"
  70. echo "$next" > "$port_alloc_file" ||\
  71. die "Failed to update port allocation file"
  72. echo -n "$port"
  73. ) 8> "${port_alloc_file}.lock"
  74. }
  75. # Returns true (0), if there are more than 1 jobs.
  76. is_parallel_run()
  77. {
  78. [ $opt_jobs -gt 1 ]
  79. }
  80. # Wait until there is at least one free job slot.
  81. wait_for_free_job_slot()
  82. {
  83. while true; do
  84. jobs -l > "$jobs_tmp_file" # can't use pipe on dash
  85. [ "$(cat "$jobs_tmp_file" | wc -l)" -lt $opt_jobs ] && break
  86. # Too many jobs. Waiting...
  87. sleep 0.1
  88. done
  89. }
  90. # $1 is the PID of the job to wait for.
  91. wait_for_job_pid()
  92. {
  93. local jobpid="$1"
  94. while true; do
  95. jobs -l > "$jobs_tmp_file" # can't use pipe on dash
  96. cat "$jobs_tmp_file" | tr -d '+-' |\
  97. sed -e 's/[[:blank:]]\+/\t/g' | cut -f2 |\
  98. grep -qe '^'"$jobpid"'$' || break
  99. # Job is still running...
  100. sleep 0.1
  101. done
  102. }
  103. # Returns true (0), if at least one background job failed.
  104. check_job_failure()
  105. {
  106. is_parallel_run &&\
  107. [ -e "$test_fail_file" ] &&\
  108. [ "0" != "$(du -s "$test_fail_file" | cut -f1)" ]
  109. }
  110. # $1=interpreter
  111. # Returns version on stdout as: MAJOR MINOR PATCHLEVEL
  112. get_interpreter_version()
  113. {
  114. local interpreter="$1"
  115. [ "$interpreter" = "cython" -o "$interpreter" = "cython2" ] && local interpreter=python2
  116. [ "$interpreter" = "cython3" ] && local interpreter=python3
  117. "$interpreter" -c 'import sys; print("%d %d %d" % sys.version_info[0:3]);' 2>/dev/null
  118. }
  119. # $1=program_name
  120. have_prog()
  121. {
  122. local program="$1"
  123. which "$program" >/dev/null 2>&1
  124. }
  125. # $1=interpreter
  126. setup_test_environment()
  127. {
  128. local interpreter="$1"
  129. if [ "$interpreter" = "cython" -o "$interpreter" = "cython2" ]; then
  130. for i in "$rootdir"/build/lib.linux-*-2.*; do
  131. export PYTHONPATH="$i"
  132. break
  133. done
  134. export AWLSIMCYTHON=2
  135. local interpreter=python2
  136. elif [ "$interpreter" = "cython3" ]; then
  137. for i in "$rootdir"/build/lib.linux-*-3.*; do
  138. export PYTHONPATH="$i"
  139. break
  140. done
  141. export AWLSIMCYTHON=2
  142. local interpreter=python3
  143. else
  144. export PYTHONPATH=
  145. export AWLSIMCYTHON=
  146. fi
  147. export PYTHONPATH="$PYTHONPATH:$EXTRA_PYTHONPATH"
  148. export JYTHONPATH="$JYTHONPATH:$EXTRA_PYTHONPATH"
  149. export IRONPYTHONPATH="$IRONPYTHONPATH:$EXTRA_PYTHONPATH"
  150. export MICROPYPATH="$MICROPYPATH:$EXTRA_PYTHONPATH"
  151. RET="$interpreter"
  152. }
  153. cleanup_test_environment()
  154. {
  155. export AWLSIMCYTHON=
  156. export PYTHONPATH=
  157. export JYTHONPATH=
  158. export IRONPYTHONPATH=
  159. export MICROPYPATH=
  160. export EXTRA_PYTHONPATH=
  161. }
  162. # $1=interpreter $2=awl_file ($3ff additional options to awlsim-test)
  163. run_awl_test()
  164. {
  165. local interpreter="$1"
  166. local awl="$2"
  167. shift; shift
  168. setup_test_environment "$interpreter"
  169. local interpreter="$RET"
  170. local test_time_file="$(mktemp --tmpdir=/tmp ${test_time_file_template}.XXXXXX)"
  171. local tries="$(get_conf "$awl" tries)"
  172. [ -n "$tries" ] || local tries=1
  173. [ $tries -lt 1 ] && local tries=1
  174. local ok=0
  175. while [ $tries -gt 0 -a $ok -eq 0 ]; do
  176. local ok=1
  177. local tries="$(expr "$tries" - 1)"
  178. command time -o "$test_time_file" -f '%E' \
  179. "$interpreter" "$rootdir/awlsim-test" --loglevel 2 --extended-insns \
  180. --hardware debug:inputAddressBase=7:outputAddressBase=8:dummyParam=True \
  181. --cycle-time 60 \
  182. "$@" \
  183. "$awl" || {
  184. local ok=0
  185. [ $tries -gt 0 ] &&\
  186. echo "Test '$(basename "$awl")' FAILED, but retrying ($tries)..."
  187. }
  188. done
  189. [ $ok -eq 0 ] && test_failed "Test '$(basename "$awl")' FAILED"
  190. if is_parallel_run; then
  191. [ $ok -ne 0 ] && echo "[$(basename "$awl"): OK $(cat "$test_time_file")]"
  192. else
  193. [ $ok -ne 0 ] && echo "[OK: $(cat "$test_time_file")]"
  194. fi
  195. rm "$test_time_file"
  196. cleanup_test_environment
  197. }
  198. # $1=interpreter $2=sh_file
  199. run_sh_test()
  200. {
  201. local interpreter="$1"
  202. local sh_file="$2"
  203. shift; shift
  204. [ -x "$sh_file" ] && die "SH-file '$sh_file' must NOT be executable"
  205. [ "$(echo "$sh_file" | cut -c1)" = '/' ] || local sh_file="$(pwd)/$sh_file"
  206. # Source the test file
  207. . "$basedir/sh-test.defaults"
  208. . "$sh_file"
  209. # Run the test
  210. (
  211. setup_test_environment "$interpreter"
  212. local interpreter="$RET"
  213. local test_dir="$(dirname "$sh_file")"
  214. local test_name="$(basename "$sh_file" .sh)"
  215. sh_test "$interpreter" "$test_dir" "$test_name"
  216. cleanup_test_environment
  217. )
  218. local result=$?
  219. [ $result -eq 0 ] || die "Test failed with error code $result"
  220. if is_parallel_run; then
  221. echo "[$(basename "$sh_file"): OK]"
  222. else
  223. echo "[OK]"
  224. fi
  225. }
  226. # $1=interpreter $2=testfile(.awl/.sh) ($3ff additional options to awlsim-test or testfile)
  227. __run_test()
  228. {
  229. local interpreter="$1"
  230. local testfile="$2"
  231. shift; shift
  232. # Don't run ourself
  233. [ "$(basename "$testfile")" = "run.sh" ] && return
  234. if is_parallel_run; then
  235. local nl=
  236. else
  237. local nl="-n"
  238. fi
  239. echo $nl "Running test '$(basename "$testfile")' with '$(basename "$interpreter")' ... "
  240. # Check the file type and run the tester
  241. if [ "$(echo -n "$testfile" | tail -c4)" = ".awl" -o\
  242. "$(echo -n "$testfile" | tail -c7)" = ".awlpro" ]; then
  243. run_awl_test "$interpreter" "$testfile" "$@"
  244. elif [ "$(echo -n "$testfile" | tail -c3)" = ".sh" ]; then
  245. run_sh_test "$interpreter" "$testfile" "$@"
  246. else
  247. die "Test file type of '$testfile' not recognized"
  248. fi
  249. }
  250. run_test()
  251. {
  252. if is_parallel_run; then
  253. # Run tests in parallel.
  254. wait_for_free_job_slot
  255. __run_test "$@" &
  256. else
  257. # Run tests one-by-one.
  258. __run_test "$@"
  259. fi
  260. }
  261. # $1=interpreter, $2=directory
  262. run_test_directory()
  263. {
  264. local interpreter="$1"
  265. local directory="$2"
  266. echo "--- Entering directory '$directory'"
  267. # run .awlpro tests
  268. for entry in "$directory"/*; do
  269. [ -d "$entry" ] && continue
  270. [ "$(echo -n "$entry" | tail -c7)" = ".awlpro" ] || continue
  271. [ -e "$(dirname "$entry")/$(basename "$entry" .awlpro).sh" ] && continue
  272. local extra=
  273. [ "$(basename "$entry")" = "EXAMPLE.awlpro" -o\
  274. "$(basename $(dirname "$entry"))" = "999-projects" ] &&\
  275. local extra="--max-runtime 1.0"
  276. run_test "$interpreter" "$entry" $extra
  277. check_job_failure && return
  278. done
  279. # run .awl tests
  280. for entry in "$directory"/*; do
  281. [ -d "$entry" ] && continue
  282. [ "$(echo -n "$entry" | tail -c4)" = ".awl" ] || continue
  283. [ -e "$(dirname "$entry")/$(basename "$entry" .awl).awlpro" ] && continue
  284. [ -e "$(dirname "$entry")/$(basename "$entry" .awl).sh" ] && continue
  285. local extra=
  286. [ "$(basename $(dirname "$entry"))" = "999-projects" ] &&\
  287. local extra="--max-runtime 1.0"
  288. run_test "$interpreter" "$entry" $extra
  289. check_job_failure && return
  290. done
  291. # run .sh tests
  292. for entry in "$directory"/*; do
  293. [ -d "$entry" ] && continue
  294. [ "$(echo -n "$entry" | tail -c3)" = ".sh" ] || continue
  295. run_test "$interpreter" "$entry"
  296. check_job_failure && return
  297. done
  298. # Recurse into subdirectories
  299. for entry in "$directory"/*; do
  300. [ -d "$entry" ] || continue
  301. run_test_directory "$interpreter" "$entry"
  302. done
  303. echo "--- Leaving directory '$directory'"
  304. }
  305. # $1=interpreter
  306. warn_skipped()
  307. {
  308. local interpreter="$1"
  309. echo "=== WARNING: '$interpreter' interpreter not found. Test skipped."
  310. echo
  311. }
  312. build_cython2()
  313. {
  314. have_prog cython && have_prog python2 || {
  315. echo "=== WARNING: Cannot build cython2 modules"
  316. return 1
  317. }
  318. cd "$rootdir" || die "cd to $rootdir failed"
  319. echo "=== Building awlsim with python2"
  320. CYTHONPARALLEL=1 \
  321. nice -n 5 \
  322. python2 ./setup.py build || die "'python2 ./setup.py build' failed"
  323. return 0
  324. }
  325. build_cython3()
  326. {
  327. have_prog cython3 && have_prog python3 || {
  328. echo "=== WARNING: Cannot build cython3 modules"
  329. return 1
  330. }
  331. cd "$rootdir" || die "cd to $rootdir failed"
  332. echo "=== Building awlsim with python3"
  333. CYTHONPARALLEL=1 \
  334. nice -n 5 \
  335. python3 ./setup.py build || die "'python3 ./setup.py build' failed"
  336. return 0
  337. }
  338. # $@=testfiles
  339. do_tests()
  340. {
  341. export EXTRA_PYTHONPATH=
  342. if [ $opt_quick -eq 0 ]; then
  343. local all_interp="python2 python3 pypy pypy3 jython ipy cython2 cython3"
  344. else
  345. local all_interp="python2 python3"
  346. fi
  347. local cython2_build_pid=
  348. local cython3_build_pid=
  349. if is_parallel_run; then
  350. # Trigger the build jobs, if required.
  351. local inter="$opt_interpreter $all_interp"
  352. if printf '%s' "$inter" | grep -Eqwe 'cython|cython2'; then
  353. wait_for_free_job_slot
  354. build_cython2 &
  355. local cython2_build_pid=$!
  356. fi
  357. if printf '%s' "$inter" | grep -qwe 'cython3'; then
  358. wait_for_free_job_slot
  359. build_cython3 &
  360. local cython3_build_pid=$!
  361. fi
  362. fi
  363. for interpreter in "$opt_interpreter" $all_interp; do
  364. [ -z "$interpreter" ] && continue
  365. if [ "$interpreter" = "cython" -o "$interpreter" = "cython2" ]; then
  366. have_prog cython && have_prog python2 || {
  367. warn_skipped "$interpreter"
  368. [ -n "$opt_interpreter" ] && break || continue
  369. }
  370. if is_parallel_run; then
  371. wait_for_job_pid $cython2_build_pid
  372. else
  373. build_cython2 || die "Cython2 build failed."
  374. fi
  375. elif [ "$interpreter" = "cython3" ]; then
  376. have_prog cython3 && have_prog python3 || {
  377. warn_skipped "$interpreter"
  378. [ -n "$opt_interpreter" ] && break || continue
  379. }
  380. if is_parallel_run; then
  381. wait_for_job_pid $cython3_build_pid
  382. else
  383. build_cython3 || die "Cython3 build failed."
  384. fi
  385. else
  386. have_prog "$interpreter" || {
  387. warn_skipped "$interpreter"
  388. [ -n "$opt_interpreter" ] && break || continue
  389. }
  390. fi
  391. local interp_ver="$(get_interpreter_version "$interpreter")"
  392. local interp_ver_dot="$(echo "$interp_ver" | tr ' ' '.')"
  393. local interp_major="$(echo "$interp_ver" | cut -d' ' -f 1)"
  394. local interp_minor="$(echo "$interp_ver" | cut -d' ' -f 2)"
  395. [ -z "$interp_ver" ] && {
  396. echo "=== WARNING: Failed to get '$interpreter' version. Test skipped."
  397. echo
  398. [ -n "$opt_interpreter" ] && break || continue
  399. }
  400. [ "$interp_major" -eq 2 -a "$interp_minor" -lt 7 ] && {
  401. echo "=== WARNING: '$interpreter' interpreter version '$interp_ver_dot' too old. Test skipped."
  402. echo
  403. [ -n "$opt_interpreter" ] && break || continue
  404. }
  405. echo "=== Running tests with '$interpreter'"
  406. if [ $# -eq 0 ]; then
  407. run_test_directory "$interpreter" "$basedir"
  408. else
  409. for opt in "$@"; do
  410. if [ -d "$opt" ]; then
  411. run_test_directory "$interpreter" "$opt"
  412. else
  413. run_test "$interpreter" "$opt"
  414. fi
  415. check_job_failure && break
  416. done
  417. fi
  418. echo
  419. check_job_failure && break
  420. [ -n "$opt_interpreter" ] && break
  421. done
  422. if is_parallel_run; then
  423. # This is a parallel run. Wait for all jobs.
  424. echo "Waiting for background jobs..."
  425. wait
  426. # Print the fail information.
  427. if check_job_failure; then
  428. echo
  429. echo "===== FAILURES in parallel run: ====="
  430. cat "$test_fail_file"
  431. echo "====================================="
  432. global_retval=1
  433. fi
  434. fi
  435. # Print summary
  436. echo
  437. if [ $global_retval -eq 0 ]; then
  438. echo -n "All tests succeeded"
  439. else
  440. echo -n "Some tests FAILED"
  441. fi
  442. if [ -n "$opt_interpreter" ]; then
  443. echo " (with interpreter '$opt_interpreter')"
  444. else
  445. if [ $opt_quick -eq 0 ]; then
  446. echo " (full run)"
  447. else
  448. echo " (quick run)"
  449. fi
  450. fi
  451. }
  452. show_help()
  453. {
  454. echo "awlsim unit test script"
  455. echo
  456. echo "Usage: run.sh [OPTIONS] [testscript.awl/.sh]"
  457. echo
  458. echo "Options:"
  459. echo " -i|--interpreter INTER Use INTER as interpreter for the tests"
  460. echo " -s|--softfail Do not abort on single test failures"
  461. echo " -j|--jobs NR Set the number of jobs to run in parallel."
  462. echo " 0 means number-of-CPUs"
  463. echo " Default: 1"
  464. echo " -q|--quick Only run python2 and python3 tests"
  465. }
  466. trap cleanup_and_exit INT TERM
  467. trap cleanup EXIT
  468. test_time_file_template="awlsim-test-time.$$"
  469. test_fail_file="$(mktemp --tmpdir=/tmp awlsim-test-fail.XXXXXX)"
  470. port_alloc_file="$(mktemp --tmpdir=/tmp awlsim-test-port.XXXXXX)"
  471. jobs_tmp_file="$(mktemp --tmpdir=/tmp awlsim-test-jobs.XXXXXX)"
  472. touch "${port_alloc_file}.lock"
  473. echo 4096 > "$port_alloc_file" || die "Failed to initialize port file"
  474. opt_interpreter=
  475. opt_softfail=0
  476. opt_quick=0
  477. opt_renice=
  478. opt_jobs=1
  479. while [ $# -ge 1 ]; do
  480. [ "$(printf '%s' "$1" | cut -c1)" != "-" ] && break
  481. case "$1" in
  482. -h|--help)
  483. show_help
  484. exit 0
  485. ;;
  486. -i|--interpreter)
  487. shift
  488. opt_interpreter="$1"
  489. have_prog "$opt_interpreter" ||\
  490. die "Interpreter '${opt_interpreter}' not found"
  491. ;;
  492. -s|--softfail)
  493. opt_softfail=1
  494. ;;
  495. -j|--jobs)
  496. shift
  497. opt_jobs="$1"
  498. [ -z "$opt_jobs" -o -n "$(printf '%s' "$opt_jobs" | tr -d '[0-9]')" ] &&\
  499. die "--jobs: '$opt_jobs' is not a positive integer number."
  500. if [ $opt_jobs -eq 0 ]; then
  501. opt_jobs="$(getconf _NPROCESSORS_ONLN)"
  502. opt_jobs="$(expr $opt_jobs + 2)"
  503. fi
  504. [ -z "$opt_jobs" ] &&\
  505. die "Could not detect number of CPUs."
  506. ;;
  507. -q|--quick)
  508. opt_quick=1
  509. ;;
  510. -n|--renice)
  511. shift
  512. opt_renice="$1"
  513. ;;
  514. *)
  515. echo "Unknown option: $1"
  516. exit 1
  517. ;;
  518. esac
  519. shift
  520. done
  521. do_renice()
  522. {
  523. renice "$1" "$$"
  524. }
  525. if [ -n "$opt_renice" ]; then
  526. do_renice "$opt_renice" || die "Failed to renice"
  527. else
  528. # Try to renice. Ignore failure.
  529. do_renice 10
  530. fi
  531. global_retval=0
  532. do_tests "$@"
  533. exit $global_retval