Check Flow Coverage
Determine which C source lines in specified files are not covered by Python flow (end-to-end) tests and report the results.
This skill uses gcov/lcov instrumentation and only works with C source files. For Rust
coverage, use the /check-rust-coverage skill instead.
Arguments
<path>: Path to a C source file.<path 1> <path 2>: Multiple C source file paths.
Paths can be relative to the repository root. E.g. src/module.c or src/query.c.
Arguments provided: $ARGUMENTS
Instructions
If no arguments were provided ($ARGUMENTS is empty), stop and ask the user to provide one or
more source file paths. Example usage: /check-flow-coverage src/module.c src/query.c
If the user provides a Rust file path (e.g., under src/redisearch_rs/), stop and redirect
them to use /check-rust-coverage instead — this skill's gcov/lcov pipeline does not capture
Rust coverage data.
Follow these steps in order. Do NOT skip ahead — each step depends on the previous one.
Step 1: Ensure a coverage build exists
Generate a unique marker path so parallel invocations don't collide. This marker is reused in later steps as a freshness check, so it must always be created regardless of whether a rebuild is needed:
COV_MARKER=$(mktemp /tmp/cov_start_marker.XXXXXX)
echo "Using marker: $COV_MARKER"
Note: Shell variables do not persist between tool calls. Capture the printed marker path
from this command's output and substitute it literally (e.g., /tmp/cov_start_marker.a1b2c3)
in all subsequent commands that reference $COV_MARKER.
Check if the coverage build output exists and was built with coverage instrumentation.
The build directory is platform-specific (bin/linux-x64-debug-cov/,
bin/macos-aarch64-debug-cov/, bin/macos-x86_64-debug-cov/, …) — use a glob so the
check works regardless of host:
ls bin/*-debug-cov/search-community/redisearch.so 2>/dev/null && \
find bin/*-debug-cov -name '*.gcno' -print -quit 2>/dev/null
Both checks must pass — the .so must exist AND .gcno files must be present (these are
generated by --coverage and are required for gcov to work). If either is missing, rebuild.
build.sh output can be very large (>60KB) and will get truncated, so save the full log to
a file and only show the tail:
./build.sh COV=1 FORCE 2>&1 | tee /tmp/build_cov.log | tail -20
This compiles C code with --coverage (gcov). The build flavor is debug-cov and artifacts
go under bin/<platform>-debug-cov/.
Step 2: Run Python tests with coverage
IMPORTANT: Always run the full test suite for the coverage measurement.
Do NOT use a pre-existing flow_standalone.info — it may be stale or from a partial run,
which would cause you to report false coverage gaps. Do NOT use targeted tests (TEST=...)
in this step.
Reuse the COV_MARKER from Step 1 as the freshness marker.
(COV=1 automatically resets gcov counters and captures the baseline via prepare_coverage_capture
before running tests, so no manual lcov prep is needed.)
Run all flow tests with coverage enabled. Pipe output through tail to avoid truncation.
Coverage is captured automatically after the run completes, even if some tests fail.
The full test suite may take 10+ minutes — you MUST set a timeout of at least 600000ms (10 min)
on this command, otherwise it will be killed prematurely at the default 2-minute timeout.
PYTEST_LOG=$(mktemp /tmp/pytest_cov.XXXXXX.log)
echo "Using log: $PYTEST_LOG"
# IMPORTANT: Use timeout of 600000ms for this command
./build.sh RUN_PYTEST COV=1 2>&1 | tee "$PYTEST_LOG" | tail -80
After the run, verify that the coverage file was produced and is newer than the start marker. Substitute the literal marker path from Step 1:
if [ -f bin/flow_standalone.info ] && [ bin/flow_standalone.info -nt /tmp/cov_start_marker.XXXXXX ]; then
echo "FRESH — coverage file updated successfully"
ls -la bin/flow_standalone.info
else
echo "STALE or MISSING — coverage capture failed"
ls -la bin/flow_standalone.info 2>/dev/null || echo "File does not exist"
fi
rm -f /tmp/cov_start_marker.XXXXXX
If the file is stale or missing, the coverage build may not be instrumented correctly —
go back to Step 1 and rebuild with COV=1 FORCE.
Step 3: Extract uncovered lines for target files
Parse the lcov info file to find coverage data for the target files:
python3 -c "
import sys, os
info_file = 'bin/flow_standalone.info'
if not os.path.exists(info_file):
print(f'Error: {info_file} not found. Run Step 2 first to generate coverage data.')
sys.exit(1)
target_files = sys.argv[1:]
repo_root = os.getcwd()
targets = []
for t in target_files:
if os.path.isabs(t):
targets.append(os.path.realpath(t))
else:
targets.append(os.path.realpath(os.path.join(repo_root, t)))
with open(info_file) as f:
lines = f.readlines()
target_set = set(targets)
current_file = None
current_real = None
uncovered = {}
file_stats = {}
for line in lines:
line = line.strip()
if line.startswith('SF:'):
current_file = line[3:]
current_real = os.path.realpath(current_file)
elif line.startswith('DA:') and current_real in target_set:
parts = line[3:].split(',')
line_no = int(parts[0])
hit_count = int(parts[1])
target = current_real
if target not in uncovered:
uncovered[target] = []
file_stats[target] = [0, 0]
file_stats[target][1] += 1
if hit_count == 0:
uncovered[target].append(line_no)
else:
file_stats[target][0] += 1
for target in targets:
if target in uncovered:
covered, total = file_stats[target]
pct = (covered / total * 100) if total > 0 else 0
print(f'\n=== {target} ({covered}/{total} lines covered, {pct:.1f}%) ===')
if uncovered[target]:
nums = sorted(uncovered[target])
ranges = []
start = nums[0]
end = nums[0]
for n in nums[1:]:
if n == end + 1:
end = n
else:
ranges.append((start, end))
start = n
end = n
ranges.append((start, end))
for s, e in ranges:
if s == e:
print(f' Line {s}')
else:
print(f' Lines {s}-{e}')
else:
print(' Fully covered!')
else:
print(f'\n=== {target} ===')
print(' No coverage data found. Ensure the file is compiled into the module.')
" $ARGUMENTS
Step 4: Read the uncovered lines
Read the source code at the uncovered line ranges to understand what code paths are not exercised. For files with many scattered uncovered ranges, read large contiguous sections (500-800 lines at a time) rather than making dozens of small reads per range — this is faster and gives better context for understanding code flow across functions. Files over ~1500 lines will need multiple reads due to tool limits; read in 2-4 large chunks covering all the uncovered ranges.
Step 5: Classify and report coverage gaps
Based on the uncovered code paths, classify each gap into one of these categories:
Non-testable gaps — silently discard these without presenting them to the user:
- Disk-only: Code behind
diskSpec/SearchDiskchecks — requires disk indexing enabled. - API-only: Code reachable only through the C module API, not through any
FT.*command syntax. Tested by C++ LLAPI unit tests instead. - Unreachable defensive code: Dead code, abort branches, allocation-failure guards, exhaustive switch default/fallthrough arms that exist only for compiler completeness.
- Disk-only: Code behind
Testable gaps — code paths reachable via standard Redis commands (
FT.CREATE,FT.SEARCH,FT.AGGREGATE,FT.EXPLAIN, etc.) in Python flow tests.
Present the testable coverage gaps to the user as a structured report, grouped by feature or logical area. Many uncovered lines belong to the same feature spread across multiple functions (e.g., creation, evaluation, freeing, and debug-dump of a single query node type). Grouping them helps the user see the full picture.
For each group, include:
- Feature/area name (e.g., "Geometry queries", "Tag prefix edge cases")
- All related line ranges under that feature, with brief descriptions
- Which test file would be the natural home for new tests
This skill is analysis only — do not write tests or modify source code. If the user
wants to improve coverage, direct them to /improve-flow-coverage.
Notes
- The coverage build uses
debug-covflavor with gcov instrumentation for C code. - LTO is disabled in coverage builds.
- Coverage data only tracks C source code under
src/anddeps/thpool/. Test files undertests/are excluded. - This skill does not capture Rust coverage. For Rust coverage, use the
/check-rust-coverageskill. - Always pipe
build.shoutput throughteeto a file and thentail— the raw output can exceed 60KB and will be truncated, but the full log is useful for debugging failures.