280 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/usr/bin/env bash
 | 
						|
 | 
						|
 | 
						|
stderr() {
 | 
						|
    echo "$@" 1>&2
 | 
						|
}
 | 
						|
 | 
						|
usage() {
 | 
						|
    b=$(basename "$0")
 | 
						|
    echo $b: ERROR: "$@" 1>&2
 | 
						|
 | 
						|
    cat 1>&2 <<EOF
 | 
						|
 | 
						|
DESCRIPTION
 | 
						|
 | 
						|
    $(basename "$0") is the script to run continuous integration commands for
 | 
						|
    go-toml on unix.
 | 
						|
 | 
						|
    Requires Go and Git to be available in the PATH. Expects to be ran from the
 | 
						|
    root of go-toml's Git repository.
 | 
						|
 | 
						|
USAGE
 | 
						|
 | 
						|
    $b COMMAND [OPTIONS...]
 | 
						|
 | 
						|
COMMANDS
 | 
						|
 | 
						|
benchmark [OPTIONS...] [BRANCH]
 | 
						|
 | 
						|
    Run benchmarks.
 | 
						|
 | 
						|
    ARGUMENTS
 | 
						|
 | 
						|
        BRANCH Optional. Defines which Git branch to use when running
 | 
						|
               benchmarks.
 | 
						|
 | 
						|
    OPTIONS
 | 
						|
 | 
						|
        -d      Compare benchmarks of HEAD with BRANCH using benchstats. In
 | 
						|
                this form the BRANCH argument is required.
 | 
						|
 | 
						|
        -a      Compare benchmarks of HEAD against go-toml v1 and
 | 
						|
                BurntSushi/toml.
 | 
						|
 | 
						|
        -html   When used with -a, emits the output as HTML, ready to be
 | 
						|
                embedded in the README.
 | 
						|
 | 
						|
coverage [OPTIONS...] [BRANCH]
 | 
						|
 | 
						|
    Generates code coverage.
 | 
						|
 | 
						|
    ARGUMENTS
 | 
						|
 | 
						|
        BRANCH  Optional. Defines which Git branch to use when reporting
 | 
						|
                coverage. Defaults to HEAD.
 | 
						|
 | 
						|
    OPTIONS
 | 
						|
 | 
						|
        -d      Compare coverage of HEAD with the one of BRANCH. In this form,
 | 
						|
                the BRANCH argument is required. Exit code is non-zero when
 | 
						|
                coverage percentage decreased.
 | 
						|
EOF
 | 
						|
    exit 1
 | 
						|
}
 | 
						|
 | 
						|
cover() {
 | 
						|
    branch="${1}"
 | 
						|
    dir="$(mktemp -d)"
 | 
						|
 | 
						|
    stderr "Executing coverage for ${branch} at ${dir}"
 | 
						|
 | 
						|
    if [ "${branch}" = "HEAD" ]; then
 | 
						|
	    cp -r . "${dir}/"
 | 
						|
    else
 | 
						|
	    git worktree add "$dir" "$branch"
 | 
						|
    fi
 | 
						|
 | 
						|
    pushd "$dir"
 | 
						|
    go test -covermode=atomic  -coverpkg=./... -coverprofile=coverage.out.tmp ./...
 | 
						|
    cat coverage.out.tmp | grep -v fuzz | grep -v testsuite | grep -v tomltestgen | grep -v gotoml-test-decoder > coverage.out
 | 
						|
    go tool cover -func=coverage.out
 | 
						|
    popd
 | 
						|
 | 
						|
    if [ "${branch}" != "HEAD" ]; then
 | 
						|
	    git worktree remove --force "$dir"
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
coverage() {
 | 
						|
    case "$1" in
 | 
						|
	-d)
 | 
						|
	    shift
 | 
						|
	    target="${1?Need to provide a target branch argument}"
 | 
						|
 | 
						|
	    output_dir="$(mktemp -d)"
 | 
						|
	    target_out="${output_dir}/target.txt"
 | 
						|
	    head_out="${output_dir}/head.txt"
 | 
						|
	    
 | 
						|
	    cover "${target}" > "${target_out}"
 | 
						|
	    cover "HEAD" > "${head_out}"
 | 
						|
 | 
						|
	    cat "${target_out}"
 | 
						|
	    cat "${head_out}"
 | 
						|
 | 
						|
	    echo ""
 | 
						|
 | 
						|
	    target_pct="$(tail -n2 ${target_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%.*/\1/')"
 | 
						|
	    head_pct="$(tail -n2 ${head_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%/\1/')"
 | 
						|
	    echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%"
 | 
						|
 | 
						|
	    delta_pct=$(echo "$head_pct - $target_pct" | bc -l)
 | 
						|
	    echo "Delta: ${delta_pct}"
 | 
						|
 | 
						|
	    if [[ $delta_pct = \-* ]]; then
 | 
						|
		    echo "Regression!";
 | 
						|
 | 
						|
            target_diff="${output_dir}/target.diff.txt"
 | 
						|
            head_diff="${output_dir}/head.diff.txt"
 | 
						|
            cat "${target_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${target_diff}"
 | 
						|
            cat "${head_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${head_diff}"
 | 
						|
 | 
						|
            diff --side-by-side --suppress-common-lines "${target_diff}" "${head_diff}"
 | 
						|
		    return 1
 | 
						|
	    fi
 | 
						|
	    return 0
 | 
						|
	    ;;
 | 
						|
    esac
 | 
						|
 | 
						|
    cover "${1-HEAD}"
 | 
						|
}
 | 
						|
 | 
						|
bench() {
 | 
						|
    branch="${1}"
 | 
						|
    out="${2}"
 | 
						|
    replace="${3}"
 | 
						|
    dir="$(mktemp -d)"
 | 
						|
 | 
						|
    stderr "Executing benchmark for ${branch} at ${dir}"
 | 
						|
 | 
						|
    if [ "${branch}" = "HEAD" ]; then
 | 
						|
    	cp -r . "${dir}/"
 | 
						|
    else
 | 
						|
	    git worktree add "$dir" "$branch"
 | 
						|
    fi
 | 
						|
 | 
						|
    pushd "$dir"
 | 
						|
 | 
						|
    if [ "${replace}" != "" ]; then
 | 
						|
        find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2|${replace}|g" {} \;
 | 
						|
        go get "${replace}"
 | 
						|
    fi
 | 
						|
 | 
						|
    export GOMAXPROCS=2
 | 
						|
    nice -n -19 taskset --cpu-list 0,1 go test '-bench=^Benchmark(Un)?[mM]arshal' -count=5 -run=Nothing ./... | tee "${out}"
 | 
						|
    popd
 | 
						|
 | 
						|
    if [ "${branch}" != "HEAD" ]; then
 | 
						|
	    git worktree remove --force "$dir"
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
fmktemp() {
 | 
						|
    if mktemp --version|grep GNU >/dev/null; then
 | 
						|
        mktemp --suffix=-$1;
 | 
						|
    else
 | 
						|
        mktemp -t $1;
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
benchstathtml() {
 | 
						|
python3 - $1 <<'EOF'
 | 
						|
import sys
 | 
						|
 | 
						|
lines = []
 | 
						|
stop = False
 | 
						|
 | 
						|
with open(sys.argv[1]) as f:
 | 
						|
    for line in f.readlines():
 | 
						|
        line = line.strip()
 | 
						|
        if line == "":
 | 
						|
            stop = True
 | 
						|
        if not stop:
 | 
						|
            lines.append(line.split(','))
 | 
						|
 | 
						|
results = []
 | 
						|
for line in reversed(lines[1:]):
 | 
						|
    v2 = float(line[1])
 | 
						|
    results.append([
 | 
						|
        line[0].replace("-32", ""),
 | 
						|
        "%.1fx" % (float(line[3])/v2),  # v1
 | 
						|
        "%.1fx" % (float(line[5])/v2),  # bs
 | 
						|
    ])
 | 
						|
# move geomean to the end
 | 
						|
results.append(results[0])
 | 
						|
del results[0]
 | 
						|
 | 
						|
 | 
						|
def printtable(data):
 | 
						|
    print("""
 | 
						|
<table>
 | 
						|
    <thead>
 | 
						|
        <tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
 | 
						|
    </thead>
 | 
						|
    <tbody>""")
 | 
						|
 | 
						|
    for r in data:
 | 
						|
        print("        <tr><td>{}</td><td>{}</td><td>{}</td></tr>".format(*r))
 | 
						|
 | 
						|
    print("""     </tbody>
 | 
						|
</table>""")
 | 
						|
 | 
						|
 | 
						|
def match(x):
 | 
						|
    return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0]
 | 
						|
 | 
						|
above = [x for x in results if match(x)]
 | 
						|
below = [x for x in results if not match(x)]
 | 
						|
 | 
						|
printtable(above)
 | 
						|
print("<details><summary>See more</summary>")
 | 
						|
print("""<p>The table above has the results of the most common use-cases. The table below
 | 
						|
contains the results of all benchmarks, including unrealistic ones. It is
 | 
						|
provided for completeness.</p>""")
 | 
						|
printtable(below)
 | 
						|
print('<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>')
 | 
						|
print("</details>")
 | 
						|
 | 
						|
EOF
 | 
						|
}
 | 
						|
 | 
						|
benchmark() {
 | 
						|
    case "$1" in
 | 
						|
    -d)
 | 
						|
        shift
 | 
						|
     	target="${1?Need to provide a target branch argument}"
 | 
						|
 | 
						|
        old=`fmktemp ${target}`
 | 
						|
        bench "${target}" "${old}"
 | 
						|
 | 
						|
        new=`fmktemp HEAD`
 | 
						|
        bench HEAD "${new}"
 | 
						|
 | 
						|
        benchstat "${old}" "${new}"
 | 
						|
        return 0
 | 
						|
        ;;
 | 
						|
    -a)
 | 
						|
        shift
 | 
						|
 | 
						|
        v2stats=`fmktemp go-toml-v2`
 | 
						|
        bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2"
 | 
						|
        v1stats=`fmktemp go-toml-v1`
 | 
						|
        bench HEAD "${v1stats}" "github.com/pelletier/go-toml"
 | 
						|
        bsstats=`fmktemp bs-toml`
 | 
						|
        bench HEAD "${bsstats}" "github.com/BurntSushi/toml"
 | 
						|
 | 
						|
        cp "${v2stats}" go-toml-v2.txt
 | 
						|
        cp "${v1stats}" go-toml-v1.txt
 | 
						|
        cp "${bsstats}" bs-toml.txt
 | 
						|
 | 
						|
        if [ "$1" = "-html" ]; then
 | 
						|
            tmpcsv=`fmktemp csv`
 | 
						|
            benchstat -csv -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv
 | 
						|
            benchstathtml $tmpcsv
 | 
						|
        else
 | 
						|
            benchstat -geomean go-toml-v2.txt go-toml-v1.txt bs-toml.txt
 | 
						|
        fi
 | 
						|
 | 
						|
        rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt
 | 
						|
        return $?
 | 
						|
    esac
 | 
						|
 | 
						|
    bench "${1-HEAD}" `mktemp`
 | 
						|
}
 | 
						|
 | 
						|
case "$1" in
 | 
						|
    coverage) shift; coverage $@;;
 | 
						|
    benchmark) shift; benchmark $@;;
 | 
						|
    *) usage "bad argument $1";;
 | 
						|
esac
 |