Files
2025-05-21 14:25:48 +02:00

150 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
{
echo "shard.sh: Shard tests for parallel execution in CI."
echo "usage: $0 [-h] -n <shard> -m <total_shards> [-d <directory>] [-s]"
echo
echo " -h: Show this help message."
echo " -n: The shard number (1-indexed)."
echo " -m: The total number of shards. Must be equal to or greater than -n."
echo " -N: The shard in shard notation (n/m), corresponding to -n and -m."
echo " -d: The directory to find packages with tests in."
echo " Can be a path or a /... style pattern."
echo " Can be repeated to specify multiple directories."
echo " Can be - to read from stdin."
echo " Default: ./..."
echo " -s: Split final package list with spaces rather than newlines."
} >&2
}
is_int() {
# we can't just return the result of the regex match shellcheck is unhappy...
if [[ "$1" =~ ^[0-9]+$ ]]; then
return 0
else
return 1
fi
}
n=0
m=0
dirs=()
s=0
while getopts ":hn:m:d:sN:" opt; do
case $opt in
h)
usage
exit 0
;;
n)
if ! is_int "$OPTARG"; then
echo "Error: -n must be an integer." >&2
usage
exit 1
fi
n=$OPTARG
;;
m)
if ! is_int "$OPTARG"; then
echo "Error: -m must be an integer." >&2
usage
exit 1
fi
m=$OPTARG
;;
N)
if [[ "$OPTARG" =~ ^([0-9]+)/([0-9]+)$ ]]; then
n="${BASH_REMATCH[1]}"
m="${BASH_REMATCH[2]}"
else
echo "Error: -N must be in the form n/m." >&2
usage
exit 1
fi
;;
d)
dirs+=("$OPTARG")
;;
s)
s=1
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $n -eq 0 || $m -eq 0 ]]; then
echo "Error: -n and -m are required." >&2
usage
exit 1
fi
if [[ $n -lt 1 || $m -lt 1 ]]; then
echo "Error: -n and -m must be greater than 0." >&2
usage
exit 1
fi
if [[ $n -gt $m ]]; then
echo "Error: -n must be less than or equal to -m." >&2
usage
exit 1
fi
if [[ ${#dirs[@]} -eq 0 ]]; then
readarray -t dirs <<< "$(find . -type f -name 'go.mod' -exec dirname '{}' ';' | awk '{ print $1 "/..."; }')"
fi
# If dirs is just ("-"), read from stdin instead.
if [[ ${#dirs[@]} -eq 1 && "${dirs[0]}" == "-" ]]; then
dirs=()
while IFS= read -r line; do
dirs+=("$line")
done
fi
if [[ $n -eq 1 && $m -eq 1 ]]; then
# If there is only one shard, just return all packages.
for pkg in "${dirs[@]}"; do
if [ $s -eq 1 ]; then
printf "%s " "$pkg"
else
printf "%s\n" "$pkg"
fi
done
exit 0
fi
readarray -t PACKAGES <<< "$(go list -f '{{.Dir}}' -e "${dirs[@]}")"
if [[ ${#PACKAGES[@]} -eq 0 ]]; then
echo "No packages found in directories: ${dirs[*]}" >&2
exit 1
fi
for i in "${!PACKAGES[@]}"; do
if [ -z "$(find "${PACKAGES[i]}" -maxdepth 1 -type f -name '*_test.go' -printf '.' -quit)" ]; then
# There are no test files in this package.
unset 'PACKAGES[i]'
fi
done
for i in "${!PACKAGES[@]}"; do
if (( (i % m) + 1 != n )); then
unset 'PACKAGES[i]'
fi
done
for pkg in "${PACKAGES[@]}"; do
if [ $s -eq 1 ]; then
printf "%s " "$pkg"
else
printf "%s\n" "$pkg"
fi
done