Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
99cadc3
bundle/fuzz: add create-payload parity fuzz test for terraform vs direct
radakam Jun 23, 2026
495d152
bundle/fuzz: fix lint (intrange, perfsprint) and correct num_workers …
radakam Jun 23, 2026
df8bc36
bundle/fuzz: wire parity tests into CI and harden harness
radakam Jun 23, 2026
a24e58e
bundle/fuzz: rotate nightly seeds and add single-seed reproduction
radakam Jun 24, 2026
51921ab
bundle: force-send num_workers for single-node task clusters
radakam Jun 24, 2026
b2c0239
bundle/fuzz: report nightly parity failures and fix create-path comment
radakam Jun 24, 2026
e70783e
bundle/fuzz: make harness files test-only and add num_workers regress…
radakam Jun 24, 2026
a69b574
bundle/fuzz: make the whole package test-only and harden parity repor…
radakam Jun 24, 2026
11dd172
bundle/fuzz: fix lint (stringsseq, testifylint) in paritySeeds
radakam Jun 25, 2026
36e287f
bundle/fuzz: fix nightly issue dedup and document paritySeeds test
radakam Jun 25, 2026
26a29c1
bundle/fuzz: document divergences instead of fixing them
radakam Jun 25, 2026
ae9f26d
bundle/fuzz: narrow num_workers ignore and tidy parity harness
radakam Jun 26, 2026
45cfba6
bundle: force-send num_workers for single-node task clusters
radakam Jun 26, 2026
6cb3060
bundle/fuzz: replace terraform/direct parity with invariant testing
radakam Jun 26, 2026
6325826
acceptance: replace bundle/fuzz parity with schema-driven invariant f…
radakam Jun 29, 2026
ead1515
acceptance/fuzz: clarify comments and tidy schema fuzz harness
radakam Jun 29, 2026
3e106ce
acceptance/fuzz: shorten and tighten comments
radakam Jun 29, 2026
854f00f
acceptance/fuzz: report nightly failures on the PR instead of an issue
radakam Jun 29, 2026
5c9ea27
acceptance/fuzz: reuse the no_drift invariant check instead of duplic…
radakam Jun 29, 2026
2ea9e37
acceptance/fuzz: skip INPUT_CONFIG_OK marker when deploy is rejected
radakam Jun 29, 2026
b4d1a31
acceptance/fuzz: propagate drift failures so the fuzzer detects them
radakam Jun 30, 2026
81fc0bc
acceptance/fuzz: make the fuzzer run any invariant, not just no_drift
radakam Jun 30, 2026
f5a9407
testserver: round-trip catalog create payload fields
radakam Jun 30, 2026
dfd55e2
acceptance/fuzz: add redeploy, canonical, update, destroy_recreate in…
radakam Jun 30, 2026
c663329
acceptance/fuzz: extract shared invariant prologue and tighten comments
radakam Jul 2, 2026
dab9d30
testserver: move catalog round-trip comment above the added fields
radakam Jul 2, 2026
8f9b3fa
Restructure invariant fuzz tests into per-target scripts
radakam Jul 6, 2026
49813ed
invariant: regenerate out.test.toml for volume_path_job_ref
radakam Jul 7, 2026
ba5309a
fuzz: pin catalog/schema references and emit valid grants
radakam Jul 7, 2026
15b4ea7
fuzz: generate multiple resources with cross-references
radakam Jul 7, 2026
565b0d6
fuzz: keep first resource stable across --resource-count
radakam Jul 7, 2026
5b7837f
fuzz: link every resource to an earlier one, not just one pair
radakam Jul 8, 2026
252e16c
invariant: regenerate fuzz out.test.toml for FUZZ_RESOURCE_COUNT=3
radakam Jul 8, 2026
fa5ef1c
fuzz: stage data fixtures and pin typed fields to cut rejections
radakam Jul 10, 2026
2c1ebcd
fuzz: add mutate mode that perturbs curated invariant configs
radakam Jul 10, 2026
b2ddf69
fuzz: probe dangerous and near-range-end values in generate mode
radakam Jul 13, 2026
c333887
acc/fuzz: skip immutable comment/description in update invariant
radakam Jul 13, 2026
e4bac49
acc/fuzz: treat unmodeled testserver routes as rejections, not failures
radakam Jul 13, 2026
c043cd1
acc/fuzz: bound seed execution by time to separate hangs from slow runs
radakam Jul 13, 2026
92e85b1
acc/fuzz: record per-seed classification and a per-variant tally
radakam Jul 13, 2026
927cff4
acc/fuzz: tighten comments added in this PR
radakam Jul 13, 2026
a35eb84
invariant: regenerate out.test.toml for inherited GOOSOnPR and job_ru…
radakam Jul 14, 2026
5e8a03e
acc/fuzz: real oracle, coverage, less rejection waste, clean truncation
radakam Jul 15, 2026
61d1d9c
Ignore root build/ (fuzz coverage + harness terraform artifacts)
radakam Jul 17, 2026
d80a27d
acc/fuzz: pin parent_path to a valid workspace folder
radakam Jul 22, 2026
691ac59
acc/fuzz: regenerate out.test.toml for instance_pool.yml.tmpl
radakam Jul 22, 2026
3a467ca
Remove canonical, update, and destroy_recreate fuzz invariants
radakam Jul 22, 2026
a72e5f9
acc/fuzz: inject valid optional fields in mutate mode
radakam Jul 23, 2026
3b2b189
acc/fuzz: address review — drop dead edit harness, testserver catalog…
radakam Jul 23, 2026
e6d0ec9
acc/fuzz: drop redeploy invariant and multi-resource fuzzing
radakam Jul 24, 2026
c66e0ef
Merge remote-tracking branch 'origin/main' into fuzz-create-payload
radakam Jul 24, 2026
d389ca6
acc/fuzz: fix set -e deploy-code capture, gate validate, guard determ…
radakam Jul 24, 2026
a0612c7
acc/fuzz: rename dispatcher, extract shared prologue, tighten comments
radakam Jul 24, 2026
d134418
acc/fuzz: fix import order after dispatcher rename
radakam Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,76 @@ jobs:
run: |
go tool -modfile=tools/task/go.mod task test-sandbox

test-fuzz:
needs:
- cleanups

# Wide rotating seed window with drift on: too slow for every PR, so nightly only and
# not part of test-result. The committed acceptance test still checks no-panic per PR.
if: ${{ github.event_name == 'schedule' }}
name: "task test-fuzz"
runs-on:
group: databricks-protected-runner-group-large
labels: linux-ubuntu-latest-large

defaults:
run:
shell: bash

permissions:
id-token: write
contents: read
# For the failure-reporting step's PR comment.
pull-requests: write

steps:
- name: Checkout repository and submodules
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup build environment
uses: ./.github/actions/setup-build-environment
with:
cache-key: test-fuzz

- name: Run tests
env:
FUZZ_SEED_COUNT: "25"
run: |
# GITHUB_RUN_NUMBER * COUNT keeps each nightly window non-overlapping, so CI
# explores new configs every run.
export FUZZ_SEED_START=$(( GITHUB_RUN_NUMBER * FUZZ_SEED_COUNT ))
go tool -modfile=tools/task/go.mod task test-fuzz

# Not in test-result, so surface failures by commenting on the PR under test.
- name: Report failure
if: ${{ failure() }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
COMMIT: ${{ github.sha }}
run: |
body=$(cat <<EOF
The nightly schema fuzz invariant job (\`task test-fuzz\`) failed on commit \`$COMMIT\`.

Run: $RUN_URL

The failing seed is printed in the job log as \`reproduce with: ...\`.
Reproduce locally with:

\`\`\`
FUZZ_SEED_START=<seed> FUZZ_SEED_COUNT=1 task test-fuzz
\`\`\`
EOF
)

# The commit's pulls endpoint returns the PR that introduced it.
pr=$(gh api "repos/$GITHUB_REPOSITORY/commits/$COMMIT/pulls" --jq '.[0].number // empty')
if [ -n "$pr" ]; then
gh pr comment "$pr" --body "$body"
else
echo "No PR found for commit $COMMIT; skipping failure comment" >&2
fi

# This job groups the result of all the above test jobs.
# It is a required check, so it blocks auto-merge and the merge queue.
#
Expand Down
47 changes: 47 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,53 @@ tasks:
--packages ./acceptance/... \
-- -timeout=${LOCAL_TIMEOUT:-60m} -run "TestAccept/cmd/sandbox"

test-fuzz:
desc: Run schema fuzz invariant tests (random configs, direct engine)
# No `sources:` fingerprint: the window depends on FUZZ_* env vars Task can't see.
cmds:
- |
# Wider window than the committed run, with drift on; a repro narrows it via
# FUZZ_SEED_START/COUNT. FUZZ_TIME_BUDGET stops each variant cleanly if 200 seeds
# don't fit the Timeout, rather than letting it be force-killed.
export FUZZ_SEED_COUNT="${FUZZ_SEED_COUNT:-200}"
export FUZZ_CHECK_DRIFT="${FUZZ_CHECK_DRIFT:-1}"
{{.GO_TOOL}} gotestsum \
--format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \
--no-summary=skipped \
--packages ./acceptance/... \
-- -timeout=${LOCAL_TIMEOUT:-30m} -run "TestAccept/bundle/invariant/fuzz"

test-fuzz-cover:
desc: Run the schema fuzzer under coverage and report which CLI packages it exercises
# No `sources:` fingerprint: like test-fuzz, the window depends on FUZZ_* env vars.
cmds:
- rm -fr ./acceptance/build/cover-fuzz/ ./acceptance/build/cover-fuzz-merged/
- mkdir -p ./acceptance/build/cover-fuzz-merged/
- |
# CLI_GOCOVERDIR makes the harness build a -cover CLI and set GOCOVERDIR per run, so
# every fuzzed `bundle` invocation drops aggregatable counter files. Drift off so the
# run exercises the full deploy/plan path instead of stopping on the first drift.
export CLI_GOCOVERDIR=build/cover-fuzz
export FUZZ_SEED_COUNT="${FUZZ_SEED_COUNT:-100}"
export FUZZ_TIME_BUDGET="${FUZZ_TIME_BUDGET:-900}"
unset FUZZ_CHECK_DRIFT || true
{{.GO_TOOL}} gotestsum \
--format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \
--no-summary=skipped \
--packages ./acceptance/... \
-- -timeout=${LOCAL_TIMEOUT:-40m} -run "TestAccept/bundle/invariant/fuzz" || true
- "go tool covdata merge -i $(printf '%s,' acceptance/build/cover-fuzz/* | sed 's/,$//') -o acceptance/build/cover-fuzz-merged/"
- go tool covdata textfmt -i acceptance/build/cover-fuzz-merged -o coverage-fuzz.txt
- |
echo "== total CLI coverage exercised by the fuzz corpus =="
go tool cover -func=coverage-fuzz.txt | awk '/^total:/{print $NF}'
echo
echo "== bundle/cmd packages by coverage (ascending; 0.0% = never exercised) =="
go tool covdata percent -i=acceptance/build/cover-fuzz-merged \
| awk '/coverage:/{p=$3; gsub(/%/,"",p); sub("github.com/databricks/cli/","",$1); printf "%6.1f%% %s\n", p, $1}' \
| grep -E ' (bundle|cmd/bundle)/' \
| sort -n

# --- Integration tests ---

integration:
Expand Down
48 changes: 48 additions & 0 deletions acceptance/bin/check_schema_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""
Assert every `type` in the bundle schema is one gen_fuzz_config.py can generate, so a new
libs/jsonschema.Type fails loudly here instead of being silently skipped by the fuzzer.
"""

import argparse
import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from gen_fuzz_config import HANDLED_TYPES


def collect_types(node, found):
if isinstance(node, dict):
t = node.get("type")
if isinstance(t, str):
found.add(t)
elif isinstance(t, list):
found.update(x for x in t if isinstance(x, str))
for v in node.values():
collect_types(v, found)
elif isinstance(node, list):
for v in node:
collect_types(v, found)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--schema", required=True)
args = parser.parse_args()

with open(args.schema) as f:
schema = json.load(f)

found = set()
collect_types(schema, found)

unhandled = sorted(found - HANDLED_TYPES)
if unhandled:
sys.exit(f"check_schema_types: gen_fuzz_config.py cannot generate schema types {unhandled}")


if __name__ == "__main__":
main()
74 changes: 74 additions & 0 deletions acceptance/bin/emit_fuzz_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""
Emit a fuzz databricks.yml on stdout for the current seed, picking the strategy from
FUZZ_MODE so the invariant scripts don't each duplicate the branch:

generate (default) - build from scratch by walking `bundle schema` (gen_fuzz_config.py).
mutate - perturb a curated invariant config (mutate_fuzz_config.py).

Reads its inputs from the environment the invariant scripts export: FUZZ_SEED, FUZZ_SCHEMA,
UNIQUE_NAME, FUZZ_RESOURCES, TESTDIR.
"""

import json
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from envsubst import substitute_variables
from gen_fuzz_config import gen_config, to_yaml
from mutate_fuzz_config import load_yaml, mutate

# Curated single-resource configs that deploy standalone (only $UNIQUE_NAME, no init
# script). All are in the invariant INPUT_CONFIG matrix, so they stay deploy-verified.
# The seed selects one; mutate_fuzz_config perturbs it.
MUTATE_BASES = [
"catalog",
"external_location",
"job",
"model",
"model_serving_endpoint",
"pipeline",
"registered_model",
"schema",
"secret_scope",
"sql_warehouse",
"volume",
]


def generate(seed):
with open(os.environ["FUZZ_SCHEMA"]) as f:
schema = json.load(f)
allowed = {r.strip() for r in os.environ.get("FUZZ_RESOURCES", "").split(",") if r.strip()}
unique = f"{os.environ['UNIQUE_NAME']}-{seed}"
return to_yaml(gen_config(schema, seed, unique, allowed))


def mutate_base(seed):
name = MUTATE_BASES[seed % len(MUTATE_BASES)]
path = os.path.join(os.environ["TESTDIR"], "..", "configs", name + ".yml.tmpl")
with open(path) as f:
rendered = substitute_variables(f.read())
config = load_yaml(rendered)
# The schema lets mutate inject valid optional fields, not just perturb existing ones.
with open(os.environ["FUZZ_SCHEMA"]) as f:
schema = json.load(f)
unique = f"{os.environ['UNIQUE_NAME']}-{seed}"
return to_yaml(mutate(config, seed, schema=schema, unique=unique))


def main():
seed = int(os.environ["FUZZ_SEED"])
mode = os.environ.get("FUZZ_MODE", "generate")
if mode == "generate":
sys.stdout.write(generate(seed))
elif mode == "mutate":
sys.stdout.write(mutate_base(seed))
else:
sys.exit(f"emit_fuzz_config: unknown FUZZ_MODE {mode!r}")


if __name__ == "__main__":
main()
Loading
Loading