Skip to content

Commit

Permalink
Merge pull request #6466 from jandubois/foreach-k3s
Browse files Browse the repository at this point in the history
foreach_k3s_version support in BATS
  • Loading branch information
mook-as authored Feb 13, 2024
2 parents 4314cd0 + d9da021 commit 62807cb
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 26 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ snakize
softmmu
someothername
somepaththatshouldnevereverexist
sourced
splatform
splunk
ssd
Expand Down
5 changes: 5 additions & 0 deletions bats/scripts/bats-lint.pl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
print "$ARGV:$.: Don't define $1(); define local_$1() instead\n";
$problems++;
}

if (/\b run \b .* \b load_var \b/x) {
print "$ARGV:$.: Running load_var in a subshell (via run) does not work\n";
$problems++;
}
}

# The semver comparison functions take arguments that are valid semver;
Expand Down
40 changes: 39 additions & 1 deletion bats/tests/helpers/defaults.bash
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ using_docker() {
########################################################################
: "${RD_RANCHER_IMAGE_TAG:=v2.7.0}"

########################################################################
# Defaults to true, except in the helper unit tests, which default to false
: "${RD_INFO:=}"

########################################################################
: "${RD_CAPTURE_LOGS:=false}"

Expand Down Expand Up @@ -170,9 +174,14 @@ using_ramdisk() {
}

########################################################################
# Use RD_PROTECTED_DOT in profile settings for WSL distro names
# Use RD_PROTECTED_DOT in profile settings for WSL distro names.
: "${RD_PROTECTED_DOT:=·}"

########################################################################
# RD_KUBELET_TIMEOUT specifies the number of minutes wait_for_kubelet()
# waits before it times out.
: "${RD_KUBELET_TIMEOUT:=10}"

########################################################################
# RD_LOCATION specifies the location where Rancher Desktop is installed
# system: default system-wide install location shared for all users
Expand All @@ -188,3 +197,32 @@ validate_enum RD_LOCATION system user dist dev ""
using_dev_mode() {
[ "$RD_LOCATION" = "dev" ]
}

########################################################################
# RD_K3S_VERSIONS specifies a list of k3s versions. foreach_k3s_version()
# can dynamically register a test to run once for each version in the
# list. Only versions between RD_K3S_MIN and RD_K3S_MAX (inclusively)
# will be used.
#
# Special values:
# "all" will fetch the list of all k3s releases from GitHub
# "latest" will fetch the list of latest versions from the release channel

: "${RD_K3S_MIN:=1.0.0}"
: "${RD_K3S_MAX:=1.99.0}"
: "${RD_K3S_VERSIONS:=$RD_KUBERNETES_PREV_VERSION}"

validate_semver RD_K3S_MIN
validate_semver RD_K3S_MAX

# Cache expansion of RD_K3S_VERSIONS special versions because they are slow to compute
if ! load_var RD_K3S_VERSIONS; then
# Fetch "all" or "latest" versions
get_k3s_versions

for k3s_version in ${RD_K3S_VERSIONS}; do
validate_semver k3s_version
done

save_var RD_K3S_VERSIONS
fi
4 changes: 4 additions & 0 deletions bats/tests/helpers/info.bash
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ show_info() { # @test
rm -rf "$PATH_BATS_LOGS"
fi

if is_false "${RD_INFO:-true}"; then
return
fi

(
local format="# %-25s %s\n"

Expand Down
24 changes: 22 additions & 2 deletions bats/tests/helpers/kubernetes.bash
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
wait_for_kubelet() {
local desired_version="${1:-$RD_KUBERNETES_PREV_VERSION}"
local timeout="$(($(date +%s) + 10 * 60))"
local desired_version=${1:-$RD_KUBERNETES_PREV_VERSION}
local timeout=$(($(date +%s) + RD_KUBELET_TIMEOUT * 60))
trace "waiting for Kubernetes ${desired_version} to be available"
while true; do
until kubectl get --raw /readyz &>/dev/null; do
Expand All @@ -26,3 +26,23 @@ wait_for_kubelet() {
sleep 1
done
}

get_k3s_versions() {
if [[ $RD_K3S_VERSIONS == "all" ]]; then
# filter out duplicates; RD only supports the latest of +k3s1, +k3s2, etc.
RD_K3S_VERSIONS=$(
gh api /repos/k3s-io/k3s/releases --paginate --jq '.[].tag_name' |
grep -E '^v1\.[0-9]+\.[0-9]+\+k3s[0-9]+$' |
sed -E 's/v([^+]+)\+.*/\1/' |
sort --unique --version-sort
)
fi

if [[ $RD_K3S_VERSIONS == "latest" ]]; then
RD_K3S_VERSIONS=$(
curl --silent --fail https://update.k3s.io/v1-release/channels |
jq --raw-output '.data[] | select(.name | test("^v[0-9]+\\.[0-9]+$")).latest' |
sed -E 's/v([^+]+)\+.*/\1/'
)
fi
}
7 changes: 4 additions & 3 deletions bats/tests/helpers/load.bash
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ source "$PATH_BATS_HELPERS/os.bash"
source "$PATH_BATS_HELPERS/utils.bash"
source "$PATH_BATS_HELPERS/snapshots.bash"

# kubernetes.bash has no load-time dependencies
source "$PATH_BATS_HELPERS/kubernetes.bash"

# defaults.bash uses is_windows() from os.bash and
# validate_enum() and is_true() from utils.bash.
# get_k3s_versions from kubernetes.bash.
source "$PATH_BATS_HELPERS/defaults.bash"

# images.bash uses using_ghcr_images() from defaults.bash
Expand All @@ -67,9 +71,6 @@ source "$PATH_BATS_HELPERS/profile.bash"
# rdctl from commands.bash, and jq_output from utils.bash
source "$PATH_BATS_HELPERS/vm.bash"

# kubernetes.bash has no load-time dependencies
source "$PATH_BATS_HELPERS/kubernetes.bash"

# Use Linux utilities (like jq) on WSL
export PATH="$PATH_BATS_ROOT/bin/${OS/windows/linux}:$PATH"

Expand Down
100 changes: 93 additions & 7 deletions bats/tests/helpers/utils.bash
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ validate_enum() {
fatal "$var=${!var} is not a valid setting; select from [$*]"
}

# Ensure that the variable contains a valid semver (major.minor.path) version, e.g.
# `validate_semver RD_K3S_MAX`
validate_semver() {
local var=$1
if ! semver_is_valid "${!var}"; then
fatal "$var=${!var} is not a valid semver value (major.minor.patch)"
fi
}

assert_nothing() {
# This is a no-op, used to show that run() has been used to continue the
# test even when the command failed, but the failure itself is ignored.
Expand Down Expand Up @@ -144,29 +153,41 @@ semver_is_valid() {
[[ ! $1 =~ $'\n' ]] && grep -q -E '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' <<<"$1"
}

# Compare 2 regular major.minor.patch versions as returned by the semver function above
# All semver comparison functions will return false when called without any argument
# and return true when called with just a single argument.

# semver_eq checks that all specified arguments are equal to each other.
# (semver_eq and semver_neq don't really depend on the arguments being versions).
# `A = B = C`
semver_eq() {
[ "$1" == "$2" ]
[[ $# -gt 0 ]] && [[ $(printf "%s\n" "$@" | sort --unique | wc -l) -eq 1 ]]
}

# semver_neq checks that all arguments are unique. `semver_neq A B C` is not the same as
# `A ≠ B ≠ C` because semver_neq will also return a failure if `A = C`.
# `(A ≠ B) & (A ≠ C) & (B ≠ C)`
semver_neq() {
! semver_eq "$@"
[[ $# -gt 0 ]] && printf "%s\n" "$@" | sort | sort --check=silent --unique
}

# `A ≤ B ≤ C`
semver_lte() {
printf "%s\n" "$1" "$2" | sort --check=silent --version-sort
[[ $# -gt 0 ]] && printf "%s\n" "$@" | sort --check=silent --version-sort
}

# `A < B < C`
semver_lt() {
semver_lte "$@" && semver_neq "$@"
[[ $# -gt 0 ]] && semver_lte "$@" && semver_neq "$@"
}

# `A ≥ B ≥ C`
semver_gte() {
! semver_lt "$@"
[[ $# -gt 0 ]] && printf "%s\n" "$@" | sort --check=silent --reverse --version-sort
}

# `A > B > C`
semver_gt() {
! semver_lte "$@"
[[ $# -gt 0 ]] && semver_gte "$@" && semver_neq "$@"
}

########################################################################
Expand Down Expand Up @@ -340,3 +361,68 @@ skip_unless_host_ip() {
skip "Test requires a routable host ip address"
fi
}

########################################################################

# Register one or more test commands for each k3s version in RD_K3S_VERSIONS.
# Versions can be filtered by RD_K3S_MIN and RD_K3S_MAX.
foreach_k3s_version() {
local k3s_version
for k3s_version in $RD_K3S_VERSIONS; do
if semver_lte "$RD_K3S_MIN" "$k3s_version" "$RD_K3S_MAX"; then
local cmd
for cmd in "$@"; do
bats_test_function --description "$cmd $k3s_version" -- _foreach_k3s_version "$k3s_version" "$cmd"
done
fi
done
}

_foreach_k3s_version() {
local RD_KUBERNETES_PREV_VERSION=$1
"$2"
}

########################################################################

_var_filename() {
# Can't use BATS_SUITE_TMPDIR because it is unset outside of @test functions
echo "${BATS_RUN_TMPDIR}/var_$1"
}

# Save env variables on disk, so they can be reloaded in different tests.
# This is mostly useful if calculating the setting takes a long time.
# Returns false if any variable was unbound, but will continue saving remaining variables.
# `save_var VAR1 VAR2`
save_var() {
local res=0
local var
for var in "$@"; do
# Using [[ -v $var ]] requires bash 4.2 but macOS only ships with 3.2
if [ -n "${!var+exists}" ]; then
printf "%s=%q\n" "$var" "${!var}" >"$(_var_filename "$var")"
else
res=1
fi
done
return $res
}

# Load env variables saved by `save_var`. Returns an error if any of the variables
# had not been saved, but will continue to try to load the remaining variables.
# `load_var VAR1 VAR2`
load_var() {
local res=0
local var
for var in "$@"; do
local file
file=$(_var_filename "$var")
if [[ -r $file ]]; then
# shellcheck disable=SC1090 # Can't follow non-constant source
source "$file"
else
res=1
fi
done
return $res
}
Loading

0 comments on commit 62807cb

Please sign in to comment.