Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .github/workflows/internal-testsuite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
git submodule update --init --checkout tests/integration/tests/libxml2/repo
git submodule update --init --checkout tests/integration/tests/python2/repo
git submodule update --init --checkout tests/integration/tests/libmcs/repo
git submodule update --init --checkout tests/integration/tests/sqlite/repo

- uses: astral-sh/setup-uv@v6

Expand All @@ -75,6 +76,7 @@ jobs:
bear \
build-essential \
cmake \
jq \
libbrotli-dev \
libbz2-dev \
libclang-${{ matrix.clang-version }}-dev \
Expand Down Expand Up @@ -126,7 +128,7 @@ jobs:
export PATH=$PWD/target/release:$PWD/c2rust-postprocess:$HOME/.local/bin:$PATH
echo "PATH=$PATH"
export C2RUST_DIR=$PWD
./tests/integration/test.py --refactor-jobs 1 curl json-c libgit2 lua nginx zstd libxml2 python2 libmcs
./tests/integration/test.py --refactor-jobs 1 curl json-c libgit2 lua nginx zstd libxml2 python2 libmcs sqlite

- uses: actions/upload-artifact@v4
with:
Expand Down
5 changes: 5 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,8 @@
url = https://github.com/libgit2/libgit2
ignore = all
update = none
[submodule "tests/integration/tests/sqlite/repo"]
path = tests/integration/tests/sqlite/repo
url = https://github.com/sqlite/sqlite.git
update = none
ignore = all
40 changes: 40 additions & 0 deletions tests/integration/tests/sqlite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# SQLite

The source submodule is pinned to SQLite 3.53.4 from the official Git mirror.
Initialize it with:

```sh
git submodule update --init --checkout tests/integration/tests/sqlite/repo
```

Run from the C2Rust checkout with the integration harness prerequisites and `jq`
installed:

```sh
export PATH="$PWD/target/release:$PWD/c2rust-postprocess:$PATH"
export C2RUST_DIR="$PWD"
./tests/integration/test.py sqlite
```

The build uses `--disable-amalgamation` and the `libsqlite3.a` target to compile
the library's individual translation units. SQLite's `sqlite3` and `speedtest1`
make targets compile the amalgamation even with that configure option, so they
are deliberately not used. Source generation runs before Bear to exclude build
tools from the compilation database. R-tree support is enabled for its workload.

After transpilation and again after refactoring, `test.sh` compiles SQLite's
upstream `test/speedtest1.c` against each of the native and translated static
libraries. It runs the `main`, `cte`, `orm`, `fp`, and `rtree` workloads with
`--size 1 --verify` on temporary on-disk databases.
Each workload must exit successfully and produce the same result byte count and
verification hash as native SQLite. Timing output is ignored. The C test driver
stays native so the same caller exercises both libraries' public C ABI.
The Rust archive's native link dependencies come from rustc's
`--print native-static-libs` output.

`smoke.c` also checks fixed SQL results, transaction/savepoint rollback, triggers,
foreign keys, blobs, JSON/JSONB operations, numeric parsing (including large
integers and floating-point values), window functions, data and WAL journal-mode
persistence after reopening, and an integrity check. The upstream `json` and
`parsenumber` workloads do not hash result rows, and `parsenumber` ignores SQL
errors, so JSON and numeric parsing use these explicit assertions instead.
29 changes: 29 additions & 0 deletions tests/integration/tests/sqlite/conf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
requirements:
generic:
programs:
in_path:
- jq
ubuntu:
apt:
packages:
- jq

transpile:
autogen: true
tflags: --reorganize-definitions --disable-refactoring

cargo.transpile:
autogen: true

refactor:
autogen: true
transforms:
- rename_unnamed
- reorganize_definitions
- shorten_imported_paths
- remove_unused_labels
- remove_redundant_casts
- remove_literal_suffixes

cargo.refactor:
autogen: true
9 changes: 9 additions & 0 deletions tests/integration/tests/sqlite/configure.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR/repo"

# Compile separate translation units to exercise cross-file translation.
./configure --disable-amalgamation --disable-shared --disable-readline \
Comment thread
thedataking marked this conversation as resolved.
--disable-tcl --enable-rtree 2>&1 | tee "$SCRIPT_DIR/$(basename "$0").log"
14 changes: 14 additions & 0 deletions tests/integration/tests/sqlite/make.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"

{
make -C repo clean
rm -f compile_commands.json
# Generate the parser, opcodes and headers before recording compilation,
# so host tools such as lemon are not included in the translated library.
make -C repo -j"$(nproc)" .target_source
bear -- make -C repo -j"$(nproc)" libsqlite3.a
} 2>&1 | tee "$SCRIPT_DIR/$(basename "$0").log"
1 change: 1 addition & 0 deletions tests/integration/tests/sqlite/repo
Submodule repo added at b09c88
91 changes: 91 additions & 0 deletions tests/integration/tests/sqlite/smoke.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include "sqlite3.h"
#include <stdio.h>
#include <stdlib.h>

#define CHECK(condition) do { \
if (!(condition)) { \
fprintf(stderr, "%s:%d: %s failed\n", __FILE__, __LINE__, #condition); \
exit(1); \
} \
} while (0)

static void exec(sqlite3 *db, const char *sql) {
char *error = NULL;
if (sqlite3_exec(db, sql, NULL, NULL, &error) != SQLITE_OK) {
fprintf(stderr, "%s: %s\n", sql, error);
sqlite3_free(error);
exit(1);
}
}

static void expect_int(sqlite3 *db, const char *sql, int expected) {
sqlite3_stmt *stmt = NULL;
CHECK(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK);
CHECK(sqlite3_step(stmt) == SQLITE_ROW);
int actual = sqlite3_column_int(stmt, 0);
if (actual != expected) {
fprintf(stderr, "%s: expected %d, got %d\n", sql, expected, actual);
exit(1);
}
CHECK(sqlite3_step(stmt) == SQLITE_DONE);
CHECK(sqlite3_finalize(stmt) == SQLITE_OK);
}

int main(int argc, char **argv) {
CHECK(argc == 2);
sqlite3 *db = NULL;
CHECK(sqlite3_open(argv[1], &db) == SQLITE_OK);
exec(db,
"PRAGMA journal_mode=WAL;"
"PRAGMA foreign_keys=ON;"
"CREATE TABLE parent(id INTEGER PRIMARY KEY);"
"CREATE TABLE child(id INTEGER REFERENCES parent, value TEXT, data BLOB);"
"CREATE TABLE audit(value TEXT);"
"CREATE TRIGGER inserted AFTER INSERT ON child BEGIN "
" INSERT INTO audit VALUES(new.value); END;"
"BEGIN;"
"INSERT INTO parent VALUES(1);"
"INSERT INTO child VALUES(1, 'hello', x'00ff80');"
"SAVEPOINT s;"
"INSERT INTO child VALUES(1, 'rolled back', NULL);"
"ROLLBACK TO s;"
"RELEASE s;"
"COMMIT;");
expect_int(db, "SELECT journal_mode = 'wal' FROM pragma_journal_mode", 1);
expect_int(db, "SELECT count(*) FROM audit", 1);
expect_int(db, "SELECT hex(data) = '00FF80' AND value = 'hello' FROM child", 1);
CHECK(sqlite3_exec(db, "INSERT INTO child VALUES(2, 'invalid', NULL)",
NULL, NULL, NULL) == SQLITE_CONSTRAINT);
expect_int(db, "SELECT json_extract('{\"a\":[1,2,3]}', '$.a[2]')", 3);
expect_int(db,
"SELECT json(jsonb_set(jsonb('{\"a\":[1,2,3]}'), '$.a[1]', 9)) "
"= '{\"a\":[1,9,3]}'", 1);
expect_int(db,
"SELECT json(jsonb_remove(jsonb_insert(jsonb('{}'), '$.x', 7, '$.y', 8), '$.x')) "
"= '{\"y\":8}'", 1);
expect_int(db,
"WITH t(x) AS (VALUES(1),(2),(3)) "
"SELECT json(jsonb_group_array(x)) = '[1,2,3]' FROM t", 1);
expect_int(db, "SELECT CAST('1_000' AS INTEGER)", 1);
expect_int(db, "SELECT 1_000 + CAST('2.5e2' AS INTEGER)", 1002);
expect_int(db, "SELECT CAST(CAST('2.5e2' AS REAL) AS INTEGER)", 250);
expect_int(db,
"SELECT printf('%lld', 8_227_256_643_844_975_616) = '8227256643844975616' "
"AND printf('%lld', -9223372036854775808) = '-9223372036854775808'", 1);
expect_int(db,
"SELECT printf('%.6f', 8.227256643844975616) = '8.227257' "
"AND printf('%.8f', CAST('1.23456e-3' AS REAL)) = '0.00123456' "
"AND printf('%.4f', 1_234.5_6e-2) = '12.3456'", 1);
expect_int(db,
"WITH t(x) AS (VALUES(1),(2),(3)) "
"SELECT sum(s) FROM (SELECT sum(x) OVER (ORDER BY x) AS s FROM t)", 10);
CHECK(sqlite3_close(db) == SQLITE_OK);

CHECK(sqlite3_open(argv[1], &db) == SQLITE_OK);
expect_int(db, "SELECT journal_mode = 'wal' FROM pragma_journal_mode", 1);
expect_int(db, "SELECT count(*) FROM audit", 1);
expect_int(db, "SELECT integrity_check = 'ok' FROM pragma_integrity_check", 1);
CHECK(sqlite3_close(db) == SQLITE_OK);
puts("SQLite SQL and persistence checks passed");
return 0;
}
58 changes: 58 additions & 0 deletions tests/integration/tests/sqlite/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR/repo"

{
TARGET_DIR=$(cargo ${TOOLCHAIN:+"$TOOLCHAIN"} metadata --no-deps --format-version 1 \
Comment thread
thedataking marked this conversation as resolved.
| jq -er .target_directory)
RUST_ARCHIVE="$TARGET_DIR/release/librepo.a"
if [[ ! -f "$RUST_ARCHIVE" ]]; then
echo "Rust archive not found: $RUST_ARCHIVE" >&2
exit 1
fi
WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT

# Ask rustc for the native dependencies of this static archive.
RUSTFLAGS="${RUSTFLAGS:--Awarnings}" cargo ${TOOLCHAIN:+"$TOOLCHAIN"} rustc \
--release --lib --color never -- --print native-static-libs \
2>&1 | tee "$WORK_DIR/native-libs.log"
LIBS=$(sed -n 's/^note: native-static-libs: //p' "$WORK_DIR/native-libs.log")
if [[ -z "$LIBS" ]]; then
echo "rustc did not report native static libraries" >&2
exit 1
fi
read -r -a RUST_LIBS <<< "$LIBS"

# Explicit archives keep both C callers from picking up a system SQLite.
for implementation in native rust; do
if [[ "$implementation" == native ]]; then
archive="$SCRIPT_DIR/repo/libsqlite3.a"
libs=(-lm -ldl -lpthread)
else
archive="$RUST_ARCHIVE"
libs=("${RUST_LIBS[@]}")
fi
cc -O2 -I. "$SCRIPT_DIR/smoke.c" "$archive" "${libs[@]}" \
-o "$WORK_DIR/smoke-$implementation"
cc -O2 -DSQLITE_ENABLE_RTREE -I. test/speedtest1.c "$archive" "${libs[@]}" \
-o "$WORK_DIR/$implementation"
"$WORK_DIR/smoke-$implementation" "$WORK_DIR/smoke-$implementation.db"
done

# Only these workloads hash result rows. JSON and numeric parsing have
# explicit assertions in smoke.c instead of comparisons of empty hashes.
for testset in main cte orm fp rtree; do
for implementation in native rust; do
"$WORK_DIR/$implementation" --size 1 --verify --testset "$testset" \
"$WORK_DIR/$implementation.db" \
| tee "$WORK_DIR/$implementation.out"
# Timings differ; the result count and verification hash must match.
grep '^Verification Hash: ' "$WORK_DIR/$implementation.out" \
> "$WORK_DIR/$implementation.hash"
done
diff -u "$WORK_DIR/native.hash" "$WORK_DIR/rust.hash"
done
} 2>&1 | tee "$SCRIPT_DIR/$(basename "$0").log"
Loading