1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
|
#!/bin/bash -eu
export LANG=C.UTF-8
export LANGUAGE=en
this_file="$0"
COVERMODE=${COVERMODE:-atomic}
COVERAGE_SUFFIX=${GO_BUILD_TAGS:-notags}
COVERAGE_OUT=${COVERAGE_OUT:-.coverage/coverage-$COVERAGE_SUFFIX.cov}
CHANGED_FILES=${CHANGED_FILES:-""}
TIMEOUT=${TIMEOUT:-15}
AUTO_INSTALL=${AUTO_INSTALL:-1} # Automatically install supported tool dependencies (default: install)
IGNORE_MISSING=${IGNORE_MISSING:-0} # Do not error on missing tool dependencies (default: error)
tool_version() {
local tool=$1
case "$tool" in
go)
$tool version
;;
shellcheck)
$tool --version | grep version
;;
golangci-lint) ;& # fallthrough
python3) ;& # fallthrough
pytest-3) ;& # fallthrough
clang-format)
$tool --version
;;
modernize)
$tool -V=full
;;
*)
return 1
;;
esac
}
tool_install() {
local tool=$1
case "$tool" in
golangci-lint)
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2 >/dev/null
;;
modernize)
go install golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.18.1 >/dev/null
;;
go) ;& # fallthrough
shellcheck) ;& # fallthrough
python3) ;& # fallthrough
pytest-3) ;& # fallthrough
clang-format) ;;
*)
return 1
;;
esac
}
tool_configure() {
local tool=$1
case "$tool" in
go)
# If GOPATH is set in the shell environment, the path will be reflected
# in $(go env GOPATH). If no shell path was set, Go will default the internal
# GOPATH to $HOME/go. Note that GOPATH may contain a colon delimited set
# of paths, so in order to run any binary from any of the installed GOPATHs
# we must add all the possible bin locations.
GOBINS=$(go env GOPATH | sed 's|:|/bin:|g' | sed 's|.*|\0/bin|g')
export PATH="$PATH:$GOBINS"
# when *not* running inside github, and go-1.18 is available, use it
if [ "${CI:-}" != "true" ] && [ -e "/usr/lib/go-1.18/bin" ]; then
export PATH=/usr/lib/go-1.18/bin:"${PATH}"
echo "WARNING: Forcing the use of Go 1.18 (preferred version for this project)" >&2
fi
;;
shellcheck) ;& # fallthrough
golangci-lint) ;& # fallthrough
modernize) ;& # fallthrough
python3) ;& # fallthrough
pytest-3) ;& # fallthrough
clang-format) ;;
*)
return 1
;;
esac
}
install_conf_show_tool() {
local tool=$1
local auto_install=$2
local ignore_missing=$3
echo ">> Depends on \"$tool\""
if [ "$auto_install" -eq 1 ] && ! command -v "$tool" >/dev/null; then # ignore-tool
if ! tool_install "$tool"; then
echo "WARNING: Cannot install tool $tool" >&2
fi
fi
if ! executable=$(command -v "$tool"); then # ignore-tool
if [ "$ignore_missing" -ne 1 ]; then
echo "ERROR: Tool $tool is not available" >&2
return 1
else
echo "WARNING: Tool $tool is not available" >&2
fi
else
if ! tool_configure "$tool"; then
echo "ERROR: Cannot configure $tool" >&2
return 1
fi
if ! version=$(tool_version "$tool"); then
echo "ERROR: Cannot retrieve version of $tool" >&2
return 1
else
echo "Version: $version"
echo "Executable: $executable"
fi
fi
}
verify_tools() {
local auto_install=$1
local ignore_missing=$2
# find tool dependencies but ignore those marked with `# check-tools-ignore`
tools=$(grep -v '# ignore-tool' "$this_file" | grep -oP 'command -v \K[\w-]+' | sort -u)
# check go first that includes setting up go paths required to find or install
# other go based tools, error early when missing
install_conf_show_tool "go" "$auto_install" "0"
# check other tools
for tool in $tools; do
install_conf_show_tool "$tool" "$auto_install" "$ignore_missing"
done
}
missing_interface_spread_test() {
snap_yaml="tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml"
core_snap_yaml="tests/main/interfaces-many-snap-provided/test-snapd-policy-app-provider-core/meta/snap.yaml"
classic_snap_yaml="tests/main/interfaces-many-snap-provided/test-snapd-policy-app-provider-classic/meta/snap.yaml"
for iface in $(go run ./tests/lib/list-interfaces.go); do
search="plugs: \\[ $iface \\]"
case "$iface" in
bool-file | gpio | pwm | dsp | netlink-driver | hidraw | i2c | iio | serial-port | spi | confdb)
# skip gadget provided interfaces for now
continue
;;
cuda-driver-libs | egl-driver-libs)
# skip interfaces with plug side only in rootfs for now
continue
;;
dbus | content)
search="interface: $iface"
;;
autopilot)
search='plugs: \[ autopilot-introspection \]'
;;
esac
# check if a standalone test already exists and that it at least
# connects and disconnects the interface
dedicated_test=$(find tests/main/ -maxdepth 1 -name "interfaces-$iface")
if [ -n "$dedicated_test" ]; then
if grep -q "$search" "$snap_yaml"; then
echo "Dedicated test '$dedicated_test' found for '$iface'." >&2
echo "Please remove '$iface' from '$snap_yaml'." >&2
exit 1
fi
# dedicated test already exists, skip high-level test check below
continue
fi
# check if high-level minimal test exists for interface
if ! grep -q "$search" "$snap_yaml"; then
echo "Missing high-level test for interface '$iface'. Please add to:" >&2
echo "* $snap_yaml" >&2
echo "* $core_snap_yaml (if needed)" >&2
echo "* $classic_snap_yaml (if needed)" >&2
exit 1
fi
done
}
CURRENT_TRAP="true"
EXIT_CODE=99
store_exit_code() {
EXIT_CODE=$?
}
exit_with_exit_code() {
exit $EXIT_CODE
}
addtrap() {
CURRENT_TRAP="$CURRENT_TRAP ; $1"
# shellcheck disable=SC2064
trap "store_exit_code; $CURRENT_TRAP ; exit_with_exit_code" EXIT
}
endmsg() {
if [ $EXIT_CODE -eq 0 ]; then
p="success.txt"
m="All good, what could possibly go wrong."
else
p="failure.txt"
m="Crushing failure and despair."
fi
echo
if [ -t 1 ] && [ -z "$STATIC" ]; then
cat "data/$p"
else
echo "$m"
fi
}
addtrap endmsg
short=
STATIC=
UNIT=
case "${1:-all}" in
all)
STATIC=1
UNIT=1
;;
--static)
STATIC=1
;;
--unit)
UNIT=1
;;
--short-unit)
UNIT=1
short="-short"
;;
*)
echo "Wrong flag ${1}. To run a single suite use --static, --unit."
exit 1
;;
esac
echo "> Verify tool dependencies"
verify_tools "$AUTO_INSTALL" "$IGNORE_MISSING"
if [ "$STATIC" = 1 ]; then
./get-deps.sh
echo "> Running static tests"
echo ">> Checking docs"
./mdlint.py ./*.md ./**/*.md
# XXX: remove once we can use an action, see workflows/test.yaml for
# details why we still use this script
if [ -n "${GITHUB_PULL_REQUEST_TITLE:-}" ]; then
echo ">> Checking pull request summary"
./check-pr-title.py "${GITHUB_PULL_REQUEST_TITLE}"
else
echo ">> Skipping pull request summary check: not a pull request"
fi
echo ">> [Git] Checking commit author/committer name for unicode"
./check-commit-email.py
if [ -z "${SKIP_GOFMT:-}" ]; then
echo ">> [Go] Checking go formatting"
fmt=""
for dir in $(go list -f '{{.Dir}}' ./...); do
s="$(gofmt -s -d "$dir" || true)"
if [ -n "$s" ]; then
fmt="$s\\n$fmt"
fi
done
if [ -n "$fmt" ]; then
echo "Formatting wrong in following files:"
# shellcheck disable=SC2001
echo "$fmt" | sed -e 's/\\n/\n/g'
exit 1
fi
else
echo ">> [Go] Skipping go formatting check"
fi
echo '>> [Go] Checking for usages of http.Status*'
got=""
for dir in $(go list -f '{{.Dir}}' ./...); do
s="$(grep -nP 'http\.Status(?!Text)' "$dir"/*.go || true)"
if [ -n "$s" ]; then
got="$s\\n$got"
fi
done
if [ -n "$got" ]; then
echo 'Usages of http.Status*, we prefer the numeric values directly:' >&2
echo "$got" >&2
exit 1
fi
echo ">> [Go] Checking for direct usages of math/rand"
got=""
for dir in $(go list -f '{{.Dir}}' ./...); do
# shellcheck disable=SC2063
s="$(grep -nP --exclude '*_test.go' --exclude 'randutil/*.go' math/rand "$dir"/*.go || true)"
if [ -n "$s" ]; then
got="$s\\n$got"
fi
done
if [ -n "$got" ]; then
echo 'Direct usages of math/rand, we prefer randutil:' >&2
echo "$got" >&2
exit 1
fi
echo ">> [Go] Checking for usages of deprecated io/ioutil"
got=""
for dir in $(go list -f '{{.Dir}}' ./...); do
# shellcheck disable=SC2063
s="$(grep -nP io/ioutil "$dir"/*.go || true)"
if [ -n "$s" ]; then
got="$s\\n$got"
fi
done
if [ -n "$got" ]; then
echo 'Found usages of deprecated io/ioutil, please use "io" or "os" equivalents' >&2
echo "$got" >&2
exit 1
fi
if command -v shellcheck >/dev/null; then
exclude_tools_path=tests/lib/external/snapd-testing-tools
echo ">> [Bash] Checking shell scripts"
if [ -n "$CHANGED_FILES" ]; then
echo "Checking just the changed bash files"
echo "Changed files: $CHANGED_FILES"
# shellcheck disable=SC2086
INITIAL_FILES="$(file -N $CHANGED_FILES | awk -F": " '$2~/shell.script/{print $1}')"
else
echo "Checking all the bash files"
INITIAL_FILES="$( (git ls-files -z 2>/dev/null || find . \( -name .git -o -name vendor -o -name c-vendor \) -prune -o -print0) | xargs -0 file -N | awk -F": " '$2~/shell.script/{print $1}')"
fi
echo "Filtering files"
FILTERED_FILES=
for file in $INITIAL_FILES; do
if ! echo "$file" | grep -q "$exclude_tools_path"; then
FILTERED_FILES="$FILTERED_FILES $file"
fi
done
if [ -n "$FILTERED_FILES" ]; then
echo "Running shellcheck for files: $FILTERED_FILES"
# shellcheck disable=SC2086
shellcheck -x $FILTERED_FILES
else
echo "Skipping shellcheck, no files to check"
fi
regexp='GOPATH(?!%%:\*)(?!:)[^= ]*/'
if grep -qPr --exclude HACKING.md --exclude 'Makefile.*' --exclude-dir .git --exclude-dir vendor "$regexp"; then
echo "Using GOPATH as if it were a single entry and not a list:" >&2
grep -PHrn -C1 --color=auto --exclude HACKING.md --exclude 'Makefile.*' --exclude-dir .git --exclude-dir vendor "$regexp"
echo "Use GOHOME, or {GOPATH%%:*}, instead." >&2
exit 1
fi
unset regexp
# also run spread-shellcheck
FILTERED_FILES="spread.yaml"
if [ -n "$CHANGED_FILES" ]; then
# shellcheck disable=SC2086
for changed_file in $CHANGED_FILES; do
if [[ $changed_file == */task.yaml ]]; then
FILTERED_FILES="$FILTERED_FILES $(pwd)/$changed_file"
fi
done
else
FILTERED_FILES="$FILTERED_FILES tests"
fi
# XXX: exclude core20-preseed test as its environment block confuses shellcheck, and it's not possible to disable shellcheck there.
# shellcheck disable=SC2086
./tests/lib/external/snapd-testing-tools/utils/spread-shellcheck $FILTERED_FILES --exclude "$exclude_tools_path" --exclude "tests/nested/manual/core20-preseed"
else
echo ">> [Bash] Skipping shellcheck check"
fi
echo ">> [Spread] Checking all interfaces have a spread test"
missing_interface_spread_test
echo ">> [Spread] Checking for incorrect multiline strings in spread tests"
badmultiline=$(find tests -name 'task.yaml' -print0 -o -name 'spread.yaml' -print0 |
xargs -0 grep -R -n -E '(restore*|prepare*|execute|debug):\s*$' || true)
if [ -n "$badmultiline" ]; then
echo "Incorrect multiline strings at the following locations:" >&2
echo "$badmultiline" >&2
exit 1
fi
echo ">> [Spread] Checking for potentially incorrect use of MATCH -v"
badMATCH=$(find tests -name 'task.yaml' -print0 -o -name 'spread.yaml' -print0 |
xargs -0 grep -R -n -E 'MATCH +-v' || true)
if [ -n "$badMATCH" ]; then
echo "Potentially incorrect use of MATCH -v at the following locations:" >&2
echo "$badMATCH" >&2
exit 1
fi
# FIXME: re-add staticcheck with a matching version for the used go-version
if [ -z "${SKIP_TESTS_FORMAT_CHECK:-}" ] || [ "$SKIP_TESTS_FORMAT_CHECK" = 0 ]; then
echo ">> [Spread] Checking tests formatting"
CHANGED_TESTS=""
FILTERED_TESTS=""
EXCLUDE_PATH=tests/lib/external/snapd-testing-tools
if [ -n "$CHANGED_FILES" ]; then
# shellcheck disable=SC2086
for changed_file in $CHANGED_FILES; do
if [[ $changed_file == */task.yaml ]]; then
CHANGED_TESTS="$CHANGED_TESTS $changed_file"
fi
done
fi
# shellcheck disable=SC2086
for test in $CHANGED_TESTS; do
if ! echo "$test" | grep -q "$EXCLUDE_PATH"; then
if [ -z "$FILTERED_TESTS" ]; then
FILTERED_TESTS="$test"
else
FILTERED_TESTS="$FILTERED_TESTS $test"
fi
fi
done
if [ -n "$FILTERED_TESTS" ]; then
# shellcheck disable=SC2086
./tests/lib/external/snapd-testing-tools/utils/check-test-format --tests $FILTERED_TESTS
fi
else
echo ">> [Spread] Skipping tests formatting check"
fi
echo ">> [Go] Checking for usages of !=, == or Equals with ErrNoState"
if got=$(grep -n -R -E "(\!=|==|Equals,) (state\.)?ErrNoState" --include=*.go); then
echo "Don't use equality checks with ErrNoState, use errors.Is() instead" >&2
echo "$got" >&2
exit 1
fi
if command -v modernize; then
echo ">> [Go] Checking modernize"
modernize -test ./...
else
echo ">> [Go] Skipping modernize check"
fi
if gcil=$(command -v golangci-lint) || [ -z "${SKIP_GOLANGCI_LINT:-}" ]; then
echo ">> [Go] Checking golangci-lint"
if echo "$gcil" | grep -q '/snap/bin/'; then
# golangci-lint was installed from the snap
if snap refresh --list | grep -q golangci-lint; then
echo "WARNING: your golangci-lint snap is out of date. The CI will install a fresh version, which may differ from yours." >&2
fi
if ! snap list golangci-lint | grep -q latest; then
echo "WARNING: your golangci-lint snap is not installed from the latest/* channel." >&2
fi
fi
# only linters can be disabled, formatters require configuration change
disable=""
if [ -n "${SKIP_INEFFASSIGN:-}" ]; then
disable+=" ineffassign"
fi
if [ -n "${SKIP_MISSPELL:-}" ]; then
disable+=" misspell"
fi
if [ -n "${SKIP_NAKEDRET:-}" ]; then
disable+=" nakedret"
fi
enabled=$(golangci-lint linters | awk '
/^Enabled by your configuration linters:/ { in_section=1; next }
/^Disabled by your configuration linters:/ { in_section=0 }
in_section && NF {
sub(/:.*/, "", $1)
linters = linters ? linters "," $1 : $1
}
END { print linters }')
echo ">> [Go] Enabled golangci-lint linters: $enabled"
if [ -n "${disable:-}" ]; then
disable="$(echo "$disable" | sed 's/^ *//' | tr ' ' ',')"
echo ">> [Go] Disabling the following golangci-lint linters on request: $disable"
golangci-lint --path-prefix= run --disable "$disable"
else
# don't run with --new-from-rev as the diff might not be enough to tell
# the change introduces problems (e.g., removing the last call to a function)
golangci-lint --path-prefix= run
fi
else
echo ">> [Go] Skipping golangci-lint check"
fi
echo ">> [C] Checking C source code formatting"
if command -v clang-format >/dev/null; then
current=$(pwd)
cd cmd/
./autogen.sh
if ! make fmt-check; then
echo "C files are not formatted correctly, run 'make fmt'" >&2
exit 1
fi
cd "$current"
else
echo ">> [C] Skipping C source code formatting check"
fi
echo ">> [Go] Checking that all ensure helper methods have been registered"
missing=0
while IFS=' ' read -r manager func; do
if ! grep -rq "swfeats.RegisterEnsure(\"$manager\",.*\"$func\")" .; then
echo "Missing ensure function registration. Add the following to the relevant file: swfeats.RegEnsure(\"$manager\", \"$func\")"
missing=1
fi
done < <(grep -r '.Trace("ensure"' overlord | sed -n 's/.*\.Trace("ensure",.*"manager",.*"\([^"]*\)",.*"func",.*"\([^"]*\)").*/\1 \2/p')
if [ $missing -eq 1 ]; then
exit 1
fi
fi
if [ "$UNIT" = 1 ]; then
./get-deps.sh
echo "> Running unit tests"
tags=
race=
timeout="${TIMEOUT}m"
if [ -n "${GO_BUILD_TAGS:-}" ]; then
echo "Using build tags: $GO_BUILD_TAGS"
tags="-tags $GO_BUILD_TAGS"
fi
if [ -n "${GO_TEST_RACE:-}" ]; then
echo "Using go test -race"
race="-race"
timeout="$((TIMEOUT * 2))m"
fi
echo Building
# shellcheck disable=SC2086
go build -v $tags $race github.com/snapcore/snapd/...
# tests
echo ">> [Go] Running go unit tests from $PWD"
if [ "$short" = "-short" ] || [ -n "${SKIP_COVERAGE:-}" ]; then
echo ">> [Go] Skipping coverage"
# shellcheck disable=SC2046,SC2086
GOTRACEBACK=1 go test ./... $tags $race $short -timeout $timeout
else
coverage="-coverprofile=$COVERAGE_OUT -covermode=$COVERMODE"
echo ">> [Go] Checking coverage with params: $coverage"
mkdir -p "$(dirname "$COVERAGE_OUT")"
# shellcheck disable=SC2046,SC2086
GOTRACEBACK=1 go test ./... $tags $race -timeout $timeout $coverage
fi
# python unit test for mountinfo.query and version-compare
if command -v python3; then
echo ">> [Python] Running python3 unit tests for mountinfo.query and version-compare"
python3 ./tests/lib/tools/mountinfo.query --run-unit-tests
python3 ./tests/lib/tools/version-compare --run-unit-tests
else
echo ">> [Python] Skipping python3 unit tests for mountinfo.query and version-compare"
fi
if command -v pytest-3; then
echo ">> [Python] Running pytest-3 test for release-tools"
PYTHONDONTWRITEBYTECODE=1 pytest-3 ./release-tools
else
echo ">> [Python] Skipping pytest-3 test for release-tools"
fi
fi
if [ -n "${SKIP_UNCLEAN:-}" ]; then
echo "> [Git] Skipping leftover files in git tree check"
else
echo "> [Git] Checking for leftover files in git tree"
UNCLEAN="$(git status -s | grep '^??')" || true
if [ -n "$UNCLEAN" ]; then
cat >&2 <<EOF
There are files left in the git tree after the tests:
$UNCLEAN
EOF
exit 1
fi
fi
if [ -n "${SKIP_DIRTY_CHECK:-}" ]; then
echo "> [Git] Skipping dirty check"
exit 0
fi
echo "> [Git] Checking for dirty build tree"
if git describe --always --dirty | grep -q dirty; then
echo "Build tree is dirty" >&2
git diff >&2
exit 1
fi
|