diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6630fd50 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# See https://EditorConfig.org + +# This is a top-most EditorConfig file. +root = true + +# Ignore any "vendor" directories. +[**/vendor/**] +ignore = true + +# Bash, bats, and sh files use 4 spaces for indentation. +[*.{bash,bats,sh}] +indent_style = space +indent_size = 4 + +# This is for shell scripts with shebang but no extensions. +# Not yet supported by editorconfig[1], only by shfmt. +# https://github.com/editorconfig/editorconfig/issues/404 +[[shell]] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3fd0bffd..b3234e71 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -58,6 +58,26 @@ jobs: sudo apt-get install -y cppcheck libglib2.0-dev pkg-config make cppcheck + shfmt: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Check shell script formatting + run: make shfmt + + shellcheck: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Lint shell scripts + run: make shellcheck + meson-build: runs-on: ubuntu-latest timeout-minutes: 10 @@ -78,6 +98,8 @@ jobs: - cppcheck - lint - meson-build + - shellcheck + - shfmt runs-on: ubuntu-latest steps: - run: echo "All jobs completed" diff --git a/.rpmbuild/prepare.sh b/.rpmbuild/prepare.sh index 339da39e..4293971b 100644 --- a/.rpmbuild/prepare.sh +++ b/.rpmbuild/prepare.sh @@ -16,7 +16,7 @@ sed "s,#COMMIT#,${COMMIT},; s,#SHORTCOMMIT#,${COMMIT_SHORT},; s,#COMMITNUM#,${COMMIT_NUM},; s,#COMMITDATE#,${COMMIT_DATE}," \ - contrib/spec/conmon.spec.in > contrib/spec/conmon.spec + contrib/spec/conmon.spec.in >contrib/spec/conmon.spec mkdir build/ git archive --prefix "conmon-${COMMIT_SHORT}/" --format "tar.gz" HEAD -o "build/conmon-${COMMIT_SHORT}.tar.gz" diff --git a/Makefile b/Makefile index 2181f2b8..709af446 100644 --- a/Makefile +++ b/Makefile @@ -182,6 +182,40 @@ cppcheck: --suppress=missingIncludeSystem --suppress=checkersReport \ $(CPPCHECK_FLAGS) src/ +# Formatting for shell scripts. Uses a pinned image so that everyone, CI +# included, gets identical output; run localshfmt to use a locally installed shfmt. +# The list of files to format is shfmt's own, and covers .bats files as well +# as shell scripts with a shebang and no extension. +CONTAINER_ENGINE ?= podman +SHFMT_IMAGE ?= docker.io/mvdan/shfmt:v3.13.1 + +.PHONY: shfmt +shfmt: + $(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \ + --rm -v $(CURDIR):/src:z -w /src \ + $(SHFMT_IMAGE) -d -w . + +.PHONY: localshfmt +localshfmt: + shfmt -d -w . + +# Shell script linting, run the same way as shfmt above. .bats files have no +# shebang, hence -s bash. +SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0 +# The last entry is a shell script with a shebang and no extension, which +# git ls-files cannot match by pattern. +SHELL_SRC := $(shell git ls-files '*.bash' '*.bats' '*.sh') hack/github-actions-setup + +.PHONY: shellcheck +shellcheck: + $(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \ + --rm -v $(CURDIR):/src:z -w /src \ + $(SHELLCHECK_IMAGE) -s bash $(SHELL_SRC) + +.PHONY: localshellcheck +localshellcheck: + shellcheck -s bash $(SHELL_SRC) + .PHONY: fmt fmt: git ls-files -z \*.c \*.h | xargs -0 clang-format -i diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh deleted file mode 100755 index 322be3bd..00000000 --- a/hack/get_ci_vm.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash - -# -# For help and usage information, simply execute the script w/o any arguments. -# -# This script is intended to be run by Red Hat podman developers who need -# to debug problems specifically related to Cirrus-CI automated testing. -# It requires that you have been granted prior access to create VMs in -# google-cloud. For non-Red Hat contributors, VMs are available as-needed, -# with supervision upon request. - -set -e - -SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}") -SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH") -REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../") - -# Help detect if we were called by get_ci_vm container -GET_CI_VM="${GET_CI_VM:-0}" -in_get_ci_vm() { - if ((GET_CI_VM==0)); then - echo "Error: $1 is not intended for use in this context" - exit 2 - fi -} - -# get_ci_vm APIv1 container entrypoint calls into this script -# to obtain required repo. specific configuration options. -if [[ "$1" == "--config" ]]; then - in_get_ci_vm "$1" - cat < /dev/stderr - source ./contrib/cirrus/lib.sh - echo "+ Running environment setup" > /dev/stderr - ./contrib/cirrus/setup_environment.sh -else - # Create and access VM for specified Cirrus-CI task - mkdir -p $HOME/.config/gcloud/ssh - podman run -it --rm \ - --tz=local \ - -e NAME="$USER" \ - -e SRCDIR=/src \ - -e GCLOUD_ZONE="$GCLOUD_ZONE" \ - -e DEBUG="${DEBUG:-0}" \ - -v $REPO_DIRPATH:/src:O \ - -v $HOME/.config/gcloud:/root/.config/gcloud:z \ - -v $HOME/.config/gcloud/ssh:/root/.ssh:z \ - quay.io/libpod/get_ci_vm:latest "$@" -fi diff --git a/hack/github-actions-setup b/hack/github-actions-setup index 9e251bdf..d0d59e3e 100755 --- a/hack/github-actions-setup +++ b/hack/github-actions-setup @@ -124,6 +124,7 @@ apt_get() { } install_packages() { + # shellcheck source=/dev/null . /etc/os-release CRIU_REPO="https://download.opensuse.org/repositories/devel:/tools:/criu/xUbuntu_$VERSION_ID" diff --git a/test/01-basic.bats b/test/01-basic.bats index 5bd33052..da522611 100644 --- a/test/01-basic.bats +++ b/test/01-basic.bats @@ -171,4 +171,4 @@ teardown() { --log-path "k8s-file:$LOG_PATH" --log-path "$invalid_log_driver:$LOG_PATH" assert_failure assert_output_contains "No such log driver $invalid_log_driver" -} \ No newline at end of file +} diff --git a/test/02-ctr-logs.bats b/test/02-ctr-logs.bats index 9843752c..88f07052 100644 --- a/test/02-ctr-logs.bats +++ b/test/02-ctr-logs.bats @@ -153,7 +153,7 @@ run_conmon_with_log_opts() { @test "ctr logs: journald partial message" { # Print a message longer than the conmon buffer. # It should split it into multiple partial messages. - setup_container_env "printf '%*s' "65535" | tr ' ' '#'" + setup_container_env "printf '%*s' 65535 '' | tr ' ' '#'" run_conmon_with_default_args \ --log-path "journald:" @@ -164,7 +164,7 @@ run_conmon_with_log_opts() { @test "ctr logs: k8s partial message" { # Print a message longer than the conmon buffer. # It should split it into multiple partial messages. - setup_container_env "printf '%*s' "65535" | tr ' ' '#'" + setup_container_env "printf '%*s' 65535 '' | tr ' ' '#'" run_conmon_with_default_args \ --log-path "k8s-file:$LOG_PATH" diff --git a/test/03-k8s-log-rotation.bats b/test/03-k8s-log-rotation.bats index 114c3342..3d967678 100644 --- a/test/03-k8s-log-rotation.bats +++ b/test/03-k8s-log-rotation.bats @@ -59,10 +59,10 @@ run_conmon_k8s_file() { # Test k8s log format handling local k8s_content='2023-07-23T18:00:00.000000000Z stdout F Test log message' - echo "$k8s_content" > "$LOG_PATH" + echo "$k8s_content" >"$LOG_PATH" # Verify content preservation local content content=$(<"$LOG_PATH") [ "$content" = "$k8s_content" ] -} \ No newline at end of file +} diff --git a/test/04-runtime.bats b/test/04-runtime.bats index 65db6215..8714e30a 100644 --- a/test/04-runtime.bats +++ b/test/04-runtime.bats @@ -18,7 +18,7 @@ teardown() { # Check that log file was created [ -f "$LOG_PATH" ] run cat "$LOG_PATH" - assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the log" + assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the log" } @test "runtime: container execution with different log drivers" { @@ -26,7 +26,7 @@ teardown() { run_conmon_with_default_args --log-path "journald:" run journalctl --user CONTAINER_ID_FULL="$CTR_ID" - assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the journald" + assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the journald" } @test "runtime: container execution with multiple log drivers" { @@ -37,10 +37,10 @@ teardown() { [ -f "$LOG_PATH" ] run cat "$LOG_PATH" - assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the log" + assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the log" run journalctl --user CONTAINER_ID_FULL="$CTR_ID" - assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the journald" + assert "${output}" =~ "hello from ubi10" "'hello from ubi10' found in the journald" } @test "runtime: container with log size limit" { @@ -54,7 +54,7 @@ teardown() { [ -f "$LOG_PATH" ] run cat "$LOG_PATH" - assert "${output}" !~ "hello from ubi10 11" "'hello from ubi10 11' not in the logs" + assert "${output}" !~ "hello from ubi10 11" "'hello from ubi10 11' not in the logs" } @test "runtime: invalid runtime binary should fail" { @@ -87,7 +87,7 @@ teardown() { # Check that the pid is sent to the sync pipe. wait_for_syncpipe_output 1 - run cat $TEST_TMPDIR/syncpipe-output + run cat "$TEST_TMPDIR/syncpipe-output" CONTAINER_PID=$(cat "$CONTAINER_PIDFILE") assert_json "${output}" =~ "\"pid\": $CONTAINER_PID" } @@ -114,13 +114,13 @@ teardown() { # Give conmon some time to run the runtime and fail. sleep 1 - assert_file_exists $CONMON_PID_FILE + assert_file_exists "$CONMON_PID_FILE" CONMON_PID=$(cat "$CONMON_PID_FILE") wait_for_conmon_exit "$CONMON_PID" # Check that the error is sent to the sync pipe. wait_for_syncpipe_output 1 - run cat $TEST_TMPDIR/syncpipe-output + run cat "$TEST_TMPDIR/syncpipe-output" assert_json "${output}" =~ "\"pid\": -1" assert_json "${output}" =~ "\"message\":" assert_json "${output}" =~ "runc create failed" diff --git a/test/05-oom-detection.bats b/test/05-oom-detection.bats index cd501a14..18c83ed6 100644 --- a/test/05-oom-detection.bats +++ b/test/05-oom-detection.bats @@ -26,7 +26,7 @@ create_mock_memory_events() { local oom_kill_count="$2" local events_file="$MOCK_CGROUP_PATH/memory.events" - cat > "$events_file" << EOF + cat >"$events_file" < "$events_file" << EOF + cat >"$events_file" </dev/null 2>&1 + if ! timeout 60 podman --conmon "$conmon_path" exec "$container_id" true; then + timeout 60 podman --conmon "$conmon_path" rm -f "$container_id" >/dev/null 2>&1 echo "FAIL: true command should succeed" return 1 fi # Test 2: Failure case - this would fail with the regression - if timeout 60 podman --conmon $conmon_path exec "$container_id" false; then - timeout 60 podman --conmon $conmon_path rm -f "$container_id" >/dev/null 2>&1 + if timeout 60 podman --conmon "$conmon_path" exec "$container_id" false; then + timeout 60 podman --conmon "$conmon_path" rm -f "$container_id" >/dev/null 2>&1 echo "FAIL: false command should fail (regression detected!)" echo "This indicates the fc0a342 regression where all exec commands return 0" return 1 fi # Test 3: Custom exit code - this would return 0 with the regression - if timeout 60 podman --conmon $conmon_path exec "$container_id" sh -c 'exit 42'; then - timeout 60 podman --conmon $conmon_path rm -f "$container_id" >/dev/null 2>&1 + if timeout 60 podman --conmon "$conmon_path" exec "$container_id" sh -c 'exit 42'; then + timeout 60 podman --conmon "$conmon_path" rm -f "$container_id" >/dev/null 2>&1 echo "FAIL: 'exit 42' should fail with code 42 (regression detected!)" echo "This indicates the fc0a342 regression where all exec commands return 0" return 1 fi # Clean up - timeout 60 podman --conmon $conmon_path rm -f "$container_id" >/dev/null 2>&1 + timeout 60 podman --conmon "$conmon_path" rm -f "$container_id" >/dev/null 2>&1 echo "Integration test passed: exec exit codes work correctly" -} \ No newline at end of file +} diff --git a/test/06-log-management.bats b/test/06-log-management.bats index 9f267c41..f123f09a 100644 --- a/test/06-log-management.bats +++ b/test/06-log-management.bats @@ -51,7 +51,7 @@ run_conmon_k8s_log() { run_conmon_k8s_log --log-size-max 1024 assert_success [ -f "$LOG_PATH" ] - [ ! -f "$LOG_PATH.1" ] # No backup files in truncation mode + [ ! -f "$LOG_PATH.1" ] # No backup files in truncation mode } @test "log management: should enable rotation with proper flags" { @@ -66,7 +66,7 @@ run_conmon_k8s_log() { # Test k8s log format preservation local k8s_entry='2023-07-23T18:00:00.000000000Z stdout F Test log message' - echo "$k8s_entry" > "$LOG_PATH" + echo "$k8s_entry" >"$LOG_PATH" local content content=$(<"$LOG_PATH") diff --git a/test/06-priority-parsing.bats b/test/06-priority-parsing.bats index 0a47ccdd..102dbb86 100644 --- a/test/06-priority-parsing.bats +++ b/test/06-priority-parsing.bats @@ -15,7 +15,7 @@ teardown() { run_conmon_journald() { local extra_args=("$@") run_conmon --cid "$CTR_ID" --cuuid "$CTR_ID" --runtime "$RUNTIME_BINARY" \ - --log-path "journald:" --bundle "$BUNDLE_PATH" "${extra_args[@]}" + --log-path "journald:" --bundle "$BUNDLE_PATH" "${extra_args[@]}" } # Helper function to create a test script with priority messages @@ -23,7 +23,7 @@ create_test_script() { local script_name="$1" local script_content="$2" - echo "$script_content" > "$ROOTFS/$script_name" + echo "$script_content" >"$ROOTFS/$script_name" chmod +x "$ROOTFS/$script_name" } @@ -33,12 +33,13 @@ update_container_args() { # Use jq to update the config.json with new args, fallback to manual edit if jq not available if command -v jq >/dev/null 2>&1; then - jq ".process.args = [\"$script_path\"]" "$BUNDLE_PATH/config.json" > "$BUNDLE_PATH/config.json.tmp" && \ - mv "$BUNDLE_PATH/config.json.tmp" "$BUNDLE_PATH/config.json" + jq ".process.args = [\"$script_path\"]" "$BUNDLE_PATH/config.json" >"$BUNDLE_PATH/config.json.tmp" && + mv "$BUNDLE_PATH/config.json.tmp" "$BUNDLE_PATH/config.json" else # Fallback: recreate config with new args - local temp_config=$(mktemp) - sed "s|\"args\": \[.*\]|\"args\": [\"$script_path\"]|" "$BUNDLE_PATH/config.json" > "$temp_config" + local temp_config + temp_config=$(mktemp) + sed "s|\"args\": \[.*\]|\"args\": [\"$script_path\"]|" "$BUNDLE_PATH/config.json" >"$temp_config" mv "$temp_config" "$BUNDLE_PATH/config.json" fi } @@ -64,7 +65,7 @@ run_priority_test() { if [ "$verify_journal" = "true" ] && [ -f "$CONMON_PID_FILE" ]; then local conmon_pid conmon_pid=$(cat "$CONMON_PID_FILE") - sleep 1 # Give journald time to process + sleep 1 # Give journald time to process local journal_output journal_output=$(get_conmon_journal_output "$conmon_pid") @@ -75,6 +76,18 @@ run_priority_test() { fi } +# Helper function to skip test if journald is not available +skip_if_no_journald() { + if ! command -v journalctl >/dev/null 2>&1; then + skip "journalctl not available - skipping journald test" + fi + + # Also check if systemd logging is compiled in + if ! $CONMON_BINARY --help 2>&1 | grep -q "journald"; then + skip "conmon not compiled with journald support" + fi +} + @test "priority parsing: valid priority prefixes are parsed correctly" { skip_if_no_journald @@ -164,7 +177,9 @@ echo ""' @test "priority parsing: all valid priority levels 0-7" { skip_if_no_journald - # Create script that tests all valid priority levels + # Create script that tests all valid priority levels. The body runs in + # the container, so $i must not be expanded here. + # shellcheck disable=SC2016 create_test_script "test_all_priorities.sh" '#!/bin/bash for i in {0..7}; do echo "<$i>Priority level $i message" @@ -212,13 +227,13 @@ echo "<6>Info message"' # Test with both k8s-file and journald logging if command -v journalctl >/dev/null 2>&1; then run_conmon --cid "$CTR_ID" --cuuid "$CTR_ID" --runtime "$RUNTIME_BINARY" \ - --bundle "$BUNDLE_PATH" --log-path "$LOG_PATH" --log-path "journald:" --terminal + --bundle "$BUNDLE_PATH" --log-path "$LOG_PATH" --log-path "journald:" --terminal assert_success [ -f "$LOG_PATH" ] else # Just test k8s-file if journald not available run_conmon --cid "$CTR_ID" --cuuid "$CTR_ID" --runtime "$RUNTIME_BINARY" \ - --bundle "$BUNDLE_PATH" --log-path "$LOG_PATH" --terminal + --bundle "$BUNDLE_PATH" --log-path "$LOG_PATH" --terminal assert_success [ -f "$LOG_PATH" ] fi @@ -228,22 +243,10 @@ echo "<6>Info message"' @test "priority parsing: error handling with invalid log path" { # Test error handling when journald is not available run_conmon --cid "$CTR_ID" --cuuid "$CTR_ID" --runtime "$RUNTIME_BINARY" \ - --bundle "$BUNDLE_PATH" --log-path "invalid_driver:" --terminal 2>&1 || true + --bundle "$BUNDLE_PATH" --log-path "invalid_driver:" --terminal 2>&1 || true # Use assert_output_contains for specific error checking if [ "$status" -ne 0 ]; then assert_output_contains "log driver" fi } - -# Helper function to skip test if journald is not available -skip_if_no_journald() { - if ! command -v journalctl >/dev/null 2>&1; then - skip "journalctl not available - skipping journald test" - fi - - # Also check if systemd logging is compiled in - if ! $CONMON_BINARY --help 2>&1 | grep -q "journald"; then - skip "conmon not compiled with journald support" - fi -} diff --git a/test/07-attach.bats b/test/07-attach.bats index 94b7fa46..8cf2c34f 100644 --- a/test/07-attach.bats +++ b/test/07-attach.bats @@ -20,8 +20,8 @@ teardown() { # Check that log file was created assert_file_exists "$LOG_PATH" run cat "$LOG_PATH" - assert "${output}" =~ "Hello there!" "'Hello there!' found in the log" - assert "${output}" =~ "Container stopped!" "'Container stopped!' found in the log" + assert "${output}" =~ "Hello there!" "'Hello there!' found in the log" + assert "${output}" =~ "Container stopped!" "'Container stopped!' found in the log" assert_file_not_exists "$ATTACH_PATH" } @@ -37,7 +37,7 @@ teardown() { # Check that log file was created assert_file_exists "$LOG_PATH" run cat "$LOG_PATH" - assert "${output}" =~ "Container stopped!" "'Container stopped!' found in the log" + assert "${output}" =~ "Container stopped!" "'Container stopped!' found in the log" } @test "attach: unix socket remains open with --leave-stdin-open" { @@ -48,12 +48,12 @@ teardown() { # Check that log file was created assert_file_exists "$LOG_PATH" run cat "$LOG_PATH" - assert "${output}" =~ "Hello there!" "'Hello there!' found in the log" - assert "${output}" !~ "Container stopped!" "'Container stopped!' not found in the log" + assert "${output}" =~ "Hello there!" "'Hello there!' found in the log" + assert "${output}" !~ "Container stopped!" "'Container stopped!' not found in the log" assert_file_exists "$ATTACH_PATH" echo "Hello there again!" | socat STDIN "UNIX:${ATTACH_PATH},socktype=5" run cat "$LOG_PATH" - assert "${output}" =~ "Hello there again!" "'Hello there again!' found in the log" - assert "${output}" !~ "Container stopped!" "'Container stopped!' not found in the log" + assert "${output}" =~ "Hello there again!" "'Hello there again!' found in the log" + assert "${output}" !~ "Container stopped!" "'Container stopped!' not found in the log" } diff --git a/test/08-exec.bats b/test/08-exec.bats index 3878b966..d63e0699 100644 --- a/test/08-exec.bats +++ b/test/08-exec.bats @@ -32,12 +32,12 @@ teardown() { # Check that the main process noticed the /tmp/test.txt. assert_file_exists "$LOG_PATH" run cat "$LOG_PATH" - assert "${output}" =~ "Hello there!" "'Hello there!' found in the log" + assert "${output}" =~ "Hello there!" "'Hello there!' found in the log" # Check that the exec process output is stored in the log. assert_file_exists "$LOG_PATH.exec" run cat "$LOG_PATH.exec" - assert "${output}" =~ "Hello from exec!" "'Hello from exec!' found in the log" + assert "${output}" =~ "Hello from exec!" "'Hello from exec!' found in the log" } @test "exec: --exec-attach without no --api-version" { @@ -87,8 +87,8 @@ teardown() { wait_for_runtime_status "$CTR_ID" stopped # Check that the conmon wrote something back. - assert_file_exists $TEST_TMPDIR/attachpipe-output - run cat $TEST_TMPDIR/attachpipe-output + assert_file_exists "$TEST_TMPDIR/attachpipe-output" + run cat "$TEST_TMPDIR/attachpipe-output" assert_json "${output}" =~ '"data": 0' } @@ -112,6 +112,8 @@ teardown() { printf 'start conmon\n' >&$w # Wait for the main test process to do initial asserts. It will signal # to this process by file creation. + # TEST_TMPDIR is exported, so let the child shell expand it. + # shellcheck disable=SC2016 timeout 5 bash -c 'while [ ! -f $TEST_TMPDIR/startpipe-continue ]; do sleep 0.1; done;' # Do the second write to really start the conmon. printf 'start attach\n' >&$w @@ -134,7 +136,7 @@ teardown() { assert "${output}" !~ "Hello from exec!" # Trigger second write to startpipeline. - touch $TEST_TMPDIR/startpipe-continue + touch "$TEST_TMPDIR/startpipe-continue" # The exec should start now. wait_for_runtime_status "$CTR_ID" stopped @@ -159,7 +161,7 @@ teardown() { # Check that the conmon wrote something back. wait_for_syncpipe_output 1 - run cat $TEST_TMPDIR/syncpipe-output + run cat "$TEST_TMPDIR/syncpipe-output" assert_json "${output}" =~ '"exit_code": 0' } @@ -180,13 +182,12 @@ teardown() { # There should be two values with "data" key. The first one is the PID and # the second one is the exit code. wait_for_syncpipe_output 2 - run cat $TEST_TMPDIR/syncpipe-output + run cat "$TEST_TMPDIR/syncpipe-output" CONTAINER_PID=$(cat "$CONTAINER_PIDFILE") assert_json "${output}" =~ "\"data\": $CONTAINER_PID" assert_json "${output}" =~ '"data": 0' } - @test "exec: runtime failure is reported to the sync pipe" { start_conmon_with_default_args --log-path "k8s-file:$LOG_PATH" wait_for_runtime_status "$CTR_ID" running @@ -206,7 +207,7 @@ teardown() { # The failure has to be reported rather than silently swallowed, and the # report has to carry what the runtime said about it. wait_for_syncpipe_output 1 - run cat $TEST_TMPDIR/syncpipe-output + run cat "$TEST_TMPDIR/syncpipe-output" assert_json "${output}" =~ '"data": -1' assert "${output}" =~ "exec failed" } diff --git a/test/10-ctrl.bats b/test/10-ctrl.bats index a3859a81..893ee8d1 100644 --- a/test/10-ctrl.bats +++ b/test/10-ctrl.bats @@ -20,7 +20,7 @@ test_ctl_command() { wait_for_runtime_status "$CTR_ID" running local main_conmon_pid=$CONMON_PID - echo "$command" > ${CTL_PATH} + echo "$command" >"${CTL_PATH}" start_conmon_with_default_args \ --log-path "k8s-file:$LOG_PATH.exec" \ @@ -79,7 +79,7 @@ test_resize_command_ok() { @test "ctrl: resize the terminal, too long line" { # Generate a very long line, longer than conmon's buffer. - long_line=$(printf '%*s' "65535" | tr ' ' "#") + long_line=$(printf '%*s' 65535 '' | tr ' ' "#") test_resize_command_fail "1 2 2 $long_line" } @@ -93,9 +93,9 @@ test_resize_command_ok() { local main_conmon_pid=$CONMON_PID # Remove the log. - rm -f $LOG_PATH + rm -f "$LOG_PATH" # The control message should reopen/recreate it. - echo "2 1 1" > ${CTL_PATH} + echo "2 1 1" >"${CTL_PATH}" start_conmon_with_default_args \ --log-path "k8s-file:$LOG_PATH.exec" \ @@ -179,7 +179,7 @@ test_resize_command_ok() { wait_for_log_line "$LOG_PATH" "before rotation" # The control message should rotate the log - echo "2 1 1" > ${CTL_PATH} + echo "2 1 1" >"${CTL_PATH}" run_conmon_with_default_args \ --log-path "k8s-file:$LOG_PATH.exec" \ diff --git a/test/run-tests.sh b/test/run-tests.sh index 09c08861..a64d1159 100755 --- a/test/run-tests.sh +++ b/test/run-tests.sh @@ -20,7 +20,7 @@ YELLOW='\033[1;33m' NC='\033[0m' # No Color usage() { - cat << EOF + cat <& /dev/null; then + if ! command -v bats >&/dev/null; then missing_deps+=("bats") fi - if ! command -v socat >& /dev/null; then + if ! command -v socat >&/dev/null; then missing_deps+=("socat") fi @@ -98,6 +98,7 @@ show_environment() { log_info "Running tests with:" log_info " kernel: $(uname -srm)" if [[ -r /etc/os-release ]]; then + # shellcheck source=/dev/null log_info " distro: $(. /etc/os-release && echo "$PRETTY_NAME")" fi log_info " conmon: $CONMON_BINARY: $("$CONMON_BINARY" --version 2>&1 | tr '\n' ' ' | sed 's/ *$//')" @@ -126,47 +127,47 @@ main() { # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in - -h|--help) - usage - exit 0 - ;; - -c|--conmon) - CONMON_BINARY="$2" - shift 2 - ;; - -r|--runtime) - RUNTIME_BINARY="$2" - shift 2 - ;; - -v|--verbose) - verbose=true - shift - ;; - -t|--tap) - tap=true - shift - ;; - -j|--jobs) - jobs="$2" - shift 2 - ;; - --filter) - filter="$2" - shift 2 - ;; - -s|--strict) - CONMON_TEST_STRICT=1 - shift - ;; - *.bats) - test_files+=("$1") - shift - ;; - *) - log_error "Unknown option: $1" - usage - exit 1 - ;; + -h | --help) + usage + exit 0 + ;; + -c | --conmon) + CONMON_BINARY="$2" + shift 2 + ;; + -r | --runtime) + RUNTIME_BINARY="$2" + shift 2 + ;; + -v | --verbose) + verbose=true + shift + ;; + -t | --tap) + tap=true + shift + ;; + -j | --jobs) + jobs="$2" + shift 2 + ;; + --filter) + filter="$2" + shift 2 + ;; + -s | --strict) + CONMON_TEST_STRICT=1 + shift + ;; + *.bats) + test_files+=("$1") + shift + ;; + *) + log_error "Unknown option: $1" + usage + exit 1 + ;; esac done @@ -191,7 +192,7 @@ main() { # Add any additional BATS options from environment if [[ -n "$BATS_OPTIONS" ]]; then - read -ra additional_opts <<< "$BATS_OPTIONS" + read -ra additional_opts <<<"$BATS_OPTIONS" bats_args+=("${additional_opts[@]}") fi diff --git a/test/setup_suite.bash b/test/setup_suite.bash index 242e1453..b3ed0fde 100644 --- a/test/setup_suite.bash +++ b/test/setup_suite.bash @@ -47,7 +47,7 @@ setup_suite() { suite_fail "failed to create a container from $UBI10_MICRO_IMAGE" return 1 fi - if ! podman export "$ctr" > "$CONMON_TEST_ROOTFS_TAR"; then + if ! podman export "$ctr" >"$CONMON_TEST_ROOTFS_TAR"; then podman rm "$ctr" >/dev/null 2>&1 || true suite_fail "failed to export the rootfs from $UBI10_MICRO_IMAGE" return 1 diff --git a/test/test_helper.bash b/test/test_helper.bash index 7aae2e98..652106ea 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -2,6 +2,11 @@ # Common test helper functions for conmon BATS tests +# status and output are set by bats' run, and several variables defined here +# are only referenced by the .bats files that load this one, so shellcheck +# cannot see either when it looks at this file on its own. +# shellcheck disable=SC2034,SC2154 + # Provide basic assertion functions if not available assert_success() { if [ "$status" -ne 0 ]; then @@ -62,12 +67,12 @@ get_conmon_journal_output() { return 0 fi - local level_filter="" + local level_filter=() if [[ "$level" != "-1" ]]; then - level_filter="-p $level" + level_filter=(-p "$level") fi - journalctl -q --no-pager $level_filter _COMM=conmon _PID="$pid" 2>/dev/null || echo "" + journalctl -q --no-pager "${level_filter[@]}" _COMM=conmon _PID="$pid" 2>/dev/null || echo "" } # Create a temporary directory for test @@ -99,11 +104,11 @@ generate_process_spec() { fi if [[ -z "$BUNDLE_PATH" || ! -e "$BUNDLE_PATH" ]]; then die "The BUNDLE_PATH directory does not exist. Ensure 'generate_process_spec'" \ - " is called after the 'setup_test_env'" + " is called after the 'setup_test_env'" fi local config_path="$BUNDLE_PATH/process.json" - cat > "$config_path" << EOF + cat >"$config_path" < "$config_path" << EOF + cat >"$config_path" <>$output_file + echo "$line" >>"$output_file" done } & } @@ -651,14 +656,15 @@ function assert() { local testname="$2" case "${#*}" in - 0) die "Internal error: 'assert' requires one or more arguments" ;; - 1|2) ;; - 3|4) actual_string="$1" - operator="$2" - expect_string="$3" - testname="$4" - ;; - *) die "Internal error: too many arguments to 'assert'" ;; + 0) die "Internal error: 'assert' requires one or more arguments" ;; + 1 | 2) ;; + 3 | 4) + actual_string="$1" + operator="$2" + expect_string="$3" + testname="$4" + ;; + *) die "Internal error: too many arguments to 'assert'" ;; esac # Comparisons. @@ -715,34 +721,36 @@ function assert() { # bash %q is really nice, except for the way it backslashes spaces local -a expect_split_q for line in "${expect_split[@]}"; do - local q=$(printf "%q" "$line" | sed -e 's/\\ / /g') + local q + q=$(printf "%q" "$line" | sed -e 's/\\ / /g') expect_split_q+=("$q") done local -a actual_split_q for line in "${actual_split[@]}"; do - local q=$(printf "%q" "$line" | sed -e 's/\\ / /g') + local q + q=$(printf "%q" "$line" | sed -e 's/\\ / /g') actual_split_q+=("$q") done - printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 - printf "#| FAIL: %s\n" "$testname" >&2 - printf "#| expected: %s%s\n" "$op" "${expect_split_q[0]}" >&2 + printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 + printf "#| FAIL: %s\n" "$testname" >&2 + printf "#| expected: %s%s\n" "$op" "${expect_split_q[0]}" >&2 local line for line in "${expect_split_q[@]:1}"; do - printf "#| > %s%s\n" "$ws" "$line" >&2 + printf "#| > %s%s\n" "$ws" "$line" >&2 done - printf "#| actual: %s%s\n" "$ws" "${actual_split_q[0]}" >&2 + printf "#| actual: %s%s\n" "$ws" "${actual_split_q[0]}" >&2 for line in "${actual_split_q[@]:1}"; do - printf "#| > %s%s\n" "$ws" "$line" >&2 + printf "#| > %s%s\n" "$ws" "$line" >&2 done - printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 + printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 bail-now } function die() { # FIXME: handle multi-line output - echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 - echo "#| FAIL: $*" >&2 + echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 + echo "#| FAIL: $*" >&2 echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 bail-now }