ci.sh 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #!/usr/bin/env bash
  2. stderr() {
  3. echo "$@" 1>&2
  4. }
  5. usage() {
  6. b=$(basename "$0")
  7. echo $b: ERROR: "$@" 1>&2
  8. cat 1>&2 <<EOF
  9. DESCRIPTION
  10. $(basename "$0") is the script to run continuous integration commands for
  11. go-toml on unix.
  12. Requires Go and Git to be available in the PATH. Expects to be ran from the
  13. root of go-toml's Git repository.
  14. USAGE
  15. $b COMMAND [OPTIONS...]
  16. COMMANDS
  17. benchmark [OPTIONS...] [BRANCH]
  18. Run benchmarks.
  19. ARGUMENTS
  20. BRANCH Optional. Defines which Git branch to use when running
  21. benchmarks.
  22. OPTIONS
  23. -d Compare benchmarks of HEAD with BRANCH using benchstats. In
  24. this form the BRANCH argument is required.
  25. -a Compare benchmarks of HEAD against go-toml v1 and
  26. BurntSushi/toml.
  27. -html When used with -a, emits the output as HTML, ready to be
  28. embedded in the README.
  29. coverage [OPTIONS...] [BRANCH]
  30. Generates code coverage.
  31. ARGUMENTS
  32. BRANCH Optional. Defines which Git branch to use when reporting
  33. coverage. Defaults to HEAD.
  34. OPTIONS
  35. -d Compare coverage of HEAD with the one of BRANCH. In this form,
  36. the BRANCH argument is required. Exit code is non-zero when
  37. coverage percentage decreased.
  38. EOF
  39. exit 1
  40. }
  41. cover() {
  42. branch="${1}"
  43. dir="$(mktemp -d)"
  44. stderr "Executing coverage for ${branch} at ${dir}"
  45. if [ "${branch}" = "HEAD" ]; then
  46. cp -r . "${dir}/"
  47. else
  48. git worktree add "$dir" "$branch"
  49. fi
  50. pushd "$dir"
  51. go test -covermode=atomic -coverpkg=./... -coverprofile=coverage.out.tmp ./...
  52. cat coverage.out.tmp | grep -v fuzz | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out
  53. go tool cover -func=coverage.out
  54. echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2
  55. popd
  56. if [ "${branch}" != "HEAD" ]; then
  57. git worktree remove --force "$dir"
  58. fi
  59. }
  60. coverage() {
  61. case "$1" in
  62. -d)
  63. shift
  64. target="${1?Need to provide a target branch argument}"
  65. output_dir="$(mktemp -d)"
  66. target_out="${output_dir}/target.txt"
  67. head_out="${output_dir}/head.txt"
  68. cover "${target}" > "${target_out}"
  69. cover "HEAD" > "${head_out}"
  70. cat "${target_out}"
  71. cat "${head_out}"
  72. echo ""
  73. target_pct="$(tail -n2 ${target_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%.*/\1/')"
  74. head_pct="$(tail -n2 ${head_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%/\1/')"
  75. echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%"
  76. delta_pct=$(echo "$head_pct - $target_pct" | bc -l)
  77. echo "Delta: ${delta_pct}"
  78. if [[ $delta_pct = \-* ]]; then
  79. echo "Regression!";
  80. target_diff="${output_dir}/target.diff.txt"
  81. head_diff="${output_dir}/head.diff.txt"
  82. cat "${target_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${target_diff}"
  83. cat "${head_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${head_diff}"
  84. diff --side-by-side --suppress-common-lines "${target_diff}" "${head_diff}"
  85. return 1
  86. fi
  87. return 0
  88. ;;
  89. esac
  90. cover "${1-HEAD}"
  91. }
  92. bench() {
  93. branch="${1}"
  94. out="${2}"
  95. replace="${3}"
  96. dir="$(mktemp -d)"
  97. stderr "Executing benchmark for ${branch} at ${dir}"
  98. if [ "${branch}" = "HEAD" ]; then
  99. cp -r . "${dir}/"
  100. else
  101. git worktree add "$dir" "$branch"
  102. fi
  103. pushd "$dir"
  104. if [ "${replace}" != "" ]; then
  105. find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2|${replace}|g" {} \;
  106. go get "${replace}"
  107. fi
  108. export GOMAXPROCS=2
  109. nice -n -19 taskset --cpu-list 0,1 go test '-bench=^Benchmark(Un)?[mM]arshal' -count=5 -run=Nothing ./... | tee "${out}"
  110. popd
  111. if [ "${branch}" != "HEAD" ]; then
  112. git worktree remove --force "$dir"
  113. fi
  114. }
  115. fmktemp() {
  116. if mktemp --version|grep GNU >/dev/null; then
  117. mktemp --suffix=-$1;
  118. else
  119. mktemp -t $1;
  120. fi
  121. }
  122. benchstathtml() {
  123. python3 - $1 <<'EOF'
  124. import sys
  125. lines = []
  126. stop = False
  127. with open(sys.argv[1]) as f:
  128. for line in f.readlines():
  129. line = line.strip()
  130. if line == "":
  131. stop = True
  132. if not stop:
  133. lines.append(line.split(','))
  134. results = []
  135. for line in reversed(lines[1:]):
  136. v2 = float(line[1])
  137. results.append([
  138. line[0].replace("-32", ""),
  139. "%.1fx" % (float(line[3])/v2), # v1
  140. "%.1fx" % (float(line[5])/v2), # bs
  141. ])
  142. # move geomean to the end
  143. results.append(results[0])
  144. del results[0]
  145. def printtable(data):
  146. print("""
  147. <table>
  148. <thead>
  149. <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
  150. </thead>
  151. <tbody>""")
  152. for r in data:
  153. print(" <tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(*r))
  154. print(""" </tbody>
  155. </table>""")
  156. def match(x):
  157. return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0]
  158. above = [x for x in results if match(x)]
  159. below = [x for x in results if not match(x)]
  160. printtable(above)
  161. print("<details><summary>See more</summary>")
  162. print("""<p>The table above has the results of the most common use-cases. The table below
  163. contains the results of all benchmarks, including unrealistic ones. It is
  164. provided for completeness.</p>""")
  165. printtable(below)
  166. print('<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>')
  167. print("</details>")
  168. EOF
  169. }
  170. benchmark() {
  171. case "$1" in
  172. -d)
  173. shift
  174. target="${1?Need to provide a target branch argument}"
  175. old=`fmktemp ${target}`
  176. bench "${target}" "${old}"
  177. new=`fmktemp HEAD`
  178. bench HEAD "${new}"
  179. benchstat "${old}" "${new}"
  180. return 0
  181. ;;
  182. -a)
  183. shift
  184. v2stats=`fmktemp go-toml-v2`
  185. bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2"
  186. v1stats=`fmktemp go-toml-v1`
  187. bench HEAD "${v1stats}" "github.com/pelletier/go-toml"
  188. bsstats=`fmktemp bs-toml`
  189. bench HEAD "${bsstats}" "github.com/BurntSushi/toml"
  190. cp "${v2stats}" go-toml-v2.txt
  191. cp "${v1stats}" go-toml-v1.txt
  192. cp "${bsstats}" bs-toml.txt
  193. if [ "$1" = "-html" ]; then
  194. tmpcsv=`fmktemp csv`
  195. benchstat -csv -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv
  196. benchstathtml $tmpcsv
  197. else
  198. benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt
  199. fi
  200. rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt
  201. return $?
  202. esac
  203. bench "${1-HEAD}" `mktemp`
  204. }
  205. case "$1" in
  206. coverage) shift; coverage $@;;
  207. benchmark) shift; benchmark $@;;
  208. *) usage "bad argument $1";;
  209. esac