feat(code): add smart test runner command (#83)

Migrated from host-uk/core-claude#39

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-02-02 07:20:09 +00:00 committed by GitHub
parent 8dacc91593
commit 21baaa54e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 180 additions and 0 deletions

View file

@ -13,6 +13,7 @@ The `core` command provides a unified interface for Go/PHP development and multi
| Task | Command |
|------|---------|
| Smart tests | `core test` |
| Go tests | `core go test` |
| Go coverage | `core go cov` |
| Go format | `core go fmt --fix` |
@ -58,3 +59,15 @@ Multiple repos?
| `git status` per repo | `core dev health` |
Run `core --help` or `core <cmd> --help` for full options.
## Smart Test Runner: `core test`
The `core test` command provides an intelligent way to run only the tests relevant to your recent changes.
- **`core test`**: Automatically detects changed files since the last commit and runs only the corresponding tests.
- **`core test --all`**: Runs the entire test suite for the project.
- **`core test --filter <TestName>`**: Runs a specific test by name.
- **`core test --coverage`**: Generates a test coverage report.
- **`core test <path/to/file>`**: Runs tests for a specific file or directory.
The runner automatically detects whether the project is Go or PHP and executes the appropriate testing tool. If it cannot map changed files to test files, it will fall back to running the full test suite.

167
claude/code/skills/core/test.sh Executable file
View file

@ -0,0 +1,167 @@
#!/bin/bash
set -e # Exit on error
# --- Configuration ---
# Set to 1 to enable debug messages
DEBUG=0
debug() {
if [ "$DEBUG" -eq 1 ]; then
echo "DEBUG: $@" >&2
fi
}
# --- Argument Parsing ---
COVERAGE=false
FILTER=""
ALL=false
# All other arguments are treated as files/directories to test
PATHS=()
while (( "$#" )); do
case "$1" in
--coverage)
COVERAGE=true
shift
;;
--filter)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
FILTER="$2"
shift 2
else
echo "Error: Argument for --filter is missing" >&2
exit 1
fi
;;
--all)
ALL=true
shift
;;
-*)
echo "Error: Unknown option $1" >&2
exit 1
;;
*) # preserve positional arguments as paths
PATHS+=("$1")
shift
;;
esac
done
debug "Coverage: $COVERAGE"
debug "Filter: $FILTER"
debug "All: $ALL"
debug "Paths: ${PATHS[*]}"
# --- Project Detection ---
is_php() { [ -f "composer.json" ]; }
is_go() { [ -f "go.mod" ]; }
# --- Test Execution Functions ---
run_php_tests() {
local args=("$@")
local cmd=("core" "php" "test")
if [ "$COVERAGE" = true ]; then
cmd+=("--coverage")
fi
if [ -n "$FILTER" ]; then
cmd+=("--filter" "$FILTER")
fi
if [ ${#args[@]} -gt 0 ]; then
cmd+=("${args[@]}")
fi
echo "Running: ${cmd[*]}"
"${cmd[@]}"
}
run_go_tests() {
local args=("$@")
local cmd_base=("core" "go")
local cmd_action="test"
if [ "$COVERAGE" = true ]; then
cmd_action="cov"
fi
local cmd=("$cmd_base" "$cmd_action")
if [ -n "$FILTER" ]; then
cmd+=("--run" "$FILTER")
fi
if [ ${#args[@]} -gt 0 ]; then
# For Go, convert file paths to package paths (./path/to/pkg)
local pkgs=()
for p in "${args[@]}"; do
pkg=$(dirname "$p")
# Add ./ prefix if not present and avoid duplicates
if [[ ! " ${pkgs[@]} " =~ " ./$pkg " ]]; then
pkgs+=("./$pkg")
fi
done
cmd+=("${pkgs[@]}")
else
# If no paths specified, run for all packages
cmd+=("./...")
fi
echo "Running: ${cmd[*]}"
"${cmd[@]}"
}
# --- Main Logic ---
# If specific paths are provided, use them
if [ ${#PATHS[@]} -gt 0 ]; then
echo "Running tests for specified paths..."
if is_php; then run_php_tests "${PATHS[@]}";
elif is_go; then run_go_tests "${PATHS[@]}";
else echo "No PHP or Go project detected." >&2; exit 1; fi
exit 0
fi
# If --all or --filter is used without paths, run against the whole project
if [ "$ALL" = true ] || [ -n "$FILTER" ]; then
echo "Running all tests (--all or --filter specified)..."
if is_php; then run_php_tests;
elif is_go; then run_go_tests;
else echo "No PHP or Go project detected." >&2; exit 1; fi
exit 0
fi
# --- Smart Detection (default behavior) ---
echo "No specific options provided. Detecting changes since last commit..."
changed_files=$(git diff --name-only HEAD~1 HEAD)
if [ -z "$changed_files" ]; then
echo "No changed files detected. Running all tests."
if is_php; then run_php_tests;
elif is_go; then run_go_tests;
else echo "No PHP or Go project detected." >&2; exit 1; fi
exit 0
fi
echo -e "Detected changed files:\n$changed_files"
test_files=()
for file in $changed_files; do
if is_php && [[ "$file" == src/*.php ]]; then
test_file=$(echo "$file" | sed 's|^src/|tests/|' | sed 's/\.php/Test.php/')
[ -f "$test_file" ] && test_files+=("$test_file")
elif is_go && [[ "$file" == *.go ]] && [[ "$file" != *_test.go ]]; then
test_file="${file%.go}_test.go"
[ -f "$test_file" ] && test_files+=("$test_file")
fi
done
if [ ${#test_files[@]} -eq 0 ]; then
echo "Could not map changed files to any existing tests. Running all tests as a fallback."
if is_php; then run_php_tests;
elif is_go; then run_go_tests;
else echo "No PHP or Go project detected." >&2; exit 1; fi
else
echo "Running tests for the following files:"
printf " - %s\n" "${test_files[@]}"
if is_php; then run_php_tests "${test_files[@]}";
elif is_go; then run_go_tests "${test_files[@]}";
else echo "No PHP or Go project detected." >&2; exit 1; fi
fi