You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
11 KiB

3 years ago
  1. #!/usr/bin/env zsh
  2. # -------------------------------------------------------------------------------------------------
  3. # Copyright (c) 2010-2017 zsh-syntax-highlighting contributors
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without modification, are permitted
  7. # provided that the following conditions are met:
  8. #
  9. # * Redistributions of source code must retain the above copyright notice, this list of conditions
  10. # and the following disclaimer.
  11. # * Redistributions in binary form must reproduce the above copyright notice, this list of
  12. # conditions and the following disclaimer in the documentation and/or other materials provided
  13. # with the distribution.
  14. # * Neither the name of the zsh-syntax-highlighting contributors nor the names of its contributors
  15. # may be used to endorse or promote products derived from this software without specific prior
  16. # written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  19. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  20. # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  21. # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
  24. # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  25. # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. # -------------------------------------------------------------------------------------------------
  27. # -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
  28. # vim: ft=zsh sw=2 ts=2 et
  29. # -------------------------------------------------------------------------------------------------
  30. setopt NO_UNSET WARN_CREATE_GLOBAL
  31. # Required for add-zle-hook-widget.
  32. zmodload zsh/zle
  33. local -r root=${0:h:h}
  34. local -a anon_argv; anon_argv=("$@")
  35. (){
  36. set -- "${(@)anon_argv}"
  37. # Check an highlighter was given as argument.
  38. [[ -n "$1" ]] || {
  39. echo >&2 "Bail out! You must provide the name of a valid highlighter as argument."
  40. exit 2
  41. }
  42. # Check the highlighter is valid.
  43. [[ -f $root/highlighters/$1/$1-highlighter.zsh ]] || {
  44. echo >&2 "Bail out! Could not find highlighter ${(qq)1}."
  45. exit 2
  46. }
  47. # Check the highlighter has test data.
  48. [[ -d $root/highlighters/$1/test-data ]] || {
  49. echo >&2 "Bail out! Highlighter ${(qq)1} has no test data."
  50. exit 2
  51. }
  52. # Set up results_filter
  53. local results_filter
  54. if [[ ${QUIET-} == y ]]; then
  55. if type -w perl >/dev/null; then
  56. results_filter=$root/tests/tap-filter
  57. else
  58. echo >&2 "Bail out! quiet mode not supported: perl not found"; exit 2
  59. fi
  60. else
  61. results_filter=cat
  62. fi
  63. [[ -n $results_filter ]] || { echo >&2 "Bail out! BUG setting \$results_filter"; exit 2 }
  64. # Load the main script.
  65. # While here, test that it doesn't eat aliases.
  66. print > >($results_filter | $root/tests/tap-colorizer.zsh) -r -- "# global (driver) tests"
  67. print > >($results_filter | $root/tests/tap-colorizer.zsh) -r -- "1..1"
  68. alias -- +plus=plus
  69. alias -- _other=other
  70. local original_alias_dash_L_output="$(alias -L)"
  71. . $root/zsh-syntax-highlighting.zsh
  72. if [[ $original_alias_dash_L_output == $(alias -L) ]]; then
  73. print -r -- "ok 1 # 'alias -- +foo=bar' is preserved"
  74. else
  75. print -r -- "not ok 1 # 'alias -- +foo=bar' is preserved"
  76. exit 1
  77. fi > >($results_filter | $root/tests/tap-colorizer.zsh)
  78. # Overwrite _zsh_highlight_add_highlight so we get the key itself instead of the style
  79. _zsh_highlight_add_highlight()
  80. {
  81. region_highlight+=("$1 $2 $3")
  82. }
  83. # Activate the highlighter.
  84. ZSH_HIGHLIGHT_HIGHLIGHTERS=($1)
  85. # In zsh<5.3, 'typeset -p arrayvar' emits two lines, so we use this wrapper instead.
  86. typeset_p() {
  87. for 1 ; do
  88. if [[ ${(tP)1} == *array* ]]; then
  89. print -r -- "$1=( ${(@qqqqP)1} )"
  90. else
  91. print -r -- "$1=${(qqqqP)1}"
  92. fi
  93. done
  94. }
  95. # Escape # as ♯ and newline as ↵ they are illegal in the 'description' part of TAP output
  96. # The string to escape is «"$@"»; the result is returned in $REPLY.
  97. tap_escape() {
  98. local s="${(j. .)@}"
  99. REPLY="${${s//'#'/♯}//$'\n'/↵}"
  100. }
  101. # Runs a highlighting test
  102. # $1: data file
  103. run_test_internal() {
  104. local tests_tempdir="$1"; shift
  105. local srcdir="$PWD"
  106. builtin cd -q -- "$tests_tempdir" || { echo >&2 "Bail out! On ${(qq)1}: cd failed: $?"; return 1 }
  107. # Load the data and prepare checking it.
  108. local BUFFER CURSOR MARK PENDING PREBUFFER REGION_ACTIVE WIDGET REPLY skip_test fail_test unsorted=0
  109. local expected_mismatch
  110. local skip_mismatch
  111. local -a expected_region_highlight region_highlight
  112. local ARG="$1"
  113. local RETURN=""
  114. () {
  115. setopt localoptions
  116. # WARNING: The remainder of this anonymous function will run with the test's options in effect
  117. if { ! . "$srcdir"/"$ARG" } || (( $#fail_test )); then
  118. print -r -- "1..1"
  119. print -r -- "## ${ARG:t:r}"
  120. tap_escape $fail_test; fail_test=$REPLY
  121. print -r -- "not ok 1 - failed setup: $fail_test"
  122. return ${RETURN:=0}
  123. fi
  124. (( $#skip_test )) && {
  125. print -r -- "1..0 # SKIP $skip_test"
  126. print -r -- "## ${ARG:t:r}"
  127. return ${RETURN:=0}
  128. }
  129. # Check the data declares $PREBUFFER or $BUFFER.
  130. [[ -z $PREBUFFER && -z $BUFFER ]] && { echo >&2 "Bail out! On ${(qq)ARG}: Either 'PREBUFFER' or 'BUFFER' must be declared and non-blank"; return ${RETURN:=1}; }
  131. [[ $PREBUFFER == (''|*$'\n') ]] || { echo >&2 "Bail out! On ${(qq)ARG}: PREBUFFER=${(qqqq)PREBUFFER} doesn't end with a newline"; return ${RETURN:=1}; }
  132. # Set sane defaults for ZLE variables
  133. : ${CURSOR=$#BUFFER} ${PENDING=0} ${WIDGET=z-sy-h-test-harness-test-widget}
  134. # Process the data.
  135. _zsh_highlight
  136. }; [[ -z $RETURN ]] || return $RETURN
  137. unset ARG
  138. integer print_expected_and_actual=0
  139. if (( unsorted )); then
  140. region_highlight=("${(@n)region_highlight}")
  141. expected_region_highlight=("${(@n)expected_region_highlight}")
  142. fi
  143. # Print the plan line, and some comments for human readers
  144. echo "1..$(( $#expected_region_highlight + 1))"
  145. echo "## ${1:t:r}" # note: tests/edit-failed-tests looks for the "##" emitted by this line
  146. [[ -n $PREBUFFER ]] && printf '# %s\n' "$(typeset_p PREBUFFER)"
  147. [[ -n $BUFFER ]] && printf '# %s\n' "$(typeset_p BUFFER)"
  148. local i
  149. for ((i=1; i<=$#expected_region_highlight; i++)); do
  150. local -a expected_highlight_zone; expected_highlight_zone=( ${(z)expected_region_highlight[i]} )
  151. integer exp_start=$expected_highlight_zone[1] exp_end=$expected_highlight_zone[2]
  152. local todo=
  153. if (( $+expected_highlight_zone[4] )); then
  154. todo="# TODO $expected_highlight_zone[4]"
  155. skip_mismatch="cardinality check disabled whilst regular test points are expected to fail"
  156. fi
  157. if ! (( $+region_highlight[i] )); then
  158. print -r -- "not ok $i - unmatched expectation ($exp_start $exp_end $expected_highlight_zone[3])" \
  159. "${skip_mismatch:+"# TODO ${(qqq)skip_mismatch}"}"
  160. if [[ -z $skip_mismatch ]]; then (( ++print_expected_and_actual )); fi
  161. continue
  162. fi
  163. local -a highlight_zone; highlight_zone=( ${(z)region_highlight[i]} )
  164. integer start=$(( highlight_zone[1] + 1 )) end=$highlight_zone[2]
  165. local desc="[$start,$end] «${BUFFER[$start,$end]}»"
  166. tap_escape $desc; desc=$REPLY
  167. if
  168. [[ $start != $exp_start ]] ||
  169. [[ $end != $exp_end ]] ||
  170. [[ ${highlight_zone[3]%,} != ${expected_highlight_zone[3]} ]] # remove the comma that's before the memo field
  171. then
  172. print -r -- "not ok $i - $desc - expected ($exp_start $exp_end ${(qqq)expected_highlight_zone[3]}), observed ($start $end ${(qqq)highlight_zone[3]}). $todo"
  173. if [[ -z $todo ]]; then (( ++print_expected_and_actual )); fi
  174. else
  175. print -r -- "ok $i - $desc${todo:+ - }$todo"
  176. fi
  177. unset expected_highlight_zone
  178. unset exp_start exp_end
  179. unset todo
  180. unset highlight_zone
  181. unset start end
  182. unset desc
  183. done
  184. # If both $skip_mismatch and $expected_mismatch are set, that means the test
  185. # has some XFail test points, _and_ explicitly sets $expected_mismatch as
  186. # well. Explicit settings should have priority, so we ignore $skip_mismatch
  187. # if $expected_mismatch is set.
  188. if [[ -n $skip_mismatch && -z $expected_mismatch ]]; then
  189. tap_escape $skip_mismatch; skip_mismatch=$REPLY
  190. print "ok $i - cardinality check" "# SKIP $skip_mismatch"
  191. else
  192. local todo
  193. if [[ -n $expected_mismatch ]]; then
  194. tap_escape $expected_mismatch; expected_mismatch=$REPLY
  195. todo="# TODO $expected_mismatch"
  196. fi
  197. if (( $#expected_region_highlight == $#region_highlight )); then
  198. print -r -- "ok $i - cardinality check${todo:+ - }$todo"
  199. else
  200. local details
  201. details+="have $#expected_region_highlight expectations and $#region_highlight region_highlight entries: "
  202. details+="«$(typeset_p expected_region_highlight)» «$(typeset_p region_highlight)»"
  203. tap_escape $details; details=$REPLY
  204. print -r -- "not ok $i - cardinality check - $details${todo:+ - }$todo"
  205. if [[ -z $todo ]]; then (( ++print_expected_and_actual )); fi
  206. fi
  207. fi
  208. if (( print_expected_and_actual )); then
  209. () {
  210. local -a left_column right_column
  211. left_column=( "expected_region_highlight" "${(qq)expected_region_highlight[@]}" )
  212. right_column=( "region_highlight" "${(qq)region_highlight[@]}" )
  213. integer difference=$(( $#right_column - $#left_column ))
  214. repeat $difference do left_column+=(.); done
  215. paste \
  216. =(print -rC1 -- $left_column) \
  217. =(print -rC1 -- $right_column) \
  218. | if type column >/dev/null; then column -t -s $'\t'; else cat; fi \
  219. | sed 's/^/# /'
  220. }
  221. fi
  222. }
  223. # Run a single test file. The exit status is 1 if the test harness had
  224. # an error and 0 otherwise. The exit status does not depend on whether
  225. # test points succeeded or failed.
  226. run_test() {
  227. # Do not combine the declaration and initialization: «local x="$(false)"» does not set $?.
  228. local __tests_tempdir
  229. __tests_tempdir="$(mktemp -d)" && [[ -d $__tests_tempdir ]] || {
  230. echo >&2 "Bail out! mktemp failed"; return 1
  231. }
  232. typeset -r __tests_tempdir # don't allow tests to override the variable that we will 'rm -rf' later on
  233. {
  234. # Use a subshell to isolate tests from each other.
  235. # (So tests can alter global shell state using 'cd', 'hash', etc)
  236. {
  237. # These braces are so multios don't come into play.
  238. { (run_test_internal "$__tests_tempdir" "$@") 3>&1 >&2 2>&3 } | grep \^
  239. local ret=$pipestatus[1] stderr=$pipestatus[2]
  240. if (( ! stderr )); then
  241. # stdout will become stderr
  242. echo "Bail out! On ${(qq)1}: output on stderr"; return 1
  243. else
  244. return $ret
  245. fi
  246. } 3>&1 >&2 2>&3
  247. } always {
  248. rm -rf -- "$__tests_tempdir"
  249. }
  250. }
  251. # Process each test data file in test data directory.
  252. integer something_failed=0
  253. ZSH_HIGHLIGHT_STYLES=()
  254. local data_file
  255. for data_file in $root/highlighters/$1/test-data/*.zsh; do
  256. run_test "$data_file" | tee >($results_filter | $root/tests/tap-colorizer.zsh) | grep -v '^not ok.*# TODO' | grep -Eq '^not ok|^ok.*# TODO' && (( something_failed=1 ))
  257. (( $pipestatus[1] )) && exit 2
  258. done
  259. exit $something_failed
  260. }