From d9d1f41e8438094bf444c02cab3781517e2cb094 Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Mon, 9 Nov 2026 23:21:34 +0000 Subject: [PATCH] acceptance: support distinct UUIDs in plan and template comparisons --- acceptance/bin/repls.py | 40 ++++++++++++---- .../immutable/no-artifacts/out.plan.txt | 46 +++++++++++++++++++ .../deploy/immutable/no-artifacts/output.txt | 46 ------------------- .../deploy/immutable/no-artifacts/script | 10 ++-- 4 files changed, 84 insertions(+), 58 deletions(-) create mode 100644 acceptance/bundle/deploy/immutable/no-artifacts/out.plan.txt diff --git a/acceptance/bin/repls.py b/acceptance/bin/repls.py index 338127cf949..f24da6ce638 100644 --- a/acceptance/bin/repls.py +++ b/acceptance/bin/repls.py @@ -29,7 +29,7 @@ def read_entries(): def read_repls(): - """Return (pattern, replacement) pairs in the order they must be applied.""" + """Return (pattern, replacement, distinct) tuples in application order.""" entries = [] for item in read_entries(): @@ -38,35 +38,57 @@ def read_repls(): literal = item.get("Literal") if literal is None: - # "Distinct" is not honoured here; unlike the harness, we do not number the matches. - entries.append((order, item["Old"], new)) + entries.append((order, item["Old"], new, item["Distinct"])) continue # Set() in libs/testdiff also registers the JSON-encoded form of the value, so that # values with quotes or backslashes are replaced inside JSON output as well. encoded = json.dumps(literal, ensure_ascii=False)[1:-1] if encoded != literal: - entries.append((order, re.escape(encoded), new)) - entries.append((order, re.escape(literal), new)) + entries.append((order, re.escape(encoded), new, False)) + entries.append((order, re.escape(literal), new, False)) # Stable sort: replacements with the same order are applied in the order they were added. entries.sort(key=lambda entry: entry[0]) - return [(old, new) for _, old, new in entries] + return [(old, new, distinct) for _, old, new, distinct in entries] def compile_repls(): """Same as read_repls(), with patterns compiled. Invalid patterns are reported and skipped.""" result = [] - for old, new in read_repls(): + for old, new, distinct in read_repls(): try: - result.append((re.compile(old), new)) + result.append((re.compile(old), new, distinct)) except re.error as e: print(f"Regex error for pattern {old}: {e}", file=sys.stderr) return result def replace_all(patterns, s): - for comp, new in patterns: + r"""Apply replacements with the same per-output numbering as the Go harness. + + >>> distinct = [(re.compile(r"item-\w+"), "[ITEM]", True)] + >>> replace_all(distinct, "item-b item-a item-b") + '[ITEM][0] [ITEM][1] [ITEM][0]' + >>> replace_all(distinct, "item-a item-a") + '[ITEM] [ITEM]' + >>> replace_all(distinct, "no matches") + 'no matches' + >>> replace_all(distinct, "[ITEM][0] [ITEM][1] [ITEM][0]") + '[ITEM][0] [ITEM][1] [ITEM][0]' + >>> replace_all([(re.compile(r"item-\w+"), "[ITEM]", False)], "item-a item-b") + '[ITEM] [ITEM]' + """ + for comp, new, distinct in patterns: + if distinct: + seen = {} + for match in comp.finditer(s): + value = match.group(0) + if value not in seen: + seen[value] = len(seen) + if len(seen) > 1: + s = comp.sub(lambda match, new=new, seen=seen: match.expand(new) + f"[{seen[match.group(0)]}]", s) + continue s = comp.sub(new, s) return s diff --git a/acceptance/bundle/deploy/immutable/no-artifacts/out.plan.txt b/acceptance/bundle/deploy/immutable/no-artifacts/out.plan.txt new file mode 100644 index 00000000000..9bdc7957f1a --- /dev/null +++ b/acceptance/bundle/deploy/immutable/no-artifacts/out.plan.txt @@ -0,0 +1,46 @@ + +>>> [CLI] bundle plan -o json + +>>> jq .plan["resources.jobs.my_job"].new_state.value.tasks plan.json +[ + { + "notebook_task": { + "base_parameters": { + "path": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot/files/some_path" + }, + "notebook_path": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot/files/src/notebook" + }, + "task_key": "notebook_task" + }, + { + "environment_key": "env", + "spark_python_task": { + "python_file": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot/files/src/main.py" + }, + "task_key": "spark_python_task" + } +] + +>>> jq .plan["resources.internal_immutable_snapshots.immutable"] plan.json +{ + "action": "create", + "new_state": { + "value": { + "relative_path": "[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]", + "full_path": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot", + "bundle_id": "[SNAPSHOT_HASH]", + "acl": [ + { + "user_name": "[USERNAME]", + "permission_level": "CAN_READ" + } + ], + "can_manage": [ + { + "service_principal_name": "[USERNAME]" + } + ], + "zip_path": "[TEST_TMP_DIR]/.databricks/bundle/default/snapshots/[SNAPSHOT_HASH]-[UUID].zip" + } + } +} diff --git a/acceptance/bundle/deploy/immutable/no-artifacts/output.txt b/acceptance/bundle/deploy/immutable/no-artifacts/output.txt index 96f778441ac..e18559b02a0 100644 --- a/acceptance/bundle/deploy/immutable/no-artifacts/output.txt +++ b/acceptance/bundle/deploy/immutable/no-artifacts/output.txt @@ -8,52 +8,6 @@ Workspace: Validation OK! ->>> [CLI] bundle plan -o json - ->>> jq .plan["resources.jobs.my_job"].new_state.value.tasks plan.json -[ - { - "notebook_task": { - "base_parameters": { - "path": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot/files/some_path" - }, - "notebook_path": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot/files/src/notebook" - }, - "task_key": "notebook_task" - }, - { - "environment_key": "env", - "spark_python_task": { - "python_file": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot/files/src/main.py" - }, - "task_key": "spark_python_task" - } -] - ->>> jq .plan["resources.internal_immutable_snapshots.immutable"] plan.json -{ - "action": "create", - "new_state": { - "value": { - "relative_path": "[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]", - "full_path": "/Workspace/Users/[UUID]/.snapshots/[SNAPSHOT_HASH]/[SNAPSHOT_HASH]-[UUID]/snapshot", - "bundle_id": "[SNAPSHOT_HASH]", - "acl": [ - { - "user_name": "[USERNAME]", - "permission_level": "CAN_READ" - } - ], - "can_manage": [ - { - "service_principal_name": "[USERNAME]" - } - ], - "zip_path": "[TEST_TMP_DIR]/.databricks/bundle/default/snapshots/[SNAPSHOT_HASH]-[UUID].zip" - } - } -} - >>> [CLI] bundle deploy Created internal_immutable_snapshots.immutable Created jobs.my_job diff --git a/acceptance/bundle/deploy/immutable/no-artifacts/script b/acceptance/bundle/deploy/immutable/no-artifacts/script index 4bf3fddd7a6..b9ea0ae82d1 100644 --- a/acceptance/bundle/deploy/immutable/no-artifacts/script +++ b/acceptance/bundle/deploy/immutable/no-artifacts/script @@ -1,9 +1,13 @@ envsubst < databricks.yml.tmpl > databricks.yml trace $CLI bundle validate -trace $CLI bundle plan -o json > plan.json -trace jq '.plan["resources.jobs.my_job"].new_state.value.tasks' plan.json -trace jq '.plan["resources.internal_immutable_snapshots.immutable"]' plan.json +# A direct deploy generates a new snapshot UUID, while --plan reuses the planned +# one. Keep the initial plan in its own golden so both variants agree per file. +{ + trace $CLI bundle plan -o json > plan.json + trace jq '.plan["resources.jobs.my_job"].new_state.value.tasks' plan.json + trace jq '.plan["resources.internal_immutable_snapshots.immutable"]' plan.json +} > out.plan.txt 2>&1 trace $CLI bundle deploy $(readplanarg plan.json)