ci.sh 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. grep -Ev '(fuzz|testsuite|tomltestgen|gotoml-test-decoder|gotoml-test-encoder)' coverage.out.tmp > 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. go test '-bench=^Benchmark(Un)?[mM]arshal' -count=10 -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 &> /dev/null; then
  117. # GNU
  118. mktemp --suffix=-$1
  119. else
  120. # BSD
  121. mktemp -t $1
  122. fi
  123. }
  124. benchstathtml() {
  125. python3 - $1 <<'EOF'
  126. import sys
  127. lines = []
  128. stop = False
  129. with open(sys.argv[1]) as f:
  130. for line in f.readlines():
  131. line = line.strip()
  132. if line == "":
  133. stop = True
  134. if not stop:
  135. lines.append(line.split(','))
  136. results = []
  137. for line in reversed(lines[2:]):
  138. if len(line) < 8 or line[0] == "":
  139. continue
  140. v2 = float(line[1])
  141. results.append([
  142. line[0].replace("-32", ""),
  143. "%.1fx" % (float(line[3])/v2), # v1
  144. "%.1fx" % (float(line[7])/v2), # bs
  145. ])
  146. # move geomean to the end
  147. results.append(results[0])
  148. del results[0]
  149. def printtable(data):
  150. print("""
  151. <table>
  152. <thead>
  153. <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
  154. </thead>
  155. <tbody>""")
  156. for r in data:
  157. print(" <tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(*r))
  158. print(""" </tbody>
  159. </table>""")
  160. def match(x):
  161. return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0]
  162. above = [x for x in results if match(x)]
  163. below = [x for x in results if not match(x)]
  164. printtable(above)
  165. print("<details><summary>See more</summary>")
  166. print("""<p>The table above has the results of the most common use-cases. The table below
  167. contains the results of all benchmarks, including unrealistic ones. It is
  168. provided for completeness.</p>""")
  169. printtable(below)
  170. print('<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>')
  171. print("</details>")
  172. EOF
  173. }
  174. benchmark() {
  175. case "$1" in
  176. -d)
  177. shift
  178. target="${1?Need to provide a target branch argument}"
  179. old=`fmktemp ${target}`
  180. bench "${target}" "${old}"
  181. new=`fmktemp HEAD`
  182. bench HEAD "${new}"
  183. benchstat "${old}" "${new}"
  184. return 0
  185. ;;
  186. -a)
  187. shift
  188. v2stats=`fmktemp go-toml-v2`
  189. bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2"
  190. v1stats=`fmktemp go-toml-v1`
  191. bench HEAD "${v1stats}" "github.com/pelletier/go-toml"
  192. bsstats=`fmktemp bs-toml`
  193. bench HEAD "${bsstats}" "github.com/BurntSushi/toml"
  194. cp "${v2stats}" go-toml-v2.txt
  195. cp "${v1stats}" go-toml-v1.txt
  196. cp "${bsstats}" bs-toml.txt
  197. if [ "$1" = "-html" ]; then
  198. tmpcsv=`fmktemp csv`
  199. benchstat -format csv go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv
  200. benchstathtml $tmpcsv
  201. else
  202. benchstat go-toml-v2.txt go-toml-v1.txt bs-toml.txt
  203. fi
  204. rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt
  205. return $?
  206. esac
  207. bench "${1-HEAD}" `mktemp`
  208. }
  209. case "$1" in
  210. coverage) shift; coverage $@;;
  211. benchmark) shift; benchmark $@;;
  212. *) usage "bad argument $1";;
  213. esac