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>
This commit is contained in:
Paolo Tranquilli
2026-05-26 17:51:54 +02:00
parent 63d8bb7d5b
commit ac4dcd536a
9 changed files with 300 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
*.testproj/
*.actual
*.log

View File

@@ -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=<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`.

View File

@@ -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 |

View File

@@ -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()
}

View File

@@ -0,0 +1,6 @@
name: codeql/rust-upgrade-66a489863649185f4a9770f894505803059a1312-test-new
version: 0.0.0
dependencies:
codeql/rust-all: ${workspace}
extractor: rust
tests: .

View File

@@ -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<T> = Clone
where
T: Copy;
fn f() {
let _ = try { 1 };
let _ = format_args!("{b} {a}", a = 1, b = 2);
}

View File

@@ -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 |

View File

@@ -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()
}

View File

@@ -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!"