blob: 35b5cc62c0b7f6228df8a94d4dbf7a2a55af9b15 [file] [edit]
#!/bin/bash
# Run IREE tests with Bazel.
#
# Usage: iree-bazel-test [options] [targets...] [bazel-args...]
#
# Examples:
# iree-bazel-test # Run all tests (CPU only)
# iree-bazel-test //compiler/... # Test compiler only
# iree-bazel-test --config=asan # Test with AddressSanitizer
# iree-bazel-test --coverage //runtime/src/iree/base:printf_test
set -e
# Source shared library.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/iree-bazel-lib"
iree_bazel_init "iree-bazel-test"
# Expand combined short flags (e.g., -nv -> -n -v).
eval "set -- $(iree_expand_combined_flags "$@")"
show_help() {
cat << 'EOF'
iree-bazel-test - Run IREE tests with Bazel
USAGE
iree-bazel-test [options] [targets...] [bazel-args...]
OPTIONS
-n, --dry_run Show the bazel command without executing
-v, --verbose Show the bazel command before executing
-w, --watch Watch mode: rerun tests on file changes
--coverage Run with source-based code coverage and report
--coverage-fuzz Like --coverage, also merge fuzzer corpus coverage
--coverage-html=DIR Generate HTML coverage report in DIR
-h, --help Show this help
NOTE: Short flags can be combined: -nv is equivalent to -n -v
NOTE: --keep_going is always enabled for test runs.
COVERAGE
--coverage builds without sanitizers (for clean instrumentation), runs
the test binary, and reports line/branch/function coverage using
llvm-cov. Only same-package source files are included in the report.
--coverage-fuzz additionally finds a sibling _fuzz target (e.g.,
printf_test -> printf_fuzz), runs it over its fuzzer corpus with
-runs=0, and merges the profiles for combined coverage.
--coverage-html=DIR writes a browsable HTML report to the given
directory (implies --coverage).
Examples:
iree-bazel-test --coverage //runtime/src/iree/base:printf_test
iree-bazel-test --coverage-fuzz //runtime/src/iree/base:printf_test
iree-bazel-test --coverage-html=/tmp/cov //runtime/src/iree/base:printf_test
ARGUMENTS
targets One or more Bazel targets (at least one required)
bazel-args Additional arguments passed to bazel test
EXAMPLES
# Test all compiler components
iree-bazel-test //compiler/...
# Test a specific test target
iree-bazel-test //runtime/src/iree/base:status_test
# Test multiple targets at once
iree-bazel-test //runtime/src/iree/tokenizer/... //tools/test:iree-tokenize.txt
# Test all tools
iree-bazel-test //tools/...
# Test with AddressSanitizer
iree-bazel-test //compiler/... --config=asan
# Watch and rerun on changes (great for TDD)
iree-bazel-test -w //runtime/src/iree/base:status_test
# Show command without executing (verbose dry-run)
iree-bazel-test -nv //compiler/...
# Coverage report
iree-bazel-test --coverage //runtime/src/iree/base:printf_test
DEFAULT BEHAVIOR
- Runs tests using drivers configured in 'iree-bazel-configure'
- Continues on test failures (--keep_going)
CONFIGURING DRIVERS
Configure drivers ONCE, then test repeatedly:
IREE_HAL_DRIVER_CUDA=ON iree-bazel-configure # Enable CUDA
iree-bazel-test //runtime/... # Tests include CUDA
IREE_HAL_DRIVER_CUDA=OFF iree-bazel-configure # Disable CUDA
iree-bazel-test //runtime/... # CUDA tests skipped
All configuration happens during 'iree-bazel-configure'.
CONFIGURATIONS
--config=localdev Local dev optimizations (disk cache, skymeld)
--config=debug Debug build tests
--config=asan Address Sanitizer
--config=msan Memory Sanitizer
--config=tsan Thread Sanitizer
SEE ALSO
iree-bazel-configure, iree-bazel-build
EOF
}
# Parse arguments.
WATCH_MODE=0
COVERAGE_MODE=0
COVERAGE_FUZZ=0
COVERAGE_HTML_DIR=""
TARGETS=()
BAZEL_ARGS=()
while [[ $# -gt 0 ]]; do
case "${1}" in
-h|--help)
show_help
exit 0
;;
--agent-md|--agent_md)
iree_show_agent_md
exit 0
;;
-n|--dry_run|--dry-run)
IREE_BAZEL_DRY_RUN=1
shift
;;
-v|--verbose)
IREE_BAZEL_VERBOSE=1
shift
;;
-w|--watch)
WATCH_MODE=1
shift
;;
--coverage)
COVERAGE_MODE=1
shift
;;
--coverage-fuzz|--coverage_fuzz)
COVERAGE_MODE=1
COVERAGE_FUZZ=1
shift
;;
--coverage-html=*|--coverage_html=*)
COVERAGE_MODE=1
COVERAGE_HTML_DIR="${1#*=}"
shift
;;
-*)
BAZEL_ARGS+=("${1}")
shift
;;
*)
TARGETS+=("${1}")
shift
;;
esac
done
# Set up worktree (after arg parsing so --help works anywhere).
iree_setup_worktree
# At least one target is required.
if [[ ${#TARGETS[@]} -eq 0 ]]; then
iree_error "Target is required for bazel test"
echo ""
echo "Examples:"
echo " iree-bazel-test //compiler/..."
echo " iree-bazel-test //runtime/src/iree/base:status_test"
echo " iree-bazel-test //tools/..."
exit 1
fi
# =============================================================================
# Coverage mode: build with instrumentation, run, report with llvm-cov
# =============================================================================
if [[ "${COVERAGE_MODE}" == "1" ]]; then
# Coverage requires a single concrete target (not //...).
if [[ ${#TARGETS[@]} -ne 1 ]]; then
iree_error "--coverage requires exactly one target"
exit 1
fi
TARGET="${TARGETS[0]}"
# Validate it looks like a concrete target (has a colon).
if [[ "${TARGET}" != *":"* ]]; then
iree_error "--coverage requires a concrete target (e.g., //path:target_test), not a wildcard"
exit 1
fi
# Extract package and target name.
TARGET_PACKAGE="${TARGET%%:*}" # //runtime/src/iree/base
TARGET_NAME="${TARGET##*:}" # printf_test
# Determine source directory for coverage filtering.
# Strip leading // to get the filesystem path.
SOURCE_DIR="${TARGET_PACKAGE#//}"
# Coverage instrumentation flags. No sanitizers — they distort inlining
# and code generation, which changes what llvm-cov attributes to each line.
COVERAGE_COPT=(
--copt=-fprofile-instr-generate
--copt=-fcoverage-mapping
--linkopt=-fprofile-instr-generate
)
# Filter out any --config=asan/msan/tsan from BAZEL_ARGS — sanitizers
# and coverage instrumentation don't mix well.
FILTERED_BAZEL_ARGS=()
for arg in "${BAZEL_ARGS[@]}"; do
case "${arg}" in
--config=asan|--config=msan|--config=tsan|--config=fuzzer)
iree_warn "Stripping ${arg} (incompatible with --coverage)"
;;
*)
FILTERED_BAZEL_ARGS+=("${arg}")
;;
esac
done
# Build targets to collect coverage from.
BUILD_TARGETS=("${TARGET}")
FUZZ_TARGET=""
FUZZ_CORPUS_DIR=""
if [[ "${COVERAGE_FUZZ}" == "1" ]]; then
# Derive fuzz target name: foo_test -> foo_fuzz.
FUZZ_NAME="${TARGET_NAME%_test}_fuzz"
FUZZ_TARGET="${TARGET_PACKAGE}:${FUZZ_NAME}"
# Check if the fuzz target exists.
BAZEL_BIN=$(iree_get_bazel_command)
if "${BAZEL_BIN}" query "${FUZZ_TARGET}" >/dev/null 2>&1; then
BUILD_TARGETS+=("${FUZZ_TARGET}")
# Find the fuzzer corpus directory (mirrors iree-bazel-fuzz layout).
FUZZ_CACHE_DIR="${IREE_FUZZ_CACHE:-${HOME}/.cache/iree-fuzz-cache}"
FUZZ_CORPUS_DIR="${FUZZ_CACHE_DIR}/${SOURCE_DIR}/${FUZZ_NAME}/corpus"
if [[ -d "${FUZZ_CORPUS_DIR}" ]]; then
local_corpus_count=$(find "${FUZZ_CORPUS_DIR}" -maxdepth 1 -type f 2>/dev/null | wc -l)
iree_info "Fuzz corpus: ${local_corpus_count} files in ${FUZZ_CORPUS_DIR}"
else
iree_warn "No fuzz corpus found at ${FUZZ_CORPUS_DIR}"
iree_warn "Run iree-bazel-fuzz ${FUZZ_TARGET} first to build a corpus"
FUZZ_TARGET=""
FUZZ_CORPUS_DIR=""
fi
else
iree_warn "Fuzz target ${FUZZ_TARGET} not found, skipping"
FUZZ_TARGET=""
fi
fi
# Build all targets with coverage instrumentation.
iree_info "Building with coverage instrumentation..."
iree_bazel_build_default_configs
BUILD_CMD_ARGS=("${IREE_BAZEL_DEFAULT_CONFIGS[@]}" "${COVERAGE_COPT[@]}" "${FILTERED_BAZEL_ARGS[@]}")
if iree_is_dry_run; then
iree_info "Would build: ${BUILD_TARGETS[*]}"
iree_info "Build args: ${BUILD_CMD_ARGS[*]}"
exit 0
fi
BAZEL_BIN=$(iree_get_bazel_command)
BUILD_LOG=$(mktemp /tmp/iree-cov-build.XXXXXX)
if ! "${BAZEL_BIN}" build "${BUILD_CMD_ARGS[@]}" "${BUILD_TARGETS[@]}" >"${BUILD_LOG}" 2>&1; then
tail -20 "${BUILD_LOG}"
rm -f "${BUILD_LOG}"
iree_error "Coverage build failed"
exit 1
fi
tail -5 "${BUILD_LOG}"
rm -f "${BUILD_LOG}"
# Get binary paths.
TEST_BINARY=$(iree_bazel_get_binary_path "${TARGET}" "${BUILD_CMD_ARGS[@]}")
if [[ -z "${TEST_BINARY}" ]] || [[ ! -x "${TEST_BINARY}" ]]; then
iree_error "Could not find test binary for ${TARGET}"
exit 1
fi
FUZZ_BINARY=""
if [[ -n "${FUZZ_TARGET}" ]]; then
FUZZ_BINARY=$(iree_bazel_get_binary_path "${FUZZ_TARGET}" "${BUILD_CMD_ARGS[@]}")
if [[ -z "${FUZZ_BINARY}" ]] || [[ ! -x "${FUZZ_BINARY}" ]]; then
iree_warn "Could not find fuzz binary for ${FUZZ_TARGET}, skipping"
FUZZ_BINARY=""
fi
fi
# Set up temp directory for profiling data.
PROF_DIR=$(mktemp -d /tmp/iree-coverage.XXXXXX)
trap "rm -rf '${PROF_DIR}'" EXIT
# Run the test binary with profiling.
iree_info "Running test: ${TARGET_NAME}..."
LLVM_PROFILE_FILE="${PROF_DIR}/test.profraw" "${TEST_BINARY}" >/dev/null 2>&1
TEST_EXIT=$?
if [[ ${TEST_EXIT} -ne 0 ]]; then
iree_warn "Test exited with code ${TEST_EXIT} (coverage may be partial)"
fi
# Collect profraw files and binary objects for llvm-cov.
PROFRAW_FILES=("${PROF_DIR}/test.profraw")
OBJECT_ARGS=("${TEST_BINARY}")
# Run the fuzzer over its corpus if available.
if [[ -n "${FUZZ_BINARY}" ]] && [[ -n "${FUZZ_CORPUS_DIR}" ]]; then
iree_info "Running fuzzer over corpus..."
LLVM_PROFILE_FILE="${PROF_DIR}/fuzz.profraw" "${FUZZ_BINARY}" "${FUZZ_CORPUS_DIR}" -runs=0 >/dev/null 2>&1 || true
if [[ -f "${PROF_DIR}/fuzz.profraw" ]]; then
PROFRAW_FILES+=("${PROF_DIR}/fuzz.profraw")
OBJECT_ARGS+=("-object" "${FUZZ_BINARY}")
fi
fi
# Merge profiles.
iree_info "Merging profiles..."
if ! llvm-profdata merge -sparse "${PROFRAW_FILES[@]}" -o "${PROF_DIR}/merged.profdata" 2>/dev/null; then
iree_error "llvm-profdata merge failed (is llvm-profdata installed?)"
exit 1
fi
# Discover source files for coverage filtering.
#
# Convention: foo_test tests foo.c (and possibly foo_impl.c, foo_internal.c).
# We include the test source itself plus any same-directory source files
# whose name starts with the base name (target name without _test suffix).
#
# Bazel stores coverage mapping paths with /proc/self/cwd/ prefix (the
# sandbox root), so we must use that prefix for llvm-cov source matching.
iree_info "Discovering source files..."
SOURCE_FILES=()
# The test source file itself.
for ext in .cc .cpp .c; do
CANDIDATE="${SOURCE_DIR}/${TARGET_NAME}${ext}"
if [[ -f "${IREE_BAZEL_WORKTREE_DIR}/${CANDIDATE}" ]]; then
SOURCE_FILES+=("/proc/self/cwd/${CANDIDATE}")
break
fi
done
# Source files for the library under test: strip _test suffix and find
# matching source files in the same directory. Excludes _test and _fuzz
# files since those aren't library sources.
BASE_NAME="${TARGET_NAME%_test}"
if [[ "${BASE_NAME}" != "${TARGET_NAME}" ]]; then
for candidate in "${IREE_BAZEL_WORKTREE_DIR}/${SOURCE_DIR}/${BASE_NAME}"*.c \
"${IREE_BAZEL_WORKTREE_DIR}/${SOURCE_DIR}/${BASE_NAME}"*.cc \
"${IREE_BAZEL_WORKTREE_DIR}/${SOURCE_DIR}/${BASE_NAME}"*.cpp; do
if [[ -f "${candidate}" ]]; then
CANDIDATE_NAME="${candidate##*/}"
# Skip test and fuzz files.
case "${CANDIDATE_NAME}" in
*_test.*|*_fuzz.*) continue ;;
esac
REL_PATH="${candidate#${IREE_BAZEL_WORKTREE_DIR}/}"
SOURCE_FILES+=("/proc/self/cwd/${REL_PATH}")
fi
done
fi
if [[ ${#SOURCE_FILES[@]} -eq 0 ]]; then
iree_warn "No source files discovered, showing all coverage"
else
# Print just filenames for readability.
BASENAMES=()
for src in "${SOURCE_FILES[@]}"; do BASENAMES+=("${src##*/}"); done
iree_info "Source files: ${BASENAMES[*]}"
fi
# Generate report.
echo ""
if ! llvm-cov report "${OBJECT_ARGS[0]}" "${OBJECT_ARGS[@]:1}" \
-instr-profile="${PROF_DIR}/merged.profdata" \
"${SOURCE_FILES[@]}" 2>/dev/null; then
iree_error "llvm-cov report failed (is llvm-cov installed?)"
exit 1
fi
# Generate HTML report if requested.
if [[ -n "${COVERAGE_HTML_DIR}" ]]; then
mkdir -p "${COVERAGE_HTML_DIR}"
iree_info "Generating HTML report in ${COVERAGE_HTML_DIR}..."
llvm-cov show "${OBJECT_ARGS[0]}" "${OBJECT_ARGS[@]:1}" \
-instr-profile="${PROF_DIR}/merged.profdata" \
"${SOURCE_FILES[@]}" \
-format=html -output-dir="${COVERAGE_HTML_DIR}" 2>/dev/null
iree_info "HTML report: ${COVERAGE_HTML_DIR}/index.html"
fi
exit 0
fi
# =============================================================================
# Normal test mode
# =============================================================================
# Build tag filters based on environment.
TAG_FILTERS="-nodocker"
BUILD_FILTERS=""
# Driver filtering is controlled by configured.bazelrc (written by iree-bazel-configure).
# No runtime env var checks - configuration happens once during configure.
# Build the command with default configs before user args.
iree_bazel_build_default_configs
BAZEL_BIN=$(iree_get_bazel_command "${WATCH_MODE}")
CMD=("${BAZEL_BIN}" test "${IREE_BAZEL_DEFAULT_CONFIGS[@]}" --keep_going
--test_tag_filters="${TAG_FILTERS}"
--build_tag_filters="${BUILD_FILTERS}"
"${TARGETS[@]}"
"${BAZEL_ARGS[@]}")
# Execute or show.
if iree_is_verbose || iree_is_dry_run; then
iree_info "Command: ${CMD[*]}"
fi
if iree_is_dry_run; then
exit 0
fi
iree_info "Testing ${TARGETS[*]}"
iree_info "Tag filters: ${TAG_FILTERS}"
exec "${CMD[@]}"