From 21ef9a711146a3a0737cc3a0b30a4148d7287e18 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Tue, 26 May 2026 17:51:54 +0200 Subject: [PATCH 1/6] Rust: Add manual regression test for dbscheme upgrade Adds a test directory with queries that verify properties are preserved when upgrading databases from rust-analyzer 0.0.301 to 0.0.328. This is a one-off manual test (not yet in CI), but could serve as the foundation for a general upgrade testing strategy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/.gitignore | 3 + .../test/README.md | 35 +++++++++ .../test/new/UpgradeShapes.expected | 20 +++++ .../test/new/UpgradeShapes.ql | 73 +++++++++++++++++++ .../test/new/qlpack.yml | 6 ++ .../test/new/upgrade_shapes.rs | 23 ++++++ .../test/old/OldShapes.expected | 22 ++++++ .../test/old/OldShapes.ql | 56 ++++++++++++++ .../test/run-test.sh | 62 ++++++++++++++++ 9 files changed, 300 insertions(+) create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/.gitignore create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/README.md create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.expected create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.ql create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/qlpack.yml create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/upgrade_shapes.rs create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.expected create mode 100644 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.ql create mode 100755 rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/.gitignore b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/.gitignore new file mode 100644 index 000000000000..3b4d77eb40d2 --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/.gitignore @@ -0,0 +1,3 @@ +*.testproj/ +*.actual +*.log diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/README.md b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/README.md new file mode 100644 index 000000000000..0acd82f4b421 --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/README.md @@ -0,0 +1,35 @@ +# Rust upgrade preservation test + +This directory contains a manual regression test for the Rust dbscheme upgrade from rust-analyzer 0.0.301 to 0.0.328. + +It is **not yet checked by CI**. The test has to be run manually because it requires building an old extractor to create an old-schema database first. + +## Directory structure + +- `old/`: Test query for the **old** schema, showing fields that will be upgraded +- `new/`: Test query and sources for the **new** schema after upgrade + +## Running the test + +From anywhere in the repository: + +```bash +rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh +``` + +Or override the old commit if needed: + +```bash +OLD_COMMIT= rust/ql/lib/upgrades/.../test/run-test.sh +``` + +The script will: +1. Copy test files to a temp directory +2. Stash uncommitted changes and checkout the old commit +3. Build the old extractor with `bazel run //rust:install` +4. Create an old-schema database with `codeql test run` +5. Restore your branch and pop the stash +6. Upgrade the database to the new schema +7. Run the preservation test on the upgraded database + +If the expected output needs to be refreshed after an intentional query change, manually run the final `codeql test run` with `--learn`. diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.expected b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.expected new file mode 100644 index 000000000000..6adf4a878beb --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.expected @@ -0,0 +1,20 @@ +formatArgNamePreserved +| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:37:22:37 | FormatArgsArgName | upgrade_shapes.rs:22:41:22:41 | 1 | 37 | 1 | +| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:44:22:44 | FormatArgsArgName | upgrade_shapes.rs:22:48:22:48 | 2 | 44 | 2 | +tryBlockModifierPreserved +| upgrade_shapes.rs:21:13:21:21 | { ... } | upgrade_shapes.rs:21:13:21:21 | TryBlockModifier | +structFieldDefaultPreserved +| upgrade_shapes.rs:9:5:9:17 | field: u8 | upgrade_shapes.rs:9:17:9:17 | ConstArg | upgrade_shapes.rs:9:17:9:17 | 1 | +variantDiscriminantPreserved +| upgrade_shapes.rs:13:5:13:9 | V | upgrade_shapes.rs:13:9:13:9 | ConstArg | upgrade_shapes.rs:13:9:13:9 | 2 | +pathMetaPreserved +| upgrade_shapes.rs:4:3:4:11 | PathMeta | path_meta | +| upgrade_shapes.rs:7:3:7:19 | PathMeta | path_meta | +keyValueMetaPreserved +| upgrade_shapes.rs:5:3:5:15 | KeyValueMeta | key_value | upgrade_shapes.rs:5:15:5:15 | 1 | +tokenTreeMetaPreserved +| upgrade_shapes.rs:6:3:6:18 | TokenTreeMeta | token_tree | upgrade_shapes.rs:6:13:6:18 | TokenTree | +unsafeMetaPreserved +| upgrade_shapes.rs:7:3:7:19 | UnsafeMeta | upgrade_shapes.rs:7:3:7:19 | PathMeta | path_meta | +traitAliasPreserved +| upgrade_shapes.rs:16:1:18:12 | trait Alias | upgrade_shapes.rs:16:7:16:11 | Alias | upgrade_shapes.rs:16:18:16:22 | ... | upgrade_shapes.rs:17:1:18:11 | WhereClause | diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.ql b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.ql new file mode 100644 index 000000000000..bd983a8290c4 --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/UpgradeShapes.ql @@ -0,0 +1,73 @@ +import codeql.rust.elements + +private predicate inUpgradeShapesFile(Locatable loc) { + loc.getFile().getBaseName() = "upgrade_shapes.rs" +} + +query predicate formatArgNamePreserved( + FormatArgsArg arg, FormatArgsArgName argName, Expr expr, int argNameColumn, string exprText +) { + inUpgradeShapesFile(arg) and + argName = arg.getArgName() and + expr = arg.getExpr() and + argNameColumn = argName.getLocation().getStartColumn() and + exprText = expr.toString() +} + +query predicate tryBlockModifierPreserved(BlockExpr block, TryBlockModifier modifier) { + inUpgradeShapesFile(block) and + modifier = block.getTryBlockModifier() and + modifier.isTry() and + not modifier.hasTypeRepr() +} + +query predicate structFieldDefaultPreserved(StructField field, ConstArg defaultVal, Expr expr) { + inUpgradeShapesFile(field) and + defaultVal = field.getDefaultVal() and + expr = defaultVal.getExpr() +} + +query predicate variantDiscriminantPreserved(Variant variant, ConstArg constArg, Expr expr) { + inUpgradeShapesFile(variant) and + constArg = variant.getConstArg() and + expr = constArg.getExpr() +} + +query predicate pathMetaPreserved(PathMeta meta, string pathText) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "path_meta" +} + +query predicate keyValueMetaPreserved(KeyValueMeta meta, string pathText, Expr expr) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "key_value" and + expr = meta.getExpr() +} + +query predicate tokenTreeMetaPreserved(TokenTreeMeta meta, string pathText, TokenTree tokenTree) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "token_tree" and + tokenTree = meta.getTokenTree() +} + +query predicate unsafeMetaPreserved(UnsafeMeta meta, PathMeta inner, string pathText) { + inUpgradeShapesFile(meta) and + meta.isUnsafe() and + inner = meta.getMeta() and + pathText = inner.getPath().getText() and + pathText = "path_meta" +} + +query predicate traitAliasPreserved( + Trait trait, Name name, TypeBoundList bounds, WhereClause whereClause +) { + inUpgradeShapesFile(trait) and + name = trait.getName() and + name.getText() = "Alias" and + bounds = trait.getTypeBoundList() and + whereClause = trait.getWhereClause() and + not trait.hasAssocItemList() +} diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/qlpack.yml b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/qlpack.yml new file mode 100644 index 000000000000..81a7b0a25031 --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/qlpack.yml @@ -0,0 +1,6 @@ +name: codeql/rust-upgrade-66a489863649185f4a9770f894505803059a1312-test-new +version: 0.0.0 +dependencies: + codeql/rust-all: ${workspace} +extractor: rust +tests: . diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/upgrade_shapes.rs b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/upgrade_shapes.rs new file mode 100644 index 000000000000..e64d8f6582c6 --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/new/upgrade_shapes.rs @@ -0,0 +1,23 @@ +#![allow(dead_code)] +#![feature(more_qualified_paths)] + +#[path_meta] +#[key_value = 1] +#[token_tree(list)] +#[unsafe(path_meta)] +struct S { + field: u8 = 1, +} + +enum E { + V = 2, +} + +trait Alias = Clone +where + T: Copy; + +fn f() { + let _ = try { 1 }; + let _ = format_args!("{b} {a}", a = 1, b = 2); +} diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.expected b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.expected new file mode 100644 index 000000000000..8cb5c2511a44 --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.expected @@ -0,0 +1,22 @@ +formatArgsArgName +| upgrade_shapes.rs:22:31:22:31 | a | upgrade_shapes.rs:22:35:22:35 | 1 | a | +| upgrade_shapes.rs:22:38:22:38 | b | upgrade_shapes.rs:22:42:22:42 | 2 | b | +metaExpr +| upgrade_shapes.rs:5:1:5:15 | #[key_value = 1] | upgrade_shapes.rs:5:15:5:15 | 1 | +metaIsUnsafe +| upgrade_shapes.rs:7:1:7:21 | #[unsafe(path_meta)] | +metaPath +| upgrade_shapes.rs:4:1:4:12 | #[path_meta] | path_meta | +| upgrade_shapes.rs:5:1:5:15 | #[key_value = 1] | key_value | +| upgrade_shapes.rs:6:1:6:19 | #[token_tree(list)] | token_tree | +| upgrade_shapes.rs:7:1:7:21 | #[unsafe(path_meta)] | path_meta | +metaTokenTree +| upgrade_shapes.rs:6:1:6:19 | #[token_tree(list)] | upgrade_shapes.rs:6:13:6:18 | (list) | +structFieldDefault +| upgrade_shapes.rs:9:5:9:17 | field: u8 = 1 | upgrade_shapes.rs:9:17:9:17 | 1 | +traitAlias +| upgrade_shapes.rs:16:1:18:12 | TraitAlias | Alias | upgrade_shapes.rs:16:18:16:23 | = Clone | upgrade_shapes.rs:17:1:18:12 | where... | +tryBlock +| upgrade_shapes.rs:21:13:21:23 | try { 1 } | +variantDiscriminant +| upgrade_shapes.rs:13:5:13:9 | V = 2 | upgrade_shapes.rs:13:9:13:9 | 2 | diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.ql b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.ql new file mode 100644 index 000000000000..ced2690b7dbb --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/old/OldShapes.ql @@ -0,0 +1,56 @@ +import codeql.rust.elements + +private predicate inUpgradeShapesFile(Locatable loc) { + loc.getFile().getBaseName() = "upgrade_shapes.rs" +} + +query predicate formatArgsArgName(FormatArgsArg arg, Name argName, Expr expr, string nameText) { + inUpgradeShapesFile(arg) and + argName = arg.getName() and + expr = arg.getExpr() and + nameText = argName.getText() +} + +query predicate tryBlock(BlockExpr block) { + inUpgradeShapesFile(block) and + block.isTry() +} + +query predicate structFieldDefault(StructField field, Expr defaultVal) { + inUpgradeShapesFile(field) and + defaultVal = field.getDefault() +} + +query predicate variantDiscriminant(Variant variant, Expr discriminant) { + inUpgradeShapesFile(variant) and + discriminant = variant.getDiscriminant() +} + +query predicate metaPath(Meta meta, string pathText) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() +} + +query predicate metaExpr(Meta meta, Expr expr) { + inUpgradeShapesFile(meta) and + expr = meta.getExpr() +} + +query predicate metaTokenTree(Meta meta, TokenTree tokenTree) { + inUpgradeShapesFile(meta) and + tokenTree = meta.getTokenTree() +} + +query predicate metaIsUnsafe(Meta meta) { + inUpgradeShapesFile(meta) and + meta.isUnsafe() +} + +query predicate traitAlias( + TraitAlias alias, Name name, TypeBoundList bounds, WhereClause whereClause +) { + inUpgradeShapesFile(alias) and + name = alias.getName() and + bounds = alias.getTypeBoundList() and + whereClause = alias.getWhereClause() +} diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh new file mode 100755 index 000000000000..93e79ff4daae --- /dev/null +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Manual regression test for the Rust dbscheme upgrade from rust-analyzer 0.0.301 to 0.0.328. +# See README.md for details. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" +OLD_COMMIT="${OLD_COMMIT:-491c373e076}" # origin/main at time of this upgrade + +cd "$REPO_ROOT" + +echo "==> Setting up temp directory for old test..." +OLD_TEST_TMP=$(mktemp -d) +trap 'rm -rf "$OLD_TEST_TMP"' EXIT + +# Copy old test query and new test sources (qlpack, upgrade_shapes.rs) to temp +cp "$SCRIPT_DIR/old/OldShapes.ql" "$SCRIPT_DIR/old/OldShapes.expected" "$OLD_TEST_TMP/" +cp "$SCRIPT_DIR/new/qlpack.yml" "$SCRIPT_DIR/new/upgrade_shapes.rs" "$OLD_TEST_TMP/" + +echo "==> Stashing any uncommitted changes..." +git stash --quiet || true + +restore_branch() { + echo "==> Restoring original branch..." + git checkout --quiet - + git stash pop --quiet 2>/dev/null || true +} +trap 'restore_branch; rm -rf "$OLD_TEST_TMP"' EXIT + +echo "==> Checking out old commit ($OLD_COMMIT)..." +git checkout --quiet "$OLD_COMMIT" + +echo "==> Building old extractor (this may take a while)..." +bazel run //rust:install + +echo "==> Creating old-schema test database..." +rm -rf "$OLD_TEST_TMP"/*.testproj +codeql test run \ + --search-path . \ + --keep-databases \ + "$OLD_TEST_TMP/OldShapes.ql" + +echo "==> Copying dataset for upgrade..." +cp -a "$OLD_TEST_TMP/test.testproj/db-rust" "$OLD_TEST_TMP/upgraded-dataset" + +restore_branch +trap 'rm -rf "$OLD_TEST_TMP"' EXIT + +echo "==> Upgrading dataset to new schema..." +codeql dataset upgrade "$OLD_TEST_TMP/upgraded-dataset" \ + --search-path . \ + --target-dbscheme rust/ql/lib/rust.dbscheme + +echo "==> Running preservation test on upgraded dataset..." +codeql test run \ + --search-path . \ + --dataset="$OLD_TEST_TMP/upgraded-dataset" \ + --check-databases \ + "$SCRIPT_DIR/new/UpgradeShapes.ql" + +echo "==> All tests passed!" From 27f8d9573d3b460608550cf295e5736e0b6a9390 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Tue, 26 May 2026 18:17:03 +0200 Subject: [PATCH 2/6] Fix REPO_ROOT computation in run-test.sh Use `git rev-parse --show-toplevel` instead of fragile relative path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../66a489863649185f4a9770f894505803059a1312/test/run-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh index 93e79ff4daae..a4e8e9f53a87 100755 --- a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh @@ -5,7 +5,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" +REPO_ROOT="$(git rev-parse --show-toplevel)" OLD_COMMIT="${OLD_COMMIT:-491c373e076}" # origin/main at time of this upgrade cd "$REPO_ROOT" From b293a4dd8c86263f97ad137706e3a153d6456eb7 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Wed, 27 May 2026 08:13:04 +0200 Subject: [PATCH 3/6] Fix unsafe stash handling in run-test.sh Only stash when there are actual changes (including untracked files), and only pop the specific stash entry we created. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/run-test.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh index a4e8e9f53a87..a3f65d9897c4 100755 --- a/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh +++ b/rust/ql/lib/upgrades/66a489863649185f4a9770f894505803059a1312/test/run-test.sh @@ -18,13 +18,22 @@ trap 'rm -rf "$OLD_TEST_TMP"' EXIT cp "$SCRIPT_DIR/old/OldShapes.ql" "$SCRIPT_DIR/old/OldShapes.expected" "$OLD_TEST_TMP/" cp "$SCRIPT_DIR/new/qlpack.yml" "$SCRIPT_DIR/new/upgrade_shapes.rs" "$OLD_TEST_TMP/" -echo "==> Stashing any uncommitted changes..." -git stash --quiet || true +# Stash changes only if there are any (including untracked files) +STASH_REF="" +if ! git diff --quiet HEAD || [ -n "$(git ls-files --others --exclude-standard)" ]; then + echo "==> Stashing uncommitted changes..." + STASH_REF=$(git stash create --include-untracked) + if [ -n "$STASH_REF" ]; then + git stash store -m "run-test.sh auto-stash" "$STASH_REF" + fi +fi restore_branch() { echo "==> Restoring original branch..." git checkout --quiet - - git stash pop --quiet 2>/dev/null || true + if [ -n "$STASH_REF" ]; then + git stash pop --quiet + fi } trap 'restore_branch; rm -rf "$OLD_TEST_TMP"' EXIT From 9abd08ceaea0853526d7d3cb91de8a851345f70a Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Wed, 27 May 2026 08:54:17 +0200 Subject: [PATCH 4/6] Rust: Move upgrade test to separate `upgrade-tests` directory Avoids issues with nested qlpack.yml breaking `codeql pack create` for the lib pack. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- codeql-workspace.yml | 1 + rust/ql/upgrade-tests/.gitignore | 9 +++ .../.gitignore | 3 + .../README.md | 25 +++++++ .../new.expected | 20 +++++ .../new.ql | 73 +++++++++++++++++++ .../old.expected | 26 +++++++ .../old.ql | 56 ++++++++++++++ .../run-test.sh | 67 +++++++++++++++++ .../upgrade_shapes.rs | 23 ++++++ rust/ql/upgrade-tests/qlpack.yml | 5 ++ 11 files changed, 308 insertions(+) create mode 100644 rust/ql/upgrade-tests/.gitignore create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.expected create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.ql create mode 100755 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs create mode 100644 rust/ql/upgrade-tests/qlpack.yml diff --git a/codeql-workspace.yml b/codeql-workspace.yml index 32cad09e20c7..a6408dc26ecf 100644 --- a/codeql-workspace.yml +++ b/codeql-workspace.yml @@ -2,6 +2,7 @@ provide: - "*/ql/src/qlpack.yml" - "*/ql/lib/qlpack.yml" - "*/ql/test*/qlpack.yml" + - "*/ql/upgrade-tests/qlpack.yml" - "*/ql/examples/qlpack.yml" - "*/ql/consistency-queries/qlpack.yml" - "*/ql/automodel/src/qlpack.yml" diff --git a/rust/ql/upgrade-tests/.gitignore b/rust/ql/upgrade-tests/.gitignore new file mode 100644 index 000000000000..29291fe2d12c --- /dev/null +++ b/rust/ql/upgrade-tests/.gitignore @@ -0,0 +1,9 @@ +target/ + +# these are all generated, see `rust/extractor/src/qltest.rs` for details +Cargo.toml +/*/**/rust-toolchain.toml +lib.rs +.proc_macro/ +.lib/ + diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore new file mode 100644 index 000000000000..3b4d77eb40d2 --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore @@ -0,0 +1,3 @@ +*.testproj/ +*.actual +*.log diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md new file mode 100644 index 000000000000..ecc94261ee51 --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md @@ -0,0 +1,25 @@ +# Upgrade Regression Test for rust-analyzer 0.0.301 → 0.0.328 + +This test verifies that the dbscheme upgrade script correctly preserves data when migrating databases from the old schema to the new schema. + +## Running the test + +```bash +./run-test.sh +``` + +This will: +1. Stash any uncommitted changes +2. Check out the old commit (491c373e076) +3. Build the old extractor +4. Create a database with the old schema from `upgrade_shapes.rs` +5. Restore your branch +6. Upgrade the database to the new schema +7. Verify that the old properties are still accessible via the new schema + +## Files + +- `old.ql` / `old.expected`: Query for the old schema (validates extraction) +- `new.ql` / `new.expected`: Query for the new schema (validates upgrade preserved data) +- `upgrade_shapes.rs`: Rust source containing test shapes for all affected schema elements +- `run-test.sh`: Test runner script diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected new file mode 100644 index 000000000000..6adf4a878beb --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected @@ -0,0 +1,20 @@ +formatArgNamePreserved +| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:37:22:37 | FormatArgsArgName | upgrade_shapes.rs:22:41:22:41 | 1 | 37 | 1 | +| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:44:22:44 | FormatArgsArgName | upgrade_shapes.rs:22:48:22:48 | 2 | 44 | 2 | +tryBlockModifierPreserved +| upgrade_shapes.rs:21:13:21:21 | { ... } | upgrade_shapes.rs:21:13:21:21 | TryBlockModifier | +structFieldDefaultPreserved +| upgrade_shapes.rs:9:5:9:17 | field: u8 | upgrade_shapes.rs:9:17:9:17 | ConstArg | upgrade_shapes.rs:9:17:9:17 | 1 | +variantDiscriminantPreserved +| upgrade_shapes.rs:13:5:13:9 | V | upgrade_shapes.rs:13:9:13:9 | ConstArg | upgrade_shapes.rs:13:9:13:9 | 2 | +pathMetaPreserved +| upgrade_shapes.rs:4:3:4:11 | PathMeta | path_meta | +| upgrade_shapes.rs:7:3:7:19 | PathMeta | path_meta | +keyValueMetaPreserved +| upgrade_shapes.rs:5:3:5:15 | KeyValueMeta | key_value | upgrade_shapes.rs:5:15:5:15 | 1 | +tokenTreeMetaPreserved +| upgrade_shapes.rs:6:3:6:18 | TokenTreeMeta | token_tree | upgrade_shapes.rs:6:13:6:18 | TokenTree | +unsafeMetaPreserved +| upgrade_shapes.rs:7:3:7:19 | UnsafeMeta | upgrade_shapes.rs:7:3:7:19 | PathMeta | path_meta | +traitAliasPreserved +| upgrade_shapes.rs:16:1:18:12 | trait Alias | upgrade_shapes.rs:16:7:16:11 | Alias | upgrade_shapes.rs:16:18:16:22 | ... | upgrade_shapes.rs:17:1:18:11 | WhereClause | diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql new file mode 100644 index 000000000000..bd983a8290c4 --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql @@ -0,0 +1,73 @@ +import codeql.rust.elements + +private predicate inUpgradeShapesFile(Locatable loc) { + loc.getFile().getBaseName() = "upgrade_shapes.rs" +} + +query predicate formatArgNamePreserved( + FormatArgsArg arg, FormatArgsArgName argName, Expr expr, int argNameColumn, string exprText +) { + inUpgradeShapesFile(arg) and + argName = arg.getArgName() and + expr = arg.getExpr() and + argNameColumn = argName.getLocation().getStartColumn() and + exprText = expr.toString() +} + +query predicate tryBlockModifierPreserved(BlockExpr block, TryBlockModifier modifier) { + inUpgradeShapesFile(block) and + modifier = block.getTryBlockModifier() and + modifier.isTry() and + not modifier.hasTypeRepr() +} + +query predicate structFieldDefaultPreserved(StructField field, ConstArg defaultVal, Expr expr) { + inUpgradeShapesFile(field) and + defaultVal = field.getDefaultVal() and + expr = defaultVal.getExpr() +} + +query predicate variantDiscriminantPreserved(Variant variant, ConstArg constArg, Expr expr) { + inUpgradeShapesFile(variant) and + constArg = variant.getConstArg() and + expr = constArg.getExpr() +} + +query predicate pathMetaPreserved(PathMeta meta, string pathText) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "path_meta" +} + +query predicate keyValueMetaPreserved(KeyValueMeta meta, string pathText, Expr expr) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "key_value" and + expr = meta.getExpr() +} + +query predicate tokenTreeMetaPreserved(TokenTreeMeta meta, string pathText, TokenTree tokenTree) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "token_tree" and + tokenTree = meta.getTokenTree() +} + +query predicate unsafeMetaPreserved(UnsafeMeta meta, PathMeta inner, string pathText) { + inUpgradeShapesFile(meta) and + meta.isUnsafe() and + inner = meta.getMeta() and + pathText = inner.getPath().getText() and + pathText = "path_meta" +} + +query predicate traitAliasPreserved( + Trait trait, Name name, TypeBoundList bounds, WhereClause whereClause +) { + inUpgradeShapesFile(trait) and + name = trait.getName() and + name.getText() = "Alias" and + bounds = trait.getTypeBoundList() and + whereClause = trait.getWhereClause() and + not trait.hasAssocItemList() +} diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.expected b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.expected new file mode 100644 index 000000000000..4fb40adf86f1 --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.expected @@ -0,0 +1,26 @@ +formatArgsArgName +| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:37:22:37 | a | upgrade_shapes.rs:22:41:22:41 | 1 | a | +| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:44:22:44 | b | upgrade_shapes.rs:22:48:22:48 | 2 | b | +tryBlock +| upgrade_shapes.rs:21:13:21:21 | { ... } | +structFieldDefault +| upgrade_shapes.rs:9:5:9:17 | field: u8 | upgrade_shapes.rs:9:17:9:17 | 1 | +variantDiscriminant +| upgrade_shapes.rs:13:5:13:9 | V | upgrade_shapes.rs:13:9:13:9 | 2 | +metaPath +| upgrade_shapes.rs:1:4:1:19 | Meta | allow | +| upgrade_shapes.rs:2:4:2:32 | Meta | feature | +| upgrade_shapes.rs:4:3:4:11 | Meta | path_meta | +| upgrade_shapes.rs:5:3:5:15 | Meta | key_value | +| upgrade_shapes.rs:6:3:6:18 | Meta | token_tree | +| upgrade_shapes.rs:7:3:7:19 | Meta | path_meta | +metaExpr +| upgrade_shapes.rs:5:3:5:15 | Meta | upgrade_shapes.rs:5:15:5:15 | 1 | +metaTokenTree +| upgrade_shapes.rs:1:4:1:19 | Meta | upgrade_shapes.rs:1:9:1:19 | TokenTree | +| upgrade_shapes.rs:2:4:2:32 | Meta | upgrade_shapes.rs:2:11:2:32 | TokenTree | +| upgrade_shapes.rs:6:3:6:18 | Meta | upgrade_shapes.rs:6:13:6:18 | TokenTree | +metaIsUnsafe +| upgrade_shapes.rs:7:3:7:19 | Meta | +traitAlias +| upgrade_shapes.rs:16:1:18:12 | TraitAlias | upgrade_shapes.rs:16:7:16:11 | Alias | upgrade_shapes.rs:16:18:16:22 | ... | upgrade_shapes.rs:17:1:18:11 | WhereClause | diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.ql b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.ql new file mode 100644 index 000000000000..ced2690b7dbb --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/old.ql @@ -0,0 +1,56 @@ +import codeql.rust.elements + +private predicate inUpgradeShapesFile(Locatable loc) { + loc.getFile().getBaseName() = "upgrade_shapes.rs" +} + +query predicate formatArgsArgName(FormatArgsArg arg, Name argName, Expr expr, string nameText) { + inUpgradeShapesFile(arg) and + argName = arg.getName() and + expr = arg.getExpr() and + nameText = argName.getText() +} + +query predicate tryBlock(BlockExpr block) { + inUpgradeShapesFile(block) and + block.isTry() +} + +query predicate structFieldDefault(StructField field, Expr defaultVal) { + inUpgradeShapesFile(field) and + defaultVal = field.getDefault() +} + +query predicate variantDiscriminant(Variant variant, Expr discriminant) { + inUpgradeShapesFile(variant) and + discriminant = variant.getDiscriminant() +} + +query predicate metaPath(Meta meta, string pathText) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() +} + +query predicate metaExpr(Meta meta, Expr expr) { + inUpgradeShapesFile(meta) and + expr = meta.getExpr() +} + +query predicate metaTokenTree(Meta meta, TokenTree tokenTree) { + inUpgradeShapesFile(meta) and + tokenTree = meta.getTokenTree() +} + +query predicate metaIsUnsafe(Meta meta) { + inUpgradeShapesFile(meta) and + meta.isUnsafe() +} + +query predicate traitAlias( + TraitAlias alias, Name name, TypeBoundList bounds, WhereClause whereClause +) { + inUpgradeShapesFile(alias) and + name = alias.getName() and + bounds = alias.getTypeBoundList() and + whereClause = alias.getWhereClause() +} diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh new file mode 100755 index 000000000000..b5ae0d8a42f7 --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Manual regression test for the Rust dbscheme upgrade from rust-analyzer 0.0.301 to 0.0.328. +# See README.md for details. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(git rev-parse --show-toplevel)" +OLD_COMMIT="${OLD_COMMIT:-491c373e076}" # origin/main at time of this upgrade + +cd "$REPO_ROOT" + +# Require clean working directory - stash handling is too fragile +if ! git diff --quiet HEAD || [ -n "$(git ls-files --others --exclude-standard)" ]; then + echo "ERROR: Working directory has uncommitted changes." >&2 + echo "Please commit or stash your changes before running this test." >&2 + exit 1 +fi + +ORIGINAL_REF="$(git rev-parse --abbrev-ref HEAD)" +if [ "$ORIGINAL_REF" = "HEAD" ]; then + # Detached HEAD - save the commit instead + ORIGINAL_REF="$(git rev-parse HEAD)" +fi + +restore_ref() { + echo "==> Restoring original ref ($ORIGINAL_REF)..." + git checkout --quiet "$ORIGINAL_REF" +} +trap 'restore_ref' EXIT + +echo "==> Checking out old commit ($OLD_COMMIT)..." +git checkout --quiet "$OLD_COMMIT" + +# Restore the upgrade-tests directory from the original ref (it doesn't exist in old commit) +git checkout --quiet "$ORIGINAL_REF" -- rust/ql/upgrade-tests codeql-workspace.yml + +echo "==> Building old extractor (this may take a while)..." +bazel run //rust:install + +echo "==> Creating old-schema test database..." +codeql test run \ + --search-path . \ + --keep-databases \ + "$SCRIPT_DIR/old.ql" "$@" + +restore_ref +trap '' EXIT + +echo "==> Upgrading dataset to new schema..." +DATASET_DIR=("$SCRIPT_DIR"/*.testproj/db-rust) +if [[ ! -d "${DATASET_DIR[0]}" ]]; then + echo "ERROR: No testproj found at $SCRIPT_DIR/*.testproj" >&2 + exit 1 +fi +codeql dataset upgrade "${DATASET_DIR[0]}" \ + --search-path . \ + --target-dbscheme rust/ql/lib/rust.dbscheme + +echo "==> Running preservation test on upgraded dataset..." +codeql test run \ + --search-path . \ + --dataset "${DATASET_DIR[0]}" \ + --check-databases \ + "$SCRIPT_DIR/new.ql" "$@" + +echo "==> All tests passed!" diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs new file mode 100644 index 000000000000..e64d8f6582c6 --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs @@ -0,0 +1,23 @@ +#![allow(dead_code)] +#![feature(more_qualified_paths)] + +#[path_meta] +#[key_value = 1] +#[token_tree(list)] +#[unsafe(path_meta)] +struct S { + field: u8 = 1, +} + +enum E { + V = 2, +} + +trait Alias = Clone +where + T: Copy; + +fn f() { + let _ = try { 1 }; + let _ = format_args!("{b} {a}", a = 1, b = 2); +} diff --git a/rust/ql/upgrade-tests/qlpack.yml b/rust/ql/upgrade-tests/qlpack.yml new file mode 100644 index 000000000000..559bca6042af --- /dev/null +++ b/rust/ql/upgrade-tests/qlpack.yml @@ -0,0 +1,5 @@ +name: codeql/rust-upgrade-tests +version: 0.0.0 +extractor: rust +dependencies: + codeql/rust-all: ${workspace} From 1a4db88715abebe90627d38fdf4d11c9557d0db3 Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Wed, 27 May 2026 09:35:26 +0200 Subject: [PATCH 5/6] Rust: Test dbscheme downgrade preservation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../README.md | 7 ++- .../downgraded.expected | 26 +++++++++ .../downgraded.ql | 56 +++++++++++++++++++ .../run-test.sh | 23 ++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected create mode 100644 rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md index ecc94261ee51..a728458bef42 100644 --- a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md @@ -1,6 +1,6 @@ # Upgrade Regression Test for rust-analyzer 0.0.301 → 0.0.328 -This test verifies that the dbscheme upgrade script correctly preserves data when migrating databases from the old schema to the new schema. +This test verifies that the dbscheme upgrade and downgrade scripts correctly preserve data when migrating databases between the old and new schemas. ## Running the test @@ -16,10 +16,13 @@ This will: 5. Restore your branch 6. Upgrade the database to the new schema 7. Verify that the old properties are still accessible via the new schema +8. Downgrade the database back to the old schema +9. Verify that the recovered old-schema properties still match ## Files -- `old.ql` / `old.expected`: Query for the old schema (validates extraction) +- `old.ql` / `old.expected`: Query for the old schema (validates extraction and downgrade) - `new.ql` / `new.expected`: Query for the new schema (validates upgrade preserved data) +- `downgraded.ql` / `downgraded.expected`: Query for the downgraded old schema - `upgrade_shapes.rs`: Rust source containing test shapes for all affected schema elements - `run-test.sh`: Test runner script diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected new file mode 100644 index 000000000000..1df9e112bbee --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected @@ -0,0 +1,26 @@ +formatArgsArgName +| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:37:22:37 | (no string representation) | upgrade_shapes.rs:22:41:22:41 | 1 | 37 | +| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:44:22:44 | (no string representation) | upgrade_shapes.rs:22:48:22:48 | 2 | 44 | +tryBlock +| upgrade_shapes.rs:21:13:21:21 | { ... } | +structFieldDefault +| upgrade_shapes.rs:9:5:9:17 | field: u8 | upgrade_shapes.rs:9:17:9:17 | 1 | +variantDiscriminant +| upgrade_shapes.rs:13:5:13:9 | V | upgrade_shapes.rs:13:9:13:9 | 2 | +metaPath +| upgrade_shapes.rs:1:4:1:19 | Meta | allow | +| upgrade_shapes.rs:2:4:2:32 | Meta | feature | +| upgrade_shapes.rs:4:3:4:11 | Meta | path_meta | +| upgrade_shapes.rs:5:3:5:15 | Meta | key_value | +| upgrade_shapes.rs:6:3:6:18 | Meta | token_tree | +| upgrade_shapes.rs:7:3:7:19 | Meta | path_meta | +metaExpr +| upgrade_shapes.rs:5:3:5:15 | Meta | upgrade_shapes.rs:5:15:5:15 | 1 | +metaTokenTree +| upgrade_shapes.rs:1:4:1:19 | Meta | upgrade_shapes.rs:1:9:1:19 | TokenTree | +| upgrade_shapes.rs:2:4:2:32 | Meta | upgrade_shapes.rs:2:11:2:32 | TokenTree | +| upgrade_shapes.rs:6:3:6:18 | Meta | upgrade_shapes.rs:6:13:6:18 | TokenTree | +metaIsUnsafe +| upgrade_shapes.rs:7:3:7:19 | Meta | +traitAlias +| upgrade_shapes.rs:16:1:18:12 | TraitAlias | upgrade_shapes.rs:16:7:16:11 | Alias | upgrade_shapes.rs:16:18:16:22 | ... | upgrade_shapes.rs:17:1:18:11 | WhereClause | diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql new file mode 100644 index 000000000000..4282fbefa0be --- /dev/null +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql @@ -0,0 +1,56 @@ +import codeql.rust.elements + +private predicate inUpgradeShapesFile(Locatable loc) { + loc.getFile().getBaseName() = "upgrade_shapes.rs" +} + +query predicate formatArgsArgName(FormatArgsArg arg, Name argName, Expr expr, int argNameColumn) { + inUpgradeShapesFile(arg) and + argName = arg.getName() and + expr = arg.getExpr() and + argNameColumn = argName.getLocation().getStartColumn() +} + +query predicate tryBlock(BlockExpr block) { + inUpgradeShapesFile(block) and + block.isTry() +} + +query predicate structFieldDefault(StructField field, Expr defaultVal) { + inUpgradeShapesFile(field) and + defaultVal = field.getDefault() +} + +query predicate variantDiscriminant(Variant variant, Expr discriminant) { + inUpgradeShapesFile(variant) and + discriminant = variant.getDiscriminant() +} + +query predicate metaPath(Meta meta, string pathText) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() +} + +query predicate metaExpr(Meta meta, Expr expr) { + inUpgradeShapesFile(meta) and + expr = meta.getExpr() +} + +query predicate metaTokenTree(Meta meta, TokenTree tokenTree) { + inUpgradeShapesFile(meta) and + tokenTree = meta.getTokenTree() +} + +query predicate metaIsUnsafe(Meta meta) { + inUpgradeShapesFile(meta) and + meta.isUnsafe() +} + +query predicate traitAlias( + TraitAlias alias, Name name, TypeBoundList bounds, WhereClause whereClause +) { + inUpgradeShapesFile(alias) and + name = alias.getName() and + bounds = alias.getTypeBoundList() and + whereClause = alias.getWhereClause() +} diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh index b5ae0d8a42f7..1dbbf2783b7a 100755 --- a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh @@ -7,6 +7,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(git rev-parse --show-toplevel)" OLD_COMMIT="${OLD_COMMIT:-491c373e076}" # origin/main at time of this upgrade +OLD_DBSCHEME="rust/downgrades/109496fd2f20f28a35e50b110859e74882ee80d6/rust.dbscheme" cd "$REPO_ROOT" @@ -64,4 +65,26 @@ codeql test run \ --check-databases \ "$SCRIPT_DIR/new.ql" "$@" +echo "==> Downgrading dataset back to old schema..." +codeql dataset upgrade "${DATASET_DIR[0]}" \ + --allow-downgrades \ + --search-path rust \ + --target-dbscheme "$OLD_DBSCHEME" + +trap 'restore_ref' EXIT + +echo "==> Checking out old commit ($OLD_COMMIT) for downgrade verification..." +git checkout --quiet "$OLD_COMMIT" +git checkout --quiet "$ORIGINAL_REF" -- rust/ql/upgrade-tests codeql-workspace.yml + +echo "==> Running preservation test on downgraded dataset..." +codeql test run \ + --search-path . \ + --dataset "${DATASET_DIR[0]}" \ + --check-databases \ + "$SCRIPT_DIR/downgraded.ql" "$@" + +restore_ref +trap '' EXIT + echo "==> All tests passed!" From ebf899e89b07e9228a63077bdffaf53f271432be Mon Sep 17 00:00:00 2001 From: Paolo Tranquilli Date: Wed, 27 May 2026 10:25:53 +0200 Subject: [PATCH 6/6] Rust: Split downgrade preservation test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- codeql-workspace.yml | 1 + rust/ql/downgrade-tests/.gitignore | 3 + .../.gitignore | 5 ++ .../README.md | 24 ++++++ .../downgraded.expected | 4 +- .../downgraded.ql | 0 .../new.expected | 20 +++++ .../new.ql | 73 +++++++++++++++++++ .../run-test.sh | 65 +++++++++++++++++ .../upgrade_shapes.rs | 23 ++++++ rust/ql/downgrade-tests/qlpack.yml | 5 ++ .../.gitignore | 2 + .../README.md | 7 +- .../run-test.sh | 23 ------ 14 files changed, 225 insertions(+), 30 deletions(-) create mode 100644 rust/ql/downgrade-tests/.gitignore create mode 100644 rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore create mode 100644 rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md rename rust/ql/{upgrade-tests => downgrade-tests}/66a489863649185f4a9770f894505803059a1312/downgraded.expected (87%) rename rust/ql/{upgrade-tests => downgrade-tests}/66a489863649185f4a9770f894505803059a1312/downgraded.ql (100%) create mode 100644 rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected create mode 100644 rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql create mode 100755 rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh create mode 100644 rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs create mode 100644 rust/ql/downgrade-tests/qlpack.yml diff --git a/codeql-workspace.yml b/codeql-workspace.yml index a6408dc26ecf..c61313163e56 100644 --- a/codeql-workspace.yml +++ b/codeql-workspace.yml @@ -3,6 +3,7 @@ provide: - "*/ql/lib/qlpack.yml" - "*/ql/test*/qlpack.yml" - "*/ql/upgrade-tests/qlpack.yml" + - "*/ql/downgrade-tests/qlpack.yml" - "*/ql/examples/qlpack.yml" - "*/ql/consistency-queries/qlpack.yml" - "*/ql/automodel/src/qlpack.yml" diff --git a/rust/ql/downgrade-tests/.gitignore b/rust/ql/downgrade-tests/.gitignore new file mode 100644 index 000000000000..3b4d77eb40d2 --- /dev/null +++ b/rust/ql/downgrade-tests/.gitignore @@ -0,0 +1,3 @@ +*.testproj/ +*.actual +*.log diff --git a/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore new file mode 100644 index 000000000000..1abb3d60e268 --- /dev/null +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore @@ -0,0 +1,5 @@ +*.testproj/ +*.actual +*.log +Cargo.toml +lib.rs diff --git a/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md new file mode 100644 index 000000000000..3cc35fab4774 --- /dev/null +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md @@ -0,0 +1,24 @@ +# Downgrade Regression Test for rust-analyzer 0.0.328 -> 0.0.301 + +This test verifies that the dbscheme downgrade script correctly preserves recoverable data when migrating databases from the new schema back to the old schema. + +## Running the test + +```bash +./run-test.sh +``` + +This will: +1. Build the current extractor +2. Create a database with the new schema from `upgrade_shapes.rs` +3. Validate the new-schema extraction +4. Downgrade the database to the old schema +5. Check out the old commit (491c373e076) +6. Verify that the recovered old-schema properties still match + +## Files + +- `new.ql` / `new.expected`: Query for the new schema (validates extraction) +- `downgraded.ql` / `downgraded.expected`: Query for the downgraded old schema +- `upgrade_shapes.rs`: Rust source containing test shapes for all affected schema elements +- `run-test.sh`: Test runner script diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected similarity index 87% rename from rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected rename to rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected index 1df9e112bbee..0616c973cb6d 100644 --- a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.expected @@ -1,6 +1,6 @@ formatArgsArgName -| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:37:22:37 | (no string representation) | upgrade_shapes.rs:22:41:22:41 | 1 | 37 | -| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:44:22:44 | (no string representation) | upgrade_shapes.rs:22:48:22:48 | 2 | 44 | +| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:13:22:49 | (no string representation) | upgrade_shapes.rs:22:41:22:41 | 1 | 13 | +| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:13:22:49 | (no string representation) | upgrade_shapes.rs:22:48:22:48 | 2 | 13 | tryBlock | upgrade_shapes.rs:21:13:21:21 | { ... } | structFieldDefault diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql similarity index 100% rename from rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql rename to rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/downgraded.ql diff --git a/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected new file mode 100644 index 000000000000..441d08ab3c36 --- /dev/null +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.expected @@ -0,0 +1,20 @@ +formatArgNamePreserved +| upgrade_shapes.rs:22:37:22:41 | FormatArgsArg | upgrade_shapes.rs:22:37:22:39 | FormatArgsArgName | upgrade_shapes.rs:22:41:22:41 | 1 | 37 | 1 | +| upgrade_shapes.rs:22:44:22:48 | FormatArgsArg | upgrade_shapes.rs:22:44:22:46 | FormatArgsArgName | upgrade_shapes.rs:22:48:22:48 | 2 | 44 | 2 | +tryBlockModifierPreserved +| upgrade_shapes.rs:21:13:21:21 | { ... } | upgrade_shapes.rs:21:13:21:15 | TryBlockModifier | +structFieldDefaultPreserved +| upgrade_shapes.rs:9:5:9:17 | field: u8 | upgrade_shapes.rs:9:17:9:17 | ConstArg | upgrade_shapes.rs:9:17:9:17 | 1 | +variantDiscriminantPreserved +| upgrade_shapes.rs:13:5:13:9 | V | upgrade_shapes.rs:13:9:13:9 | ConstArg | upgrade_shapes.rs:13:9:13:9 | 2 | +pathMetaPreserved +| upgrade_shapes.rs:4:3:4:11 | PathMeta | path_meta | +| upgrade_shapes.rs:7:10:7:18 | PathMeta | path_meta | +keyValueMetaPreserved +| upgrade_shapes.rs:5:3:5:15 | KeyValueMeta | key_value | upgrade_shapes.rs:5:15:5:15 | 1 | +tokenTreeMetaPreserved +| upgrade_shapes.rs:6:3:6:18 | TokenTreeMeta | token_tree | upgrade_shapes.rs:6:13:6:18 | TokenTree | +unsafeMetaPreserved +| upgrade_shapes.rs:7:3:7:19 | UnsafeMeta | upgrade_shapes.rs:7:10:7:18 | PathMeta | path_meta | +traitAliasPreserved +| upgrade_shapes.rs:16:1:18:12 | trait Alias | upgrade_shapes.rs:16:7:16:11 | Alias | upgrade_shapes.rs:16:18:16:22 | ... | upgrade_shapes.rs:17:1:18:11 | WhereClause | diff --git a/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql new file mode 100644 index 000000000000..bd983a8290c4 --- /dev/null +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/new.ql @@ -0,0 +1,73 @@ +import codeql.rust.elements + +private predicate inUpgradeShapesFile(Locatable loc) { + loc.getFile().getBaseName() = "upgrade_shapes.rs" +} + +query predicate formatArgNamePreserved( + FormatArgsArg arg, FormatArgsArgName argName, Expr expr, int argNameColumn, string exprText +) { + inUpgradeShapesFile(arg) and + argName = arg.getArgName() and + expr = arg.getExpr() and + argNameColumn = argName.getLocation().getStartColumn() and + exprText = expr.toString() +} + +query predicate tryBlockModifierPreserved(BlockExpr block, TryBlockModifier modifier) { + inUpgradeShapesFile(block) and + modifier = block.getTryBlockModifier() and + modifier.isTry() and + not modifier.hasTypeRepr() +} + +query predicate structFieldDefaultPreserved(StructField field, ConstArg defaultVal, Expr expr) { + inUpgradeShapesFile(field) and + defaultVal = field.getDefaultVal() and + expr = defaultVal.getExpr() +} + +query predicate variantDiscriminantPreserved(Variant variant, ConstArg constArg, Expr expr) { + inUpgradeShapesFile(variant) and + constArg = variant.getConstArg() and + expr = constArg.getExpr() +} + +query predicate pathMetaPreserved(PathMeta meta, string pathText) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "path_meta" +} + +query predicate keyValueMetaPreserved(KeyValueMeta meta, string pathText, Expr expr) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "key_value" and + expr = meta.getExpr() +} + +query predicate tokenTreeMetaPreserved(TokenTreeMeta meta, string pathText, TokenTree tokenTree) { + inUpgradeShapesFile(meta) and + pathText = meta.getPath().getText() and + pathText = "token_tree" and + tokenTree = meta.getTokenTree() +} + +query predicate unsafeMetaPreserved(UnsafeMeta meta, PathMeta inner, string pathText) { + inUpgradeShapesFile(meta) and + meta.isUnsafe() and + inner = meta.getMeta() and + pathText = inner.getPath().getText() and + pathText = "path_meta" +} + +query predicate traitAliasPreserved( + Trait trait, Name name, TypeBoundList bounds, WhereClause whereClause +) { + inUpgradeShapesFile(trait) and + name = trait.getName() and + name.getText() = "Alias" and + bounds = trait.getTypeBoundList() and + whereClause = trait.getWhereClause() and + not trait.hasAssocItemList() +} diff --git a/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh new file mode 100755 index 000000000000..48c78127bc5d --- /dev/null +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Manual regression test for the Rust dbscheme downgrade from rust-analyzer 0.0.328 to 0.0.301. +# See README.md for details. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(git rev-parse --show-toplevel)" +OLD_COMMIT="${OLD_COMMIT:-491c373e076}" # origin/main at time of this upgrade +OLD_DBSCHEME="rust/downgrades/109496fd2f20f28a35e50b110859e74882ee80d6/rust.dbscheme" + +cd "$REPO_ROOT" + +if ! git diff --quiet HEAD || [ -n "$(git ls-files --others --exclude-standard)" ]; then + echo "ERROR: Working directory has uncommitted changes." >&2 + echo "Please commit or stash your changes before running this test." >&2 + exit 1 +fi + +ORIGINAL_REF="$(git rev-parse --abbrev-ref HEAD)" +if [ "$ORIGINAL_REF" = "HEAD" ]; then + ORIGINAL_REF="$(git rev-parse HEAD)" +fi + +restore_ref() { + echo "==> Restoring original ref ($ORIGINAL_REF)..." + git checkout --quiet "$ORIGINAL_REF" +} +trap 'restore_ref' EXIT + +echo "==> Building current extractor (this may take a while)..." +bazel run //rust:install + +echo "==> Creating new-schema test database..." +codeql test run \ + --search-path . \ + --keep-databases \ + "$SCRIPT_DIR/new.ql" "$@" + +echo "==> Downgrading dataset to old schema..." +DATASET_DIR=("$SCRIPT_DIR"/*.testproj/db-rust) +if [[ ! -d "${DATASET_DIR[0]}" ]]; then + echo "ERROR: No testproj found at $SCRIPT_DIR/*.testproj" >&2 + exit 1 +fi +codeql dataset upgrade "${DATASET_DIR[0]}" \ + --allow-downgrades \ + --search-path rust \ + --target-dbscheme "$OLD_DBSCHEME" + +echo "==> Checking out old commit ($OLD_COMMIT) for downgrade verification..." +git checkout --quiet "$OLD_COMMIT" +git checkout --quiet "$ORIGINAL_REF" -- rust/ql/downgrade-tests codeql-workspace.yml + +echo "==> Running preservation test on downgraded dataset..." +codeql test run \ + --search-path . \ + --dataset "${DATASET_DIR[0]}" \ + --check-databases \ + "$SCRIPT_DIR/downgraded.ql" "$@" + +restore_ref +trap '' EXIT + +echo "==> All tests passed!" diff --git a/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs new file mode 100644 index 000000000000..e64d8f6582c6 --- /dev/null +++ b/rust/ql/downgrade-tests/66a489863649185f4a9770f894505803059a1312/upgrade_shapes.rs @@ -0,0 +1,23 @@ +#![allow(dead_code)] +#![feature(more_qualified_paths)] + +#[path_meta] +#[key_value = 1] +#[token_tree(list)] +#[unsafe(path_meta)] +struct S { + field: u8 = 1, +} + +enum E { + V = 2, +} + +trait Alias = Clone +where + T: Copy; + +fn f() { + let _ = try { 1 }; + let _ = format_args!("{b} {a}", a = 1, b = 2); +} diff --git a/rust/ql/downgrade-tests/qlpack.yml b/rust/ql/downgrade-tests/qlpack.yml new file mode 100644 index 000000000000..e2e42acf460f --- /dev/null +++ b/rust/ql/downgrade-tests/qlpack.yml @@ -0,0 +1,5 @@ +name: codeql/rust-downgrade-tests +version: 0.0.0 +extractor: rust +dependencies: + codeql/rust-all: ${workspace} diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore index 3b4d77eb40d2..1abb3d60e268 100644 --- a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/.gitignore @@ -1,3 +1,5 @@ *.testproj/ *.actual *.log +Cargo.toml +lib.rs diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md index a728458bef42..ecc94261ee51 100644 --- a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/README.md @@ -1,6 +1,6 @@ # Upgrade Regression Test for rust-analyzer 0.0.301 → 0.0.328 -This test verifies that the dbscheme upgrade and downgrade scripts correctly preserve data when migrating databases between the old and new schemas. +This test verifies that the dbscheme upgrade script correctly preserves data when migrating databases from the old schema to the new schema. ## Running the test @@ -16,13 +16,10 @@ This will: 5. Restore your branch 6. Upgrade the database to the new schema 7. Verify that the old properties are still accessible via the new schema -8. Downgrade the database back to the old schema -9. Verify that the recovered old-schema properties still match ## Files -- `old.ql` / `old.expected`: Query for the old schema (validates extraction and downgrade) +- `old.ql` / `old.expected`: Query for the old schema (validates extraction) - `new.ql` / `new.expected`: Query for the new schema (validates upgrade preserved data) -- `downgraded.ql` / `downgraded.expected`: Query for the downgraded old schema - `upgrade_shapes.rs`: Rust source containing test shapes for all affected schema elements - `run-test.sh`: Test runner script diff --git a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh index 1dbbf2783b7a..b5ae0d8a42f7 100755 --- a/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh +++ b/rust/ql/upgrade-tests/66a489863649185f4a9770f894505803059a1312/run-test.sh @@ -7,7 +7,6 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(git rev-parse --show-toplevel)" OLD_COMMIT="${OLD_COMMIT:-491c373e076}" # origin/main at time of this upgrade -OLD_DBSCHEME="rust/downgrades/109496fd2f20f28a35e50b110859e74882ee80d6/rust.dbscheme" cd "$REPO_ROOT" @@ -65,26 +64,4 @@ codeql test run \ --check-databases \ "$SCRIPT_DIR/new.ql" "$@" -echo "==> Downgrading dataset back to old schema..." -codeql dataset upgrade "${DATASET_DIR[0]}" \ - --allow-downgrades \ - --search-path rust \ - --target-dbscheme "$OLD_DBSCHEME" - -trap 'restore_ref' EXIT - -echo "==> Checking out old commit ($OLD_COMMIT) for downgrade verification..." -git checkout --quiet "$OLD_COMMIT" -git checkout --quiet "$ORIGINAL_REF" -- rust/ql/upgrade-tests codeql-workspace.yml - -echo "==> Running preservation test on downgraded dataset..." -codeql test run \ - --search-path . \ - --dataset "${DATASET_DIR[0]}" \ - --check-databases \ - "$SCRIPT_DIR/downgraded.ql" "$@" - -restore_ref -trap '' EXIT - echo "==> All tests passed!"