Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .mise/tasks/lint/bats-setup-suite-path
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
#MISE description="Flag setup_suite.bash that calls mise env without preserving BATS_LIBEXEC"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"
#USAGE example "codebase lint:bats-setup-suite-path ." header="Flag repos with unprotected mise env in setup_suite"

set -euo pipefail

# shellcheck source=../../../lib/shell-files.sh
source "$MISE_CONFIG_ROOT/lib/shell-files.sh"

# Rationale: BATS 1.13.0 looks up bats-exec-file, bats-format-*,
# etc. by bare name from files under its libexec dir. If
# test/setup_suite.bash calls `mise env` (which rewrites PATH to
# include tool-manager shims), Bats' libexec directory can fall
# off PATH, and the suite fails before running any tests:
#
# bats-exec-suite: line 323: bats-exec-file: command not found
# # bats warning: Executed 0 instead of expected N tests
#
# The safe shape saves BATS_LIBEXEC before mise env and prepends
# it afterward:
#
# setup_suite() {
# REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
# export REPO_DIR
# bats_libexec="${BATS_LIBEXEC:-}"
# eval "$(cd "$REPO_DIR" && mise env)"
# if [ -n "$bats_libexec" ]; then
# export PATH="$bats_libexec:$PATH"
# fi
# }
#
# When upstream Bats releases a fix (bats-core/bats-core#1209)
# and KKL repos move past Bats 1.13.0, this rule may become less
# necessary.

SETUP_SUITE="test/setup_suite.bash"

IFS=' ' read -ra TARGETS <<< "${usage_targets}"

if [[ ${#TARGETS[@]} -eq 0 ]]; then
echo "ERROR: at least one target is required" >&2
exit 1
fi

# Resolve relative paths against CALLER_PWD (see lib/shell-files.sh).
for i in "${!TARGETS[@]}"; do
TARGETS[i]=$(resolve_target "${TARGETS[i]}")
done

failures=0

for target in "${TARGETS[@]}"; do
if [[ ! -e "$target" ]]; then
echo "ERROR: target does not exist: $target" >&2
exit 1
fi

name=$(basename "$target")

# Repo-level ignore via mise.toml
toml="$target/mise.toml"
if [[ -f "$toml" ]] && rg -q 'codebase:ignore bats-setup-suite-path' "$toml"; then
echo "SKIP $name (codebase:ignore)"
continue
fi

suite="$target/$SETUP_SUITE"
if [[ ! -f "$suite" ]]; then
echo "OK $name (no $SETUP_SUITE -- nothing to check)"
continue
fi

# Inline ignore
if rg -q 'codebase:ignore bats-setup-suite-path' "$suite"; then
echo "SKIP $name (inline codebase:ignore)"
continue
fi

# Check: does the file call `mise env`?
if ! rg -q 'mise env' "$suite"; then
echo "OK $name ($SETUP_SUITE doesn't call mise env -- nothing to check)"
continue
fi

# Check: is BATS_LIBEXEC preserved before the mise env call?
# Walk through the file top-to-bottom. If any `mise env` line appears
# without a preceding BATS_LIBEXEC capture, flag it.
bats_preserved=false
found_offense=false

while IFS= read -r line; do
# Skip comments
[[ "$line" =~ ^[[:space:]]*# ]] && continue

# Detect BATS_LIBEXEC capture (any assignment reading BATS_LIBEXEC)
if grep -q 'BATS_LIBEXEC' <<< "$line"; then
bats_preserved=true
fi

# Detect `mise env` invocation
if grep -q 'mise env' <<< "$line"; then
if [[ "$bats_preserved" == false ]]; then
echo "FAIL $name (mise env without BATS_LIBEXEC preservation in $SETUP_SUITE)"
echo " hint: save BATS_LIBEXEC before mise env and prepend it afterward:"
# shellcheck disable=SC2016
echo ' bats_libexec="${BATS_LIBEXEC:-}"'
# shellcheck disable=SC2016
echo ' eval "$(cd "$REPO_DIR" && mise env)"'
# shellcheck disable=SC2016
echo ' if [ -n "$bats_libexec" ]; then'
# shellcheck disable=SC2016
echo ' export PATH="$bats_libexec:$PATH"'
echo ' fi'
echo " see: https://github.com/bats-core/bats-core/pull/1209"
failures=$((failures + 1))
found_offense=true
break
fi
# Reset for potential subsequent mise env calls (defensive)
bats_preserved=false
fi
done < "$suite"

if [[ "$found_offense" == false ]]; then
echo "OK $name ($SETUP_SUITE preserves BATS_LIBEXEC correctly)"
fi
done

exit "$failures"
75 changes: 75 additions & 0 deletions .mise/tasks/lint/mise-shiv-plugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
#MISE description="Check that mise.toml has shiv plugin setup when declaring shiv-managed tools"
#USAGE arg "<targets>…" help="Paths to codebases to check (one or more)"

set -euo pipefail

# shellcheck source=../../../lib/shell-files.sh
source "$MISE_CONFIG_ROOT/lib/shell-files.sh"

# Required shiv plugin setup
REQUIRED_PLUGIN_LINE='shiv = "https://github.com/KnickKnackLabs/vfox-shiv"'
REQUIRED_SETTING='experimental = true'

IFS=' ' read -ra TARGETS <<< "${usage_targets}"

if [[ ${#TARGETS[@]} -eq 0 ]]; then
echo "ERROR: at least one target is required" >&2
exit 1
fi

# Resolve relative paths against CALLER_PWD (see lib/shell-files.sh).
for i in "${!TARGETS[@]}"; do
TARGETS[i]=$(resolve_target "${TARGETS[i]}")
done

failures=0

for target in "${TARGETS[@]}"; do
if [[ ! -e "$target" ]]; then
echo "ERROR: target does not exist: $target" >&2
exit 1
fi

toml="$target/mise.toml"
name=$(basename "$target")

if [[ ! -f "$toml" ]]; then
echo "INFO $name: no mise.toml found"
continue
fi

# Check for file-level ignore
if head -20 "$toml" | grep -q 'codebase:ignore mise-shiv-plugin'; then
echo "SKIP $name (codebase:ignore)"
continue
fi

# Check if any [tools] key starts with shiv:
if ! grep -q '^\s*"shiv:' "$toml" && ! grep -q "^shiv:" "$toml"; then
echo "OK $name (no shiv tools declared)"
continue
fi

missing=()

# Check [plugins] for shiv entry
if ! grep -Eq '^\s*shiv\s*=' "$toml"; then
missing+=("$REQUIRED_PLUGIN_LINE")
fi

# Check [settings] for experimental = true
if ! grep -q "^experimental = true" "$toml"; then
missing+=("$REQUIRED_SETTING")
fi

if [[ ${#missing[@]} -eq 0 ]]; then
echo "OK $name"
continue
fi

echo "FAIL $name: missing ${missing[*]}"
failures=$((failures + 1))
done

exit "$failures"
2 changes: 0 additions & 2 deletions test/lib/shell-files.bats
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ setup() {
source "$REPO_DIR/lib/shell-files.sh"
}

# ============================================================================
# resolve_target
# ============================================================================

@test "resolve_target: absolute path passes through unchanged" {
result=$(resolve_target "/some/absolute/path")
Expand Down
85 changes: 85 additions & 0 deletions test/lint/bats-setup-suite-path/bats-setup-suite-path.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bats
# Tests for bats-setup-suite-path lint rule

load ../../test_helper

setup() {
FIXTURES="$BATS_TEST_DIRNAME/fixtures"
}

# Detection

@test "lint: passes when no test/setup_suite.bash exists" {
run codebase lint:bats-setup-suite-path "$FIXTURES/no-setup-suite"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
[[ "$output" == *"no test/setup_suite.bash"* ]]
}

@test "lint: passes when BATS_LIBEXEC is preserved correctly" {
run codebase lint:bats-setup-suite-path "$FIXTURES/preserved"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
[[ "$output" == *"preserves BATS_LIBEXEC correctly"* ]]
}

@test "lint: fails when mise env is called without BATS_LIBEXEC preservation" {
run codebase lint:bats-setup-suite-path "$FIXTURES/missing-preserve"
[ "$status" -ne 0 ]
[[ "$output" == *"FAIL"* ]]
[[ "$output" == *"mise env without BATS_LIBEXEC preservation"* ]]
# Should mention the fix
[[ "$output" == *"bats_libexec"* ]]
[[ "$output" == *"BATS_LIBEXEC:-"* ]]
}

@test "lint: passes when setup_suite.bash doesn't call mise env" {
run codebase lint:bats-setup-suite-path "$FIXTURES/no-mise-env"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
[[ "$output" == *"doesn't call mise env"* ]]
}

@test "lint: skips when inline codebase:ignore is present" {
run codebase lint:bats-setup-suite-path "$FIXTURES/ignored-inline"
[ "$status" -eq 0 ]
[[ "$output" == *"SKIP"* ]]
}

@test "lint: skips when repo-level codebase:ignore is present in mise.toml" {
run codebase lint:bats-setup-suite-path "$FIXTURES/ignored-repo"
[ "$status" -eq 0 ]
[[ "$output" == *"SKIP"* ]]
}

@test "lint: passes when no test/ directory exists" {
run codebase lint:bats-setup-suite-path "$FIXTURES/no-test-dir"
[ "$status" -eq 0 ]
[[ "$output" == *"OK"* ]]
[[ "$output" == *"no test/setup_suite.bash"* ]]
}

@test "lint: handles multiple targets with mixed results" {
run codebase lint:bats-setup-suite-path \
"$FIXTURES/preserved" \
"$FIXTURES/missing-preserve" \
"$FIXTURES/no-mise-env"
[ "$status" -ne 0 ]
[[ "$output" == *"OK"*"preserved"* ]]
[[ "$output" == *"FAIL"*"missing-preserve"* ]]
[[ "$output" == *"OK"*"no-mise-env"* ]]
}

# Error handling

@test "lint: fails when target does not exist" {
run codebase lint:bats-setup-suite-path /nonexistent
[ "$status" -ne 0 ]
[[ "$output" == *"ERROR"* ]]
}

@test "lint: fails when no targets provided" {
run codebase lint:bats-setup-suite-path
[ "$status" -ne 0 ]
[[ "$output" == *"ERROR"* ]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
setup_suite() {
REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
export REPO_DIR
# codebase:ignore bats-setup-suite-path -- intentionally bypassing for test setup reasons
eval "$(cd "$REPO_DIR" && mise env)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[_.codebase]
lint = ["mise-settings"]
codebase:ignore bats-setup-suite-path
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
setup_suite() {
REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
export REPO_DIR
eval "$(cd "$REPO_DIR" && mise env)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
setup_suite() {
REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
export REPO_DIR
eval "$(cd "$REPO_DIR" && mise env)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
setup_suite() {
REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
export REPO_DIR
echo "setup complete"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Minimal repo fixture — no test/setup_suite.bash to check
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
setup_suite() {
REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
export REPO_DIR

bats_libexec="${BATS_LIBEXEC:-}"
eval "$(cd "$REPO_DIR" && mise env)"
if [ -n "$bats_libexec" ]; then
export PATH="$bats_libexec:$PATH"
fi
}
Loading