#!/bin/sh

set -eu

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
TMPDIR_ROOT=${TMPDIR:-/tmp}
WORK_DIR=$(mktemp -d "${TMPDIR_ROOT}/virtiofs-bench-transcript.XXXXXX")
trap 'rm -rf "$WORK_DIR"' EXIT HUP INT TERM

BIN="$WORK_DIR/virtiofs_bench"
"${CXX:-c++}" -Wall -Wextra -Werror -O2 -std=c++17 -pthread \
    "$SCRIPT_DIR/virtiofs_bench.cc" -o "$BIN"

check_transcript() {
    awk '
        function value(prefix,    i) {
            for (i = 1; i <= NF; ++i) {
                if (index($i, prefix "=") == 1) {
                    return substr($i, length(prefix) + 2)
                }
            }
            return "__missing__"
        }
        function decimal(name, v) {
            v = value(name)
            return v != "__missing__" && v ~ /^[0-9]+$/
        }
        $1 == "phase" {
            phases++
            if (value("workload") == "__missing__" ||
                value("dataset") == "__missing__" ||
                value("phase") == "__missing__" ||
                (value("event") != "begin" && value("event") != "end") ||
                !decimal("pid") || !decimal("monotonic_us") || !decimal("elapsed_us") ||
                !decimal("offset") || !decimal("requested") ||
                value("returned") !~ /^-?[0-9]+$/ || !decimal("errno") ||
                value("run_id") == "__missing__") {
                bad = 1
            }
            next
        }
        $1 == "result" {
            results++
            checksum = value("checksum")
            if (value("workload") == "__missing__" ||
                (value("status") != "ok" && value("status") != "fail") ||
                !decimal("errno") || !decimal("elapsed_us") || !decimal("bytes") ||
                !decimal("ops") || !decimal("syscalls") || !decimal("short_io") ||
                !decimal("eintr") || !decimal("data_loop_us") || !decimal("fsync_us") ||
                !decimal("close_us") || !decimal("end_to_end_us") ||
                !decimal("process_cpu_us") ||
                checksum !~ /^[0-9a-f]+$/ || length(checksum) != 16) {
                bad = 1
            }
            workload = value("workload")
            data_loop = value("data_loop_us") + 0
            fsync_time = value("fsync_us") + 0
            close_time = value("close_us") + 0
            end_to_end = value("end_to_end_us") + 0
            elapsed = value("elapsed_us") + 0
            if (workload == "sequential_write" || workload == "prepare") {
                if (data_loop == 0 || end_to_end == 0 || elapsed != data_loop ||
                    end_to_end < data_loop + fsync_time + close_time) {
                    bad = 1
                }
            } else if (data_loop != 0 || fsync_time != 0 || close_time != 0 ||
                       end_to_end != 0) {
                bad = 1
            }
        }
        END { exit bad || phases == 0 || results != 1 }
    '
}

check_result_only_transcript() {
    awk '
        $1 == "phase" { phases++ }
        $1 == "result" {
            results++
            for (i = 1; i <= NF; ++i) {
                if ($i ~ /^(data_loop_us|fsync_us|close_us|end_to_end_us)=[0-9]+$/) {
                    timings++
                }
            }
        }
        END { exit phases != 0 || results != 1 || timings != 4 }
    '
}

check_readdir_failure_result() {
    expected_errno=$1
    awk -v expected_errno="$expected_errno" '
        function value(prefix,    i) {
            for (i = 1; i <= NF; ++i) {
                if (index($i, prefix "=") == 1) {
                    return substr($i, length(prefix) + 2)
                }
            }
            return "__missing__"
        }
        $1 == "result" {
            seen++
            if (value("workload") != "readdir_scan" || value("status") != "fail" ||
                value("errno") != expected_errno || value("bytes") != "0" ||
                value("ops") != "0") {
                bad = 1
            }
        }
        END { exit bad || seen != 1 }
    '
}

MOUNT="$WORK_DIR/mount"
mkdir "$MOUNT"

VIRTIOFS_BENCH_RUN_ID=readdir_prepare \
    "$BIN" --mount "$MOUNT" --workload readdir_prepare --path readdir_schema \
    --files 2048 >"$WORK_DIR/readdir-prepare.log" 2>&1
check_transcript <"$WORK_DIR/readdir-prepare.log"

VIRTIOFS_BENCH_RUN_ID=readdir_scan \
    "$BIN" --mount "$MOUNT" --workload readdir_scan --path readdir_schema \
    --files 2048 --iterations 3 >"$WORK_DIR/readdir-scan.log" 2>&1
check_transcript <"$WORK_DIR/readdir-scan.log"
awk '
    function value(prefix,    i) {
        for (i = 1; i <= NF; ++i) {
            if (index($i, prefix "=") == 1) {
                return substr($i, length(prefix) + 2)
            }
        }
        return "__missing__"
    }
    $1 == "result" {
        seen++
        if (value("workload") != "readdir_scan" || value("status") != "ok" ||
            value("files") != "2048" || value("iterations") != "3" ||
            value("ops") != "6144" || value("syscalls") !~ /^[1-9][0-9]*$/ ||
            value("syscalls") + 0 <= 6 ||
            value("process_cpu_us") !~ /^[0-9]+$/ ||
            value("checksum") !~ /^[0-9a-f]+$/ || length(value("checksum")) != 16) {
            bad = 1
        }
    }
    END { exit bad || seen != 1 }
' "$WORK_DIR/readdir-scan.log"

# A pre-scan quiescence timeout must still terminate the workload transcript
# with one machine-readable failure result for benchmark parsers.
: >"$WORK_DIR/non-quiescent.stats"
set +e
VIRTIOFS_BENCH_RUN_ID=readdir_timeout \
    VIRTIOFS_STATS_PATH="$WORK_DIR/non-quiescent.stats" \
    "$BIN" --mount "$MOUNT" --workload readdir_scan --path readdir_schema \
    --files 2048 --iterations 3 >"$WORK_DIR/readdir-timeout.log" 2>&1
readdir_timeout_rc=$?
set -e
if [ "$readdir_timeout_rc" -eq 0 ]; then
    echo "readdir quiescence timeout unexpectedly succeeded" >&2
    exit 1
fi
check_result_only_transcript <"$WORK_DIR/readdir-timeout.log"
check_readdir_failure_result 110 <"$WORK_DIR/readdir-timeout.log"

# Keep quiescence sampling independent from stats collection so that a stable
# transport with an unavailable baseline exercises the adjacent EIO path.
cat >"$WORK_DIR/quiescent.stats" <<'EOF'
[fuse]
request_queue_current 0
dispatch_current 0
processing_current 0
background_inflight_current 0
read_reservation_current 0
requests_queued_total 0
requests_dequeued_total 0
requests_replied_ok_total 0
requests_replied_err_total 0
requests_aborted_total 0
[virtiofs]
inflight_current 0
hiprio_inflight_current 0
request_inflight_current 0
queue_full_blocked_current 0
reply_retained_current 0
bridge_submitted_total 0
bridge_completed_total 0
direct_read_requested_bytes_total 0
direct_read_completed_bytes_total 0
EOF
set +e
VIRTIOFS_BENCH_RUN_ID=readdir_baseline_unavailable \
    VIRTIOFS_QUIESCENCE_PATH="$WORK_DIR/quiescent.stats" \
    VIRTIOFS_STATS_PATH="$WORK_DIR/missing.stats" \
    "$BIN" --mount "$MOUNT" --workload readdir_scan --path readdir_schema \
    --files 2048 --iterations 3 >"$WORK_DIR/readdir-baseline-unavailable.log" 2>&1
readdir_baseline_rc=$?
set -e
if [ "$readdir_baseline_rc" -eq 0 ]; then
    echo "readdir missing baseline unexpectedly succeeded" >&2
    exit 1
fi
check_result_only_transcript <"$WORK_DIR/readdir-baseline-unavailable.log"
check_readdir_failure_result 5 <"$WORK_DIR/readdir-baseline-unavailable.log"

VIRTIOFS_BENCH_RUN_ID=readdir_cleanup \
    "$BIN" --mount "$MOUNT" --workload readdir_cleanup --path readdir_schema \
    --files 2048 >"$WORK_DIR/readdir-cleanup.log" 2>&1
check_transcript <"$WORK_DIR/readdir-cleanup.log"
if [ -e "$MOUNT/.virtiofs_bench_readdir_schema" ]; then
    echo "readdir cleanup left its dataset directory" >&2
    exit 1
fi

VIRTIOFS_BENCH_RUN_ID=host_prepare VIRTIOFS_BENCH_CACHE_MODE=warm \
    "$BIN" --mount "$MOUNT" --workload prepare --path schema_test \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/prepare.log" 2>&1
check_transcript <"$WORK_DIR/prepare.log"

VIRTIOFS_BENCH_PHASE_MARKERS=0 VIRTIOFS_BENCH_RUN_ID=host_perf \
    "$BIN" --mount "$MOUNT" --workload prepare --path perf_schema_test \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/perf.log" 2>&1
check_result_only_transcript <"$WORK_DIR/perf.log"

VIRTIOFS_BENCH_RUN_ID=host_read VIRTIOFS_BENCH_CACHE_MODE=warm \
    "$BIN" --mount "$MOUNT" --workload sequential_read --path schema_test \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/read.log" 2>&1
check_transcript <"$WORK_DIR/read.log"

VIRTIOFS_BENCH_RUN_ID=host_write VIRTIOFS_BENCH_CACHE_MODE=warm \
    "$BIN" --mount "$MOUNT" --workload sequential_write --path schema_test \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/write.log" 2>&1
check_transcript <"$WORK_DIR/write.log"

# Timing fields are a semantic schema, not just four decimal strings.
sed 's/ data_loop_us=[^ ]*/ data_loop_us=1/' "$WORK_DIR/read.log" \
    >"$WORK_DIR/malformed-read-timing.log"
if check_transcript <"$WORK_DIR/malformed-read-timing.log"; then
    echo "non-write timing unexpectedly accepted" >&2
    exit 1
fi
sed 's/ elapsed_us=[^ ]*/ elapsed_us=0/' "$WORK_DIR/write.log" \
    >"$WORK_DIR/malformed-write-timing.log"
if check_transcript <"$WORK_DIR/malformed-write-timing.log"; then
    echo "inconsistent write timing unexpectedly accepted" >&2
    exit 1
fi

# A missing mandatory result field must be rejected by the host parser.
sed 's/ syscalls=[^ ]*//' "$WORK_DIR/read.log" >"$WORK_DIR/malformed.log"
if check_transcript <"$WORK_DIR/malformed.log"; then
    echo "malformed transcript unexpectedly accepted" >&2
    exit 1
fi

# Argument parsing must reject path traversal and unknown dispatch names before
# any workload is run.
set +e
"$BIN" --mount "$MOUNT" --workload prepare --path ../escape \
    --file-size 4096 --block-size 4096 >"$WORK_DIR/bad-path.log" 2>&1
bad_path_rc=$?
"$BIN" --mount "$MOUNT" --workload does_not_exist \
    >"$WORK_DIR/bad-workload.log" 2>&1
bad_workload_rc=$?
set -e
if [ "$bad_path_rc" -ne 2 ]; then
    echo "unsafe dataset path returned $bad_path_rc, expected parse failure 2" >&2
    exit 1
fi
if [ "$bad_workload_rc" -ne 2 ]; then
    echo "unknown workload returned $bad_workload_rc, expected parse failure 2" >&2
    exit 1
fi
if find "$WORK_DIR" -name '*escape*' -print | grep -q .; then
    echo "unsafe dataset path changed the filesystem" >&2
    exit 1
fi

expect_parse_failure() {
    name=$1
    shift
    set +e
    "$BIN" --mount "$MOUNT" --workload prepare --path "parse_${name}" "$@" \
        >"$WORK_DIR/parse-${name}.log" 2>&1
    rc=$?
    set -e
    if [ "$rc" -ne 2 ]; then
        echo "$name returned $rc, expected parse failure 2" >&2
        exit 1
    fi
}

# strtoull accepts signs and can turn -1 into SIZE_MAX. Resource-sized
# arguments must reject both forms, as well as values beyond their documented
# workload limits, before allocating or creating a dataset.
expect_parse_failure negative --file-size -1
expect_parse_failure plus --block-size +4096
expect_parse_failure size_max --file-size 18446744073709551615
expect_parse_failure file_too_large --file-size 1073741825
expect_parse_failure block_too_large --block-size 16777217
expect_parse_failure too_many_files --files 1000001
expect_parse_failure too_many_workers --workers 1025
expect_parse_failure too_many_iterations --iterations 100000001

DATASET_DIR="$MOUNT/.virtiofs_bench_schema_test"
cp "$DATASET_DIR/seq.dat" "$WORK_DIR/old-seq.dat"
cp "$DATASET_DIR/manifest.v1" "$WORK_DIR/old-manifest.v1"
set +e
VIRTIOFS_BENCH_TEST_FAULT=before_manifest_publish \
    "$BIN" --mount "$MOUNT" --workload prepare --path schema_test --seed 99 \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/rollback.log" 2>&1
rollback_rc=$?
set -e
if [ "$rollback_rc" -eq 0 ] || ! cmp -s "$DATASET_DIR/seq.dat" "$WORK_DIR/old-seq.dat" || \
    ! cmp -s "$DATASET_DIR/manifest.v1" "$WORK_DIR/old-manifest.v1"; then
    echo "failed prepare did not restore the old valid dataset" >&2
    exit 1
fi
VIRTIOFS_BENCH_RUN_ID=rollback_verify \
    "$BIN" --mount "$MOUNT" --workload sequential_read --path schema_test \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/rollback-verify.log" 2>&1
check_transcript <"$WORK_DIR/rollback-verify.log"

# Force one prepare to pause after publishing seq.dat but before publishing its
# manifest. A second prepare for the same fixed dataset must wait on the stable
# per-dataset lock and then publish a complete pair of its own.
for seed in 101 202; do
    VIRTIOFS_BENCH_RUN_ID="expected_${seed}" \
        "$BIN" --mount "$MOUNT" --workload prepare --path "expected_${seed}" --seed "$seed" \
        --file-size 16384 --block-size 4096 >"$WORK_DIR/expected-${seed}.log" 2>&1
done
VIRTIOFS_BENCH_RUN_ID=concurrent_a VIRTIOFS_BENCH_TEST_FAULT=delay_before_manifest_publish \
    "$BIN" --mount "$MOUNT" --workload prepare --path concurrent_prepare --seed 101 \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/concurrent-a.log" 2>&1 &
prepare_a_pid=$!
concurrent_dir="$MOUNT/.virtiofs_bench_concurrent_prepare"
published_a=0
for unused in $(seq 1 200); do
    if [ -f "$concurrent_dir/seq.dat" ] && \
        cmp -s "$concurrent_dir/seq.dat" "$MOUNT/.virtiofs_bench_expected_101/seq.dat"; then
        published_a=1
        break
    fi
    sleep 0.01
done
if [ "$published_a" -ne 1 ]; then
    kill "$prepare_a_pid" 2>/dev/null || true
    wait "$prepare_a_pid" 2>/dev/null || true
    echo "first concurrent prepare did not enter the publish window" >&2
    exit 1
fi
VIRTIOFS_BENCH_RUN_ID=concurrent_b \
    "$BIN" --mount "$MOUNT" --workload prepare --path concurrent_prepare --seed 202 \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/concurrent-b.log" 2>&1 &
prepare_b_pid=$!
wait "$prepare_a_pid"
wait "$prepare_b_pid"
check_transcript <"$WORK_DIR/concurrent-a.log"
check_transcript <"$WORK_DIR/concurrent-b.log"
if ! cmp -s "$concurrent_dir/seq.dat" "$MOUNT/.virtiofs_bench_expected_202/seq.dat" || \
    ! cmp -s "$concurrent_dir/manifest.v1" \
        "$MOUNT/.virtiofs_bench_expected_202/manifest.v1"; then
    echo "concurrent prepare left a mixed data/manifest pair" >&2
    exit 1
fi
if find "$concurrent_dir" -maxdepth 1 \( -name '.seq.tmp.*' -o -name '.manifest.tmp.*' \
    -o -name '.seq.backup.*' -o -name '.manifest.backup.*' \) -print | grep -q .; then
    echo "concurrent prepare left transaction artifacts" >&2
    exit 1
fi
VIRTIOFS_BENCH_RUN_ID=concurrent_verify \
    "$BIN" --mount "$MOUNT" --workload sequential_read --path concurrent_prepare --seed 202 \
    --file-size 16384 --block-size 4096 >"$WORK_DIR/concurrent-verify.log" 2>&1
check_transcript <"$WORK_DIR/concurrent-verify.log"

expect_manifest_failure() {
    name=$1
    set +e
    "$BIN" --mount "$MOUNT" --workload sequential_read --path schema_test \
        --file-size 16384 --block-size 4096 >"$WORK_DIR/manifest-${name}.log" 2>&1
    rc=$?
    set -e
    if [ "$rc" -eq 0 ]; then
        echo "malformed manifest $name unexpectedly accepted" >&2
        exit 1
    fi
}

cp "$WORK_DIR/old-manifest.v1" "$DATASET_DIR/manifest.v1"
printf 'trailing_token\n' >>"$DATASET_DIR/manifest.v1"
expect_manifest_failure trailing

cp "$WORK_DIR/old-manifest.v1" "$DATASET_DIR/manifest.v1"
printf 'size 16384\n' >>"$DATASET_DIR/manifest.v1"
expect_manifest_failure duplicate

sed 's/^size .*/size -1/' "$WORK_DIR/old-manifest.v1" >"$DATASET_DIR/manifest.v1"
expect_manifest_failure negative

sed 's/^size .*/size 18446744073709551616/' "$WORK_DIR/old-manifest.v1" \
    >"$DATASET_DIR/manifest.v1"
expect_manifest_failure overflow

# Cleanup is control-plane recovery: it must not depend on parsing a corrupt
# manifest, must remove transaction leftovers, and must be idempotent.
touch "$DATASET_DIR/.seq.tmp.stale" "$DATASET_DIR/.manifest.tmp.stale" \
    "$DATASET_DIR/.seq.backup.stale" "$DATASET_DIR/.manifest.backup.stale"
VIRTIOFS_BENCH_RUN_ID=cleanup_corrupt \
    "$BIN" --mount "$MOUNT" --workload cleanup --path schema_test \
    >"$WORK_DIR/cleanup.log" 2>&1
check_transcript <"$WORK_DIR/cleanup.log"
if [ -e "$DATASET_DIR" ]; then
    echo "cleanup left known dataset objects behind" >&2
    exit 1
fi
VIRTIOFS_BENCH_RUN_ID=cleanup_again \
    "$BIN" --mount "$MOUNT" --workload cleanup --path schema_test \
    >"$WORK_DIR/cleanup-again.log" 2>&1
check_transcript <"$WORK_DIR/cleanup-again.log"

# The formal parallel-read phase uses one immutable file per worker and starts
# only after every fd, buffer and worker thread is ready.
VIRTIOFS_BENCH_RUN_ID=parallel_prepare \
    "$BIN" --mount "$MOUNT" --workload parallel_read_prepare --path parallel_schema \
    --file-size 16384 --block-size 4096 --workers 4 --iterations 1 \
    >"$WORK_DIR/parallel-prepare.log" 2>&1
check_result_only_transcript <"$WORK_DIR/parallel-prepare.log"
awk '
    function value(prefix,    i) {
        for (i = 1; i <= NF; ++i) {
            if (index($i, prefix "=") == 1) {
                return substr($i, length(prefix) + 2)
            }
        }
        return "__missing__"
    }
    $1 == "result" {
        seen++
        if (value("workload") != "parallel_read_prepare" || value("status") != "ok" ||
            value("errno") != "0" || value("bytes") != "65536" || value("ops") != "4") {
            bad = 1
        }
    }
    END { exit bad || seen != 1 }
' "$WORK_DIR/parallel-prepare.log"
PARALLEL_DIR="$MOUNT/.virtiofs_bench_parallel_schema"
if [ "$(find "$PARALLEL_DIR" -maxdepth 1 -type f -name 'parallel_read_worker_*.dat' | wc -l)" \
    -ne 4 ]; then
    echo "parallel prepare did not create one file per worker" >&2
    exit 1
fi
find "$PARALLEL_DIR" -maxdepth 1 -type f -name 'parallel_read_worker_*.dat' -print0 \
    | sort -z | xargs -0 sha256sum >"$WORK_DIR/parallel-before.sha256"

for run in first second; do
    VIRTIOFS_BENCH_RUN_ID="parallel_${run}" \
        "$BIN" --mount "$MOUNT" --workload parallel_read --path parallel_schema \
        --file-size 16384 --block-size 4096 --workers 4 --iterations 1 \
        >"$WORK_DIR/parallel-${run}.log" 2>&1
    check_result_only_transcript <"$WORK_DIR/parallel-${run}.log"
    awk '
        function value(prefix,    i) {
            for (i = 1; i <= NF; ++i) {
                if (index($i, prefix "=") == 1) {
                    return substr($i, length(prefix) + 2)
                }
            }
            return "__missing__"
        }
        $1 == "result" {
            seen++
            if (value("workload") != "parallel_read" || value("status") != "ok" ||
                value("errno") != "0" || value("bytes") != "65536" ||
                value("ops") != "16" || value("syscalls") != "20" ||
                value("short_io") != "0" || value("eintr") != "0") {
                bad = 1
            }
        }
        END { exit bad || seen != 1 }
    ' "$WORK_DIR/parallel-${run}.log"
done
first_checksum=$(sed -n 's/.* checksum=\([^ ]*\).*/\1/p' "$WORK_DIR/parallel-first.log")
second_checksum=$(sed -n 's/.* checksum=\([^ ]*\).*/\1/p' "$WORK_DIR/parallel-second.log")
if [ -z "$first_checksum" ] || [ "$first_checksum" != "$second_checksum" ] || \
    ! grep -q ' bytes=65536 ' "$WORK_DIR/parallel-first.log"; then
    echo "parallel read aggregate result is not deterministic" >&2
    exit 1
fi
find "$PARALLEL_DIR" -maxdepth 1 -type f -name 'parallel_read_worker_*.dat' -print0 \
    | sort -z | xargs -0 sha256sum >"$WORK_DIR/parallel-after.sha256"
cmp -s "$WORK_DIR/parallel-before.sha256" "$WORK_DIR/parallel-after.sha256"

mv "$PARALLEL_DIR/parallel_read_worker_2.dat" "$WORK_DIR/missing-worker.dat"
set +e
"$BIN" --mount "$MOUNT" --workload parallel_read --path parallel_schema \
    --file-size 16384 --block-size 4096 --workers 4 --iterations 1 \
    >"$WORK_DIR/parallel-missing.log" 2>&1
parallel_missing_rc=$?
set -e
if [ "$parallel_missing_rc" -eq 0 ]; then
    echo "parallel read unexpectedly accepted a missing worker file" >&2
    exit 1
fi
check_result_only_transcript <"$WORK_DIR/parallel-missing.log"
awk '
    function value(prefix,    i) {
        for (i = 1; i <= NF; ++i) {
            if (index($i, prefix "=") == 1) {
                return substr($i, length(prefix) + 2)
            }
        }
        return "__missing__"
    }
    $1 == "result" {
        seen++
        if (value("workload") != "parallel_read" || value("status") != "fail" ||
            value("errno") != "2" || value("bytes") != "0" || value("ops") != "0" ||
            value("syscalls") != "0") {
            bad = 1
        }
    }
    END { exit bad || seen != 1 }
' "$WORK_DIR/parallel-missing.log"
mv "$WORK_DIR/missing-worker.dat" "$PARALLEL_DIR/parallel_read_worker_2.dat"

set +e
"$BIN" --mount "$MOUNT" --workload parallel_read --path parallel_schema \
    --file-size 67108865 --block-size 4096 --workers 4 --iterations 1 \
    >"$WORK_DIR/parallel-memory-limit.log" 2>&1
parallel_memory_rc=$?
set -e
if [ "$parallel_memory_rc" -ne 2 ]; then
    echo "parallel read memory limit returned $parallel_memory_rc, expected parse failure 2" >&2
    exit 1
fi
set +e
"$BIN" --mount "$MOUNT" --workload parallel_read --path parallel_schema \
    --file-size 16384 --block-size 4096 --workers 0 --iterations 1 \
    >"$WORK_DIR/parallel-zero-workers.log" 2>&1
parallel_zero_workers_rc=$?
set -e
if [ "$parallel_zero_workers_rc" -ne 2 ]; then
    echo "parallel read zero workers returned $parallel_zero_workers_rc, expected parse failure 2" >&2
    exit 1
fi

VIRTIOFS_BENCH_RUN_ID=parallel_cleanup \
    "$BIN" --mount "$MOUNT" --workload cleanup --path parallel_schema \
    >"$WORK_DIR/parallel-cleanup.log" 2>&1
check_transcript <"$WORK_DIR/parallel-cleanup.log"
if [ -e "$PARALLEL_DIR" ]; then
    echo "parallel cleanup left its dataset behind" >&2
    exit 1
fi
echo "virtiofs benchmark transcript tests: PASS"
