test-lint-shell.sh 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  4. REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  5. SCRIPT_UNDER_TEST="$REPO_ROOT/scripts/lint-shell.sh"
  6. FAILURES=0
  7. TEST_ROOT="$(mktemp -d)"
  8. cleanup() {
  9. rm -rf "$TEST_ROOT"
  10. }
  11. trap cleanup EXIT
  12. pass() {
  13. echo " [PASS] $1"
  14. }
  15. fail() {
  16. echo " [FAIL] $1"
  17. FAILURES=$((FAILURES + 1))
  18. }
  19. assert_contains() {
  20. local haystack="$1"
  21. local needle="$2"
  22. local description="$3"
  23. if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
  24. pass "$description"
  25. else
  26. fail "$description"
  27. echo " expected to find: $needle"
  28. echo " in:"
  29. printf '%s\n' "$haystack" | sed 's/^/ /'
  30. fi
  31. }
  32. assert_not_contains() {
  33. local haystack="$1"
  34. local needle="$2"
  35. local description="$3"
  36. if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
  37. fail "$description"
  38. echo " did not expect to find: $needle"
  39. echo " in:"
  40. printf '%s\n' "$haystack" | sed 's/^/ /'
  41. else
  42. pass "$description"
  43. fi
  44. }
  45. configure_git_identity() {
  46. local repo="$1"
  47. git -C "$repo" config user.name "Test Bot"
  48. git -C "$repo" config user.email "test@example.com"
  49. }
  50. write_stub_tool() {
  51. local path="$1"
  52. local name="$2"
  53. cat >"$path" <<EOF
  54. #!/usr/bin/env bash
  55. {
  56. printf '${name}:'
  57. for arg in "\$@"; do
  58. printf ' <%s>' "\$arg"
  59. done
  60. printf '\n'
  61. } >> "\$SUPERPOWERS_SHELL_LINT_TEST_LOG"
  62. exit 0
  63. EOF
  64. chmod +x "$path"
  65. }
  66. make_fixture_repo() {
  67. local repo="$1"
  68. git init -q -b main "$repo"
  69. configure_git_identity "$repo"
  70. mkdir -p "$repo/hooks"
  71. cat >"$repo/tracked.sh" <<'EOF'
  72. #!/usr/bin/env bash
  73. echo "tracked"
  74. EOF
  75. cat >"$repo/hooks/session-start" <<'EOF'
  76. #!/bin/sh
  77. echo "extensionless"
  78. EOF
  79. cat >"$repo/README.md" <<'EOF'
  80. # Fixture
  81. ```bash
  82. echo "not a shell script"
  83. ```
  84. EOF
  85. cat >"$repo/untracked.sh" <<'EOF'
  86. #!/usr/bin/env bash
  87. echo "untracked"
  88. EOF
  89. git -C "$repo" add tracked.sh hooks/session-start README.md
  90. git -C "$repo" commit -q -m "fixture"
  91. printf '\necho "changed"\n' >>"$repo/tracked.sh"
  92. printf '\necho "changed extensionless"\n' >>"$repo/hooks/session-start"
  93. }
  94. run_lint_shell() {
  95. local repo="$1"
  96. local fakebin="$2"
  97. local log="$3"
  98. shift 3
  99. (
  100. cd "$repo"
  101. PATH="$fakebin:$PATH" \
  102. SUPERPOWERS_SHELL_LINT_TEST_LOG="$log" \
  103. bash "$SCRIPT_UNDER_TEST" "$@"
  104. )
  105. }
  106. echo "Shell lint script tests"
  107. fixture="$TEST_ROOT/repo"
  108. fakebin="$TEST_ROOT/bin"
  109. log="$TEST_ROOT/tool.log"
  110. mkdir -p "$fixture" "$fakebin"
  111. : >"$log"
  112. write_stub_tool "$fakebin/shellcheck" "shellcheck"
  113. write_stub_tool "$fakebin/shfmt" "shfmt"
  114. make_fixture_repo "$fixture"
  115. if output="$(run_lint_shell "$fixture" "$fakebin" "$log" 2>&1)"; then
  116. pass "lint-shell check mode exits successfully with stub tools"
  117. else
  118. fail "lint-shell check mode exits successfully with stub tools"
  119. printf '%s\n' "$output" | sed 's/^/ /'
  120. fi
  121. tool_log="$(cat "$log")"
  122. assert_contains "$output" "Linting 3 shell files" "reports changed shell file count"
  123. assert_not_contains "$tool_log" "shfmt:" "does not run shfmt in lint mode"
  124. assert_contains "$tool_log" "shellcheck:" "runs ShellCheck"
  125. assert_contains "$tool_log" "<--severity=warning>" "uses warning severity as the baseline"
  126. assert_contains "$tool_log" "<--external-sources>" "allows ShellCheck to follow sourced files"
  127. assert_contains "$tool_log" "<--source-path=SCRIPTDIR>" "resolves ShellCheck sources relative to each script"
  128. assert_contains "$tool_log" "<hooks/session-start>" "includes changed extensionless shell shebang file"
  129. assert_contains "$tool_log" "<tracked.sh>" "includes changed tracked .sh file"
  130. assert_contains "$tool_log" "<untracked.sh>" "includes untracked shell files by default"
  131. assert_not_contains "$tool_log" "README.md" "ignores Markdown with shell snippets"
  132. : >"$log"
  133. if output="$(run_lint_shell "$fixture" "$fakebin" "$log" --all --format 2>&1)"; then
  134. pass "lint-shell --format exits successfully with stub tools"
  135. else
  136. fail "lint-shell --format exits successfully with stub tools"
  137. printf '%s\n' "$output" | sed 's/^/ /'
  138. fi
  139. tool_log="$(cat "$log")"
  140. assert_contains "$tool_log" "<-w>" "uses shfmt write mode with --format"
  141. assert_contains "$tool_log" "shellcheck:" "runs ShellCheck after --format"
  142. assert_contains "$tool_log" "<--severity=warning>" "keeps warning severity after --format"
  143. assert_contains "$tool_log" "<hooks/session-start>" "--all includes tracked extensionless shell shebang file"
  144. assert_contains "$tool_log" "<tracked.sh>" "--all includes tracked .sh file"
  145. assert_not_contains "$tool_log" "untracked.sh" "--all ignores untracked shell files"
  146. if [[ "$FAILURES" -eq 0 ]]; then
  147. echo "All shell lint script tests passed"
  148. else
  149. echo "$FAILURES shell lint script test(s) failed"
  150. exit 1
  151. fi