mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 01:40:13 +00:00
213 lines
7.3 KiB
Bash
Executable File
213 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Copyright 2014 The Kubernetes Authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# This script verifies the following e2e test ownership policies
|
|
# - tests MUST start with [sig-foo]
|
|
# - tests SHOULD NOT have multiple [sig-foo] tags
|
|
# TODO: these two can be dropped if KubeDescribe is gone from codebase
|
|
# - tests MUST NOT have [k8s.io] in test names
|
|
# - tests MUST NOT use KubeDescribe
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
set -o pipefail
|
|
|
|
# This will canonicalize the path
|
|
KUBE_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd -P)
|
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
|
|
|
# Set REUSE_BUILD_OUTPUT=y to skip rebuilding dependencies if present
|
|
REUSE_BUILD_OUTPUT=${REUSE_BUILD_OUTPUT:-n}
|
|
# set VERBOSE_OUTPUT=y to output .jq files and shell commands
|
|
VERBOSE_OUTPUT=${VERBOSE_OUTPUT:-n}
|
|
|
|
if [[ ${VERBOSE_OUTPUT} =~ ^[yY]$ ]]; then
|
|
set -x
|
|
fi
|
|
|
|
pushd "${KUBE_ROOT}" > /dev/null
|
|
|
|
# Setup a tmpdir to hold generated scripts and results
|
|
tmpdir=$(mktemp -d -t verify-e2e-test-ownership.XXXX)
|
|
readonly tmpdir
|
|
trap 'rm -rf ${tmpdir}' EXIT
|
|
|
|
# input
|
|
spec_summaries="${KUBE_ROOT}/_output/specsummaries.json"
|
|
# output
|
|
results_json="${tmpdir}/results.json"
|
|
summary_json="${tmpdir}/summary.json"
|
|
failures_json="${tmpdir}/failures.json"
|
|
|
|
# rebuild dependencies if necessary
|
|
function ensure_dependencies() {
|
|
local -r ginkgo="${KUBE_ROOT}/_output/bin/ginkgo"
|
|
local -r e2e_test="${KUBE_ROOT}/_output/bin/e2e.test"
|
|
if ! { [ -f "${ginkgo}" ] && [[ "${REUSE_BUILD_OUTPUT}" =~ ^[yY]$ ]]; }; then
|
|
make ginkgo
|
|
fi
|
|
if ! { [ -f "${e2e_test}" ] && [[ "${REUSE_BUILD_OUTPUT}" =~ ^[yY]$ ]]; }; then
|
|
hack/make-rules/build.sh test/e2e/e2e.test
|
|
fi
|
|
if ! { [ -f "${spec_summaries}" ] && [[ "${REUSE_BUILD_OUTPUT}" =~ ^[yY]$ ]]; }; then
|
|
"${ginkgo}" --dry-run=true "${e2e_test}" -- --spec-dump "${spec_summaries}" > /dev/null
|
|
fi
|
|
}
|
|
|
|
# evaluate ginkgo spec summaries against e2e test ownership polices
|
|
# output to ${results_json}
|
|
function generate_results_json() {
|
|
readonly results_jq=${tmpdir}/results.jq
|
|
cat >"${results_jq}" <<EOS
|
|
[.[] | select( .LeafNodeType == "It") | . as { ContainerHierarchyTexts: \$text, ContainerHierarchyLocations: \$code, LeafNodeText: \$leafText, LeafNodeLocation: \$leafCode} | {
|
|
calls: ([ \$text | range(0;length) as \$i | {
|
|
sig: ((\$text[\$i] | match("\\\[(sig-[^\\\]]+)\\\]") | .captures[0].string) // "unknown"),
|
|
text: \$text[\$i],
|
|
# unused, but if we ever wanted to have policies based on other tags...
|
|
# tags: \$text[\$i] | [match("(\\\[[^\\\]]+\\\])"; "g").string],
|
|
line: \$code[\$i] | "\(.FileName):\(.LineNumber)"
|
|
}] + [{
|
|
sig: ((\$leafText | match("\\\[(sig-[^\\\]]+)\\\]") | .captures[0].string) // "unknown"),
|
|
text: \$leafText,
|
|
# unused, but if we ever wanted to have policies based on other tags...
|
|
# tags: \$leafText | [match("(\\\[[^\\\]]+\\\])"; "g").string],
|
|
line: \$leafCode | "\(.FileName):\(.LineNumber)"
|
|
}]),
|
|
} | {
|
|
owner: .calls[0].sig,
|
|
calls: .calls,
|
|
testname: .calls | map(.text) | join(" "),
|
|
policies: [(
|
|
.calls[0] |
|
|
{
|
|
fail: (.sig == "unknown"),
|
|
level: "FAIL",
|
|
category: "unowned_test",
|
|
reason: "must start with [sig-foo]",
|
|
found: .,
|
|
}
|
|
), (
|
|
.calls[1:] |
|
|
(map(select(.sig != "unknown")) // [] | {
|
|
fail: . | any,
|
|
level: "WARN",
|
|
category: "too_many_sigs",
|
|
reason: "should not have multiple [sig-foo] tags",
|
|
found: .,
|
|
})
|
|
)
|
|
]
|
|
}]
|
|
EOS
|
|
if [[ ${VERBOSE_OUTPUT} =~ ^[yY]$ ]]; then
|
|
echo "about to ${results_jq}..."
|
|
cat -n "${results_jq}"
|
|
echo
|
|
fi
|
|
<"${spec_summaries}" jq --slurp --from-file "${results_jq}" > "${results_json}"
|
|
}
|
|
|
|
# summarize e2e test policy results
|
|
# output to ${summary_json}
|
|
function generate_summary_json() {
|
|
summary_jq=${tmpdir}/summary.jq
|
|
cat >"${summary_jq}" <<EOS
|
|
. as \$results |
|
|
# for each policy category
|
|
reduce \$results[0].policies[] as \$p ({}; . + {
|
|
# add a convenience .policy field containing that policy's result
|
|
(\$p.category): \$results | map(. + {policy: .policies[] | select(.category == \$p.category)}) | {
|
|
level: \$p.level,
|
|
reason: \$p.reason,
|
|
passing: map(select(.policy.fail | not)) | length,
|
|
failing: map(select(.policy.fail)) | length,
|
|
testnames: map(select(.policy.fail) | .testname),
|
|
}
|
|
})
|
|
# add a meta policy based on whether any policy failed
|
|
+ {
|
|
all_policies: \$results | {
|
|
level: "WARN",
|
|
reason: "should pass all policies",
|
|
passing: map(select(.policies | map(.fail) | any | not)) | length,
|
|
failing: map(select(.policies | map(.fail) | any)) | length,
|
|
testnames: map(select(.policies | map(.fail) | any) | .testname),
|
|
}
|
|
}
|
|
# if a policy has no failing tests, change its log output to PASS
|
|
| with_entries(.value += { log: (if (.value.failing == 0) then "PASS" else .value.level end) })
|
|
# sort by policies with the most failing tests first
|
|
| to_entries | sort_by(.value.failing) | reverse | from_entries
|
|
EOS
|
|
if [[ ${VERBOSE_OUTPUT} =~ ^[yY]$ ]]; then
|
|
echo "about to run ${results_jq}..."
|
|
cat -n "${summary_jq}"
|
|
echo
|
|
fi
|
|
<"${results_json}" jq --from-file "${summary_jq}" > "${summary_json}"
|
|
}
|
|
|
|
# filter e2e policy tests results to tests that failed, with the policies they failed
|
|
# output to ${failures_json}
|
|
function generate_failures_json() {
|
|
local -r failures_jq="${tmpdir}/failures.jq"
|
|
cat >"${failures_jq}" <<EOS
|
|
.
|
|
# for each test
|
|
| map(
|
|
# filter down to failing policies; trim category, .reason is more verbose
|
|
.policies |= map(select(.fail) | del(.category))
|
|
# trim the full callstack, .found will contain the relevant call
|
|
| del(.calls)
|
|
)
|
|
# filter down to tests that have failed policies
|
|
| map(select(.policies | map (.fail) | any))
|
|
EOS
|
|
if [[ ${VERBOSE_OUTPUT} =~ ^[yY]$ ]]; then
|
|
echo "about to run ${failures_jq}..."
|
|
cat -n "${failures_jq}"
|
|
echo
|
|
fi
|
|
<"${results_json}" jq --from-file "${failures_jq}" > "${failures_json}"
|
|
}
|
|
|
|
function output_results_and_exit_if_failed() {
|
|
local -r total_tests=$(<"${spec_summaries}" wc -l | awk '{print $1}')
|
|
|
|
# output results to console
|
|
(
|
|
echo "run at datetime: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo "based on commit: $(git log -n1 --date=iso-strict --pretty='%h - %cd - %s')"
|
|
echo
|
|
<"${failures_json}" cat
|
|
printf "%4s: e2e tests %-40s: %-4d\n" "INFO" "in total" "${total_tests}"
|
|
<"${summary_json}" jq -r 'to_entries[].value |
|
|
"printf \"%4s: ..failing %-40s: %-4d\\n\" \"\(.log)\" \"\(.reason)\" \"\(.failing)\""' | sh
|
|
) | tee "${tmpdir}/output.txt"
|
|
# if we said "FAIL" in that output, we should fail
|
|
if <"${tmpdir}/output.txt" grep -q "^FAIL"; then
|
|
echo "FAIL"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_dependencies
|
|
generate_results_json
|
|
generate_failures_json
|
|
generate_summary_json
|
|
output_results_and_exit_if_failed
|
|
echo "PASS"
|