| #!/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[@]}" |