Introduces directory-level locking to prevent concurrent index operations from corrupting shared directories, along with explicit APIs for acquiring, probing, and releasing locks. Restructures the index cache crate to use a layered store architecture that eagerly initializes metadata and provides fast hierarchical lookups. Updates dependent modules, test suites, and CLI commands to align with the refactored API surface, and adds an end-to-end smoke test for validation.
127 lines
4.4 KiB
Bash
Executable File
127 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# smoke_test_index2.sh — end-to-end smoke test of `obikmer2 index` (this
|
|
# working tree) + `obikmer query` (the release binary already on PATH).
|
|
#
|
|
# obikmer2 is being rebuilt incrementally from obikmer (see DevDocMD) and
|
|
# for now only implements `index`. This script checks that an index it
|
|
# produces is readable by an already-released, PATH-installed `obikmer`
|
|
# binary — the cross-binary counterpart to scripts/smoke_test_index.sh,
|
|
# which builds and queries with the same (in-tree) `obikmer`.
|
|
#
|
|
# What it does:
|
|
# 1. builds `obikmer2` (debug, via `cargo run`)
|
|
# 2. generates a small deterministic random FASTA
|
|
# 3. runs `obikmer2 index` on it
|
|
# 4. picks a real k-mer from the source sequence, avoiding low-complexity
|
|
# substrings (see scripts/smoke_test_index.sh's own note on this)
|
|
# 5. runs `obikmer query` (PATH binary, not built from this tree) and
|
|
# checks the k-mer round-trips
|
|
# 6. prints total-kmers-indexed and a clear PASS/FAIL, exit code matches
|
|
#
|
|
# Usage:
|
|
# scripts/smoke_test_index2.sh [-k KMER_SIZE] [-m MINIMIZER_SIZE] [-p PARTITIONS] [--keep]
|
|
#
|
|
# --keep leaves the temp directory in place (path printed) instead of
|
|
# deleting it on exit, for manual inspection of a failure.
|
|
|
|
set -euo pipefail
|
|
|
|
K=11
|
|
M=5
|
|
PARTITIONS=4
|
|
KEEP=0
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-k) K="$2"; shift 2 ;;
|
|
-m) M="$2"; shift 2 ;;
|
|
-p) PARTITIONS="$2"; shift 2 ;;
|
|
--keep) KEEP=1; shift ;;
|
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
WORK="$(mktemp -d -t obikmer2_smoke.XXXXXX)"
|
|
|
|
cleanup() {
|
|
if [ "$KEEP" -eq 1 ]; then
|
|
echo "kept: $WORK"
|
|
else
|
|
rm -rf "$WORK"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
fail() {
|
|
echo "FAIL: $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
command -v obikmer >/dev/null 2>&1 || fail "obikmer not found on PATH"
|
|
|
|
# ── 1. generate a small deterministic random FASTA ─────────────────────────
|
|
python3 - "$WORK/test.fasta" "$K" <<'EOF'
|
|
import random, sys
|
|
path, k = sys.argv[1], int(sys.argv[2])
|
|
random.seed(1234)
|
|
bases = "ACGT"
|
|
with open(path, "w") as f:
|
|
for i in range(3):
|
|
seq = "".join(random.choice(bases) for _ in range(300))
|
|
f.write(f">seq{i}\n{seq}\n")
|
|
EOF
|
|
|
|
# ── 2. build + run index (obikmer2, in-tree) ────────────────────────────────
|
|
cd "$REPO_ROOT/src"
|
|
INDEX_LOG="$WORK/index.log"
|
|
if ! cargo run -q -p obikmer2 --bin obikmer2 -- \
|
|
index -k "$K" -m "$M" --theta 0 -p "$PARTITIONS" \
|
|
-o "$WORK/out.idx" "$WORK/test.fasta" > "$INDEX_LOG" 2>&1
|
|
then
|
|
cat "$INDEX_LOG" >&2
|
|
fail "obikmer2 index exited non-zero"
|
|
fi
|
|
|
|
N_KMERS="$(grep -o '[0-9]* total kmers indexed' "$INDEX_LOG" | grep -o '^[0-9]*' || true)"
|
|
[ -n "$N_KMERS" ] || { cat "$INDEX_LOG" >&2; fail "could not find 'N total kmers indexed' in index log"; }
|
|
[ "$N_KMERS" -gt 0 ] || fail "index reports 0 kmers indexed"
|
|
|
|
# ── 3+4. try candidate k-mers spread across the source sequence until one
|
|
# round-trips — see scripts/smoke_test_index.sh's own note: query's
|
|
# entropy filter can reject a genuinely-indexed low-complexity
|
|
# window, that's not a bug, just try the next candidate.
|
|
readarray -t CANDIDATES < <(python3 - "$WORK/test.fasta" "$K" <<'EOF'
|
|
import sys
|
|
path, k = sys.argv[1], int(sys.argv[2])
|
|
with open(path) as f:
|
|
seq = "".join(l.strip() for l in f if not l.startswith(">"))
|
|
for start in range(0, len(seq) - k, 17):
|
|
print(seq[start:start+k])
|
|
EOF
|
|
)
|
|
[ "${#CANDIDATES[@]}" -gt 0 ] || fail "could not extract any candidate k-mer from the source FASTA"
|
|
|
|
QUERY_LOG="$WORK/query.log"
|
|
FOUND=0
|
|
for QUERY_KMER in "${CANDIDATES[@]}"; do
|
|
printf ">q1\n%s\n" "$QUERY_KMER" > "$WORK/query.fasta"
|
|
# PATH binary, deliberately not built from this tree.
|
|
if ! obikmer query "$WORK/out.idx" "$WORK/query.fasta" > "$QUERY_LOG" 2>&1
|
|
then
|
|
cat "$QUERY_LOG" >&2
|
|
fail "obikmer query exited non-zero"
|
|
fi
|
|
if grep -q '"kmer_count":1' "$QUERY_LOG"; then
|
|
FOUND=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$FOUND" -ne 1 ]; then
|
|
cat "$QUERY_LOG" >&2
|
|
fail "no candidate k-mer round-tripped (tried ${#CANDIDATES[@]}) — likely a real regression, not a low-complexity fixture"
|
|
fi
|
|
|
|
echo "PASS: obikmer2 index + PATH obikmer query round-trip OK — $N_KMERS kmers indexed, query k-mer '$QUERY_KMER' found"
|