Files
greptimedb/tests-fuzz

Fuzz Test for GreptimeDB

Setup

  1. Install the fuzz cli first.
cargo install cargo-fuzz

Note: cargo-fuzz instruments targets with -Zsanitizer=fuzzer, so fuzz targets must be built with a nightly toolchain (the workspace default toolchain does not need to be nightly — make fuzz / make fuzz-ls invoke cargo +nightly for you; CI pins its own nightly in FUZZ_RUST_TOOLCHAIN).

  1. Start GreptimeDB
  2. Copy the .env.example, which is at project root, to .env and change the values on need.

For stable fuzz tests

Set the GreptimeDB MySQL address.

GT_MYSQL_ADDR = localhost:4002

For unstable fuzz tests

Set the binary path of the GreptimeDB:

GT_FUZZ_BINARY_PATH = /path/to/

Change the instance root directory(the default value: /tmp/unstable_greptime/)

GT_FUZZ_INSTANCE_ROOT_DIR = /path/to/

Run

  1. List all fuzz targets
cargo fuzz list --fuzz-dir tests-fuzz
  1. Run a fuzz target.
cargo fuzz run fuzz_create_table --fuzz-dir tests-fuzz -D -s none

CI Corpus Persistence

The shared .github/actions/fuzz-test action restores inputs before running the selected targets and saves the updated corpus afterward, including when a target fails. Cache failures are best-effort and do not change the fuzz result. Crash files, diagnostic artifacts, and fail-fast behavior are unchanged.

Each target reads and writes its own tests-fuzz/corpus/<target> directory. Both prebuilt executables and the cargo fuzz fallback receive that directory explicitly. For local runs of .github/scripts/run-fuzz-targets.sh, set FUZZ_CORPUS_ROOT to use a different corpus root.

The selected target directories are cached together to preserve the existing job grouping. The stable key prefix identifies the sorted, deduplicated target set, not a source hash, so code changes do not invalidate existing inputs. Each job saves a new immutable entry, distinguished by run ID, attempt, job, and matrix index. Restore uses the latest accessible entry matching the prefix. Changing the target set starts a different cache; corpora are not shared between different target sets. Parallel jobs may start from the same entry, but their new inputs are not merged automatically.

The restore step logs the matched key (including prefix matches) or a cache miss. GitHub's cache access restrictions still apply: PR caches are scoped to that PR, while a default-branch cache can seed other PRs. The existing scheduled workflow does not run fuzz jobs; a maintainer can populate the default-branch corpus through the existing manual fuzz workflow. This change does not add scheduling, target selection, or corpus pruning. Rolling entries remain subject to GitHub's cache limits and eviction.

Validation

The orchestration tests use mock fuzz executables and do not build Rust or start GreptimeDB. They cover both runner paths, restored inputs, separate target directories, retained inputs after failure, cache identity, and invalid target names. Run them on Linux with Bash, jq, and GNU coreutils:

bash .github/scripts/run-fuzz-targets-test.sh

These tests do not exercise the hosted GitHub cache service. To verify that service, inspect two fuzz workflow runs on the same PR: the first should save an entry, and the second should report its restored key and load a nonempty corpus. Also verify that cache save still runs after a target failure, without turning that failure into a successful fuzz result.

To compare useful coverage, use the same fuzz executable, GreptimeDB revision, service configuration, and time budget for empty and restored corpora. Both runner paths enable -print_final_stats=1; the target's fuzz.log retains the libFuzzer coverage and final statistics. Seed the restored corpus with a separate warm-up run (or a downloaded corpus), then compare it with a fresh, empty directory. Keep warm-up time separate from the comparison and repeat trials to account for randomness. Report initial corpus size, final covered edges (cov:), executions, and elapsed time, rather than inferring a coverage improvement merely from a cache hit.

For example, with a prebuilt fuzz_create_table binary and a running standalone service, the following creates separate warm-up, empty, and seeded corpora. Set FUZZ_BIN_DIR to the absolute binary directory and GT_MYSQL_ADDR to the test service address first. All runs use the same executable and 120-second budget; the warm-up is not part of the measured comparison. This creates temporary inputs, logs, and crash files, and executes DDL against the test service.

set -euo pipefail
: "${FUZZ_BIN_DIR:?Set the prebuilt fuzz binary directory}"
: "${GT_MYSQL_ADDR:?Set the test service address}"
comparison_dir="$(mktemp -d -t fuzz-corpus-comparison.XXXXXX)"
for trial in seed cold warm; do
  mkdir -p "${comparison_dir}/${trial}" "${comparison_dir}/artifacts/${trial}"
  if [[ "${trial}" == warm ]]; then
    cp -a "${comparison_dir}/seed/." "${comparison_dir}/warm/"
  fi
  "${FUZZ_BIN_DIR}/fuzz_create_table" "${comparison_dir}/${trial}" \
    -max_total_time=120 -print_final_stats=1 \
    "-artifact_prefix=${comparison_dir}/artifacts/${trial}/" \
    2>&1 | tee "${comparison_dir}/${trial}.log"
done
printf 'Comparison logs and corpora: %s\n' "${comparison_dir}"

Crash Reproduction

If you want to reproduce a crash, you first need to obtain the Base64 encoded code, which usually appears at the end of a crash report, and store it in a file.

Alternatively, if you already have the crash file, you can skip this step.

echo "Base64" > .crash

Print the std::fmt::Debug output for an input.

cargo fuzz fmt fuzz_target .crash --fuzz-dir tests-fuzz -D -s none

Rerun the fuzz test with the input. You can override fuzz input with environment variables. For example, to override fuzz input like:

FuzzInput {
    seed: 6666,
    actions: 175
}

you can run with GT_FUZZ_OVERRIDE_SEED=6666 and GT_FUZZ_OVERRIDE_ACTIONS=175:

GT_FUZZ_OVERRIDE_SEED=6666 GT_FUZZ_OVERRIDE_ACTIONS=175 cargo fuzz run fuzz_target .crash --fuzz-dir tests-fuzz -D -s none

For more details, visit cargo fuzz or run the command cargo fuzz --help.

Repartition Metric Dump Artifacts

For fuzz_repartition_metric_table, dump artifacts are written under one run directory.

  • Table data snapshots: <logical_table>.table-data.csv
  • SQL traces per logical table: <logical_table>.trace.sql
  • Seed metadata: seed.meta

SQL trace behavior:

  • Insert SQL is appended after successful execution with comment fields including started_at_ms and elapsed_ms.
  • Repartition events are broadcast to all logical table trace files with comment fields including action_idx, started_at_ms, elapsed_ms, and SQL text.

Run directory lifecycle:

  • On success, the run directory is cleaned up.
  • On failure, the run directory is retained for CI/local diffing.